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:
co-authored by
Claude Opus 4.8
parent
0ca06b4431
commit
15a4fbf7d7
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user