chore(web): migration svelte 5 syntax (#13883)
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user