Files
aurora-curator/src/components/RenamePanel.css
T
Renamr Scaffold b409d67510 Naming scheme editor: clickable field tokens + persist across restarts
Two usability fixes to the naming-scheme editor, both reported/requested
directly: the field-reference list ({show name}, {episode number}, etc.)
was plain unclickable text, so building a scheme meant typing every field
name out by hand; and the editor's text always reset to the built-in
sample expression on every launch, since nothing ever wrote it to disk -
only the explicit 1-9 preset slots (presets.rs) were persisted.

Field list -> clickable buttons: each field reference is now a real
<button> (.format-field-chip) wired to insertField(), which reads the
input's real DOM selectionStart/selectionEnd and inserts the token at
the caret, then restores focus and moves the caret to just after the
inserted text so clicking several fields in a row builds up
left-to-right. Purely a frontend change - no new Tauri commands needed.

Persistence: added last_naming_scheme to settings.rs's StoredSettings
(same settings.json already used for tv/movie source, credentials,
etc.), plus get_last_naming_scheme/save_last_naming_scheme commands.
RenamePanel.tsx loads it once on mount (restoring the saved scheme
before the user can interact with anything) and saves it back debounced
(600ms after typing settles) as the user edits - gated behind a
schemeLoaded flag so the save effect can never fire before the load
completes and clobber a saved scheme with the not-yet-replaced sample
default. An empty/blank saved value (fresh install) leaves the built-in
sample expression in place, same fallback behavior as every other
optional setting in this file.

Verified via cargo test --workspace (191 -> 193, two new settings.rs
tests: schema-evolution for the new field, and the blank-clears-to-None
behavior) + npm run build (clean) + a 7-check Playwright script against
npm run dev (mocking window.__TAURI_INTERNALS__.invoke): confirmed the
editor restores a mocked saved scheme on launch, clicking a field chip
inserts its token at a caret placed via setSelectionRange (not just
appended), a second click lands immediately after the first rather than
at the old position, and save_last_naming_scheme fires once (debounced)
with the exact edited text rather than on every keystroke.

Updated README's naming-scheme paragraph to describe both.
2026-08-30 04:20:59 +00:00

695 lines
12 KiB
CSS

