114 lines
4.4 KiB
JavaScript
114 lines
4.4 KiB
JavaScript
// Centre folder: the weekly decision card as a paper document with two rubber
|
|
// stamps. Also shows the idle and end-of-term messages.
|
|
|
|
import { t, tx, dateParts } from "../i18n/i18n.js";
|
|
import { dateOfDay } from "../engine/index.js";
|
|
import { $, esc } from "./desk.js";
|
|
|
|
const STAMP_MS = 750; // how long the stamp animation plays before the answer is applied
|
|
|
|
export function createDecision(root, { cardsById, characters, onAnswer, onProfile, keysEnabled = () => true }) {
|
|
root.innerHTML = `
|
|
<h2 class="inbox__tab">${t("inbox.title")}</h2>
|
|
<div class="inbox__body"></div>`;
|
|
const body = $(root, ".inbox__body");
|
|
|
|
let shown = "";
|
|
let locked = false;
|
|
let latest = null;
|
|
|
|
function choose(choice) {
|
|
if (locked) return;
|
|
const card = $(body, ".card");
|
|
if (!card) return;
|
|
locked = true;
|
|
card.dataset.stamped = choice;
|
|
for (const b of card.querySelectorAll("button")) b.disabled = true;
|
|
const stamp = document.createElement("div");
|
|
stamp.className = `card__stamp card__stamp--${choice}`;
|
|
stamp.textContent = t(choice === "yes" ? "card.approved" : "card.rejected");
|
|
card.append(stamp);
|
|
setTimeout(() => onAnswer(choice), STAMP_MS);
|
|
}
|
|
|
|
body.addEventListener("click", (e) => {
|
|
const b = e.target.closest("button[data-choice]");
|
|
if (b) choose(b.dataset.choice);
|
|
const p = e.target.closest("button[data-pronouns]");
|
|
if (p) onProfile(p.dataset.pronouns);
|
|
});
|
|
|
|
// The panel is rebuilt on language changes and new games, so its key listener must be removable.
|
|
const onKey = (e) => {
|
|
if (!keysEnabled() || !latest?.pendingCard) return;
|
|
if (latest.pending.review || latest.pending.profile || latest.pending.alert) return;
|
|
if (e.ctrlKey || e.metaKey || e.altKey) return;
|
|
if (e.target.closest?.("input, textarea, select")) return;
|
|
const k = e.key.toLowerCase();
|
|
if (k === "y") choose("yes");
|
|
else if (k === "n") choose("no");
|
|
};
|
|
document.addEventListener("keydown", onKey);
|
|
|
|
function option(card, side) {
|
|
const label = side === "yes" ? t("card.approved") : t("card.rejected");
|
|
return `
|
|
<button class="stamp-btn stamp-btn--${side}" data-choice="${side}">
|
|
<span class="stamp-btn__label">${esc(tx(card[side].label))}</span>
|
|
<span class="stamp-btn__ink">${esc(label)}</span>
|
|
</button>`;
|
|
}
|
|
|
|
function cardHtml(card, state) {
|
|
const from = characters[card.sender];
|
|
const date = dateParts(dateOfDay(state.startDate, state.pendingCard.day).iso);
|
|
const paragraphs = tx(card.body).split("\n\n").map((p) => `<p>${esc(p)}</p>`).join("");
|
|
return `
|
|
<article class="card card--${esc(card.doc)}">
|
|
<header class="card__head">
|
|
<span class="card__kind">${t("kind." + card.doc)}</span>
|
|
<span class="card__date">${esc(date.day)} ${esc(date.month)} ${esc(date.year)}</span>
|
|
</header>
|
|
<div class="card__from">
|
|
<span class="seal">${esc(from.seal)}</span>
|
|
<span><b>${esc(tx(from.name))}</b><small>${esc(tx(from.role))}</small></span>
|
|
</div>
|
|
<h3 class="card__title">${esc(tx(card.title))}</h3>
|
|
<div class="card__body">${paragraphs}</div>
|
|
<div class="card__choices">${option(card, "yes")}${option(card, "no")}</div>
|
|
<p class="card__keys">${t("card.keys")}</p>
|
|
</article>`;
|
|
}
|
|
|
|
function update(state) {
|
|
latest = state;
|
|
let kind = "idle";
|
|
if (state.over) kind = "over";
|
|
else if (state.pending.profile) kind = "profile";
|
|
else if (state.pendingCard) kind = `card:${state.pendingCard.id}`;
|
|
if (kind === shown) return;
|
|
shown = kind;
|
|
locked = false;
|
|
body.classList.toggle("is-card", kind.startsWith("card:"));
|
|
|
|
if (kind === "profile") {
|
|
body.innerHTML = `
|
|
<h3>${t("profile.title")}</h3>
|
|
<p>${t("profile.lead")}</p>
|
|
<p class="profile__ask">${t("profile.body")}</p>
|
|
<div class="profile__choices">
|
|
${["he", "she", "they"].map((p) => `<button class="brass brass--wide" data-pronouns="${p}">${t("profile." + p)}</button>`).join("")}
|
|
</div>
|
|
<p class="profile__note">${t("profile.note")}</p>`;
|
|
} else if (kind === "idle") {
|
|
body.innerHTML = `<p class="inbox__idle">${t("inbox.idle")}</p>`;
|
|
} else if (kind === "over") {
|
|
body.innerHTML = `<h3>${t("inbox.overTitle")}</h3><p>${t("inbox.overBody")}</p>`;
|
|
} else {
|
|
body.innerHTML = cardHtml(cardsById.get(state.pendingCard.id), state);
|
|
}
|
|
}
|
|
|
|
return { update, destroy: () => document.removeEventListener("keydown", onKey) };
|
|
}
|