Movie Editor: preserve scroll, stop per-second autosave (save on blur instead)

- Inspector preserves scrollTop across rebuilds, so editing no longer jumps the panel
  to the top.
- Autosave only persists to disk; it doesn't need to fire every keystroke. Free-text/
  number fields now debounce 6s and are FLUSHED on blur (flushSave); discrete actions
  save ~1.2s after the last change. generate()/generateMontage() flush first so the
  server has the latest edits before reading the project.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
digital-garbage
2026-06-07 13:22:02 +03:00
co-authored by Claude Opus 4.8
parent 0ca06b4431
commit 15a4fbf7d7
2 changed files with 29 additions and 6 deletions
+9 -2
View File
@@ -462,6 +462,7 @@
function render(st) {
if (_editing) return; // editing in progress — leave the DOM (and the user's caret) alone
const scrollTop = body.scrollTop; // preserve scroll across the rebuild
clear(body);
if (!st.project) { title.textContent = "Inspector"; body.append(el("div", "pj-meta", "No project open.")); return; }
const scene = st.selectedSceneId ? S.scene(st.selectedSceneId) : null;
@@ -470,6 +471,7 @@
if (scene) renderScene(st, scene); else renderProject(st);
renderExposed(st);
renderSplit(st);
body.scrollTop = scrollTop; // restore so editing doesn't jump to the top
}
// Track edit state so autosave re-renders never interrupt the focused field.
@@ -481,8 +483,13 @@
const t = e.target;
if (!(t && t.dataset && t.dataset.k)) return;
_editing = false;
// If focus didn't move to another of our fields, re-sync the inspector to state.
setTimeout(() => { if (!_editing) render(S.get()); }, 60);
// Persist the edit now (no more per-second autosave), then re-sync if focus didn't
// move to another field. flushSave commits + notifies (which renders); fall back to a
// manual render when there was nothing pending.
setTimeout(() => {
if (_editing) return;
if (!S.flushSave()) render(S.get());
}, 60);
});
S.subscribe(render);
+20 -4
View File
@@ -106,15 +106,29 @@
}
// ── editing ────────────────────────────────────────────────────────────────
// Autosave just persists the project to disk so edits aren't lost — it does NOT need
// to fire every keystroke. Discrete actions (dropdowns, checkboxes) save ~1s after the
// last change; free typing waits longer and is flushed on blur (see flushSave).
function scheduleSave() {
state.saving = true; notify();
clearTimeout(saveTimer);
saveTimer = setTimeout(commit, 900);
saveTimer = setTimeout(() => { saveTimer = null; commit(); }, 1200);
}
// For free-text / number fields: update state and queue a save WITHOUT re-rendering,
// so typing isn't interrupted. The eventual commit() re-renders once, after a pause.
function scheduleSaveSilent() { clearTimeout(saveTimer); saveTimer = setTimeout(commit, 1100); }
// so typing isn't interrupted. Long debounce; blur flushes it (flushSave).
function scheduleSaveSilent() {
clearTimeout(saveTimer);
saveTimer = setTimeout(() => { saveTimer = null; commit(); }, 6000);
}
// Commit any pending save immediately (field blur / before generate). Returns the
// commit promise when it flushed, else null.
function flushSave() {
if (!saveTimer) return null;
clearTimeout(saveTimer); saveTimer = null;
return commit();
}
async function commit() {
if (!state.project) return;
@@ -395,6 +409,7 @@
async function generate(onlyScene) {
if (!state.project) return;
await flushSave(); // ensure the server has the latest edits before generating
if (!onlyScene) return generateMontage();
await _generateRun([onlyScene], onlyScene, "Generating scene");
}
@@ -403,6 +418,7 @@
// (one GPU at a time). Each run's first scene supplies its i2v anchor.
async function generateMontage() {
if (!state.project) return;
await flushSave(); // persist pending edits before reading scenes/runs
const runs = _runs();
if (!runs.length) { set({ gen: { state: "error", promptId: null, media: [], msg: "No active scenes to generate." } }); return; }
for (let i = 0; i < runs.length; i++) {
@@ -568,7 +584,7 @@
window.Store = {
get, set, subscribe, init,
refreshProjectList, loadProject, newProject, deleteProject, downloadProject, importProject,
patchProject, patchProjectQuiet, patchScene, patchSceneQuiet, selectScene, addScene, removeScene, moveScene, scene,
patchProject, patchProjectQuiet, patchScene, patchSceneQuiet, flushSave, selectScene, addScene, removeScene, moveScene, scene,
resizeScene, splitScene, snapFrames,
refreshPreview, syncFromPreview, applyGlobalPrompt, generate, generateMontage, renderFinal, exportSelected, loadModels, loadImageTargets, setModelInput, setModelLink,
setConditioningSlot, setSamplerSlot, setSamplerInput, setSamplerInputNow, setStudioInput, setStudioInputNow,