import { findTask, onEach, state } from "./state.js"; type Saved = { composer: Record; checked: Record; texts: Record; }; const composerTexts = new Map(); const composerImages = new Map(); type Send = { key: string; text: string; images: number }; const sending = new Set(); const lostImages = new Map(); const checked = new Map(); const texts = new Map(); let storeKey: string | undefined; let timer = 0; export function loadDrafts(): void { storeKey = `byre.drafts.${state.me!.login}`; const saved = readSaved(storeKey); for (const [key, d] of Object.entries(saved.composer)) { if (d.text) { composerTexts.set(key, d.text); } if (d.images > 0) { lostImages.set(key, d.images); } } for (const [id, opts] of Object.entries(saved.checked)) { checked.set(id, opts); } for (const [id, text] of Object.entries(saved.texts)) { texts.set(id, text); } prune(); onEach(["tasks", "inbox"], prune); addEventListener("pagehide", flushDrafts); } function readSaved(key: string): Saved { const out: Saved = { composer: {}, checked: {}, texts: {} }; let raw: unknown; try { raw = JSON.parse(localStorage.getItem(key) ?? "{}"); } catch (err) { console.error("read drafts", err); return out; } if (!isRecord(raw)) { return out; } for (const [k, v] of Object.entries(isRecord(raw["composer"]) ? raw["composer"] : {})) { if (isRecord(v) && typeof v["text"] === "string" && typeof v["images"] === "number") { out.composer[k] = { text: v["text"], images: v["images"] }; } } for (const [k, v] of Object.entries(isRecord(raw["checked"]) ? raw["checked"] : {})) { if (Array.isArray(v) && v.every((o) => typeof o === "string")) { out.checked[k] = v as string[]; } } for (const [k, v] of Object.entries(isRecord(raw["texts"]) ? raw["texts"] : {})) { if (typeof v === "string") { out.texts[k] = v; } } return out; } function isRecord(v: unknown): v is Record { return typeof v === "object" && v !== null && !Array.isArray(v); } function prune(): void { let changed = false; for (const map of [composerTexts, composerImages, lostImages]) { for (const key of map.keys()) { const taskId = key.replace(/^message:/, ""); if (taskId !== "lead" && findTask(taskId)?.status !== "active") { map.delete(key); changed = true; } } } for (const map of [checked, texts]) { for (const id of map.keys()) { if (!state.inbox.some((i) => i.id === id)) { map.delete(id); changed = true; } } } if (changed) { save(); } } function save(): void { clearTimeout(timer); timer = setTimeout(flushDrafts, 400); } export function flushDrafts(): void { clearTimeout(timer); timer = 0; if (!storeKey) { return; } const out: Saved = { composer: {}, checked: Object.fromEntries(checked), texts: Object.fromEntries(texts) }; const keys = new Set([...composerTexts.keys(), ...composerImages.keys(), ...[...sending].map((s) => s.key), ...lostImages.keys()]); for (const key of keys) { const inFlight = [...sending].filter((s) => s.key === key); const text = [...inFlight.map((s) => s.text), composerTexts.get(key) ?? ""].filter((t) => t).join("\n\n"); const images = (composerImages.get(key) ?? 0) + inFlight.reduce((n, s) => n + s.images, 0) + (lostImages.get(key) ?? 0); if (text || images > 0) { out.composer[key] = { text, images }; } } try { localStorage.setItem(storeKey, JSON.stringify(out)); } catch (err) { console.error("save drafts", err); } } export function composerText(key: string): string { return composerTexts.get(key) ?? ""; } export function setComposerText(key: string, text: string): void { if (text) { composerTexts.set(key, text); } else { composerTexts.delete(key); } save(); } export function setComposerImages(key: string, n: number): void { if (n > 0) { composerImages.set(key, n); } else { composerImages.delete(key); } save(); } export function lostImageCount(key: string): number { return lostImages.get(key) ?? 0; } export function forgetLostImages(key: string): void { lostImages.delete(key); save(); } export function startSend(key: string, text: string, images: number): Send { const send = { key, text, images }; sending.add(send); composerTexts.delete(key); return send; } export function endSend(send: Send, ok: boolean): void { sending.delete(send); if (ok) { lostImages.delete(send.key); } else { const text = [send.text, composerTexts.get(send.key) ?? ""].filter((t) => t).join("\n\n"); if (text) { composerTexts.set(send.key, text); } } flushDrafts(); } export function checkedAnswer(id: string): string[] { return checked.get(id) ?? []; } export function setCheckedAnswer(id: string, options: string[]): void { checked.set(id, options); save(); } export function hasTextAnswer(id: string): boolean { return texts.has(id); } export function textAnswer(id: string): string | undefined { return texts.get(id); } export function setTextAnswer(id: string, text: string): void { texts.set(id, text); save(); } export function dropTextAnswer(id: string): void { texts.delete(id); save(); } export function clearAnswer(id: string): void { checked.delete(id); texts.delete(id); flushDrafts(); }