@hadoku/watchparty-ui 0.0.0-stage → 0.43.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,715 @@
1
+ import { jsxs as u, jsx as a, Fragment as j } from "react/jsx-runtime";
2
+ import { useState as x, useEffect as D, useRef as ce, useMemo as ke, useCallback as Se } from "react";
3
+ import { g as K, as as Y, d as A, ai as Fe, w as q, R as Q, at as Be, au as De, h as se, v as Oe, F as Ce, av as Pe } from "./index-CwuV6ab3.js";
4
+ import { D as $e, a as ae, u as Ge, s as je, b as qe } from "./Room-CPV26zAk.js";
5
+ import { s as Ee, a as W, g as ue, f as He, b as pe, h as Ve, u as We, c as ze, e as Ye, d as Je, i as Ke, j as Qe, k as Xe } from "./submissionsApi-CqGsgEg5.js";
6
+ import { Icon as re } from "@wolffm/themes";
7
+ import { b as Ze } from "./cover-AUIrlUMF.js";
8
+ const ne = /* @__PURE__ */ new Map(), Z = /* @__PURE__ */ new Map(), et = 6e4;
9
+ async function J(e = "") {
10
+ const r = Date.now(), t = ne.get(e);
11
+ if (t && r - t.ts < et) return t.data;
12
+ const l = Z.get(e);
13
+ if (l) return l;
14
+ const o = (async () => {
15
+ const p = `${K()}/api/catalog${e ? `?prefix=${encodeURIComponent(e)}` : ""}`, c = await fetch(p, { cache: "no-store" });
16
+ if (!c.ok) throw new Error(`catalog_http_${c.status}`);
17
+ const y = await c.json();
18
+ return Y() && A("catalog", "Catalog fetched", { prefix: e, dirs: y.dirs?.length, files: y.files?.length }), ne.set(e, { ts: r, data: y }), y;
19
+ })();
20
+ Z.set(e, o);
21
+ try {
22
+ return await o;
23
+ } finally {
24
+ Z.delete(e);
25
+ }
26
+ }
27
+ function tt(e) {
28
+ ne.clear();
29
+ }
30
+ async function at() {
31
+ if (Y())
32
+ try {
33
+ const e = ["output/fmp4/anime", "output/fmp4/movie", "output/fmp4/tv"];
34
+ for (const r of e) {
35
+ const t = await J(r);
36
+ A("catalog", "[probe] catalog " + r, { dirs: t.dirs.length, files: t.files.length });
37
+ const l = t.files[0];
38
+ if (l) {
39
+ const o = rt(), i = `${o}/${l.id}/index.json`.replace(/\\/g, "/"), p = `${o}/${l.id}/${Fe}`.replace(/\\/g, "/");
40
+ await me(i, "index"), await me(p, "fragment", { headers: { Range: "bytes=0-1023" } });
41
+ }
42
+ }
43
+ } catch (e) {
44
+ q("catalog", "[probe] error", { error: e instanceof Error ? e.message : String(e) });
45
+ }
46
+ }
47
+ function rt() {
48
+ return "/media";
49
+ }
50
+ async function me(e, r, t) {
51
+ const l = performance.now(), o = await fetch(e, t), i = Math.round(performance.now() - l);
52
+ return A("catalog", "[probe] " + r, { url: e, status: o.status, ms: i, bytes: o.headers.get("content-length") }), o;
53
+ }
54
+ const nt = [
55
+ "yuuna",
56
+ "gushingovermagicalgirls",
57
+ "immoralguild",
58
+ "ingokudanchi",
59
+ "isekaionsenparadise"
60
+ ], ge = (e) => e.toLowerCase().replace(/[^a-z0-9]/g, "");
61
+ function st(e, r) {
62
+ return new Set(r.map(ge)).has(ge(e));
63
+ }
64
+ function it(e) {
65
+ return st(e, nt);
66
+ }
67
+ function fe(e, r) {
68
+ const t = /* @__PURE__ */ new Map();
69
+ for (const l of e) {
70
+ const o = /* @__PURE__ */ new Map();
71
+ for (const i of r(l))
72
+ o.set(i.name, i.expanded === !0);
73
+ t.set(l.name, { expanded: l.expanded === !0, children: o });
74
+ }
75
+ return t;
76
+ }
77
+ function he(e, r, t, l) {
78
+ return e.map((o) => {
79
+ const i = r.get(o.name);
80
+ if (!i) return o;
81
+ const c = t(o).map((v) => {
82
+ const f = i.children.get(v.name);
83
+ return f === void 0 ? v : { ...v, expanded: f };
84
+ });
85
+ return l({ ...o, expanded: i.expanded }, c);
86
+ });
87
+ }
88
+ const lt = "Anime";
89
+ function z(e) {
90
+ return e === lt;
91
+ }
92
+ function ot(e, r, t) {
93
+ return e.map(
94
+ (l) => t(
95
+ { ...l, expanded: z(l.name) },
96
+ r(l).map((o) => ({ ...o, expanded: !1 }))
97
+ )
98
+ );
99
+ }
100
+ const be = "output/fmp4", xe = [
101
+ { key: "anime", label: "Anime" },
102
+ { key: "movie", label: "Movie" },
103
+ { key: "tv", label: "TV" }
104
+ ];
105
+ async function ee(e, r) {
106
+ try {
107
+ return await e(r);
108
+ } catch (t) {
109
+ return q("catalog", "Fetch failed", { prefix: r, ...Q(t) }), null;
110
+ }
111
+ }
112
+ async function ye(e) {
113
+ const [r, t] = await Promise.all([
114
+ Promise.all(xe.map((c) => ee(e, `${be}/${c.key}`))),
115
+ ee(e, `${be}/playlists`)
116
+ ]), l = await Promise.all(r.map((c) => Promise.all((c?.dirs ?? []).map(async (y) => {
117
+ const f = ((await ee(e, y.path))?.files ?? []).map(($) => ({ type: "episode", name: $.title, mediaId: $.id }));
118
+ return f.length ? { type: "series", name: y.name, episodes: f, expanded: !1 } : null;
119
+ })))), o = [], i = [];
120
+ xe.forEach((c, y) => {
121
+ const v = [];
122
+ for (const f of l[y])
123
+ f && (c.key === "anime" && it(f.name) ? i.push(f) : v.push(f));
124
+ v.length && o.push({ type: "category", name: c.label, series: v, expanded: z(c.label) });
125
+ });
126
+ const p = (t?.dirs ?? []).map((c) => ({ type: "playlist", name: c.name, playlistId: c.name }));
127
+ return p.length && o.push({ type: "playlist_category", name: "Playlists", playlists: p, expanded: z("Playlists") }), i.length && o.push({ type: "category", name: "Ecchi", series: i, expanded: z("Ecchi") }), o;
128
+ }
129
+ const dt = /^(?:https?|magnet):/i;
130
+ function Ae(e) {
131
+ const r = e.trim();
132
+ return r.length >= Be && r.length <= De && !dt.test(r);
133
+ }
134
+ function ve(e) {
135
+ return e ? `${K()}${e}` : null;
136
+ }
137
+ function ct(e) {
138
+ if (e === null || !Number.isFinite(e) || e < 0) return "";
139
+ const r = Math.floor(e % 60), t = Math.floor(e / 60) % 60, l = Math.floor(e / 3600), o = String(r).padStart(2, "0");
140
+ return l > 0 ? `${l}:${String(t).padStart(2, "0")}:${o}` : `${t}:${o}`;
141
+ }
142
+ async function Me(e, r) {
143
+ try {
144
+ const t = await fetch(`${K()}${e}`, { signal: r });
145
+ return t.ok ? { ok: !0, value: await t.json() } : { ok: !1, status: t.status };
146
+ } catch (t) {
147
+ return r.aborted ? { ok: !1, status: "aborted" } : (q("search", "Search request threw", Q(t)), { ok: !1, status: "network" });
148
+ }
149
+ }
150
+ function ut(e, r) {
151
+ return Me(`/api/search/shows?q=${encodeURIComponent(e.trim())}`, r);
152
+ }
153
+ function pt(e, r) {
154
+ return Me(`/api/search/youtube?q=${encodeURIComponent(e.trim())}`, r);
155
+ }
156
+ async function mt(e, r) {
157
+ try {
158
+ const t = await fetch(`${K()}/api/watchlist`, {
159
+ method: "POST",
160
+ headers: { "Content-Type": "application/json" },
161
+ body: JSON.stringify({ source: e, id: r })
162
+ });
163
+ if (t.status === 201) {
164
+ const l = await t.json();
165
+ return A("search", "Added to watch list", { source: e, id: r, series: l.series }), { ok: !0, value: l };
166
+ }
167
+ return { ok: !1, code: t.status === 409 ? "exists" : "failed", status: t.status };
168
+ } catch (t) {
169
+ return q("search", "Watch-list add threw", Q(t)), { ok: !1, code: "failed", status: "network" };
170
+ }
171
+ }
172
+ const gt = 350;
173
+ function ft({ query: e, onSubmitted: r }) {
174
+ const t = e.trim(), l = Ae(t), [o, i] = x(null), [p, c] = x(null), [y, v] = x({});
175
+ if (D(() => {
176
+ if (!l) return;
177
+ const d = new AbortController(), N = setTimeout(() => {
178
+ ut(t, d.signal).then((m) => {
179
+ d.signal.aborted || i({ q: t, outcome: m.ok ? { ok: !0, value: m.value.results } : m });
180
+ }), pt(t, d.signal).then((m) => {
181
+ d.signal.aborted || c({ q: t, outcome: m.ok ? { ok: !0, value: m.value.results } : m });
182
+ });
183
+ }, gt);
184
+ return () => {
185
+ clearTimeout(N), d.abort();
186
+ };
187
+ }, [t, l]), !l) return null;
188
+ const f = o?.q === t ? o.outcome : null, $ = p?.q === t ? p.outcome : null;
189
+ async function M(d) {
190
+ const N = `${d.source}:${d.id}`;
191
+ v((S) => ({ ...S, [N]: "adding" }));
192
+ const m = await mt(d.source, d.id);
193
+ v((S) => ({ ...S, [N]: m.ok || m.code === "exists" ? "added" : `error ${m.status}` }));
194
+ }
195
+ async function L(d) {
196
+ const N = `yt:${d.id}`;
197
+ v((S) => ({ ...S, [N]: "adding" }));
198
+ const m = await Ee(d.url, d.title);
199
+ m.ok ? (v((S) => ({ ...S, [N]: "added" })), r(m.value)) : v((S) => ({ ...S, [N]: W(m.failure) }));
200
+ }
201
+ const _ = f?.ok && $?.ok && f.value.length === 0 && $.value.length === 0;
202
+ return /* @__PURE__ */ u("div", { className: "border-b border-border", "data-testid": "media-search", children: [
203
+ _ && /* @__PURE__ */ a("div", { className: "px-3 py-2 text-[11px] text-text-muted", children: "no results" }),
204
+ /* @__PURE__ */ a(we, { label: "Shows", answer: f, testId: "search-shows", children: f?.ok && f.value.map((d) => {
205
+ const N = `${d.source}:${d.id}`, m = d.onWatchlist ? "added" : y[N];
206
+ return /* @__PURE__ */ a(
207
+ Ne,
208
+ {
209
+ testId: `show-${N}`,
210
+ image: ve(d.poster),
211
+ imageClass: "w-8 h-11",
212
+ title: d.title,
213
+ meta: [d.year, d.format, d.episodes ? `${d.episodes} ep` : null],
214
+ state: m,
215
+ canAdd: d.addable,
216
+ addLabel: `Add ${d.title} to watch list`,
217
+ onAdd: () => {
218
+ M(d);
219
+ }
220
+ },
221
+ N
222
+ );
223
+ }) }),
224
+ /* @__PURE__ */ a(we, { label: "YouTube", answer: $, testId: "search-youtube", children: $?.ok && $.value.map((d) => /* @__PURE__ */ a(
225
+ Ne,
226
+ {
227
+ testId: `video-${d.id}`,
228
+ image: ve(d.thumb),
229
+ imageClass: "w-16 h-9",
230
+ title: d.title,
231
+ meta: [d.channel, ct(d.durationS)],
232
+ state: y[`yt:${d.id}`],
233
+ canAdd: !0,
234
+ addLabel: `Request ${d.title}`,
235
+ onAdd: () => {
236
+ L(d);
237
+ }
238
+ },
239
+ d.id
240
+ )) })
241
+ ] });
242
+ }
243
+ function we({ label: e, answer: r, testId: t, children: l }) {
244
+ if (r?.ok && r.value.length === 0) return null;
245
+ const o = r && !r.ok && r.status !== "aborted";
246
+ return /* @__PURE__ */ u("div", { "data-testid": t, "data-loaded": r ? "true" : "false", children: [
247
+ /* @__PURE__ */ u("div", { className: "px-3 pt-2 pb-1 flex items-center gap-2 text-[10px] uppercase tracking-wider text-text-muted", children: [
248
+ /* @__PURE__ */ a("span", { children: e }),
249
+ !r && /* @__PURE__ */ a("span", { className: "text-text-tertiary", children: "…" }),
250
+ o && /* @__PURE__ */ a("span", { className: "bg-danger-bg text-on-danger-bg px-1 rounded normal-case", children: "unavailable" })
251
+ ] }),
252
+ l
253
+ ] });
254
+ }
255
+ function Ne({ testId: e, image: r, imageClass: t, title: l, meta: o, state: i, canAdd: p, addLabel: c, onAdd: y }) {
256
+ const v = o.filter((f) => f !== null && f !== "").join(" · ");
257
+ return /* @__PURE__ */ u("div", { className: "px-3 py-1 flex items-center gap-2", "data-testid": e, "data-state": i ?? "", children: [
258
+ r ? /* @__PURE__ */ a("img", { src: r, alt: "", loading: "lazy", className: `${t} object-cover rounded shrink-0 bg-bg-alt` }) : /* @__PURE__ */ a("div", { className: `${t} rounded shrink-0 bg-bg-alt` }),
259
+ /* @__PURE__ */ u("div", { className: "flex-1 min-w-0", children: [
260
+ /* @__PURE__ */ a("div", { className: "text-[11px] text-text truncate", title: l, children: l }),
261
+ v && /* @__PURE__ */ a("div", { className: "text-[10px] text-text-muted truncate", children: v })
262
+ ] }),
263
+ i === "added" ? /* @__PURE__ */ a(re, { name: "check", variant: "tint", family: "success", round: !0, className: "shrink-0" }) : i && i !== "adding" ? /* @__PURE__ */ a("span", { className: "text-[10px] bg-danger-bg text-on-danger-bg px-1 rounded shrink-0", children: i }) : p && /* @__PURE__ */ a(
264
+ "button",
265
+ {
266
+ onClick: y,
267
+ disabled: i === "adding",
268
+ "aria-label": c,
269
+ className: "drawer-btn shrink-0",
270
+ children: /* @__PURE__ */ a(re, { name: "plus" })
271
+ }
272
+ )
273
+ ] });
274
+ }
275
+ const ht = 4e3, bt = {
276
+ pending: "bg-neutral-bg text-on-neutral-bg",
277
+ approved: "bg-primary-bg text-on-primary-bg",
278
+ fetching: "bg-primary-bg text-on-primary-bg",
279
+ ingesting: "bg-primary-bg text-on-primary-bg",
280
+ ready: "bg-success-bg text-on-success-bg",
281
+ rejected: "bg-danger-bg text-on-danger-bg",
282
+ failed: "bg-danger-bg text-on-danger-bg"
283
+ };
284
+ function xt({ item: e }) {
285
+ const r = e, t = e.title || e.source;
286
+ return /* @__PURE__ */ u(
287
+ "div",
288
+ {
289
+ className: "px-3 py-1.5 border-b border-border/50 flex items-center gap-2",
290
+ "data-submission-id": e.id,
291
+ "data-submission-state": e.state,
292
+ "data-submission-kind": e.kind,
293
+ "data-submission-series": r.series ?? "",
294
+ children: [
295
+ /* @__PURE__ */ a("span", { className: "text-[10px] uppercase tracking-wider text-text-muted shrink-0", children: e.kind }),
296
+ /* @__PURE__ */ a("span", { className: "text-[11px] text-text-tertiary truncate flex-1", title: e.source, children: r.series ? `${r.series} — ${t}` : t }),
297
+ /* @__PURE__ */ a("span", { className: `text-[10px] uppercase tracking-wider px-1 rounded ${bt[e.state] ?? "text-text-muted"}`, children: e.state }),
298
+ e.error && /* @__PURE__ */ a("span", { className: "text-[10px] bg-danger-bg text-on-danger-bg px-1 rounded truncate max-w-[10rem]", children: e.error })
299
+ ]
300
+ }
301
+ );
302
+ }
303
+ function yt({ open: e, onClose: r }) {
304
+ const [t, l] = x(""), [o, i] = x(""), [p, c] = x(""), [y, v] = x(!1), [f, $] = x([]), [M, L] = x(!1), [_, d] = x(null), [N, m] = x(null), [S, H] = x(null), [R, V] = x(null), [T, O] = x(null), [U, n] = x(!1), [h, g] = x(null), [b, k] = x(null), E = ce(null), P = ce(!1), F = ke(() => ue(t), [t]) === "magnet" && t.trim().length > 0, ie = Ae(t), B = Se(async () => {
305
+ const s = await He();
306
+ P.current && (s.ok ? (m(s.value), H(null)) : H(s.failure));
307
+ }, []);
308
+ D(() => {
309
+ if (!e) return;
310
+ P.current = !0, B();
311
+ const s = setInterval(() => {
312
+ B();
313
+ }, ht);
314
+ return () => {
315
+ P.current = !1, clearInterval(s);
316
+ };
317
+ }, [e, B]), D(() => {
318
+ if (!e) return;
319
+ let s = !0;
320
+ return pe().then((w) => {
321
+ s && w.ok && V(w.value);
322
+ }), () => {
323
+ s = !1;
324
+ };
325
+ }, [e]), D(() => {
326
+ if (!e) return;
327
+ let s = !0;
328
+ return J("output/fmp4/anime").then((w) => {
329
+ s && $(w.dirs.map((C) => C.name));
330
+ }).catch(() => {
331
+ }), () => {
332
+ s = !1;
333
+ };
334
+ }, [e]);
335
+ function Re(s) {
336
+ if (l(s), ue(s) === "magnet" && s.trim().length > 0 && !y) {
337
+ const C = Ye(s);
338
+ C && c(C);
339
+ }
340
+ }
341
+ function Le() {
342
+ l(""), i(""), c(""), v(!1);
343
+ }
344
+ const le = !F || o.trim().length > 0 && Ve(p);
345
+ async function oe() {
346
+ const s = t.trim();
347
+ if (!s || M || ie || F && !le) return;
348
+ L(!0), d(null);
349
+ const w = F ? await Je(s, { title: p.trim() || void 0, series: o.trim() }) : await Ee(s);
350
+ if (L(!1), !w.ok) {
351
+ d(w.failure);
352
+ return;
353
+ }
354
+ A("submissions", "Submit accepted", { id: w.value.id, state: w.value.state }), Le(), m((C) => [w.value, ...(C ?? []).filter((I) => I.id !== w.value.id)]), B();
355
+ }
356
+ function Te(s) {
357
+ const w = s.target.files?.[0] ?? null;
358
+ if (k(null), w && R && w.size > R.maxBytes) {
359
+ O(null), k({ code: "too_large", maxBytes: R.maxBytes }), E.current && (E.current.value = "");
360
+ return;
361
+ }
362
+ O(w);
363
+ }
364
+ async function _e() {
365
+ if (!T || U) return;
366
+ n(!0), k(null);
367
+ let s = R;
368
+ if (!s) {
369
+ const I = await pe();
370
+ if (!I.ok) {
371
+ n(!1), k(I.failure);
372
+ return;
373
+ }
374
+ s = I.value, V(s);
375
+ }
376
+ if (T.size > s.maxBytes) {
377
+ n(!1), k({ code: "too_large", maxBytes: s.maxBytes });
378
+ return;
379
+ }
380
+ const w = await Ke(T, s, (I, X, Ue) => {
381
+ g({ sent: I, total: X, phase: Ue });
382
+ });
383
+ if (!w.ok) {
384
+ n(!1), g(null), k(w.failure);
385
+ return;
386
+ }
387
+ const C = await Qe(w.value.fileId, T.name);
388
+ if (n(!1), g(null), !C.ok) {
389
+ k(C.failure);
390
+ return;
391
+ }
392
+ A("submissions", "Upload accepted", { id: C.value.id }), O(null), E.current && (E.current.value = ""), m((I) => [C.value, ...(I ?? []).filter((X) => X.id !== C.value.id)]), B();
393
+ }
394
+ const de = b ? "maxBytes" in b || b.code === "target_unavailable" || b.code === "upload_failed" ? We(b) : W(b) : null;
395
+ return /* @__PURE__ */ u(
396
+ $e,
397
+ {
398
+ open: e,
399
+ onClose: r,
400
+ header: /* @__PURE__ */ u(j, { children: [
401
+ /* @__PURE__ */ a("span", { className: "text-xs uppercase tracking-wider text-text-muted", children: "Add media" }),
402
+ /* @__PURE__ */ a(
403
+ "button",
404
+ {
405
+ onClick: () => {
406
+ B();
407
+ },
408
+ "aria-label": "Refresh submissions",
409
+ className: `${ae} ml-auto`,
410
+ children: /* @__PURE__ */ a(re, { name: "refresh" })
411
+ }
412
+ )
413
+ ] }),
414
+ children: [
415
+ /* @__PURE__ */ u("div", { className: "p-3 border-b border-border flex flex-col gap-2", children: [
416
+ /* @__PURE__ */ a(
417
+ "input",
418
+ {
419
+ value: t,
420
+ onChange: (s) => Re(s.target.value),
421
+ onKeyDown: (s) => {
422
+ s.key === "Enter" && !F && oe();
423
+ },
424
+ "aria-label": "Media link",
425
+ placeholder: "search, https:// or magnet:",
426
+ className: "w-full bg-bg-alt border border-border focus:border-border-light outline-none px-2 py-1 rounded text-xs"
427
+ }
428
+ ),
429
+ F && /* @__PURE__ */ u("div", { className: "flex flex-col gap-1.5", "data-testid": "magnet-fields", children: [
430
+ /* @__PURE__ */ a(
431
+ "input",
432
+ {
433
+ value: o,
434
+ onChange: (s) => i(s.target.value),
435
+ list: "add-media-series-options",
436
+ "aria-label": "Series",
437
+ placeholder: "series",
438
+ className: "w-full bg-bg-alt border border-border focus:border-border-light outline-none px-2 py-1 rounded text-xs"
439
+ }
440
+ ),
441
+ /* @__PURE__ */ a("datalist", { id: "add-media-series-options", children: f.map((s) => /* @__PURE__ */ a("option", { value: s }, s)) }),
442
+ /* @__PURE__ */ a(
443
+ "input",
444
+ {
445
+ value: p,
446
+ onChange: (s) => {
447
+ v(!0), c(s.target.value);
448
+ },
449
+ "aria-label": "Title",
450
+ placeholder: "title (SxxExx)",
451
+ className: "w-full bg-bg-alt border border-border focus:border-border-light outline-none px-2 py-1 rounded text-xs"
452
+ }
453
+ )
454
+ ] }),
455
+ /* @__PURE__ */ u("div", { className: "flex items-center gap-2", children: [
456
+ /* @__PURE__ */ a(
457
+ "button",
458
+ {
459
+ onClick: () => {
460
+ oe();
461
+ },
462
+ disabled: M || !t.trim() || ie || F && !le,
463
+ "aria-label": "Submit media",
464
+ className: "drawer-btn drawer-btn-primary",
465
+ children: M ? "Submitting…" : "Submit"
466
+ }
467
+ ),
468
+ _ && /* @__PURE__ */ a("span", { "data-testid": "submit-failure", className: "text-[11px] bg-danger-bg text-on-danger-bg px-1.5 py-0.5 rounded", children: W(_) })
469
+ ] })
470
+ ] }),
471
+ /* @__PURE__ */ a(
472
+ ft,
473
+ {
474
+ query: t,
475
+ onSubmitted: (s) => m((w) => [s, ...(w ?? []).filter((C) => C.id !== s.id)])
476
+ }
477
+ ),
478
+ /* @__PURE__ */ u("div", { className: "p-3 border-b border-border flex flex-col gap-2", children: [
479
+ /* @__PURE__ */ u("div", { className: "flex items-center gap-2", children: [
480
+ /* @__PURE__ */ a(
481
+ "input",
482
+ {
483
+ ref: E,
484
+ type: "file",
485
+ onChange: Te,
486
+ "aria-label": "Upload file",
487
+ className: "text-[11px] flex-1 min-w-0"
488
+ }
489
+ ),
490
+ /* @__PURE__ */ a(
491
+ "button",
492
+ {
493
+ onClick: () => {
494
+ _e();
495
+ },
496
+ disabled: !T || U,
497
+ "aria-label": "Upload",
498
+ className: "drawer-btn drawer-btn-primary",
499
+ children: U ? h?.phase === "assembling" ? "Assembling…" : "Uploading…" : "Upload"
500
+ }
501
+ )
502
+ ] }),
503
+ /* @__PURE__ */ a("span", { className: "text-[10px] text-text-muted", "data-testid": "upload-cap", children: R ? `cap ${ze(R.maxBytes)}` : "" }),
504
+ de && /* @__PURE__ */ a("span", { "data-testid": "upload-failure", className: "text-[11px] bg-danger-bg text-on-danger-bg px-1.5 py-0.5 rounded", children: de })
505
+ ] }),
506
+ /* @__PURE__ */ u("div", { className: "flex-1 overflow-auto", "data-testid": "my-submissions", children: [
507
+ S && /* @__PURE__ */ a("div", { className: "mx-3 my-2 px-3 py-2 text-[11px] bg-danger-bg text-on-danger-bg rounded", children: W(S) }),
508
+ N?.map((s) => /* @__PURE__ */ a(xt, { item: s }, s.id))
509
+ ] })
510
+ ]
511
+ }
512
+ );
513
+ }
514
+ const vt = { bomberman: Ze };
515
+ function Ie() {
516
+ Ce(null), A("game", "game_select", { gameId: null });
517
+ }
518
+ function wt({ onDone: e }) {
519
+ const r = se((i) => i.activeGameId), t = Oe();
520
+ function l(i) {
521
+ Ce(i), A("game", "game_select", { gameId: i }), e();
522
+ }
523
+ function o() {
524
+ Ie(), e();
525
+ }
526
+ return /* @__PURE__ */ a("div", { className: "flex-1 overflow-auto p-3", children: /* @__PURE__ */ a("div", { className: "grid grid-cols-2 gap-3", children: t.map((i) => {
527
+ const p = r === i.id, c = i.cover ?? vt[i.id];
528
+ return /* @__PURE__ */ u(
529
+ "button",
530
+ {
531
+ onClick: () => p ? o() : l(i.id),
532
+ className: `group relative aspect-square overflow-hidden rounded-lg border-2 transition-all cursor-pointer ${p ? "border-primary shadow-lg shadow-primary/30" : "border-border hover:border-primary"}`,
533
+ title: p ? `Stop ${i.name}` : `Start ${i.name}`,
534
+ children: [
535
+ c ? /* @__PURE__ */ a(
536
+ "img",
537
+ {
538
+ src: c,
539
+ alt: i.name,
540
+ className: "absolute inset-0 w-full h-full object-cover transition-transform group-hover:scale-105"
541
+ }
542
+ ) : /* @__PURE__ */ a("div", { className: "absolute inset-0 bg-bg-alt" }),
543
+ /* @__PURE__ */ a("div", { className: "absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/85 via-black/40 to-transparent px-2 pt-6 pb-2", children: /* @__PURE__ */ a("div", { className: "text-sm font-medium text-white leading-tight", children: i.name }) }),
544
+ p && /* @__PURE__ */ a("span", { className: "absolute top-2 left-2 px-1.5 py-0.5 bg-primary text-[10px] uppercase tracking-wide rounded text-white", children: "Active" })
545
+ ]
546
+ },
547
+ i.id
548
+ );
549
+ }) }) });
550
+ }
551
+ function Nt({ onDone: e }) {
552
+ return se((t) => t.activeGameId) ? /* @__PURE__ */ a("button", { onClick: () => {
553
+ Ie(), e();
554
+ }, className: "drawer-btn drawer-btn-danger ml-auto", children: "Stop game" }) : null;
555
+ }
556
+ const G = (e) => e.type === "category" ? e.series : [], te = (e, r) => e.type === "category" ? { ...e, series: r } : e;
557
+ function Rt({ open: e, onClose: r }) {
558
+ const [t, l] = x(!1), [o, i] = x(null), [p, c] = x([]), [y, v] = x(""), [f, $] = x(!1), [M, L] = x("watch"), _ = se((n) => n.snapshot.mediaId), d = Xe(), N = Se(async () => {
559
+ l(!0), i(null);
560
+ try {
561
+ const n = await ye(J);
562
+ c((h) => he(n, fe(h, G), G, te)), Y() && at();
563
+ } catch (n) {
564
+ i(n.message || "catalog_error");
565
+ } finally {
566
+ l(!1);
567
+ }
568
+ }, []), m = Ge();
569
+ D(() => {
570
+ if (!m) return;
571
+ let n = !0;
572
+ return ye(J).then(
573
+ (h) => {
574
+ n && c((g) => he(h, fe(g, G), G, te));
575
+ },
576
+ (h) => q("catalog", "Catalog prefetch failed", Q(h))
577
+ ), () => {
578
+ n = !1;
579
+ };
580
+ }, [m]), D(() => {
581
+ e && (L("watch"), c((n) => ot(n, G, te)), N());
582
+ }, [e, N]);
583
+ function S(n) {
584
+ je(n), A("catalog", "control_load", { mediaId: n }), r();
585
+ }
586
+ function H(n) {
587
+ Pe(n), A("catalog", "playlist_load", { playlistId: n }), r();
588
+ }
589
+ function R() {
590
+ qe(), A("controls", "Unload media clicked (media drawer)"), r();
591
+ }
592
+ function V(n, h) {
593
+ c((g) => g.map((b, k) => k !== n || !("series" in b) ? b : { ...b, series: b.series.map((E, P) => P !== h ? E : { ...E, expanded: !E.expanded }) }));
594
+ }
595
+ function T(n) {
596
+ c((h) => h.map((g, b) => b !== n ? g : { ...g, expanded: !g.expanded }));
597
+ }
598
+ function O() {
599
+ tt(), N();
600
+ }
601
+ const U = ke(() => {
602
+ if (!y.trim()) return p;
603
+ const n = y.toLowerCase();
604
+ return p.map((h) => {
605
+ if (h.type === "playlist_category") {
606
+ const b = h.playlists.filter((k) => k.name.toLowerCase().includes(n));
607
+ return b.length ? { ...h, playlists: b, expanded: !0 } : h.name.toLowerCase().includes(n) ? { ...h, expanded: !0 } : null;
608
+ }
609
+ const g = h.series.map((b) => {
610
+ const k = b.episodes.filter((E) => E.name.toLowerCase().includes(n));
611
+ return k.length ? { ...b, episodes: k, expanded: !0 } : b.name.toLowerCase().includes(n) ? { ...b, expanded: !0 } : null;
612
+ }).filter(Boolean);
613
+ return g.length ? { ...h, series: g, expanded: !0 } : h.name.toLowerCase().includes(n) ? { ...h, expanded: !0 } : null;
614
+ }).filter(Boolean);
615
+ }, [p, y]);
616
+ return /* @__PURE__ */ u(j, { children: [
617
+ /* @__PURE__ */ a(yt, { open: f, onClose: () => $(!1) }),
618
+ /* @__PURE__ */ a(
619
+ $e,
620
+ {
621
+ open: e && !f,
622
+ onClose: r,
623
+ header: /* @__PURE__ */ u(j, { children: [
624
+ /* @__PURE__ */ a("div", { className: "tl-tabs", role: "tablist", "aria-label": "Media drawer", children: ["watch", "play"].map((n) => /* @__PURE__ */ a(
625
+ "button",
626
+ {
627
+ role: "tab",
628
+ "aria-selected": M === n,
629
+ onClick: () => L(n),
630
+ className: "tl-tab",
631
+ children: n === "watch" ? "Watch" : "Play"
632
+ },
633
+ n
634
+ )) }),
635
+ M === "play" && /* @__PURE__ */ a(Nt, { onDone: r }),
636
+ M === "watch" && /* @__PURE__ */ u(j, { children: [
637
+ d && /* @__PURE__ */ a(
638
+ "button",
639
+ {
640
+ onClick: () => $(!0),
641
+ "aria-label": "Add Media",
642
+ className: ae,
643
+ children: "Add"
644
+ }
645
+ ),
646
+ _ && /* @__PURE__ */ a(
647
+ "button",
648
+ {
649
+ onClick: R,
650
+ title: "Unload the current media for everyone in the room",
651
+ className: "drawer-btn drawer-btn-primary",
652
+ children: "Unload"
653
+ }
654
+ ),
655
+ /* @__PURE__ */ u("button", { onClick: O, disabled: t, className: `${ae} inline-flex items-center gap-1.5`, children: [
656
+ t && /* @__PURE__ */ a("span", { className: "inline-block w-3 h-3 border-2 border-text-muted border-t-transparent rounded-full animate-spin" }),
657
+ t ? "Reloading…" : "Reload"
658
+ ] }),
659
+ /* @__PURE__ */ a("input", { value: y, onChange: (n) => v(n.target.value), placeholder: "search", className: "ml-auto bg-bg-alt border border-border focus:border-border-light outline-none px-2 py-1 rounded text-xs" })
660
+ ] })
661
+ ] }),
662
+ footer: Y() ? /* @__PURE__ */ a("div", { className: "p-2 text-[10px] text-text-muted border-t border-border flex justify-end", children: /* @__PURE__ */ a("span", { className: "opacity-70", children: "verbose" }) }) : void 0,
663
+ children: M === "play" ? /* @__PURE__ */ a(wt, { onDone: r }) : /* @__PURE__ */ u(j, { children: [
664
+ t && !p.length && /* @__PURE__ */ a("div", { className: "px-4 py-2 text-xs text-text-muted", children: "Loading catalog…" }),
665
+ o && /* @__PURE__ */ a("div", { className: "mx-3 px-3 py-2 text-xs bg-danger-bg text-on-danger-bg rounded", children: o }),
666
+ /* @__PURE__ */ u("div", { className: "flex-1 overflow-auto text-sm py-2", children: [
667
+ U.map((n, h) => /* @__PURE__ */ u("div", { className: "px-3 pb-3", children: [
668
+ /* @__PURE__ */ u(
669
+ "div",
670
+ {
671
+ className: "flex items-center gap-2 cursor-pointer select-none group",
672
+ "data-category": n.name,
673
+ "data-expanded": n.expanded ? "true" : "false",
674
+ onClick: () => T(h),
675
+ children: [
676
+ /* @__PURE__ */ a("span", { className: "text-xs uppercase tracking-wider text-text-muted group-hover:text-text-secondary", children: n.name }),
677
+ /* @__PURE__ */ a("span", { className: "text-[10px] opacity-50", children: n.expanded ? "−" : "+" })
678
+ ]
679
+ }
680
+ ),
681
+ n.expanded && n.type === "playlist_category" && /* @__PURE__ */ u("div", { className: "mt-2 space-y-1", children: [
682
+ n.playlists.map((g) => /* @__PURE__ */ u("button", { onClick: () => H(g.playlistId), className: "w-full text-left px-2 py-1 rounded hover:bg-bg-alt flex items-center gap-2 group", children: [
683
+ /* @__PURE__ */ a("span", { className: "text-[11px] text-text-tertiary group-hover:text-text-secondary truncate", children: g.name }),
684
+ /* @__PURE__ */ a("span", { className: "ml-auto text-[10px] text-text underline decoration-primary underline-offset-2 opacity-0 group-hover:opacity-100 transition-opacity", children: "Load" })
685
+ ] }, g.playlistId)),
686
+ n.playlists.length === 0 && /* @__PURE__ */ a("div", { className: "text-[11px] text-text-muted italic", children: "(no playlists)" })
687
+ ] }),
688
+ n.expanded && n.type === "category" && /* @__PURE__ */ u("div", { className: "mt-2 space-y-2", children: [
689
+ n.series.map((g, b) => /* @__PURE__ */ u("div", { children: [
690
+ /* @__PURE__ */ u("div", { className: "flex items-center gap-2 cursor-pointer text-text-secondary hover:text-text", onClick: () => V(h, b), children: [
691
+ /* @__PURE__ */ a("span", { className: "text-xs font-medium", children: g.name }),
692
+ /* @__PURE__ */ a("span", { className: "text-[10px] opacity-40", children: g.expanded ? "−" : "+" })
693
+ ] }),
694
+ g.expanded && /* @__PURE__ */ u("div", { className: "mt-1 ml-3 border-l border-border pl-3 space-y-1", children: [
695
+ g.episodes.map((k) => /* @__PURE__ */ u("button", { onClick: () => S(k.mediaId), className: "w-full text-left px-2 py-1 rounded hover:bg-bg-alt flex items-center gap-2 group", children: [
696
+ /* @__PURE__ */ a("span", { className: "text-[11px] text-text-tertiary group-hover:text-text-secondary truncate", children: k.name }),
697
+ /* @__PURE__ */ a("span", { className: "ml-auto text-[10px] text-text underline decoration-primary underline-offset-2 opacity-0 group-hover:opacity-100 transition-opacity", children: "Load" })
698
+ ] }, k.mediaId)),
699
+ g.episodes.length === 0 && /* @__PURE__ */ a("div", { className: "text-[11px] text-text-muted italic", children: "(no episodes)" })
700
+ ] })
701
+ ] }, g.name)),
702
+ n.series.length === 0 && /* @__PURE__ */ a("div", { className: "text-[11px] text-text-muted italic", children: "(empty)" })
703
+ ] })
704
+ ] }, n.name)),
705
+ !t && !U.length && /* @__PURE__ */ a("div", { className: "px-4 py-6 text-center text-xs text-text-muted", children: "No matches" })
706
+ ] })
707
+ ] })
708
+ }
709
+ )
710
+ ] });
711
+ }
712
+ export {
713
+ Rt as default
714
+ };
715
+ //# sourceMappingURL=MediaDrawer-BzR_DusQ.js.map