mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
refactor: settings accordion reactivity (#28281)
This commit is contained in:
@@ -1,10 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { accordionManager } from '$lib/managers/accordion-manager.svelte';
|
||||
import { Icon } from '@immich/ui';
|
||||
import { onDestroy, onMount, type Snippet } from 'svelte';
|
||||
import { onDestroy, type Snippet } from 'svelte';
|
||||
import { slide } from 'svelte/transition';
|
||||
import { getAccordionState } from './SettingAccordionState.svelte';
|
||||
|
||||
const accordionState = getAccordionState();
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
@@ -21,7 +19,7 @@
|
||||
title,
|
||||
subtitle = '',
|
||||
key,
|
||||
isOpen = $bindable($accordionState.has(key)),
|
||||
isOpen = $bindable(false),
|
||||
autoScrollTo = false,
|
||||
icon = '',
|
||||
subtitleSnippet,
|
||||
@@ -30,9 +28,15 @@
|
||||
|
||||
let accordionElement: HTMLDivElement | undefined = $state();
|
||||
|
||||
const setIsOpen = (isOpen: boolean) => {
|
||||
$effect(() => {
|
||||
isOpen = accordionManager.isOpen(key);
|
||||
});
|
||||
|
||||
const toggleOpen = () => {
|
||||
if (isOpen) {
|
||||
$accordionState = $accordionState.add(key);
|
||||
accordionManager.close(key);
|
||||
} else {
|
||||
accordionManager.open(key);
|
||||
|
||||
if (autoScrollTo) {
|
||||
setTimeout(() => {
|
||||
@@ -42,24 +46,11 @@
|
||||
});
|
||||
}, 200);
|
||||
}
|
||||
} else {
|
||||
$accordionState.delete(key);
|
||||
// eslint-disable-next-line no-self-assign
|
||||
$accordionState = $accordionState;
|
||||
}
|
||||
};
|
||||
|
||||
onDestroy(() => {
|
||||
setIsOpen(false);
|
||||
});
|
||||
|
||||
const onclick = () => {
|
||||
isOpen = !isOpen;
|
||||
setIsOpen(isOpen);
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
setIsOpen(isOpen);
|
||||
accordionManager.close(key);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -72,7 +63,7 @@
|
||||
<button
|
||||
type="button"
|
||||
aria-expanded={isOpen}
|
||||
{onclick}
|
||||
onclick={toggleOpen}
|
||||
class="flex w-full place-items-center justify-between text-start"
|
||||
>
|
||||
<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?.()}
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user