feat: discard view with restore and empty actions

Adds the destructive-action loop the discard concept needed:

- Click "Discarded" in the left sidebar → activates the discarded
  filter; the timeline reloads showing discarded photos.
- DiscardActionBar appears at the top of the timeline only when the
  discarded filter is active. Shows the count, a Restore button (when
  photos are selected), and an Empty discard pile button.
- Empty action goes through a ConfirmDialog (new tiny reusable modal,
  same overlay pattern as AddSourceFolderDialog).
- Restore goes through POST /api/v1/discard/restore.
- DELETE /api/v1/discard/empty now actually os.unlink()s the files
  from disk in addition to removing the DB rows. Per-file failures
  are logged and reported in the response so a single permission
  error doesn't abort the batch.

Other library nodes wired in passing:
- "All Photos"   → clearAll()
- "Rated"        → setRatingMin(1)
- "Flagged"      → setFlag('picked')
- "Discarded"    → setFlag('discarded')
- "By Date"      left unwired (needs a date-grouping UI)

Single-photo restore via the U keyboard shortcut already worked from
an earlier round, no change needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-07 22:59:34 +02:00
parent c7d2cc47e1
commit 322969c938
5 changed files with 240 additions and 4 deletions

View File

@@ -9,6 +9,7 @@ import { KeyboardHints } from './components/KeyboardHints'
import { PreviewView } from './components/preview/PreviewView'
import { FilterBar } from './components/filter/FilterBar'
import { ActiveFilterChips } from './components/filter/ActiveFilterChips'
import { DiscardActionBar } from './components/discard/DiscardActionBar'
import { usePhotoStore } from './store/photoStore'
import { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts'
import { useFilterUrlSync } from './hooks/useFilterUrlSync'
@@ -52,6 +53,7 @@ function App() {
<TopBar />
<FilterBar />
<ActiveFilterChips />
<DiscardActionBar />
<KeyboardHints />
<div className="flex flex-1 overflow-hidden">