1
0
mirror of https://github.com/immich-app/immich.git synced 2025-01-12 15:32:36 +02:00

feat(web): add types to dispatcher (#5700)

* feat: add types to dispatcher

* fix: create album name

* pr feedback

* pr feedback

* pr feedback

* fix: api key name

* remove newSharedAlbum

* pr feedback

* fix: api key creation

* on:close

* fix: owner

* fix: onclose

* remove unused code

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
martin 2023-12-15 03:54:21 +01:00 committed by GitHub
parent 502495883d
commit 4c5397d7e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 199 additions and 130 deletions

View File

@ -6,19 +6,22 @@
export let user: UserResponseDto; export let user: UserResponseDto;
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher<{
success: void;
fail: void;
}>();
const deleteUser = async () => { const deleteUser = async () => {
try { try {
const deletedUser = await api.userApi.deleteUser({ id: user.id }); const deletedUser = await api.userApi.deleteUser({ id: user.id });
if (deletedUser.data.deletedAt != null) { if (deletedUser.data.deletedAt != null) {
dispatch('user-delete-success'); dispatch('success');
} else { } else {
dispatch('user-delete-fail'); dispatch('fail');
} }
} catch (error) { } catch (error) {
handleError(error, 'Unable to delete user'); handleError(error, 'Unable to delete user');
dispatch('user-delete-fail'); dispatch('fail');
} }
}; };
</script> </script>

View File

@ -5,14 +5,17 @@
export let user: UserResponseDto; export let user: UserResponseDto;
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher<{
success: void;
fail: void;
}>();
const restoreUser = async () => { const restoreUser = async () => {
const restoredUser = await api.userApi.restoreUser({ id: user.id }); const restoredUser = await api.userApi.restoreUser({ id: user.id });
if (restoredUser.data.deletedAt == null) { if (restoredUser.data.deletedAt == null) {
dispatch('user-restore-success'); dispatch('success');
} else { } else {
dispatch('user-restore-fail'); dispatch('fail');
} }
}; };
</script> </script>

View File

@ -10,6 +10,7 @@
import SettingButtonsRow from '../setting-buttons-row.svelte'; import SettingButtonsRow from '../setting-buttons-row.svelte';
import SettingSwitch from '../setting-switch.svelte'; import SettingSwitch from '../setting-switch.svelte';
import SettingSelect from '../setting-select.svelte'; import SettingSelect from '../setting-select.svelte';
import type { ResetOptions } from '$lib/utils/dipatch';
export let loggingConfig: SystemConfigLoggingDto; // this is the config that is being edited export let loggingConfig: SystemConfigLoggingDto; // this is the config that is being edited
export let disabled = false; export let disabled = false;
@ -24,6 +25,14 @@
]); ]);
} }
const handleReset = (detail: ResetOptions) => {
if (detail.default) {
resetToDefault();
} else {
reset();
}
};
async function saveSetting() { async function saveSetting() {
try { try {
const { data: current } = await api.systemConfigApi.getConfig(); const { data: current } = await api.systemConfigApi.getConfig();
@ -96,9 +105,8 @@
/> />
<SettingButtonsRow <SettingButtonsRow
on:reset={reset} on:reset={({ detail }) => handleReset(detail)}
on:save={saveSetting} on:save={saveSetting}
on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(savedConfig, defaultConfig)} showResetToDefault={!isEqual(savedConfig, defaultConfig)}
{disabled} {disabled}
/> />

View File

@ -10,7 +10,10 @@
export let album: AlbumResponseDto; export let album: AlbumResponseDto;
let selectedThumbnail: AssetResponseDto | undefined; let selectedThumbnail: AssetResponseDto | undefined;
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher<{
close: void;
thumbnail: AssetResponseDto | undefined;
}>();
$: isSelected = (id: string): boolean | undefined => { $: isSelected = (id: string): boolean | undefined => {
if (!selectedThumbnail && album.albumThumbnailAssetId == id) { if (!selectedThumbnail && album.albumThumbnailAssetId == id) {
@ -25,7 +28,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 py-[160px] dark:bg-immich-dark-bg" class="absolute left-0 top-0 z-[9999] h-full w-full bg-immich-bg py-[160px] dark:bg-immich-dark-bg"
> >
<ControlAppBar on:close-button-click={() => dispatch('close')}> <ControlAppBar on:close={() => dispatch('close')}>
<svelte:fragment slot="leading"> <svelte:fragment slot="leading">
<p class="text-lg">Select album cover</p> <p class="text-lg">Select album cover</p>
</svelte:fragment> </svelte:fragment>
@ -35,7 +38,7 @@
size="sm" size="sm"
rounded="lg" rounded="lg"
disabled={selectedThumbnail == undefined} disabled={selectedThumbnail == undefined}
on:click={() => dispatch('thumbnail-selected', { asset: selectedThumbnail })} on:click={() => dispatch('thumbnail', selectedThumbnail)}
> >
Done Done
</Button> </Button>

View File

@ -9,7 +9,10 @@
export let isShowActivity: boolean | undefined; export let isShowActivity: boolean | undefined;
export let disabled: boolean; export let disabled: boolean;
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher<{
openActivityTab: void;
favorite: void;
}>();
</script> </script>
<div <div

View File

@ -2,7 +2,9 @@
import { AlbumResponseDto, ThumbnailFormat, api } from '@api'; import { AlbumResponseDto, ThumbnailFormat, api } from '@api';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
const dispatcher = createEventDispatcher(); const dispatch = createEventDispatcher<{
album: void;
}>();
export let album: AlbumResponseDto; export let album: AlbumResponseDto;
export let variant: 'simple' | 'full' = 'full'; export let variant: 'simple' | 'full' = 'full';
@ -24,7 +26,7 @@
</script> </script>
<button <button
on:click={() => dispatcher('album')} on:click={() => dispatch('album')}
class="flex w-full gap-4 px-6 py-2 text-left transition-colors hover:bg-gray-200 dark:hover:bg-gray-700" class="flex w-full gap-4 px-6 py-2 text-left transition-colors hover:bg-gray-200 dark:hover:bg-gray-700"
> >
<div class="h-12 w-12 shrink-0 rounded-xl bg-slate-300"> <div class="h-12 w-12 shrink-0 rounded-xl bg-slate-300">

View File

@ -39,7 +39,7 @@
type MenuItemEvent = 'addToAlbum' | 'addToSharedAlbum' | 'asProfileImage' | 'runJob' | 'playSlideShow' | 'unstack'; type MenuItemEvent = 'addToAlbum' | 'addToSharedAlbum' | 'asProfileImage' | 'runJob' | 'playSlideShow' | 'unstack';
const dispatch = createEventDispatcher<{ const dispatch = createEventDispatcher<{
goBack: void; back: void;
stopMotionPhoto: void; stopMotionPhoto: void;
playMotionPhoto: void; playMotionPhoto: void;
download: void; download: void;
@ -78,7 +78,7 @@
class="z-[1001] flex h-16 place-items-center justify-between bg-gradient-to-b from-black/40 px-3 transition-transform duration-200" class="z-[1001] flex h-16 place-items-center justify-between bg-gradient-to-b from-black/40 px-3 transition-transform duration-200"
> >
<div class="text-white"> <div class="text-white">
<CircleIconButton isOpacity={true} icon={mdiArrowLeft} on:click={() => dispatch('goBack')} /> <CircleIconButton isOpacity={true} icon={mdiArrowLeft} on:click={() => dispatch('back')} />
</div> </div>
<div class="flex w-[calc(100%-3rem)] justify-end gap-2 overflow-hidden text-white"> <div class="flex w-[calc(100%-3rem)] justify-end gap-2 overflow-hidden text-white">
{#if asset.isOffline} {#if asset.isOffline}

View File

@ -424,19 +424,17 @@
addToSharedAlbum = shared; addToSharedAlbum = shared;
}; };
const handleAddToNewAlbum = (event: CustomEvent) => { const handleAddToNewAlbum = (albumName: string) => {
isShowAlbumPicker = false; isShowAlbumPicker = false;
const { albumName }: { albumName: string } = event.detail;
api.albumApi.createAlbum({ createAlbumDto: { albumName, assetIds: [asset.id] } }).then((response) => { api.albumApi.createAlbum({ createAlbumDto: { albumName, assetIds: [asset.id] } }).then((response) => {
const album = response.data; const album = response.data;
goto(`${AppRoute.ALBUMS}/${album.id}`); goto(`${AppRoute.ALBUMS}/${album.id}`);
}); });
}; };
const handleAddToAlbum = async (event: CustomEvent<{ album: AlbumResponseDto }>) => { const handleAddToAlbum = async (album: AlbumResponseDto) => {
isShowAlbumPicker = false; isShowAlbumPicker = false;
const album = event.detail.album;
await addAssetsToAlbum(album.id, [asset.id]); await addAssetsToAlbum(album.id, [asset.id]);
await getAllAlbums(); await getAllAlbums();
@ -575,7 +573,7 @@
showDetailButton={shouldShowDetailButton} showDetailButton={shouldShowDetailButton}
showSlideshow={!!assetStore} showSlideshow={!!assetStore}
hasStackChildren={$stackAssetsStore.length > 0} hasStackChildren={$stackAssetsStore.length > 0}
on:goBack={closeViewer} on:back={closeViewer}
on:showDetail={showDetailInfoHandler} on:showDetail={showDetailInfoHandler}
on:download={() => downloadFile(asset)} on:download={() => downloadFile(asset)}
on:delete={trashOrDelete} on:delete={trashOrDelete}
@ -759,9 +757,8 @@
{#if isShowAlbumPicker} {#if isShowAlbumPicker}
<AlbumSelectionModal <AlbumSelectionModal
shared={addToSharedAlbum} shared={addToSharedAlbum}
on:newAlbum={handleAddToNewAlbum} on:newAlbum={({ detail }) => handleAddToNewAlbum(detail)}
on:newSharedAlbum={handleAddToNewAlbum} on:album={({ detail }) => handleAddToAlbum(detail)}
on:album={handleAddToAlbum}
on:close={() => (isShowAlbumPicker = false)} on:close={() => (isShowAlbumPicker = false)}
/> />
{/if} {/if}
@ -784,11 +781,7 @@
{/if} {/if}
{#if isShowProfileImageCrop} {#if isShowProfileImageCrop}
<ProfileImageCropper <ProfileImageCropper {asset} on:close={() => (isShowProfileImageCrop = false)} />
{asset}
on:close={() => (isShowProfileImageCrop = false)}
on:close-viewer={handleCloseViewer}
/>
{/if} {/if}
</section> </section>

View File

@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import { page } from '$app/stores';
import { locale } from '$lib/stores/preferences.store'; import { locale } from '$lib/stores/preferences.store';
import { featureFlags } from '$lib/stores/server-config.store'; import { featureFlags } from '$lib/stores/server-config.store';
import { getAssetFilename } from '$lib/utils/asset-utils'; import { getAssetFilename } from '$lib/utils/asset-utils';
@ -53,7 +52,7 @@
} }
} }
$: isOwner = $page?.data?.user?.id === asset.ownerId; $: isOwner = $user.id === asset.ownerId;
$: { $: {
// Get latest description from server // Get latest description from server

View File

@ -28,7 +28,11 @@
let searchFaces = false; let searchFaces = false;
let searchName = ''; let searchName = '';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher<{
close: void;
createPerson: string | null;
reassign: PersonResponseDto;
}>();
const handleBackButton = () => { const handleBackButton = () => {
dispatch('close'); dispatch('close');
}; };

View File

@ -10,7 +10,9 @@
export let circle = false; export let circle = false;
export let border = false; export let border = false;
let dispatch = createEventDispatcher(); let dispatch = createEventDispatcher<{
click: PersonResponseDto;
}>();
const handleOnClicked = () => { const handleOnClicked = () => {
dispatch('click', person); dispatch('click', person);

View File

@ -24,7 +24,10 @@
let screenHeight: number; let screenHeight: number;
let isShowConfirmation = false; let isShowConfirmation = false;
let dispatch = createEventDispatcher(); let dispatch = createEventDispatcher<{
back: void;
merge: void;
}>();
$: hasSelection = selectedPeople.length > 0; $: hasSelection = selectedPeople.length > 0;
$: unselectedPeople = people.filter( $: unselectedPeople = people.filter(
@ -37,7 +40,7 @@
}); });
const onClose = () => { const onClose = () => {
dispatch('go-back'); dispatch('back');
}; };
const handleSwapPeople = () => { const handleSwapPeople = () => {
@ -89,7 +92,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-button-click={onClose}> <ControlAppBar on:close={onClose}>
<svelte:fragment slot="leading"> <svelte:fragment slot="leading">
{#if hasSelection} {#if hasSelection}
Selected {selectedPeople.length} Selected {selectedPeople.length}

View File

@ -40,7 +40,10 @@
let automaticRefreshTimeout: NodeJS.Timeout; let automaticRefreshTimeout: NodeJS.Timeout;
const { onPersonThumbnail } = websocketStore; const { onPersonThumbnail } = websocketStore;
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher<{
close: void;
refresh: void;
}>();
// Reset value // Reset value
$onPersonThumbnail = ''; $onPersonThumbnail = '';

View File

@ -7,7 +7,12 @@
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte'; import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
import { mdiClose, mdiEye, mdiEyeOff, mdiRestart } from '@mdi/js'; import { mdiClose, mdiEye, mdiEyeOff, mdiRestart } from '@mdi/js';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher<{
close: void;
reset: void;
change: void;
done: void;
}>();
export let showLoadingSpinner: boolean; export let showLoadingSpinner: boolean;
export let toggleVisibility: boolean; export let toggleVisibility: boolean;
@ -21,24 +26,20 @@
class="sticky top-0 z-10 flex h-16 w-full items-center justify-between border-b bg-white p-1 dark:border-immich-dark-gray dark:bg-black dark:text-immich-dark-fg md:p-8" class="sticky top-0 z-10 flex h-16 w-full items-center justify-between border-b bg-white p-1 dark:border-immich-dark-gray dark:bg-black dark:text-immich-dark-fg md:p-8"
> >
<div class="flex items-center"> <div class="flex items-center">
<CircleIconButton icon={mdiClose} on:click={() => dispatch('closeClick')} /> <CircleIconButton icon={mdiClose} on:click={() => dispatch('close')} />
<p class="ml-4 hidden sm:block">Show & hide people</p> <p class="ml-4 hidden sm:block">Show & hide people</p>
</div> </div>
<div class="flex items-center justify-end"> <div class="flex items-center justify-end">
<div class="flex items-center md:mr-8"> <div class="flex items-center md:mr-8">
<CircleIconButton <CircleIconButton title="Reset people visibility" icon={mdiRestart} on:click={() => dispatch('reset')} />
title="Reset people visibility"
icon={mdiRestart}
on:click={() => dispatch('reset-visibility')}
/>
<CircleIconButton <CircleIconButton
title="Toggle visibility" title="Toggle visibility"
icon={toggleVisibility ? mdiEye : mdiEyeOff} icon={toggleVisibility ? mdiEye : mdiEyeOff}
on:click={() => dispatch('toggle-visibility')} on:click={() => dispatch('change')}
/> />
</div> </div>
{#if !showLoadingSpinner} {#if !showLoadingSpinner}
<IconButton on:click={() => dispatch('doneClick')}>Done</IconButton> <IconButton on:click={() => dispatch('done')}>Done</IconButton>
{:else} {:else}
<LoadingSpinner /> <LoadingSpinner />
{/if} {/if}

View File

@ -28,7 +28,10 @@
? people.filter((person) => selectedPerson && person.id !== selectedPerson.id && personAssets.id !== person.id) ? people.filter((person) => selectedPerson && person.id !== selectedPerson.id && personAssets.id !== person.id)
: people; : people;
let dispatch = createEventDispatcher(); let dispatch = createEventDispatcher<{
confirm: void;
close: void;
}>();
const selectedPeople: AssetFaceUpdateItem[] = []; const selectedPeople: AssetFaceUpdateItem[] = [];
@ -117,7 +120,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-button-click={onClose}> <ControlAppBar on:close={onClose}>
<svelte:fragment slot="leading"> <svelte:fragment slot="leading">
<slot name="header" /> <slot name="header" />
<div /> <div />

View File

@ -5,18 +5,32 @@
import FullScreenModal from '../shared-components/full-screen-modal.svelte'; import FullScreenModal from '../shared-components/full-screen-modal.svelte';
import Icon from '$lib/components/elements/icon.svelte'; import Icon from '$lib/components/elements/icon.svelte';
import { mdiKeyVariant } from '@mdi/js'; import { mdiKeyVariant } from '@mdi/js';
import { NotificationType, notificationController } from '../shared-components/notification/notification';
export let apiKey: Partial<APIKeyResponseDto>; export let apiKey: Partial<APIKeyResponseDto>;
export let title = 'API Key'; export let title = 'API Key';
export let cancelText = 'Cancel'; export let cancelText = 'Cancel';
export let submitText = 'Save'; export let submitText = 'Save';
export let apiKeyName = 'API Key';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher<{
cancel: void;
submit: Partial<APIKeyResponseDto>;
}>();
const handleCancel = () => dispatch('cancel'); const handleCancel = () => dispatch('cancel');
const handleSubmit = () => dispatch('submit', { ...apiKey, name: apiKey.name }); const handleSubmit = () => {
if (apiKeyName) {
dispatch('submit', { ...apiKey, name: apiKeyName });
} else {
notificationController.show({
message: "Your API Key name shouldn't be empty",
type: NotificationType.Warning,
});
}
};
</script> </script>
<FullScreenModal on:clickOutside={() => handleCancel()}> <FullScreenModal on:clickOutside={handleCancel}>
<div <div
class="w-[500px] max-w-[95vw] rounded-3xl border bg-immich-bg p-4 py-8 shadow-sm dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-fg" class="w-[500px] max-w-[95vw] rounded-3xl border bg-immich-bg p-4 py-8 shadow-sm dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-fg"
> >
@ -29,14 +43,14 @@
</h1> </h1>
</div> </div>
<form on:submit|preventDefault={() => handleSubmit()} autocomplete="off"> <form on:submit|preventDefault={handleSubmit} autocomplete="off">
<div class="m-4 flex flex-col gap-2"> <div class="m-4 flex flex-col gap-2">
<label class="immich-form-label" for="name">Name</label> <label class="immich-form-label" for="name">Name</label>
<input class="immich-form-input" id="name" name="name" type="text" bind:value={apiKey.name} /> <input class="immich-form-input" id="name" name="name" type="text" bind:value={apiKeyName} />
</div> </div>
<div class="mt-8 flex w-full gap-4 px-4"> <div class="mt-8 flex w-full gap-4 px-4">
<Button color="gray" fullwidth on:click={() => handleCancel()}>{cancelText}</Button> <Button color="gray" fullwidth on:click={handleCancel}>{cancelText}</Button>
<Button type="submit" fullwidth>{submitText}</Button> <Button type="submit" fullwidth>{submitText}</Button>
</div> </div>
</form> </form>

View File

@ -8,7 +8,9 @@
export let secret = ''; export let secret = '';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher<{
done: void;
}>();
const handleDone = () => dispatch('done'); const handleDone = () => dispatch('done');
let canCopyImagesToClipboard = true; let canCopyImagesToClipboard = true;

View File

@ -22,7 +22,9 @@
} }
} }
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher<{
success: void;
}>();
async function changePassword() { async function changePassword() {
if (changeChagePassword) { if (changeChagePassword) {

View File

@ -24,7 +24,10 @@
canCreateUser = true; canCreateUser = true;
} }
} }
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher<{
submit: void;
cancel: void;
}>();
async function registerUser(event: SubmitEvent) { async function registerUser(event: SubmitEvent) {
if (canCreateUser && !isCreatingUser) { if (canCreateUser && !isCreatingUser) {
@ -52,7 +55,7 @@
if (status === 201) { if (status === 201) {
success = 'New user created'; success = 'New user created';
dispatch('user-created'); dispatch('submit');
isCreatingUser = false; isCreatingUser = false;
return; return;

View File

@ -9,7 +9,11 @@
export let canDelete = false; export let canDelete = false;
export let submitText = 'Submit'; export let submitText = 'Submit';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher<{
cancel: void;
submit: { excludePattern: string };
delete: void;
}>();
const handleCancel = () => dispatch('cancel'); const handleCancel = () => dispatch('cancel');
const handleSubmit = () => dispatch('submit', { excludePattern: exclusionPattern }); const handleSubmit = () => dispatch('submit', { excludePattern: exclusionPattern });
</script> </script>

View File

@ -11,7 +11,11 @@
export let submitText = 'Save'; export let submitText = 'Save';
export let canDelete = false; export let canDelete = false;
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher<{
cancel: void;
submit: { importPath: string };
delete: void;
}>();
const handleCancel = () => dispatch('cancel'); const handleCancel = () => dispatch('cancel');
const handleSubmit = () => dispatch('submit', { importPath }); const handleSubmit = () => dispatch('submit', { importPath });
</script> </script>

View File

@ -26,7 +26,10 @@
} }
}); });
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher<{
cancel: void;
submit: Partial<LibraryResponseDto>;
}>();
const handleCancel = () => { const handleCancel = () => {
dispatch('cancel'); dispatch('cancel');
}; };

View File

@ -5,7 +5,10 @@
export let library: Partial<LibraryResponseDto>; export let library: Partial<LibraryResponseDto>;
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher<{
cancel: void;
submit: Partial<LibraryResponseDto>;
}>();
const handleCancel = () => { const handleCancel = () => {
dispatch('cancel'); dispatch('cancel');
}; };

View File

@ -26,13 +26,16 @@
} }
}); });
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher<{
cancel: void;
submit: { library: Partial<LibraryResponseDto>; type: LibraryType };
}>();
const handleCancel = () => { const handleCancel = () => {
dispatch('cancel'); dispatch('cancel');
}; };
const handleSubmit = () => { const handleSubmit = () => {
dispatch('submit', { ...library, libraryType: LibraryType.External }); dispatch('submit', { library, type: LibraryType.External });
}; };
const handleAddExclusionPattern = async () => { const handleAddExclusionPattern = async () => {

View File

@ -101,7 +101,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 currentMemory} {#if currentMemory}
<ControlAppBar on:close-button-click={() => goto(AppRoute.PHOTOS)} forceDark> <ControlAppBar on:close={() => goto(AppRoute.PHOTOS)} forceDark>
<svelte:fragment slot="leading"> <svelte:fragment slot="leading">
<p class="text-lg"> <p class="text-lg">
{currentMemory.title} {currentMemory.title}

View File

@ -23,10 +23,9 @@
closeMenu(); closeMenu();
}; };
const handleAddToNewAlbum = (event: CustomEvent) => { const handleAddToNewAlbum = (albumName: string) => {
showAlbumPicker = false; showAlbumPicker = false;
const { albumName }: { albumName: string } = event.detail;
const assetIds = Array.from(getAssets()).map((asset) => asset.id); const assetIds = Array.from(getAssets()).map((asset) => asset.id);
api.albumApi.createAlbum({ createAlbumDto: { albumName, assetIds } }).then((response) => { api.albumApi.createAlbum({ createAlbumDto: { albumName, assetIds } }).then((response) => {
const { id, albumName } = response.data; const { id, albumName } = response.data;
@ -42,9 +41,8 @@
}); });
}; };
const handleAddToAlbum = async (event: CustomEvent<{ album: AlbumResponseDto }>) => { const handleAddToAlbum = async (album: AlbumResponseDto) => {
showAlbumPicker = false; showAlbumPicker = false;
const album = event.detail.album;
const assetIds = Array.from(getAssets()).map((asset) => asset.id); const assetIds = Array.from(getAssets()).map((asset) => asset.id);
await addAssetsToAlbum(album.id, assetIds); await addAssetsToAlbum(album.id, assetIds);
clearSelect(); clearSelect();
@ -56,9 +54,8 @@
{#if showAlbumPicker} {#if showAlbumPicker}
<AlbumSelectionModal <AlbumSelectionModal
{shared} {shared}
on:newAlbum={handleAddToNewAlbum} on:newAlbum={({ detail }) => handleAddToNewAlbum(detail)}
on:newSharedAlbum={handleAddToNewAlbum} on:album={({ detail }) => handleAddToAlbum(detail)}
on:album={handleAddToAlbum}
on:close={handleHideAlbumPicker} on:close={handleHideAlbumPicker}
/> />
{/if} {/if}

View File

@ -36,7 +36,7 @@
}); });
</script> </script>
<ControlAppBar on:close-button-click={clearSelect} backIcon={mdiClose} tailwindClasses="bg-white shadow-md"> <ControlAppBar on:close={clearSelect} backIcon={mdiClose} tailwindClasses="bg-white shadow-md">
<p class="font-medium text-immich-primary dark:text-immich-dark-primary" slot="leading"> <p class="font-medium text-immich-primary dark:text-immich-dark-primary" slot="leading">
Selected {assets.size.toLocaleString($locale)} Selected {assets.size.toLocaleString($locale)}
</p> </p>

View File

@ -79,7 +79,7 @@
{/if} {/if}
</AssetSelectControlBar> </AssetSelectControlBar>
{:else} {:else}
<ControlAppBar on:close-button-click={() => goto(AppRoute.PHOTOS)} backIcon={mdiArrowLeft} showBackButton={false}> <ControlAppBar on:close={() => goto(AppRoute.PHOTOS)} backIcon={mdiArrowLeft} showBackButton={false}>
<svelte:fragment slot="leading"> <svelte:fragment slot="leading">
<a <a
data-sveltekit-preload-data="hover" data-sveltekit-preload-data="hover"

View File

@ -13,15 +13,8 @@
let search = ''; let search = '';
const dispatch = createEventDispatcher<{ const dispatch = createEventDispatcher<{
newAlbum: { newAlbum: string;
albumName: string; album: AlbumResponseDto;
};
album: {
album: AlbumResponseDto;
};
newSharedAlbum: {
albumName: string;
};
close: void; close: void;
}>(); }>();
@ -47,15 +40,11 @@
} }
const handleSelect = (album: AlbumResponseDto) => { const handleSelect = (album: AlbumResponseDto) => {
dispatch('album', { album }); dispatch('album', album);
}; };
const handleNew = () => { const handleNew = () => {
if (shared) { dispatch('newAlbum', search.length > 0 ? search : '');
dispatch('newAlbum', { albumName: search.length > 0 ? search : '' });
} else {
dispatch('newSharedAlbum', { albumName: search.length > 0 ? search : '' });
}
}; };
</script> </script>

View File

@ -13,7 +13,9 @@
let appBarBorder = 'bg-immich-bg border border-transparent'; let appBarBorder = 'bg-immich-bg border border-transparent';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher<{
close: void;
}>();
const onScroll = () => { const onScroll = () => {
if (window.pageYOffset > 80) { if (window.pageYOffset > 80) {
@ -50,7 +52,7 @@
<div class="flex place-items-center gap-6 justify-self-start dark:text-immich-dark-fg"> <div class="flex place-items-center gap-6 justify-self-start dark:text-immich-dark-fg">
{#if showBackButton} {#if showBackButton}
<CircleIconButton <CircleIconButton
on:click={() => dispatch('close-button-click')} on:click={() => dispatch('close')}
icon={backIcon} icon={backIcon}
backgroundColor={'transparent'} backgroundColor={'transparent'}
hoverColor={'#e2e7e9'} hoverColor={'#e2e7e9'}

View File

@ -12,7 +12,11 @@
export let link: SharedLinkResponseDto; export let link: SharedLinkResponseDto;
let expirationCountdown: luxon.DurationObjectUnits; let expirationCountdown: luxon.DurationObjectUnits;
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher<{
delete: void;
copy: void;
edit: void;
}>();
const getThumbnail = async (): Promise<AssetResponseDto> => { const getThumbnail = async (): Promise<AssetResponseDto> => {
let assetId = ''; let assetId = '';

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { api, UpdateLibraryDto, LibraryResponseDto, LibraryType, LibraryStatsResponseDto } from '@api'; import { api, LibraryResponseDto, LibraryType, LibraryStatsResponseDto } from '@api';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import Button from '../elements/buttons/button.svelte'; import Button from '../elements/buttons/button.svelte';
import { notificationController, NotificationType } from '../shared-components/notification/notification'; import { notificationController, NotificationType } from '../shared-components/notification/notification';
@ -112,16 +112,15 @@
} }
}; };
const handleUpdate = async (event: CustomEvent<UpdateLibraryDto>) => { const handleUpdate = async (event: Partial<LibraryResponseDto>) => {
if (updateLibraryIndex === null) { if (updateLibraryIndex === null) {
return; return;
} }
try { try {
const dto = event.detail;
const libraryId = libraries[updateLibraryIndex].id; const libraryId = libraries[updateLibraryIndex].id;
await api.libraryApi.updateLibrary({ id: libraryId, updateLibraryDto: dto }); await api.libraryApi.updateLibrary({ id: libraryId, updateLibraryDto: { ...event } });
} catch (error) { } catch (error) {
handleError(error, 'Unable to update library'); handleError(error, 'Unable to update library');
} finally { } finally {
@ -375,19 +374,27 @@
</tr> </tr>
{#if renameLibrary === index} {#if renameLibrary === index}
<div transition:slide={{ duration: 250 }}> <div transition:slide={{ duration: 250 }}>
<LibraryRenameForm {library} on:submit={handleUpdate} on:cancel={() => (renameLibrary = null)} /> <LibraryRenameForm
{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 {library} on:submit={handleUpdate} on:cancel={() => (editImportPaths = null)} /> <LibraryImportPathsForm
{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={handleUpdate} on:submit={({ detail }) => handleUpdate(detail)}
on:cancel={() => (editScanSettings = null)} on:cancel={() => (editScanSettings = null)}
/> />
</div> </div>

View File

@ -29,10 +29,9 @@
keys = data; keys = data;
} }
const handleCreate = async (event: CustomEvent<APIKeyResponseDto>) => { const handleCreate = async (detail: Partial<APIKeyResponseDto>) => {
try { try {
const dto = event.detail; const { data } = await api.keyApi.createApiKey({ aPIKeyCreateDto: detail });
const { data } = await api.keyApi.createApiKey({ aPIKeyCreateDto: dto });
secret = data.secret; secret = data.secret;
} catch (error) { } catch (error) {
handleError(error, 'Unable to create a new API Key'); handleError(error, 'Unable to create a new API Key');
@ -42,15 +41,13 @@
} }
}; };
const handleUpdate = async (event: CustomEvent<APIKeyResponseDto>) => { const handleUpdate = async (detail: Partial<APIKeyResponseDto>) => {
if (!editKey) { if (!editKey || !detail.name) {
return; return;
} }
const dto = event.detail;
try { try {
await api.keyApi.updateApiKey({ id: editKey.id, aPIKeyUpdateDto: { name: dto.name } }); await api.keyApi.updateApiKey({ id: editKey.id, aPIKeyUpdateDto: { name: detail.name } });
notificationController.show({ notificationController.show({
message: `Saved API Key`, message: `Saved API Key`,
type: NotificationType.Info, type: NotificationType.Info,
@ -88,7 +85,7 @@
title="New API Key" title="New API Key"
submitText="Create" submitText="Create"
apiKey={newKey} apiKey={newKey}
on:submit={handleCreate} on:submit={({ detail }) => handleCreate(detail)}
on:cancel={() => (newKey = null)} on:cancel={() => (newKey = null)}
/> />
{/if} {/if}
@ -98,7 +95,12 @@
{/if} {/if}
{#if editKey} {#if editKey}
<APIKeyForm submitText="Save" apiKey={editKey} on:submit={handleUpdate} on:cancel={() => (editKey = null)} /> <APIKeyForm
submitText="Save"
apiKey={editKey}
on:submit={({ detail }) => handleUpdate(detail)}
on:cancel={() => (editKey = null)}
/>
{/if} {/if}
{#if deleteKey} {#if deleteKey}

View File

@ -457,7 +457,7 @@
</AssetSelectControlBar> </AssetSelectControlBar>
{:else} {:else}
{#if viewMode === ViewMode.VIEW || viewMode === ViewMode.ALBUM_OPTIONS} {#if viewMode === ViewMode.VIEW || viewMode === ViewMode.ALBUM_OPTIONS}
<ControlAppBar showBackButton backIcon={mdiArrowLeft} on:close-button-click={() => goto(backUrl)}> <ControlAppBar showBackButton backIcon={mdiArrowLeft} on:close={() => goto(backUrl)}>
<svelte:fragment slot="trailing"> <svelte:fragment slot="trailing">
<CircleIconButton <CircleIconButton
title="Add Photos" title="Add Photos"
@ -513,7 +513,7 @@
{/if} {/if}
{#if viewMode === ViewMode.SELECT_ASSETS} {#if viewMode === ViewMode.SELECT_ASSETS}
<ControlAppBar on:close-button-click={handleCloseSelectAssets}> <ControlAppBar on:close={handleCloseSelectAssets}>
<svelte:fragment slot="leading"> <svelte:fragment slot="leading">
<p class="text-lg dark:text-immich-dark-fg"> <p class="text-lg dark:text-immich-dark-fg">
{#if $timelineSelected.size === 0} {#if $timelineSelected.size === 0}
@ -539,7 +539,7 @@
{/if} {/if}
{#if viewMode === ViewMode.SELECT_THUMBNAIL} {#if viewMode === ViewMode.SELECT_THUMBNAIL}
<ControlAppBar on:close-button-click={() => (viewMode = ViewMode.VIEW)}> <ControlAppBar on:close={() => (viewMode = ViewMode.VIEW)}>
<svelte:fragment slot="leading">Select Album Cover</svelte:fragment> <svelte:fragment slot="leading">Select Album Cover</svelte:fragment>
</ControlAppBar> </ControlAppBar>
{/if} {/if}

View File

@ -37,7 +37,7 @@
<DownloadAction /> <DownloadAction />
</AssetSelectControlBar> </AssetSelectControlBar>
{:else} {:else}
<ControlAppBar showBackButton backIcon={mdiArrowLeft} on:close-button-click={() => goto(AppRoute.SHARING)}> <ControlAppBar showBackButton backIcon={mdiArrowLeft} on:close={() => goto(AppRoute.SHARING)}>
<svelte:fragment slot="leading"> <svelte:fragment slot="leading">
<p class="whitespace-nowrap text-immich-fg dark:text-immich-dark-fg"> <p class="whitespace-nowrap text-immich-fg dark:text-immich-dark-fg">
{data.partner.name}'s photos {data.partner.name}'s photos

View File

@ -438,10 +438,10 @@
</UserPageLayout> </UserPageLayout>
{#if selectHidden} {#if selectHidden}
<ShowHide <ShowHide
on:doneClick={handleDoneClick} on:done={handleDoneClick}
on:closeClick={handleCloseClick} on:close={handleCloseClick}
on:reset-visibility={handleResetVisibility} on:reset={handleResetVisibility}
on:toggle-visibility={handleToggleVisibility} on:change={handleToggleVisibility}
bind:showLoadingSpinner bind:showLoadingSpinner
bind:toggleVisibility bind:toggleVisibility
> >

View File

@ -374,7 +374,7 @@
{/if} {/if}
{#if viewMode === ViewMode.MERGE_PEOPLE} {#if viewMode === ViewMode.MERGE_PEOPLE}
<MergeFaceSelector person={data.person} on:go-back={handleGoBack} on:merge={handleMerge} /> <MergeFaceSelector person={data.person} on:back={handleGoBack} on:merge={handleMerge} />
{/if} {/if}
<header> <header>
@ -398,7 +398,7 @@
</AssetSelectControlBar> </AssetSelectControlBar>
{:else} {:else}
{#if viewMode === ViewMode.VIEW_ASSETS || viewMode === ViewMode.SUGGEST_MERGE || viewMode === ViewMode.BIRTH_DATE} {#if viewMode === ViewMode.VIEW_ASSETS || viewMode === ViewMode.SUGGEST_MERGE || viewMode === ViewMode.BIRTH_DATE}
<ControlAppBar showBackButton backIcon={mdiArrowLeft} on:close-button-click={() => goto(previousRoute)}> <ControlAppBar showBackButton backIcon={mdiArrowLeft} on:close={() => goto(previousRoute)}>
<svelte:fragment slot="trailing"> <svelte:fragment slot="trailing">
<AssetSelectContextMenu icon={mdiDotsVertical} title="Menu"> <AssetSelectContextMenu icon={mdiDotsVertical} title="Menu">
<MenuOption text="Change feature photo" on:click={() => (viewMode = ViewMode.SELECT_PERSON)} /> <MenuOption text="Change feature photo" on:click={() => (viewMode = ViewMode.SELECT_PERSON)} />
@ -414,7 +414,7 @@
{/if} {/if}
{#if viewMode === ViewMode.SELECT_PERSON} {#if viewMode === ViewMode.SELECT_PERSON}
<ControlAppBar on:close-button-click={() => (viewMode = ViewMode.VIEW_ASSETS)}> <ControlAppBar on:close={() => (viewMode = ViewMode.VIEW_ASSETS)}>
<svelte:fragment slot="leading">Select feature photo</svelte:fragment> <svelte:fragment slot="leading">Select feature photo</svelte:fragment>
</ControlAppBar> </ControlAppBar>
{/if} {/if}

View File

@ -130,7 +130,7 @@
</AssetSelectControlBar> </AssetSelectControlBar>
</div> </div>
{:else} {:else}
<ControlAppBar on:close-button-click={() => goto(previousRoute)} backIcon={mdiArrowLeft}> <ControlAppBar on:close={() => goto(previousRoute)} backIcon={mdiArrowLeft}>
<div class="w-full flex-1 pl-4"> <div class="w-full flex-1 pl-4">
<SearchBar grayTheme={false} value={term} /> <SearchBar grayTheme={false} value={term} />
</div> </div>

View File

@ -53,7 +53,7 @@
}; };
</script> </script>
<ControlAppBar backIcon={mdiArrowLeft} on:close-button-click={() => goto(AppRoute.SHARING)}> <ControlAppBar backIcon={mdiArrowLeft} on:close={() => goto(AppRoute.SHARING)}>
<svelte:fragment slot="leading">Shared links</svelte:fragment> <svelte:fragment slot="leading">Shared links</svelte:fragment>
</ControlAppBar> </ControlAppBar>

View File

@ -110,7 +110,7 @@
<section class="w-full pb-28 lg:w-[850px]"> <section class="w-full pb-28 lg:w-[850px]">
{#if shouldShowCreateUserForm} {#if shouldShowCreateUserForm}
<FullScreenModal on:clickOutside={() => (shouldShowCreateUserForm = false)}> <FullScreenModal on:clickOutside={() => (shouldShowCreateUserForm = false)}>
<CreateUserForm on:user-created={onUserCreated} on:cancel={() => (shouldShowCreateUserForm = false)} /> <CreateUserForm on:submit={onUserCreated} on:cancel={() => (shouldShowCreateUserForm = false)} />
</FullScreenModal> </FullScreenModal>
{/if} {/if}
@ -120,7 +120,7 @@
user={selectedUser} user={selectedUser}
canResetPassword={selectedUser?.id !== $user.id} canResetPassword={selectedUser?.id !== $user.id}
on:editSuccess={onEditUserSuccess} on:editSuccess={onEditUserSuccess}
on:reset-password-success={onEditPasswordSuccess} on:resetPasswordSuccess={onEditPasswordSuccess}
on:close={() => (shouldShowEditUserForm = false)} on:close={() => (shouldShowEditUserForm = false)}
/> />
</FullScreenModal> </FullScreenModal>
@ -129,8 +129,8 @@
{#if shouldShowDeleteConfirmDialog} {#if shouldShowDeleteConfirmDialog}
<DeleteConfirmDialog <DeleteConfirmDialog
user={selectedUser} user={selectedUser}
on:user-delete-success={onUserDeleteSuccess} on:succes={onUserDeleteSuccess}
on:user-delete-fail={onUserDeleteFail} on:fail={onUserDeleteFail}
on:cancel={() => (shouldShowDeleteConfirmDialog = false)} on:cancel={() => (shouldShowDeleteConfirmDialog = false)}
/> />
{/if} {/if}
@ -138,8 +138,8 @@
{#if shouldShowRestoreDialog} {#if shouldShowRestoreDialog}
<RestoreDialogue <RestoreDialogue
user={selectedUser} user={selectedUser}
on:user-restore-success={onUserRestoreSuccess} on:success={onUserRestoreSuccess}
on:user-restore-fail={onUserRestoreFail} on:fail={onUserRestoreFail}
on:cancel={() => (shouldShowRestoreDialog = false)} on:cancel={() => (shouldShowRestoreDialog = false)}
/> />
{/if} {/if}

View File

@ -18,7 +18,7 @@
<LoginForm <LoginForm
on:success={() => goto(AppRoute.PHOTOS, { invalidateAll: true })} on:success={() => goto(AppRoute.PHOTOS, { invalidateAll: true })}
on:first-login={() => goto(AppRoute.AUTH_CHANGE_PASSWORD)} on:firstLogin={() => goto(AppRoute.AUTH_CHANGE_PASSWORD)}
/> />
</FullscreenContainer> </FullscreenContainer>
{/if} {/if}