mirror of
https://github.com/immich-app/immich
synced 2026-08-15 13:03:57 +00:00
refactor: settings accordion reactivity (#28281)
This commit is contained in:
parent
52b00b0bad
commit
2039c129f2
8 changed files with 178 additions and 202 deletions
|
|
@ -11,5 +11,6 @@
|
||||||
"@types/oidc-provider": "^9.0.0",
|
"@types/oidc-provider": "^9.0.0",
|
||||||
"oidc-provider": "^9.0.0",
|
"oidc-provider": "^9.0.0",
|
||||||
"tsx": "^4.20.6"
|
"tsx": "^4.20.6"
|
||||||
}
|
},
|
||||||
|
"packageManager": "pnpm@10.33.1"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { accordionManager } from '$lib/managers/accordion-manager.svelte';
|
||||||
import { Icon } from '@immich/ui';
|
import { Icon } from '@immich/ui';
|
||||||
import { onDestroy, onMount, type Snippet } from 'svelte';
|
import { onDestroy, type Snippet } from 'svelte';
|
||||||
import { slide } from 'svelte/transition';
|
import { slide } from 'svelte/transition';
|
||||||
import { getAccordionState } from './SettingAccordionState.svelte';
|
|
||||||
|
|
||||||
const accordionState = getAccordionState();
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
|
|
@ -21,7 +19,7 @@
|
||||||
title,
|
title,
|
||||||
subtitle = '',
|
subtitle = '',
|
||||||
key,
|
key,
|
||||||
isOpen = $bindable($accordionState.has(key)),
|
isOpen = $bindable(false),
|
||||||
autoScrollTo = false,
|
autoScrollTo = false,
|
||||||
icon = '',
|
icon = '',
|
||||||
subtitleSnippet,
|
subtitleSnippet,
|
||||||
|
|
@ -30,9 +28,15 @@
|
||||||
|
|
||||||
let accordionElement: HTMLDivElement | undefined = $state();
|
let accordionElement: HTMLDivElement | undefined = $state();
|
||||||
|
|
||||||
const setIsOpen = (isOpen: boolean) => {
|
$effect(() => {
|
||||||
|
isOpen = accordionManager.isOpen(key);
|
||||||
|
});
|
||||||
|
|
||||||
|
const toggleOpen = () => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
$accordionState = $accordionState.add(key);
|
accordionManager.close(key);
|
||||||
|
} else {
|
||||||
|
accordionManager.open(key);
|
||||||
|
|
||||||
if (autoScrollTo) {
|
if (autoScrollTo) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
@ -42,24 +46,11 @@
|
||||||
});
|
});
|
||||||
}, 200);
|
}, 200);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
$accordionState.delete(key);
|
|
||||||
// eslint-disable-next-line no-self-assign
|
|
||||||
$accordionState = $accordionState;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
setIsOpen(false);
|
accordionManager.close(key);
|
||||||
});
|
|
||||||
|
|
||||||
const onclick = () => {
|
|
||||||
isOpen = !isOpen;
|
|
||||||
setIsOpen(isOpen);
|
|
||||||
};
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
setIsOpen(isOpen);
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -72,7 +63,7 @@
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
aria-expanded={isOpen}
|
aria-expanded={isOpen}
|
||||||
{onclick}
|
onclick={toggleOpen}
|
||||||
class="flex w-full place-items-center justify-between text-start"
|
class="flex w-full place-items-center justify-between text-start"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
<script lang="ts" module>
|
|
||||||
export type AccordionState = Set<string>;
|
|
||||||
|
|
||||||
const { get: getAccordionState, set: setAccordionState } = createContext<Writable<AccordionState>>();
|
|
||||||
export { getAccordionState };
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { writable, type Writable } from 'svelte/store';
|
|
||||||
import { createContext } from '$lib/utils/context';
|
|
||||||
import { page } from '$app/state';
|
|
||||||
import { goto } from '$app/navigation';
|
|
||||||
import type { Snippet } from 'svelte';
|
|
||||||
import { handlePromiseError } from '$lib/utils';
|
|
||||||
import { SvelteURLSearchParams } from 'svelte/reactivity';
|
|
||||||
|
|
||||||
const getParamValues = (param: string) => {
|
|
||||||
return new Set((page.url.searchParams.get(param) || '').split(' ').filter((x) => x !== ''));
|
|
||||||
};
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
queryParam: string;
|
|
||||||
state?: Writable<AccordionState>;
|
|
||||||
children?: Snippet;
|
|
||||||
}
|
|
||||||
|
|
||||||
let { queryParam, state = writable(getParamValues(queryParam)), children }: Props = $props();
|
|
||||||
setAccordionState(state);
|
|
||||||
|
|
||||||
const searchParams = new SvelteURLSearchParams(page.url.searchParams);
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
if ($state.size > 0) {
|
|
||||||
searchParams.set(queryParam, [...$state].join(' '));
|
|
||||||
} else {
|
|
||||||
searchParams.delete(queryParam);
|
|
||||||
}
|
|
||||||
|
|
||||||
handlePromiseError(goto(`?${searchParams.toString()}`, { replaceState: true, noScroll: true, keepFocus: true }));
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{@render children?.()}
|
|
||||||
46
web/src/lib/managers/accordion-manager.svelte.ts
Normal file
46
web/src/lib/managers/accordion-manager.svelte.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { SvelteSet, SvelteURLSearchParams } from 'svelte/reactivity';
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
import { page } from '$app/state';
|
||||||
|
import { QueryParameter } from '$lib/constants';
|
||||||
|
import { handlePromiseError } from '$lib/utils';
|
||||||
|
|
||||||
|
class AccordionManager {
|
||||||
|
// needs to be derived since `page.url.searchParams` isn't actually initialized by the time this class gets instantiated.
|
||||||
|
#searchParams = $derived(new SvelteURLSearchParams(page.url.searchParams));
|
||||||
|
#state = $derived(
|
||||||
|
new SvelteSet(
|
||||||
|
this.#searchParams
|
||||||
|
.get(QueryParameter.IS_OPEN)
|
||||||
|
?.split(' ')
|
||||||
|
.filter((x) => x !== ''),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
isOpen(key: string) {
|
||||||
|
return this.#state.has(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
#refreshSearchParams() {
|
||||||
|
if (this.#state.size === 0) {
|
||||||
|
this.#searchParams.delete(QueryParameter.IS_OPEN);
|
||||||
|
} else {
|
||||||
|
this.#searchParams.set(QueryParameter.IS_OPEN, [...this.#state].join(' '));
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePromiseError(
|
||||||
|
goto(`?${this.#searchParams.toString()}`, { replaceState: true, noScroll: true, keepFocus: true }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
open(key: string) {
|
||||||
|
this.#state.add(key);
|
||||||
|
this.#refreshSearchParams();
|
||||||
|
}
|
||||||
|
|
||||||
|
close(key: string) {
|
||||||
|
this.#state.delete(key);
|
||||||
|
this.#refreshSearchParams();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const accordionManager = new AccordionManager();
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
import { getContext, setContext } from 'svelte';
|
|
||||||
|
|
||||||
export function createContext<T>(key: string | symbol = Symbol()) {
|
|
||||||
return {
|
|
||||||
get: () => getContext<T>(key),
|
|
||||||
set: (context: T) => setContext<T>(key, context),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -26,7 +26,6 @@
|
||||||
mdiTwoFactorAuthentication,
|
mdiTwoFactorAuthentication,
|
||||||
} from '@mdi/js';
|
} from '@mdi/js';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import SettingAccordionState from '$lib/components/shared-components/settings/SettingAccordionState.svelte';
|
|
||||||
import SettingAccordion from '$lib/components/shared-components/settings/SettingAccordion.svelte';
|
import SettingAccordion from '$lib/components/shared-components/settings/SettingAccordion.svelte';
|
||||||
import AppSettings from './AppSettings.svelte';
|
import AppSettings from './AppSettings.svelte';
|
||||||
import ChangePasswordSettings from './ChangePasswordSettings.svelte';
|
import ChangePasswordSettings from './ChangePasswordSettings.svelte';
|
||||||
|
|
@ -48,7 +47,6 @@
|
||||||
$page.url.searchParams.get(QueryParameter.OPEN_SETTING) === OpenQueryParam.OAUTH;
|
$page.url.searchParams.get(QueryParameter.OPEN_SETTING) === OpenQueryParam.OAUTH;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SettingAccordionState queryParam={QueryParameter.IS_OPEN}>
|
|
||||||
<SettingAccordion
|
<SettingAccordion
|
||||||
icon={mdiCogOutline}
|
icon={mdiCogOutline}
|
||||||
key="app-settings"
|
key="app-settings"
|
||||||
|
|
@ -160,4 +158,3 @@
|
||||||
>
|
>
|
||||||
<UserPurchaseSettings />
|
<UserPurchaseSettings />
|
||||||
</SettingAccordion>
|
</SettingAccordion>
|
||||||
</SettingAccordionState>
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import AdminPageLayout from '$lib/components/layouts/AdminPageLayout.svelte';
|
import AdminPageLayout from '$lib/components/layouts/AdminPageLayout.svelte';
|
||||||
import MaintenanceBackupsList from '$lib/components/maintenance/MaintenanceBackupsList.svelte';
|
import MaintenanceBackupsList from '$lib/components/maintenance/MaintenanceBackupsList.svelte';
|
||||||
import SettingAccordionState from '$lib/components/shared-components/settings/SettingAccordionState.svelte';
|
|
||||||
import SettingAccordion from '$lib/components/shared-components/settings/SettingAccordion.svelte';
|
import SettingAccordion from '$lib/components/shared-components/settings/SettingAccordion.svelte';
|
||||||
import { QueryParameter } from '$lib/constants';
|
|
||||||
import { getMaintenanceAdminActions } from '$lib/services/maintenance.service';
|
import { getMaintenanceAdminActions } from '$lib/services/maintenance.service';
|
||||||
import { mdiRefresh } from '@mdi/js';
|
import { mdiRefresh } from '@mdi/js';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
@ -20,7 +18,6 @@
|
||||||
<AdminPageLayout breadcrumbs={[{ title: data.meta.title }]} actions={[StartMaintenance]}>
|
<AdminPageLayout breadcrumbs={[{ title: data.meta.title }]} actions={[StartMaintenance]}>
|
||||||
<section id="setting-content" class="flex place-content-center sm:mx-4">
|
<section id="setting-content" class="flex place-content-center sm:mx-4">
|
||||||
<section class="w-full pb-28 sm:w-5/6 md:w-212.5">
|
<section class="w-full pb-28 sm:w-5/6 md:w-212.5">
|
||||||
<SettingAccordionState queryParam={QueryParameter.IS_OPEN}>
|
|
||||||
<SettingAccordion
|
<SettingAccordion
|
||||||
title={$t('admin.maintenance_restore_database_backup')}
|
title={$t('admin.maintenance_restore_database_backup')}
|
||||||
subtitle={$t('admin.maintenance_restore_database_backup_description')}
|
subtitle={$t('admin.maintenance_restore_database_backup_description')}
|
||||||
|
|
@ -29,7 +26,6 @@
|
||||||
>
|
>
|
||||||
<MaintenanceBackupsList backups={data.backups} expectedVersion={data.expectedVersion} />
|
<MaintenanceBackupsList backups={data.backups} expectedVersion={data.expectedVersion} />
|
||||||
</SettingAccordion>
|
</SettingAccordion>
|
||||||
</SettingAccordionState>
|
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
</AdminPageLayout>
|
</AdminPageLayout>
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,7 @@
|
||||||
import TrashSettings from './TrashSettings.svelte';
|
import TrashSettings from './TrashSettings.svelte';
|
||||||
import UserSettings from './UserSettings.svelte';
|
import UserSettings from './UserSettings.svelte';
|
||||||
import AdminPageLayout from '$lib/components/layouts/AdminPageLayout.svelte';
|
import AdminPageLayout from '$lib/components/layouts/AdminPageLayout.svelte';
|
||||||
import SettingAccordionState from '$lib/components/shared-components/settings/SettingAccordionState.svelte';
|
|
||||||
import SettingAccordion from '$lib/components/shared-components/settings/SettingAccordion.svelte';
|
import SettingAccordion from '$lib/components/shared-components/settings/SettingAccordion.svelte';
|
||||||
import { QueryParameter } from '$lib/constants';
|
|
||||||
import SearchBar from '$lib/elements/SearchBar.svelte';
|
import SearchBar from '$lib/elements/SearchBar.svelte';
|
||||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||||
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
|
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
|
||||||
|
|
@ -215,12 +213,10 @@
|
||||||
<div>
|
<div>
|
||||||
<SearchBar placeholder={$t('search_settings')} bind:name={searchQuery} showLoadingSpinner={false} />
|
<SearchBar placeholder={$t('search_settings')} bind:name={searchQuery} showLoadingSpinner={false} />
|
||||||
</div>
|
</div>
|
||||||
<SettingAccordionState queryParam={QueryParameter.IS_OPEN}>
|
|
||||||
{#each filteredSettings as { component: Component, title, subtitle, key, icon } (key)}
|
{#each filteredSettings as { component: Component, title, subtitle, key, icon } (key)}
|
||||||
<SettingAccordion {title} {subtitle} {key} {icon}>
|
<SettingAccordion {title} {subtitle} {key} {icon}>
|
||||||
<Component />
|
<Component />
|
||||||
</SettingAccordion>
|
</SettingAccordion>
|
||||||
{/each}
|
{/each}
|
||||||
</SettingAccordionState>
|
|
||||||
</Container>
|
</Container>
|
||||||
</AdminPageLayout>
|
</AdminPageLayout>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue