@behindthemusictree/app-kit-ui 34.1.0 → 35.0.0

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.
@@ -5,14 +5,12 @@ import {
5
5
  } from "./chunk-DKQQLLMY.js";
6
6
 
7
7
  // src/genre-tree/GenreTreeView.tsx
8
- import { useCallback as useCallback6, useDeferredValue as useDeferredValue2, useEffect as useEffect5, useMemo as useMemo7, useRef as useRef3, useState as useState9 } from "react";
8
+ import { useCallback as useCallback6, useDeferredValue as useDeferredValue2, useEffect as useEffect5, useMemo as useMemo8, useRef as useRef3, useState as useState9 } from "react";
9
9
  import { useIsMutating } from "@tanstack/react-query";
10
10
  import { Plus as Plus2 } from "lucide-react";
11
- import { Maximize2, Minimize2 } from "lucide-react";
12
- import { IconTextButton, IconButton, Button, SegmentedControl as SegmentedControl2, DetailPanel, useObscuredInset } from "@behindthemusictree/ui";
11
+ import { Maximize2 as Maximize22, Minimize2 as Minimize22 } from "lucide-react";
12
+ import { IconTextButton, IconButton as IconButton2, Button, SegmentedControl as SegmentedControl2, DetailPanel as DetailPanel2, useObscuredInset } from "@behindthemusictree/ui";
13
13
  import {
14
- GenreNodeAncestors,
15
- GenreNodeFamily,
16
14
  GenreTreeWheelSkeleton,
17
15
  GenreTreeViewSkeleton,
18
16
  useGenreNodeRelations
@@ -683,6 +681,7 @@ function GenreTreeSearch({
683
681
  artistSearch,
684
682
  songSearch,
685
683
  SearchGroups,
684
+ onSelectGenre,
686
685
  extraGroups = [],
687
686
  onQueryChange,
688
687
  onFocus,
@@ -708,9 +707,30 @@ function GenreTreeSearch({
708
707
  extraGroups: [...extraGroups, ...hostGroups, artistGroup, songGroup].filter((group) => group !== void 0)
709
708
  }
710
709
  );
711
- return SearchGroups ? /* @__PURE__ */ jsx9(SearchGroups, { query: deferredQuery, focused, children: render }) : render([]);
710
+ return SearchGroups ? /* @__PURE__ */ jsx9(SearchGroups, { query: deferredQuery, focused, onSelectGenre, children: render }) : render([]);
712
711
  }
713
712
 
713
+ // src/genre-tree/GenreDetailPanel.tsx
714
+ import { useMemo as useMemo6 } from "react";
715
+ import {
716
+ ACCENT_TEXT_COLOR,
717
+ GenreNodeAncestors,
718
+ GenreNodeFamily,
719
+ getGenreTreeColor as getGenreTreeColor2,
720
+ TEXT_COLOR,
721
+ tintSurface
722
+ } from "@behindthemusictree/genre-tree-view";
723
+ import { DetailPanel, IconButton, Skeleton as Skeleton3 } from "@behindthemusictree/ui";
724
+ import { Maximize2, Minimize2 } from "lucide-react";
725
+ import {
726
+ useFetchGenreDetail,
727
+ genreMusicCount as genreMusicCount2,
728
+ GenrePlaylistSummarySchema,
729
+ GenreCatalogueCountsSchema as GenreCatalogueCountsSchema3,
730
+ CriteriaMinimumSchema,
731
+ GenreUuidSchema
732
+ } from "@behindthemusictree/app-kit/genre-tree";
733
+
714
734
  // src/genre-tree/GenreInfoPanelBody.tsx
715
735
  import {
716
736
  genreMusicCount,
@@ -1136,6 +1156,7 @@ var nextCursor = (next) => {
1136
1156
  function GenrePlaylistTracks({
1137
1157
  genreUuid,
1138
1158
  genrePlaylist,
1159
+ genreName: selectedGenreName,
1139
1160
  selection,
1140
1161
  scope,
1141
1162
  getBackendBaseUrl,
@@ -1151,13 +1172,17 @@ function GenrePlaylistTracks({
1151
1172
  const { playerTrackObject, playState, handlePlayPauseAction } = usePlayer4();
1152
1173
  const { showPopup } = usePopup4();
1153
1174
  const trackNoun = useTrackNoun();
1154
- const { uuid } = genrePlaylist;
1175
+ const uuid = genrePlaylist?.uuid;
1176
+ const genreName = selectedGenreName ?? genrePlaylist?.name ?? "Genre";
1155
1177
  const { fetch } = useFetchWrapper(getBackendBaseUrl);
1156
1178
  const playback = usePlaybackContext();
1157
1179
  const { data, isPending, isError, refetch, hasNextPage, isFetchingNextPage, fetchNextPage } = useInfiniteQuery({
1158
- queryKey: [...scope === "me" ? genrePlaylistQueryKeys.me.tracks(uuid) : artist ? [...libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid, selection), "artist", artist.uuid] : libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid, selection), ...playback.key ? [playback.key] : []],
1180
+ queryKey: [...scope === "me" ? uuid ? genrePlaylistQueryKeys.me.tracks(uuid) : ["missing-personal-genre-playlist", genreUuid] : artist ? [...libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid, selection), "artist", artist.uuid] : libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid, selection), ...playback.key ? [playback.key] : []],
1159
1181
  queryFn: async ({ pageParam }) => {
1160
- if (scope === "me") return fetchGenrePlaylistTracksPage(uuid, scope, Number(pageParam));
1182
+ if (scope === "me") {
1183
+ if (!uuid) throw new Error("Personal genre has no playlist");
1184
+ return fetchGenrePlaylistTracksPage(uuid, scope, Number(pageParam));
1185
+ }
1161
1186
  const response = await fetch(
1162
1187
  libraryEndpoints.reference.songs.list(),
1163
1188
  true,
@@ -1170,7 +1195,7 @@ function GenrePlaylistTracks({
1170
1195
  },
1171
1196
  initialPageParam: scope === "me" ? 1 : "",
1172
1197
  getNextPageParam: (lastPage) => lastPage.nextPage ?? void 0,
1173
- enabled: playback.ready && (scope !== "me" || sessionRestored && !!session?.accessToken)
1198
+ enabled: playback.ready && (scope !== "me" || uuid !== void 0 && sessionRestored && !!session?.accessToken)
1174
1199
  });
1175
1200
  const [sentinel, setSentinel] = useState6(null);
1176
1201
  useEffect3(() => {
@@ -1192,7 +1217,7 @@ function GenrePlaylistTracks({
1192
1217
  ordering: "title",
1193
1218
  genreUuid,
1194
1219
  ...selection && { selection }
1195
- }) : scope === "reference" ? playNewTrackListFromGenreCatalogue({ uuid: genreUuid, name: genrePlaylist.name }, scope, { startSongUuid, ...selection && { selection } }) : playNewTrackListFromGenrePlaylist(genrePlaylist, scope, { startSongUuid });
1220
+ }) : scope === "reference" ? playNewTrackListFromGenreCatalogue({ uuid: genreUuid, name: genreName }, scope, { startSongUuid, ...selection && { selection } }) : genrePlaylist ? playNewTrackListFromGenrePlaylist(genrePlaylist, scope, { startSongUuid }) : Promise.reject(new Error("Personal genre has no playlist"));
1196
1221
  started.catch((error) => {
1197
1222
  console.error("Failed to play genre playlist:", error);
1198
1223
  showPopup(/* @__PURE__ */ jsx17(InternalErrorPopup, { errorCode: ErrorCode3.CLIENT_UNKNOWN }));
@@ -1200,13 +1225,13 @@ function GenrePlaylistTracks({
1200
1225
  };
1201
1226
  return /* @__PURE__ */ jsx17(PanelSection, { title: `${capitalize(trackNoun.plural)}${total !== void 0 ? ` (${total})` : ""}`, children: /* @__PURE__ */ jsxs12("div", { children: [
1202
1227
  renderTracksToolbar?.(),
1203
- isPending ? /* @__PURE__ */ jsx17(Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ jsxs12("p", { role: "alert", children: [
1228
+ scope === "me" && !genrePlaylist ? /* @__PURE__ */ jsx17("p", { children: "\u2014" }) : isPending ? /* @__PURE__ */ jsx17(Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ jsxs12("p", { role: "alert", children: [
1204
1229
  "Couldn't load ",
1205
1230
  trackNoun.plural,
1206
1231
  ".",
1207
1232
  " ",
1208
1233
  /* @__PURE__ */ jsx17("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
1209
- ] }) : tracks.length === 0 ? /* @__PURE__ */ jsx17("p", { children: "\u2014" }) : /* @__PURE__ */ jsxs12("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}`, children: [
1234
+ ] }) : tracks.length === 0 ? /* @__PURE__ */ jsx17("p", { children: "\u2014" }) : /* @__PURE__ */ jsxs12("ul", { className: "mt-1 max-h-64 overflow-y-auto text-[0.8125rem] leading-[1.45] [container-type:inline-size]", "aria-label": `${genreName} ${trackNoun.plural}`, children: [
1210
1235
  tracks.map((track, index) => {
1211
1236
  const label = track.artists?.length ? `${track.title} \u2014 ${track.artists.map((artist2) => artist2.name).join(", ")}` : track.title;
1212
1237
  const isCurrent = playerTrackObject?.track.id === track.uuid;
@@ -1273,6 +1298,7 @@ import { Fragment as Fragment8, jsx as jsx18, jsxs as jsxs13 } from "react/jsx-r
1273
1298
  function GenreDetailExtras({
1274
1299
  genreUuid,
1275
1300
  genrePlaylist,
1301
+ genreName,
1276
1302
  selection,
1277
1303
  wikidataId,
1278
1304
  scope,
@@ -1340,6 +1366,7 @@ function GenreDetailExtras({
1340
1366
  {
1341
1367
  genreUuid,
1342
1368
  genrePlaylist,
1369
+ genreName,
1343
1370
  selection,
1344
1371
  scope,
1345
1372
  getBackendBaseUrl,
@@ -1381,6 +1408,7 @@ function GenreInfoPanelBody({
1381
1408
  genreUuid,
1382
1409
  genrePlaylist,
1383
1410
  catalogue,
1411
+ countsContext,
1384
1412
  wikidataId,
1385
1413
  scope,
1386
1414
  getBackendBaseUrl,
@@ -1389,7 +1417,7 @@ function GenreInfoPanelBody({
1389
1417
  extras
1390
1418
  }) {
1391
1419
  const noun = useTrackNoun3();
1392
- const summary = scope === "reference" ? GenreCatalogueCountsSchema2.parse(genrePlaylist) : void 0;
1420
+ const summary = scope === "reference" ? GenreCatalogueCountsSchema2.parse(genrePlaylist ?? catalogue) : void 0;
1393
1421
  const selection = summary ? defaultGenreMusicSelection2(summary) : void 0;
1394
1422
  const describeCounts = (counts) => {
1395
1423
  const scopes = counts.genreAndSubgenresTracksCount;
@@ -1397,17 +1425,19 @@ function GenreInfoPanelBody({
1397
1425
  };
1398
1426
  return /* @__PURE__ */ jsxs14(Fragment9, { children: [
1399
1427
  toolbar,
1400
- /* @__PURE__ */ jsx19(GenreNodeActionBand, { ...band, genrePlaylist, playback }),
1428
+ genrePlaylist ? /* @__PURE__ */ jsx19(GenreNodeActionBand, { ...band, genrePlaylist, playback }) : /* @__PURE__ */ jsx19(GenreActionBand, { ...band }),
1401
1429
  summary && catalogue && /* @__PURE__ */ jsxs14("div", { className: "px-3.5 py-3 text-sm text-[var(--color-text)]", children: [
1402
1430
  /* @__PURE__ */ jsxs14("p", { children: [
1403
1431
  "Catalogue ",
1404
1432
  noun.plural,
1433
+ countsContext ? ` (${countsContext})` : "",
1405
1434
  ": ",
1406
1435
  describeCounts(catalogue)
1407
1436
  ] }),
1408
- /* @__PURE__ */ jsxs14("p", { children: [
1437
+ genrePlaylist && /* @__PURE__ */ jsxs14("p", { children: [
1409
1438
  "Playlist ",
1410
1439
  noun.plural,
1440
+ countsContext ? ` (${countsContext})` : "",
1411
1441
  ": ",
1412
1442
  describeCounts(summary)
1413
1443
  ] })
@@ -1420,6 +1450,7 @@ function GenreInfoPanelBody({
1420
1450
  family,
1421
1451
  genreUuid,
1422
1452
  genrePlaylist,
1453
+ genreName: band.node.name,
1423
1454
  selection,
1424
1455
  wikidataId,
1425
1456
  scope,
@@ -1429,13 +1460,163 @@ function GenreInfoPanelBody({
1429
1460
  ] });
1430
1461
  }
1431
1462
 
1463
+ // src/genre-tree/GenreDetailPanel.tsx
1464
+ import { Fragment as Fragment10, jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
1465
+ var RELATIVE_ITEM_COUNT = 1;
1466
+ function toNode(genre, parentId) {
1467
+ return {
1468
+ id: genre.uuid,
1469
+ parentId,
1470
+ name: genre.name,
1471
+ itemCount: RELATIVE_ITEM_COUNT,
1472
+ side: genre.side ?? void 0,
1473
+ wikidataId: genre.wikidataId
1474
+ };
1475
+ }
1476
+ function useGenreDetailContent({
1477
+ genreUuid,
1478
+ scope,
1479
+ getBackendBaseUrl,
1480
+ onSelectGenre,
1481
+ extras,
1482
+ band,
1483
+ playback: hostPlayback,
1484
+ trailing,
1485
+ genrePlaylist: hostPlaylist,
1486
+ expanded
1487
+ }) {
1488
+ const { data: detail, isPending, isError } = useFetchGenreDetail(genreUuid, scope, getBackendBaseUrl);
1489
+ const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists: [], artist: extras?.artist });
1490
+ const playback = hostPlayback ?? defaultPlayback;
1491
+ const view = useMemo6(() => {
1492
+ if (!detail) return null;
1493
+ const rootColor = getGenreTreeColor2(detail.root.wikidataId);
1494
+ const relative = (genre, parentId) => ({
1495
+ node: toNode(genre, parentId),
1496
+ fill: tintSurface(rootColor),
1497
+ textColor: TEXT_COLOR
1498
+ });
1499
+ const ancestors2 = [...detail.ascendants].sort((a, b) => b.degree - a.degree);
1500
+ return {
1501
+ node: {
1502
+ id: detail.uuid,
1503
+ parentId: detail.parent?.uuid ?? null,
1504
+ name: detail.name,
1505
+ itemCount: scope === "reference" ? genreMusicCount2(detail.criteriaPlaylist ? GenrePlaylistSummarySchema.parse(detail.criteriaPlaylist) : GenreCatalogueCountsSchema3.parse(detail.catalogue)) : detail.tracksCount,
1506
+ wikidataId: detail.wikidataId
1507
+ },
1508
+ fill: rootColor,
1509
+ parentNode: detail.parent ? relative(detail.parent, null) : null,
1510
+ childNodes: detail.children.map((child) => relative(child, detail.uuid)),
1511
+ ancestorNodes: ancestors2.map(
1512
+ ({ ascendant }, index) => relative(ascendant, index === 0 ? null : ancestors2[index - 1].ascendant.uuid)
1513
+ )
1514
+ };
1515
+ }, [detail, scope]);
1516
+ if (isError) return {
1517
+ title: "Genre unavailable",
1518
+ content: /* @__PURE__ */ jsx20("p", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." })
1519
+ };
1520
+ if (isPending || !detail || !view) return {
1521
+ title: "Loading genre\u2026",
1522
+ content: /* @__PURE__ */ jsxs15("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
1523
+ /* @__PURE__ */ jsx20(Skeleton3, { className: "h-6 w-2/3" }),
1524
+ /* @__PURE__ */ jsx20(Skeleton3, { className: "h-4 w-full" }),
1525
+ /* @__PURE__ */ jsx20(Skeleton3, { className: "h-4 w-3/4" })
1526
+ ] })
1527
+ };
1528
+ const selectGenre = (nodeId) => onSelectGenre(GenreUuidSchema.parse(nodeId));
1529
+ const family = /* @__PURE__ */ jsx20(
1530
+ GenreNodeFamily,
1531
+ {
1532
+ node: view.node,
1533
+ fill: view.fill,
1534
+ textColor: ACCENT_TEXT_COLOR,
1535
+ parentNode: view.parentNode,
1536
+ childNodes: view.childNodes,
1537
+ onSelectNode: selectGenre
1538
+ }
1539
+ );
1540
+ const ancestors = /* @__PURE__ */ jsx20(GenreNodeAncestors, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
1541
+ return {
1542
+ title: detail.name,
1543
+ headerAccent: { background: view.fill, color: ACCENT_TEXT_COLOR },
1544
+ aside: expanded ? ancestors : void 0,
1545
+ content: /* @__PURE__ */ jsxs15(Fragment10, { children: [
1546
+ /* @__PURE__ */ jsx20(
1547
+ GenreInfoPanelBody,
1548
+ {
1549
+ family: /* @__PURE__ */ jsxs15(Fragment10, { children: [
1550
+ family,
1551
+ !expanded && ancestors
1552
+ ] }),
1553
+ genreUuid: detail.uuid,
1554
+ genrePlaylist: hostPlaylist ?? (detail.criteriaPlaylist === null ? null : scope === "reference" ? { ...GenrePlaylistSummarySchema.parse(detail.criteriaPlaylist), criteria: CriteriaMinimumSchema.parse(detail) } : { ...detail.criteriaPlaylist, criteria: CriteriaMinimumSchema.parse(detail), tracksCount: detail.tracksCount }),
1555
+ catalogue: detail.catalogue,
1556
+ countsContext: !hostPlaylist && extras?.artist ? "all artists" : void 0,
1557
+ wikidataId: detail.wikidataId,
1558
+ scope,
1559
+ getBackendBaseUrl,
1560
+ playback,
1561
+ band: { node: view.node, ...band, shareTarget: band?.shareTarget?.kind === "genre" ? { ...band.shareTarget, name: detail.name } : band?.shareTarget },
1562
+ extras
1563
+ }
1564
+ ),
1565
+ trailing
1566
+ ] })
1567
+ };
1568
+ }
1569
+ function GenreDetailPanel({
1570
+ genreUuid,
1571
+ scope,
1572
+ getBackendBaseUrl,
1573
+ onClose,
1574
+ onSelectGenre,
1575
+ onZoomIn,
1576
+ shareUrl,
1577
+ shareTarget,
1578
+ collapsed,
1579
+ expanded,
1580
+ onExpandedChange,
1581
+ edgeTab,
1582
+ extras,
1583
+ headerClassName
1584
+ }) {
1585
+ const panel = useGenreDetailContent({
1586
+ genreUuid,
1587
+ scope,
1588
+ getBackendBaseUrl,
1589
+ onSelectGenre,
1590
+ extras,
1591
+ expanded,
1592
+ band: { onZoomIn, shareUrl, shareTarget: shareUrl ? shareTarget : void 0 }
1593
+ });
1594
+ return /* @__PURE__ */ jsx20(
1595
+ DetailPanel,
1596
+ {
1597
+ side: "left",
1598
+ title: panel.title,
1599
+ headerAccent: panel.headerAccent,
1600
+ onClose,
1601
+ collapsed,
1602
+ expanded,
1603
+ onExpandedChange,
1604
+ edgeTab,
1605
+ aside: panel.aside,
1606
+ headerClassName,
1607
+ headerActions: onExpandedChange ? /* @__PURE__ */ jsx20(IconButton, { "aria-label": expanded ? "Shrink panel" : "Extend panel", onClick: () => onExpandedChange(!expanded), children: expanded ? /* @__PURE__ */ jsx20(Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ jsx20(Maximize2, { "aria-hidden": "true", className: "h-4 w-4" }) }) : void 0,
1608
+ children: panel.content
1609
+ }
1610
+ );
1611
+ }
1612
+
1432
1613
  // src/genre-tree/ChangeParentPopup.tsx
1433
- import { useEffect as useEffect4, useMemo as useMemo6 } from "react";
1614
+ import { useEffect as useEffect4, useMemo as useMemo7 } from "react";
1434
1615
  import {
1435
1616
  getReparentCandidates,
1436
1617
  useUpdateGenre
1437
1618
  } from "@behindthemusictree/app-kit/genre-tree";
1438
- import { jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
1619
+ import { jsx as jsx21, jsxs as jsxs16 } from "react/jsx-runtime";
1439
1620
  function ChangeParentPopup({
1440
1621
  genrePlaylist,
1441
1622
  genrePlaylists,
@@ -1445,7 +1626,7 @@ function ChangeParentPopup({
1445
1626
  onParentChanged
1446
1627
  }) {
1447
1628
  const { mutate, isPending, formErrors } = useUpdateGenre(scope, getBackendBaseUrl);
1448
- const candidates = useMemo6(
1629
+ const candidates = useMemo7(
1449
1630
  () => getReparentCandidates(genrePlaylists, genrePlaylist.uuid),
1450
1631
  [genrePlaylists, genrePlaylist.uuid]
1451
1632
  );
@@ -1470,12 +1651,12 @@ function ChangeParentPopup({
1470
1651
  window.addEventListener("keydown", handleKeyDown);
1471
1652
  return () => window.removeEventListener("keydown", handleKeyDown);
1472
1653
  }, [onClose]);
1473
- return /* @__PURE__ */ jsxs15(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
1474
- /* @__PURE__ */ jsxs15("p", { className: "mb-3 text-sm text-[var(--color-text-muted)]", children: [
1654
+ return /* @__PURE__ */ jsxs16(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
1655
+ /* @__PURE__ */ jsxs16("p", { className: "mb-3 text-sm text-[var(--color-text-muted)]", children: [
1475
1656
  "Current parent: ",
1476
1657
  genrePlaylist.parent?.name ?? "none"
1477
1658
  ] }),
1478
- /* @__PURE__ */ jsx20("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ jsx20(
1659
+ /* @__PURE__ */ jsx21("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ jsx21(
1479
1660
  GenreSearch,
1480
1661
  {
1481
1662
  genrePlaylists: candidates,
@@ -1483,12 +1664,12 @@ function ChangeParentPopup({
1483
1664
  placeholder: "Search a new parent\u2026"
1484
1665
  }
1485
1666
  ) }),
1486
- formErrors.length > 0 && /* @__PURE__ */ jsx20("p", { role: "alert", className: "mt-3 text-sm text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
1667
+ formErrors.length > 0 && /* @__PURE__ */ jsx21("p", { role: "alert", className: "mt-3 text-sm text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
1487
1668
  ] });
1488
1669
  }
1489
1670
 
1490
1671
  // src/genre-tree/artist/useArtistPanel.tsx
1491
- import { Skeleton as Skeleton4 } from "@behindthemusictree/ui";
1672
+ import { Skeleton as Skeleton5 } from "@behindthemusictree/ui";
1492
1673
  import { useArtist } from "@behindthemusictree/app-kit/genre-tree";
1493
1674
 
1494
1675
  // src/genre-tree/artist/ArtistHeader.tsx
@@ -1507,7 +1688,7 @@ import {
1507
1688
  SiYoutube
1508
1689
  } from "react-icons/si";
1509
1690
  import { Chip } from "@behindthemusictree/ui";
1510
- import { jsx as jsx21 } from "react/jsx-runtime";
1691
+ import { jsx as jsx22 } from "react/jsx-runtime";
1511
1692
  var LINK_KINDS = {
1512
1693
  official: { label: "Official website", Icon: Globe },
1513
1694
  wikipedia: { label: "Wikipedia", Icon: SiWikipedia2 },
@@ -1544,7 +1725,7 @@ function artistLinks(artist) {
1544
1725
  return links;
1545
1726
  }
1546
1727
  function ArtistLinks({ links }) {
1547
- return /* @__PURE__ */ jsx21("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ jsx21("li", { children: /* @__PURE__ */ jsx21(Chip, { href: url, icon: /* @__PURE__ */ jsx21(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
1728
+ return /* @__PURE__ */ jsx22("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ jsx22("li", { children: /* @__PURE__ */ jsx22(Chip, { href: url, icon: /* @__PURE__ */ jsx22(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
1548
1729
  }
1549
1730
 
1550
1731
  // src/genre-tree/artist/formatYears.ts
@@ -1555,7 +1736,7 @@ function formatYears(beginYear, endYear, ended) {
1555
1736
  }
1556
1737
 
1557
1738
  // src/genre-tree/artist/ArtistHeader.tsx
1558
- import { jsx as jsx22 } from "react/jsx-runtime";
1739
+ import { jsx as jsx23 } from "react/jsx-runtime";
1559
1740
  var countryNames = new Intl.DisplayNames(["en"], { type: "region" });
1560
1741
  function ArtistHeader({ artist }) {
1561
1742
  const fields = [];
@@ -1565,35 +1746,35 @@ function ArtistHeader({ artist }) {
1565
1746
  const years = formatYears(artist.beginYear, artist.endYear, artist.ended);
1566
1747
  if (years) fields.push({ label: "Active", value: years });
1567
1748
  const links = artistLinks(artist);
1568
- if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ jsx22(ArtistLinks, { links }) });
1749
+ if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ jsx23(ArtistLinks, { links }) });
1569
1750
  if (fields.length === 0) return null;
1570
- return /* @__PURE__ */ jsx22(FieldList, { items: fields });
1751
+ return /* @__PURE__ */ jsx23(FieldList, { items: fields });
1571
1752
  }
1572
1753
 
1573
1754
  // src/genre-tree/artist/ArtistPhoto.tsx
1574
- import { jsx as jsx23, jsxs as jsxs16 } from "react/jsx-runtime";
1755
+ import { jsx as jsx24, jsxs as jsxs17 } from "react/jsx-runtime";
1575
1756
  function ExternalLink({ href, children }) {
1576
- return /* @__PURE__ */ jsx23("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
1757
+ return /* @__PURE__ */ jsx24("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
1577
1758
  }
1578
1759
  function ArtistPhoto({ image, name }) {
1579
- return /* @__PURE__ */ jsxs16("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
1580
- /* @__PURE__ */ jsx23("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
1581
- /* @__PURE__ */ jsxs16("figcaption", { className: "text-xs text-[var(--color-text-muted)]", children: [
1760
+ return /* @__PURE__ */ jsxs17("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
1761
+ /* @__PURE__ */ jsx24("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
1762
+ /* @__PURE__ */ jsxs17("figcaption", { className: "text-xs text-[var(--color-text-muted)]", children: [
1582
1763
  "Photo: ",
1583
- /* @__PURE__ */ jsx23(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
1764
+ /* @__PURE__ */ jsx24(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
1584
1765
  ",",
1585
1766
  " ",
1586
- image.licenseUrl ? /* @__PURE__ */ jsx23(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
1767
+ image.licenseUrl ? /* @__PURE__ */ jsx24(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
1587
1768
  ] })
1588
1769
  ] });
1589
1770
  }
1590
1771
 
1591
1772
  // src/genre-tree/artist/ArtistMembers.tsx
1592
1773
  import { PanelGroup, PanelSection as PanelSection3 } from "@behindthemusictree/ui";
1593
- import { jsx as jsx24, jsxs as jsxs17 } from "react/jsx-runtime";
1774
+ import { jsx as jsx25, jsxs as jsxs18 } from "react/jsx-runtime";
1594
1775
  function MembershipList({ title, memberships, renderArtistLink }) {
1595
1776
  if (memberships.length === 0) return null;
1596
- return /* @__PURE__ */ jsx24(PanelGroup, { title, children: /* @__PURE__ */ jsx24("ul", { children: memberships.map((membership, index) => {
1777
+ return /* @__PURE__ */ jsx25(PanelGroup, { title, children: /* @__PURE__ */ jsx25("ul", { children: memberships.map((membership, index) => {
1597
1778
  const details = [
1598
1779
  formatYears(membership.beginYear, membership.endYear, membership.ended),
1599
1780
  ...membership.roles
@@ -1601,9 +1782,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
1601
1782
  const detailsText = details.filter(Boolean).join(" \xB7 ");
1602
1783
  return (
1603
1784
  // A member can have several periods in the same group.
1604
- /* @__PURE__ */ jsxs17("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
1605
- /* @__PURE__ */ jsx24("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
1606
- detailsText && /* @__PURE__ */ jsxs17("span", { className: "text-[var(--color-text-muted)]", children: [
1785
+ /* @__PURE__ */ jsxs18("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
1786
+ /* @__PURE__ */ jsx25("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
1787
+ detailsText && /* @__PURE__ */ jsxs18("span", { className: "text-[var(--color-text-muted)]", children: [
1607
1788
  " \u2014 ",
1608
1789
  detailsText
1609
1790
  ] })
@@ -1614,9 +1795,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
1614
1795
  function ArtistMembers({ title, memberships, renderArtistLink }) {
1615
1796
  if (memberships.length === 0) return null;
1616
1797
  const isCurrent = (membership) => !membership.ended && membership.endYear === null;
1617
- return /* @__PURE__ */ jsxs17(PanelSection3, { title, children: [
1618
- /* @__PURE__ */ jsx24(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
1619
- /* @__PURE__ */ jsx24(
1798
+ return /* @__PURE__ */ jsxs18(PanelSection3, { title, children: [
1799
+ /* @__PURE__ */ jsx25(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
1800
+ /* @__PURE__ */ jsx25(
1620
1801
  MembershipList,
1621
1802
  {
1622
1803
  title: "Past",
@@ -1629,7 +1810,7 @@ function ArtistMembers({ title, memberships, renderArtistLink }) {
1629
1810
 
1630
1811
  // src/genre-tree/artist/ArtistDiscography.tsx
1631
1812
  import { PanelGroup as PanelGroup2, PanelSection as PanelSection4 } from "@behindthemusictree/ui";
1632
- import { jsx as jsx25, jsxs as jsxs18 } from "react/jsx-runtime";
1813
+ import { jsx as jsx26, jsxs as jsxs19 } from "react/jsx-runtime";
1633
1814
  var GROUPS = ["Albums", "EPs", "Singles", "Live", "Compilations", "Other"];
1634
1815
  var PRIMARY_GROUPS = { album: "Albums", ep: "EPs", single: "Singles" };
1635
1816
  function discographyGroup({ primaryType, secondaryTypes }) {
@@ -1649,12 +1830,12 @@ function byYear(a, b) {
1649
1830
  function ArtistDiscography({ albums }) {
1650
1831
  if (albums.length === 0) return null;
1651
1832
  const sorted = [...albums].sort(byYear);
1652
- return /* @__PURE__ */ jsx25(PanelSection4, { title: "Discography", children: GROUPS.map((group) => {
1833
+ return /* @__PURE__ */ jsx26(PanelSection4, { title: "Discography", children: GROUPS.map((group) => {
1653
1834
  const groupAlbums = sorted.filter((album) => discographyGroup(album) === group);
1654
1835
  if (groupAlbums.length === 0) return null;
1655
- return /* @__PURE__ */ jsx25(PanelGroup2, { title: group, children: /* @__PURE__ */ jsx25("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ jsxs18("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
1656
- /* @__PURE__ */ jsx25("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
1657
- /* @__PURE__ */ jsx25("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
1836
+ return /* @__PURE__ */ jsx26(PanelGroup2, { title: group, children: /* @__PURE__ */ jsx26("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ jsxs19("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
1837
+ /* @__PURE__ */ jsx26("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
1838
+ /* @__PURE__ */ jsx26("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
1658
1839
  ] }, album.uuid)) }) }, group);
1659
1840
  }) });
1660
1841
  }
@@ -1663,7 +1844,7 @@ function ArtistDiscography({ albums }) {
1663
1844
  import { useState as useState7 } from "react";
1664
1845
  import { keepPreviousData } from "@tanstack/react-query";
1665
1846
  import { Play as Play2 } from "lucide-react";
1666
- import { Pagination, PanelSection as PanelSection5, Skeleton as Skeleton3 } from "@behindthemusictree/ui";
1847
+ import { Pagination, PanelSection as PanelSection5, Skeleton as Skeleton4 } from "@behindthemusictree/ui";
1667
1848
  import { usePlayer as usePlayer8 } from "@behindthemusictree/app-kit/player";
1668
1849
  import {
1669
1850
  SongDetailedSchema as SongDetailedSchema2,
@@ -1681,7 +1862,7 @@ import { useTrackList as useTrackList3 } from "@behindthemusictree/app-kit/genre
1681
1862
  import { FaPlay as FaPlay2, FaPause as FaPause2 } from "react-icons/fa";
1682
1863
  import { usePlayer as usePlayer6 } from "@behindthemusictree/app-kit/player";
1683
1864
  import { PlayStates as PlayStates2 } from "@behindthemusictree/app-kit/player";
1684
- import { jsx as jsx26, jsxs as jsxs19 } from "react/jsx-runtime";
1865
+ import { jsx as jsx27, jsxs as jsxs20 } from "react/jsx-runtime";
1685
1866
  function TrackPositionPlayPause({
1686
1867
  position,
1687
1868
  uuid,
@@ -1695,14 +1876,14 @@ function TrackPositionPlayPause({
1695
1876
  const isPlaying = isThisTrack && isConfirmedPlayable && playState === PlayStates2.PLAYING;
1696
1877
  const isPaused = isThisTrack && isConfirmedPlayable && playState === PlayStates2.PAUSED;
1697
1878
  const isCurrentTrackActive = isPlaying || isPaused;
1698
- return /* @__PURE__ */ jsxs19(
1879
+ return /* @__PURE__ */ jsxs20(
1699
1880
  "div",
1700
1881
  {
1701
1882
  className: "track-position-play-pause relative flex items-center justify-center gap-2 text-lg w-16 cursor-pointer",
1702
1883
  style: { minWidth: "4rem", maxWidth: "4rem" },
1703
1884
  onClick: handlePlayPauseClick,
1704
1885
  children: [
1705
- /* @__PURE__ */ jsx26("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ jsx26("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ jsxs19(
1886
+ /* @__PURE__ */ jsx27("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ jsx27("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ jsxs20(
1706
1887
  "span",
1707
1888
  {
1708
1889
  className: "track-position-play-pause__bars",
@@ -1710,18 +1891,18 @@ function TrackPositionPlayPause({
1710
1891
  role: "img",
1711
1892
  "aria-label": isPlaying ? "Playing" : "Paused",
1712
1893
  children: [
1713
- /* @__PURE__ */ jsx26("span", { className: "track-position-play-pause__bar" }),
1714
- /* @__PURE__ */ jsx26("span", { className: "track-position-play-pause__bar" }),
1715
- /* @__PURE__ */ jsx26("span", { className: "track-position-play-pause__bar" })
1894
+ /* @__PURE__ */ jsx27("span", { className: "track-position-play-pause__bar" }),
1895
+ /* @__PURE__ */ jsx27("span", { className: "track-position-play-pause__bar" }),
1896
+ /* @__PURE__ */ jsx27("span", { className: "track-position-play-pause__bar" })
1716
1897
  ]
1717
1898
  }
1718
1899
  ) : position }),
1719
- /* @__PURE__ */ jsx26(
1900
+ /* @__PURE__ */ jsx27(
1720
1901
  "div",
1721
1902
  {
1722
1903
  className: `hidden group-hover:flex items-center justify-center ${isCurrentTrackActive ? "" : "absolute inset-0"}`,
1723
1904
  "aria-hidden": "true",
1724
- children: isAwaitingPlayback ? /* @__PURE__ */ jsx26("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ jsx26(FaPause2, {}) : /* @__PURE__ */ jsx26(FaPlay2, {})
1905
+ children: isAwaitingPlayback ? /* @__PURE__ */ jsx27("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ jsx27(FaPause2, {}) : /* @__PURE__ */ jsx27(FaPlay2, {})
1725
1906
  }
1726
1907
  )
1727
1908
  ]
@@ -1732,7 +1913,7 @@ function TrackPositionPlayPause({
1732
1913
  // src/genre-tree/track-list-sidebar/TrackItem.tsx
1733
1914
  import { isSong as isSong2, songUnplayableReason as songUnplayableReason2 } from "@behindthemusictree/app-kit/genre-tree";
1734
1915
  import { ArtistNames as ArtistNames2 } from "@behindthemusictree/app-kit/genre-tree";
1735
- import { jsx as jsx27, jsxs as jsxs20 } from "react/jsx-runtime";
1916
+ import { jsx as jsx28, jsxs as jsxs21 } from "react/jsx-runtime";
1736
1917
  function TrackItem({
1737
1918
  track,
1738
1919
  position,
@@ -1756,12 +1937,12 @@ function TrackItem({
1756
1937
  loadTrackForPlayer(track.uuid);
1757
1938
  }
1758
1939
  };
1759
- return /* @__PURE__ */ jsxs20(
1940
+ return /* @__PURE__ */ jsxs21(
1760
1941
  "div",
1761
1942
  {
1762
1943
  className: `track-item flex h-14 text-[var(--color-text)] ${isUnplayable ? "opacity-50" : "hover:bg-[var(--color-surface-muted)] group"}`,
1763
1944
  children: [
1764
- isUnplayable ? /* @__PURE__ */ jsx27(
1945
+ isUnplayable ? /* @__PURE__ */ jsx28(
1765
1946
  "div",
1766
1947
  {
1767
1948
  className: "flex items-center justify-center text-lg w-16",
@@ -1769,7 +1950,7 @@ function TrackItem({
1769
1950
  "aria-disabled": "true",
1770
1951
  children: position
1771
1952
  }
1772
- ) : /* @__PURE__ */ jsx27(
1953
+ ) : /* @__PURE__ */ jsx28(
1773
1954
  TrackPositionPlayPause,
1774
1955
  {
1775
1956
  position,
@@ -1778,12 +1959,12 @@ function TrackItem({
1778
1959
  isSelected: selectedTrack?.uuid === track.uuid
1779
1960
  }
1780
1961
  ),
1781
- /* @__PURE__ */ jsxs20("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
1782
- /* @__PURE__ */ jsx27("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ jsx27("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: renderSongLink ? renderSongLink(track) : isSong2(track) ? /* @__PURE__ */ jsx27(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
1783
- track.artists && track.artists.length > 0 ? /* @__PURE__ */ jsx27("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ jsx27(ArtistNames2, { artists: track.artists, renderArtistLink }) }) : ""
1962
+ /* @__PURE__ */ jsxs21("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
1963
+ /* @__PURE__ */ jsx28("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ jsx28("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: renderSongLink ? renderSongLink(track) : isSong2(track) ? /* @__PURE__ */ jsx28(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
1964
+ track.artists && track.artists.length > 0 ? /* @__PURE__ */ jsx28("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ jsx28(ArtistNames2, { artists: track.artists, renderArtistLink }) }) : ""
1784
1965
  ] }),
1785
- /* @__PURE__ */ jsx27("div", { className: "genre-name-container flex items-center justify-start w-[21.6%] min-w-0 shrink-0", children: track.genre ? /* @__PURE__ */ jsx27("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
1786
- renderDuration && /* @__PURE__ */ jsx27(
1966
+ /* @__PURE__ */ jsx28("div", { className: "genre-name-container flex items-center justify-start w-[21.6%] min-w-0 shrink-0", children: track.genre ? /* @__PURE__ */ jsx28("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
1967
+ renderDuration && /* @__PURE__ */ jsx28(
1787
1968
  "div",
1788
1969
  {
1789
1970
  className: "duration flex text-base w-16 items-center justify-center",
@@ -1791,14 +1972,14 @@ function TrackItem({
1791
1972
  children: renderDuration(track)
1792
1973
  }
1793
1974
  ),
1794
- renderActions && /* @__PURE__ */ jsx27("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
1975
+ renderActions && /* @__PURE__ */ jsx28("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
1795
1976
  ]
1796
1977
  }
1797
1978
  );
1798
1979
  }
1799
1980
 
1800
1981
  // src/genre-tree/artist/ArtistSongs.tsx
1801
- import { Fragment as Fragment10, jsx as jsx28, jsxs as jsxs21 } from "react/jsx-runtime";
1982
+ import { Fragment as Fragment11, jsx as jsx29, jsxs as jsxs22 } from "react/jsx-runtime";
1802
1983
  var SongPageSchema2 = PaginatedResponseSchema2(SongDetailedSchema2);
1803
1984
  var SORTS = [
1804
1985
  { ordering: "title", label: "Title" },
@@ -1831,21 +2012,21 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
1831
2012
  event.stopPropagation();
1832
2013
  void play(song.uuid);
1833
2014
  };
1834
- return /* @__PURE__ */ jsx28(PanelSection5, { title: "Songs", children: isPending ? /* @__PURE__ */ jsx28(Skeleton3, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ jsx28("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ jsx28("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ jsxs21(Fragment10, { children: [
1835
- /* @__PURE__ */ jsxs21("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
1836
- /* @__PURE__ */ jsxs21(
2015
+ return /* @__PURE__ */ jsx29(PanelSection5, { title: "Songs", children: isPending ? /* @__PURE__ */ jsx29(Skeleton4, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ jsx29("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ jsx29("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ jsxs22(Fragment11, { children: [
2016
+ /* @__PURE__ */ jsxs22("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
2017
+ /* @__PURE__ */ jsxs22(
1837
2018
  "button",
1838
2019
  {
1839
2020
  type: "button",
1840
2021
  onClick: () => void play(),
1841
2022
  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",
1842
2023
  children: [
1843
- /* @__PURE__ */ jsx28(Play2, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
2024
+ /* @__PURE__ */ jsx29(Play2, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
1844
2025
  "Play all"
1845
2026
  ]
1846
2027
  }
1847
2028
  ),
1848
- /* @__PURE__ */ jsx28("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ jsx28(
2029
+ /* @__PURE__ */ jsx29("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ jsx29(
1849
2030
  "button",
1850
2031
  {
1851
2032
  type: "button",
@@ -1860,7 +2041,7 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
1860
2041
  sort.ordering
1861
2042
  )) })
1862
2043
  ] }),
1863
- /* @__PURE__ */ jsx28("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ jsx28("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ jsx28(
2044
+ /* @__PURE__ */ jsx29("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ jsx29("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ jsx29(
1864
2045
  TrackItem,
1865
2046
  {
1866
2047
  track: song,
@@ -1869,12 +2050,12 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
1869
2050
  renderSongLink
1870
2051
  }
1871
2052
  ) }, song.uuid)) }),
1872
- /* @__PURE__ */ jsx28(Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
2053
+ /* @__PURE__ */ jsx29(Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
1873
2054
  ] }) });
1874
2055
  }
1875
2056
 
1876
2057
  // src/genre-tree/artist/useArtistPanel.tsx
1877
- import { Fragment as Fragment11, jsx as jsx29, jsxs as jsxs22 } from "react/jsx-runtime";
2058
+ import { Fragment as Fragment12, jsx as jsx30, jsxs as jsxs23 } from "react/jsx-runtime";
1878
2059
  function useArtistPanel({
1879
2060
  uuid,
1880
2061
  scope,
@@ -1895,26 +2076,26 @@ function useArtistPanel({
1895
2076
  return {
1896
2077
  ...panel,
1897
2078
  title: "Artist",
1898
- content: /* @__PURE__ */ jsxs22("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
1899
- /* @__PURE__ */ jsx29(Skeleton4, { className: "h-3 w-16" }),
1900
- /* @__PURE__ */ jsx29(Skeleton4, { className: "h-4 w-32" }),
1901
- /* @__PURE__ */ jsx29(Skeleton4, { className: "h-3 w-16" }),
1902
- /* @__PURE__ */ jsx29(Skeleton4, { className: "h-4 w-24" }),
1903
- /* @__PURE__ */ jsx29(Skeleton4, { className: "h-16 mt-2.5" }),
1904
- /* @__PURE__ */ jsx29(Skeleton4, { className: "h-16" })
2079
+ content: /* @__PURE__ */ jsxs23("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2080
+ /* @__PURE__ */ jsx30(Skeleton5, { className: "h-3 w-16" }),
2081
+ /* @__PURE__ */ jsx30(Skeleton5, { className: "h-4 w-32" }),
2082
+ /* @__PURE__ */ jsx30(Skeleton5, { className: "h-3 w-16" }),
2083
+ /* @__PURE__ */ jsx30(Skeleton5, { className: "h-4 w-24" }),
2084
+ /* @__PURE__ */ jsx30(Skeleton5, { className: "h-16 mt-2.5" }),
2085
+ /* @__PURE__ */ jsx30(Skeleton5, { className: "h-16" })
1905
2086
  ] })
1906
2087
  };
1907
2088
  case "not-found":
1908
2089
  return {
1909
2090
  ...panel,
1910
2091
  title: "Artist not found",
1911
- content: /* @__PURE__ */ jsx29("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This artist does not exist." })
2092
+ content: /* @__PURE__ */ jsx30("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This artist does not exist." })
1912
2093
  };
1913
2094
  case "error":
1914
2095
  return {
1915
2096
  ...panel,
1916
2097
  title: "Artist",
1917
- content: /* @__PURE__ */ jsx29("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
2098
+ content: /* @__PURE__ */ jsx30("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
1918
2099
  };
1919
2100
  case "success": {
1920
2101
  const { artist } = result;
@@ -1923,13 +2104,13 @@ function useArtistPanel({
1923
2104
  return {
1924
2105
  ...panel,
1925
2106
  title: artist.name,
1926
- content: /* @__PURE__ */ jsxs22(Fragment11, { children: [
1927
- shareUrl && /* @__PURE__ */ jsx29("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ jsx29("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: shareTarget && /* @__PURE__ */ jsx29(ShareActionButton, { target: shareTarget, url: shareUrl }) }) }),
1928
- artist.image && /* @__PURE__ */ jsx29(ArtistPhoto, { image: artist.image, name: artist.name }),
1929
- /* @__PURE__ */ jsx29(ArtistHeader, { artist }),
1930
- /* @__PURE__ */ jsx29(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
1931
- /* @__PURE__ */ jsx29(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
1932
- artist.tracksCount > 0 && /* @__PURE__ */ jsx29(
2107
+ content: /* @__PURE__ */ jsxs23(Fragment12, { children: [
2108
+ shareUrl && /* @__PURE__ */ jsx30("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ jsx30("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: shareTarget && /* @__PURE__ */ jsx30(ShareActionButton, { target: shareTarget, url: shareUrl }) }) }),
2109
+ artist.image && /* @__PURE__ */ jsx30(ArtistPhoto, { image: artist.image, name: artist.name }),
2110
+ /* @__PURE__ */ jsx30(ArtistHeader, { artist }),
2111
+ /* @__PURE__ */ jsx30(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
2112
+ /* @__PURE__ */ jsx30(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
2113
+ artist.tracksCount > 0 && /* @__PURE__ */ jsx30(
1933
2114
  ArtistSongs,
1934
2115
  {
1935
2116
  artist: { uuid: artist.uuid, name: artist.name },
@@ -1939,14 +2120,14 @@ function useArtistPanel({
1939
2120
  }
1940
2121
  )
1941
2122
  ] }),
1942
- extraContent: artist.albums.length > 0 ? /* @__PURE__ */ jsx29(ArtistDiscography, { albums: artist.albums }) : void 0
2123
+ extraContent: artist.albums.length > 0 ? /* @__PURE__ */ jsx30(ArtistDiscography, { albums: artist.albums }) : void 0
1943
2124
  };
1944
2125
  }
1945
2126
  }
1946
2127
  }
1947
2128
 
1948
2129
  // src/genre-tree/song/useSongPanel.tsx
1949
- import { Skeleton as Skeleton6 } from "@behindthemusictree/ui";
2130
+ import { Skeleton as Skeleton7 } from "@behindthemusictree/ui";
1950
2131
  import {
1951
2132
  useSong
1952
2133
  } from "@behindthemusictree/app-kit/genre-tree";
@@ -1954,7 +2135,7 @@ import {
1954
2135
  // src/genre-tree/song/SongPanelContent.tsx
1955
2136
  import { useState as useState8 } from "react";
1956
2137
  import { Pause as Pause2, Play as Play3 } from "lucide-react";
1957
- import { FieldList as FieldList2, PanelSection as PanelSection6, Skeleton as Skeleton5 } from "@behindthemusictree/ui";
2138
+ import { FieldList as FieldList2, PanelSection as PanelSection6, Skeleton as Skeleton6 } from "@behindthemusictree/ui";
1958
2139
  import { PlayStates as PlayStates3, usePlayer as usePlayer9 } from "@behindthemusictree/app-kit/player";
1959
2140
  import {
1960
2141
  ArtistNames as ArtistNames3,
@@ -1963,7 +2144,7 @@ import {
1963
2144
  useSongHistory,
1964
2145
  useTrackList as useTrackList5
1965
2146
  } from "@behindthemusictree/app-kit/genre-tree";
1966
- import { Fragment as Fragment12, jsx as jsx30, jsxs as jsxs23 } from "react/jsx-runtime";
2147
+ import { Fragment as Fragment13, jsx as jsx31, jsxs as jsxs24 } from "react/jsx-runtime";
1967
2148
  var numberFormat = new Intl.NumberFormat("en");
1968
2149
  var dateFormat = new Intl.DateTimeFormat("en", { dateStyle: "medium" });
1969
2150
  var externalLink = "text-[var(--color-primary)] hover:underline focus-visible:underline";
@@ -1971,32 +2152,32 @@ function SongHistory({ uuid, getBackendBaseUrl }) {
1971
2152
  const [open, setOpen] = useState8(false);
1972
2153
  const history = useSongHistory(uuid, getBackendBaseUrl, open);
1973
2154
  let body = null;
1974
- if (history.status === "loading") body = /* @__PURE__ */ jsx30(Skeleton5, { className: "h-10", "aria-busy": "true" });
1975
- else if (history.status === "error") body = /* @__PURE__ */ jsx30("p", { className: "text-red-600", children: "Could not load the history." });
2155
+ if (history.status === "loading") body = /* @__PURE__ */ jsx31(Skeleton6, { className: "h-10", "aria-busy": "true" });
2156
+ else if (history.status === "error") body = /* @__PURE__ */ jsx31("p", { className: "text-red-600", children: "Could not load the history." });
1976
2157
  else if (history.status === "success")
1977
- body = history.entries.length === 0 ? /* @__PURE__ */ jsx30("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ jsx30("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ jsxs23("li", { className: "py-1.5 text-sm", children: [
1978
- /* @__PURE__ */ jsx30("span", { className: "font-medium", children: entry.action }),
1979
- entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ jsxs23("span", { className: "text-[var(--color-text-muted)]", children: [
2158
+ body = history.entries.length === 0 ? /* @__PURE__ */ jsx31("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ jsx31("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ jsxs24("li", { className: "py-1.5 text-sm", children: [
2159
+ /* @__PURE__ */ jsx31("span", { className: "font-medium", children: entry.action }),
2160
+ entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ jsxs24("span", { className: "text-[var(--color-text-muted)]", children: [
1980
2161
  " ",
1981
2162
  "\u2014 ",
1982
2163
  entry.oldValue ?? "none",
1983
2164
  " \u2192 ",
1984
2165
  entry.newValue ?? "none"
1985
2166
  ] }) : null,
1986
- /* @__PURE__ */ jsxs23("span", { className: "block text-xs text-[var(--color-text-muted)]", children: [
2167
+ /* @__PURE__ */ jsxs24("span", { className: "block text-xs text-[var(--color-text-muted)]", children: [
1987
2168
  entry.actorPseudo ?? "Anonymous",
1988
2169
  " \xB7 ",
1989
2170
  dateFormat.format(new Date(entry.createdOn))
1990
2171
  ] })
1991
2172
  ] }, entry.uuid)) });
1992
- return /* @__PURE__ */ jsxs23(
2173
+ return /* @__PURE__ */ jsxs24(
1993
2174
  "details",
1994
2175
  {
1995
2176
  onToggle: (event) => setOpen(event.currentTarget.open),
1996
2177
  className: "px-6 pb-2.5 mt-2.5 border-t border-[var(--color-border)]",
1997
2178
  children: [
1998
- /* @__PURE__ */ jsx30("summary", { className: "mt-2.5 cursor-pointer text-[0.6875rem] font-semibold uppercase text-[var(--color-text-muted)]", children: "History" }),
1999
- /* @__PURE__ */ jsx30("div", { className: "mt-1.5", children: body })
2179
+ /* @__PURE__ */ jsx31("summary", { className: "mt-2.5 cursor-pointer text-[0.6875rem] font-semibold uppercase text-[var(--color-text-muted)]", children: "History" }),
2180
+ /* @__PURE__ */ jsx31("div", { className: "mt-1.5", children: body })
2000
2181
  ]
2001
2182
  }
2002
2183
  );
@@ -2022,13 +2203,13 @@ function SongPanelContent({
2022
2203
  const path = genrePath.length > 0 ? genrePath : null;
2023
2204
  fields.push({
2024
2205
  label: "Genre",
2025
- value: path ? /* @__PURE__ */ jsx30("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ jsxs23("span", { children: [
2026
- index > 0 && /* @__PURE__ */ jsx30("span", { "aria-hidden": "true", children: "\u203A " }),
2027
- /* @__PURE__ */ jsx30("button", { type: "button", onClick: () => onSelectGenre(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
2206
+ value: path ? /* @__PURE__ */ jsx31("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ jsxs24("span", { children: [
2207
+ index > 0 && /* @__PURE__ */ jsx31("span", { "aria-hidden": "true", children: "\u203A " }),
2208
+ /* @__PURE__ */ jsx31("button", { type: "button", onClick: () => onSelectGenre(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
2028
2209
  ] }, genrePlaylist.uuid)) }) : song.genre.name
2029
2210
  });
2030
2211
  } else {
2031
- fields.push({ label: "Genre", value: /* @__PURE__ */ jsx30("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
2212
+ fields.push({ label: "Genre", value: /* @__PURE__ */ jsx31("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
2032
2213
  }
2033
2214
  if (song.firstReleaseYear) fields.push({ label: "First released", value: song.firstReleaseYear });
2034
2215
  const viewCount = song.youtubeVideo?.viewCount;
@@ -2038,11 +2219,11 @@ function SongPanelContent({
2038
2219
  const links = [];
2039
2220
  if (musicBrainzUrl)
2040
2221
  links.push(
2041
- /* @__PURE__ */ jsx30("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
2222
+ /* @__PURE__ */ jsx31("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
2042
2223
  );
2043
2224
  if (song.youtubeVideo)
2044
2225
  links.push(
2045
- /* @__PURE__ */ jsx30(
2226
+ /* @__PURE__ */ jsx31(
2046
2227
  "a",
2047
2228
  {
2048
2229
  href: `https://www.youtube.com/watch?v=${encodeURIComponent(song.youtubeVideo.id)}`,
@@ -2057,33 +2238,33 @@ function SongPanelContent({
2057
2238
  if (links.length > 0)
2058
2239
  fields.push({
2059
2240
  label: "Links",
2060
- value: /* @__PURE__ */ jsx30("span", { className: "flex gap-3", children: links })
2241
+ value: /* @__PURE__ */ jsx31("span", { className: "flex gap-3", children: links })
2061
2242
  });
2062
- return /* @__PURE__ */ jsxs23(Fragment12, { children: [
2063
- /* @__PURE__ */ jsx30("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ jsxs23("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
2064
- unplayableReason ? /* @__PURE__ */ jsx30("p", { className: "flex items-center px-2.5 text-sm text-[var(--color-text-muted)]", children: unplayableReason }) : /* @__PURE__ */ jsxs23(
2243
+ return /* @__PURE__ */ jsxs24(Fragment13, { children: [
2244
+ /* @__PURE__ */ jsx31("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ jsxs24("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
2245
+ unplayableReason ? /* @__PURE__ */ jsx31("p", { className: "flex items-center px-2.5 text-sm text-[var(--color-text-muted)]", children: unplayableReason }) : /* @__PURE__ */ jsxs24(
2065
2246
  "button",
2066
2247
  {
2067
2248
  type: "button",
2068
2249
  onClick: () => isCurrent ? handlePlayPauseAction() : playNewTrackListFromTrackUuid(song, scope),
2069
2250
  className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none",
2070
2251
  children: [
2071
- /* @__PURE__ */ jsx30("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)]", children: isPlaying ? /* @__PURE__ */ jsx30(Pause2, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ jsx30(Play3, { "aria-hidden": "true", className: "w-5 h-5" }) }),
2252
+ /* @__PURE__ */ jsx31("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)]", children: isPlaying ? /* @__PURE__ */ jsx31(Pause2, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ jsx31(Play3, { "aria-hidden": "true", className: "w-5 h-5" }) }),
2072
2253
  isPlaying ? "Pause" : "Play"
2073
2254
  ]
2074
2255
  }
2075
2256
  ),
2076
- shareUrl && /* @__PURE__ */ jsx30(ShareActionButton, { target: shareTarget, url: shareUrl })
2257
+ shareUrl && /* @__PURE__ */ jsx31(ShareActionButton, { target: shareTarget, url: shareUrl })
2077
2258
  ] }) }),
2078
- song.artists && song.artists.length > 0 && /* @__PURE__ */ jsx30("p", { className: "px-6 pt-2.5 text-[var(--color-text-muted)]", children: /* @__PURE__ */ jsx30(ArtistNames3, { artists: song.artists, renderArtistLink }) }),
2079
- /* @__PURE__ */ jsx30(FieldList2, { items: fields }),
2080
- song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ jsx30(PanelSection6, { title: "Essential in", children: /* @__PURE__ */ jsx30("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ jsx30("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
2081
- /* @__PURE__ */ jsx30(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
2259
+ song.artists && song.artists.length > 0 && /* @__PURE__ */ jsx31("p", { className: "px-6 pt-2.5 text-[var(--color-text-muted)]", children: /* @__PURE__ */ jsx31(ArtistNames3, { artists: song.artists, renderArtistLink }) }),
2260
+ /* @__PURE__ */ jsx31(FieldList2, { items: fields }),
2261
+ song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ jsx31(PanelSection6, { title: "Essential in", children: /* @__PURE__ */ jsx31("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ jsx31("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
2262
+ /* @__PURE__ */ jsx31(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
2082
2263
  ] });
2083
2264
  }
2084
2265
 
2085
2266
  // src/genre-tree/song/useSongPanel.tsx
2086
- import { jsx as jsx31, jsxs as jsxs24 } from "react/jsx-runtime";
2267
+ import { jsx as jsx32, jsxs as jsxs25 } from "react/jsx-runtime";
2087
2268
  function useSongPanel({
2088
2269
  uuid,
2089
2270
  scope,
@@ -2105,24 +2286,24 @@ function useSongPanel({
2105
2286
  return {
2106
2287
  ...panel,
2107
2288
  title: "Song",
2108
- content: /* @__PURE__ */ jsxs24("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2109
- /* @__PURE__ */ jsx31(Skeleton6, { className: "h-3 w-16" }),
2110
- /* @__PURE__ */ jsx31(Skeleton6, { className: "h-4 w-32" }),
2111
- /* @__PURE__ */ jsx31(Skeleton6, { className: "h-3 w-16" }),
2112
- /* @__PURE__ */ jsx31(Skeleton6, { className: "h-4 w-24" })
2289
+ content: /* @__PURE__ */ jsxs25("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
2290
+ /* @__PURE__ */ jsx32(Skeleton7, { className: "h-3 w-16" }),
2291
+ /* @__PURE__ */ jsx32(Skeleton7, { className: "h-4 w-32" }),
2292
+ /* @__PURE__ */ jsx32(Skeleton7, { className: "h-3 w-16" }),
2293
+ /* @__PURE__ */ jsx32(Skeleton7, { className: "h-4 w-24" })
2113
2294
  ] })
2114
2295
  };
2115
2296
  case "not-found":
2116
2297
  return {
2117
2298
  ...panel,
2118
2299
  title: "Song not found",
2119
- content: /* @__PURE__ */ jsx31("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This song does not exist." })
2300
+ content: /* @__PURE__ */ jsx32("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This song does not exist." })
2120
2301
  };
2121
2302
  case "error":
2122
2303
  return {
2123
2304
  ...panel,
2124
2305
  title: "Song",
2125
- content: /* @__PURE__ */ jsx31("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this song." })
2306
+ content: /* @__PURE__ */ jsx32("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this song." })
2126
2307
  };
2127
2308
  case "success": {
2128
2309
  const { song } = result;
@@ -2130,7 +2311,7 @@ function useSongPanel({
2130
2311
  return {
2131
2312
  ...panel,
2132
2313
  title: song.title,
2133
- content: /* @__PURE__ */ jsx31(
2314
+ content: /* @__PURE__ */ jsx32(
2134
2315
  SongPanelContent,
2135
2316
  {
2136
2317
  song,
@@ -2150,12 +2331,12 @@ function useSongPanel({
2150
2331
  // src/genre-tree/SizeMetricToggle.tsx
2151
2332
  import { MdMusicNote, MdVisibility } from "react-icons/md";
2152
2333
  import { SegmentedControl } from "@behindthemusictree/ui";
2153
- import { jsx as jsx32 } from "react/jsx-runtime";
2334
+ import { jsx as jsx33 } from "react/jsx-runtime";
2154
2335
  function SizeMetricToggle({
2155
2336
  value,
2156
2337
  onChange
2157
2338
  }) {
2158
- return /* @__PURE__ */ jsx32(
2339
+ return /* @__PURE__ */ jsx33(
2159
2340
  SegmentedControl,
2160
2341
  {
2161
2342
  variant: "floating",
@@ -2163,15 +2344,15 @@ function SizeMetricToggle({
2163
2344
  value,
2164
2345
  onChange,
2165
2346
  options: [
2166
- { value: "songs", label: /* @__PURE__ */ jsx32(MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
2167
- { value: "views", label: /* @__PURE__ */ jsx32(MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
2347
+ { value: "songs", label: /* @__PURE__ */ jsx33(MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
2348
+ { value: "views", label: /* @__PURE__ */ jsx33(MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
2168
2349
  ]
2169
2350
  }
2170
2351
  );
2171
2352
  }
2172
2353
 
2173
2354
  // src/genre-tree/GenreTreeView.tsx
2174
- import { Fragment as Fragment13, jsx as jsx33, jsxs as jsxs25 } from "react/jsx-runtime";
2355
+ import { Fragment as Fragment14, jsx as jsx34, jsxs as jsxs26 } from "react/jsx-runtime";
2175
2356
  var NO_POP_ROOT_TITLE = "This genre tree has no 'Mainstream Pop' root yet";
2176
2357
  var REGIONS_CLASS = "[--gtv-center-left:var(--layout-center-left)] [--gtv-center-right:var(--layout-center-right)] [--gtv-center-top:var(--layout-band-top)]";
2177
2358
  var noop = () => {
@@ -2184,8 +2365,8 @@ function GenreTreeView({
2184
2365
  getBackendBaseUrl,
2185
2366
  additionalActions,
2186
2367
  viewMode: controlledViewMode,
2187
- selectedNodeId: controlledSelectedNodeId,
2188
- onSelectedNodeIdChange,
2368
+ selectedGenreUuid: controlledSelectedGenreUuid,
2369
+ onSelectedGenreUuidChange,
2189
2370
  defaultViewport,
2190
2371
  viewportRestore,
2191
2372
  onViewportChange,
@@ -2262,6 +2443,8 @@ function GenreTreeView({
2262
2443
  genrePlaylistByNodeId,
2263
2444
  visibleGenrePlaylists,
2264
2445
  groupedGenrePlaylistsByRoot,
2446
+ selectedGenreUuid,
2447
+ selectGenre,
2265
2448
  selectedNodeId,
2266
2449
  selectedGenrePlaylist,
2267
2450
  selectNode,
@@ -2278,8 +2461,8 @@ function GenreTreeView({
2278
2461
  scope,
2279
2462
  getBackendBaseUrl,
2280
2463
  viewMode: controlledViewMode,
2281
- selectedNodeId: controlledSelectedNodeId,
2282
- onSelectedNodeIdChange,
2464
+ selectedGenreUuid: controlledSelectedGenreUuid,
2465
+ onSelectedGenreUuidChange,
2283
2466
  criteriaOverviewSchema,
2284
2467
  artistUuid
2285
2468
  });
@@ -2300,25 +2483,25 @@ function GenreTreeView({
2300
2483
  const canSizeByViews = !isArtistFiltered && hasViewsCount(visibleGenrePlaylists);
2301
2484
  const artistResult = useArtist2(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
2302
2485
  const filterArtistName = artistResult.status === "success" ? artistResult.artist.name : null;
2303
- const filterArtist = useMemo7(
2486
+ const filterArtist = useMemo8(
2304
2487
  () => artistUuid !== null && filterArtistName !== null ? { uuid: artistUuid, name: filterArtistName } : null,
2305
2488
  [artistUuid, filterArtistName]
2306
2489
  );
2307
2490
  const hasSelectedGenreNode = selectedGenrePlaylist !== void 0 && visibleGenrePlaylists.some(({ uuid }) => uuid === selectedGenrePlaylist.uuid);
2308
2491
  const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: visibleGenrePlaylists, artist: filterArtist });
2309
2492
  const openArtist = (uuid) => {
2310
- if (selectedNodeId !== null) clearSelection();
2493
+ if (selectedGenreUuid !== null) clearSelection();
2311
2494
  if (songUuid !== null) changeSong(null);
2312
2495
  changeArtist(uuid);
2313
2496
  };
2314
2497
  const openSong = (uuid) => {
2315
- if (selectedNodeId !== null) clearSelection();
2498
+ if (selectedGenreUuid !== null) clearSelection();
2316
2499
  changeSong(uuid);
2317
2500
  };
2318
2501
  const openSongRef = useRef3(openSong);
2319
2502
  openSongRef.current = openSong;
2320
2503
  const resolvedRenderSongLink = useCallback6(
2321
- (song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ jsx33("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
2504
+ (song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ jsx34("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
2322
2505
  [renderSongLink]
2323
2506
  );
2324
2507
  const [expandedSongUuid, setExpandedSongUuid] = useState9(null);
@@ -2330,7 +2513,7 @@ function GenreTreeView({
2330
2513
  scope,
2331
2514
  getBackendBaseUrl: artistBaseUrl,
2332
2515
  getShareUrl: getShareUrlRef.current,
2333
- renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ jsx33("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
2516
+ renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ jsx34("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
2334
2517
  expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
2335
2518
  onExpandedChange: (on) => {
2336
2519
  if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
@@ -2339,7 +2522,7 @@ function GenreTreeView({
2339
2522
  renderSongLink: resolvedRenderSongLink,
2340
2523
  onClose: () => changeArtist(null)
2341
2524
  });
2342
- const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ jsx33("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
2525
+ const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ jsx34("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
2343
2526
  const getGenrePath = (genre) => {
2344
2527
  const path = [];
2345
2528
  let current = visibleGenrePlaylists.find((playlist) => playlist.criteria?.uuid === genre.uuid);
@@ -2357,9 +2540,9 @@ function GenreTreeView({
2357
2540
  getShareUrl: getShareUrlRef.current,
2358
2541
  renderArtistLink: resolvedRenderArtistLink,
2359
2542
  getGenrePath,
2360
- onSelectGenre: (genrePlaylist) => {
2543
+ onSelectGenre: (genrePlaylist2) => {
2361
2544
  changeSong(null);
2362
- selectGenrePlaylist(genrePlaylist);
2545
+ selectGenrePlaylist(genrePlaylist2);
2363
2546
  },
2364
2547
  expanded: controlledSongExpanded ?? (songUuid !== null && expandedSongUuid === songUuid),
2365
2548
  onExpandedChange: (on) => {
@@ -2368,8 +2551,8 @@ function GenreTreeView({
2368
2551
  },
2369
2552
  onClose: () => changeSong(null)
2370
2553
  });
2371
- const builtInSongPanel = overlayPanel || hasSelectedGenreNode ? void 0 : songPanel;
2372
- const builtInArtistPanel = overlayPanel || hasSelectedGenreNode || builtInSongPanel ? void 0 : artistPanel;
2554
+ const builtInSongPanel = overlayPanel || selectedGenreUuid !== null ? void 0 : songPanel;
2555
+ const builtInArtistPanel = overlayPanel || selectedGenreUuid !== null || builtInSongPanel ? void 0 : artistPanel;
2373
2556
  const builtInPanel = builtInSongPanel ?? builtInArtistPanel;
2374
2557
  const [uncontrolledCollapsed, setUncontrolledCollapsed] = useState9(false);
2375
2558
  const [uncontrolledExpanded, setUncontrolledExpanded] = useState9(false);
@@ -2385,7 +2568,7 @@ function GenreTreeView({
2385
2568
  };
2386
2569
  useEffect5(() => {
2387
2570
  setUncontrolledCollapsed(false);
2388
- }, [selectedNodeId, artistUuid, songUuid]);
2571
+ }, [selectedGenreUuid, artistUuid, songUuid]);
2389
2572
  const [isWheelRevealed, setIsWheelRevealed] = useState9(false);
2390
2573
  const usesHandoff = viewMode === "wheel" || viewMode === "pop-core";
2391
2574
  const isTreeReady = !isLoading && (!usesHandoff || isWheelRevealed);
@@ -2398,140 +2581,99 @@ function GenreTreeView({
2398
2581
  const obscuredInset = useObscuredInset(panelRef);
2399
2582
  const hostPanel = { obscuredInset, onNodeStyleResolverChange: handleNodeStyleResolverChange };
2400
2583
  const nodeMetric = isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric;
2401
- const treeNodes = useMemo7(() => toTreeNodes(visibleGenrePlaylists, nodeMetric), [visibleGenrePlaylists, nodeMetric]);
2584
+ const treeNodes = useMemo8(() => toTreeNodes(visibleGenrePlaylists, nodeMetric), [visibleGenrePlaylists, nodeMetric]);
2402
2585
  const relations = useGenreNodeRelations(
2403
2586
  treeNodes,
2404
2587
  !overlayPanel && hasSelectedGenreNode ? selectedNodeId : null,
2405
2588
  resolveStyle
2406
2589
  );
2407
- const [selectionHistory, setSelectionHistory] = useState9({
2408
- current: selectedNodeId,
2409
- previous: null
2410
- });
2411
- if (selectionHistory.current !== selectedNodeId) {
2412
- setSelectionHistory({
2413
- current: selectedNodeId,
2414
- previous: selectionHistory.current ?? selectionHistory.previous
2415
- });
2590
+ const [selectionHistory, setSelectionHistory] = useState9({ current: selectedGenreUuid, previous: null });
2591
+ if (selectionHistory.current !== selectedGenreUuid) {
2592
+ setSelectionHistory({ current: selectedGenreUuid, previous: selectionHistory.current ?? selectionHistory.previous });
2416
2593
  }
2417
- const previousPlaylist = selectionHistory.previous !== null && selectionHistory.previous !== selectedNodeId ? genrePlaylistByNodeId.get(selectionHistory.previous) : void 0;
2418
- const handlePrevious = previousPlaylist && visibleGenrePlaylists.includes(previousPlaylist) ? () => handleGenreSearchSelect(previousPlaylist) : void 0;
2594
+ const handlePrevious = selectionHistory.previous ? () => selectGenre(selectionHistory.previous) : void 0;
2419
2595
  const { showPopup, hidePopup } = usePopup5();
2596
+ const reopenPanel = () => {
2597
+ setUncontrolledCollapsed(false);
2598
+ onCollapsedChange?.(false);
2599
+ };
2420
2600
  const handleSelectedNodeChange = (node) => {
2601
+ reopenPanel();
2421
2602
  if (node !== null && songUuid !== null) changeSong(null);
2422
2603
  selectNode(node);
2423
2604
  };
2424
2605
  const handleGenreSearchClear = clearSelection;
2425
- const handleGenreSearchSelect = (genrePlaylist) => {
2606
+ const handleGenreSearchSelect = (genrePlaylist2) => {
2607
+ reopenPanel();
2426
2608
  if (songUuid !== null) changeSong(null);
2427
- selectGenrePlaylist(genrePlaylist);
2609
+ selectGenrePlaylist(genrePlaylist2);
2428
2610
  };
2429
2611
  const selectedUuid = selectedGenrePlaylist?.uuid;
2430
- const selectedChildren = selectedUuid ? visibleGenrePlaylists.filter((genrePlaylist) => genrePlaylist.parent?.uuid === selectedUuid) : [];
2612
+ const selectedChildren = selectedUuid ? visibleGenrePlaylists.filter((genrePlaylist2) => genrePlaylist2.parent?.uuid === selectedUuid) : [];
2431
2613
  const canZoomIn = viewMode === "pop-core" && focus.onFocusGenreChange !== void 0 && selectedUuid !== void 0 && focus.focusGenreUuid !== selectedUuid && selectedChildren.length > 0 && selectedChildren.filter((child) => child.criteria?.side === "pop").length <= 1;
2432
2614
  const handleGenreSearchZoomIn = () => {
2433
2615
  if (selectedUuid) focus.onFocusGenreChange?.(selectedUuid, "zoom-in");
2434
2616
  };
2435
2617
  const zoomInRef = useRef3(void 0);
2436
2618
  zoomInRef.current = canZoomIn ? handleGenreSearchZoomIn : void 0;
2437
- const selectGenreFromPanel = (nodeId) => {
2438
- const genrePlaylist = genrePlaylistByNodeId.get(nodeId);
2439
- if (genrePlaylist) handleGenreSearchSelect(genrePlaylist);
2440
- };
2441
- const renderGenreBody = (relation) => {
2442
- const { node } = relation;
2443
- const family = /* @__PURE__ */ jsx33(
2444
- GenreNodeFamily,
2445
- {
2446
- node,
2447
- fill: relation.fill,
2448
- textColor: relation.textColor,
2449
- parentNode: relation.parentNode,
2450
- childNodes: relation.childNodes,
2451
- onSelectNode: selectGenreFromPanel
2452
- }
2453
- );
2454
- const ancestors = /* @__PURE__ */ jsx33(GenreNodeAncestors, { ancestorNodes: relation.ancestorNodes, onSelectNode: selectGenreFromPanel });
2455
- const genrePlaylist = genrePlaylistByNodeId.get(node.id);
2456
- const criteria = genrePlaylist?.criteria;
2457
- if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ jsxs25(Fragment13, { children: [
2458
- family,
2459
- ancestors
2460
- ] }) };
2461
- const genreUuid = criteria.uuid;
2462
- const parentPlaylist = visibleGenrePlaylists.find(({ uuid }) => uuid === genrePlaylist.parent?.uuid);
2463
- const canToggleSide = !readOnly && parentPlaylist !== void 0 && parentPlaylist.parent === null;
2464
- const side = criteria.side ?? "core";
2465
- const shareTarget = {
2466
- kind: "genre",
2467
- genrePlaylistUuid: genrePlaylist.uuid,
2468
- name: criteria.name,
2469
- scope,
2470
- artistUuid
2471
- };
2472
- const shareUrl = getShareUrlRef.current?.(shareTarget) ?? void 0;
2473
- const popSibling = canToggleSide && side === "core" ? visibleGenrePlaylists.find(
2474
- (sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist.uuid && sibling.criteria?.side === "pop"
2475
- ) : void 0;
2476
- return {
2477
- primary: /* @__PURE__ */ jsxs25(Fragment13, { children: [
2478
- /* @__PURE__ */ jsx33(
2479
- GenreInfoPanelBody,
2619
+ const genrePlaylist = selectedGenrePlaylist;
2620
+ const criteria = genrePlaylist?.criteria;
2621
+ const parentPlaylist = visibleGenrePlaylists.find(({ uuid }) => uuid === genrePlaylist?.parent?.uuid);
2622
+ const canToggleSide = !readOnly && criteria !== void 0 && criteria !== null && parentPlaylist !== void 0 && parentPlaylist.parent === null;
2623
+ const side = criteria?.side ?? "core";
2624
+ const popSibling = canToggleSide && side === "core" ? visibleGenrePlaylists.find((sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist?.uuid && sibling.criteria?.side === "pop") : void 0;
2625
+ const shareTarget = selectedGenreUuid ? { kind: "genre", genreUuid: selectedGenreUuid, name: criteria?.name ?? "Genre", scope, artistUuid } : void 0;
2626
+ const shareUrl = shareTarget ? getShareUrlRef.current?.(shareTarget) ?? void 0 : void 0;
2627
+ const genreContent = useGenreDetailContent({
2628
+ genreUuid: selectedGenreUuid,
2629
+ scope,
2630
+ getBackendBaseUrl,
2631
+ onSelectGenre: (uuid) => {
2632
+ reopenPanel();
2633
+ selectGenre(uuid);
2634
+ },
2635
+ expanded: isExpanded,
2636
+ playback,
2637
+ genrePlaylist: selectedGenrePlaylist,
2638
+ band: {
2639
+ ...relations ? { node: relations.node } : {},
2640
+ shareTarget: shareUrl ? shareTarget : void 0,
2641
+ shareUrl,
2642
+ onZoomIn: zoomInRef.current,
2643
+ onAddChild: readOnly || !criteria ? void 0 : () => handleGenreCreationAction(criteria),
2644
+ onRename: readOnly || !criteria ? void 0 : () => handleGenreRenameAction(criteria),
2645
+ onChangeParent: readOnly || !genrePlaylist || !criteria ? void 0 : () => showPopup(
2646
+ /* @__PURE__ */ jsx34(
2647
+ ChangeParentPopup,
2480
2648
  {
2481
- family: /* @__PURE__ */ jsxs25(Fragment13, { children: [
2482
- family,
2483
- ancestors
2484
- ] }),
2485
- genreUuid,
2486
- genrePlaylist,
2487
- catalogue: genrePlaylist.catalogue,
2488
- wikidataId: criteria.wikidataId,
2649
+ genrePlaylist: { ...genrePlaylist, criteria },
2650
+ genrePlaylists: visibleGenrePlaylists,
2489
2651
  scope,
2490
2652
  getBackendBaseUrl,
2491
- playback,
2492
- band: {
2493
- node,
2494
- shareTarget: shareUrl ? shareTarget : void 0,
2495
- shareUrl,
2496
- onZoomIn: zoomInRef.current,
2497
- onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
2498
- onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
2499
- onChangeParent: readOnly ? void 0 : () => showPopup(
2500
- /* @__PURE__ */ jsx33(
2501
- ChangeParentPopup,
2502
- {
2503
- genrePlaylist: { ...genrePlaylist, criteria },
2504
- genrePlaylists: visibleGenrePlaylists,
2505
- scope,
2506
- getBackendBaseUrl,
2507
- onClose: hidePopup,
2508
- onParentChanged
2509
- }
2510
- )
2511
- ),
2512
- side,
2513
- onToggleSide: canToggleSide ? () => updateGenre({ uuid: genreUuid, data: { side: side === "pop" ? "core" : "pop" } }) : void 0,
2514
- toggleSideDisabledReason: popSibling ? `${popSibling.name} is already the pop side of ${parentPlaylist?.name}` : void 0,
2515
- additionalActions
2516
- },
2517
- extras: {
2518
- criteriaOverviewSchema,
2519
- renderGenreDetailExtras,
2520
- renderArtistLink,
2521
- renderSongLink: resolvedRenderSongLink,
2522
- artist: filterArtist,
2523
- renderSummary,
2524
- renderEssentialTracks,
2525
- renderSongLeading,
2526
- renderSongActions,
2527
- renderTracksToolbar
2528
- }
2653
+ onClose: hidePopup,
2654
+ onParentChanged
2529
2655
  }
2530
- ),
2531
- renderDangerZone?.(node)
2532
- ] })
2533
- };
2534
- };
2656
+ )
2657
+ ),
2658
+ side,
2659
+ onToggleSide: canToggleSide && criteria ? () => updateGenre({ uuid: criteria.uuid, data: { side: side === "pop" ? "core" : "pop" } }) : void 0,
2660
+ toggleSideDisabledReason: popSibling ? `${popSibling.name} is already the pop side of ${parentPlaylist?.name}` : void 0,
2661
+ additionalActions
2662
+ },
2663
+ extras: {
2664
+ criteriaOverviewSchema,
2665
+ renderGenreDetailExtras,
2666
+ renderArtistLink,
2667
+ renderSongLink: resolvedRenderSongLink,
2668
+ artist: filterArtist,
2669
+ renderSummary,
2670
+ renderEssentialTracks,
2671
+ renderSongLeading,
2672
+ renderSongActions,
2673
+ renderTracksToolbar
2674
+ },
2675
+ trailing: relations ? renderDangerZone?.(relations.node) : void 0
2676
+ });
2535
2677
  const isRotationControlled = controlledAllowWheelRotation !== void 0;
2536
2678
  const allowWheelRotation = controlledAllowWheelRotation ?? internalAllowWheelRotation;
2537
2679
  let activePanel = null;
@@ -2545,16 +2687,16 @@ function GenreTreeView({
2545
2687
  onClose: overlayPanel.onClose,
2546
2688
  compact: false
2547
2689
  };
2548
- } else if (relations) {
2549
- const { primary } = renderGenreBody(relations);
2690
+ } else if (selectedGenreUuid) {
2550
2691
  activePanel = {
2551
- title: relations.node.name,
2552
- content: primary,
2692
+ title: genreContent.title,
2693
+ content: genreContent.content,
2694
+ aside: genreContent.aside,
2553
2695
  expanded: isExpanded,
2554
2696
  onExpandedChange: changeExpanded,
2555
2697
  onClose: clearSelection,
2556
- compact: true,
2557
- headerAccent: { background: relations.fill, color: relations.textColor }
2698
+ compact: false,
2699
+ headerAccent: relations ? { background: relations.fill, color: relations.textColor } : genreContent.headerAccent
2558
2700
  };
2559
2701
  } else if (builtInPanel) {
2560
2702
  activePanel = {
@@ -2567,36 +2709,36 @@ function GenreTreeView({
2567
2709
  compact: true
2568
2710
  };
2569
2711
  }
2570
- const isPanelShown = isTreeReady && activePanel !== null;
2712
+ const isPanelShown = activePanel !== null && (selectedGenreUuid !== null || isTreeReady);
2571
2713
  const detailTab = placement === "regions" && activePanel?.onExpandedChange ? {
2572
2714
  edge: activePanel.expanded ? { label: "Shrink panel", onClick: () => activePanel.onExpandedChange?.(false) } : isCollapsed ? null : { label: "Hide panel", onClick: () => changeCollapsed(true) },
2573
2715
  center: activePanel.expanded ? null : isCollapsed ? { label: "Show panel", onClick: () => changeCollapsed(false) } : activePanel.onExpandedChange ? { label: "Extend panel", onClick: () => activePanel.onExpandedChange?.(true) } : null
2574
2716
  } : void 0;
2575
- const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ jsx33(
2576
- IconButton,
2717
+ const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ jsx34(
2718
+ IconButton2,
2577
2719
  {
2578
2720
  "aria-label": activePanel.expanded ? "Shrink panel" : "Extend panel",
2579
2721
  style: activePanel.headerAccent && { color: "inherit" },
2580
2722
  onClick: () => activePanel.onExpandedChange?.(!activePanel.expanded),
2581
- children: activePanel.expanded ? /* @__PURE__ */ jsx33(Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ jsx33(Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
2723
+ children: activePanel.expanded ? /* @__PURE__ */ jsx34(Minimize22, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ jsx34(Maximize22, { "aria-hidden": "true", className: "h-4 w-4" })
2582
2724
  }
2583
2725
  ) : null;
2584
2726
  const renderArtistFilter = () => {
2585
2727
  if (isLoading || !filterArtist) return null;
2586
- return /* @__PURE__ */ jsxs25("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
2587
- /* @__PURE__ */ jsx33(
2728
+ return /* @__PURE__ */ jsxs26("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
2729
+ /* @__PURE__ */ jsx34(
2588
2730
  "button",
2589
2731
  {
2590
2732
  type: "button",
2591
2733
  className: "py-1 pl-3 pr-2 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] hover:underline",
2592
2734
  "aria-label": `Show artist ${filterArtist.name}`,
2593
2735
  onClick: () => {
2594
- if (selectedNodeId !== null) clearSelection();
2736
+ if (selectedGenreUuid !== null) clearSelection();
2595
2737
  },
2596
2738
  children: filterArtist.name
2597
2739
  }
2598
2740
  ),
2599
- /* @__PURE__ */ jsx33(
2741
+ /* @__PURE__ */ jsx34(
2600
2742
  "button",
2601
2743
  {
2602
2744
  type: "button",
@@ -2608,8 +2750,8 @@ function GenreTreeView({
2608
2750
  )
2609
2751
  ] });
2610
2752
  };
2611
- const actions = /* @__PURE__ */ jsxs25(Fragment13, { children: [
2612
- !isLoading && /* @__PURE__ */ jsx33(
2753
+ const actions = /* @__PURE__ */ jsxs26(Fragment14, { children: [
2754
+ !isLoading && /* @__PURE__ */ jsx34(
2613
2755
  GenreTreeSearch,
2614
2756
  {
2615
2757
  artistBaseUrl,
@@ -2627,7 +2769,12 @@ function GenreTreeView({
2627
2769
  SearchGroups,
2628
2770
  genrePlaylists: visibleGenrePlaylists,
2629
2771
  onSelect: handleGenreSearchSelect,
2630
- selectedName: selectedGenrePlaylist?.name ?? (typeof builtInPanel?.title === "string" ? builtInPanel.title : null),
2772
+ onSelectGenre: (uuid) => {
2773
+ reopenPanel();
2774
+ if (songUuid) changeSong(null);
2775
+ selectGenre(uuid);
2776
+ },
2777
+ selectedName: (selectedGenreUuid ? genreContent.title : null) ?? (typeof builtInPanel?.title === "string" ? builtInPanel.title : null),
2631
2778
  onClear: builtInSongPanel ? () => changeSong(null) : builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
2632
2779
  onPrevious: handlePrevious,
2633
2780
  onQueryChange: onSearchQueryChange,
@@ -2635,9 +2782,9 @@ function GenreTreeView({
2635
2782
  extraGroups: extraSearchGroups
2636
2783
  }
2637
2784
  ),
2638
- !isLoading && filterArtist && /* @__PURE__ */ jsx33("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
2639
- /* @__PURE__ */ jsxs25("div", { className: "contents max-md:flex max-md:w-full max-md:items-center max-md:gap-2 max-md:overflow-x-auto max-md:[scrollbar-width:none] max-md:[&>*]:shrink-0", children: [
2640
- !isLoading && !isControlled && /* @__PURE__ */ jsx33(
2785
+ !isLoading && filterArtist && /* @__PURE__ */ jsx34("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
2786
+ /* @__PURE__ */ jsxs26("div", { className: "contents max-md:flex max-md:w-full max-md:items-center max-md:gap-2 max-md:overflow-x-auto max-md:[scrollbar-width:none] max-md:[&>*]:shrink-0", children: [
2787
+ !isLoading && !isControlled && /* @__PURE__ */ jsx34(
2641
2788
  SegmentedControl2,
2642
2789
  {
2643
2790
  "aria-label": "Tree view mode",
@@ -2651,13 +2798,13 @@ function GenreTreeView({
2651
2798
  ]
2652
2799
  }
2653
2800
  ),
2654
- !isLoading && showDisplayOptions && /* @__PURE__ */ jsx33(
2801
+ !isLoading && showDisplayOptions && /* @__PURE__ */ jsx34(
2655
2802
  "div",
2656
2803
  {
2657
2804
  className: "flex items-center gap-1",
2658
2805
  role: "group",
2659
2806
  "aria-label": "Tree display options",
2660
- children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ jsx33(
2807
+ children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ jsx34(
2661
2808
  Button,
2662
2809
  {
2663
2810
  variant: allowWheelRotation ? "default" : "outline",
@@ -2668,7 +2815,7 @@ function GenreTreeView({
2668
2815
  )
2669
2816
  }
2670
2817
  ),
2671
- !isLoading && !readOnly && showAddRoot && /* @__PURE__ */ jsx33(
2818
+ !isLoading && !readOnly && showAddRoot && /* @__PURE__ */ jsx34(
2672
2819
  IconTextButton,
2673
2820
  {
2674
2821
  icon: Plus2,
@@ -2678,34 +2825,34 @@ function GenreTreeView({
2678
2825
  )
2679
2826
  ] })
2680
2827
  ] });
2681
- return /* @__PURE__ */ jsxs25(
2828
+ return /* @__PURE__ */ jsxs26(
2682
2829
  "div",
2683
2830
  {
2684
2831
  "data-left-panel": isPanelShown && !isCollapsed ? void 0 : "hidden",
2685
2832
  className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
2686
2833
  children: [
2687
- /* @__PURE__ */ jsx33(
2834
+ /* @__PURE__ */ jsx34(
2688
2835
  "div",
2689
2836
  {
2690
2837
  className: `actions-container absolute z-30 flex items-center gap-2 ${placement === "regions" ? "left-[calc(var(--layout-gutter)+var(--layout-panel-inset))] top-[calc(var(--layout-gutter)+var(--layout-panel-inset))] max-md:right-[calc(var(--layout-gutter)+var(--layout-panel-inset))] max-md:top-[var(--layout-band-top)] max-md:flex-col max-md:items-stretch" : "left-5 top-5"}`,
2691
2838
  children: actions
2692
2839
  }
2693
2840
  ),
2694
- !isLoading && filterArtist && /* @__PURE__ */ jsx33(
2841
+ !isLoading && filterArtist && /* @__PURE__ */ jsx34(
2695
2842
  "div",
2696
2843
  {
2697
2844
  className: `absolute left-1/2 z-30 hidden -translate-x-1/2 md:block ${placement === "regions" ? "top-[calc(var(--layout-gutter)+var(--layout-panel-inset))]" : "top-5"}`,
2698
2845
  children: renderArtistFilter()
2699
2846
  }
2700
2847
  ),
2701
- /* @__PURE__ */ jsx33("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ jsxs25("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
2702
- !isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ jsx33("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
2703
- isLoading ? /* @__PURE__ */ jsx33(GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
2848
+ /* @__PURE__ */ jsx34("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ jsxs26("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
2849
+ !isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ jsx34("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
2850
+ isLoading ? /* @__PURE__ */ jsx34(GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
2704
2851
  // The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
2705
2852
  // and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
2706
2853
  // Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
2707
2854
  // The panel's own Zoom in button is hidden: the search bar carries it.
2708
- /* @__PURE__ */ jsx33("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ jsx33(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ jsx33(GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ jsx33(
2855
+ /* @__PURE__ */ jsx34("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ jsx34(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ jsx34(GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ jsx34(
2709
2856
  GenrePlaylistTreeWheel,
2710
2857
  {
2711
2858
  scope,
@@ -2722,9 +2869,9 @@ function GenreTreeView({
2722
2869
  allowWheelRotation,
2723
2870
  sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
2724
2871
  busy,
2725
- floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ jsxs25("div", { className: "flex items-center gap-2", children: [
2872
+ floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ jsxs26("div", { className: "flex items-center gap-2", children: [
2726
2873
  floatingControlsLeading,
2727
- canSizeByViews && /* @__PURE__ */ jsx33(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2874
+ canSizeByViews && /* @__PURE__ */ jsx34(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2728
2875
  ] }) : void 0,
2729
2876
  defaultViewport,
2730
2877
  viewportRestore,
@@ -2733,7 +2880,7 @@ function GenreTreeView({
2733
2880
  ) }) })
2734
2881
  ) : viewMode === "pop-core" ? (
2735
2882
  // Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
2736
- /* @__PURE__ */ jsx33("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ jsx33(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ jsx33(GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ jsx33(
2883
+ /* @__PURE__ */ jsx34("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ jsx34(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ jsx34(GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ jsx34(
2737
2884
  GenrePlaylistTreeWheelRadialPopCore,
2738
2885
  {
2739
2886
  scope,
@@ -2750,9 +2897,9 @@ function GenreTreeView({
2750
2897
  allowWheelRotation,
2751
2898
  sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
2752
2899
  busy,
2753
- floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ jsxs25("div", { className: "flex items-center gap-2", children: [
2900
+ floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ jsxs26("div", { className: "flex items-center gap-2", children: [
2754
2901
  floatingControlsLeading,
2755
- canSizeByViews && /* @__PURE__ */ jsx33(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2902
+ canSizeByViews && /* @__PURE__ */ jsx34(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
2756
2903
  ] }) : void 0,
2757
2904
  defaultViewport,
2758
2905
  viewportRestore,
@@ -2760,7 +2907,7 @@ function GenreTreeView({
2760
2907
  ...focus
2761
2908
  }
2762
2909
  ) }) })
2763
- ) : viewMode === "outline" ? /* @__PURE__ */ jsx33("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ jsx33(
2910
+ ) : viewMode === "outline" ? /* @__PURE__ */ jsx34("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ jsx34(
2764
2911
  GenrePlaylistTreeWheelRadialPopCore,
2765
2912
  {
2766
2913
  outline: true,
@@ -2776,13 +2923,13 @@ function GenreTreeView({
2776
2923
  selectedNodeId,
2777
2924
  readOnly
2778
2925
  }
2779
- ) }) : /* @__PURE__ */ jsx33("div", { className: "tree-container relative flex flex-col gap-4 w-full pt-14 overflow-x-auto overflow-y-auto text-[var(--color-text)]", children: Object.entries(groupedGenrePlaylistsByRoot).map(
2926
+ ) }) : /* @__PURE__ */ jsx34("div", { className: "tree-container relative flex flex-col gap-4 w-full pt-14 overflow-x-auto overflow-y-auto text-[var(--color-text)]", children: Object.entries(groupedGenrePlaylistsByRoot).map(
2780
2927
  ([uuid, genrePlaylistTreePerRoot]) => {
2781
- return /* @__PURE__ */ jsx33(
2928
+ return /* @__PURE__ */ jsx34(
2782
2929
  "div",
2783
2930
  {
2784
2931
  className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
2785
- children: /* @__PURE__ */ jsx33("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ jsx33(
2932
+ children: /* @__PURE__ */ jsx34("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ jsx34(
2786
2933
  GenrePlaylistTreePerRoot,
2787
2934
  {
2788
2935
  scope,
@@ -2808,8 +2955,8 @@ function GenreTreeView({
2808
2955
  );
2809
2956
  }
2810
2957
  ) }),
2811
- /* @__PURE__ */ jsx33(
2812
- DetailPanel,
2958
+ /* @__PURE__ */ jsx34(
2959
+ DetailPanel2,
2813
2960
  {
2814
2961
  ref: panelRef,
2815
2962
  side: "left",
@@ -2819,13 +2966,13 @@ function GenreTreeView({
2819
2966
  headerAccent: activePanel?.headerAccent,
2820
2967
  headerActions: expandButton,
2821
2968
  onClose: activePanel?.onClose ?? noop,
2822
- collapsed: isCollapsed || !isTreeReady || !activePanel,
2969
+ collapsed: isCollapsed || !isPanelShown,
2823
2970
  expanded: activePanel?.expanded,
2824
2971
  onExpandedChange: activePanel?.onExpandedChange,
2825
2972
  aside: activePanel?.aside,
2826
2973
  edgeTab: detailTab,
2827
- headerClassName: activePanel?.compact ? "pt-[calc(var(--layout-search-h)+var(--layout-panel-inset)+0.5rem)]" : void 0,
2828
- children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ jsxs25(Fragment13, { children: [
2974
+ headerClassName: "pt-[calc(var(--layout-search-h)+var(--layout-panel-inset)+0.5rem)]",
2975
+ children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ jsxs26(Fragment14, { children: [
2829
2976
  activePanel.content,
2830
2977
  activePanel.aside
2831
2978
  ] }) : activePanel?.content
@@ -2837,145 +2984,6 @@ function GenreTreeView({
2837
2984
  );
2838
2985
  }
2839
2986
 
2840
- // src/genre-tree/GenreDetailPanel.tsx
2841
- import { useMemo as useMemo8 } from "react";
2842
- import {
2843
- ACCENT_TEXT_COLOR,
2844
- GenreNodeAncestors as GenreNodeAncestors2,
2845
- GenreNodeFamily as GenreNodeFamily2,
2846
- getGenreTreeColor as getGenreTreeColor2,
2847
- TEXT_COLOR,
2848
- tintSurface
2849
- } from "@behindthemusictree/genre-tree-view";
2850
- import { DetailPanel as DetailPanel2, IconButton as IconButton2, Skeleton as Skeleton7 } from "@behindthemusictree/ui";
2851
- import { Maximize2 as Maximize22, Minimize2 as Minimize22 } from "lucide-react";
2852
- import {
2853
- useFetchGenreDetail,
2854
- genreMusicCount as genreMusicCount2,
2855
- GenrePlaylistSummarySchema
2856
- } from "@behindthemusictree/app-kit/genre-tree";
2857
- import { Fragment as Fragment14, jsx as jsx34, jsxs as jsxs26 } from "react/jsx-runtime";
2858
- var RELATIVE_ITEM_COUNT = 1;
2859
- function toNode(genre, parentId) {
2860
- return {
2861
- id: genre.uuid,
2862
- parentId,
2863
- name: genre.name,
2864
- itemCount: RELATIVE_ITEM_COUNT,
2865
- side: genre.side ?? void 0,
2866
- wikidataId: genre.wikidataId
2867
- };
2868
- }
2869
- function GenreDetailPanel({
2870
- genreUuid,
2871
- scope,
2872
- getBackendBaseUrl,
2873
- onClose,
2874
- onSelectGenre,
2875
- onZoomIn,
2876
- shareUrl,
2877
- shareTarget,
2878
- collapsed,
2879
- expanded,
2880
- onExpandedChange,
2881
- edgeTab,
2882
- extras
2883
- }) {
2884
- const { data: detail, isPending, isError } = useFetchGenreDetail(genreUuid, scope, getBackendBaseUrl);
2885
- const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: [] });
2886
- const view = useMemo8(() => {
2887
- if (!detail) return null;
2888
- const rootColor = getGenreTreeColor2(detail.root.wikidataId);
2889
- const relative = (genre, parentId) => ({
2890
- node: toNode(genre, parentId),
2891
- fill: tintSurface(rootColor),
2892
- textColor: TEXT_COLOR
2893
- });
2894
- const ancestors2 = [...detail.ascendants].sort((a, b) => b.degree - a.degree);
2895
- return {
2896
- node: {
2897
- id: detail.uuid,
2898
- parentId: detail.parent?.uuid ?? null,
2899
- name: detail.name,
2900
- itemCount: scope === "reference" ? genreMusicCount2(GenrePlaylistSummarySchema.parse(detail.criteriaPlaylist)) : detail.tracksCount,
2901
- wikidataId: detail.wikidataId
2902
- },
2903
- fill: rootColor,
2904
- parentNode: detail.parent ? relative(detail.parent, null) : null,
2905
- childNodes: detail.children.map((child) => relative(child, detail.uuid)),
2906
- ancestorNodes: ancestors2.map(
2907
- ({ ascendant }, index) => relative(ascendant, index === 0 ? null : ancestors2[index - 1].ascendant.uuid)
2908
- )
2909
- };
2910
- }, [detail, scope]);
2911
- if (isError) {
2912
- return /* @__PURE__ */ jsx34("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
2913
- }
2914
- if (isPending || !detail || !view) {
2915
- return /* @__PURE__ */ jsxs26("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
2916
- /* @__PURE__ */ jsx34(Skeleton7, { className: "h-6 w-2/3" }),
2917
- /* @__PURE__ */ jsx34(Skeleton7, { className: "h-4 w-full" }),
2918
- /* @__PURE__ */ jsx34(Skeleton7, { className: "h-4 w-3/4" })
2919
- ] });
2920
- }
2921
- const selectGenre = (nodeId) => onSelectGenre(nodeId);
2922
- const family = /* @__PURE__ */ jsx34(
2923
- GenreNodeFamily2,
2924
- {
2925
- node: view.node,
2926
- fill: view.fill,
2927
- textColor: ACCENT_TEXT_COLOR,
2928
- parentNode: view.parentNode,
2929
- childNodes: view.childNodes,
2930
- onSelectNode: selectGenre
2931
- }
2932
- );
2933
- const ancestors = /* @__PURE__ */ jsx34(GenreNodeAncestors2, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
2934
- return /* @__PURE__ */ jsxs26(
2935
- DetailPanel2,
2936
- {
2937
- side: "left",
2938
- title: view.node.name,
2939
- headerAccent: { background: view.fill, color: ACCENT_TEXT_COLOR },
2940
- headerActions: onExpandedChange ? /* @__PURE__ */ jsx34(
2941
- IconButton2,
2942
- {
2943
- "aria-label": expanded ? "Shrink panel" : "Extend panel",
2944
- style: { color: "inherit" },
2945
- onClick: () => onExpandedChange(!expanded),
2946
- children: expanded ? /* @__PURE__ */ jsx34(Minimize22, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ jsx34(Maximize22, { "aria-hidden": "true", className: "h-4 w-4" })
2947
- }
2948
- ) : void 0,
2949
- onClose,
2950
- collapsed,
2951
- expanded,
2952
- onExpandedChange,
2953
- edgeTab,
2954
- children: [
2955
- /* @__PURE__ */ jsx34(
2956
- GenreInfoPanelBody,
2957
- {
2958
- family: /* @__PURE__ */ jsxs26(Fragment14, { children: [
2959
- family,
2960
- ancestors
2961
- ] }),
2962
- genreUuid: detail.uuid,
2963
- genrePlaylist: scope === "reference" ? GenrePlaylistSummarySchema.parse(detail.criteriaPlaylist) : { ...detail.criteriaPlaylist, tracksCount: detail.tracksCount },
2964
- catalogue: detail.catalogue,
2965
- wikidataId: detail.wikidataId,
2966
- scope,
2967
- getBackendBaseUrl,
2968
- playback,
2969
- band: { node: view.node, onZoomIn, shareUrl, shareTarget: shareUrl ? shareTarget : void 0 },
2970
- extras
2971
- }
2972
- ),
2973
- !expanded && ancestors
2974
- ]
2975
- }
2976
- );
2977
- }
2978
-
2979
2987
  // src/genre-tree/index.ts
2980
2988
  import {
2981
2989
  GenreTreeSkeleton,
@@ -3433,10 +3441,10 @@ export {
3433
3441
  GenreSearch,
3434
3442
  GenreNodeActionBand,
3435
3443
  GenreInfoPanelBody,
3444
+ GenreDetailPanel,
3436
3445
  TrackPositionPlayPause,
3437
3446
  TrackItem,
3438
3447
  GenreTreeView,
3439
- GenreDetailPanel,
3440
3448
  TrackListSidebar,
3441
3449
  PlayerPanel,
3442
3450
  Rating,
@@ -3445,4 +3453,4 @@ export {
3445
3453
  GenreTreeViewSkeleton2 as GenreTreeViewSkeleton,
3446
3454
  GenreTreeOutlineSkeleton
3447
3455
  };
3448
- //# sourceMappingURL=chunk-JP7PID7L.js.map
3456
+ //# sourceMappingURL=chunk-YC3WBUVO.js.map