fix: Reactive focus modal ignores outside clicks; only x / Cancel / a step ends it

This commit is contained in:
digital-garbage
2026-09-30 20:37:35 +03:00
parent 13e2001f47
commit 30409ab3e8
3 changed files with 19 additions and 8 deletions
+2 -2
View File
@@ -79,7 +79,7 @@
return sel;
}
function openModal({ title, subtitle, widthClass, onClose }) {
function openModal({ title, subtitle, widthClass, onClose, sticky }) {
const overlay = document.createElement("div");
overlay.className = "modal-overlay ov-modal-overlay";
const box = document.createElement("div");
@@ -115,7 +115,7 @@
if (onClose) onClose();
};
closeBtn.onclick = close;
overlay.addEventListener("click", (e) => { if (e.target === overlay) close(); });
if (!sticky) overlay.addEventListener("click", (e) => { if (e.target === overlay) close(); });
document.body.append(overlay);
return { overlay, body, foot, close };
}
+13 -3
View File
@@ -103,6 +103,7 @@
title: "Reactive focus",
subtitle: "",
widthClass: "ov-modal-wide",
sticky: true, // a stray click must not cancel the run; only × / Cancel / a step does
onClose: () => { if (!done) { done = true; resolve(null); } },
});
const show = () => {
@@ -138,9 +139,10 @@
const wantMoves = !!(rf.camera_moves && rf.reactive_focus && chance(rf.camera_moves_chance, 0.7) > 0);
const wantViews = !!(rf.shot_views && rf.reactive_focus && chance(rf.shot_view_chance, 0.4) > 0);
if (!wantMoves && !wantViews) return true;
let scenes;
let scenes, seen = {};
try {
scenes = (await window.MovieEditorAPI.focusOptions(project.id, sceneIds)).scenes || [];
seen = await window.MovieEditorAPI.focusOptions(project.id, sceneIds);
scenes = seen.scenes || [];
} catch (e) {
console.warn("[FunPack] reactive focus: could not list targets —", e.message);
window.Store.get().notice = `Reactive focus was skipped: ${e.message}`;
@@ -157,7 +159,15 @@
const todo = pending(scenes, savedViews, "views", (s) => !s.already && s.candidates.length);
if (todo.length) steps.push({ kind: "views", title: "Views", page: viewsPage(todo) });
}
if (!steps.length) return true;
if (!steps.length) {
// Say why nothing was asked, or "no modal" is indistinguishable from "broken".
const why = !seen.with_shot_marks
? `no [Shot N] found in the ${seen.texts == null ? "" : seen.texts + " "}scene text(s) sent to generation`
: scenes.length ? "every shot is already decided or already has a camera move/view"
: "no shot has a target or view to offer";
window.Store.get().notice = `Reactive focus: nothing to ask — ${why}.`;
return true;
}
const res = await ask(steps);
if (!res) return false;
const patch = {}, learn = { decisions: [], views: [] };
+4 -3
View File
@@ -3129,13 +3129,14 @@ if web is not None and PromptServer is not None:
if shots or views:
out.append({"index": i, "preview": " ".join(text.split())[:80],
"shots": shots, "views": views})
return out
marked = sum(1 for t in texts if "[shot" in t.lower())
return out, {"texts": len(texts), "with_shot_marks": marked}
try:
scenes = await asyncio.to_thread(_build)
scenes, seen = await asyncio.to_thread(_build)
except Exception as e: # noqa: BLE001
return web.json_response({"detail": f"Could not list camera targets: {e}"}, status=500)
return web.json_response({"scenes": scenes})
return web.json_response({"scenes": scenes, **seen})
@routes.post(UI_PREFIX + "/api/focus/learn")
async def _focus_learn(req):