feat: Doing more polishing (M8b)
This commit is contained in:
+48
-13
@@ -1,26 +1,34 @@
|
||||
// Centre folder: the weekly decision card as a paper document with two rubber
|
||||
// stamps. Also shows the idle and end-of-term messages.
|
||||
// Centre folder: the in-tray. The oldest waiting proposal is the document on top, answered with
|
||||
// two rubber stamps; the others show as a pile of sheets under it. 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
|
||||
const STAMP_MS = 600; // how long the stamp animation plays before the paper leaves the tray
|
||||
const LEAVE_MS = 320; // and how long the paper takes to slide away
|
||||
|
||||
export function createDecision(root, { cardsById, characters, onAnswer, onProfile, keysEnabled = () => true }) {
|
||||
export function createDecision(root, { cardsById, characters, inboxMax = 10, onAnswer, onStamp = () => {}, onProfile, keysEnabled = () => true }) {
|
||||
root.innerHTML = `
|
||||
<h2 class="inbox__tab">${t("inbox.title")}</h2>
|
||||
<h2 class="inbox__tab">${t("inbox.title")} <span class="inbox__count" data-count></span></h2>
|
||||
<p class="inbox__full" role="status" hidden>${t("inbox.full")}</p>
|
||||
<div class="inbox__body"></div>`;
|
||||
const body = $(root, ".inbox__body");
|
||||
const countEl = $(root, "[data-count]");
|
||||
const fullEl = $(root, ".inbox__full");
|
||||
|
||||
let shown = "";
|
||||
let locked = false;
|
||||
let latest = null;
|
||||
let lastCount = 0;
|
||||
let timers = [];
|
||||
|
||||
function choose(choice) {
|
||||
if (locked) return;
|
||||
const card = $(body, ".card");
|
||||
if (!card) return;
|
||||
const id = latest?.inbox[0]?.id;
|
||||
if (!card || !id) return;
|
||||
locked = true;
|
||||
card.dataset.stamped = choice;
|
||||
for (const b of card.querySelectorAll("button")) b.disabled = true;
|
||||
@@ -28,7 +36,9 @@ export function createDecision(root, { cardsById, characters, onAnswer, onProfil
|
||||
stamp.className = `card__stamp card__stamp--${choice}`;
|
||||
stamp.textContent = t(choice === "yes" ? "card.approved" : "card.rejected");
|
||||
card.append(stamp);
|
||||
setTimeout(() => onAnswer(choice), STAMP_MS);
|
||||
onStamp(choice);
|
||||
timers.push(setTimeout(() => card.classList.add("card--leave"), STAMP_MS));
|
||||
timers.push(setTimeout(() => onAnswer(choice, id), STAMP_MS + LEAVE_MS));
|
||||
}
|
||||
|
||||
body.addEventListener("click", (e) => {
|
||||
@@ -40,7 +50,7 @@ export function createDecision(root, { cardsById, characters, onAnswer, onProfil
|
||||
|
||||
// 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 (!keysEnabled() || !latest?.inbox.length) 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;
|
||||
@@ -61,10 +71,10 @@ export function createDecision(root, { cardsById, characters, onAnswer, onProfil
|
||||
|
||||
function cardHtml(card, state) {
|
||||
const from = characters[card.sender];
|
||||
const date = dateParts(dateOfDay(state.startDate, state.pendingCard.day).iso);
|
||||
const date = dateParts(dateOfDay(state.startDate, state.inbox[0].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)}">
|
||||
<article class="card card--enter card--${esc(card.doc)}" data-pile="${Math.min(3, state.inbox.length - 1)}">
|
||||
<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>
|
||||
@@ -80,15 +90,34 @@ export function createDecision(root, { cardsById, characters, onAnswer, onProfil
|
||||
</article>`;
|
||||
}
|
||||
|
||||
// The tab counts the proposals waiting; the pile under the top paper grows with them.
|
||||
function updateTray(state) {
|
||||
const n = state.inbox.length;
|
||||
countEl.textContent = n ? `${n}/${inboxMax}` : "";
|
||||
countEl.classList.toggle("is-warn", n >= inboxMax - 2);
|
||||
if (n > lastCount) {
|
||||
countEl.classList.remove("is-bump");
|
||||
void countEl.offsetWidth; // restart the animation
|
||||
countEl.classList.add("is-bump");
|
||||
}
|
||||
lastCount = n;
|
||||
fullEl.hidden = !(state.pending.inboxFull && !state.over);
|
||||
const card = $(body, ".card");
|
||||
if (card) card.dataset.pile = Math.min(3, Math.max(0, n - 1));
|
||||
}
|
||||
|
||||
function update(state) {
|
||||
latest = state;
|
||||
updateTray(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}`;
|
||||
else if (state.inbox.length) kind = `card:${state.inbox[0].id}`;
|
||||
if (kind === shown) return;
|
||||
shown = kind;
|
||||
locked = false;
|
||||
timers.forEach(clearTimeout);
|
||||
timers = [];
|
||||
body.classList.toggle("is-card", kind.startsWith("card:"));
|
||||
|
||||
if (kind === "profile") {
|
||||
@@ -105,9 +134,15 @@ export function createDecision(root, { cardsById, characters, onAnswer, onProfil
|
||||
} 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);
|
||||
body.innerHTML = cardHtml(cardsById.get(state.inbox[0].id), state);
|
||||
}
|
||||
}
|
||||
|
||||
return { update, destroy: () => document.removeEventListener("keydown", onKey) };
|
||||
return {
|
||||
update,
|
||||
destroy: () => {
|
||||
timers.forEach(clearTimeout);
|
||||
document.removeEventListener("keydown", onKey);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user