mirror of
https://github.com/immich-app/immich.git
synced 2025-11-18 20:43:20 -05:00
refactor: album delete (#23773)
This commit is contained in:
parent
dea95ac2e6
commit
d5c5bdffcb
@ -3,6 +3,7 @@
|
|||||||
import { resolve } from '$app/paths';
|
import { resolve } from '$app/paths';
|
||||||
import AlbumCardGroup from '$lib/components/album-page/album-card-group.svelte';
|
import AlbumCardGroup from '$lib/components/album-page/album-card-group.svelte';
|
||||||
import AlbumsTable from '$lib/components/album-page/albums-table.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 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 RightClickContextMenu from '$lib/components/shared-components/context-menu/right-click-context-menu.svelte';
|
||||||
import ToastAction from '$lib/components/ToastAction.svelte';
|
import ToastAction from '$lib/components/ToastAction.svelte';
|
||||||
@ -10,7 +11,7 @@
|
|||||||
import AlbumEditModal from '$lib/modals/AlbumEditModal.svelte';
|
import AlbumEditModal from '$lib/modals/AlbumEditModal.svelte';
|
||||||
import AlbumShareModal from '$lib/modals/AlbumShareModal.svelte';
|
import AlbumShareModal from '$lib/modals/AlbumShareModal.svelte';
|
||||||
import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.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 {
|
import {
|
||||||
AlbumFilter,
|
AlbumFilter,
|
||||||
AlbumGroupBy,
|
AlbumGroupBy,
|
||||||
@ -26,7 +27,7 @@
|
|||||||
import type { ContextMenuPosition } from '$lib/utils/context-menu';
|
import type { ContextMenuPosition } from '$lib/utils/context-menu';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { normalizeSearchString } from '$lib/utils/string-utils';
|
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 { modalManager, toastManager } from '@immich/ui';
|
||||||
import { mdiDeleteOutline, mdiDownload, mdiRenameOutline, mdiShareVariantOutline } from '@mdi/js';
|
import { mdiDeleteOutline, mdiDownload, mdiRenameOutline, mdiShareVariantOutline } from '@mdi/js';
|
||||||
import { groupBy } from 'lodash-es';
|
import { groupBy } from 'lodash-es';
|
||||||
@ -210,25 +211,6 @@
|
|||||||
isOpen = false;
|
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') => {
|
const handleSelect = async (action: 'edit' | 'share' | 'download' | 'delete') => {
|
||||||
closeAlbumContextMenu();
|
closeAlbumContextMenu();
|
||||||
|
|
||||||
@ -272,17 +254,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 'delete': {
|
case 'delete': {
|
||||||
const isConfirmed = await handleConfirmAlbumDelete(selectedAlbum);
|
await handleDeleteAlbum(selectedAlbum);
|
||||||
if (!isConfirmed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await handleDeleteAlbum(selectedAlbum);
|
|
||||||
} catch (error) {
|
|
||||||
handleError(error, $t('errors.unable_to_delete_album'));
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -290,7 +262,7 @@
|
|||||||
|
|
||||||
const removeAlbumsIfEmpty = async () => {
|
const removeAlbumsIfEmpty = async () => {
|
||||||
const albumsToRemove = ownedAlbums.filter((album) => album.assetCount === 0 && !album.albumName);
|
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) => {
|
const updateAlbumInfo = (album: AlbumResponseDto) => {
|
||||||
@ -346,8 +318,15 @@
|
|||||||
albumToShare = null;
|
albumToShare = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onAlbumDelete = (album: AlbumResponseDto) => {
|
||||||
|
ownedAlbums = ownedAlbums.filter(({ id }) => id !== album.id);
|
||||||
|
sharedAlbums = sharedAlbums.filter(({ id }) => id !== album.id);
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<OnEvents {onAlbumDelete} />
|
||||||
|
|
||||||
{#if albums.length > 0}
|
{#if albums.length > 0}
|
||||||
{#if userSettings.view === AlbumViewMode.Cover}
|
{#if userSettings.view === AlbumViewMode.Cover}
|
||||||
<!-- Album Cards -->
|
<!-- Album Cards -->
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { ThemeSetting } from '$lib/managers/theme-manager.svelte';
|
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 = {
|
export type Events = {
|
||||||
AppInit: [];
|
AppInit: [];
|
||||||
@ -8,6 +8,9 @@ export type Events = {
|
|||||||
AuthLogout: [];
|
AuthLogout: [];
|
||||||
LanguageChange: [{ name: string; code: string; rtl?: boolean }];
|
LanguageChange: [{ name: string; code: string; rtl?: boolean }];
|
||||||
ThemeChange: [ThemeSetting];
|
ThemeChange: [ThemeSetting];
|
||||||
|
|
||||||
|
AlbumDelete: [AlbumResponseDto];
|
||||||
|
|
||||||
SharedLinkCreate: [SharedLinkResponseDto];
|
SharedLinkCreate: [SharedLinkResponseDto];
|
||||||
SharedLinkUpdate: [SharedLinkResponseDto];
|
SharedLinkUpdate: [SharedLinkResponseDto];
|
||||||
SharedLinkDelete: [SharedLinkResponseDto];
|
SharedLinkDelete: [SharedLinkResponseDto];
|
||||||
|
|||||||
@ -1,7 +1,42 @@
|
|||||||
|
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||||
import { downloadArchive } from '$lib/utils/asset-utils';
|
import { downloadArchive } from '$lib/utils/asset-utils';
|
||||||
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { getFormatter } from '$lib/utils/i18n';
|
import { getFormatter } from '$lib/utils/i18n';
|
||||||
import type { AlbumResponseDto } from '@immich/sdk';
|
import { deleteAlbum, type AlbumResponseDto } from '@immich/sdk';
|
||||||
import { modalManager } from '@immich/ui';
|
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) => {
|
export const handleDownloadAlbum = async (album: AlbumResponseDto) => {
|
||||||
await downloadArchive(`${album.albumName}.zip`, { albumId: album.id });
|
await downloadArchive(`${album.albumName}.zip`, { albumId: album.id });
|
||||||
|
|||||||
@ -36,7 +36,7 @@
|
|||||||
import AlbumShareModal from '$lib/modals/AlbumShareModal.svelte';
|
import AlbumShareModal from '$lib/modals/AlbumShareModal.svelte';
|
||||||
import AlbumUsersModal from '$lib/modals/AlbumUsersModal.svelte';
|
import AlbumUsersModal from '$lib/modals/AlbumUsersModal.svelte';
|
||||||
import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.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 { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
import { featureFlags } from '$lib/stores/server-config.store';
|
import { featureFlags } from '$lib/stores/server-config.store';
|
||||||
@ -59,9 +59,9 @@
|
|||||||
AssetVisibility,
|
AssetVisibility,
|
||||||
addAssetsToAlbum,
|
addAssetsToAlbum,
|
||||||
addUsersToAlbum,
|
addUsersToAlbum,
|
||||||
deleteAlbum,
|
|
||||||
getAlbumInfo,
|
getAlbumInfo,
|
||||||
updateAlbumInfo,
|
updateAlbumInfo,
|
||||||
|
type AlbumResponseDto,
|
||||||
type AlbumUserAddDto,
|
type AlbumUserAddDto,
|
||||||
} from '@immich/sdk';
|
} from '@immich/sdk';
|
||||||
import { Button, Icon, IconButton, modalManager, toastManager } from '@immich/ui';
|
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[]) => {
|
const handleSetVisibility = (assetIds: string[]) => {
|
||||||
timelineManager.removeAssets(assetIds);
|
timelineManager.removeAssets(assetIds);
|
||||||
assetInteraction.clearMultiselect();
|
assetInteraction.clearMultiselect();
|
||||||
@ -301,7 +283,7 @@
|
|||||||
|
|
||||||
onNavigate(async ({ to }) => {
|
onNavigate(async ({ to }) => {
|
||||||
if (!isAlbumsRoute(to?.route.id) && album.assetCount === 0 && !album.albumName) {
|
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();
|
await refreshAlbum();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onAlbumDelete = async ({ id }: AlbumResponseDto) => {
|
||||||
|
if (id === album.id) {
|
||||||
|
await goto(backUrl);
|
||||||
|
viewMode = AlbumPageViewMode.VIEW;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleShareLink = async () => {
|
const handleShareLink = async () => {
|
||||||
await modalManager.show(SharedLinkCreateModal, { albumId: album.id });
|
await modalManager.show(SharedLinkCreateModal, { albumId: album.id });
|
||||||
};
|
};
|
||||||
@ -424,7 +413,7 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<OnEvents {onSharedLinkCreate} />
|
<OnEvents {onSharedLinkCreate} {onAlbumDelete} />
|
||||||
|
|
||||||
<div class="flex overflow-hidden" use:scrollMemoryClearer={{ routeStartsWith: AppRoute.ALBUMS }}>
|
<div class="flex overflow-hidden" use:scrollMemoryClearer={{ routeStartsWith: AppRoute.ALBUMS }}>
|
||||||
<div class="relative w-full shrink">
|
<div class="relative w-full shrink">
|
||||||
@ -672,7 +661,11 @@
|
|||||||
<MenuOption icon={mdiCogOutline} text={$t('options')} onClick={handleOptions} />
|
<MenuOption icon={mdiCogOutline} text={$t('options')} onClick={handleOptions} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<MenuOption icon={mdiDeleteOutline} text={$t('delete_album')} onClick={() => handleRemoveAlbum()} />
|
<MenuOption
|
||||||
|
icon={mdiDeleteOutline}
|
||||||
|
text={$t('delete_album')}
|
||||||
|
onClick={() => handleDeleteAlbum(album)}
|
||||||
|
/>
|
||||||
</ButtonContextMenu>
|
</ButtonContextMenu>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user