.rename-panel {
height: 100%;
display: flex;
flex-direction: column;
position: relative;
}
.rename-lists {
flex: 1;
min-height: 0;
min-width: 0;
overflow: hidden;
display: grid;
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
gap: 14px;
padding: 18px 18px 18px 16px;
}
.list-card {
min-width: 0;
min-height: 0;
display: flex;
flex-direction: column;
gap: 8px;
}
.list-card-title {
font-size: 12px;
font-weight: 700;
color: var(--muted-text);
padding-left: 4px;
}
.list-card-body {
flex: 1;
min-width: 0;
min-height: 0;
overflow-y: auto;
background: var(--card);
border: 1px solid var(--surface-deep);
border-radius: 12px;
padding: 8px;
display: flex;
flex-direction: column;
gap: 2px;
}
.list-row {
height: 32px;
min-height: 32px;
min-width: 0;
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 0 10px;
border-radius: 8px;
cursor: pointer;
color: var(--text);
}
.list-row:hover {
background: rgba(255, 255, 255, 0.03);
}
.list-row.selected {
background: var(--list-selection);
}
.list-row.muted {
color: var(--muted-text);
opacity: 0.6;
}
.list-row-text {
flex: 1 1 auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 12.5px;
}
.list-row-badge {
flex: 0 0 auto;
font-size: 9.5px;
font-weight: 700;
letter-spacing: 0.03em;
text-transform: uppercase;
color: var(--muted-text);
background: rgba(255, 255, 255, 0.05);
border-radius: 999px;
padding: 2px 8px;
}
.list-button-row {
display: flex;
align-items: center;
gap: 4px;
padding: 2px 4px;
}
.list-button-gap {
flex: 1;
}
.list-icon-button {
all: unset;
box-sizing: border-box;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
border: 1px solid var(--surface-deep);
background: #172a31;
color: var(--text);
cursor: pointer;
}
.list-icon-button:hover {
background: #203941;
}
.list-icon-button:active {
background: #28515a;
}
.list-icon-button:disabled {
opacity: 0.45;
cursor: default;
pointer-events: none;
}
.load-menu {
position: relative;
display: inline-flex;
}
.load-menu-popup {
position: absolute;
bottom: calc(100% + 6px);
left: 0;
z-index: 10;
display: flex;
flex-direction: column;
min-width: 150px;
padding: 4px;
background: #16262d;
border: 1px solid var(--surface-deep);
border-radius: 10px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
}
.load-menu-popup button {
all: unset;
box-sizing: border-box;
padding: 8px 10px;
border-radius: 6px;
font-size: 12px;
color: var(--text);
cursor: pointer;
white-space: nowrap;
}
.load-menu-popup button:hover {
background: var(--surface-deep);
}
.list-empty-hint {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
padding: 24px;
color: var(--muted-text);
font-size: 12px;
}
.rename-actions {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16px;
}
.action-button {
all: unset;
box-sizing: border-box;
width: 116px;
height: 42px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
border: 1px solid var(--surface-deep);
background: #172a31;
color: var(--text);
font-size: 13px;
font-weight: 600;
cursor: pointer;
text-align: center;
}
.action-button:hover {
background: #203941;
}
.action-button.primary {
background: var(--aurora-green);
color: var(--dock-deep);
border-color: transparent;
font-weight: 700;
}
.action-button.primary:hover {
background: #70e3bb;
}
.format-editor {
margin: 0 18px 18px;
padding: 14px 16px;
background: var(--card);
border: 1px solid var(--surface-deep);
border-radius: 12px;
}
.format-editor label {
display: block;
font-size: 11px;
font-weight: 700;
color: var(--muted-text);
margin-bottom: 6px;
}
.format-editor input {
width: 100%;
box-sizing: border-box;
padding: 8px 10px;
border-radius: 8px;
border: 1px solid var(--surface-deep);
background: var(--surface);
color: var(--text);
font-family: ui-monospace, "SF Mono", monospace;
font-size: 13px;
}
.format-error {
margin: 8px 0 0;
color: #ff8a8a;
font-family: ui-monospace, monospace;
font-size: 11.5px;
}
.format-hint {
margin: 8px 0 0;
color: var(--muted-text);
font-size: 11px;
line-height: 1.5;
}
.format-hint code,
.format-editor label code {
background: rgba(255, 255, 255, 0.06);
border-radius: 4px;
padding: 1px 5px;
font-family: ui-monospace, "SF Mono", monospace;
font-size: 10.5px;
color: var(--text);
}
.format-hint a {
color: var(--aurora-cyan);
}
.format-fields {
margin: 10px 0 0;
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.format-fields-group {
flex: 1 1 160px;
min-width: 140px;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 5px;
padding: 8px 10px;
background: var(--surface);
border: 1px solid var(--surface-deep);
border-radius: 8px;
}
.format-fields-group-title {
flex-basis: 100%;
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.03em;
color: var(--muted-text);
margin-bottom: 2px;
}
.format-field-chip {
background: rgba(255, 255, 255, 0.06);
border: none;
border-radius: 4px;
padding: 2px 6px;
font-family: ui-monospace, "SF Mono", monospace;
font-size: 10.5px;
color: var(--aurora-green);
cursor: pointer;
transition: background 0.12s ease, color 0.12s ease, transform 0.06s ease;
}
.format-field-chip:hover {
background: rgba(255, 255, 255, 0.16);
}
.format-field-chip:active {
background: var(--aurora-green);
color: var(--surface);
transform: scale(0.96);
}
.modal-overlay {
position: fixed;
inset: 0;
z-index: 100;
display: flex;
align-items: center;
justify-content: center;
background: rgba(3, 10, 13, 0.65);
backdrop-filter: blur(2px);
}
.modal-card {
width: min(560px, calc(100vw - 48px));
max-height: min(560px, calc(100vh - 64px));
display: flex;
flex-direction: column;
background: var(--card);
border: 1px solid var(--surface-deep);
border-radius: 14px;
padding: 20px 22px;
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.45);
}
.modal-title {
margin: 0 0 8px;
font-size: 16px;
font-weight: 700;
color: var(--text);
}
.modal-subtitle {
margin: 0 0 14px;
font-size: 12px;
line-height: 1.5;
color: var(--muted-text);
}
.modal-plan-list {
flex: 1;
min-height: 0;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 2px;
background: var(--surface);
border: 1px solid var(--surface-deep);
border-radius: 10px;
padding: 6px;
}
.modal-plan-row {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
padding: 6px 8px;
border-radius: 6px;
font-size: 12px;
}
.modal-plan-row:hover {
background: rgba(255, 255, 255, 0.03);
}
.modal-plan-old {
flex: 1 1 0;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--muted-text);
}
.modal-plan-arrow {
flex: 0 0 auto;
color: var(--aurora-cyan);
}
.modal-plan-new {
flex: 1 1 0;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--text);
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;
color: var(--muted-text);
}
.modal-actions {
margin-top: 16px;
display: flex;
justify-content: flex-end;
gap: 10px;
}
.modal-actions .action-button {
width: auto;
padding: 0 18px;
}
.modal-actions-split {
justify-content: space-between;
align-items: center;
}
.modal-card-compact {
max-height: none;
}
.modal-subtitle a {
color: var(--aurora-cyan);
}
.action-segmented {
display: flex;
gap: 4px;
margin: 2px 0 12px;
padding: 3px;
background: var(--surface);
border: 1px solid var(--surface-deep);
border-radius: 10px;
}
.action-segmented-option {
all: unset;
box-sizing: border-box;
flex: 1 1 0;
text-align: center;
padding: 7px 0;
border-radius: 7px;
font-size: 12.5px;
font-weight: 600;
color: var(--muted-text);
cursor: pointer;
}
.action-segmented-option:hover {
color: var(--text);
}
.action-segmented-option.selected {
background: var(--aurora-green);
color: var(--dock-deep);
}
.action-segmented-option:disabled {
opacity: 0.6;
cursor: default;
pointer-events: none;
}
.action-segmented-option-danger.selected {
background: #ff8a8a;
color: var(--dock-deep);
}
.action-segmented-option-danger:not(.selected):hover {
color: #ff8a8a;
}
.modal-note-danger {
color: #ff8a8a;
}
.modal-field-label {
display: block;
font-size: 11px;
font-weight: 700;
color: var(--muted-text);
margin: 4px 0 6px;
}
.modal-text-input {
width: 100%;
box-sizing: border-box;
padding: 8px 10px;
border-radius: 8px;
border: 1px solid var(--surface-deep);
background: var(--surface);
color: var(--text);
font-family: ui-monospace, "SF Mono", monospace;
font-size: 12.5px;
}
.modal-inline-remove {
width: auto;
align-self: flex-start;
margin: 6px 0 4px;
padding: 4px 12px;
font-size: 11.5px;
}
.history-batch-list {
gap: 6px;
}
.history-batch {
padding: 8px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.02);
}
.history-batch-header {
display: flex;
align-items: center;
gap: 10px;
padding: 2px 4px 6px;
}
.history-batch-when {
flex: 1 1 auto;
min-width: 0;
font-size: 12px;
color: var(--text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.history-batch-count {
flex: 0 0 auto;
font-size: 10.5px;
font-weight: 700;
color: var(--muted-text);
background: rgba(255, 255, 255, 0.05);
border-radius: 999px;
padding: 2px 8px;
}
.history-undo-button {
flex: 0 0 auto;
width: auto;
height: 26px;
padding: 0 12px;
font-size: 11.5px;
}
.history-batch-entries {
display: flex;
flex-direction: column;
gap: 2px;
}
.preset-row {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
padding: 6px 8px;
border-radius: 8px;
}
.preset-row:hover {
background: rgba(255, 255, 255, 0.03);
}
.preset-slot-badge {
flex: 0 0 auto;
width: 22px;
height: 22px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
background: rgba(255, 255, 255, 0.06);
color: var(--aurora-green);
font-size: 11.5px;
font-weight: 700;
}
.preset-expression {
all: unset;
box-sizing: border-box;
flex: 1 1 0;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 6px 8px;
border-radius: 6px;
font-family: ui-monospace, "SF Mono", monospace;
font-size: 12px;
color: var(--text);
cursor: pointer;
}
.preset-expression:hover {
background: rgba(255, 255, 255, 0.06);
}
.preset-expression:disabled {
cursor: default;
pointer-events: none;
}
.preset-expression-empty {
color: var(--muted-text);
font-style: italic;
cursor: default;
}
.preset-row-actions {
flex: 0 0 auto;
display: flex;
align-items: center;
gap: 6px;
}
.preset-save-button {
width: auto;
height: 26px;
padding: 0 10px;
font-size: 11px;
}
.preset-clear-button {
width: 26px;
height: 26px;
}
.status-bar {
position: absolute;
left: 18px;
right: 18px;
bottom: 14px;
padding: 8px 14px;
background: rgba(19, 34, 41, 0.95);
border: 1px solid var(--surface-deep);
border-radius: 8px;
font-size: 11.5px;
color: var(--sunset);
text-align: center;
}