fix: reference slot numbers repaint in the Scene panel and the bin badge; a pin dropped by a clash shows unticked; a deleted fixed picture is pruned

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
digital-garbage
2026-10-13 04:21:35 +03:00
co-authored by Claude Haiku 5.5
parent 976a5bb90c
commit 6b351c4dba
4 changed files with 16 additions and 10 deletions
+11 -6
View File
@@ -73,12 +73,17 @@ export default {
// A picture deleted from the bin (in another project, or on another machine) is still named by id here: it would fail the run in ComfyUI.
const bin = await Promise.resolve().then(() => app.api.media()).then((r) => new Set((r.media || []).map((m) => m.id)), () => null);
const dead = (id) => bin && id && !bin.has(id);
const prune = (pr) => pr.scenes.reduce((hit, s) => {
const had = dead(s.source_image) || (s.references || []).some(dead);
if (dead(s.source_image)) s.source_image = "";
if (had) s.references = (s.references || []).filter((r) => !dead(r));
return hit || had;
}, false);
const prune = (pr) => {
const fx = pr.fixed_references || {}, deadFx = Object.keys(fx).filter(dead); // a fixed slot whose picture is gone goes too, or every scene sends it
for (const id of deadFx) delete fx[id];
if (deadFx.length) pr.fixed_references = fx;
return pr.scenes.reduce((hit, s) => {
const had = dead(s.source_image) || (s.references || []).some(dead);
if (dead(s.source_image)) s.source_image = "";
if (had) s.references = (s.references || []).filter((r) => !dead(r));
return hit || had;
}, deadFx.length > 0);
};
if (prune(snap)) { tell("Some pictures this project used are no longer in the media bin; they were removed from their scenes."); p.edit(prune); }
const fixedSeed = times === 1 && p.pref("random_seed", true) === false; // takes are compared across seeds: always fresh ones
const drawn = new Map();
+1 -1
View File
@@ -35,7 +35,7 @@ export default {
function draw(force) { // not on every keystroke: that would rebuild the box being typed in
const sc = p.selected, open = p.project, slots = app.pipeline && app.pipeline.slots();
learnGrid(app.api, slots).then((learned) => { if (learned) draw(true); }); // the length arrows follow the model's frame grid once its node is described
const next = `${tab}|${open && open.id}|${p.selectedId}|${JSON.stringify(gridOf(slots))}|${tab === "project" && open ? [open.num_frames_per_scene, open.frame_rate, JSON.stringify(open.video || {}), open.postfix_enabled !== false, open.generation_mode, open.export_size_from, open.width, open.height, open.scenes.map((s) => s.frames_mode === "custom"), Object.keys(open.scene_renders || {}), open.scenes.length] : ""}|${tab === "scene" && sc && open ? [effFrames(sc, open), effFps(sc, open), p.scenes.indexOf(sc), sc.frames_mode, sc.fps_mode, p.scenes.length, sc.source_image, (sc.references || []).join(), sc.source_in, sc.source_dur, sc.removed_from_plan, (sc.source || {}).type, sc.excluded, typing() ? shownText : (shownText = sc.text), ((open.scene_renders || {})[sc.id] || {}).durationSec, Boolean(((open.scene_renders || {})[sc.id] || {}).media), bin.version, ...sections.map((s) => { try { return s.key(sc, open); } catch { return ""; } })] : ""}`;
const next = `${tab}|${open && open.id}|${p.selectedId}|${JSON.stringify(gridOf(slots))}|${tab === "project" && open ? [open.num_frames_per_scene, open.frame_rate, JSON.stringify(open.video || {}), open.postfix_enabled !== false, open.generation_mode, open.export_size_from, open.width, open.height, open.scenes.map((s) => s.frames_mode === "custom"), Object.keys(open.scene_renders || {}), open.scenes.length] : ""}|${tab === "scene" && sc && open ? [effFrames(sc, open), effFps(sc, open), p.scenes.indexOf(sc), sc.frames_mode, sc.fps_mode, p.scenes.length, sc.source_image, (sc.references || []).join(), JSON.stringify(sc.reference_pins || {}), JSON.stringify(open.fixed_references || {}), sc.source_in, sc.source_dur, sc.removed_from_plan, (sc.source || {}).type, sc.excluded, typing() ? shownText : (shownText = sc.text), ((open.scene_renders || {})[sc.id] || {}).durationSec, Boolean(((open.scene_renders || {})[sc.id] || {}).media), bin.version, ...sections.map((s) => { try { return s.key(sc, open); } catch { return ""; } })] : ""}`;
titled();
if (!force && next === key) return;
key = next;
+1 -1
View File
@@ -26,7 +26,7 @@ const pinRows = (p, sc, app) => {
if (!plan.ids.length) return [];
const numbers = referenceNumbers(plan.ids, plan.pins);
return [card(...plan.ids.map((id, i) => c.checkboxRow.default({ label: `Pin reference to slot R${numbers[i]}`,
hint: namesOf([id], app.api, () => app.say("bin"))[0] || id, checked: plan.pins[id] != null,
hint: namesOf([id], app.api, () => app.say("bin"))[0] || id, checked: plan.pins[id] === numbers[i],
onChange: (on) => {
if (id in fixed) { app.project.edit((x) => { const next = { ...(x.fixed_references || {}) }; delete next[id]; x.fixed_references = next; return true; }); return; }
const next = { ...(sc.reference_pins || {}) }; if (on) next[id] = numbers[i]; else delete next[id];
+3 -2
View File
@@ -1,6 +1,7 @@
// The media bin: files brought in (reference images, clips), kept apart from what runs produce.
import { composer as c } from "../../composer/composer.js";
import { learn } from "../../shell/bin.js";
import { referenceNumbers, sceneReferences } from "../../shell/reference_wiring.js";
import { MEDIA_DRAG } from "../../shell/dnd.js";
const base = (id) => `/funpack/api/media/${encodeURIComponent(id)}`;
@@ -96,7 +97,7 @@ export default {
let drawn = "";
function draw(force) {
const sc = p.selected, refs = (sc && sc.references) || [];
const sc = p.selected, plan = sc ? sceneReferences(sc, (p.project || {}).fixed_references) : { ids: [], pins: {} }, refs = plan.ids, nums = referenceNumbers(refs, plan.pins);
const key = `${(sc || {}).source_image}|${refs}|${items.map((m) => m.id)}|${selecting}|${[...chosen]}|${show.filter}`;
if (!force && key === drawn) return; // typing elsewhere must not rebuild the thumbnails
drawn = key;
@@ -110,7 +111,7 @@ export default {
if (g.setCols) g.setCols(Number(show.cols));
// what the selected scene uses is marked on its tile, so a reference can be told from the rest
const flags = (m) => [sc && sc.source_image === m.id ? { text: "◐", title: "The selected scene's resolution source" } : null,
refs.includes(m.id) ? { text: `R${refs.indexOf(m.id) + 1}`, title: `Reference ${refs.indexOf(m.id) + 1} of ${refs.length} for the selected scene` } : null].filter(Boolean);
refs.includes(m.id) ? { text: `R${nums[refs.indexOf(m.id)]}`, title: `Reference slot ${nums[refs.indexOf(m.id)]} for the selected scene` } : null].filter(Boolean);
g.setItems(shown.map((m) => ({ id: m.id, label: m.name, thumb: m.kind === "audio" ? undefined : `${base(m.id)}/thumb`, badge: m.kind, flags: flags(m) })));
g.setValue(selecting ? [...chosen] : sc ? [sc.source_image, ...refs].filter(Boolean) : []);
selectBtn.setLabel(selecting ? "Done" : "Select");