diff --git a/README.md b/README.md index 4154fa8..aac56a6 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,8 @@ TMDB ("Fetch Data") are working for both TV episodes and movies — including fu title disambiguation, so a search that returns several similarly-named results doesn't just take TMDB's top-ranked hit — and the actual on-disk rename operation is wired up (skip-on-conflict, with a confirmation step showing the full old-path/new-path plan -before anything touches disk). Rename can also organize files into subfolders — +before anything touches disk, and a per-row ✕ to drop an individual file from that plan +at the last step). Rename can also organize files into subfolders — including ones that don't exist yet — when the naming scheme includes a `/`. Every completed rename is logged to a history panel that can undo it, moving files back to their original paths. A single loaded batch can freely mix TV episodes and movies — @@ -230,12 +231,10 @@ ported from, for anyone picking this up: `--conflict` variants beyond skip (auto-number, override), and the CLI flag surface itself. 7. **The rest of the UI** — the Rename panel (file loading, drag-drop, live preview, - local matching, confirmed rename, rename history/undo) is built; the other five - sidebar panels (Episodes/Subtitles/SFV/Filter/List) are still stub screens, plus - Align up/down reordering and Presets on the Rename panel itself are still no-ops. - There's also no per-item exclude inside the rename confirmation modal yet (it's - all-or-nothing — use "Exclude Selected" on the file list beforehand to leave a file - out entirely). + local TV/movie matching, confirmed rename with per-item exclude, rename history/undo) + is built; the other five sidebar panels (Episodes/Subtitles/SFV/Filter/List) are + still stub screens, plus Align up/down reordering and Presets on the Rename panel + itself are still no-ops. 8. **Undo/history for renames** — done, as a flat JSON-backed undo stack rather than a full port of `net.filebot.HistoryDialog`'s session/element log (`crates/renamr-core/src/history`, `src-tauri/src/history.rs`). Every successful diff --git a/src/components/RenamePanel.css b/src/components/RenamePanel.css index df6eb1f..fcaabd2 100644 --- a/src/components/RenamePanel.css +++ b/src/components/RenamePanel.css @@ -403,6 +403,31 @@ font-weight: 600; } +.modal-plan-remove { + all: unset; + box-sizing: border-box; + flex: 0 0 auto; + width: 22px; + height: 22px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 6px; + color: var(--muted-text); + cursor: pointer; +} + +.modal-plan-remove:hover { + background: rgba(255, 255, 255, 0.06); + color: #ff8a8a; +} + +.modal-plan-remove:disabled { + opacity: 0.35; + cursor: default; + pointer-events: none; +} + .modal-note { margin: 10px 0 0; font-size: 11px; diff --git a/src/components/RenamePanel.tsx b/src/components/RenamePanel.tsx index 52628c7..435dcc2 100644 --- a/src/components/RenamePanel.tsx +++ b/src/components/RenamePanel.tsx @@ -586,7 +586,7 @@ export default function RenamePanel() { // on disk via the Rust `execute_rename` command, then reconcile local state (a // renamed file's id, which is its path, changes) and report the outcome. const confirmRename = async () => { - if (!renamePlan) return; + if (!renamePlan || renamePlan.length === 0) return; setRenaming(true); try { @@ -649,6 +649,15 @@ export default function RenamePanel() { setRenamePlan(null); }; + // Per-item exclude, inside the confirmation modal itself: drops one row from the + // plan without closing the modal or having to cancel and re-select beforehand. Every + // bit of the modal's copy (file counts, the confirm button's label) reads directly + // from `renamePlan.length`, so removing a row here updates all of it for free. + const excludeFromPlan = (id: string) => { + if (renaming) return; + setRenamePlan((prev) => (prev ? prev.filter((p) => p.id !== id) : prev)); + }; + // Rename history / undo - Renamr's only safety net beyond the confirm-before-execute // step, since a filesystem rename has no OS-level undo. `execute_rename` records a // batch of every successfully-renamed file after each Rename; this panel lists those @@ -1073,24 +1082,37 @@ export default function RenamePanel() {
This will rename {renamePlan.length} file{renamePlan.length === 1 ? "" : "s"} on disk right now. - There's no undo yet — review the list below before continuing. + There's no undo yet — review the list below before continuing, and use the ✕ on any row to drop just + that file from this batch. {renamePlanSkipped > 0 && ` (${renamePlanSkipped} other file${renamePlanSkipped === 1 ? "" : "s"} excluded: no valid preview.)`} {renamePlan.some((p) => p.newName.includes("/")) && " Some of these also move the file into a new subfolder — any missing folders are created automatically."}
If a computed name already exists on disk, that file is skipped rather than overwritten. @@ -1099,7 +1121,11 @@ export default function RenamePanel() { -