Phase 10E: Scene Resolution and Writing Brief Display
This commit is contained in:
parent
07da6ff8b5
commit
8c4525d3b3
@ -75,6 +75,59 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="word-companion-section word-companion-plotdirector-scene" aria-label="PlotDirector Scene">
|
||||
<div class="word-companion-section-header">
|
||||
<span>PlotDirector Scene</span>
|
||||
<button type="button" data-refresh-plotdirector-scene>Refresh PlotDirector Scene</button>
|
||||
</div>
|
||||
<span>Scene Status</span>
|
||||
<p class="word-companion-message" data-plotdirector-scene-status>Select a PlotDirector book.</p>
|
||||
<div class="word-companion-scene-details" data-plotdirector-scene-details hidden>
|
||||
<div>
|
||||
<span>Scene Title</span>
|
||||
<strong data-plotdirector-scene-title>-</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>Revision Status</span>
|
||||
<strong data-plotdirector-revision-status>-</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>Estimated Words</span>
|
||||
<strong data-plotdirector-estimated-words>-</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>Actual Words</span>
|
||||
<strong data-plotdirector-actual-words>-</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>Blocked Status</span>
|
||||
<strong data-plotdirector-blocked-status>-</strong>
|
||||
</div>
|
||||
<div data-plotdirector-blocked-reason-row hidden>
|
||||
<span>Blocked Reason</span>
|
||||
<strong data-plotdirector-blocked-reason>-</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="word-companion-brief" data-writing-brief-block hidden>
|
||||
<span>Writing Brief</span>
|
||||
<p data-writing-brief></p>
|
||||
</div>
|
||||
<div class="word-companion-link-groups" data-plotdirector-link-groups hidden>
|
||||
<div>
|
||||
<span>Characters</span>
|
||||
<div class="word-companion-chip-list" data-character-chips></div>
|
||||
</div>
|
||||
<div>
|
||||
<span>Assets</span>
|
||||
<div class="word-companion-chip-list" data-asset-chips></div>
|
||||
</div>
|
||||
<div>
|
||||
<span>Locations</span>
|
||||
<div class="word-companion-chip-list" data-location-chips></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="word-companion-section" aria-label="Diagnostics">
|
||||
<div class="word-companion-section-header">
|
||||
<span>Diagnostics</span>
|
||||
|
||||
@ -174,6 +174,60 @@ body {
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
.word-companion-scene-details,
|
||||
.word-companion-link-groups {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.word-companion-scene-details div,
|
||||
.word-companion-link-groups div {
|
||||
display: grid;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.word-companion-brief {
|
||||
display: grid;
|
||||
gap: 5px;
|
||||
border-top: 1px solid var(--companion-line);
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.word-companion-brief p {
|
||||
margin: 0;
|
||||
border: 1px solid var(--companion-line);
|
||||
border-radius: 6px;
|
||||
padding: 8px;
|
||||
background: var(--companion-soft);
|
||||
color: var(--companion-ink);
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.35;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.word-companion-chip-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.word-companion-chip,
|
||||
.word-companion-empty-chip {
|
||||
border: 1px solid rgba(47, 111, 99, 0.25);
|
||||
border-radius: 999px;
|
||||
padding: 3px 7px;
|
||||
background: var(--companion-soft);
|
||||
color: var(--companion-accent-dark);
|
||||
font-size: 0.82rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.word-companion-empty-chip {
|
||||
border-color: var(--companion-line);
|
||||
color: var(--companion-muted);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.word-companion-outline {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
|
||||
@ -21,6 +21,22 @@
|
||||
const documentMessage = document.querySelector("[data-document-message]");
|
||||
const syncNote = document.querySelector("[data-sync-note]");
|
||||
const documentOutline = document.querySelector("[data-document-outline]");
|
||||
const refreshPlotDirectorSceneButton = document.querySelector("[data-refresh-plotdirector-scene]");
|
||||
const plotDirectorSceneStatus = document.querySelector("[data-plotdirector-scene-status]");
|
||||
const plotDirectorSceneDetails = document.querySelector("[data-plotdirector-scene-details]");
|
||||
const plotDirectorSceneTitle = document.querySelector("[data-plotdirector-scene-title]");
|
||||
const plotDirectorRevisionStatus = document.querySelector("[data-plotdirector-revision-status]");
|
||||
const plotDirectorEstimatedWords = document.querySelector("[data-plotdirector-estimated-words]");
|
||||
const plotDirectorActualWords = document.querySelector("[data-plotdirector-actual-words]");
|
||||
const plotDirectorBlockedStatus = document.querySelector("[data-plotdirector-blocked-status]");
|
||||
const plotDirectorBlockedReasonRow = document.querySelector("[data-plotdirector-blocked-reason-row]");
|
||||
const plotDirectorBlockedReason = document.querySelector("[data-plotdirector-blocked-reason]");
|
||||
const writingBriefBlock = document.querySelector("[data-writing-brief-block]");
|
||||
const writingBrief = document.querySelector("[data-writing-brief]");
|
||||
const plotDirectorLinkGroups = document.querySelector("[data-plotdirector-link-groups]");
|
||||
const characterChips = document.querySelector("[data-character-chips]");
|
||||
const assetChips = document.querySelector("[data-asset-chips]");
|
||||
const locationChips = document.querySelector("[data-location-chips]");
|
||||
const runDiagnosticsButton = document.querySelector("[data-run-diagnostics]");
|
||||
const diagnosticHost = document.querySelector("[data-diagnostic-host]");
|
||||
const diagnosticPlatform = document.querySelector("[data-diagnostic-platform]");
|
||||
@ -37,6 +53,8 @@
|
||||
let wordHostAvailable = false;
|
||||
let officeHost = "Unknown";
|
||||
let officePlatform = "Unknown";
|
||||
let detectedChapterTitle = "";
|
||||
let detectedSceneTitle = "";
|
||||
|
||||
const setOfficeStatus = (message) => {
|
||||
if (officeStatus) {
|
||||
@ -71,7 +89,16 @@
|
||||
}
|
||||
};
|
||||
|
||||
const setPlotDirectorSceneStatus = (message) => {
|
||||
if (plotDirectorSceneStatus) {
|
||||
plotDirectorSceneStatus.textContent = message;
|
||||
}
|
||||
};
|
||||
|
||||
const setCurrentStructure = (chapterTitle, sceneTitle, wordCount) => {
|
||||
detectedChapterTitle = chapterTitle || "";
|
||||
detectedSceneTitle = sceneTitle || "";
|
||||
|
||||
if (currentChapter) {
|
||||
currentChapter.textContent = chapterTitle || "Not detected";
|
||||
}
|
||||
@ -83,6 +110,91 @@
|
||||
}
|
||||
};
|
||||
|
||||
const setHidden = (element, hidden) => {
|
||||
if (element) {
|
||||
element.hidden = hidden;
|
||||
}
|
||||
};
|
||||
|
||||
const displayValue = (value) => {
|
||||
if (value === null || value === undefined || value === "") {
|
||||
return "-";
|
||||
}
|
||||
|
||||
return String(value);
|
||||
};
|
||||
|
||||
const resetPlotDirectorScene = (message) => {
|
||||
setPlotDirectorSceneStatus(message);
|
||||
setHidden(plotDirectorSceneDetails, true);
|
||||
setHidden(writingBriefBlock, true);
|
||||
setHidden(plotDirectorLinkGroups, true);
|
||||
};
|
||||
|
||||
const renderLinkedItems = (container, items) => {
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = "";
|
||||
const linkedItems = Array.isArray(items) ? items.filter((item) => item.linked) : [];
|
||||
if (linkedItems.length === 0) {
|
||||
const empty = document.createElement("span");
|
||||
empty.className = "word-companion-empty-chip";
|
||||
empty.textContent = "None";
|
||||
container.append(empty);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const item of linkedItems) {
|
||||
const chip = document.createElement("span");
|
||||
chip.className = "word-companion-chip";
|
||||
chip.textContent = item.name || "Unnamed";
|
||||
container.append(chip);
|
||||
}
|
||||
};
|
||||
|
||||
const renderCompanionData = (data) => {
|
||||
setText(plotDirectorSceneTitle, displayValue(data?.sceneTitle));
|
||||
setText(plotDirectorRevisionStatus, displayValue(data?.revisionStatus || data?.revisionStatusName || "Not provided"));
|
||||
setText(plotDirectorEstimatedWords, displayValue(data?.estimatedWords));
|
||||
setText(plotDirectorActualWords, displayValue(data?.actualWords));
|
||||
setText(plotDirectorBlockedStatus, data?.blocked ? "Blocked" : "Not blocked");
|
||||
|
||||
if (data?.blockedReason) {
|
||||
setText(plotDirectorBlockedReason, data.blockedReason);
|
||||
setHidden(plotDirectorBlockedReasonRow, false);
|
||||
} else {
|
||||
setText(plotDirectorBlockedReason, "-");
|
||||
setHidden(plotDirectorBlockedReasonRow, true);
|
||||
}
|
||||
|
||||
if (writingBrief) {
|
||||
writingBrief.textContent = data?.writingBrief || "No writing brief available.";
|
||||
}
|
||||
|
||||
renderLinkedItems(characterChips, data?.characters);
|
||||
renderLinkedItems(assetChips, data?.assets);
|
||||
renderLinkedItems(locationChips, data?.locations);
|
||||
setHidden(plotDirectorSceneDetails, false);
|
||||
setHidden(writingBriefBlock, false);
|
||||
setHidden(plotDirectorLinkGroups, false);
|
||||
};
|
||||
|
||||
const findSceneRevisionStatus = async (bookId, sceneId) => {
|
||||
const structure = await fetchJson(`/api/word-companion/books/${bookId}/structure`);
|
||||
const chapters = Array.isArray(structure?.chapters) ? structure.chapters : [];
|
||||
for (const chapter of chapters) {
|
||||
const scenes = Array.isArray(chapter.scenes) ? chapter.scenes : [];
|
||||
const scene = scenes.find((item) => item.sceneId === sceneId);
|
||||
if (scene) {
|
||||
return scene.revisionStatus || "";
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
};
|
||||
|
||||
const setText = (element, value) => {
|
||||
if (element) {
|
||||
element.textContent = value;
|
||||
@ -477,10 +589,14 @@
|
||||
}
|
||||
};
|
||||
|
||||
const fetchJson = async (url) => {
|
||||
const fetchJson = async (url, options = {}) => {
|
||||
const response = await window.fetch(url, {
|
||||
credentials: "same-origin",
|
||||
headers: { Accept: "application/json" }
|
||||
...options,
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
...(options.headers || {})
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@ -490,6 +606,72 @@
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
const postJson = (url, body) => fetchJson(url, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
|
||||
const refreshPlotDirectorScene = async () => {
|
||||
const book = selectedBook();
|
||||
if (!book) {
|
||||
resetPlotDirectorScene("Select a PlotDirector book.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!detectedChapterTitle || !detectedSceneTitle) {
|
||||
resetPlotDirectorScene("No scene detected in Word.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (refreshPlotDirectorSceneButton) {
|
||||
refreshPlotDirectorSceneButton.disabled = true;
|
||||
refreshPlotDirectorSceneButton.textContent = "Refreshing...";
|
||||
}
|
||||
const restorePlotDirectorSceneButton = () => {
|
||||
if (refreshPlotDirectorSceneButton) {
|
||||
refreshPlotDirectorSceneButton.disabled = false;
|
||||
refreshPlotDirectorSceneButton.textContent = "Refresh PlotDirector Scene";
|
||||
}
|
||||
};
|
||||
resetPlotDirectorScene("Resolving PlotDirector scene...");
|
||||
|
||||
let resolvedSceneId = null;
|
||||
try {
|
||||
const resolveResponse = await postJson(`/api/word-companion/books/${book.bookId}/resolve-scene`, {
|
||||
chapterTitle: detectedChapterTitle,
|
||||
sceneTitle: detectedSceneTitle
|
||||
});
|
||||
|
||||
if (!resolveResponse?.matched || !resolveResponse.sceneId) {
|
||||
resetPlotDirectorScene("Scene not found in PlotDirector.");
|
||||
restorePlotDirectorSceneButton();
|
||||
return;
|
||||
}
|
||||
|
||||
resolvedSceneId = resolveResponse.sceneId;
|
||||
} catch {
|
||||
resetPlotDirectorScene("Unable to resolve scene.");
|
||||
restorePlotDirectorSceneButton();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const companionData = await fetchJson(`/api/word-companion/scenes/${resolvedSceneId}/companion`);
|
||||
try {
|
||||
companionData.revisionStatus = await findSceneRevisionStatus(book.bookId, resolvedSceneId);
|
||||
} catch {
|
||||
companionData.revisionStatus = "";
|
||||
}
|
||||
setPlotDirectorSceneStatus("Linked to PlotDirector");
|
||||
renderCompanionData(companionData);
|
||||
} catch {
|
||||
resetPlotDirectorScene("Unable to load PlotDirector scene data.");
|
||||
} finally {
|
||||
restorePlotDirectorSceneButton();
|
||||
}
|
||||
};
|
||||
|
||||
const loadBooks = async (projectId, preferredBookId) => {
|
||||
if (!projectId) {
|
||||
books = [];
|
||||
@ -497,11 +679,13 @@
|
||||
setBookMessage("");
|
||||
writeStoredId(storageKeys.bookId, null);
|
||||
updateSummary();
|
||||
resetPlotDirectorScene("Select a PlotDirector book.");
|
||||
return;
|
||||
}
|
||||
|
||||
resetSelect(bookSelect, "Loading books...");
|
||||
setBookMessage("");
|
||||
resetPlotDirectorScene("Select a PlotDirector book.");
|
||||
|
||||
try {
|
||||
const data = await fetchJson(`/api/word-companion/projects/${projectId}/books`);
|
||||
@ -524,12 +708,14 @@
|
||||
writeStoredId(storageKeys.bookId, null);
|
||||
}
|
||||
updateSummary();
|
||||
resetPlotDirectorScene(selectedBook() ? "No scene detected in Word." : "Select a PlotDirector book.");
|
||||
} catch {
|
||||
books = [];
|
||||
resetSelect(bookSelect, "Unable to load books.");
|
||||
setBookMessage("Unable to load books.");
|
||||
writeStoredId(storageKeys.bookId, null);
|
||||
updateSummary();
|
||||
resetPlotDirectorScene("Select a PlotDirector book.");
|
||||
}
|
||||
};
|
||||
|
||||
@ -581,6 +767,7 @@
|
||||
const projectId = Number.parseInt(projectSelect.value || "", 10);
|
||||
writeStoredId(storageKeys.projectId, Number.isInteger(projectId) && projectId > 0 ? projectId : null);
|
||||
writeStoredId(storageKeys.bookId, null);
|
||||
resetPlotDirectorScene("Select a PlotDirector book.");
|
||||
await loadBooks(projectId, null);
|
||||
});
|
||||
|
||||
@ -588,9 +775,11 @@
|
||||
const bookId = Number.parseInt(bookSelect.value || "", 10);
|
||||
writeStoredId(storageKeys.bookId, Number.isInteger(bookId) && bookId > 0 ? bookId : null);
|
||||
updateSummary();
|
||||
resetPlotDirectorScene(selectedBook() ? "No scene detected in Word." : "Select a PlotDirector book.");
|
||||
});
|
||||
|
||||
refreshDocumentButton?.addEventListener("click", refreshDocumentStructure);
|
||||
refreshPlotDirectorSceneButton?.addEventListener("click", refreshPlotDirectorScene);
|
||||
runDiagnosticsButton?.addEventListener("click", runDiagnostics);
|
||||
|
||||
if (isAuthenticated) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user