From a4e65a7ea822e9ce406e2a9dd0aca4e1acd92d74 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Mon, 10 Nov 2025 11:49:59 -0500 Subject: [PATCH] refactor: albums-list (#23765) --- .../components/album-page/albums-list.svelte | 139 +++++++----------- web/src/lib/services/album.service.ts | 15 ++ web/src/lib/utils/album-utils.ts | 14 -- .../[[assetId=id]]/+page.svelte | 5 +- 4 files changed, 74 insertions(+), 99 deletions(-) diff --git a/web/src/lib/components/album-page/albums-list.svelte b/web/src/lib/components/album-page/albums-list.svelte index 5a93dd08f1..deb206ca9f 100644 --- a/web/src/lib/components/album-page/albums-list.svelte +++ b/web/src/lib/components/album-page/albums-list.svelte @@ -11,7 +11,7 @@ import AlbumShareModal from '$lib/modals/AlbumShareModal.svelte'; import QrCodeModal from '$lib/modals/QrCodeModal.svelte'; import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.svelte'; - import { handleDownloadAlbum } from '$lib/services/album.service'; + import { handleConfirmAlbumDelete, handleDownloadAlbum } from '$lib/services/album.service'; import { AlbumFilter, AlbumGroupBy, @@ -24,13 +24,7 @@ import { user } from '$lib/stores/user.store'; import { userInteraction } from '$lib/stores/user.svelte'; import { makeSharedLinkUrl } from '$lib/utils'; - import { - confirmAlbumDelete, - getSelectedAlbumGroupOption, - sortAlbums, - stringToSortOrder, - type AlbumGroup, - } from '$lib/utils/album-utils'; + import { getSelectedAlbumGroupOption, sortAlbums, stringToSortOrder, type AlbumGroup } from '$lib/utils/album-utils'; import type { ContextMenuPosition } from '$lib/utils/context-menu'; import { handleError } from '$lib/utils/handle-error'; import { normalizeSearchString } from '$lib/utils/string-utils'; @@ -142,10 +136,9 @@ let albumGroupOption: string = $state(AlbumGroupBy.None); let albumToShare: AlbumResponseDto | null = $state(null); - let albumToDelete: AlbumResponseDto | null = null; let contextMenuPosition: ContextMenuPosition = $state({ x: 0, y: 0 }); - let contextMenuTargetAlbum: AlbumResponseDto | undefined = $state(); + let selectedAlbum: AlbumResponseDto | undefined = $state(); let isOpen = $state(false); // Step 1: Filter between Owned and Shared albums, or both. @@ -198,9 +191,7 @@ albumGroupIds = groupedAlbums.map(({ id }) => id); }); - let showFullContextMenu = $derived( - allowEdit && contextMenuTargetAlbum && contextMenuTargetAlbum.ownerId === $user.id, - ); + let showFullContextMenu = $derived(allowEdit && selectedAlbum && selectedAlbum.ownerId === $user.id); onMount(async () => { if (allowEdit) { @@ -209,7 +200,7 @@ }); const showAlbumContextMenu = (contextMenuDetail: ContextMenuPosition, album: AlbumResponseDto) => { - contextMenuTargetAlbum = album; + selectedAlbum = album; contextMenuPosition = { x: contextMenuDetail.x, y: contextMenuDetail.y, @@ -221,13 +212,6 @@ isOpen = false; }; - const onDownloadAlbum = async () => { - if (contextMenuTargetAlbum) { - closeAlbumContextMenu(); - await handleDownloadAlbum(contextMenuTargetAlbum); - } - }; - const handleDeleteAlbum = async (albumToDelete: AlbumResponseDto) => { try { await deleteAlbum({ @@ -247,39 +231,61 @@ sharedAlbums = sharedAlbums.filter(({ id }) => id !== albumToDelete.id); }; - const setAlbumToDelete = async () => { - albumToDelete = contextMenuTargetAlbum ?? null; + const handleSelect = async (action: 'edit' | 'share' | 'download' | 'delete') => { closeAlbumContextMenu(); - await deleteSelectedAlbum(); - }; - const handleEdit = async (album: AlbumResponseDto) => { - closeAlbumContextMenu(); - const editedAlbum = await modalManager.show(AlbumEditModal, { - album, - }); - if (editedAlbum) { - successEditAlbumInfo(editedAlbum); - } - }; - - const deleteSelectedAlbum = async () => { - if (!albumToDelete) { + if (!selectedAlbum) { return; } - const isConfirmed = await confirmAlbumDelete(albumToDelete); + switch (action) { + case 'edit': { + const editedAlbum = await modalManager.show(AlbumEditModal, { album: selectedAlbum }); + if (editedAlbum) { + successEditAlbumInfo(editedAlbum); + } + break; + } - if (!isConfirmed) { - return; - } + case 'share': { + const result = await modalManager.show(AlbumShareModal, { album: selectedAlbum }); + switch (result?.action) { + case 'sharedUsers': { + await handleAddUsers(result.data); + break; + } - try { - await handleDeleteAlbum(albumToDelete); - } catch (error) { - handleError(error, $t('errors.unable_to_delete_album')); - } finally { - albumToDelete = null; + case 'sharedLink': { + const sharedLink = await modalManager.show(SharedLinkCreateModal, { albumId: selectedAlbum.id }); + if (sharedLink) { + handleSharedLinkCreated(selectedAlbum); + await modalManager.show(QrCodeModal, { title: $t('view_link'), value: makeSharedLinkUrl(sharedLink) }); + } + break; + } + } + break; + } + + case 'download': { + await handleDownloadAlbum(selectedAlbum); + break; + } + + case 'delete': { + const isConfirmed = await handleConfirmAlbumDelete(selectedAlbum); + if (!isConfirmed) { + return; + } + + try { + await handleDeleteAlbum(selectedAlbum); + } catch (error) { + handleError(error, $t('errors.unable_to_delete_album')); + } + + break; + } } }; @@ -347,33 +353,6 @@ album.hasSharedLink = true; updateAlbumInfo(album); }; - - const openShareModal = async () => { - if (!contextMenuTargetAlbum) { - return; - } - - albumToShare = contextMenuTargetAlbum; - closeAlbumContextMenu(); - const result = await modalManager.show(AlbumShareModal, { album: albumToShare }); - - switch (result?.action) { - case 'sharedUsers': { - await handleAddUsers(result.data); - return; - } - - case 'sharedLink': { - const sharedLink = await modalManager.show(SharedLinkCreateModal, { albumId: albumToShare.id }); - - if (sharedLink) { - handleSharedLinkCreated(albumToShare); - await modalManager.show(QrCodeModal, { title: $t('view_link'), value: makeSharedLinkUrl(sharedLink) }); - } - return; - } - } - }; {#if albums.length > 0} @@ -411,15 +390,11 @@ {#if showFullContextMenu} - contextMenuTargetAlbum && handleEdit(contextMenuTargetAlbum)} - /> - openShareModal()} /> + handleSelect('edit')} /> + handleSelect('share')} /> {/if} - + handleSelect('download')} /> {#if showFullContextMenu} - setAlbumToDelete()} /> + handleSelect('delete')} /> {/if} diff --git a/web/src/lib/services/album.service.ts b/web/src/lib/services/album.service.ts index cb7b55bc11..52fa09d103 100644 --- a/web/src/lib/services/album.service.ts +++ b/web/src/lib/services/album.service.ts @@ -1,6 +1,21 @@ import { downloadArchive } from '$lib/utils/asset-utils'; +import { getFormatter } from '$lib/utils/i18n'; import type { AlbumResponseDto } from '@immich/sdk'; +import { modalManager } from '@immich/ui'; export const handleDownloadAlbum = async (album: AlbumResponseDto) => { await downloadArchive(`${album.albumName}.zip`, { albumId: album.id }); }; + +export const handleConfirmAlbumDelete = async (album: AlbumResponseDto) => { + const $t = await getFormatter(); + 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 prompt = `${confirmation} ${description}`; + + return modalManager.showDialog({ prompt }); +}; diff --git a/web/src/lib/utils/album-utils.ts b/web/src/lib/utils/album-utils.ts index 0cb8b7fc04..d4541949ca 100644 --- a/web/src/lib/utils/album-utils.ts +++ b/web/src/lib/utils/album-utils.ts @@ -12,7 +12,6 @@ import { import { handleError } from '$lib/utils/handle-error'; import type { AlbumResponseDto } from '@immich/sdk'; import * as sdk from '@immich/sdk'; -import { modalManager } from '@immich/ui'; import { orderBy } from 'lodash-es'; import { t } from 'svelte-i18n'; import { get } from 'svelte/store'; @@ -203,19 +202,6 @@ export const expandAllAlbumGroups = () => { collapseAllAlbumGroups([]); }; -export const confirmAlbumDelete = async (album: AlbumResponseDto) => { - const $t = get(t); - 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 prompt = `${confirmation} ${description}`; - - return modalManager.showDialog({ prompt }); -}; - interface AlbumSortOption { [option: string]: (order: SortOrder, albums: AlbumResponseDto[]) => AlbumResponseDto[]; } 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 f9453c41cf..34f3240e84 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,14 +36,13 @@ import AlbumUsersModal from '$lib/modals/AlbumUsersModal.svelte'; import QrCodeModal from '$lib/modals/QrCodeModal.svelte'; import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.svelte'; - import { handleDownloadAlbum } from '$lib/services/album.service'; + import { handleConfirmAlbumDelete, 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'; import { SlideshowNavigation, SlideshowState, slideshowStore } from '$lib/stores/slideshow.store'; import { preferences, user } from '$lib/stores/user.store'; import { handlePromiseError, makeSharedLinkUrl } from '$lib/utils'; - import { confirmAlbumDelete } from '$lib/utils/album-utils'; import { cancelMultiselect } from '$lib/utils/asset-utils'; import { openFileUploadDialog } from '$lib/utils/file-uploader'; import { handleError } from '$lib/utils/handle-error'; @@ -235,7 +234,7 @@ }; const handleRemoveAlbum = async () => { - const isConfirmed = await confirmAlbumDelete(album); + const isConfirmed = await handleConfirmAlbumDelete(album); if (!isConfirmed) { viewMode = AlbumPageViewMode.VIEW;