1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-01-27 22:22:45 +01:00

feat(web): store accordion state in search params ()

* keep admin settings accordion state in search params

* refactor: sync implementation

* fix: avoid mutating svelte's internal search params

* add query parameter to enum

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
Daniel Dietzler 2024-02-11 00:25:02 +01:00 committed by GitHub
parent 6e7865519a
commit a9e6657a37
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 132 additions and 41 deletions
web/src
lib
routes/admin/system-settings

View file

@ -235,6 +235,7 @@
/> />
<SettingAccordion <SettingAccordion
key="hardware-acceleration"
title="Hardware Acceleration" title="Hardware Acceleration"
subtitle="Experimental; much faster, but will have lower quality at the same bitrate" subtitle="Experimental; much faster, but will have lower quality at the same bitrate"
> >
@ -296,7 +297,11 @@
</div> </div>
</SettingAccordion> </SettingAccordion>
<SettingAccordion title="Advanced" subtitle="Options most users should not need to change"> <SettingAccordion
key="advanced-options"
title="Advanced"
subtitle="Options most users should not need to change"
>
<div class="ml-4 mt-4 flex flex-col gap-4"> <div class="ml-4 mt-4 flex flex-col gap-4">
<SettingInputField <SettingInputField
inputType={SettingInputFieldType.NUMBER} inputType={SettingInputFieldType.NUMBER}

View file

@ -26,7 +26,12 @@
<div> <div>
<div in:fade={{ duration: 500 }}> <div in:fade={{ duration: 500 }}>
<SettingAccordion title="Library watching (EXPERIMENTAL)" subtitle="Automatically watch for changed files" isOpen> <SettingAccordion
key="library-watching"
title="Library watching (EXPERIMENTAL)"
subtitle="Automatically watch for changed files"
isOpen
>
<form autocomplete="off" on:submit|preventDefault> <form autocomplete="off" on:submit|preventDefault>
<div class="ml-4 mt-4 flex flex-col gap-4"> <div class="ml-4 mt-4 flex flex-col gap-4">
<SettingSwitch <SettingSwitch
@ -70,7 +75,12 @@
</form> </form>
</SettingAccordion> </SettingAccordion>
<SettingAccordion title="Periodic Scanning" subtitle="Configure periodic library scanning" isOpen> <SettingAccordion
key="library-scanning"
title="Periodic Scanning"
subtitle="Configure periodic library scanning"
isOpen
>
<form autocomplete="off" on:submit|preventDefault> <form autocomplete="off" on:submit|preventDefault>
<div class="ml-4 mt-4 flex flex-col gap-4"> <div class="ml-4 mt-4 flex flex-col gap-4">
<SettingSwitch <SettingSwitch

View file

@ -42,7 +42,11 @@
/> />
</div> </div>
<SettingAccordion title="Smart Search" subtitle="Search for images semantically using CLIP embeddings"> <SettingAccordion
key="smart-search"
title="Smart Search"
subtitle="Search for images semantically using CLIP embeddings"
>
<div class="ml-4 mt-4 flex flex-col gap-4"> <div class="ml-4 mt-4 flex flex-col gap-4">
<SettingSwitch <SettingSwitch
title="ENABLED" title="ENABLED"
@ -69,7 +73,11 @@
</div> </div>
</SettingAccordion> </SettingAccordion>
<SettingAccordion title="Facial Recognition" subtitle="Detect, recognize and group faces in images"> <SettingAccordion
key="facial-recognition"
title="Facial Recognition"
subtitle="Detect, recognize and group faces in images"
>
<div class="ml-4 mt-4 flex flex-col gap-4"> <div class="ml-4 mt-4 flex flex-col gap-4">
<SettingSwitch <SettingSwitch
title="ENABLED" title="ENABLED"

View file

@ -21,7 +21,7 @@
<div in:fade={{ duration: 500 }}> <div in:fade={{ duration: 500 }}>
<form autocomplete="off" on:submit|preventDefault> <form autocomplete="off" on:submit|preventDefault>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<SettingAccordion title="Map Settings" subtitle="Manage map settings"> <SettingAccordion key="map" title="Map Settings" subtitle="Manage map settings">
<div class="ml-4 mt-4 flex flex-col gap-4"> <div class="ml-4 mt-4 flex flex-col gap-4">
<SettingSwitch <SettingSwitch
title="ENABLED" title="ENABLED"
@ -51,7 +51,7 @@
</div></SettingAccordion </div></SettingAccordion
> >
<SettingAccordion title="Reverse Geocoding Settings"> <SettingAccordion key="reverse-geocoding" title="Reverse Geocoding Settings">
<svelte:fragment slot="subtitle"> <svelte:fragment slot="subtitle">
<p class="text-sm dark:text-immich-dark-fg"> <p class="text-sm dark:text-immich-dark-fg">
Manage <a Manage <a

