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.
This commit is contained in:
digital-garbage
2026-06-10 16:56:36 +03:00
parent b72878f866
commit be03c321f4
6 changed files with 55 additions and 15 deletions
+39
View File
@@ -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());
})();
+2
View File
@@ -87,6 +87,7 @@
<section class="zone timeline" id="timeline-zone">
<div class="zone-head">
<span class="zone-title">Timeline</span>
<div class="transport-actions" id="transport-actions" aria-label="Generate and render"></div>
<div class="zone-head-right">
<span id="timeline-meta"></span>
<nav class="dock-tabs" id="dock-tabs" aria-label="Workspace panels"></nav>
@@ -104,6 +105,7 @@
<script src="store.js"></script>
<script src="models.js"></script>
<script src="menubar.js"></script>
<script src="actionbar.js"></script>
<script src="layout.js"></script>
<script src="mediabrowser.js"></script>
<script src="log.js"></script>
+1 -1
View File
@@ -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) {
+4 -13
View File
@@ -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() },
+1 -1
View File
@@ -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
+8
View File
@@ -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; }