1
0
mirror of https://github.com/immich-app/immich.git synced 2024-11-28 09:33:27 +02:00

chore: migrate away from event dispatcher (#12820)

This commit is contained in:
Daniel Dietzler 2024-09-20 23:02:58 +02:00 committed by GitHub
parent 529d49471f
commit 124eb8251b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
72 changed files with 360 additions and 656 deletions

View File

@ -2,7 +2,6 @@
import Icon from '$lib/components/elements/icon.svelte';
import { updateAlbumInfo, type AlbumResponseDto, type UserResponseDto, AssetOrder } from '@immich/sdk';
import { mdiArrowDownThin, mdiArrowUpThin, mdiPlus } from '@mdi/js';
import { createEventDispatcher } from 'svelte';
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
@ -16,6 +15,9 @@
export let order: AssetOrder | undefined;
export let user: UserResponseDto;
export let onChangeOrder: (order: AssetOrder) => void;
export let onClose: () => void;
export let onToggleEnabledActivity: () => void;
export let onShowSelectSharedUser: () => void;
const options: Record<AssetOrder, RenderedOption> = {
[AssetOrder.Asc]: { icon: mdiArrowUpThin, title: $t('oldest_first') },
@ -24,12 +26,6 @@
$: selectedOption = order ? options[order] : options[AssetOrder.Desc];
const dispatch = createEventDispatcher<{
close: void;
toggleEnableActivity: void;
showSelectSharedUser: void;
}>();
const handleToggle = async (returnedOption: RenderedOption) => {
if (selectedOption === returnedOption) {
return;
@ -51,7 +47,7 @@
};
</script>
<FullScreenModal title={$t('options')} onClose={() => dispatch('close')}>
<FullScreenModal title={$t('options')} {onClose}>
<div class="items-center justify-center">
<div class="py-2">
<h2 class="text-gray text-sm mb-2">{$t('settings').toUpperCase()}</h2>
@ -68,14 +64,14 @@
title={$t('comments_and_likes')}
subtitle={$t('let_others_respond')}
checked={album.isActivityEnabled}
on:toggle={() => dispatch('toggleEnableActivity')}
onToggle={onToggleEnabledActivity}
/>
</div>
</div>
<div class="py-2">
<div class="text-gray text-sm mb-3">{$t('people').toUpperCase()}</div>
<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><Icon path={mdiPlus} size="25" /></div>
</div>

View File

@ -154,7 +154,7 @@
title={$t('sort_albums_by')}
options={Object.values(sortOptionsMetadata)}
selectedOption={selectedSortOption}
on:select={({ detail }) => handleChangeSortBy(detail)}
onSelect={handleChangeSortBy}
render={({ id }) => ({
title: albumSortByNames[id],
icon: sortIcon,
@ -166,7 +166,7 @@
title={$t('group_albums_by')}
options={Object.values(groupOptionsMetadata)}
selectedOption={selectedGroupOption}
on:select={({ detail }) => handleChangeGroupBy(detail)}
onSelect={handleChangeGroupBy}
render={({ id, isDisabled }) => ({
title: albumGroupByNames[id],
icon: groupIcon,

View File

@ -394,13 +394,13 @@
<CreateSharedLinkModal
albumId={albumToShare.id}
onClose={() => closeShareModal()}
on:created={() => albumToShare && handleSharedLinkCreated(albumToShare)}
onCreated={() => albumToShare && handleSharedLinkCreated(albumToShare)}
/>
{:else}
<UserSelectionModal
album={albumToShare}
on:select={({ detail: users }) => handleAddUsers(users)}
on:share={() => (showShareByURLModal = true)}
onSelect={handleAddUsers}
onShare={() => (showShareByURLModal = true)}
onClose={() => closeShareModal()}
/>
{/if}

View File

@ -8,7 +8,7 @@
AlbumUserRole,
} from '@immich/sdk';
import { mdiDotsVertical } from '@mdi/js';
import { createEventDispatcher, onMount } from 'svelte';
import { onMount } from 'svelte';
import { handleError } from '../../utils/handle-error';
import ConfirmDialog from '../shared-components/dialog/confirm-dialog.svelte';
import MenuOption from '../shared-components/context-menu/menu-option.svelte';
@ -20,11 +20,8 @@
export let album: AlbumResponseDto;
export let onClose: () => void;
const dispatch = createEventDispatcher<{
remove: string;
refreshAlbum: void;
}>();
export let onRemove: (userId: string) => void;
export let onRefreshAlbum: () => void;
let currentUser: UserResponseDto;
let selectedRemoveUser: UserResponseDto | null = null;
@ -52,7 +49,7 @@
try {
await removeUserFromAlbum({ id: album.id, userId });
dispatch('remove', userId);
onRemove(userId);
const message =
userId === 'me'
? $t('album_user_left', { values: { album: album.albumName } })
@ -71,7 +68,7 @@
const message = $t('user_role_set', {
values: { user: user.name, role: role == AlbumUserRole.Viewer ? $t('role_viewer') : $t('role_editor') },
});
dispatch('refreshAlbum');
onRefreshAlbum();
notificationController.show({ type: NotificationType.Info, message });
} catch (error) {
handleError(error, $t('errors.unable_to_change_album_user_role'));

View File

@ -13,13 +13,16 @@
type UserResponseDto,
} from '@immich/sdk';
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 UserAvatar from '../shared-components/user-avatar.svelte';
import { t } from 'svelte-i18n';
export let album: AlbumResponseDto;
export let onClose: () => void;
export let onSelect: (selectedUsers: AlbumUserAddDto[]) => void;
export let onShare: () => void;
let users: UserResponseDto[] = [];
let selectedUsers: Record<string, { user: UserResponseDto; role: AlbumUserRole }> = {};
@ -29,10 +32,6 @@
{ title: $t('remove_user'), value: 'none' },
];
const dispatch = createEventDispatcher<{
select: AlbumUserAddDto[];
share: void;
}>();
let sharedLinks: SharedLinkResponseDto[] = [];
onMount(async () => {
await getSharedLinks();
@ -99,7 +98,7 @@
title={$t('role')}
options={roleOptions}
render={({ title, icon }) => ({ title, icon })}
on:select={({ detail: { value } }) => handleChangeRole(user, value)}
onSelect={({ value }) => handleChangeRole(user, value)}
/>
</div>
{/key}
@ -152,10 +151,8 @@
rounded="full"
disabled={Object.keys(selectedUsers).length === 0}
on:click={() =>
dispatch(
'select',
Object.values(selectedUsers).map(({ user, ...rest }) => ({ userId: user.id, ...rest })),
)}>{$t('add')}</Button
onSelect(Object.values(selectedUsers).map(({ user, ...rest }) => ({ userId: user.id, ...rest })))}
>{$t('add')}</Button
>
</div>
{/if}
@ -166,7 +163,7 @@
<button
type="button"
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} />
<p class="text-sm">{$t('create_link')}</p>

View File

@ -40,8 +40,8 @@
<Portal target="body">
<AlbumSelectionModal
{shared}
on:newAlbum={({ detail }) => handleAddToNewAlbum(detail)}
on:album={({ detail }) => handleAddToAlbum(detail)}
onNewAlbum={handleAddToNewAlbum}
onAlbumClick={handleAddToAlbum}
onClose={() => (showSelectionModal = false)}
/>
</Portal>

View File

@ -82,6 +82,6 @@
{#if showConfirmModal}
<Portal target="body">
<DeleteAssetDialog size={1} on:cancel={() => (showConfirmModal = false)} on:confirm={() => deleteAsset()} />
<DeleteAssetDialog size={1} onCancel={() => (showConfirmModal = false)} onConfirm={deleteAsset} />
</Portal>
{/if}

View File

@ -2,26 +2,22 @@
import { locale } from '$lib/stores/preferences.store';
import type { ActivityResponseDto } from '@immich/sdk';
import { mdiCommentOutline, mdiHeart, mdiHeartOutline } from '@mdi/js';
import { createEventDispatcher } from 'svelte';
import Icon from '../elements/icon.svelte';
export let isLiked: ActivityResponseDto | null;
export let numberOfComments: number | undefined;
export let disabled: boolean;
const dispatch = createEventDispatcher<{
openActivityTab: void;
favorite: void;
}>();
export let onOpenActivityTab: () => void;
export let onFavorite: () => void;
</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">
<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">
<Icon path={isLiked ? mdiHeart : mdiHeartOutline} size={24} />
</div>
</button>
<button type="button" on:click={() => dispatch('openActivityTab')}>
<button type="button" on:click={onOpenActivityTab}>
<div class="flex gap-2 items-center justify-center">
<Icon path={mdiCommentOutline} class="scale-x-[-1]" size={24} />
{#if numberOfComments}

View File

@ -17,7 +17,7 @@
} from '@immich/sdk';
import { mdiClose, mdiDotsVertical, mdiHeart, mdiSend, mdiDeleteOutline } from '@mdi/js';
import * as luxon from 'luxon';
import { createEventDispatcher, onMount } from 'svelte';
import { onMount } from 'svelte';
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
import { NotificationType, notificationController } from '../shared-components/notification/notification';
@ -55,6 +55,10 @@
export let albumOwnerId: string;
export let disabled: boolean;
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 innerHeight: number;
@ -65,13 +69,6 @@
let message = '';
let isSendingMessage = false;
const dispatch = createEventDispatcher<{
deleteComment: void;
deleteLike: void;
addComment: void;
close: void;
}>();
$: {
if (innerHeight && activityHeight) {
divHeight = innerHeight - activityHeight;
@ -111,9 +108,9 @@
reactions.splice(index, 1);
reactions = reactions;
if (isLiked && reaction.type === ReactionType.Like && reaction.id == isLiked.id) {
dispatch('deleteLike');
onDeleteLike();
} else {
dispatch('deleteComment');
onDeleteComment();
}
const deleteMessages: Record<ReactionType, string> = {
@ -141,7 +138,7 @@
reactions.push(data);
textArea.style.height = '18px';
message = '';
dispatch('addComment');
onAddComment();
// Re-render the activity feed
reactions = reactions;
} catch (error) {
@ -160,7 +157,7 @@
bind:clientHeight={activityHeight}
>
<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>
</div>

View File

@ -1,16 +1,13 @@
<script lang="ts">
import { getAssetThumbnailUrl } from '$lib/utils';
import { type AlbumResponseDto } from '@immich/sdk';
import { createEventDispatcher } from 'svelte';
import { normalizeSearchString } from '$lib/utils/string-utils.js';
import AlbumListItemDetails from './album-list-item-details.svelte';
const dispatch = createEventDispatcher<{
album: void;
}>();
export let album: AlbumResponseDto;
export let searchQuery = '';
export let onAlbumClick: () => void;
let albumNameArray: string[] = ['', '', ''];
// 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
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"
>
<span class="h-12 w-12 shrink-0 rounded-xl bg-slate-300">

View File

@ -32,7 +32,7 @@
type AssetResponseDto,
type StackResponseDto,
} from '@immich/sdk';
import { createEventDispatcher, onDestroy, onMount } from 'svelte';
import { onDestroy, onMount } from 'svelte';
import { t } from 'svelte-i18n';
import { fly } from 'svelte/transition';
import Thumbnail from '../assets/thumbnail/thumbnail.svelte';
@ -56,8 +56,10 @@
export let isShared = false;
export let album: AlbumResponseDto | null = null;
export let onAction: OnAction | undefined = undefined;
let reactions: ActivityResponseDto[] = [];
export let reactions: ActivityResponseDto[] = [];
export let onClose: (dto: { asset: AssetResponseDto }) => void;
export let onNext: () => void;
export let onPrevious: () => void;
const { setAsset } = assetViewingStore;
const {
@ -67,13 +69,6 @@
slideshowState,
} = slideshowStore;
const dispatch = createEventDispatcher<{
action: { type: AssetAction; asset: AssetResponseDto };
close: { asset: AssetResponseDto };
next: void;
previous: void;
}>();
let appearsInAlbums: AlbumResponseDto[] = [];
let shouldPlayMotionPhoto = false;
let sharedLink = getSharedLink();
@ -267,7 +262,7 @@
};
const closeViewer = () => {
dispatch('close', { asset });
onClose({ asset });
};
const closeEditor = () => {
@ -316,7 +311,8 @@
}
e?.stopPropagation();
dispatch(order);
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
order === 'previous' ? onPrevious() : onNext();
};
// const showEditorHandler = () => {
@ -533,8 +529,8 @@
disabled={!album?.isActivityEnabled}
{isLiked}
{numberOfComments}
on:favorite={handleFavorite}
on:openActivityTab={handleOpenActivity}
onFavorite={handleFavorite}
onOpenActivityTab={handleOpenActivity}
/>
</div>
{/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"
translate="yes"
>
<DetailPanel {asset} currentAlbum={album} albums={appearsInAlbums} on:close={() => ($isShowDetail = false)} />
<DetailPanel {asset} currentAlbum={album} albums={appearsInAlbums} onClose={() => ($isShowDetail = false)} />
</div>
{/if}
@ -625,10 +621,10 @@
assetId={asset.id}
{isLiked}
bind:reactions
on:addComment={handleAddComment}
on:deleteComment={handleRemoveComment}
on:deleteLike={() => (isLiked = null)}
on:close={() => (isShowActivity = false)}
onAddComment={handleAddComment}
onDeleteComment={handleRemoveComment}
onDeleteLike={() => (isLiked = null)}
onClose={() => (isShowActivity = false)}
/>
</div>
{/if}

View File

@ -81,10 +81,6 @@
{#if isShowChangeLocation}
<Portal>
<ChangeLocation
{asset}
on:confirm={({ detail: gps }) => handleConfirmChangeLocation(gps)}
on:cancel={() => (isShowChangeLocation = false)}
/>
<ChangeLocation {asset} onConfirm={handleConfirmChangeLocation} onCancel={() => (isShowChangeLocation = false)} />
</Portal>
{/if}

View File

@ -36,7 +36,6 @@
mdiPencil,
} from '@mdi/js';
import { DateTime } from 'luxon';
import { createEventDispatcher } from 'svelte';
import { t } from 'svelte-i18n';
import { slide } from 'svelte/transition';
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
@ -49,6 +48,7 @@
export let asset: AssetResponseDto;
export let albums: AlbumResponseDto[] = [];
export let currentAlbum: AlbumResponseDto | null = null;
export let onClose: () => void;
const getDimensions = (exifInfo: ExifResponseDto) => {
const { exifImageWidth: width, exifImageHeight: height } = exifInfo;
@ -106,10 +106,6 @@
? fromDateTimeOriginal(asset.exifInfo.dateTimeOriginal, timeZone)
: fromLocalDateTime(asset.localDateTime);
const dispatch = createEventDispatcher<{
close: void;
}>();
const getMegapixel = (width: number, height: number): number | undefined => {
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">
<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>
</div>
@ -332,8 +328,8 @@
<ChangeDate
initialDate={dateTime}
initialTimeZone={timeZone ?? ''}
on:confirm={({ detail: date }) => handleConfirmChangeDate(date)}
on:cancel={() => (isShowChangeDate = false)}
onConfirm={handleConfirmChangeDate}
onCancel={() => (isShowChangeDate = false)}
/>
{/if}
@ -511,9 +507,7 @@
<PersonSidePanel
assetId={asset.id}
assetType={asset.type}
on:close={() => {
showEditFaces = false;
}}
on:refresh={handleRefreshPeople}
onClose={() => (showEditFaces = false)}
onRefresh={handleRefreshPeople}
/>
{/if}

View File

@ -139,5 +139,5 @@
duration={$slideshowDelay}
bind:this={progressBar}
bind:status={progressBarStatus}
on:done={handleDone}
onDone={handleDone}
/>

View File

@ -4,7 +4,7 @@
import { getAssetPlaybackUrl, getAssetThumbnailUrl } from '$lib/utils';
import { handleError } from '$lib/utils/handle-error';
import { AssetMediaSize } from '@immich/sdk';
import { createEventDispatcher, tick } from 'svelte';
import { tick } from 'svelte';
import { swipe } from 'svelte-gestures';
import type { SwipeCustomEvent } from 'svelte-gestures';
import { fade } from 'svelte/transition';
@ -13,8 +13,10 @@
export let assetId: string;
export let loopVideo: boolean;
export let checksum: string;
export let onPreviousAsset: () => void;
export let onNextAsset: () => void;
export let onPreviousAsset: () => void = () => {};
export let onNextAsset: () => void = () => {};
export let onVideoEnded: () => void = () => {};
export let onVideoStarted: () => void = () => {};
let element: HTMLVideoElement | undefined = undefined;
let isVideoLoading = true;
@ -27,12 +29,10 @@
element.load();
}
const dispatch = createEventDispatcher<{ onVideoEnded: void; onVideoStarted: void }>();
const handleCanPlay = async (video: HTMLVideoElement) => {
try {
await video.play();
dispatch('onVideoStarted');
onVideoStarted();
} catch (error) {
if (error instanceof DOMException && error.name === 'NotAllowedError' && !forceMuted) {
await tryForceMutedPlay(video);
@ -75,7 +75,7 @@
use:swipe
on:swipe={onSwipe}
on:canplay={(e) => handleCanPlay(e.currentTarget)}
on:ended={() => dispatch('onVideoEnded')}
on:ended={onVideoEnded}
on:volumechange={(e) => {
if (!forceMuted) {
$videoViewerMuted = e.currentTarget.muted;

View File

@ -15,13 +15,5 @@
{#if projectionType === ProjectionType.EQUIRECTANGULAR}
<PanoramaViewer asset={{ id: assetId, type: AssetTypeEnum.Video }} />
{:else}
<VideoNativeViewer
{loopVideo}
{checksum}
{assetId}
{onPreviousAsset}
{onNextAsset}
on:onVideoEnded
on:onVideoStarted
/>
<VideoNativeViewer {loopVideo} {checksum} {assetId} {onPreviousAsset} {onNextAsset} />
{/if}

View File

@ -19,22 +19,18 @@
import LinkButton from './buttons/link-button.svelte';
import { clickOutside } from '$lib/actions/click-outside';
import { fly } from 'svelte/transition';
import { createEventDispatcher } from 'svelte';
let className = '';
export { className as class };
const dispatch = createEventDispatcher<{
select: T;
'click-outside': void;
}>();
export let options: T[];
export let selectedOption = options[0];
export let showMenu = false;
export let controlable = false;
export let hideTextOnSmallScreen = true;
export let title: string | undefined = undefined;
export let onSelect: (option: T) => void;
export let onClickOutside: () => void = () => {};
export let render: (item: T) => string | RenderedOption = String;
@ -43,11 +39,11 @@
showMenu = false;
}
dispatch('click-outside');
onClickOutside();
};
const handleSelectOption = (option: T) => {
dispatch('select', option);
onSelect(option);
selectedOption = option;
showMenu = false;

View File

@ -1,6 +1,5 @@
<script lang="ts">
import { mdiClose, mdiMagnify } from '@mdi/js';
import { createEventDispatcher } from 'svelte';
import type { SearchOptions } from '$lib/utils/dipatch';
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
@ -10,20 +9,20 @@
export let roundedBottom = true;
export let showLoadingSpinner: boolean;
export let placeholder: string;
export let onSearch: (options: SearchOptions) => void = () => {};
export let onReset: () => void = () => {};
let inputRef: HTMLElement;
const dispatch = createEventDispatcher<{ search: SearchOptions; reset: void }>();
const resetSearch = () => {
name = '';
dispatch('reset');
onReset();
inputRef?.focus();
};
const handleSearch = (event: KeyboardEvent) => {
if (event.key === 'Enter') {
dispatch('search', { force: true });
onSearch({ force: true });
}
};
</script>
@ -38,7 +37,7 @@
title={$t('search')}
size="16"
padding="2"
on:click={() => dispatch('search', { force: true })}
on:click={() => onSearch({ force: true })}
/>
<input
class="w-full gap-2 bg-gray-200 dark:bg-immich-dark-gray dark:text-white"
@ -47,7 +46,7 @@
bind:value={name}
bind:this={inputRef}
on:keydown={handleSearch}
on:input={() => dispatch('search', { force: false })}
on:input={() => onSearch({ force: false })}
/>
{#if showLoadingSpinner}
<div class="flex place-items-center">

View File

@ -4,7 +4,6 @@
import { getPeopleThumbnailUrl } from '$lib/utils';
import { AssetTypeEnum, type AssetFaceResponseDto, type PersonResponseDto } from '@immich/sdk';
import { mdiArrowLeftThin, mdiClose, mdiMagnify, mdiPlus } from '@mdi/js';
import { createEventDispatcher } from 'svelte';
import { linear } from 'svelte/easing';
import { fly } from 'svelte/transition';
import { photoViewer } from '$lib/stores/assets.store';
@ -19,6 +18,9 @@
export let editedFace: AssetFaceResponseDto;
export let assetId: string;
export let assetType: AssetTypeEnum;
export let onClose: () => void;
export let onCreatePerson: (featurePhoto: string | null) => void;
export let onReassign: (person: PersonResponseDto) => void;
// loading spinners
let isShowLoadingNewPerson = false;
@ -31,25 +33,16 @@
$: 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 timeout = setTimeout(() => (isShowLoadingNewPerson = true), timeBeforeShowLoadingSpinner);
const newFeaturePhoto = await zoomImageToBase64(editedFace, assetId, assetType, $photoViewer);
dispatch('createPerson', newFeaturePhoto);
onCreatePerson(newFeaturePhoto);
clearTimeout(timeout);
isShowLoadingNewPerson = false;
dispatch('createPerson', newFeaturePhoto);
onCreatePerson(newFeaturePhoto);
};
</script>
@ -60,7 +53,7 @@
<div class="flex place-items-center justify-between gap-2">
{#if !searchFaces}
<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>
</div>
<div class="flex justify-end gap-2">
@ -80,7 +73,7 @@
{/if}
</div>
{:else}
<CircleIconButton icon={mdiArrowLeftThin} title={$t('back')} on:click={handleBackButton} />
<CircleIconButton icon={mdiArrowLeftThin} title={$t('back')} on:click={onClose} />
<div class="w-full flex">
<SearchPeople
type="input"
@ -103,7 +96,7 @@
{#each showPeople as person (person.id)}
{#if !editedFace.person || person.id !== editedFace.person.id}
<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">
<ImageThumbnail
curve

View File

@ -1,6 +1,5 @@
<script lang="ts">
import { type PersonResponseDto } from '@immich/sdk';
import { createEventDispatcher } from 'svelte';
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
import Button from '../elements/buttons/button.svelte';
import SearchPeople from '$lib/components/faces-page/people-search.svelte';
@ -11,10 +10,7 @@
export let suggestedPeople: PersonResponseDto[];
export let thumbnailData: string;
export let isSearchingPeople: boolean;
const dispatch = createEventDispatcher<{
change: string;
}>();
export let onChange: (name: string) => void;
</script>
<div
@ -26,7 +22,7 @@
<form
class="ml-4 flex w-full justify-between gap-16"
autocomplete="off"
on:submit|preventDefault={() => dispatch('change', name)}
on:submit|preventDefault={() => onChange(name)}
>
<SearchPeople
bind:searchName={name}

View File

@ -1,7 +1,6 @@
<script lang="ts">
import { getPeopleThumbnailUrl } from '$lib/utils';
import { type PersonResponseDto } from '@immich/sdk';
import { createEventDispatcher } from 'svelte';
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
export let person: PersonResponseDto;
@ -10,20 +9,13 @@
export let thumbnailSize: number | null = null;
export let circle = false;
export let border = false;
let dispatch = createEventDispatcher<{
click: PersonResponseDto;
}>();
const handleOnClicked = () => {
dispatch('click', person);
};
export let onClick: (person: PersonResponseDto) => void = () => {};
</script>
<button
type="button"
class="relative rounded-lg transition-all"
on:click={handleOnClicked}
on:click={() => onClick(person)}
disabled={!selectable}
style:width={thumbnailSize ? thumbnailSize + 'px' : '100%'}
style:height={thumbnailSize ? thumbnailSize + 'px' : '100%'}

View File

@ -6,7 +6,7 @@
import { handleError } from '$lib/utils/handle-error';
import { getAllPeople, getPerson, mergePerson, type PersonResponseDto } from '@immich/sdk';
import { mdiCallMerge, mdiMerge, mdiSwapHorizontal } from '@mdi/js';
import { createEventDispatcher, onMount } from 'svelte';
import { onMount } from 'svelte';
import { flip } from 'svelte/animate';
import { quintOut } from 'svelte/easing';
import { fly } from 'svelte/transition';
@ -20,15 +20,13 @@
import { t } from 'svelte-i18n';
export let person: PersonResponseDto;
export let onBack: () => void;
export let onMerge: (mergedPerson: PersonResponseDto) => void;
let people: PersonResponseDto[] = [];
let selectedPeople: PersonResponseDto[] = [];
let screenHeight: number;
let dispatch = createEventDispatcher<{
back: void;
merge: PersonResponseDto;
}>();
$: hasSelection = selectedPeople.length > 0;
$: peopleToNotShow = [...selectedPeople, person];
@ -37,10 +35,6 @@
people = data.people;
});
const onClose = () => {
dispatch('back');
};
const handleSwapPeople = async () => {
[person, selectedPeople[0]] = [selectedPeople[0], person];
$page.url.searchParams.set(QueryParameter.ACTION, ActionQueryParameterValue.MERGE);
@ -88,7 +82,7 @@
message: $t('merged_people_count', { values: { count } }),
type: NotificationType.Info,
});
dispatch('merge', mergedPerson);
onMerge(mergedPerson);
} catch (error) {
handleError(error, $t('cannot_merge_people'));
}
@ -101,7 +95,7 @@
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"
>
<ControlAppBar on:close={onClose}>
<ControlAppBar onClose={onBack}>
<svelte:fragment slot="leading">
{#if hasSelection}
{$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">
{#each selectedPeople as person (person.id)}
<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>
{/each}
@ -152,7 +146,7 @@
</div>
</div>
<PeopleList {people} {peopleToNotShow} {screenHeight} on:select={({ detail }) => onSelect(detail)} />
<PeopleList {people} {peopleToNotShow} {screenHeight} {onSelect} />
</section>
</section>
</section>

View File

@ -4,7 +4,6 @@
import { getPeopleThumbnailUrl } from '$lib/utils';
import { type PersonResponseDto } from '@immich/sdk';
import { mdiArrowLeft, mdiMerge } from '@mdi/js';
import { createEventDispatcher } from 'svelte';
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
import Button from '../elements/buttons/button.svelte';
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
@ -13,25 +12,22 @@
export let personMerge1: PersonResponseDto;
export let personMerge2: PersonResponseDto;
export let potentialMergePeople: PersonResponseDto[];
export let onReject: () => void;
export let onConfirm: ([personMerge1, personMerge2]: [PersonResponseDto, PersonResponseDto]) => void;
export let onClose: () => void;
let choosePersonToMerge = false;
const title = personMerge2.name;
const dispatch = createEventDispatcher<{
reject: void;
confirm: [PersonResponseDto, PersonResponseDto];
close: void;
}>();
const changePersonToMerge = (newperson: PersonResponseDto) => {
const index = potentialMergePeople.indexOf(newperson);
const changePersonToMerge = (newPerson: PersonResponseDto) => {
const index = potentialMergePeople.indexOf(newPerson);
[potentialMergePeople[index], personMerge2] = [personMerge2, potentialMergePeople[index]];
choosePersonToMerge = false;
};
</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">
{#if !choosePersonToMerge}
<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>
</div>
<svelte:fragment slot="sticky-bottom">
<Button fullwidth color="gray" on:click={() => dispatch('reject')}>{$t('no')}</Button>
<Button fullwidth on:click={() => dispatch('confirm', [personMerge1, personMerge2])}>{$t('yes')}</Button>
<Button fullwidth color="gray" on:click={onReject}>{$t('no')}</Button>
<Button fullwidth on:click={() => onConfirm([personMerge1, personMerge2])}>{$t('yes')}</Button>
</svelte:fragment>
</FullScreenModal>

View File

@ -9,7 +9,6 @@
mdiDotsVertical,
mdiEyeOffOutline,
} from '@mdi/js';
import { createEventDispatcher } from 'svelte';
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
import MenuOption from '../shared-components/context-menu/menu-option.svelte';
import { t } from 'svelte-i18n';
@ -18,19 +17,12 @@
export let person: PersonResponseDto;
export let preload = false;
type MenuItemEvent = 'change-name' | 'set-birth-date' | 'merge-people' | 'hide-person';
let dispatch = createEventDispatcher<{
'change-name': void;
'set-birth-date': void;
'merge-people': void;
'hide-person': void;
}>();
export let onChangeName: () => void;
export let onSetBirthDate: () => void;
export let onMergePeople: () => void;
export let onHidePerson: () => void;
let showVerticalDots = false;
const onMenuClick = (event: MenuItemEvent) => {
dispatch(event);
};
</script>
<div
@ -76,18 +68,10 @@
icon={mdiDotsVertical}
title={$t('show_person_options')}
>
<MenuOption onClick={() => onMenuClick('hide-person')} icon={mdiEyeOffOutline} text={$t('hide_person')} />
<MenuOption onClick={() => onMenuClick('change-name')} icon={mdiAccountEditOutline} text={$t('change_name')} />
<MenuOption
onClick={() => onMenuClick('set-birth-date')}
icon={mdiCalendarEditOutline}
text={$t('set_date_of_birth')}
/>
<MenuOption
onClick={() => onMenuClick('merge-people')}
icon={mdiAccountMultipleCheckOutline}
text={$t('merge_people')}
/>
<MenuOption onClick={onHidePerson} icon={mdiEyeOffOutline} text={$t('hide_person')} />
<MenuOption onClick={onChangeName} icon={mdiAccountEditOutline} text={$t('change_name')} />
<MenuOption onClick={onSetBirthDate} icon={mdiCalendarEditOutline} text={$t('set_date_of_birth')} />
<MenuOption onClick={onMergePeople} icon={mdiAccountMultipleCheckOutline} text={$t('merge_people')} />
</ButtonContextMenu>
</div>
{/if}

View File

@ -1,6 +1,5 @@
<script lang="ts">
import { type PersonResponseDto } from '@immich/sdk';
import { createEventDispatcher } from 'svelte';
import FaceThumbnail from './face-thumbnail.svelte';
import SearchPeople from '$lib/components/faces-page/people-search.svelte';
import { t } from 'svelte-i18n';
@ -8,15 +7,13 @@
export let screenHeight: number;
export let people: PersonResponseDto[];
export let peopleToNotShow: PersonResponseDto[];
export let onSelect: (person: PersonResponseDto) => void;
let searchedPeopleLocal: PersonResponseDto[] = [];
let name = '';
let showPeople: PersonResponseDto[];
let dispatch = createEventDispatcher<{
select: PersonResponseDto;
}>();
$: {
showPeople = name ? searchedPeopleLocal : people;
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">
{#each showPeople as person (person.id)}
<FaceThumbnail
{person}
on:click={() => {
dispatch('select', person);
}}
circle
border
selectable
/>
<FaceThumbnail {person} on:click={() => onSelect(person)} circle border selectable />
{/each}
</div>
</div>

View File

@ -18,7 +18,7 @@
import { mdiAccountOff } from '@mdi/js';
import Icon from '$lib/components/elements/icon.svelte';
import { mdiArrowLeftThin, mdiMinus, mdiRestart } from '@mdi/js';
import { createEventDispatcher, onMount } from 'svelte';
import { onMount } from 'svelte';
import { linear } from 'svelte/easing';
import { fly } from 'svelte/transition';
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
@ -31,6 +31,8 @@
export let assetId: string;
export let assetType: AssetTypeEnum;
export let onClose: () => void;
export let onRefresh: () => void;
// keep track of the changes
let peopleToCreate: string[] = [];
@ -56,11 +58,6 @@
const thumbnailWidth = '90px';
const dispatch = createEventDispatcher<{
close: void;
refresh: void;
}>();
async function loadPeople() {
const timeout = setTimeout(() => (isShowLoadingPeople = true), timeBeforeShowLoadingSpinner);
try {
@ -85,7 +82,7 @@
) {
clearTimeout(loaderLoadingDoneTimeout);
clearTimeout(automaticRefreshTimeout);
dispatch('refresh');
onRefresh();
}
};
@ -98,10 +95,6 @@
return b.every((valueB) => a.includes(valueB));
};
const handleBackButton = () => {
dispatch('close');
};
const handleReset = (id: string) => {
if (selectedPersonToReassign[id]) {
delete selectedPersonToReassign[id];
@ -153,9 +146,9 @@
isShowLoadingDone = false;
if (peopleToCreate.length === 0) {
clearTimeout(loaderLoadingDoneTimeout);
dispatch('refresh');
onRefresh();
} 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 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>
</div>
{#if !isShowLoadingDone}
@ -336,8 +329,8 @@
{editedFace}
{assetId}
{assetType}
on:close={() => (showSelectedFaces = false)}
on:createPerson={(event) => handleCreatePerson(event.detail)}
on:reassign={(event) => handleReassignFace(event.detail)}
onClose={() => (showSelectedFaces = false)}
onCreatePerson={handleCreatePerson}
onReassign={handleReassignFace}
/>
{/if}

View File

@ -1,5 +1,4 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import Button from '../elements/buttons/button.svelte';
import FullScreenModal from '../shared-components/full-screen-modal.svelte';
import { mdiCake } from '@mdi/js';
@ -7,28 +6,20 @@
import { t } from 'svelte-i18n';
export let birthDate: string;
const dispatch = createEventDispatcher<{
close: void;
updated: string;
}>();
export let onClose: () => void;
export let onUpdate: (birthDate: string) => void;
const todayFormatted = new Date().toISOString().split('T')[0];
const handleCancel = () => dispatch('close');
const handleSubmit = () => {
dispatch('updated', birthDate);
};
</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">
<p class="text-sm dark:text-immich-dark-fg">
{$t('birthdate_set_description')}
</p>
</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">
<DateInput
class="immich-form-input"
@ -41,7 +32,7 @@
</div>
</form>
<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>
</svelte:fragment>
</FullScreenModal>

View File

@ -10,7 +10,7 @@
type PersonResponseDto,
} from '@immich/sdk';
import { mdiMerge, mdiPlus } from '@mdi/js';
import { createEventDispatcher, onMount } from 'svelte';
import { onMount } from 'svelte';
import { quintOut } from 'svelte/easing';
import { fly } from 'svelte/transition';
import Button from '../elements/buttons/button.svelte';
@ -23,6 +23,8 @@
export let assetIds: string[];
export let personAssets: PersonResponseDto;
export let onConfirm: () => void;
export let onClose: () => void;
let people: PersonResponseDto[] = [];
let selectedPerson: PersonResponseDto | null = null;
@ -34,11 +36,6 @@
$: peopleToNotShow = selectedPerson ? [personAssets, selectedPerson] : [personAssets];
let dispatch = createEventDispatcher<{
confirm: void;
close: void;
}>();
const selectedPeople: AssetFaceUpdateItem[] = [];
for (const assetId of assetIds) {
@ -50,10 +47,6 @@
people = data.people;
});
const onClose = () => {
dispatch('close');
};
const handleSelectedPerson = (person: PersonResponseDto) => {
if (selectedPerson && selectedPerson.id === person.id) {
handleRemoveSelectedPerson();
@ -87,7 +80,7 @@
}
showLoadingSpinnerCreate = false;
dispatch('confirm');
onConfirm();
};
const handleReassign = async () => {
@ -113,7 +106,7 @@
}
showLoadingSpinnerReassign = false;
dispatch('confirm');
onConfirm();
};
</script>
@ -123,7 +116,7 @@
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"
>
<ControlAppBar on:close={onClose}>
<ControlAppBar {onClose}>
<svelte:fragment slot="leading">
<slot name="header" />
<div />
@ -180,7 +173,7 @@
</div>
</div>
{/if}
<PeopleList {people} {peopleToNotShow} {screenHeight} on:select={({ detail }) => handleSelectedPerson(detail)} />
<PeopleList {people} {peopleToNotShow} {screenHeight} onSelect={handleSelectedPerson} />
</section>
</section>
</section>

View File

@ -1,20 +1,15 @@
<script lang="ts">
import { copyToClipboard } from '$lib/utils';
import { mdiKeyVariant } from '@mdi/js';
import { createEventDispatcher } from 'svelte';
import Button from '../elements/buttons/button.svelte';
import FullScreenModal from '../shared-components/full-screen-modal.svelte';
import { t } from 'svelte-i18n';
export let secret = '';
const dispatch = createEventDispatcher<{
done: void;
}>();
const handleDone = () => dispatch('done');
export let onDone: () => void;
</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">
<p class="text-sm dark:text-immich-dark-fg">
{$t('api_key_description')}
@ -28,6 +23,6 @@
<svelte:fragment slot="sticky-bottom">
<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>
</FullScreenModal>

View File

@ -1,10 +1,11 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import Button from '../elements/buttons/button.svelte';
import PasswordField from '../shared-components/password-field.svelte';
import { updateMyUser } from '@immich/sdk';
import { t } from 'svelte-i18n';
export let onSuccess: () => void;
let errorMessage: string;
let success: string;
@ -23,17 +24,13 @@
}
}
const dispatch = createEventDispatcher<{
success: void;
}>();
async function changePassword() {
if (valid) {
errorMessage = '';
await updateMyUser({ userUpdateMeDto: { password: String(password) } });
dispatch('success');
onSuccess();
}
}
</script>

View File

@ -5,13 +5,14 @@
import { ByteUnit, convertToBytes } from '$lib/utils/byte-units';
import { handleError } from '$lib/utils/handle-error';
import { createUserAdmin } from '@immich/sdk';
import { createEventDispatcher } from 'svelte';
import { t } from 'svelte-i18n';
import Button from '../elements/buttons/button.svelte';
import Slider from '../elements/slider.svelte';
import PasswordField from '../shared-components/password-field.svelte';
export let onClose: () => void;
export let onSubmit: () => void;
export let onCancel: () => void;
let error: string;
let success: string;
@ -39,10 +40,6 @@
canCreateUser = true;
}
}
const dispatch = createEventDispatcher<{
submit: void;
cancel: void;
}>();
async function registerUser() {
if (canCreateUser && !isCreatingUser) {
@ -63,7 +60,7 @@
success = $t('new_user_created');
dispatch('submit');
onSubmit();
return;
} catch (error) {
@ -132,7 +129,7 @@
{/if}
</form>
<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>
</svelte:fragment>
</FullScreenModal>

View File

@ -5,7 +5,6 @@
import { handleError } from '$lib/utils/handle-error';
import { updateUserAdmin, type UserAdminResponseDto } from '@immich/sdk';
import { mdiAccountEditOutline } from '@mdi/js';
import { createEventDispatcher } from 'svelte';
import Button from '../elements/buttons/button.svelte';
import { dialogController } from '$lib/components/shared-components/dialog/dialog';
import { t } from 'svelte-i18n';
@ -15,6 +14,8 @@
export let canResetPassword = true;
export let newPassword: string;
export let onClose: () => void;
export let onResetPasswordSuccess: () => void;
export let onEditSuccess: () => void;
let error: string;
let success: string;
@ -27,12 +28,6 @@
!!quotaSize &&
convertToBytes(Number(quotaSize), ByteUnit.GiB) > $serverInfo.diskSizeRaw;
const dispatch = createEventDispatcher<{
close: void;
resetPasswordSuccess: void;
editSuccess: void;
}>();
const editUser = async () => {
try {
const { id, email, name, storageLabel } = user;
@ -46,7 +41,7 @@
},
});
dispatch('editSuccess');
onEditSuccess();
} catch (error) {
handleError(error, $t('errors.unable_to_update_user'));
}
@ -72,7 +67,7 @@
},
});
dispatch('resetPasswordSuccess');
onResetPasswordSuccess();
} catch (error) {
handleError(error, $t('errors.unable_to_reset_password'));
}

View File

@ -1,5 +1,4 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import Button from '../elements/buttons/button.svelte';
import FullScreenModal from '../shared-components/full-screen-modal.svelte';
import { mdiFolderRemove } from '@mdi/js';
@ -10,6 +9,9 @@
export let exclusionPatterns: string[] = [];
export let isEditing = false;
export let submitText = $t('submit');
export let onCancel: () => void;
export let onSubmit: (exclusionPattern: string) => void;
export let onDelete: () => void = () => {};
onMount(() => {
if (isEditing) {
@ -19,18 +21,10 @@
$: isDuplicate = exclusionPattern !== null && 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>
<FullScreenModal title={$t('add_exclusion_pattern')} icon={mdiFolderRemove} onClose={handleCancel}>
<form on:submit|preventDefault={() => handleSubmit()} autocomplete="off" id="add-exclusion-pattern-form">
<FullScreenModal title={$t('add_exclusion_pattern')} icon={mdiFolderRemove} onClose={onCancel}>
<form on:submit|preventDefault={() => onSubmit(exclusionPattern)} autocomplete="off" id="add-exclusion-pattern-form">
<p class="py-5 text-sm">
{$t('admin.exclusion_pattern_description')}
<br /><br />
@ -53,9 +47,9 @@
</div>
</form>
<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}
<Button color="red" fullwidth on:click={() => dispatch('delete')}>{$t('delete')}</Button>
<Button color="red" fullwidth on:click={onDelete}>{$t('delete')}</Button>
{/if}
<Button type="submit" disabled={!canSubmit} fullwidth form="add-exclusion-pattern-form">{submitText}</Button>
</svelte:fragment>

View File

@ -1,5 +1,4 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import Button from '../elements/buttons/button.svelte';
import FullScreenModal from '../shared-components/full-screen-modal.svelte';
import { mdiFolderSync } from '@mdi/js';
@ -12,6 +11,9 @@
export let cancelText = $t('cancel');
export let submitText = $t('save');
export let isEditing = false;
export let onCancel: () => void;
export let onSubmit: (importPath: string | null) => void;
export let onDelete: () => void = () => {};
onMount(() => {
if (isEditing) {
@ -21,18 +23,10 @@
$: isDuplicate = 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>
<FullScreenModal {title} icon={mdiFolderSync} onClose={handleCancel}>
<form on:submit|preventDefault={() => handleSubmit()} autocomplete="off" id="library-import-path-form">
<FullScreenModal {title} icon={mdiFolderSync} onClose={onCancel}>
<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>
<div class="my-4 flex flex-col gap-2">
@ -47,9 +41,9 @@
</div>
</form>
<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}
<Button color="red" fullwidth on:click={() => dispatch('delete')}>{$t('delete')}</Button>
<Button color="red" fullwidth on:click={onDelete}>{$t('delete')}</Button>
{/if}
<Button type="submit" disabled={!canSubmit} fullwidth form="library-import-path-form">{submitText}</Button>
</svelte:fragment>

View File

@ -1,5 +1,5 @@
<script lang="ts">
import { createEventDispatcher, onMount } from 'svelte';
import { onMount } from 'svelte';
import { handleError } from '../../utils/handle-error';
import Button from '../elements/buttons/button.svelte';
import LibraryImportPathForm from './library-import-path-form.svelte';
@ -12,6 +12,8 @@
import { t } from 'svelte-i18n';
export let library: LibraryResponseDto;
export let onCancel: () => void;
export let onSubmit: (library: LibraryResponseDto) => void;
let addImportPath = false;
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 () => {
if (!addImportPath || !importPathToAdd) {
return;
@ -153,8 +142,8 @@
submitText={$t('add')}
bind:importPath={importPathToAdd}
{importPaths}
on:submit={handleAddImportPath}
on:cancel={() => {
onSubmit={handleAddImportPath}
onCancel={() => {
addImportPath = false;
importPathToAdd = null;
}}
@ -168,15 +157,13 @@
isEditing={true}
bind:importPath={editedImportPath}
{importPaths}
on:submit={handleEditImportPath}
on:delete={handleDeleteImportPath}
on:cancel={() => {
editImportPath = null;
}}
onSubmit={handleEditImportPath}
onDelete={handleDeleteImportPath}
onCancel={() => (editImportPath = null)}
/>
{/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">
<tbody class="block w-full overflow-y-auto rounded-md border dark:border-immich-dark-gray">
{#each validatedPaths as validatedPath, listIndex}
@ -251,7 +238,7 @@
>
</div>
<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>
</div>
</div>

View File

@ -1,31 +1,20 @@
<script lang="ts">
import type { LibraryResponseDto } from '@immich/sdk';
import { createEventDispatcher } from 'svelte';
import Button from '../elements/buttons/button.svelte';
import { t } from 'svelte-i18n';
export let library: Partial<LibraryResponseDto>;
const dispatch = createEventDispatcher<{
cancel: void;
submit: Partial<LibraryResponseDto>;
}>();
const handleCancel = () => {
dispatch('cancel');
};
const handleSubmit = () => {
dispatch('submit', { ...library });
};
export let onCancel: () => void;
export let onSubmit: (library: Partial<LibraryResponseDto>) => void;
</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">
<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} />
</div>
<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>
</div>
</form>

View File

@ -1,7 +1,7 @@
<script lang="ts">
import { type LibraryResponseDto } from '@immich/sdk';
import { mdiPencilOutline } from '@mdi/js';
import { createEventDispatcher, onMount } from 'svelte';
import { onMount } from 'svelte';
import { handleError } from '../../utils/handle-error';
import Button from '../elements/buttons/button.svelte';
import LibraryExclusionPatternForm from './library-exclusion-pattern-form.svelte';
@ -9,6 +9,8 @@
import { t } from 'svelte-i18n';
export let library: Partial<LibraryResponseDto>;
export let onCancel: () => void;
export let onSubmit: (library: Partial<LibraryResponseDto>) => void;
let addExclusionPattern = false;
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 = () => {
if (!addExclusionPattern) {
return;
@ -106,10 +96,8 @@
submitText={$t('add')}
bind:exclusionPattern={exclusionPatternToAdd}
{exclusionPatterns}
on:submit={handleAddExclusionPattern}
on:cancel={() => {
addExclusionPattern = false;
}}
onSubmit={handleAddExclusionPattern}
onCancel={() => (addExclusionPattern = false)}
/>
{/if}
@ -119,15 +107,13 @@
isEditing={true}
bind:exclusionPattern={editedExclusionPattern}
{exclusionPatterns}
on:submit={handleEditExclusionPattern}
on:delete={handleDeleteExclusionPattern}
on:cancel={() => {
editExclusionPattern = null;
}}
onSubmit={handleEditExclusionPattern}
onDelete={handleDeleteExclusionPattern}
onCancel={() => (editExclusionPattern = null)}
/>
{/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">
<tbody class="block w-full overflow-y-auto rounded-md border dark:border-immich-dark-gray">
{#each exclusionPatterns as exclusionPattern, listIndex}
@ -178,7 +164,7 @@
</table>
<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>
</div>
</form>

View File

@ -1,5 +1,4 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import Button from '../elements/buttons/button.svelte';
import FullScreenModal from '../shared-components/full-screen-modal.svelte';
import { mdiFolderSync } from '@mdi/js';
@ -9,6 +8,9 @@
import SettingSelect from '$lib/components/shared-components/settings/setting-select.svelte';
import { t } from 'svelte-i18n';
export let onCancel: () => void;
export let onSubmit: (ownerId: string) => void;
let ownerId: string = $user.id;
let userOptions: { value: string; text: string }[] = [];
@ -17,25 +19,16 @@
const users = await searchUsersAdmin({});
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>
<FullScreenModal title={$t('select_library_owner')} icon={mdiFolderSync} onClose={handleCancel}>
<form on:submit|preventDefault={() => handleSubmit()} autocomplete="off" id="select-library-owner-form">
<FullScreenModal title={$t('select_library_owner')} icon={mdiFolderSync} onClose={onCancel}>
<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>
<SettingSelect bind:value={ownerId} options={userOptions} name="user" />
</form>
<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>
</svelte:fragment>
</FullScreenModal>

View File

@ -21,7 +21,7 @@
<header>
{#if !hideNavbar}
<NavigationBar {showUploadButton} on:uploadClicked={() => openFileUploadDialog()} />
<NavigationBar {showUploadButton} onUploadClick={() => openFileUploadDialog()} />
{/if}
<slot name="header" />

View File

@ -4,7 +4,6 @@
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
import type { MapSettings } from '$lib/stores/preferences.store';
import { Duration } from 'luxon';
import { createEventDispatcher } from 'svelte';
import { t } from 'svelte-i18n';
import { fly } from 'svelte/transition';
import Button from '../elements/buttons/button.svelte';
@ -12,19 +11,15 @@
import DateInput from '../elements/date-input.svelte';
export let settings: MapSettings;
export let onClose: () => void;
export let onSave: (settings: MapSettings) => void;
let customDateRange = !!settings.dateAfter || !!settings.dateBefore;
const dispatch = createEventDispatcher<{
close: void;
save: MapSettings;
}>();
const handleClose = () => dispatch('close');
</script>
<FullScreenModal title={$t('map_settings')} onClose={handleClose}>
<FullScreenModal title={$t('map_settings')} {onClose}>
<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"
id="map-settings-form"
>
@ -108,7 +103,7 @@
{/if}
</form>
<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>
</svelte:fragment>
</FullScreenModal>

View File

@ -250,7 +250,7 @@
<section id="memory-viewer" class="w-full bg-immich-dark-gray" bind:this={memoryWrapper}>
{#if current && current.memory.assets.length > 0}
<ControlAppBar on:close={() => goto(AppRoute.PHOTOS)} forceDark>
<ControlAppBar onClose={() => goto(AppRoute.PHOTOS)} forceDark>
<svelte:fragment slot="leading">
<p class="text-lg">
{$memoryLaneTitle(current.memory.yearsAgo)}

View File

@ -40,8 +40,8 @@
{#if showAlbumPicker}
<AlbumSelectionModal
{shared}
on:newAlbum={({ detail }) => handleAddToNewAlbum(detail)}
on:album={({ detail }) => handleAddToAlbum(detail)}
onNewAlbum={handleAddToNewAlbum}
onAlbumClick={handleAddToAlbum}
onClose={handleHideAlbumPicker}
/>
{/if}

View File

@ -31,9 +31,5 @@
<MenuOption text={$t('change_date')} icon={mdiCalendarEditOutline} onClick={() => (isShowChangeDate = true)} />
{/if}
{#if isShowChangeDate}
<ChangeDate
initialDate={DateTime.now()}
on:confirm={({ detail: date }) => handleConfirm(date)}
on:cancel={() => (isShowChangeDate = false)}
/>
<ChangeDate initialDate={DateTime.now()} onConfirm={handleConfirm} onCancel={() => (isShowChangeDate = false)} />
{/if}

View File

@ -35,8 +35,5 @@
/>
{/if}
{#if isShowChangeLocation}
<ChangeLocation
on:confirm={({ detail: point }) => handleConfirm(point)}
on:cancel={() => (isShowChangeLocation = false)}
/>
<ChangeLocation onConfirm={handleConfirm} onCancel={() => (isShowChangeLocation = false)} />
{/if}

View File

@ -49,7 +49,7 @@
{#if isShowConfirmation}
<DeleteAssetDialog
size={getOwnedAssets().size}
on:confirm={handleDelete}
on:cancel={() => (isShowConfirmation = false)}
onConfirm={handleDelete}
onCancel={() => (isShowConfirmation = false)}
/>
{/if}

View File

@ -8,7 +8,7 @@
import { findTotalOffset, type DateGroup, type ScrollTargetListener } from '$lib/utils/timeline-util';
import type { AssetResponseDto } from '@immich/sdk';
import { mdiCheckCircle, mdiCircleOutline } from '@mdi/js';
import { createEventDispatcher, onDestroy } from 'svelte';
import { onDestroy } from 'svelte';
import { fly } from 'svelte/transition';
import Thumbnail from '../assets/thumbnail/thumbnail.svelte';
import { TUNABLES } from '$lib/utils/tunables';
@ -29,6 +29,9 @@
export let onScrollTarget: ScrollTargetListener | 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();
$: bucketDate = bucket.bucketDate;
@ -41,11 +44,6 @@
const TITLE_HEIGHT = 51;
const { selectedGroup, selectedAssets, assetSelectionCandidates, isMultiSelectState } = assetInteractionStore;
const dispatch = createEventDispatcher<{
select: { title: string; assets: AssetResponseDto[] };
selectAssets: AssetResponseDto;
selectAssetCandidates: AssetResponseDto | null;
}>();
let isMouseOverGroup = false;
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) => {
dispatch('selectAssets', asset);
onSelectAssets(asset);
// 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;
@ -86,7 +84,7 @@
hoveredDateGroup = groupTitle;
if ($isMultiSelectState) {
dispatch('selectAssetCandidates', asset);
onSelectAssetCandidates(asset);
}
};

View File

@ -28,7 +28,7 @@
import { TUNABLES } from '$lib/utils/tunables';
import type { AlbumResponseDto, AssetResponseDto } from '@immich/sdk';
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 Scrubber from '../shared-components/scrubber/scrubber.svelte';
import ShowShortcuts from '../shared-components/show-shortcuts.svelte';
@ -64,6 +64,8 @@
export let isShared = false;
export let album: AlbumResponseDto | null = null;
export let isShowDeleteConfirmation = false;
export let onSelect: (asset: AssetResponseDto) => void = () => {};
export let onEscape: () => void = () => {};
let { isViewing: showAssetViewer, asset: viewingAsset, preloadAssets, gridScrollTarget } = assetViewingStore;
const { assetSelectionCandidates, assetSelectionStart, selectedGroup, selectedAssets, isMultiSelectState } =
@ -127,8 +129,6 @@
},
} = TUNABLES;
const dispatch = createEventDispatcher<{ select: AssetResponseDto; escape: void }>();
const isViewportOrigin = () => {
return viewport.height === 0 && viewport.width === 0;
};
@ -447,7 +447,7 @@
const ids = await stackAssets(Array.from($selectedAssets));
if (ids) {
$assetStore.removeAssets(ids);
dispatch('escape');
onEscape();
}
};
@ -471,7 +471,7 @@
}
const shortcuts: ShortcutOptions[] = [
{ shortcut: { key: 'Escape' }, onShortcut: () => dispatch('escape') },
{ shortcut: { key: 'Escape' }, onShortcut: onEscape },
{ shortcut: { key: '?', shift: true }, onShortcut: () => (showShortcuts = !showShortcuts) },
{ shortcut: { key: '/' }, onShortcut: () => goto(AppRoute.EXPLORE) },
{ shortcut: { key: 'A', ctrl: true }, onShortcut: () => selectAllAssets($assetStore, assetInteractionStore) },
@ -539,7 +539,7 @@
return !!nextAsset;
};
const handleClose = async ({ detail: { asset } }: { detail: { asset: AssetResponseDto } }) => {
const handleClose = async ({ asset }: { asset: AssetResponseDto }) => {
assetViewingStore.showAssetViewer(false);
showSkeleton = true;
$gridScrollTarget = { at: asset.id };
@ -554,7 +554,7 @@
case AssetAction.DELETE: {
// find the next asset to show or close the viewer
// 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
assetStore.removeAssets([action.asset.id]);
@ -649,7 +649,7 @@
return;
}
dispatch('select', asset);
onSelect(asset);
if (singleSelect) {
element.scrollTop = 0;
@ -754,8 +754,8 @@
{#if isShowDeleteConfirmation}
<DeleteAssetDialog
size={idsSelectedAssets.length}
on:cancel={() => (isShowDeleteConfirmation = false)}
on:confirm={() => handlePromiseError(trashOrDelete(true))}
onCancel={() => (isShowDeleteConfirmation = false)}
onConfirm={() => handlePromiseError(trashOrDelete(true))}
/>
{/if}
@ -847,9 +847,9 @@
{onAssetInGrid}
{bucket}
viewport={safeViewport}
on:select={({ detail: group }) => handleGroupSelect(group.title, group.assets)}
on:selectAssetCandidates={({ detail: asset }) => handleSelectAssetCandidates(asset)}
on:selectAssets={({ detail: asset }) => handleSelectAssets(asset)}
onSelect={({ title, assets }) => handleGroupSelect(title, assets)}
onSelectAssetCandidates={handleSelectAssetCandidates}
onSelectAssets={handleSelectAssets}
/>
{/if}
</div>
@ -869,9 +869,9 @@
{isShared}
{album}
onAction={handleAction}
on:previous={handlePrevious}
on:next={handleNext}
on:close={handleClose}
onPrevious={handlePrevious}
onNext={handleNext}
onClose={handleClose}
/>
{/await}
{/if}

View File

@ -30,7 +30,7 @@
});
</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">
<p class="block sm:hidden">{assets.size}</p>
<p class="hidden sm:block">{$t('selected_count', { values: { count: assets.size } })}</p>

View File

@ -1,5 +1,4 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import ConfirmDialog from '../shared-components/dialog/confirm-dialog.svelte';
import { showDeleteModal } from '$lib/stores/preferences.store';
import Checkbox from '$lib/components/elements/checkbox.svelte';
@ -7,19 +6,16 @@
import FormatMessage from '$lib/components/i18n/format-message.svelte';
export let size: number;
export let onConfirm: () => void;
export let onCancel: () => void;
let checked = false;
const dispatch = createEventDispatcher<{
confirm: void;
cancel: void;
}>();
const handleConfirm = () => {
if (checked) {
$showDeleteModal = false;
}
dispatch('confirm');
onConfirm();
};
</script>
@ -27,7 +23,7 @@
title={$t('permanently_delete_assets_count', { values: { count: size } })}
confirmText={$t('delete')}
onConfirm={handleConfirm}
onCancel={() => dispatch('cancel')}
{onCancel}
>
<svelte:fragment slot="prompt">
<p>

View File

@ -2,7 +2,7 @@
import Icon from '$lib/components/elements/icon.svelte';
import { getAllAlbums, type AlbumResponseDto } from '@immich/sdk';
import { mdiPlus } from '@mdi/js';
import { createEventDispatcher, onMount } from 'svelte';
import { onMount } from 'svelte';
import AlbumListItem from '../asset-viewer/album-list-item.svelte';
import { normalizeSearchString } from '$lib/utils/string-utils';
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
@ -11,17 +11,15 @@
import { sortAlbums } from '$lib/utils/album-utils';
import { albumViewSettings } from '$lib/stores/preferences.store';
export let onNewAlbum: (search: string) => void;
export let onAlbumClick: (album: AlbumResponseDto) => void;
let albums: AlbumResponseDto[] = [];
let recentAlbums: AlbumResponseDto[] = [];
let filteredAlbums: AlbumResponseDto[] = [];
let loading = true;
let search = '';
const dispatch = createEventDispatcher<{
newAlbum: string;
album: AlbumResponseDto;
}>();
export let shared: boolean;
export let onClose: () => void;
@ -40,14 +38,6 @@
{ sortBy: $albumViewSettings.sortBy, orderBy: $albumViewSettings.sortOrder },
);
const handleSelect = (album: AlbumResponseDto) => {
dispatch('album', album);
};
const handleNew = () => {
dispatch('newAlbum', search.length > 0 ? search : '');
};
const getTitle = () => {
if (shared) {
return $t('add_to_shared_album');
@ -81,7 +71,7 @@
<div class="immich-scrollbar overflow-y-auto">
<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"
>
<div class="flex h-12 w-12 items-center justify-center">
@ -96,7 +86,7 @@
{#if !shared && search.length === 0}
<p class="px-5 py-3 text-xs">{$t('recent').toUpperCase()}</p>
{#each recentAlbums as album (album.id)}
<AlbumListItem {album} on:album={() => handleSelect(album)} />
<AlbumListItem {album} onAlbumClick={() => onAlbumClick(album)} />
{/each}
{/if}
@ -106,7 +96,7 @@
</p>
{/if}
{#each filteredAlbums as album (album.id)}
<AlbumListItem {album} searchQuery={search} on:album={() => handleSelect(album)} />
<AlbumListItem {album} searchQuery={search} onAlbumClick={() => onAlbumClick(album)} />
{/each}
{:else if albums.length > 0}
<p class="px-5 py-1 text-sm">{$t('no_albums_with_name_yet')}</p>

View File

@ -1,5 +1,4 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import { DateTime } from 'luxon';
import ConfirmDialog from './dialog/confirm-dialog.svelte';
import Combobox from './combobox.svelte';
@ -8,6 +7,8 @@
export let initialDate: DateTime = DateTime.now();
export let initialTimeZone: string = '';
export let onCancel: () => void;
export let onConfirm: (date: string) => void;
type ZoneOption = {
/**
@ -118,17 +119,10 @@
return zoneA.value.localeCompare(zoneB.value, undefined, { sensitivity: 'base' });
}
const dispatch = createEventDispatcher<{
cancel: void;
confirm: string;
}>();
const handleCancel = () => dispatch('cancel');
const handleConfirm = () => {
const value = date.toISO();
if (value) {
dispatch('confirm', value);
onConfirm(value);
}
};
</script>
@ -139,7 +133,7 @@
prompt="Please select a new date:"
disabled={!date.isValid}
onConfirm={handleConfirm}
onCancel={handleCancel}
{onCancel}
>
<div class="flex flex-col text-left gap-2" slot="prompt">
<div class="flex flex-col">

View File

@ -1,5 +1,4 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import ConfirmDialog from './dialog/confirm-dialog.svelte';
import { timeDebounceOnSearch } from '$lib/constants';
import { handleError } from '$lib/utils/handle-error';
@ -14,13 +13,15 @@
import { t } from 'svelte-i18n';
import CoordinatesInput from '$lib/components/shared-components/coordinates-input.svelte';
export let asset: AssetResponseDto | undefined = undefined;
interface Point {
lng: number;
lat: number;
}
export let asset: AssetResponseDto | undefined = undefined;
export let onCancel: () => void;
export let onConfirm: (point: Point) => void;
let places: PlacesResponseDto[] = [];
let suggestedPlaces: PlacesResponseDto[] = [];
let searchWord: string;
@ -30,11 +31,6 @@
let hideSuggestion = false;
let addClipMapMarker: (long: number, lat: number) => void;
const dispatch = createEventDispatcher<{
cancel: void;
confirm: Point;
}>();
$: lat = asset?.exifInfo?.latitude ?? undefined;
$: lng = asset?.exifInfo?.longitude ?? undefined;
$: zoom = lat !== undefined && lng !== undefined ? 12.5 : 1;
@ -50,17 +46,11 @@
let point: Point | null = null;
const handleCancel = () => dispatch('cancel');
const handleSelect = (selected: Point) => {
point = selected;
};
const handleConfirm = () => {
if (point) {
dispatch('confirm', point);
onConfirm(point);
} else {
dispatch('cancel');
onCancel();
}
};
@ -108,13 +98,7 @@
};
</script>
<ConfirmDialog
confirmColor="primary"
title={$t('change_location')}
width="wide"
onConfirm={handleConfirm}
onCancel={handleCancel}
>
<ConfirmDialog confirmColor="primary" title={$t('change_location')} width="wide" onConfirm={handleConfirm} {onCancel}>
<div slot="prompt" class="flex flex-col w-full h-full gap-2">
<div
class="relative w-64 sm:w-96"
@ -126,10 +110,8 @@
placeholder={$t('search_places')}
bind:name={searchWord}
{showLoadingSpinner}
on:reset={() => {
suggestedPlaces = [];
}}
on:search={handleSearchPlaces}
onReset={() => (suggestedPlaces = [])}
onSearch={handleSearchPlaces}
roundedBottom={suggestedPlaces.length === 0 || hideSuggestion}
/>
</button>
@ -180,7 +162,7 @@
center={lat && lng ? { lat, lng } : undefined}
simplified={true}
clickable={true}
on:clickedPoint={({ detail: point }) => handleSelect(point)}
onClickPoint={(selected) => (point = selected)}
/>
{/await}
</div>

View File

@ -21,7 +21,7 @@
import { fly } from 'svelte/transition';
import Icon from '$lib/components/elements/icon.svelte';
import { mdiMagnify, mdiUnfoldMoreHorizontal, mdiClose } from '@mdi/js';
import { createEventDispatcher, tick } from 'svelte';
import { tick } from 'svelte';
import type { FormEventHandler } from 'svelte/elements';
import { shortcuts } from '$lib/actions/shortcut';
import { focusOutside } from '$lib/actions/focus-outside';
@ -35,6 +35,7 @@
export let options: ComboBoxOption[] = [];
export let selectedOption: ComboBoxOption | undefined = undefined;
export let placeholder = '';
export let onSelect: (option: ComboBoxOption | undefined) => void = () => {};
/**
* Unique identifier for the combobox.
@ -61,10 +62,6 @@
searchQuery = selectedOption ? selectedOption.label : '';
}
const dispatch = createEventDispatcher<{
select: ComboBoxOption | undefined;
}>();
const activate = () => {
isActive = true;
searchQuery = '';
@ -105,10 +102,10 @@
optionRefs[0]?.scrollIntoView({ block: 'nearest' });
};
let onSelect = (option: ComboBoxOption) => {
let handleSelect = (option: ComboBoxOption) => {
selectedOption = option;
searchQuery = option.label;
dispatch('select', option);
onSelect(option);
closeDropdown();
};
@ -117,7 +114,7 @@
selectedIndex = undefined;
selectedOption = undefined;
searchQuery = '';
dispatch('select', selectedOption);
onSelect(selectedOption);
};
</script>
@ -188,7 +185,7 @@
shortcut: { key: 'Enter' },
onShortcut: () => {
if (selectedIndex !== undefined && filteredOptions.length > 0) {
onSelect(filteredOptions[selectedIndex]);
handleSelect(filteredOptions[selectedIndex]);
}
closeDropdown();
},
@ -245,7 +242,7 @@
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"
id={`${listboxId}-${index}`}
on:click={() => onSelect(option)}
on:click={() => handleSelect(option)}
role="option"
>
{option.label}

View File

@ -1,7 +1,7 @@
<script lang="ts">
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 { fly } from 'svelte/transition';
import { mdiClose } from '@mdi/js';
@ -12,13 +12,10 @@
export let backIcon = mdiClose;
export let tailwindClasses = '';
export let forceDark = false;
export let onClose: () => void = () => {};
let appBarBorder = 'bg-immich-bg border border-transparent';
const dispatch = createEventDispatcher<{
close: void;
}>();
const onScroll = () => {
if (window.pageYOffset > 80) {
appBarBorder = 'border border-gray-200 bg-gray-50 dark:border-gray-600';
@ -33,7 +30,7 @@
const handleClose = () => {
$isSelectingAllAssets = false;
dispatch('close');
onClose();
};
onMount(() => {

View File

@ -7,7 +7,6 @@
import { handleError } from '$lib/utils/handle-error';
import { SharedLinkType, createSharedLink, updateSharedLink, type SharedLinkResponseDto } from '@immich/sdk';
import { mdiContentCopy, mdiLink } from '@mdi/js';
import { createEventDispatcher } from 'svelte';
import { NotificationType, notificationController } from '../notification/notification';
import SettingInputField, { SettingInputFieldType } from '../settings/setting-input-field.svelte';
import SettingSwitch from '../settings/setting-switch.svelte';
@ -21,6 +20,7 @@
export let albumId: string | undefined = undefined;
export let assetIds: string[] = [];
export let editingLink: SharedLinkResponseDto | undefined = undefined;
export let onCreated: () => void = () => {};
let sharedLink: string | null = null;
let description = '';
@ -32,10 +32,6 @@
let shouldChangeExpirationTime = false;
let enablePassword = false;
const dispatch = createEventDispatcher<{
created: void;
}>();
const expirationOptions: [number, Intl.RelativeTimeFormatUnit][] = [
[30, 'minutes'],
[1, 'hour'],
@ -97,7 +93,7 @@
},
});
sharedLink = makeSharedLinkUrl($serverConfig.externalDomain, data.key);
dispatch('created');
onCreated();
} catch (error) {
handleError(error, $t('errors.failed_to_create_shared_link'));
}

View File

@ -163,9 +163,9 @@
<AssetViewer
asset={$viewingAsset}
onAction={handleAction}
on:previous={handlePrevious}
on:next={handleNext}
on:close={() => {
onPrevious={handlePrevious}
onNext={handleNext}
onClose={() => {
assetViewingStore.showAssetViewer(false);
handlePromiseError(navigate({ targetRoute: 'current', assetId: null }));
}}

View File

@ -13,7 +13,6 @@
import type { Feature, GeoJsonProperties, Geometry, Point } from 'geojson';
import type { GeoJSONSource, LngLatLike, StyleSpecification } from 'maplibre-gl';
import maplibregl from 'maplibre-gl';
import { createEventDispatcher } from 'svelte';
import { t } from 'svelte-i18n';
import {
AttributionControl,
@ -52,6 +51,8 @@
}
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 marker: maplibregl.Marker | null = null;
@ -62,16 +63,11 @@
key: getKey(),
}) as Promise<StyleSpecification>)();
const dispatch = createEventDispatcher<{
selected: string[];
clickedPoint: { lat: number; lng: number };
}>();
function handleAssetClick(assetId: string, map: Map | null) {
if (!map) {
return;
}
dispatch('selected', [assetId]);
onSelect([assetId]);
}
async function handleClusterClick(clusterId: number, map: Map | null) {
@ -82,13 +78,13 @@
const mapSource = map?.getSource('geojson') as GeoJSONSource;
const leaves = await mapSource.getClusterLeaves(clusterId, 10_000, 0);
const ids = leaves.map((leaf) => leaf.properties?.id);
dispatch('selected', ids);
onSelect(ids);
}
function handleMapClick(event: maplibregl.MapMouseEvent) {
if (clickable) {
const { lng, lat } = event.lngLat;
dispatch('clickedPoint', { lng, lat });
onClickPoint({ lng, lat });
if (marker) {
marker.remove();

View File

@ -9,19 +9,16 @@
import { handleError } from '$lib/utils/handle-error';
import { deleteProfileImage, updateMyPreferences, type UserAvatarColor } from '@immich/sdk';
import { mdiCog, mdiLogout, mdiPencil, mdiWrench } from '@mdi/js';
import { createEventDispatcher } from 'svelte';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
import { NotificationType, notificationController } from '../notification/notification';
import UserAvatar from '../user-avatar.svelte';
import AvatarSelector from './avatar-selector.svelte';
let isShowSelectAvatar = false;
export let onLogout: () => void;
export let onClose: () => void = () => {};
const dispatch = createEventDispatcher<{
logout: void;
close: void;
}>();
let isShowSelectAvatar = false;
const handleSaveProfile = async (color: UserAvatarColor) => {
try {
@ -75,14 +72,7 @@
</div>
<div class="flex flex-col gap-1">
<Button
href={AppRoute.USER_SETTINGS}
on:click={() => dispatch('close')}
color="dark-gray"
size="sm"
shadow={false}
border
>
<Button href={AppRoute.USER_SETTINGS} on:click={onClose} color="dark-gray" size="sm" shadow={false} border>
<div class="flex place-content-center place-items-center text-center gap-2 px-2">
<Icon path={mdiCog} size="18" ariaHidden />
{$t('account_settings')}
@ -91,7 +81,7 @@
{#if $user.isAdmin}
<Button
href={AppRoute.ADMIN_USER_MANAGEMENT}
on:click={() => dispatch('close')}
on:click={onClose}
color="dark-gray"
size="sm"
shadow={false}
@ -111,7 +101,7 @@
<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"
on:click={() => dispatch('logout')}
on:click={onLogout}
>
<Icon path={mdiLogout} size={24} />
{$t('sign_out')}</button

View File

@ -10,7 +10,6 @@
import { handleLogout } from '$lib/utils/auth';
import { logout } from '@immich/sdk';
import { mdiMagnify, mdiTrayArrowUp } from '@mdi/js';
import { createEventDispatcher } from 'svelte';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
import { AppRoute } from '../../../constants';
@ -21,13 +20,11 @@
import AccountInfoPanel from './account-info-panel.svelte';
export let showUploadButton = true;
export let onUploadClick: () => void;
let shouldShowAccountInfo = false;
let shouldShowAccountInfoPanel = false;
let innerWidth: number;
const dispatch = createEventDispatcher<{
uploadClicked: void;
}>();
const onLogout = async () => {
const { redirectUri } = await logout();
@ -67,14 +64,14 @@
<ThemeButton padding="2" />
{#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">
<Icon path={mdiTrayArrowUp} size="1.5em" />
<span>{$t('upload')}</span>
</div>
</LinkButton>
<CircleIconButton
on:click={() => dispatch('uploadClicked')}
on:click={onUploadClick}
title={$t('upload')}
icon={mdiTrayArrowUp}
class="lg:hidden"
@ -114,7 +111,7 @@
{/if}
{#if shouldShowAccountInfoPanel}
<AccountInfoPanel on:logout={onLogout} />
<AccountInfoPanel {onLogout} />
{/if}
</div>
</section>

View File

@ -8,7 +8,7 @@
<script lang="ts">
import { handlePromiseError } from '$lib/utils';
import { createEventDispatcher, onMount } from 'svelte';
import { onMount } from 'svelte';
import { tweened } from 'svelte/motion';
/**
@ -26,6 +26,10 @@
export let duration = 5;
export let onDone: () => void;
export let onPlaying: () => void = () => {};
export let onPaused: () => void = () => {};
const onChange = async () => {
progress = setDuration(duration);
await play();
@ -39,16 +43,10 @@
$: {
if ($progress === 1) {
dispatch('done');
onDone();
}
}
const dispatch = createEventDispatcher<{
done: void;
playing: void;
paused: void;
}>();
onMount(async () => {
if (autoplay) {
await play();
@ -57,13 +55,13 @@
export const play = async () => {
status = ProgressBarStatus.Playing;
dispatch('playing');
onPlaying();
await progress.set(1);
};
export const pause = async () => {
status = ProgressBarStatus.Paused;
dispatch('paused');
onPaused();
await progress.set($progress);
};

View File

@ -43,7 +43,7 @@
icon: option.icon,
};
}}
on:select={({ detail }) => onToggle(detail)}
onSelect={onToggle}
/>
</div>
</div>

View File

@ -1,7 +1,6 @@
<script lang="ts">
import { quintOut } from 'svelte/easing';
import { fly } from 'svelte/transition';
import { createEventDispatcher } from 'svelte';
import { t } from 'svelte-i18n';
import Icon from '$lib/components/elements/icon.svelte';
import { mdiChevronDown } from '@mdi/js';
@ -14,15 +13,14 @@
export let isEdited = false;
export let number = false;
export let disabled = false;
const dispatch = createEventDispatcher<{ select: string | number }>();
export let onSelect: (setting: string | number) => void = () => {};
const handleChange = (e: Event) => {
value = (e.target as HTMLInputElement).value;
if (number) {
value = Number.parseInt(value);
}
dispatch('select', value);
onSelect(value);
};
</script>

View File

@ -1,7 +1,6 @@
<script lang="ts">
import { quintOut } from 'svelte/easing';
import { fly } from 'svelte/transition';
import { createEventDispatcher } from 'svelte';
import Slider from '$lib/components/elements/slider.svelte';
import { generateId } from '$lib/utils/generate-id';
import { t } from 'svelte-i18n';
@ -11,14 +10,12 @@
export let checked = false;
export let disabled = false;
export let isEdited = false;
export let onToggle: (isChecked: boolean) => void = () => {};
let id: string = generateId();
$: sliderId = `${id}-slider`;
$: subtitleId = subtitle ? `${id}-subtitle` : undefined;
const dispatch = createEventDispatcher<{ toggle: boolean }>();
const onToggle = (isChecked: boolean) => dispatch('toggle', isChecked);
</script>
<div class="flex place-items-center justify-between">

View File

@ -102,7 +102,7 @@
{/if}
{#if secret}
<APIKeySecret {secret} on:done={() => (secret = '')} />
<APIKeySecret {secret} onDone={() => (secret = '')} />
{/if}
{#if editKey}

View File

@ -151,15 +151,15 @@
<AssetViewer
asset={$viewingAsset}
showNavigation={assets.length > 1}
on:next={() => {
onNext={() => {
const index = getAssetIndex($viewingAsset.id) + 1;
setAsset(assets[index % assets.length]);
}}
on:previous={() => {
onPrevious={() => {
const index = getAssetIndex($viewingAsset.id) - 1 + assets.length;
setAsset(assets[index % assets.length]);
}}
on:close={() => {
onClose={() => {
assetViewingStore.showAssetViewer(false);
handlePromiseError(navigate({ targetRoute: 'current', assetId: null }));
}}

View File

@ -674,8 +674,8 @@
disabled={!album.isActivityEnabled}
{isLiked}
numberOfComments={$numberOfComments}
on:favorite={handleFavorite}
on:openActivityTab={handleOpenAndCloseActivityTab}
onFavorite={handleFavorite}
onOpenActivityTab={handleOpenAndCloseActivityTab}
/>
</div>
{/if}
@ -697,10 +697,10 @@
albumId={album.id}
{isLiked}
bind:reactions
on:addComment={() => updateNumberOfComments(1)}
on:deleteComment={() => updateNumberOfComments(-1)}
on:deleteLike={() => (isLiked = null)}
on:close={handleOpenAndCloseActivityTab}
onAddComment={() => updateNumberOfComments(1)}
onDeleteComment={() => updateNumberOfComments(-1)}
onDeleteLike={() => (isLiked = null)}
onClose={handleOpenAndCloseActivityTab}
/>
</div>
</div>
@ -709,8 +709,8 @@
{#if viewMode === ViewMode.SELECT_USERS}
<UserSelectionModal
{album}
on:select={({ detail: users }) => handleAddUsers(users)}
on:share={() => (viewMode = ViewMode.LINK_SHARING)}
onSelect={handleAddUsers}
onShare={() => (viewMode = ViewMode.LINK_SHARING)}
onClose={() => (viewMode = ViewMode.VIEW)}
/>
{/if}
@ -723,8 +723,8 @@
<ShareInfoModal
onClose={() => (viewMode = ViewMode.VIEW)}
{album}
on:remove={({ detail: userId }) => handleRemoveUser(userId)}
on:refreshAlbum={refreshAlbum}
onRemove={handleRemoveUser}
onRefreshAlbum={refreshAlbum}
/>
{/if}
@ -737,9 +737,9 @@
albumOrder = order;
await setModeToView();
}}
on:close={() => (viewMode = ViewMode.VIEW)}
on:toggleEnableActivity={handleToggleEnableActivity}
on:showSelectSharedUser={() => (viewMode = ViewMode.SELECT_USERS)}
onClose={() => (viewMode = ViewMode.VIEW)}
onToggleEnabledActivity={handleToggleEnableActivity}
onShowSelectSharedUser={() => (viewMode = ViewMode.SELECT_USERS)}
/>
{/if}

View File

@ -122,9 +122,9 @@
<AssetViewer
asset={$viewingAsset}
showNavigation={viewingAssets.length > 1}
on:next={navigateNext}
on:previous={navigatePrevious}
on:close={() => {
onNext={navigateNext}
onPrevious={navigatePrevious}
onClose={() => {
assetViewingStore.showAssetViewer(false);
handlePromiseError(navigate({ targetRoute: 'current', assetId: null }));
}}
@ -137,11 +137,11 @@
{#if showSettingsModal}
<MapSettingsModal
settings={{ ...$mapSettings }}
on:close={() => (showSettingsModal = false)}
on:save={async ({ detail }) => {
const shouldUpdate = !isEqual(omit(detail, 'allowDarkMode'), omit($mapSettings, 'allowDarkMode'));
onClose={() => (showSettingsModal = false)}
onSave={async (settings) => {
const shouldUpdate = !isEqual(omit(settings, 'allowDarkMode'), omit($mapSettings, 'allowDarkMode'));
showSettingsModal = false;
$mapSettings = detail;
$mapSettings = settings;
if (shouldUpdate) {
mapMarkers = await loadMapMarkers();

View File

@ -302,9 +302,9 @@
{personMerge1}
{personMerge2}
{potentialMergePeople}
on:close={() => (showMergeModal = false)}
on:reject={() => changeName()}
on:confirm={(event) => handleMergeSamePerson(event.detail)}
onClose={() => (showMergeModal = false)}
onReject={changeName}
onConfirm={handleMergeSamePerson}
/>
{/if}
@ -349,10 +349,10 @@
<PeopleCard
{person}
preload={index < 20}
on:change-name={() => handleChangeName(person)}
on:set-birth-date={() => handleSetBirthDate(person)}
on:merge-people={() => handleMergePeople(person)}
on:hide-person={() => handleHidePerson(person)}
onChangeName={() => handleChangeName(person)}
onSetBirthDate={() => handleSetBirthDate(person)}
onMergePeople={() => handleMergePeople(person)}
onHidePerson={() => handleHidePerson(person)}
/>
</PeopleInfiniteScroll>
{:else}
@ -397,8 +397,8 @@
{#if showSetBirthDateModal}
<SetBirthDateModal
birthDate={edittingPerson?.birthDate ?? ''}
on:close={() => (showSetBirthDateModal = false)}
on:updated={(event) => submitBirthDateChange(event.detail)}
onClose={() => (showSetBirthDateModal = false)}
onUpdate={submitBirthDateChange}
/>
{/if}
</UserPageLayout>

View File

@ -347,8 +347,8 @@
<UnMergeFaceSelector
assetIds={[...$selectedAssets].map((a) => a.id)}
personAssets={person}
on:close={() => (viewMode = ViewMode.VIEW_ASSETS)}
on:confirm={handleUnmerge}
onClose={() => (viewMode = ViewMode.VIEW_ASSETS)}
onConfirm={handleUnmerge}
/>
{/if}
@ -357,22 +357,22 @@
{personMerge1}
{personMerge2}
{potentialMergePeople}
on:close={() => (viewMode = ViewMode.VIEW_ASSETS)}
on:reject={() => changeName()}
on:confirm={(event) => handleMergeSamePerson(event.detail)}
onClose={() => (viewMode = ViewMode.VIEW_ASSETS)}
onReject={changeName}
onConfirm={handleMergeSamePerson}
/>
{/if}
{#if viewMode === ViewMode.BIRTH_DATE}
<SetBirthDateModal
birthDate={person.birthDate ?? ''}
on:close={() => (viewMode = ViewMode.VIEW_ASSETS)}
on:updated={(event) => handleSetBirthDate(event.detail)}
onClose={() => (viewMode = ViewMode.VIEW_ASSETS)}
onUpdate={handleSetBirthDate}
/>
{/if}
{#if viewMode === ViewMode.MERGE_PEOPLE}
<MergeFaceSelector {person} on:back={handleGoBack} on:merge={({ detail }) => handleMerge(detail)} />
<MergeFaceSelector {person} onBack={handleGoBack} onMerge={handleMerge} />
{/if}
<header>
@ -464,7 +464,7 @@
bind:suggestedPeople
name={person.name}
bind:isSearchingPeople
on:change={(event) => handleNameChange(event.detail)}
onChange={handleNameChange}
{thumbnailData}
/>
{:else}

View File

@ -267,10 +267,7 @@
</script>
{#if toCreateLibrary}
<LibraryUserPickerForm
on:submit={({ detail }) => handleCreate(detail.ownerId)}
on:cancel={() => (toCreateLibrary = false)}
/>
<LibraryUserPickerForm onSubmit={handleCreate} onCancel={() => (toCreateLibrary = false)} />
{/if}
<UserPageLayout title={data.meta.title} admin>
@ -385,28 +382,20 @@
</tr>
{#if renameLibrary === index}
<div transition:slide={{ duration: 250 }}>
<LibraryRenameForm
{library}
on:submit={({ detail }) => handleUpdate(detail)}
on:cancel={() => (renameLibrary = null)}
/>
<LibraryRenameForm {library} onSubmit={handleUpdate} onCancel={() => (renameLibrary = null)} />
</div>
{/if}
{#if editImportPaths === index}
<div transition:slide={{ duration: 250 }}>
<LibraryImportPathsForm
{library}
on:submit={({ detail }) => handleUpdate(detail)}
on:cancel={() => (editImportPaths = null)}
/>
<LibraryImportPathsForm {library} onSubmit={handleUpdate} onCancel={() => (editImportPaths = null)} />
</div>
{/if}
{#if editScanSettings === index}
<div transition:slide={{ duration: 250 }} class="mb-4 ml-4 mr-4">
<LibraryScanSettingsForm
{library}
on:submit={({ detail: library }) => handleUpdate(library)}
on:cancel={() => (editScanSettings = null)}
onSubmit={handleUpdate}
onCancel={() => (editScanSettings = null)}
/>
</div>
{/if}

View File

@ -110,8 +110,8 @@
<section class="w-full pb-28 lg:w-[850px]">
{#if shouldShowCreateUserForm}
<CreateUserForm
on:submit={onUserCreated}
on:cancel={() => (shouldShowCreateUserForm = false)}
onSubmit={onUserCreated}
onCancel={() => (shouldShowCreateUserForm = false)}
onClose={() => (shouldShowCreateUserForm = false)}
/>
{/if}
@ -121,8 +121,8 @@
user={selectedUser}
bind:newPassword
canResetPassword={selectedUser?.id !== $user.id}
on:editSuccess={onEditUserSuccess}
on:resetPasswordSuccess={onEditPasswordSuccess}
onEditSuccess={onEditUserSuccess}
onResetPasswordSuccess={onEditPasswordSuccess}
onClose={() => (shouldShowEditUserForm = false)}
/>
{/if}

View File

@ -25,5 +25,5 @@
{$t('change_password_description')}
</p>
<ChangePasswordForm on:success={onSuccess} />
<ChangePasswordForm {onSuccess} />
</FullscreenContainer>