浏览代码

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

Alex 2 年之前
父节点
当前提交
fe3d6b870a

+ 6 - 1
web/src/lib/components/album-page/album-viewer.svelte

@@ -526,7 +526,12 @@
 		{/if}
 
 		{#if album.assetCount > 0}
-			<GalleryViewer assets={album.assets} {sharedLink} bind:selectedAssets={multiSelectAsset} />
+			<GalleryViewer
+				assets={album.assets}
+				{sharedLink}
+				bind:selectedAssets={multiSelectAsset}
+				viewFrom="album-page"
+			/>
 		{:else}
 			<!-- Album is empty - Show asset selectection buttons -->
 			<section id="empty-album" class=" mt-[200px] flex place-content-center place-items-center">

+ 10 - 0
web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte

@@ -14,6 +14,8 @@
 	import ContentCopy from 'svelte-material-icons/ContentCopy.svelte';
 	import MotionPlayOutline from 'svelte-material-icons/MotionPlayOutline.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 { AssetResponseDto } from '../../../api';
@@ -49,6 +51,14 @@
 		<CircleIconButton logo={ArrowLeft} on:click={() => dispatch('goBack')} />
 	</div>
 	<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 isMotionPhotoPlaying}
 				<CircleIconButton

+ 30 - 0
web/src/lib/components/asset-viewer/asset-viewer.svelte

@@ -263,6 +263,35 @@
 			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>
 
 <section
@@ -285,6 +314,7 @@
 			on:addToSharedAlbum={() => openAlbumPicker(true)}
 			on:playMotionPhoto={() => (shouldPlayMotionPhoto = true)}
 			on:stopMotionPhoto={() => (shouldPlayMotionPhoto = false)}
+			on:toggleArchive={toggleArchive}
 		/>
 	</div>
 

+ 8 - 1
web/src/lib/components/photos-page/asset-grid.svelte

@@ -3,7 +3,7 @@
 
 	import IntersectionObserver from '../asset-viewer/intersection-observer.svelte';
 	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 Portal from '../shared-components/portal/portal.svelte';
 	import AssetViewer from '../asset-viewer/asset-viewer.svelte';
@@ -89,6 +89,12 @@
 	const handleScrollbarDrag = (e: OnScrollbarDragDetail) => {
 		assetGridElement.scrollTop = e.scrollTo;
 	};
+
+	const handleArchiveSuccess = (e: CustomEvent) => {
+		const asset = e.detail as AssetResponseDto;
+		navigateToNextAsset();
+		assetStore.removeAsset(asset.id);
+	};
 </script>
 
 {#if bucketInfo && viewportHeight && $assetGridState.timelineHeight > viewportHeight}
@@ -149,6 +155,7 @@
 			on:close={() => {
 				assetInteractionStore.setIsViewingAsset(false);
 			}}
+			on:archived={handleArchiveSuccess}
 		/>
 	{/if}
 </Portal>

+ 1 - 1
web/src/lib/components/share-page/individual-shared-viewer.svelte

@@ -147,6 +147,6 @@
 		</ControlAppBar>
 	{/if}
 	<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>

+ 1 - 1
web/src/lib/components/shared-components/fullscreen-container.svelte

@@ -18,7 +18,7 @@
 
 		{#if showMessage}
 			<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" />
 			</div>

+ 22 - 0
web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte

@@ -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">
 	import { page } from '$app/stores';
 	import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte';
@@ -6,11 +15,13 @@
 	import AssetViewer from '../../asset-viewer/asset-viewer.svelte';
 	import justifiedLayout from 'justified-layout';
 	import { flip } from 'svelte/animate';
+	import { archivedAsset } from '$lib/stores/archived-asset.store';
 
 	export let assets: AssetResponseDto[];
 	export let sharedLink: SharedLinkResponseDto | undefined = undefined;
 	export let selectedAssets: Set<AssetResponseDto> = new Set();
 	export let disableAssetSelect = false;
+	export let viewFrom: ViewFrom;
 
 	let isShowAssetViewer = false;
 
@@ -97,6 +108,16 @@
 		isShowAssetViewer = false;
 		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>
 
 {#if assets.length > 0}
@@ -136,5 +157,6 @@
 		on:navigate-previous={navigateAssetBackward}
 		on:navigate-next={navigateAssetForward}
 		on:close={closeViewer}
+		on:unarchived={handleUnarchivedSuccess}
 	/>
 {/if}

+ 4 - 0
web/src/lib/stores/archived-asset.store.ts

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

+ 6 - 6
web/src/routes/(user)/archive/+page.svelte

@@ -25,13 +25,14 @@
 	import { onMount } from 'svelte';
 	import { handleError } from '$lib/utils/handle-error';
 	import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte';
+	import { archivedAsset } from '$lib/stores/archived-asset.store';
 
 	export let data: PageData;
 
 	onMount(async () => {
 		try {
 			const { data: assets } = await api.assetApi.getAllAssets(undefined, true);
-			archived = assets;
+			$archivedAsset = assets;
 		} catch {
 			handleError(Error, 'Unable to load archived assets');
 		}
@@ -54,7 +55,7 @@
 
 				for (const asset of deletedAssets) {
 					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;
 
 	let selectedAssets: Set<AssetResponseDto> = new Set();
-	let archived: AssetResponseDto[] = [];
 
 	let contextMenuPosition = { x: 0, y: 0 };
 	let isShowCreateSharedLinkModal = false;
@@ -157,7 +157,7 @@
 				});
 				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}>
 	<!-- Empty Message -->
-	{#if archived.length === 0}
+	{#if $archivedAsset.length === 0}
 		<EmptyPlaceholder
 			text="Archive photos and videos to hide them from your Photos view"
 			alt="Empty archive"
@@ -255,5 +255,5 @@
 		{/if}
 	</svelte:fragment>
 
-	<GalleryViewer assets={archived} bind:selectedAssets />
+	<GalleryViewer assets={$archivedAsset} bind:selectedAssets viewFrom="archive-page" />
 </UserPageLayout>

+ 1 - 1
web/src/routes/(user)/favorites/+page.svelte

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

+ 5 - 1
web/src/routes/(user)/search/+page.svelte

@@ -38,7 +38,11 @@
 		<section id="search-content" class="relative bg-immich-bg dark:bg-immich-dark-bg">
 			{#if data.results?.assets?.items.length > 0}
 				<div class="pl-4">
-					<GalleryViewer assets={data.results.assets.items} disableAssetSelect />
+					<GalleryViewer
+						assets={data.results.assets.items}
+						disableAssetSelect
+						viewFrom="search-page"
+					/>
 				</div>
 			{:else}
 				<div