refactor: shared link queries (#32666)
(cherry picked from commit 0a73481a8fdb3cfbab29f23da48e26133dc4c064)
This commit is contained in:
@@ -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[] }];
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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 }),
|
||||||
|
});
|
||||||
@@ -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' } },
|
||||||
|
});
|
||||||
@@ -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) });
|
|
||||||
};
|
};
|
||||||
|
|||||||
+9
-16
@@ -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>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user