PlotDirector/PlotLine/wwwroot/js/word-companion-presence.js

81 lines
3.6 KiB
JavaScript

(() => {
const roots = [...document.querySelectorAll("[data-word-companion-presence]")]
.filter((node) => node.dataset.wordCompanionPresence !== "false");
if (roots.length === 0 || !window.signalR) {
return;
}
const labelFor = (status) => (status?.isConnected || status?.IsConnected ? "Connected" : status?.status || status?.Status || "Offline");
const field = (status, camel, pascal) => status?.[camel] ?? status?.[pascal] ?? null;
const formatTime = (value) => {
if (!value) {
return "No heartbeat yet";
}
const date = new Date(value);
return Number.isNaN(date.getTime()) ? "No heartbeat yet" : `Last heartbeat ${date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}`;
};
const applyStatus = (status) => {
const connected = !!field(status, "isConnected", "IsConnected");
const version = field(status, "companionVersion", "CompanionVersion") || "Waiting";
const documentName = field(status, "currentDocumentName", "CurrentDocumentName") || "No document reported yet";
const label = connected ? "Connected" : labelFor(status);
const title = [
`Word Companion: ${label}`,
version && version !== "Waiting" ? `Version ${version}` : "",
documentName && documentName !== "No document reported yet" ? documentName : "",
formatTime(field(status, "lastHeartbeatUtc", "LastHeartbeatUtc"))
].filter(Boolean).join("\n");
for (const root of roots) {
root.classList.toggle("is-connected", connected);
root.classList.toggle("is-offline", !connected);
root.dataset.wordCompanionStatus = label;
root.title = title;
}
document.querySelectorAll("[data-word-companion-status-label]").forEach((node) => {
node.textContent = label;
});
document.querySelectorAll("[data-word-companion-waiting-status]").forEach((node) => {
node.textContent = connected ? "Word Companion Connected" : "Word Companion disconnected. Waiting for reconnection...";
});
document.querySelectorAll("[data-word-companion-connected-copy]").forEach((node) => {
node.textContent = connected
? "Your Companion is connected. Continue when you are ready."
: "Open Word and start the Companion. This page will update automatically.";
});
document.querySelectorAll("[data-word-companion-version]").forEach((node) => {
node.textContent = version;
});
document.querySelectorAll("[data-word-companion-document]").forEach((node) => {
node.textContent = documentName;
});
document.querySelectorAll("[data-word-companion-dashboard-title]").forEach((node) => {
node.textContent = connected ? "Word Companion Connected" : "Connect your Word Companion";
});
document.querySelectorAll("[data-word-companion-dashboard-description]").forEach((node) => {
node.textContent = connected
? "Your Word Companion is ready for this story setup."
: "Open Word, start the Companion, and sign in with your PlotDirector account.";
});
};
const connection = new signalR.HubConnectionBuilder()
.withUrl("/hubs/word-companion-follow")
.withAutomaticReconnect()
.build();
connection.on("WordCompanionPresenceChanged", applyStatus);
connection.onreconnecting(() => applyStatus({ status: "Connecting", isConnected: false }));
connection.onreconnected(() => connection.invoke("WatchCompanionPresence").then(applyStatus).catch(() => {}));
connection.onclose(() => applyStatus({ status: "Offline", isConnected: false }));
connection.start()
.then(() => connection.invoke("WatchCompanionPresence"))
.then(applyStatus)
.catch(() => applyStatus({ status: "Offline", isConnected: false }));
})();