From d5c5bdffcb2710dae78a5a4de59d4d0982d50bbd Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Mon, 10 Nov 2025 16:10:29 -0500 Subject: [PATCH] refactor: album delete (#23773) --- .../components/album-page/albums-list.svelte | 45 +++++-------------- web/src/lib/managers/event-manager.svelte.ts | 5 ++- web/src/lib/services/album.service.ts | 39 +++++++++++++++- .../[[assetId=id]]/+page.svelte | 39 +++++++--------- 4 files changed, 69 insertions(+), 59 deletions(-) diff --git a/web/src/lib/components/album-page/albums-list.svelte b/web/src/lib/components/album-page/albums-list.svelte index fb8d044b1a..bb826110b7 100644 --- a/web/src/lib/components/album-page/albums-list.svelte +++ b/web/src/lib/components/album-page/albums-list.svelte @@ -3,6 +3,7 @@ import { resolve } from '$app/paths'; import AlbumCardGroup from '$lib/components/album-page/album-card-group.svelte'; import AlbumsTable from '$lib/components/album-page/albums-table.svelte'; + import OnEvents from '$lib/components/OnEvents.svelte'; import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte'; import RightClickContextMenu from '$lib/components/shared-components/context-menu/right-click-context-menu.svelte'; import ToastAction from '$lib/components/ToastAction.svelte'; @@ -10,7 +11,7 @@ import AlbumEditModal from '$lib/modals/AlbumEditModal.svelte'; import AlbumShareModal from '$lib/modals/AlbumShareModal.svelte'; import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.svelte'; - import { handleConfirmAlbumDelete, handleDownloadAlbum } from '$lib/services/album.service'; + import { handleDeleteAlbum, handleDownloadAlbum } from '$lib/services/album.service'; import { AlbumFilter, AlbumGroupBy, @@ -26,7 +27,7 @@ import type { ContextMenuPosition } from '$lib/utils/context-menu'; import { handleError } from '$lib/utils/handle-error'; import { normalizeSearchString } from '$lib/utils/string-utils'; - import { addUsersToAlbum, deleteAlbum, isHttpError, type AlbumResponseDto, type AlbumUserAddDto } from '@immich/sdk'; + import { addUsersToAlbum, type AlbumResponseDto, type AlbumUserAddDto } from '@immich/sdk'; import { modalManager, toastManager } from '@immich/ui'; import { mdiDeleteOutline, mdiDownload, mdiRenameOutline, mdiShareVariantOutline } from '@mdi/js'; import { groupBy } from 'lodash-es'; @@ -210,25 +211,6 @@ isOpen = false; }; - const handleDeleteAlbum = async (albumToDelete: AlbumResponseDto) => { - try { - await deleteAlbum({ - id: albumToDelete.id, - }); - } catch (error) { - // In rare cases deleting an album completes after the list of albums has been requested, - // leading to a bad request error. - // Since the album is already deleted, the error is ignored. - const isBadRequest = isHttpError(error) && error.status === 400; - if (!isBadRequest) { - throw error; - } - } - - ownedAlbums = ownedAlbums.filter(({ id }) => id !== albumToDelete.id); - sharedAlbums = sharedAlbums.filter(({ id }) => id !== albumToDelete.id); - }; - const handleSelect = async (action: 'edit' | 'share' | 'download' | 'delete') => { closeAlbumContextMenu(); @@ -272,17 +254,7 @@ } case 'delete': { - const isConfirmed = await handleConfirmAlbumDelete(selectedAlbum); - if (!isConfirmed) { - return; - } - - try { - await handleDeleteAlbum(selectedAlbum); - } catch (error) { - handleError(error, $t('errors.unable_to_delete_album')); - } - + await handleDeleteAlbum(selectedAlbum); break; } } @@ -290,7 +262,7 @@ const removeAlbumsIfEmpty = async () => { const albumsToRemove = ownedAlbums.filter((album) => album.assetCount === 0 && !album.albumName); - await Promise.allSettled(albumsToRemove.map((album) => handleDeleteAlbum(album))); + await Promise.allSettled(albumsToRemove.map((album) => handleDeleteAlbum(album, { prompt: false, notify: false }))); }; const updateAlbumInfo = (album: AlbumResponseDto) => { @@ -346,8 +318,15 @@ albumToShare = null; } }; + + const onAlbumDelete = (album: AlbumResponseDto) => { + ownedAlbums = ownedAlbums.filter(({ id }) => id !== album.id); + sharedAlbums = sharedAlbums.filter(({ id }) => id !== album.id); + }; + + {#if albums.length > 0} {#if userSettings.view === AlbumViewMode.Cover} diff --git a/web/src/lib/managers/event-manager.svelte.ts b/web/src/lib/managers/event-manager.svelte.ts index e7d50f026d..1bd437b4cc 100644 --- a/web/src/lib/managers/event-manager.svelte.ts +++ b/web/src/lib/managers/event-manager.svelte.ts @@ -1,5 +1,5 @@ import type { ThemeSetting } from '$lib/managers/theme-manager.svelte'; -import type { LoginResponseDto, SharedLinkResponseDto } from '@immich/sdk'; +import type { AlbumResponseDto, LoginResponseDto, SharedLinkResponseDto } from '@immich/sdk'; export type Events = { AppInit: []; @@ -8,6 +8,9 @@ export type Events = { AuthLogout: []; LanguageChange: [{ name: string; code: string; rtl?: boolean }]; ThemeChange: [ThemeSetting]; + + AlbumDelete: [AlbumResponseDto]; + SharedLinkCreate: [SharedLinkResponseDto]; SharedLinkUpdate: [SharedLinkResponseDto]; SharedLinkDelete: [SharedLinkResponseDto]; diff --git a/web/src/lib/services/album.service.ts b/web/src/lib/services/album.service.ts index 52fa09d103..6e5583495f 100644 --- a/web/src/lib/services/album.service.ts +++ b/web/src/lib/services/album.service.ts @@ -1,7 +1,42 @@ +import { eventManager } from '$lib/managers/event-manager.svelte'; import { downloadArchive } from '$lib/utils/asset-utils'; +import { handleError } from '$lib/utils/handle-error'; import { getFormatter } from '$lib/utils/i18n'; -import type { AlbumResponseDto } from '@immich/sdk'; -import { modalManager } from '@immich/ui'; +import { deleteAlbum, type AlbumResponseDto } from '@immich/sdk'; +import { modalManager, toastManager } from '@immich/ui'; + +export const handleDeleteAlbum = async (album: AlbumResponseDto, options?: { prompt?: boolean; notify?: boolean }) => { + const $t = await getFormatter(); + const { prompt = true, notify = true } = options ?? {}; + + if (prompt) { + const confirmation = + album.albumName.length > 0 + ? $t('album_delete_confirmation', { values: { album: album.albumName } }) + : $t('unnamed_album_delete_confirmation'); + const description = $t('album_delete_confirmation_description'); + + const success = await modalManager.showDialog({ prompt: `${confirmation} ${description}` }); + if (!success) { + return false; + } + } + + try { + await deleteAlbum({ id: album.id }); + + eventManager.emit('AlbumDelete', album); + + if (notify) { + toastManager.success(); + } + + return true; + } catch (error) { + handleError(error, $t('errors.unable_to_delete_album')); + return false; + } +}; export const handleDownloadAlbum = async (album: AlbumResponseDto) => { await downloadArchive(`${album.albumName}.zip`, { albumId: album.id }); diff --git a/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte index 6b543e1ecf..9af2b611f2 100644 --- a/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -36,7 +36,7 @@ import AlbumShareModal from '$lib/modals/AlbumShareModal.svelte'; import AlbumUsersModal from '$lib/modals/AlbumUsersModal.svelte'; import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.svelte'; - import { handleConfirmAlbumDelete, handleDownloadAlbum } from '$lib/services/album.service'; + import { handleDeleteAlbum, handleDownloadAlbum } from '$lib/services/album.service'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { featureFlags } from '$lib/stores/server-config.store'; @@ -59,9 +59,9 @@ AssetVisibility, addAssetsToAlbum, addUsersToAlbum, - deleteAlbum, getAlbumInfo, updateAlbumInfo, + type AlbumResponseDto, type AlbumUserAddDto, } from '@immich/sdk'; import { Button, Icon, IconButton, modalManager, toastManager } from '@immich/ui'; @@ -233,24 +233,6 @@ } }; - const handleRemoveAlbum = async () => { - const isConfirmed = await handleConfirmAlbumDelete(album); - - if (!isConfirmed) { - viewMode = AlbumPageViewMode.VIEW; - return; - } - - try { - await deleteAlbum({ id: album.id }); - await goto(backUrl); - } catch (error) { - handleError(error, $t('errors.unable_to_delete_album')); - } finally { - viewMode = AlbumPageViewMode.VIEW; - } - }; - const handleSetVisibility = (assetIds: string[]) => { timelineManager.removeAssets(assetIds); assetInteraction.clearMultiselect(); @@ -301,7 +283,7 @@ onNavigate(async ({ to }) => { if (!isAlbumsRoute(to?.route.id) && album.assetCount === 0 && !album.albumName) { - await deleteAlbum(album); + await handleDeleteAlbum(album, { notify: false, prompt: false }); } }); @@ -388,6 +370,13 @@ await refreshAlbum(); }; + const onAlbumDelete = async ({ id }: AlbumResponseDto) => { + if (id === album.id) { + await goto(backUrl); + viewMode = AlbumPageViewMode.VIEW; + } + }; + const handleShareLink = async () => { await modalManager.show(SharedLinkCreateModal, { albumId: album.id }); }; @@ -424,7 +413,7 @@ }; - +
@@ -672,7 +661,11 @@ {/if} - handleRemoveAlbum()} /> + handleDeleteAlbum(album)} + /> {/if}