refactor: album page (#25140)

This commit is contained in:
Daniel Dietzler
2026-01-08 16:27:20 -06:00
committed by GitHub
parent 191401f2f1
commit 520b825511
7 changed files with 142 additions and 159 deletions
@@ -6,7 +6,6 @@
import RightClickContextMenu from '$lib/components/shared-components/context-menu/right-click-context-menu.svelte';
import AlbumEditModal from '$lib/modals/AlbumEditModal.svelte';
import AlbumShareModal from '$lib/modals/AlbumShareModal.svelte';
import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.svelte';
import { handleDeleteAlbum, handleDownloadAlbum } from '$lib/services/album.service';
import {
AlbumFilter,
@@ -21,14 +20,8 @@
import { userInteraction } from '$lib/stores/user.svelte';
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';
import {
addUsersToAlbum,
type AlbumResponseDto,
type AlbumUserAddDto,
type SharedLinkResponseDto,
} from '@immich/sdk';
import { type AlbumResponseDto, type SharedLinkResponseDto } from '@immich/sdk';
import { modalManager } from '@immich/ui';
import { mdiDeleteOutline, mdiDownload, mdiRenameOutline, mdiShareVariantOutline } from '@mdi/js';
import { groupBy } from 'lodash-es';
@@ -205,18 +198,7 @@
}
case 'share': {
const result = await modalManager.show(AlbumShareModal, { album: selectedAlbum });
switch (result?.action) {
case 'sharedUsers': {
await handleAddUsers(selectedAlbum, result.data);
break;
}
case 'sharedLink': {
await modalManager.show(SharedLinkCreateModal, { albumId: selectedAlbum.id });
break;
}
}
await modalManager.show(AlbumShareModal, { album: selectedAlbum });
break;
}
@@ -251,20 +233,6 @@
sharedAlbums = findAndUpdate(sharedAlbums, album);
};
const handleAddUsers = async (album: AlbumResponseDto, albumUsers: AlbumUserAddDto[]) => {
try {
const updatedAlbum = await addUsersToAlbum({
id: album.id,
addUsersDto: {
albumUsers,
},
});
onUpdate(updatedAlbum);
} catch (error) {
handleError(error, $t('errors.unable_to_add_album_users'));
}
};
const onAlbumUpdate = (album: AlbumResponseDto) => {
onUpdate(album);
userInteraction.recentAlbums = findAndUpdate(userInteraction.recentAlbums || [], album);
@@ -33,8 +33,10 @@ export type Events = {
AssetUpdate: [AssetResponseDto];
AssetReplace: [{ oldAssetId: string; newAssetId: string }];
AlbumAddAssets: [];
AlbumUpdate: [AlbumResponseDto];
AlbumDelete: [AlbumResponseDto];
AlbumShare: [];
PersonUpdate: [PersonResponseDto];
+25 -15
View File
@@ -2,16 +2,17 @@
import AlbumSharedLink from '$lib/components/album-page/album-shared-link.svelte';
import { AppRoute } from '$lib/constants';
import Dropdown from '$lib/elements/Dropdown.svelte';
import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.svelte';
import { handleAddUsersToAlbum } from '$lib/services/album.service';
import {
AlbumUserRole,
getAllSharedLinks,
searchUsers,
type AlbumResponseDto,
type AlbumUserAddDto,
type SharedLinkResponseDto,
type UserResponseDto,
} from '@immich/sdk';
import { Button, Icon, Link, Modal, ModalBody, Stack, Text } from '@immich/ui';
import { Button, Icon, Link, Modal, ModalBody, modalManager, Stack, Text } from '@immich/ui';
import { mdiCheck, mdiEye, mdiLink, mdiPencil } from '@mdi/js';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';
@@ -19,7 +20,7 @@
interface Props {
album: AlbumResponseDto;
onClose: (result?: { action: 'sharedLink' } | { action: 'sharedUsers'; data: AlbumUserAddDto[] }) => void;
onClose: () => void;
}
let { album, onClose }: Props = $props();
@@ -62,6 +63,21 @@
selectedUsers[user.id].role = role;
}
};
const onShareUser = async () => {
const success = await handleAddUsersToAlbum(
album,
Object.values(selectedUsers).map(({ user, ...rest }) => ({ userId: user.id, ...rest })),
);
if (success) {
onClose();
}
};
const onShareLink = () => {
void modalManager.show(SharedLinkCreateModal, { albumId: album.id });
onClose();
};
</script>
<Modal size="small" title={$t('share')} {onClose}>
@@ -145,12 +161,10 @@
fullWidth
shape="round"
disabled={Object.keys(selectedUsers).length === 0}
onclick={() =>
onClose({
action: 'sharedUsers',
data: Object.values(selectedUsers).map(({ user, ...rest }) => ({ userId: user.id, ...rest })),
})}>{$t('add')}</Button
onclick={onShareUser}
>
{$t('add')}
</Button>
</div>
{/if}
@@ -170,13 +184,9 @@
</Stack>
{/if}
<Button
leadingIcon={mdiLink}
size="small"
shape="round"
fullWidth
onclick={() => onClose({ action: 'sharedLink' })}>{$t('create_link')}</Button
>
<Button leadingIcon={mdiLink} size="small" shape="round" fullWidth onclick={onShareLink}>
{$t('create_link')}
</Button>
</Stack>
</ModalBody>
</Modal>
+80 -2
View File
@@ -2,11 +2,89 @@ import { goto } from '$app/navigation';
import ToastAction from '$lib/components/ToastAction.svelte';
import { AppRoute } from '$lib/constants';
import { eventManager } from '$lib/managers/event-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import AlbumShareModal from '$lib/modals/AlbumShareModal.svelte';
import { user } from '$lib/stores/user.store';
import { downloadArchive } from '$lib/utils/asset-utils';
import { openFileUploadDialog } from '$lib/utils/file-uploader';
import { handleError } from '$lib/utils/handle-error';
import { getFormatter } from '$lib/utils/i18n';
import { deleteAlbum, updateAlbumInfo, type AlbumResponseDto, type UpdateAlbumDto } from '@immich/sdk';
import { modalManager, toastManager } from '@immich/ui';
import {
addAssetsToAlbum,
addUsersToAlbum,
deleteAlbum,
updateAlbumInfo,
type AlbumResponseDto,
type AlbumUserAddDto,
type UpdateAlbumDto,
} from '@immich/sdk';
import { modalManager, toastManager, type ActionItem } from '@immich/ui';
import { mdiPlusBoxOutline, mdiShareVariantOutline, mdiUpload } from '@mdi/js';
import { type MessageFormatter } from 'svelte-i18n';
import { get } from 'svelte/store';
export const getAlbumActions = ($t: MessageFormatter, album: AlbumResponseDto) => {
const isOwned = get(user).id === album.ownerId;
const Share: ActionItem = {
title: $t('share'),
type: $t('command'),
icon: mdiShareVariantOutline,
$if: () => isOwned,
onAction: () => modalManager.show(AlbumShareModal, { album }),
};
return { Share };
};
export const getAlbumAssetsActions = ($t: MessageFormatter, album: AlbumResponseDto, assets: TimelineAsset[]) => {
const AddAssets: ActionItem = {
title: $t('add_assets'),
type: $t('command'),
icon: mdiPlusBoxOutline,
$if: () => assets.length > 0,
onAction: () => addAssets(album, assets),
};
const Upload: ActionItem = {
title: $t('select_from_computer'),
description: $t('album_upload_assets'),
type: $t('command'),
icon: mdiUpload,
onAction: () => void openFileUploadDialog({ albumId: album.id }),
};
return { AddAssets, Upload };
};
const addAssets = async (album: AlbumResponseDto, assets: TimelineAsset[]) => {
const $t = await getFormatter();
const assetIds = assets.map(({ id }) => id);
try {
const results = await addAssetsToAlbum({ id: album.id, bulkIdsDto: { ids: assetIds } });
const count = results.filter(({ success }) => success).length;
toastManager.success($t('assets_added_count', { values: { count } }));
eventManager.emit('AlbumAddAssets');
} catch (error) {
handleError(error, $t('errors.error_adding_assets_to_album'));
}
};
export const handleAddUsersToAlbum = async (album: AlbumResponseDto, albumUsers: AlbumUserAddDto[]) => {
const $t = await getFormatter();
try {
await addUsersToAlbum({ id: album.id, addUsersDto: { albumUsers } });
eventManager.emit('AlbumShare');
return true;
} catch (error) {
handleError(error, $t('errors.error_adding_users_to_album'));
}
return false;
};
export const handleUpdateAlbum = async ({ id }: { id: string }, dto: UpdateAlbumDto) => {
const $t = await getFormatter();