feat(web): add button to archive and unarchive in detail viewer (#2296)

This commit is contained in:
Alex 2023-04-20 09:09:27 -05:00 committed by GitHub
parent 14be63039f
commit fe3d6b870a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 94 additions and 12 deletions

View File

@ -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">

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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}

View File

@ -0,0 +1,4 @@
import { AssetResponseDto } from '@api';
import { writable } from 'svelte/store';
export const archivedAsset = writable<AssetResponseDto[]>([]);

View File

@ -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>

View File

@ -106,6 +106,6 @@
/> />
{/if} {/if}
<GalleryViewer assets={favorites} bind:selectedAssets /> <GalleryViewer assets={favorites} bind:selectedAssets viewFrom="favorites-page" />
</section> </section>
</UserPageLayout> </UserPageLayout>

View File

@ -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