diff --git a/client/gallery/Gallery.tsx b/client/gallery/Gallery.tsx index 98147aa..234c0bf 100644 --- a/client/gallery/Gallery.tsx +++ b/client/gallery/Gallery.tsx @@ -203,7 +203,7 @@ const DEMO: WidgetInstance[] = [ // Column E (x 900, w 150) — DDC/CI monitor input switcher (VCP 0x60): the active input highlighted // among the panel's supported sources, with the current display mode beneath. buildInstance('monitorswitch', 'd-ddc', { - rect: { x: 900, y: 36, w: 150, h: 308 }, + rect: { x: 900, y: 58, w: 150, h: 264 }, config: { showStats: true } }) ]; diff --git a/client/gallery/seed.ts b/client/gallery/seed.ts index 011f0f1..ebe7973 100644 --- a/client/gallery/seed.ts +++ b/client/gallery/seed.ts @@ -287,14 +287,16 @@ export const fakeSpectrum: SpectrumSource = { latestFrame: () => spectrumFrame() }; -/** Composite the branding icon onto a deterministic gradient and return PNG bytes — the now-playing - * placeholder cover. Falls back to the gradient alone if the icon can't be drawn. */ -async function coverBytes(): Promise { +/** Composite the branding icon onto a deterministic gradient and return a PNG data: URL — the + * now-playing cover. (ThumbnailInfo carries a `url`, not bytes; in the app that's an `art://` URL + * from art.rs, but a data: URL renders identically and needs no backend.) Falls back to the + * gradient alone if the icon can't be drawn. */ +async function coverDataUrl(): Promise { const c = document.createElement('canvas'); c.width = 240; c.height = 240; const ctx = c.getContext('2d'); - if (!ctx) return []; + if (!ctx) return ''; const g = ctx.createLinearGradient(0, 0, 240, 240); g.addColorStop(0, '#1c3a44'); g.addColorStop(0.55, '#122a31'); @@ -308,8 +310,7 @@ async function coverBytes(): Promise { } catch { // gradient-only fallback } - const bytes = atob(c.toDataURL('image/png').split(',')[1]); - return Array.from(bytes, (ch) => ch.charCodeAt(0)); + return c.toDataURL('image/png'); } function loadImage(src: string): Promise { @@ -323,7 +324,7 @@ function loadImage(src: string): Promise { /** Seed the module-level media store with one playing session (so NowPlaying renders a real track). */ export async function seedMedia(): Promise { - const data = await coverBytes(); + const url = await coverDataUrl(); const model: SessionModel = { source: 'foobar2000', playback: { auto_repeat: 'None', rate: 1, shuffle: false, status: 'Playing', type: 'Music' }, @@ -343,7 +344,7 @@ export async function seedMedia(): Promise { source: 'foobar2000', timestamp_created: null, timestamp_updated: null, - last_media_update: { Media: [model, { content_type: 'image/png', data }] }, + last_media_update: { Media: [model, { content_type: 'image/png', url }] }, last_model_update: { Model: model } }; handleUpdate({ sessionRecord: record }); diff --git a/docs/img/demo.png b/docs/img/demo.png index 7441d8b..290fbfc 100644 Binary files a/docs/img/demo.png and b/docs/img/demo.png differ diff --git a/docs/img/widgets/nowplaying.png b/docs/img/widgets/nowplaying.png index c55fb5e..28862ff 100644 Binary files a/docs/img/widgets/nowplaying.png and b/docs/img/widgets/nowplaying.png differ