mirror of
https://github.com/immich-app/immich
synced 2025-11-14 17:36:12 +00:00
refactor: share modals (#18183)
This commit is contained in:
parent
47b1938f17
commit
6a69dafd31
10 changed files with 375 additions and 379 deletions
|
|
@ -1,43 +1,47 @@
|
|||
<script lang="ts">
|
||||
import { onMount, type Snippet } from 'svelte';
|
||||
import { groupBy } from 'lodash-es';
|
||||
import { addUsersToAlbum, deleteAlbum, type AlbumUserAddDto, type AlbumResponseDto, isHttpError } from '@immich/sdk';
|
||||
import { mdiDeleteOutline, mdiShareVariantOutline, mdiFolderDownloadOutline, mdiRenameOutline } from '@mdi/js';
|
||||
import { goto } from '$app/navigation';
|
||||
import AlbumCardGroup from '$lib/components/album-page/album-card-group.svelte';
|
||||
import AlbumsTable from '$lib/components/album-page/albums-table.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 {
|
||||
NotificationType,
|
||||
notificationController,
|
||||
} from '$lib/components/shared-components/notification/notification';
|
||||
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 AlbumsTable from '$lib/components/album-page/albums-table.svelte';
|
||||
import AlbumCardGroup from '$lib/components/album-page/album-card-group.svelte';
|
||||
import UserSelectionModal from '$lib/components/album-page/user-selection-modal.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 { AppRoute } from '$lib/constants';
|
||||
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 {
|
||||
AlbumFilter,
|
||||
AlbumGroupBy,
|
||||
AlbumSortBy,
|
||||
AlbumFilter,
|
||||
AlbumViewMode,
|
||||
SortOrder,
|
||||
locale,
|
||||
type AlbumViewSettings,
|
||||
} 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 { goto } from '$app/navigation';
|
||||
import { AppRoute } from '$lib/constants';
|
||||
import { makeSharedLinkUrl } from '$lib/utils';
|
||||
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 { run } from 'svelte/legacy';
|
||||
|
||||
|
|
@ -140,8 +144,6 @@
|
|||
|
||||
let albumGroupOption: string = $state(AlbumGroupBy.None);
|
||||
|
||||
let showShareByURLModal = $state(false);
|
||||
|
||||
let albumToEdit: AlbumResponseDto | null = $state(null);
|
||||
let albumToShare: AlbumResponseDto | null = $state(null);
|
||||
let albumToDelete: AlbumResponseDto | null = null;
|
||||
|
|
@ -346,18 +348,32 @@
|
|||
updateAlbumInfo(album);
|
||||
};
|
||||
|
||||
const openShareModal = () => {
|
||||
const openShareModal = async () => {
|
||||
if (!contextMenuTargetAlbum) {
|
||||
return;
|
||||
}
|
||||
|
||||
albumToShare = contextMenuTargetAlbum;
|
||||
closeAlbumContextMenu();
|
||||
};
|
||||
const result = await modalManager.show(AlbumShareModal, { album: albumToShare });
|
||||
|
||||
const closeShareModal = () => {
|
||||
albumToShare = null;
|
||||
showShareByURLModal = false;
|
||||
switch (result?.action) {
|
||||
case 'sharedUsers': {
|
||||
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>
|
||||
|
||||
|
|
@ -419,22 +435,4 @@
|
|||
onClose={() => (albumToEdit = null)}
|
||||
/>
|
||||
{/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}
|
||||
|
|
|
|||
|
|
@ -1,188 +0,0 @@
|
|||
<script lang="ts">
|
||||
import AlbumSharedLink from '$lib/components/album-page/album-shared-link.svelte';
|
||||
import Dropdown from '$lib/components/elements/dropdown.svelte';
|
||||
import Icon from '$lib/components/elements/icon.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 { serverConfig } from '$lib/stores/server-config.store';
|
||||
import { makeSharedLinkUrl } from '$lib/utils';
|
||||
import {
|
||||
AlbumUserRole,
|
||||
getAllSharedLinks,
|
||||
searchUsers,
|
||||
type AlbumResponseDto,
|
||||
type AlbumUserAddDto,
|
||||
type SharedLinkResponseDto,
|
||||
type UserResponseDto,
|
||||
} from '@immich/sdk';
|
||||
import { Button, Link, Stack, Text } from '@immich/ui';
|
||||
import { mdiCheck, mdiEye, mdiLink, mdiPencil } from '@mdi/js';
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import UserAvatar from '../shared-components/user-avatar.svelte';
|
||||
|
||||
interface Props {
|
||||
album: AlbumResponseDto;
|
||||
onClose: () => void;
|
||||
onSelect: (selectedUsers: AlbumUserAddDto[]) => void;
|
||||
onShare: () => void;
|
||||
}
|
||||
|
||||
let { album, onClose, onSelect, onShare }: Props = $props();
|
||||
|
||||
let users: UserResponseDto[] = $state([]);
|
||||
let selectedUsers: Record<string, { user: UserResponseDto; role: AlbumUserRole }> = $state({});
|
||||
|
||||
let sharedLinkUrl = $state('');
|
||||
const handleViewQrCode = (sharedLink: SharedLinkResponseDto) => {
|
||||
sharedLinkUrl = makeSharedLinkUrl($serverConfig.externalDomain, sharedLink.key);
|
||||
};
|
||||
|
||||
const roleOptions: Array<{ title: string; value: AlbumUserRole | 'none'; icon?: string }> = [
|
||||
{ title: $t('role_editor'), value: AlbumUserRole.Editor, icon: mdiPencil },
|
||||
{ title: $t('role_viewer'), value: AlbumUserRole.Viewer, icon: mdiEye },
|
||||
{ title: $t('remove_user'), value: 'none' },
|
||||
];
|
||||
|
||||
let sharedLinks: SharedLinkResponseDto[] = $state([]);
|
||||
onMount(async () => {
|
||||
sharedLinks = await getAllSharedLinks({ albumId: album.id });
|
||||
const data = await searchUsers();
|
||||
|
||||
// remove album owner
|
||||
users = data.filter((user) => user.id !== album.ownerId);
|
||||
|
||||
// Remove the existed shared users from the album
|
||||
for (const sharedUser of album.albumUsers) {
|
||||
users = users.filter((user) => user.id !== sharedUser.user.id);
|
||||
}
|
||||
});
|
||||
|
||||
const handleToggle = (user: UserResponseDto) => {
|
||||
if (Object.keys(selectedUsers).includes(user.id)) {
|
||||
delete selectedUsers[user.id];
|
||||
} else {
|
||||
selectedUsers[user.id] = { user, role: AlbumUserRole.Editor };
|
||||
}
|
||||
};
|
||||
|
||||
const handleChangeRole = (user: UserResponseDto, role: AlbumUserRole | 'none') => {
|
||||
if (role === 'none') {
|
||||
delete selectedUsers[user.id];
|
||||
} else {
|
||||
selectedUsers[user.id].role = role;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
{#if sharedLinkUrl}
|
||||
<QrCodeModal title={$t('view_link')} onClose={() => (sharedLinkUrl = '')} value={sharedLinkUrl} />
|
||||
{:else}
|
||||
<FullScreenModal title={$t('share')} showLogo {onClose}>
|
||||
{#if Object.keys(selectedUsers).length > 0}
|
||||
<div class="mb-2 py-2 sticky">
|
||||
<p class="text-xs font-medium">{$t('selected')}</p>
|
||||
<div class="my-2">
|
||||
{#each Object.values(selectedUsers) as { user } (user.id)}
|
||||
{#key user.id}
|
||||
<div class="flex place-items-center gap-4 p-4">
|
||||
<div
|
||||
class="flex h-10 w-10 items-center justify-center rounded-full border bg-immich-dark-success text-3xl text-white dark:border-immich-dark-gray dark:bg-immich-dark-success"
|
||||
>
|
||||
<Icon path={mdiCheck} size={24} />
|
||||
</div>
|
||||
|
||||
<!-- <UserAvatar {user} size="md" /> -->
|
||||
<div class="text-start flex-grow">
|
||||
<p class="text-immich-fg dark:text-immich-dark-fg">
|
||||
{user.name}
|
||||
</p>
|
||||
<p class="text-xs">
|
||||
{user.email}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Dropdown
|
||||
title={$t('role')}
|
||||
options={roleOptions}
|
||||
render={({ title, icon }) => ({ title, icon })}
|
||||
onSelect={({ value }) => handleChangeRole(user, value)}
|
||||
/>
|
||||
</div>
|
||||
{/key}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if users.length + Object.keys(selectedUsers).length === 0}
|
||||
<p class="p-5 text-sm">
|
||||
{$t('album_share_no_users')}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
<div class="immich-scrollbar max-h-[500px] overflow-y-auto">
|
||||
{#if users.length > 0 && users.length !== Object.keys(selectedUsers).length}
|
||||
<Text>{$t('users')}</Text>
|
||||
|
||||
<div class="my-2">
|
||||
{#each users as user (user.id)}
|
||||
{#if !Object.keys(selectedUsers).includes(user.id)}
|
||||
<div class="flex place-items-center transition-all hover:bg-gray-200 dark:hover:bg-gray-700 rounded-xl">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => handleToggle(user)}
|
||||
class="flex w-full place-items-center gap-4 p-4"
|
||||
>
|
||||
<UserAvatar {user} size="md" />
|
||||
<div class="text-start flex-grow">
|
||||
<p class="text-immich-fg dark:text-immich-dark-fg">
|
||||
{user.name}
|
||||
</p>
|
||||
<p class="text-xs">
|
||||
{user.email}
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if users.length > 0}
|
||||
<div class="py-3">
|
||||
<Button
|
||||
size="small"
|
||||
fullWidth
|
||||
shape="round"
|
||||
disabled={Object.keys(selectedUsers).length === 0}
|
||||
onclick={() =>
|
||||
onSelect(Object.values(selectedUsers).map(({ user, ...rest }) => ({ userId: user.id, ...rest })))}
|
||||
>{$t('add')}</Button
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<hr class="my-4" />
|
||||
|
||||
<Stack gap={6}>
|
||||
{#if sharedLinks.length > 0}
|
||||
<div class="flex justify-between items-center">
|
||||
<Text>{$t('shared_links')}</Text>
|
||||
<Link href={AppRoute.SHARED_LINKS} class="text-sm">{$t('view_all')}</Link>
|
||||
</div>
|
||||
|
||||
<Stack gap={4}>
|
||||
{#each sharedLinks as sharedLink (sharedLink.id)}
|
||||
<AlbumSharedLink {album} {sharedLink} onViewQrCode={() => handleViewQrCode(sharedLink)} />
|
||||
{/each}
|
||||
</Stack>
|
||||
{/if}
|
||||
|
||||
<Button leadingIcon={mdiLink} size="small" shape="round" fullWidth onclick={onShare}>{$t('create_link')}</Button>
|
||||
</Stack>
|
||||
</FullScreenModal>
|
||||
{/if}
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
<script lang="ts">
|
||||
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 Portal from '$lib/components/shared-components/portal/portal.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 type { AssetResponseDto } from '@immich/sdk';
|
||||
import { mdiShareVariantOutline } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
|
@ -12,13 +15,14 @@
|
|||
|
||||
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>
|
||||
|
||||
<CircleIconButton color="opaque" icon={mdiShareVariantOutline} onclick={() => (showModal = true)} title={$t('share')} />
|
||||
|
||||
{#if showModal}
|
||||
<Portal target="body">
|
||||
<CreateSharedLinkModal assetIds={[asset.id]} onClose={() => (showModal = false)} />
|
||||
</Portal>
|
||||
{/if}
|
||||
<CircleIconButton color="opaque" icon={mdiShareVariantOutline} onclick={handleClick} title={$t('share')} />
|
||||
|
|
|
|||
|
|
@ -1,16 +1,26 @@
|
|||
<script lang="ts">
|
||||
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 { getAssetControlContext } from '../asset-select-control-bar.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
let showModal = $state(false);
|
||||
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>
|
||||
|
||||
<CircleIconButton title={$t('share')} icon={mdiShareVariantOutline} onclick={() => (showModal = true)} />
|
||||
|
||||
{#if showModal}
|
||||
<CreateSharedLinkModal assetIds={[...getAssets()].map(({ id }) => id)} onClose={() => (showModal = false)} />
|
||||
{/if}
|
||||
<CircleIconButton title={$t('share')} icon={mdiShareVariantOutline} onclick={handleClick} />
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
<script lang="ts">
|
||||
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
|
||||
import QRCode from '$lib/components/shared-components/qrcode.svelte';
|
||||
import { copyToClipboard } from '$lib/utils';
|
||||
import { HStack, IconButton, Input } from '@immich/ui';
|
||||
import { mdiContentCopy, mdiLink } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
onClose: () => void;
|
||||
value: string;
|
||||
};
|
||||
|
||||
let { onClose, title, value }: Props = $props();
|
||||
|
||||
let modalWidth = $state(0);
|
||||
</script>
|
||||
|
||||
<FullScreenModal {title} icon={mdiLink} {onClose}>
|
||||
<div class="w-full">
|
||||
<div class="w-full py-2 px-10">
|
||||
<div bind:clientWidth={modalWidth} class="w-full">
|
||||
<QRCode {value} width={modalWidth} />
|
||||
</div>
|
||||
</div>
|
||||
<HStack class="w-full pt-3" gap={1}>
|
||||
<Input bind:value disabled class="flex flex-row" />
|
||||
<div>
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
shape="round"
|
||||
color="secondary"
|
||||
icon={mdiContentCopy}
|
||||
onclick={() => (value ? copyToClipboard(value) : '')}
|
||||
aria-label={$t('copy_link_to_clipboard')}
|
||||
/>
|
||||
</div>
|
||||
</HStack>
|
||||
</div>
|
||||
</FullScreenModal>
|
||||
Loading…
Add table
Add a link
Reference in a new issue