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

@@ -18,13 +18,17 @@
import { t } from 'svelte-i18n';
import type { PageData } from './$types';
export let data: PageData;
interface Props {
data: PageData;
}
let jobs: AllJobStatusResponseDto;
let { data }: Props = $props();
let jobs: AllJobStatusResponseDto | undefined = $state();
let running = true;
let isOpen = false;
let selectedJob: ComboBoxOption | undefined = undefined;
let isOpen = $state(false);
let selectedJob: ComboBoxOption | undefined = $state(undefined);
onMount(async () => {
while (running) {
@@ -58,23 +62,30 @@
handleError(error, $t('errors.unable_to_submit_job'));
}
};
const onsubmit = async (event: Event) => {
event.preventDefault();
await handleCreate();
};
</script>
<UserPageLayout title={data.meta.title} admin>
<div class="flex justify-end" slot="buttons">
<LinkButton on:click={() => (isOpen = true)}>
<div class="flex place-items-center gap-2 text-sm">
<Icon path={mdiPlus} size="18" />
{$t('admin.create_job')}
</div>
</LinkButton>
<LinkButton href="{AppRoute.ADMIN_SETTINGS}?isOpen=job">
<div class="flex place-items-center gap-2 text-sm">
<Icon path={mdiCog} size="18" />
{$t('admin.manage_concurrency')}
</div>
</LinkButton>
</div>
{#snippet buttons()}
<div class="flex justify-end">
<LinkButton onclick={() => (isOpen = true)}>
<div class="flex place-items-center gap-2 text-sm">
<Icon path={mdiPlus} size="18" />
{$t('admin.create_job')}
</div>
</LinkButton>
<LinkButton href="{AppRoute.ADMIN_SETTINGS}?isOpen=job">
<div class="flex place-items-center gap-2 text-sm">
<Icon path={mdiCog} size="18" />
{$t('admin.manage_concurrency')}
</div>
</LinkButton>
</div>
{/snippet}
<section id="setting-content" class="flex place-content-center sm:mx-4">
<section class="w-full pb-28 sm:w-5/6 md:w-[850px]">
{#if jobs}
@@ -92,15 +103,17 @@
onConfirm={handleCreate}
onCancel={handleCancel}
>
<form on:submit|preventDefault={handleCreate} autocomplete="off" id="create-tag-form" slot="prompt" class="w-full">
<div class="flex flex-col gap-1 text-left">
<Combobox
bind:selectedOption={selectedJob}
label={$t('jobs')}
{options}
placeholder={$t('admin.search_jobs')}
/>
</div>
</form>
{#snippet promptSnippet()}
<form {onsubmit} autocomplete="off" id="create-tag-form" class="w-full">
<div class="flex flex-col gap-1 text-left">
<Combobox
bind:selectedOption={selectedJob}
label={$t('jobs')}
{options}
placeholder={$t('admin.search_jobs')}
/>
</div>
</form>
{/snippet}
</ConfirmDialog>
{/if}

View File

@@ -36,32 +36,36 @@
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
import { locale } from '$lib/stores/preferences.store';
export let data: PageData;
interface Props {
data: PageData;
}
let libraries: LibraryResponseDto[] = [];
let { data }: Props = $props();
let libraries: LibraryResponseDto[] = $state([]);
let stats: LibraryStatsResponseDto[] = [];
let owner: UserResponseDto[] = [];
let owner: UserResponseDto[] = $state([]);
let photos: number[] = [];
let videos: number[] = [];
let totalCount: number[] = [];
let diskUsage: number[] = [];
let diskUsageUnit: ByteUnit[] = [];
let editImportPaths: number | null;
let editScanSettings: number | null;
let renameLibrary: number | null;
let totalCount: number[] = $state([]);
let diskUsage: number[] = $state([]);
let diskUsageUnit: ByteUnit[] = $state([]);
let editImportPaths: number | undefined = $state();
let editScanSettings: number | undefined = $state();
let renameLibrary: number | undefined = $state();
let updateLibraryIndex: number | null;
let dropdownOpen: boolean[] = [];
let toCreateLibrary = false;
let toCreateLibrary = $state(false);
onMount(async () => {
await readLibraryList();
});
const closeAll = () => {
editImportPaths = null;
editScanSettings = null;
renameLibrary = null;
editImportPaths = undefined;
editScanSettings = undefined;
renameLibrary = undefined;
updateLibraryIndex = null;
for (let index = 0; index < dropdownOpen.length; index++) {
@@ -213,22 +217,24 @@
{/if}
<UserPageLayout title={data.meta.title} admin>
<div class="flex justify-end gap-2" slot="buttons">
{#if libraries.length > 0}
<LinkButton on:click={() => handleScanAll()}>
{#snippet buttons()}
<div class="flex justify-end gap-2">
{#if libraries.length > 0}
<LinkButton onclick={() => handleScanAll()}>
<div class="flex gap-1 text-sm">
<Icon path={mdiSync} size="18" />
<span>{$t('scan_all_libraries')}</span>
</div>
</LinkButton>
{/if}
<LinkButton onclick={() => (toCreateLibrary = true)}>
<div class="flex gap-1 text-sm">
<Icon path={mdiSync} size="18" />
<span>{$t('scan_all_libraries')}</span>
<Icon path={mdiPlusBoxOutline} size="18" />
<span>{$t('create_library')}</span>
</div>
</LinkButton>
{/if}
<LinkButton on:click={() => (toCreateLibrary = true)}>
<div class="flex gap-1 text-sm">
<Icon path={mdiPlusBoxOutline} size="18" />
<span>{$t('create_library')}</span>
</div>
</LinkButton>
</div>
</div>
{/snippet}
<section class="my-4">
<div class="flex flex-col gap-2" in:fade={{ duration: 500 }}>
{#if libraries.length > 0}
@@ -311,13 +317,17 @@
{#if renameLibrary === index}
<!-- svelte-ignore node_invalid_placement_ssr -->
<div transition:slide={{ duration: 250 }}>
<LibraryRenameForm {library} onSubmit={handleUpdate} onCancel={() => (renameLibrary = null)} />
<LibraryRenameForm {library} onSubmit={handleUpdate} onCancel={() => (renameLibrary = undefined)} />
</div>
{/if}
{#if editImportPaths === index}
<!-- svelte-ignore node_invalid_placement_ssr -->
<div transition:slide={{ duration: 250 }}>
<LibraryImportPathsForm {library} onSubmit={handleUpdate} onCancel={() => (editImportPaths = null)} />
<LibraryImportPathsForm
{library}
onSubmit={handleUpdate}
onCancel={() => (editImportPaths = undefined)}
/>
</div>
{/if}
{#if editScanSettings === index}
@@ -326,7 +336,7 @@
<LibraryScanSettingsForm
{library}
onSubmit={handleUpdate}
onCancel={() => (editScanSettings = null)}
onCancel={() => (editScanSettings = undefined)}
/>
</div>
{/if}

View File

@@ -19,7 +19,11 @@
import { t } from 'svelte-i18n';
import { locale } from '$lib/stores/preferences.store';
export let data: PageData;
interface Props {
data: PageData;
}
let { data }: Props = $props();
interface UntrackedFile {
filename: string;
@@ -33,12 +37,12 @@
const normalize = (filenames: string[]) => filenames.map((filename) => ({ filename, checksum: null }));
let checking = false;
let repairing = false;
let checking = $state(false);
let repairing = $state(false);
let orphans: FileReportItemDto[] = data.orphans;
let extras: UntrackedFile[] = normalize(data.extras);
let matches: Match[] = [];
let orphans: FileReportItemDto[] = $state(data.orphans);
let extras: UntrackedFile[] = $state(normalize(data.extras));
let matches: Match[] = $state([]);
const handleDownload = () => {
if (extras.length > 0) {
@@ -180,33 +184,34 @@
</script>
<UserPageLayout title={data.meta.title} admin>
<svelte:fragment slot="sidebar" />
<div class="flex justify-end gap-2" slot="buttons">
<LinkButton on:click={() => handleRepair()} disabled={matches.length === 0 || repairing}>
<div class="flex place-items-center gap-2 text-sm">
<Icon path={mdiWrench} size="18" />
{$t('admin.repair_all')}
</div>
</LinkButton>
<LinkButton on:click={() => handleCheckAll()} disabled={extras.length === 0 || checking}>
<div class="flex place-items-center gap-2 text-sm">
<Icon path={mdiCheckAll} size="18" />
{$t('admin.check_all')}
</div>
</LinkButton>
<LinkButton on:click={() => handleDownload()} disabled={extras.length + orphans.length === 0}>
<div class="flex place-items-center gap-2 text-sm">
<Icon path={mdiDownload} size="18" />
{$t('export')}
</div>
</LinkButton>
<LinkButton on:click={() => handleRefresh()}>
<div class="flex place-items-center gap-2 text-sm">
<Icon path={mdiRefresh} size="18" />
{$t('refresh')}
</div>
</LinkButton>
</div>
{#snippet buttons()}
<div class="flex justify-end gap-2">
<LinkButton onclick={() => handleRepair()} disabled={matches.length === 0 || repairing}>
<div class="flex place-items-center gap-2 text-sm">
<Icon path={mdiWrench} size="18" />
{$t('admin.repair_all')}
</div>
</LinkButton>
<LinkButton onclick={() => handleCheckAll()} disabled={extras.length === 0 || checking}>
<div class="flex place-items-center gap-2 text-sm">
<Icon path={mdiCheckAll} size="18" />
{$t('admin.check_all')}
</div>
</LinkButton>
<LinkButton onclick={() => handleDownload()} disabled={extras.length + orphans.length === 0}>
<div class="flex place-items-center gap-2 text-sm">
<Icon path={mdiDownload} size="18" />
{$t('export')}
</div>
</LinkButton>
<LinkButton onclick={() => handleRefresh()}>
<div class="flex place-items-center gap-2 text-sm">
<Icon path={mdiRefresh} size="18" />
{$t('refresh')}
</div>
</LinkButton>
</div>
{/snippet}
<section id="setting-content" class="flex place-content-center sm:mx-4">
<section class="w-full pb-28 sm:w-5/6 md:w-[850px]">
{#if matches.length + extras.length + orphans.length === 0}
@@ -238,7 +243,7 @@
<tr
class="w-full h-[75px] place-items-center border-[3px] border-transparent p-2 odd:bg-immich-gray even:bg-immich-bg hover:cursor-pointer hover:border-immich-primary/75 odd:dark:bg-immich-dark-gray/75 even:dark:bg-immich-dark-gray/50 dark:hover:border-immich-dark-primary/75 md:p-5 flex justify-between"
tabindex="0"
on:click={() => handleSplit(match)}
onclick={() => handleSplit(match)}
>
<td class="text-sm text-ellipsis flex flex-col gap-1 font-mono">
<span>{match.orphan.pathValue} =></span>
@@ -279,8 +284,8 @@
tabindex="0"
title={orphan.pathValue}
>
<td on:click={() => copyToClipboard(orphan.pathValue)}>
<CircleIconButton title={$t('copy_file_path')} icon={mdiContentCopy} size="18" />
<td onclick={() => copyToClipboard(orphan.pathValue)}>
<CircleIconButton title={$t('copy_file_path')} icon={mdiContentCopy} size="18" onclick={() => {}} />
</td>
<td class="truncate text-sm font-mono text-left" title={orphan.pathValue}>
{orphan.pathValue}
@@ -318,11 +323,11 @@
<tr
class="flex h-[50px] w-full place-items-center border-[3px] border-transparent p-1 odd:bg-immich-gray even:bg-immich-bg hover:cursor-pointer hover:border-immich-primary/75 odd:dark:bg-immich-dark-gray/75 even:dark:bg-immich-dark-gray/50 dark:hover:border-immich-dark-primary/75 md:p-5 justify-between"
tabindex="0"
on:click={() => handleCheckOne(extra.filename)}
onclick={() => handleCheckOne(extra.filename)}
title={extra.filename}
>
<td on:click={() => copyToClipboard(extra.filename)}>
<CircleIconButton title={$t('copy_file_path')} icon={mdiContentCopy} size="18" />
<td onclick={() => copyToClipboard(extra.filename)}>
<CircleIconButton title={$t('copy_file_path')} icon={mdiContentCopy} size="18" onclick={() => {}} />
</td>
<td class="w-full text-md text-ellipsis flex justify-between pr-5">
<span class="text-ellipsis grow truncate font-mono text-sm pr-5" title={extra.filename}

View File

@@ -6,7 +6,11 @@
import type { PageData } from './$types';
import { asyncTimeout } from '$lib/utils';
export let data: PageData;
interface Props {
data: PageData;
}
let { data = $bindable() }: Props = $props();
let running = true;

View File

@@ -27,7 +27,6 @@
import { featureFlags } from '$lib/stores/server-config.store';
import { copyToClipboard } from '$lib/utils';
import { downloadBlob } from '$lib/utils/asset-utils';
import type { SystemConfigDto } from '@immich/sdk';
import {
mdiAccountOutline,
mdiAlert,
@@ -53,16 +52,20 @@
} from '@mdi/js';
import type { PageData } from './$types';
import { t } from 'svelte-i18n';
import type { ComponentType, SvelteComponent } from 'svelte';
import type { Component } from 'svelte';
import type { SettingsComponentProps } from '$lib/components/admin-page/settings/admin-settings';
import SearchBar from '$lib/components/elements/search-bar.svelte';
export let data: PageData;
interface Props {
data: PageData;
}
let config = data.configs;
let handleSave: (update: Partial<SystemConfigDto>) => Promise<void>;
let { data }: Props = $props();
type SettingsComponent = ComponentType<SvelteComponent<SettingsComponentProps>>;
let config = $state(data.configs);
let adminSettingElement = $state<ReturnType<typeof AdminSettings>>();
type SettingsComponent = Component<SettingsComponentProps>;
// https://stackoverflow.com/questions/16167581/sort-object-properties-and-json-stringify/43636793#43636793
const jsonReplacer = (key: string, value: unknown) =>
@@ -85,7 +88,8 @@
setTimeout(() => downloadManager.clear(downloadKey), 5000);
};
let inputElement: HTMLInputElement;
let inputElement: HTMLInputElement | undefined = $state();
const uploadConfig = (e: Event) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (!file) {
@@ -94,7 +98,7 @@
const reader = async () => {
const text = await file.text();
const newConfig = JSON.parse(text);
await handleSave(newConfig);
await adminSettingElement?.handleSave(newConfig);
};
reader().catch((error) => console.error('Error handling JSON config upload', error));
};
@@ -227,15 +231,17 @@
},
];
let searchQuery = '';
let searchQuery = $state('');
$: filteredSettings = settings.filter(({ title, subtitle }) => {
const query = searchQuery.toLowerCase();
return title.toLowerCase().includes(query) || subtitle.toLowerCase().includes(query);
});
let filteredSettings = $derived(
settings.filter(({ title, subtitle }) => {
const query = searchQuery.toLowerCase();
return title.toLowerCase().includes(query) || subtitle.toLowerCase().includes(query);
}),
);
</script>
<input bind:this={inputElement} type="file" accept=".json" style="display: none" on:change={uploadConfig} />
<input bind:this={inputElement} type="file" accept=".json" style="display: none" onchange={uploadConfig} />
<div class="h-svh flex flex-col overflow-hidden">
{#if $featureFlags.configFile}
@@ -248,54 +254,58 @@
{/if}
<UserPageLayout title={data.meta.title} admin>
<div class="flex justify-end gap-2" slot="buttons">
<div class="hidden lg:block">
<SearchBar placeholder={$t('search_settings')} bind:name={searchQuery} showLoadingSpinner={false} />
</div>
<LinkButton on:click={() => copyToClipboard(JSON.stringify(config, jsonReplacer, 2))}>
<div class="flex place-items-center gap-2 text-sm">
<Icon path={mdiContentCopy} size="18" />
{$t('copy_to_clipboard')}
{#snippet buttons()}
<div class="flex justify-end gap-2">
<div class="hidden lg:block">
<SearchBar placeholder={$t('search_settings')} bind:name={searchQuery} showLoadingSpinner={false} />
</div>
</LinkButton>
<LinkButton on:click={() => downloadConfig()}>
<div class="flex place-items-center gap-2 text-sm">
<Icon path={mdiDownload} size="18" />
{$t('export_as_json')}
</div>
</LinkButton>
{#if !$featureFlags.configFile}
<LinkButton on:click={() => inputElement?.click()}>
<LinkButton onclick={() => copyToClipboard(JSON.stringify(config, jsonReplacer, 2))}>
<div class="flex place-items-center gap-2 text-sm">
<Icon path={mdiUpload} size="18" />
{$t('import_from_json')}
<Icon path={mdiContentCopy} size="18" />
{$t('copy_to_clipboard')}
</div>
</LinkButton>
{/if}
</div>
<AdminSettings bind:config let:handleReset bind:handleSave let:savedConfig let:defaultConfig>
<section id="setting-content" class="flex place-content-center sm:mx-4">
<section class="w-full pb-28 sm:w-5/6 md:w-[896px]">
<div class="block lg:hidden">
<SearchBar placeholder={$t('search_settings')} bind:name={searchQuery} showLoadingSpinner={false} />
<LinkButton onclick={() => downloadConfig()}>
<div class="flex place-items-center gap-2 text-sm">
<Icon path={mdiDownload} size="18" />
{$t('export_as_json')}
</div>
<SettingAccordionState queryParam={QueryParameter.IS_OPEN}>
{#each filteredSettings as { component: Component, title, subtitle, key, icon } (key)}
<SettingAccordion {title} {subtitle} {key} {icon}>
<Component
onSave={(config) => handleSave(config)}
onReset={(options) => handleReset(options)}
disabled={$featureFlags.configFile}
{defaultConfig}
{config}
{savedConfig}
/>
</SettingAccordion>
{/each}
</SettingAccordionState>
</LinkButton>
{#if !$featureFlags.configFile}
<LinkButton onclick={() => inputElement?.click()}>
<div class="flex place-items-center gap-2 text-sm">
<Icon path={mdiUpload} size="18" />
{$t('import_from_json')}
</div>
</LinkButton>
{/if}
</div>
{/snippet}
<AdminSettings bind:config bind:this={adminSettingElement}>
{#snippet children({ savedConfig, defaultConfig })}
<section id="setting-content" class="flex place-content-center sm:mx-4">
<section class="w-full pb-28 sm:w-5/6 md:w-[896px]">
<div class="block lg:hidden">
<SearchBar placeholder={$t('search_settings')} bind:name={searchQuery} showLoadingSpinner={false} />
</div>
<SettingAccordionState queryParam={QueryParameter.IS_OPEN}>
{#each filteredSettings as { component: Component, title, subtitle, key, icon } (key)}
<SettingAccordion {title} {subtitle} {key} {icon}>
<Component
onSave={(config) => adminSettingElement?.handleSave(config)}
onReset={(options) => adminSettingElement?.handleReset(options)}
disabled={$featureFlags.configFile}
bind:config
{defaultConfig}
{savedConfig}
/>
</SettingAccordion>
{/each}
</SettingAccordionState>
</section>
</section>
</section>
{/snippet}
</AdminSettings>
</UserPageLayout>
</div>

View File

@@ -27,16 +27,20 @@
import { t } from 'svelte-i18n';
import type { PageData } from './$types';
export let data: PageData;
interface Props {
data: PageData;
}
let allUsers: UserAdminResponseDto[] = [];
let shouldShowEditUserForm = false;
let shouldShowCreateUserForm = false;
let shouldShowPasswordResetSuccess = false;
let shouldShowDeleteConfirmDialog = false;
let shouldShowRestoreDialog = false;
let selectedUser: UserAdminResponseDto;
let newPassword: string;
let { data }: Props = $props();
let allUsers: UserAdminResponseDto[] = $state([]);
let shouldShowEditUserForm = $state(false);
let shouldShowCreateUserForm = $state(false);
let shouldShowPasswordResetSuccess = $state(false);
let shouldShowDeleteConfirmDialog = $state(false);
let shouldShowRestoreDialog = $state(false);
let selectedUser = $state<UserAdminResponseDto>();
let newPassword = $state('');
const refresh = async () => {
allUsers = await searchUsersAdmin({ withDeleted: true });
@@ -117,7 +121,7 @@
/>
{/if}
{#if shouldShowEditUserForm}
{#if shouldShowEditUserForm && selectedUser}
<EditUserForm
user={selectedUser}
bind:newPassword
@@ -128,7 +132,7 @@
/>
{/if}
{#if shouldShowDeleteConfirmDialog}
{#if shouldShowDeleteConfirmDialog && selectedUser}
<DeleteConfirmDialog
user={selectedUser}
onSuccess={onUserDelete}
@@ -137,7 +141,7 @@
/>
{/if}
{#if shouldShowRestoreDialog}
{#if shouldShowRestoreDialog && selectedUser}
<RestoreDialogue
user={selectedUser}
onSuccess={onUserRestore}
@@ -155,7 +159,7 @@
hideCancelButton={true}
confirmColor="green"
>
<svelte:fragment slot="prompt">
{#snippet promptSnippet()}
<div class="flex flex-col gap-4">
<p>{$t('admin.user_password_has_been_reset')}</p>
@@ -165,7 +169,7 @@
>
{newPassword}
</code>
<LinkButton on:click={() => copyToClipboard(newPassword)} title={$t('copy_password')}>
<LinkButton onclick={() => copyToClipboard(newPassword)} title={$t('copy_password')}>
<div class="flex place-items-center gap-2 text-sm">
<Icon path={mdiContentCopy} size="18" />
</div>
@@ -174,7 +178,7 @@
<p>{$t('admin.user_password_reset_description')}</p>
</div>
</svelte:fragment>
{/snippet}
</ConfirmDialog>
{/if}
@@ -223,7 +227,7 @@
title={$t('edit_user')}
color="primary"
size="16"
on:click={() => editUserHandler(immichUser)}
onclick={() => editUserHandler(immichUser)}
/>
{#if immichUser.id !== $user.id}
<CircleIconButton
@@ -231,7 +235,7 @@
title={$t('delete_user')}
color="primary"
size="16"
on:click={() => deleteUserHandler(immichUser)}
onclick={() => deleteUserHandler(immichUser)}
/>
{/if}
{/if}
@@ -243,7 +247,7 @@
})}
color="primary"
size="16"
on:click={() => restoreUserHandler(immichUser)}
onclick={() => restoreUserHandler(immichUser)}
/>
{/if}
</td>
@@ -253,7 +257,7 @@
</tbody>
</table>
<Button size="sm" on:click={() => (shouldShowCreateUserForm = true)}>{$t('create_user')}</Button>
<Button size="sm" onclick={() => (shouldShowCreateUserForm = true)}>{$t('create_user')}</Button>
</section>
</section>
</UserPageLayout>