summaryrefslogtreecommitdiffstats
path: root/web/play/src/dom.ts
diff options
context:
space:
mode:
Diffstat (limited to 'web/play/src/dom.ts')
-rw-r--r--web/play/src/dom.ts104
1 files changed, 104 insertions, 0 deletions
diff --git a/web/play/src/dom.ts b/web/play/src/dom.ts
new file mode 100644
index 0000000..d8ed212
--- /dev/null
+++ b/web/play/src/dom.ts
@@ -0,0 +1,104 @@
1export function byId<T extends HTMLElement = HTMLElement>(id: string): T {
2 const node = document.getElementById(id);
3 if (!node) throw new Error(`missing #${id}`);
4 return node as T;
5}
6
7export function node<K extends keyof HTMLElementTagNameMap>(
8 tag: K,
9 className?: string,
10 text?: string,
11): HTMLElementTagNameMap[K] {
12 const element = document.createElement(tag);
13 if (className) element.className = className;
14 if (text !== undefined) element.textContent = text;
15 return element;
16}
17
18export function clear(element: HTMLElement): void {
19 element.replaceChildren();
20}
21
22export function groupDigits(amount: number): string {
23 const negative = amount < 0;
24 const digits = String(Math.abs(Math.trunc(amount)));
25 let grouped = "";
26 for (let index = 0; index < digits.length; index += 1) {
27 if (index > 0 && (digits.length - index) % 3 === 0) grouped += "\u2009";
28 grouped += digits[index];
29 }
30 return negative ? `-${grouped}` : grouped;
31}
32
33export function money(amount: number | null | undefined): string {
34 if (amount === null || amount === undefined || Number.isNaN(amount)) return "— Kr";
35 return `${groupDigits(amount)} Kr`;
36}
37
38export function signedMoney(amount: number): string {
39 return `${amount < 0 ? "-" : "+"}${money(Math.abs(amount))}`;
40}
41
42export function toast(text: string, tone: "info" | "good" | "bad" = "info"): void {
43 const root = document.getElementById("toasts");
44 if (!root || !text) return;
45 const item = node("div", `toast ${tone}`, text);
46 root.append(item);
47 window.setTimeout(() => {
48 item.style.opacity = "0";
49 item.style.transition = "opacity 220ms ease";
50 window.setTimeout(() => item.remove(), 240);
51 }, 4200);
52 while (root.children.length > 4) root.firstElementChild?.remove();
53}
54
55export function toneFor(text: string): "info" | "good" | "bad" {
56 const lowered = text.toLowerCase();
57 if (
58 text.startsWith("ERROR") ||
59 lowered.includes("failed") ||
60 lowered.includes("lost") ||
61 lowered.includes("invalid") ||
62 lowered.includes("forfeit")
63 ) {
64 return "bad";
65 }
66 if (
67 text.startsWith("Logged in") ||
68 text.startsWith("Account created") ||
69 lowered.includes("won") ||
70 lowered.includes("cashed")
71 ) {
72 return "good";
73 }
74 return "info";
75}
76
77export async function sha256Hex(text: string): Promise<string> {
78 const bytes = new TextEncoder().encode(text);
79 const digest = await crypto.subtle.digest("SHA-256", bytes);
80 return [...new Uint8Array(digest)]
81 .map((byte) => byte.toString(16).padStart(2, "0"))
82 .join("");
83}
84
85export function shortHash(hash: string, keep = 10): string {
86 if (hash.length <= keep * 2) return hash;
87 return `${hash.slice(0, keep)}…${hash.slice(-keep)}`;
88}
89
90export function formatTime(raw: string): string {
91 // SQLite stores `YYYY-MM-DD HH:MM:SS` in UTC.
92 const parsed = Date.parse(raw.includes("T") ? raw : raw.replace(" ", "T") + "Z");
93 if (Number.isNaN(parsed)) return raw;
94 return new Date(parsed).toLocaleString();
95}
96
97export function cardFace(card: string): { rank: string; suit: string; red: boolean } {
98 if (card === "?" || !card) return { rank: "?", suit: "", red: false };
99 const suit = card.slice(-1);
100 const rank = card.slice(0, -1);
101 const glyph =
102 suit === "C" ? "♣" : suit === "D" ? "♦" : suit === "H" ? "♥" : suit === "S" ? "♠" : suit;
103 return { rank, suit: glyph, red: suit === "D" || suit === "H" };
104}