diff --git a/movie_editor/README.md b/movie_editor/README.md index 6a033c8a..85785764 100644 --- a/movie_editor/README.md +++ b/movie_editor/README.md @@ -62,6 +62,27 @@ Nothing to start separately — the editor loads with ComfyUI: | `FUNPACK_MOVIE_TEMPLATE` | `backend/templates/ltxav_chain.api.json` | workflow template | | `FUNPACK_COMFY_URL` | auto (detected from the running server) | override only if loopback self-calls need a different address | +## Interface ("Cutting Room") + +A classic NLE three-zone layout with a top menu bar: + +``` + ┌ menu bar — File · Edit · View · Generate · FunPack ─────────────┐ + ├───────────────┬────────────────────────────────────────────────┤ + │ Media Browser │ Preview (program monitor) │ + │ projects │ │ + │ media bin ├────────────────────────────────────────────────┤ + │ (drag-drop) │ Inspector — selected clip OR project + split │ + ├───────────────┴────────────────────────────────────────────────┤ + │ Timeline — clips with transition seams; click a clip to edit it │ + └──────────────────────────────────────────────────────────────────┘ +``` + +Editing happens in the **Inspector**: select a clip in the timeline to edit its prompt, +source (Empty/Image), transition, and exclude flag; deselect (Generate ▸ Project Settings) +to edit the anchor and global seed/frames/fps. Panel borders are draggable. The media bin +drag-drop and Render/Export are stubbed for later phases. + ## How the timeline maps to Studio Studio splits one prompt into scenes by transition triggers; `segments[0]` (text before the diff --git a/movie_editor/frontend/app.js b/movie_editor/frontend/app.js new file mode 100644 index 00000000..037c45b4 --- /dev/null +++ b/movie_editor/frontend/app.js @@ -0,0 +1,31 @@ +// Entry: wire splitters, boot the store (view modules self-mount on load). +(function () { + function makeSplitter(splitterId, targetVar, axis, min, max, invert) { + const sp = document.getElementById(splitterId); + if (!sp) return; + let dragging = false; + const root = document.documentElement; + sp.addEventListener("pointerdown", (e) => { + dragging = true; sp.setPointerCapture(e.pointerId); + document.body.classList.add("dragging"); + }); + sp.addEventListener("pointermove", (e) => { + if (!dragging) return; + let v = axis === "x" + ? (invert ? window.innerWidth - e.clientX : e.clientX) + : (invert ? window.innerHeight - e.clientY : e.clientY); + v = Math.max(min, Math.min(max(), v)); + root.style.setProperty(targetVar, v + "px"); + }); + sp.addEventListener("pointerup", (e) => { + dragging = false; try { sp.releasePointerCapture(e.pointerId); } catch (_) {} + document.body.classList.remove("dragging"); + }); + } + + window.addEventListener("DOMContentLoaded", () => { + makeSplitter("split-left", "--media-w", "x", 200, () => window.innerWidth * 0.5, false); + makeSplitter("split-bottom", "--timeline-h", "y", 160, () => window.innerHeight * 0.6, true); + window.Store.init(); + }); +})(); diff --git a/movie_editor/frontend/dom.js b/movie_editor/frontend/dom.js new file mode 100644 index 00000000..4ed74bc1 --- /dev/null +++ b/movie_editor/frontend/dom.js @@ -0,0 +1,20 @@ +// Tiny DOM helpers shared by view modules. +(function () { + function el(tag, cls, txt) { + const e = document.createElement(tag); + if (cls) e.className = cls; + if (txt != null) e.textContent = txt; + return e; + } + function $(sel) { return document.querySelector(sel); } + function clear(node) { while (node && node.firstChild) node.removeChild(node.firstChild); return node; } + function icon(name) { + // minimal inline glyphs (keeps us font-icon-free) + const map = { + empty: "▦", image: "◐", frame: "⛶", play: "▶", add: "+", + del: "✕", up: "‹", down: "›", warn: "▲", dot: "●", film: "🎞", + }; + return map[name] || "•"; + } + window.dom = { el, $, clear, icon }; +})(); diff --git a/movie_editor/frontend/index.html b/movie_editor/frontend/index.html index 5fadd5f3..4bd54e35 100644 --- a/movie_editor/frontend/index.html +++ b/movie_editor/frontend/index.html @@ -3,67 +3,73 @@
-