mirror of
https://github.com/immich-app/immich.git
synced 2025-05-30 19:54:52 -04:00
chore: migrate away from event dispatcher (#12820)
This commit is contained in:
parent
529d49471f
commit
124eb8251b
@ -2,7 +2,6 @@
|
|||||||
import Icon from '$lib/components/elements/icon.svelte';
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
import { updateAlbumInfo, type AlbumResponseDto, type UserResponseDto, AssetOrder } from '@immich/sdk';
|
import { updateAlbumInfo, type AlbumResponseDto, type UserResponseDto, AssetOrder } from '@immich/sdk';
|
||||||
import { mdiArrowDownThin, mdiArrowUpThin, mdiPlus } from '@mdi/js';
|
import { mdiArrowDownThin, mdiArrowUpThin, mdiPlus } from '@mdi/js';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
|
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
|
||||||
import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
|
import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
|
||||||
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
|
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
|
||||||
@ -16,6 +15,9 @@
|
|||||||
export let order: AssetOrder | undefined;
|
export let order: AssetOrder | undefined;
|
||||||
export let user: UserResponseDto;
|
export let user: UserResponseDto;
|
||||||
export let onChangeOrder: (order: AssetOrder) => void;
|
export let onChangeOrder: (order: AssetOrder) => void;
|
||||||
|
export let onClose: () => void;
|
||||||
|
export let onToggleEnabledActivity: () => void;
|
||||||
|
export let onShowSelectSharedUser: () => void;
|
||||||
|
|
||||||
const options: Record<AssetOrder, RenderedOption> = {
|
const options: Record<AssetOrder, RenderedOption> = {
|
||||||
[AssetOrder.Asc]: { icon: mdiArrowUpThin, title: $t('oldest_first') },
|
[AssetOrder.Asc]: { icon: mdiArrowUpThin, title: $t('oldest_first') },
|
||||||
@ -24,12 +26,6 @@
|
|||||||
|
|
||||||
$: selectedOption = order ? options[order] : options[AssetOrder.Desc];
|
$: selectedOption = order ? options[order] : options[AssetOrder.Desc];
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
close: void;
|
|
||||||
toggleEnableActivity: void;
|
|
||||||
showSelectSharedUser: void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const handleToggle = async (returnedOption: RenderedOption) => {
|
const handleToggle = async (returnedOption: RenderedOption) => {
|
||||||
if (selectedOption === returnedOption) {
|
if (selectedOption === returnedOption) {
|
||||||
return;
|
return;
|
||||||
@ -51,7 +47,7 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FullScreenModal title={$t('options')} onClose={() => dispatch('close')}>
|
<FullScreenModal title={$t('options')} {onClose}>
|
||||||
<div class="items-center justify-center">
|
<div class="items-center justify-center">
|
||||||
<div class="py-2">
|
<div class="py-2">
|
||||||
<h2 class="text-gray text-sm mb-2">{$t('settings').toUpperCase()}</h2>
|
<h2 class="text-gray text-sm mb-2">{$t('settings').toUpperCase()}</h2>
|
||||||
@ -68,14 +64,14 @@
|
|||||||
title={$t('comments_and_likes')}
|
title={$t('comments_and_likes')}
|
||||||
subtitle={$t('let_others_respond')}
|
subtitle={$t('let_others_respond')}
|
||||||
checked={album.isActivityEnabled}
|
checked={album.isActivityEnabled}
|
||||||
on:toggle={() => dispatch('toggleEnableActivity')}
|
onToggle={onToggleEnabledActivity}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="py-2">
|
<div class="py-2">
|
||||||
<div class="text-gray text-sm mb-3">{$t('people').toUpperCase()}</div>
|
<div class="text-gray text-sm mb-3">{$t('people').toUpperCase()}</div>
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
<button type="button" class="flex items-center gap-2" on:click={() => dispatch('showSelectSharedUser')}>
|
<button type="button" class="flex items-center gap-2" on:click={onShowSelectSharedUser}>
|
||||||
<div class="rounded-full w-10 h-10 border border-gray-500 flex items-center justify-center">
|
<div class="rounded-full w-10 h-10 border border-gray-500 flex items-center justify-center">
|
||||||
<div><Icon path={mdiPlus} size="25" /></div>
|
<div><Icon path={mdiPlus} size="25" /></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -154,7 +154,7 @@
|
|||||||
title={$t('sort_albums_by')}
|
title={$t('sort_albums_by')}
|
||||||
options={Object.values(sortOptionsMetadata)}
|
options={Object.values(sortOptionsMetadata)}
|
||||||
selectedOption={selectedSortOption}
|
selectedOption={selectedSortOption}
|
||||||
on:select={({ detail }) => handleChangeSortBy(detail)}
|
onSelect={handleChangeSortBy}
|
||||||
render={({ id }) => ({
|
render={({ id }) => ({
|
||||||
title: albumSortByNames[id],
|
title: albumSortByNames[id],
|
||||||
icon: sortIcon,
|
icon: sortIcon,
|
||||||
@ -166,7 +166,7 @@
|
|||||||
title={$t('group_albums_by')}
|
title={$t('group_albums_by')}
|
||||||
options={Object.values(groupOptionsMetadata)}
|
options={Object.values(groupOptionsMetadata)}
|
||||||
selectedOption={selectedGroupOption}
|
selectedOption={selectedGroupOption}
|
||||||
on:select={({ detail }) => handleChangeGroupBy(detail)}
|
onSelect={handleChangeGroupBy}
|
||||||
render={({ id, isDisabled }) => ({
|
render={({ id, isDisabled }) => ({
|
||||||
title: albumGroupByNames[id],
|
title: albumGroupByNames[id],
|
||||||
icon: groupIcon,
|
icon: groupIcon,
|
||||||
|
@ -394,13 +394,13 @@
|
|||||||
<CreateSharedLinkModal
|
<CreateSharedLinkModal
|
||||||
albumId={albumToShare.id}
|
albumId={albumToShare.id}
|
||||||
onClose={() => closeShareModal()}
|
onClose={() => closeShareModal()}
|
||||||
on:created={() => albumToShare && handleSharedLinkCreated(albumToShare)}
|
onCreated={() => albumToShare && handleSharedLinkCreated(albumToShare)}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<UserSelectionModal
|
<UserSelectionModal
|
||||||
album={albumToShare}
|
album={albumToShare}
|
||||||
on:select={({ detail: users }) => handleAddUsers(users)}
|
onSelect={handleAddUsers}
|
||||||
on:share={() => (showShareByURLModal = true)}
|
onShare={() => (showShareByURLModal = true)}
|
||||||
onClose={() => closeShareModal()}
|
onClose={() => closeShareModal()}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
AlbumUserRole,
|
AlbumUserRole,
|
||||||
} from '@immich/sdk';
|
} from '@immich/sdk';
|
||||||
import { mdiDotsVertical } from '@mdi/js';
|
import { mdiDotsVertical } from '@mdi/js';
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { handleError } from '../../utils/handle-error';
|
import { handleError } from '../../utils/handle-error';
|
||||||
import ConfirmDialog from '../shared-components/dialog/confirm-dialog.svelte';
|
import ConfirmDialog from '../shared-components/dialog/confirm-dialog.svelte';
|
||||||
import MenuOption from '../shared-components/context-menu/menu-option.svelte';
|
import MenuOption from '../shared-components/context-menu/menu-option.svelte';
|
||||||
@ -20,11 +20,8 @@
|
|||||||
|
|
||||||
export let album: AlbumResponseDto;
|
export let album: AlbumResponseDto;
|
||||||
export let onClose: () => void;
|
export let onClose: () => void;
|
||||||
|
export let onRemove: (userId: string) => void;
|
||||||
const dispatch = createEventDispatcher<{
|
export let onRefreshAlbum: () => void;
|
||||||
remove: string;
|
|
||||||
refreshAlbum: void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
let currentUser: UserResponseDto;
|
let currentUser: UserResponseDto;
|
||||||
let selectedRemoveUser: UserResponseDto | null = null;
|
let selectedRemoveUser: UserResponseDto | null = null;
|
||||||
@ -52,7 +49,7 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await removeUserFromAlbum({ id: album.id, userId });
|
await removeUserFromAlbum({ id: album.id, userId });
|
||||||
dispatch('remove', userId);
|
onRemove(userId);
|
||||||
const message =
|
const message =
|
||||||
userId === 'me'
|
userId === 'me'
|
||||||
? $t('album_user_left', { values: { album: album.albumName } })
|
? $t('album_user_left', { values: { album: album.albumName } })
|
||||||
@ -71,7 +68,7 @@
|
|||||||
const message = $t('user_role_set', {
|
const message = $t('user_role_set', {
|
||||||
values: { user: user.name, role: role == AlbumUserRole.Viewer ? $t('role_viewer') : $t('role_editor') },
|
values: { user: user.name, role: role == AlbumUserRole.Viewer ? $t('role_viewer') : $t('role_editor') },
|
||||||
});
|
});
|
||||||
dispatch('refreshAlbum');
|
onRefreshAlbum();
|
||||||
notificationController.show({ type: NotificationType.Info, message });
|
notificationController.show({ type: NotificationType.Info, message });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, $t('errors.unable_to_change_album_user_role'));
|
handleError(error, $t('errors.unable_to_change_album_user_role'));
|
||||||
|
@ -13,13 +13,16 @@
|
|||||||
type UserResponseDto,
|
type UserResponseDto,
|
||||||
} from '@immich/sdk';
|
} from '@immich/sdk';
|
||||||
import { mdiCheck, mdiEye, mdiLink, mdiPencil, mdiShareCircle } from '@mdi/js';
|
import { mdiCheck, mdiEye, mdiLink, mdiPencil, mdiShareCircle } from '@mdi/js';
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import Button from '../elements/buttons/button.svelte';
|
import Button from '../elements/buttons/button.svelte';
|
||||||
import UserAvatar from '../shared-components/user-avatar.svelte';
|
import UserAvatar from '../shared-components/user-avatar.svelte';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
export let album: AlbumResponseDto;
|
export let album: AlbumResponseDto;
|
||||||
export let onClose: () => void;
|
export let onClose: () => void;
|
||||||
|
export let onSelect: (selectedUsers: AlbumUserAddDto[]) => void;
|
||||||
|
export let onShare: () => void;
|
||||||
|
|
||||||
let users: UserResponseDto[] = [];
|
let users: UserResponseDto[] = [];
|
||||||
let selectedUsers: Record<string, { user: UserResponseDto; role: AlbumUserRole }> = {};
|
let selectedUsers: Record<string, { user: UserResponseDto; role: AlbumUserRole }> = {};
|
||||||
|
|
||||||
@ -29,10 +32,6 @@
|
|||||||
{ title: $t('remove_user'), value: 'none' },
|
{ title: $t('remove_user'), value: 'none' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
select: AlbumUserAddDto[];
|
|
||||||
share: void;
|
|
||||||
}>();
|
|
||||||
let sharedLinks: SharedLinkResponseDto[] = [];
|
let sharedLinks: SharedLinkResponseDto[] = [];
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await getSharedLinks();
|
await getSharedLinks();
|
||||||
@ -99,7 +98,7 @@
|
|||||||
title={$t('role')}
|
title={$t('role')}
|
||||||
options={roleOptions}
|
options={roleOptions}
|
||||||
render={({ title, icon }) => ({ title, icon })}
|
render={({ title, icon }) => ({ title, icon })}
|
||||||
on:select={({ detail: { value } }) => handleChangeRole(user, value)}
|
onSelect={({ value }) => handleChangeRole(user, value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/key}
|
{/key}
|
||||||
@ -152,10 +151,8 @@
|
|||||||
rounded="full"
|
rounded="full"
|
||||||
disabled={Object.keys(selectedUsers).length === 0}
|
disabled={Object.keys(selectedUsers).length === 0}
|
||||||
on:click={() =>
|
on:click={() =>
|
||||||
dispatch(
|
onSelect(Object.values(selectedUsers).map(({ user, ...rest }) => ({ userId: user.id, ...rest })))}
|
||||||
'select',
|
>{$t('add')}</Button
|
||||||
Object.values(selectedUsers).map(({ user, ...rest }) => ({ userId: user.id, ...rest })),
|
|
||||||
)}>{$t('add')}</Button
|
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@ -166,7 +163,7 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="flex flex-col place-content-center place-items-center gap-2 hover:cursor-pointer"
|
class="flex flex-col place-content-center place-items-center gap-2 hover:cursor-pointer"
|
||||||
on:click={() => dispatch('share')}
|
on:click={onShare}
|
||||||
>
|
>
|
||||||
<Icon path={mdiLink} size={24} />
|
<Icon path={mdiLink} size={24} />
|
||||||
<p class="text-sm">{$t('create_link')}</p>
|
<p class="text-sm">{$t('create_link')}</p>
|
||||||
|
@ -40,8 +40,8 @@
|
|||||||
<Portal target="body">
|
<Portal target="body">
|
||||||
<AlbumSelectionModal
|
<AlbumSelectionModal
|
||||||
{shared}
|
{shared}
|
||||||
on:newAlbum={({ detail }) => handleAddToNewAlbum(detail)}
|
onNewAlbum={handleAddToNewAlbum}
|
||||||
on:album={({ detail }) => handleAddToAlbum(detail)}
|
onAlbumClick={handleAddToAlbum}
|
||||||
onClose={() => (showSelectionModal = false)}
|
onClose={() => (showSelectionModal = false)}
|
||||||
/>
|
/>
|
||||||
</Portal>
|
</Portal>
|
||||||
|
@ -82,6 +82,6 @@
|
|||||||
|
|
||||||
{#if showConfirmModal}
|
{#if showConfirmModal}
|
||||||
<Portal target="body">
|
<Portal target="body">
|
||||||
<DeleteAssetDialog size={1} on:cancel={() => (showConfirmModal = false)} on:confirm={() => deleteAsset()} />
|
<DeleteAssetDialog size={1} onCancel={() => (showConfirmModal = false)} onConfirm={deleteAsset} />
|
||||||
</Portal>
|
</Portal>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -2,26 +2,22 @@
|
|||||||
import { locale } from '$lib/stores/preferences.store';
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
import type { ActivityResponseDto } from '@immich/sdk';
|
import type { ActivityResponseDto } from '@immich/sdk';
|
||||||
import { mdiCommentOutline, mdiHeart, mdiHeartOutline } from '@mdi/js';
|
import { mdiCommentOutline, mdiHeart, mdiHeartOutline } from '@mdi/js';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import Icon from '../elements/icon.svelte';
|
import Icon from '../elements/icon.svelte';
|
||||||
|
|
||||||
export let isLiked: ActivityResponseDto | null;
|
export let isLiked: ActivityResponseDto | null;
|
||||||
export let numberOfComments: number | undefined;
|
export let numberOfComments: number | undefined;
|
||||||
export let disabled: boolean;
|
export let disabled: boolean;
|
||||||
|
export let onOpenActivityTab: () => void;
|
||||||
const dispatch = createEventDispatcher<{
|
export let onFavorite: () => void;
|
||||||
openActivityTab: void;
|
|
||||||
favorite: void;
|
|
||||||
}>();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="w-full flex p-4 text-white items-center justify-center rounded-full gap-5 bg-immich-dark-bg bg-opacity-60">
|
<div class="w-full flex p-4 text-white items-center justify-center rounded-full gap-5 bg-immich-dark-bg bg-opacity-60">
|
||||||
<button type="button" class={disabled ? 'cursor-not-allowed' : ''} on:click={() => dispatch('favorite')} {disabled}>
|
<button type="button" class={disabled ? 'cursor-not-allowed' : ''} on:click={onFavorite} {disabled}>
|
||||||
<div class="items-center justify-center">
|
<div class="items-center justify-center">
|
||||||
<Icon path={isLiked ? mdiHeart : mdiHeartOutline} size={24} />
|
<Icon path={isLiked ? mdiHeart : mdiHeartOutline} size={24} />
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" on:click={() => dispatch('openActivityTab')}>
|
<button type="button" on:click={onOpenActivityTab}>
|
||||||
<div class="flex gap-2 items-center justify-center">
|
<div class="flex gap-2 items-center justify-center">
|
||||||
<Icon path={mdiCommentOutline} class="scale-x-[-1]" size={24} />
|
<Icon path={mdiCommentOutline} class="scale-x-[-1]" size={24} />
|
||||||
{#if numberOfComments}
|
{#if numberOfComments}
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
} from '@immich/sdk';
|
} from '@immich/sdk';
|
||||||
import { mdiClose, mdiDotsVertical, mdiHeart, mdiSend, mdiDeleteOutline } from '@mdi/js';
|
import { mdiClose, mdiDotsVertical, mdiHeart, mdiSend, mdiDeleteOutline } from '@mdi/js';
|
||||||
import * as luxon from 'luxon';
|
import * as luxon from 'luxon';
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
|
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
|
||||||
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
||||||
import { NotificationType, notificationController } from '../shared-components/notification/notification';
|
import { NotificationType, notificationController } from '../shared-components/notification/notification';
|
||||||
@ -55,6 +55,10 @@
|
|||||||
export let albumOwnerId: string;
|
export let albumOwnerId: string;
|
||||||
export let disabled: boolean;
|
export let disabled: boolean;
|
||||||
export let isLiked: ActivityResponseDto | null;
|
export let isLiked: ActivityResponseDto | null;
|
||||||
|
export let onDeleteComment: () => void;
|
||||||
|
export let onDeleteLike: () => void;
|
||||||
|
export let onAddComment: () => void;
|
||||||
|
export let onClose: () => void;
|
||||||
|
|
||||||
let textArea: HTMLTextAreaElement;
|
let textArea: HTMLTextAreaElement;
|
||||||
let innerHeight: number;
|
let innerHeight: number;
|
||||||
@ -65,13 +69,6 @@
|
|||||||
let message = '';
|
let message = '';
|
||||||
let isSendingMessage = false;
|
let isSendingMessage = false;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
deleteComment: void;
|
|
||||||
deleteLike: void;
|
|
||||||
addComment: void;
|
|
||||||
close: void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (innerHeight && activityHeight) {
|
if (innerHeight && activityHeight) {
|
||||||
divHeight = innerHeight - activityHeight;
|
divHeight = innerHeight - activityHeight;
|
||||||
@ -111,9 +108,9 @@
|
|||||||
reactions.splice(index, 1);
|
reactions.splice(index, 1);
|
||||||
reactions = reactions;
|
reactions = reactions;
|
||||||
if (isLiked && reaction.type === ReactionType.Like && reaction.id == isLiked.id) {
|
if (isLiked && reaction.type === ReactionType.Like && reaction.id == isLiked.id) {
|
||||||
dispatch('deleteLike');
|
onDeleteLike();
|
||||||
} else {
|
} else {
|
||||||
dispatch('deleteComment');
|
onDeleteComment();
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteMessages: Record<ReactionType, string> = {
|
const deleteMessages: Record<ReactionType, string> = {
|
||||||
@ -141,7 +138,7 @@
|
|||||||
reactions.push(data);
|
reactions.push(data);
|
||||||
textArea.style.height = '18px';
|
textArea.style.height = '18px';
|
||||||
message = '';
|
message = '';
|
||||||
dispatch('addComment');
|
onAddComment();
|
||||||
// Re-render the activity feed
|
// Re-render the activity feed
|
||||||
reactions = reactions;
|
reactions = reactions;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -160,7 +157,7 @@
|
|||||||
bind:clientHeight={activityHeight}
|
bind:clientHeight={activityHeight}
|
||||||
>
|
>
|
||||||
<div class="flex place-items-center gap-2">
|
<div class="flex place-items-center gap-2">
|
||||||
<CircleIconButton on:click={() => dispatch('close')} icon={mdiClose} title={$t('close')} />
|
<CircleIconButton on:click={onClose} icon={mdiClose} title={$t('close')} />
|
||||||
|
|
||||||
<p class="text-lg text-immich-fg dark:text-immich-dark-fg">{$t('activity')}</p>
|
<p class="text-lg text-immich-fg dark:text-immich-dark-fg">{$t('activity')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,16 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getAssetThumbnailUrl } from '$lib/utils';
|
import { getAssetThumbnailUrl } from '$lib/utils';
|
||||||
import { type AlbumResponseDto } from '@immich/sdk';
|
import { type AlbumResponseDto } from '@immich/sdk';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import { normalizeSearchString } from '$lib/utils/string-utils.js';
|
import { normalizeSearchString } from '$lib/utils/string-utils.js';
|
||||||
import AlbumListItemDetails from './album-list-item-details.svelte';
|
import AlbumListItemDetails from './album-list-item-details.svelte';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
album: void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
export let album: AlbumResponseDto;
|
export let album: AlbumResponseDto;
|
||||||
export let searchQuery = '';
|
export let searchQuery = '';
|
||||||
|
export let onAlbumClick: () => void;
|
||||||
|
|
||||||
let albumNameArray: string[] = ['', '', ''];
|
let albumNameArray: string[] = ['', '', ''];
|
||||||
|
|
||||||
// This part of the code is responsible for splitting album name into 3 parts where part 2 is the search query
|
// This part of the code is responsible for splitting album name into 3 parts where part 2 is the search query
|
||||||
@ -29,7 +26,7 @@
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
on:click={() => dispatch('album')}
|
on:click={onAlbumClick}
|
||||||
class="flex w-full gap-4 px-6 py-2 text-left transition-colors hover:bg-gray-200 dark:hover:bg-gray-700 rounded-xl"
|
class="flex w-full gap-4 px-6 py-2 text-left transition-colors hover:bg-gray-200 dark:hover:bg-gray-700 rounded-xl"
|
||||||
>
|
>
|
||||||
<span class="h-12 w-12 shrink-0 rounded-xl bg-slate-300">
|
<span class="h-12 w-12 shrink-0 rounded-xl bg-slate-300">
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
type AssetResponseDto,
|
type AssetResponseDto,
|
||||||
type StackResponseDto,
|
type StackResponseDto,
|
||||||
} from '@immich/sdk';
|
} from '@immich/sdk';
|
||||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import Thumbnail from '../assets/thumbnail/thumbnail.svelte';
|
import Thumbnail from '../assets/thumbnail/thumbnail.svelte';
|
||||||
@ -56,8 +56,10 @@
|
|||||||
export let isShared = false;
|
export let isShared = false;
|
||||||
export let album: AlbumResponseDto | null = null;
|
export let album: AlbumResponseDto | null = null;
|
||||||
export let onAction: OnAction | undefined = undefined;
|
export let onAction: OnAction | undefined = undefined;
|
||||||
|
export let reactions: ActivityResponseDto[] = [];
|
||||||
let reactions: ActivityResponseDto[] = [];
|
export let onClose: (dto: { asset: AssetResponseDto }) => void;
|
||||||
|
export let onNext: () => void;
|
||||||
|
export let onPrevious: () => void;
|
||||||
|
|
||||||
const { setAsset } = assetViewingStore;
|
const { setAsset } = assetViewingStore;
|
||||||
const {
|
const {
|
||||||
@ -67,13 +69,6 @@
|
|||||||
slideshowState,
|
slideshowState,
|
||||||
} = slideshowStore;
|
} = slideshowStore;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
action: { type: AssetAction; asset: AssetResponseDto };
|
|
||||||
close: { asset: AssetResponseDto };
|
|
||||||
next: void;
|
|
||||||
previous: void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
let appearsInAlbums: AlbumResponseDto[] = [];
|
let appearsInAlbums: AlbumResponseDto[] = [];
|
||||||
let shouldPlayMotionPhoto = false;
|
let shouldPlayMotionPhoto = false;
|
||||||
let sharedLink = getSharedLink();
|
let sharedLink = getSharedLink();
|
||||||
@ -267,7 +262,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const closeViewer = () => {
|
const closeViewer = () => {
|
||||||
dispatch('close', { asset });
|
onClose({ asset });
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeEditor = () => {
|
const closeEditor = () => {
|
||||||
@ -316,7 +311,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
e?.stopPropagation();
|
e?.stopPropagation();
|
||||||
dispatch(order);
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||||
|
order === 'previous' ? onPrevious() : onNext();
|
||||||
};
|
};
|
||||||
|
|
||||||
// const showEditorHandler = () => {
|
// const showEditorHandler = () => {
|
||||||
@ -533,8 +529,8 @@
|
|||||||
disabled={!album?.isActivityEnabled}
|
disabled={!album?.isActivityEnabled}
|
||||||
{isLiked}
|
{isLiked}
|
||||||
{numberOfComments}
|
{numberOfComments}
|
||||||
on:favorite={handleFavorite}
|
onFavorite={handleFavorite}
|
||||||
on:openActivityTab={handleOpenActivity}
|
onOpenActivityTab={handleOpenActivity}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@ -555,7 +551,7 @@
|
|||||||
class="z-[1002] row-start-1 row-span-4 w-[360px] overflow-y-auto bg-immich-bg transition-all dark:border-l dark:border-l-immich-dark-gray dark:bg-immich-dark-bg"
|
class="z-[1002] row-start-1 row-span-4 w-[360px] overflow-y-auto bg-immich-bg transition-all dark:border-l dark:border-l-immich-dark-gray dark:bg-immich-dark-bg"
|
||||||
translate="yes"
|
translate="yes"
|
||||||
>
|
>
|
||||||
<DetailPanel {asset} currentAlbum={album} albums={appearsInAlbums} on:close={() => ($isShowDetail = false)} />
|
<DetailPanel {asset} currentAlbum={album} albums={appearsInAlbums} onClose={() => ($isShowDetail = false)} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@ -625,10 +621,10 @@
|
|||||||
assetId={asset.id}
|
assetId={asset.id}
|
||||||
{isLiked}
|
{isLiked}
|
||||||
bind:reactions
|
bind:reactions
|
||||||
on:addComment={handleAddComment}
|
onAddComment={handleAddComment}
|
||||||
on:deleteComment={handleRemoveComment}
|
onDeleteComment={handleRemoveComment}
|
||||||
on:deleteLike={() => (isLiked = null)}
|
onDeleteLike={() => (isLiked = null)}
|
||||||
on:close={() => (isShowActivity = false)}
|
onClose={() => (isShowActivity = false)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -81,10 +81,6 @@
|
|||||||
|
|
||||||
{#if isShowChangeLocation}
|
{#if isShowChangeLocation}
|
||||||
<Portal>
|
<Portal>
|
||||||
<ChangeLocation
|
<ChangeLocation {asset} onConfirm={handleConfirmChangeLocation} onCancel={() => (isShowChangeLocation = false)} />
|
||||||
{asset}
|
|
||||||
on:confirm={({ detail: gps }) => handleConfirmChangeLocation(gps)}
|
|
||||||
on:cancel={() => (isShowChangeLocation = false)}
|
|
||||||
/>
|
|
||||||
</Portal>
|
</Portal>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -36,7 +36,6 @@
|
|||||||
mdiPencil,
|
mdiPencil,
|
||||||
} from '@mdi/js';
|
} from '@mdi/js';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import { slide } from 'svelte/transition';
|
import { slide } from 'svelte/transition';
|
||||||
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
|
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
|
||||||
@ -49,6 +48,7 @@
|
|||||||
export let asset: AssetResponseDto;
|
export let asset: AssetResponseDto;
|
||||||
export let albums: AlbumResponseDto[] = [];
|
export let albums: AlbumResponseDto[] = [];
|
||||||
export let currentAlbum: AlbumResponseDto | null = null;
|
export let currentAlbum: AlbumResponseDto | null = null;
|
||||||
|
export let onClose: () => void;
|
||||||
|
|
||||||
const getDimensions = (exifInfo: ExifResponseDto) => {
|
const getDimensions = (exifInfo: ExifResponseDto) => {
|
||||||
const { exifImageWidth: width, exifImageHeight: height } = exifInfo;
|
const { exifImageWidth: width, exifImageHeight: height } = exifInfo;
|
||||||
@ -106,10 +106,6 @@
|
|||||||
? fromDateTimeOriginal(asset.exifInfo.dateTimeOriginal, timeZone)
|
? fromDateTimeOriginal(asset.exifInfo.dateTimeOriginal, timeZone)
|
||||||
: fromLocalDateTime(asset.localDateTime);
|
: fromLocalDateTime(asset.localDateTime);
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
close: void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const getMegapixel = (width: number, height: number): number | undefined => {
|
const getMegapixel = (width: number, height: number): number | undefined => {
|
||||||
const megapixel = Math.round((height * width) / 1_000_000);
|
const megapixel = Math.round((height * width) / 1_000_000);
|
||||||
|
|
||||||
@ -144,7 +140,7 @@
|
|||||||
|
|
||||||
<section class="relative p-2 dark:bg-immich-dark-bg dark:text-immich-dark-fg">
|
<section class="relative p-2 dark:bg-immich-dark-bg dark:text-immich-dark-fg">
|
||||||
<div class="flex place-items-center gap-2">
|
<div class="flex place-items-center gap-2">
|
||||||
<CircleIconButton icon={mdiClose} title={$t('close')} on:click={() => dispatch('close')} />
|
<CircleIconButton icon={mdiClose} title={$t('close')} on:click={onClose} />
|
||||||
<p class="text-lg text-immich-fg dark:text-immich-dark-fg">{$t('info')}</p>
|
<p class="text-lg text-immich-fg dark:text-immich-dark-fg">{$t('info')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -332,8 +328,8 @@
|
|||||||
<ChangeDate
|
<ChangeDate
|
||||||
initialDate={dateTime}
|
initialDate={dateTime}
|
||||||
initialTimeZone={timeZone ?? ''}
|
initialTimeZone={timeZone ?? ''}
|
||||||
on:confirm={({ detail: date }) => handleConfirmChangeDate(date)}
|
onConfirm={handleConfirmChangeDate}
|
||||||
on:cancel={() => (isShowChangeDate = false)}
|
onCancel={() => (isShowChangeDate = false)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@ -511,9 +507,7 @@
|
|||||||
<PersonSidePanel
|
<PersonSidePanel
|
||||||
assetId={asset.id}
|
assetId={asset.id}
|
||||||
assetType={asset.type}
|
assetType={asset.type}
|
||||||
on:close={() => {
|
onClose={() => (showEditFaces = false)}
|
||||||
showEditFaces = false;
|
onRefresh={handleRefreshPeople}
|
||||||
}}
|
|
||||||
on:refresh={handleRefreshPeople}
|
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -139,5 +139,5 @@
|
|||||||
duration={$slideshowDelay}
|
duration={$slideshowDelay}
|
||||||
bind:this={progressBar}
|
bind:this={progressBar}
|
||||||
bind:status={progressBarStatus}
|
bind:status={progressBarStatus}
|
||||||
on:done={handleDone}
|
onDone={handleDone}
|
||||||
/>
|
/>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
import { getAssetPlaybackUrl, getAssetThumbnailUrl } from '$lib/utils';
|
import { getAssetPlaybackUrl, getAssetThumbnailUrl } from '$lib/utils';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { AssetMediaSize } from '@immich/sdk';
|
import { AssetMediaSize } from '@immich/sdk';
|
||||||
import { createEventDispatcher, tick } from 'svelte';
|
import { tick } from 'svelte';
|
||||||
import { swipe } from 'svelte-gestures';
|
import { swipe } from 'svelte-gestures';
|
||||||
import type { SwipeCustomEvent } from 'svelte-gestures';
|
import type { SwipeCustomEvent } from 'svelte-gestures';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
@ -13,8 +13,10 @@
|
|||||||
export let assetId: string;
|
export let assetId: string;
|
||||||
export let loopVideo: boolean;
|
export let loopVideo: boolean;
|
||||||
export let checksum: string;
|
export let checksum: string;
|
||||||
export let onPreviousAsset: () => void;
|
export let onPreviousAsset: () => void = () => {};
|
||||||
export let onNextAsset: () => void;
|
export let onNextAsset: () => void = () => {};
|
||||||
|
export let onVideoEnded: () => void = () => {};
|
||||||
|
export let onVideoStarted: () => void = () => {};
|
||||||
|
|
||||||
let element: HTMLVideoElement | undefined = undefined;
|
let element: HTMLVideoElement | undefined = undefined;
|
||||||
let isVideoLoading = true;
|
let isVideoLoading = true;
|
||||||
@ -27,12 +29,10 @@
|
|||||||
element.load();
|
element.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{ onVideoEnded: void; onVideoStarted: void }>();
|
|
||||||
|
|
||||||
const handleCanPlay = async (video: HTMLVideoElement) => {
|
const handleCanPlay = async (video: HTMLVideoElement) => {
|
||||||
try {
|
try {
|
||||||
await video.play();
|
await video.play();
|
||||||
dispatch('onVideoStarted');
|
onVideoStarted();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof DOMException && error.name === 'NotAllowedError' && !forceMuted) {
|
if (error instanceof DOMException && error.name === 'NotAllowedError' && !forceMuted) {
|
||||||
await tryForceMutedPlay(video);
|
await tryForceMutedPlay(video);
|
||||||
@ -75,7 +75,7 @@
|
|||||||
use:swipe
|
use:swipe
|
||||||
on:swipe={onSwipe}
|
on:swipe={onSwipe}
|
||||||
on:canplay={(e) => handleCanPlay(e.currentTarget)}
|
on:canplay={(e) => handleCanPlay(e.currentTarget)}
|
||||||
on:ended={() => dispatch('onVideoEnded')}
|
on:ended={onVideoEnded}
|
||||||
on:volumechange={(e) => {
|
on:volumechange={(e) => {
|
||||||
if (!forceMuted) {
|
if (!forceMuted) {
|
||||||
$videoViewerMuted = e.currentTarget.muted;
|
$videoViewerMuted = e.currentTarget.muted;
|
||||||
|
@ -15,13 +15,5 @@
|
|||||||
{#if projectionType === ProjectionType.EQUIRECTANGULAR}
|
{#if projectionType === ProjectionType.EQUIRECTANGULAR}
|
||||||
<PanoramaViewer asset={{ id: assetId, type: AssetTypeEnum.Video }} />
|
<PanoramaViewer asset={{ id: assetId, type: AssetTypeEnum.Video }} />
|
||||||
{:else}
|
{:else}
|
||||||
<VideoNativeViewer
|
<VideoNativeViewer {loopVideo} {checksum} {assetId} {onPreviousAsset} {onNextAsset} />
|
||||||
{loopVideo}
|
|
||||||
{checksum}
|
|
||||||
{assetId}
|
|
||||||
{onPreviousAsset}
|
|
||||||
{onNextAsset}
|
|
||||||
on:onVideoEnded
|
|
||||||
on:onVideoStarted
|
|
||||||
/>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -19,22 +19,18 @@
|
|||||||
import LinkButton from './buttons/link-button.svelte';
|
import LinkButton from './buttons/link-button.svelte';
|
||||||
import { clickOutside } from '$lib/actions/click-outside';
|
import { clickOutside } from '$lib/actions/click-outside';
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
|
|
||||||
let className = '';
|
let className = '';
|
||||||
export { className as class };
|
export { className as class };
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
select: T;
|
|
||||||
'click-outside': void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
export let options: T[];
|
export let options: T[];
|
||||||
export let selectedOption = options[0];
|
export let selectedOption = options[0];
|
||||||
export let showMenu = false;
|
export let showMenu = false;
|
||||||
export let controlable = false;
|
export let controlable = false;
|
||||||
export let hideTextOnSmallScreen = true;
|
export let hideTextOnSmallScreen = true;
|
||||||
export let title: string | undefined = undefined;
|
export let title: string | undefined = undefined;
|
||||||
|
export let onSelect: (option: T) => void;
|
||||||
|
export let onClickOutside: () => void = () => {};
|
||||||
|
|
||||||
export let render: (item: T) => string | RenderedOption = String;
|
export let render: (item: T) => string | RenderedOption = String;
|
||||||
|
|
||||||
@ -43,11 +39,11 @@
|
|||||||
showMenu = false;
|
showMenu = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch('click-outside');
|
onClickOutside();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelectOption = (option: T) => {
|
const handleSelectOption = (option: T) => {
|
||||||
dispatch('select', option);
|
onSelect(option);
|
||||||
selectedOption = option;
|
selectedOption = option;
|
||||||
|
|
||||||
showMenu = false;
|
showMenu = false;
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { mdiClose, mdiMagnify } from '@mdi/js';
|
import { mdiClose, mdiMagnify } from '@mdi/js';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import type { SearchOptions } from '$lib/utils/dipatch';
|
import type { SearchOptions } from '$lib/utils/dipatch';
|
||||||
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
||||||
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
||||||
@ -10,20 +9,20 @@
|
|||||||
export let roundedBottom = true;
|
export let roundedBottom = true;
|
||||||
export let showLoadingSpinner: boolean;
|
export let showLoadingSpinner: boolean;
|
||||||
export let placeholder: string;
|
export let placeholder: string;
|
||||||
|
export let onSearch: (options: SearchOptions) => void = () => {};
|
||||||
|
export let onReset: () => void = () => {};
|
||||||
|
|
||||||
let inputRef: HTMLElement;
|
let inputRef: HTMLElement;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{ search: SearchOptions; reset: void }>();
|
|
||||||
|
|
||||||
const resetSearch = () => {
|
const resetSearch = () => {
|
||||||
name = '';
|
name = '';
|
||||||
dispatch('reset');
|
onReset();
|
||||||
inputRef?.focus();
|
inputRef?.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSearch = (event: KeyboardEvent) => {
|
const handleSearch = (event: KeyboardEvent) => {
|
||||||
if (event.key === 'Enter') {
|
if (event.key === 'Enter') {
|
||||||
dispatch('search', { force: true });
|
onSearch({ force: true });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@ -38,7 +37,7 @@
|
|||||||
title={$t('search')}
|
title={$t('search')}
|
||||||
size="16"
|
size="16"
|
||||||
padding="2"
|
padding="2"
|
||||||
on:click={() => dispatch('search', { force: true })}
|
on:click={() => onSearch({ force: true })}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
class="w-full gap-2 bg-gray-200 dark:bg-immich-dark-gray dark:text-white"
|
class="w-full gap-2 bg-gray-200 dark:bg-immich-dark-gray dark:text-white"
|
||||||
@ -47,7 +46,7 @@
|
|||||||
bind:value={name}
|
bind:value={name}
|
||||||
bind:this={inputRef}
|
bind:this={inputRef}
|
||||||
on:keydown={handleSearch}
|
on:keydown={handleSearch}
|
||||||
on:input={() => dispatch('search', { force: false })}
|
on:input={() => onSearch({ force: false })}
|
||||||
/>
|
/>
|
||||||
{#if showLoadingSpinner}
|
{#if showLoadingSpinner}
|
||||||
<div class="flex place-items-center">
|
<div class="flex place-items-center">
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
import { getPeopleThumbnailUrl } from '$lib/utils';
|
import { getPeopleThumbnailUrl } from '$lib/utils';
|
||||||
import { AssetTypeEnum, type AssetFaceResponseDto, type PersonResponseDto } from '@immich/sdk';
|
import { AssetTypeEnum, type AssetFaceResponseDto, type PersonResponseDto } from '@immich/sdk';
|
||||||
import { mdiArrowLeftThin, mdiClose, mdiMagnify, mdiPlus } from '@mdi/js';
|
import { mdiArrowLeftThin, mdiClose, mdiMagnify, mdiPlus } from '@mdi/js';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import { linear } from 'svelte/easing';
|
import { linear } from 'svelte/easing';
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import { photoViewer } from '$lib/stores/assets.store';
|
import { photoViewer } from '$lib/stores/assets.store';
|
||||||
@ -19,6 +18,9 @@
|
|||||||
export let editedFace: AssetFaceResponseDto;
|
export let editedFace: AssetFaceResponseDto;
|
||||||
export let assetId: string;
|
export let assetId: string;
|
||||||
export let assetType: AssetTypeEnum;
|
export let assetType: AssetTypeEnum;
|
||||||
|
export let onClose: () => void;
|
||||||
|
export let onCreatePerson: (featurePhoto: string | null) => void;
|
||||||
|
export let onReassign: (person: PersonResponseDto) => void;
|
||||||
|
|
||||||
// loading spinners
|
// loading spinners
|
||||||
let isShowLoadingNewPerson = false;
|
let isShowLoadingNewPerson = false;
|
||||||
@ -31,25 +33,16 @@
|
|||||||
|
|
||||||
$: showPeople = searchName ? searchedPeople : allPeople.filter((person) => !person.isHidden);
|
$: showPeople = searchName ? searchedPeople : allPeople.filter((person) => !person.isHidden);
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
close: void;
|
|
||||||
createPerson: string | null;
|
|
||||||
reassign: PersonResponseDto;
|
|
||||||
}>();
|
|
||||||
const handleBackButton = () => {
|
|
||||||
dispatch('close');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCreatePerson = async () => {
|
const handleCreatePerson = async () => {
|
||||||
const timeout = setTimeout(() => (isShowLoadingNewPerson = true), timeBeforeShowLoadingSpinner);
|
const timeout = setTimeout(() => (isShowLoadingNewPerson = true), timeBeforeShowLoadingSpinner);
|
||||||
|
|
||||||
const newFeaturePhoto = await zoomImageToBase64(editedFace, assetId, assetType, $photoViewer);
|
const newFeaturePhoto = await zoomImageToBase64(editedFace, assetId, assetType, $photoViewer);
|
||||||
|
|
||||||
dispatch('createPerson', newFeaturePhoto);
|
onCreatePerson(newFeaturePhoto);
|
||||||
|
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
isShowLoadingNewPerson = false;
|
isShowLoadingNewPerson = false;
|
||||||
dispatch('createPerson', newFeaturePhoto);
|
onCreatePerson(newFeaturePhoto);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -60,7 +53,7 @@
|
|||||||
<div class="flex place-items-center justify-between gap-2">
|
<div class="flex place-items-center justify-between gap-2">
|
||||||
{#if !searchFaces}
|
{#if !searchFaces}
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<CircleIconButton icon={mdiArrowLeftThin} title={$t('back')} on:click={handleBackButton} />
|
<CircleIconButton icon={mdiArrowLeftThin} title={$t('back')} on:click={onClose} />
|
||||||
<p class="flex text-lg text-immich-fg dark:text-immich-dark-fg">{$t('select_face')}</p>
|
<p class="flex text-lg text-immich-fg dark:text-immich-dark-fg">{$t('select_face')}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-end gap-2">
|
<div class="flex justify-end gap-2">
|
||||||
@ -80,7 +73,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<CircleIconButton icon={mdiArrowLeftThin} title={$t('back')} on:click={handleBackButton} />
|
<CircleIconButton icon={mdiArrowLeftThin} title={$t('back')} on:click={onClose} />
|
||||||
<div class="w-full flex">
|
<div class="w-full flex">
|
||||||
<SearchPeople
|
<SearchPeople
|
||||||
type="input"
|
type="input"
|
||||||
@ -103,7 +96,7 @@
|
|||||||
{#each showPeople as person (person.id)}
|
{#each showPeople as person (person.id)}
|
||||||
{#if !editedFace.person || person.id !== editedFace.person.id}
|
{#if !editedFace.person || person.id !== editedFace.person.id}
|
||||||
<div class="w-fit">
|
<div class="w-fit">
|
||||||
<button type="button" class="w-[90px]" on:click={() => dispatch('reassign', person)}>
|
<button type="button" class="w-[90px]" on:click={() => onReassign(person)}>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<ImageThumbnail
|
<ImageThumbnail
|
||||||
curve
|
curve
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { type PersonResponseDto } from '@immich/sdk';
|
import { type PersonResponseDto } from '@immich/sdk';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
|
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
|
||||||
import Button from '../elements/buttons/button.svelte';
|
import Button from '../elements/buttons/button.svelte';
|
||||||
import SearchPeople from '$lib/components/faces-page/people-search.svelte';
|
import SearchPeople from '$lib/components/faces-page/people-search.svelte';
|
||||||
@ -11,10 +10,7 @@
|
|||||||
export let suggestedPeople: PersonResponseDto[];
|
export let suggestedPeople: PersonResponseDto[];
|
||||||
export let thumbnailData: string;
|
export let thumbnailData: string;
|
||||||
export let isSearchingPeople: boolean;
|
export let isSearchingPeople: boolean;
|
||||||
|
export let onChange: (name: string) => void;
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
change: string;
|
|
||||||
}>();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@ -26,7 +22,7 @@
|
|||||||
<form
|
<form
|
||||||
class="ml-4 flex w-full justify-between gap-16"
|
class="ml-4 flex w-full justify-between gap-16"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
on:submit|preventDefault={() => dispatch('change', name)}
|
on:submit|preventDefault={() => onChange(name)}
|
||||||
>
|
>
|
||||||
<SearchPeople
|
<SearchPeople
|
||||||
bind:searchName={name}
|
bind:searchName={name}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getPeopleThumbnailUrl } from '$lib/utils';
|
import { getPeopleThumbnailUrl } from '$lib/utils';
|
||||||
import { type PersonResponseDto } from '@immich/sdk';
|
import { type PersonResponseDto } from '@immich/sdk';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
|
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
|
||||||
|
|
||||||
export let person: PersonResponseDto;
|
export let person: PersonResponseDto;
|
||||||
@ -10,20 +9,13 @@
|
|||||||
export let thumbnailSize: number | null = null;
|
export let thumbnailSize: number | null = null;
|
||||||
export let circle = false;
|
export let circle = false;
|
||||||
export let border = false;
|
export let border = false;
|
||||||
|
export let onClick: (person: PersonResponseDto) => void = () => {};
|
||||||
let dispatch = createEventDispatcher<{
|
|
||||||
click: PersonResponseDto;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const handleOnClicked = () => {
|
|
||||||
dispatch('click', person);
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="relative rounded-lg transition-all"
|
class="relative rounded-lg transition-all"
|
||||||
on:click={handleOnClicked}
|
on:click={() => onClick(person)}
|
||||||
disabled={!selectable}
|
disabled={!selectable}
|
||||||
style:width={thumbnailSize ? thumbnailSize + 'px' : '100%'}
|
style:width={thumbnailSize ? thumbnailSize + 'px' : '100%'}
|
||||||
style:height={thumbnailSize ? thumbnailSize + 'px' : '100%'}
|
style:height={thumbnailSize ? thumbnailSize + 'px' : '100%'}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { getAllPeople, getPerson, mergePerson, type PersonResponseDto } from '@immich/sdk';
|
import { getAllPeople, getPerson, mergePerson, type PersonResponseDto } from '@immich/sdk';
|
||||||
import { mdiCallMerge, mdiMerge, mdiSwapHorizontal } from '@mdi/js';
|
import { mdiCallMerge, mdiMerge, mdiSwapHorizontal } from '@mdi/js';
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { flip } from 'svelte/animate';
|
import { flip } from 'svelte/animate';
|
||||||
import { quintOut } from 'svelte/easing';
|
import { quintOut } from 'svelte/easing';
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
@ -20,15 +20,13 @@
|
|||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
export let person: PersonResponseDto;
|
export let person: PersonResponseDto;
|
||||||
|
export let onBack: () => void;
|
||||||
|
export let onMerge: (mergedPerson: PersonResponseDto) => void;
|
||||||
|
|
||||||
let people: PersonResponseDto[] = [];
|
let people: PersonResponseDto[] = [];
|
||||||
let selectedPeople: PersonResponseDto[] = [];
|
let selectedPeople: PersonResponseDto[] = [];
|
||||||
let screenHeight: number;
|
let screenHeight: number;
|
||||||
|
|
||||||
let dispatch = createEventDispatcher<{
|
|
||||||
back: void;
|
|
||||||
merge: PersonResponseDto;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
$: hasSelection = selectedPeople.length > 0;
|
$: hasSelection = selectedPeople.length > 0;
|
||||||
$: peopleToNotShow = [...selectedPeople, person];
|
$: peopleToNotShow = [...selectedPeople, person];
|
||||||
|
|
||||||
@ -37,10 +35,6 @@
|
|||||||
people = data.people;
|
people = data.people;
|
||||||
});
|
});
|
||||||
|
|
||||||
const onClose = () => {
|
|
||||||
dispatch('back');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSwapPeople = async () => {
|
const handleSwapPeople = async () => {
|
||||||
[person, selectedPeople[0]] = [selectedPeople[0], person];
|
[person, selectedPeople[0]] = [selectedPeople[0], person];
|
||||||
$page.url.searchParams.set(QueryParameter.ACTION, ActionQueryParameterValue.MERGE);
|
$page.url.searchParams.set(QueryParameter.ACTION, ActionQueryParameterValue.MERGE);
|
||||||
@ -88,7 +82,7 @@
|
|||||||
message: $t('merged_people_count', { values: { count } }),
|
message: $t('merged_people_count', { values: { count } }),
|
||||||
type: NotificationType.Info,
|
type: NotificationType.Info,
|
||||||
});
|
});
|
||||||
dispatch('merge', mergedPerson);
|
onMerge(mergedPerson);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, $t('cannot_merge_people'));
|
handleError(error, $t('cannot_merge_people'));
|
||||||
}
|
}
|
||||||
@ -101,7 +95,7 @@
|
|||||||
transition:fly={{ y: 500, duration: 100, easing: quintOut }}
|
transition:fly={{ y: 500, duration: 100, easing: quintOut }}
|
||||||
class="absolute left-0 top-0 z-[9999] h-full w-full bg-immich-bg dark:bg-immich-dark-bg"
|
class="absolute left-0 top-0 z-[9999] h-full w-full bg-immich-bg dark:bg-immich-dark-bg"
|
||||||
>
|
>
|
||||||
<ControlAppBar on:close={onClose}>
|
<ControlAppBar onClose={onBack}>
|
||||||
<svelte:fragment slot="leading">
|
<svelte:fragment slot="leading">
|
||||||
{#if hasSelection}
|
{#if hasSelection}
|
||||||
{$t('selected_count', { values: { count: selectedPeople.length } })}
|
{$t('selected_count', { values: { count: selectedPeople.length } })}
|
||||||
@ -125,7 +119,7 @@
|
|||||||
<div class="grid grid-flow-col-dense place-content-center place-items-center gap-4">
|
<div class="grid grid-flow-col-dense place-content-center place-items-center gap-4">
|
||||||
{#each selectedPeople as person (person.id)}
|
{#each selectedPeople as person (person.id)}
|
||||||
<div animate:flip={{ duration: 250, easing: quintOut }}>
|
<div animate:flip={{ duration: 250, easing: quintOut }}>
|
||||||
<FaceThumbnail border circle {person} selectable thumbnailSize={120} on:click={() => onSelect(person)} />
|
<FaceThumbnail border circle {person} selectable thumbnailSize={120} onClick={() => onSelect(person)} />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
@ -152,7 +146,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PeopleList {people} {peopleToNotShow} {screenHeight} on:select={({ detail }) => onSelect(detail)} />
|
<PeopleList {people} {peopleToNotShow} {screenHeight} {onSelect} />
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
import { getPeopleThumbnailUrl } from '$lib/utils';
|
import { getPeopleThumbnailUrl } from '$lib/utils';
|
||||||
import { type PersonResponseDto } from '@immich/sdk';
|
import { type PersonResponseDto } from '@immich/sdk';
|
||||||
import { mdiArrowLeft, mdiMerge } from '@mdi/js';
|
import { mdiArrowLeft, mdiMerge } from '@mdi/js';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
|
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
|
||||||
import Button from '../elements/buttons/button.svelte';
|
import Button from '../elements/buttons/button.svelte';
|
||||||
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
|
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
|
||||||
@ -13,25 +12,22 @@
|
|||||||
export let personMerge1: PersonResponseDto;
|
export let personMerge1: PersonResponseDto;
|
||||||
export let personMerge2: PersonResponseDto;
|
export let personMerge2: PersonResponseDto;
|
||||||
export let potentialMergePeople: PersonResponseDto[];
|
export let potentialMergePeople: PersonResponseDto[];
|
||||||
|
export let onReject: () => void;
|
||||||
|
export let onConfirm: ([personMerge1, personMerge2]: [PersonResponseDto, PersonResponseDto]) => void;
|
||||||
|
export let onClose: () => void;
|
||||||
|
|
||||||
let choosePersonToMerge = false;
|
let choosePersonToMerge = false;
|
||||||
|
|
||||||
const title = personMerge2.name;
|
const title = personMerge2.name;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
const changePersonToMerge = (newPerson: PersonResponseDto) => {
|
||||||
reject: void;
|
const index = potentialMergePeople.indexOf(newPerson);
|
||||||
confirm: [PersonResponseDto, PersonResponseDto];
|
|
||||||
close: void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const changePersonToMerge = (newperson: PersonResponseDto) => {
|
|
||||||
const index = potentialMergePeople.indexOf(newperson);
|
|
||||||
[potentialMergePeople[index], personMerge2] = [personMerge2, potentialMergePeople[index]];
|
[potentialMergePeople[index], personMerge2] = [personMerge2, potentialMergePeople[index]];
|
||||||
choosePersonToMerge = false;
|
choosePersonToMerge = false;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FullScreenModal title="{$t('merge_people')} - {title}" onClose={() => dispatch('close')}>
|
<FullScreenModal title="{$t('merge_people')} - {title}" {onClose}>
|
||||||
<div class="flex items-center justify-center py-4 md:h-36 md:py-4">
|
<div class="flex items-center justify-center py-4 md:h-36 md:py-4">
|
||||||
{#if !choosePersonToMerge}
|
{#if !choosePersonToMerge}
|
||||||
<div class="flex h-20 w-20 items-center px-1 md:h-24 md:w-24 md:px-2">
|
<div class="flex h-20 w-20 items-center px-1 md:h-24 md:w-24 md:px-2">
|
||||||
@ -105,7 +101,7 @@
|
|||||||
<p class="text-sm text-gray-500 dark:text-gray-300">{$t('they_will_be_merged_together')}</p>
|
<p class="text-sm text-gray-500 dark:text-gray-300">{$t('they_will_be_merged_together')}</p>
|
||||||
</div>
|
</div>
|
||||||
<svelte:fragment slot="sticky-bottom">
|
<svelte:fragment slot="sticky-bottom">
|
||||||
<Button fullwidth color="gray" on:click={() => dispatch('reject')}>{$t('no')}</Button>
|
<Button fullwidth color="gray" on:click={onReject}>{$t('no')}</Button>
|
||||||
<Button fullwidth on:click={() => dispatch('confirm', [personMerge1, personMerge2])}>{$t('yes')}</Button>
|
<Button fullwidth on:click={() => onConfirm([personMerge1, personMerge2])}>{$t('yes')}</Button>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</FullScreenModal>
|
</FullScreenModal>
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
mdiDotsVertical,
|
mdiDotsVertical,
|
||||||
mdiEyeOffOutline,
|
mdiEyeOffOutline,
|
||||||
} from '@mdi/js';
|
} from '@mdi/js';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
|
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
|
||||||
import MenuOption from '../shared-components/context-menu/menu-option.svelte';
|
import MenuOption from '../shared-components/context-menu/menu-option.svelte';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
@ -18,19 +17,12 @@
|
|||||||
|
|
||||||
export let person: PersonResponseDto;
|
export let person: PersonResponseDto;
|
||||||
export let preload = false;
|
export let preload = false;
|
||||||
|
export let onChangeName: () => void;
|
||||||
type MenuItemEvent = 'change-name' | 'set-birth-date' | 'merge-people' | 'hide-person';
|
export let onSetBirthDate: () => void;
|
||||||
let dispatch = createEventDispatcher<{
|
export let onMergePeople: () => void;
|
||||||
'change-name': void;
|
export let onHidePerson: () => void;
|
||||||
'set-birth-date': void;
|
|
||||||
'merge-people': void;
|
|
||||||
'hide-person': void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
let showVerticalDots = false;
|
let showVerticalDots = false;
|
||||||
const onMenuClick = (event: MenuItemEvent) => {
|
|
||||||
dispatch(event);
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@ -76,18 +68,10 @@
|
|||||||
icon={mdiDotsVertical}
|
icon={mdiDotsVertical}
|
||||||
title={$t('show_person_options')}
|
title={$t('show_person_options')}
|
||||||
>
|
>
|
||||||
<MenuOption onClick={() => onMenuClick('hide-person')} icon={mdiEyeOffOutline} text={$t('hide_person')} />
|
<MenuOption onClick={onHidePerson} icon={mdiEyeOffOutline} text={$t('hide_person')} />
|
||||||
<MenuOption onClick={() => onMenuClick('change-name')} icon={mdiAccountEditOutline} text={$t('change_name')} />
|
<MenuOption onClick={onChangeName} icon={mdiAccountEditOutline} text={$t('change_name')} />
|
||||||
<MenuOption
|
<MenuOption onClick={onSetBirthDate} icon={mdiCalendarEditOutline} text={$t('set_date_of_birth')} />
|
||||||
onClick={() => onMenuClick('set-birth-date')}
|
<MenuOption onClick={onMergePeople} icon={mdiAccountMultipleCheckOutline} text={$t('merge_people')} />
|
||||||
icon={mdiCalendarEditOutline}
|
|
||||||
text={$t('set_date_of_birth')}
|
|
||||||
/>
|
|
||||||
<MenuOption
|
|
||||||
onClick={() => onMenuClick('merge-people')}
|
|
||||||
icon={mdiAccountMultipleCheckOutline}
|
|
||||||
text={$t('merge_people')}
|
|
||||||
/>
|
|
||||||
</ButtonContextMenu>
|
</ButtonContextMenu>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { type PersonResponseDto } from '@immich/sdk';
|
import { type PersonResponseDto } from '@immich/sdk';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import FaceThumbnail from './face-thumbnail.svelte';
|
import FaceThumbnail from './face-thumbnail.svelte';
|
||||||
import SearchPeople from '$lib/components/faces-page/people-search.svelte';
|
import SearchPeople from '$lib/components/faces-page/people-search.svelte';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
@ -8,15 +7,13 @@
|
|||||||
export let screenHeight: number;
|
export let screenHeight: number;
|
||||||
export let people: PersonResponseDto[];
|
export let people: PersonResponseDto[];
|
||||||
export let peopleToNotShow: PersonResponseDto[];
|
export let peopleToNotShow: PersonResponseDto[];
|
||||||
|
export let onSelect: (person: PersonResponseDto) => void;
|
||||||
|
|
||||||
let searchedPeopleLocal: PersonResponseDto[] = [];
|
let searchedPeopleLocal: PersonResponseDto[] = [];
|
||||||
|
|
||||||
let name = '';
|
let name = '';
|
||||||
let showPeople: PersonResponseDto[];
|
let showPeople: PersonResponseDto[];
|
||||||
|
|
||||||
let dispatch = createEventDispatcher<{
|
|
||||||
select: PersonResponseDto;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
showPeople = name ? searchedPeopleLocal : people;
|
showPeople = name ? searchedPeopleLocal : people;
|
||||||
showPeople = showPeople.filter(
|
showPeople = showPeople.filter(
|
||||||
@ -35,15 +32,7 @@
|
|||||||
>
|
>
|
||||||
<div class="grid-col-2 grid gap-8 md:grid-cols-3 lg:grid-cols-6 xl:grid-cols-8 2xl:grid-cols-10">
|
<div class="grid-col-2 grid gap-8 md:grid-cols-3 lg:grid-cols-6 xl:grid-cols-8 2xl:grid-cols-10">
|
||||||
{#each showPeople as person (person.id)}
|
{#each showPeople as person (person.id)}
|
||||||
<FaceThumbnail
|
<FaceThumbnail {person} on:click={() => onSelect(person)} circle border selectable />
|
||||||
{person}
|
|
||||||
on:click={() => {
|
|
||||||
dispatch('select', person);
|
|
||||||
}}
|
|
||||||
circle
|
|
||||||
border
|
|
||||||
selectable
|
|
||||||
/>
|
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
import { mdiAccountOff } from '@mdi/js';
|
import { mdiAccountOff } from '@mdi/js';
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
import { mdiArrowLeftThin, mdiMinus, mdiRestart } from '@mdi/js';
|
import { mdiArrowLeftThin, mdiMinus, mdiRestart } from '@mdi/js';
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { linear } from 'svelte/easing';
|
import { linear } from 'svelte/easing';
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
|
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
|
||||||
@ -31,6 +31,8 @@
|
|||||||
|
|
||||||
export let assetId: string;
|
export let assetId: string;
|
||||||
export let assetType: AssetTypeEnum;
|
export let assetType: AssetTypeEnum;
|
||||||
|
export let onClose: () => void;
|
||||||
|
export let onRefresh: () => void;
|
||||||
|
|
||||||
// keep track of the changes
|
// keep track of the changes
|
||||||
let peopleToCreate: string[] = [];
|
let peopleToCreate: string[] = [];
|
||||||
@ -56,11 +58,6 @@
|
|||||||
|
|
||||||
const thumbnailWidth = '90px';
|
const thumbnailWidth = '90px';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
close: void;
|
|
||||||
refresh: void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
async function loadPeople() {
|
async function loadPeople() {
|
||||||
const timeout = setTimeout(() => (isShowLoadingPeople = true), timeBeforeShowLoadingSpinner);
|
const timeout = setTimeout(() => (isShowLoadingPeople = true), timeBeforeShowLoadingSpinner);
|
||||||
try {
|
try {
|
||||||
@ -85,7 +82,7 @@
|
|||||||
) {
|
) {
|
||||||
clearTimeout(loaderLoadingDoneTimeout);
|
clearTimeout(loaderLoadingDoneTimeout);
|
||||||
clearTimeout(automaticRefreshTimeout);
|
clearTimeout(automaticRefreshTimeout);
|
||||||
dispatch('refresh');
|
onRefresh();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -98,10 +95,6 @@
|
|||||||
return b.every((valueB) => a.includes(valueB));
|
return b.every((valueB) => a.includes(valueB));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBackButton = () => {
|
|
||||||
dispatch('close');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleReset = (id: string) => {
|
const handleReset = (id: string) => {
|
||||||
if (selectedPersonToReassign[id]) {
|
if (selectedPersonToReassign[id]) {
|
||||||
delete selectedPersonToReassign[id];
|
delete selectedPersonToReassign[id];
|
||||||
@ -153,9 +146,9 @@
|
|||||||
isShowLoadingDone = false;
|
isShowLoadingDone = false;
|
||||||
if (peopleToCreate.length === 0) {
|
if (peopleToCreate.length === 0) {
|
||||||
clearTimeout(loaderLoadingDoneTimeout);
|
clearTimeout(loaderLoadingDoneTimeout);
|
||||||
dispatch('refresh');
|
onRefresh();
|
||||||
} else {
|
} else {
|
||||||
automaticRefreshTimeout = setTimeout(() => dispatch('refresh'), 15_000);
|
automaticRefreshTimeout = setTimeout(onRefresh, 15_000);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -185,7 +178,7 @@
|
|||||||
>
|
>
|
||||||
<div class="flex place-items-center justify-between gap-2">
|
<div class="flex place-items-center justify-between gap-2">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<CircleIconButton icon={mdiArrowLeftThin} title={$t('back')} on:click={handleBackButton} />
|
<CircleIconButton icon={mdiArrowLeftThin} title={$t('back')} on:click={onClose} />
|
||||||
<p class="flex text-lg text-immich-fg dark:text-immich-dark-fg">{$t('edit_faces')}</p>
|
<p class="flex text-lg text-immich-fg dark:text-immich-dark-fg">{$t('edit_faces')}</p>
|
||||||
</div>
|
</div>
|
||||||
{#if !isShowLoadingDone}
|
{#if !isShowLoadingDone}
|
||||||
@ -336,8 +329,8 @@
|
|||||||
{editedFace}
|
{editedFace}
|
||||||
{assetId}
|
{assetId}
|
||||||
{assetType}
|
{assetType}
|
||||||
on:close={() => (showSelectedFaces = false)}
|
onClose={() => (showSelectedFaces = false)}
|
||||||
on:createPerson={(event) => handleCreatePerson(event.detail)}
|
onCreatePerson={handleCreatePerson}
|
||||||
on:reassign={(event) => handleReassignFace(event.detail)}
|
onReassign={handleReassignFace}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import Button from '../elements/buttons/button.svelte';
|
import Button from '../elements/buttons/button.svelte';
|
||||||
import FullScreenModal from '../shared-components/full-screen-modal.svelte';
|
import FullScreenModal from '../shared-components/full-screen-modal.svelte';
|
||||||
import { mdiCake } from '@mdi/js';
|
import { mdiCake } from '@mdi/js';
|
||||||
@ -7,28 +6,20 @@
|
|||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
export let birthDate: string;
|
export let birthDate: string;
|
||||||
|
export let onClose: () => void;
|
||||||
const dispatch = createEventDispatcher<{
|
export let onUpdate: (birthDate: string) => void;
|
||||||
close: void;
|
|
||||||
updated: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const todayFormatted = new Date().toISOString().split('T')[0];
|
const todayFormatted = new Date().toISOString().split('T')[0];
|
||||||
|
|
||||||
const handleCancel = () => dispatch('close');
|
|
||||||
const handleSubmit = () => {
|
|
||||||
dispatch('updated', birthDate);
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FullScreenModal title={$t('set_date_of_birth')} icon={mdiCake} onClose={handleCancel}>
|
<FullScreenModal title={$t('set_date_of_birth')} icon={mdiCake} {onClose}>
|
||||||
<div class="text-immich-primary dark:text-immich-dark-primary">
|
<div class="text-immich-primary dark:text-immich-dark-primary">
|
||||||
<p class="text-sm dark:text-immich-dark-fg">
|
<p class="text-sm dark:text-immich-dark-fg">
|
||||||
{$t('birthdate_set_description')}
|
{$t('birthdate_set_description')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form on:submit|preventDefault={() => handleSubmit()} autocomplete="off" id="set-birth-date-form">
|
<form on:submit|preventDefault={() => onUpdate(birthDate)} autocomplete="off" id="set-birth-date-form">
|
||||||
<div class="my-4 flex flex-col gap-2">
|
<div class="my-4 flex flex-col gap-2">
|
||||||
<DateInput
|
<DateInput
|
||||||
class="immich-form-input"
|
class="immich-form-input"
|
||||||
@ -41,7 +32,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<svelte:fragment slot="sticky-bottom">
|
<svelte:fragment slot="sticky-bottom">
|
||||||
<Button color="gray" fullwidth on:click={() => handleCancel()}>{$t('cancel')}</Button>
|
<Button color="gray" fullwidth on:click={onClose}>{$t('cancel')}</Button>
|
||||||
<Button type="submit" fullwidth form="set-birth-date-form">{$t('set')}</Button>
|
<Button type="submit" fullwidth form="set-birth-date-form">{$t('set')}</Button>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</FullScreenModal>
|
</FullScreenModal>
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
type PersonResponseDto,
|
type PersonResponseDto,
|
||||||
} from '@immich/sdk';
|
} from '@immich/sdk';
|
||||||
import { mdiMerge, mdiPlus } from '@mdi/js';
|
import { mdiMerge, mdiPlus } from '@mdi/js';
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { quintOut } from 'svelte/easing';
|
import { quintOut } from 'svelte/easing';
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import Button from '../elements/buttons/button.svelte';
|
import Button from '../elements/buttons/button.svelte';
|
||||||
@ -23,6 +23,8 @@
|
|||||||
|
|
||||||
export let assetIds: string[];
|
export let assetIds: string[];
|
||||||
export let personAssets: PersonResponseDto;
|
export let personAssets: PersonResponseDto;
|
||||||
|
export let onConfirm: () => void;
|
||||||
|
export let onClose: () => void;
|
||||||
|
|
||||||
let people: PersonResponseDto[] = [];
|
let people: PersonResponseDto[] = [];
|
||||||
let selectedPerson: PersonResponseDto | null = null;
|
let selectedPerson: PersonResponseDto | null = null;
|
||||||
@ -34,11 +36,6 @@
|
|||||||
|
|
||||||
$: peopleToNotShow = selectedPerson ? [personAssets, selectedPerson] : [personAssets];
|
$: peopleToNotShow = selectedPerson ? [personAssets, selectedPerson] : [personAssets];
|
||||||
|
|
||||||
let dispatch = createEventDispatcher<{
|
|
||||||
confirm: void;
|
|
||||||
close: void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const selectedPeople: AssetFaceUpdateItem[] = [];
|
const selectedPeople: AssetFaceUpdateItem[] = [];
|
||||||
|
|
||||||
for (const assetId of assetIds) {
|
for (const assetId of assetIds) {
|
||||||
@ -50,10 +47,6 @@
|
|||||||
people = data.people;
|
people = data.people;
|
||||||
});
|
});
|
||||||
|
|
||||||
const onClose = () => {
|
|
||||||
dispatch('close');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSelectedPerson = (person: PersonResponseDto) => {
|
const handleSelectedPerson = (person: PersonResponseDto) => {
|
||||||
if (selectedPerson && selectedPerson.id === person.id) {
|
if (selectedPerson && selectedPerson.id === person.id) {
|
||||||
handleRemoveSelectedPerson();
|
handleRemoveSelectedPerson();
|
||||||
@ -87,7 +80,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
showLoadingSpinnerCreate = false;
|
showLoadingSpinnerCreate = false;
|
||||||
dispatch('confirm');
|
onConfirm();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReassign = async () => {
|
const handleReassign = async () => {
|
||||||
@ -113,7 +106,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
showLoadingSpinnerReassign = false;
|
showLoadingSpinnerReassign = false;
|
||||||
dispatch('confirm');
|
onConfirm();
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -123,7 +116,7 @@
|
|||||||
transition:fly={{ y: 500, duration: 100, easing: quintOut }}
|
transition:fly={{ y: 500, duration: 100, easing: quintOut }}
|
||||||
class="absolute left-0 top-0 z-[9999] h-full w-full bg-immich-bg dark:bg-immich-dark-bg"
|
class="absolute left-0 top-0 z-[9999] h-full w-full bg-immich-bg dark:bg-immich-dark-bg"
|
||||||
>
|
>
|
||||||
<ControlAppBar on:close={onClose}>
|
<ControlAppBar {onClose}>
|
||||||
<svelte:fragment slot="leading">
|
<svelte:fragment slot="leading">
|
||||||
<slot name="header" />
|
<slot name="header" />
|
||||||
<div />
|
<div />
|
||||||
@ -180,7 +173,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<PeopleList {people} {peopleToNotShow} {screenHeight} on:select={({ detail }) => handleSelectedPerson(detail)} />
|
<PeopleList {people} {peopleToNotShow} {screenHeight} onSelect={handleSelectedPerson} />
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
@ -1,20 +1,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { copyToClipboard } from '$lib/utils';
|
import { copyToClipboard } from '$lib/utils';
|
||||||
import { mdiKeyVariant } from '@mdi/js';
|
import { mdiKeyVariant } from '@mdi/js';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import Button from '../elements/buttons/button.svelte';
|
import Button from '../elements/buttons/button.svelte';
|
||||||
import FullScreenModal from '../shared-components/full-screen-modal.svelte';
|
import FullScreenModal from '../shared-components/full-screen-modal.svelte';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
export let secret = '';
|
export let secret = '';
|
||||||
|
export let onDone: () => void;
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
done: void;
|
|
||||||
}>();
|
|
||||||
const handleDone = () => dispatch('done');
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FullScreenModal title={$t('api_key')} icon={mdiKeyVariant} onClose={() => handleDone()}>
|
<FullScreenModal title={$t('api_key')} icon={mdiKeyVariant} onClose={onDone}>
|
||||||
<div class="text-immich-primary dark:text-immich-dark-primary">
|
<div class="text-immich-primary dark:text-immich-dark-primary">
|
||||||
<p class="text-sm dark:text-immich-dark-fg">
|
<p class="text-sm dark:text-immich-dark-fg">
|
||||||
{$t('api_key_description')}
|
{$t('api_key_description')}
|
||||||
@ -28,6 +23,6 @@
|
|||||||
|
|
||||||
<svelte:fragment slot="sticky-bottom">
|
<svelte:fragment slot="sticky-bottom">
|
||||||
<Button on:click={() => copyToClipboard(secret)} fullwidth>{$t('copy_to_clipboard')}</Button>
|
<Button on:click={() => copyToClipboard(secret)} fullwidth>{$t('copy_to_clipboard')}</Button>
|
||||||
<Button on:click={() => handleDone()} fullwidth>{$t('done')}</Button>
|
<Button on:click={onDone} fullwidth>{$t('done')}</Button>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</FullScreenModal>
|
</FullScreenModal>
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import Button from '../elements/buttons/button.svelte';
|
import Button from '../elements/buttons/button.svelte';
|
||||||
import PasswordField from '../shared-components/password-field.svelte';
|
import PasswordField from '../shared-components/password-field.svelte';
|
||||||
import { updateMyUser } from '@immich/sdk';
|
import { updateMyUser } from '@immich/sdk';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
|
export let onSuccess: () => void;
|
||||||
|
|
||||||
let errorMessage: string;
|
let errorMessage: string;
|
||||||
let success: string;
|
let success: string;
|
||||||
|
|
||||||
@ -23,17 +24,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
success: void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
async function changePassword() {
|
async function changePassword() {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
errorMessage = '';
|
errorMessage = '';
|
||||||
|
|
||||||
await updateMyUser({ userUpdateMeDto: { password: String(password) } });
|
await updateMyUser({ userUpdateMeDto: { password: String(password) } });
|
||||||
|
|
||||||
dispatch('success');
|
onSuccess();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -5,13 +5,14 @@
|
|||||||
import { ByteUnit, convertToBytes } from '$lib/utils/byte-units';
|
import { ByteUnit, convertToBytes } from '$lib/utils/byte-units';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { createUserAdmin } from '@immich/sdk';
|
import { createUserAdmin } from '@immich/sdk';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import Button from '../elements/buttons/button.svelte';
|
import Button from '../elements/buttons/button.svelte';
|
||||||
import Slider from '../elements/slider.svelte';
|
import Slider from '../elements/slider.svelte';
|
||||||
import PasswordField from '../shared-components/password-field.svelte';
|
import PasswordField from '../shared-components/password-field.svelte';
|
||||||
|
|
||||||
export let onClose: () => void;
|
export let onClose: () => void;
|
||||||
|
export let onSubmit: () => void;
|
||||||
|
export let onCancel: () => void;
|
||||||
|
|
||||||
let error: string;
|
let error: string;
|
||||||
let success: string;
|
let success: string;
|
||||||
@ -39,10 +40,6 @@
|
|||||||
canCreateUser = true;
|
canCreateUser = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
submit: void;
|
|
||||||
cancel: void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
async function registerUser() {
|
async function registerUser() {
|
||||||
if (canCreateUser && !isCreatingUser) {
|
if (canCreateUser && !isCreatingUser) {
|
||||||
@ -63,7 +60,7 @@
|
|||||||
|
|
||||||
success = $t('new_user_created');
|
success = $t('new_user_created');
|
||||||
|
|
||||||
dispatch('submit');
|
onSubmit();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -132,7 +129,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</form>
|
</form>
|
||||||
<svelte:fragment slot="sticky-bottom">
|
<svelte:fragment slot="sticky-bottom">
|
||||||
<Button color="gray" fullwidth on:click={() => dispatch('cancel')}>{$t('cancel')}</Button>
|
<Button color="gray" fullwidth on:click={onCancel}>{$t('cancel')}</Button>
|
||||||
<Button type="submit" disabled={isCreatingUser} fullwidth form="create-new-user-form">{$t('create')}</Button>
|
<Button type="submit" disabled={isCreatingUser} fullwidth form="create-new-user-form">{$t('create')}</Button>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</FullScreenModal>
|
</FullScreenModal>
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { updateUserAdmin, type UserAdminResponseDto } from '@immich/sdk';
|
import { updateUserAdmin, type UserAdminResponseDto } from '@immich/sdk';
|
||||||
import { mdiAccountEditOutline } from '@mdi/js';
|
import { mdiAccountEditOutline } from '@mdi/js';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import Button from '../elements/buttons/button.svelte';
|
import Button from '../elements/buttons/button.svelte';
|
||||||
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
|
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
@ -15,6 +14,8 @@
|
|||||||
export let canResetPassword = true;
|
export let canResetPassword = true;
|
||||||
export let newPassword: string;
|
export let newPassword: string;
|
||||||
export let onClose: () => void;
|
export let onClose: () => void;
|
||||||
|
export let onResetPasswordSuccess: () => void;
|
||||||
|
export let onEditSuccess: () => void;
|
||||||
|
|
||||||
let error: string;
|
let error: string;
|
||||||
let success: string;
|
let success: string;
|
||||||
@ -27,12 +28,6 @@
|
|||||||
!!quotaSize &&
|
!!quotaSize &&
|
||||||
convertToBytes(Number(quotaSize), ByteUnit.GiB) > $serverInfo.diskSizeRaw;
|
convertToBytes(Number(quotaSize), ByteUnit.GiB) > $serverInfo.diskSizeRaw;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
close: void;
|
|
||||||
resetPasswordSuccess: void;
|
|
||||||
editSuccess: void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const editUser = async () => {
|
const editUser = async () => {
|
||||||
try {
|
try {
|
||||||
const { id, email, name, storageLabel } = user;
|
const { id, email, name, storageLabel } = user;
|
||||||
@ -46,7 +41,7 @@
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
dispatch('editSuccess');
|
onEditSuccess();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, $t('errors.unable_to_update_user'));
|
handleError(error, $t('errors.unable_to_update_user'));
|
||||||
}
|
}
|
||||||
@ -72,7 +67,7 @@
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
dispatch('resetPasswordSuccess');
|
onResetPasswordSuccess();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, $t('errors.unable_to_reset_password'));
|
handleError(error, $t('errors.unable_to_reset_password'));
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import Button from '../elements/buttons/button.svelte';
|
import Button from '../elements/buttons/button.svelte';
|
||||||
import FullScreenModal from '../shared-components/full-screen-modal.svelte';
|
import FullScreenModal from '../shared-components/full-screen-modal.svelte';
|
||||||
import { mdiFolderRemove } from '@mdi/js';
|
import { mdiFolderRemove } from '@mdi/js';
|
||||||
@ -10,6 +9,9 @@
|
|||||||
export let exclusionPatterns: string[] = [];
|
export let exclusionPatterns: string[] = [];
|
||||||
export let isEditing = false;
|
export let isEditing = false;
|
||||||
export let submitText = $t('submit');
|
export let submitText = $t('submit');
|
||||||
|
export let onCancel: () => void;
|
||||||
|
export let onSubmit: (exclusionPattern: string) => void;
|
||||||
|
export let onDelete: () => void = () => {};
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
@ -19,18 +21,10 @@
|
|||||||
|
|
||||||
$: isDuplicate = exclusionPattern !== null && exclusionPatterns.includes(exclusionPattern);
|
$: isDuplicate = exclusionPattern !== null && exclusionPatterns.includes(exclusionPattern);
|
||||||
$: canSubmit = exclusionPattern && !exclusionPatterns.includes(exclusionPattern);
|
$: canSubmit = exclusionPattern && !exclusionPatterns.includes(exclusionPattern);
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
cancel: void;
|
|
||||||
submit: { excludePattern: string };
|
|
||||||
delete: void;
|
|
||||||
}>();
|
|
||||||
const handleCancel = () => dispatch('cancel');
|
|
||||||
const handleSubmit = () => dispatch('submit', { excludePattern: exclusionPattern });
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FullScreenModal title={$t('add_exclusion_pattern')} icon={mdiFolderRemove} onClose={handleCancel}>
|
<FullScreenModal title={$t('add_exclusion_pattern')} icon={mdiFolderRemove} onClose={onCancel}>
|
||||||
<form on:submit|preventDefault={() => handleSubmit()} autocomplete="off" id="add-exclusion-pattern-form">
|
<form on:submit|preventDefault={() => onSubmit(exclusionPattern)} autocomplete="off" id="add-exclusion-pattern-form">
|
||||||
<p class="py-5 text-sm">
|
<p class="py-5 text-sm">
|
||||||
{$t('admin.exclusion_pattern_description')}
|
{$t('admin.exclusion_pattern_description')}
|
||||||
<br /><br />
|
<br /><br />
|
||||||
@ -53,9 +47,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<svelte:fragment slot="sticky-bottom">
|
<svelte:fragment slot="sticky-bottom">
|
||||||
<Button color="gray" fullwidth on:click={() => handleCancel()}>{$t('cancel')}</Button>
|
<Button color="gray" fullwidth on:click={onCancel}>{$t('cancel')}</Button>
|
||||||
{#if isEditing}
|
{#if isEditing}
|
||||||
<Button color="red" fullwidth on:click={() => dispatch('delete')}>{$t('delete')}</Button>
|
<Button color="red" fullwidth on:click={onDelete}>{$t('delete')}</Button>
|
||||||
{/if}
|
{/if}
|
||||||
<Button type="submit" disabled={!canSubmit} fullwidth form="add-exclusion-pattern-form">{submitText}</Button>
|
<Button type="submit" disabled={!canSubmit} fullwidth form="add-exclusion-pattern-form">{submitText}</Button>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import Button from '../elements/buttons/button.svelte';
|
import Button from '../elements/buttons/button.svelte';
|
||||||
import FullScreenModal from '../shared-components/full-screen-modal.svelte';
|
import FullScreenModal from '../shared-components/full-screen-modal.svelte';
|
||||||
import { mdiFolderSync } from '@mdi/js';
|
import { mdiFolderSync } from '@mdi/js';
|
||||||
@ -12,6 +11,9 @@
|
|||||||
export let cancelText = $t('cancel');
|
export let cancelText = $t('cancel');
|
||||||
export let submitText = $t('save');
|
export let submitText = $t('save');
|
||||||
export let isEditing = false;
|
export let isEditing = false;
|
||||||
|
export let onCancel: () => void;
|
||||||
|
export let onSubmit: (importPath: string | null) => void;
|
||||||
|
export let onDelete: () => void = () => {};
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
@ -21,18 +23,10 @@
|
|||||||
|
|
||||||
$: isDuplicate = importPath !== null && importPaths.includes(importPath);
|
$: isDuplicate = importPath !== null && importPaths.includes(importPath);
|
||||||
$: canSubmit = importPath !== '' && importPath !== null && !importPaths.includes(importPath);
|
$: canSubmit = importPath !== '' && importPath !== null && !importPaths.includes(importPath);
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
cancel: void;
|
|
||||||
submit: { importPath: string | null };
|
|
||||||
delete: void;
|
|
||||||
}>();
|
|
||||||
const handleCancel = () => dispatch('cancel');
|
|
||||||
const handleSubmit = () => dispatch('submit', { importPath });
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FullScreenModal {title} icon={mdiFolderSync} onClose={handleCancel}>
|
<FullScreenModal {title} icon={mdiFolderSync} onClose={onCancel}>
|
||||||
<form on:submit|preventDefault={() => handleSubmit()} autocomplete="off" id="library-import-path-form">
|
<form on:submit|preventDefault={() => onSubmit(importPath)} autocomplete="off" id="library-import-path-form">
|
||||||
<p class="py-5 text-sm">{$t('admin.library_import_path_description')}</p>
|
<p class="py-5 text-sm">{$t('admin.library_import_path_description')}</p>
|
||||||
|
|
||||||
<div class="my-4 flex flex-col gap-2">
|
<div class="my-4 flex flex-col gap-2">
|
||||||
@ -47,9 +41,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<svelte:fragment slot="sticky-bottom">
|
<svelte:fragment slot="sticky-bottom">
|
||||||
<Button color="gray" fullwidth on:click={() => handleCancel()}>{cancelText}</Button>
|
<Button color="gray" fullwidth on:click={onCancel}>{cancelText}</Button>
|
||||||
{#if isEditing}
|
{#if isEditing}
|
||||||
<Button color="red" fullwidth on:click={() => dispatch('delete')}>{$t('delete')}</Button>
|
<Button color="red" fullwidth on:click={onDelete}>{$t('delete')}</Button>
|
||||||
{/if}
|
{/if}
|
||||||
<Button type="submit" disabled={!canSubmit} fullwidth form="library-import-path-form">{submitText}</Button>
|
<Button type="submit" disabled={!canSubmit} fullwidth form="library-import-path-form">{submitText}</Button>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { handleError } from '../../utils/handle-error';
|
import { handleError } from '../../utils/handle-error';
|
||||||
import Button from '../elements/buttons/button.svelte';
|
import Button from '../elements/buttons/button.svelte';
|
||||||
import LibraryImportPathForm from './library-import-path-form.svelte';
|
import LibraryImportPathForm from './library-import-path-form.svelte';
|
||||||
@ -12,6 +12,8 @@
|
|||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
export let library: LibraryResponseDto;
|
export let library: LibraryResponseDto;
|
||||||
|
export let onCancel: () => void;
|
||||||
|
export let onSubmit: (library: LibraryResponseDto) => void;
|
||||||
|
|
||||||
let addImportPath = false;
|
let addImportPath = false;
|
||||||
let editImportPath: number | null = null;
|
let editImportPath: number | null = null;
|
||||||
@ -65,19 +67,6 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
cancel: void;
|
|
||||||
submit: Partial<LibraryResponseDto>;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const handleCancel = () => {
|
|
||||||
dispatch('cancel');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = () => {
|
|
||||||
dispatch('submit', { ...library });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAddImportPath = async () => {
|
const handleAddImportPath = async () => {
|
||||||
if (!addImportPath || !importPathToAdd) {
|
if (!addImportPath || !importPathToAdd) {
|
||||||
return;
|
return;
|
||||||
@ -153,8 +142,8 @@
|
|||||||
submitText={$t('add')}
|
submitText={$t('add')}
|
||||||
bind:importPath={importPathToAdd}
|
bind:importPath={importPathToAdd}
|
||||||
{importPaths}
|
{importPaths}
|
||||||
on:submit={handleAddImportPath}
|
onSubmit={handleAddImportPath}
|
||||||
on:cancel={() => {
|
onCancel={() => {
|
||||||
addImportPath = false;
|
addImportPath = false;
|
||||||
importPathToAdd = null;
|
importPathToAdd = null;
|
||||||
}}
|
}}
|
||||||
@ -168,15 +157,13 @@
|
|||||||
isEditing={true}
|
isEditing={true}
|
||||||
bind:importPath={editedImportPath}
|
bind:importPath={editedImportPath}
|
||||||
{importPaths}
|
{importPaths}
|
||||||
on:submit={handleEditImportPath}
|
onSubmit={handleEditImportPath}
|
||||||
on:delete={handleDeleteImportPath}
|
onDelete={handleDeleteImportPath}
|
||||||
on:cancel={() => {
|
onCancel={() => (editImportPath = null)}
|
||||||
editImportPath = null;
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<form on:submit|preventDefault={() => handleSubmit()} autocomplete="off" class="m-4 flex flex-col gap-4">
|
<form on:submit|preventDefault={() => onSubmit({ ...library })} autocomplete="off" class="m-4 flex flex-col gap-4">
|
||||||
<table class="text-left">
|
<table class="text-left">
|
||||||
<tbody class="block w-full overflow-y-auto rounded-md border dark:border-immich-dark-gray">
|
<tbody class="block w-full overflow-y-auto rounded-md border dark:border-immich-dark-gray">
|
||||||
{#each validatedPaths as validatedPath, listIndex}
|
{#each validatedPaths as validatedPath, listIndex}
|
||||||
@ -251,7 +238,7 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="justify-end gap-2">
|
<div class="justify-end gap-2">
|
||||||
<Button size="sm" color="gray" on:click={() => handleCancel()}>{$t('cancel')}</Button>
|
<Button size="sm" color="gray" on:click={onCancel}>{$t('cancel')}</Button>
|
||||||
<Button size="sm" type="submit">{$t('save')}</Button>
|
<Button size="sm" type="submit">{$t('save')}</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,31 +1,20 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { LibraryResponseDto } from '@immich/sdk';
|
import type { LibraryResponseDto } from '@immich/sdk';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import Button from '../elements/buttons/button.svelte';
|
import Button from '../elements/buttons/button.svelte';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
export let library: Partial<LibraryResponseDto>;
|
export let library: Partial<LibraryResponseDto>;
|
||||||
|
export let onCancel: () => void;
|
||||||
const dispatch = createEventDispatcher<{
|
export let onSubmit: (library: Partial<LibraryResponseDto>) => void;
|
||||||
cancel: void;
|
|
||||||
submit: Partial<LibraryResponseDto>;
|
|
||||||
}>();
|
|
||||||
const handleCancel = () => {
|
|
||||||
dispatch('cancel');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = () => {
|
|
||||||
dispatch('submit', { ...library });
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form on:submit|preventDefault={() => handleSubmit()} autocomplete="off" class="m-4 flex flex-col gap-2">
|
<form on:submit|preventDefault={() => onSubmit({ ...library })} autocomplete="off" class="m-4 flex flex-col gap-2">
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<label class="immich-form-label" for="path">{$t('name')}</label>
|
<label class="immich-form-label" for="path">{$t('name')}</label>
|
||||||
<input class="immich-form-input" id="name" name="name" type="text" bind:value={library.name} />
|
<input class="immich-form-input" id="name" name="name" type="text" bind:value={library.name} />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex w-full justify-end gap-2 pt-2">
|
<div class="flex w-full justify-end gap-2 pt-2">
|
||||||
<Button size="sm" color="gray" on:click={() => handleCancel()}>{$t('cancel')}</Button>
|
<Button size="sm" color="gray" on:click={onCancel}>{$t('cancel')}</Button>
|
||||||
<Button size="sm" type="submit">{$t('save')}</Button>
|
<Button size="sm" type="submit">{$t('save')}</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { type LibraryResponseDto } from '@immich/sdk';
|
import { type LibraryResponseDto } from '@immich/sdk';
|
||||||
import { mdiPencilOutline } from '@mdi/js';
|
import { mdiPencilOutline } from '@mdi/js';
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { handleError } from '../../utils/handle-error';
|
import { handleError } from '../../utils/handle-error';
|
||||||
import Button from '../elements/buttons/button.svelte';
|
import Button from '../elements/buttons/button.svelte';
|
||||||
import LibraryExclusionPatternForm from './library-exclusion-pattern-form.svelte';
|
import LibraryExclusionPatternForm from './library-exclusion-pattern-form.svelte';
|
||||||
@ -9,6 +9,8 @@
|
|||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
export let library: Partial<LibraryResponseDto>;
|
export let library: Partial<LibraryResponseDto>;
|
||||||
|
export let onCancel: () => void;
|
||||||
|
export let onSubmit: (library: Partial<LibraryResponseDto>) => void;
|
||||||
|
|
||||||
let addExclusionPattern = false;
|
let addExclusionPattern = false;
|
||||||
let editExclusionPattern: number | null = null;
|
let editExclusionPattern: number | null = null;
|
||||||
@ -26,18 +28,6 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
cancel: void;
|
|
||||||
submit: Partial<LibraryResponseDto>;
|
|
||||||
}>();
|
|
||||||
const handleCancel = () => {
|
|
||||||
dispatch('cancel');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = () => {
|
|
||||||
dispatch('submit', library);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAddExclusionPattern = () => {
|
const handleAddExclusionPattern = () => {
|
||||||
if (!addExclusionPattern) {
|
if (!addExclusionPattern) {
|
||||||
return;
|
return;
|
||||||
@ -106,10 +96,8 @@
|
|||||||
submitText={$t('add')}
|
submitText={$t('add')}
|
||||||
bind:exclusionPattern={exclusionPatternToAdd}
|
bind:exclusionPattern={exclusionPatternToAdd}
|
||||||
{exclusionPatterns}
|
{exclusionPatterns}
|
||||||
on:submit={handleAddExclusionPattern}
|
onSubmit={handleAddExclusionPattern}
|
||||||
on:cancel={() => {
|
onCancel={() => (addExclusionPattern = false)}
|
||||||
addExclusionPattern = false;
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@ -119,15 +107,13 @@
|
|||||||
isEditing={true}
|
isEditing={true}
|
||||||
bind:exclusionPattern={editedExclusionPattern}
|
bind:exclusionPattern={editedExclusionPattern}
|
||||||
{exclusionPatterns}
|
{exclusionPatterns}
|
||||||
on:submit={handleEditExclusionPattern}
|
onSubmit={handleEditExclusionPattern}
|
||||||
on:delete={handleDeleteExclusionPattern}
|
onDelete={handleDeleteExclusionPattern}
|
||||||
on:cancel={() => {
|
onCancel={() => (editExclusionPattern = null)}
|
||||||
editExclusionPattern = null;
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<form on:submit|preventDefault={() => handleSubmit()} autocomplete="off" class="m-4 flex flex-col gap-4">
|
<form on:submit|preventDefault={() => onSubmit(library)} autocomplete="off" class="m-4 flex flex-col gap-4">
|
||||||
<table class="w-full text-left">
|
<table class="w-full text-left">
|
||||||
<tbody class="block w-full overflow-y-auto rounded-md border dark:border-immich-dark-gray">
|
<tbody class="block w-full overflow-y-auto rounded-md border dark:border-immich-dark-gray">
|
||||||
{#each exclusionPatterns as exclusionPattern, listIndex}
|
{#each exclusionPatterns as exclusionPattern, listIndex}
|
||||||
@ -178,7 +164,7 @@
|
|||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div class="flex w-full justify-end gap-4">
|
<div class="flex w-full justify-end gap-4">
|
||||||
<Button size="sm" color="gray" on:click={() => handleCancel()}>{$t('cancel')}</Button>
|
<Button size="sm" color="gray" on:click={onCancel}>{$t('cancel')}</Button>
|
||||||
<Button size="sm" type="submit">{$t('save')}</Button>
|
<Button size="sm" type="submit">{$t('save')}</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import Button from '../elements/buttons/button.svelte';
|
import Button from '../elements/buttons/button.svelte';
|
||||||
import FullScreenModal from '../shared-components/full-screen-modal.svelte';
|
import FullScreenModal from '../shared-components/full-screen-modal.svelte';
|
||||||
import { mdiFolderSync } from '@mdi/js';
|
import { mdiFolderSync } from '@mdi/js';
|
||||||
@ -9,6 +8,9 @@
|
|||||||
import SettingSelect from '$lib/components/shared-components/settings/setting-select.svelte';
|
import SettingSelect from '$lib/components/shared-components/settings/setting-select.svelte';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
|
export let onCancel: () => void;
|
||||||
|
export let onSubmit: (ownerId: string) => void;
|
||||||
|
|
||||||
let ownerId: string = $user.id;
|
let ownerId: string = $user.id;
|
||||||
|
|
||||||
let userOptions: { value: string; text: string }[] = [];
|
let userOptions: { value: string; text: string }[] = [];
|
||||||
@ -17,25 +19,16 @@
|
|||||||
const users = await searchUsersAdmin({});
|
const users = await searchUsersAdmin({});
|
||||||
userOptions = users.map((user) => ({ value: user.id, text: user.name }));
|
userOptions = users.map((user) => ({ value: user.id, text: user.name }));
|
||||||
});
|
});
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
cancel: void;
|
|
||||||
submit: { ownerId: string };
|
|
||||||
delete: void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const handleCancel = () => dispatch('cancel');
|
|
||||||
const handleSubmit = () => dispatch('submit', { ownerId });
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FullScreenModal title={$t('select_library_owner')} icon={mdiFolderSync} onClose={handleCancel}>
|
<FullScreenModal title={$t('select_library_owner')} icon={mdiFolderSync} onClose={onCancel}>
|
||||||
<form on:submit|preventDefault={() => handleSubmit()} autocomplete="off" id="select-library-owner-form">
|
<form on:submit|preventDefault={() => onSubmit(ownerId)} autocomplete="off" id="select-library-owner-form">
|
||||||
<p class="p-5 text-sm">{$t('admin.note_cannot_be_changed_later')}</p>
|
<p class="p-5 text-sm">{$t('admin.note_cannot_be_changed_later')}</p>
|
||||||
|
|
||||||
<SettingSelect bind:value={ownerId} options={userOptions} name="user" />
|
<SettingSelect bind:value={ownerId} options={userOptions} name="user" />
|
||||||
</form>
|
</form>
|
||||||
<svelte:fragment slot="sticky-bottom">
|
<svelte:fragment slot="sticky-bottom">
|
||||||
<Button color="gray" fullwidth on:click={() => handleCancel()}>{$t('cancel')}</Button>
|
<Button color="gray" fullwidth on:click={onCancel}>{$t('cancel')}</Button>
|
||||||
<Button type="submit" fullwidth form="select-library-owner-form">{$t('create')}</Button>
|
<Button type="submit" fullwidth form="select-library-owner-form">{$t('create')}</Button>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</FullScreenModal>
|
</FullScreenModal>
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
<header>
|
<header>
|
||||||
{#if !hideNavbar}
|
{#if !hideNavbar}
|
||||||
<NavigationBar {showUploadButton} on:uploadClicked={() => openFileUploadDialog()} />
|
<NavigationBar {showUploadButton} onUploadClick={() => openFileUploadDialog()} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<slot name="header" />
|
<slot name="header" />
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
|
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
|
||||||
import type { MapSettings } from '$lib/stores/preferences.store';
|
import type { MapSettings } from '$lib/stores/preferences.store';
|
||||||
import { Duration } from 'luxon';
|
import { Duration } from 'luxon';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import Button from '../elements/buttons/button.svelte';
|
import Button from '../elements/buttons/button.svelte';
|
||||||
@ -12,19 +11,15 @@
|
|||||||
import DateInput from '../elements/date-input.svelte';
|
import DateInput from '../elements/date-input.svelte';
|
||||||
|
|
||||||
export let settings: MapSettings;
|
export let settings: MapSettings;
|
||||||
|
export let onClose: () => void;
|
||||||
|
export let onSave: (settings: MapSettings) => void;
|
||||||
|
|
||||||
let customDateRange = !!settings.dateAfter || !!settings.dateBefore;
|
let customDateRange = !!settings.dateAfter || !!settings.dateBefore;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
close: void;
|
|
||||||
save: MapSettings;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const handleClose = () => dispatch('close');
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FullScreenModal title={$t('map_settings')} onClose={handleClose}>
|
<FullScreenModal title={$t('map_settings')} {onClose}>
|
||||||
<form
|
<form
|
||||||
on:submit|preventDefault={() => dispatch('save', settings)}
|
on:submit|preventDefault={() => onSave(settings)}
|
||||||
class="flex flex-col gap-4 text-immich-primary dark:text-immich-dark-primary"
|
class="flex flex-col gap-4 text-immich-primary dark:text-immich-dark-primary"
|
||||||
id="map-settings-form"
|
id="map-settings-form"
|
||||||
>
|
>
|
||||||
@ -108,7 +103,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</form>
|
</form>
|
||||||
<svelte:fragment slot="sticky-bottom">
|
<svelte:fragment slot="sticky-bottom">
|
||||||
<Button color="gray" size="sm" fullwidth on:click={handleClose}>{$t('cancel')}</Button>
|
<Button color="gray" size="sm" fullwidth on:click={onClose}>{$t('cancel')}</Button>
|
||||||
<Button type="submit" size="sm" fullwidth form="map-settings-form">{$t('save')}</Button>
|
<Button type="submit" size="sm" fullwidth form="map-settings-form">{$t('save')}</Button>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</FullScreenModal>
|
</FullScreenModal>
|
||||||
|
@ -250,7 +250,7 @@
|
|||||||
|
|
||||||
<section id="memory-viewer" class="w-full bg-immich-dark-gray" bind:this={memoryWrapper}>
|
<section id="memory-viewer" class="w-full bg-immich-dark-gray" bind:this={memoryWrapper}>
|
||||||
{#if current && current.memory.assets.length > 0}
|
{#if current && current.memory.assets.length > 0}
|
||||||
<ControlAppBar on:close={() => goto(AppRoute.PHOTOS)} forceDark>
|
<ControlAppBar onClose={() => goto(AppRoute.PHOTOS)} forceDark>
|
||||||
<svelte:fragment slot="leading">
|
<svelte:fragment slot="leading">
|
||||||
<p class="text-lg">
|
<p class="text-lg">
|
||||||
{$memoryLaneTitle(current.memory.yearsAgo)}
|
{$memoryLaneTitle(current.memory.yearsAgo)}
|
||||||
|
@ -40,8 +40,8 @@
|
|||||||
{#if showAlbumPicker}
|
{#if showAlbumPicker}
|
||||||
<AlbumSelectionModal
|
<AlbumSelectionModal
|
||||||
{shared}
|
{shared}
|
||||||
on:newAlbum={({ detail }) => handleAddToNewAlbum(detail)}
|
onNewAlbum={handleAddToNewAlbum}
|
||||||
on:album={({ detail }) => handleAddToAlbum(detail)}
|
onAlbumClick={handleAddToAlbum}
|
||||||
onClose={handleHideAlbumPicker}
|
onClose={handleHideAlbumPicker}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -31,9 +31,5 @@
|
|||||||
<MenuOption text={$t('change_date')} icon={mdiCalendarEditOutline} onClick={() => (isShowChangeDate = true)} />
|
<MenuOption text={$t('change_date')} icon={mdiCalendarEditOutline} onClick={() => (isShowChangeDate = true)} />
|
||||||
{/if}
|
{/if}
|
||||||
{#if isShowChangeDate}
|
{#if isShowChangeDate}
|
||||||
<ChangeDate
|
<ChangeDate initialDate={DateTime.now()} onConfirm={handleConfirm} onCancel={() => (isShowChangeDate = false)} />
|
||||||
initialDate={DateTime.now()}
|
|
||||||
on:confirm={({ detail: date }) => handleConfirm(date)}
|
|
||||||
on:cancel={() => (isShowChangeDate = false)}
|
|
||||||
/>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -35,8 +35,5 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{#if isShowChangeLocation}
|
{#if isShowChangeLocation}
|
||||||
<ChangeLocation
|
<ChangeLocation onConfirm={handleConfirm} onCancel={() => (isShowChangeLocation = false)} />
|
||||||
on:confirm={({ detail: point }) => handleConfirm(point)}
|
|
||||||
on:cancel={() => (isShowChangeLocation = false)}
|
|
||||||
/>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -49,7 +49,7 @@
|
|||||||
{#if isShowConfirmation}
|
{#if isShowConfirmation}
|
||||||
<DeleteAssetDialog
|
<DeleteAssetDialog
|
||||||
size={getOwnedAssets().size}
|
size={getOwnedAssets().size}
|
||||||
on:confirm={handleDelete}
|
onConfirm={handleDelete}
|
||||||
on:cancel={() => (isShowConfirmation = false)}
|
onCancel={() => (isShowConfirmation = false)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
import { findTotalOffset, type DateGroup, type ScrollTargetListener } from '$lib/utils/timeline-util';
|
import { findTotalOffset, type DateGroup, type ScrollTargetListener } from '$lib/utils/timeline-util';
|
||||||
import type { AssetResponseDto } from '@immich/sdk';
|
import type { AssetResponseDto } from '@immich/sdk';
|
||||||
import { mdiCheckCircle, mdiCircleOutline } from '@mdi/js';
|
import { mdiCheckCircle, mdiCircleOutline } from '@mdi/js';
|
||||||
import { createEventDispatcher, onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import Thumbnail from '../assets/thumbnail/thumbnail.svelte';
|
import Thumbnail from '../assets/thumbnail/thumbnail.svelte';
|
||||||
import { TUNABLES } from '$lib/utils/tunables';
|
import { TUNABLES } from '$lib/utils/tunables';
|
||||||
@ -29,6 +29,9 @@
|
|||||||
|
|
||||||
export let onScrollTarget: ScrollTargetListener | undefined = undefined;
|
export let onScrollTarget: ScrollTargetListener | undefined = undefined;
|
||||||
export let onAssetInGrid: ((asset: AssetResponseDto) => void) | undefined = undefined;
|
export let onAssetInGrid: ((asset: AssetResponseDto) => void) | undefined = undefined;
|
||||||
|
export let onSelect: ({ title, assets }: { title: string; assets: AssetResponseDto[] }) => void;
|
||||||
|
export let onSelectAssets: (asset: AssetResponseDto) => void;
|
||||||
|
export let onSelectAssetCandidates: (asset: AssetResponseDto | null) => void;
|
||||||
|
|
||||||
const componentId = generateId();
|
const componentId = generateId();
|
||||||
$: bucketDate = bucket.bucketDate;
|
$: bucketDate = bucket.bucketDate;
|
||||||
@ -41,11 +44,6 @@
|
|||||||
const TITLE_HEIGHT = 51;
|
const TITLE_HEIGHT = 51;
|
||||||
|
|
||||||
const { selectedGroup, selectedAssets, assetSelectionCandidates, isMultiSelectState } = assetInteractionStore;
|
const { selectedGroup, selectedAssets, assetSelectionCandidates, isMultiSelectState } = assetInteractionStore;
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
select: { title: string; assets: AssetResponseDto[] };
|
|
||||||
selectAssets: AssetResponseDto;
|
|
||||||
selectAssetCandidates: AssetResponseDto | null;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
let isMouseOverGroup = false;
|
let isMouseOverGroup = false;
|
||||||
let hoveredDateGroup = '';
|
let hoveredDateGroup = '';
|
||||||
@ -65,10 +63,10 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelectGroup = (title: string, assets: AssetResponseDto[]) => dispatch('select', { title, assets });
|
const handleSelectGroup = (title: string, assets: AssetResponseDto[]) => onSelect({ title, assets });
|
||||||
|
|
||||||
const assetSelectHandler = (asset: AssetResponseDto, assetsInDateGroup: AssetResponseDto[], groupTitle: string) => {
|
const assetSelectHandler = (asset: AssetResponseDto, assetsInDateGroup: AssetResponseDto[], groupTitle: string) => {
|
||||||
dispatch('selectAssets', asset);
|
onSelectAssets(asset);
|
||||||
|
|
||||||
// Check if all assets are selected in a group to toggle the group selection's icon
|
// Check if all assets are selected in a group to toggle the group selection's icon
|
||||||
let selectedAssetsInGroupCount = assetsInDateGroup.filter((asset) => $selectedAssets.has(asset)).length;
|
let selectedAssetsInGroupCount = assetsInDateGroup.filter((asset) => $selectedAssets.has(asset)).length;
|
||||||
@ -86,7 +84,7 @@
|
|||||||
hoveredDateGroup = groupTitle;
|
hoveredDateGroup = groupTitle;
|
||||||
|
|
||||||
if ($isMultiSelectState) {
|
if ($isMultiSelectState) {
|
||||||
dispatch('selectAssetCandidates', asset);
|
onSelectAssetCandidates(asset);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
import { TUNABLES } from '$lib/utils/tunables';
|
import { TUNABLES } from '$lib/utils/tunables';
|
||||||
import type { AlbumResponseDto, AssetResponseDto } from '@immich/sdk';
|
import type { AlbumResponseDto, AssetResponseDto } from '@immich/sdk';
|
||||||
import { throttle } from 'lodash-es';
|
import { throttle } from 'lodash-es';
|
||||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import Portal from '../shared-components/portal/portal.svelte';
|
import Portal from '../shared-components/portal/portal.svelte';
|
||||||
import Scrubber from '../shared-components/scrubber/scrubber.svelte';
|
import Scrubber from '../shared-components/scrubber/scrubber.svelte';
|
||||||
import ShowShortcuts from '../shared-components/show-shortcuts.svelte';
|
import ShowShortcuts from '../shared-components/show-shortcuts.svelte';
|
||||||
@ -64,6 +64,8 @@
|
|||||||
export let isShared = false;
|
export let isShared = false;
|
||||||
export let album: AlbumResponseDto | null = null;
|
export let album: AlbumResponseDto | null = null;
|
||||||
export let isShowDeleteConfirmation = false;
|
export let isShowDeleteConfirmation = false;
|
||||||
|
export let onSelect: (asset: AssetResponseDto) => void = () => {};
|
||||||
|
export let onEscape: () => void = () => {};
|
||||||
|
|
||||||
let { isViewing: showAssetViewer, asset: viewingAsset, preloadAssets, gridScrollTarget } = assetViewingStore;
|
let { isViewing: showAssetViewer, asset: viewingAsset, preloadAssets, gridScrollTarget } = assetViewingStore;
|
||||||
const { assetSelectionCandidates, assetSelectionStart, selectedGroup, selectedAssets, isMultiSelectState } =
|
const { assetSelectionCandidates, assetSelectionStart, selectedGroup, selectedAssets, isMultiSelectState } =
|
||||||
@ -127,8 +129,6 @@
|
|||||||
},
|
},
|
||||||
} = TUNABLES;
|
} = TUNABLES;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{ select: AssetResponseDto; escape: void }>();
|
|
||||||
|
|
||||||
const isViewportOrigin = () => {
|
const isViewportOrigin = () => {
|
||||||
return viewport.height === 0 && viewport.width === 0;
|
return viewport.height === 0 && viewport.width === 0;
|
||||||
};
|
};
|
||||||
@ -447,7 +447,7 @@
|
|||||||
const ids = await stackAssets(Array.from($selectedAssets));
|
const ids = await stackAssets(Array.from($selectedAssets));
|
||||||
if (ids) {
|
if (ids) {
|
||||||
$assetStore.removeAssets(ids);
|
$assetStore.removeAssets(ids);
|
||||||
dispatch('escape');
|
onEscape();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -471,7 +471,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const shortcuts: ShortcutOptions[] = [
|
const shortcuts: ShortcutOptions[] = [
|
||||||
{ shortcut: { key: 'Escape' }, onShortcut: () => dispatch('escape') },
|
{ shortcut: { key: 'Escape' }, onShortcut: onEscape },
|
||||||
{ shortcut: { key: '?', shift: true }, onShortcut: () => (showShortcuts = !showShortcuts) },
|
{ shortcut: { key: '?', shift: true }, onShortcut: () => (showShortcuts = !showShortcuts) },
|
||||||
{ shortcut: { key: '/' }, onShortcut: () => goto(AppRoute.EXPLORE) },
|
{ shortcut: { key: '/' }, onShortcut: () => goto(AppRoute.EXPLORE) },
|
||||||
{ shortcut: { key: 'A', ctrl: true }, onShortcut: () => selectAllAssets($assetStore, assetInteractionStore) },
|
{ shortcut: { key: 'A', ctrl: true }, onShortcut: () => selectAllAssets($assetStore, assetInteractionStore) },
|
||||||
@ -539,7 +539,7 @@
|
|||||||
return !!nextAsset;
|
return !!nextAsset;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = async ({ detail: { asset } }: { detail: { asset: AssetResponseDto } }) => {
|
const handleClose = async ({ asset }: { asset: AssetResponseDto }) => {
|
||||||
assetViewingStore.showAssetViewer(false);
|
assetViewingStore.showAssetViewer(false);
|
||||||
showSkeleton = true;
|
showSkeleton = true;
|
||||||
$gridScrollTarget = { at: asset.id };
|
$gridScrollTarget = { at: asset.id };
|
||||||
@ -554,7 +554,7 @@
|
|||||||
case AssetAction.DELETE: {
|
case AssetAction.DELETE: {
|
||||||
// find the next asset to show or close the viewer
|
// find the next asset to show or close the viewer
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||||
(await handleNext()) || (await handlePrevious()) || (await handleClose({ detail: { asset: action.asset } }));
|
(await handleNext()) || (await handlePrevious()) || (await handleClose({ asset: action.asset }));
|
||||||
|
|
||||||
// delete after find the next one
|
// delete after find the next one
|
||||||
assetStore.removeAssets([action.asset.id]);
|
assetStore.removeAssets([action.asset.id]);
|
||||||
@ -649,7 +649,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch('select', asset);
|
onSelect(asset);
|
||||||
|
|
||||||
if (singleSelect) {
|
if (singleSelect) {
|
||||||
element.scrollTop = 0;
|
element.scrollTop = 0;
|
||||||
@ -754,8 +754,8 @@
|
|||||||
{#if isShowDeleteConfirmation}
|
{#if isShowDeleteConfirmation}
|
||||||
<DeleteAssetDialog
|
<DeleteAssetDialog
|
||||||
size={idsSelectedAssets.length}
|
size={idsSelectedAssets.length}
|
||||||
on:cancel={() => (isShowDeleteConfirmation = false)}
|
onCancel={() => (isShowDeleteConfirmation = false)}
|
||||||
on:confirm={() => handlePromiseError(trashOrDelete(true))}
|
onConfirm={() => handlePromiseError(trashOrDelete(true))}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@ -847,9 +847,9 @@
|
|||||||
{onAssetInGrid}
|
{onAssetInGrid}
|
||||||
{bucket}
|
{bucket}
|
||||||
viewport={safeViewport}
|
viewport={safeViewport}
|
||||||
on:select={({ detail: group }) => handleGroupSelect(group.title, group.assets)}
|
onSelect={({ title, assets }) => handleGroupSelect(title, assets)}
|
||||||
on:selectAssetCandidates={({ detail: asset }) => handleSelectAssetCandidates(asset)}
|
onSelectAssetCandidates={handleSelectAssetCandidates}
|
||||||
on:selectAssets={({ detail: asset }) => handleSelectAssets(asset)}
|
onSelectAssets={handleSelectAssets}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
@ -869,9 +869,9 @@
|
|||||||
{isShared}
|
{isShared}
|
||||||
{album}
|
{album}
|
||||||
onAction={handleAction}
|
onAction={handleAction}
|
||||||
on:previous={handlePrevious}
|
onPrevious={handlePrevious}
|
||||||
on:next={handleNext}
|
onNext={handleNext}
|
||||||
on:close={handleClose}
|
onClose={handleClose}
|
||||||
/>
|
/>
|
||||||
{/await}
|
{/await}
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ControlAppBar on:close={clearSelect} backIcon={mdiClose} tailwindClasses="bg-white shadow-md">
|
<ControlAppBar onClose={clearSelect} backIcon={mdiClose} tailwindClasses="bg-white shadow-md">
|
||||||
<div class="font-medium text-immich-primary dark:text-immich-dark-primary" slot="leading">
|
<div class="font-medium text-immich-primary dark:text-immich-dark-primary" slot="leading">
|
||||||
<p class="block sm:hidden">{assets.size}</p>
|
<p class="block sm:hidden">{assets.size}</p>
|
||||||
<p class="hidden sm:block">{$t('selected_count', { values: { count: assets.size } })}</p>
|
<p class="hidden sm:block">{$t('selected_count', { values: { count: assets.size } })}</p>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import ConfirmDialog from '../shared-components/dialog/confirm-dialog.svelte';
|
import ConfirmDialog from '../shared-components/dialog/confirm-dialog.svelte';
|
||||||
import { showDeleteModal } from '$lib/stores/preferences.store';
|
import { showDeleteModal } from '$lib/stores/preferences.store';
|
||||||
import Checkbox from '$lib/components/elements/checkbox.svelte';
|
import Checkbox from '$lib/components/elements/checkbox.svelte';
|
||||||
@ -7,19 +6,16 @@
|
|||||||
import FormatMessage from '$lib/components/i18n/format-message.svelte';
|
import FormatMessage from '$lib/components/i18n/format-message.svelte';
|
||||||
|
|
||||||
export let size: number;
|
export let size: number;
|
||||||
|
export let onConfirm: () => void;
|
||||||
|
export let onCancel: () => void;
|
||||||
|
|
||||||
let checked = false;
|
let checked = false;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
confirm: void;
|
|
||||||
cancel: void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const handleConfirm = () => {
|
const handleConfirm = () => {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
$showDeleteModal = false;
|
$showDeleteModal = false;
|
||||||
}
|
}
|
||||||
dispatch('confirm');
|
onConfirm();
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -27,7 +23,7 @@
|
|||||||
title={$t('permanently_delete_assets_count', { values: { count: size } })}
|
title={$t('permanently_delete_assets_count', { values: { count: size } })}
|
||||||
confirmText={$t('delete')}
|
confirmText={$t('delete')}
|
||||||
onConfirm={handleConfirm}
|
onConfirm={handleConfirm}
|
||||||
onCancel={() => dispatch('cancel')}
|
{onCancel}
|
||||||
>
|
>
|
||||||
<svelte:fragment slot="prompt">
|
<svelte:fragment slot="prompt">
|
||||||
<p>
|
<p>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import Icon from '$lib/components/elements/icon.svelte';
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
import { getAllAlbums, type AlbumResponseDto } from '@immich/sdk';
|
import { getAllAlbums, type AlbumResponseDto } from '@immich/sdk';
|
||||||
import { mdiPlus } from '@mdi/js';
|
import { mdiPlus } from '@mdi/js';
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import AlbumListItem from '../asset-viewer/album-list-item.svelte';
|
import AlbumListItem from '../asset-viewer/album-list-item.svelte';
|
||||||
import { normalizeSearchString } from '$lib/utils/string-utils';
|
import { normalizeSearchString } from '$lib/utils/string-utils';
|
||||||
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
|
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
|
||||||
@ -11,17 +11,15 @@
|
|||||||
import { sortAlbums } from '$lib/utils/album-utils';
|
import { sortAlbums } from '$lib/utils/album-utils';
|
||||||
import { albumViewSettings } from '$lib/stores/preferences.store';
|
import { albumViewSettings } from '$lib/stores/preferences.store';
|
||||||
|
|
||||||
|
export let onNewAlbum: (search: string) => void;
|
||||||
|
export let onAlbumClick: (album: AlbumResponseDto) => void;
|
||||||
|
|
||||||
let albums: AlbumResponseDto[] = [];
|
let albums: AlbumResponseDto[] = [];
|
||||||
let recentAlbums: AlbumResponseDto[] = [];
|
let recentAlbums: AlbumResponseDto[] = [];
|
||||||
let filteredAlbums: AlbumResponseDto[] = [];
|
let filteredAlbums: AlbumResponseDto[] = [];
|
||||||
let loading = true;
|
let loading = true;
|
||||||
let search = '';
|
let search = '';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
newAlbum: string;
|
|
||||||
album: AlbumResponseDto;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
export let shared: boolean;
|
export let shared: boolean;
|
||||||
export let onClose: () => void;
|
export let onClose: () => void;
|
||||||
|
|
||||||
@ -40,14 +38,6 @@
|
|||||||
{ sortBy: $albumViewSettings.sortBy, orderBy: $albumViewSettings.sortOrder },
|
{ sortBy: $albumViewSettings.sortBy, orderBy: $albumViewSettings.sortOrder },
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSelect = (album: AlbumResponseDto) => {
|
|
||||||
dispatch('album', album);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleNew = () => {
|
|
||||||
dispatch('newAlbum', search.length > 0 ? search : '');
|
|
||||||
};
|
|
||||||
|
|
||||||
const getTitle = () => {
|
const getTitle = () => {
|
||||||
if (shared) {
|
if (shared) {
|
||||||
return $t('add_to_shared_album');
|
return $t('add_to_shared_album');
|
||||||
@ -81,7 +71,7 @@
|
|||||||
<div class="immich-scrollbar overflow-y-auto">
|
<div class="immich-scrollbar overflow-y-auto">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
on:click={handleNew}
|
on:click={() => onNewAlbum(search)}
|
||||||
class="flex w-full items-center gap-4 px-6 py-2 transition-colors hover:bg-gray-200 dark:hover:bg-gray-700 rounded-xl"
|
class="flex w-full items-center gap-4 px-6 py-2 transition-colors hover:bg-gray-200 dark:hover:bg-gray-700 rounded-xl"
|
||||||
>
|
>
|
||||||
<div class="flex h-12 w-12 items-center justify-center">
|
<div class="flex h-12 w-12 items-center justify-center">
|
||||||
@ -96,7 +86,7 @@
|
|||||||
{#if !shared && search.length === 0}
|
{#if !shared && search.length === 0}
|
||||||
<p class="px-5 py-3 text-xs">{$t('recent').toUpperCase()}</p>
|
<p class="px-5 py-3 text-xs">{$t('recent').toUpperCase()}</p>
|
||||||
{#each recentAlbums as album (album.id)}
|
{#each recentAlbums as album (album.id)}
|
||||||
<AlbumListItem {album} on:album={() => handleSelect(album)} />
|
<AlbumListItem {album} onAlbumClick={() => onAlbumClick(album)} />
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@ -106,7 +96,7 @@
|
|||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
{#each filteredAlbums as album (album.id)}
|
{#each filteredAlbums as album (album.id)}
|
||||||
<AlbumListItem {album} searchQuery={search} on:album={() => handleSelect(album)} />
|
<AlbumListItem {album} searchQuery={search} onAlbumClick={() => onAlbumClick(album)} />
|
||||||
{/each}
|
{/each}
|
||||||
{:else if albums.length > 0}
|
{:else if albums.length > 0}
|
||||||
<p class="px-5 py-1 text-sm">{$t('no_albums_with_name_yet')}</p>
|
<p class="px-5 py-1 text-sm">{$t('no_albums_with_name_yet')}</p>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import ConfirmDialog from './dialog/confirm-dialog.svelte';
|
import ConfirmDialog from './dialog/confirm-dialog.svelte';
|
||||||
import Combobox from './combobox.svelte';
|
import Combobox from './combobox.svelte';
|
||||||
@ -8,6 +7,8 @@
|
|||||||
|
|
||||||
export let initialDate: DateTime = DateTime.now();
|
export let initialDate: DateTime = DateTime.now();
|
||||||
export let initialTimeZone: string = '';
|
export let initialTimeZone: string = '';
|
||||||
|
export let onCancel: () => void;
|
||||||
|
export let onConfirm: (date: string) => void;
|
||||||
|
|
||||||
type ZoneOption = {
|
type ZoneOption = {
|
||||||
/**
|
/**
|
||||||
@ -118,17 +119,10 @@
|
|||||||
return zoneA.value.localeCompare(zoneB.value, undefined, { sensitivity: 'base' });
|
return zoneA.value.localeCompare(zoneB.value, undefined, { sensitivity: 'base' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
cancel: void;
|
|
||||||
confirm: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const handleCancel = () => dispatch('cancel');
|
|
||||||
|
|
||||||
const handleConfirm = () => {
|
const handleConfirm = () => {
|
||||||
const value = date.toISO();
|
const value = date.toISO();
|
||||||
if (value) {
|
if (value) {
|
||||||
dispatch('confirm', value);
|
onConfirm(value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@ -139,7 +133,7 @@
|
|||||||
prompt="Please select a new date:"
|
prompt="Please select a new date:"
|
||||||
disabled={!date.isValid}
|
disabled={!date.isValid}
|
||||||
onConfirm={handleConfirm}
|
onConfirm={handleConfirm}
|
||||||
onCancel={handleCancel}
|
{onCancel}
|
||||||
>
|
>
|
||||||
<div class="flex flex-col text-left gap-2" slot="prompt">
|
<div class="flex flex-col text-left gap-2" slot="prompt">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import ConfirmDialog from './dialog/confirm-dialog.svelte';
|
import ConfirmDialog from './dialog/confirm-dialog.svelte';
|
||||||
import { timeDebounceOnSearch } from '$lib/constants';
|
import { timeDebounceOnSearch } from '$lib/constants';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
@ -14,13 +13,15 @@
|
|||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import CoordinatesInput from '$lib/components/shared-components/coordinates-input.svelte';
|
import CoordinatesInput from '$lib/components/shared-components/coordinates-input.svelte';
|
||||||
|
|
||||||
export let asset: AssetResponseDto | undefined = undefined;
|
|
||||||
|
|
||||||
interface Point {
|
interface Point {
|
||||||
lng: number;
|
lng: number;
|
||||||
lat: number;
|
lat: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export let asset: AssetResponseDto | undefined = undefined;
|
||||||
|
export let onCancel: () => void;
|
||||||
|
export let onConfirm: (point: Point) => void;
|
||||||
|
|
||||||
let places: PlacesResponseDto[] = [];
|
let places: PlacesResponseDto[] = [];
|
||||||
let suggestedPlaces: PlacesResponseDto[] = [];
|
let suggestedPlaces: PlacesResponseDto[] = [];
|
||||||
let searchWord: string;
|
let searchWord: string;
|
||||||
@ -30,11 +31,6 @@
|
|||||||
let hideSuggestion = false;
|
let hideSuggestion = false;
|
||||||
let addClipMapMarker: (long: number, lat: number) => void;
|
let addClipMapMarker: (long: number, lat: number) => void;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
cancel: void;
|
|
||||||
confirm: Point;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
$: lat = asset?.exifInfo?.latitude ?? undefined;
|
$: lat = asset?.exifInfo?.latitude ?? undefined;
|
||||||
$: lng = asset?.exifInfo?.longitude ?? undefined;
|
$: lng = asset?.exifInfo?.longitude ?? undefined;
|
||||||
$: zoom = lat !== undefined && lng !== undefined ? 12.5 : 1;
|
$: zoom = lat !== undefined && lng !== undefined ? 12.5 : 1;
|
||||||
@ -50,17 +46,11 @@
|
|||||||
|
|
||||||
let point: Point | null = null;
|
let point: Point | null = null;
|
||||||
|
|
||||||
const handleCancel = () => dispatch('cancel');
|
|
||||||
|
|
||||||
const handleSelect = (selected: Point) => {
|
|
||||||
point = selected;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleConfirm = () => {
|
const handleConfirm = () => {
|
||||||
if (point) {
|
if (point) {
|
||||||
dispatch('confirm', point);
|
onConfirm(point);
|
||||||
} else {
|
} else {
|
||||||
dispatch('cancel');
|
onCancel();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -108,13 +98,7 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ConfirmDialog
|
<ConfirmDialog confirmColor="primary" title={$t('change_location')} width="wide" onConfirm={handleConfirm} {onCancel}>
|
||||||
confirmColor="primary"
|
|
||||||
title={$t('change_location')}
|
|
||||||
width="wide"
|
|
||||||
onConfirm={handleConfirm}
|
|
||||||
onCancel={handleCancel}
|
|
||||||
>
|
|
||||||
<div slot="prompt" class="flex flex-col w-full h-full gap-2">
|
<div slot="prompt" class="flex flex-col w-full h-full gap-2">
|
||||||
<div
|
<div
|
||||||
class="relative w-64 sm:w-96"
|
class="relative w-64 sm:w-96"
|
||||||
@ -126,10 +110,8 @@
|
|||||||
placeholder={$t('search_places')}
|
placeholder={$t('search_places')}
|
||||||
bind:name={searchWord}
|
bind:name={searchWord}
|
||||||
{showLoadingSpinner}
|
{showLoadingSpinner}
|
||||||
on:reset={() => {
|
onReset={() => (suggestedPlaces = [])}
|
||||||
suggestedPlaces = [];
|
onSearch={handleSearchPlaces}
|
||||||
}}
|
|
||||||
on:search={handleSearchPlaces}
|
|
||||||
roundedBottom={suggestedPlaces.length === 0 || hideSuggestion}
|
roundedBottom={suggestedPlaces.length === 0 || hideSuggestion}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
@ -180,7 +162,7 @@
|
|||||||
center={lat && lng ? { lat, lng } : undefined}
|
center={lat && lng ? { lat, lng } : undefined}
|
||||||
simplified={true}
|
simplified={true}
|
||||||
clickable={true}
|
clickable={true}
|
||||||
on:clickedPoint={({ detail: point }) => handleSelect(point)}
|
onClickPoint={(selected) => (point = selected)}
|
||||||
/>
|
/>
|
||||||
{/await}
|
{/await}
|
||||||
</div>
|
</div>
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
import { mdiMagnify, mdiUnfoldMoreHorizontal, mdiClose } from '@mdi/js';
|
import { mdiMagnify, mdiUnfoldMoreHorizontal, mdiClose } from '@mdi/js';
|
||||||
import { createEventDispatcher, tick } from 'svelte';
|
import { tick } from 'svelte';
|
||||||
import type { FormEventHandler } from 'svelte/elements';
|
import type { FormEventHandler } from 'svelte/elements';
|
||||||
import { shortcuts } from '$lib/actions/shortcut';
|
import { shortcuts } from '$lib/actions/shortcut';
|
||||||
import { focusOutside } from '$lib/actions/focus-outside';
|
import { focusOutside } from '$lib/actions/focus-outside';
|
||||||
@ -35,6 +35,7 @@
|
|||||||
export let options: ComboBoxOption[] = [];
|
export let options: ComboBoxOption[] = [];
|
||||||
export let selectedOption: ComboBoxOption | undefined = undefined;
|
export let selectedOption: ComboBoxOption | undefined = undefined;
|
||||||
export let placeholder = '';
|
export let placeholder = '';
|
||||||
|
export let onSelect: (option: ComboBoxOption | undefined) => void = () => {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unique identifier for the combobox.
|
* Unique identifier for the combobox.
|
||||||
@ -61,10 +62,6 @@
|
|||||||
searchQuery = selectedOption ? selectedOption.label : '';
|
searchQuery = selectedOption ? selectedOption.label : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
select: ComboBoxOption | undefined;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const activate = () => {
|
const activate = () => {
|
||||||
isActive = true;
|
isActive = true;
|
||||||
searchQuery = '';
|
searchQuery = '';
|
||||||
@ -105,10 +102,10 @@
|
|||||||
optionRefs[0]?.scrollIntoView({ block: 'nearest' });
|
optionRefs[0]?.scrollIntoView({ block: 'nearest' });
|
||||||
};
|
};
|
||||||
|
|
||||||
let onSelect = (option: ComboBoxOption) => {
|
let handleSelect = (option: ComboBoxOption) => {
|
||||||
selectedOption = option;
|
selectedOption = option;
|
||||||
searchQuery = option.label;
|
searchQuery = option.label;
|
||||||
dispatch('select', option);
|
onSelect(option);
|
||||||
closeDropdown();
|
closeDropdown();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -117,7 +114,7 @@
|
|||||||
selectedIndex = undefined;
|
selectedIndex = undefined;
|
||||||
selectedOption = undefined;
|
selectedOption = undefined;
|
||||||
searchQuery = '';
|
searchQuery = '';
|
||||||
dispatch('select', selectedOption);
|
onSelect(selectedOption);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -188,7 +185,7 @@
|
|||||||
shortcut: { key: 'Enter' },
|
shortcut: { key: 'Enter' },
|
||||||
onShortcut: () => {
|
onShortcut: () => {
|
||||||
if (selectedIndex !== undefined && filteredOptions.length > 0) {
|
if (selectedIndex !== undefined && filteredOptions.length > 0) {
|
||||||
onSelect(filteredOptions[selectedIndex]);
|
handleSelect(filteredOptions[selectedIndex]);
|
||||||
}
|
}
|
||||||
closeDropdown();
|
closeDropdown();
|
||||||
},
|
},
|
||||||
@ -245,7 +242,7 @@
|
|||||||
bind:this={optionRefs[index]}
|
bind:this={optionRefs[index]}
|
||||||
class="text-left w-full px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 transition-all cursor-pointer aria-selected:bg-gray-100 aria-selected:dark:bg-gray-700"
|
class="text-left w-full px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 transition-all cursor-pointer aria-selected:bg-gray-100 aria-selected:dark:bg-gray-700"
|
||||||
id={`${listboxId}-${index}`}
|
id={`${listboxId}-${index}`}
|
||||||
on:click={() => onSelect(option)}
|
on:click={() => handleSelect(option)}
|
||||||
role="option"
|
role="option"
|
||||||
>
|
>
|
||||||
{option.label}
|
{option.label}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
|
|
||||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
|
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import { mdiClose } from '@mdi/js';
|
import { mdiClose } from '@mdi/js';
|
||||||
@ -12,13 +12,10 @@
|
|||||||
export let backIcon = mdiClose;
|
export let backIcon = mdiClose;
|
||||||
export let tailwindClasses = '';
|
export let tailwindClasses = '';
|
||||||
export let forceDark = false;
|
export let forceDark = false;
|
||||||
|
export let onClose: () => void = () => {};
|
||||||
|
|
||||||
let appBarBorder = 'bg-immich-bg border border-transparent';
|
let appBarBorder = 'bg-immich-bg border border-transparent';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
close: void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const onScroll = () => {
|
const onScroll = () => {
|
||||||
if (window.pageYOffset > 80) {
|
if (window.pageYOffset > 80) {
|
||||||
appBarBorder = 'border border-gray-200 bg-gray-50 dark:border-gray-600';
|
appBarBorder = 'border border-gray-200 bg-gray-50 dark:border-gray-600';
|
||||||
@ -33,7 +30,7 @@
|
|||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
$isSelectingAllAssets = false;
|
$isSelectingAllAssets = false;
|
||||||
dispatch('close');
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { SharedLinkType, createSharedLink, updateSharedLink, type SharedLinkResponseDto } from '@immich/sdk';
|
import { SharedLinkType, createSharedLink, updateSharedLink, type SharedLinkResponseDto } from '@immich/sdk';
|
||||||
import { mdiContentCopy, mdiLink } from '@mdi/js';
|
import { mdiContentCopy, mdiLink } from '@mdi/js';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import { NotificationType, notificationController } from '../notification/notification';
|
import { NotificationType, notificationController } from '../notification/notification';
|
||||||
import SettingInputField, { SettingInputFieldType } from '../settings/setting-input-field.svelte';
|
import SettingInputField, { SettingInputFieldType } from '../settings/setting-input-field.svelte';
|
||||||
import SettingSwitch from '../settings/setting-switch.svelte';
|
import SettingSwitch from '../settings/setting-switch.svelte';
|
||||||
@ -21,6 +20,7 @@
|
|||||||
export let albumId: string | undefined = undefined;
|
export let albumId: string | undefined = undefined;
|
||||||
export let assetIds: string[] = [];
|
export let assetIds: string[] = [];
|
||||||
export let editingLink: SharedLinkResponseDto | undefined = undefined;
|
export let editingLink: SharedLinkResponseDto | undefined = undefined;
|
||||||
|
export let onCreated: () => void = () => {};
|
||||||
|
|
||||||
let sharedLink: string | null = null;
|
let sharedLink: string | null = null;
|
||||||
let description = '';
|
let description = '';
|
||||||
@ -32,10 +32,6 @@
|
|||||||
let shouldChangeExpirationTime = false;
|
let shouldChangeExpirationTime = false;
|
||||||
let enablePassword = false;
|
let enablePassword = false;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
created: void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const expirationOptions: [number, Intl.RelativeTimeFormatUnit][] = [
|
const expirationOptions: [number, Intl.RelativeTimeFormatUnit][] = [
|
||||||
[30, 'minutes'],
|
[30, 'minutes'],
|
||||||
[1, 'hour'],
|
[1, 'hour'],
|
||||||
@ -97,7 +93,7 @@
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
sharedLink = makeSharedLinkUrl($serverConfig.externalDomain, data.key);
|
sharedLink = makeSharedLinkUrl($serverConfig.externalDomain, data.key);
|
||||||
dispatch('created');
|
onCreated();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, $t('errors.failed_to_create_shared_link'));
|
handleError(error, $t('errors.failed_to_create_shared_link'));
|
||||||
}
|
}
|
||||||
|
@ -163,9 +163,9 @@
|
|||||||
<AssetViewer
|
<AssetViewer
|
||||||
asset={$viewingAsset}
|
asset={$viewingAsset}
|
||||||
onAction={handleAction}
|
onAction={handleAction}
|
||||||
on:previous={handlePrevious}
|
onPrevious={handlePrevious}
|
||||||
on:next={handleNext}
|
onNext={handleNext}
|
||||||
on:close={() => {
|
onClose={() => {
|
||||||
assetViewingStore.showAssetViewer(false);
|
assetViewingStore.showAssetViewer(false);
|
||||||
handlePromiseError(navigate({ targetRoute: 'current', assetId: null }));
|
handlePromiseError(navigate({ targetRoute: 'current', assetId: null }));
|
||||||
}}
|
}}
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
import type { Feature, GeoJsonProperties, Geometry, Point } from 'geojson';
|
import type { Feature, GeoJsonProperties, Geometry, Point } from 'geojson';
|
||||||
import type { GeoJSONSource, LngLatLike, StyleSpecification } from 'maplibre-gl';
|
import type { GeoJSONSource, LngLatLike, StyleSpecification } from 'maplibre-gl';
|
||||||
import maplibregl from 'maplibre-gl';
|
import maplibregl from 'maplibre-gl';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import {
|
import {
|
||||||
AttributionControl,
|
AttributionControl,
|
||||||
@ -52,6 +51,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
export let onOpenInMapView: (() => Promise<void> | void) | undefined = undefined;
|
export let onOpenInMapView: (() => Promise<void> | void) | undefined = undefined;
|
||||||
|
export let onSelect: (assetIds: string[]) => void = () => {};
|
||||||
|
export let onClickPoint: ({ lat, lng }: { lat: number; lng: number }) => void = () => {};
|
||||||
|
|
||||||
let map: maplibregl.Map;
|
let map: maplibregl.Map;
|
||||||
let marker: maplibregl.Marker | null = null;
|
let marker: maplibregl.Marker | null = null;
|
||||||
@ -62,16 +63,11 @@
|
|||||||
key: getKey(),
|
key: getKey(),
|
||||||
}) as Promise<StyleSpecification>)();
|
}) as Promise<StyleSpecification>)();
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
selected: string[];
|
|
||||||
clickedPoint: { lat: number; lng: number };
|
|
||||||
}>();
|
|
||||||
|
|
||||||
function handleAssetClick(assetId: string, map: Map | null) {
|
function handleAssetClick(assetId: string, map: Map | null) {
|
||||||
if (!map) {
|
if (!map) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dispatch('selected', [assetId]);
|
onSelect([assetId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleClusterClick(clusterId: number, map: Map | null) {
|
async function handleClusterClick(clusterId: number, map: Map | null) {
|
||||||
@ -82,13 +78,13 @@
|
|||||||
const mapSource = map?.getSource('geojson') as GeoJSONSource;
|
const mapSource = map?.getSource('geojson') as GeoJSONSource;
|
||||||
const leaves = await mapSource.getClusterLeaves(clusterId, 10_000, 0);
|
const leaves = await mapSource.getClusterLeaves(clusterId, 10_000, 0);
|
||||||
const ids = leaves.map((leaf) => leaf.properties?.id);
|
const ids = leaves.map((leaf) => leaf.properties?.id);
|
||||||
dispatch('selected', ids);
|
onSelect(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMapClick(event: maplibregl.MapMouseEvent) {
|
function handleMapClick(event: maplibregl.MapMouseEvent) {
|
||||||
if (clickable) {
|
if (clickable) {
|
||||||
const { lng, lat } = event.lngLat;
|
const { lng, lat } = event.lngLat;
|
||||||
dispatch('clickedPoint', { lng, lat });
|
onClickPoint({ lng, lat });
|
||||||
|
|
||||||
if (marker) {
|
if (marker) {
|
||||||
marker.remove();
|
marker.remove();
|
||||||
|
@ -9,19 +9,16 @@
|
|||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { deleteProfileImage, updateMyPreferences, type UserAvatarColor } from '@immich/sdk';
|
import { deleteProfileImage, updateMyPreferences, type UserAvatarColor } from '@immich/sdk';
|
||||||
import { mdiCog, mdiLogout, mdiPencil, mdiWrench } from '@mdi/js';
|
import { mdiCog, mdiLogout, mdiPencil, mdiWrench } from '@mdi/js';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import { NotificationType, notificationController } from '../notification/notification';
|
import { NotificationType, notificationController } from '../notification/notification';
|
||||||
import UserAvatar from '../user-avatar.svelte';
|
import UserAvatar from '../user-avatar.svelte';
|
||||||
import AvatarSelector from './avatar-selector.svelte';
|
import AvatarSelector from './avatar-selector.svelte';
|
||||||
|
|
||||||
let isShowSelectAvatar = false;
|
export let onLogout: () => void;
|
||||||
|
export let onClose: () => void = () => {};
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
let isShowSelectAvatar = false;
|
||||||
logout: void;
|
|
||||||
close: void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const handleSaveProfile = async (color: UserAvatarColor) => {
|
const handleSaveProfile = async (color: UserAvatarColor) => {
|
||||||
try {
|
try {
|
||||||
@ -75,14 +72,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<Button
|
<Button href={AppRoute.USER_SETTINGS} on:click={onClose} color="dark-gray" size="sm" shadow={false} border>
|
||||||
href={AppRoute.USER_SETTINGS}
|
|
||||||
on:click={() => dispatch('close')}
|
|
||||||
color="dark-gray"
|
|
||||||
size="sm"
|
|
||||||
shadow={false}
|
|
||||||
border
|
|
||||||
>
|
|
||||||
<div class="flex place-content-center place-items-center text-center gap-2 px-2">
|
<div class="flex place-content-center place-items-center text-center gap-2 px-2">
|
||||||
<Icon path={mdiCog} size="18" ariaHidden />
|
<Icon path={mdiCog} size="18" ariaHidden />
|
||||||
{$t('account_settings')}
|
{$t('account_settings')}
|
||||||
@ -91,7 +81,7 @@
|
|||||||
{#if $user.isAdmin}
|
{#if $user.isAdmin}
|
||||||
<Button
|
<Button
|
||||||
href={AppRoute.ADMIN_USER_MANAGEMENT}
|
href={AppRoute.ADMIN_USER_MANAGEMENT}
|
||||||
on:click={() => dispatch('close')}
|
on:click={onClose}
|
||||||
color="dark-gray"
|
color="dark-gray"
|
||||||
size="sm"
|
size="sm"
|
||||||
shadow={false}
|
shadow={false}
|
||||||
@ -111,7 +101,7 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="flex w-full place-content-center place-items-center gap-2 py-3 font-medium text-gray-500 hover:bg-immich-primary/10 dark:text-gray-300"
|
class="flex w-full place-content-center place-items-center gap-2 py-3 font-medium text-gray-500 hover:bg-immich-primary/10 dark:text-gray-300"
|
||||||
on:click={() => dispatch('logout')}
|
on:click={onLogout}
|
||||||
>
|
>
|
||||||
<Icon path={mdiLogout} size={24} />
|
<Icon path={mdiLogout} size={24} />
|
||||||
{$t('sign_out')}</button
|
{$t('sign_out')}</button
|
||||||
|
@ -10,7 +10,6 @@
|
|||||||
import { handleLogout } from '$lib/utils/auth';
|
import { handleLogout } from '$lib/utils/auth';
|
||||||
import { logout } from '@immich/sdk';
|
import { logout } from '@immich/sdk';
|
||||||
import { mdiMagnify, mdiTrayArrowUp } from '@mdi/js';
|
import { mdiMagnify, mdiTrayArrowUp } from '@mdi/js';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import { AppRoute } from '../../../constants';
|
import { AppRoute } from '../../../constants';
|
||||||
@ -21,13 +20,11 @@
|
|||||||
import AccountInfoPanel from './account-info-panel.svelte';
|
import AccountInfoPanel from './account-info-panel.svelte';
|
||||||
|
|
||||||
export let showUploadButton = true;
|
export let showUploadButton = true;
|
||||||
|
export let onUploadClick: () => void;
|
||||||
|
|
||||||
let shouldShowAccountInfo = false;
|
let shouldShowAccountInfo = false;
|
||||||
let shouldShowAccountInfoPanel = false;
|
let shouldShowAccountInfoPanel = false;
|
||||||
let innerWidth: number;
|
let innerWidth: number;
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
uploadClicked: void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const onLogout = async () => {
|
const onLogout = async () => {
|
||||||
const { redirectUri } = await logout();
|
const { redirectUri } = await logout();
|
||||||
@ -67,14 +64,14 @@
|
|||||||
<ThemeButton padding="2" />
|
<ThemeButton padding="2" />
|
||||||
|
|
||||||
{#if !$page.url.pathname.includes('/admin') && showUploadButton}
|
{#if !$page.url.pathname.includes('/admin') && showUploadButton}
|
||||||
<LinkButton on:click={() => dispatch('uploadClicked')} class="hidden lg:block">
|
<LinkButton on:click={onUploadClick} class="hidden lg:block">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<Icon path={mdiTrayArrowUp} size="1.5em" />
|
<Icon path={mdiTrayArrowUp} size="1.5em" />
|
||||||
<span>{$t('upload')}</span>
|
<span>{$t('upload')}</span>
|
||||||
</div>
|
</div>
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
<CircleIconButton
|
<CircleIconButton
|
||||||
on:click={() => dispatch('uploadClicked')}
|
on:click={onUploadClick}
|
||||||
title={$t('upload')}
|
title={$t('upload')}
|
||||||
icon={mdiTrayArrowUp}
|
icon={mdiTrayArrowUp}
|
||||||
class="lg:hidden"
|
class="lg:hidden"
|
||||||
@ -114,7 +111,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if shouldShowAccountInfoPanel}
|
{#if shouldShowAccountInfoPanel}
|
||||||
<AccountInfoPanel on:logout={onLogout} />
|
<AccountInfoPanel {onLogout} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { handlePromiseError } from '$lib/utils';
|
import { handlePromiseError } from '$lib/utils';
|
||||||
|
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { tweened } from 'svelte/motion';
|
import { tweened } from 'svelte/motion';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -26,6 +26,10 @@
|
|||||||
|
|
||||||
export let duration = 5;
|
export let duration = 5;
|
||||||
|
|
||||||
|
export let onDone: () => void;
|
||||||
|
export let onPlaying: () => void = () => {};
|
||||||
|
export let onPaused: () => void = () => {};
|
||||||
|
|
||||||
const onChange = async () => {
|
const onChange = async () => {
|
||||||
progress = setDuration(duration);
|
progress = setDuration(duration);
|
||||||
await play();
|
await play();
|
||||||
@ -39,16 +43,10 @@
|
|||||||
|
|
||||||
$: {
|
$: {
|
||||||
if ($progress === 1) {
|
if ($progress === 1) {
|
||||||
dispatch('done');
|
onDone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
|
||||||
done: void;
|
|
||||||
playing: void;
|
|
||||||
paused: void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (autoplay) {
|
if (autoplay) {
|
||||||
await play();
|
await play();
|
||||||
@ -57,13 +55,13 @@
|
|||||||
|
|
||||||
export const play = async () => {
|
export const play = async () => {
|
||||||
status = ProgressBarStatus.Playing;
|
status = ProgressBarStatus.Playing;
|
||||||
dispatch('playing');
|
onPlaying();
|
||||||
await progress.set(1);
|
await progress.set(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pause = async () => {
|
export const pause = async () => {
|
||||||
status = ProgressBarStatus.Paused;
|
status = ProgressBarStatus.Paused;
|
||||||
dispatch('paused');
|
onPaused();
|
||||||
await progress.set($progress);
|
await progress.set($progress);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@
|
|||||||
icon: option.icon,
|
icon: option.icon,
|
||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
on:select={({ detail }) => onToggle(detail)}
|
onSelect={onToggle}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { quintOut } from 'svelte/easing';
|
import { quintOut } from 'svelte/easing';
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
import { mdiChevronDown } from '@mdi/js';
|
import { mdiChevronDown } from '@mdi/js';
|
||||||
@ -14,15 +13,14 @@
|
|||||||
export let isEdited = false;
|
export let isEdited = false;
|
||||||
export let number = false;
|
export let number = false;
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
|
export let onSelect: (setting: string | number) => void = () => {};
|
||||||
const dispatch = createEventDispatcher<{ select: string | number }>();
|
|
||||||
|
|
||||||
const handleChange = (e: Event) => {
|
const handleChange = (e: Event) => {
|
||||||
value = (e.target as HTMLInputElement).value;
|
value = (e.target as HTMLInputElement).value;
|
||||||
if (number) {
|
if (number) {
|
||||||
value = Number.parseInt(value);
|
value = Number.parseInt(value);
|
||||||
}
|
}
|
||||||
dispatch('select', value);
|
onSelect(value);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { quintOut } from 'svelte/easing';
|
import { quintOut } from 'svelte/easing';
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import Slider from '$lib/components/elements/slider.svelte';
|
import Slider from '$lib/components/elements/slider.svelte';
|
||||||
import { generateId } from '$lib/utils/generate-id';
|
import { generateId } from '$lib/utils/generate-id';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
@ -11,14 +10,12 @@
|
|||||||
export let checked = false;
|
export let checked = false;
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
export let isEdited = false;
|
export let isEdited = false;
|
||||||
|
export let onToggle: (isChecked: boolean) => void = () => {};
|
||||||
|
|
||||||
let id: string = generateId();
|
let id: string = generateId();
|
||||||
|
|
||||||
$: sliderId = `${id}-slider`;
|
$: sliderId = `${id}-slider`;
|
||||||
$: subtitleId = subtitle ? `${id}-subtitle` : undefined;
|
$: subtitleId = subtitle ? `${id}-subtitle` : undefined;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{ toggle: boolean }>();
|
|
||||||
const onToggle = (isChecked: boolean) => dispatch('toggle', isChecked);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex place-items-center justify-between">
|
<div class="flex place-items-center justify-between">
|
||||||
|
@ -102,7 +102,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if secret}
|
{#if secret}
|
||||||
<APIKeySecret {secret} on:done={() => (secret = '')} />
|
<APIKeySecret {secret} onDone={() => (secret = '')} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if editKey}
|
{#if editKey}
|
||||||
|
@ -151,15 +151,15 @@
|
|||||||
<AssetViewer
|
<AssetViewer
|
||||||
asset={$viewingAsset}
|
asset={$viewingAsset}
|
||||||
showNavigation={assets.length > 1}
|
showNavigation={assets.length > 1}
|
||||||
on:next={() => {
|
onNext={() => {
|
||||||
const index = getAssetIndex($viewingAsset.id) + 1;
|
const index = getAssetIndex($viewingAsset.id) + 1;
|
||||||
setAsset(assets[index % assets.length]);
|
setAsset(assets[index % assets.length]);
|
||||||
}}
|
}}
|
||||||
on:previous={() => {
|
onPrevious={() => {
|
||||||
const index = getAssetIndex($viewingAsset.id) - 1 + assets.length;
|
const index = getAssetIndex($viewingAsset.id) - 1 + assets.length;
|
||||||
setAsset(assets[index % assets.length]);
|
setAsset(assets[index % assets.length]);
|
||||||
}}
|
}}
|
||||||
on:close={() => {
|
onClose={() => {
|
||||||
assetViewingStore.showAssetViewer(false);
|
assetViewingStore.showAssetViewer(false);
|
||||||
handlePromiseError(navigate({ targetRoute: 'current', assetId: null }));
|
handlePromiseError(navigate({ targetRoute: 'current', assetId: null }));
|
||||||
}}
|
}}
|
||||||
|
@ -674,8 +674,8 @@
|
|||||||
disabled={!album.isActivityEnabled}
|
disabled={!album.isActivityEnabled}
|
||||||
{isLiked}
|
{isLiked}
|
||||||
numberOfComments={$numberOfComments}
|
numberOfComments={$numberOfComments}
|
||||||
on:favorite={handleFavorite}
|
onFavorite={handleFavorite}
|
||||||
on:openActivityTab={handleOpenAndCloseActivityTab}
|
onOpenActivityTab={handleOpenAndCloseActivityTab}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@ -697,10 +697,10 @@
|
|||||||
albumId={album.id}
|
albumId={album.id}
|
||||||
{isLiked}
|
{isLiked}
|
||||||
bind:reactions
|
bind:reactions
|
||||||
on:addComment={() => updateNumberOfComments(1)}
|
onAddComment={() => updateNumberOfComments(1)}
|
||||||
on:deleteComment={() => updateNumberOfComments(-1)}
|
onDeleteComment={() => updateNumberOfComments(-1)}
|
||||||
on:deleteLike={() => (isLiked = null)}
|
onDeleteLike={() => (isLiked = null)}
|
||||||
on:close={handleOpenAndCloseActivityTab}
|
onClose={handleOpenAndCloseActivityTab}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -709,8 +709,8 @@
|
|||||||
{#if viewMode === ViewMode.SELECT_USERS}
|
{#if viewMode === ViewMode.SELECT_USERS}
|
||||||
<UserSelectionModal
|
<UserSelectionModal
|
||||||
{album}
|
{album}
|
||||||
on:select={({ detail: users }) => handleAddUsers(users)}
|
onSelect={handleAddUsers}
|
||||||
on:share={() => (viewMode = ViewMode.LINK_SHARING)}
|
onShare={() => (viewMode = ViewMode.LINK_SHARING)}
|
||||||
onClose={() => (viewMode = ViewMode.VIEW)}
|
onClose={() => (viewMode = ViewMode.VIEW)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
@ -723,8 +723,8 @@
|
|||||||
<ShareInfoModal
|
<ShareInfoModal
|
||||||
onClose={() => (viewMode = ViewMode.VIEW)}
|
onClose={() => (viewMode = ViewMode.VIEW)}
|
||||||
{album}
|
{album}
|
||||||
on:remove={({ detail: userId }) => handleRemoveUser(userId)}
|
onRemove={handleRemoveUser}
|
||||||
on:refreshAlbum={refreshAlbum}
|
onRefreshAlbum={refreshAlbum}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@ -737,9 +737,9 @@
|
|||||||
albumOrder = order;
|
albumOrder = order;
|
||||||
await setModeToView();
|
await setModeToView();
|
||||||
}}
|
}}
|
||||||
on:close={() => (viewMode = ViewMode.VIEW)}
|
onClose={() => (viewMode = ViewMode.VIEW)}
|
||||||
on:toggleEnableActivity={handleToggleEnableActivity}
|
onToggleEnabledActivity={handleToggleEnableActivity}
|
||||||
on:showSelectSharedUser={() => (viewMode = ViewMode.SELECT_USERS)}
|
onShowSelectSharedUser={() => (viewMode = ViewMode.SELECT_USERS)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
@ -122,9 +122,9 @@
|
|||||||
<AssetViewer
|
<AssetViewer
|
||||||
asset={$viewingAsset}
|
asset={$viewingAsset}
|
||||||
showNavigation={viewingAssets.length > 1}
|
showNavigation={viewingAssets.length > 1}
|
||||||
on:next={navigateNext}
|
onNext={navigateNext}
|
||||||
on:previous={navigatePrevious}
|
onPrevious={navigatePrevious}
|
||||||
on:close={() => {
|
onClose={() => {
|
||||||
assetViewingStore.showAssetViewer(false);
|
assetViewingStore.showAssetViewer(false);
|
||||||
handlePromiseError(navigate({ targetRoute: 'current', assetId: null }));
|
handlePromiseError(navigate({ targetRoute: 'current', assetId: null }));
|
||||||
}}
|
}}
|
||||||
@ -137,11 +137,11 @@
|
|||||||
{#if showSettingsModal}
|
{#if showSettingsModal}
|
||||||
<MapSettingsModal
|
<MapSettingsModal
|
||||||
settings={{ ...$mapSettings }}
|
settings={{ ...$mapSettings }}
|
||||||
on:close={() => (showSettingsModal = false)}
|
onClose={() => (showSettingsModal = false)}
|
||||||
on:save={async ({ detail }) => {
|
onSave={async (settings) => {
|
||||||
const shouldUpdate = !isEqual(omit(detail, 'allowDarkMode'), omit($mapSettings, 'allowDarkMode'));
|
const shouldUpdate = !isEqual(omit(settings, 'allowDarkMode'), omit($mapSettings, 'allowDarkMode'));
|
||||||
showSettingsModal = false;
|
showSettingsModal = false;
|
||||||
$mapSettings = detail;
|
$mapSettings = settings;
|
||||||
|
|
||||||
if (shouldUpdate) {
|
if (shouldUpdate) {
|
||||||
mapMarkers = await loadMapMarkers();
|
mapMarkers = await loadMapMarkers();
|
||||||
|
@ -302,9 +302,9 @@
|
|||||||
{personMerge1}
|
{personMerge1}
|
||||||
{personMerge2}
|
{personMerge2}
|
||||||
{potentialMergePeople}
|
{potentialMergePeople}
|
||||||
on:close={() => (showMergeModal = false)}
|
onClose={() => (showMergeModal = false)}
|
||||||
on:reject={() => changeName()}
|
onReject={changeName}
|
||||||
on:confirm={(event) => handleMergeSamePerson(event.detail)}
|
onConfirm={handleMergeSamePerson}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@ -349,10 +349,10 @@
|
|||||||
<PeopleCard
|
<PeopleCard
|
||||||
{person}
|
{person}
|
||||||
preload={index < 20}
|
preload={index < 20}
|
||||||
on:change-name={() => handleChangeName(person)}
|
onChangeName={() => handleChangeName(person)}
|
||||||
on:set-birth-date={() => handleSetBirthDate(person)}
|
onSetBirthDate={() => handleSetBirthDate(person)}
|
||||||
on:merge-people={() => handleMergePeople(person)}
|
onMergePeople={() => handleMergePeople(person)}
|
||||||
on:hide-person={() => handleHidePerson(person)}
|
onHidePerson={() => handleHidePerson(person)}
|
||||||
/>
|
/>
|
||||||
</PeopleInfiniteScroll>
|
</PeopleInfiniteScroll>
|
||||||
{:else}
|
{:else}
|
||||||
@ -397,8 +397,8 @@
|
|||||||
{#if showSetBirthDateModal}
|
{#if showSetBirthDateModal}
|
||||||
<SetBirthDateModal
|
<SetBirthDateModal
|
||||||
birthDate={edittingPerson?.birthDate ?? ''}
|
birthDate={edittingPerson?.birthDate ?? ''}
|
||||||
on:close={() => (showSetBirthDateModal = false)}
|
onClose={() => (showSetBirthDateModal = false)}
|
||||||
on:updated={(event) => submitBirthDateChange(event.detail)}
|
onUpdate={submitBirthDateChange}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</UserPageLayout>
|
</UserPageLayout>
|
||||||
|
@ -347,8 +347,8 @@
|
|||||||
<UnMergeFaceSelector
|
<UnMergeFaceSelector
|
||||||
assetIds={[...$selectedAssets].map((a) => a.id)}
|
assetIds={[...$selectedAssets].map((a) => a.id)}
|
||||||
personAssets={person}
|
personAssets={person}
|
||||||
on:close={() => (viewMode = ViewMode.VIEW_ASSETS)}
|
onClose={() => (viewMode = ViewMode.VIEW_ASSETS)}
|
||||||
on:confirm={handleUnmerge}
|
onConfirm={handleUnmerge}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@ -357,22 +357,22 @@
|
|||||||
{personMerge1}
|
{personMerge1}
|
||||||
{personMerge2}
|
{personMerge2}
|
||||||
{potentialMergePeople}
|
{potentialMergePeople}
|
||||||
on:close={() => (viewMode = ViewMode.VIEW_ASSETS)}
|
onClose={() => (viewMode = ViewMode.VIEW_ASSETS)}
|
||||||
on:reject={() => changeName()}
|
onReject={changeName}
|
||||||
on:confirm={(event) => handleMergeSamePerson(event.detail)}
|
onConfirm={handleMergeSamePerson}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if viewMode === ViewMode.BIRTH_DATE}
|
{#if viewMode === ViewMode.BIRTH_DATE}
|
||||||
<SetBirthDateModal
|
<SetBirthDateModal
|
||||||
birthDate={person.birthDate ?? ''}
|
birthDate={person.birthDate ?? ''}
|
||||||
on:close={() => (viewMode = ViewMode.VIEW_ASSETS)}
|
onClose={() => (viewMode = ViewMode.VIEW_ASSETS)}
|
||||||
on:updated={(event) => handleSetBirthDate(event.detail)}
|
onUpdate={handleSetBirthDate}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if viewMode === ViewMode.MERGE_PEOPLE}
|
{#if viewMode === ViewMode.MERGE_PEOPLE}
|
||||||
<MergeFaceSelector {person} on:back={handleGoBack} on:merge={({ detail }) => handleMerge(detail)} />
|
<MergeFaceSelector {person} onBack={handleGoBack} onMerge={handleMerge} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
@ -464,7 +464,7 @@
|
|||||||
bind:suggestedPeople
|
bind:suggestedPeople
|
||||||
name={person.name}
|
name={person.name}
|
||||||
bind:isSearchingPeople
|
bind:isSearchingPeople
|
||||||
on:change={(event) => handleNameChange(event.detail)}
|
onChange={handleNameChange}
|
||||||
{thumbnailData}
|
{thumbnailData}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
|
@ -267,10 +267,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if toCreateLibrary}
|
{#if toCreateLibrary}
|
||||||
<LibraryUserPickerForm
|
<LibraryUserPickerForm onSubmit={handleCreate} onCancel={() => (toCreateLibrary = false)} />
|
||||||
on:submit={({ detail }) => handleCreate(detail.ownerId)}
|
|
||||||
on:cancel={() => (toCreateLibrary = false)}
|
|
||||||
/>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<UserPageLayout title={data.meta.title} admin>
|
<UserPageLayout title={data.meta.title} admin>
|
||||||
@ -385,28 +382,20 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{#if renameLibrary === index}
|
{#if renameLibrary === index}
|
||||||
<div transition:slide={{ duration: 250 }}>
|
<div transition:slide={{ duration: 250 }}>
|
||||||
<LibraryRenameForm
|
<LibraryRenameForm {library} onSubmit={handleUpdate} onCancel={() => (renameLibrary = null)} />
|
||||||
{library}
|
|
||||||
on:submit={({ detail }) => handleUpdate(detail)}
|
|
||||||
on:cancel={() => (renameLibrary = null)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if editImportPaths === index}
|
{#if editImportPaths === index}
|
||||||
<div transition:slide={{ duration: 250 }}>
|
<div transition:slide={{ duration: 250 }}>
|
||||||
<LibraryImportPathsForm
|
<LibraryImportPathsForm {library} onSubmit={handleUpdate} onCancel={() => (editImportPaths = null)} />
|
||||||
{library}
|
|
||||||
on:submit={({ detail }) => handleUpdate(detail)}
|
|
||||||
on:cancel={() => (editImportPaths = null)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if editScanSettings === index}
|
{#if editScanSettings === index}
|
||||||
<div transition:slide={{ duration: 250 }} class="mb-4 ml-4 mr-4">
|
<div transition:slide={{ duration: 250 }} class="mb-4 ml-4 mr-4">
|
||||||
<LibraryScanSettingsForm
|
<LibraryScanSettingsForm
|
||||||
{library}
|
{library}
|
||||||
on:submit={({ detail: library }) => handleUpdate(library)}
|
onSubmit={handleUpdate}
|
||||||
on:cancel={() => (editScanSettings = null)}
|
onCancel={() => (editScanSettings = null)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -110,8 +110,8 @@
|
|||||||
<section class="w-full pb-28 lg:w-[850px]">
|
<section class="w-full pb-28 lg:w-[850px]">
|
||||||
{#if shouldShowCreateUserForm}
|
{#if shouldShowCreateUserForm}
|
||||||
<CreateUserForm
|
<CreateUserForm
|
||||||
on:submit={onUserCreated}
|
onSubmit={onUserCreated}
|
||||||
on:cancel={() => (shouldShowCreateUserForm = false)}
|
onCancel={() => (shouldShowCreateUserForm = false)}
|
||||||
onClose={() => (shouldShowCreateUserForm = false)}
|
onClose={() => (shouldShowCreateUserForm = false)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
@ -121,8 +121,8 @@
|
|||||||
user={selectedUser}
|
user={selectedUser}
|
||||||
bind:newPassword
|
bind:newPassword
|
||||||
canResetPassword={selectedUser?.id !== $user.id}
|
canResetPassword={selectedUser?.id !== $user.id}
|
||||||
on:editSuccess={onEditUserSuccess}
|
onEditSuccess={onEditUserSuccess}
|
||||||
on:resetPasswordSuccess={onEditPasswordSuccess}
|
onResetPasswordSuccess={onEditPasswordSuccess}
|
||||||
onClose={() => (shouldShowEditUserForm = false)}
|
onClose={() => (shouldShowEditUserForm = false)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -25,5 +25,5 @@
|
|||||||
{$t('change_password_description')}
|
{$t('change_password_description')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<ChangePasswordForm on:success={onSuccess} />
|
<ChangePasswordForm {onSuccess} />
|
||||||
</FullscreenContainer>
|
</FullscreenContainer>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user