refactor: shared link queries (#32666)

(cherry picked from commit 0a73481a8fdb3cfbab29f23da48e26133dc4c064)
This commit is contained in:
Jason Rasmussen
2026-10-22 20:43:53 +03:00
committed by Codex
parent fa77f168a0
commit 0991f506bb
14 changed files with 167 additions and 222 deletions
@@ -15,7 +15,7 @@
} from '$lib/stores/preferences.store'; } from '$lib/stores/preferences.store';
import { getSelectedAlbumGroupOption, sortAlbums, stringToSortOrder, type AlbumGroup } from '$lib/utils/album-utils'; import { getSelectedAlbumGroupOption, sortAlbums, stringToSortOrder, type AlbumGroup } from '$lib/utils/album-utils';
import { normalizeSearchString } from '$lib/utils/string-utils'; import { normalizeSearchString } from '$lib/utils/string-utils';
import { type AlbumResponseDto, type SharedLinkResponseDto } from '@immich/sdk'; import { type AlbumResponseDto } from '@immich/sdk';
import { groupBy } from 'lodash-es'; import { groupBy } from 'lodash-es';
import { onMount, type Snippet } from 'svelte'; import { onMount, type Snippet } from 'svelte';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
@@ -185,15 +185,9 @@
ownedAlbums = ownedAlbums.filter(({ id }) => id !== album.id); ownedAlbums = ownedAlbums.filter(({ id }) => id !== album.id);
sharedAlbums = sharedAlbums.filter(({ id }) => id !== album.id); sharedAlbums = sharedAlbums.filter(({ id }) => id !== album.id);
}; };
const onSharedLinkCreate = (sharedLink: SharedLinkResponseDto) => {
if (sharedLink.album) {
onAlbumUpdate(sharedLink.album);
}
};
</script> </script>
<OnEvents {onAlbumUpdate} {onAlbumDelete} {onSharedLinkCreate} /> <OnEvents {onAlbumUpdate} {onAlbumDelete} />
{#if albums.length > 0} {#if albums.length > 0}
{#if userSettings.view === AlbumViewMode.Cover} {#if userSettings.view === AlbumViewMode.Cover}
@@ -1,9 +1,10 @@
<script lang="ts"> <script lang="ts">
import { shortcut } from '$lib/actions/shortcut'; import { shortcut } from '$lib/actions/shortcut';
import { assetMultiSelectManager } from '$lib/managers/asset-multi-select-manager.svelte'; import { assetMultiSelectManager } from '$lib/managers/asset-multi-select-manager.svelte';
import { handleRemoveSharedLinkAssets } from '$lib/services/shared-link.service'; import { executeMutation } from '$lib/queries/client';
import { removeSharedLinkAssetsMutation } from '$lib/queries/shared-link.query';
import { type SharedLinkResponseDto } from '@immich/sdk'; import { type SharedLinkResponseDto } from '@immich/sdk';
import { IconButton } from '@immich/ui'; import { IconButton, modalManager, toastManager } from '@immich/ui';
import { mdiDeleteOutline } from '@mdi/js'; import { mdiDeleteOutline } from '@mdi/js';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
@@ -13,22 +14,35 @@
let { sharedLink = $bindable() }: Props = $props(); let { sharedLink = $bindable() }: Props = $props();
const handleSelect = async () => { const onAction = async () => {
const assetIds = assetMultiSelectManager.assets.map(({ id }) => id); const assetIds = assetMultiSelectManager.assets.map(({ id }) => id);
const success = await handleRemoveSharedLinkAssets(sharedLink, assetIds); const success = await modalManager.showDialog({
if (success) { title: $t('remove_assets_title'),
assetMultiSelectManager.clear(); prompt: $t('remove_assets_shared_link_confirmation', { values: { count: assetIds.length } }),
confirmText: $t('remove'),
});
if (!success) {
return;
} }
await executeMutation(removeSharedLinkAssetsMutation, [{ id: sharedLink.id, assetIdsDto: { assetIds } }], {
onSuccess: (results) => {
const removed = new Set(results.filter(({ success }) => success).map(({ assetId }) => assetId));
sharedLink.assets = sharedLink.assets.filter(({ id }) => !removed.has(id));
toastManager.primary($t('assets_removed_count', { values: { count: removed.size } }));
assetMultiSelectManager.clear();
},
});
}; };
</script> </script>
<svelte:document use:shortcut={{ shortcut: { key: 'Delete' }, onShortcut: handleSelect }} /> <svelte:document use:shortcut={{ shortcut: { key: 'Delete' }, onShortcut: onAction }} />
<IconButton <IconButton
shape="round" shape="round"
color="secondary" color="secondary"
variant="ghost" variant="ghost"
aria-label={$t('remove_from_shared_link')} aria-label={$t('remove_from_shared_link')}
onclick={handleSelect} onclick={onAction}
icon={mdiDeleteOutline} icon={mdiDeleteOutline}
/> />
@@ -10,7 +10,6 @@ import type {
PersonResponseDto, PersonResponseDto,
QueueResponseDto, QueueResponseDto,
ReleaseEventV1, ReleaseEventV1,
SharedLinkResponseDto,
StackResponseDto, StackResponseDto,
AdminConfigDto, AdminConfigDto,
TagResponseDto, TagResponseDto,
@@ -67,10 +66,6 @@ export type Events = {
QueueUpdate: [QueueResponseDto]; QueueUpdate: [QueueResponseDto];
SharedLinkCreate: [SharedLinkResponseDto];
SharedLinkUpdate: [SharedLinkResponseDto];
SharedLinkDelete: [SharedLinkResponseDto];
StackCreate: [StackResponseDto]; StackCreate: [StackResponseDto];
/** Unstacked, with assets to handle */ /** Unstacked, with assets to handle */
StackDelete: [{ id: string; assets: AssetResponseDto[] }]; StackDelete: [{ id: string; assets: AssetResponseDto[] }];
+10 -30
View File
@@ -9,17 +9,11 @@
handleUpdateAlbum, handleUpdateAlbum,
handleUpdateUserAlbumRole, handleUpdateUserAlbumRole,
} from '$lib/services/album.service'; } from '$lib/services/album.service';
import { import { AlbumUserRole, AssetOrder, getAlbumInfo, type AlbumResponseDto, type UserResponseDto } from '@immich/sdk';
AlbumUserRole,
AssetOrder,
getAlbumInfo,
getAllSharedLinks,
type AlbumResponseDto,
type SharedLinkResponseDto,
type UserResponseDto,
} from '@immich/sdk';
import { Field, HStack, Modal, ModalBody, Select, Stack, Switch, Text, type SelectOption } from '@immich/ui'; import { Field, HStack, Modal, ModalBody, Select, Stack, Switch, Text, type SelectOption } from '@immich/ui';
import { onMount } from 'svelte'; import { createQuery } from '@tanstack/svelte-query';
import { queryClient } from '$lib/queries/client';
import { getAllSharedLinksQuery } from '$lib/queries/shared-link.query';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
type Props = { type Props = {
@@ -48,30 +42,16 @@
await refreshAlbum(); await refreshAlbum();
}; };
const onSharedLinkCreate = (sharedLink: SharedLinkResponseDto) => {
sharedLinks.push(sharedLink);
};
const onSharedLinkDelete = (sharedLink: SharedLinkResponseDto) => {
sharedLinks = sharedLinks.filter(({ id }) => sharedLink.id !== id);
};
const { AddUsers, CreateSharedLink } = $derived(getAlbumActions($t, album)); const { AddUsers, CreateSharedLink } = $derived(getAlbumActions($t, album));
let sharedLinks: SharedLinkResponseDto[] = $state([]); const query = createQuery(
() => getAllSharedLinksQuery({ albumId: album.id }),
onMount(async () => { () => queryClient,
sharedLinks = await getAllSharedLinks({ albumId: album.id }); );
}); const sharedLinks = $derived(query.data ?? []);
</script> </script>
<OnEvents <OnEvents {onAlbumUserDelete} onAlbumShare={refreshAlbum} onAlbumUpdate={(newAlbum) => (album = newAlbum)} />
{onAlbumUserDelete}
onAlbumShare={refreshAlbum}
{onSharedLinkCreate}
{onSharedLinkDelete}
onAlbumUpdate={(newAlbum) => (album = newAlbum)}
/>
<Modal title={readOnly ? $t('album') : $t('options')} {onClose} size="small"> <Modal title={readOnly ? $t('album') : $t('options')} {onClose} size="small">
<ModalBody> <ModalBody>
@@ -1,6 +1,8 @@
<script lang="ts"> <script lang="ts">
import SharedLinkFormFields from '$lib/components/SharedLinkFormFields.svelte'; import SharedLinkFormFields from '$lib/components/SharedLinkFormFields.svelte';
import { handleCreateSharedLink } from '$lib/services/shared-link.service'; import { executeMutation } from '$lib/queries/client';
import { createSharedLinkMutation } from '$lib/queries/shared-link.query';
import { showSharedLinkQrCode } from '$lib/services/shared-link.service';
import { SharedLinkType } from '@immich/sdk'; import { SharedLinkType } from '@immich/sdk';
import { FormModal } from '@immich/ui'; import { FormModal } from '@immich/ui';
import { mdiLink } from '@mdi/js'; import { mdiLink } from '@mdi/js';
@@ -25,7 +27,7 @@
let type = $derived(albumId ? SharedLinkType.Album : SharedLinkType.Individual); let type = $derived(albumId ? SharedLinkType.Album : SharedLinkType.Individual);
const onSubmit = async () => { const onSubmit = async () => {
const success = await handleCreateSharedLink({ const sharedLinkCreateDto = {
type, type,
albumId, albumId,
assetIds, assetIds,
@@ -36,10 +38,13 @@
allowDownload, allowDownload,
showMetadata, showMetadata,
slug, slug,
};
await executeMutation(createSharedLinkMutation, [{ sharedLinkCreateDto }], {
onSuccess: (sharedLink) => {
onClose();
void showSharedLinkQrCode(sharedLink);
},
}); });
if (success) {
onClose();
}
}; };
</script> </script>
+8
View File
@@ -0,0 +1,8 @@
import { getAlbumInfo } from '@immich/sdk';
import { queryOptions } from '@tanstack/svelte-query';
export const getAlbumInfoQuery = (id: string) =>
queryOptions({
queryKey: ['album', id],
queryFn: ({ signal }) => getAlbumInfo({ id }, { signal }),
});
+72
View File
@@ -0,0 +1,72 @@
import {
createSharedLink,
getAllSharedLinks,
removeSharedLink,
removeSharedLinkAssets,
updateSharedLink,
type SharedLinkResponseDto,
} from '@immich/sdk';
import { queryOptions } from '@tanstack/svelte-query';
import { getAlbumInfoQuery } from '$lib/queries/album.query';
import { asMutation, queryClient } from '$lib/queries/client';
const findOrFail = (sharedLinks: SharedLinkResponseDto[], id: string) => {
const sharedLink = sharedLinks.find((sharedLink) => sharedLink.id === id);
if (!sharedLink) {
throw new Error(`Shared link ${id} not found`);
}
return sharedLink;
};
export const getAllSharedLinksQuery = ({ albumId }: { albumId?: string } = {}) =>
queryOptions({
queryKey: ['shared-links'],
queryFn: ({ signal }) => getAllSharedLinks({}, { signal }),
select: (sharedLinks) => (albumId ? sharedLinks.filter(({ album }) => album?.id === albumId) : sharedLinks),
});
export const getSharedLinkQuery = (id: string) =>
queryOptions({
...getAllSharedLinksQuery(),
select: (sharedLinks) => findOrFail(sharedLinks, id),
});
const invalidateAlbum = (albumId?: string | null) =>
albumId ? queryClient.invalidateQueries({ queryKey: getAlbumInfoQuery(albumId).queryKey }) : undefined;
export const createSharedLinkMutation = () =>
asMutation(createSharedLink, {
onSuccess: async (_, [{ sharedLinkCreateDto }]) => {
await Promise.all([
queryClient.invalidateQueries({ queryKey: getAllSharedLinksQuery().queryKey, exact: true }),
invalidateAlbum(sharedLinkCreateDto.albumId),
]);
},
meta: { error: { toast: 'errors.failed_to_create_shared_link' } },
});
export const updateSharedLinkMutation = () =>
asMutation(updateSharedLink, {
onSuccess: () => queryClient.invalidateQueries({ queryKey: getAllSharedLinksQuery().queryKey, exact: true }),
meta: { success: { toast: 'saved' }, error: { toast: 'errors.failed_to_edit_shared_link' } },
});
export const removeSharedLinkMutation = () =>
asMutation(removeSharedLink, {
onSuccess: async (_, [{ id }]) => {
const albumId = queryClient
.getQueryData(getAllSharedLinksQuery().queryKey)
?.find((sharedLink) => sharedLink.id === id)?.album?.id;
queryClient.setQueryData(getAllSharedLinksQuery().queryKey, (sharedLinks) =>
sharedLinks?.filter((sharedLink) => sharedLink.id !== id),
);
await invalidateAlbum(albumId);
},
meta: { success: { toast: 'deleted_shared_link' }, error: { toast: 'errors.unable_to_delete_shared_link' } },
});
export const removeSharedLinkAssetsMutation = () =>
asMutation(removeSharedLinkAssets, {
onSuccess: () => queryClient.invalidateQueries({ queryKey: getAllSharedLinksQuery().queryKey, exact: true }),
meta: { error: { toast: 'errors.unable_to_remove_assets_from_shared_link' } },
});
+8 -96
View File
@@ -1,24 +1,14 @@
import { import { type SharedLinkResponseDto } from '@immich/sdk';
createSharedLink, import { modalManager, type ActionItem } from '@immich/ui';
getSharedLinkById,
removeSharedLink,
removeSharedLinkAssets,
updateSharedLink,
type SharedLinkCreateDto,
type SharedLinkEditDto,
type SharedLinkResponseDto,
} from '@immich/sdk';
import { modalManager, toastManager, type ActionItem } from '@immich/ui';
import { mdiContentCopy, mdiLink, mdiPencilOutline, mdiQrcode, mdiTrashCanOutline } from '@mdi/js'; import { mdiContentCopy, mdiLink, mdiPencilOutline, mdiQrcode, mdiTrashCanOutline } from '@mdi/js';
import type { MessageFormatter } from 'svelte-i18n'; import type { MessageFormatter } from 'svelte-i18n';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { eventManager } from '$lib/managers/event-manager.svelte';
import QrCodeModal from '$lib/modals/QrCodeModal.svelte'; import QrCodeModal from '$lib/modals/QrCodeModal.svelte';
import { readPreloadedQuery } from '$lib/queries/client'; import { executeMutation, readPreloadedQuery } from '$lib/queries/client';
import { getUserConfigQuery } from '$lib/queries/config-user.query'; import { getUserConfigQuery } from '$lib/queries/config-user.query';
import { removeSharedLinkMutation } from '$lib/queries/shared-link.query';
import { Route } from '$lib/route'; import { Route } from '$lib/route';
import { copyToClipboard } from '$lib/utils'; import { copyToClipboard } from '$lib/utils';
import { handleError } from '$lib/utils/handle-error';
import { getFormatter } from '$lib/utils/i18n'; import { getFormatter } from '$lib/utils/i18n';
export const getSharedLinksActions = ($t: MessageFormatter) => { export const getSharedLinksActions = ($t: MessageFormatter) => {
@@ -54,7 +44,7 @@ export const getSharedLinkActions = ($t: MessageFormatter, sharedLink: SharedLin
const ViewQrCode: ActionItem = { const ViewQrCode: ActionItem = {
title: $t('view_qr_code'), title: $t('view_qr_code'),
icon: mdiQrcode, icon: mdiQrcode,
onAction: () => handleShowSharedLinkQrCode(sharedLink), onAction: () => showSharedLinkQrCode(sharedLink),
}; };
return { Edit, Delete, Copy, ViewQrCode }; return { Edit, Delete, Copy, ViewQrCode };
@@ -65,42 +55,9 @@ export const asUrl = (sharedLink: SharedLinkResponseDto) => {
return new URL(path, readPreloadedQuery(getUserConfigQuery()).server.externalDomain || location.origin).href; return new URL(path, readPreloadedQuery(getUserConfigQuery()).server.externalDomain || location.origin).href;
}; };
export const handleCreateSharedLink = async (dto: SharedLinkCreateDto) => { export const showSharedLinkQrCode = async (sharedLink: SharedLinkResponseDto) => {
const $t = await getFormatter(); const $t = await getFormatter();
await modalManager.show(QrCodeModal, { title: $t('view_link'), value: asUrl(sharedLink) });
try {
let sharedLink = await createSharedLink({ sharedLinkCreateDto: dto });
if (dto.albumId) {
// fetch album details, for event
sharedLink = await getSharedLinkById({ id: sharedLink.id });
}
eventManager.emit('SharedLinkCreate', sharedLink);
// prevent nested modal
void handleShowSharedLinkQrCode(sharedLink);
return true;
} catch (error) {
handleError(error, $t('errors.failed_to_create_shared_link'));
return false;
}
};
export const handleUpdateSharedLink = async (sharedLink: SharedLinkResponseDto, dto: SharedLinkEditDto) => {
const $t = await getFormatter();
try {
const response = await updateSharedLink({ id: sharedLink.id, sharedLinkEditDto: dto });
eventManager.emit('SharedLinkUpdate', { album: sharedLink.album, ...response });
toastManager.primary($t('saved'));
return true;
} catch (error) {
handleError(error, $t('errors.failed_to_edit_shared_link'));
return false;
}
}; };
const handleDeleteSharedLink = async (sharedLink: SharedLinkResponseDto) => { const handleDeleteSharedLink = async (sharedLink: SharedLinkResponseDto) => {
@@ -114,50 +71,5 @@ const handleDeleteSharedLink = async (sharedLink: SharedLinkResponseDto) => {
return; return;
} }
try { await executeMutation(removeSharedLinkMutation, [{ id: sharedLink.id }]);
await removeSharedLink({ id: sharedLink.id });
eventManager.emit('SharedLinkDelete', sharedLink);
toastManager.primary($t('deleted_shared_link'));
} catch (error) {
handleError(error, $t('errors.unable_to_delete_shared_link'));
}
};
export const handleRemoveSharedLinkAssets = async (sharedLink: SharedLinkResponseDto, assetIds: string[]) => {
const $t = await getFormatter();
const success = await modalManager.showDialog({
title: $t('remove_assets_title'),
prompt: $t('remove_assets_shared_link_confirmation', { values: { count: assetIds.length } }),
confirmText: $t('remove'),
});
if (!success) {
return false;
}
try {
const results = await removeSharedLinkAssets({
id: sharedLink.id,
assetIdsDto: { assetIds },
});
for (const result of results) {
if (!result.success) {
continue;
}
sharedLink.assets = sharedLink.assets.filter((asset) => asset.id !== result.assetId);
}
const count = results.filter((item) => item.success).length;
toastManager.primary($t('assets_removed_count', { values: { count } }));
return true;
} catch (error) {
handleError(error, $t('errors.unable_to_remove_assets_from_shared_link'));
return false;
}
};
const handleShowSharedLinkQrCode = async (sharedLink: SharedLinkResponseDto) => {
const $t = await getFormatter();
await modalManager.show(QrCodeModal, { title: $t('view_link'), value: asUrl(sharedLink) });
}; };
@@ -1,6 +1,5 @@
<script lang="ts"> <script lang="ts">
import { goto, invalidate, onNavigate } from '$app/navigation'; import { goto, onNavigate } from '$app/navigation';
import { navigating } from '$app/state';
import { scrollMemoryClearer } from '$lib/actions/scroll-memory'; import { scrollMemoryClearer } from '$lib/actions/scroll-memory';
import AlbumMap from '$lib/components/album-page/AlbumMap.svelte'; import AlbumMap from '$lib/components/album-page/AlbumMap.svelte';
import AlbumSummary from '$lib/components/album-page/AlbumSummary.svelte'; import AlbumSummary from '$lib/components/album-page/AlbumSummary.svelte';
@@ -29,6 +28,8 @@
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte'; import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
import { authManager } from '$lib/managers/auth-manager.svelte'; import { authManager } from '$lib/managers/auth-manager.svelte';
import { eventManager } from '$lib/managers/event-manager.svelte'; import { eventManager } from '$lib/managers/event-manager.svelte';
import { getAlbumInfoQuery } from '$lib/queries/album.query';
import { createPreloadedQuery, queryClient } from '$lib/queries/client';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte'; import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import { Route } from '$lib/route'; import { Route } from '$lib/route';
@@ -62,22 +63,17 @@
import { onDestroy } from 'svelte'; import { onDestroy } from 'svelte';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import { fly } from 'svelte/transition'; import { fly } from 'svelte/transition';
import type { PageData } from './$types'; import type { PageProps } from './$types';
import AlbumDescription from './AlbumDescription.svelte'; import AlbumDescription from './AlbumDescription.svelte';
import AlbumTitle from './AlbumTitle.svelte'; import AlbumTitle from './AlbumTitle.svelte';
import ActionMenuItem from '$lib/components/ActionMenuItem.svelte'; import ActionMenuItem from '$lib/components/ActionMenuItem.svelte';
import { delay } from '$lib/utils/asset-utils'; import { delay } from '$lib/utils/asset-utils';
import { createPreloadedQuery } from '$lib/queries/client';
import { getUserConfigQuery } from '$lib/queries/config-user.query'; import { getUserConfigQuery } from '$lib/queries/config-user.query';
const query = createPreloadedQuery(getUserConfigQuery); const query = createPreloadedQuery(getUserConfigQuery);
const config = $derived(query.data); const config = $derived(query.data);
interface Props { const { params }: PageProps = $props();
data: PageData;
}
let { data = $bindable() }: Props = $props();
let { slideshowState, slideshowNavigation } = slideshowStore; let { slideshowState, slideshowNavigation } = slideshowStore;
let oldAt: AssetGridRouteSearchParams | null | undefined = $state(); let oldAt: AssetGridRouteSearchParams | null | undefined = $state();
let viewMode: AlbumPageViewMode = $state(AlbumPageViewMode.VIEW); let viewMode: AlbumPageViewMode = $state(AlbumPageViewMode.VIEW);
@@ -131,12 +127,10 @@
if (updated.id !== album.id) { if (updated.id !== album.id) {
return; return;
} }
album = updated; queryClient.setQueryData(getAlbumInfoQuery(album.id).queryKey, updated);
} }
// invalidating during navigation causes an infinite page load await queryClient.invalidateQueries({ queryKey: getAlbumInfoQuery(album.id).queryKey });
await navigating.complete;
await invalidate('album:data');
}; };
const setModeToView = async () => { const setModeToView = async () => {
@@ -218,7 +212,8 @@
} }
}); });
let album = $derived(data.album); const albumQuery = createPreloadedQuery(() => getAlbumInfoQuery(params.albumId));
let album = $derived(albumQuery.data);
const albumId = $derived(album.id); const albumId = $derived(album.id);
const albumHasViewers = $derived(album.albumUsers.some(({ role }) => role === AlbumUserRole.Viewer)); const albumHasViewers = $derived(album.albumUsers.some(({ role }) => role === AlbumUserRole.Viewer));
@@ -329,8 +324,6 @@
</script> </script>
<OnEvents <OnEvents
onSharedLinkCreate={(link) => refreshAlbum(link.album)}
onSharedLinkDelete={() => refreshAlbum()}
{onAlbumDelete} {onAlbumDelete}
{onAlbumAddAssets} {onAlbumAddAssets}
{onAlbumRemoveAssets} {onAlbumRemoveAssets}
@@ -1,16 +1,14 @@
import { getAlbumInfo } from '@immich/sdk'; import { getAlbumInfoQuery } from '$lib/queries/album.query';
import { queryClient } from '$lib/queries/client';
import { authenticate } from '$lib/utils/auth'; import { authenticate } from '$lib/utils/auth';
import type { PageLoad } from './$types'; import type { PageLoad } from './$types';
export const load = (async ({ params, url, depends }) => { export const load = (async ({ params, url }) => {
await authenticate(url); await authenticate(url);
depends('album:data'); const album = await queryClient.query(getAlbumInfoQuery(params.albumId));
const album = await getAlbumInfo({ id: params.albumId });
return { return {
album,
meta: { meta: {
title: album.albumName, title: album.albumName,
}, },
@@ -2,14 +2,15 @@
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { page } from '$app/state'; import { page } from '$app/state';
import UserPageLayout from '$lib/components/layouts/UserPageLayout.svelte'; import UserPageLayout from '$lib/components/layouts/UserPageLayout.svelte';
import OnEvents from '$lib/components/OnEvents.svelte';
import SharedLinkCard from './SharedLinkCard.svelte'; import SharedLinkCard from './SharedLinkCard.svelte';
import { type SharedLinkTab } from '$lib/constants'; import { type SharedLinkTab } from '$lib/constants';
import GroupTab from '$lib/elements/GroupTab.svelte'; import GroupTab from '$lib/elements/GroupTab.svelte';
import { Route } from '$lib/route'; import { Route } from '$lib/route';
import { getAllSharedLinks, SharedLinkType, type SharedLinkResponseDto } from '@immich/sdk'; import { createPreloadedQuery } from '$lib/queries/client';
import { getAllSharedLinksQuery } from '$lib/queries/shared-link.query';
import { SharedLinkType } from '@immich/sdk';
import { Container } from '@immich/ui'; import { Container } from '@immich/ui';
import { onMount, type Snippet } from 'svelte'; import { type Snippet } from 'svelte';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import type { LayoutData } from './$types'; import type { LayoutData } from './$types';
@@ -20,15 +21,8 @@
const { children, data }: Props = $props(); const { children, data }: Props = $props();
let sharedLinks: SharedLinkResponseDto[] = $state([]); const query = createPreloadedQuery(getAllSharedLinksQuery);
const sharedLinks = $derived(query.data);
const refresh = async () => {
sharedLinks = await getAllSharedLinks({});
};
onMount(async () => {
await refresh();
});
const filterMap: Record<SharedLinkTab, string> = { const filterMap: Record<SharedLinkTab, string> = {
all: $t('all'), all: $t('all'),
@@ -54,21 +48,8 @@
(type === SharedLinkType.Individual && selectedTab === 'individual'), (type === SharedLinkType.Individual && selectedTab === 'individual'),
), ),
); );
const onSharedLinkUpdate = (sharedLink: SharedLinkResponseDto) => {
const index = sharedLinks.findIndex((link) => link.id === sharedLink.id);
if (index !== -1) {
sharedLinks[index] = sharedLink;
}
};
const onSharedLinkDelete = (sharedLink: SharedLinkResponseDto) => {
sharedLinks = sharedLinks.filter(({ id }) => id !== sharedLink.id);
};
</script> </script>
<OnEvents {onSharedLinkUpdate} {onSharedLinkDelete} />
<UserPageLayout title={data.meta.title}> <UserPageLayout title={data.meta.title}>
{#snippet buttons()} {#snippet buttons()}
<div class="hidden h-10 xl:block"> <div class="hidden h-10 xl:block">
@@ -1,9 +1,12 @@
import { queryClient } from '$lib/queries/client';
import { getAllSharedLinksQuery } from '$lib/queries/shared-link.query';
import { authenticate } from '$lib/utils/auth'; import { authenticate } from '$lib/utils/auth';
import { getFormatter } from '$lib/utils/i18n'; import { getFormatter } from '$lib/utils/i18n';
import type { LayoutLoad } from './$types'; import type { LayoutLoad } from './$types';
export const load = (async ({ url }) => { export const load = (async ({ url }) => {
await authenticate(url); await authenticate(url);
await queryClient.query(getAllSharedLinksQuery());
const $t = await getFormatter(); const $t = await getFormatter();
return { return {
@@ -1,6 +1,7 @@
import { getAllSharedLinks } from '@immich/sdk';
import { redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import { UUID_REGEX } from '$lib/constants'; import { UUID_REGEX } from '$lib/constants';
import { queryClient } from '$lib/queries/client';
import { getSharedLinkQuery } from '$lib/queries/shared-link.query';
import { Route } from '$lib/route'; import { Route } from '$lib/route';
import { authenticate } from '$lib/utils/auth'; import { authenticate } from '$lib/utils/auth';
import { getFormatter } from '$lib/utils/i18n'; import { getFormatter } from '$lib/utils/i18n';
@@ -13,15 +14,11 @@ export const load = (async ({ params, url }) => {
redirect(307, Route.sharedLinks()); redirect(307, Route.sharedLinks());
} }
const [sharedLink] = await getAllSharedLinks({ id: params.id }); await queryClient.query(getSharedLinkQuery(params.id)).catch(() => redirect(307, Route.sharedLinks()));
if (!sharedLink) {
redirect(307, Route.sharedLinks());
}
const $t = await getFormatter(); const $t = await getFormatter();
return { return {
sharedLink,
meta: { meta: {
title: $t('shared_links'), title: $t('shared_links'),
}, },
@@ -2,20 +2,18 @@
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import SharedLinkFormFields from '$lib/components/SharedLinkFormFields.svelte'; import SharedLinkFormFields from '$lib/components/SharedLinkFormFields.svelte';
import { Route } from '$lib/route'; import { Route } from '$lib/route';
import { handleUpdateSharedLink } from '$lib/services/shared-link.service'; import { createPreloadedQuery, executeMutation } from '$lib/queries/client';
import { getSharedLinkQuery, updateSharedLinkMutation } from '$lib/queries/shared-link.query';
import { SharedLinkType } from '@immich/sdk'; import { SharedLinkType } from '@immich/sdk';
import { FormModal } from '@immich/ui'; import { FormModal } from '@immich/ui';
import { mdiLink } from '@mdi/js'; import { mdiLink } from '@mdi/js';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import type { PageData } from './$types'; import type { PageProps } from './$types';
type Props = { const { params }: PageProps = $props();
data: PageData;
};
let { data }: Props = $props(); const query = createPreloadedQuery(() => getSharedLinkQuery(params.id));
const sharedLink = $derived(query.data);
const sharedLink = $state(data.sharedLink);
let description = $state(sharedLink.description ?? ''); let description = $state(sharedLink.description ?? '');
let allowDownload = $state(sharedLink.allowDownload); let allowDownload = $state(sharedLink.allowDownload);
@@ -26,12 +24,9 @@
let shareType = sharedLink.album ? SharedLinkType.Album : SharedLinkType.Individual; let shareType = sharedLink.album ? SharedLinkType.Album : SharedLinkType.Individual;
let expiresAt = $state(sharedLink.expiresAt); let expiresAt = $state(sharedLink.expiresAt);
const onClose = async () => { const onClose = () => void goto(Route.sharedLinks());
await goto(Route.sharedLinks());
};
const onSubmit = async () => { const onSubmit = async () => {
const success = await handleUpdateSharedLink(sharedLink, { const sharedLinkEditDto = {
description, description,
password: password ?? null, password: password ?? null,
expiresAt, expiresAt,
@@ -39,10 +34,8 @@
allowDownload, allowDownload,
showMetadata, showMetadata,
slug: slug.trim() ?? null, slug: slug.trim() ?? null,
}); };
if (success) { await executeMutation(updateSharedLinkMutation, [{ id: sharedLink.id, sharedLinkEditDto }], { onSuccess: onClose });
await onClose();
}
}; };
</script> </script>