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:
2026-05-17 16:51:56 +02:00
parent 17df1ecd09
commit 0766b47bb2
7 changed files with 729 additions and 43 deletions

View File

@@ -3,14 +3,12 @@
import { goto } from '$app/navigation';
import { page } from '$app/state';
import { createInfiniteQuery, createQuery, useQueryClient } from '@tanstack/svelte-query';
import { toggleMode, mode } from 'mode-watcher';
import { toast } from 'svelte-sonner';
import {
batchDelete,
getPhoto,
listHeaps,
listPhotos,
logout,
type PpAlbum
} from '$lib/services/photoprism';
import {
@@ -21,7 +19,7 @@
setSearch,
setSection
} from '$lib/stores/filters.svelte';
import { isAuthenticated, session, thumbUrl } from '$lib/stores/session.svelte';
import { isAuthenticated, thumbUrl } from '$lib/stores/session.svelte';
import { untrack } from 'svelte';
import {
isSelected,
@@ -52,7 +50,7 @@
import BulkMetadataSidebar from '$lib/components/sidebar/BulkMetadataSidebar.svelte';
import RightSidebar from '$lib/components/sidebar/RightSidebar.svelte';
import Toolbar from '$lib/components/layout/Toolbar.svelte';
import { primaryFile, type PpPhoto } from '$lib/types/photoprism';
import { isVideo, primaryFile, type PpPhoto } from '$lib/types/photoprism';
// ── URL ↔ filter store sync ──────────────────────────────────────────────
// On nav (back/forward, deep link), reflect the URL into the store.
@@ -514,11 +512,6 @@
staleTime: 0
}));
async function onSignOut() {
await logout();
await goto('/login', { replaceState: true });
}
const qc = useQueryClient();
let emptyingArchive = $state(false);
@@ -660,22 +653,6 @@
{/each}
</div>
<span class="hidden text-[11px] text-muted-foreground sm:inline">
{session.user?.DisplayName ?? session.user?.Name}
</span>
<button
class="rounded border border-border px-2 py-0.5 text-xs hover:bg-accent"
onclick={toggleMode}
title="Toggle theme"
>
{mode.current === 'dark' ? '☀' : '☾'}
</button>
<button
class="rounded border border-border px-2 py-0.5 text-xs hover:bg-accent"
onclick={onSignOut}
>
Sign out
</button>
{/snippet}
</Toolbar>
@@ -796,6 +773,12 @@
></span
>
{/if}
{#if isVideo(photo)}
<span
class="absolute left-1.5 top-1.5 rounded bg-background/80 px-1 text-[10px] font-medium text-foreground"
>VIDEO</span
>
{/if}
</button>
{/if}
</div>