(() => { const root = document.querySelector("[data-writing-workspace-launcher]"); const workspace = window.PlotDirectorWritingWorkspace; if (!root || !workspace) { return; } const statusNodes = { book: root.querySelector("[data-writing-workspace-book]"), word: root.querySelector("[data-writing-workspace-word]"), context: root.querySelector("[data-writing-workspace-context]"), intent: root.querySelector("[data-writing-workspace-intent]"), layout: root.querySelector("[data-writing-workspace-layout]"), message: root.querySelector("[data-writing-workspace-message]") }; const startButton = root.querySelector("[data-start-writing-workspace]"); const detectDisplaysButton = root.querySelector("[data-detect-writing-displays]"); const contextDisplaySelect = root.querySelector("[data-writing-context-display]"); const intentDisplaySelect = root.querySelector("[data-writing-intent-display]"); const contextButton = root.querySelector("[data-open-writing-context]"); const intentButton = root.querySelector("[data-open-writing-intent]"); const focusContextButton = root.querySelector("[data-focus-writing-context]"); const focusIntentButton = root.querySelector("[data-focus-writing-intent]"); const fallbackScope = workspace.scopeFrom({ projectID: root.dataset.projectId, bookID: root.dataset.bookId }); let activeScope = { ...fallbackScope }; let latestStatus = null; let latestScreenInfo = null; const text = (node, value) => { if (node) node.textContent = value; }; const statusText = (status, role) => { const match = (status?.windows || []).find((windowPresence) => String(windowPresence.writingRole || windowPresence.WritingRole || "").toLowerCase() === role.toLowerCase() && (!activeScope.bookID || Number(windowPresence.bookID || windowPresence.BookID) === activeScope.bookID)); if (!match) return "Not open"; const id = match.browserWindowID || match.BrowserWindowID || ""; return id === workspace.getBrowserWindowId() ? "This window" : "Open"; }; const setMessage = (message) => text(statusNodes.message, message || ""); const selectedDisplayPreferences = () => ({ contextDisplay: contextDisplaySelect?.value || "left", intentDisplay: intentDisplaySelect?.value || "right" }); const displayLabel = (display, fallback) => { if (!display) return fallback; const position = Number.isFinite(display.left) ? `${display.left}, ${display.top}` : ""; return `${display.label || fallback}${position ? ` (${position})` : ""}`; }; const updateDisplayOptions = (screenInfo) => { latestScreenInfo = screenInfo; const screens = screenInfo?.screens || []; if (!contextDisplaySelect || !intentDisplaySelect || !screens.length) { return; } const currentContext = contextDisplaySelect.value; const currentIntent = intentDisplaySelect.value; const baseContext = [ ["left", "Left display"], ["primary", "Primary display"], ["right", "Right display"] ]; const baseIntent = [ ["right", "Right display"], ["primary", "Primary display"], ["left", "Left display"] ]; const physicalOptions = screens.map((screen, index) => [ screen.key, `${index + 1}: ${displayLabel(screen, "Display")}` ]); const fill = (select, options, previous) => { select.replaceChildren(); [...options, ...physicalOptions].forEach(([value, label]) => { const option = document.createElement("option"); option.value = value; option.textContent = label; select.append(option); }); if ([...select.options].some((option) => option.value === previous)) { select.value = previous; } }; fill(contextDisplaySelect, baseContext, currentContext); fill(intentDisplaySelect, baseIntent, currentIntent); }; const render = (status = latestStatus) => { latestStatus = status; activeScope = workspace.statusScope(status, activeScope); text(statusNodes.book, activeScope.bookID ? `Book ${activeScope.bookID}` : "Waiting for book"); text(statusNodes.word, status?.wordCompanionConnected || status?.WordCompanionConnected ? "Connected" : "Not connected"); text(statusNodes.context, statusText(status, "Context")); text(statusNodes.intent, statusText(status, "Intent")); const contextOpen = statusText(status, "Context") !== "Not open"; const intentOpen = statusText(status, "Intent") !== "Not open"; if (focusContextButton) focusContextButton.hidden = !contextOpen; if (focusIntentButton) focusIntentButton.hidden = !intentOpen; if (contextButton) contextButton.textContent = contextOpen ? "Focus Context" : "Open Context"; if (intentButton) intentButton.textContent = intentOpen ? "Focus Intent" : "Open Intent"; }; const refresh = async () => { try { render(await workspace.watchWorkspace(activeScope)); } catch (error) { setMessage("Writing workspace presence is unavailable."); console.debug("Writing workspace presence unavailable.", error); } }; const openOrFocus = async (role) => { const scope = workspace.statusScope(latestStatus, activeScope); if (!scope.bookID) { setMessage("Open a linked Word manuscript or focus a book before opening writing windows."); return; } const screenInfo = latestScreenInfo || await workspace.getScreenLayout(false, selectedDisplayPreferences()); updateDisplayOptions(screenInfo); const targetDisplay = role === workspace.roles.context ? screenInfo.layout?.contextDisplay : screenInfo.layout?.intentDisplay; await workspace.focusRole(role, scope, targetDisplay); setMessage(`${role} requested on ${displayLabel(targetDisplay, "the selected display")}. If it is behind Word, use the taskbar once; future clicks should reuse it.`); await refresh(); }; detectDisplaysButton?.addEventListener("click", async () => { detectDisplaysButton.disabled = true; setMessage("Detecting displays..."); try { const screenInfo = await workspace.getScreenLayout(true, selectedDisplayPreferences()); updateDisplayOptions(screenInfo); const count = screenInfo.screens?.length || 0; text(statusNodes.layout, screenInfo.layout?.mode || "single-screen"); if (count > 0) { setMessage(`Detected ${count} display${count === 1 ? "" : "s"}. Choose target displays, then start or reapply the workspace.`); } else if (screenInfo.permission === "denied") { setMessage("Display permission was denied. PlotDirector will reuse named windows, but your browser may open them on the current monitor."); } else { setMessage("Display detection is unavailable in this browser. PlotDirector will reuse named windows without monitor placement."); } } catch (error) { console.debug("Display detection failed.", error); setMessage("Display detection failed. The workspace can still open using named windows."); } finally { detectDisplaysButton.disabled = false; } }); startButton?.addEventListener("click", async () => { startButton.disabled = true; setMessage("Starting writing workspace..."); try { const result = await workspace.startWorkspace(activeScope, { requestScreens: true, displayPreferences: selectedDisplayPreferences() }); updateDisplayOptions(result.screenInfo); if (!result.ok) { setMessage(result.message || `Pop-up blocked for ${result.blocked?.join(", ") || "a writing window"}. Use the explicit open buttons.`); } else { const contextDisplay = displayLabel(result.screenInfo?.layout?.contextDisplay, "selected display"); const intentDisplay = displayLabel(result.screenInfo?.layout?.intentDisplay, "selected display"); setMessage(`Workspace requested: Context on ${contextDisplay}, Intent on ${intentDisplay}. If your browser ignores placement, use Focus instead of opening more copies.`); } text(statusNodes.layout, result.screenInfo?.layout?.mode || "single-screen"); await refresh(); } catch (error) { console.debug("Unable to start writing workspace.", error); setMessage("Writing workspace could not be started. Use the direct open buttons."); } finally { startButton.disabled = false; } }); contextButton?.addEventListener("click", () => openOrFocus(workspace.roles.context)); intentButton?.addEventListener("click", () => openOrFocus(workspace.roles.intent)); focusContextButton?.addEventListener("click", () => openOrFocus(workspace.roles.context)); focusIntentButton?.addEventListener("click", () => openOrFocus(workspace.roles.intent)); contextDisplaySelect?.addEventListener("change", () => { setMessage("Context display preference updated. Click Start / Reapply Workspace to use it."); }); intentDisplaySelect?.addEventListener("change", () => { setMessage("Intent display preference updated. Click Start / Reapply Workspace to use it."); }); document.addEventListener("plotdirector:writing-workspace-status", (event) => render(event.detail)); document.addEventListener(window.PlotDirectorStoryPosition?.eventName || "plotdirector:story-position-changed", (event) => { const position = window.PlotDirectorStoryPosition?.normalise?.(event.detail, event.detail?.source || event.detail?.Source) || event.detail; if (position?.projectId && position?.bookId) { activeScope = { projectID: position.projectId, bookID: position.bookId }; void workspace.registerWindow({ ...activeScope, writingRole: workspace.roles.launcher, displayLabel: "Writing Workspace" }).then(render).catch(() => null); } }); void workspace.registerWindow({ ...activeScope, writingRole: workspace.roles.launcher, displayLabel: "Writing Workspace" }).then(async (status) => { render(status); const screenInfo = await workspace.getScreenLayout(false, selectedDisplayPreferences()); updateDisplayOptions(screenInfo); text(statusNodes.layout, screenInfo.layout?.mode || "adaptive"); }).catch(() => null); })();