feat(sidebar): library + general settings dialogs, sidebar footer, alignment polish
- 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>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/state';
|
||||
import { createMutation, createQuery, useQueryClient } from '@tanstack/svelte-query';
|
||||
import { mode, toggleMode } from 'mode-watcher';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import {
|
||||
createFolder,
|
||||
@@ -12,6 +13,7 @@
|
||||
heapDownloadUrl,
|
||||
listFolders,
|
||||
listHeaps,
|
||||
logout,
|
||||
renameFolder,
|
||||
renameHeap,
|
||||
triggerDownload,
|
||||
@@ -24,11 +26,23 @@
|
||||
setSection,
|
||||
type Section
|
||||
} from '$lib/stores/filters.svelte';
|
||||
import { isAuthenticated } from '$lib/stores/session.svelte';
|
||||
import { isAuthenticated, session } from '$lib/stores/session.svelte';
|
||||
import FolderTree, { buildTree } from './FolderTree.svelte';
|
||||
import GeneralSettingsDialog from './GeneralSettingsDialog.svelte';
|
||||
import HeapConvertDialog from './HeapConvertDialog.svelte';
|
||||
import KebabMenu, { Item, Separator } from './KebabMenu.svelte';
|
||||
import { Copy, Download, FolderInput, Pencil, Trash2 } from 'lucide-svelte';
|
||||
import SettingsDialog from './SettingsDialog.svelte';
|
||||
import {
|
||||
Copy,
|
||||
Download,
|
||||
FolderInput,
|
||||
LogOut,
|
||||
Moon,
|
||||
Pencil,
|
||||
Settings,
|
||||
Sun,
|
||||
Trash2
|
||||
} from 'lucide-svelte';
|
||||
|
||||
const qc = useQueryClient();
|
||||
|
||||
@@ -88,6 +102,18 @@
|
||||
// mounts <HeapConvertDialog>; the dialog clears it on close.
|
||||
let convertingHeap = $state<PpAlbum | null>(null);
|
||||
|
||||
// Library/admin settings dialog visibility.
|
||||
let settingsOpen = $state(false);
|
||||
|
||||
// App-wide preferences dialog (theme etc.). Distinct from the library
|
||||
// admin dialog above — opened from the bottom-of-sidebar footer.
|
||||
let generalSettingsOpen = $state(false);
|
||||
|
||||
async function onSignOut() {
|
||||
await logout();
|
||||
await goto('/login', { replaceState: true });
|
||||
}
|
||||
|
||||
async function navigateTo(section: Section, heapUid: string | null = null) {
|
||||
setSection(section, heapUid);
|
||||
setFolderPath(null);
|
||||
@@ -220,7 +246,8 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<nav class="space-y-3">
|
||||
<div class="flex h-full flex-col">
|
||||
<nav class="flex-1 space-y-3 overflow-y-auto p-3">
|
||||
<!-- Views — section-driven entries + route-driven entries under a
|
||||
single uppercase eyebrow. Compact rows, no icons. -->
|
||||
<div>
|
||||
@@ -279,12 +306,14 @@
|
||||
<ul>
|
||||
{#each heapsQuery.data ?? [] as heap (heap.UID)}
|
||||
{@const active = isActive('heap', heap.UID)}
|
||||
<li class="group flex items-center">
|
||||
<li
|
||||
class="group flex h-[24px] items-center rounded text-[12px] leading-tight hover:bg-accent"
|
||||
class:bg-primary={active}
|
||||
class:text-primary-foreground={active}
|
||||
class:hover:bg-primary={active}
|
||||
>
|
||||
<button
|
||||
class="flex h-[24px] flex-1 items-center gap-2 rounded px-2 text-left text-[12px] leading-tight hover:bg-accent"
|
||||
class:bg-primary={active}
|
||||
class:text-primary-foreground={active}
|
||||
class:hover:bg-primary={active}
|
||||
class="flex flex-1 items-center gap-2 px-2 text-left"
|
||||
onclick={() => navigateTo('heap', heap.UID)}
|
||||
ondblclick={() => onRenameHeap(heap)}
|
||||
title={`${heap.Title} (${heap.PhotoCount ?? 0})`}
|
||||
@@ -298,7 +327,7 @@
|
||||
{heap.PhotoCount ?? 0}
|
||||
</span>
|
||||
</button>
|
||||
<div class="pl-0.5">
|
||||
<div class="mr-1">
|
||||
<KebabMenu label="Heap actions">
|
||||
<Item
|
||||
class="flex cursor-pointer items-center gap-2 rounded px-2 py-1.5 text-[12px] outline-none hover:bg-accent focus:bg-accent"
|
||||
@@ -345,10 +374,18 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="group/header flex items-center px-3 pb-1">
|
||||
<div class="group/header flex items-center gap-0.5 px-3 pb-1">
|
||||
<span class="flex-1 text-[10px] font-semibold uppercase tracking-[0.14em] text-muted-foreground">
|
||||
Folders
|
||||
</span>
|
||||
<button
|
||||
class="rounded p-0.5 text-muted-foreground opacity-0 hover:bg-accent hover:text-foreground group-hover/header:opacity-100"
|
||||
onclick={() => (settingsOpen = true)}
|
||||
title="Library settings"
|
||||
aria-label="Library settings"
|
||||
>
|
||||
<Settings class="h-3 w-3" />
|
||||
</button>
|
||||
<button
|
||||
class="rounded p-0.5 text-xs text-muted-foreground opacity-0 hover:bg-accent hover:text-foreground group-hover/header:opacity-100"
|
||||
onclick={() => onCreateFolder(null)}
|
||||
@@ -386,4 +423,57 @@
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!--
|
||||
Footer — fixed to the bottom of the sidebar. Holds the per-user
|
||||
affordances (display name, quick theme toggle, general preferences,
|
||||
sign-out) that used to live in the top toolbar.
|
||||
-->
|
||||
<footer
|
||||
class="flex shrink-0 items-center gap-1 border-t border-border bg-card/50 px-3 py-2"
|
||||
>
|
||||
<span
|
||||
class="min-w-0 flex-1 truncate text-[12px] text-foreground"
|
||||
title={session.user?.DisplayName ?? session.user?.Name ?? ''}
|
||||
>
|
||||
{session.user?.DisplayName ?? session.user?.Name ?? 'Signed in'}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
onclick={toggleMode}
|
||||
title="Toggle theme"
|
||||
aria-label="Toggle theme"
|
||||
>
|
||||
{#if mode.current === 'dark'}
|
||||
<Sun class="h-3.5 w-3.5" />
|
||||
{:else}
|
||||
<Moon class="h-3.5 w-3.5" />
|
||||
{/if}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
onclick={() => (generalSettingsOpen = true)}
|
||||
title="General settings"
|
||||
aria-label="General settings"
|
||||
>
|
||||
<Settings class="h-3.5 w-3.5" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
onclick={onSignOut}
|
||||
title="Sign out"
|
||||
aria-label="Sign out"
|
||||
>
|
||||
<LogOut class="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<HeapConvertDialog heap={convertingHeap} onClose={() => (convertingHeap = null)} />
|
||||
<SettingsDialog open={settingsOpen} onClose={() => (settingsOpen = false)} />
|
||||
<GeneralSettingsDialog
|
||||
open={generalSettingsOpen}
|
||||
onClose={() => (generalSettingsOpen = false)}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user