Add per-item exclude to the rename confirmation modal
Each row in the "Confirm Rename" plan now has a small x button that drops just that file from the batch without closing the modal or needing to cancel and re-select beforehand (previously it was all-or-nothing - "Exclude Selected" on the file list was the only way to leave a file out, and only before opening Rename at all). Excluding every row disables the confirm button and shows an empty-plan hint instead of sending an empty batch. No Rust changes needed - this is pure plan-state filtering in the frontend, RenamePlanEntry's shape is unchanged. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
45a9f5d523
commit
aab8e28246
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
<h2 className="modal-title">Confirm Rename</h2>
|
||||
<p className="modal-subtitle">
|
||||
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."}
|
||||
</p>
|
||||
<div className="modal-plan-list">
|
||||
{renamePlan.map((p) => (
|
||||
<div className="modal-plan-row" key={p.id}>
|
||||
<span className="modal-plan-old" title={p.currentName}>
|
||||
{p.currentName}
|
||||
</span>
|
||||
<span className="modal-plan-arrow">→</span>
|
||||
<span className="modal-plan-new" title={p.newName}>
|
||||
{p.newName}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
{renamePlan.length === 0 ? (
|
||||
<div className="list-empty-hint">Every file was excluded — nothing left to rename.</div>
|
||||
) : (
|
||||
renamePlan.map((p) => (
|
||||
<div className="modal-plan-row" key={p.id}>
|
||||
<span className="modal-plan-old" title={p.currentName}>
|
||||
{p.currentName}
|
||||
</span>
|
||||
<span className="modal-plan-arrow">→</span>
|
||||
<span className="modal-plan-new" title={p.newName}>
|
||||
{p.newName}
|
||||
</span>
|
||||
<button
|
||||
className="modal-plan-remove"
|
||||
title="Exclude this file from the rename"
|
||||
onClick={() => excludeFromPlan(p.id)}
|
||||
disabled={renaming}
|
||||
>
|
||||
<RemoveIcon />
|
||||
</button>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
<p className="modal-note">
|
||||
If a computed name already exists on disk, that file is skipped rather than overwritten.
|
||||
@@ -1099,7 +1121,11 @@ export default function RenamePanel() {
|
||||
<button className="action-button" onClick={cancelRename} disabled={renaming}>
|
||||
Cancel
|
||||
</button>
|
||||
<button className="action-button primary" onClick={confirmRename} disabled={renaming}>
|
||||
<button
|
||||
className="action-button primary"
|
||||
onClick={confirmRename}
|
||||
disabled={renaming || renamePlan.length === 0}
|
||||
>
|
||||
{renaming ? "Renaming…" : `Rename ${renamePlan.length} File${renamePlan.length === 1 ? "" : "s"}`}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user