webOS: a TV app with the views of arr-tui
A TypeScript web app for LG webOS TVs (webOS 5 / Chromium 68 and newer), driven by the remote: Movies and Series search with the Trakt lists, Library with status, title and file-property filters and download marks, seasons and episodes, release search and grab, Downloads, Calendar, Settings and Log. It talks to Radarr, Sonarr, SABnzbd and Trakt directly (they answer cross-origin requests); the arr-tui config is baked in at build time and can be changed on the TV. dev/screenshots/server.py answers cross-origin requests and accepts writes for it; webos/dev/drive.mjs drives the app in Chromium for screenshots. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
// Tiny DOM helpers: h("div.card", {onclick}, child, "text").
|
||||
|
||||
type Child = Node | string | number | null | undefined | false;
|
||||
type Attrs = Record<string, unknown>;
|
||||
|
||||
export function h<K extends keyof HTMLElementTagNameMap>(spec: K | `${K}.${string}` | `${K}#${string}`, attrs?: Attrs | Child, ...children: Child[]): HTMLElementTagNameMap[K] {
|
||||
const [tagAndId, ...classes] = spec.split(".");
|
||||
const [tag, id] = tagAndId.split("#");
|
||||
const el = document.createElement(tag) as HTMLElementTagNameMap[K];
|
||||
if (id) el.id = id;
|
||||
if (classes.length) el.className = classes.join(" ");
|
||||
if (attrs && typeof attrs === "object" && !(attrs instanceof Node)) {
|
||||
for (const [k, v] of Object.entries(attrs)) {
|
||||
if (v === undefined || v === null || v === false) continue;
|
||||
if (k.startsWith("on") && typeof v === "function") el.addEventListener(k.slice(2), v as EventListener);
|
||||
else if (k === "class") el.className += (el.className ? " " : "") + v;
|
||||
else if (k === "style") el.setAttribute("style", String(v));
|
||||
else if (k in el && typeof v !== "string") (el as unknown as Record<string, unknown>)[k] = v;
|
||||
else el.setAttribute(k, v === true ? "" : String(v));
|
||||
}
|
||||
} else if (attrs !== undefined) {
|
||||
children.unshift(attrs as Child);
|
||||
}
|
||||
append(el, ...children);
|
||||
return el;
|
||||
}
|
||||
|
||||
export function append(el: Element, ...children: Child[]): void {
|
||||
for (const c of children) {
|
||||
if (c === null || c === undefined || c === false) continue;
|
||||
el.appendChild(c instanceof Node ? c : document.createTextNode(String(c)));
|
||||
}
|
||||
}
|
||||
|
||||
export function clear(el: Element): void {
|
||||
while (el.firstChild) el.removeChild(el.firstChild);
|
||||
}
|
||||
|
||||
export function replace(el: Element, ...children: Child[]): void {
|
||||
clear(el);
|
||||
append(el, ...children);
|
||||
}
|
||||
Reference in New Issue
Block a user