- api.ts: switch baseURL from http://localhost:8001/api/v1 to relative /api/v1. Both nginx (prod) and vite (dev) already proxy /api/ to the backend, so requests become same-origin and the app works from any host (LAN IP, reverse proxy, another machine) with no CORS dance. - backend CORS: open to "*" as a fallback for the rare direct-hit case; the normal flow is same-origin via the proxy and never touches CORS. - App layout: move FilterBar and DiscardActionBar inside the main content column (right of the left sidebar) so the filter row no longer bleeds across the sidebar. - FilterBar: justify-center the pills so they sit centered above the timeline. Clear-all uses ml-2 instead of ml-auto. - KeyboardHints: convert to a floating, glassy pill pinned bottom- center (fixed positioning + backdrop-blur + ring) instead of a flat toolbar row. Removed from the column layout — now mounted as an overlay sibling. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
53 lines
1.9 KiB
TypeScript
53 lines
1.9 KiB
TypeScript
import { usePhotoStore } from '../store/photoStore'
|
|
|
|
export function KeyboardHints() {
|
|
const selectedCount = usePhotoStore((state) => state.selectedPhotos.length)
|
|
const viewMode = usePhotoStore((state) => state.viewMode)
|
|
|
|
// In preview mode the viewer has its own context, so the grid hints
|
|
// would just be confusing. Hide them.
|
|
if (viewMode === 'preview') return null
|
|
|
|
const hints = selectedCount > 0
|
|
? [
|
|
{ key: '1-5', action: 'Rate' },
|
|
{ key: 'P', action: 'Pick → heap' },
|
|
{ key: 'X', action: 'Discard' },
|
|
{ key: 'Space', action: 'Preview' },
|
|
{ key: 'Esc', action: 'Deselect' },
|
|
]
|
|
: [
|
|
{ key: '↑↓←→', action: 'Navigate' },
|
|
{ key: 'Click', action: 'Select' },
|
|
{ key: 'Shift+Click', action: 'Range' },
|
|
{ key: 'Space', action: 'Preview' },
|
|
{ key: '/', action: 'Search' },
|
|
]
|
|
|
|
return (
|
|
<div className="pointer-events-none fixed bottom-4 left-1/2 z-30 -translate-x-1/2">
|
|
<div className="pointer-events-auto flex items-center gap-3 rounded-full border border-border/60 bg-surface/40 px-4 py-1.5 shadow-lg ring-1 ring-white/5 backdrop-blur-md">
|
|
{hints.map((hint, i) => (
|
|
<div key={i} className="flex items-center gap-1.5">
|
|
<kbd className="rounded bg-surface-offset/80 px-1.5 py-0.5 text-[11px] font-medium text-text">
|
|
{hint.key}
|
|
</kbd>
|
|
<span className="text-xs text-text-muted">{hint.action}</span>
|
|
{i < hints.length - 1 && (
|
|
<span className="ml-1 text-text-faint">•</span>
|
|
)}
|
|
</div>
|
|
))}
|
|
{selectedCount > 0 && (
|
|
<>
|
|
<span className="text-text-faint">•</span>
|
|
<span className="text-xs font-medium text-primary">
|
|
{selectedCount} selected
|
|
</span>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|