276 lines
8.3 KiB
JavaScript
276 lines
8.3 KiB
JavaScript
(() => {
|
|
const titles = {
|
|
"scene-details": "Manage scene details",
|
|
purpose: "Manage purpose",
|
|
metrics: "Manage metrics",
|
|
threads: "Manage threads",
|
|
characters: "Manage characters",
|
|
knowledge: "Manage knowledge",
|
|
relationships: "Manage relationships",
|
|
locations: "Manage locations",
|
|
assets: "Manage assets",
|
|
custody: "Manage custody",
|
|
notes: "Manage notes",
|
|
checklist: "Manage checklist",
|
|
attachments: "Manage attachments",
|
|
warnings: "Review warnings"
|
|
};
|
|
|
|
function openOverlay(root, overlay, key) {
|
|
const title = overlay.querySelector("[data-scene-editor-title]");
|
|
if (title) {
|
|
title.textContent = titles[key] || "Focused editor";
|
|
}
|
|
overlay.hidden = false;
|
|
overlay.setAttribute("aria-hidden", "false");
|
|
root.classList.add("editor-overlay-open");
|
|
}
|
|
|
|
function closeOverlay(overlay) {
|
|
const root = overlay?.closest("[data-scene-inspector-v2]");
|
|
if (overlay) {
|
|
overlay.hidden = true;
|
|
overlay.setAttribute("aria-hidden", "true");
|
|
}
|
|
root?.classList.remove("editor-overlay-open");
|
|
}
|
|
|
|
function setEditorBody(overlay, html) {
|
|
const body = overlay.querySelector("[data-scene-editor-body]");
|
|
if (body) {
|
|
body.innerHTML = html;
|
|
}
|
|
}
|
|
|
|
function setEditorError(form, message) {
|
|
const error = form.querySelector("[data-scene-editor-error]");
|
|
if (!error) {
|
|
return;
|
|
}
|
|
error.textContent = message || "The editor could not be saved.";
|
|
error.hidden = false;
|
|
}
|
|
|
|
function replaceSummaryParts(root, html) {
|
|
if (!html) {
|
|
return;
|
|
}
|
|
|
|
Object.entries(html).forEach(([part, markup]) => {
|
|
if (!markup) {
|
|
return;
|
|
}
|
|
|
|
const current = root.querySelector(`[data-scene-summary-part="${part}"]`);
|
|
if (!current) {
|
|
return;
|
|
}
|
|
|
|
const template = document.createElement("template");
|
|
template.innerHTML = markup.trim();
|
|
const replacement = template.content.firstElementChild;
|
|
if (replacement) {
|
|
current.replaceWith(replacement);
|
|
}
|
|
});
|
|
}
|
|
|
|
function updateTimelineSceneCard(scene) {
|
|
if (!scene?.sceneID && !scene?.SceneID) {
|
|
return;
|
|
}
|
|
|
|
const sceneId = scene.sceneID || scene.SceneID;
|
|
const card = document.querySelector(`.timeline-scene-card[data-scene-id="${sceneId}"]`);
|
|
if (!card) {
|
|
return;
|
|
}
|
|
|
|
const title = scene.sceneTitle || scene.SceneTitle;
|
|
const sceneNumber = scene.sceneNumber || scene.SceneNumber;
|
|
const status = scene.revisionStatusName || scene.RevisionStatusName;
|
|
const pov = scene.povLabel || scene.PovLabel;
|
|
const location = scene.primaryLocationName || scene.PrimaryLocationName;
|
|
|
|
if (title) {
|
|
const titleElement = card.querySelector("strong");
|
|
if (titleElement) {
|
|
titleElement.textContent = title;
|
|
}
|
|
}
|
|
if (sceneNumber) {
|
|
const numberElement = card.querySelector(".scene-number");
|
|
if (numberElement) {
|
|
numberElement.textContent = `Scene ${sceneNumber}`;
|
|
}
|
|
}
|
|
if (status) {
|
|
const statusElement = card.querySelector(".status-pill");
|
|
if (statusElement) {
|
|
statusElement.textContent = status;
|
|
}
|
|
}
|
|
if (pov) {
|
|
const povElement = card.querySelector(".scene-pov-label");
|
|
if (povElement) {
|
|
povElement.textContent = pov;
|
|
}
|
|
}
|
|
const locationElement = card.querySelector(".location-pill");
|
|
if (locationElement && location) {
|
|
locationElement.textContent = location;
|
|
locationElement.title = location;
|
|
}
|
|
}
|
|
|
|
function editorWidthBounds(root) {
|
|
const rootWidth = root.getBoundingClientRect().width || 960;
|
|
return {
|
|
min: Math.min(360, Math.max(280, rootWidth - 420)),
|
|
max: Math.max(360, rootWidth * 0.5)
|
|
};
|
|
}
|
|
|
|
function setEditEditorWidth(root, width) {
|
|
const bounds = editorWidthBounds(root);
|
|
const nextWidth = Math.min(bounds.max, Math.max(bounds.min, width));
|
|
root.style.setProperty("--scene-editor-width", `${Math.round(nextWidth)}px`);
|
|
}
|
|
|
|
document.addEventListener("click", async event => {
|
|
const openButton = event.target.closest("[data-scene-editor]");
|
|
if (openButton) {
|
|
const root = openButton.closest("[data-scene-inspector-v2]");
|
|
const overlay = root?.querySelector("[data-scene-editor-overlay]");
|
|
if (!overlay) {
|
|
return;
|
|
}
|
|
|
|
const key = openButton.dataset.sceneEditor;
|
|
const url = openButton.dataset.sceneEditorUrl;
|
|
openOverlay(root, overlay, key);
|
|
|
|
if (!url) {
|
|
setEditorBody(overlay, '<div class="scene-inspector-editor-pending">This focused editor has not been migrated yet.</div>');
|
|
overlay.querySelector("[data-scene-editor-close]")?.focus();
|
|
return;
|
|
}
|
|
|
|
setEditorBody(overlay, '<div class="scene-inspector-editor-loading">Loading editor...</div>');
|
|
try {
|
|
const response = await fetch(url, {
|
|
headers: { "X-Requested-With": "XMLHttpRequest" },
|
|
credentials: "same-origin"
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error("Editor load failed");
|
|
}
|
|
setEditorBody(overlay, await response.text());
|
|
overlay.querySelector("input, select, textarea, button")?.focus();
|
|
} catch {
|
|
setEditorBody(overlay, '<div class="scene-inspector-editor-error">The editor could not be loaded.</div>');
|
|
}
|
|
return;
|
|
}
|
|
|
|
const closeButton = event.target.closest("[data-scene-editor-close]");
|
|
if (closeButton) {
|
|
closeOverlay(closeButton.closest("[data-scene-editor-overlay]"));
|
|
}
|
|
});
|
|
|
|
document.addEventListener("input", event => {
|
|
const range = event.target.closest("[data-scene-metric-range]");
|
|
if (!range) {
|
|
return;
|
|
}
|
|
|
|
const output = range.closest(".scene-inspector-editor-metric")?.querySelector("output");
|
|
if (output) {
|
|
output.textContent = range.value;
|
|
}
|
|
});
|
|
|
|
document.addEventListener("submit", async event => {
|
|
const form = event.target.closest("[data-scene-editor-form]");
|
|
if (!form) {
|
|
return;
|
|
}
|
|
|
|
event.preventDefault();
|
|
const overlay = form.closest("[data-scene-editor-overlay]");
|
|
const root = form.closest("[data-scene-inspector-v2]");
|
|
const saveButton = form.querySelector("[data-scene-editor-save]");
|
|
const originalText = saveButton?.textContent;
|
|
|
|
if (saveButton) {
|
|
saveButton.disabled = true;
|
|
saveButton.textContent = "Saving...";
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(form.action, {
|
|
method: form.method || "POST",
|
|
body: new FormData(form),
|
|
headers: { "X-Requested-With": "XMLHttpRequest" },
|
|
credentials: "same-origin"
|
|
});
|
|
const contentType = response.headers.get("content-type") || "";
|
|
const payload = contentType.includes("application/json") ? await response.json() : null;
|
|
|
|
if (!response.ok || !payload?.success) {
|
|
setEditorError(form, payload?.message);
|
|
return;
|
|
}
|
|
|
|
replaceSummaryParts(root, payload.html);
|
|
updateTimelineSceneCard(payload.scene);
|
|
|
|
const timelineHeader = document.querySelector(".scene-inspector-panel .inspector-header h2");
|
|
const title = payload.scene?.sceneTitle || payload.scene?.SceneTitle;
|
|
if (timelineHeader && title) {
|
|
timelineHeader.textContent = title;
|
|
}
|
|
|
|
closeOverlay(overlay);
|
|
} catch {
|
|
setEditorError(form, "The editor could not be saved.");
|
|
} finally {
|
|
if (saveButton) {
|
|
saveButton.disabled = false;
|
|
saveButton.textContent = originalText;
|
|
}
|
|
}
|
|
});
|
|
|
|
document.addEventListener("pointerdown", event => {
|
|
const resizer = event.target.closest("[data-scene-editor-resizer]");
|
|
if (!resizer) {
|
|
return;
|
|
}
|
|
|
|
const root = resizer.closest(".scene-inspector-v2-edit");
|
|
if (!root) {
|
|
return;
|
|
}
|
|
|
|
event.preventDefault();
|
|
document.body.classList.add("scene-inspector-editor-resizing");
|
|
resizer.setPointerCapture?.(event.pointerId);
|
|
|
|
const onMove = moveEvent => {
|
|
const rect = root.getBoundingClientRect();
|
|
setEditEditorWidth(root, rect.right - moveEvent.clientX);
|
|
};
|
|
|
|
const onUp = () => {
|
|
document.body.classList.remove("scene-inspector-editor-resizing");
|
|
document.removeEventListener("pointermove", onMove);
|
|
document.removeEventListener("pointerup", onUp);
|
|
};
|
|
|
|
document.addEventListener("pointermove", onMove);
|
|
document.addEventListener("pointerup", onUp);
|
|
});
|
|
})();
|