PlotDirector/PlotLine/wwwroot/js/writing-context.js

166 lines
5.6 KiB
JavaScript

(() => {
const root = document.querySelector("[data-writing-context]");
if (!root) {
return;
}
const contentHost = root.querySelector("[data-writing-context-content]");
const statusText = root.querySelector("[data-writing-context-status-text]");
const endpoint = root.dataset.contextUrl || "/Writing/Context/Scene";
const tabStorageKey = "plotdirector.writingContext.activeTab";
const scopedProjectId = Number.parseInt(root.dataset.scopeProjectId || "", 10);
const scopedBookId = Number.parseInt(root.dataset.scopeBookId || "", 10);
let currentSceneId = Number.parseInt(contentHost?.querySelector("[data-writing-context-fragment]")?.dataset.sceneId || "", 10);
let loadingSceneId = null;
const setStatus = (value) => {
if (statusText && value) {
statusText.textContent = value;
}
};
const activeTab = () => {
const active = contentHost?.querySelector("[data-writing-context-tab].active")?.dataset.writingContextTab;
if (active) {
return active;
}
try {
return localStorage.getItem(tabStorageKey) || "overview";
} catch {
return "overview";
}
};
const activateTab = (tabName) => {
const nextTab = tabName || "overview";
contentHost?.querySelectorAll("[data-writing-context-tab]").forEach((button) => {
const active = button.dataset.writingContextTab === nextTab;
button.classList.toggle("active", active);
button.setAttribute("aria-selected", String(active));
});
contentHost?.querySelectorAll("[data-writing-context-panel]").forEach((panel) => {
const active = panel.dataset.writingContextPanel === nextTab;
panel.classList.toggle("active", active);
panel.hidden = !active;
});
try {
localStorage.setItem(tabStorageKey, nextTab);
} catch {
// Remembering the tab is a convenience only.
}
};
const wireFragment = () => {
activateTab(activeTab());
const fragment = contentHost?.querySelector("[data-writing-context-fragment]");
const fragmentStatus = fragment?.dataset.followStatus;
if (fragmentStatus) {
setStatus(fragmentStatus);
}
const fragmentSceneId = Number.parseInt(fragment?.dataset.sceneId || "", 10);
if (Number.isInteger(fragmentSceneId) && fragmentSceneId > 0) {
currentSceneId = fragmentSceneId;
}
contentHost?.querySelectorAll("[data-writing-context-tab]").forEach((button) => {
button.addEventListener("click", () => activateTab(button.dataset.writingContextTab));
});
contentHost?.querySelector("[data-writing-context-scene-select]")?.addEventListener("change", (event) => {
const sceneId = Number.parseInt(event.target.value || "", 10);
if (!Number.isInteger(sceneId) || sceneId <= 0) {
return;
}
const fragment = contentHost?.querySelector("[data-writing-context-fragment]");
window.PlotDirectorStoryPosition?.publishChanged({
source: "WritingContext",
projectId: fragment?.dataset.projectId,
bookId: fragment?.dataset.bookId,
chapterId: fragment?.dataset.chapterId,
sceneId
}, "WritingContext");
});
};
const inScope = (position) => {
if (Number.isInteger(scopedProjectId) && scopedProjectId > 0 && position.projectId !== scopedProjectId) {
return false;
}
if (Number.isInteger(scopedBookId) && scopedBookId > 0 && position.bookId && position.bookId !== scopedBookId) {
return false;
}
return true;
};
const loadPosition = async (position) => {
if (!position || !Number.isInteger(position.sceneId) || position.sceneId <= 0 || !inScope(position)) {
return;
}
if (position.sceneId === currentSceneId || position.sceneId === loadingSceneId) {
return;
}
const tab = activeTab();
loadingSceneId = position.sceneId;
setStatus("Updating");
const url = new URL(endpoint, window.location.origin);
if (position.projectId) url.searchParams.set("projectId", position.projectId);
if (position.bookId) url.searchParams.set("bookId", position.bookId);
if (position.chapterId) url.searchParams.set("chapterId", position.chapterId);
url.searchParams.set("sceneId", position.sceneId);
try {
const response = await fetch(url, { headers: { "X-Requested-With": "XMLHttpRequest" } });
if (!response.ok) {
throw new Error(`Context request failed: ${response.status}`);
}
contentHost.innerHTML = await response.text();
currentSceneId = position.sceneId;
wireFragment();
activateTab(tab);
} catch (error) {
console.debug("Writing Context could not update.", error);
setStatus("Connected, waiting for scene");
} finally {
loadingSceneId = null;
}
};
document.addEventListener(window.PlotDirectorStoryPosition?.eventName || "plotdirector:story-position-changed", (event) => {
void loadPosition(event.detail);
});
if (window.signalR) {
const connection = new signalR.HubConnectionBuilder()
.withUrl("/hubs/word-companion-follow")
.withAutomaticReconnect()
.build();
connection.on("storyPositionChanged", (payload) => {
window.PlotDirectorStoryPosition?.publishChanged(payload, payload?.source || payload?.Source || "WordCompanion");
});
connection.start()
.then(() => {
if (!currentSceneId) {
setStatus("Connected, waiting for scene");
}
})
.catch((error) => {
console.debug("Writing Context follow connection unavailable.", error);
setStatus("Word not connected");
});
} else {
setStatus("Word not connected");
}
wireFragment();
})();