feat(web): keyboard rating/color marks, search focus, shortcuts overlay
Lightroom-style keys in grid+preview: 0-5 rating (re-key toggles), 6-9 color labels, optimistic marks cache patch with rollback. / focuses the search box, ? opens a new shortcut-reference overlay that inertly swallows other keys while open. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
120
web/src/lib/components/layout/ShortcutsDialog.svelte
Normal file
120
web/src/lib/components/layout/ShortcutsDialog.svelte
Normal file
@@ -0,0 +1,120 @@
|
||||
<!--
|
||||
Keyboard-shortcut reference overlay, opened with `?` (and the toolbar
|
||||
help affordance). Read-only: gridKeyNav swallows every key except
|
||||
Esc / ? while it's up, so nothing here can fire the actions it lists.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { closeShortcuts, view } from '$lib/stores/view.svelte';
|
||||
import { X } from 'lucide-svelte';
|
||||
|
||||
interface Row {
|
||||
keys: string[];
|
||||
desc: string;
|
||||
}
|
||||
interface Group {
|
||||
title: string;
|
||||
rows: Row[];
|
||||
}
|
||||
|
||||
const GROUPS: Group[] = [
|
||||
{
|
||||
title: 'Navigate',
|
||||
rows: [
|
||||
{ keys: ['↑', '↓', '←', '→'], desc: 'Move focus in the grid' },
|
||||
{ keys: ['Shift', '+', 'Arrows'], desc: 'Extend selection' },
|
||||
{ keys: ['Space'], desc: 'Open / close preview' },
|
||||
{ keys: ['Esc'], desc: 'Collapse selection, then clear' },
|
||||
{ keys: ['/'], desc: 'Focus search' },
|
||||
{ keys: ['⌘', 'A'], desc: 'Select all visible' },
|
||||
{ keys: ['⌘', 'K'], desc: 'Command palette' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Rate & label',
|
||||
rows: [
|
||||
{ keys: ['1', '…', '5'], desc: 'Set rating (re-key to clear)' },
|
||||
{ keys: ['0'], desc: 'Clear rating' },
|
||||
{ keys: ['6', '7', '8', '9'], desc: 'Color label: red / yellow / green / blue' },
|
||||
{ keys: ['F'], desc: 'Toggle favorite' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Act',
|
||||
rows: [
|
||||
{ keys: ['X'], desc: 'Archive (Delete in Archive view)' },
|
||||
{ keys: ['U'], desc: 'Restore from archive' },
|
||||
{ keys: ['S'], desc: 'Keep (review) · add to heap' },
|
||||
{ keys: ['S', 'then', '1–9'], desc: 'Add to heap N' },
|
||||
{ keys: ['A'], desc: 'Accept date & keep (EXIF review)' },
|
||||
{ keys: ['M'], desc: 'Move to folder' },
|
||||
{ keys: ['⌘', 'Z'], desc: 'Undo last action' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Panels',
|
||||
rows: [
|
||||
{ keys: ['B'], desc: 'Toggle left sidebar' },
|
||||
{ keys: ['Tab'], desc: 'Toggle left sidebar' },
|
||||
{ keys: ['I'], desc: 'Toggle info sidebar' },
|
||||
{ keys: ['?'], desc: 'This overlay' }
|
||||
]
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
{#if view.shortcutsOpen}
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="fixed inset-0 z-[70] flex items-center justify-center bg-black/50 backdrop-blur-sm"
|
||||
onclick={(e) => {
|
||||
if (e.target === e.currentTarget) closeShortcuts();
|
||||
}}
|
||||
>
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Keyboard shortcuts"
|
||||
class="max-h-[85vh] w-[min(680px,92vw)] overflow-y-auto rounded-lg border border-border bg-popover p-5 text-popover-foreground shadow-xl"
|
||||
>
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<h2 class="text-sm font-semibold">Keyboard shortcuts</h2>
|
||||
<button
|
||||
class="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
onclick={closeShortcuts}
|
||||
aria-label="Close"
|
||||
>
|
||||
<X class="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="grid gap-x-8 gap-y-4 sm:grid-cols-2">
|
||||
{#each GROUPS as group (group.title)}
|
||||
<section>
|
||||
<h3 class="mb-1.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground">
|
||||
{group.title}
|
||||
</h3>
|
||||
<dl class="space-y-1">
|
||||
{#each group.rows as row (row.desc)}
|
||||
<div class="flex items-center justify-between gap-3 text-xs">
|
||||
<dt class="text-muted-foreground">{row.desc}</dt>
|
||||
<dd class="flex shrink-0 items-center gap-0.5">
|
||||
{#each row.keys as k (k)}
|
||||
{#if k === 'then' || k === '+' || k === '…'}
|
||||
<span class="px-0.5 text-[10px] text-muted-foreground">{k}</span>
|
||||
{:else}
|
||||
<kbd
|
||||
class="rounded border border-border bg-muted px-1.5 py-0.5 font-mono text-[10px] leading-none"
|
||||
>
|
||||
{k}
|
||||
</kbd>
|
||||
{/if}
|
||||
{/each}
|
||||
</dd>
|
||||
</div>
|
||||
{/each}
|
||||
</dl>
|
||||
</section>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
Reference in New Issue
Block a user