mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 10:49:11 -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 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"> | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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> | ||||
| 
 | ||||
|  | ||||
| @ -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> | ||||
|  | ||||
| @ -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> | ||||
|  | ||||
| @ -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> | ||||
|  | ||||
| @ -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
									
								
								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 { 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> | ||||
|  | ||||
| @ -106,6 +106,6 @@ | ||||
| 			/> | ||||
| 		{/if} | ||||
| 
 | ||||
| 		<GalleryViewer assets={favorites} bind:selectedAssets /> | ||||
| 		<GalleryViewer assets={favorites} bind:selectedAssets viewFrom="favorites-page" /> | ||||
| 	</section> | ||||
| </UserPageLayout> | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user