import type { Metadata } from "next"; import { getPaths } from "yt-dlp-transcript-common/lib/paths"; import { findWidgetPreset, readWidgetPresets, } from "yt-dlp-transcript-common/lib/widgetPresets"; import { buildActiveJobsPayload } from "../jobs/active/buildActiveJobs"; import { buildWorkersPayload } from "../workers/buildWorkers"; import { BUILT_IN_PRESETS } from "yt-dlp-transcript-common/views/builtInPresets"; import { MonitorWidget } from "./components/MonitorWidget"; import { parseWidgetConfig } from "./lib/config"; export const dynamic = "force-dynamic"; export const metadata: Metadata = { title: "Monitor" }; type RawParams = Record; function first(v: string | string[] | undefined): string | undefined { return Array.isArray(v) ? v[0] : v; } // Expand `?preset=` into the params it stands for. A preset stores a // query string, so this is a merge of two links — and ANY param written // explicitly in the URL WINS, so `?preset=cockpit&titles=0` is the preset with // its titles off rather than a contradiction. Built-ins resolve too, and an // unknown reference is simply the rest of the URL. async function resolvePreset(params: RawParams): Promise { const ref = first(params.preset)?.trim(); if (!ref) return params; const saved = await readWidgetPresets(getPaths()); const preset = findWidgetPreset(saved, ref) ?? BUILT_IN_PRESETS.find( (p) => p.id === ref || p.name.toLowerCase() === ref.toLowerCase(), ) ?? null; if (!preset) return params; const merged: RawParams = {}; for (const [k, v] of new URLSearchParams(preset.query)) merged[k] = v; for (const [k, v] of Object.entries(params)) { if (k !== "preset") merged[k] = v; } return merged; } // Bare, read-only monitor. The root layout strips its chrome (see AppFrame) so // it can be embedded in a small pinned window or iframe. What it shows is driven // entirely by GET params (see lib/config); the /widget/builder page composes // those links, and `?preset=` names one that was saved. Initial payloads are // built server-side here for a flicker-free first paint; MonitorWidget then // polls the same /api endpoints for live data. export default async function WidgetPage({ searchParams, }: { searchParams: Promise>; }) { const config = parseWidgetConfig(await resolvePreset((await searchParams) ?? {})); // The disk strip reads its numbers off this payload as well, so a // disk-without-jobs widget needs it built too (see MonitorWidget's poll gate). const initialJobs = config.jobs || config.disk ? await buildActiveJobsPayload() : null; // Controls need the workers payload (for `paused`) even if the Workers section // itself is hidden. const initialWorkers = config.workers || config.controls ? buildWorkersPayload() : null; return ( ); }