From 1c34cc04c7252e76ff1575b50344a734731c205b Mon Sep 17 00:00:00 2001 From: Frank Stellmacher <171614930+Psychotoxical@users.noreply.github.com> Date: Wed, 13 May 2026 14:09:37 +0200 Subject: [PATCH] =?UTF-8?q?refactor(now-playing):=20G.71=20=E2=80=94=20ext?= =?UTF-8?q?ract=20helpers=20+=20cache=20+=20NpCardWrap=20+=20NpColumnEl=20?= =?UTF-8?q?+=20RadioView=20(cluster)=20(#638)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five-cut cluster opening the NowPlaying refactor. 1384 → 1119 LOC (−265). nowPlayingHelpers — eight pure helpers + ContributorRow type: formatTime, formatCompact, formatTotalDuration, sanitizeHtml (strip dangerous attributes + trailing Last.fm "Read more" link), isoToParts (date formatting for Bandsintown), buildContributorRows (dedupes contributor list, hides redundant "Artist = main artist" row), isRealArtistImage (filter the Last.fm "2a96…" placeholder MD5 that aggregating Subsonic backends still emit). nowPlayingCache — module-level TTL cache used by all subcomponents: CACHE_TTL_MS (5 min), CacheEntry type, makeCache() factory. NowPlaying still instantiates eight `makeCache<…>()` typed caches inline at module scope; only the factory + TTL constant move. NpCardWrap — drag-source wrapper around each dashboard card, participates in the psyDnD drag stream via useDragSource. NpColumnEl — drop-target column. Owns the document mousemove listener that determines which wrapper the dragged card would land before (x-axis decides column, y-axis bisects wrapper rects to compute insert index). No-op when no card is being dragged. RadioView — full radio-playing layout: hero card with stream name + current artist/title/album + AzuraCast progress bar + listeners badge, "Up Next" card, recently-played list. Subscribes to nothing on its own; takes the radioMeta tuple + currentRadio + resolvedCover from the parent. NonNullStoreField type alias moves with it. NowPlaying drops the inline definitions; renderStars + the eight typed cache instances stay in the page for now (renderStars is used by Hero + TopSongsCard, both of which still live inline; the typed caches are consumed by NowPlaying's load effects). Pure code move otherwise. --- src/components/nowPlaying/NpCardWrap.tsx | 27 +++ src/components/nowPlaying/NpColumnEl.tsx | 52 +++++ src/components/nowPlaying/RadioView.tsx | 102 ++++++++ src/pages/NowPlaying.tsx | 283 +---------------------- src/utils/nowPlayingCache.ts | 20 ++ src/utils/nowPlayingHelpers.ts | 87 +++++++ 6 files changed, 297 insertions(+), 274 deletions(-) create mode 100644 src/components/nowPlaying/NpCardWrap.tsx create mode 100644 src/components/nowPlaying/NpColumnEl.tsx create mode 100644 src/components/nowPlaying/RadioView.tsx create mode 100644 src/utils/nowPlayingCache.ts create mode 100644 src/utils/nowPlayingHelpers.ts diff --git a/src/components/nowPlaying/NpCardWrap.tsx b/src/components/nowPlaying/NpCardWrap.tsx new file mode 100644 index 00000000..451f952d --- /dev/null +++ b/src/components/nowPlaying/NpCardWrap.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { useDragSource } from '../../contexts/DragDropContext'; +import type { NpCardId } from '../../store/nowPlayingLayoutStore'; + +interface NpCardWrapProps { + id: NpCardId; + label: string; + isDraggingThis: boolean; + children: React.ReactNode; +} + +export default function NpCardWrap({ id, label, isDraggingThis, children }: NpCardWrapProps) { + const dragProps = useDragSource(() => ({ + data: JSON.stringify({ kind: 'np-card', id }), + label, + })); + return ( +