mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	Delete album on web (#373)
* Show context menu * Show context menu at the correct location * Implement delete album button * Delete album within album viewer
This commit is contained in:
		| @@ -1,7 +1,9 @@ | ||||
| <script lang="ts"> | ||||
| 	import { AlbumResponseDto, api, ThumbnailFormat } from '@api'; | ||||
| 	import { createEventDispatcher, onMount } from 'svelte'; | ||||
| 	import DotsVertical from 'svelte-material-icons/DotsVertical.svelte'; | ||||
| 	import { fade } from 'svelte/transition'; | ||||
| 	import CircleIconButton from '../shared-components/circle-icon-button.svelte'; | ||||
|  | ||||
| 	export let album: AlbumResponseDto; | ||||
|  | ||||
| @@ -21,13 +23,33 @@ | ||||
| 			return imageData; | ||||
| 		} | ||||
| 	}; | ||||
|  | ||||
| 	const showAlbumContextMenu = (e: MouseEvent) => { | ||||
| 		dispatch('showalbumcontextmenu', { | ||||
| 			x: e.clientX, | ||||
| 			y: e.clientY | ||||
| 		}); | ||||
| 	}; | ||||
| </script> | ||||
|  | ||||
| <div | ||||
| 	class="h-[339px] w-[275px] hover:cursor-pointer mt-4" | ||||
| 	class="h-[339px] w-[275px] hover:cursor-pointer mt-4 relative" | ||||
| 	on:click={() => dispatch('click', album)} | ||||
| > | ||||
| 	<div class={`h-[275px] w-[275px]`}> | ||||
| 	<div | ||||
| 		id={`icon-${album.id}`} | ||||
| 		class="absolute top-2 right-2" | ||||
| 		on:click|stopPropagation|preventDefault={showAlbumContextMenu} | ||||
| 	> | ||||
| 		<CircleIconButton | ||||
| 			logo={DotsVertical} | ||||
| 			size={'20'} | ||||
| 			hoverColor={'rgba(95,99,104, 0.5)'} | ||||
| 			logoColor={'#fdf8ec'} | ||||
| 		/> | ||||
| 	</div> | ||||
|  | ||||
| 	<div class={`h-[275px] w-[275px] z-[-1]`}> | ||||
| 		{#await loadImageData(album.albumThumbnailAssetId)} | ||||
| 			<div | ||||
| 				class={`bg-immich-primary/10 w-full h-full  flex place-items-center place-content-center rounded-xl`} | ||||
| @@ -39,7 +61,7 @@ | ||||
| 				in:fade={{ duration: 250 }} | ||||
| 				src={imageData} | ||||
| 				alt={album.id} | ||||
| 				class={`object-cover w-full h-full transition-all z-0 rounded-xl duration-300 hover:translate-x-2 hover:-translate-y-2 hover:shadow-[-8px_8px_0px_0_#FFB800]`} | ||||
| 				class={`object-cover w-full h-full transition-all z-0 rounded-xl duration-300 hover:shadow-lg`} | ||||
| 			/> | ||||
| 		{/await} | ||||
| 	</div> | ||||
| @@ -59,6 +81,3 @@ | ||||
| 		</span> | ||||
| 	</div> | ||||
| </div> | ||||
|  | ||||
| <style> | ||||
| </style> | ||||
|   | ||||
| @@ -222,6 +222,21 @@ | ||||
| 			console.log('Error [sharedUserDeletedHandler] ', e); | ||||
| 		} | ||||
| 	}; | ||||
|  | ||||
| 	const removeAlbum = async () => { | ||||
| 		if ( | ||||
| 			window.confirm( | ||||
| 				`Are you sure you want to delete album ${album.albumName}? If the album is shared, other users will not be able to access it.` | ||||
| 			) | ||||
| 		) { | ||||
| 			try { | ||||
| 				await api.albumApi.deleteAlbum(album.id); | ||||
| 				goto(backUrl); | ||||
| 			} catch (e) { | ||||
| 				console.log('Error [userDeleteMenu] ', e); | ||||
| 			} | ||||
| 		} | ||||
| 	}; | ||||
| </script> | ||||
|  | ||||
| <section class="bg-immich-bg"> | ||||
| @@ -265,6 +280,7 @@ | ||||
| 							on:click={() => (isShowShareUserSelection = true)} | ||||
| 							logo={ShareVariantOutline} | ||||
| 						/> | ||||
| 						<CircleIconButton title="Remove album" on:click={removeAlbum} logo={DeleteOutline} /> | ||||
| 					{/if} | ||||
| 				{/if} | ||||
|  | ||||
|   | ||||
| @@ -25,7 +25,7 @@ | ||||
| 	{title} | ||||
| 	bind:this={iconButton} | ||||
| 	class={`immich-circle-icon-button rounded-full p-3 flex place-items-center place-content-center transition-all`} | ||||
| 	on:click={() => dispatch('click')} | ||||
| 	on:click={(mouseEvent) => dispatch('click', { mouseEvent })} | ||||
| > | ||||
| 	<svelte:component this={logo} {size} color={logoColor} /> | ||||
| </button> | ||||
|   | ||||
| @@ -4,10 +4,20 @@ | ||||
| 	import { quintOut } from 'svelte/easing'; | ||||
| 	import { slide } from 'svelte/transition'; | ||||
|  | ||||
| 	/** | ||||
| 	 * x coordiante of the context menu. | ||||
| 	 * @type {number} | ||||
| 	 */ | ||||
| 	export let x: number = 0; | ||||
|  | ||||
| 	/** | ||||
| 	 * x coordiante of the context menu. | ||||
| 	 * @type {number} | ||||
| 	 */ | ||||
| 	export let y: number = 0; | ||||
|  | ||||
| 	const dispatch = createEventDispatcher(); | ||||
|  | ||||
| 	let menuEl: HTMLElement; | ||||
|  | ||||
| 	$: (() => { | ||||
| @@ -24,7 +34,7 @@ | ||||
| <div | ||||
| 	transition:slide={{ duration: 200, easing: quintOut }} | ||||
| 	bind:this={menuEl} | ||||
| 	class="absolute bg-white w-[150px] z-[99999] rounded-lg shadow-md" | ||||
| 	class="absolute bg-white w-[175px] z-[99999] rounded-lg shadow-md" | ||||
| 	style={`top: ${y}px; left: ${x}px;`} | ||||
| 	use:clickOutside | ||||
| 	on:out-click={() => dispatch('clickoutside')} | ||||
|   | ||||
| @@ -16,7 +16,7 @@ | ||||
| <button | ||||
| 	class:disabled={isDisabled} | ||||
| 	on:click={handleClick} | ||||
| 	class="bg-white hover:bg-immich-bg transition-all p-4 w-full text-left rounded-lg" | ||||
| 	class="bg-white hover:bg-immich-bg transition-all p-4 w-full text-left rounded-lg text-sm" | ||||
| > | ||||
| 	{#if text} | ||||
| 		{text} | ||||
|   | ||||
| @@ -39,10 +39,17 @@ | ||||
| 	import AlbumCard from '$lib/components/album-page/album-card.svelte'; | ||||
| 	import { goto } from '$app/navigation'; | ||||
| 	import { onMount } from 'svelte'; | ||||
| 	import ContextMenu from '$lib/components/shared-components/context-menu/context-menu.svelte'; | ||||
| 	import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte'; | ||||
| 	import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte'; | ||||
|  | ||||
| 	export let user: ImmichUser; | ||||
| 	export let albums: AlbumResponseDto[]; | ||||
|  | ||||
| 	let isShowContextMenu = false; | ||||
| 	let contextMenuPosition = { x: 0, y: 0 }; | ||||
| 	let targetAlbum: AlbumResponseDto; | ||||
|  | ||||
| 	onMount(async () => { | ||||
| 		const { data } = await api.albumApi.getAllAlbums(); | ||||
| 		albums = data; | ||||
| @@ -50,7 +57,7 @@ | ||||
| 		// Delete album that has no photos and is named 'Untitled' | ||||
| 		for (const album of albums) { | ||||
| 			if (album.albumName === 'Untitled' && album.assets.length === 0) { | ||||
| 				const isDeleted = await deleteAlbum(album); | ||||
| 				const isDeleted = await autoDeleteAlbum(album); | ||||
|  | ||||
| 				if (isDeleted) { | ||||
| 					albums = albums.filter((a) => a.id !== album.id); | ||||
| @@ -71,15 +78,43 @@ | ||||
| 		} | ||||
| 	}; | ||||
|  | ||||
| 	const deleteAlbum = async (album: AlbumResponseDto) => { | ||||
| 	const autoDeleteAlbum = async (album: AlbumResponseDto) => { | ||||
| 		try { | ||||
| 			await api.albumApi.deleteAlbum(album.id); | ||||
| 			return true; | ||||
| 		} catch (e) { | ||||
| 			console.log('Error [deleteAlbum] ', e); | ||||
| 			console.log('Error [autoDeleteAlbum] ', e); | ||||
| 			return false; | ||||
| 		} | ||||
| 	}; | ||||
|  | ||||
| 	const userDeleteMenu = async () => { | ||||
| 		if ( | ||||
| 			window.confirm( | ||||
| 				`Are you sure you want to delete album ${targetAlbum.albumName}? If the album is shared, other users will not be able to access it.` | ||||
| 			) | ||||
| 		) { | ||||
| 			try { | ||||
| 				await api.albumApi.deleteAlbum(targetAlbum.id); | ||||
| 				albums = albums.filter((a) => a.id !== targetAlbum.id); | ||||
| 			} catch (e) { | ||||
| 				console.log('Error [userDeleteMenu] ', e); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		isShowContextMenu = false; | ||||
| 	}; | ||||
|  | ||||
| 	const showAlbumContextMenu = (event: CustomEvent, album: AlbumResponseDto) => { | ||||
| 		targetAlbum = album; | ||||
|  | ||||
| 		contextMenuPosition = { | ||||
| 			x: event.detail.x, | ||||
| 			y: event.detail.y | ||||
| 		}; | ||||
|  | ||||
| 		isShowContextMenu = !isShowContextMenu; | ||||
| 	}; | ||||
| </script> | ||||
|  | ||||
| <svelte:head> | ||||
| @@ -119,11 +154,25 @@ | ||||
| 			<!-- Album Card --> | ||||
| 			<div class="flex flex-wrap gap-8"> | ||||
| 				{#each albums as album} | ||||
| 					{#key album.id} | ||||
| 						<a sveltekit:prefetch href={`albums/${album.id}`}> | ||||
| 						<AlbumCard {album} /> | ||||
| 							<AlbumCard {album} on:showalbumcontextmenu={(e) => showAlbumContextMenu(e, album)} /> | ||||
| 						</a> | ||||
| 					{/key} | ||||
| 				{/each} | ||||
| 			</div> | ||||
| 		</section> | ||||
| 	</section> | ||||
|  | ||||
| 	<!-- Context Menu --> | ||||
| 	{#if isShowContextMenu} | ||||
| 		<ContextMenu {...contextMenuPosition} on:clickoutside={() => (isShowContextMenu = false)}> | ||||
| 			<MenuOption on:click={userDeleteMenu}> | ||||
| 				<span class="flex place-items-center place-content-center gap-2"> | ||||
| 					<DeleteOutline size="18" /> | ||||
| 					<p>Delete album</p> | ||||
| 				</span> | ||||
| 			</MenuOption> | ||||
| 		</ContextMenu> | ||||
| 	{/if} | ||||
| </section> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user