Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
50 lines
4.4 KiB
JavaScript
50 lines
4.4 KiB
JavaScript
// The Properties zone: Project and Scene tabs, titled for what they show.
|
|
import { composer as c } from "../../composer/composer.js";
|
|
import { effFrames, effFps, genUnitId } from "../../shell/scenes.js";
|
|
import { sceneRows } from "./scene.js";
|
|
import { bin } from "../../shell/bin.js";
|
|
import { projectRows, homed } from "./project.js";
|
|
import { gridOf, learnGrid, offersLength } from "../../shell/frame_grid.js";
|
|
|
|
export default {
|
|
id: "inspector",
|
|
mount: "inspector",
|
|
needs: ["project", "dom"],
|
|
setup({ host, app }) {
|
|
const p = app.project;
|
|
let tab = "scene", key = "";
|
|
const body = c.region.stack({ gap: "sm", label: "Properties" });
|
|
const tabs = c.segmented.sm({ label: "Properties", options: [{ value: "project", label: "Project" }, { value: "scene", label: "Scene" }], value: tab,
|
|
onChange: (v) => { tab = v; draw(true); } });
|
|
host.append(tabs.node, body.node);
|
|
|
|
const titled = () => {
|
|
const sc = p.selected;
|
|
if (tab === "project" || !sc) return app.title && app.title("inspector", tab === "project" ? "Project" : "Scene");
|
|
const unit = p.scenes.filter((s) => genUnitId(s) === genUnitId(sc));
|
|
app.title && app.title("inspector", `Scene · ${p.scenes.indexOf(sc) + 1}${unit.length > 1 ? ` · cut ${unit.indexOf(sc) + 1}/${unit.length}` : ""}`);
|
|
};
|
|
const sections = app.sceneSections || []; // extra rows other features put under the scene's own: { rows(scene, project), key(scene, project) }
|
|
// The scene's text is in the key, so an edit made elsewhere (the Story box) is shown -- but not while this panel's
|
|
// own box is being typed in: that would rebuild it under the caret. It catches up when the focus leaves.
|
|
let shownText;
|
|
const typing = () => app.dom.typingIn(body.node);
|
|
// Catch up once focus leaves the panel. Focus moving to a button or select inside it is a click under way: rebuilding
|
|
// between its mouse-down and mouse-up dropped the click; the change it makes redraws afterwards anyway.
|
|
body.node.addEventListener("focusout", (e) => { if (!(e.relatedTarget && body.node.contains(e.relatedTarget))) setTimeout(() => { if (!typing()) draw(); }); });
|
|
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.view || 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))}|${offersLength(slots)}|${Object.keys((app.pipeline && app.pipeline.skipped && app.pipeline.skipped()) || {}).join()}|${tab === "project" && open ? [open.num_frames_per_scene, open.frame_rate, JSON.stringify(open.video || {}), JSON.stringify(open.video_input || {}), open.video_input_from, open.postfix_enabled !== false, JSON.stringify(homed(app)), open.export_size_from, open.width, open.height, open.resolution_basis, 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(open.fixed_references || {}), sc.source_in, sc.source_dur, JSON.stringify(sc.video_input || {}), JSON.stringify(open.video_input || {}), open.video_input_from, 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;
|
|
body.set((tab === "scene" ? [...sceneRows(p, app), ...(p.selected ? sections.flatMap((s) => { try { return s.rows(p.selected, p); } catch { return []; } }) : [])] : projectRows(p, slots, app.api, app)));
|
|
}
|
|
draw(true);
|
|
const offSlots = app.pipeline && app.pipeline.subscribe ? app.pipeline.subscribe(() => draw(true)) : null; // the pipeline arrives after the project
|
|
const offApp = app.on((what) => draw(what === "open"));
|
|
return () => { offApp(); if (offSlots) offSlots(); };
|
|
},
|
|
};
|