From be03c321f41a07c9334e98472e89134c03b0f4dc Mon Sep 17 00:00:00 2001 From: digital-garbage <133010426+digital-garbage@users.noreply.github.com> Date: Wed, 10 Jun 2026 16:56:36 +0300 Subject: [PATCH] Move Generate/Render to timeline header; rename menu to Settings Renames the top Generate menu to Settings (engine, models, pipeline slots). Removes the standalone Models menu. Adds Generate, Selected, and Render buttons to the timeline zone header. --- movie_editor/frontend/actionbar.js | 39 ++++++++++++++++++++++++++++++ movie_editor/frontend/index.html | 2 ++ movie_editor/frontend/inspector.js | 2 +- movie_editor/frontend/menubar.js | 17 +++---------- movie_editor/frontend/player.js | 2 +- movie_editor/frontend/styles.css | 8 ++++++ 6 files changed, 55 insertions(+), 15 deletions(-) create mode 100644 movie_editor/frontend/actionbar.js diff --git a/movie_editor/frontend/actionbar.js b/movie_editor/frontend/actionbar.js new file mode 100644 index 00000000..cec76ecd --- /dev/null +++ b/movie_editor/frontend/actionbar.js @@ -0,0 +1,39 @@ +// Main transport actions: Generate / Generate selection / Render (timeline header). +(function () { + const { el, clear } = window.dom; + const S = window.Store; + const mount = document.getElementById("transport-actions"); + if (!mount) return; + + function selCount() { return S.selectedSceneCount ? S.selectedSceneCount() : (S.get().selectedSceneId ? 1 : 0); } + function hasProject() { return !!S.get().project; } + function busy(st) { return ["queuing", "running", "pending"].includes(st.gen?.state); } + + function render(st) { + clear(mount); + const genAll = el("button", "btn primary compact", "▶ Generate"); + genAll.title = "Generate the whole montage"; + genAll.disabled = !hasProject() || busy(st); + genAll.onclick = () => S.generate(null); + mount.append(genAll); + + const n = selCount(); + const selLabel = n > 1 ? `Selected (${n})` : "Selected"; + const genSel = el("button", "btn ghost compact", selLabel); + genSel.title = n > 1 + ? `Generate ${n} selected scenes (one chain run per segment)` + : "Generate the selected scene"; + genSel.disabled = n === 0 || busy(st); + genSel.onclick = () => S.generateSelected(); + mount.append(genSel); + + const renderBtn = el("button", "btn render compact", "⧉ Render"); + renderBtn.title = "Stitch generated clips into a final video"; + renderBtn.disabled = !hasProject() || busy(st); + renderBtn.onclick = () => S.renderFinal(); + mount.append(renderBtn); + } + + S.subscribe(render); + render(S.get()); +})(); \ No newline at end of file diff --git a/movie_editor/frontend/index.html b/movie_editor/frontend/index.html index 4192ad59..0558fc41 100644 --- a/movie_editor/frontend/index.html +++ b/movie_editor/frontend/index.html @@ -87,6 +87,7 @@
Timeline +
@@ -104,6 +105,7 @@ + diff --git a/movie_editor/frontend/inspector.js b/movie_editor/frontend/inspector.js index e65aad51..c7d3f404 100644 --- a/movie_editor/frontend/inspector.js +++ b/movie_editor/frontend/inspector.js @@ -145,7 +145,7 @@ const selN = S.selectedSceneCount ? S.selectedSceneCount() : 1; if (selN > 1) { const mh = el("div", "insp-hint"); - mh.textContent = `${selN} clips selected — Generate → Generate Selected Scenes runs each chain segment that includes a selected clip (⌘/Ctrl-click toggle · Shift-click range).`; + mh.textContent = `${selN} clips selected — use Selected in the timeline header to generate each chain segment that includes a selected clip (⌘/Ctrl-click toggle · Shift-click range).`; body.append(mh); } if (unitScenes.length > 1) { diff --git a/movie_editor/frontend/menubar.js b/movie_editor/frontend/menubar.js index 0e84f4f2..da745110 100644 --- a/movie_editor/frontend/menubar.js +++ b/movie_editor/frontend/menubar.js @@ -1,4 +1,4 @@ -// Top menu bar: File / Edit / View / Generate / FunPack + status chips. +// Top menu bar: File / Edit / View / Settings / FunPack + status chips. (function () { const { el, clear } = window.dom; const S = window.Store; @@ -7,7 +7,6 @@ let openName = null; function sel() { return S.get().selectedSceneId; } - function selCount() { return S.selectedSceneCount ? S.selectedSceneCount() : (sel() ? 1 : 0); } function hasProject() { return !!S.get().project; } // hidden file input for importing a project file @@ -72,25 +71,17 @@ { sep: true }, { label: "Reset Layout", action: () => { const r = document.documentElement; r.style.removeProperty("--media-w"); r.style.removeProperty("--timeline-h"); } }, ], - Generate: [ + Settings: [ { label: "Engine settings…", disabled: !hasProject(), action: () => window.EngineSettingsModal.open() }, { sep: true }, - { label: "Generate Whole Montage", hint: "▶", disabled: !hasProject(), action: () => S.generate(null) }, - { label: selCount() > 1 ? `Generate Selected Scenes (${selCount()})` : "Generate Selected Scene", - hint: selCount() > 1 ? "⌘-click" : undefined, - disabled: selCount() === 0, action: () => S.generateSelected() }, - { sep: true }, - { label: "Render Final Video", hint: "⧉", disabled: !hasProject(), action: () => S.renderFinal() }, + { label: "Models…", action: () => window.ModelsModal.open() }, + { label: "Refresh model list", hint: "R", action: async () => { try { await window.MovieEditorAPI.refreshModels(); } catch (_) {} } }, { sep: true }, { label: `Conditioning: ${_roleLabel(st.project?.conditioning_slot, "FunPack Studio")}`, disabled: !hasProject(), action: () => _pickRole("conditioning_slot", "Conditioning node", st) }, { label: `Sampler: ${_roleLabel(st.project?.sampler_slot, "FunPack Chain Sampler")}`, disabled: !hasProject(), action: () => _pickRole("sampler_slot", "Sampler node", st) }, ], - Models: [ - { label: "Settings…", action: () => window.ModelsModal.open() }, - { label: "Refresh model list", hint: "R", action: async () => { try { await window.MovieEditorAPI.refreshModels(); } catch (_) {} } }, - ], FunPack: [ { label: st.resetSessionArmed ? "Reset Studio session ✓ armed — click to cancel" : "Reset Studio session", disabled: !hasProject(), action: () => S.resetStudioSession() }, diff --git a/movie_editor/frontend/player.js b/movie_editor/frontend/player.js index d09a492e..2682bec9 100644 --- a/movie_editor/frontend/player.js +++ b/movie_editor/frontend/player.js @@ -513,7 +513,7 @@ const empty = el("div", "program-empty"); empty.append(el("div", "reel", "🎬")); empty.append(el("div", null, p ? "No render yet" : "Open or create a project")); - empty.append(el("div", "pj-meta", "Generate from the menu or timeline")); + empty.append(el("div", "pj-meta", "Use Generate in the timeline header")); canvas.append(empty); } // generation progress overlay (visible even when there's already media). Built once per diff --git a/movie_editor/frontend/styles.css b/movie_editor/frontend/styles.css index 913a5ea4..5ed23652 100644 --- a/movie_editor/frontend/styles.css +++ b/movie_editor/frontend/styles.css @@ -179,6 +179,14 @@ button, input, select, textarea { font-family: inherit; color: var(--text); } font-family: var(--font-mono); font-size: 11px; letter-spacing: 2px; text-transform: uppercase; color: var(--muted); } .zone-title::before { content: ""; display: inline-block; width: 6px; height: 6px; margin-right: 9px; border-radius: 1px; background: var(--amber); transform: translateY(-1px); box-shadow: 0 0 7px var(--amber-soft); } +.transport-actions { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; } +.transport-actions .btn.compact { padding: 3px 10px; font-size: 12px; font-family: var(--font-mono); line-height: 1.35; } +.transport-actions .btn:disabled { opacity: 0.42; pointer-events: none; cursor: default; } +.transport-actions .btn.render { + background: linear-gradient(#3ab3a8, #2d9a90); color: #0a1a18; border-color: #3ab3a8; + font-weight: 600; box-shadow: 0 4px 14px -6px color-mix(in srgb, var(--teal) 55%, transparent); +} +.transport-actions .btn.render:hover { filter: brightness(1.08); } .zone-head-right { margin-left: auto; font-family: var(--font-mono); font-size: 11px; color: var(--faint); display: flex; gap: 10px; align-items: center; flex-wrap: wrap; justify-content: flex-end; } .zone-body { flex: 1; overflow: auto; padding: 12px; min-height: 0; }