preview: full-screen modal replaces inline split + tags route reorg
The old SplitGrid + InlinePreview pane is replaced by a full-screen PreviewModal mounted once at the layout root. Open via Space on the focused tile or double-click; close on Esc (or X / Space again). Inside, PreviewPane renders the focused photo, RightSidebar carries the metadata, BulkActionBar reuses the existing per-photo actions, and PreviewCarousel windows ±50 thumbs around the focused index. Selection contract matches the grid: plain click reduces, shift extends the range, ⌘/Ctrl toggles, plain arrow drops the multi- selection, shift-arrow extends. New clearBulkToFirst() helper makes Esc / Clear collapse a bulk back to single-focus on its first member before the next press fully dismisses (modal closes, grid clears focus). Tags route reorganised into /tags/[category]/[[value]] with its own +layout and TagsBrowserSidebar; the old monolithic /tags/+page is trimmed to a legacy redirect. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
||||
import { queryClient } from '$lib/queryClient';
|
||||
import { filters } from '$lib/stores/filters.svelte';
|
||||
import {
|
||||
clearBulkToFirst,
|
||||
clearSelection,
|
||||
focusAfter,
|
||||
indexOf,
|
||||
@@ -23,7 +24,7 @@ import {
|
||||
toggle
|
||||
} from '$lib/stores/selection.svelte';
|
||||
import { popAndRun, push as pushUndo } from '$lib/stores/undo.svelte';
|
||||
import { toggleLeftSidebar, toggleRightSidebar } from '$lib/stores/view.svelte';
|
||||
import { openPreview, toggleLeftSidebar, toggleRightSidebar, view } from '$lib/stores/view.svelte';
|
||||
import type { PpPhoto } from '$lib/types/photoprism';
|
||||
|
||||
/**
|
||||
@@ -381,6 +382,24 @@ export function gridKeyNav(node: HTMLElement, params: GridKeyNavParams = {}) {
|
||||
const tag = (e.target as HTMLElement | null)?.tagName?.toLowerCase();
|
||||
if (tag === 'input' || tag === 'textarea' || tag === 'select') return;
|
||||
|
||||
// Modal owns arrow / Escape / Space while it's open — it handles
|
||||
// its own linear nav, close-on-Esc, and close-on-Space. Action
|
||||
// keys (X/S/U/A/Z) still pass through because they target the
|
||||
// shared selection store and work the same in either context.
|
||||
if (view.previewOpen) {
|
||||
if (
|
||||
e.key === 'ArrowLeft' ||
|
||||
e.key === 'ArrowRight' ||
|
||||
e.key === 'ArrowUp' ||
|
||||
e.key === 'ArrowDown' ||
|
||||
e.key === 'Escape' ||
|
||||
e.key === ' ' ||
|
||||
e.code === 'Space'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// S+digit chord. A digit 1–9 within the chord window consumes the key
|
||||
// and fires add-to-heap-N. Any other key cancels the chord without
|
||||
// firing the default active-heap action — the user switched intent —
|
||||
@@ -398,6 +417,18 @@ export function gridKeyNav(node: HTMLElement, params: GridKeyNavParams = {}) {
|
||||
const meta = e.metaKey || e.ctrlKey;
|
||||
const shift = e.shiftKey;
|
||||
|
||||
// Space on a focused tile opens the full-screen preview modal.
|
||||
// Matches the dblclick gesture so the user has both keyboard and
|
||||
// mouse paths to the same surface. `e.code === 'Space'` covers
|
||||
// layouts where `e.key` is the dead-key combining mark.
|
||||
if ((e.key === ' ' || e.code === 'Space') && !meta && !shift) {
|
||||
if (selection.focused) {
|
||||
e.preventDefault();
|
||||
openPreview();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Grid nav keys ────────────────────────────────────────────────────
|
||||
switch (e.key) {
|
||||
case 'ArrowLeft':
|
||||
@@ -424,6 +455,11 @@ export function gridKeyNav(node: HTMLElement, params: GridKeyNavParams = {}) {
|
||||
}
|
||||
return;
|
||||
case 'Escape':
|
||||
// First Esc collapses a multi-selection back to single-focus
|
||||
// on its first member — the user's "starting photo" stays
|
||||
// visible instead of vanishing. Only when there's no bulk
|
||||
// does Esc fully dismiss focus.
|
||||
if (clearBulkToFirst()) return;
|
||||
clearSelection();
|
||||
setFocused(null);
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user