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
|
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
|
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
|
(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
|
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
|
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 —
|
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
|
`--conflict` variants beyond skip (auto-number, override), and the CLI flag surface
|
||||||
itself.
|
itself.
|
||||||
7. **The rest of the UI** — the Rename panel (file loading, drag-drop, live preview,
|
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
|
local TV/movie matching, confirmed rename with per-item exclude, rename history/undo)
|
||||||
sidebar panels (Episodes/Subtitles/SFV/Filter/List) are still stub screens, plus
|
is built; the other five sidebar panels (Episodes/Subtitles/SFV/Filter/List) are
|
||||||
Align up/down reordering and Presets on the Rename panel itself are still no-ops.
|
still stub screens, plus Align up/down reordering and Presets on the Rename panel
|
||||||
There's also no per-item exclude inside the rename confirmation modal yet (it's
|
itself are still no-ops.
|
||||||
all-or-nothing — use "Exclude Selected" on the file list beforehand to leave a file
|
|
||||||
out entirely).
|
|
||||||
8. **Undo/history for renames** — done, as a flat JSON-backed undo stack rather than a
|
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
|
full port of `net.filebot.HistoryDialog`'s session/element log
|
||||||
(`crates/renamr-core/src/history`, `src-tauri/src/history.rs`). Every successful
|
(`crates/renamr-core/src/history`, `src-tauri/src/history.rs`). Every successful
|
||||||
|
|||||||
@@ -403,6 +403,31 @@
|
|||||||
font-weight: 600;
|
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 {
|
.modal-note {
|
||||||
margin: 10px 0 0;
|
margin: 10px 0 0;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
|
|||||||
@@ -586,7 +586,7 @@ export default function RenamePanel() {
|
|||||||
// on disk via the Rust `execute_rename` command, then reconcile local state (a
|
// 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.
|
// renamed file's id, which is its path, changes) and report the outcome.
|
||||||
const confirmRename = async () => {
|
const confirmRename = async () => {
|
||||||
if (!renamePlan) return;
|
if (!renamePlan || renamePlan.length === 0) return;
|
||||||
setRenaming(true);
|
setRenaming(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -649,6 +649,15 @@ export default function RenamePanel() {
|
|||||||
setRenamePlan(null);
|
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
|
// 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
|
// 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
|
// 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>
|
<h2 className="modal-title">Confirm Rename</h2>
|
||||||
<p className="modal-subtitle">
|
<p className="modal-subtitle">
|
||||||
This will rename {renamePlan.length} file{renamePlan.length === 1 ? "" : "s"} on disk right now.
|
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 > 0 &&
|
||||||
` (${renamePlanSkipped} other file${renamePlanSkipped === 1 ? "" : "s"} excluded: no valid preview.)`}
|
` (${renamePlanSkipped} other file${renamePlanSkipped === 1 ? "" : "s"} excluded: no valid preview.)`}
|
||||||
{renamePlan.some((p) => p.newName.includes("/")) &&
|
{renamePlan.some((p) => p.newName.includes("/")) &&
|
||||||
" Some of these also move the file into a new subfolder — any missing folders are created automatically."}
|
" Some of these also move the file into a new subfolder — any missing folders are created automatically."}
|
||||||
</p>
|
</p>
|
||||||
<div className="modal-plan-list">
|
<div className="modal-plan-list">
|
||||||
{renamePlan.map((p) => (
|
{renamePlan.length === 0 ? (
|
||||||
<div className="modal-plan-row" key={p.id}>
|
<div className="list-empty-hint">Every file was excluded — nothing left to rename.</div>
|
||||||
<span className="modal-plan-old" title={p.currentName}>
|
) : (
|
||||||
{p.currentName}
|
renamePlan.map((p) => (
|
||||||
</span>
|
<div className="modal-plan-row" key={p.id}>
|
||||||
<span className="modal-plan-arrow">→</span>
|
<span className="modal-plan-old" title={p.currentName}>
|
||||||
<span className="modal-plan-new" title={p.newName}>
|
{p.currentName}
|
||||||
{p.newName}
|
</span>
|
||||||
</span>
|
<span className="modal-plan-arrow">→</span>
|
||||||
</div>
|
<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>
|
</div>
|
||||||
<p className="modal-note">
|
<p className="modal-note">
|
||||||
If a computed name already exists on disk, that file is skipped rather than overwritten.
|
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}>
|
<button className="action-button" onClick={cancelRename} disabled={renaming}>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</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"}`}
|
{renaming ? "Renaming…" : `Rename ${renamePlan.length} File${renamePlan.length === 1 ? "" : "s"}`}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user