feat: Models & Pipeline has Save and Cancel; Cancel puts back the pipeline as it was when the window opened
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
196f8cd807
commit
7c4f353df2
+17
-1
@@ -509,15 +509,31 @@
|
||||
_body.append(content);
|
||||
_mounted = { content };
|
||||
if (ctx && ctx.sub && ctx.sub.startsWith("node:")) openNodeId = ctx.sub.slice(5);
|
||||
let snap = null; // the pipeline as it was when this window opened
|
||||
if (ctx && ctx.setActions) {
|
||||
const card = el("button", "btn ghost tiny", "🖼 Export settings…");
|
||||
card.title = "Render this pipeline as a PNG — loaders, typed-in node values, "
|
||||
+ "and the host's torch / CUDA / attention";
|
||||
card.onclick = openSettingsCard;
|
||||
ctx.setActions([card]);
|
||||
// Changes apply as they are made (the pipeline is the live one); Cancel puts back what was here
|
||||
// when the window opened, Save keeps it and closes.
|
||||
const cancel = el("button", "btn ghost tiny", "Cancel");
|
||||
cancel.title = "Undo everything changed in this window since it opened";
|
||||
cancel.onclick = async () => {
|
||||
if (snap) {
|
||||
const res = await PS.restore(snap);
|
||||
if (res.refused.length) { alert(res.refused[0]); return; }
|
||||
}
|
||||
window.SettingsWindow.close();
|
||||
};
|
||||
const save = el("button", "btn primary tiny", "Save");
|
||||
save.title = "Keep the changes and close";
|
||||
save.onclick = () => window.SettingsWindow.close();
|
||||
ctx.setActions([card, cancel, save]);
|
||||
}
|
||||
render();
|
||||
PS.ensureLoaded().then(() => {
|
||||
snap = PS.snapshot();
|
||||
if (openNodeId) {
|
||||
const slot = (PS.slots() || []).find((s) => s.id === openNodeId);
|
||||
if (slot) group = slot.group || "Other";
|
||||
|
||||
@@ -345,6 +345,42 @@
|
||||
return { refused: refusedNow };
|
||||
}
|
||||
|
||||
// The pipeline as it stands, to come back to: Models & Pipeline's Cancel puts this back.
|
||||
function snapshot() {
|
||||
if (slots === null) return null;
|
||||
return { slots: JSON.parse(JSON.stringify(slots)), removed: [...removed], unwired: JSON.parse(JSON.stringify(unwired)) };
|
||||
}
|
||||
// -> {refused: [...]}; empty when the snapshot is back. Announced as a change, so the project follows.
|
||||
async function restore(snap) {
|
||||
if (!snap || !Array.isArray(snap.slots)) return { refused: ["Nothing to go back to."] };
|
||||
if (adoptGate) await adoptGate;
|
||||
for (let waited = 0; saving && waited < 10000; waited += 20) await new Promise((r) => setTimeout(r, 20));
|
||||
if (saving) return { refused: ["A save is still in progress: try again in a moment."] };
|
||||
const mine = epoch;
|
||||
saving = true;
|
||||
let refusedNow = [];
|
||||
try {
|
||||
const res = await API.editPipeline({ slots: JSON.parse(JSON.stringify(snap.slots)) });
|
||||
if (mine !== epoch) return { refused: ["The project was changed while this was being sent."] };
|
||||
refusedNow = (res && res.refused) || [];
|
||||
if (res && res.slots && !refusedNow.length) {
|
||||
slots = res.slots;
|
||||
removed = new Set(snap.removed || []);
|
||||
unwired = JSON.parse(JSON.stringify(snap.unwired || {}));
|
||||
groupEdits = {}; pendingValues = {}; pendingBody = null; pending = false;
|
||||
incomplete = res.incomplete || []; refused = []; queueable = !!res.queueable;
|
||||
saveNotes = res.notes || [];
|
||||
try { await refreshManifest(); } catch (_) { /* the next save retries it */ }
|
||||
_changed();
|
||||
}
|
||||
} catch (e) {
|
||||
refusedNow = [e && e.message ? e.message : String(e)];
|
||||
} finally {
|
||||
saving = false;
|
||||
}
|
||||
return { refused: refusedNow };
|
||||
}
|
||||
|
||||
// A slot's group is the person's own to name (any node in any group).
|
||||
// Kept until it has landed: a save already in flight answers with the slots as they were when
|
||||
// it left, and would otherwise put the old group back.
|
||||
@@ -444,7 +480,7 @@
|
||||
}
|
||||
|
||||
window.PipelineState = {
|
||||
ensureLoaded, save, edit, setGroup, adopt, subscribe: (fn) => { listeners.add(fn); return () => listeners.delete(fn); }, valuesAlreadyPlaced, currentValues, setModuleValue,
|
||||
ensureLoaded, save, edit, restore, snapshot, setGroup, adopt, subscribe: (fn) => { listeners.add(fn); return () => listeners.delete(fn); }, valuesAlreadyPlaced, currentValues, setModuleValue,
|
||||
modulesById: () => modulesById,
|
||||
removedIds: () => [...removed],
|
||||
unwiredMap: () => JSON.parse(JSON.stringify(unwired)),
|
||||
|
||||
@@ -211,3 +211,31 @@ test("a group change queued behind a save in flight is not re-stamped over a lat
|
||||
await PS.setGroup("gen", "Other");
|
||||
assert.strictEqual(PS.slots().find((s) => s.id === "gen").group, "Other");
|
||||
});
|
||||
|
||||
test("restore puts back the pipeline as snapshotted: slots, removed, unwired, and tells the project", async () => {
|
||||
const PS = load([]);
|
||||
await PS.ensureLoaded();
|
||||
const snap = PS.snapshot();
|
||||
await PS.save({ inputs: { model: { file: "changed" } } });
|
||||
global.window.MovieEditorAPI.editPipeline = async (body) => ({ refused: [], incomplete: [], queueable: true, slots: body.slots });
|
||||
await PS.edit({ action: "remove", slot: "gen" });
|
||||
assert.deepStrictEqual(PS.removedIds(), ["gen"]);
|
||||
let heard = 0;
|
||||
PS.subscribe(() => { heard++; });
|
||||
const res = await PS.restore(snap);
|
||||
assert.deepStrictEqual(res.refused, []);
|
||||
assert.deepStrictEqual(PS.slots(), snap.slots);
|
||||
assert.deepStrictEqual(PS.removedIds(), []);
|
||||
assert.strictEqual(heard, 1);
|
||||
});
|
||||
|
||||
test("a refused restore leaves the pipeline as it is", async () => {
|
||||
const PS = load([]);
|
||||
await PS.ensureLoaded();
|
||||
const snap = PS.snapshot();
|
||||
await PS.save({ inputs: { model: { file: "changed" } } });
|
||||
global.window.MovieEditorAPI.editPipeline = async () => ({ refused: ["no"], slots: [] });
|
||||
const res = await PS.restore(snap);
|
||||
assert.deepStrictEqual(res.refused, ["no"]);
|
||||
assert.strictEqual(PS.slots().find((s) => s.id === "model").inputs.file, "changed");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user