From 8ca4c1b0e382166ddd2e7372df433968f44eb776 Mon Sep 17 00:00:00 2001 From: digital-garbage <133010426+digital-garbage@users.noreply.github.com> Date: Thu, 13 Aug 2026 14:23:13 +0300 Subject: [PATCH] =?UTF-8?q?feat(ui):=20Simple=20=E2=87=84=20Editor=20mode?= =?UTF-8?q?=20switch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Easy Gen becomes a mode of the editor rather than a second app. FunPackMode stamps data-ui-mode on ; Simple hides the timeline, assets, dock tabs, Composer, scene-selection and Auto Montage, and the rating-gated Studio and Chain Sampler settings. Nothing is disabled and no state is dropped — switching back shows everything as it was. The transport bar moves to the preview header in Simple mode instead of being rebuilt, so a running generation's timer keeps writing to the same buttons across a switch. The mode is folded into every ViewBus fingerprint, in one place rather than per zone: each zone builds different content per mode, and without it a switch left the old copy on screen. First stage of the merge. Routes, the hub, and the easy_gen frontend still stand; the inspector still shows its full field set in Simple mode. --- movie_editor/frontend/actionbar.js | 25 ++++++++--- movie_editor/frontend/engine_settings.js | 38 ++++++++-------- movie_editor/frontend/index.html | 7 ++- movie_editor/frontend/menubar.js | 19 ++++++++ movie_editor/frontend/player.js | 4 +- movie_editor/frontend/styles.css | 36 +++++++++++++++ movie_editor/frontend/ui_mode.js | 57 ++++++++++++++++++++++++ movie_editor/frontend/view_bus.js | 5 ++- 8 files changed, 164 insertions(+), 27 deletions(-) create mode 100644 movie_editor/frontend/ui_mode.js diff --git a/movie_editor/frontend/actionbar.js b/movie_editor/frontend/actionbar.js index f223d081..947e634c 100644 --- a/movie_editor/frontend/actionbar.js +++ b/movie_editor/frontend/actionbar.js @@ -1,9 +1,23 @@ -// Main transport actions: Generate / Generate selection / Render (timeline header). +// Main transport actions: Generate / Generate selection / Render. (function () { const { el, clear } = window.dom; const S = window.Store; - const mount = document.getElementById("transport-actions"); - if (!mount) return; + const timelineHost = document.getElementById("transport-actions"); + const simpleHost = document.getElementById("simple-actions"); + if (!timelineHost) return; + + // Home is the timeline header. Simple mode hides that whole zone, so the bar moves to + // the preview header rather than being rebuilt — one set of buttons, one set of handlers, + // and the running-generation tick keeps writing to the same nodes across a mode switch. + let mount = timelineHost; + function reparent() { + const want = (window.FunPackMode?.isSimple() && simpleHost) ? simpleHost : timelineHost; + if (want === mount) return; + clear(mount); // leave nothing behind in the header we came from + mount = want; + render(S.get()); // ViewBus only fires on a state change, and this isn't one + } + window.addEventListener("funpack-ui-mode", reparent); function selCount() { return S.selectedSceneCount ? S.selectedSceneCount() : (S.get().selectedSceneId ? 1 : 0); } function hasProject() { return !!S.get().project; } @@ -62,7 +76,7 @@ const n = selCount(); const selLabel = n > 1 ? `Selected (${n})` : "Selected"; - const genSel = el("button", "btn ghost compact", selLabel); + const genSel = el("button", "btn ghost compact mode-full-only", selLabel); genSel.title = n > 1 ? `Generate ${n} selected scenes (one chain run per segment)` : "Generate the selected scene"; @@ -88,7 +102,7 @@ renderBtn.onclick = () => S.renderFinal(); mount.append(renderBtn); - const montageBtn = el("button", "btn ghost compact", "⚡ Auto Montage"); + const montageBtn = el("button", "btn ghost compact mode-full-only", "⚡ Auto Montage"); montageBtn.title = "Build a trailer-style cut from already-rendered clips"; montageBtn.disabled = !hasProject() || busy(st); montageBtn.onclick = () => window.MontageDialog?.open(); @@ -124,6 +138,7 @@ }); } + if (window.FunPackMode?.isSimple() && simpleHost) mount = simpleHost; if (window.ViewBus) window.ViewBus.subscribeActionbar(render); else S.subscribe(render); render(S.get()); diff --git a/movie_editor/frontend/engine_settings.js b/movie_editor/frontend/engine_settings.js index 4acc459e..fa34e5c4 100644 --- a/movie_editor/frontend/engine_settings.js +++ b/movie_editor/frontend/engine_settings.js @@ -13,12 +13,10 @@ // the migration re-enters itself and recurses until the stack blows. const _ALG_MIGRATED = new Set(); - // Easy Gen has no rating UI at all (by design — see easy_gen/frontend/), so every - // setting that is a no-op without a trained refinement key / rated history is hidden - // there, not just made harder to find. window.FunPackAppName is the same discriminator - // settings_window.js already uses to relabel the About section for a different frontend - // sharing this file; Editor leaves it unset. - const EASY = !!window.FunPackAppName; + // Simple mode has no rating UI, so every setting that is a no-op without a trained + // refinement key / rated history is hidden there rather than just made harder to find. + // Read per render, not once: the mode switch flips it live. + const EASY = () => !!window.FunPackMode?.isSimple(); const RATING_GATED_KNOBS = new Set([ "embed_guidance", "embed_guidance_source", "embed_guidance_strength", "score_slider", "score_slider_strength", "taste_nearest_prompt", @@ -117,11 +115,11 @@ const { rf } = parseStudioSettings(p); let n = 0; [...STUDIO_REFINER_ESSENTIALS, ...STUDIO_REFINER_ADVANCED].forEach((f) => { - if (EASY && RATING_GATED_STUDIO.has(f.name)) return; + if (EASY() && RATING_GATED_STUDIO.has(f.name)) return; const cur = rf[f.name] != null ? rf[f.name] : f.default; if (cur !== f.default) n++; }); - if (!EASY && (p.refinement_key || "default") !== "default") n++; + if (!EASY() && (p.refinement_key || "default") !== "default") n++; return n; } @@ -385,7 +383,7 @@ } function knobVisible(k, si) { - if (EASY && RATING_GATED_KNOBS.has(k.name)) return false; + if (EASY() && RATING_GATED_KNOBS.has(k.name)) return false; // dependsOn/dependsValue: single condition (dependsValue absent = plain truthy // gate, as every existing boolean dependsOn already relies on). if (k.dependsOn && !_depSatisfied(k.dependsOn, k.dependsValue, si)) return false; @@ -470,7 +468,7 @@ const si = p.sampler_inputs || {}; let n = 0; (CHAIN_VIEW_KNOBS[id] || []).forEach((name) => { - if (EASY && RATING_GATED_KNOBS.has(name)) return; + if (EASY() && RATING_GATED_KNOBS.has(name)) return; const k = SAMPLER_KNOB_MAP[name]; if (k && si[name] != null && si[name] !== k.default) n++; }); @@ -562,7 +560,7 @@ const p = st.project; const { rf } = parseStudioSettings(p); - if (!EASY) { + if (!EASY()) { // Refinement key — project-level (feeds Studio / Chain Sampler / SaveRefinementLatent). // "default" uses the keyless store; a custom name trains/loads its own key. Shortcuts // bound to a non-default key layer per-scene training on top of this. @@ -575,18 +573,18 @@ } const gEss = group(pane, "Essentials"); - STUDIO_REFINER_ESSENTIALS.filter((f) => !EASY || !RATING_GATED_STUDIO.has(f.name)) + STUDIO_REFINER_ESSENTIALS.filter((f) => !EASY() || !RATING_GATED_STUDIO.has(f.name)) .forEach((f) => renderStudioRefinerBool(gEss, rf, f)); - const gAdv = group(pane, EASY ? "Prompt shaping" : "Refinement"); - STUDIO_REFINER_ADVANCED.filter((f) => !EASY || !RATING_GATED_STUDIO.has(f.name)) + const gAdv = group(pane, EASY() ? "Prompt shaping" : "Refinement"); + STUDIO_REFINER_ADVANCED.filter((f) => !EASY() || !RATING_GATED_STUDIO.has(f.name)) .forEach((f) => renderStudioRefinerField(gAdv, rf, f)); - if (EASY) { + if (EASY()) { pane.append(hintEl( "Studio runs in Prompt-only mode here: it shapes and splits the prompt, nothing more. " - + "Rating-dependent controls are hidden — Easy Gen has no rating UI to feed them. " - + "Use the Cutting Room for the full learned refiner.")); + + "Rating-dependent controls are hidden — Simple mode has no rating UI to feed them. " + + "Switch to Editor for the full learned refiner.")); } else { pane.append(hintEl("Scene text and transitions come from the timeline. Advisor, LoRA, and batch training remain in the ComfyUI Studio popup on the graph.")); } @@ -951,8 +949,8 @@ case "chain_continuity": return renderChainContinuity(pane, st); case "chain_timing": return renderChainTiming(pane, st); case "chain_guidance": return renderChainKnobsView(pane, st, "chain_guidance", "Guidance", - EASY ? "Rating-dependent guidance (embed guidance, score slider, output guidance, taste retrieval, " - + "DynaShift) is hidden here — use the Cutting Room or ComfyUI graph for those." : null); + EASY() ? "Rating-dependent guidance (embed guidance, score slider, output guidance, taste retrieval, " + + "DynaShift) is hidden in Simple mode — switch to Editor for those." : null); case "chain_decode": return renderChainKnobsView(pane, st, "chain_decode", "Decode"); case "chain_experimental": return renderChainExperimental(pane, st); default: return renderOverview(pane, st); @@ -1030,8 +1028,10 @@ }); unsub = S.subscribe(() => render()); + const offMode = window.FunPackMode?.onChange(() => render()); render(); return () => { + if (offMode) offMode(); if (unsub) { unsub(); unsub = null; } _mounted = null; _editing = false; diff --git a/movie_editor/frontend/index.html b/movie_editor/frontend/index.html index e7c874fd..171c020c 100644 --- a/movie_editor/frontend/index.html +++ b/movie_editor/frontend/index.html @@ -7,6 +7,7 @@ + @@ -48,7 +49,8 @@