@behindthemusictree/app-kit-ui 26.1.0 → 28.0.2
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.
- package/dist/{chunk-KPWV4LEJ.js → chunk-5PQWJRDZ.js} +1061 -541
- package/dist/chunk-5PQWJRDZ.js.map +1 -0
- package/dist/chunk-DKQQLLMY.js +246 -0
- package/dist/chunk-DKQQLLMY.js.map +1 -0
- package/dist/{chunk-ZLCRD7OK.js → chunk-XUC4UJDQ.js} +2 -2
- package/dist/genre-tree/index.cjs +1260 -635
- package/dist/genre-tree/index.cjs.map +1 -1
- package/dist/genre-tree/index.d.ts +35 -8
- package/dist/genre-tree/index.js +2 -2
- package/dist/genre-tree/styles.css +80 -0
- package/dist/index.cjs +1286 -661
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +6 -4
- package/dist/popup/index.cjs +179 -73
- package/dist/popup/index.cjs.map +1 -1
- package/dist/popup/index.d.ts +22 -2
- package/dist/popup/index.js +5 -3
- package/package.json +7 -7
- package/dist/chunk-KPWV4LEJ.js.map +0 -1
- package/dist/chunk-VDXUW7PP.js +0 -141
- package/dist/chunk-VDXUW7PP.js.map +0 -1
- /package/dist/{chunk-ZLCRD7OK.js.map → chunk-XUC4UJDQ.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/genre-tree/GenreTreeView.tsx","../src/genre-tree/GenreDetailExtras.tsx","../src/genre-tree/ClampedText.tsx","../src/genre-tree/GenrePlaylistTracks.tsx","../src/genre-tree/UnplayableReason.tsx","../src/genre-tree/MusicBrainzRecordingLink.tsx","../src/genre-tree/playlist-tree/TreePerRoot.tsx","../src/genre-tree/playlist-tree/useGenrePlaylistPlayback.ts","../src/genre-tree/playlist-tree/TreeWheel.tsx","../src/genre-tree/playlist-tree/size-metric.ts","../src/genre-tree/playlist-tree/TreeWheelRadialPopCore.tsx","../src/genre-tree/GenreTreeWheelHandoff.tsx","../src/genre-tree/GenreSearch.tsx","../src/genre-tree/GenreActionBand.tsx","../src/genre-tree/PanelEdgeTab.tsx","../src/genre-tree/ChangeParentPopup.tsx","../src/genre-tree/artist/useArtistPanel.tsx","../src/genre-tree/artist/ArtistHeader.tsx","../src/genre-tree/artist/ArtistLinks.tsx","../src/genre-tree/artist/formatYears.ts","../src/genre-tree/artist/ArtistPhoto.tsx","../src/genre-tree/artist/ArtistMembers.tsx","../src/genre-tree/artist/ArtistDiscography.tsx","../src/genre-tree/artist/ArtistSongs.tsx","../src/genre-tree/track-list-sidebar/TrackItem.tsx","../src/genre-tree/TrackPositionPlayPause.tsx","../src/genre-tree/SizeMetricToggle.tsx","../src/genre-tree/index.ts","../src/genre-tree/track-list-sidebar/TrackListSidebar.tsx","../src/genre-tree/PlayerPanel.tsx","../src/genre-tree/Rating.tsx"],"sourcesContent":["\"use client\";\n\nimport { useCallback, useDeferredValue, useEffect, useMemo, useRef, useState, type ComponentProps, type ReactNode } from \"react\";\nimport { useIsMutating } from \"@tanstack/react-query\";\nimport { z } from \"zod\";\nimport { Plus } from \"lucide-react\";\nimport { IconTextButton, Button, SegmentedControl } from \"@behindthemusictree/ui\";\n\nimport {\n GenreTreeWheelSkeleton,\n GenreTreeViewSkeleton,\n} from \"@behindthemusictree/genre-tree-view\";\nimport type {\n ControlsOrientation,\n GenreTreeAction,\n GenreTreeNode,\n GenreTreeViewMode,\n GenreTreeViewport,\n InfoPanelBody,\n InfoPanelSections,\n} from \"@behindthemusictree/genre-tree-view\";\n\nimport {\n CriteriaMinimum,\n CriteriaPlaylistSimple,\n useArtistSearch,\n type ArtistSearchResult,\n} from \"@behindthemusictree/app-kit/genre-tree\";\nimport { CriteriaOverview } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { Scope } from \"@behindthemusictree/app-kit/transport\";\nimport { useGenreTreeViewState, useArtist, genreUpdateMutationKey } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { usePlayer } from \"@behindthemusictree/app-kit/player\";\nimport { usePopup } from \"@behindthemusictree/app-kit/popup\";\nimport { GenreDetailExtras, type GenreDetailSlots } from \"./GenreDetailExtras\";\nimport type { GenrePlaylistTracksSlots } from \"./GenrePlaylistTracks\";\nimport type { InfoPanelCurationProps } from \"./playlist-tree/infoPanelCurationProps\";\nimport type { RenderArtistLink } from \"@behindthemusictree/app-kit/genre-tree\";\n\nimport GenrePlaylistTreePerRoot from \"./playlist-tree/TreePerRoot\";\nimport GenrePlaylistTreeWheel from \"./playlist-tree/TreeWheel\";\nimport GenrePlaylistTreeWheelRadialPopCore, { type GenreFocusProps } from \"./playlist-tree/TreeWheelRadialPopCore\";\nimport { GenreTreeWheelHandoff } from \"./GenreTreeWheelHandoff\";\nimport GenreSearch, { type GenreSearchProps } from \"./GenreSearch\";\nimport GenreActionBand from \"./GenreActionBand\";\nimport PanelEdgeTab from \"./PanelEdgeTab\";\nimport ChangeParentPopup, { type ParentChange } from \"./ChangeParentPopup\";\nimport { useArtistPanel } from \"./artist/useArtistPanel\";\nimport { SizeMetricToggle } from \"./SizeMetricToggle\";\nimport type { GenreTreeSizeMetric } from \"./playlist-tree/size-metric\";\nimport { useGenrePlaylistPlayback, type GenrePlaylistPlayback } from \"./playlist-tree/useGenrePlaylistPlayback\";\n\n// genre-tree-view's \"previous\" button (back to the last selected genre) renders inside the info panel, which sits\n// under the floating search bar. It is hidden and its click is proxied by the search field's own button, which\n// appears (fading in, sliding from the left) whenever the original exists; the field makes room for it.\nconst PREVIOUS_BUTTON_CSS = `\n.genre-tree-view .search-overlay .gtv-info-panel-previous { display: none; }\n.genre-tree-view:has(.search-overlay .gtv-info-panel-previous) .genre-search input { padding-left: 2.75rem; }\n.genre-tree-view:has(.search-overlay .gtv-info-panel-previous) .genre-search-previous { display: flex; }\n@keyframes genre-tree-view-previous-in { from { opacity: 0; transform: translate(-0.5rem, -50%); } to { opacity: 1; transform: translate(0, -50%); } }\n@media (prefers-reduced-motion: no-preference) {\n .genre-search-previous { animation: genre-tree-view-previous-in 200ms ease-out; }\n}\n`;\n\ntype GenreNodeActionBandProps = Omit<ComponentProps<typeof GenreActionBand>, \"onPlayPause\" | \"isPlaying\"> & {\n genrePlaylist: CriteriaPlaylistSimple;\n scope: Scope;\n playback: GenrePlaylistPlayback;\n};\n\nfunction GenreNodeActionBand({ genrePlaylist, scope, playback, ...bandProps }: GenreNodeActionBandProps) {\n const { isPlaying, setIsPlaying } = usePlayer();\n const isCurrentTrackList = playback.playingNodeId === genrePlaylist.uuid;\n const handlePlayPause = () => {\n if (isCurrentTrackList) {\n setIsPlaying(!isPlaying);\n return;\n }\n playback.play(genrePlaylist).catch((error) => {\n console.error(\"Failed to load genre playlist tracks:\", error);\n });\n };\n return (\n <GenreActionBand\n {...bandProps}\n onPlayPause={genrePlaylist.tracksCount > 0 ? handlePlayPause : undefined}\n isPlaying={isCurrentTrackList && isPlaying}\n />\n );\n}\n\nexport type {\n ControlsOrientation,\n FocusChangeReason,\n GenreTreeViewMode,\n} from \"@behindthemusictree/genre-tree-view\";\nexport type { GenreFocusProps } from \"./playlist-tree/TreeWheelRadialPopCore\";\n\n/** Artists are searched by default; picking one opens its panel (see `artistUuid`). */\nexport type ArtistSearchProps =\n | { artistSearch: false; onArtistSelect?: never; artistSearchBaseUrl?: never }\n | {\n artistSearch?: true;\n /** Notified when a suggestion is picked, after the view opens its artist panel. */\n onArtistSelect?: (artist: ArtistSearchResult) => void;\n /** Backend that serves `artists/` (search and the artist panel). Defaults to `getBackendBaseUrl`. */\n artistSearchBaseUrl?: () => string;\n };\n\nconst NO_POP_ROOT_TITLE = \"This genre tree has no 'Mainstream Pop' root yet\";\n\nexport type GenreTreeViewProps<O extends CriteriaOverview = CriteriaOverview> = {\n scope: Scope;\n /** \"regions\" lays the info panel out in the brand's left/center/right columns (see brand `--layout-center-*`). */\n placement?: \"default\" | \"regions\";\n handleGenreCreationAction: (parent: CriteriaMinimum | null) => void;\n handleGenreRenameAction: (genre: CriteriaMinimum) => void;\n getBackendBaseUrl: () => string;\n /** Called after the Parent popup moved a genre; `from` and `to` are genre (criteria) uuids, `from` null for a root genre. */\n onParentChanged?: (change: ParentChange) => void;\n additionalActions?: (node: GenreTreeNode) => GenreTreeAction[];\n /** Controlled view mode. When provided, the internal Stacked/Wheel toggle is not rendered — the consumer owns that UI. */\n viewMode?: GenreTreeViewMode;\n /** Controlled selection (a genre playlist uuid, i.e. the tree node id). When provided, the\n * consumer owns it — e.g. to move the selection after deleting the selected genre. */\n selectedNodeId?: string | null;\n /** Called on every user-driven selection change (tree click, search pick, search clear). */\n onSelectedNodeIdChange?: (nodeId: string | null) => void;\n /** Mount-only seed for the pan/zoom (wheel, pop-core and stacked views), replacing the initial fit. */\n defaultViewport?: GenreTreeViewport;\n /** Fires after a user-driven pan/zoom settles. In stacked view, only the tree holding the selection reports. */\n onViewportChange?: (viewport: GenreTreeViewport) => void;\n /** When true, hides the \"Add root\" button and suppresses per-node\n * create/rename affordances, for a read-only consumer. Defaults to false. */\n readOnly?: boolean;\n /** Root element classes; the component adds no outer margin. */\n className?: string;\n /** Shows the \"Tree display options\" group (e.g. Rotation). Defaults to true. */\n showDisplayOptions?: boolean;\n /** Shows the \"Add root\" button (still hidden when `readOnly`). Defaults to true; a host rendering its own passes false. */\n showAddRoot?: boolean;\n /** Controlled wheel rotation. When provided, the internal Rotation toggle is not rendered — the consumer owns that UI. */\n allowWheelRotation?: boolean;\n /** Parses the selected genre's overview; pass an extended schema to keep consumer-specific fields. */\n criteriaOverviewSchema?: z.ZodType<O, unknown>;\n /** Consumer-specific rows rendered in the info panel after Summary. */\n renderGenreDetailExtras?: (overview: O) => ReactNode;\n /** Layout of the wheel/tree floating zoom/pop/fit controls. Defaults to \"horizontal\". */\n controlsOrientation?: ControlsOrientation;\n /** Controlled size metric of the wheel and pop-core nodes: song count or summed song views. Uncontrolled when omitted\n * (starts on songs). The toggle is hidden while an artist filters the tree, which only has song counts. */\n sizeMetric?: GenreTreeSizeMetric;\n onSizeMetricChange?: (metric: GenreTreeSizeMetric) => void;\n /** When set, artist names in the info panel's track list render through it (e.g. a router `Link`). In the artist\n * panel, names otherwise render as buttons that switch the panel to that artist. */\n renderArtistLink?: RenderArtistLink;\n /** Controlled artist panel (an artist uuid). It shares the info panel's slot: opening an artist clears the selected\n * genre; selecting a genre keeps the artist as a filter (the tree shows only genres the artist has songs in, and\n * the genre's songs and play button are limited to the artist) and shows the genre's panel instead. A host\n * `overlayPanel` takes precedence over the artist panel. */\n artistUuid?: string | null;\n /** Called when the user opens an artist (search pick, member link) or closes its panel. */\n onArtistUuidChange?: (uuid: string | null) => void;\n /** Controlled expanded state of the artist panel. Uncontrolled, it resets to compact on each artist. */\n artistExpanded?: boolean;\n onArtistExpandedChange?: (expanded: boolean) => void;\n} & ArtistSearchProps &\n GenreDetailSlots<O> &\n GenrePlaylistTracksSlots &\n InfoPanelCurationProps &\n GenreFocusProps & {\n /** Hides the info panel behind its edge tab; uncontrolled when omitted. Only applies with `placement=\"regions\"`. */\n collapsed?: boolean;\n onCollapsedChange?: (collapsed: boolean) => void;\n };\n\nconst REGIONS_CLASS =\n \"[--gtv-center-left:var(--layout-center-left)] [--gtv-center-right:var(--layout-center-right)] [--gtv-center-top:var(--layout-band-top)] [&_.gtv-info-panel--overlay]:[--gtv-info-panel-header-inset:0px]\";\n\nexport function GenreTreeView<O extends CriteriaOverview = CriteriaOverview>({\n scope,\n handleGenreCreationAction,\n handleGenreRenameAction,\n onParentChanged,\n getBackendBaseUrl,\n additionalActions,\n viewMode: controlledViewMode,\n selectedNodeId: controlledSelectedNodeId,\n onSelectedNodeIdChange,\n defaultViewport,\n onViewportChange,\n readOnly = false,\n className,\n showDisplayOptions = true,\n showAddRoot = true,\n allowWheelRotation: controlledAllowWheelRotation,\n criteriaOverviewSchema,\n renderGenreDetailExtras,\n controlsOrientation = \"horizontal\",\n sizeMetric: controlledSizeMetric,\n onSizeMetricChange,\n renderArtistLink,\n artistUuid: controlledArtistUuid,\n onArtistUuidChange,\n artistExpanded: controlledArtistExpanded,\n onArtistExpandedChange,\n artistSearch = true,\n onArtistSelect,\n artistSearchBaseUrl,\n renderSummary,\n renderEssentialTracks,\n renderSongLeading,\n renderSongActions,\n renderTracksToolbar,\n renderDangerZone,\n expanded,\n onExpandedChange,\n collapsed,\n onCollapsedChange,\n overlayPanel,\n placement = \"default\",\n ...focus\n}: GenreTreeViewProps<O>) {\n const [internalArtistUuid, setInternalArtistUuid] = useState<string | null>(null);\n const isArtistControlled = controlledArtistUuid !== undefined;\n const artistUuid = isArtistControlled ? controlledArtistUuid : internalArtistUuid;\n const [internalSizeMetric, setInternalSizeMetric] = useState<GenreTreeSizeMetric>(\"songs\");\n const sizeMetric = controlledSizeMetric ?? internalSizeMetric;\n const deferredSizeMetric = useDeferredValue(sizeMetric);\n const changeSizeMetric = (metric: GenreTreeSizeMetric) => {\n if (controlledSizeMetric === undefined) setInternalSizeMetric(metric);\n onSizeMetricChange?.(metric);\n };\n const changeArtist = (uuid: string | null) => {\n if (!isArtistControlled) setInternalArtistUuid(uuid);\n onArtistUuidChange?.(uuid);\n };\n const {\n isLoading,\n isFetching: isTreeFetching,\n genrePlaylistByNodeId,\n visibleGenrePlaylists,\n groupedGenrePlaylistsByRoot,\n selectedNodeId,\n selectedGenrePlaylist,\n selectNode,\n selectGenrePlaylist,\n clearSelection,\n viewMode,\n isViewModeControlled: isControlled,\n setViewMode,\n canShowPopCore,\n allowWheelRotation: internalAllowWheelRotation,\n toggleWheelRotation,\n onNodeHover: handleNodeHover,\n } = useGenreTreeViewState<O>({\n scope,\n getBackendBaseUrl,\n viewMode: controlledViewMode,\n selectedNodeId: controlledSelectedNodeId,\n onSelectedNodeIdChange,\n criteriaOverviewSchema,\n artistUuid,\n });\n\n const isGenreUpdating = useIsMutating({ mutationKey: genreUpdateMutationKey }) > 0;\n const busy = deferredSizeMetric !== sizeMetric || isTreeFetching || isGenreUpdating;\n\n const artistResult = useArtist(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);\n const filterArtistName = artistResult.status === \"success\" ? artistResult.artist.name : null;\n const filterArtist = useMemo(\n () => (artistUuid !== null && filterArtistName !== null ? { uuid: artistUuid, name: filterArtistName } : null),\n [artistUuid, filterArtistName],\n );\n const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: visibleGenrePlaylists, artist: filterArtist });\n const openArtist = (uuid: string) => {\n if (selectedNodeId !== null) clearSelection();\n changeArtist(uuid);\n };\n // Keyed by artist, so switching artists (including a host's back/forward) starts compact.\n const [expandedArtistUuid, setExpandedArtistUuid] = useState<string | null>(null);\n const artistBaseUrl = artistSearchBaseUrl ?? getBackendBaseUrl;\n const artistPanel = useArtistPanel({\n uuid: artistUuid,\n getBackendBaseUrl: artistBaseUrl,\n renderArtistLink:\n renderArtistLink ??\n ((artist) =>\n artist.isSpecialPurpose ? (\n artist.name\n ) : (\n <button type=\"button\" onClick={() => openArtist(artist.uuid)} className=\"hover:underline\">\n {artist.name}\n </button>\n )),\n expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),\n onExpandedChange: (on) => {\n if (controlledArtistExpanded === undefined) setExpandedArtistUuid(on ? artistUuid : null);\n onArtistExpandedChange?.(on);\n },\n onClose: () => changeArtist(null),\n });\n // Like a genre, the built-in artist panel shows its name in the search field and closes from its clear button.\n // A selected genre shows its own panel instead; the artist stays active as a filter.\n const builtInArtistPanel = overlayPanel || selectedNodeId !== null ? undefined : artistPanel;\n const activeOverlayPanel =\n overlayPanel ?? (builtInArtistPanel && { ...builtInArtistPanel, hideTitle: true, hideClose: true });\n\n const [uncontrolledCollapsed, setUncontrolledCollapsed] = useState(false);\n const [uncontrolledExpanded, setUncontrolledExpanded] = useState(false);\n const isCollapsed = collapsed ?? uncontrolledCollapsed;\n const isExpanded = expanded ?? uncontrolledExpanded;\n const changeCollapsed = (next: boolean) => {\n setUncontrolledCollapsed(next);\n onCollapsedChange?.(next);\n };\n const changeExpanded = (next: boolean) => {\n setUncontrolledExpanded(next);\n onExpandedChange?.(next);\n };\n useEffect(() => {\n setUncontrolledCollapsed(false);\n }, [selectedNodeId, artistUuid]);\n\n // The brand layout tokens derive the center column from this variable.\n const isDetailHidden = isCollapsed || (!selectedNodeId && !activeOverlayPanel);\n useEffect(() => {\n if (placement !== \"regions\" || !isDetailHidden) return undefined;\n const root = document.documentElement;\n root.style.setProperty(\"--layout-left-w\", \"0px\");\n return () => {\n root.style.removeProperty(\"--layout-left-w\");\n };\n }, [placement, isDetailHidden]);\n\n // An expandable overlay (e.g. an artist) takes the info panel's slot, so the edge tab drives it instead.\n const detailOverlay =\n placement === \"regions\" && activeOverlayPanel?.onExpandedChange ? activeOverlayPanel : undefined;\n const detailExpanded = detailOverlay ? !!detailOverlay.expanded : isExpanded;\n const onDetailExpandedChange = detailOverlay ? detailOverlay.onExpandedChange : changeExpanded;\n const infoPanelCuration: InfoPanelCurationProps = {\n renderDangerZone,\n expanded: placement === \"regions\" ? isExpanded : expanded,\n onExpandedChange: placement === \"regions\" ? changeExpanded : onExpandedChange,\n collapsed: placement === \"regions\" ? isCollapsed : undefined,\n hideExpandButton: placement === \"regions\" ? true : undefined,\n overlayPanel: detailOverlay ? { ...detailOverlay, collapsed: isCollapsed, hideExpandButton: true } : activeOverlayPanel,\n };\n\n const [isWheelRevealed, setIsWheelRevealed] = useState(false);\n const usesHandoff = viewMode === \"wheel\" || viewMode === \"pop-core\";\n // The edge tab controls a panel drawn by the tree, so it shows only once the tree does.\n const isTreeReady = !isLoading && (!usesHandoff || isWheelRevealed);\n\n const rootRef = useRef<HTMLDivElement>(null);\n const handlePrevious = () => {\n rootRef.current?.querySelector<HTMLButtonElement>(\".search-overlay .gtv-info-panel-previous\")?.click();\n };\n\n const { showPopup, hidePopup } = usePopup();\n const [searchText, setSearchText] = useState(\"\");\n const artistSearchResult = useArtistSearch(artistBaseUrl, artistSearch ? searchText : \"\");\n const artistExtraResults: GenreSearchProps[\"extraResults\"] = artistSearch\n ? {\n label: \"Artists\",\n items: artistSearchResult.artists.map((artist) => ({\n key: artist.uuid,\n content: (\n <>\n <span className=\"block truncate font-medium\">\n {artist.name}\n {artist.disambiguation && (\n <span className=\"font-normal text-[var(--color-neutral-500)]\"> · {artist.disambiguation}</span>\n )}\n </span>\n {artist.matchedAlias && (\n <span className=\"block truncate text-xs text-[var(--color-neutral-500)]\">\n also known as {artist.matchedAlias}\n </span>\n )}\n </>\n ),\n onSelect: () => {\n openArtist(artist.uuid);\n onArtistSelect?.(artist);\n },\n })),\n status: artistSearchResult.isError\n ? \"Search failed\"\n : artistSearchResult.settled && artistSearchResult.artists.length === 0\n ? \"No artists found\"\n : \"\",\n loading: artistSearchResult.pending,\n }\n : undefined;\n const handleSelectedNodeChange = (node: { id: string } | null) => {\n selectNode(node);\n };\n const handleGenreSearchClear = clearSelection;\n const handleGenreSearchSelect = (genrePlaylist: CriteriaPlaylistSimple) => {\n selectGenrePlaylist(genrePlaylist);\n };\n\n // Mirrors genre-tree-view's isZoomableNode: a node zooms when it has children and at most one pop-side child.\n const selectedUuid = selectedGenrePlaylist?.uuid;\n const selectedChildren = selectedUuid\n ? visibleGenrePlaylists.filter((genrePlaylist) => genrePlaylist.parent?.uuid === selectedUuid)\n : [];\n const canZoomIn =\n viewMode === \"pop-core\" &&\n focus.onFocusGenreChange !== undefined &&\n selectedUuid !== undefined &&\n focus.focusGenreUuid !== selectedUuid &&\n selectedChildren.length > 0 &&\n selectedChildren.filter((child) => child.criteria?.side === \"pop\").length <= 1;\n const handleGenreSearchZoomIn = () => {\n if (selectedUuid) focus.onFocusGenreChange?.(selectedUuid, \"zoom-in\");\n };\n const zoomInRef = useRef<(() => void) | undefined>(undefined);\n zoomInRef.current = canZoomIn ? handleGenreSearchZoomIn : undefined;\n\n // Stable across selections: the overview fetch lives in GenreDetailExtras, so its loading\n // states re-render only the info panel, never the tree. The count row is left out: the tracks\n // section title carries it.\n const renderInfoPanelBody = useCallback(\n (node: GenreTreeNode, { toolbar, ancestors, family }: InfoPanelSections): InfoPanelBody => {\n const genrePlaylist = genrePlaylistByNodeId.get(node.id);\n const criteria = genrePlaylist?.criteria;\n if (!genrePlaylist || !criteria) return { primary: <>{toolbar}{family}</>, aside: ancestors };\n const genreUuid = criteria.uuid;\n return {\n primary: (\n <>\n {toolbar}\n <GenreNodeActionBand\n node={node}\n genrePlaylist={genrePlaylist}\n scope={scope}\n onZoomIn={zoomInRef.current}\n onAddChild={readOnly ? undefined : () => handleGenreCreationAction(criteria)}\n onRename={readOnly ? undefined : () => handleGenreRenameAction(criteria)}\n onChangeParent={\n readOnly\n ? undefined\n : () =>\n showPopup(\n <ChangeParentPopup\n genrePlaylist={{ ...genrePlaylist, criteria }}\n genrePlaylists={visibleGenrePlaylists}\n scope={scope}\n getBackendBaseUrl={getBackendBaseUrl}\n onClose={hidePopup}\n onParentChanged={onParentChanged}\n />,\n )\n }\n additionalActions={additionalActions}\n playback={playback}\n />\n <GenreDetailExtras<O>\n key={genreUuid}\n family={family}\n genreUuid={genreUuid}\n genrePlaylist={genrePlaylist}\n scope={scope}\n getBackendBaseUrl={getBackendBaseUrl}\n criteriaOverviewSchema={criteriaOverviewSchema}\n renderGenreDetailExtras={renderGenreDetailExtras}\n renderArtistLink={renderArtistLink}\n artist={filterArtist}\n renderSummary={renderSummary}\n renderEssentialTracks={renderEssentialTracks}\n renderSongLeading={renderSongLeading}\n renderSongActions={renderSongActions}\n renderTracksToolbar={renderTracksToolbar}\n />\n </>\n ),\n aside: ancestors,\n };\n },\n [genrePlaylistByNodeId, additionalActions, readOnly, handleGenreCreationAction, handleGenreRenameAction,\n showPopup, hidePopup, onParentChanged, visibleGenrePlaylists, playback, filterArtist,\n scope, getBackendBaseUrl, criteriaOverviewSchema,\n renderGenreDetailExtras,\n renderArtistLink,\n renderSummary,\n renderEssentialTracks,\n renderSongLeading,\n renderSongActions,\n renderTracksToolbar,\n ],\n );\n\n const isRotationControlled = controlledAllowWheelRotation !== undefined;\n const allowWheelRotation = controlledAllowWheelRotation ?? internalAllowWheelRotation;\n\n const detailTab =\n placement === \"regions\" && (selectedNodeId || detailOverlay) ? (\n <PanelEdgeTab\n side=\"left\"\n edge={\n detailExpanded\n ? { label: \"Shrink panel\", onClick: () => onDetailExpandedChange?.(false) }\n : isCollapsed\n ? null\n : { label: \"Hide panel\", onClick: () => changeCollapsed(true) }\n }\n center={\n detailExpanded\n ? null\n : isCollapsed\n ? { label: \"Show panel\", onClick: () => changeCollapsed(false) }\n : onDetailExpandedChange\n ? { label: \"Extend panel\", onClick: () => onDetailExpandedChange(true) }\n : null\n }\n />\n ) : null;\n\n const actions = (\n <>\n {!isLoading && (\n <GenreSearch\n genrePlaylists={visibleGenrePlaylists}\n onSelect={handleGenreSearchSelect}\n selectedName={selectedGenrePlaylist?.name ?? builtInArtistPanel?.title ?? null}\n onClear={builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear}\n onPrevious={handlePrevious}\n onQueryChange={artistSearch ? setSearchText : undefined}\n extraResults={artistExtraResults}\n />\n )}\n {!isLoading && filterArtist && (\n <span className=\"inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm\">\n <button\n type=\"button\"\n className=\"py-1 pl-3 pr-2 hover:underline\"\n aria-label={`Show artist ${filterArtist.name}`}\n onClick={() => {\n if (selectedNodeId !== null) clearSelection();\n }}\n >\n {filterArtist.name}\n </button>\n <button\n type=\"button\"\n className=\"py-1 pl-1 pr-3 hover:text-[var(--color-text)]\"\n aria-label={`Clear artist filter ${filterArtist.name}`}\n onClick={() => changeArtist(null)}\n >\n ×\n </button>\n </span>\n )}\n {!isLoading && !isControlled && (\n <SegmentedControl\n aria-label=\"Tree view mode\"\n value={viewMode}\n onChange={setViewMode}\n options={[\n { value: \"pop-core\", label: \"Pop/Core\", disabled: !canShowPopCore, title: canShowPopCore ? undefined : NO_POP_ROOT_TITLE },\n { value: \"wheel\", label: \"Wheel\" },\n { value: \"stacked\", label: \"Stacked\" },\n { value: \"outline\", label: \"Outline\", disabled: !canShowPopCore, title: canShowPopCore ? undefined : NO_POP_ROOT_TITLE },\n ]}\n />\n )}\n {!isLoading && showDisplayOptions && (\n <div\n className=\"flex items-center gap-1\"\n role=\"group\"\n aria-label=\"Tree display options\"\n >\n {!isRotationControlled && viewMode !== \"stacked\" && viewMode !== \"outline\" && (\n <Button\n variant={allowWheelRotation ? \"default\" : \"outline\"}\n size=\"sm\"\n onClick={toggleWheelRotation}\n >\n Rotation\n </Button>\n )}\n </div>\n )}\n {!isLoading && !readOnly && showAddRoot && (\n <IconTextButton\n icon={Plus}\n text=\"Add root\"\n onClick={() => handleGenreCreationAction(null)}\n />\n )}\n </>\n );\n\n return (\n <div\n ref={rootRef}\n className={[\"genre-tree-view relative flex flex-col h-full\", placement === \"regions\" && REGIONS_CLASS, className]\n .filter(Boolean)\n .join(\" \")}\n >\n <style>{PREVIOUS_BUTTON_CSS}</style>\n <div\n className={`actions-container absolute z-30 flex items-center gap-2 ${\n placement === \"regions\"\n ? \"left-[calc(var(--layout-gutter)+var(--layout-panel-inset))] top-[calc(var(--layout-gutter)+var(--layout-panel-inset))]\"\n : \"left-5 top-5\"\n }`}\n >\n {actions}\n </div>\n {detailTab && (\n <div\n className={`absolute top-1/2 z-30 -translate-y-1/2 transition-[opacity,visibility] duration-150 ease-out ${\n isTreeReady ? \"opacity-100\" : \"pointer-events-none invisible opacity-0\"\n } ${\n detailExpanded\n ? \"right-[calc(var(--layout-center-right)-var(--layout-edge-tab-w))]\"\n : isCollapsed\n ? \"left-0\"\n : \"left-[calc(var(--layout-gutter)+var(--layout-left-w))]\"\n }`}\n >\n {detailTab}\n </div>\n )}\n <div className=\"content-container flex-1 min-h-0 flex flex-row gap-4\">\n <div className=\"tree-view-container flex-1 min-w-0 flex flex-col h-full\">\n {isLoading ? (\n <GenreTreeViewSkeleton viewMode={viewMode} />\n ) : viewMode === \"wheel\" ? (\n // The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,\n // and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,\n // Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.\n // The panel's own Zoom in button is hidden: the search bar carries it.\n <div className=\"tree-container search-overlay relative flex-1 w-full min-h-0 [--gtv-info-panel-header-inset:calc(var(--layout-search-h,2.5rem)+var(--layout-panel-inset,0.5rem))] [&_.gtv-info-panel-header]:pb-2 [&_.gtv-info-panel-title--hidden]:h-0 [&_.gtv-toolbar]:hidden\">\n <GenreTreeWheelHandoff skeleton={<GenreTreeWheelSkeleton />} onRevealedChange={setIsWheelRevealed}>\n <GenrePlaylistTreeWheel\n scope={scope}\n genrePlaylists={visibleGenrePlaylists}\n handleGenreCreationAction={handleGenreCreationAction}\n handleGenreRenameAction={handleGenreRenameAction}\n additionalActions={additionalActions}\n onSelectedNodeChange={handleSelectedNodeChange}\n hideInfoPanelClose\n hideInfoPanelTitle\n onNodeHover={handleNodeHover}\n renderInfoPanelBody={renderInfoPanelBody}\n playback={playback}\n selectedNodeId={selectedNodeId}\n readOnly={readOnly}\n allowWheelRotation={allowWheelRotation}\n controlsOrientation={controlsOrientation}\n sizeMetric={filterArtist ? \"songs\" : deferredSizeMetric}\n busy={busy}\n floatingControlsLeading={filterArtist ? undefined : <SizeMetricToggle value={sizeMetric} onChange={changeSizeMetric} />}\n defaultViewport={defaultViewport}\n onViewportChange={onViewportChange}\n {...infoPanelCuration}\n />\n </GenreTreeWheelHandoff>\n </div>\n ) : viewMode === \"pop-core\" ? (\n // Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.\n <div className=\"tree-container search-overlay relative flex-1 w-full min-h-0 [--gtv-info-panel-header-inset:calc(var(--layout-search-h,2.5rem)+var(--layout-panel-inset,0.5rem))] [&_.gtv-info-panel-header]:pb-2 [&_.gtv-info-panel-title--hidden]:h-0 [&_.gtv-toolbar]:hidden [&_.gtv-info-panel-zoom-in]:hidden\">\n <GenreTreeWheelHandoff skeleton={<GenreTreeWheelSkeleton />} onRevealedChange={setIsWheelRevealed}>\n <GenrePlaylistTreeWheelRadialPopCore\n scope={scope}\n genrePlaylists={visibleGenrePlaylists}\n handleGenreCreationAction={handleGenreCreationAction}\n handleGenreRenameAction={handleGenreRenameAction}\n additionalActions={additionalActions}\n onSelectedNodeChange={handleSelectedNodeChange}\n hideInfoPanelClose\n hideInfoPanelTitle\n onNodeHover={handleNodeHover}\n renderInfoPanelBody={renderInfoPanelBody}\n playback={playback}\n selectedNodeId={selectedNodeId}\n readOnly={readOnly}\n allowWheelRotation={allowWheelRotation}\n controlsOrientation={controlsOrientation}\n sizeMetric={filterArtist ? \"songs\" : deferredSizeMetric}\n busy={busy}\n floatingControlsLeading={filterArtist ? undefined : <SizeMetricToggle value={sizeMetric} onChange={changeSizeMetric} />}\n defaultViewport={defaultViewport}\n onViewportChange={onViewportChange}\n {...infoPanelCuration}\n {...focus}\n />\n </GenreTreeWheelHandoff>\n </div>\n ) : viewMode === \"outline\" ? (\n <div className=\"tree-container flex-1 min-h-0 w-full overflow-y-auto relative [--gtv-info-panel-header-inset:calc(var(--layout-search-h,2.5rem)+var(--layout-panel-inset,0.5rem)+0.75rem)] [&_.gtv-toolbar]:hidden\">\n <GenrePlaylistTreeWheelRadialPopCore\n outline\n scope={scope}\n genrePlaylists={visibleGenrePlaylists}\n handleGenreCreationAction={handleGenreCreationAction}\n handleGenreRenameAction={handleGenreRenameAction}\n additionalActions={additionalActions}\n onSelectedNodeChange={handleSelectedNodeChange}\n hideInfoPanelClose\n hideInfoPanelTitle\n onNodeHover={handleNodeHover}\n renderInfoPanelBody={renderInfoPanelBody}\n playback={playback}\n selectedNodeId={selectedNodeId}\n readOnly={readOnly}\n {...infoPanelCuration}\n />\n </div>\n ) : (\n <div className=\"tree-container relative flex flex-col gap-4 w-full pt-14 overflow-x-auto overflow-y-auto text-[var(--color-text)] [&_.gtv-toolbar]:hidden\">\n {Object.entries(groupedGenrePlaylistsByRoot).map(\n ([uuid, genrePlaylistTreePerRoot], rootIndex) => {\n return (\n <div\n key={uuid}\n className=\"tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg\"\n >\n <div className=\"graph-container relative z-10 h-full\">\n <GenrePlaylistTreePerRoot\n scope={scope}\n rootUuid={uuid}\n genrePlaylistTreePerRoot={genrePlaylistTreePerRoot}\n handleGenreCreationAction={handleGenreCreationAction}\n handleGenreRenameAction={handleGenreRenameAction}\n additionalActions={additionalActions}\n onSelectedNodeChange={handleSelectedNodeChange}\n hideInfoPanelClose\n hideInfoPanelTitle\n onNodeHover={handleNodeHover}\n renderInfoPanelBody={renderInfoPanelBody}\n playback={playback}\n // Each root has its own panel; null closes the one not holding the selection.\n selectedNodeId={\n uuid === selectedGenrePlaylist?.root.uuid ? selectedNodeId : null\n }\n readOnly={readOnly}\n wheelZoom=\"modifier\"\n controlsOrientation={controlsOrientation}\n defaultViewport={\n uuid === selectedGenrePlaylist?.root.uuid ? defaultViewport : undefined\n }\n onViewportChange={\n uuid === selectedGenrePlaylist?.root.uuid ? onViewportChange : undefined\n }\n {...infoPanelCuration}\n // Every tree draws its own overlay; render it once, not per root.\n overlayPanel={rootIndex === 0 ? activeOverlayPanel : undefined}\n />\n </div>\n </div>\n );\n },\n )}\n </div>\n )}\n </div>\n </div>\n </div>\n );\n}\n","\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { Skeleton } from \"@behindthemusictree/ui\";\nimport { z } from \"zod\";\n\nimport { CriteriaOverview } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { Scope } from \"@behindthemusictree/app-kit/transport\";\nimport { useFetchGenreOverview } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { CriteriaPlaylistMinimum, type GenreUuid } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { ClampedText } from \"./ClampedText\";\nimport { GenrePlaylistTracks, type GenrePlaylistTracksSlots } from \"./GenrePlaylistTracks\";\nimport { RenderArtistLink } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { useTrackNoun } from \"@behindthemusictree/app-kit/track-noun\";\n\nexport type GenreDetailSlots<O extends CriteriaOverview> = {\n /** Replaces the default Summary section. */\n renderSummary?: (overview: O) => ReactNode;\n /** Replaces the default Essential section (e.g. \"Essential songs\"). */\n renderEssentialTracks?: (overview: O) => ReactNode;\n};\n\nexport type GenreDetailExtrasProps<O extends CriteriaOverview> = GenreDetailSlots<O> & GenrePlaylistTracksSlots & {\n genreUuid: GenreUuid;\n genrePlaylist: CriteriaPlaylistMinimum;\n scope: Scope;\n getBackendBaseUrl: () => string;\n criteriaOverviewSchema?: z.ZodType<O, unknown>;\n renderGenreDetailExtras?: (overview: O) => ReactNode;\n renderArtistLink?: RenderArtistLink;\n /** When set, the tracks list shows only this artist's songs in the genre. */\n artist?: { uuid: string; name: string } | null;\n /** genre-tree-view's Family section, placed right after the Summary. */\n family: ReactNode;\n};\n\n// Owns the overview fetch so its pending → success transitions re-render only the info panel,\n// not the tree that renders it.\nexport function GenreDetailExtras<O extends CriteriaOverview>({\n genreUuid,\n genrePlaylist,\n scope,\n getBackendBaseUrl,\n criteriaOverviewSchema,\n renderGenreDetailExtras,\n renderArtistLink,\n renderSummary,\n renderEssentialTracks,\n family,\n ...trackSlots\n}: GenreDetailExtrasProps<O>) {\n const trackNoun = useTrackNoun();\n const { data: overview, isPending } = useFetchGenreOverview<O>(\n genreUuid,\n scope,\n getBackendBaseUrl,\n criteriaOverviewSchema,\n );\n\n if (isPending) {\n return (\n <>\n <div className=\"gtv-info-panel-children\" aria-busy=\"true\" data-testid=\"genre-detail-extras-skeleton\">\n <span className=\"gtv-info-panel-children-title\">Summary</span>\n <Skeleton className=\"h-4 w-3/4\" />\n </div>\n {family}\n </>\n );\n }\n if (!overview) return <>{family}</>;\n\n const { summary, description, summarySourceUrl, essentialSongs } = overview;\n\n return (\n <>\n {description && (\n <div className=\"px-3.5 py-2.5\">\n <ClampedText key={genreUuid} className=\"text-[0.8125rem] leading-[1.45] text-[var(--color-text-muted)]\">{description}</ClampedText>\n </div>\n )}\n {renderSummary ? (\n renderSummary(overview)\n ) : (\n <div className=\"gtv-info-panel-children text-[0.8125rem] leading-[1.45] text-[var(--color-text)]\">\n <span className=\"gtv-info-panel-children-title\">Summary</span>\n <ClampedText key={genreUuid} className=\"mt-1\">{summary ?? \"—\"}</ClampedText>\n {summarySourceUrl && (\n <a\n href={summarySourceUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"mt-1 inline-block text-xs text-[var(--color-text-muted)] underline hover:text-[var(--color-text)]\"\n >\n Source: Wikipedia\n </a>\n )}\n </div>\n )}\n {renderSummary && summarySourceUrl && (\n <a\n href={summarySourceUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"mx-3.5 mb-2.5 inline-block text-xs text-[var(--color-text-muted)] underline hover:text-[var(--color-text)]\"\n >\n Source: Wikipedia\n </a>\n )}\n {family}\n {renderGenreDetailExtras?.(overview)}\n {renderEssentialTracks ? (\n renderEssentialTracks(overview)\n ) : (\n essentialSongs && (\n <div className=\"gtv-info-panel-children text-[0.8125rem] leading-[1.45] text-[var(--color-text)]\">\n <span className=\"gtv-info-panel-children-title\">Essential {trackNoun.plural}</span>\n {essentialSongs.length > 0 ? (\n <ul className=\"mt-1 divide-y divide-[var(--color-border)]\">\n {essentialSongs.map((track) => (\n <li key={track.uuid} className=\"py-1\">\n {track.title}\n </li>\n ))}\n </ul>\n ) : (\n <p className=\"mt-1\">—</p>\n )}\n </div>\n )\n )}\n <GenrePlaylistTracks\n genreUuid={genreUuid}\n genrePlaylist={genrePlaylist}\n scope={scope}\n getBackendBaseUrl={getBackendBaseUrl}\n renderArtistLink={renderArtistLink}\n {...trackSlots}\n />\n </>\n );\n}\n","\"use client\";\n\nimport { useLayoutEffect, useRef, useState } from \"react\";\n\ntype ClampedTextProps = {\n children: string;\n className?: string;\n};\n\nexport function ClampedText({ children, className = \"\" }: ClampedTextProps) {\n const ref = useRef<HTMLParagraphElement>(null);\n const [expanded, setExpanded] = useState(false);\n const [overflows, setOverflows] = useState(false);\n\n useLayoutEffect(() => {\n const element = ref.current;\n if (!element || expanded) return;\n setOverflows(element.scrollHeight > element.clientHeight);\n }, [children, expanded]);\n\n return (\n <>\n <p ref={ref} className={`${className} ${expanded ? \"\" : \"line-clamp-3\"}`}>\n {children}\n </p>\n {(overflows || expanded) && (\n <button\n type=\"button\"\n aria-expanded={expanded}\n onClick={() => setExpanded((value) => !value)}\n className=\"mt-1 text-xs text-[var(--color-text-muted)] underline hover:text-[var(--color-text)]\"\n >\n {expanded ? \"Show less\" : \"Show more\"}\n </button>\n )}\n </>\n );\n}\n","\"use client\";\n\nimport { useEffect, useRef, type ReactNode } from \"react\";\nimport { useInfiniteQuery } from \"@tanstack/react-query\";\nimport { z } from \"zod\";\nimport { FaPlay, FaPause } from \"react-icons/fa\";\nimport { RingLoader, Skeleton } from \"@behindthemusictree/ui\";\n\nimport { useSession } from \"@behindthemusictree/app-kit/auth\";\nimport { useFetchWrapper } from \"@behindthemusictree/app-kit/transport\";\nimport { PaginatedResponseSchema } from \"@behindthemusictree/app-kit/transport\";\nimport { parseWithLog } from \"@behindthemusictree/app-kit/transport\";\nimport { usePlayer } from \"@behindthemusictree/app-kit/player\";\nimport { PlayStates } from \"@behindthemusictree/app-kit/player\";\nimport { usePopup } from \"@behindthemusictree/app-kit/popup\";\nimport InternalErrorPopup from \"../popup/InternalErrorPopup\";\nimport { UnplayableReason } from \"./UnplayableReason\";\nimport { ErrorCode } from \"@behindthemusictree/app-kit/transport\";\nimport { Scope } from \"@behindthemusictree/app-kit/transport\";\nimport { genrePlaylistQueryKeys } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { libraryEndpoints, libraryQueryKeys } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { CriteriaPlaylistMinimum, type GenreUuid } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { TrackBase } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { isSong, songUnplayableReason, SongDetailedSchema } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { GenrePlaylistTracksPage, useTrackList } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { MusicBrainzRecordingLink } from \"./MusicBrainzRecordingLink\";\nimport { ArtistNames, RenderArtistLink } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { capitalize, useTrackNoun } from \"@behindthemusictree/app-kit/track-noun\";\n\nconst CATALOGUE_PAGE_SIZE = 100;\n// withTotal=false: the backend skips COUNT(*) and pages by keyset cursor, so huge genre subtrees\n// stay O(page) at any depth; page numbers and totals are then null.\nconst SongPageSchema = PaginatedResponseSchema(SongDetailedSchema).extend({\n overallTotal: z.number().nullable(),\n page: z.number().nullable(),\n totalPages: z.number().nullable(),\n});\n\n// Container query on the list (arbitrary variant: Tailwind 3 core has no `@container`): once the\n// list is wide (expanded info panel), title, artist and album become columns instead of one line.\n// Class names stay literal so Tailwind's scanner sees them.\nconst TEXT_CELL = \"min-w-0 flex-1 truncate [@container(min-width:40rem)]:flex [@container(min-width:40rem)]:gap-2\";\nconst TITLE_CELL = \"[@container(min-width:40rem)]:flex-[2] [@container(min-width:40rem)]:truncate\";\nconst COLUMN_CELL = \"[@container(min-width:40rem)]:flex-1 [@container(min-width:40rem)]:truncate\";\nconst ALBUM_CELL = `hidden [@container(min-width:40rem)]:block ${COLUMN_CELL}`;\nconst SEPARATOR = \"[@container(min-width:40rem)]:hidden\";\n\nconst nextCursor = (next: string): string => {\n const cursor = new URL(next).searchParams.get(\"cursor\");\n if (!cursor) throw new Error(`Songs next link has no cursor: ${next}`);\n return cursor;\n};\n\n// Songs are not narrowed to SongDetailed here: \"me\" scope lists uploaded tracks too.\nexport type GenrePlaylistTracksSlots = {\n /** Rendered first in each row, e.g. a selection checkbox. `index` is the row's position in the loaded list. */\n renderSongLeading?: (song: TrackBase, index: number) => ReactNode;\n /** Rendered last in each row. The song's own genre is `song.genre`. */\n renderSongActions?: (song: TrackBase) => ReactNode;\n /** Rendered between the title and the list, e.g. a \"+ Link\" button or a selection bar. */\n renderTracksToolbar?: () => ReactNode;\n};\n\nexport type GenrePlaylistTracksProps = GenrePlaylistTracksSlots & {\n genreUuid: GenreUuid;\n genrePlaylist: CriteriaPlaylistMinimum;\n scope: Scope;\n getBackendBaseUrl: () => string;\n /** When set, artist names render through it (e.g. a router `Link`). */\n renderArtistLink?: RenderArtistLink;\n /** When set, lists and plays only this artist's songs in the genre. */\n artist?: { uuid: string; name: string } | null;\n};\n\n/**\n * \"reference\" lists the genre's whole songs catalogue (genre subtree), playable or not, while\n * playing goes through the playable-only playlist. Other scopes have no catalogue endpoint; their\n * playlist is the catalogue (e.g. hear's uploaded tracks are always playable).\n */\nexport function GenrePlaylistTracks({\n genreUuid,\n genrePlaylist,\n scope,\n getBackendBaseUrl,\n renderArtistLink,\n renderSongLeading,\n renderSongActions,\n renderTracksToolbar,\n artist = null,\n}: GenrePlaylistTracksProps) {\n const { fetchGenrePlaylistTracksPage, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist } = useTrackList();\n const { session, sessionRestored } = useSession();\n const { playerTrackObject, playState, handlePlayPauseAction } = usePlayer();\n const { showPopup } = usePopup();\n const trackNoun = useTrackNoun();\n const { uuid } = genrePlaylist;\n\n const { fetch } = useFetchWrapper(getBackendBaseUrl);\n\n const { data, isPending, isError, refetch, hasNextPage, isFetchingNextPage, fetchNextPage } = useInfiniteQuery({\n queryKey:\n scope === \"me\"\n ? genrePlaylistQueryKeys.me.tracks(uuid)\n : artist\n ? [...libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid), \"artist\", artist.uuid]\n : libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid),\n queryFn: async ({ pageParam }): Promise<Omit<GenrePlaylistTracksPage<TrackBase>, \"total\" | \"nextPage\"> & { total: number | null; nextPage: number | string | null }> => {\n if (scope === \"me\") return fetchGenrePlaylistTracksPage(uuid, scope, Number(pageParam));\n const response = await fetch(\n libraryEndpoints.reference.songs.list(),\n true,\n false,\n {},\n { genre: genreUuid, ...(artist && { artist: artist.uuid }), pageSize: CATALOGUE_PAGE_SIZE, withTotal: false, ...(pageParam && { cursor: pageParam }) },\n );\n const parsed = parseWithLog(SongPageSchema, response, \"GenrePlaylistTracks\");\n return { tracks: parsed.results, total: parsed.overallTotal, nextPage: parsed.next ? nextCursor(parsed.next) : null };\n },\n initialPageParam: scope === \"me\" ? 1 : \"\",\n getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined,\n enabled: scope !== \"me\" || (sessionRestored && !!session?.accessToken),\n });\n\n const sentinelRef = useRef<HTMLLIElement>(null);\n useEffect(() => {\n const sentinel = sentinelRef.current;\n if (!hasNextPage || isFetchingNextPage || !sentinel) return;\n const observer = new IntersectionObserver((entries) => {\n if (entries.some((entry) => entry.isIntersecting)) {\n void fetchNextPage();\n }\n });\n observer.observe(sentinel);\n return () => observer.disconnect();\n }, [hasNextPage, isFetchingNextPage, fetchNextPage]);\n\n const pages = data?.pages ?? [];\n const tracks = pages.flatMap((page) => page.tracks);\n const total = pages[0]?.total ?? undefined;\n\n const play = (startSongUuid: string) => {\n const started = artist\n ? playNewTrackListFromArtist(artist, scope, { startSongUuid, ordering: \"title\", genreUuid })\n : playNewTrackListFromGenrePlaylist(genrePlaylist, scope, { startSongUuid });\n started.catch((error) => {\n console.error(\"Failed to play genre playlist:\", error);\n showPopup(<InternalErrorPopup errorCode={ErrorCode.CLIENT_UNKNOWN} />);\n });\n };\n\n return (\n <div className=\"gtv-info-panel-children\">\n <span className=\"gtv-info-panel-children-title\">{capitalize(trackNoun.plural)}{total !== undefined && ` (${total})`}</span>\n {renderTracksToolbar?.()}\n {isPending ? (\n <Skeleton className=\"h-4 w-3/4\" aria-busy=\"true\" />\n ) : isError && tracks.length === 0 ? (\n <p role=\"alert\">\n Couldn't load {trackNoun.plural}.{\" \"}\n <button type=\"button\" className=\"underline\" onClick={() => void refetch()}>\n Retry\n </button>\n </p>\n ) : tracks.length === 0 ? (\n <p>—</p>\n ) : (\n <ul className=\"mt-1 max-h-64 overflow-y-auto text-[0.8125rem] leading-[1.45] [container-type:inline-size]\" aria-label={`${genrePlaylist.name} ${trackNoun.plural}`}>\n {tracks.map((track, index) => {\n const label = track.artists?.length\n ? `${track.title} — ${track.artists.map((artist) => artist.name).join(\", \")}`\n : track.title;\n const isCurrent = playerTrackObject?.track.id === track.uuid;\n const isPlaying = isCurrent && playState === PlayStates.PLAYING;\n const text = (\n <span className={TEXT_CELL}>\n <span className={TITLE_CELL}>{track.title}</span>\n {track.artists?.length ? <span className={SEPARATOR}>{\" — \"}</span> : null}\n <span className={COLUMN_CELL}>\n {track.artists?.length ? (\n <ArtistNames artists={track.artists} renderArtistLink={renderArtistLink} />\n ) : null}\n </span>\n {track.album && <span className={ALBUM_CELL}>{track.album.name}</span>}\n </span>\n );\n const leading = renderSongLeading?.(track, index);\n const actions = renderSongActions?.(track);\n const unplayableReason = isSong(track) ? songUnplayableReason(track) : null;\n if (unplayableReason) {\n return (\n <li key={track.uuid} className=\"flex min-h-8 items-center gap-1 px-1 opacity-50\" title={unplayableReason}>\n {leading}\n <button\n type=\"button\"\n disabled\n aria-disabled=\"true\"\n aria-label={`${label} — ${unplayableReason}`}\n className=\"flex h-6 w-6 shrink-0 items-center justify-center rounded-full opacity-0 cursor-not-allowed\"\n >\n <FaPlay size={10} />\n </button>\n {text}\n {isSong(track) && <MusicBrainzRecordingLink song={track} />}\n <span className=\"shrink-0 text-xs\">\n <UnplayableReason reason={unplayableReason} />\n </span>\n {actions}\n </li>\n );\n }\n return (\n <li\n key={track.uuid}\n className={`group flex min-h-8 items-center gap-1 rounded px-1 hover:bg-[var(--color-neutral-100)] ${isCurrent ? \"font-medium text-[var(--color-primary)]\" : \"\"}`}\n >\n {leading}\n <button\n type=\"button\"\n aria-label={`${isPlaying ? \"Pause\" : \"Play\"} ${label}`}\n className={`flex h-6 w-6 shrink-0 items-center justify-center rounded-full cursor-pointer group-hover:opacity-100 focus-visible:opacity-100 ${isCurrent && playState !== PlayStates.STOPPED ? \"opacity-100\" : \"opacity-0 [@media(pointer:coarse)]:opacity-100\"}`}\n onClick={() => (isCurrent ? handlePlayPauseAction() : play(track.uuid))}\n >\n {isCurrent && playState === PlayStates.LOADING ? (\n <RingLoader size={10} />\n ) : isPlaying ? (\n <FaPause size={10} />\n ) : (\n <FaPlay size={10} />\n )}\n </button>\n {text}\n {isSong(track) && <MusicBrainzRecordingLink song={track} />}\n {actions}\n </li>\n );\n })}\n {hasNextPage && <li ref={sentinelRef} aria-hidden=\"true\" className=\"h-4\" />}\n </ul>\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport { FaYoutube } from \"react-icons/fa\";\n\nimport { NO_VIDEO_LABEL } from \"@behindthemusictree/app-kit/genre-tree\";\n\nexport function UnplayableReason({ reason }: { reason: string }) {\n if (reason !== NO_VIDEO_LABEL) return <>{reason}</>;\n return (\n <span role=\"img\" aria-label={reason} className=\"relative inline-flex shrink-0 items-center\">\n <FaYoutube size={16} aria-hidden=\"true\" />\n <span aria-hidden=\"true\" className=\"absolute left-1/2 top-1/2 h-0.5 w-[130%] -translate-x-1/2 -translate-y-1/2 -rotate-45 rounded bg-current\" />\n </span>\n );\n}\n","\"use client\";\n\nimport { FaExternalLinkAlt } from \"react-icons/fa\";\n\nimport { SongDetailed, musicBrainzRecordingUrl } from \"@behindthemusictree/app-kit/genre-tree\";\n\nexport function MusicBrainzRecordingLink({ song }: { song: SongDetailed }) {\n const url = musicBrainzRecordingUrl(song);\n if (!url) return null;\n return (\n <a\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n aria-label={`View ${song.title} on MusicBrainz`}\n title=\"View on MusicBrainz\"\n className=\"inline-flex shrink-0 items-center opacity-50 hover:opacity-100 focus-visible:opacity-100\"\n onClick={(event) => event.stopPropagation()}\n >\n <FaExternalLinkAlt size={10} />\n </a>\n );\n}\n","\"use client\";\n\nimport { useCallback, useMemo } from \"react\";\nimport {\n GenreTree,\n getGenreTreeColor,\n type ControlsOrientation,\n type GenreTreeViewport,\n type GenreTreeAction,\n type GenreTreeNode,\n type InfoPanelBody,\n type InfoPanelSections,\n type WheelZoomMode,\n} from \"@behindthemusictree/genre-tree-view\";\n\nimport { usePlayer } from \"@behindthemusictree/app-kit/player\";\nimport { usePopup } from \"@behindthemusictree/app-kit/popup\";\nimport InternalErrorPopup from \"../../popup/InternalErrorPopup\";\nimport { ErrorCode } from \"@behindthemusictree/app-kit/transport\";\n\nimport { TrackBase } from \"@behindthemusictree/app-kit/genre-tree\";\n\nimport { CriteriaPlaylistSimple } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { CriteriaMinimum } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { Scope } from \"@behindthemusictree/app-kit/transport\";\nimport type { InfoPanelCurationProps } from \"./infoPanelCurationProps\";\nimport { useGenrePlaylistPlayback, type GenrePlaylistPlayback } from \"./useGenrePlaylistPlayback\";\nimport { capitalize, useTrackNoun } from \"@behindthemusictree/app-kit/track-noun\";\n\nexport type GenrePlaylistTreePerRootProps<T extends TrackBase> = {\n scope: Scope;\n className?: string;\n rootUuid: string;\n genrePlaylistTreePerRoot: CriteriaPlaylistSimple[];\n handleGenreCreationAction: (parent: CriteriaMinimum | null) => void;\n handleGenreRenameAction: (genre: CriteriaMinimum) => void;\n additionalActions?: (node: GenreTreeNode) => GenreTreeAction[];\n onNodeClick?: (node: GenreTreeNode) => void;\n onNodeHover?: (node: GenreTreeNode) => void;\n renderInfoPanelBody?: (node: GenreTreeNode, sections: InfoPanelSections) => InfoPanelBody;\n /** Overrides which node is shown highlighted, e.g. from a search selection. */\n selectedNodeId?: string | null;\n /** Fires when a user action changes the info panel's node, or `null` when it is closed. */\n onSelectedNodeChange?: (node: GenreTreeNode | null) => void;\n /** Hides the info panel's header close button. */\n hideInfoPanelClose?: boolean;\n /** Hides the info panel header's node name. */\n hideInfoPanelTitle?: boolean;\n /** When true, suppresses per-node create/rename affordances. Defaults to false. */\n readOnly?: boolean;\n /** When false, suppresses the hover toolbar on every node. Defaults to true. */\n showToolbar?: boolean;\n wheelZoom?: WheelZoomMode;\n /** Layout of the floating zoom/pop/fit controls. Defaults to \"vertical\". */\n controlsOrientation?: ControlsOrientation;\n /** Mount-only seed for the pan/zoom, replacing the initial fit. */\n defaultViewport?: GenreTreeViewport;\n /** Fires after a user-driven pan/zoom settles. */\n onViewportChange?: (viewport: GenreTreeViewport) => void;\n /** What node play buttons play; defaults to each node's whole genre playlist. */\n playback?: GenrePlaylistPlayback;\n} & InfoPanelCurationProps;\n\nexport default function GenrePlaylistTreePerRoot<T extends TrackBase>({\n scope,\n className,\n rootUuid,\n genrePlaylistTreePerRoot,\n handleGenreCreationAction,\n handleGenreRenameAction,\n additionalActions,\n onNodeClick,\n onNodeHover,\n renderInfoPanelBody,\n selectedNodeId,\n onSelectedNodeChange,\n hideInfoPanelClose,\n hideInfoPanelTitle,\n readOnly = false,\n showToolbar,\n wheelZoom,\n controlsOrientation,\n defaultViewport,\n onViewportChange,\n playback,\n ...infoPanelCuration\n}: GenrePlaylistTreePerRootProps<T>) {\n const { isPlaying, setIsPlaying } = usePlayer();\n const defaultPlayback = useGenrePlaylistPlayback<T>({ scope, genrePlaylists: genrePlaylistTreePerRoot });\n const { playingNodeId, play } = playback ?? defaultPlayback;\n const trackNoun = useTrackNoun();\n const { showPopup } = usePopup();\n\n const nodes: GenreTreeNode[] = useMemo(\n () =>\n genrePlaylistTreePerRoot.map((genrePlaylist) => ({\n id: genrePlaylist.uuid,\n parentId: genrePlaylist.parent?.uuid ?? null,\n name: genrePlaylist.name,\n itemCount: genrePlaylist.tracksCount,\n actionable: Boolean(genrePlaylist.criteria),\n side: genrePlaylist.criteria?.side ?? undefined,\n wikidataId: genrePlaylist.criteria?.wikidataId ?? null,\n umbrella: genrePlaylist.isUmbrella,\n })),\n [genrePlaylistTreePerRoot],\n );\n\n const handlePlayPause = useCallback(\n (nodeId: string) => {\n const genrePlaylist = genrePlaylistTreePerRoot.find((g) => g.uuid === nodeId);\n if (!genrePlaylist) return;\n\n if (nodeId === playingNodeId) {\n setIsPlaying(!isPlaying);\n return;\n }\n\n if (genrePlaylist.tracksCount === 0) {\n return;\n }\n\n play(genrePlaylist).catch((error) => {\n console.error(\"Failed to load genre playlist tracks:\", error);\n showPopup(<InternalErrorPopup errorCode={ErrorCode.CLIENT_UNKNOWN} />);\n });\n },\n [\n genrePlaylistTreePerRoot,\n playingNodeId,\n isPlaying,\n setIsPlaying,\n play,\n showPopup,\n ],\n );\n\n const handleAddChild = useCallback(\n (parentId: string) => {\n const genrePlaylist = genrePlaylistTreePerRoot.find((g) => g.uuid === parentId);\n if (!genrePlaylist?.criteria) return;\n handleGenreCreationAction(genrePlaylist.criteria);\n },\n [genrePlaylistTreePerRoot, handleGenreCreationAction],\n );\n\n const handleRenameRequest = useCallback(\n (node: GenreTreeNode) => {\n const genrePlaylist = genrePlaylistTreePerRoot.find((g) => g.uuid === node.id);\n if (!genrePlaylist?.criteria) return;\n handleGenreRenameAction(genrePlaylist.criteria);\n },\n [genrePlaylistTreePerRoot, handleGenreRenameAction],\n );\n\n const handleDeleteRequest = useCallback((node: GenreTreeNode) => {\n if (confirm(`Are you sure you want to delete \"${node.name}\"?`)) {\n // TODO: Implement delete genre\n }\n }, []);\n\n return (\n <GenreTree\n className={className}\n nodes={nodes}\n itemCountLabel={`${capitalize(trackNoun.singular)} count`}\n rootColor={getGenreTreeColor(rootUuid)}\n playingNodeId={playingNodeId}\n playState={isPlaying ? \"playing\" : \"paused\"}\n onPlayPause={handlePlayPause}\n onAddChild={readOnly ? undefined : handleAddChild}\n onRenameRequest={readOnly ? undefined : handleRenameRequest}\n onDeleteRequest={readOnly ? undefined : handleDeleteRequest}\n additionalActions={additionalActions}\n onNodeClick={onNodeClick}\n onNodeHover={onNodeHover}\n renderInfoPanelBody={renderInfoPanelBody}\n selectedNodeId={selectedNodeId}\n onSelectedNodeChange={onSelectedNodeChange}\n hideInfoPanelClose={hideInfoPanelClose}\n hideInfoPanelTitle={hideInfoPanelTitle}\n showToolbar={showToolbar}\n wheelZoom={wheelZoom}\n controlsOrientation={controlsOrientation}\n defaultViewport={defaultViewport}\n onViewportChange={onViewportChange}\n {...infoPanelCuration}\n readOnly={readOnly}\n />\n );\n}\n","\"use client\";\n\nimport { useCallback, useMemo } from \"react\";\nimport {\n TrackListOriginType,\n useTrackList,\n type CriteriaPlaylistSimple,\n type TrackBase,\n type TrackListOriginFromArtist,\n} from \"@behindthemusictree/app-kit/genre-tree\";\nimport type { Scope } from \"@behindthemusictree/app-kit/transport\";\n\n/** What a tree node's play button plays, and which node is the one currently playing. */\nexport type GenrePlaylistPlayback = {\n playingNodeId: string | null;\n play: (genrePlaylist: CriteriaPlaylistSimple) => Promise<void>;\n};\n\nexport type UseGenrePlaylistPlaybackOptions = {\n scope: Scope;\n genrePlaylists: CriteriaPlaylistSimple[];\n /** When set, nodes play only this artist's songs in the genre's subtree instead of the whole genre playlist. */\n artist?: { uuid: string; name: string } | null;\n};\n\nexport function useGenrePlaylistPlayback<T extends TrackBase = TrackBase>({\n scope,\n genrePlaylists,\n artist = null,\n}: UseGenrePlaylistPlaybackOptions): GenrePlaylistPlayback {\n const { trackList, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist } = useTrackList<T>();\n const origin = trackList?.origin;\n\n const playingNodeId = useMemo(() => {\n if (!origin) return null;\n if (!artist) return origin.type === TrackListOriginType.GENRE_PLAYLIST ? origin.uuid : null;\n if (origin.type !== TrackListOriginType.ARTIST || origin.uuid !== artist.uuid) return null;\n const { genreUuid } = origin as TrackListOriginFromArtist;\n return genreUuid ? (genrePlaylists.find((gp) => gp.criteria?.uuid === genreUuid)?.uuid ?? null) : null;\n }, [origin, artist, genrePlaylists]);\n\n const play = useCallback(\n async (genrePlaylist: CriteriaPlaylistSimple) => {\n if (!artist) return playNewTrackListFromGenrePlaylist(genrePlaylist, scope);\n // Genre-less songs have no genre to filter on.\n if (!genrePlaylist.criteria) return;\n return playNewTrackListFromArtist(artist, scope, { ordering: \"title\", genreUuid: genrePlaylist.criteria.uuid });\n },\n [artist, scope, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist],\n );\n\n return useMemo(() => ({ playingNodeId, play }), [playingNodeId, play]);\n}\n","\"use client\";\n\nimport { useCallback, useMemo, type ReactNode } from \"react\";\nimport {\n GenreTreeWheel,\n type ControlsOrientation,\n type GenreTreeViewport,\n type GenreTreeAction,\n type GenreTreeNode,\n type InfoPanelBody,\n type InfoPanelSections,\n} from \"@behindthemusictree/genre-tree-view\";\n\nimport { usePlayer } from \"@behindthemusictree/app-kit/player\";\nimport { usePopup } from \"@behindthemusictree/app-kit/popup\";\nimport InternalErrorPopup from \"../../popup/InternalErrorPopup\";\nimport { ErrorCode } from \"@behindthemusictree/app-kit/transport\";\n\nimport { TrackBase } from \"@behindthemusictree/app-kit/genre-tree\";\n\nimport { CriteriaPlaylistSimple } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { CriteriaMinimum } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { Scope } from \"@behindthemusictree/app-kit/transport\";\nimport type { InfoPanelCurationProps } from \"./infoPanelCurationProps\";\nimport { useGenrePlaylistPlayback, type GenrePlaylistPlayback } from \"./useGenrePlaylistPlayback\";\nimport { genrePlaylistSize, type GenreTreeSizeMetric } from \"./size-metric\";\nimport { capitalize, useTrackNoun } from \"@behindthemusictree/app-kit/track-noun\";\n\nexport type GenrePlaylistTreeWheelProps<T extends TrackBase> = {\n scope: Scope;\n className?: string;\n genrePlaylists: CriteriaPlaylistSimple[];\n handleGenreCreationAction: (parent: CriteriaMinimum | null) => void;\n handleGenreRenameAction: (genre: CriteriaMinimum) => void;\n additionalActions?: (node: GenreTreeNode) => GenreTreeAction[];\n onNodeClick?: (node: GenreTreeNode) => void;\n onNodeHover?: (node: GenreTreeNode) => void;\n renderInfoPanelBody?: (node: GenreTreeNode, sections: InfoPanelSections) => InfoPanelBody;\n /** Overrides which node is shown highlighted, e.g. from a search selection. */\n selectedNodeId?: string | null;\n /** Fires when a user action changes the info panel's node, or `null` when it is closed. */\n onSelectedNodeChange?: (node: GenreTreeNode | null) => void;\n /** Hides the info panel's header close button. */\n hideInfoPanelClose?: boolean;\n /** Hides the info panel header's node name. */\n hideInfoPanelTitle?: boolean;\n /** When true, suppresses per-node create/rename affordances. Defaults to false. */\n readOnly?: boolean;\n /** When false, clicking a chip still selects its root, but the wheel doesn't spin to the\n * anchor. Defaults to true. */\n allowWheelRotation?: boolean;\n /** When false, suppresses the hover toolbar on every node. Defaults to true. */\n showToolbar?: boolean;\n /** Layout of the floating zoom/pop/fit controls. Defaults to \"vertical\". */\n controlsOrientation?: ControlsOrientation;\n /** What node size is proportional to; defaults to the song count. */\n sizeMetric?: GenreTreeSizeMetric;\n /** Rendered in the floating controls, beside the zoom buttons. */\n floatingControlsLeading?: ReactNode;\n /** Shows the tree's busy indicator while the data behind it is changing. */\n busy?: boolean;\n /** Mount-only seed for the pan/zoom, replacing the initial fit. */\n defaultViewport?: GenreTreeViewport;\n /** Fires after a user-driven pan/zoom settles. */\n onViewportChange?: (viewport: GenreTreeViewport) => void;\n /** What node play buttons play; defaults to each node's whole genre playlist. */\n playback?: GenrePlaylistPlayback;\n} & InfoPanelCurationProps;\n\nexport default function GenrePlaylistTreeWheel<T extends TrackBase>({\n scope,\n className,\n genrePlaylists,\n handleGenreCreationAction,\n handleGenreRenameAction,\n additionalActions,\n onNodeClick,\n onNodeHover,\n renderInfoPanelBody,\n selectedNodeId,\n onSelectedNodeChange,\n hideInfoPanelClose,\n hideInfoPanelTitle,\n readOnly = false,\n allowWheelRotation,\n showToolbar,\n controlsOrientation,\n sizeMetric = \"songs\",\n floatingControlsLeading,\n busy,\n defaultViewport,\n onViewportChange,\n playback,\n ...infoPanelCuration\n}: GenrePlaylistTreeWheelProps<T>) {\n const { isPlaying, setIsPlaying } = usePlayer();\n const defaultPlayback = useGenrePlaylistPlayback<T>({ scope, genrePlaylists });\n const { playingNodeId, play } = playback ?? defaultPlayback;\n const trackNoun = useTrackNoun();\n const { showPopup } = usePopup();\n\n const nodes: GenreTreeNode[] = useMemo(\n () =>\n genrePlaylists.map((genrePlaylist) => ({\n id: genrePlaylist.uuid,\n parentId: genrePlaylist.parent?.uuid ?? null,\n name: genrePlaylist.name,\n itemCount: genrePlaylistSize(genrePlaylist, sizeMetric),\n actionable: Boolean(genrePlaylist.criteria),\n side: genrePlaylist.criteria?.side ?? undefined,\n wikidataId: genrePlaylist.criteria?.wikidataId ?? null,\n })),\n [genrePlaylists, sizeMetric],\n );\n\n const handlePlayPause = useCallback(\n (nodeId: string) => {\n const genrePlaylist = genrePlaylists.find((g) => g.uuid === nodeId);\n if (!genrePlaylist) return;\n\n if (nodeId === playingNodeId) {\n setIsPlaying(!isPlaying);\n return;\n }\n\n if (genrePlaylist.tracksCount === 0) {\n return;\n }\n\n play(genrePlaylist).catch((error) => {\n console.error(\"Failed to load genre playlist tracks:\", error);\n showPopup(<InternalErrorPopup errorCode={ErrorCode.CLIENT_UNKNOWN} />);\n });\n },\n [\n genrePlaylists,\n playingNodeId,\n isPlaying,\n setIsPlaying,\n play,\n showPopup,\n ],\n );\n\n const handleAddChild = useCallback(\n (parentId: string) => {\n const genrePlaylist = genrePlaylists.find((g) => g.uuid === parentId);\n if (!genrePlaylist?.criteria) return;\n handleGenreCreationAction(genrePlaylist.criteria);\n },\n [genrePlaylists, handleGenreCreationAction],\n );\n\n const handleRenameRequest = useCallback(\n (node: GenreTreeNode) => {\n const genrePlaylist = genrePlaylists.find((g) => g.uuid === node.id);\n if (!genrePlaylist?.criteria) return;\n handleGenreRenameAction(genrePlaylist.criteria);\n },\n [genrePlaylists, handleGenreRenameAction],\n );\n\n const handleDeleteRequest = useCallback((node: GenreTreeNode) => {\n if (confirm(`Are you sure you want to delete \"${node.name}\"?`)) {\n // TODO: Implement delete genre\n }\n }, []);\n\n return (\n <GenreTreeWheel\n className={className}\n nodes={nodes}\n itemCountLabel={sizeMetric === \"views\" ? \"Views\" : `${capitalize(trackNoun.singular)} count`}\n playingNodeId={playingNodeId}\n playState={isPlaying ? \"playing\" : \"paused\"}\n onPlayPause={handlePlayPause}\n onAddChild={readOnly ? undefined : handleAddChild}\n onRenameRequest={readOnly ? undefined : handleRenameRequest}\n onDeleteRequest={readOnly ? undefined : handleDeleteRequest}\n additionalActions={additionalActions}\n onNodeClick={onNodeClick}\n onNodeHover={onNodeHover}\n renderInfoPanelBody={renderInfoPanelBody}\n selectedNodeId={selectedNodeId}\n onSelectedNodeChange={onSelectedNodeChange}\n hideInfoPanelClose={hideInfoPanelClose}\n hideInfoPanelTitle={hideInfoPanelTitle}\n allowWheelRotation={allowWheelRotation}\n showToolbar={showToolbar}\n controlsOrientation={controlsOrientation}\n floatingControlsLeading={floatingControlsLeading}\n busy={busy}\n defaultViewport={defaultViewport}\n onViewportChange={onViewportChange}\n {...infoPanelCuration}\n readOnly={readOnly}\n />\n );\n}\n","import type { CriteriaPlaylistSimple } from \"@behindthemusictree/app-kit/genre-tree\";\n\nexport type GenreTreeSizeMetric = \"songs\" | \"views\";\n\nexport const genrePlaylistSize = (genrePlaylist: CriteriaPlaylistSimple, metric: GenreTreeSizeMetric): number =>\n metric === \"views\" ? genrePlaylist.viewsCount : genrePlaylist.tracksCount;\n","\"use client\";\n\nimport { useCallback, useMemo, type ReactNode } from \"react\";\nimport {\n GenreTreeOutline,\n GenreTreeWheelRadialPopCore,\n type ControlsOrientation,\n type GenreTreeViewport,\n type FocusChangeReason,\n type GenreTreeAction,\n type GenreTreeNode,\n type InfoPanelBody,\n type InfoPanelSections,\n} from \"@behindthemusictree/genre-tree-view\";\n\nimport { usePlayer } from \"@behindthemusictree/app-kit/player\";\n\nimport { TrackBase } from \"@behindthemusictree/app-kit/genre-tree\";\n\nimport { CriteriaPlaylistSimple } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { CriteriaMinimum } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { Scope } from \"@behindthemusictree/app-kit/transport\";\nimport type { InfoPanelCurationProps } from \"./infoPanelCurationProps\";\nimport { useGenrePlaylistPlayback, type GenrePlaylistPlayback } from \"./useGenrePlaylistPlayback\";\nimport { genrePlaylistSize, type GenreTreeSizeMetric } from \"./size-metric\";\nimport { capitalize, useTrackNoun } from \"@behindthemusictree/app-kit/track-noun\";\n\n/** Pop-core focus is controlled-only: pass both props or neither. Ignored in outline mode. */\nexport type GenreFocusProps =\n | { focusGenreUuid?: undefined; onFocusGenreChange?: undefined }\n | {\n /** Zooms the pop-core wheel into this genre's subtree. An unknown or non-zoomable uuid shows\n * the whole tree and fires `onFocusGenreChange(null, \"rejected\")`. */\n focusGenreUuid: string | null;\n /** Fires with the genre to focus and why: `\"zoom-in\"` / `\"back\"` (parent) / `\"previous\"` /\n * `\"root\"` from the user's zoom controls, `\"search\"` when a selection outside the zoom refocuses\n * to its top-level root (null for Mainstream Pop), and\n * `\"rejected\"` when an invalid `focusGenreUuid` is reset to null. */\n onFocusGenreChange: (uuid: string | null, reason: FocusChangeReason) => void;\n };\n\nexport type GenrePlaylistTreeWheelRadialPopCoreProps<T extends TrackBase> = {\n scope: Scope;\n className?: string;\n genrePlaylists: CriteriaPlaylistSimple[];\n handleGenreCreationAction: (parent: CriteriaMinimum | null) => void;\n handleGenreRenameAction: (genre: CriteriaMinimum) => void;\n additionalActions?: (node: GenreTreeNode) => GenreTreeAction[];\n onNodeClick?: (node: GenreTreeNode) => void;\n onNodeHover?: (node: GenreTreeNode) => void;\n renderInfoPanelBody?: (node: GenreTreeNode, sections: InfoPanelSections) => InfoPanelBody;\n /** Overrides which node is shown highlighted, e.g. from a search selection. */\n selectedNodeId?: string | null;\n /** Fires when a user action changes the info panel's node, or `null` when it is closed. */\n onSelectedNodeChange?: (node: GenreTreeNode | null) => void;\n /** Hides the info panel's header close button. */\n hideInfoPanelClose?: boolean;\n /** Hides the info panel header's node name. */\n hideInfoPanelTitle?: boolean;\n /** When true, suppresses per-node create/rename affordances. Defaults to false. */\n readOnly?: boolean;\n /** When false, clicking a chip still selects its root, but the ring doesn't spin to the\n * landing angle. Defaults to true. */\n allowWheelRotation?: boolean;\n /** When false, suppresses the hover toolbar on every node. Defaults to true. */\n showToolbar?: boolean;\n /** Layout of the wheel's floating zoom/pop/fit controls; ignored in outline mode. Defaults to\n * \"vertical\". */\n controlsOrientation?: ControlsOrientation;\n /** What node size is proportional to; defaults to the song count. */\n sizeMetric?: GenreTreeSizeMetric;\n /** Rendered in the floating controls, beside the zoom buttons. */\n floatingControlsLeading?: ReactNode;\n /** Shows the tree's busy indicator while the data behind it is changing; ignored in outline mode. */\n busy?: boolean;\n /** When true, renders the same forest as GenreTreeOutline's nested-list text view instead of\n * the radial wheel. Defaults to false. */\n outline?: boolean;\n /** Mount-only seed for the pan/zoom, replacing the initial fit. */\n defaultViewport?: GenreTreeViewport;\n /** Fires after a user-driven pan/zoom settles. */\n onViewportChange?: (viewport: GenreTreeViewport) => void;\n /** What node play buttons play; defaults to each node's whole genre playlist. */\n playback?: GenrePlaylistPlayback;\n} & GenreFocusProps &\n InfoPanelCurationProps;\n\nexport default function GenrePlaylistTreeWheelRadialPopCore<T extends TrackBase>(\n props: GenrePlaylistTreeWheelRadialPopCoreProps<T>,\n) {\n const {\n scope,\n className,\n genrePlaylists,\n handleGenreCreationAction,\n handleGenreRenameAction,\n additionalActions,\n onNodeClick,\n onNodeHover,\n renderInfoPanelBody,\n selectedNodeId,\n onSelectedNodeChange,\n hideInfoPanelClose,\n hideInfoPanelTitle,\n readOnly = false,\n allowWheelRotation,\n showToolbar,\n controlsOrientation,\n sizeMetric = \"songs\",\n floatingControlsLeading,\n busy,\n outline = false,\n defaultViewport,\n onViewportChange,\n playback,\n } = props;\n const focus: GenreFocusProps = props;\n const infoPanelCuration: InfoPanelCurationProps = {\n renderDangerZone: props.renderDangerZone,\n expanded: props.expanded,\n onExpandedChange: props.onExpandedChange,\n collapsed: props.collapsed,\n hideExpandButton: props.hideExpandButton,\n overlayPanel: props.overlayPanel,\n };\n const { isPlaying, setIsPlaying } = usePlayer();\n const defaultPlayback = useGenrePlaylistPlayback<T>({ scope, genrePlaylists });\n const { playingNodeId, play } = playback ?? defaultPlayback;\n const trackNoun = useTrackNoun();\n\n const nodes: GenreTreeNode[] = useMemo(\n () =>\n genrePlaylists.map((genrePlaylist) => ({\n id: genrePlaylist.uuid,\n parentId: genrePlaylist.parent?.uuid ?? null,\n name: genrePlaylist.name,\n itemCount: genrePlaylistSize(genrePlaylist, sizeMetric),\n actionable: Boolean(genrePlaylist.criteria),\n detached: !genrePlaylist.criteria,\n side: genrePlaylist.criteria?.side ?? undefined,\n wikidataId: genrePlaylist.criteria?.wikidataId ?? null,\n umbrella: genrePlaylist.isUmbrella,\n scene: genrePlaylist.scene ?? undefined,\n })),\n [genrePlaylists, sizeMetric],\n );\n\n const handlePlayPause = useCallback(\n (nodeId: string) => {\n const genrePlaylist = genrePlaylists.find((g) => g.uuid === nodeId);\n if (!genrePlaylist) return;\n\n if (nodeId === playingNodeId) {\n setIsPlaying(!isPlaying);\n return;\n }\n\n if (genrePlaylist.tracksCount === 0) {\n return;\n }\n\n play(genrePlaylist).catch((error) => {\n console.error(\"Failed to load genre playlist tracks:\", error);\n });\n },\n [genrePlaylists, playingNodeId, isPlaying, setIsPlaying, play],\n );\n\n const handleAddChild = useCallback(\n (parentId: string) => {\n const genrePlaylist = genrePlaylists.find((g) => g.uuid === parentId);\n if (!genrePlaylist?.criteria) return;\n handleGenreCreationAction(genrePlaylist.criteria);\n },\n [genrePlaylists, handleGenreCreationAction],\n );\n\n const handleRenameRequest = useCallback(\n (node: GenreTreeNode) => {\n const genrePlaylist = genrePlaylists.find((g) => g.uuid === node.id);\n if (!genrePlaylist?.criteria) return;\n handleGenreRenameAction(genrePlaylist.criteria);\n },\n [genrePlaylists, handleGenreRenameAction],\n );\n\n const handleDeleteRequest = useCallback((node: GenreTreeNode) => {\n if (confirm(`Are you sure you want to delete \"${node.name}\"?`)) {\n // TODO: Implement delete genre\n }\n }, []);\n\n const treeProps = {\n className,\n nodes,\n itemCountLabel: sizeMetric === \"views\" ? \"Views\" : `${capitalize(trackNoun.singular)} count`,\n playingNodeId,\n playState: isPlaying ? (\"playing\" as const) : (\"paused\" as const),\n onPlayPause: handlePlayPause,\n onAddChild: readOnly ? undefined : handleAddChild,\n onRenameRequest: readOnly ? undefined : handleRenameRequest,\n onDeleteRequest: readOnly ? undefined : handleDeleteRequest,\n additionalActions,\n onNodeClick,\n onNodeHover,\n renderInfoPanelBody,\n selectedNodeId,\n onSelectedNodeChange,\n hideInfoPanelClose,\n hideInfoPanelTitle,\n showToolbar,\n ...infoPanelCuration,\n readOnly,\n };\n\n return outline ? (\n <GenreTreeOutline {...treeProps} />\n ) : (\n <GenreTreeWheelRadialPopCore\n {...treeProps}\n allowWheelRotation={allowWheelRotation}\n controlsOrientation={controlsOrientation}\n floatingControlsLeading={floatingControlsLeading}\n busy={busy}\n defaultViewport={defaultViewport}\n onViewportChange={onViewportChange}\n {...(focus.onFocusGenreChange\n ? { focusNodeId: focus.focusGenreUuid, onFocusChange: focus.onFocusGenreChange }\n : { focusNodeId: undefined, onFocusChange: undefined })}\n />\n );\n}\n","\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\n\nexport type GenreTreeWheelHandoffProps = {\n skeleton: React.ReactNode;\n children: React.ReactNode;\n /** Called with `true` once `children` is revealed, and with `false` when the handoff unmounts. */\n onRevealedChange?: (revealed: boolean) => void;\n};\n\n/**\n * Keeps `skeleton` fully opaque over `children` for the first two animation frames after\n * `children` mounts, then cross-fades to `children`.\n *\n * `children` is one of genre-tree-view's wheel components (`GenreTreeWheel` /\n * `GenreTreeWheelRadialPopCore`), which compute their own pan/zoom \"fit to frame\" in a passive\n * effect (`hasInitialFitRef`) that runs *after* their first paint. Without this handoff, that\n * first paint — unfit, `translate(0,0) scale(1)` — is briefly visible the instant the skeleton is\n * swapped out, which reads as a flash/jump right as loading finishes. Two nested\n * `requestAnimationFrame` calls reliably span that effect's own render-commit-paint cycle, so\n * `children` is already laid out and stable by the time it's revealed — the skeleton itself never\n * changes, it just stays up until the real graph is ready underneath it.\n *\n * Remounting this component (e.g. via a `key` change, or the parent unmounting/remounting the\n * branch that renders it) restarts the handoff, which is what's wanted any time `children` itself\n * is about to mount fresh and re-run its own fit-to-frame effect.\n */\nexport function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }: GenreTreeWheelHandoffProps) {\n const [revealed, setRevealed] = useState(false);\n const onRevealedChangeRef = useRef(onRevealedChange);\n onRevealedChangeRef.current = onRevealedChange;\n\n useEffect(() => {\n let innerFrame = 0;\n const outerFrame = requestAnimationFrame(() => {\n innerFrame = requestAnimationFrame(() => {\n setRevealed(true);\n onRevealedChangeRef.current?.(true);\n });\n });\n return () => {\n onRevealedChangeRef.current?.(false);\n cancelAnimationFrame(outerFrame);\n cancelAnimationFrame(innerFrame);\n };\n }, []);\n\n return (\n <div className=\"relative w-full h-full\">\n <div\n className=\"absolute inset-0 transition-opacity duration-150 ease-out\"\n style={{ opacity: revealed ? 1 : 0, pointerEvents: revealed ? \"auto\" : \"none\" }}\n aria-hidden={!revealed}\n >\n {children}\n </div>\n {!revealed && (\n <div className=\"absolute inset-0\" aria-hidden=\"true\">\n {skeleton}\n </div>\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { ArrowLeft, LoaderCircle, Search, X } from \"lucide-react\";\nimport { Input } from \"@behindthemusictree/ui\";\n\nimport { CriteriaPlaylistSimple, useGenreSearch } from \"@behindthemusictree/app-kit/genre-tree\";\n\nexport type GenreSearchExtraResults = {\n /** Heading of this group; genre matches then get a \"Genres\" heading above it. */\n label: string;\n items: { key: string; content: ReactNode; onSelect: () => void }[];\n /** Announced politely, e.g. \"No artists found\". */\n status?: string;\n /** Shows a spinner in place of the search icon while the host resolves this group. */\n loading?: boolean;\n};\n\nexport type GenreSearchProps<T extends CriteriaPlaylistSimple = CriteriaPlaylistSimple> = {\n genrePlaylists: T[];\n onSelect: (genrePlaylist: T) => void;\n /** Name of the currently selected genre; fills the input whenever it changes. */\n selectedName?: string | null;\n /** When set, renders a \"Previous\" button left of the text; shown only when the host's CSS reveals `.genre-search-previous`. */\n onPrevious?: () => void;\n /** Called by the clear button, after it empties the input. */\n onClear?: () => void;\n placeholder?: string;\n /** Called with what the user types, and with \"\" on clear or pick; never when `selectedName` refills the input. */\n onQueryChange?: (query: string) => void;\n /** A second result group (e.g. artists) the host resolves from `onQueryChange`. */\n extraResults?: GenreSearchExtraResults;\n};\n\nexport default function GenreSearch<T extends CriteriaPlaylistSimple = CriteriaPlaylistSimple>({\n genrePlaylists,\n onSelect,\n selectedName = null,\n onClear,\n onPrevious,\n placeholder,\n onQueryChange,\n extraResults,\n}: GenreSearchProps<T>) {\n const { query, setQuery, results, canClear, clear, select } = useGenreSearch({\n genrePlaylists,\n onSelect,\n selectedName,\n onClear,\n });\n\n const extraItems = query !== \"\" && query !== selectedName ? (extraResults?.items ?? []) : [];\n const label = extraResults ? \"Search\" : \"Search a genre\";\n const loading = query !== \"\" && query !== selectedName && (extraResults?.loading ?? false);\n const showList = results.length > 0 || extraItems.length > 0;\n const heading = \"px-3 pt-2 pb-1 text-xs font-medium uppercase text-[var(--color-text-muted)]\";\n\n return (\n <div className=\"genre-search relative w-[calc(var(--layout-detail-panel-w,25.5rem)-2*var(--layout-panel-inset,1rem))] shrink-0\">\n {onPrevious && (\n <button\n type=\"button\"\n aria-label=\"Previous\"\n title=\"Previous\"\n className=\"genre-search-previous absolute left-2 top-1/2 z-10 hidden p-1.5 text-[var(--color-text-muted)] rounded-full -translate-y-1/2 hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-text)]\"\n onClick={onPrevious}\n >\n <ArrowLeft aria-hidden=\"true\" className=\"w-4 h-4\" />\n </button>\n )}\n <Input\n type=\"text\"\n value={query}\n onChange={(event) => {\n setQuery(event.target.value);\n onQueryChange?.(event.target.value);\n }}\n placeholder={placeholder ?? `${label}…`}\n aria-label={label}\n aria-busy={loading}\n className=\"h-[var(--layout-search-h,3rem)] pl-6 pr-20 text-base transition-[padding] duration-200 ease-out motion-reduce:transition-none bg-[var(--color-surface)] border-[var(--color-border)] rounded-full shadow-md\"\n />\n <div className=\"absolute right-3 top-1/2 flex items-center -translate-y-1/2\">\n <span\n data-loading={loading}\n className=\"relative p-2 text-[var(--color-text-muted)] pointer-events-none\"\n >\n <Search\n aria-hidden=\"true\"\n className={`w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? \"opacity-0\" : \"\"}`}\n />\n <LoaderCircle\n aria-hidden=\"true\"\n className={`absolute inset-2 w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${\n loading ? \"opacity-100 delay-300 animate-spin motion-reduce:animate-none\" : \"opacity-0\"\n }`}\n />\n </span>\n {canClear && (\n <button\n type=\"button\"\n aria-label=\"Clear search\"\n className=\"p-2 text-[var(--color-text-muted)] rounded-full hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-text)]\"\n onClick={() => {\n clear();\n onQueryChange?.(\"\");\n }}\n >\n <X aria-hidden=\"true\" className=\"w-6 h-6\" />\n </button>\n )}\n </div>\n {extraResults?.status && (\n <p role=\"status\" aria-live=\"polite\" className=\"sr-only\">\n {extraResults.status}\n </p>\n )}\n {showList && (\n <ul className=\"genre-search-results absolute z-40 w-full max-h-64 mt-1 overflow-y-auto bg-[var(--color-surface)] border border-[var(--color-border)] rounded-xl shadow-md\">\n {extraResults && results.length > 0 && <li className={heading}>Genres</li>}\n {results.map((genrePlaylist) => (\n <li key={genrePlaylist.uuid}>\n <button\n type=\"button\"\n className=\"w-full px-3 py-1.5 text-left hover:bg-[var(--color-surface-muted)]\"\n onClick={() => {\n select(genrePlaylist);\n onQueryChange?.(\"\");\n }}\n >\n {genrePlaylist.name}\n {genrePlaylist.isUmbrella && <span className=\"ml-2 text-xs italic text-[var(--color-text-muted)]\">umbrella</span>}\n </button>\n </li>\n ))}\n {extraResults && extraItems.length > 0 && <li className={heading}>{extraResults.label}</li>}\n {extraItems.map((item) => (\n <li key={item.key}>\n <button\n type=\"button\"\n className=\"w-full px-3 py-1.5 text-left hover:bg-[var(--color-surface-muted)]\"\n onClick={() => {\n setQuery(\"\");\n onQueryChange?.(\"\");\n item.onSelect();\n }}\n >\n {item.content}\n </button>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { MoveUpRight, Pause, Pencil, Play, Plus, ZoomIn } from \"lucide-react\";\nimport type {\n GenreTreeAction,\n GenreTreeNode,\n} from \"@behindthemusictree/genre-tree-view\";\n\nexport type GenreActionBandProps = {\n node: GenreTreeNode;\n /** When set, shows the Play/Pause action. */\n onPlayPause?: () => void;\n isPlaying?: boolean;\n /** When set, shows the \"Zoom in\" action. */\n onZoomIn?: () => void;\n /** When set, shows the \"Add\" (sub-genre) action. */\n onAddChild?: () => void;\n /** When set, shows the \"Rename\" action. */\n onRename?: () => void;\n /** When set, shows the \"Parent\" (change parent) action. */\n onChangeParent?: () => void;\n additionalActions?: (node: GenreTreeNode) => GenreTreeAction[];\n};\n\ntype BandItem = {\n key: string;\n label: string;\n icon: ReactNode;\n onClick: (event: React.MouseEvent) => void;\n disabled?: boolean;\n};\n\nexport default function GenreActionBand({\n node,\n onPlayPause,\n isPlaying = false,\n onZoomIn,\n onAddChild,\n onRename,\n onChangeParent,\n additionalActions,\n}: GenreActionBandProps) {\n const items: BandItem[] = [];\n const adminItems: BandItem[] = [];\n if (onPlayPause) {\n items.push({\n key: \"play-pause\",\n label: isPlaying ? \"Pause\" : \"Play\",\n icon: isPlaying ? (\n <Pause aria-hidden=\"true\" className=\"w-5 h-5\" />\n ) : (\n <Play aria-hidden=\"true\" className=\"w-5 h-5\" />\n ),\n onClick: onPlayPause,\n });\n }\n if (onZoomIn) {\n items.push({\n key: \"zoom-in\",\n label: \"Focus\",\n icon: <ZoomIn aria-hidden=\"true\" className=\"w-5 h-5\" />,\n onClick: onZoomIn,\n });\n }\n if (onAddChild) {\n adminItems.push({ key: \"add-child\", label: \"Add\", icon: <Plus aria-hidden=\"true\" className=\"w-5 h-5\" />, onClick: onAddChild });\n }\n if (onRename) {\n adminItems.push({ key: \"rename\", label: \"Rename\", icon: <Pencil aria-hidden=\"true\" className=\"w-5 h-5\" />, onClick: onRename });\n }\n if (onChangeParent) {\n adminItems.push({ key: \"change-parent\", label: \"Parent\", icon: <MoveUpRight aria-hidden=\"true\" className=\"w-5 h-5\" />, onClick: onChangeParent });\n }\n for (const action of additionalActions?.(node) ?? []) {\n items.push({\n key: action.key,\n label: action.label(node),\n icon: action.icon(node),\n onClick: (event) => action.onClick(event, node),\n disabled: action.enabled ? !action.enabled(node) : false,\n });\n }\n if (items.length === 0 && adminItems.length === 0) return null;\n\n const renderItems = (list: BandItem[]) =>\n list.map((item) => (\n <button\n key={item.key}\n type=\"button\"\n disabled={item.disabled}\n onClick={item.onClick}\n className=\"group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none disabled:opacity-50\"\n >\n <span className=\"flex items-center justify-center w-10 h-10 rounded-full group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)] bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)] group-disabled:group-hover:bg-[var(--color-surface-muted)]\">\n {item.icon}\n </span>\n {item.label}\n </button>\n ));\n\n return (\n <div className=\"genre-action-band border-b border-[var(--color-border)]\">\n {items.length > 0 && <div className=\"flex flex-wrap gap-1 px-3.5 py-3\">{renderItems(items)}</div>}\n {adminItems.length > 0 && (\n <section\n aria-label=\"Admin actions\"\n className={`px-3.5 py-3 ${items.length > 0 ? \"border-t border-[var(--color-border)]\" : \"\"}`}\n >\n <h3 className=\"mb-2 text-xs font-semibold uppercase tracking-wide text-[var(--color-text-muted)]\">Admin</h3>\n <div className=\"flex flex-wrap gap-1\">{renderItems(adminItems)}</div>\n </section>\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { ChevronLeft, ChevronRight } from \"lucide-react\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport type PanelEdgeTabAction = {\n label: string;\n onClick: () => void;\n /** Reflected as `aria-expanded` on the button; omit when the button is not a disclosure. */\n expanded?: boolean;\n};\n\nexport interface PanelEdgeTabProps {\n /** Side of the screen the panel sits on; the tab hangs off the panel's inner edge. */\n side: \"left\" | \"right\";\n /** Button whose chevron points toward the screen edge. */\n edge: PanelEdgeTabAction | null;\n /** Button whose chevron points toward the center of the screen. */\n center: PanelEdgeTabAction | null;\n /** Id of the panel the buttons control, when the host can name it. */\n controls?: string;\n className?: string;\n}\n\nconst buttonClassName =\n \"flex h-12 w-full items-center justify-center p-0 text-[var(--color-text-muted)] hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-text)] focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--color-primary)]\";\n\nexport default function PanelEdgeTab({ side, edge, center, controls, className }: PanelEdgeTabProps) {\n if (!edge && !center) return null;\n const isLeft = side === \"left\";\n const EdgeIcon = isLeft ? ChevronLeft : ChevronRight;\n const CenterIcon = isLeft ? ChevronRight : ChevronLeft;\n\n const renderButton = (action: PanelEdgeTabAction, icon: ReactNode) => (\n <button\n type=\"button\"\n aria-label={action.label}\n title={action.label}\n aria-controls={controls}\n aria-expanded={action.expanded}\n className={buttonClassName}\n onClick={action.onClick}\n >\n {icon}\n </button>\n );\n\n const edgeButton = edge && renderButton(edge, <EdgeIcon aria-hidden=\"true\" className=\"h-5 w-5\" />);\n const centerButton = center && renderButton(center, <CenterIcon aria-hidden=\"true\" className=\"h-5 w-5\" />);\n const buttons = isLeft ? [edgeButton, centerButton] : [centerButton, edgeButton];\n\n return (\n <div\n className={twMerge(\n \"panel-edge-tab flex w-[var(--layout-edge-tab-w)] flex-col overflow-hidden bg-[var(--color-surface)] shadow-[0_1px_2px_rgba(60,64,67,0.3),0_2px_6px_2px_rgba(60,64,67,0.15)] [clip-path:inset(-0.5rem_-0.5rem_-0.5rem_0)]\",\n isLeft ? \"rounded-r-lg\" : \"rounded-l-lg [clip-path:inset(-0.5rem_0_-0.5rem_-0.5rem)]\",\n className,\n )}\n >\n {buttons[0]}\n {buttons[0] && buttons[1] && <div role=\"presentation\" className=\"h-px shrink-0 bg-[var(--color-border)]\" />}\n {buttons[1]}\n </div>\n );\n}\n","\"use client\";\n\nimport { useEffect, useMemo } from \"react\";\n\nimport {\n CriteriaPlaylistSimple,\n ReparentCandidate,\n type GenreUuid,\n getReparentCandidates,\n useUpdateGenre,\n} from \"@behindthemusictree/app-kit/genre-tree\";\nimport type { Scope } from \"@behindthemusictree/app-kit/transport\";\nimport BasePopup from \"../popup/BasePopup\";\nimport GenreSearch from \"./GenreSearch\";\n\nexport type ParentChange = { genre: GenreUuid; from: GenreUuid | null; to: GenreUuid };\n\ntype ChangeParentPopupProps = {\n genrePlaylist: ReparentCandidate;\n genrePlaylists: CriteriaPlaylistSimple[];\n scope: Scope;\n getBackendBaseUrl: () => string;\n onClose: () => void;\n onParentChanged?: (change: ParentChange) => void;\n};\n\nexport default function ChangeParentPopup({\n genrePlaylist,\n genrePlaylists,\n scope,\n getBackendBaseUrl,\n onClose,\n onParentChanged,\n}: ChangeParentPopupProps) {\n const { mutate, isPending, formErrors } = useUpdateGenre(scope, getBackendBaseUrl);\n const candidates = useMemo(\n () => getReparentCandidates(genrePlaylists, genrePlaylist.uuid),\n [genrePlaylists, genrePlaylist.uuid],\n );\n\n const handleSelect = (newParent: ReparentCandidate) => {\n const genre = genrePlaylist.criteria.uuid;\n const parentPlaylistUuid = genrePlaylist.parent?.uuid;\n const from = parentPlaylistUuid\n ? (genrePlaylists.find(({ uuid }) => uuid === parentPlaylistUuid)?.criteria?.uuid ?? null)\n : null;\n mutate(\n { uuid: genre, data: { parent: newParent.criteria.uuid } },\n {\n onSuccess: () => {\n onClose();\n onParentChanged?.({ genre, from, to: newParent.criteria.uuid });\n },\n },\n );\n };\n\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") onClose();\n };\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [onClose]);\n\n return (\n <BasePopup title={`Change parent of ${genrePlaylist.name}`} onClose={onClose} showCancelButton fitContent>\n <p className=\"mb-3 text-sm text-[var(--color-text-muted)]\">\n Current parent: {genrePlaylist.parent?.name ?? \"none\"}\n </p>\n <div aria-busy={isPending} className={isPending ? \"pointer-events-none opacity-60\" : undefined}>\n <GenreSearch\n genrePlaylists={candidates}\n onSelect={handleSelect}\n placeholder=\"Search a new parent…\"\n />\n </div>\n {formErrors.length > 0 && (\n <p role=\"alert\" className=\"mt-3 text-sm text-[var(--color-red-500)]\">\n {formErrors.map(({ message }) => message).join(\" \")}\n </p>\n )}\n </BasePopup>\n );\n}\n","\"use client\";\n\nimport { Skeleton } from \"@behindthemusictree/ui\";\nimport type { GenreTreeOverlayPanel } from \"@behindthemusictree/genre-tree-view\";\nimport { useArtist, type RenderArtistLink } from \"@behindthemusictree/app-kit/genre-tree\";\nimport ArtistHeader from \"./ArtistHeader\";\nimport ArtistPhoto from \"./ArtistPhoto\";\nimport ArtistMembers from \"./ArtistMembers\";\nimport ArtistDiscography from \"./ArtistDiscography\";\nimport ArtistSongs from \"./ArtistSongs\";\n\nexport interface ArtistPanelOptions {\n uuid: string | null;\n getBackendBaseUrl: () => string;\n /** Renders member and song artist names. */\n renderArtistLink: RenderArtistLink;\n expanded: boolean;\n onExpandedChange: (expanded: boolean) => void;\n onClose: () => void;\n}\n\n/** The artist detail overlay panel for `uuid`, or undefined when no artist is set. */\nexport function useArtistPanel({\n uuid,\n getBackendBaseUrl,\n renderArtistLink,\n expanded,\n onExpandedChange,\n onClose,\n}: ArtistPanelOptions): GenreTreeOverlayPanel | undefined {\n const result = useArtist(uuid, getBackendBaseUrl);\n\n const panel = { expanded, onExpandedChange, onClose };\n switch (result.status) {\n case \"idle\":\n return undefined;\n case \"loading\":\n return {\n ...panel,\n title: \"Artist\",\n content: (\n <div aria-busy=\"true\" className=\"flex flex-col gap-2.5 px-6 py-2.5\">\n <Skeleton className=\"h-3 w-16\" />\n <Skeleton className=\"h-4 w-32\" />\n <Skeleton className=\"h-3 w-16\" />\n <Skeleton className=\"h-4 w-24\" />\n <Skeleton className=\"h-16 mt-2.5\" />\n <Skeleton className=\"h-16\" />\n </div>\n ),\n };\n case \"not-found\":\n return {\n ...panel,\n title: \"Artist not found\",\n content: <p className=\"px-6 py-2.5 text-[var(--color-text-muted)]\">This artist does not exist.</p>,\n };\n case \"error\":\n return {\n ...panel,\n title: \"Artist\",\n content: <p className=\"px-6 py-2.5 text-red-600\">Could not load this artist.</p>,\n };\n case \"success\": {\n const { artist } = result;\n return {\n ...panel,\n title: artist.name,\n content: (\n <>\n {artist.image && <ArtistPhoto image={artist.image} name={artist.name} />}\n <ArtistHeader artist={artist} />\n <ArtistMembers title=\"Members\" memberships={artist.members} renderArtistLink={renderArtistLink} />\n <ArtistMembers title=\"Member of\" memberships={artist.memberOf} renderArtistLink={renderArtistLink} />\n {artist.tracksCount > 0 && (\n <ArtistSongs\n artist={{ uuid: artist.uuid, name: artist.name }}\n getBackendBaseUrl={getBackendBaseUrl}\n renderArtistLink={renderArtistLink}\n />\n )}\n </>\n ),\n extraContent: artist.albums.length > 0 ? <ArtistDiscography albums={artist.albums} /> : undefined,\n };\n }\n }\n}\n","import type { ReactNode } from \"react\";\nimport { FieldList } from \"@behindthemusictree/ui\";\nimport type { ArtistDetailed } from \"@behindthemusictree/app-kit/genre-tree\";\nimport ArtistLinks, { artistLinks } from \"./ArtistLinks\";\nimport { formatYears } from \"./formatYears\";\n\nconst countryNames = new Intl.DisplayNames([\"en\"], { type: \"region\" });\n\nexport default function ArtistHeader({ artist }: { artist: ArtistDetailed }) {\n const fields: { label: string; value: ReactNode }[] = [];\n if (artist.type) fields.push({ label: \"Type\", value: artist.type.charAt(0).toUpperCase() + artist.type.slice(1) });\n const country = artist.country && countryNames.of(artist.country);\n if (country) fields.push({ label: \"Country\", value: country });\n const years = formatYears(artist.beginYear, artist.endYear, artist.ended);\n if (years) fields.push({ label: \"Active\", value: years });\n const links = artistLinks(artist);\n if (links.length > 0) fields.push({ label: \"Links\", value: <ArtistLinks links={links} /> });\n\n if (fields.length === 0) return null;\n return <FieldList items={fields} />;\n}\n","import type { ComponentType } from \"react\";\nimport { Globe, Share2 } from \"lucide-react\";\nimport {\n SiBandcamp,\n SiDiscogs,\n SiMusicbrainz,\n SiSoundcloud,\n SiSpotify,\n SiWikidata,\n SiWikipedia,\n SiYoutube,\n} from \"react-icons/si\";\nimport { Chip } from \"@behindthemusictree/ui\";\nimport type { ArtistDetailed, ArtistLink } from \"@behindthemusictree/app-kit/genre-tree\";\n\nconst LINK_KINDS: Record<ArtistLink[\"kind\"], { label: string; Icon: ComponentType<{ className?: string }> }> = {\n official: { label: \"Official website\", Icon: Globe },\n wikipedia: { label: \"Wikipedia\", Icon: SiWikipedia },\n wikidata: { label: \"Wikidata\", Icon: SiWikidata },\n spotify: { label: \"Spotify\", Icon: SiSpotify },\n youtube: { label: \"YouTube\", Icon: SiYoutube },\n bandcamp: { label: \"Bandcamp\", Icon: SiBandcamp },\n soundcloud: { label: \"SoundCloud\", Icon: SiSoundcloud },\n discogs: { label: \"Discogs\", Icon: SiDiscogs },\n social: { label: \"Social\", Icon: Share2 },\n};\n\nfunction socialLabel(url: string): string {\n try {\n return new URL(url).hostname.replace(/^www\\./, \"\");\n } catch {\n return LINK_KINDS.social.label;\n }\n}\n\ninterface ArtistLinkItem {\n key: string;\n url: string;\n Icon: ComponentType<{ className?: string }>;\n label: string;\n}\n\nexport function artistLinks(artist: ArtistDetailed): ArtistLinkItem[] {\n const links = artist.links.map(({ kind, url }) => ({\n key: `${kind}-${url}`,\n url,\n Icon: LINK_KINDS[kind].Icon,\n label: kind === \"social\" ? socialLabel(url) : LINK_KINDS[kind].label,\n }));\n if (artist.musicbrainzArtistId) {\n links.push({\n key: \"musicbrainz\",\n url: `https://musicbrainz.org/artist/${artist.musicbrainzArtistId}`,\n Icon: SiMusicbrainz,\n label: \"MusicBrainz\",\n });\n }\n return links;\n}\n\nexport default function ArtistLinks({ links }: { links: ArtistLinkItem[] }) {\n return (\n <ul className=\"flex flex-wrap gap-1.5\">\n {links.map(({ key, url, Icon, label }) => (\n <li key={key}>\n <Chip href={url} icon={<Icon className=\"h-3.5 w-3.5 shrink-0\" aria-hidden=\"true\" />}>\n {label}\n </Chip>\n </li>\n ))}\n </ul>\n );\n}\n","/** \"1994–present\", \"1994–2008\", \"?–2008\", or null when nothing is known. */\nexport function formatYears(beginYear: number | null, endYear: number | null, ended: boolean): string | null {\n if (beginYear === null && endYear === null) return ended ? \"ended\" : null;\n const end = endYear ?? (ended ? \"?\" : \"present\");\n return `${beginYear ?? \"?\"}–${end}`;\n}\n","import type { ReactNode } from \"react\";\nimport type { ArtistImage } from \"@behindthemusictree/app-kit/genre-tree\";\n\nfunction ExternalLink({ href, children }: { href: string; children: ReactNode }) {\n return (\n <a href={href} target=\"_blank\" rel=\"noopener noreferrer\" className=\"underline-offset-2 hover:underline\">\n {children}\n </a>\n );\n}\n\nexport default function ArtistPhoto({ image, name }: { image: ArtistImage; name: string }) {\n return (\n <figure className=\"flex flex-col gap-1.5 px-6 py-2.5\">\n <img src={image.url} alt={name} className=\"h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded\" />\n <figcaption className=\"text-xs text-[var(--color-text-muted)]\">\n Photo: <ExternalLink href={image.pageUrl}>{image.author ?? \"Wikimedia Commons\"}</ExternalLink>,{\" \"}\n {image.licenseUrl ? <ExternalLink href={image.licenseUrl}>{image.license}</ExternalLink> : image.license}\n </figcaption>\n </figure>\n );\n}\n","import type { ArtistMembership, RenderArtistLink } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { PanelGroup, PanelSection } from \"@behindthemusictree/ui\";\nimport { formatYears } from \"./formatYears\";\n\ninterface MembershipListProps {\n title: string;\n memberships: ArtistMembership[];\n renderArtistLink: RenderArtistLink;\n}\n\nfunction MembershipList({ title, memberships, renderArtistLink }: MembershipListProps) {\n if (memberships.length === 0) return null;\n return (\n <PanelGroup title={title}>\n <ul>\n {memberships.map((membership, index) => {\n const details = [\n formatYears(membership.beginYear, membership.endYear, membership.ended),\n ...membership.roles,\n ];\n const detailsText = details.filter(Boolean).join(\" · \");\n return (\n // A member can have several periods in the same group.\n <li key={`${membership.artist.uuid}-${index}`} className=\"truncate py-0.5\" title={detailsText || undefined}>\n <span className=\"font-medium text-[var(--color-text)]\">{renderArtistLink(membership.artist)}</span>\n {detailsText && <span className=\"text-[var(--color-text-muted)]\"> — {detailsText}</span>}\n </li>\n );\n })}\n </ul>\n </PanelGroup>\n );\n}\n\nexport default function ArtistMembers({ title, memberships, renderArtistLink }: MembershipListProps) {\n if (memberships.length === 0) return null;\n const isCurrent = (membership: ArtistMembership) => !membership.ended && membership.endYear === null;\n\n return (\n <PanelSection title={title}>\n <MembershipList title=\"Current\" memberships={memberships.filter(isCurrent)} renderArtistLink={renderArtistLink} />\n <MembershipList\n title=\"Past\"\n memberships={memberships.filter((membership) => !isCurrent(membership))}\n renderArtistLink={renderArtistLink}\n />\n </PanelSection>\n );\n}\n","import type { ArtistAlbum } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { PanelGroup, PanelSection } from \"@behindthemusictree/ui\";\n\nconst GROUPS = [\"Albums\", \"EPs\", \"Singles\", \"Live\", \"Compilations\", \"Other\"] as const;\ntype Group = (typeof GROUPS)[number];\n\nconst PRIMARY_GROUPS: Record<string, Group> = { album: \"Albums\", ep: \"EPs\", single: \"Singles\" };\n\n// Secondary types win over the primary one: a live album belongs with live releases, not studio albums.\nexport function discographyGroup({ primaryType, secondaryTypes }: ArtistAlbum): Group {\n const secondary = secondaryTypes.map((type) => type.toLowerCase());\n if (secondary.includes(\"live\")) return \"Live\";\n if (secondary.includes(\"compilation\")) return \"Compilations\";\n return PRIMARY_GROUPS[primaryType?.toLowerCase() ?? \"\"] ?? \"Other\";\n}\n\nfunction byYear(a: ArtistAlbum, b: ArtistAlbum): number {\n if (a.year !== b.year) {\n if (a.year === null) return 1;\n if (b.year === null) return -1;\n return a.year.localeCompare(b.year);\n }\n return a.name.localeCompare(b.name);\n}\n\nexport default function ArtistDiscography({ albums }: { albums: ArtistAlbum[] }) {\n if (albums.length === 0) return null;\n const sorted = [...albums].sort(byYear);\n\n return (\n <PanelSection title=\"Discography\">\n {GROUPS.map((group) => {\n const groupAlbums = sorted.filter((album) => discographyGroup(album) === group);\n if (groupAlbums.length === 0) return null;\n return (\n <PanelGroup key={group} title={group}>\n <ul>\n {groupAlbums.map((album) => (\n <li key={album.uuid} className=\"grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5\">\n <span className=\"tabular-nums text-[var(--color-text-muted)]\">{album.year ?? \"—\"}</span>\n <span className=\"truncate text-[var(--color-text)]\" title={album.name}>\n {album.name}\n </span>\n </li>\n ))}\n </ul>\n </PanelGroup>\n );\n })}\n </PanelSection>\n );\n}\n","\"use client\";\n\nimport { useState, type MouseEvent } from \"react\";\nimport { keepPreviousData } from \"@tanstack/react-query\";\nimport { Play } from \"lucide-react\";\nimport { Pagination, PanelSection, Skeleton } from \"@behindthemusictree/ui\";\nimport { usePlayer } from \"@behindthemusictree/app-kit/player\";\nimport {\n SongDetailedSchema,\n libraryEndpoints,\n songUnplayableReason,\n useTrackList,\n type RenderArtistLink,\n type SongDetailed,\n} from \"@behindthemusictree/app-kit/genre-tree\";\nimport { PaginatedResponseSchema, useFetchWrapper, useQueryWithParse } from \"@behindthemusictree/app-kit/transport\";\nimport TrackItem from \"../track-list-sidebar/TrackItem\";\n\nconst SongPageSchema = PaginatedResponseSchema(SongDetailedSchema);\n\nconst SORTS = [\n { ordering: \"title\", label: \"Title\" },\n { ordering: \"-albumYear\", label: \"Year\" },\n] as const;\ntype Ordering = (typeof SORTS)[number][\"ordering\"];\n\ninterface ArtistSongsProps {\n artist: { uuid: string; name: string };\n getBackendBaseUrl: () => string;\n renderArtistLink: RenderArtistLink;\n}\n\nexport default function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }: ArtistSongsProps) {\n const [page, setPage] = useState(1);\n const [ordering, setOrdering] = useState<Ordering>(\"title\");\n const { fetch } = useFetchWrapper(getBackendBaseUrl);\n const { playNewTrackListFromArtist } = useTrackList<SongDetailed>();\n const { playerTrackObject } = usePlayer();\n const { data, isPending, isError } = useQueryWithParse({\n queryKey: [\"referenceSongs\", \"artist\", artist.uuid, ordering, page],\n queryFn: () =>\n fetch(libraryEndpoints.reference.songs.list(), true, false, undefined, {\n artist: artist.uuid,\n ordering,\n page,\n }),\n schema: SongPageSchema,\n placeholderData: keepPreviousData,\n context: \"ArtistSongs\",\n });\n\n const play = (startSongUuid?: string) =>\n playNewTrackListFromArtist({ uuid: artist.uuid, name: artist.name }, \"reference\", {\n ordering,\n startSongUuid,\n });\n\n // TrackItem's own play control (`.track-position-play-pause`, a div) plays from whatever list is active; a click on a track that isn't\n // loaded yet must queue this artist's list instead.\n const handleRowClickCapture = (event: MouseEvent, song: SongDetailed) => {\n if (!(event.target instanceof Element) || !event.target.closest(\".track-position-play-pause\")) return;\n if (playerTrackObject?.track.id === song.uuid || songUnplayableReason(song) !== null) return;\n event.stopPropagation();\n void play(song.uuid);\n };\n\n return (\n <PanelSection title=\"Songs\">\n {isPending ? (\n <Skeleton className=\"h-14\" aria-busy=\"true\" />\n ) : isError ? (\n <p className=\"text-red-600\">Could not load songs.</p>\n ) : data.results.length === 0 ? (\n <p className=\"text-[var(--color-text-muted)]\">No songs yet.</p>\n ) : (\n <>\n <div className=\"flex flex-wrap items-center justify-between gap-2 mb-2\">\n <button\n type=\"button\"\n onClick={() => void play()}\n className=\"flex w-fit items-center gap-1.5 px-3 py-1 text-xs font-medium bg-gray-900 text-white rounded-full hover:bg-gray-700\"\n >\n <Play className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n Play all\n </button>\n <div role=\"group\" aria-label=\"Sort songs by\" className=\"flex gap-1 text-xs\">\n {SORTS.map((sort) => (\n <button\n key={sort.ordering}\n type=\"button\"\n aria-pressed={ordering === sort.ordering}\n onClick={() => {\n setOrdering(sort.ordering);\n setPage(1);\n }}\n className=\"px-2.5 py-0.5 text-[var(--color-text)] rounded-full hover:bg-[var(--color-surface-muted)] aria-pressed:bg-gray-900 aria-pressed:text-white\"\n >\n {sort.label}\n </button>\n ))}\n </div>\n </div>\n <ul className=\"overflow-hidden mb-2 bg-zinc-950 rounded-lg\">\n {data.results.map((song, index) => (\n <li key={song.uuid} onClickCapture={(event) => handleRowClickCapture(event, song)}>\n <TrackItem\n track={song}\n position={(page - 1) * data.pageSize + index + 1}\n renderArtistLink={renderArtistLink}\n />\n </li>\n ))}\n </ul>\n <Pagination currentPage={page} totalPages={data.totalPages} onPageChange={setPage} />\n </>\n )}\n </PanelSection>\n );\n}\n","\"use client\";\n\nimport { ReactNode } from \"react\";\n\nimport { usePlayer } from \"@behindthemusictree/app-kit/player\";\nimport { useTrackList } from \"@behindthemusictree/app-kit/genre-tree\";\nimport TrackPositionPlayPause from \"../TrackPositionPlayPause\";\nimport { TrackBase } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { isSong, songUnplayableReason } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { MusicBrainzRecordingLink } from \"../MusicBrainzRecordingLink\";\nimport { UnplayableReason } from \"../UnplayableReason\";\nimport { ArtistNames, RenderArtistLink } from \"@behindthemusictree/app-kit/genre-tree\";\n\nexport interface TrackItemProps<T extends TrackBase> {\n track: T;\n position: number;\n renderDuration?: (track: T) => ReactNode;\n renderActions?: (track: T) => ReactNode;\n /** When set, artist names render through it (e.g. a router `Link`). */\n renderArtistLink?: RenderArtistLink;\n}\n\nexport default function TrackItem<T extends TrackBase>({\n track,\n position,\n renderDuration,\n renderActions,\n renderArtistLink,\n}: TrackItemProps<T>) {\n const { handlePlayPauseAction, playerTrackObject, loadTrackForPlayer } = usePlayer();\n const { trackList, toTrackAtPosition } = useTrackList<T>();\n const scope = trackList?.origin?.scope ?? null;\n const unplayableReason = isSong(track) ? songUnplayableReason(track) : null;\n\n const handlePlayPauseClick = (event: React.MouseEvent) => {\n event.stopPropagation();\n if (playerTrackObject && playerTrackObject.track.id === track.uuid) {\n handlePlayPauseAction();\n } else if (scope != null) {\n toTrackAtPosition(position);\n loadTrackForPlayer(track.uuid);\n }\n };\n\n return (\n <div\n className={`track-item flex h-14 text-[var(--color-text)] ${unplayableReason ? \"opacity-50\" : \"hover:bg-[var(--color-surface-muted)] group\"}`}\n title={unplayableReason ?? undefined}\n >\n {unplayableReason ? (\n <div\n className=\"flex items-center justify-center text-lg w-16\"\n style={{ minWidth: \"4rem\", maxWidth: \"4rem\" }}\n aria-disabled=\"true\"\n >\n {position}\n </div>\n ) : (\n <TrackPositionPlayPause position={position} uuid={track.uuid} handlePlayPauseClick={handlePlayPauseClick} />\n )}\n <div className=\"title-artist-container flex flex-col items-start justify-center min-w-0 flex-1\">\n <div className=\"flex max-w-full items-center gap-2\">\n <div className=\"title text-lg font-bold text-[var(--color-text)] max-w-full truncate\">{track.title}</div>\n {isSong(track) && <MusicBrainzRecordingLink song={track} />}\n </div>\n {track.artists && track.artists.length > 0 ? (\n <div className=\"artist text-base max-w-full truncate text-[var(--color-text-muted)]\">\n <ArtistNames artists={track.artists} renderArtistLink={renderArtistLink} />\n </div>\n ) : (\n \"\"\n )}\n {unplayableReason && (\n <div className=\"text-xs\">\n <UnplayableReason reason={unplayableReason} />\n </div>\n )}\n </div>\n <div className=\"album-name items-start justify-center w-1/3 min-w-0 ml-2 max-w-full truncate text-[var(--color-text-muted)]\">\n {track.album ? track.album.name : \"\"}\n </div>\n <div className=\"genre-name-container flex items-center justify-end w-1/6 min-w-0 shrink-0\">\n {track.genre ? (\n <div className=\"genre-name font-bold p-1 border border-[var(--color-text-muted)] text-xs max-w-full truncate\">{track.genre.name}</div>\n ) : (\n \"\"\n )}\n </div>\n {renderDuration && (\n <div\n className=\"duration flex text-base w-16 items-center justify-center\"\n style={{ minWidth: \"4rem\", maxWidth: \"4rem\" }}\n >\n {renderDuration(track)}\n </div>\n )}\n {renderActions && (\n <div className=\"edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer\">\n {renderActions(track)}\n </div>\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport { FaPlay, FaPause } from \"react-icons/fa\";\n\nimport { usePlayer } from \"@behindthemusictree/app-kit/player\";\nimport { PlayStates } from \"@behindthemusictree/app-kit/player\";\nimport { RingLoader } from \"@behindthemusictree/ui\";\n\nexport interface TrackPositionPlayPauseProps {\n position: number;\n uuid: string;\n handlePlayPauseClick: (event: React.MouseEvent) => void;\n}\n\nexport default function TrackPositionPlayPause({ position, uuid, handlePlayPauseClick }: TrackPositionPlayPauseProps) {\n const { playerTrackObject, playState } = usePlayer();\n const isThisTrack = playerTrackObject?.track.id === uuid;\n\n return (\n <div\n className=\"track-position-play-pause flex items-center justify-center text-lg w-16 cursor-pointer\"\n style={{ minWidth: \"4rem\", maxWidth: \"4rem\" }}\n onClick={handlePlayPauseClick}\n >\n <div className=\"group-hover:hidden\">\n <div>\n {isThisTrack && playState !== PlayStates.STOPPED ? (\n <div className=\"flex space-x-1 items-end\">\n <div\n className={`w-playingbar bg-[var(--color-green-500)] h-3 origin-bottom ${\n playState === PlayStates.PLAYING ? \"animate-scale-pulse\" : \"\"\n }`}\n ></div>\n <div\n className={`w-playingbar bg-[var(--color-green-500)] h-4 origin-bottom ${\n playState === PlayStates.PLAYING ? \"animate-scale-pulse delay-200\" : \"\"\n }`}\n style={{ animationDelay: \"0.2s\" }}\n ></div>\n <div\n className={`w-playingbar bg-[var(--color-green-500)] h-3 origin-bottom ${\n playState === PlayStates.PLAYING ? \"animate-scale-pulse delay-400\" : \"\"\n }`}\n style={{ animationDelay: \"0.3s\" }}\n ></div>\n </div>\n ) : (\n position\n )}\n </div>\n </div>\n <div className=\"hidden group-hover:flex items-center justify-center\">\n {isThisTrack && playState === PlayStates.LOADING ? (\n <RingLoader size={16} />\n ) : isThisTrack && playState === PlayStates.PLAYING ? (\n <FaPause />\n ) : (\n <FaPlay />\n )}\n </div>\n </div>\n );\n}\n","\"use client\";\n\nimport { MdMusicNote, MdVisibility } from \"react-icons/md\";\nimport { SegmentedControl } from \"@behindthemusictree/ui\";\n\nimport type { GenreTreeSizeMetric } from \"./playlist-tree/size-metric\";\n\nexport function SizeMetricToggle({\n value,\n onChange,\n}: {\n value: GenreTreeSizeMetric;\n onChange: (value: GenreTreeSizeMetric) => void;\n}) {\n return (\n <SegmentedControl\n aria-label=\"Size nodes by\"\n value={value}\n onChange={onChange}\n options={[\n { value: \"songs\", label: <MdMusicNote size={18} aria-hidden />, \"aria-label\": \"Size by songs\", title: \"Node size by song count\" },\n { value: \"views\", label: <MdVisibility size={18} aria-hidden />, \"aria-label\": \"Size by views\", title: \"Node size by song views\" },\n ]}\n />\n );\n}\n","export * from \"./GenreTreeView\";\nexport type { ParentChange } from \"./ChangeParentPopup\";\nexport type { GenreDetailSlots } from \"./GenreDetailExtras\";\nexport type { GenrePlaylistTracksSlots } from \"./GenrePlaylistTracks\";\nexport type { InfoPanelCurationProps } from \"./playlist-tree/infoPanelCurationProps\";\nexport { default as GenrePlaylistTreePerRoot } from \"./playlist-tree/TreePerRoot\";\nexport type { GenrePlaylistTreePerRootProps } from \"./playlist-tree/TreePerRoot\";\nexport { default as GenreSearch } from \"./GenreSearch\";\nexport type { GenreSearchProps, GenreSearchExtraResults } from \"./GenreSearch\";\nexport {\n GenreTreeSkeleton,\n GenreTreeWheelSkeleton,\n GenreTreeViewSkeleton,\n GenreTreeOutlineSkeleton,\n} from \"@behindthemusictree/genre-tree-view\";\nexport type { GenreTreeAction, GenreTreeViewport, GenreTreeViewSkeletonProps } from \"@behindthemusictree/genre-tree-view\";\nexport { default as TrackListSidebar } from \"./track-list-sidebar/TrackListSidebar\";\nexport type { TrackListSidebarProps } from \"./track-list-sidebar/TrackListSidebar\";\nexport { default as PlayerPanel } from \"./PlayerPanel\";\nexport type { PlayerPanelProps } from \"./PlayerPanel\";\nexport { default as TrackItem } from \"./track-list-sidebar/TrackItem\";\nexport type { TrackItemProps } from \"./track-list-sidebar/TrackItem\";\nexport { default as TrackPositionPlayPause } from \"./TrackPositionPlayPause\";\nexport type { TrackPositionPlayPauseProps } from \"./TrackPositionPlayPause\";\nexport { default as Rating } from \"./Rating\";\nexport type { RatingProps } from \"./Rating\";\nexport { default as PanelEdgeTab } from \"./PanelEdgeTab\";\nexport type { PanelEdgeTabProps, PanelEdgeTabAction } from \"./PanelEdgeTab\";\n","\"use client\";\n\nimport { ReactNode, useEffect, useRef } from \"react\";\n\nimport TrackItem from \"./TrackItem\";\nimport { useTrackList } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { useTrackListSidebarVisibility } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { TrackListOriginType } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { TrackBase } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { RenderArtistLink } from \"@behindthemusictree/app-kit/genre-tree\";\nimport { useTrackNoun } from \"@behindthemusictree/app-kit/track-noun\";\n\nexport interface TrackListSidebarProps<T extends TrackBase> {\n className?: string;\n renderDuration?: (track: T) => ReactNode;\n renderActions?: (track: T) => ReactNode;\n layout?: \"fixed\" | \"inline\";\n /** When set, artist names render through it (e.g. a router `Link`). */\n renderArtistLink?: RenderArtistLink;\n}\n\nexport default function TrackListSidebar<T extends TrackBase>({\n className = \"\",\n renderDuration,\n renderActions,\n layout = \"fixed\",\n renderArtistLink,\n}: TrackListSidebarProps<T>) {\n const { trackList, loadMore } = useTrackList<T>();\n const trackNoun = useTrackNoun();\n const loadMoreSentinelRef = useRef<HTMLLIElement>(null);\n const hasMore = !!trackList && trackList.nextPage !== null;\n\n useEffect(() => {\n const sentinel = loadMoreSentinelRef.current;\n if (!hasMore || !sentinel) return;\n const observer = new IntersectionObserver((entries) => {\n if (entries.some((entry) => entry.isIntersecting)) {\n loadMore().catch((error) => console.error(\"Failed to load more tracks:\", error));\n }\n });\n observer.observe(sentinel);\n return () => observer.disconnect();\n }, [hasMore, loadMore]);\n const { hideTrackListSidebar } = useTrackListSidebarVisibility();\n\n const positionClasses =\n layout === \"inline\"\n ? \"relative w-full flex h-full flex-col\"\n : \"fixed right-0 w-[50rem]\";\n\n return trackList ? (\n <div\n data-theme=\"dark\"\n className={`track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${className}`}\n style={layout === \"fixed\" ? { bottom: \"79px\" /* Match player height exactly */ } : undefined}\n >\n <div className=\"header flex h-16 px-4 py-2 text-[var(--color-text)]\">\n <div className=\"origin flex text-xl \">\n <div className=\"from h-auto flex flex-col justify-center items-center mr-2\">From</div>\n <div className=\"name-container flex flex-col justify-center items-center text-[var(--color-text)] font-bold pr-2 max-w-[27.5rem]\">\n <div className=\"name max-w-full truncate\">{trackList.origin.label}</div>\n </div>\n </div>\n <div className=\"info flex flex-col justify-center items-center pt-1 mr-2 text-[var(--color-text-muted)]\">\n {trackList.origin.type === TrackListOriginType.GENRE_PLAYLIST\n ? \"• Genre playlist • \"\n : trackList.origin.type === TrackListOriginType.ARTIST\n ? \"• Artist • \"\n : `• ${trackNoun.singular} playlist • `}\n {`${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular} •`}\n </div>\n <button\n type=\"button\"\n aria-label=\"Close track list\"\n className=\"flex-grow flex flex-col items-end justify-center h-full p-3 cursor-pointer text-inherit\"\n onClick={hideTrackListSidebar}\n >\n ✕\n </button>\n </div>\n <ul\n className={\n layout === \"inline\"\n ? \"track-list flex-1 min-h-0 overflow-auto list-none p-0 m-0\"\n : \"track-list overflow-auto max-h-[calc(100vh-63.5px-79px-56px-3.5px)] list-none p-0 m-0\"\n }\n >\n {trackList.tracks.map((track, index) => (\n <li key={track.uuid}>\n <TrackItem\n track={track}\n position={index + 1}\n renderDuration={renderDuration}\n renderActions={renderActions}\n renderArtistLink={renderArtistLink}\n />\n </li>\n ))}\n {hasMore && <li ref={loadMoreSentinelRef} aria-hidden=\"true\" className=\"h-px\" />}\n </ul>\n </div>\n ) : null;\n}\n","\"use client\";\n\nimport { useState } from \"react\";\nimport { X } from \"lucide-react\";\nimport { twMerge } from \"tailwind-merge\";\nimport {\n usePlayer,\n PlayerVideoSurface,\n} from \"@behindthemusictree/app-kit/player\";\nimport {\n useTrackListSidebarVisibility,\n type RenderArtistLink,\n} from \"@behindthemusictree/app-kit/genre-tree\";\nimport PanelEdgeTab from \"./PanelEdgeTab\";\nimport TrackListSidebar from \"./track-list-sidebar/TrackListSidebar\";\n\nexport interface PlayerPanelProps {\n className?: string;\n /** Hidden via CSS, never unmounted, so the video keeps playing while the panel is closed. */\n isVisible?: boolean;\n onClose: () => void;\n /** Controlled extended state; uncontrolled when omitted. */\n expanded?: boolean;\n onExpandedChange?: (expanded: boolean) => void;\n /** Width once extended, e.g. to stop short of a left-hand detail panel. */\n extendedWidth?: string;\n renderArtistLink?: RenderArtistLink;\n}\n\nconst PANEL_ID = \"player-panel\";\n\nconst iconButtonClassName =\n \"flex h-6 w-6 shrink-0 items-center justify-center p-0 rounded-md hover:bg-white/10\";\n\n// Spans the center and right columns.\nconst DEFAULT_EXTENDED_WIDTH =\n \"calc(100vw - var(--layout-center-left) - var(--layout-gutter))\";\n\nexport default function PlayerPanel({\n className,\n isVisible = true,\n onClose,\n expanded,\n onExpandedChange,\n extendedWidth = DEFAULT_EXTENDED_WIDTH,\n renderArtistLink,\n}: PlayerPanelProps) {\n const { playerTrackObject } = usePlayer();\n const { toggleTrackListSidebar, isTrackListSidebarVisible } =\n useTrackListSidebarVisibility();\n const [uncontrolledExtended, setUncontrolledExtended] = useState(false);\n const isExtendRequested = expanded ?? uncontrolledExtended;\n const setIsExtendRequested = (next: boolean) => {\n setUncontrolledExtended(next);\n onExpandedChange?.(next);\n };\n const isExtended = isExtendRequested && isTrackListSidebarVisible;\n\n const tabEdge = !isTrackListSidebarVisible\n ? null\n : isExtended\n ? {\n label: \"Shrink song list panel\",\n onClick: () => setIsExtendRequested(false),\n }\n : { label: \"Hide song list\", onClick: toggleTrackListSidebar };\n const tabCenter = !isTrackListSidebarVisible\n ? { label: \"Show song list\", onClick: toggleTrackListSidebar }\n : isExtended\n ? null\n : {\n label: \"Extend song list panel\",\n onClick: () => setIsExtendRequested(true),\n };\n\n return (\n <div\n id={PANEL_ID}\n className={twMerge(\n \"fixed right-[var(--layout-gutter)] top-[var(--layout-band-top)] z-40 max-h-[calc(100vh-var(--layout-band-top)-3.5rem)]\",\n !isExtended && \"w-[var(--layout-right-w)]\",\n isTrackListSidebarVisible &&\n \"h-[calc(100vh-var(--layout-band-top)-3.5rem)]\",\n !isVisible && \"hidden\",\n className,\n )}\n style={isExtended ? { width: extendedWidth } : undefined}\n >\n <PanelEdgeTab\n side=\"right\"\n edge={tabEdge}\n center={tabCenter}\n controls={PANEL_ID}\n className=\"absolute right-full top-1/2 -translate-y-1/2\"\n />\n <div\n className={twMerge(\n \"flex h-full max-h-[inherit] overflow-hidden rounded-[var(--radius-panel,1rem)] bg-zinc-950 text-zinc-100 shadow-[0_1px_2px_rgba(0,0,0,0.5),0_2px_6px_2px_rgba(0,0,0,0.3)]\",\n isExtended ? \"flex-row\" : \"flex-col\",\n )}\n >\n <div className=\"flex w-[var(--layout-right-w)] shrink-0 flex-col\">\n <div className=\"flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2\">\n <span className=\"min-w-0 truncate text-sm font-semibold\">\n {playerTrackObject?.track?.title}\n </span>\n <div className=\"flex shrink-0 items-center gap-1\">\n <button\n type=\"button\"\n onClick={onClose}\n className={iconButtonClassName}\n aria-label=\"Close player\"\n >\n <X size={16} />\n </button>\n </div>\n </div>\n <div className=\"relative w-full shrink-0\">\n <PlayerVideoSurface className=\"aspect-video w-full bg-black\" />\n {playerTrackObject?.loadError && (\n <span className=\"absolute bottom-2 left-2 right-2 text-xs text-red-400\">\n {playerTrackObject.loadError}\n </span>\n )}\n </div>\n </div>\n {isTrackListSidebarVisible && (\n <TrackListSidebar\n layout=\"inline\"\n className={twMerge(\n \"min-h-0 min-w-0 flex-1\",\n isExtended && \"border-l border-zinc-800\",\n )}\n renderArtistLink={renderArtistLink}\n />\n )}\n </div>\n </div>\n );\n}\n","\"use client\";\n\nimport { v4 as uuidv4 } from \"uuid\";\nimport { AiFillStar, AiOutlineStar } from \"react-icons/ai\";\n\nimport { FORM_RATING_NULL_VALUE } from \"@behindthemusictree/app-kit/genre-tree\";\n\nexport interface RatingProps {\n rating: number | null | undefined;\n handleChange: (event: React.ChangeEvent<HTMLInputElement>) => void;\n}\n\nexport default function Rating({ rating, handleChange }: RatingProps) {\n const instanceId = uuidv4();\n return (\n <div className=\"flex items-center\">\n {[...Array(6)].map((_, i) => (\n <div key={i} className={`relative ${i === 0 ? \"mr-4\" : \"mr-1\"}`}>\n <input\n id={`star-${instanceId}-${i}`}\n className=\"w-6 h-6 opacity-0\"\n type=\"radio\"\n name=\"rating\"\n value={i === 0 ? FORM_RATING_NULL_VALUE : i}\n checked={rating === (i === 0 ? null : i)}\n onChange={handleChange}\n />\n <label htmlFor={`star-${instanceId}-${i}`} className=\"cursor-pointer\">\n {i === 0 ? (\n <div className=\"flex\">\n <AiOutlineStar\n className={`w-7 h-7 absolute top-0 left-0 ${\n rating === FORM_RATING_NULL_VALUE ? \"text-[var(--color-accent-rating)]\" : \"text-[var(--color-text-muted)]\"\n }`}\n />\n <span className=\"flex-grow w-8\"></span>{\" \"}\n </div>\n ) : (\n <AiFillStar\n className={`w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? \"text-[var(--color-accent-rating)]\" : \"text-[var(--color-text-muted)]\"}`}\n />\n )}\n </label>\n </div>\n ))}\n </div>\n );\n}\n"],"mappings":"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","names":["useCallback","useEffect","useMemo","useRef","useState","Plus","SegmentedControl","useArtist","usePlayer","usePopup","Skeleton","useRef","Fragment","jsx","jsxs","jsx","jsx","jsxs","useRef","artist","useTrackNoun","Fragment","jsx","jsxs","Skeleton","useCallback","useMemo","usePlayer","usePopup","ErrorCode","useTrackList","capitalize","useTrackNoun","jsx","usePlayer","usePopup","useMemo","useCallback","ErrorCode","useCallback","useMemo","usePlayer","usePopup","ErrorCode","capitalize","useTrackNoun","jsx","usePlayer","usePopup","useMemo","useCallback","ErrorCode","useCallback","useMemo","usePlayer","capitalize","useTrackNoun","jsx","usePlayer","useMemo","useCallback","useEffect","useRef","useState","jsx","jsxs","jsx","jsxs","jsx","jsxs","jsx","jsxs","useEffect","useMemo","jsx","jsxs","useMemo","useEffect","Skeleton","jsx","jsx","jsx","jsxs","jsx","jsxs","PanelGroup","PanelSection","jsx","jsxs","useState","Play","PanelSection","Skeleton","usePlayer","SongDetailedSchema","libraryEndpoints","songUnplayableReason","useTrackList","PaginatedResponseSchema","useFetchWrapper","usePlayer","useTrackList","FaPlay","FaPause","usePlayer","PlayStates","RingLoader","jsx","jsxs","isSong","songUnplayableReason","ArtistNames","jsx","jsxs","usePlayer","useTrackList","isSong","songUnplayableReason","Fragment","jsx","jsxs","SongPageSchema","PaginatedResponseSchema","SongDetailedSchema","useState","useFetchWrapper","useTrackList","usePlayer","libraryEndpoints","songUnplayableReason","PanelSection","Skeleton","Play","Fragment","jsx","jsxs","Skeleton","jsx","Fragment","jsx","jsxs","usePlayer","useState","useArtist","useMemo","useEffect","useRef","usePopup","useCallback","SegmentedControl","Plus","GenreTreeWheelSkeleton","GenreTreeViewSkeleton","useEffect","useRef","useTrackList","TrackListOriginType","useTrackNoun","jsx","jsxs","useRef","useEffect","useState","X","twMerge","usePlayer","useTrackListSidebarVisibility","jsx","jsxs","usePlayer","useTrackListSidebarVisibility","useState","twMerge","X","jsx","jsxs"]}
|