chore(web): migration svelte 5 syntax (#13883)

This commit is contained in:
Alex
2024-11-14 08:43:25 -06:00
committed by GitHub
parent 9203a61709
commit 0b3742cf13
310 changed files with 6435 additions and 4176 deletions

View File

@@ -8,15 +8,15 @@
import { t } from 'svelte-i18n';
import { retrieveServerConfig } from '$lib/stores/server-config.store';
let email = '';
let password = '';
let confirmPassword = '';
let name = '';
let email = $state('');
let password = $state('');
let confirmPassword = $state('');
let name = $state('');
let errorMessage: string;
let canRegister = false;
let errorMessage: string = $state('');
let canRegister = $state(false);
$: {
$effect(() => {
if (password !== confirmPassword && confirmPassword.length > 0) {
errorMessage = $t('password_does_not_match');
canRegister = false;
@@ -24,7 +24,7 @@
errorMessage = '';
canRegister = true;
}
}
});
async function registerAdmin() {
if (canRegister) {
@@ -40,9 +40,14 @@
}
}
}
const onsubmit = async (event: Event) => {
event.preventDefault();
await registerAdmin();
};
</script>
<form on:submit|preventDefault={registerAdmin} method="post" class="mt-5 flex flex-col gap-5">
<form {onsubmit} method="post" class="mt-5 flex flex-col gap-5">
<div class="flex flex-col gap-2">
<label class="immich-form-label" for="email">{$t('admin_email')}</label>
<input class="immich-form-input" id="email" bind:value={email} type="email" autocomplete="email" required />

View File

