mirror of
https://github.com/immich-app/immich
synced 2025-06-07 17:40:57 +00:00
refactor: share modals (#18183)
This commit is contained in:
parent
47b1938f17
commit
6a69dafd31
@ -1,43 +1,47 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount, type Snippet } from 'svelte';
|
import { goto } from '$app/navigation';
|
||||||
import { groupBy } from 'lodash-es';
|
import AlbumCardGroup from '$lib/components/album-page/album-card-group.svelte';
|
||||||
import { addUsersToAlbum, deleteAlbum, type AlbumUserAddDto, type AlbumResponseDto, isHttpError } from '@immich/sdk';
|
import AlbumsTable from '$lib/components/album-page/albums-table.svelte';
|
||||||
import { mdiDeleteOutline, mdiShareVariantOutline, mdiFolderDownloadOutline, mdiRenameOutline } from '@mdi/js';
|
|
||||||
import EditAlbumForm from '$lib/components/forms/edit-album-form.svelte';
|
import EditAlbumForm from '$lib/components/forms/edit-album-form.svelte';
|
||||||
import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.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 {
|
import {
|
||||||
NotificationType,
|
NotificationType,
|
||||||
notificationController,
|
notificationController,
|
||||||
} from '$lib/components/shared-components/notification/notification';
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
import { AppRoute } from '$lib/constants';
|
||||||
import RightClickContextMenu from '$lib/components/shared-components/context-menu/right-click-context-menu.svelte';
|
import { modalManager } from '$lib/managers/modal-manager.svelte';
|
||||||
import AlbumsTable from '$lib/components/album-page/albums-table.svelte';
|
import AlbumShareModal from '$lib/modals/AlbumShareModal.svelte';
|
||||||
import AlbumCardGroup from '$lib/components/album-page/album-card-group.svelte';
|
import QrCodeModal from '$lib/modals/QrCodeModal.svelte';
|
||||||
import UserSelectionModal from '$lib/components/album-page/user-selection-modal.svelte';
|
import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.svelte';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
|
||||||
import { downloadAlbum } from '$lib/utils/asset-utils';
|
|
||||||
import { normalizeSearchString } from '$lib/utils/string-utils';
|
|
||||||
import {
|
|
||||||
getSelectedAlbumGroupOption,
|
|
||||||
type AlbumGroup,
|
|
||||||
confirmAlbumDelete,
|
|
||||||
sortAlbums,
|
|
||||||
stringToSortOrder,
|
|
||||||
} from '$lib/utils/album-utils';
|
|
||||||
import type { ContextMenuPosition } from '$lib/utils/context-menu';
|
|
||||||
import { user } from '$lib/stores/user.store';
|
|
||||||
import {
|
import {
|
||||||
|
AlbumFilter,
|
||||||
AlbumGroupBy,
|
AlbumGroupBy,
|
||||||
AlbumSortBy,
|
AlbumSortBy,
|
||||||
AlbumFilter,
|
|
||||||
AlbumViewMode,
|
AlbumViewMode,
|
||||||
SortOrder,
|
SortOrder,
|
||||||
locale,
|
locale,
|
||||||
type AlbumViewSettings,
|
type AlbumViewSettings,
|
||||||
} from '$lib/stores/preferences.store';
|
} from '$lib/stores/preferences.store';
|
||||||
|
import { serverConfig } from '$lib/stores/server-config.store';
|
||||||
|
import { user } from '$lib/stores/user.store';
|
||||||
import { userInteraction } from '$lib/stores/user.svelte';
|
import { userInteraction } from '$lib/stores/user.svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { makeSharedLinkUrl } from '$lib/utils';
|
||||||
import { AppRoute } from '$lib/constants';
|
import {
|
||||||
|
confirmAlbumDelete,
|
||||||
|
getSelectedAlbumGroupOption,
|
||||||
|
sortAlbums,
|
||||||
|
stringToSortOrder,
|
||||||
|
type AlbumGroup,
|
||||||
|
} from '$lib/utils/album-utils';
|
||||||
|
import { downloadAlbum } from '$lib/utils/asset-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, deleteAlbum, isHttpError, type AlbumResponseDto, type AlbumUserAddDto } from '@immich/sdk';
|
||||||
|
import { mdiDeleteOutline, mdiFolderDownloadOutline, mdiRenameOutline, mdiShareVariantOutline } from '@mdi/js';
|
||||||
|
import { groupBy } from 'lodash-es';
|
||||||
|
import { onMount, type Snippet } from 'svelte';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import { run } from 'svelte/legacy';
|
import { run } from 'svelte/legacy';
|
||||||
|
|
||||||
@ -140,8 +144,6 @@
|
|||||||
|
|
||||||
let albumGroupOption: string = $state(AlbumGroupBy.None);
|
let albumGroupOption: string = $state(AlbumGroupBy.None);
|
||||||
|
|
||||||
let showShareByURLModal = $state(false);
|
|
||||||
|
|
||||||
let albumToEdit: AlbumResponseDto | null = $state(null);
|
let albumToEdit: AlbumResponseDto | null = $state(null);
|
||||||
let albumToShare: AlbumResponseDto | null = $state(null);
|
let albumToShare: AlbumResponseDto | null = $state(null);
|
||||||
let albumToDelete: AlbumResponseDto | null = null;
|
let albumToDelete: AlbumResponseDto | null = null;
|
||||||
@ -346,18 +348,32 @@
|
|||||||
updateAlbumInfo(album);
|
updateAlbumInfo(album);
|
||||||
};
|
};
|
||||||
|
|
||||||
const openShareModal = () => {
|
const openShareModal = async () => {
|
||||||
if (!contextMenuTargetAlbum) {
|
if (!contextMenuTargetAlbum) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
albumToShare = contextMenuTargetAlbum;
|
albumToShare = contextMenuTargetAlbum;
|
||||||
closeAlbumContextMenu();
|
closeAlbumContextMenu();
|
||||||
};
|
const result = await modalManager.show(AlbumShareModal, { album: albumToShare });
|
||||||
|
|
||||||
const closeShareModal = () => {
|
switch (result?.action) {
|
||||||
albumToShare = null;
|
case 'sharedUsers': {
|
||||||
showShareByURLModal = false;
|
await handleAddUsers(result.data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'sharedLink': {
|
||||||
|
const sharedLink = await modalManager.show(SharedLinkCreateModal, { albumId: albumToShare.id });
|
||||||
|
|
||||||
|
if (sharedLink) {
|
||||||
|
const url = makeSharedLinkUrl($serverConfig.externalDomain, sharedLink.key);
|
||||||
|
handleSharedLinkCreated(albumToShare);
|
||||||
|
await modalManager.show(QrCodeModal, { title: $t('view_link'), value: url });
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -419,22 +435,4 @@
|
|||||||
onClose={() => (albumToEdit = null)}
|
onClose={() => (albumToEdit = null)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Share Modal -->
|
|
||||||
{#if albumToShare}
|
|
||||||
{#if showShareByURLModal}
|
|
||||||
<CreateSharedLinkModal
|
|
||||||
albumId={albumToShare.id}
|
|
||||||
onClose={() => closeShareModal()}
|
|
||||||
onCreated={() => albumToShare && handleSharedLinkCreated(albumToShare)}
|
|
||||||
/>
|
|
||||||
{:else}
|
|
||||||
<UserSelectionModal
|
|
||||||
album={albumToShare}
|
|
||||||
onSelect={handleAddUsers}
|
|
||||||
onShare={() => (showShareByURLModal = true)}
|
|
||||||
onClose={() => closeShareModal()}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
||||||
import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte';
|
import { modalManager } from '$lib/managers/modal-manager.svelte';
|
||||||
import Portal from '$lib/components/shared-components/portal/portal.svelte';
|
import QrCodeModal from '$lib/modals/QrCodeModal.svelte';
|
||||||
|
import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.svelte';
|
||||||
|
import { serverConfig } from '$lib/stores/server-config.store';
|
||||||
|
import { makeSharedLinkUrl } from '$lib/utils';
|
||||||
import type { AssetResponseDto } from '@immich/sdk';
|
import type { AssetResponseDto } from '@immich/sdk';
|
||||||
import { mdiShareVariantOutline } from '@mdi/js';
|
import { mdiShareVariantOutline } from '@mdi/js';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
@ -12,13 +15,14 @@
|
|||||||
|
|
||||||
let { asset }: Props = $props();
|
let { asset }: Props = $props();
|
||||||
|
|
||||||
let showModal = $state(false);
|
const handleClick = async () => {
|
||||||
|
const sharedLink = await modalManager.show(SharedLinkCreateModal, { assetIds: [asset.id] });
|
||||||
|
|
||||||
|
if (sharedLink) {
|
||||||
|
const url = makeSharedLinkUrl($serverConfig.externalDomain, sharedLink.key);
|
||||||
|
await modalManager.show(QrCodeModal, { title: $t('view_link'), value: url });
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<CircleIconButton color="opaque" icon={mdiShareVariantOutline} onclick={() => (showModal = true)} title={$t('share')} />
|
<CircleIconButton color="opaque" icon={mdiShareVariantOutline} onclick={handleClick} title={$t('share')} />
|
||||||
|
|
||||||
{#if showModal}
|
|
||||||
<Portal target="body">
|
|
||||||
<CreateSharedLinkModal assetIds={[asset.id]} onClose={() => (showModal = false)} />
|
|
||||||
</Portal>
|
|
||||||
{/if}
|
|
||||||
|
@ -1,16 +1,26 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
||||||
import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte';
|
import { getAssetControlContext } from '$lib/components/photos-page/asset-select-control-bar.svelte';
|
||||||
|
import { modalManager } from '$lib/managers/modal-manager.svelte';
|
||||||
|
import QrCodeModal from '$lib/modals/QrCodeModal.svelte';
|
||||||
|
import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.svelte';
|
||||||
|
import { serverConfig } from '$lib/stores/server-config.store';
|
||||||
|
import { makeSharedLinkUrl } from '$lib/utils';
|
||||||
import { mdiShareVariantOutline } from '@mdi/js';
|
import { mdiShareVariantOutline } from '@mdi/js';
|
||||||
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
|
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
let showModal = $state(false);
|
|
||||||
const { getAssets } = getAssetControlContext();
|
const { getAssets } = getAssetControlContext();
|
||||||
|
|
||||||
|
const handleClick = async () => {
|
||||||
|
const sharedLink = await modalManager.show(SharedLinkCreateModal, {
|
||||||
|
assetIds: [...getAssets()].map(({ id }) => id),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sharedLink) {
|
||||||
|
const url = makeSharedLinkUrl($serverConfig.externalDomain, sharedLink.key);
|
||||||
|
await modalManager.show(QrCodeModal, { title: $t('view_link'), value: url });
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<CircleIconButton title={$t('share')} icon={mdiShareVariantOutline} onclick={() => (showModal = true)} />
|
<CircleIconButton title={$t('share')} icon={mdiShareVariantOutline} onclick={handleClick} />
|
||||||
|
|
||||||
{#if showModal}
|
|
||||||
<CreateSharedLinkModal assetIds={[...getAssets()].map(({ id }) => id)} onClose={() => (showModal = false)} />
|
|
||||||
{/if}
|
|
||||||
|
@ -1,251 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
|
|
||||||
import QrCodeModal from '$lib/components/shared-components/qr-code-modal.svelte';
|
|
||||||
import SettingSelect from '$lib/components/shared-components/settings/setting-select.svelte';
|
|
||||||
import { SettingInputFieldType } from '$lib/constants';
|
|
||||||
import { locale } from '$lib/stores/preferences.store';
|
|
||||||
import { serverConfig } from '$lib/stores/server-config.store';
|
|
||||||
import { makeSharedLinkUrl } from '$lib/utils';
|
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
|
||||||
import { SharedLinkType, createSharedLink, updateSharedLink, type SharedLinkResponseDto } from '@immich/sdk';
|
|
||||||
import { Button } from '@immich/ui';
|
|
||||||
import { mdiLink } from '@mdi/js';
|
|
||||||
import { DateTime, Duration } from 'luxon';
|
|
||||||
import { t } from 'svelte-i18n';
|
|
||||||
import { NotificationType, notificationController } from '../notification/notification';
|
|
||||||
import SettingInputField from '../settings/setting-input-field.svelte';
|
|
||||||
import SettingSwitch from '../settings/setting-switch.svelte';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
onClose: () => void;
|
|
||||||
albumId?: string | undefined;
|
|
||||||
assetIds?: string[];
|
|
||||||
editingLink?: SharedLinkResponseDto | undefined;
|
|
||||||
onCreated?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
let {
|
|
||||||
onClose,
|
|
||||||
albumId = $bindable(undefined),
|
|
||||||
assetIds = $bindable([]),
|
|
||||||
editingLink = undefined,
|
|
||||||
onCreated = () => {},
|
|
||||||
}: Props = $props();
|
|
||||||
|
|
||||||
let sharedLink: string | null = $state(null);
|
|
||||||
let description = $state('');
|
|
||||||
let allowDownload = $state(true);
|
|
||||||
let allowUpload = $state(false);
|
|
||||||
let showMetadata = $state(true);
|
|
||||||
let expirationOption: number = $state(0);
|
|
||||||
let password = $state('');
|
|
||||||
let shouldChangeExpirationTime = $state(false);
|
|
||||||
let enablePassword = $state(false);
|
|
||||||
|
|
||||||
const expirationOptions: [number, Intl.RelativeTimeFormatUnit][] = [
|
|
||||||
[30, 'minutes'],
|
|
||||||
[1, 'hour'],
|
|
||||||
[6, 'hours'],
|
|
||||||
[1, 'day'],
|
|
||||||
[7, 'days'],
|
|
||||||
[30, 'days'],
|
|
||||||
[3, 'months'],
|
|
||||||
[1, 'year'],
|
|
||||||
];
|
|
||||||
|
|
||||||
let relativeTime = $derived(new Intl.RelativeTimeFormat($locale));
|
|
||||||
let expiredDateOptions = $derived([
|
|
||||||
{ text: $t('never'), value: 0 },
|
|
||||||
...expirationOptions.map(([value, unit]) => ({
|
|
||||||
text: relativeTime.format(value, unit),
|
|
||||||
value: Duration.fromObject({ [unit]: value }).toMillis(),
|
|
||||||
})),
|
|
||||||
]);
|
|
||||||
|
|
||||||
let shareType = $derived(albumId ? SharedLinkType.Album : SharedLinkType.Individual);
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
if (!showMetadata) {
|
|
||||||
allowDownload = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (editingLink) {
|
|
||||||
if (editingLink.description) {
|
|
||||||
description = editingLink.description;
|
|
||||||
}
|
|
||||||
if (editingLink.password) {
|
|
||||||
password = editingLink.password;
|
|
||||||
}
|
|
||||||
allowUpload = editingLink.allowUpload;
|
|
||||||
allowDownload = editingLink.allowDownload;
|
|
||||||
showMetadata = editingLink.showMetadata;
|
|
||||||
|
|
||||||
albumId = editingLink.album?.id;
|
|
||||||
assetIds = editingLink.assets.map(({ id }) => id);
|
|
||||||
|
|
||||||
enablePassword = !!editingLink.password;
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleCreateSharedLink = async () => {
|
|
||||||
const expirationDate = expirationOption > 0 ? DateTime.now().plus(expirationOption).toISO() : undefined;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const data = await createSharedLink({
|
|
||||||
sharedLinkCreateDto: {
|
|
||||||
type: shareType,
|
|
||||||
albumId,
|
|
||||||
assetIds,
|
|
||||||
expiresAt: expirationDate,
|
|
||||||
allowUpload,
|
|
||||||
description,
|
|
||||||
password,
|
|
||||||
allowDownload,
|
|
||||||
showMetadata,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
sharedLink = makeSharedLinkUrl($serverConfig.externalDomain, data.key);
|
|
||||||
onCreated();
|
|
||||||
} catch (error) {
|
|
||||||
handleError(error, $t('errors.failed_to_create_shared_link'));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEditLink = async () => {
|
|
||||||
if (!editingLink) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const expirationDate = expirationOption > 0 ? DateTime.now().plus(expirationOption).toISO() : null;
|
|
||||||
|
|
||||||
await updateSharedLink({
|
|
||||||
id: editingLink.id,
|
|
||||||
sharedLinkEditDto: {
|
|
||||||
description,
|
|
||||||
password: enablePassword ? password : '',
|
|
||||||
expiresAt: shouldChangeExpirationTime ? expirationDate : undefined,
|
|
||||||
allowUpload,
|
|
||||||
allowDownload,
|
|
||||||
showMetadata,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
notificationController.show({
|
|
||||||
type: NotificationType.Info,
|
|
||||||
message: $t('edited'),
|
|
||||||
});
|
|
||||||
|
|
||||||
onClose();
|
|
||||||
} catch (error) {
|
|
||||||
handleError(error, $t('errors.failed_to_edit_shared_link'));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getTitle = () => {
|
|
||||||
if (sharedLink) {
|
|
||||||
return $t('view_link');
|
|
||||||
}
|
|
||||||
if (editingLink) {
|
|
||||||
return $t('edit_link');
|
|
||||||
}
|
|
||||||
return $t('create_link_to_share');
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if !sharedLink || editingLink}
|
|
||||||
<FullScreenModal title={getTitle()} icon={mdiLink} {onClose}>
|
|
||||||
<section>
|
|
||||||
{#if shareType === SharedLinkType.Album}
|
|
||||||
{#if !editingLink}
|
|
||||||
<div>{$t('album_with_link_access')}</div>
|
|
||||||
{:else}
|
|
||||||
<div class="text-sm">
|
|
||||||
{$t('public_album')} |
|
|
||||||
<span class="text-immich-primary dark:text-immich-dark-primary">{editingLink.album?.albumName}</span>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if shareType === SharedLinkType.Individual}
|
|
||||||
{#if !editingLink}
|
|
||||||
<div>{$t('create_link_to_share_description')}</div>
|
|
||||||
{:else}
|
|
||||||
<div class="text-sm">
|
|
||||||
{$t('individual_share')} |
|
|
||||||
<span class="text-immich-primary dark:text-immich-dark-primary">{editingLink.description || ''}</span>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="mb-2 mt-4">
|
|
||||||
<p class="text-xs">{$t('link_options').toUpperCase()}</p>
|
|
||||||
</div>
|
|
||||||
<div class="rounded-lg bg-gray-100 p-4 dark:bg-black/40 overflow-y-auto">
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<div class="mb-2">
|
|
||||||
<SettingInputField
|
|
||||||
inputType={SettingInputFieldType.TEXT}
|
|
||||||
label={$t('description')}
|
|
||||||
bind:value={description}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-2">
|
|
||||||
<SettingInputField
|
|
||||||
inputType={SettingInputFieldType.TEXT}
|
|
||||||
label={$t('password')}
|
|
||||||
bind:value={password}
|
|
||||||
disabled={!enablePassword}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="my-3">
|
|
||||||
<SettingSwitch bind:checked={enablePassword} title={$t('require_password')} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="my-3">
|
|
||||||
<SettingSwitch bind:checked={showMetadata} title={$t('show_metadata')} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="my-3">
|
|
||||||
<SettingSwitch
|
|
||||||
bind:checked={allowDownload}
|
|
||||||
title={$t('allow_public_user_to_download')}
|
|
||||||
disabled={!showMetadata}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="my-3">
|
|
||||||
<SettingSwitch bind:checked={allowUpload} title={$t('allow_public_user_to_upload')} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if editingLink}
|
|
||||||
<div class="my-3">
|
|
||||||
<SettingSwitch bind:checked={shouldChangeExpirationTime} title={$t('change_expiration_time')} />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<div class="mt-3">
|
|
||||||
<SettingSelect
|
|
||||||
bind:value={expirationOption}
|
|
||||||
options={expiredDateOptions}
|
|
||||||
label={$t('expire_after')}
|
|
||||||
disabled={editingLink && !shouldChangeExpirationTime}
|
|
||||||
number={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{#snippet stickyBottom()}
|
|
||||||
{#if editingLink}
|
|
||||||
<Button fullWidth onclick={handleEditLink}>{$t('confirm')}</Button>
|
|
||||||
{:else}
|
|
||||||
<Button fullWidth onclick={handleCreateSharedLink}>{$t('create_link')}</Button>
|
|
||||||
{/if}
|
|
||||||
{/snippet}
|
|
||||||
</FullScreenModal>
|
|
||||||
{:else}
|
|
||||||
<QrCodeModal title={$t('view_link')} {onClose} value={sharedLink} />
|
|
||||||
{/if}
|
|
@ -367,8 +367,6 @@ export enum SettingInputFieldType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const AlbumPageViewMode = {
|
export const AlbumPageViewMode = {
|
||||||
LINK_SHARING: 'link-sharing',
|
|
||||||
SELECT_USERS: 'select-users',
|
|
||||||
SELECT_THUMBNAIL: 'select-thumbnail',
|
SELECT_THUMBNAIL: 'select-thumbnail',
|
||||||
SELECT_ASSETS: 'select-assets',
|
SELECT_ASSETS: 'select-assets',
|
||||||
VIEW_USERS: 'view-users',
|
VIEW_USERS: 'view-users',
|
||||||
@ -377,8 +375,6 @@ export const AlbumPageViewMode = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type AlbumPageViewMode =
|
export type AlbumPageViewMode =
|
||||||
| typeof AlbumPageViewMode.LINK_SHARING
|
|
||||||
| typeof AlbumPageViewMode.SELECT_USERS
|
|
||||||
| typeof AlbumPageViewMode.SELECT_THUMBNAIL
|
| typeof AlbumPageViewMode.SELECT_THUMBNAIL
|
||||||
| typeof AlbumPageViewMode.SELECT_ASSETS
|
| typeof AlbumPageViewMode.SELECT_ASSETS
|
||||||
| typeof AlbumPageViewMode.VIEW_USERS
|
| typeof AlbumPageViewMode.VIEW_USERS
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
import Dropdown from '$lib/components/elements/dropdown.svelte';
|
import Dropdown from '$lib/components/elements/dropdown.svelte';
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
|
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
|
||||||
import QrCodeModal from '$lib/components/shared-components/qr-code-modal.svelte';
|
|
||||||
import { AppRoute } from '$lib/constants';
|
import { AppRoute } from '$lib/constants';
|
||||||
|
import QrCodeModal from '$lib/modals/QrCodeModal.svelte';
|
||||||
import { serverConfig } from '$lib/stores/server-config.store';
|
import { serverConfig } from '$lib/stores/server-config.store';
|
||||||
import { makeSharedLinkUrl } from '$lib/utils';
|
import { makeSharedLinkUrl } from '$lib/utils';
|
||||||
import {
|
import {
|
||||||
@ -20,16 +20,14 @@
|
|||||||
import { mdiCheck, mdiEye, mdiLink, mdiPencil } from '@mdi/js';
|
import { mdiCheck, mdiEye, mdiLink, mdiPencil } from '@mdi/js';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import UserAvatar from '../shared-components/user-avatar.svelte';
|
import UserAvatar from '../components/shared-components/user-avatar.svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
album: AlbumResponseDto;
|
album: AlbumResponseDto;
|
||||||
onClose: () => void;
|
onClose: (result?: { action: 'sharedLink' } | { action: 'sharedUsers'; data: AlbumUserAddDto[] }) => void;
|
||||||
onSelect: (selectedUsers: AlbumUserAddDto[]) => void;
|
|
||||||
onShare: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let { album, onClose, onSelect, onShare }: Props = $props();
|
let { album, onClose }: Props = $props();
|
||||||
|
|
||||||
let users: UserResponseDto[] = $state([]);
|
let users: UserResponseDto[] = $state([]);
|
||||||
let selectedUsers: Record<string, { user: UserResponseDto; role: AlbumUserRole }> = $state({});
|
let selectedUsers: Record<string, { user: UserResponseDto; role: AlbumUserRole }> = $state({});
|
||||||
@ -160,8 +158,10 @@
|
|||||||
shape="round"
|
shape="round"
|
||||||
disabled={Object.keys(selectedUsers).length === 0}
|
disabled={Object.keys(selectedUsers).length === 0}
|
||||||
onclick={() =>
|
onclick={() =>
|
||||||
onSelect(Object.values(selectedUsers).map(({ user, ...rest }) => ({ userId: user.id, ...rest })))}
|
onClose({
|
||||||
>{$t('add')}</Button
|
action: 'sharedUsers',
|
||||||
|
data: Object.values(selectedUsers).map(({ user, ...rest }) => ({ userId: user.id, ...rest })),
|
||||||
|
})}>{$t('add')}</Button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@ -182,7 +182,13 @@
|
|||||||
</Stack>
|
</Stack>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Button leadingIcon={mdiLink} size="small" shape="round" fullWidth onclick={onShare}>{$t('create_link')}</Button>
|
<Button
|
||||||
|
leadingIcon={mdiLink}
|
||||||
|
size="small"
|
||||||
|
shape="round"
|
||||||
|
fullWidth
|
||||||
|
onclick={() => onClose({ action: 'sharedLink' })}>{$t('create_link')}</Button
|
||||||
|
>
|
||||||
</Stack>
|
</Stack>
|
||||||
</FullScreenModal>
|
</FullScreenModal>
|
||||||
{/if}
|
{/if}
|
@ -1,24 +1,23 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
|
|
||||||
import QRCode from '$lib/components/shared-components/qrcode.svelte';
|
import QRCode from '$lib/components/shared-components/qrcode.svelte';
|
||||||
import { copyToClipboard } from '$lib/utils';
|
import { copyToClipboard } from '$lib/utils';
|
||||||
import { HStack, IconButton, Input } from '@immich/ui';
|
import { HStack, IconButton, Input, Modal, ModalBody } from '@immich/ui';
|
||||||
import { mdiContentCopy, mdiLink } from '@mdi/js';
|
import { mdiContentCopy, mdiLink } from '@mdi/js';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
title: string;
|
title: string;
|
||||||
onClose: () => void;
|
|
||||||
value: string;
|
value: string;
|
||||||
|
onClose: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
let { onClose, title, value }: Props = $props();
|
let { title, value, onClose }: Props = $props();
|
||||||
|
|
||||||
let modalWidth = $state(0);
|
let modalWidth = $state(0);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FullScreenModal {title} icon={mdiLink} {onClose}>
|
<Modal {title} icon={mdiLink} {onClose} size="small">
|
||||||
<div class="w-full">
|
<ModalBody>
|
||||||
<div class="w-full py-2 px-10">
|
<div class="w-full py-2 px-10">
|
||||||
<div bind:clientWidth={modalWidth} class="w-full">
|
<div bind:clientWidth={modalWidth} class="w-full">
|
||||||
<QRCode {value} width={modalWidth} />
|
<QRCode {value} width={modalWidth} />
|
||||||
@ -37,5 +36,5 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</HStack>
|
</HStack>
|
||||||
</div>
|
</ModalBody>
|
||||||
</FullScreenModal>
|
</Modal>
|
235
web/src/lib/modals/SharedLinkCreateModal.svelte
Normal file
235
web/src/lib/modals/SharedLinkCreateModal.svelte
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import SettingSelect from '$lib/components/shared-components/settings/setting-select.svelte';
|
||||||
|
import { SettingInputFieldType } from '$lib/constants';
|
||||||
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
|
import { SharedLinkType, createSharedLink, updateSharedLink, type SharedLinkResponseDto } from '@immich/sdk';
|
||||||
|
import { Button, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||||
|
import { mdiLink } from '@mdi/js';
|
||||||
|
import { DateTime, Duration } from 'luxon';
|
||||||
|
import { t } from 'svelte-i18n';
|
||||||
|
import { NotificationType, notificationController } from '../components/shared-components/notification/notification';
|
||||||
|
import SettingInputField from '../components/shared-components/settings/setting-input-field.svelte';
|
||||||
|
import SettingSwitch from '../components/shared-components/settings/setting-switch.svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onClose: (sharedLink?: SharedLinkResponseDto) => void;
|
||||||
|
albumId?: string | undefined;
|
||||||
|
assetIds?: string[];
|
||||||
|
editingLink?: SharedLinkResponseDto | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { onClose, albumId = $bindable(undefined), assetIds = $bindable([]), editingLink = undefined }: Props = $props();
|
||||||
|
|
||||||
|
let sharedLink: string | null = $state(null);
|
||||||
|
let description = $state('');
|
||||||
|
let allowDownload = $state(true);
|
||||||
|
let allowUpload = $state(false);
|
||||||
|
let showMetadata = $state(true);
|
||||||
|
let expirationOption: number = $state(0);
|
||||||
|
let password = $state('');
|
||||||
|
let shouldChangeExpirationTime = $state(false);
|
||||||
|
let enablePassword = $state(false);
|
||||||
|
|
||||||
|
const expirationOptions: [number, Intl.RelativeTimeFormatUnit][] = [
|
||||||
|
[30, 'minutes'],
|
||||||
|
[1, 'hour'],
|
||||||
|
[6, 'hours'],
|
||||||
|
[1, 'day'],
|
||||||
|
[7, 'days'],
|
||||||
|
[30, 'days'],
|
||||||
|
[3, 'months'],
|
||||||
|
[1, 'year'],
|
||||||
|
];
|
||||||
|
|
||||||
|
let relativeTime = $derived(new Intl.RelativeTimeFormat($locale));
|
||||||
|
let expiredDateOptions = $derived([
|
||||||
|
{ text: $t('never'), value: 0 },
|
||||||
|
...expirationOptions.map(([value, unit]) => ({
|
||||||
|
text: relativeTime.format(value, unit),
|
||||||
|
value: Duration.fromObject({ [unit]: value }).toMillis(),
|
||||||
|
})),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let shareType = $derived(albumId ? SharedLinkType.Album : SharedLinkType.Individual);
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (!showMetadata) {
|
||||||
|
allowDownload = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (editingLink) {
|
||||||
|
if (editingLink.description) {
|
||||||
|
description = editingLink.description;
|
||||||
|
}
|
||||||
|
if (editingLink.password) {
|
||||||
|
password = editingLink.password;
|
||||||
|
}
|
||||||
|
allowUpload = editingLink.allowUpload;
|
||||||
|
allowDownload = editingLink.allowDownload;
|
||||||
|
showMetadata = editingLink.showMetadata;
|
||||||
|
|
||||||
|
albumId = editingLink.album?.id;
|
||||||
|
assetIds = editingLink.assets.map(({ id }) => id);
|
||||||
|
|
||||||
|
enablePassword = !!editingLink.password;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCreateSharedLink = async () => {
|
||||||
|
const expirationDate = expirationOption > 0 ? DateTime.now().plus(expirationOption).toISO() : undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await createSharedLink({
|
||||||
|
sharedLinkCreateDto: {
|
||||||
|
type: shareType,
|
||||||
|
albumId,
|
||||||
|
assetIds,
|
||||||
|
expiresAt: expirationDate,
|
||||||
|
allowUpload,
|
||||||
|
description,
|
||||||
|
password,
|
||||||
|
allowDownload,
|
||||||
|
showMetadata,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
onClose(data);
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, $t('errors.failed_to_create_shared_link'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditLink = async () => {
|
||||||
|
if (!editingLink) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const expirationDate = expirationOption > 0 ? DateTime.now().plus(expirationOption).toISO() : null;
|
||||||
|
|
||||||
|
await updateSharedLink({
|
||||||
|
id: editingLink.id,
|
||||||
|
sharedLinkEditDto: {
|
||||||
|
description,
|
||||||
|
password: enablePassword ? password : '',
|
||||||
|
expiresAt: shouldChangeExpirationTime ? expirationDate : undefined,
|
||||||
|
allowUpload,
|
||||||
|
allowDownload,
|
||||||
|
showMetadata,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
notificationController.show({
|
||||||
|
type: NotificationType.Info,
|
||||||
|
message: $t('edited'),
|
||||||
|
});
|
||||||
|
|
||||||
|
onClose();
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, $t('errors.failed_to_edit_shared_link'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTitle = () => {
|
||||||
|
if (sharedLink) {
|
||||||
|
return $t('view_link');
|
||||||
|
}
|
||||||
|
if (editingLink) {
|
||||||
|
return $t('edit_link');
|
||||||
|
}
|
||||||
|
return $t('create_link_to_share');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Modal title={getTitle()} icon={mdiLink} size="small" {onClose}>
|
||||||
|
<ModalBody>
|
||||||
|
{#if shareType === SharedLinkType.Album}
|
||||||
|
{#if !editingLink}
|
||||||
|
<div>{$t('album_with_link_access')}</div>
|
||||||
|
{:else}
|
||||||
|
<div class="text-sm">
|
||||||
|
{$t('public_album')} |
|
||||||
|
<span class="text-immich-primary dark:text-immich-dark-primary">{editingLink.album?.albumName}</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if shareType === SharedLinkType.Individual}
|
||||||
|
{#if !editingLink}
|
||||||
|
<div>{$t('create_link_to_share_description')}</div>
|
||||||
|
{:else}
|
||||||
|
<div class="text-sm">
|
||||||
|
{$t('individual_share')} |
|
||||||
|
<span class="text-immich-primary dark:text-immich-dark-primary">{editingLink.description || ''}</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="mb-2 mt-4">
|
||||||
|
<p class="text-xs">{$t('link_options').toUpperCase()}</p>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-lg bg-gray-100 p-4 dark:bg-black/40 overflow-y-auto">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="mb-2">
|
||||||
|
<SettingInputField
|
||||||
|
inputType={SettingInputFieldType.TEXT}
|
||||||
|
label={$t('description')}
|
||||||
|
bind:value={description}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-2">
|
||||||
|
<SettingInputField
|
||||||
|
inputType={SettingInputFieldType.TEXT}
|
||||||
|
label={$t('password')}
|
||||||
|
bind:value={password}
|
||||||
|
disabled={!enablePassword}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="my-3">
|
||||||
|
<SettingSwitch bind:checked={enablePassword} title={$t('require_password')} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="my-3">
|
||||||
|
<SettingSwitch bind:checked={showMetadata} title={$t('show_metadata')} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="my-3">
|
||||||
|
<SettingSwitch
|
||||||
|
bind:checked={allowDownload}
|
||||||
|
title={$t('allow_public_user_to_download')}
|
||||||
|
disabled={!showMetadata}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="my-3">
|
||||||
|
<SettingSwitch bind:checked={allowUpload} title={$t('allow_public_user_to_upload')} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if editingLink}
|
||||||
|
<div class="my-3">
|
||||||
|
<SettingSwitch bind:checked={shouldChangeExpirationTime} title={$t('change_expiration_time')} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<div class="mt-3">
|
||||||
|
<SettingSelect
|
||||||
|
bind:value={expirationOption}
|
||||||
|
options={expiredDateOptions}
|
||||||
|
label={$t('expire_after')}
|
||||||
|
disabled={editingLink && !shouldChangeExpirationTime}
|
||||||
|
number={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
{#if editingLink}
|
||||||
|
<Button fullWidth onclick={handleEditLink}>{$t('confirm')}</Button>
|
||||||
|
{:else}
|
||||||
|
<Button fullWidth onclick={handleCreateSharedLink}>{$t('create_link')}</Button>
|
||||||
|
{/if}
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
@ -7,7 +7,6 @@
|
|||||||
import AlbumSummary from '$lib/components/album-page/album-summary.svelte';
|
import AlbumSummary from '$lib/components/album-page/album-summary.svelte';
|
||||||
import AlbumTitle from '$lib/components/album-page/album-title.svelte';
|
import AlbumTitle from '$lib/components/album-page/album-title.svelte';
|
||||||
import ShareInfoModal from '$lib/components/album-page/share-info-modal.svelte';
|
import ShareInfoModal from '$lib/components/album-page/share-info-modal.svelte';
|
||||||
import UserSelectionModal from '$lib/components/album-page/user-selection-modal.svelte';
|
|
||||||
import ActivityStatus from '$lib/components/asset-viewer/activity-status.svelte';
|
import ActivityStatus from '$lib/components/asset-viewer/activity-status.svelte';
|
||||||
import ActivityViewer from '$lib/components/asset-viewer/activity-viewer.svelte';
|
import ActivityViewer from '$lib/components/asset-viewer/activity-viewer.svelte';
|
||||||
import Button from '$lib/components/elements/buttons/button.svelte';
|
import Button from '$lib/components/elements/buttons/button.svelte';
|
||||||
@ -29,7 +28,6 @@
|
|||||||
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.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 ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
|
import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
|
||||||
import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte';
|
|
||||||
import {
|
import {
|
||||||
NotificationType,
|
NotificationType,
|
||||||
notificationController,
|
notificationController,
|
||||||
@ -37,12 +35,17 @@
|
|||||||
import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
|
import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
|
||||||
import { AlbumPageViewMode, AppRoute } from '$lib/constants';
|
import { AlbumPageViewMode, AppRoute } from '$lib/constants';
|
||||||
import { activityManager } from '$lib/managers/activity-manager.svelte';
|
import { activityManager } from '$lib/managers/activity-manager.svelte';
|
||||||
|
import { modalManager } from '$lib/managers/modal-manager.svelte';
|
||||||
|
import AlbumShareModal from '$lib/modals/AlbumShareModal.svelte';
|
||||||
|
import QrCodeModal from '$lib/modals/QrCodeModal.svelte';
|
||||||
|
import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.svelte';
|
||||||
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 { AssetStore } from '$lib/stores/assets-store.svelte';
|
import { AssetStore } from '$lib/stores/assets-store.svelte';
|
||||||
|
import { serverConfig } from '$lib/stores/server-config.store';
|
||||||
import { SlideshowNavigation, SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
|
import { SlideshowNavigation, SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
|
||||||
import { preferences, user } from '$lib/stores/user.store';
|
import { preferences, user } from '$lib/stores/user.store';
|
||||||
import { handlePromiseError } from '$lib/utils';
|
import { handlePromiseError, makeSharedLinkUrl } from '$lib/utils';
|
||||||
import { confirmAlbumDelete } from '$lib/utils/album-utils';
|
import { confirmAlbumDelete } from '$lib/utils/album-utils';
|
||||||
import { cancelMultiselect, downloadAlbum } from '$lib/utils/asset-utils';
|
import { cancelMultiselect, downloadAlbum } from '$lib/utils/asset-utils';
|
||||||
import { openFileUploadDialog } from '$lib/utils/file-uploader';
|
import { openFileUploadDialog } from '$lib/utils/file-uploader';
|
||||||
@ -178,10 +181,6 @@
|
|||||||
|
|
||||||
const handleEscape = async () => {
|
const handleEscape = async () => {
|
||||||
assetStore.suspendTransitions = true;
|
assetStore.suspendTransitions = true;
|
||||||
if (viewMode === AlbumPageViewMode.SELECT_USERS) {
|
|
||||||
viewMode = AlbumPageViewMode.VIEW;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (viewMode === AlbumPageViewMode.SELECT_THUMBNAIL) {
|
if (viewMode === AlbumPageViewMode.SELECT_THUMBNAIL) {
|
||||||
viewMode = AlbumPageViewMode.VIEW;
|
viewMode = AlbumPageViewMode.VIEW;
|
||||||
return;
|
return;
|
||||||
@ -190,10 +189,6 @@
|
|||||||
await handleCloseSelectAssets();
|
await handleCloseSelectAssets();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (viewMode === AlbumPageViewMode.LINK_SHARING) {
|
|
||||||
viewMode = AlbumPageViewMode.VIEW;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (viewMode === AlbumPageViewMode.OPTIONS) {
|
if (viewMode === AlbumPageViewMode.OPTIONS) {
|
||||||
viewMode = AlbumPageViewMode.VIEW;
|
viewMode = AlbumPageViewMode.VIEW;
|
||||||
return;
|
return;
|
||||||
@ -423,6 +418,31 @@
|
|||||||
const currentAssetIntersection = $derived(
|
const currentAssetIntersection = $derived(
|
||||||
viewMode === AlbumPageViewMode.SELECT_ASSETS ? timelineInteraction : assetInteraction,
|
viewMode === AlbumPageViewMode.SELECT_ASSETS ? timelineInteraction : assetInteraction,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleShare = async () => {
|
||||||
|
const result = await modalManager.show(AlbumShareModal, { album });
|
||||||
|
|
||||||
|
switch (result?.action) {
|
||||||
|
case 'sharedLink': {
|
||||||
|
await handleShareLink();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'sharedUsers': {
|
||||||
|
await handleAddUsers(result.data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleShareLink = async () => {
|
||||||
|
const sharedLink = await modalManager.show(SharedLinkCreateModal, { albumId: album.id });
|
||||||
|
|
||||||
|
if (sharedLink) {
|
||||||
|
const url = makeSharedLinkUrl($serverConfig.externalDomain, sharedLink.key);
|
||||||
|
await modalManager.show(QrCodeModal, { title: $t('view_link'), value: url });
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex overflow-hidden" use:scrollMemoryClearer={{ routeStartsWith: AppRoute.ALBUMS }}>
|
<div class="flex overflow-hidden" use:scrollMemoryClearer={{ routeStartsWith: AppRoute.ALBUMS }}>
|
||||||
@ -496,11 +516,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if isOwned}
|
{#if isOwned}
|
||||||
<CircleIconButton
|
<CircleIconButton title={$t('share')} onclick={handleShare} icon={mdiShareVariantOutline} />
|
||||||
title={$t('share')}
|
|
||||||
onclick={() => (viewMode = AlbumPageViewMode.SELECT_USERS)}
|
|
||||||
icon={mdiShareVariantOutline}
|
|
||||||
/>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<AlbumMap {album} />
|
<AlbumMap {album} />
|
||||||
@ -530,12 +546,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if isCreatingSharedAlbum && album.albumUsers.length === 0}
|
{#if isCreatingSharedAlbum && album.albumUsers.length === 0}
|
||||||
<Button
|
<Button size="sm" rounded="lg" disabled={album.assetCount === 0} onclick={handleShare}>
|
||||||
size="sm"
|
|
||||||
rounded="lg"
|
|
||||||
disabled={album.assetCount === 0}
|
|
||||||
onclick={() => (viewMode = AlbumPageViewMode.SELECT_USERS)}
|
|
||||||
>
|
|
||||||
{$t('share')}
|
{$t('share')}
|
||||||
</Button>
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
@ -619,7 +630,7 @@
|
|||||||
color="gray"
|
color="gray"
|
||||||
size="20"
|
size="20"
|
||||||
icon={mdiLink}
|
icon={mdiLink}
|
||||||
onclick={() => (viewMode = AlbumPageViewMode.LINK_SHARING)}
|
onclick={handleShareLink}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@ -651,7 +662,7 @@
|
|||||||
color="gray"
|
color="gray"
|
||||||
size="20"
|
size="20"
|
||||||
icon={mdiPlus}
|
icon={mdiPlus}
|
||||||
onclick={() => (viewMode = AlbumPageViewMode.SELECT_USERS)}
|
onclick={handleShare}
|
||||||
title={$t('add_more_users')}
|
title={$t('add_more_users')}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
@ -714,18 +725,6 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if viewMode === AlbumPageViewMode.SELECT_USERS}
|
|
||||||
<UserSelectionModal
|
|
||||||
{album}
|
|
||||||
onSelect={handleAddUsers}
|
|
||||||
onShare={() => (viewMode = AlbumPageViewMode.LINK_SHARING)}
|
|
||||||
onClose={() => (viewMode = AlbumPageViewMode.VIEW)}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if viewMode === AlbumPageViewMode.LINK_SHARING}
|
|
||||||
<CreateSharedLinkModal albumId={album.id} onClose={() => (viewMode = AlbumPageViewMode.VIEW)} />
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if viewMode === AlbumPageViewMode.VIEW_USERS}
|
{#if viewMode === AlbumPageViewMode.VIEW_USERS}
|
||||||
<ShareInfoModal
|
<ShareInfoModal
|
||||||
@ -749,7 +748,7 @@
|
|||||||
onRefreshAlbum={refreshAlbum}
|
onRefreshAlbum={refreshAlbum}
|
||||||
onClose={() => (viewMode = AlbumPageViewMode.VIEW)}
|
onClose={() => (viewMode = AlbumPageViewMode.VIEW)}
|
||||||
onToggleEnabledActivity={handleToggleEnableActivity}
|
onToggleEnabledActivity={handleToggleEnableActivity}
|
||||||
onShowSelectSharedUser={() => (viewMode = AlbumPageViewMode.SELECT_USERS)}
|
onShowSelectSharedUser={handleShare}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import GroupTab from '$lib/components/elements/group-tab.svelte';
|
import GroupTab from '$lib/components/elements/group-tab.svelte';
|
||||||
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||||
import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte';
|
|
||||||
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
|
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
|
||||||
import {
|
import {
|
||||||
notificationController,
|
notificationController,
|
||||||
@ -11,6 +10,7 @@
|
|||||||
} from '$lib/components/shared-components/notification/notification';
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
import SharedLinkCard from '$lib/components/sharedlinks-page/shared-link-card.svelte';
|
import SharedLinkCard from '$lib/components/sharedlinks-page/shared-link-card.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { AppRoute } from '$lib/constants';
|
||||||
|
import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.svelte';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { getAllSharedLinks, removeSharedLink, SharedLinkType, type SharedLinkResponseDto } from '@immich/sdk';
|
import { getAllSharedLinks, removeSharedLink, SharedLinkType, type SharedLinkResponseDto } from '@immich/sdk';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
@ -113,7 +113,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if sharedLink}
|
{#if sharedLink}
|
||||||
<CreateSharedLinkModal editingLink={sharedLink} onClose={handleEditDone} />
|
<SharedLinkCreateModal editingLink={sharedLink} onClose={handleEditDone} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</UserPageLayout>
|
</UserPageLayout>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user