View file

@ -1,10 +1,24 @@
<script lang="ts"> <script lang="ts">
import { page } from '$app/stores';
import { QueryParameter } from '$lib/constants';
import { hasParamValue, updateParamList } from '$lib/utils';
import { slide } from 'svelte/transition'; import { slide } from 'svelte/transition';
export let title: string; export let title: string;
export let subtitle = ''; export let subtitle = '';
export let key: string;
export let isOpen = false; export let isOpen = false;
const toggle = () => (isOpen = !isOpen);
const syncFromUrl = () => (isOpen = hasParamValue(QueryParameter.IS_OPEN, key));
const syncToUrl = (isOpen: boolean) => updateParamList({ param: QueryParameter.IS_OPEN, value: key, add: isOpen });
isOpen ? syncToUrl(true) : syncFromUrl();
$: $page.url && syncFromUrl();
const toggle = () => {
isOpen = !isOpen;
syncToUrl(isOpen);
};
</script> </script>
<div class="border-b-[1px] border-gray-200 py-4 dark:border-gray-700"> <div class="border-b-[1px] border-gray-200 py-4 dark:border-gray-700">

View file

@ -27,32 +27,33 @@
} }
</script> </script>
<SettingAccordion title="Appearance" subtitle="Manage your Immich appearance"> <SettingAccordion key="appearance" title="Appearance" subtitle="Manage your Immich appearance">
<AppearanceSettings /> <AppearanceSettings />
</SettingAccordion> </SettingAccordion>
<SettingAccordion title="Account" subtitle="Manage your account"> <SettingAccordion key="account" title="Account" subtitle="Manage your account">
<UserProfileSettings user={$user} /> <UserProfileSettings user={$user} />
</SettingAccordion> </SettingAccordion>
<SettingAccordion title="API Keys" subtitle="Manage your API keys"> <SettingAccordion key="api-keys" title="API Keys" subtitle="Manage your API keys">
<UserAPIKeyList bind:keys /> <UserAPIKeyList bind:keys />
</SettingAccordion> </SettingAccordion>
<SettingAccordion title="Authorized Devices" subtitle="Manage your logged-in devices"> <SettingAccordion key="authorized-devices" title="Authorized Devices" subtitle="Manage your logged-in devices">
<DeviceList bind:devices /> <DeviceList bind:devices />
</SettingAccordion> </SettingAccordion>
<SettingAccordion title="Libraries" subtitle="Manage your asset libraries"> <SettingAccordion key="libraries" title="Libraries" subtitle="Manage your asset libraries">
<LibraryList /> <LibraryList />
</SettingAccordion> </SettingAccordion>
<SettingAccordion title="Memories" subtitle="Manage what you see in your memories."> <SettingAccordion key="memories" title="Memories" subtitle="Manage what you see in your memories.">
<MemoriesSettings user={$user} /> <MemoriesSettings user={$user} />
</SettingAccordion> </SettingAccordion>
{#if $featureFlags.loaded && $featureFlags.oauth} {#if $featureFlags.loaded && $featureFlags.oauth}
<SettingAccordion <SettingAccordion
key="oauth"
title="OAuth" title="OAuth"
subtitle="Manage your OAuth connection" subtitle="Manage your OAuth connection"
isOpen={oauthOpen || isOpen={oauthOpen ||
@ -62,18 +63,18 @@
</SettingAccordion> </SettingAccordion>
{/if} {/if}
<SettingAccordion title="Password" subtitle="Change your password"> <SettingAccordion key="password" title="Password" subtitle="Change your password">
<ChangePasswordSettings /> <ChangePasswordSettings />
</SettingAccordion> </SettingAccordion>
<SettingAccordion title="Sharing" subtitle="Manage sharing with partners"> <SettingAccordion key="sharing" title="Sharing" subtitle="Manage sharing with partners">
<PartnerSettings user={$user} /> <PartnerSettings user={$user} />
</SettingAccordion> </SettingAccordion>
<SettingAccordion title="Sidebar" subtitle="Manage sidebar settings"> <SettingAccordion key="sidebar" title="Sidebar" subtitle="Manage sidebar settings">
<SidebarSettings /> <SidebarSettings />
</SettingAccordion> </SettingAccordion>
<SettingAccordion title="Trash" subtitle="Manage trash settings"> <SettingAccordion key="trash" title="Trash" subtitle="Manage trash settings">
<TrashSettings /> <TrashSettings />
</SettingAccordion> </SettingAccordion>

View file

@ -63,14 +63,15 @@ export const dateFormats = {
export enum QueryParameter { export enum QueryParameter {
ACTION = 'action', ACTION = 'action',
ASSET_INDEX = 'assetIndex', ASSET_INDEX = 'assetIndex',
SMART_SEARCH = 'smartSearch', IS_OPEN = 'isOpen',
MEMORY_INDEX = 'memoryIndex', MEMORY_INDEX = 'memoryIndex',
ONBOARDING_STEP = 'step', ONBOARDING_STEP = 'step',
OPEN_SETTING = 'openSetting', OPEN_SETTING = 'openSetting',
QUERY = 'query',
PREVIOUS_ROUTE = 'previousRoute', PREVIOUS_ROUTE = 'previousRoute',
QUERY = 'query',
SEARCHED_PEOPLE = 'searchedPeople', SEARCHED_PEOPLE = 'searchedPeople',
SEARCH_TERM = 'q', SEARCH_TERM = 'q',
SMART_SEARCH = 'smartSearch',
} }
export enum OpenSettingQueryParameterValue { export enum OpenSettingQueryParameterValue {

33
web/src/lib/utils.ts Normal file
View file

@ -0,0 +1,33 @@
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { get } from 'svelte/store';
interface UpdateParamAction {
param: string;
value: string;
add: boolean;
}
const getParamValues = (param: string) =>
new Set((get(page).url.searchParams.get(param) || '').split(' ').filter((x) => x !== ''));
export const hasParamValue = (param: string, value: string) => getParamValues(param).has(value);
export const updateParamList = async ({ param, value, add }: UpdateParamAction) => {
const values = getParamValues(param);
if (add) {
values.add(value);
} else {
values.delete(value);
}
const searchParams = new URLSearchParams(get(page).url.searchParams);
searchParams.set(param, [...values.values()].join(' '));
if (values.size === 0) {
searchParams.delete(param);
}
await goto(`?${searchParams.toString()}`, { replaceState: true, noScroll: true, keepFocus: true });
};

View file

@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import { page } from '$app/stores';
import FFmpegSettings from '$lib/components/admin-page/settings/ffmpeg/ffmpeg-settings.svelte'; import FFmpegSettings from '$lib/components/admin-page/settings/ffmpeg/ffmpeg-settings.svelte';
import JobSettings from '$lib/components/admin-page/settings/job-settings/job-settings.svelte'; import JobSettings from '$lib/components/admin-page/settings/job-settings/job-settings.svelte';
import MachineLearningSettings from '$lib/components/admin-page/settings/machine-learning-settings/machine-learning-settings.svelte'; import MachineLearningSettings from '$lib/components/admin-page/settings/machine-learning-settings/machine-learning-settings.svelte';
@ -17,7 +16,7 @@
import { downloadManager } from '$lib/stores/download'; import { downloadManager } from '$lib/stores/download';
import { featureFlags } from '$lib/stores/server-config.store'; import { featureFlags } from '$lib/stores/server-config.store';
import { downloadBlob } from '$lib/utils/asset-utils'; import { downloadBlob } from '$lib/utils/asset-utils';
import { type SystemConfigDto, copyToClipboard } from '@api'; import { copyToClipboard } from '@api';
import Icon from '$lib/components/elements/icon.svelte'; import Icon from '$lib/components/elements/icon.svelte';
import type { PageData } from './$types'; import type { PageData } from './$types';
import NewVersionCheckSettings from '$lib/components/admin-page/settings/new-version-check-settings/new-version-check-settings.svelte'; import NewVersionCheckSettings from '$lib/components/admin-page/settings/new-version-check-settings/new-version-check-settings.svelte';
@ -29,7 +28,22 @@
export let data: PageData; export let data: PageData;
let config = data.configs; let config = data.configs;
let openSettings = ($page.url.searchParams.get('open')?.split(',') || []) as Array<keyof SystemConfigDto>;
type Settings =
| typeof JobSettings
| typeof LibrarySettings
| typeof LoggingSettings
| typeof MachineLearningSettings
| typeof MapSettings
| typeof OAuthSettings
| typeof PasswordLoginSettings
| typeof ServerSettings
| typeof StorageTemplateSettings
| typeof ThemeSettings
| typeof ThumbnailSettings
| typeof TrashSettings
| typeof NewVersionCheckSettings
| typeof FFmpegSettings;
const downloadConfig = () => { const downloadConfig = () => {
const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' }); const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' });
@ -40,90 +54,95 @@
setTimeout(() => downloadManager.clear(downloadKey), 5000); setTimeout(() => downloadManager.clear(downloadKey), 5000);
}; };
const settings = [ const settings: Array<{
item: Settings;
title: string;
subtitle: string;
key: string;
}> = [
{ {
item: JobSettings, item: JobSettings,
title: 'Job Settings', title: 'Job Settings',
subtitle: 'Manage job concurrency', subtitle: 'Manage job concurrency',
isOpen: openSettings.includes('job'), key: 'job',
}, },
{ {
item: LibrarySettings, item: LibrarySettings,
title: 'Library', title: 'Library',
subtitle: 'Manage library settings', subtitle: 'Manage library settings',
isOpen: openSettings.includes('library'), key: 'library',
}, },
{ {
item: LoggingSettings, item: LoggingSettings,
title: 'Logging', title: 'Logging',
subtitle: 'Manage log settings', subtitle: 'Manage log settings',
isOpen: openSettings.includes('logging'), key: 'logging',
}, },
{ {
item: MachineLearningSettings, item: MachineLearningSettings,
title: 'Machine Learning Settings', title: 'Machine Learning Settings',
subtitle: 'Manage machine learning features and settings', subtitle: 'Manage machine learning features and settings',
isOpen: openSettings.includes('machineLearning'), key: 'machine-learning',
}, },
{ {
item: MapSettings, item: MapSettings,
title: 'Map & GPS Settings', title: 'Map & GPS Settings',
subtitle: 'Manage map related features and setting', subtitle: 'Manage map related features and setting',
isOpen: openSettings.some((key) => ['map', 'reverseGeocoding'].includes(key)), key: 'location',
}, },
{ {
item: OAuthSettings, item: OAuthSettings,
title: 'OAuth Authentication', title: 'OAuth Authentication',
subtitle: 'Manage the login with OAuth settings', subtitle: 'Manage the login with OAuth settings',
isOpen: openSettings.includes('oauth'), key: 'oauth',
}, },
{ {
item: PasswordLoginSettings, item: PasswordLoginSettings,
title: 'Password Authentication', title: 'Password Authentication',
subtitle: 'Manage the login with password settings', subtitle: 'Manage the login with password settings',
isOpen: openSettings.includes('passwordLogin'), key: 'password',
}, },
{ {
item: ServerSettings, item: ServerSettings,
title: 'Server Settings', title: 'Server Settings',
subtitle: 'Manage server settings', subtitle: 'Manage server settings',
isOpen: openSettings.includes('server'), key: 'server',
}, },
{ {
item: StorageTemplateSettings, item: StorageTemplateSettings,
title: 'Storage Template', title: 'Storage Template',
subtitle: 'Manage the folder structure and file name of the upload asset', subtitle: 'Manage the folder structure and file name of the upload asset',
isOpen: openSettings.includes('storageTemplate'), key: 'storage-template',
}, },
{ {
item: ThemeSettings, item: ThemeSettings,
title: 'Theme Settings', title: 'Theme Settings',
subtitle: 'Manage customization of the Immich web interface', subtitle: 'Manage customization of the Immich web interface',
isOpen: openSettings.includes('theme'), key: 'theme',
}, },
{ {
item: ThumbnailSettings, item: ThumbnailSettings,
title: 'Thumbnail Settings', title: 'Thumbnail Settings',
subtitle: 'Manage the resolution of thumbnail sizes', subtitle: 'Manage the resolution of thumbnail sizes',
isOpen: openSettings.includes('thumbnail'), key: 'thumbnail',
}, },
{ {
item: TrashSettings, item: TrashSettings,
title: 'Trash Settings', title: 'Trash Settings',
subtitle: 'Manage trash settings', subtitle: 'Manage trash settings',
isOpen: openSettings.includes('trash'), key: 'trash',
}, },
{ {
item: NewVersionCheckSettings, item: NewVersionCheckSettings,
title: 'Version Check', title: 'Version Check',
subtitle: 'Enable/disable the new version notification', subtitle: 'Enable/disable the new version notification',
isOpen: openSettings.includes('newVersionCheck'), key: 'version-check',
}, },
{ {
item: FFmpegSettings, item: FFmpegSettings,
title: 'Video Transcoding Settings', title: 'Video Transcoding Settings',
subtitle: 'Manage the resolution and encoding information of the video files', subtitle: 'Manage the resolution and encoding information of the video files',
isOpen: openSettings.includes('ffmpeg'), key: 'video-transcoding',
}, },
]; ];
</script> </script>
@ -157,8 +176,8 @@
<AdminSettings bind:config let:handleReset let:handleSave let:savedConfig let:defaultConfig> <AdminSettings bind:config let:handleReset let:handleSave let:savedConfig let:defaultConfig>
<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-[850px]"> <section class="w-full pb-28 sm:w-5/6 md:w-[850px]">
{#each settings as { item, title, subtitle, isOpen }} {#each settings as { item, title, subtitle, key }}
<SettingAccordion {title} {subtitle} {isOpen}> <SettingAccordion {title} {subtitle} {key}>
<svelte:component <svelte:component
this={item} this={item}
on:save={({ detail }) => handleSave(detail)} on:save={({ detail }) => handleSave(detail)}