|
1 | | -import { GameCard, Moq } from "@moq/boy"; |
2 | | -import { gridStyles } from "@moq/boy/styles"; |
| 1 | +import "@moq/boy/element"; |
| 2 | +import "@moq/boy/ui"; |
3 | 3 |
|
4 | | -// Inject styles into the document. |
5 | | -const style = document.createElement("style"); |
6 | | -style.textContent = gridStyles; |
7 | | -document.head.appendChild(style); |
8 | | - |
9 | | -// Header. |
10 | | -const header = document.createElement("header"); |
11 | | -const h1 = document.createElement("h1"); |
12 | | -h1.textContent = "MoQ Boy"; |
13 | | -const statusEl = document.createElement("span"); |
14 | | -statusEl.className = "status"; |
15 | | -statusEl.textContent = "Disconnected"; |
16 | | -header.appendChild(h1); |
17 | | -header.appendChild(statusEl); |
18 | | -document.body.appendChild(header); |
19 | | - |
20 | | -// Grid. |
21 | | -const gridEl = document.createElement("div"); |
22 | | -gridEl.className = "grid"; |
23 | | -document.body.appendChild(gridEl); |
24 | | - |
25 | | -// Empty state. |
26 | | -const emptyState = document.createElement("div"); |
27 | | -emptyState.className = "empty-state"; |
28 | | - |
29 | | -const emptyIcon = document.createElement("div"); |
30 | | -emptyIcon.className = "icon"; |
31 | | -emptyIcon.textContent = "\u{1F3AE}"; |
32 | | -emptyState.appendChild(emptyIcon); |
33 | | - |
34 | | -const emptyMsg = document.createElement("div"); |
35 | | -emptyMsg.className = "msg"; |
36 | | -emptyMsg.textContent = "No games online"; |
37 | | -emptyState.appendChild(emptyMsg); |
38 | | - |
39 | | -const emptyHint = document.createElement("div"); |
40 | | -emptyHint.className = "hint"; |
41 | | -emptyHint.textContent = "Waiting for Game Boy sessions to connect..."; |
42 | | -emptyState.appendChild(emptyHint); |
43 | | - |
44 | | -gridEl.appendChild(emptyState); |
45 | | - |
46 | | -// About section. |
47 | | -const about = document.createElement("div"); |
48 | | -about.className = "about"; |
49 | | - |
50 | | -const aboutP1 = document.createElement("p"); |
51 | | -aboutP1.textContent = "Click a game to play. Everyone controls the same game (anarchy mode)."; |
52 | | -about.appendChild(aboutP1); |
53 | | - |
54 | | -const aboutP2 = document.createElement("p"); |
55 | | -aboutP2.textContent = "A generic "; |
56 | | -const moqLink = document.createElement("a"); |
57 | | -moqLink.href = "https://moq.dev"; |
58 | | -moqLink.textContent = "MoQ"; |
59 | | -aboutP2.appendChild(moqLink); |
60 | | -aboutP2.appendChild(document.createTextNode(" relay is used for everything:")); |
61 | | -about.appendChild(aboutP2); |
62 | | - |
63 | | -const aboutUl = document.createElement("ul"); |
64 | | -for (const text of [ |
65 | | - "Discovering online games and players.", |
66 | | - "Transmitting audio/video tracks, metadata, and (multiple) player controls.", |
67 | | - "Subscribing to audio/video on-demand.", |
68 | | - "Pausing emulation/encoding when there are no subscribers.", |
69 | | -]) { |
70 | | - const li = document.createElement("li"); |
71 | | - li.textContent = text; |
72 | | - aboutUl.appendChild(li); |
73 | | -} |
74 | | -about.appendChild(aboutUl); |
75 | | -document.body.appendChild(about); |
76 | | - |
77 | | -// Connection. |
78 | 4 | const url = import.meta.env.VITE_RELAY_URL || "http://localhost:4443/anon"; |
79 | | -const enabled = new Moq.Signals.Signal(true); |
80 | | -const connection = new Moq.Connection.Reload({ url: new URL(url), enabled }); |
81 | | - |
82 | | -const signals = new Moq.Signals.Effect(); |
83 | | -const sessions = new Map<string, GameCard>(); |
84 | | -const expanded = new Moq.Signals.Signal<string | undefined>(undefined); |
85 | | - |
86 | | -function updateEmptyState() { |
87 | | - emptyState.style.display = sessions.size === 0 ? "block" : "none"; |
88 | | -} |
89 | | - |
90 | | -// Track connection status. |
91 | | -signals.run((e) => { |
92 | | - const status = e.get(connection.status); |
93 | | - statusEl.textContent = status.charAt(0).toUpperCase() + status.slice(1); |
94 | | - statusEl.style.color = status === "connected" ? "#8bac0f" : status === "connecting" ? "#facc15" : "#888"; |
95 | | -}); |
96 | | - |
97 | | -// Discover game sessions via announcements. |
98 | | -signals.run((effect) => { |
99 | | - const conn = effect.get(connection.established); |
100 | | - if (!conn) return; |
101 | | - |
102 | | - const announced = conn.announced(Moq.Path.from("boy")); |
103 | | - effect.cleanup(() => announced.close()); |
104 | | - |
105 | | - effect.spawn(async () => { |
106 | | - for (;;) { |
107 | | - const entry = await Promise.race([effect.cancel, announced.next()]); |
108 | | - if (!entry) break; |
109 | | - |
110 | | - const suffix = Moq.Path.stripPrefix(Moq.Path.from("boy"), entry.path); |
111 | | - if (!suffix || suffix.includes("/")) continue; |
112 | 5 |
|
113 | | - const id = suffix; |
114 | | - if (entry.active && !sessions.has(id)) { |
115 | | - const card = new GameCard({ |
116 | | - sessionId: id, |
117 | | - connection, |
118 | | - expanded, |
119 | | - root: document.body, |
120 | | - }); |
121 | | - sessions.set(id, card); |
122 | | - gridEl.appendChild(card.el); |
123 | | - updateEmptyState(); |
124 | | - } else if (!entry.active) { |
125 | | - const card = sessions.get(id); |
126 | | - if (card) { |
127 | | - card.close(); |
128 | | - card.el.remove(); |
129 | | - sessions.delete(id); |
130 | | - updateEmptyState(); |
131 | | - } |
132 | | - } |
133 | | - } |
134 | | - }); |
135 | | -}); |
| 6 | +const boy = document.querySelector("moq-boy"); |
| 7 | +if (boy) boy.url = url; |
0 commit comments