annotate mrjunejune/src/jrpg/jrpg.js @ 273:e02e2036ef84 default tip

add Layer 2 JRPG component system Add reusable content and window modals, an isolated component sandbox, shared cyberpunk scroll areas, production-safe cache freshness, and server-rendered JRPG panel state. Co-authored-by: Copilot <[email protected]>
author MrJuneJune <me@mrjunejune.com>
date Sat, 08 Aug 2026 02:08:08 -0700
parents 41a49c29a28f
children
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1 const CHARACTER_STATES = Object.freeze({
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2 default: {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3 alt: "Epi the Shiba Inu standing happily",
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
4 src: "/public/jrpg/shiba-default.webp",
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
5 },
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
6 sad: {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
7 alt: "Epi the Shiba Inu sitting sadly",
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
8 src: "/public/jrpg/shiba-sad.webp",
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
9 },
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
10 thinking: {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
11 alt: "Epi the Shiba Inu tilting her head in thought",
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
12 src: "/public/jrpg/shiba-thinking.webp",
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
13 },
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
14 });
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
15
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
16 const PREVIEWS = Object.freeze({
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
17 resume: {
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
18 copy: "Member of Technical Staff and engineering leader with 10+ years building AI agent platforms and production systems across Microsoft, Meta, Google, and growth-stage companies.",
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
19 title: "Resume",
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
20 url: "/resume",
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
21 works: [
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
22 {
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
23 detail: "Career dossier",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
24 label: "Full resume",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
25 modal: "resume",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
26 url: "/resume",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
27 },
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
28 {
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
29 detail: "Led engineering",
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
30 label: "Copilot Tasks",
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
31 url: "https://www.microsoft.com/en-us/microsoft-copilot/blog/2026/02/26/copilot-tasks-from-answers-to-actions/",
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
32 },
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
33 {
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
34 detail: "Foundational platform",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
35 label: "AIX Harness / Copilot",
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
36 url: "https://www.cio.com/article/3977098/microsoft-doubles-down-on-multi-model-ai-as-it-builds-a-copilot-super-app.html",
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
37 },
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
38 {
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
39 detail: "Build 2026 products",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
40 label: "Code & Autopilot",
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
41 url: "https://news.microsoft.com/build-2026/",
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
42 },
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
43 {
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
44 detail: "Full-stack ads systems",
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
45 label: "Meta",
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
46 url: "https://www.meta.com/",
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
47 },
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
48 {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
49 detail: "Workspace platform",
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
50 label: "Google",
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
51 url: "https://www.google.com/",
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
52 },
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
53 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
54 detail: "Technical lead",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
55 label: "Warner Music Group",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
56 url: "https://www.wmg.com/",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
57 },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
58 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
59 detail: "Personal agent launch",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
60 label: "Microsoft Scout",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
61 url: "https://www.microsoft.com/en-us/microsoft-365/blog/2026/06/02/introducing-microsoft-scout-your-always-on-personal-agent/",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
62 },
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
63 ],
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
64 },
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
65 tools: {
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
66 copy: "Useful browser tools backed by first-party C, WASM, media, and document-processing systems.",
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
67 title: "Tools",
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
68 url: "/tools",
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
69 works: [
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
70 { detail: "Writing", label: "Markdown", url: "/tools/markdown_to_html" },
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
71 { detail: "Media", label: "Converter", url: "/tools/file_converter" },
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
72 { detail: "Streaming", label: "HLS Player", url: "/tools/hls_player" },
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
73 ],
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
74 },
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
75 blog: {
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
76 copy: "Technical writing about networking, rendering, performance, developer tooling, and experiments.",
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
77 title: "Blogs",
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
78 url: "/blog",
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
79 works: [
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
80 { detail: "Archive", label: "All posts", url: "/blog" },
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
81 ],
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
82 },
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
83 });
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
84
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
85 const CONVERSATION_STORAGE_KEY = "mjj-jrpg-conversation-id";
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
86 const CONVERSATION_LEGACY_KEY = "mjj-jrpg-legacy-claim";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
87 const ARCHIVE_PAGE_SIZE = 20;
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
88 const TYPING_INTERVAL_MS = 18;
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
89 const SCRIPT_LOADS = new Map();
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
90 const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
91 const VALID_PANELS = Object.freeze(["resume", "tools", "blog", "conversations"]);
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
92 const ROLE_GREETINGS = Object.freeze({
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
93 guest: "Hi, my name is Epi. June usually calls me Epi-chan. I’m here to help you navigate his personal website and learn about his work, projects, and writing.",
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
94 invited: "Hi! I’m Epi, but June usually calls me Epi-chan. Since June invited you, I can be a little more casual. I can help you explore his site, talk about his projects, or brainstorm simple ideas with you.",
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
95 admin: "Hi June! I’m Epi-chan. I’m here to help you review your personal website, think through your projects, and draft or refine public-facing ideas.",
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
96 });
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
97 const TOOL_ICON_NAMES = Object.freeze({
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
98 "/notes": "repository",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
99 "/tools/file_converter": "retry",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
100 "/tools/hls_player": "play",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
101 "/tools/latex_editor": "file",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
102 "/tools/markdown_to_html": "code",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
103 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
104
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
105 function loadScript(source) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
106 if (SCRIPT_LOADS.has(source)) return SCRIPT_LOADS.get(source);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
107 const loading = new Promise((resolve, reject) => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
108 const script = document.createElement("script");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
109 script.src = source;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
110 script.addEventListener("load", resolve, { once: true });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
111 script.addEventListener("error", () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
112 SCRIPT_LOADS.delete(source);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
113 script.remove();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
114 reject(new Error(`Unable to load ${source}`));
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
115 }, { once: true });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
116 document.head.append(script);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
117 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
118 SCRIPT_LOADS.set(source, loading);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
119 return loading;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
120 }
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
121
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
122 function greetingForSession(sessionData, hasConversations = false) {
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
123 const greeting = sessionData?.kind !== "user"
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
124 ? ROLE_GREETINGS.guest
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
125 : sessionData.role === "admin"
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
126 ? ROLE_GREETINGS.admin
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
127 : ROLE_GREETINGS.invited;
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
128 return hasConversations
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
129 ? `${greeting} We’ve talked before. Open Conversations if you’d like to continue one.`
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
130 : greeting;
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
131 }
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
132
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
133 class StreamingTextAnimator {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
134 constructor(render) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
135 this._render = render;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
136 this._displayed = "";
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
137 this._target = "";
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
138 this._timer = 0;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
139 this._waiters = [];
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
140 this._reduceMotion = window.matchMedia(
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
141 "(prefers-reduced-motion: reduce)",
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
142 ).matches;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
143 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
144
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
145 append(text) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
146 if (typeof text !== "string" || !text) return;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
147 this._target += text;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
148 this._schedule();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
149 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
150
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
151 complete(text) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
152 if (typeof text === "string") {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
153 this._target = text;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
154 if (!this._target.startsWith(this._displayed)) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
155 let shared = 0;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
156 while (
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
157 shared < this._target.length &&
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
158 shared < this._displayed.length &&
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
159 this._target[shared] === this._displayed[shared]
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
160 ) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
161 shared++;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
162 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
163 this._displayed = this._displayed.slice(0, shared);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
164 this._render(this._displayed);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
165 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
166 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
167 this._schedule();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
168 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
169
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
170 waitForIdle() {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
171 if (!this._timer && this._displayed === this._target) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
172 return Promise.resolve();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
173 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
174 return new Promise(resolve => {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
175 this._waiters.push(resolve);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
176 });
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
177 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
178
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
179 cancel() {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
180 window.clearTimeout(this._timer);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
181 this._timer = 0;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
182 this._target = this._displayed;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
183 this._settle();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
184 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
185
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
186 _schedule() {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
187 if (this._reduceMotion) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
188 this._displayed = this._target;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
189 this._render(this._displayed);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
190 this._settle();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
191 return;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
192 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
193 if (this._timer || this._displayed === this._target) return;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
194 this._timer = window.setTimeout(() => this._tick(), TYPING_INTERVAL_MS);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
195 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
196
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
197 _tick() {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
198 this._timer = 0;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
199 const remaining = this._target.length - this._displayed.length;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
200 if (remaining <= 0) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
201 this._settle();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
202 return;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
203 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
204 const amount = Math.max(1, Math.ceil(remaining / 80));
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
205 this._displayed = this._target.slice(
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
206 0,
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
207 this._displayed.length + amount,
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
208 );
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
209 this._render(this._displayed);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
210 if (this._displayed === this._target) this._settle();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
211 else this._schedule();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
212 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
213
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
214 _settle() {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
215 if (this._timer || this._displayed !== this._target) return;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
216 const waiters = this._waiters.splice(0);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
217 for (const resolve of waiters) resolve();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
218 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
219 }
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
220
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
221 class MjjJrpgCharacter extends HTMLElement {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
222 static get observedAttributes() {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
223 return ["state"];
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
224 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
225
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
226 connectedCallback() {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
227 this.render();
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
228 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
229
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
230 attributeChangedCallback() {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
231 if (this.isConnected) this.render();
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
232 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
233
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
234 render() {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
235 const image = this.querySelector("[data-character]");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
236 if (!image) return;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
237 const state = CHARACTER_STATES[this.getAttribute("state")] ||
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
238 CHARACTER_STATES.default;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
239 image.src = state.src;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
240 image.alt = state.alt;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
241 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
242 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
243
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
244 class MjjJrpgChat extends HTMLElement {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
245 connectedCallback() {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
246 this._messages = this.querySelector("[data-messages]");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
247 this._viewport = this.querySelector("[data-zen-viewport]");
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
248 this._previousButton = this.querySelector("[data-turn-previous]");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
249 this._nextButton = this.querySelector("[data-turn-next]");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
250 this._position = this.querySelector("[data-turn-position]");
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
251 this._greetingText = ROLE_GREETINGS.guest;
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
252 this._greetingAnimator = null;
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
253 this._turnSequence = 0;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
254 this._activeGroup = null;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
255 this._onNavigationClick = event => {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
256 const button = event.target.closest("button");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
257 if (!button || !this.contains(button) || button.disabled) return;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
258 if (button === this._previousButton) this._moveTurn(-1);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
259 else if (button === this._nextButton) this._moveTurn(1);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
260 };
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
261 this.addEventListener("click", this._onNavigationClick);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
262 this._refreshTurnGroups("start");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
263 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
264
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
265 disconnectedCallback() {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
266 this.removeEventListener("click", this._onNavigationClick);
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
267 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
268
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
269 appendMessage(speaker, text) {
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
270 let group = this._activeGroup || "start";
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
271 if (speaker === "June") {
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
272 this._greetingAnimator?.cancel();
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
273 this._greetingAnimator = null;
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
274 const greeting = this._messages?.querySelector("[data-greeting]");
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
275 if (greeting) this.setMessageStreaming(greeting, false);
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
276 this._turnSequence++;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
277 group = `turn-${this._turnSequence}`;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
278 this._activeGroup = group;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
279 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
280 return this._createMessage(speaker, text, group, true);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
281 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
282
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
283 setGreeting(text, animate = true) {
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
284 this._greetingText = typeof text === "string" && text
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
285 ? text
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
286 : ROLE_GREETINGS.guest;
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
287 this._showGreeting(animate);
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
288 }
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
289
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
290 _showGreeting(animate) {
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
291 if (!this._messages) return;
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
292 this._greetingAnimator?.cancel();
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
293 this._greetingAnimator = null;
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
294 this._messages.replaceChildren();
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
295 this._turnSequence = 0;
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
296 this._activeGroup = null;
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
297 const item = this._createMessage("Epi", "", "start", false);
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
298 if (!item) return;
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
299 item.dataset.greeting = "true";
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
300 this._refreshTurnGroups("start");
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
301 if (!animate) {
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
302 this.setMessageText(item, this._greetingText);
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
303 this.setMessageStreaming(item, false);
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
304 return;
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
305 }
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
306 this.setMessageStreaming(item, true);
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
307 const animator = new StreamingTextAnimator(value => {
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
308 this.setMessageText(item, value);
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
309 });
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
310 this._greetingAnimator = animator;
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
311 animator.complete(this._greetingText);
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
312 void animator.waitForIdle().then(() => {
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
313 if (this._greetingAnimator !== animator) return;
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
314 this.setMessageStreaming(item, false);
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
315 this._greetingAnimator = null;
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
316 });
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
317 }
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
318
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
319 _createMessage(speaker, text, group, select) {
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
320 if (!this._messages) return;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
321 const item = document.createElement("li");
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
322 const speakerCell = document.createElement("span");
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
323 const name = document.createElement("strong");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
324 const copy = document.createElement("p");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
325 item.className = "jrpg-message";
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
326 item.dataset.speaker = speaker;
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
327 item.dataset.turnGroup = group;
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
328 speakerCell.className = "jrpg-message-speaker";
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
329 if (speaker === "Epi") {
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
330 const avatar = document.createElement("img");
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
331 avatar.className = "jrpg-message-avatar";
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
332 avatar.src = "/public/jrpg/shiba-default.webp";
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
333 avatar.alt = "";
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
334 avatar.setAttribute("aria-hidden", "true");
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
335 speakerCell.append(avatar);
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
336 }
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
337 name.textContent = speaker;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
338 copy.textContent = text;
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
339 speakerCell.append(name);
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
340 item.append(speakerCell, copy);
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
341 this._messages.append(item);
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
342 this._refreshTurnGroups(select ? group : this._currentGroup);
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
343 return item;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
344 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
345
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
346 replaceMessages(turns) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
347 if (!this._messages) return;
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
348 this._greetingAnimator?.cancel();
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
349 this._greetingAnimator = null;
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
350 this._messages.replaceChildren();
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
351 this._turnSequence = 0;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
352 this._activeGroup = null;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
353 const visibleTurns = turns.filter(turn =>
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
354 ["user", "assistant"].includes(turn.role)
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
355 );
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
356 if (!visibleTurns.length) {
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
357 this._showGreeting(true);
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
358 return;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
359 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
360 let group = null;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
361 for (const turn of visibleTurns) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
362 if (turn.role === "user" || !group) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
363 this._turnSequence++;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
364 group = `turn-${this._turnSequence}`;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
365 this._activeGroup = group;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
366 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
367 this._createMessage(
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
368 turn.role === "user" ? "June" : "Epi",
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
369 turn.content ||
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
370 (turn.status === "aborted"
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
371 ? "Quest cancelled."
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
372 : turn.status === "failed"
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
373 ? "The quest failed."
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
374 : ""),
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
375 group,
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
376 false,
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
377 );
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
378 }
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
379 this._refreshTurnGroups(group);
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
380 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
381
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
382 startAssistantMessage() {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
383 return this.appendMessage("Epi", "");
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
384 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
385
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
386 setMessageText(item, text) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
387 const copy = item?.querySelector("p");
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
388 if (copy) copy.textContent = text;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
389 this.scrollEnd();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
390 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
391
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
392 setMessageStreaming(item, streaming) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
393 if (!item) return;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
394 if (streaming) item.dataset.streaming = "true";
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
395 else delete item.dataset.streaming;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
396 this._viewport?.setAttribute("aria-busy", String(streaming));
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
397 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
398
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
399 _refreshTurnGroups(preferredGroup) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
400 const groups = [];
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
401 for (const item of this._messages?.children || []) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
402 const group = item.dataset.turnGroup || "start";
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
403 item.dataset.turnGroup = group;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
404 if (!groups.includes(group)) groups.push(group);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
405 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
406 this._turnGroups = groups;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
407 const preferredIndex = groups.indexOf(preferredGroup);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
408 const currentIndex = groups.indexOf(this._currentGroup);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
409 this._turnIndex = preferredIndex >= 0
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
410 ? preferredIndex
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
411 : currentIndex >= 0
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
412 ? currentIndex
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
413 : Math.max(0, groups.length - 1);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
414 this._showTurn();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
415 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
416
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
417 _moveTurn(offset) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
418 const nextIndex = Math.max(
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
419 0,
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
420 Math.min(this._turnGroups.length - 1, this._turnIndex + offset),
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
421 );
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
422 if (nextIndex === this._turnIndex) return;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
423 this._turnIndex = nextIndex;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
424 this._showTurn();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
425 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
426
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
427 _showTurn() {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
428 const group = this._turnGroups?.[this._turnIndex];
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
429 this._currentGroup = group;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
430 for (const item of this._messages?.children || []) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
431 item.hidden = item.dataset.turnGroup !== group;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
432 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
433 if (this._previousButton) this._previousButton.disabled = this._turnIndex <= 0;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
434 if (this._nextButton) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
435 this._nextButton.disabled =
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
436 this._turnIndex >= (this._turnGroups?.length || 0) - 1;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
437 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
438 if (this._position) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
439 if (group === "start") {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
440 this._position.textContent = "START";
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
441 } else {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
442 const turns = this._turnGroups.filter(item => item !== "start");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
443 this._position.textContent =
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
444 `TURN ${turns.indexOf(group) + 1} / ${turns.length}`;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
445 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
446 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
447 this.scrollEnd();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
448 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
449
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
450 scrollEnd() {
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
451 requestAnimationFrame(() => {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
452 if (this._viewport) {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
453 this._viewport.scrollTop = this._viewport.scrollHeight;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
454 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
455 });
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
456 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
457 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
458
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
459 class MjjJrpgMenu extends HTMLElement {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
460 connectedCallback() {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
461 this._onClick = event => {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
462 const button = event.target.closest("button[data-preview]");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
463 if (!button || !this.contains(button)) return;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
464 for (const item of this.querySelectorAll("button[data-preview]")) {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
465 item.setAttribute("aria-pressed", String(item === button));
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
466 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
467 this.dispatchEvent(new CustomEvent("mjj-jrpg-preview-change", {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
468 bubbles: true,
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
469 detail: { selection: button.dataset.preview },
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
470 }));
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
471 };
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
472 this.addEventListener("click", this._onClick);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
473 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
474
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
475 disconnectedCallback() {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
476 this.removeEventListener("click", this._onClick);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
477 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
478 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
479
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
480 function sanitizeFetchedMain(main) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
481 for (const unsafe of main.querySelectorAll(
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
482 "embed, form, iframe, object, script, style, svg",
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
483 )) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
484 unsafe.remove();
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
485 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
486 for (const element of main.querySelectorAll("*")) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
487 for (const attribute of [...element.attributes]) {
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
488 const name = attribute.name.toLowerCase();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
489 if (name.startsWith("on") || name === "id" || name === "style") {
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
490 element.removeAttribute(attribute.name);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
491 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
492 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
493 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
494 for (const anchor of main.querySelectorAll("a")) {
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
495 const href = anchor.getAttribute("href");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
496 if (!href || anchor.getAttribute("aria-disabled") === "true") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
497 anchor.remove();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
498 continue;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
499 }
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
500 const target = new URL(
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
501 href,
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
502 window.location.href,
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
503 );
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
504 anchor.href = target.href;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
505 if (target.origin !== window.location.origin) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
506 anchor.target = "_blank";
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
507 anchor.rel = "noreferrer";
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
508 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
509 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
510 for (const image of main.querySelectorAll("img")) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
511 const source = image.getAttribute("src") || "";
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
512 if (source.startsWith("/public/") && source.endsWith(".png")) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
513 image.src = source.replace(/\.png$/i, ".webp");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
514 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
515 image.loading = "lazy";
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
516 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
517 return main;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
518 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
519
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
520 function sanitizeToolDetail(main) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
521 sanitizeFetchedMain(main);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
522 for (const textarea of main.querySelectorAll("textarea")) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
523 const sample = document.createElement("pre");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
524 sample.className = "jrpg-tool-source-sample";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
525 sample.textContent = textarea.value || textarea.textContent;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
526 textarea.replaceWith(sample);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
527 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
528 for (const inactive of main.querySelectorAll(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
529 "button, canvas, dialog, input, select, video, [hidden]",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
530 )) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
531 inactive.remove();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
532 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
533 for (const owner of main.querySelectorAll(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
534 "zen-button, zen-checkbox, zen-field, zen-input, zen-textarea",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
535 )) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
536 owner.replaceWith(...owner.childNodes);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
537 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
538 for (const label of main.querySelectorAll("label[for]")) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
539 label.removeAttribute("for");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
540 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
541 return main;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
542 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
543
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
544 class MjjJrpgPreview extends HTMLElement {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
545 connectedCallback() {
273
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
546 this._modalOwner = this.querySelector("[data-detail-modal-owner]");
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
547 this._dialog = this.querySelector("dialog");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
548 this._onDialogClose = () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
549 this._toolRequest = (this._toolRequest || 0) + 1;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
550 this.cleanupTool();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
551 };
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
552 this._onClick = event => {
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
553 const trigger = event.target.closest("[data-zen-trigger]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
554 if (trigger) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
555 if (this.dataset.selection === "resume") void this.loadResume();
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
556 if (this.dataset.selection === "blog") {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
557 this.show("blog");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
558 void this.loadBlogs();
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
559 }
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
560 if (this.dataset.selection === "tools") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
561 this.show("tools");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
562 void this.openToolWorkspace();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
563 }
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
564 return;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
565 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
566 const blogEntry = event.target.closest("[data-blog-entry]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
567 if (blogEntry) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
568 void this.loadBlogDetail(blogEntry.dataset.blogUrl);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
569 return;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
570 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
571 const latestBlog = event.target.closest("[data-latest-blog]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
572 if (latestBlog && !event.metaKey && !event.ctrlKey && !event.shiftKey) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
573 event.preventDefault();
273
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
574 this.show("blog");
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
575 this._modalOwner?.open();
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
576 void this.loadBlogDetail(latestBlog.dataset.blogUrl);
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
577 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
578 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
579 const blogArchive = event.target.closest("[data-blog-archive]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
580 if (blogArchive) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
581 event.preventDefault();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
582 this.show("blog");
273
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
583 this._modalOwner?.open();
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
584 void this.loadBlogs();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
585 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
586 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
587 const resumeModal = event.target.closest("[data-resume-modal]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
588 if (resumeModal) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
589 event.preventDefault();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
590 this.show("resume");
273
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
591 this._modalOwner?.open();
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
592 void this.loadResume();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
593 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
594 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
595 const latestTool = event.target.closest("[data-latest-tool]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
596 if (latestTool && !event.metaKey && !event.ctrlKey && !event.shiftKey) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
597 event.preventDefault();
273
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
598 this.show("tools");
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
599 this._modalOwner?.open();
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
600 void this.loadToolDetail(latestTool.dataset.toolUrl);
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
601 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
602 };
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
603 this.addEventListener("click", this._onClick);
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
604 this._dialog.addEventListener("close", this._onDialogClose);
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
605 this.show(this.dataset.selection || "resume");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
606 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
607
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
608 disconnectedCallback() {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
609 this.removeEventListener("click", this._onClick);
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
610 this._dialog?.removeEventListener("close", this._onDialogClose);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
611 this.cleanupTool();
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
612 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
613
273
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
614 _setDetailModalType(useWindow) {
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
615 const targetName = useWindow ? "mjj-window-modal" : "mjj-content-modal";
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
616 if (!this._modalOwner || this._modalOwner.localName === targetName) return;
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
617 const replacement = document.createElement(targetName);
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
618 for (const attribute of [...this._modalOwner.attributes]) {
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
619 replacement.setAttribute(attribute.name, attribute.value);
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
620 }
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
621 replacement.append(...this._modalOwner.childNodes);
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
622 this._modalOwner.replaceWith(replacement);
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
623 this._modalOwner = replacement;
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
624 }
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
625
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
626 show(selection) {
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
627 this.cleanupTool();
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
628 const preview = PREVIEWS[selection] || PREVIEWS.resume;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
629 this.dataset.selection = selection;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
630 this.querySelector("[data-preview-title]").textContent = preview.title;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
631 this.querySelector("[data-preview-copy]").textContent = preview.copy;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
632 this.querySelector("[data-dialog-title]").textContent = preview.title;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
633 this.querySelector("[data-dialog-copy]").textContent = preview.copy;
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
634 this.renderShowcase(preview.works);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
635 const resumeDossier = this.querySelector("[data-resume-dossier]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
636 const resumeDownload = this.querySelector("[data-resume-download]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
637 const blogBrowser = this.querySelector("[data-blog-browser]");
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
638 const toolBrowser = this.querySelector("[data-tool-browser]");
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
639 const isResume = selection === "resume";
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
640 const isBlog = selection === "blog";
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
641 const isTools = selection === "tools";
273
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
642 this._setDetailModalType(isTools);
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
643 this._dialog.dataset.detailMode = isTools ? "tools" : "content";
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
644 resumeDossier.hidden = !isResume;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
645 resumeDownload.hidden = !isResume;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
646 blogBrowser.hidden = !isBlog;
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
647 toolBrowser.hidden = !isTools;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
648 this.querySelector("[data-dialog-copy]").hidden =
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
649 isResume || isBlog || isTools;
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
650 if (!isBlog) this._blogRequest = (this._blogRequest || 0) + 1;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
651 if (isBlog) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
652 const status = this.querySelector("[data-blog-status]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
653 status.hidden = false;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
654 status.textContent = "Select a blog to inspect.";
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
655 this.querySelector("[data-blog-content]").replaceChildren();
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
656 for (const button of this.querySelectorAll("[data-blog-entry]")) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
657 button.setAttribute("aria-pressed", "false");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
658 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
659 void this.loadBlogs();
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
660 }
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
661 if (!isTools) this._toolRequest = (this._toolRequest || 0) + 1;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
662 if (isTools) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
663 const status = this.querySelector("[data-tool-status]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
664 status.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
665 status.textContent = "Select a tool to inspect.";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
666 this.querySelector("[data-tool-content]").replaceChildren();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
667 void this.loadTools();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
668 }
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
669 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
670
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
671 renderShowcase(works, latestBlogs = false, latestKind = null) {
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
672 const showcase = this.querySelector("[data-work-showcase]");
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
673 showcase.replaceChildren(...works.map(work => {
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
674 const item = document.createElement("li");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
675 const link = document.createElement("a");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
676 const label = document.createElement("span");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
677 const detail = document.createElement("small");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
678 link.href = work.url;
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
679 if (latestBlogs) {
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
680 if (work.modal === "blog") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
681 link.dataset.blogArchive = "";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
682 } else {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
683 link.dataset.latestBlog = "";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
684 link.dataset.blogUrl = work.url;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
685 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
686 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
687 if (work.modal === "resume") link.dataset.resumeModal = "";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
688 if (latestKind === "tool") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
689 link.dataset.latestTool = "";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
690 link.dataset.toolUrl = work.url;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
691 const icon = document.createElement("zen-icon");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
692 icon.setAttribute("name", TOOL_ICON_NAMES[work.url] || "settings");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
693 link.prepend(icon);
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
694 }
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
695 if (new URL(work.url, window.location.href).origin !== window.location.origin) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
696 link.target = "_blank";
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
697 link.rel = "noreferrer";
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
698 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
699 label.textContent = work.label;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
700 detail.textContent = work.detail;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
701 link.append(label, detail);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
702 item.append(link);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
703 return item;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
704 }));
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
705 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
706
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
707 async loadResume() {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
708 if (this._resumeLoaded || this._resumeLoading) return;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
709 this._resumeLoading = true;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
710 const status = this.querySelector("[data-resume-status]");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
711 const content = this.querySelector("[data-resume-content]");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
712 try {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
713 const response = await fetch("/resume", {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
714 headers: { Accept: "text/html" },
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
715 });
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
716 if (!response.ok) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
717 throw new Error(`Resume request failed (${response.status})`);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
718 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
719 const documentCopy = new DOMParser().parseFromString(
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
720 await response.text(),
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
721 "text/html",
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
722 );
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
723 const resume = documentCopy.querySelector("main");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
724 if (!resume) throw new Error("Resume content is unavailable");
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
725 sanitizeFetchedMain(resume);
273
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
726 for (const duplicateDownload of resume.querySelectorAll(
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
727 'a[href="/public/resume.pdf"]',
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
728 )) {
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
729 duplicateDownload.closest("zen-button")?.remove();
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
730 duplicateDownload.remove();
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
731 }
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
732 content.replaceChildren(...resume.childNodes);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
733 status.hidden = true;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
734 this._resumeLoaded = true;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
735 } catch (error) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
736 status.textContent = `Unable to load resume: ${error.message}`;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
737 } finally {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
738 this._resumeLoading = false;
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
739 }
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
740 }
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
741
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
742 async loadBlogs() {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
743 if (this._blogs) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
744 if (this.dataset.selection === "blog") {
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
745 this.renderShowcase([
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
746 ...this._blogs.slice(0, 4),
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
747 {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
748 detail: "Full archive",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
749 label: "All blogs",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
750 modal: "blog",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
751 url: "/blog",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
752 },
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
753 ], true);
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
754 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
755 return true;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
756 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
757 if (this._blogsLoading) return this._blogsLoading;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
758 const list = this.querySelector("[data-blog-list]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
759 this._blogsLoading = (async () => {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
760 const response = await fetch("/blog", {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
761 headers: { Accept: "text/html" },
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
762 });
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
763 if (!response.ok) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
764 throw new Error(`Blog archive request failed (${response.status})`);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
765 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
766 const documentCopy = new DOMParser().parseFromString(
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
767 await response.text(),
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
768 "text/html",
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
769 );
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
770 this._blogs = [...documentCopy.querySelectorAll(
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
771 'main li a[href^="/blog/"]',
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
772 )].map(anchor => ({
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
773 date: anchor.closest("li")?.querySelector("span")?.textContent.trim() ||
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
774 "Archive",
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
775 detail: anchor.closest("li")?.querySelector("span")?.textContent.trim() ||
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
776 "Archive",
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
777 label: anchor.textContent.trim(),
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
778 url: anchor.getAttribute("href"),
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
779 }));
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
780 if (!this._blogs.length) throw new Error("No blog entries were found");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
781 this.renderBlogList();
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
782 if (this.dataset.selection === "blog") {
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
783 this.renderShowcase([
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
784 ...this._blogs.slice(0, 4),
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
785 {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
786 detail: "Full archive",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
787 label: "All blogs",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
788 modal: "blog",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
789 url: "/blog",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
790 },
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
791 ], true);
262
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
792 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
793 return true;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
794 })().catch(error => {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
795 list.replaceChildren();
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
796 const item = document.createElement("li");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
797 item.textContent = `Unable to load blogs: ${error.message}`;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
798 list.append(item);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
799 return false;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
800 }).finally(() => {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
801 this._blogsLoading = null;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
802 });
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
803 return this._blogsLoading;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
804 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
805
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
806 renderBlogList() {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
807 const list = this.querySelector("[data-blog-list]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
808 list.replaceChildren(...this._blogs.map(blog => {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
809 const item = document.createElement("li");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
810 const owner = document.createElement("zen-button");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
811 const button = document.createElement("button");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
812 const title = document.createElement("span");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
813 const date = document.createElement("small");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
814 owner.setAttribute("appearance", "plain");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
815 owner.setAttribute("size", "sm");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
816 button.type = "button";
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
817 button.dataset.blogEntry = "";
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
818 button.dataset.blogUrl = blog.url;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
819 title.textContent = blog.label;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
820 date.textContent = blog.date;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
821 button.append(title, date);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
822 owner.append(button);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
823 item.append(owner);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
824 return item;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
825 }));
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
826 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
827
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
828 async loadBlogDetail(url) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
829 if (!await this.loadBlogs()) return;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
830 const blog = this._blogs.find(item => item.url === url);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
831 const status = this.querySelector("[data-blog-status]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
832 const content = this.querySelector("[data-blog-content]");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
833 if (!blog) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
834 status.hidden = false;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
835 status.textContent = "Unable to load blog: unknown entry.";
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
836 return;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
837 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
838 const request = (this._blogRequest || 0) + 1;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
839 this._blogRequest = request;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
840 status.hidden = false;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
841 status.textContent = `Loading ${blog.label}...`;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
842 content.replaceChildren();
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
843 this.querySelector("[data-dialog-title]").textContent = blog.label;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
844 for (const button of this.querySelectorAll("[data-blog-entry]")) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
845 button.setAttribute(
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
846 "aria-pressed",
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
847 String(button.dataset.blogUrl === blog.url),
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
848 );
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
849 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
850 try {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
851 const response = await fetch(blog.url, {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
852 headers: { Accept: "text/html" },
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
853 });
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
854 if (!response.ok) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
855 throw new Error(`Blog request failed (${response.status})`);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
856 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
857 const documentCopy = new DOMParser().parseFromString(
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
858 await response.text(),
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
859 "text/html",
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
860 );
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
861 const article = documentCopy.querySelector("main");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
862 if (!article) throw new Error("Blog content is unavailable");
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
863 sanitizeFetchedMain(article);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
864 if (request !== this._blogRequest || this.dataset.selection !== "blog") {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
865 return;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
866 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
867 content.replaceChildren(...article.childNodes);
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
868 status.hidden = true;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
869 } catch (error) {
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
870 if (request !== this._blogRequest) return;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
871 status.hidden = false;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
872 status.textContent = `Unable to load blog: ${error.message}`;
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
873 }
0f45474c1b1a Add cyberpunk JRPG blog browser
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 261
diff changeset
874 }
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
875
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
876 async loadTools() {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
877 if (this._tools) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
878 if (this.dataset.selection === "tools") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
879 this.renderShowcase(this._tools, false, "tool");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
880 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
881 return true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
882 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
883 if (this._toolsLoading) return this._toolsLoading;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
884 this._toolsLoading = (async () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
885 const response = await fetch("/tools", {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
886 headers: { Accept: "text/html" },
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
887 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
888 if (!response.ok) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
889 throw new Error(`Tools request failed (${response.status})`);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
890 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
891 const documentCopy = new DOMParser().parseFromString(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
892 await response.text(),
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
893 "text/html",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
894 );
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
895 this._tools = [...documentCopy.querySelectorAll("main li a[href]")].map(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
896 anchor => ({
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
897 detail: anchor.getAttribute("href").startsWith("/notes")
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
898 ? "Writing"
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
899 : "Utility",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
900 label: anchor.textContent.trim(),
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
901 url: anchor.getAttribute("href"),
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
902 }),
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
903 );
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
904 if (!this._tools.length) throw new Error("No tools were found");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
905 if (this.dataset.selection === "tools") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
906 this.renderShowcase(this._tools, false, "tool");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
907 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
908 return true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
909 })().catch(error => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
910 const status = this.querySelector("[data-tool-status]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
911 status.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
912 status.textContent = `Unable to load tools: ${error.message}`;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
913 return false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
914 }).finally(() => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
915 this._toolsLoading = null;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
916 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
917 return this._toolsLoading;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
918 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
919
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
920 async openToolWorkspace() {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
921 if (!await this.loadTools() || this.dataset.selection !== "tools") return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
922 await this.loadToolDetail(this._activeToolUrl || this._tools[0].url);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
923 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
924
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
925 async loadToolDetail(url) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
926 const request = (this._toolRequest || 0) + 1;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
927 this._toolRequest = request;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
928 if (!await this.loadTools()) return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
929 if (request !== this._toolRequest || this.dataset.selection !== "tools") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
930 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
931 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
932 const tool = this._tools.find(item => item.url === url);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
933 const status = this.querySelector("[data-tool-status]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
934 const content = this.querySelector("[data-tool-content]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
935 if (!tool) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
936 status.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
937 status.textContent = "Unable to load tool: unknown entry.";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
938 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
939 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
940 this._activeToolUrl = tool.url;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
941 status.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
942 status.textContent = `Loading ${tool.label}...`;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
943 this.cleanupTool();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
944 content.replaceChildren();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
945 this.querySelector("[data-dialog-title]").textContent = tool.label;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
946 if (tool.url === "/tools/markdown_to_html") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
947 await this.renderMarkdownTool(content, status, request);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
948 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
949 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
950 if (tool.url === "/tools/file_converter") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
951 this.renderFileConverterTool(content, status);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
952 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
953 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
954 if (tool.url === "/tools/hls_player") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
955 await this.renderHlsTool(content, status, request);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
956 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
957 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
958 if (tool.url === "/tools/latex_editor") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
959 this.renderLatexTool(content, status);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
960 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
961 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
962 if (tool.url === "/notes") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
963 const heading = document.createElement("h1");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
964 const description = document.createElement("p");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
965 const privacy = document.createElement("p");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
966 heading.textContent = "Personal Notes";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
967 description.textContent =
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
968 "A private browser-based writing workspace with rich-text editing and server-backed persistence.";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
969 privacy.textContent =
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
970 "The interactive editor and authentication flow are available on the full Notes page.";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
971 content.replaceChildren(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
972 this.createReadonlyToolNote(),
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
973 heading,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
974 description,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
975 privacy,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
976 );
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
977 status.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
978 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
979 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
980 try {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
981 const response = await fetch(tool.url, {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
982 headers: { Accept: "text/html" },
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
983 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
984 if (!response.ok) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
985 throw new Error(`Tool request failed (${response.status})`);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
986 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
987 const documentCopy = new DOMParser().parseFromString(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
988 await response.text(),
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
989 "text/html",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
990 );
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
991 const detail = documentCopy.querySelector("main, .container") ||
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
992 documentCopy.body;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
993 if (!detail) throw new Error("Tool content is unavailable");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
994 sanitizeToolDetail(detail);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
995 if (request !== this._toolRequest || this.dataset.selection !== "tools") {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
996 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
997 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
998 content.replaceChildren(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
999 this.createReadonlyToolNote(),
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1000 ...detail.childNodes,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1001 );
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1002 status.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1003 } catch (error) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1004 if (request !== this._toolRequest) return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1005 status.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1006 status.textContent = `Unable to load tool: ${error.message}`;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1007 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1008 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1009
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1010 async renderMarkdownTool(content, status, request) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1011 content.innerHTML = `
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1012 <section class="jrpg-functional-tool" data-functional-tool="markdown">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1013 <div class="jrpg-tool-panes">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1014 <section class="jrpg-tool-pane jrpg-tool-window">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1015 <h2><zen-icon name="code"></zen-icon> Markdown source</h2>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1016 <zen-field appearance="plain" size="md">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1017 <label for="jrpgMarkdownSource">Markdown</label>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1018 <textarea id="jrpgMarkdownSource" data-markdown-source></textarea>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1019 </zen-field>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1020 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1021 <section class="jrpg-tool-pane jrpg-tool-window">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1022 <h2><zen-icon name="check"></zen-icon> Converted HTML</h2>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1023 <div class="jrpg-markdown-output" data-markdown-output></div>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1024 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1025 </div>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1026 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1027 `;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1028 const source = content.querySelector("[data-markdown-source]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1029 const output = content.querySelector("[data-markdown-output]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1030 source.value = [
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1031 "# JRPG Markdown",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1032 "",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1033 "Edit this **Markdown** to update the converted panel.",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1034 "",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1035 "- Bazel-built WASM",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1036 "- Sanitized HTML output",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1037 ].join("\n");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1038 status.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1039 status.textContent = "Loading the Markdown converter...";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1040 try {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1041 this._markdownModule ||= import("/markdown_to_html_bin.js")
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1042 .then(module => module.default());
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1043 const module = await this._markdownModule;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1044 if (request !== this._toolRequest || !source.isConnected) return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1045 const convertMarkdown = module.cwrap(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1046 "markdown_to_html",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1047 "number",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1048 ["string"],
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1049 );
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1050 const freeMarkdown = module.cwrap("markdown_free", null, ["number"]);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1051 const convert = () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1052 const pointer = convertMarkdown(source.value);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1053 const html = module.UTF8ToString(pointer);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1054 freeMarkdown(pointer);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1055 const documentCopy = new DOMParser().parseFromString(html, "text/html");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1056 sanitizeFetchedMain(documentCopy.body);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1057 output.replaceChildren(...documentCopy.body.childNodes);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1058 };
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1059 source.addEventListener("input", convert);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1060 convert();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1061 status.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1062 } catch (error) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1063 this._markdownModule = null;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1064 if (request !== this._toolRequest) return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1065 status.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1066 status.textContent = `Unable to start Markdown: ${error.message}`;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1067 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1068 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1069
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1070 renderFileConverterTool(content, status) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1071 content.innerHTML = `
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1072 <section class="jrpg-functional-tool" data-functional-tool="converter">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1073 <p class="jrpg-tool-status" data-converter-status>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1074 Choose a file in either panel.
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1075 </p>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1076 <div class="jrpg-tool-panes">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1077 <section class="jrpg-tool-pane jrpg-tool-window">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1078 <h2><zen-icon name="file"></zen-icon> Image to WebP</h2>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1079 <zen-field appearance="plain" size="md">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1080 <label for="jrpgImageInput">Image file</label>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1081 <input id="jrpgImageInput" data-converter-input="image" type="file" accept="image/*">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1082 </zen-field>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1083 <zen-button appearance="plain" size="md">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1084 <button type="button" data-convert-kind="image">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1085 Convert image <zen-icon name="arrow-right"></zen-icon>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1086 </button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1087 </zen-button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1088 <a data-converter-download="image" hidden>Download WebP</a>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1089 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1090 <section class="jrpg-tool-pane jrpg-tool-window">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1091 <h2><zen-icon name="play"></zen-icon> Video to MP4</h2>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1092 <zen-field appearance="plain" size="md">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1093 <label for="jrpgVideoInput">Video file</label>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1094 <input id="jrpgVideoInput" data-converter-input="video" type="file" accept="video/*">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1095 </zen-field>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1096 <zen-button appearance="plain" size="md">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1097 <button type="button" data-convert-kind="video">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1098 Convert video <zen-icon name="arrow-right"></zen-icon>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1099 </button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1100 </zen-button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1101 <a data-converter-download="video" hidden>Download MP4</a>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1102 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1103 </div>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1104 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1105 `;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1106 const toolStatus = content.querySelector("[data-converter-status]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1107 let disposed = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1108 const downloads = new Set();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1109 const discardDownload = url => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1110 if (!url) return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1111 downloads.delete(url);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1112 void fetch(url, {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1113 method: "DELETE",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1114 keepalive: true,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1115 }).then(response => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1116 if (!response.ok && response.status !== 404) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1117 console.warn(`Unable to discard converted file (${response.status})`);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1118 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1119 }).catch(error => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1120 console.warn(`Unable to discard converted file: ${error.message}`);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1121 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1122 };
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1123 const convert = async kind => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1124 const input = content.querySelector(`[data-converter-input="${kind}"]`);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1125 const button = content.querySelector(`[data-convert-kind="${kind}"]`);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1126 const download = content.querySelector(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1127 `[data-converter-download="${kind}"]`,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1128 );
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1129 const file = input.files?.[0];
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1130 if (!file) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1131 toolStatus.textContent = `Choose a ${kind} file first.`;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1132 toolStatus.dataset.state = "error";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1133 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1134 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1135 button.disabled = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1136 discardDownload(download.dataset.cleanupUrl);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1137 delete download.dataset.cleanupUrl;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1138 download.removeAttribute("href");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1139 download.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1140 toolStatus.textContent = `Converting ${file.name}...`;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1141 toolStatus.dataset.state = "working";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1142 try {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1143 const endpoint = kind === "image"
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1144 ? "/api/convert/image-to-webp"
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1145 : "/api/convert/video-to-mp4";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1146 const response = await fetch(endpoint, {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1147 method: "POST",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1148 body: file,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1149 headers: { "Content-Type": file.type || "application/octet-stream" },
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1150 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1151 if (!response.ok) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1152 throw new Error(await response.text() || `Conversion failed (${response.status})`);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1153 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1154 const result = await response.json();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1155 if (disposed || !download.isConnected) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1156 discardDownload(result.download_url);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1157 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1158 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1159 download.href = result.download_url;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1160 download.dataset.cleanupUrl = result.download_url;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1161 downloads.add(result.download_url);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1162 download.download = file.name.replace(/\.[^/.]+$/, "") +
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1163 (kind === "image" ? ".webp" : ".mp4");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1164 download.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1165 toolStatus.textContent = "Conversion complete.";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1166 toolStatus.dataset.state = "ready";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1167 } catch (error) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1168 if (!disposed) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1169 toolStatus.textContent = `Conversion failed: ${error.message}`;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1170 toolStatus.dataset.state = "error";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1171 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1172 } finally {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1173 if (button.isConnected) button.disabled = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1174 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1175 };
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1176 for (const button of content.querySelectorAll("[data-convert-kind]")) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1177 button.addEventListener("click", () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1178 void convert(button.dataset.convertKind);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1179 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1180 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1181 for (const download of content.querySelectorAll("[data-converter-download]")) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1182 download.addEventListener("click", () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1183 downloads.delete(download.dataset.cleanupUrl);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1184 delete download.dataset.cleanupUrl;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1185 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1186 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1187 this._toolCleanup = () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1188 disposed = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1189 for (const download of [...downloads]) discardDownload(download);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1190 };
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1191 status.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1192 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1193
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1194 async renderHlsTool(content, status, request) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1195 content.innerHTML = `
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1196 <section class="jrpg-functional-tool" data-functional-tool="hls">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1197 <div class="jrpg-tool-panes">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1198 <section class="jrpg-tool-pane jrpg-tool-window">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1199 <h2><zen-icon name="file"></zen-icon> HLS source</h2>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1200 <zen-field appearance="plain" size="md">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1201 <label for="jrpgHlsUrl">Playlist URL</label>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1202 <input
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1203 id="jrpgHlsUrl"
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1204 data-hls-url
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1205 type="url"
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1206 value="/public/hls-sample/h264-ts-stream.m3u8"
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1207 >
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1208 </zen-field>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1209 <div class="jrpg-tool-actions">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1210 <zen-button appearance="plain" size="md">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1211 <button type="button" data-hls-load>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1212 Load URL <zen-icon name="arrow-right"></zen-icon>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1213 </button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1214 </zen-button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1215 <zen-button appearance="plain" size="md">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1216 <button type="button" data-hls-sample>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1217 Load sample <zen-icon name="play"></zen-icon>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1218 </button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1219 </zen-button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1220 </div>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1221 <p class="jrpg-tool-status" data-hls-status>Loading player engine...</p>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1222 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1223 <section class="jrpg-tool-pane jrpg-tool-window">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1224 <h2><zen-icon name="play"></zen-icon> Playback</h2>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1225 <video data-hls-video controls playsinline preload="metadata"></video>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1226 <dl class="jrpg-tool-details" data-hls-details hidden>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1227 <div><dt>Playback</dt><dd data-detail="mode">-</dd></div>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1228 <div><dt>Segments</dt><dd data-detail="segments">-</dd></div>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1229 <div><dt>Duration</dt><dd data-detail="duration">-</dd></div>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1230 <div><dt>Codec</dt><dd data-detail="codecs">-</dd></div>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1231 </dl>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1232 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1233 </div>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1234 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1235 `;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1236 try {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1237 await loadScript("/public/hls.min.js");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1238 await loadScript("/tools/hls_player/hls-player.js");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1239 if (request !== this._toolRequest || !content.isConnected) return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1240 const video = content.querySelector("[data-hls-video]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1241 const playerStatus = content.querySelector("[data-hls-status]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1242 const details = content.querySelector("[data-hls-details]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1243 const url = content.querySelector("[data-hls-url]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1244 const player = new window.HlsPlayerModule.HlsPlayer(video, {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1245 statusElement: playerStatus,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1246 detailsElement: details,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1247 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1248 const load = () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1249 void player.load(url.value.trim()).catch(() => {});
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1250 };
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1251 content.querySelector("[data-hls-load]").addEventListener("click", load);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1252 content.querySelector("[data-hls-sample]").addEventListener("click", () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1253 url.value = "/public/hls-sample/h264-ts-stream.m3u8";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1254 load();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1255 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1256 playerStatus.textContent = "Ready to load a playlist.";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1257 playerStatus.dataset.state = "ready";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1258 this._toolCleanup = () => player.destroy();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1259 status.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1260 } catch (error) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1261 if (request !== this._toolRequest) return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1262 status.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1263 status.textContent = `Unable to start HLS: ${error.message}`;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1264 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1265 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1266
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1267 renderLatexTool(content, status) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1268 content.innerHTML = `
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1269 <section class="jrpg-functional-tool" data-functional-tool="latex">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1270 <p class="jrpg-tool-status" data-latex-status>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1271 Edit the source, then compile the PDF.
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1272 </p>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1273 <div class="jrpg-tool-panes">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1274 <section class="jrpg-tool-pane jrpg-tool-window">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1275 <div class="jrpg-tool-pane-heading">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1276 <h2><zen-icon name="code"></zen-icon> LaTeX source</h2>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1277 <span data-latex-size></span>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1278 </div>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1279 <zen-field appearance="plain" size="md">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1280 <label for="jrpgLatexSource">document.tex</label>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1281 <textarea id="jrpgLatexSource" data-latex-source></textarea>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1282 </zen-field>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1283 <div class="jrpg-tool-actions">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1284 <zen-button appearance="plain" size="md">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1285 <button type="button" data-latex-compile>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1286 Compile PDF <zen-icon name="file"></zen-icon>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1287 </button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1288 </zen-button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1289 <zen-button appearance="plain" size="md" data-latex-download hidden>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1290 <a download="document.pdf">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1291 Download PDF <zen-icon name="download"></zen-icon>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1292 </a>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1293 </zen-button>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1294 </div>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1295 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1296 <section class="jrpg-tool-pane jrpg-tool-window">
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1297 <h2><zen-icon name="file"></zen-icon> PDF preview</h2>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1298 <iframe data-latex-preview title="Compiled LaTeX PDF preview"></iframe>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1299 <pre data-latex-diagnostics hidden></pre>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1300 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1301 </div>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1302 </section>
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1303 `;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1304 const source = content.querySelector("[data-latex-source]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1305 const compileButton = content.querySelector("[data-latex-compile]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1306 const toolStatus = content.querySelector("[data-latex-status]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1307 const size = content.querySelector("[data-latex-size]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1308 const preview = content.querySelector("[data-latex-preview]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1309 const diagnostics = content.querySelector("[data-latex-diagnostics]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1310 const downloadOwner = content.querySelector("[data-latex-download]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1311 const download = downloadOwner.querySelector("a");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1312 source.value = String.raw`\documentclass[11pt]{article}
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1313 \usepackage[margin=1in]{geometry}
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1314 \title{JRPG LaTeX}
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1315 \author{MrJuneJune}
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1316 \begin{document}
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1317 \maketitle
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1318 This PDF was compiled from the cyberpunk tool modal.
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1319 \end{document}`;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1320 let controller = null;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1321 let pdfUrl = null;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1322 let compileGeneration = 0;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1323 const clearPdf = () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1324 if (pdfUrl) URL.revokeObjectURL(pdfUrl);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1325 pdfUrl = null;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1326 preview.src = "about:blank";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1327 preview.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1328 diagnostics.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1329 download.removeAttribute("href");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1330 downloadOwner.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1331 };
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1332 const updateSize = () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1333 const bytes = new TextEncoder().encode(source.value).byteLength;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1334 size.textContent = `${bytes.toLocaleString()} / 65,536 bytes`;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1335 return bytes;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1336 };
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1337 const compile = async () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1338 const generation = ++compileGeneration;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1339 clearPdf();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1340 const bytes = updateSize();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1341 if (!source.value.trim() || bytes > 64 * 1024) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1342 toolStatus.textContent = bytes > 64 * 1024
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1343 ? "Source exceeds 64 KiB."
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1344 : "Write LaTeX before compiling.";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1345 toolStatus.dataset.state = "error";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1346 return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1347 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1348 controller?.abort();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1349 const requestController = new AbortController();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1350 controller = requestController;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1351 compileButton.disabled = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1352 toolStatus.textContent = "Compiling on the server...";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1353 toolStatus.dataset.state = "working";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1354 try {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1355 const response = await fetch("/api/latex/render", {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1356 method: "POST",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1357 headers: { "Content-Type": "text/plain; charset=utf-8" },
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1358 body: source.value,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1359 cache: "no-store",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1360 signal: requestController.signal,
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1361 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1362 if (generation !== compileGeneration) return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1363 if (!response.ok) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1364 throw new Error(await response.text() || `Compilation failed (${response.status})`);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1365 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1366 const blob = await response.blob();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1367 if (generation !== compileGeneration) return;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1368 if (blob.type !== "application/pdf" || blob.size < 5) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1369 throw new Error("The server returned an invalid PDF.");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1370 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1371 if (pdfUrl) URL.revokeObjectURL(pdfUrl);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1372 pdfUrl = URL.createObjectURL(blob);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1373 preview.src = pdfUrl;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1374 preview.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1375 diagnostics.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1376 download.href = pdfUrl;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1377 downloadOwner.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1378 toolStatus.textContent = `PDF ready (${Math.ceil(blob.size / 1024)} KiB).`;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1379 toolStatus.dataset.state = "ready";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1380 } catch (error) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1381 if (error.name !== "AbortError" && generation === compileGeneration) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1382 diagnostics.textContent = error.message;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1383 diagnostics.hidden = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1384 preview.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1385 toolStatus.textContent = "Compilation failed.";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1386 toolStatus.dataset.state = "error";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1387 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1388 } finally {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1389 if (controller === requestController) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1390 controller = null;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1391 compileButton.disabled = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1392 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1393 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1394 };
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1395 source.addEventListener("input", () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1396 compileGeneration++;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1397 controller?.abort();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1398 controller = null;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1399 compileButton.disabled = false;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1400 clearPdf();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1401 updateSize();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1402 toolStatus.textContent = "Changes ready to compile.";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1403 toolStatus.dataset.state = "working";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1404 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1405 compileButton.addEventListener("click", () => void compile());
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1406 updateSize();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1407 this._toolCleanup = () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1408 compileGeneration++;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1409 controller?.abort();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1410 clearPdf();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1411 };
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1412 status.hidden = true;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1413 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1414
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1415 cleanupTool() {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1416 this._toolCleanup?.();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1417 this._toolCleanup = null;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1418 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1419
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1420 createReadonlyToolNote() {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1421 const introduction = document.createElement("p");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1422 introduction.className = "jrpg-tool-readonly-note";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1423 introduction.textContent =
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1424 "Read-only preview. Open the full page to use this tool.";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1425 return introduction;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
1426 }
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1427 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1428
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1429 /* =====================================================================
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1430 MjjConversationArchive — archive panel within the scene
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1431 ===================================================================== */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1432
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1433 class MjjConversationArchive extends HTMLElement {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1434 connectedCallback() {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1435 this._list = this.querySelector("[data-archive-list]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1436 this._status = this.querySelector("[data-archive-status]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1437 this._loadMoreOwner = this.querySelector("[data-archive-load-more-owner]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1438 this._loadMoreButton = this.querySelector("[data-archive-load-more]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1439 this._newButton = this.querySelector("[data-archive-new]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1440 this._closeButton = this.querySelector("[data-archive-close]");
273
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
1441 this._deleteModal = this.querySelector("[data-archive-delete-owner]");
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1442 this._deleteDialog = this.querySelector("[data-archive-delete-dialog]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1443 this._deleteNameEl = this.querySelector("[data-archive-delete-name]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1444 this._deleteCancelButton = this.querySelector("[data-archive-delete-cancel]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1445 this._deleteConfirmButton = this.querySelector("[data-archive-delete-confirm]");
273
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
1446 this._renameModal = this.querySelector("[data-archive-rename-owner]");
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1447 this._renameDialog = this.querySelector("[data-archive-rename-dialog]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1448 this._renameInput = this.querySelector("[data-archive-rename-input]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1449 this._renameForm = this.querySelector("[data-archive-rename-form]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1450 this._renameCancelButton = this.querySelector("[data-archive-rename-cancel]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1451 this._pendingDeleteId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1452 this._pendingDeleteTrigger = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1453 this._pendingRenameId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1454 this._pendingRenameTrigger = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1455
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1456 this._closeButton?.addEventListener("click", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1457 this.dispatchEvent(new CustomEvent("mjj-archive-toggle", { bubbles: true }));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1458 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1459
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1460 this._newButton?.addEventListener("click", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1461 this.dispatchEvent(new CustomEvent("mjj-archive-new", { bubbles: true }));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1462 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1463
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1464 this._loadMoreButton?.addEventListener("click", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1465 this.dispatchEvent(new CustomEvent("mjj-archive-load-more", { bubbles: true }));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1466 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1467
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1468 this._deleteCancelButton?.addEventListener("click", () => {
273
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
1469 this._deleteModal?.close();
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1470 const t = this._pendingDeleteTrigger;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1471 this._pendingDeleteId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1472 this._pendingDeleteTrigger = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1473 t?.focus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1474 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1475
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1476 this._deleteConfirmButton?.addEventListener("click", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1477 const id = this._pendingDeleteId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1478 const trigger = this._pendingDeleteTrigger;
273
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
1479 this._deleteModal?.close();
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1480 this._pendingDeleteId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1481 this._pendingDeleteTrigger = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1482 if (id) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1483 this.dispatchEvent(new CustomEvent("mjj-archive-delete", {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1484 bubbles: true,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1485 detail: { id, trigger },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1486 }));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1487 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1488 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1489
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1490 this._deleteDialog?.addEventListener("close", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1491 const t = this._pendingDeleteTrigger;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1492 this._pendingDeleteId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1493 this._pendingDeleteTrigger = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1494 t?.focus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1495 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1496
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1497 this._renameCancelButton?.addEventListener("click", () => {
273
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
1498 this._renameModal?.close();
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1499 const t = this._pendingRenameTrigger;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1500 this._pendingRenameId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1501 this._pendingRenameTrigger = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1502 t?.focus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1503 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1504
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1505 this._renameForm?.addEventListener("submit", event => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1506 event.preventDefault();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1507 const id = this._pendingRenameId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1508 const trigger = this._pendingRenameTrigger;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1509 const newTitle = this._renameInput?.value.trim();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1510 if (!id || !newTitle) return;
273
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
1511 this._renameModal?.close();
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1512 this._pendingRenameId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1513 this._pendingRenameTrigger = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1514 this.dispatchEvent(new CustomEvent("mjj-archive-rename", {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1515 bubbles: true,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1516 detail: { id, newTitle, trigger },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1517 }));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1518 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1519
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1520 this._renameDialog?.addEventListener("close", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1521 const t = this._pendingRenameTrigger;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1522 this._pendingRenameId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1523 this._pendingRenameTrigger = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1524 t?.focus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1525 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1526
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1527 this._onListClick = event => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1528 const openButton = event.target.closest("[data-conv-open]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1529 if (openButton) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1530 const id = openButton.closest("[data-conv-item]")?._convId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1531 if (id) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1532 this.dispatchEvent(new CustomEvent("mjj-archive-open", {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1533 bubbles: true,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1534 detail: { id },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1535 }));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1536 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1537 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1538 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1539 const renameButton = event.target.closest("[data-conv-rename]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1540 if (renameButton) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1541 const item = renameButton.closest("[data-conv-item]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1542 if (item?._convId) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1543 this._pendingRenameId = item._convId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1544 this._pendingRenameTrigger = renameButton;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1545 if (this._renameInput) this._renameInput.value = item._convTitle || "";
273
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
1546 this._renameModal?.open();
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1547 requestAnimationFrame(() => { this._renameInput?.select(); });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1548 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1549 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1550 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1551 const deleteButton = event.target.closest("[data-conv-delete]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1552 if (deleteButton) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1553 const item = deleteButton.closest("[data-conv-item]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1554 if (item?._convId) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1555 this._pendingDeleteId = item._convId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1556 this._pendingDeleteTrigger = deleteButton;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1557 if (this._deleteNameEl) this._deleteNameEl.textContent = item._convTitle || "";
273
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
1558 this._deleteModal?.open();
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1559 requestAnimationFrame(() => { this._deleteConfirmButton?.focus(); });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1560 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1561 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1562 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1563 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1564 this._list?.addEventListener("click", this._onListClick);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1565
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1566 this._onListKeydown = event => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1567 if (!["ArrowUp", "ArrowDown"].includes(event.key)) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1568 const items = [...(this._list?.querySelectorAll("[data-conv-open]") || [])];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1569 const index = items.indexOf(document.activeElement);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1570 if (index < 0) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1571 event.preventDefault();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1572 const next = event.key === "ArrowDown"
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1573 ? items[Math.min(index + 1, items.length - 1)]
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1574 : items[Math.max(index - 1, 0)];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1575 next?.focus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1576 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1577 this._list?.addEventListener("keydown", this._onListKeydown);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1578 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1579
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1580 disconnectedCallback() {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1581 this._list?.removeEventListener("click", this._onListClick);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1582 this._list?.removeEventListener("keydown", this._onListKeydown);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1583 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1584
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1585 setConversations(conversations, cursor, append = false) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1586 if (!this._list) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1587 if (!append) this._list.replaceChildren();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1588 for (const conv of conversations) this._list.append(this._createItem(conv));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1589 if (this._loadMoreOwner) this._loadMoreOwner.hidden = !cursor;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1590 if (!conversations.length && !append) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1591 this._showStatus("No conversations yet. Start a new one!");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1592 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1593 this._hideStatus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1594 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1595 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1596
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1597 setCurrentId(id) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1598 for (const button of (this._list?.querySelectorAll("[data-conv-open]") || [])) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1599 const isCurrent = id && button.closest("[data-conv-item]")?._convId === id;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1600 button.setAttribute("aria-current", String(Boolean(isCurrent)));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1601 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1602 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1603
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1604 addConversation(conv) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1605 if (!this._list) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1606 this._list.prepend(this._createItem(conv));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1607 this._hideStatus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1608 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1609
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1610 updateConversation(conv) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1611 if (!this._list) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1612 for (const item of this._list.querySelectorAll("[data-conv-item]")) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1613 if (item._convId === conv.id) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1614 const titleEl = item.querySelector("[data-conv-title]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1615 const metaEl = item.querySelector("[data-conv-meta]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1616 if (titleEl) titleEl.textContent = conv.title;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1617 if (metaEl) metaEl.textContent = this._formatMeta(conv);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1618 item._convTitle = conv.title;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1619 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1620 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1621 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1622 this.addConversation(conv);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1623 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1624
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1625 removeConversation(id) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1626 for (const item of (this._list?.querySelectorAll("[data-conv-item]") || [])) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1627 if (item._convId === id) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1628 item.remove();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1629 break;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1630 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1631 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1632 if (!this._list?.querySelector("[data-conv-item]")) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1633 this._showStatus("No conversations yet. Start a new one!");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1634 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1635 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1636
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1637 setLoading(loading) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1638 if (loading) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1639 this._showStatus("Loading conversations...");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1640 } else if (!this._list?.querySelector("[data-conv-item]")) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1641 this._showStatus("No conversations yet. Start a new one!");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1642 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1643 this._hideStatus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1644 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1645 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1646
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1647 setError(message) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1648 this._showStatus(message);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1649 if (this._status) this._status.dataset.state = "error";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1650 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1651
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1652 setLoadMoreDisabled(disabled) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1653 if (this._loadMoreButton) this._loadMoreButton.disabled = disabled;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1654 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1655
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1656 /* Disable/enable destructive archive controls while a stream is active. */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1657 setStreamActive(active) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1658 const controls = this._list?.querySelectorAll(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1659 "[data-conv-delete], [data-conv-rename]",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1660 ) || [];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1661 for (const el of controls) el.disabled = active;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1662 if (this._newButton) this._newButton.disabled = active;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1663 if (this._deleteConfirmButton) this._deleteConfirmButton.disabled = active;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1664 this.dataset.streamActive = active ? "true" : "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1665 if (!active) delete this.dataset.streamActive;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1666 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1667
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1668 /* Return the open-button of the adjacent surviving item, or New button. */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1669 adjacentItemOrNewButton(id) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1670 const items = [...(this._list?.querySelectorAll("[data-conv-item]") || [])];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1671 const idx = items.findIndex(el => el._convId === id);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1672 if (idx < 0) return this._newButton;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1673 const sibling = items[idx + 1] || items[idx - 1];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1674 return sibling?.querySelector("[data-conv-open]") || this._newButton;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1675 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1676
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1677 showLegacyClaim(statusMessage = null, showButton = true) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1678 if (!this._status) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1679 this._status.hidden = false;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1680 delete this._status.dataset.state;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1681 this._status.textContent = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1682 const msg = document.createElement("span");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1683 msg.textContent = statusMessage ?? "You have a prior conversation. ";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1684 this._status.append(msg);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1685 if (showButton) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1686 const btn = document.createElement("button");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1687 btn.type = "button";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1688 btn.textContent = "Claim it";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1689 btn.dataset.archiveClaim = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1690 btn.addEventListener("click", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1691 this.dispatchEvent(new CustomEvent("mjj-archive-claim", { bubbles: true }));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1692 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1693 this._status.append(btn);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1694 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1695 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1696
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1697 _showStatus(text) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1698 if (this._status) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1699 this._status.textContent = text;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1700 this._status.hidden = false;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1701 delete this._status.dataset.state;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1702 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1703 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1704
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1705 _hideStatus() {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1706 if (this._status) this._status.hidden = true;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1707 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1708
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1709 _createItem(conv) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1710 const item = document.createElement("li");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1711 item.dataset.convItem = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1712 item._convId = conv.id;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1713 item._convTitle = conv.title;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1714
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1715 const openButton = document.createElement("button");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1716 openButton.type = "button";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1717 openButton.dataset.convOpen = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1718 openButton.setAttribute("aria-current", "false");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1719
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1720 const titleEl = document.createElement("span");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1721 titleEl.dataset.convTitle = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1722 titleEl.textContent = conv.title;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1723
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1724 const metaEl = document.createElement("small");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1725 metaEl.dataset.convMeta = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1726 metaEl.textContent = this._formatMeta(conv);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1727
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1728 openButton.append(titleEl, metaEl);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1729
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1730 const actions = document.createElement("div");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1731 actions.className = "jrpg-archive-item-actions";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1732
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1733 const renameButton = document.createElement("button");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1734 renameButton.type = "button";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1735 renameButton.dataset.convRename = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1736 renameButton.setAttribute("aria-label", `Rename "${conv.title}"`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1737 renameButton.textContent = "Rename";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1738
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1739 const deleteButton = document.createElement("button");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1740 deleteButton.type = "button";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1741 deleteButton.dataset.convDelete = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1742 deleteButton.setAttribute("aria-label", `Delete "${conv.title}"`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1743 deleteButton.textContent = "Delete";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1744
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1745 actions.append(renameButton, deleteButton);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1746 item.append(openButton, actions);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1747 return item;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1748 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1749
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1750 _formatMeta(conv) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1751 const count = conv.turn_count ?? 0;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1752 const turns = count === 1 ? "1 turn" : `${count} turns`;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1753 if (conv.last_message_preview) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1754 return `${turns} · ${conv.last_message_preview.slice(0, 28)}`;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1755 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1756 return turns;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1757 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1758 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1759
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1760 class MjjJrpgShell extends HTMLElement {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1761 connectedCallback() {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1762 this._mobileMenuQuery = window.matchMedia(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1763 "(max-width: 52rem) and (orientation: portrait)",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1764 );
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1765 this._mobileDestinationOwner = this.querySelector(
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1766 "[data-mobile-destination-owner]",
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1767 );
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1768 this._mobileDestinationDialog = this.querySelector(
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1769 "[data-mobile-destination-dialog]",
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1770 );
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1771 this._mobileDestinationContent = this.querySelector(
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1772 "[data-mobile-destination-content]",
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1773 );
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1774 this._mobileDestinationTitle = this.querySelector(
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1775 "[data-mobile-destination-title]",
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1776 );
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1777 this._mobileDestinationBackOwner = this.querySelector(
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1778 "[data-mobile-destination-back-owner]",
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1779 );
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1780 this._mobileDestinationBack = this.querySelector(
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1781 "[data-mobile-destination-back]",
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1782 );
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1783 this._utility = this.querySelector(".jrpg-utility");
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1784 this._menu = this.querySelector("mjj-jrpg-menu");
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1785 this._utilityPlaceholder = document.createComment("jrpg-utility");
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1786 this._menuPlaceholder = document.createComment("jrpg-menu");
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1787 this._utility?.before(this._utilityPlaceholder);
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1788 this._menu?.before(this._menuPlaceholder);
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1789
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1790 this._syncMobileLayout = () => {
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1791 if (!this._utility || !this._menu || !this._mobileDestinationContent) {
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1792 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1793 }
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1794 if (this._mobileMenuQuery.matches) {
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1795 this._mobileDestinationContent.append(this._menu, this._utility);
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1796 this.showMobileMenu();
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1797 return;
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1798 }
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1799 this._mobileDestinationOwner?.close();
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1800 this._utilityPlaceholder.after(this._utility);
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1801 this._menuPlaceholder.after(this._menu);
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1802 this._utility.hidden = false;
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1803 this._menu.hidden = false;
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1804 };
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1805 this._onMobileMenuMediaChange = () => this._syncMobileLayout();
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1806 this._onMobileDestinationBack = () => this.showMobileMenu();
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1807 this._onMobileDestinationClose = () => this.showMobileMenu();
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1808 this._mobileDestinationBack?.addEventListener(
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1809 "click",
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1810 this._onMobileDestinationBack,
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1811 );
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1812 this._mobileDestinationDialog?.addEventListener(
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1813 "close",
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1814 this._onMobileDestinationClose,
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1815 );
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1816 this._mobileMenuQuery.addEventListener(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1817 "change",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1818 this._onMobileMenuMediaChange,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1819 );
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1820 this._syncMobileLayout();
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1821 }
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1822
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1823 isMobileDestinationModal() {
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1824 return Boolean(this._mobileMenuQuery?.matches);
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1825 }
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1826
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1827 isMobileDestinationOpen() {
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1828 return this.isMobileDestinationModal() &&
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1829 Boolean(this._mobileDestinationDialog?.open);
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1830 }
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1831
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1832 showMobileMenu() {
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1833 if (!this.isMobileDestinationModal() || !this._menu || !this._utility) {
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1834 return;
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1835 }
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1836 this._menu.hidden = false;
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1837 this._utility.hidden = true;
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1838 if (this._mobileDestinationTitle) {
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1839 this._mobileDestinationTitle.textContent = "Menu";
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1840 }
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1841 if (this._mobileDestinationBackOwner) {
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1842 this._mobileDestinationBackOwner.hidden = true;
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1843 }
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1844 }
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1845
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1846 showMobileDestination(selection) {
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1847 if (!this.isMobileDestinationModal() || !this._menu || !this._utility) {
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1848 return;
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1849 }
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1850 const labels = {
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1851 blog: "Blogs",
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1852 conversations: "Conversations",
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1853 resume: "Resume",
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1854 tools: "Tools",
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1855 };
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1856 this._menu.hidden = true;
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1857 this._utility.hidden = false;
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1858 if (this._mobileDestinationTitle) {
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1859 this._mobileDestinationTitle.textContent = labels[selection] || "Destination";
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1860 }
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1861 if (this._mobileDestinationBackOwner) {
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1862 this._mobileDestinationBackOwner.hidden = false;
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1863 }
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1864 }
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1865
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1866 closeMobileDestinations() {
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1867 if (this.isMobileDestinationModal()) {
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1868 this._mobileDestinationOwner?.close();
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1869 }
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1870 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1871
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1872 disconnectedCallback() {
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1873 this._mobileDestinationBack?.removeEventListener(
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1874 "click",
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1875 this._onMobileDestinationBack,
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1876 );
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1877 this._mobileDestinationDialog?.removeEventListener(
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1878 "close",
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1879 this._onMobileDestinationClose,
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
1880 );
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1881 this._mobileMenuQuery?.removeEventListener(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1882 "change",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1883 this._onMobileMenuMediaChange,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1884 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1885 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1886 }
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1887
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1888 for (const [name, constructor] of Object.entries({
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1889 "mjj-conversation-archive": MjjConversationArchive,
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1890 "mjj-jrpg-character": MjjJrpgCharacter,
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1891 "mjj-jrpg-chat": MjjJrpgChat,
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1892 "mjj-jrpg-menu": MjjJrpgMenu,
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1893 "mjj-jrpg-preview": MjjJrpgPreview,
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1894 "mjj-jrpg-shell": MjjJrpgShell,
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1895 })) {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1896 if (!customElements.get(name)) customElements.define(name, constructor);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1897 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1898
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1899 async function requestJson(url, options = {}) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1900 const response = await fetch(url, {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1901 ...options,
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1902 credentials: "same-origin",
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1903 headers: {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1904 "Content-Type": "application/json",
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1905 ...options.headers,
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1906 },
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1907 });
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1908 if (!response.ok) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1909 let message = `Request failed (${response.status})`;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1910 try {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1911 const body = await response.json();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1912 message = body.error?.message || message;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1913 } catch {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1914 // Preserve the status-derived message.
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1915 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1916 throw new Error(message);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1917 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1918 return response.status === 204 ? null : response.json();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1919 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
1920
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1921 /* Session state for CSRF: bootstrapped once, refreshed at most once on 401/403 */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1922 let _csrfToken = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1923 let _sessionData = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1924 let _sessionRefreshInFlight = null; /* single-flight guard */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1925
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1926 function _principalFingerprint(data) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1927 if (!data) return null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1928 if (data.kind === "user") return `user:${data.username || ""}:${data.role || ""}`;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1929 if (data.kind === "guest") return `guest:${data.csrfToken || ""}`;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1930 return null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1931 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1932
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1933 async function _fetchSession() {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1934 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1935 const response = await fetch("/api/auth/session", {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1936 credentials: "same-origin",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1937 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1938 if (!response.ok) return null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1939 const data = await response.json().catch(() => null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1940 if (!data) return null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1941 _sessionData = data;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1942 return data.csrfToken || null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1943 } catch {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1944 return null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1945 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1946 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1947
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1948 /* Single-flight session refresh: dedupe concurrent callers. */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1949 async function _refreshSessionOnce() {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1950 if (_sessionRefreshInFlight) return _sessionRefreshInFlight;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1951 _sessionRefreshInFlight = (async () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1952 const prev = _principalFingerprint(_sessionData);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1953 const token = await _fetchSession();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1954 const next = _principalFingerprint(_sessionData);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1955 return { token, prev, next };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1956 })();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1957 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1958 return await _sessionRefreshInFlight;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1959 } finally {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1960 _sessionRefreshInFlight = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1961 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1962 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1963
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1964 /*
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1965 * Perform a state-changing (mutating) fetch with CSRF token and same-origin
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1966 * credentials. On 401 or CSRF-specific 403 (csrf_invalid), refreshes the
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1967 * session once and retries — but only if the refreshed principal matches the
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1968 * original. If the user becomes a guest or a different account, rejects and
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1969 * triggers the session-changed callback.
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1970 */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1971 let _sessionRefreshCallback = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1972
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1973 async function mutatingFetch(url, options = {}) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1974 const buildHeaders = () => ({
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1975 ...(options.headers || {}),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1976 Origin: window.location.origin,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1977 ...(_csrfToken ? { "X-CSRF-Token": _csrfToken } : {}),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1978 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1979
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1980 const response = await fetch(url, {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1981 ...options,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1982 credentials: "same-origin",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1983 headers: buildHeaders(),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1984 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1985
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1986 const shouldRefresh = response.status === 401 ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1987 (response.status === 403 && await (async () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1988 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1989 const clone = response.clone();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1990 const body = await clone.json();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1991 return body?.error?.code === "csrf_invalid";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1992 } catch { return false; }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1993 })());
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1994
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1995 if (shouldRefresh) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1996 const prevFingerprint = _principalFingerprint(_sessionData);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1997 const { token, next } = await _refreshSessionOnce();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1998 _csrfToken = token;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
1999 if (next !== prevFingerprint) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2000 if (_sessionRefreshCallback) _sessionRefreshCallback();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2001 const err = Object.assign(new Error("Session changed"), { status: 401 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2002 throw err;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2003 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2004 const retried = await fetch(url, {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2005 ...options,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2006 credentials: "same-origin",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2007 headers: buildHeaders(),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2008 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2009 if (retried.status === 401 && _sessionRefreshCallback) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2010 _sessionRefreshCallback();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2011 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2012 return retried;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2013 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2014 return response;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2015 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2016
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2017 async function consumeSse(response, onEvent) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2018 if (!response.body) throw new Error("Streaming response body is unavailable");
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2019 const reader = response.body.getReader();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2020 const decoder = new TextDecoder();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2021 let buffer = "";
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2022
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2023 const dispatch = block => {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2024 let eventName = "message";
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2025 const data = [];
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2026 for (const line of block.split("\n")) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2027 if (line.startsWith("event:")) eventName = line.slice(6).trim();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2028 else if (line.startsWith("data:")) data.push(line.slice(5).trimStart());
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2029 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2030 if (!data.length) return;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2031 onEvent(eventName, JSON.parse(data.join("\n")));
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2032 };
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2033
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2034 while (true) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2035 const { done, value } = await reader.read();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2036 buffer += decoder.decode(value || new Uint8Array(), { stream: !done });
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2037 buffer = buffer.replaceAll("\r\n", "\n");
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2038 let boundary;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2039 while ((boundary = buffer.indexOf("\n\n")) >= 0) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2040 const block = buffer.slice(0, boundary);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2041 buffer = buffer.slice(boundary + 2);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2042 if (block.trim()) dispatch(block);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2043 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2044 if (done) break;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2045 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2046 if (buffer.trim()) dispatch(buffer);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2047 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2048
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2049 /* =====================================================================
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2050 Account slot rendering
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2051 ===================================================================== */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2052
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2053 function renderAccountSlot(slotEl, sessionData) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2054 if (!slotEl) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2055 slotEl.textContent = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2056 if (!sessionData || sessionData.kind === "guest") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2057 const owner = document.createElement("zen-button");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2058 const btn = document.createElement("button");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2059 owner.setAttribute("appearance", "plain");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2060 owner.setAttribute("size", "xs");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2061 btn.type = "button";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2062 btn.dataset.loginOpen = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2063 btn.textContent = "LOGIN";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2064 owner.append(btn);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2065 slotEl.setAttribute("aria-label", "Not signed in");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2066 slotEl.append(owner);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2067 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2068 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2069 const username = (sessionData.username || "").slice(0, 8).toUpperCase();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2070 if (sessionData.mustChangePassword) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2071 const link = document.createElement("a");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2072 link.href = "/account/password";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2073 link.textContent = username ? `${username}\u26A0` : "PASSWD";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2074 link.title = "Password change required";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2075 slotEl.setAttribute("aria-label", `${sessionData.username}: password change required`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2076 slotEl.append(link);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2077 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2078 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2079 const nameSpan = document.createElement("span");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2080 nameSpan.dataset.accountUser = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2081 nameSpan.textContent = username;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2082 slotEl.append(nameSpan);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2083 if (sessionData.role === "admin") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2084 const adminLink = document.createElement("a");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2085 adminLink.href = "/admin/users";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2086 adminLink.textContent = "\u00A0ADM";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2087 slotEl.append(adminLink);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2088 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2089 const logoutBtn = document.createElement("button");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2090 logoutBtn.type = "button";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2091 logoutBtn.textContent = "\u00A0LGOUT";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2092 logoutBtn.addEventListener("click", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2093 logoutBtn.disabled = true;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2094 mutatingFetch("/api/auth/logout", { method: "POST" }).then(response => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2095 if (!response.ok) throw new Error(`Logout failed (${response.status})`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2096 window.dispatchEvent(new CustomEvent("mjj-logout"));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2097 }).catch(() => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2098 logoutBtn.disabled = false;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2099 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2100 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2101 slotEl.append(logoutBtn);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2102 slotEl.setAttribute("aria-label", `Signed in as ${sessionData.username}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2103 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2104
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2105 /* =====================================================================
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2106 Quota display helpers
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2107 ===================================================================== */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2108
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2109 function formatQuotaText(quota) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2110 if (!quota) return null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2111 const turns = quota.turnsRemaining ?? 0;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2112 const tokens = quota.outputTokensRemaining ?? 0;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2113 if (turns <= 0) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2114 const resetAt = quota.resetsAt ? new Date(quota.resetsAt * 1000) : null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2115 const resetStr = resetAt
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2116 ? ` Resets ${resetAt.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}`
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2117 : "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2118 return { text: `Quota exhausted.${resetStr}`, state: "exhausted" };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2119 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2120 if (turns <= 2) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2121 return {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2122 text: `${turns} turn${turns === 1 ? "" : "s"} remaining (${tokens.toLocaleString()} tokens)`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2123 state: "warning",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2124 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2125 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2126 return {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2127 text: `${turns} turns remaining (${tokens.toLocaleString()} tokens)`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2128 state: "ok",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2129 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2130 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2131
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2132 function updateQuotaDisplay(quotaEl, quota) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2133 if (!quotaEl) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2134 if (!quota) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2135 quotaEl.hidden = true;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2136 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2137 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2138 const result = formatQuotaText(quota);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2139 /* Only show the bar for low or exhausted quota; hide when turns are plentiful */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2140 if (!result || result.state === "ok") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2141 quotaEl.hidden = true;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2142 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2143 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2144 quotaEl.textContent = result.text;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2145 if (result.state === "exhausted") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2146 quotaEl.dataset.state = "exhausted";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2147 } else if (result.state === "warning") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2148 quotaEl.dataset.state = "warning";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2149 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2150 delete quotaEl.dataset.state;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2151 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2152 quotaEl.hidden = false;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2153 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2154
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2155 /* =====================================================================
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2156 Conversation archive helpers
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2157 ===================================================================== */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2158
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2159 async function fetchConversationList(cursor) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2160 const url = cursor
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2161 ? `/api/conversations?limit=${ARCHIVE_PAGE_SIZE}&cursor=${encodeURIComponent(cursor)}`
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2162 : `/api/conversations?limit=${ARCHIVE_PAGE_SIZE}`;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2163 const response = await fetch(url, { credentials: "same-origin" });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2164 if (!response.ok) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2165 let message = `Failed to load conversations (${response.status})`;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2166 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2167 const body = await response.json();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2168 message = body.error?.message || message;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2169 } catch { /* keep status message */ }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2170 throw new Error(message);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2171 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2172 return response.json();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2173 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2174
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2175 async function initializeJrpgPage() {
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2176 const colorProbe = document.createElement("span");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2177 colorProbe.style.background = "var(--zenbu-sys-color-surface-page)";
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2178 document.body.append(colorProbe);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2179 const themeColor = getComputedStyle(colorProbe).backgroundColor;
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2180 colorProbe.remove();
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2181 for (const meta of document.querySelectorAll('meta[name="theme-color"]')) {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2182 meta.setAttribute("content", themeColor);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2183 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2184
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2185 /* Bootstrap session to obtain CSRF token and session data */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2186 _csrfToken = await _fetchSession();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2187
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2188 const shell = document.querySelector("mjj-jrpg-shell");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2189 const character = shell?.querySelector("mjj-jrpg-character");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2190 const chat = shell?.querySelector("mjj-jrpg-chat");
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2191 const composer = shell?.querySelector("mjj-composer");
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2192 const preview = shell?.querySelector("mjj-jrpg-preview");
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2193 const archive = shell?.querySelector("mjj-conversation-archive");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2194 const accountSlot = shell?.querySelector("[data-frame-account]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2195 const quotaEl = shell?.querySelector("[data-quota]");
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2196 const frameNetwork = shell?.querySelector("[data-frame-network]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2197 const frameUptime = shell?.querySelector("[data-frame-uptime]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2198 const minimizeButton = shell?.querySelector("[data-frame-minimize]");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2199 const fullscreenButton = shell?.querySelector("[data-frame-fullscreen]");
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2200
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2201 let characterTimer = 0;
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2202 let currentConversationId = null;
273
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
2203 const serverInitialPanel = VALID_PANELS.includes(shell?.dataset.initialPanel)
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
2204 ? shell.dataset.initialPanel
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
2205 : "resume";
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
2206 let currentPanel = serverInitialPanel;
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2207 let archiveCursor = null;
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2208 let activeController = null;
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2209 let archiveLoadInFlight = false; /* serialize pagination requests */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2210 let openSeq = 0; /* sequence guard for conversation open */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2211 let popSeq = 0; /* sequence guard for popstate */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2212 let _pendingPopState = null; /* queued popstate received during active stream */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2213 let _claimInFlight = false; /* guard against duplicate concurrent claims */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2214 let legacyClaimId = sessionStorage.getItem(CONVERSATION_LEGACY_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2215 let principalEpoch = 0; /* rejects responses from a prior owner */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2216 /* Dedupe conversation IDs: set of IDs currently rendered in archive */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2217 const _renderedIds = new Set();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2218 const invalidatePrincipalRequests = () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2219 principalEpoch += 1;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2220 openSeq += 1;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2221 popSeq += 1;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2222 archiveCursor = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2223 };
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
2224 chat?.setGreeting(greetingForSession(_sessionData), true);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2225
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2226 /* Disable composer until fully initialized */
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2227 composer?.setDisabled(true);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2228
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2229 const setFrameStatus = (label, state, title = "") => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2230 if (frameNetwork) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2231 frameNetwork.textContent = label;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2232 frameNetwork.setAttribute("aria-label", `Status ${label.toLowerCase()}`);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2233 frameNetwork.dataset.state = state;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2234 frameNetwork.title = title;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2235 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2236 };
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2237
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2238 /* Bootstrap failure: keep controls disabled and expose retry */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2239 if (!_csrfToken) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2240 setFrameStatus("OFFLINE", "offline", "Bootstrap failed");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2241 archive?.setError("Connection failed. Reload to retry.");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2242 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2243 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2244
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2245 setFrameStatus("ONLINE", "online");
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2246
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2247 const startedAt = Date.now();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2248 const updateUptime = () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2249 const elapsed = Math.floor((Date.now() - startedAt) / 1000);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2250 const hours = String(Math.floor(elapsed / 3600)).padStart(2, "0");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2251 const minutes = String(Math.floor((elapsed % 3600) / 60)).padStart(2, "0");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2252 const seconds = String(elapsed % 60).padStart(2, "0");
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2253 if (frameUptime) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2254 frameUptime.textContent = `${hours}:${minutes}:${seconds}`;
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2255 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2256 };
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2257 updateUptime();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2258 window.setInterval(updateUptime, 1000);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2259
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2260 minimizeButton?.addEventListener("click", () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2261 const minimized = shell.dataset.minimized !== "true";
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2262 shell.dataset.minimized = String(minimized);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2263 minimizeButton.setAttribute("aria-pressed", String(minimized));
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2264 minimizeButton.setAttribute(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2265 "aria-label",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2266 minimized ? "Restore interface" : "Minimize interface",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2267 );
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2268 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2269 fullscreenButton?.addEventListener("click", async () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2270 try {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2271 if (document.fullscreenElement) await document.exitFullscreen();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2272 else await shell.requestFullscreen();
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2273 } catch (error) {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2274 setFrameStatus("DENIED", "offline", error.message);
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2275 }
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2276 });
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2277 document.addEventListener("fullscreenchange", () => {
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2278 fullscreenButton?.setAttribute(
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2279 "aria-pressed",
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2280 String(Boolean(document.fullscreenElement)),
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2281 );
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
2282 });
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2283
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2284 /* Render account slot */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2285 renderAccountSlot(accountSlot, _sessionData);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2286
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2287 /* ---- Login modal ---- */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2288 const loginDialogOwner = shell?.querySelector("[data-login-dialog-owner]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2289 const loginDialog = loginDialogOwner?.querySelector("[data-login-dialog]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2290 const loginForm = loginDialog?.querySelector("[data-login-form]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2291 const loginSubmitBtn = loginDialog?.querySelector("[data-login-submit]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2292 const loginCancelBtn = loginDialog?.querySelector("[data-login-cancel]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2293 const loginErrorEl = loginDialog?.querySelector("[data-login-error]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2294 const loginUsernameInput = loginDialog?.querySelector("#jrpg-login-username");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2295 const loginPasswordInput = loginDialog?.querySelector("#jrpg-login-password");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2296 let _loginTrigger = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2297 const setAccountActionsDisabled = disabled => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2298 for (const control of accountSlot?.querySelectorAll("button") || []) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2299 control.disabled = disabled;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2300 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2301 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2302
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2303 const _clearLoginError = () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2304 if (loginErrorEl) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2305 loginErrorEl.textContent = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2306 loginErrorEl.hidden = true;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2307 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2308 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2309
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2310 const _showLoginError = (message) => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2311 if (!loginErrorEl) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2312 loginErrorEl.textContent = message;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2313 loginErrorEl.hidden = false;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2314 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2315
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2316 const openLoginModal = (triggerEl) => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2317 if (!loginDialogOwner || !loginDialog) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2318 _loginTrigger = triggerEl || null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2319 _clearLoginError();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2320 loginForm?.reset();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2321 loginDialogOwner.open();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2322 requestAnimationFrame(() => { loginUsernameInput?.focus(); });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2323 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2324
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2325 /* Delegate clicks on data-login-open in the account slot */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2326 accountSlot?.addEventListener("click", event => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2327 const btn = event.target.closest("[data-login-open]");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2328 if (!btn || activeController) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2329 openLoginModal(btn);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2330 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2331
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2332 /* Cancel button */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2333 loginCancelBtn?.addEventListener("click", () => { loginDialog?.close(); });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2334
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2335 /* Cleanup on any close (Escape, X button, cancel) */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2336 loginDialog?.addEventListener("close", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2337 if (loginPasswordInput) loginPasswordInput.value = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2338 _clearLoginError();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2339 const trigger = _loginTrigger;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2340 _loginTrigger = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2341 requestAnimationFrame(() => { trigger?.focus(); });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2342 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2343
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2344 /* Login form submission */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2345 loginForm?.addEventListener("submit", async event => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2346 event.preventDefault();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2347 if (loginSubmitBtn?.disabled) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2348 const username = loginUsernameInput?.value.trim() || "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2349 const password = loginPasswordInput?.value || "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2350 if (!username || !password) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2351 _showLoginError("Please enter your username and password.");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2352 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2353 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2354 if (loginSubmitBtn) loginSubmitBtn.disabled = true;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2355 _clearLoginError();
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2356 try {
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2357 const response = await fetch("/api/auth/login", {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2358 method: "POST",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2359 headers: {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2360 "Content-Type": "application/json",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2361 "Origin": window.location.origin,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2362 },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2363 credentials: "same-origin",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2364 body: JSON.stringify({ username, password, csrfToken: _csrfToken }),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2365 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2366 if (loginPasswordInput) loginPasswordInput.value = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2367 if (response.status === 429) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2368 _showLoginError("Too many attempts. Please try again later.");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2369 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2370 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2371 if (!response.ok) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2372 _showLoginError("Invalid username or password.");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2373 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2374 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2375 let data = await response.json().catch(() => null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2376 if (!data) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2377 _csrfToken = await _fetchSession();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2378 if (_sessionData?.kind !== "user") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2379 loginDialog?.close();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2380 window.location.reload();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2381 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2382 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2383 data = _sessionData;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2384 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2385 invalidatePrincipalRequests();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2386 loginDialog?.close();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2387 _sessionData = {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2388 kind: "user",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2389 username: data.username || username,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2390 role: data.role || "member",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2391 mustChangePassword: Boolean(data.mustChangePassword),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2392 csrfToken: data.csrfToken,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2393 quota: null,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2394 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2395 _csrfToken = data.csrfToken || null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2396 if (data.mustChangePassword) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2397 window.location.href = "/account/password";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2398 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2399 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2400 /* Commit the returned principal before optional UI refresh work. */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2401 renderAccountSlot(accountSlot, _sessionData);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2402 updateQuotaDisplay(quotaEl, _sessionData?.quota || null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2403 composer?.setDisabled(false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2404 /* Clear conversation state — guest convs may transfer to user */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2405 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2406 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2407 sessionStorage.removeItem(CONVERSATION_LEGACY_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2408 legacyClaimId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2409 _replaceUrlState(currentPanel, null);
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
2410 chat?.setGreeting(greetingForSession(_sessionData), true);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2411 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2412 await refreshArchiveFromScratch();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2413 } catch (error) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2414 archive?.setError(`Signed in, but archive refresh failed: ${error.message}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2415 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2416 requestAnimationFrame(() => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2417 accountSlot?.querySelector("button, a")?.focus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2418 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2419 } catch {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2420 if (loginPasswordInput) loginPasswordInput.value = "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2421 _showLoginError("Sign-in failed. Please try again.");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2422 } finally {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2423 if (loginSubmitBtn) loginSubmitBtn.disabled = false;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2424 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2425 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2426
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2427 /* Handle logout */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2428 _sessionRefreshCallback = () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2429 /* Account changed or session lost — clear active state */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2430 invalidatePrincipalRequests();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2431 if (activeController) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2432 activeController.abort();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2433 activeController = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2434 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2435 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2436 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2437 sessionStorage.removeItem(CONVERSATION_LEGACY_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2438 legacyClaimId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2439 _replaceUrlState(currentPanel, null);
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
2440 chat?.setGreeting(greetingForSession(_sessionData), true);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2441 _renderedIds.clear();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2442 archiveCursor = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2443 archive?.setConversations([], null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2444 renderAccountSlot(accountSlot, _sessionData);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2445 archive?.setError("Session changed. Loading conversations...");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2446 void refreshArchiveFromScratch();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2447 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2448 window.addEventListener("mjj-logout", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2449 invalidatePrincipalRequests();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2450 if (activeController) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2451 activeController.abort();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2452 activeController = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2453 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2454 archive?.setStreamActive(false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2455 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2456 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2457 sessionStorage.removeItem(CONVERSATION_LEGACY_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2458 legacyClaimId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2459 _replaceUrlState(currentPanel, null);
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
2460 chat?.setGreeting(ROLE_GREETINGS.guest, true);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2461 _renderedIds.clear();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2462 archiveCursor = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2463 archive?.setConversations([], null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2464 void refreshArchiveFromScratch();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2465 void (async () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2466 _csrfToken = await _fetchSession();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2467 renderAccountSlot(accountSlot, _sessionData);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2468 updateQuotaDisplay(quotaEl, _sessionData?.quota || null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2469 if (_sessionData?.kind === "user" && _sessionData?.mustChangePassword) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2470 composer?.setDisabled(true);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2471 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2472 composer?.setDisabled(false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2473 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2474 })();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2475 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2476
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2477 /* Session quota display */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2478 if (_sessionData?.kind === "guest") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2479 updateQuotaDisplay(quotaEl, _sessionData.quota || null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2480 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2481
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2482 /* Disable composer for forced-password-change */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2483 const isForced = _sessionData?.kind === "user" && _sessionData?.mustChangePassword;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2484
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2485 /* ---- URL state helpers ---- */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2486 const _buildStateUrl = (panel, convId) => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2487 const url = new URL(window.location.href);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2488 if (panel && VALID_PANELS.includes(panel)) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2489 url.searchParams.set("panel", panel);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2490 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2491 url.searchParams.delete("panel");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2492 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2493 if (convId && UUID_REGEX.test(convId)) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2494 url.searchParams.set("conversation", convId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2495 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2496 url.searchParams.delete("conversation");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2497 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2498 return url;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2499 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2500
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2501 const _pushUrlState = (panel, convId) => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2502 history.pushState({ panel, conversation: convId || null }, "", _buildStateUrl(panel, convId));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2503 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2504
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2505 const _replaceUrlState = (panel, convId) => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2506 history.replaceState({ panel, conversation: convId || null }, "", _buildStateUrl(panel, convId));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2507 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2508
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2509 /* ---- Panel selection: shows archive or preview in utility ---- */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2510 const utilityEl = shell?.querySelector(".jrpg-utility");
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2511 const previewContent = preview?.querySelector("[data-preview-content]");
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2512
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2513 const selectPanel = (panel, pushHistory = true) => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2514 const validPanel = VALID_PANELS.includes(panel) ? panel : "resume";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2515 currentPanel = validPanel;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2516 for (const btn of (shell?.querySelectorAll("button[data-preview]") || [])) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2517 btn.setAttribute("aria-pressed", String(btn.dataset.preview === validPanel));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2518 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2519 if (validPanel === "conversations") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2520 if (archive) archive.hidden = false;
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2521 if (previewContent) previewContent.hidden = true;
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2522 if (utilityEl) utilityEl.setAttribute("aria-label", "Conversations");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2523 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2524 if (archive) archive.hidden = true;
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2525 if (previewContent) previewContent.hidden = false;
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2526 preview?.show(validPanel);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2527 if (utilityEl) utilityEl.setAttribute("aria-label", "Selected destination");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2528 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2529 if (pushHistory) _pushUrlState(validPanel, currentConversationId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2530 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2531
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2532 /* ---- Archive close button: navigate back to resume panel ---- */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2533 shell?.addEventListener("mjj-archive-toggle", () => {
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2534 if (shell?.isMobileDestinationModal()) {
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2535 shell.showMobileMenu();
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2536 return;
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2537 }
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2538 selectPanel("resume");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2539 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2540
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2541 shell?.addEventListener("mjj-archive-new", () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2542 if (activeController) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2543 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2544 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2545 _pushUrlState(currentPanel, null);
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
2546 chat?.setGreeting(greetingForSession(_sessionData), true);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2547 archive?.setCurrentId(null);
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2548 shell?.closeMobileDestinations();
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2549 composer?.querySelector("textarea")?.focus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2550 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2551
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2552 /* ---- Popstate: re-apply panel and conversation on back/forward ---- */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2553 /* _applyPopState always increments openSeq so any pending archive-open
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2554 fetch (including a transition to no conversation) cannot overwrite the
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2555 state the user navigated to (Fix 2). */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2556 const _applyPopState = async (seq, popPanel, popConvId) => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2557 const localSeq = ++openSeq;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2558 const epoch = principalEpoch;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2559
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2560 selectPanel(popPanel, false);
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2561 if (shell?.isMobileDestinationOpen()) {
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2562 shell.showMobileDestination(popPanel);
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2563 }
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2564
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2565 if (popConvId && popConvId !== currentConversationId) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2566 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2567 const conversation = await requestJson(`/api/conversations/${encodeURIComponent(popConvId)}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2568 if (seq !== popSeq || localSeq !== openSeq ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2569 epoch !== principalEpoch) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2570 currentConversationId = popConvId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2571 sessionStorage.setItem(CONVERSATION_STORAGE_KEY, popConvId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2572 chat?.replaceMessages(conversation.turns);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2573 archive?.setCurrentId(popConvId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2574 } catch {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2575 if (seq !== popSeq || localSeq !== openSeq ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2576 epoch !== principalEpoch) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2577 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2578 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
2579 chat?.setGreeting(greetingForSession(_sessionData), true);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2580 archive?.setCurrentId(null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2581 history.replaceState(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2582 { panel: popPanel, conversation: null },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2583 "",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2584 _buildStateUrl(popPanel, null),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2585 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2586 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2587 } else if (!popConvId && currentConversationId) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2588 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2589 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
2590 chat?.setGreeting(greetingForSession(_sessionData), true);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2591 archive?.setCurrentId(null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2592 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2593 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2594
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2595 /* When popstate fires while a stream is active the URL has already changed.
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2596 Queue the parsed destination, abort the stream, and apply exactly once in
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2597 the stream's finally block so URL and UI always converge (Fix 1). */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2598 window.addEventListener("popstate", async event => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2599 const seq = ++popSeq;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2600 ++openSeq;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2601 const state = event.state || {};
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2602 const popPanel = VALID_PANELS.includes(state.panel) ? state.panel : "resume";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2603 const popConvId = (state.conversation && UUID_REGEX.test(state.conversation))
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2604 ? state.conversation : null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2605
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2606 if (activeController) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2607 _pendingPopState = { seq, popPanel, popConvId };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2608 activeController.abort();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2609 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2610 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2611
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2612 await _applyPopState(seq, popPanel, popConvId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2613 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2614
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2615 shell?.addEventListener("mjj-archive-rename", async event => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2616 const { id, newTitle, trigger } = event.detail;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2617 if (activeController) { trigger?.focus(); return; }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2618 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2619 const response = await mutatingFetch(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2620 `/api/conversations/${encodeURIComponent(id)}`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2621 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2622 method: "PATCH",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2623 headers: { "Content-Type": "application/json" },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2624 body: JSON.stringify({ title: newTitle }),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2625 },
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2626 );
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2627 if (!response.ok) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2628 const body = await response.json().catch(() => ({}));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2629 throw new Error(body.error?.message || `Rename failed (${response.status})`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2630 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2631 archive?.updateConversation({ id, title: newTitle, turn_count: null });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2632 } catch (error) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2633 archive?.setError(`Rename failed: ${error.message}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2634 } finally {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2635 trigger?.focus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2636 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2637 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2638
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2639 shell?.addEventListener("mjj-archive-open", async event => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2640 const { id } = event.detail;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2641 if (id === currentConversationId) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2642 if (activeController) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2643 const seq = ++openSeq;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2644 const epoch = principalEpoch;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2645 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2646 const conversation = await requestJson(`/api/conversations/${encodeURIComponent(id)}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2647 if (seq !== openSeq || epoch !== principalEpoch) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2648 currentConversationId = id;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2649 sessionStorage.setItem(CONVERSATION_STORAGE_KEY, id);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2650 _pushUrlState(currentPanel, id);
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2651 chat?.replaceMessages(conversation.turns);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2652 archive?.setCurrentId(id);
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2653 shell?.closeMobileDestinations();
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2654 } catch (error) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2655 if (seq !== openSeq || epoch !== principalEpoch) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2656 archive?.setError(`Unable to open: ${error.message}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2657 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2658 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2659
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2660 shell?.addEventListener("mjj-archive-delete", async event => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2661 const { id, trigger } = event.detail;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2662 if (activeController) { trigger?.focus(); return; } /* block while streaming */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2663 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2664 const response = await mutatingFetch(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2665 `/api/conversations/${encodeURIComponent(id)}`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2666 { method: "DELETE" },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2667 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2668 if (response.ok || response.status === 404) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2669 /* Focus adjacent archive item or New button before removing the node */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2670 const focusTarget = archive?.adjacentItemOrNewButton(id);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2671 _renderedIds.delete(id);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2672 archive?.removeConversation(id);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2673 if (id === currentConversationId) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2674 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2675 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2676 _replaceUrlState(currentPanel, null);
265
056790c4fb0d add role-aware Epi assistant prompts
MrJuneJune <me@mrjunejune.com>
parents: 264
diff changeset
2677 chat?.setGreeting(greetingForSession(_sessionData), true);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2678 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2679 /* Focus surviving element — never the detached trigger */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2680 requestAnimationFrame(() => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2681 (focusTarget || archive?.querySelector("[data-archive-new]"))?.focus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2682 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2683 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2684 const body = await response.json().catch(() => ({}));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2685 archive?.setError(`Delete failed: ${body.error?.message || response.status}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2686 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2687 } catch (error) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2688 archive?.setError(`Delete failed: ${error.message}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2689 /* On error, the trigger is still attached — focus it */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2690 trigger?.focus();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2691 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2692 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2693
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2694 shell?.addEventListener("mjj-archive-load-more", async () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2695 if (!archiveCursor || archiveLoadInFlight) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2696 archiveLoadInFlight = true;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2697 const epoch = principalEpoch;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2698 archive?.setLoadMoreDisabled(true);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2699 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2700 const data = await fetchConversationList(archiveCursor);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2701 if (epoch !== principalEpoch) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2702 archiveCursor = data.cursor || null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2703 const newConvs = (data.conversations || []).filter(c => !_renderedIds.has(c.id));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2704 newConvs.forEach(c => _renderedIds.add(c.id));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2705 archive?.setConversations(newConvs, archiveCursor, true);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2706 } catch (error) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2707 archive?.setError(`Load failed: ${error.message}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2708 } finally {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2709 archiveLoadInFlight = false;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2710 archive?.setLoadMoreDisabled(false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2711 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2712 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2713
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2714 shell?.addEventListener("mjj-archive-claim", async () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2715 const legacyId =
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2716 legacyClaimId || sessionStorage.getItem(CONVERSATION_LEGACY_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2717 if (!legacyId || _sessionData?.kind !== "user" || _claimInFlight) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2718 _claimInFlight = true;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2719 archive?.showLegacyClaim("Claiming prior conversation...", false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2720 let _claimOutcome = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2721 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2722 const response = await mutatingFetch(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2723 "/api/conversations/claim",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2724 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2725 method: "POST",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2726 headers: { "Content-Type": "application/json" },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2727 body: JSON.stringify({ conversationId: legacyId }),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2728 },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2729 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2730 if (response.ok) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2731 sessionStorage.removeItem(CONVERSATION_LEGACY_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2732 legacyClaimId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2733 _claimOutcome = "success";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2734 } else if (response.status === 409) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2735 /* Definitive non-legacy conflict: clear key, no retry needed */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2736 sessionStorage.removeItem(CONVERSATION_LEGACY_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2737 legacyClaimId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2738 _claimOutcome = "conflict";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2739 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2740 /* Retryable (401, 403, 429, 5xx): retain key */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2741 sessionStorage.setItem(CONVERSATION_LEGACY_KEY, legacyId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2742 legacyClaimId = legacyId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2743 _claimOutcome = "retry";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2744 }
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2745 } catch {
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2746 /* Network error: retain key */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2747 sessionStorage.setItem(CONVERSATION_LEGACY_KEY, legacyId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2748 legacyClaimId = legacyId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2749 _claimOutcome = "network-error";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2750 } finally {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2751 /* Reset guard BEFORE re-rendering so the retry button is immediately usable */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2752 _claimInFlight = false;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2753 if (_claimOutcome === "success") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2754 void refreshArchiveFromScratch();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2755 } else if (_claimOutcome === "conflict") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2756 archive?.showLegacyClaim("Prior conversation already claimed.", false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2757 } else if (_claimOutcome === "retry") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2758 selectPanel("conversations", false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2759 archive?.showLegacyClaim("Claim failed. Try again. ");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2760 } else if (_claimOutcome === "network-error") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2761 selectPanel("conversations", false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2762 archive?.showLegacyClaim("Claim failed. Check connection. ");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2763 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2764 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2765 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2766
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2767 async function refreshArchiveFromScratch() {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2768 if (!archive) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2769 const epoch = principalEpoch;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2770 archive.setLoading(true);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2771 _renderedIds.clear();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2772 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2773 const data = await fetchConversationList(null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2774 if (epoch !== principalEpoch) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2775 archiveCursor = data.cursor || null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2776 const convs = data.conversations || [];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2777 convs.forEach(c => _renderedIds.add(c.id));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2778 archive.setConversations(convs, archiveCursor);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2779 archive.setCurrentId(currentConversationId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2780 } catch (error) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2781 archive.setError(`Archive error: ${error.message}`);
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2782 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2783 }
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2784
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2785 async function refreshArchiveHead() {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2786 if (!archive) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2787 const epoch = principalEpoch;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2788 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2789 const data = await fetchConversationList(null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2790 if (epoch !== principalEpoch) return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2791 const fresh = data.conversations || [];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2792 for (const conv of fresh) archive.updateConversation(conv);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2793 if (fresh.length > 0) archive.setCurrentId(currentConversationId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2794 } catch { /* silent refresh failure */ }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2795 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2796
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2797 /* ---- Parse and validate URL params at startup ---- */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2798 /* Track whether ?conversation was explicitly present (even if malformed)
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2799 so we never migrate sessionStorage when the param was given but invalid. */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2800 let _convParamWasExplicit = false;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2801 {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2802 const initParams = new URLSearchParams(window.location.search);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2803 const rawPanel = initParams.get("panel");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2804 const rawConvId = initParams.get("conversation");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2805 _convParamWasExplicit = rawConvId !== null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2806 const validPanel = VALID_PANELS.includes(rawPanel) ? rawPanel : null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2807 const validConvId = (rawConvId && UUID_REGEX.test(rawConvId)) ? rawConvId : null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2808 /* Remove invalid params with replaceState */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2809 if ((rawPanel && !validPanel) || (rawConvId && !validConvId)) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2810 const normalUrl = new URL(window.location.href);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2811 if (!validPanel) normalUrl.searchParams.delete("panel");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2812 if (!validConvId) normalUrl.searchParams.delete("conversation");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2813 history.replaceState(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2814 { panel: validPanel, conversation: validConvId },
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2815 "",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2816 normalUrl,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2817 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2818 }
273
e02e2036ef84 add Layer 2 JRPG component system
MrJuneJune <me@mrjunejune.com>
parents: 272
diff changeset
2819 currentPanel = validPanel || serverInitialPanel;
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2820 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2821
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2822 /* ---- Load archive; only an explicit URL restores a conversation ---- */
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2823 const startupEpoch = principalEpoch;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2824 const ensureStartupPrincipal = () => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2825 if (startupEpoch !== principalEpoch) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2826 const error = new Error("Principal changed during startup");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2827 error.name = "StalePrincipalError";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2828 throw error;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2829 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2830 };
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2831 archive?.setLoading(true);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2832 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2833 const data = await fetchConversationList(null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2834 ensureStartupPrincipal();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2835 archiveCursor = data.cursor || null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2836 const conversations = data.conversations || [];
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2837 conversations.forEach(c => _renderedIds.add(c.id));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2838 archive?.setConversations(conversations, archiveCursor);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2839
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2840 /* A conversation opens only through an explicit URL or a user archive action. */
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2841 const initParams2 = new URLSearchParams(window.location.search);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2842 const urlConvId = initParams2.get("conversation");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2843 const urlConvValid = urlConvId && UUID_REGEX.test(urlConvId) ? urlConvId : null;
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2844 const storedConvId = !_convParamWasExplicit
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2845 ? sessionStorage.getItem(CONVERSATION_STORAGE_KEY)
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2846 : null;
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2847
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2848 if (urlConvValid) {
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2849 const inArchive = conversations.find(c => c.id === urlConvValid);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2850 if (inArchive) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2851 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2852 const conversation = await requestJson(
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2853 `/api/conversations/${encodeURIComponent(urlConvValid)}`,
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2854 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2855 ensureStartupPrincipal();
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2856 currentConversationId = urlConvValid;
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2857 sessionStorage.setItem(CONVERSATION_STORAGE_KEY, urlConvValid);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2858 chat?.replaceMessages(conversation.turns);
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2859 archive?.setCurrentId(urlConvValid);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2860 } catch (error) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2861 if (error.name === "StalePrincipalError" ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2862 startupEpoch !== principalEpoch) throw error;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2863 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2864 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2865 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2866 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2867 /* Not on first page — fetch directly to verify ownership */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2868 let ownedConversation = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2869 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2870 ownedConversation = await requestJson(
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2871 `/api/conversations/${encodeURIComponent(urlConvValid)}`,
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2872 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2873 ensureStartupPrincipal();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2874 } catch (error) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2875 if (error.name === "StalePrincipalError" ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2876 startupEpoch !== principalEpoch) throw error;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2877 ownedConversation = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2878 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2879 if (ownedConversation) {
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2880 currentConversationId = urlConvValid;
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2881 sessionStorage.setItem(CONVERSATION_STORAGE_KEY, urlConvValid);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2882 chat?.replaceMessages(ownedConversation.turns);
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2883 if (!_renderedIds.has(urlConvValid)) {
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2884 _renderedIds.add(urlConvValid);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2885 archive?.addConversation({
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2886 id: urlConvValid,
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2887 title: ownedConversation.title || "Conversation",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2888 turn_count: ownedConversation.turns?.length || 0,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2889 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2890 }
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2891 archive?.setCurrentId(urlConvValid);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2892 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2893 currentConversationId = null;
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2894 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2895 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2896 }
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2897 } else {
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2898 currentConversationId = null;
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2899 archive?.setCurrentId(null);
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2900 if (conversations.length > 0) {
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2901 chat?.setGreeting(greetingForSession(_sessionData, true), true);
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2902 }
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2903 if (storedConvId && UUID_REGEX.test(storedConvId)) {
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2904 const storedConversationIsOwned = conversations.some(
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2905 conversation => conversation.id === storedConvId,
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2906 );
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2907 if (_sessionData?.kind === "user" && !storedConversationIsOwned) {
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2908 sessionStorage.setItem(CONVERSATION_LEGACY_KEY, storedConvId);
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2909 legacyClaimId = storedConvId;
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2910 archive?.showLegacyClaim();
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2911 }
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2912 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2913 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2914 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2915 } catch (error) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2916 if (error.name !== "StalePrincipalError" &&
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2917 startupEpoch === principalEpoch) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2918 archive?.setError(`Archive error: ${error.message}`);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2919 }
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2920 /* Fall back only for an explicit deep-linked conversation. */
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2921 const initParams3 = new URLSearchParams(window.location.search);
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2922 const fallbackId = initParams3.get("conversation");
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2923 if (startupEpoch === principalEpoch &&
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2924 fallbackId && UUID_REGEX.test(fallbackId)) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2925 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2926 const conversation = await requestJson(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2927 `/api/conversations/${encodeURIComponent(fallbackId)}`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2928 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2929 ensureStartupPrincipal();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2930 currentConversationId = fallbackId;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2931 chat?.replaceMessages(conversation.turns);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2932 } catch (fallbackError) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2933 if (fallbackError.name === "StalePrincipalError" ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2934 startupEpoch !== principalEpoch) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2935 // The new principal owns subsequent rendering.
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2936 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2937 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2938 currentConversationId = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2939 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2940 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2941 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2942 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2943
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2944 /* Apply startup panel and normalize URL (replaceState — not a user navigation) */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2945 if (startupEpoch === principalEpoch) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2946 selectPanel(currentPanel, false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2947 _replaceUrlState(currentPanel, currentConversationId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2948 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2949
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2950 shell?.addEventListener("mjj-jrpg-preview-change", event => {
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2951 selectPanel(event.detail.selection);
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2952 shell?.showMobileDestination(event.detail.selection);
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2953 character?.setAttribute("state", "thinking");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2954 window.clearTimeout(characterTimer);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2955 characterTimer = window.setTimeout(() => {
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2956 character?.setAttribute("state", "default");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2957 }, 650);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2958 });
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2959
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2960 shell?.addEventListener("mjj-composer-cancel", () => {
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2961 activeController?.abort();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2962 });
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2963
272
41a49c29a28f polish JRPG conversation experience
MrJuneJune <me@mrjunejune.com>
parents: 265
diff changeset
2964 shell?.addEventListener("mjj-composer-submit", async event => {
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2965 if (activeController) return;
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2966 /* Block sends when password change is required */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2967 if (_sessionData?.kind === "user" && _sessionData?.mustChangePassword) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2968 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2969 }
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2970 chat?.appendMessage("June", event.detail.message);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2971 composer?.setBusy(true);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2972 character?.setAttribute("state", "thinking");
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2973 window.clearTimeout(characterTimer);
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2974 const assistantItem = chat?.startAssistantMessage();
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
2975 chat?.setMessageStreaming(assistantItem, true);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
2976 const assistantText = new StreamingTextAnimator(text => {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
2977 chat?.setMessageText(assistantItem, text);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
2978 });
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2979 let turnFinished = false;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2980 activeController = new AbortController();
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
2981 activeController.signal.addEventListener("abort", () => {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
2982 assistantText.cancel();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
2983 }, { once: true });
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2984
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2985 /* Disable archive/account mutations while stream is active (finding #7) */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2986 archive?.setStreamActive(true);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2987 setAccountActionsDisabled(true);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2988
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2989 try {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2990 const createConversation = async () => {
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2991 const resp = await mutatingFetch("/api/conversations", {
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2992 method: "POST",
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2993 headers: { "Content-Type": "application/json" },
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2994 body: JSON.stringify({ title: "Shiba Quest" }),
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
2995 });
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2996 if (!resp.ok) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2997 let message = `Create failed (${resp.status})`;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2998 try {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
2999 const body = await resp.json();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3000 message = body.error?.message || message;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3001 } catch { /* keep */ }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3002 throw new Error(message);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3003 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3004 const conversation = await resp.json();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3005 currentConversationId = conversation.id;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3006 sessionStorage.setItem(CONVERSATION_STORAGE_KEY, currentConversationId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3007 _pushUrlState(currentPanel, currentConversationId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3008 if (!_renderedIds.has(currentConversationId)) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3009 _renderedIds.add(currentConversationId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3010 archive?.addConversation({
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3011 id: currentConversationId,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3012 title: "Shiba Quest",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3013 turn_count: 0,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3014 last_message_preview: event.detail.message.slice(0, 32),
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3015 });
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3016 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3017 archive?.setCurrentId(currentConversationId);
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3018 };
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3019
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3020 if (!currentConversationId) {
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3021 await createConversation();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3022 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3023
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3024 const postTurn = () => mutatingFetch(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3025 `/api/conversations/${encodeURIComponent(currentConversationId)}/turns`,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3026 {
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3027 method: "POST",
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3028 headers: { "Content-Type": "application/json" },
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3029 body: JSON.stringify({ prompt: event.detail.message }),
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3030 signal: activeController.signal,
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3031 },
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3032 );
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3033
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3034 let response = await postTurn();
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3035
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3036 /* Handle 404: conversation may have expired — recreate it */
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3037 if (response.status === 404) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3038 sessionStorage.removeItem(CONVERSATION_STORAGE_KEY);
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3039 currentConversationId = null;
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3040 await createConversation();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3041 response = await postTurn();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3042 }
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3043
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3044 /* Handle 429: distinguish permanent quota from temporary capacity */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3045 if (response.status === 429) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3046 let errorBody = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3047 try { errorBody = await response.json(); } catch { /* keep */ }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3048 const code = errorBody?.error?.code || "";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3049 const isQuotaExhausted =
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3050 code === "guest_quota_turns_exhausted" ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3051 code === "guest_quota_tokens_exhausted" ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3052 code === "guest_turn_quota_exhausted" ||
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3053 code === "guest_token_quota_exhausted";
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3054
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3055 if (isQuotaExhausted) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3056 const quotaState = errorBody?.error?.quota || null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3057 updateQuotaDisplay(quotaEl, quotaState);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3058 composer?.setDisabled(true);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3059 chat?.setMessageStreaming(assistantItem, false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3060 chat?.setMessageText(
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3061 assistantItem,
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3062 quotaState
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3063 ? `Quota exhausted. ${formatQuotaText(quotaState)?.text || ""}`
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3064 : "Quota exhausted. Try again later.",
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3065 );
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3066 character?.setAttribute("state", "sad");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3067 setFrameStatus("QUOTA", "offline");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3068 /* Re-enable when quota resets */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3069 const resetsAt = quotaState?.resetsAt;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3070 if (resetsAt) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3071 const delay = Math.max(0, resetsAt * 1000 - Date.now());
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3072 window.setTimeout(() => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3073 if (_sessionData?.kind !== "user" || !_sessionData?.mustChangePassword) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3074 composer?.setDisabled(false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3075 setFrameStatus("ONLINE", "online");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3076 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3077 }, Math.min(delay + 1000, 24 * 3600 * 1000));
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3078 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3079 } else {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3080 /* Temporary capacity 429 — use Retry-After or default 5 s */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3081 const retryAfter = parseInt(response.headers.get("Retry-After") || "5", 10);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3082 chat?.setMessageStreaming(assistantItem, false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3083 chat?.setMessageText(assistantItem, "Server busy. Retrying shortly…");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3084 character?.setAttribute("state", "sad");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3085 setFrameStatus("BUSY", "offline");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3086 window.setTimeout(() => {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3087 if (!activeController?.signal.aborted) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3088 setFrameStatus("ONLINE", "online");
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3089 composer?.setDisabled(false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3090 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3091 }, retryAfter * 1000);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3092 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3093 return;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3094 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3095
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3096 if (!response.ok) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3097 let message = `Turn failed (${response.status})`;
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3098 try {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3099 const body = await response.json();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3100 message = body.error?.message || message;
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3101 } catch { /* keep */ }
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3102 throw new Error(message);
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3103 }
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3104
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3105 await consumeSse(response, (eventName, data) => {
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3106 shell?.dispatchEvent(new CustomEvent("mjj-jrpg-stream-event", {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3107 bubbles: true,
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3108 detail: { data, type: eventName },
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3109 }));
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3110 if (eventName === "assistant.delta") {
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3111 assistantText.append(data.delta || "");
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3112 } else if (eventName === "assistant.completed") {
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3113 assistantText.complete(data.content);
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3114 } else if (eventName === "turn.error") {
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3115 throw new Error(
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3116 data.error?.message || data.message || "Inference turn failed",
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3117 );
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3118 } else if (eventName === "turn.done") {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3119 turnFinished = true;
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3120 composer?.setCancellable(false);
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3121 if (data.failed || data.aborted) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3122 throw new Error(
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3123 data.aborted ? "Inference turn aborted" : "Inference turn failed",
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3124 );
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3125 }
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3126 }
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3127 });
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3128
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3129 if (!turnFinished) throw new Error("Inference stream ended unexpectedly");
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3130 await assistantText.waitForIdle();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3131 if (activeController.signal.aborted) {
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3132 throw new DOMException("Quest cancelled", "AbortError");
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3133 }
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3134 chat?.setMessageStreaming(assistantItem, false);
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3135 character?.setAttribute("state", "default");
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
3136 setFrameStatus("ONLINE", "online");
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3137
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3138 /* Refresh archive and quota after successful turn */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3139 void refreshArchiveHead();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3140 if (_sessionData?.kind === "guest") {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3141 _csrfToken = await _fetchSession();
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3142 updateQuotaDisplay(quotaEl, _sessionData?.quota || null);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3143 }
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3144 } catch (error) {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3145 const aborted = error instanceof DOMException && error.name === "AbortError";
261
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3146 assistantText.cancel();
b401627fc49e Add JRPG mock flows and interactive previews
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 260
diff changeset
3147 chat?.setMessageStreaming(assistantItem, false);
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3148 chat?.setMessageText(
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3149 assistantItem,
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3150 aborted ? "Quest cancelled." : `System error: ${error.message}`,
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3151 );
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3152 character?.setAttribute("state", "sad");
263
ee04e4e69fed Add functional JRPG frame and tools
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 262
diff changeset
3153 if (!aborted) setFrameStatus("OFFLINE", "offline");
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3154 } finally {
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3155 composer?.setBusy(false);
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3156 composer?.querySelector("textarea")?.focus();
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3157 activeController = null;
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3158 /* Re-enable archive/account mutations exactly once */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3159 archive?.setStreamActive(false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3160 setAccountActionsDisabled(false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3161 /* Apply popstate that was queued during the stream (Fix 1) */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3162 if (_pendingPopState) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3163 const pending = _pendingPopState;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3164 _pendingPopState = null;
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3165 if (pending.seq === popSeq) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3166 await _applyPopState(pending.seq, pending.popPanel, pending.popConvId);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3167 }
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3168 }
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3169 }
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3170 });
264
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3171
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3172 /* Enable composer unless forced password change */
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3173 if (!isForced) {
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3174 composer?.setDisabled(false);
04fee26ecce0 add authenticated JRPG conversation platform
MrJuneJune <me@mrjunejune.com>
parents: 263
diff changeset
3175 }
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3176 }
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3177
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3178 if (document.readyState === "loading") {
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3179 document.addEventListener("DOMContentLoaded", () => {
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3180 void initializeJrpgPage();
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3181 }, {
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3182 once: true,
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3183 });
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3184 } else {
260
1f9877b637e9 Add Copilot-powered cyberpunk JRPG chat
MrJuneJune <mrjunejune@users.noreply.github.com>
parents: 259
diff changeset
3185 void initializeJrpgPage();
259
667156fcd3e3 Add dev-only cyberpunk JRPG page
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3186 }