mirror of
https://github.com/immich-app/immich.git
synced 2025-05-30 19:54:52 -04:00
feat(web): add button to archive and unarchive in detail viewer (#2296)
This commit is contained in:
parent
14be63039f
commit
fe3d6b870a
@ -526,7 +526,12 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if album.assetCount > 0}
|
{#if album.assetCount > 0}
|
||||||
<GalleryViewer assets={album.assets} {sharedLink} bind:selectedAssets={multiSelectAsset} />
|
<GalleryViewer
|
||||||
|
assets={album.assets}
|
||||||
|
{sharedLink}
|
||||||
|
bind:selectedAssets={multiSelectAsset}
|
||||||
|
viewFrom="album-page"
|
||||||
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<!-- Album is empty - Show asset selectection buttons -->
|
<!-- Album is empty - Show asset selectection buttons -->
|
||||||
<section id="empty-album" class=" mt-[200px] flex place-content-center place-items-center">
|
<section id="empty-album" class=" mt-[200px] flex place-content-center place-items-center">
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
import ContentCopy from 'svelte-material-icons/ContentCopy.svelte';
|
import ContentCopy from 'svelte-material-icons/ContentCopy.svelte';
|
||||||
import MotionPlayOutline from 'svelte-material-icons/MotionPlayOutline.svelte';
|
import MotionPlayOutline from 'svelte-material-icons/MotionPlayOutline.svelte';
|
||||||
import MotionPauseOutline from 'svelte-material-icons/MotionPauseOutline.svelte';
|
import MotionPauseOutline from 'svelte-material-icons/MotionPauseOutline.svelte';
|
||||||
|
import ArchiveArrowDownOutline from 'svelte-material-icons/ArchiveArrowDownOutline.svelte';
|
||||||
|
import ArchiveArrowUpOutline from 'svelte-material-icons/ArchiveArrowUpOutline.svelte';
|
||||||
|
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { AssetResponseDto } from '../../../api';
|
import { AssetResponseDto } from '../../../api';
|
||||||
@ -49,6 +51,14 @@
|
|||||||
<CircleIconButton logo={ArrowLeft} on:click={() => dispatch('goBack')} />
|
<CircleIconButton logo={ArrowLeft} on:click={() => dispatch('goBack')} />
|
||||||
</div>
|
</div>
|
||||||
<div class="text-white flex gap-2">
|
<div class="text-white flex gap-2">
|
||||||
|
{#if isOwner}
|
||||||
|
<CircleIconButton
|
||||||
|
logo={asset.isArchived ? ArchiveArrowUpOutline : ArchiveArrowDownOutline}
|
||||||
|
title={asset.isArchived ? 'Unarchive' : 'Archive'}
|
||||||
|
on:click={() => dispatch('toggleArchive')}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if showMotionPlayButton}
|
{#if showMotionPlayButton}
|
||||||
{#if isMotionPhotoPlaying}
|
{#if isMotionPhotoPlaying}
|
||||||
<CircleIconButton
|
<CircleIconButton
|
||||||
|
@ -263,6 +263,35 @@
|
|||||||
document.addEventListener('keydown', onKeyboardPress);
|
document.addEventListener('keydown', onKeyboardPress);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toggleArchive = async () => {
|
||||||
|
try {
|
||||||
|
const { data } = await api.assetApi.updateAsset(asset.id, {
|
||||||
|
isArchived: !asset.isArchived
|
||||||
|
});
|
||||||
|
|
||||||
|
asset.isArchived = data.isArchived;
|
||||||
|
|
||||||
|
if (data.isArchived) {
|
||||||
|
dispatch('archived', data);
|
||||||
|
} else {
|
||||||
|
dispatch('unarchived', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationController.show({
|
||||||
|
type: NotificationType.Info,
|
||||||
|
message: asset.isArchived ? `Added to archive` : `Removed from archive`
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
notificationController.show({
|
||||||
|
type: NotificationType.Error,
|
||||||
|
message: `Error ${
|
||||||
|
asset.isArchived ? 'archiving' : 'unarchiving'
|
||||||
|
} asset, check console for more details`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section
|
<section
|
||||||
@ -285,6 +314,7 @@
|
|||||||
on:addToSharedAlbum={() => openAlbumPicker(true)}
|
on:addToSharedAlbum={() => openAlbumPicker(true)}
|
||||||
on:playMotionPhoto={() => (shouldPlayMotionPhoto = true)}
|
on:playMotionPhoto={() => (shouldPlayMotionPhoto = true)}
|
||||||
on:stopMotionPhoto={() => (shouldPlayMotionPhoto = false)}
|
on:stopMotionPhoto={() => (shouldPlayMotionPhoto = false)}
|
||||||
|
on:toggleArchive={toggleArchive}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import IntersectionObserver from '../asset-viewer/intersection-observer.svelte';
|
import IntersectionObserver from '../asset-viewer/intersection-observer.svelte';
|
||||||
import { assetGridState, assetStore, loadingBucketState } from '$lib/stores/assets.store';
|
import { assetGridState, assetStore, loadingBucketState } from '$lib/stores/assets.store';
|
||||||
import { api, AssetCountByTimeBucketResponseDto, TimeGroupEnum } from '@api';
|
import { api, AssetCountByTimeBucketResponseDto, AssetResponseDto, TimeGroupEnum } from '@api';
|
||||||
import AssetDateGroup from './asset-date-group.svelte';
|
import AssetDateGroup from './asset-date-group.svelte';
|
||||||
import Portal from '../shared-components/portal/portal.svelte';
|
import Portal from '../shared-components/portal/portal.svelte';
|
||||||
import AssetViewer from '../asset-viewer/asset-viewer.svelte';
|
import AssetViewer from '../asset-viewer/asset-viewer.svelte';
|
||||||
@ -89,6 +89,12 @@
|
|||||||
const handleScrollbarDrag = (e: OnScrollbarDragDetail) => {
|
const handleScrollbarDrag = (e: OnScrollbarDragDetail) => {
|
||||||
assetGridElement.scrollTop = e.scrollTo;
|
assetGridElement.scrollTop = e.scrollTo;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleArchiveSuccess = (e: CustomEvent) => {
|
||||||
|
const asset = e.detail as AssetResponseDto;
|
||||||
|
navigateToNextAsset();
|
||||||
|
assetStore.removeAsset(asset.id);
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if bucketInfo && viewportHeight && $assetGridState.timelineHeight > viewportHeight}
|
{#if bucketInfo && viewportHeight && $assetGridState.timelineHeight > viewportHeight}
|
||||||
@ -149,6 +155,7 @@
|
|||||||
on:close={() => {
|
on:close={() => {
|
||||||
assetInteractionStore.setIsViewingAsset(false);
|
assetInteractionStore.setIsViewingAsset(false);
|
||||||
}}
|
}}
|
||||||
|
on:archived={handleArchiveSuccess}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</Portal>
|
</Portal>
|
||||||
|
@ -147,6 +147,6 @@
|
|||||||
</ControlAppBar>
|
</ControlAppBar>
|
||||||
{/if}
|
{/if}
|
||||||
<section class="flex flex-col my-[160px] px-6 sm:px-12 md:px-24 lg:px-40">
|
<section class="flex flex-col my-[160px] px-6 sm:px-12 md:px-24 lg:px-40">
|
||||||
<GalleryViewer {assets} {sharedLink} bind:selectedAssets />
|
<GalleryViewer {assets} {sharedLink} bind:selectedAssets viewFrom="shared-link-page" />
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
{#if showMessage}
|
{#if showMessage}
|
||||||
<div
|
<div
|
||||||
class="text-sm border rounded-xl p-4 text-immich-primary dark:text-immich-dark-primary font-medium bg-immich-primary/5 dark:border-immich-dark-bg w-full border-immich-primary border-2"
|
class="text-sm rounded-xl p-4 text-immich-primary dark:text-immich-dark-primary font-medium bg-immich-primary/5 dark:border-immich-dark-bg w-full border-immich-primary border-2"
|
||||||
>
|
>
|
||||||
<slot name="message" />
|
<slot name="message" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,3 +1,12 @@
|
|||||||
|
<script lang="ts" context="module">
|
||||||
|
export type ViewFrom =
|
||||||
|
| 'archive-page'
|
||||||
|
| 'album-page'
|
||||||
|
| 'favorites-page'
|
||||||
|
| 'search-page'
|
||||||
|
| 'shared-link-page';
|
||||||
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte';
|
import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte';
|
||||||
@ -6,11 +15,13 @@
|
|||||||
import AssetViewer from '../../asset-viewer/asset-viewer.svelte';
|
import AssetViewer from '../../asset-viewer/asset-viewer.svelte';
|
||||||
import justifiedLayout from 'justified-layout';
|
import justifiedLayout from 'justified-layout';
|
||||||
import { flip } from 'svelte/animate';
|
import { flip } from 'svelte/animate';
|
||||||
|
import { archivedAsset } from '$lib/stores/archived-asset.store';
|
||||||
|
|
||||||
export let assets: AssetResponseDto[];
|
export let assets: AssetResponseDto[];
|
||||||
export let sharedLink: SharedLinkResponseDto | undefined = undefined;
|
export let sharedLink: SharedLinkResponseDto | undefined = undefined;
|
||||||
export let selectedAssets: Set<AssetResponseDto> = new Set();
|
export let selectedAssets: Set<AssetResponseDto> = new Set();
|
||||||
export let disableAssetSelect = false;
|
export let disableAssetSelect = false;
|
||||||
|
export let viewFrom: ViewFrom;
|
||||||
|
|
||||||
let isShowAssetViewer = false;
|
let isShowAssetViewer = false;
|
||||||
|
|
||||||
@ -97,6 +108,16 @@
|
|||||||
isShowAssetViewer = false;
|
isShowAssetViewer = false;
|
||||||
history.pushState(null, '', `${$page.url.pathname}`);
|
history.pushState(null, '', `${$page.url.pathname}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleUnarchivedSuccess = (event: CustomEvent) => {
|
||||||
|
const asset = event.detail as AssetResponseDto;
|
||||||
|
switch (viewFrom) {
|
||||||
|
case 'archive-page':
|
||||||
|
$archivedAsset = $archivedAsset.filter((a) => a.id != asset.id);
|
||||||
|
navigateAssetForward();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if assets.length > 0}
|
{#if assets.length > 0}
|
||||||
@ -136,5 +157,6 @@
|
|||||||
on:navigate-previous={navigateAssetBackward}
|
on:navigate-previous={navigateAssetBackward}
|
||||||
on:navigate-next={navigateAssetForward}
|
on:navigate-next={navigateAssetForward}
|
||||||
on:close={closeViewer}
|
on:close={closeViewer}
|
||||||
|
on:unarchived={handleUnarchivedSuccess}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
4
web/src/lib/stores/archived-asset.store.ts
Normal file
4
web/src/lib/stores/archived-asset.store.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { AssetResponseDto } from '@api';
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
|
export const archivedAsset = writable<AssetResponseDto[]>([]);
|
@ -25,13 +25,14 @@
|
|||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte';
|
import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte';
|
||||||
|
import { archivedAsset } from '$lib/stores/archived-asset.store';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
try {
|
try {
|
||||||
const { data: assets } = await api.assetApi.getAllAssets(undefined, true);
|
const { data: assets } = await api.assetApi.getAllAssets(undefined, true);
|
||||||
archived = assets;
|
$archivedAsset = assets;
|
||||||
} catch {
|
} catch {
|
||||||
handleError(Error, 'Unable to load archived assets');
|
handleError(Error, 'Unable to load archived assets');
|
||||||
}
|
}
|
||||||
@ -54,7 +55,7 @@
|
|||||||
|
|
||||||
for (const asset of deletedAssets) {
|
for (const asset of deletedAssets) {
|
||||||
if (asset.status == 'SUCCESS') {
|
if (asset.status == 'SUCCESS') {
|
||||||
archived = archived.filter((a) => a.id != asset.id);
|
$archivedAsset = $archivedAsset.filter((a) => a.id != asset.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,7 +73,6 @@
|
|||||||
$: isMultiSelectionMode = selectedAssets.size > 0;
|
$: isMultiSelectionMode = selectedAssets.size > 0;
|
||||||
|
|
||||||
let selectedAssets: Set<AssetResponseDto> = new Set();
|
let selectedAssets: Set<AssetResponseDto> = new Set();
|
||||||
let archived: AssetResponseDto[] = [];
|
|
||||||
|
|
||||||
let contextMenuPosition = { x: 0, y: 0 };
|
let contextMenuPosition = { x: 0, y: 0 };
|
||||||
let isShowCreateSharedLinkModal = false;
|
let isShowCreateSharedLinkModal = false;
|
||||||
@ -157,7 +157,7 @@
|
|||||||
});
|
});
|
||||||
cnt = cnt + 1;
|
cnt = cnt + 1;
|
||||||
|
|
||||||
archived = archived.filter((a) => a.id != asset.id);
|
$archivedAsset = $archivedAsset.filter((a) => a.id != asset.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,7 +181,7 @@
|
|||||||
|
|
||||||
<UserPageLayout user={data.user} hideNavbar={isMultiSelectionMode}>
|
<UserPageLayout user={data.user} hideNavbar={isMultiSelectionMode}>
|
||||||
<!-- Empty Message -->
|
<!-- Empty Message -->
|
||||||
{#if archived.length === 0}
|
{#if $archivedAsset.length === 0}
|
||||||
<EmptyPlaceholder
|
<EmptyPlaceholder
|
||||||
text="Archive photos and videos to hide them from your Photos view"
|
text="Archive photos and videos to hide them from your Photos view"
|
||||||
alt="Empty archive"
|
alt="Empty archive"
|
||||||
@ -255,5 +255,5 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
|
|
||||||
<GalleryViewer assets={archived} bind:selectedAssets />
|
<GalleryViewer assets={$archivedAsset} bind:selectedAssets viewFrom="archive-page" />
|
||||||
</UserPageLayout>
|
</UserPageLayout>
|
||||||
|
@ -106,6 +106,6 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<GalleryViewer assets={favorites} bind:selectedAssets />
|
<GalleryViewer assets={favorites} bind:selectedAssets viewFrom="favorites-page" />
|
||||||
</section>
|
</section>
|
||||||
</UserPageLayout>
|
</UserPageLayout>
|
||||||
|
@ -38,7 +38,11 @@
|
|||||||
<section id="search-content" class="relative bg-immich-bg dark:bg-immich-dark-bg">
|
<section id="search-content" class="relative bg-immich-bg dark:bg-immich-dark-bg">
|
||||||
{#if data.results?.assets?.items.length > 0}
|
{#if data.results?.assets?.items.length > 0}
|
||||||
<div class="pl-4">
|
<div class="pl-4">
|
||||||
<GalleryViewer assets={data.results.assets.items} disableAssetSelect />
|
<GalleryViewer
|
||||||
|
assets={data.results.assets.items}
|
||||||
|
disableAssetSelect
|
||||||
|
viewFrom="search-page"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div
|
<div
|
||||||
|
Loading…
x
Reference in New Issue
Block a user