- Add a Settings cog to the Folders header that opens a tabbed library admin dialog (Library / Index / Import / Logs) wrapping PhotoPrism's /api/v1 settings, index, import and errors endpoints. - Add a sticky footer to the left sidebar with the signed-in user's display name plus quick-toggle theme, general-settings cog (separate dialog for app prefs), and sign-out. Pull these out of the top Toolbar trailing slot. - Align depth-0 folder rows with the rest of the sidebar entries (drop the leading chevron column when no children) and bring heap rows in line with folder rows so the kebab is part of the row's hover background instead of a detached chip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
84 lines
2.8 KiB
Svelte
84 lines
2.8 KiB
Svelte
<!--
|
|
General app preferences. Distinct from the PhotoPrism-library admin dialog:
|
|
this one owns settings that affect *this* SvelteKit shell (theme), not the
|
|
server. Opened from the bottom of the left sidebar.
|
|
-->
|
|
<script lang="ts">
|
|
import { Dialog } from 'bits-ui';
|
|
import { mode, setMode } from 'mode-watcher';
|
|
import { Monitor, Moon, Settings as SettingsIcon, Sun, X } from 'lucide-svelte';
|
|
|
|
interface Props {
|
|
open: boolean;
|
|
onClose: () => void;
|
|
}
|
|
let { open, onClose }: Props = $props();
|
|
|
|
const themeOptions = [
|
|
{ value: 'light', label: 'Light', Icon: Sun },
|
|
{ value: 'dark', label: 'Dark', Icon: Moon },
|
|
{ value: 'system', label: 'System', Icon: Monitor }
|
|
] as const;
|
|
</script>
|
|
|
|
<Dialog.Root
|
|
{open}
|
|
onOpenChange={(o) => {
|
|
if (!o) onClose();
|
|
}}
|
|
>
|
|
<Dialog.Portal>
|
|
<Dialog.Overlay
|
|
class="fixed inset-0 z-40 bg-background/80 backdrop-blur-sm data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0"
|
|
/>
|
|
<Dialog.Content
|
|
class="fixed left-1/2 top-1/2 z-50 grid w-full max-w-[440px] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-lg border border-border bg-card p-5 text-card-foreground shadow-lg outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95"
|
|
>
|
|
<div class="flex items-start gap-2">
|
|
<SettingsIcon class="mt-0.5 h-4 w-4 text-muted-foreground" />
|
|
<div class="flex-1">
|
|
<Dialog.Title class="text-sm font-semibold leading-tight">
|
|
General settings
|
|
</Dialog.Title>
|
|
<Dialog.Description class="mt-1 text-xs text-muted-foreground">
|
|
Preferences for this app. Library-side settings live under
|
|
Folders → ⚙.
|
|
</Dialog.Description>
|
|
</div>
|
|
<Dialog.Close
|
|
class="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
|
aria-label="Close"
|
|
>
|
|
<X class="h-3.5 w-3.5" />
|
|
</Dialog.Close>
|
|
</div>
|
|
|
|
<section class="space-y-2 text-[12px]">
|
|
<h3 class="text-[10px] font-semibold uppercase tracking-[0.14em] text-muted-foreground">
|
|
Appearance
|
|
</h3>
|
|
<div
|
|
class="flex items-center overflow-hidden rounded-md border border-border"
|
|
role="group"
|
|
aria-label="Theme"
|
|
>
|
|
{#each themeOptions as opt (opt.value)}
|
|
{@const active = mode.current === opt.value}
|
|
<button
|
|
type="button"
|
|
class="flex flex-1 items-center justify-center gap-1.5 px-3 py-1.5 hover:bg-accent"
|
|
class:bg-primary={active}
|
|
class:text-primary-foreground={active}
|
|
class:hover:bg-primary={active}
|
|
onclick={() => setMode(opt.value)}
|
|
>
|
|
<opt.Icon class="h-3.5 w-3.5" />
|
|
{opt.label}
|
|
</button>
|
|
{/each}
|
|
</div>
|
|
</section>
|
|
</Dialog.Content>
|
|
</Dialog.Portal>
|
|
</Dialog.Root>
|