@@ -5,13 +5,23 @@
import FullScreenModal from '../shared-components/full-screen-modal.svelte';
import { NotificationType, notificationController } from '../shared-components/notification/notification';
export let apiKey: { name: string };
export let title: string;
export let cancelText = $t('cancel');
export let submitText = $t('save');
interface Props {
apiKey: { name: string };
title: string;
cancelText?: string;
submitText?: string;
onSubmit: (apiKey: { name: string }) => void;
onCancel: () => void;
}
export let onSubmit: (apiKey: { name: string }) => void;
export let onCancel: () => void;
let {
apiKey = $bindable(),
title,
cancelText = $t('cancel'),
submitText = $t('save'),
onSubmit,
onCancel,
}: Props = $props();
const handleSubmit = () => {
if (apiKey.name) {
@@ -23,17 +33,23 @@
});
}
};
const onsubmit = (event: Event) => {
event.preventDefault();
handleSubmit();
};
</script>
<FullScreenModal {title} icon={mdiKeyVariant} onClose={() => onCancel()}>
<form on:submit|preventDefault={handleSubmit} autocomplete="off" id="api-key-form">
<form {onsubmit} autocomplete="off" id="api-key-form">
<div class="mb-4 flex flex-col gap-2">
<label class="immich-form-label" for="name">{$t('name')}</label>
<input class="immich-form-input" id="name" name="name" type="text" bind:value={apiKey.name} />
</div>
</form>
<svelte:fragment slot="sticky-bottom">
<Button color="gray" fullwidth on:click={() => onCancel()}>{cancelText}</Button>
{#snippet stickyBottom()}
<Button color="gray" fullwidth onclick={() => onCancel()}>{cancelText}</Button>
<Button type="submit" fullwidth form="api-key-form">{submitText}</Button>
</svelte:fragment>
{/snippet}
</FullScreenModal>

View File

@@ -5,8 +5,12 @@
import FullScreenModal from '../shared-components/full-screen-modal.svelte';
import { t } from 'svelte-i18n';
export let secret = '';
export let onDone: () => void;
interface Props {
secret?: string;
onDone: () => void;
}
let { secret = '', onDone }: Props = $props();
</script>
<FullScreenModal title={$t('api_key')} icon={mdiKeyVariant} onClose={onDone}>
@@ -21,8 +25,8 @@
<textarea class="immich-form-input" id="secret" name="secret" readonly={true} value={secret}></textarea>
</div>
<svelte:fragment slot="sticky-bottom">
<Button on:click={() => copyToClipboard(secret)} fullwidth>{$t('copy_to_clipboard')}</Button>
<Button on:click={onDone} fullwidth>{$t('done')}</Button>
</svelte:fragment>
{#snippet stickyBottom()}
<Button onclick={() => copyToClipboard(secret)} fullwidth>{$t('copy_to_clipboard')}</Button>
<Button onclick={onDone} fullwidth>{$t('done')}</Button>
{/snippet}
</FullScreenModal>

View File

@@ -4,17 +4,21 @@
import { updateMyUser } from '@immich/sdk';
import { t } from 'svelte-i18n';
export let onSuccess: () => void;
interface Props {
onSuccess: () => void;
}
let errorMessage: string;
let { onSuccess }: Props = $props();
let errorMessage: string = $state('');
let success: string;
let password = '';
let passwordConfirm = '';
let password = $state('');
let passwordConfirm = $state('');
let valid = false;
let valid = $state(false);
$: {
$effect(() => {
if (password !== passwordConfirm && passwordConfirm.length > 0) {
errorMessage = $t('password_does_not_match');
valid = false;
@@ -22,7 +26,7 @@
errorMessage = '';
valid = true;
}
}
});
async function changePassword() {
if (valid) {
@@ -33,9 +37,14 @@
onSuccess();
}
}
const onsubmit = async (event: Event) => {
event.preventDefault();
await changePassword();
};
</script>
<form on:submit|preventDefault={changePassword} method="post" class="mt-5 flex flex-col gap-5">
<form {onsubmit} method="post" class="mt-5 flex flex-col gap-5">
<div class="flex flex-col gap-2">
<label class="immich-form-label" for="password">{$t('new_password')}</label>
<PasswordField id="password" bind:password autocomplete="new-password" />

View File

@@ -10,29 +10,33 @@
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;
export let oauthEnabled = false;
interface Props {
onClose: () => void;
onSubmit: () => void;
onCancel: () => void;
oauthEnabled?: boolean;
}
let error: string;
let success: string;
let { onClose, onSubmit, onCancel, oauthEnabled = false }: Props = $props();
let email = '';
let password = '';
let confirmPassword = '';
let name = '';
let shouldChangePassword = true;
let notify = true;
let error = $state('');
let success = $state('');
let canCreateUser = false;
let quotaSize: number | undefined;
let isCreatingUser = false;
let email = $state('');
let password = $state('');
let confirmPassword = $state('');
let name = $state('');
let shouldChangePassword = $state(true);
let notify = $state(true);
$: quotaSizeInBytes = quotaSize ? convertToBytes(quotaSize, ByteUnit.GiB) : null;
$: quotaSizeWarning = quotaSizeInBytes && quotaSizeInBytes > $serverInfo.diskSizeRaw;
let canCreateUser = $state(false);
let quotaSize: number | undefined = $state();
let isCreatingUser = $state(false);
$: {
let quotaSizeInBytes = $derived(quotaSize ? convertToBytes(quotaSize, ByteUnit.GiB) : null);
let quotaSizeWarning = $derived(quotaSizeInBytes && quotaSizeInBytes > $serverInfo.diskSizeRaw);
$effect(() => {
if (password !== confirmPassword && confirmPassword.length > 0) {
error = $t('password_does_not_match');
canCreateUser = false;
@@ -40,7 +44,7 @@
error = '';
canCreateUser = true;
}
}
});
async function registerUser() {
if (canCreateUser && !isCreatingUser) {
@@ -71,10 +75,15 @@
}
}
}
const onsubmit = async (event: Event) => {
event.preventDefault();
await registerUser();
};
</script>
<FullScreenModal title={$t('create_new_user')} showLogo {onClose}>
<form on:submit|preventDefault={registerUser} autocomplete="off" id="create-new-user-form">
<form {onsubmit} autocomplete="off" id="create-new-user-form">
<div class="my-4 flex flex-col gap-2">
<label class="immich-form-label" for="email">{$t('email')}</label>
<input class="immich-form-input" id="email" bind:value={email} type="email" required />
@@ -134,8 +143,9 @@
<p class="text-sm text-immich-primary">{success}</p>
{/if}
</form>
<svelte:fragment slot="sticky-bottom">
<Button color="gray" fullwidth on:click={onCancel}>{$t('cancel')}</Button>
{#snippet stickyBottom()}
<Button color="gray" fullwidth onclick={onCancel}>{$t('cancel')}</Button>
<Button type="submit" disabled={isCreatingUser} fullwidth form="create-new-user-form">{$t('create')}</Button>
</svelte:fragment>
{/snippet}
</FullScreenModal>

View File

@@ -6,15 +6,19 @@
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
import { t } from 'svelte-i18n';
export let album: AlbumResponseDto;
export let onEditSuccess: ((album: AlbumResponseDto) => unknown) | undefined = undefined;
export let onCancel: (() => unknown) | undefined = undefined;
export let onClose: () => void;
interface Props {
album: AlbumResponseDto;
onEditSuccess?: ((album: AlbumResponseDto) => unknown) | undefined;
onCancel?: (() => unknown) | undefined;
onClose: () => void;
}
let albumName = album.albumName;
let description = album.description;
let { album = $bindable(), onEditSuccess = undefined, onCancel = undefined, onClose }: Props = $props();
let isSubmitting = false;
let albumName = $state(album.albumName);
let description = $state(album.description);
let isSubmitting = $state(false);
const handleUpdateAlbumInfo = async () => {
isSubmitting = true;
@@ -35,10 +39,15 @@
isSubmitting = false;
}
};
const onsubmit = async (event: Event) => {
event.preventDefault();
await handleUpdateAlbumInfo();
};
</script>
<FullScreenModal title={$t('edit_album')} width="wide" {onClose}>
<form on:submit|preventDefault={handleUpdateAlbumInfo} autocomplete="off" id="edit-album-form">
<form {onsubmit} autocomplete="off" id="edit-album-form">
<div class="flex items-center">
<div class="hidden sm:flex">
<AlbumCover {album} class="h-[200px] w-[200px] m-4 shadow-lg" />
@@ -57,8 +66,9 @@
</div>
</div>
</form>
<svelte:fragment slot="sticky-bottom">
<Button color="gray" fullwidth on:click={() => onCancel?.()}>{$t('cancel')}</Button>
{#snippet stickyBottom()}
<Button color="gray" fullwidth onclick={() => onCancel?.()}>{$t('cancel')}</Button>
<Button type="submit" fullwidth disabled={isSubmitting} form="edit-album-form">{$t('ok')}</Button>
</svelte:fragment>
{/snippet}
</FullScreenModal>

View File

@@ -10,23 +10,35 @@
import { t } from 'svelte-i18n';
import { ByteUnit, convertFromBytes, convertToBytes } from '$lib/utils/byte-units';
export let user: UserAdminResponseDto;
export let canResetPassword = true;
export let newPassword: string;
export let onClose: () => void;
export let onResetPasswordSuccess: () => void;
export let onEditSuccess: () => void;
interface Props {
user: UserAdminResponseDto;
canResetPassword?: boolean;
newPassword: string;
onClose: () => void;
onResetPasswordSuccess: () => void;
onEditSuccess: () => void;
}
let {
user,
canResetPassword = true,
newPassword = $bindable(),
onClose,
onResetPasswordSuccess,
onEditSuccess,
}: Props = $props();
let error: string;
let success: string;
let quotaSize = user.quotaSizeInBytes ? convertFromBytes(user.quotaSizeInBytes, ByteUnit.GiB) : null;
let quotaSize = $state(user.quotaSizeInBytes ? convertFromBytes(user.quotaSizeInBytes, ByteUnit.GiB) : null);
const previousQutoa = user.quotaSizeInBytes;
$: quotaSizeWarning =
let quotaSizeWarning = $derived(
previousQutoa !== convertToBytes(Number(quotaSize), ByteUnit.GiB) &&
!!quotaSize &&
convertToBytes(Number(quotaSize), ByteUnit.GiB) > $serverInfo.diskSizeRaw;
!!quotaSize &&
convertToBytes(Number(quotaSize), ByteUnit.GiB) > $serverInfo.diskSizeRaw,
);
const editUser = async () => {
try {
@@ -89,10 +101,15 @@
return generatedPassword;
}
const onSubmit = async (event: Event) => {
event.preventDefault();
await editUser();
};
</script>
<FullScreenModal title={$t('edit_user')} icon={mdiAccountEditOutline} {onClose}>
<form on:submit|preventDefault={editUser} autocomplete="off" id="edit-user-form">
<form onsubmit={onSubmit} autocomplete="off" id="edit-user-form">
<div class="my-4 flex flex-col gap-2">
<label class="immich-form-label" for="email">{$t('email')}</label>
<input class="immich-form-input" id="email" name="email" type="email" bind:value={user.email} />
@@ -140,10 +157,11 @@
<p class="ml-4 text-sm text-immich-primary">{success}</p>
{/if}
</form>
<svelte:fragment slot="sticky-bottom">
{#snippet stickyBottom()}
{#if canResetPassword}
<Button color="light-red" fullwidth on:click={resetPassword}>{$t('reset_password')}</Button>
<Button color="light-red" fullwidth onclick={resetPassword}>{$t('reset_password')}</Button>
{/if}
<Button type="submit" fullwidth form="edit-user-form">{$t('confirm')}</Button>
</svelte:fragment>
{/snippet}
</FullScreenModal>

View File

@@ -5,13 +5,25 @@
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';
export let exclusionPattern: string;
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 = () => {};
interface Props {
exclusionPattern: string;
exclusionPatterns?: string[];
isEditing?: boolean;
submitText?: string;
onCancel: () => void;
onSubmit: (exclusionPattern: string) => void;
onDelete?: () => void;
}
let {
exclusionPattern = $bindable(),
exclusionPatterns = $bindable([]),
isEditing = false,
submitText = $t('submit'),
onCancel,
onSubmit,
onDelete,
}: Props = $props();
onMount(() => {
if (isEditing) {
@@ -19,12 +31,19 @@
}
});
$: isDuplicate = exclusionPattern !== null && exclusionPatterns.includes(exclusionPattern);
$: canSubmit = exclusionPattern && !exclusionPatterns.includes(exclusionPattern);
let isDuplicate = $derived(exclusionPattern !== null && exclusionPatterns.includes(exclusionPattern));
let canSubmit = $derived(exclusionPattern && !exclusionPatterns.includes(exclusionPattern));
const onsubmit = (event: Event) => {
event.preventDefault();
if (canSubmit) {
onSubmit(exclusionPattern);
}
};
</script>
<FullScreenModal title={$t('add_exclusion_pattern')} icon={mdiFolderRemove} onClose={onCancel}>
<form on:submit|preventDefault={() => onSubmit(exclusionPattern)} autocomplete="off" id="add-exclusion-pattern-form">
<form {onsubmit} autocomplete="off" id="add-exclusion-pattern-form">
<p class="py-5 text-sm">
{$t('admin.exclusion_pattern_description')}
<br /><br />
@@ -46,11 +65,12 @@
{/if}
</div>
</form>
<svelte:fragment slot="sticky-bottom">
<Button color="gray" fullwidth on:click={onCancel}>{$t('cancel')}</Button>
{#snippet stickyBottom()}
<Button color="gray" fullwidth onclick={onCancel}>{$t('cancel')}</Button>
{#if isEditing}
<Button color="red" fullwidth on:click={onDelete}>{$t('delete')}</Button>
<Button color="red" fullwidth onclick={onDelete}>{$t('delete')}</Button>
{/if}
<Button type="submit" disabled={!canSubmit} fullwidth form="add-exclusion-pattern-form">{submitText}</Button>
</svelte:fragment>
{/snippet}
</FullScreenModal>

View File

@@ -5,15 +5,29 @@
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';
export let importPath: string | null;
export let importPaths: string[] = [];
export let title = $t('import_path');
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 = () => {};
interface Props {
importPath: string | null;
importPaths?: string[];
title?: string;
cancelText?: string;
submitText?: string;
isEditing?: boolean;
onCancel: () => void;
onSubmit: (importPath: string | null) => void;
onDelete?: () => void;
}
let {
importPath = $bindable(),
importPaths = $bindable([]),
title = $t('import_path'),
cancelText = $t('cancel'),
submitText = $t('save'),
isEditing = false,
onCancel,
onSubmit,
onDelete,
}: Props = $props();
onMount(() => {
if (isEditing) {
@@ -21,12 +35,19 @@
}
});
$: isDuplicate = importPath !== null && importPaths.includes(importPath);
$: canSubmit = importPath !== '' && importPath !== null && !importPaths.includes(importPath);
let isDuplicate = $derived(importPath !== null && importPaths.includes(importPath));
let canSubmit = $derived(importPath !== '' && importPath !== null && !importPaths.includes(importPath));
const onsubmit = (event: Event) => {
event.preventDefault();
if (canSubmit) {
onSubmit(importPath);
}
};
</script>
<FullScreenModal {title} icon={mdiFolderSync} onClose={onCancel}>
<form on:submit|preventDefault={() => onSubmit(importPath)} autocomplete="off" id="library-import-path-form">
<form {onsubmit} 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">
@@ -40,11 +61,12 @@
{/if}
</div>
</form>
<svelte:fragment slot="sticky-bottom">
<Button color="gray" fullwidth on:click={onCancel}>{cancelText}</Button>
{#snippet stickyBottom()}
<Button color="gray" fullwidth onclick={onCancel}>{cancelText}</Button>
{#if isEditing}
<Button color="red" fullwidth on:click={onDelete}>{$t('delete')}</Button>
<Button color="red" fullwidth onclick={onDelete}>{$t('delete')}</Button>
{/if}
<Button type="submit" disabled={!canSubmit} fullwidth form="library-import-path-form">{submitText}</Button>
</svelte:fragment>
{/snippet}
</FullScreenModal>

View File

@@ -11,19 +11,23 @@
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import { t } from 'svelte-i18n';
export let library: LibraryResponseDto;
export let onCancel: () => void;
export let onSubmit: (library: LibraryResponseDto) => void;
interface Props {
library: LibraryResponseDto;
onCancel: () => void;
onSubmit: (library: LibraryResponseDto) => void;
}
let addImportPath = false;
let editImportPath: number | null = null;
let { library = $bindable(), onCancel, onSubmit }: Props = $props();
let importPathToAdd: string | null = null;
let editedImportPath: string;
let addImportPath = $state(false);
let editImportPath: number | null = $state(null);
let validatedPaths: ValidateLibraryImportPathResponseDto[] = [];
let importPathToAdd: string | null = $state(null);
let editedImportPath: string = $state('');
$: importPaths = validatedPaths.map((validatedPath) => validatedPath.importPath);
let validatedPaths: ValidateLibraryImportPathResponseDto[] = $state([]);
let importPaths = $derived(validatedPaths.map((validatedPath) => validatedPath.importPath));
onMount(async () => {
if (library.importPaths) {
@@ -134,6 +138,11 @@
editImportPath = null;
}
};
const onsubmit = (event: Event) => {
event.preventDefault();
onSubmit({ ...library });
};
</script>
{#if addImportPath}
@@ -163,7 +172,7 @@
/>
{/if}
<form on:submit|preventDefault={() => onSubmit({ ...library })} autocomplete="off" class="m-4 flex flex-col gap-4">
<form {onsubmit} 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}
@@ -199,7 +208,7 @@
icon={mdiPencilOutline}
title={$t('edit_import_path')}
size="16"
on:click={() => {
onclick={() => {
editImportPath = listIndex;
editedImportPath = validatedPath.importPath;
}}
@@ -223,7 +232,7 @@
><Button
type="button"
size="sm"
on:click={() => {
onclick={() => {
addImportPath = true;
}}>{$t('add_path')}</Button
></td
@@ -233,12 +242,12 @@
</table>
<div class="flex justify-between w-full">
<div class="justify-end gap-2">
<Button size="sm" color="gray" on:click={() => revalidate()}
<Button size="sm" color="gray" onclick={() => revalidate()}
><Icon path={mdiRefresh} size={20} />{$t('validate')}</Button
>
</div>
<div class="justify-end gap-2">
<Button size="sm" color="gray" on:click={onCancel}>{$t('cancel')}</Button>
<Button size="sm" color="gray" onclick={onCancel}>{$t('cancel')}</Button>
<Button size="sm" type="submit">{$t('save')}</Button>
</div>
</div>

View File

@@ -3,18 +3,27 @@
import Button from '../elements/buttons/button.svelte';
import { t } from 'svelte-i18n';
export let library: Partial<LibraryResponseDto>;
export let onCancel: () => void;
export let onSubmit: (library: Partial<LibraryResponseDto>) => void;
interface Props {
library: Partial<LibraryResponseDto>;
onCancel: () => void;
onSubmit: (library: Partial<LibraryResponseDto>) => void;
}
let { library = $bindable(), onCancel, onSubmit }: Props = $props();
const onsubmit = (event: Event) => {
event.preventDefault();
onSubmit({ ...library });
};
</script>
<form on:submit|preventDefault={() => onSubmit({ ...library })} autocomplete="off" class="m-4 flex flex-col gap-2">
<form {onsubmit} 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={onCancel}>{$t('cancel')}</Button>
<Button size="sm" color="gray" onclick={onCancel}>{$t('cancel')}</Button>
<Button size="sm" type="submit">{$t('save')}</Button>
</div>
</form>

View File

@@ -8,17 +8,21 @@
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import { t } from 'svelte-i18n';
export let library: Partial<LibraryResponseDto>;
export let onCancel: () => void;
export let onSubmit: (library: Partial<LibraryResponseDto>) => void;
interface Props {
library: Partial<LibraryResponseDto>;
onCancel: () => void;
onSubmit: (library: Partial<LibraryResponseDto>) => void;
}
let addExclusionPattern = false;
let editExclusionPattern: number | null = null;
let { library = $bindable(), onCancel, onSubmit }: Props = $props();
let exclusionPatternToAdd: string;
let editedExclusionPattern: string;
let addExclusionPattern = $state(false);
let editExclusionPattern: number | null = $state(null);
let exclusionPatterns: string[] = [];
let exclusionPatternToAdd: string = $state('');
let editedExclusionPattern: string = $state('');
let exclusionPatterns: string[] = $state([]);
onMount(() => {
if (library.exclusionPatterns) {
@@ -89,6 +93,11 @@
editExclusionPattern = null;
}
};
const onsubmit = (event: Event) => {
event.preventDefault();
onSubmit(library);
};
</script>
{#if addExclusionPattern}
@@ -113,7 +122,7 @@
/>
{/if}
<form on:submit|preventDefault={() => onSubmit(library)} autocomplete="off" class="m-4 flex flex-col gap-4">
<form {onsubmit} 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}
@@ -131,7 +140,7 @@
icon={mdiPencilOutline}
title={$t('edit_exclusion_pattern')}
size="16"
on:click={() => {
onclick={() => {
editExclusionPattern = listIndex;
editedExclusionPattern = exclusionPattern;
}}
@@ -154,7 +163,7 @@
<td class="w-1/4 text-ellipsis px-4 text-sm"
><Button
size="sm"
on:click={() => {
onclick={() => {
addExclusionPattern = true;
}}>{$t('add_exclusion_pattern')}</Button
></td
@@ -164,7 +173,7 @@
</table>
<div class="flex w-full justify-end gap-4">
<Button size="sm" color="gray" on:click={onCancel}>{$t('cancel')}</Button>
<Button size="sm" color="gray" onclick={onCancel}>{$t('cancel')}</Button>
<Button size="sm" type="submit">{$t('save')}</Button>
</div>
</form>

View File

@@ -8,27 +8,37 @@
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;
interface Props {
onCancel: () => void;
onSubmit: (ownerId: string) => void;
}
let ownerId: string = $user.id;
let { onCancel, onSubmit }: Props = $props();
let userOptions: { value: string; text: string }[] = [];
let ownerId: string = $state($user.id);
let userOptions: { value: string; text: string }[] = $state([]);
onMount(async () => {
const users = await searchUsersAdmin({});
userOptions = users.map((user) => ({ value: user.id, text: user.name }));
});
const onsubmit = (event: Event) => {
event.preventDefault();
onSubmit(ownerId);
};
</script>
<FullScreenModal title={$t('select_library_owner')} icon={mdiFolderSync} onClose={onCancel}>
<form on:submit|preventDefault={() => onSubmit(ownerId)} autocomplete="off" id="select-library-owner-form">
<form {onsubmit} 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={onCancel}>{$t('cancel')}</Button>
{#snippet stickyBottom()}
<Button color="gray" fullwidth onclick={onCancel}>{$t('cancel')}</Button>
<Button type="submit" fullwidth form="select-library-owner-form">{$t('create')}</Button>
</svelte:fragment>
{/snippet}
</FullScreenModal>

View File

@@ -12,16 +12,20 @@
import PasswordField from '../shared-components/password-field.svelte';
import { t } from 'svelte-i18n';
export let onSuccess: () => unknown | Promise<unknown>;
export let onFirstLogin: () => unknown | Promise<unknown>;
export let onOnboarding: () => unknown | Promise<unknown>;
interface Props {
onSuccess: () => unknown | Promise<unknown>;
onFirstLogin: () => unknown | Promise<unknown>;
onOnboarding: () => unknown | Promise<unknown>;
}
let errorMessage: string;
let email = '';
let password = '';
let oauthError = '';
let loading = false;
let oauthLoading = true;
let { onSuccess, onFirstLogin, onOnboarding }: Props = $props();
let errorMessage: string = $state('');
let email = $state('');
let password = $state('');
let oauthError = $state('');
let loading = $state(false);
let oauthLoading = $state(true);
onMount(async () => {
if (!$featureFlags.oauth) {
@@ -87,10 +91,15 @@
oauthError = $t('errors.unable_to_login_with_oauth');
}
};
const onsubmit = async (event: Event) => {
event.preventDefault();
await handleLogin();
};
</script>
{#if !oauthLoading && $featureFlags.passwordLogin}
<form on:submit|preventDefault={handleLogin} class="mt-5 flex flex-col gap-5">
<form {onsubmit} class="mt-5 flex flex-col gap-5">
{#if errorMessage}
<p class="text-red-400" transition:fade>
{errorMessage}
@@ -150,7 +159,7 @@
size="lg"
fullwidth
color={$featureFlags.passwordLogin ? 'secondary' : 'primary'}
on:click={handleOAuthLogin}
onclick={handleOAuthLogin}
>
{#if oauthLoading}
<span class="h-6">

View File

@@ -9,14 +9,19 @@
import Icon from '$lib/components/elements/icon.svelte';
import { AppRoute } from '$lib/constants';
import FormatMessage from '$lib/components/i18n/format-message.svelte';
import { SvelteSet } from 'svelte/reactivity';
export let onTag: (tagIds: string[]) => void;
export let onCancel: () => void;
interface Props {
onTag: (tagIds: string[]) => void;
onCancel: () => void;
}
let allTags: TagResponseDto[] = [];
$: tagMap = Object.fromEntries(allTags.map((tag) => [tag.id, tag]));
let selectedIds = new Set<string>();
$: disabled = selectedIds.size === 0;
let { onTag, onCancel }: Props = $props();
let allTags: TagResponseDto[] = $state([]);
let tagMap = $derived(Object.fromEntries(allTags.map((tag) => [tag.id, tag])));
let selectedIds = $state(new SvelteSet<string>());
let disabled = $derived(selectedIds.size === 0);
onMount(async () => {
allTags = await getAllTags();
@@ -37,19 +42,26 @@
selectedIds.delete(tag);
selectedIds = selectedIds;
};
const onsubmit = (event: Event) => {
event.preventDefault();
handleSubmit();
};
</script>
<FullScreenModal title={$t('tag_assets')} icon={mdiTag} onClose={onCancel}>
<div class="text-sm">
<p>
<FormatMessage key="tag_not_found_question" let:message>
<a href={AppRoute.TAGS} class="text-immich-primary dark:text-immich-dark-primary underline">
{message}
</a>
<FormatMessage key="tag_not_found_question">
{#snippet children({ message })}
<a href={AppRoute.TAGS} class="text-immich-primary dark:text-immich-dark-primary underline">
{message}
</a>
{/snippet}
</FormatMessage>
</p>
</div>
<form on:submit|preventDefault={handleSubmit} autocomplete="off" id="create-tag-form">
<form {onsubmit} autocomplete="off" id="create-tag-form">
<div class="my-4 flex flex-col gap-2">
<Combobox
onSelect={handleSelect}
@@ -77,7 +89,7 @@
type="button"
class="text-gray-100 dark:text-immich-dark-gray bg-immich-primary/95 dark:bg-immich-dark-primary/95 rounded-tr-full rounded-br-full place-items-center place-content-center pr-2 pl-1 py-1 hover:bg-immich-primary/80 dark:hover:bg-immich-dark-primary/80 transition-all"
title="Remove tag"
on:click={() => handleRemove(tagId)}
onclick={() => handleRemove(tagId)}
>
<Icon path={mdiClose} />
</button>
@@ -86,8 +98,8 @@
{/each}
</section>
<svelte:fragment slot="sticky-bottom">
<Button color="gray" fullwidth on:click={onCancel}>{$t('cancel')}</Button>
{#snippet stickyBottom()}
<Button color="gray" fullwidth onclick={onCancel}>{$t('cancel')}</Button>
<Button type="submit" fullwidth form="create-tag-form" {disabled}>{$t('tag_assets')}</Button>
</svelte:fragment>
{/snippet}
</FullScreenModal>