Keep an edit under a name, not just on the clipboard
FR-DEV-6 asks for three things — named presets, copy/paste between images, and batch-apply to a selection. The last two have been here for a while; this is the first. The format is the sidecar's, deliberately. A preset *is* the non-default half of a version, so the lines are the same lines keyed the same way, which makes the two files diffable against each other and lets someone debugging an edit paste a block from one into the other. One file rather than one per preset: a preset per file makes the name a path, and every name then has to survive a filesystem — a `/` becomes a directory, a name differing only in case collides on one platform and not another, and renaming becomes two operations that can half-fail. As a key in a document it is none of those. Unknown *parameters* needed no machinery. `Preset` already holds whatever keys it is given and resolves them against the descriptors only at apply time, so one written by a newer build survives by being stored. Only lines that are not `op.param = float` at all are preserved verbatim, which is the sidecar's version-skew promise made here too. Applying is the paste path with a different source, so a preset reaches a selection through the sidecar read-modify-write that was already there: no graph, no decode, no GPU, forty files or one. Two smaller decisions worth the record. A library that fails to parse is held empty in memory and *not* written back over — settings regenerate themselves and this is work, so a parse failure must not be the moment it is destroyed. And every save persists immediately and rolls the in-memory copy back if the write fails, so the sheet never lists a preset the file does not have. The grid's "Presets" button is gated on the selection alone, unlike the "Paste to 40" beside it. That button needs a clipboard armed this session; the preset list is whatever was saved last month, and hiding it behind an unrelated action is what makes a feature only its author knows about. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ import { LibraryGrid, LibraryCell, TimelineBar, PhotoRoll, KeywordRow, PersonChi
|
||||
import { Button, PanelHeading, Label, Value, Caption, Panel, EmptyState, ProgressBar, ActivityRow } from "widgets.slint";
|
||||
import { CollectionsPanel, CollectionRow, OfflinePrompt } from "collections.slint";
|
||||
import { HistogramPanel, HistogramView } from "histogram.slint";
|
||||
import { PresetSheet } from "presets.slint";
|
||||
import { SettingsPage } from "settings.slint";
|
||||
import { ImportPage } from "import.slint";
|
||||
import { StatusBar, InfoPanel } from "develop.slint";
|
||||
@@ -639,6 +640,35 @@ export component AppWindow inherits Window {
|
||||
/// Apply the clipboard to every selected image in the grid.
|
||||
callback paste-settings-to-selection();
|
||||
|
||||
// --- named presets (FR-DEV-6) ---
|
||||
//
|
||||
// The saved half of the same requirement. On the window rather than in a
|
||||
// view for the reason the clipboard is: a preset is saved in develop,
|
||||
// where there is an edit to capture, and applied most often in the grid,
|
||||
// where there is a selection to apply it to.
|
||||
in-out property <bool> presets-open: false;
|
||||
in property <[string]> preset-names;
|
||||
/// Whether there is an edit in hand to save, set by whichever view opened
|
||||
/// the sheet. `in-out` because that is where the answer is known.
|
||||
in-out property <bool> preset-can-save: false;
|
||||
/// What applying would act on: 0 is the open photograph, higher is that
|
||||
/// many selected. Set at open, and read back by Rust when one is picked —
|
||||
/// so the action matches the count the user read on the way in.
|
||||
in-out property <int> preset-apply-count: 0;
|
||||
/// What saving would capture, from the routine the clipboard summary uses.
|
||||
in property <string> preset-capture-summary;
|
||||
/// Why the last name was refused, cleared by the next keystroke.
|
||||
in property <string> preset-name-error;
|
||||
callback save-preset(string);
|
||||
callback apply-preset(string);
|
||||
callback rename-preset(string, string);
|
||||
callback delete-preset(string);
|
||||
callback preset-name-edited(string);
|
||||
/// Raised as the sheet opens over an open photograph, so Rust can say what
|
||||
/// saving would capture. A property refreshed on every slider drag would
|
||||
/// be recomputing a string nobody is looking at.
|
||||
callback presets-opened();
|
||||
|
||||
// --- settings (FR-EXP-1, FR-EXP-3, FR-NC-6a) ---
|
||||
//
|
||||
// A page rather than an overlay, and the outermost of the view conditions
|
||||
@@ -1410,6 +1440,15 @@ in property <bool> panel-visible: true;
|
||||
paste-settings-to-selection => {
|
||||
root.paste-settings-to-selection();
|
||||
}
|
||||
// TRACES: FR-DEV-6
|
||||
// Opened with the selection's size, which is both what the
|
||||
// sheet says it would act on and what the apply handler
|
||||
// reads back to decide it means the batch.
|
||||
open-presets => {
|
||||
root.preset-apply-count = root.library-selected-count;
|
||||
root.preset-can-save = false;
|
||||
root.presets-open = true;
|
||||
}
|
||||
|
||||
// TRACES: FR-EXP-7
|
||||
exporting: root.library-exporting;
|
||||
@@ -2203,6 +2242,15 @@ in property <bool> panel-visible: true;
|
||||
framing-withheld: root.settings-framing-withheld;
|
||||
copy => { root.copy-settings(); }
|
||||
paste => { root.paste-settings(); }
|
||||
// Opened with no count, which is what tells
|
||||
// the apply handler this means the open
|
||||
// photograph rather than a selection.
|
||||
open-presets => {
|
||||
root.preset-apply-count = 0;
|
||||
root.preset-can-save = root.adjust-enabled;
|
||||
root.presets-opened();
|
||||
root.presets-open = true;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -2402,6 +2450,26 @@ in property <bool> panel-visible: true;
|
||||
// Below the load bar, above everything else: a download started from
|
||||
// here shows its progress in that bar, and the bar must not be the
|
||||
// thing the dialogue covers.
|
||||
// TRACES: FR-DEV-6
|
||||
// Over the shell rather than inside a view, because both views open
|
||||
// it — and because the develop column is 320px wide, which is not
|
||||
// enough to list presets and rename one in.
|
||||
if root.presets-open: PresetSheet {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
names: root.preset-names;
|
||||
can-save: root.preset-can-save;
|
||||
apply-count: root.preset-apply-count;
|
||||
capture-summary: root.preset-capture-summary;
|
||||
name-error: root.preset-name-error;
|
||||
save(name) => { root.save-preset(name); }
|
||||
apply(name) => { root.apply-preset(name); }
|
||||
rename(from, to) => { root.rename-preset(from, to); }
|
||||
remove(name) => { root.delete-preset(name); }
|
||||
name-edited(text) => { root.preset-name-edited(text); }
|
||||
dismiss => { root.presets-open = false; }
|
||||
}
|
||||
|
||||
OfflinePrompt {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
Reference in New Issue
Block a user