@behindthemusictree/app-kit-ui 34.1.0 → 36.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.
- package/dist/{chunk-JP7PID7L.js → chunk-RTXLUHJY.js} +469 -453
- package/dist/chunk-RTXLUHJY.js.map +1 -0
- package/dist/genre-tree/index.cjs +518 -503
- package/dist/genre-tree/index.cjs.map +1 -1
- package/dist/genre-tree/index.d.ts +20 -16
- package/dist/genre-tree/index.js +1 -1
- package/dist/index.cjs +518 -503
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +3 -3
- package/dist/chunk-JP7PID7L.js.map +0 -1
|
@@ -46,13 +46,13 @@ __export(genre_tree_exports, {
|
|
|
46
46
|
module.exports = __toCommonJS(genre_tree_exports);
|
|
47
47
|
|
|
48
48
|
// src/genre-tree/GenreTreeView.tsx
|
|
49
|
-
var
|
|
49
|
+
var import_react18 = require("react");
|
|
50
50
|
var import_react_query3 = require("@tanstack/react-query");
|
|
51
|
-
var import_lucide_react11 = require("lucide-react");
|
|
52
51
|
var import_lucide_react12 = require("lucide-react");
|
|
53
|
-
var
|
|
54
|
-
var
|
|
55
|
-
var
|
|
52
|
+
var import_lucide_react13 = require("lucide-react");
|
|
53
|
+
var import_ui17 = require("@behindthemusictree/ui");
|
|
54
|
+
var import_genre_tree_view5 = require("@behindthemusictree/genre-tree-view");
|
|
55
|
+
var import_genre_tree28 = require("@behindthemusictree/app-kit/genre-tree");
|
|
56
56
|
var import_popup5 = require("@behindthemusictree/app-kit/popup");
|
|
57
57
|
|
|
58
58
|
// src/genre-tree/playlist-tree/size-metric.ts
|
|
@@ -915,6 +915,7 @@ function GenreTreeSearch({
|
|
|
915
915
|
artistSearch,
|
|
916
916
|
songSearch,
|
|
917
917
|
SearchGroups,
|
|
918
|
+
onSelectGenre,
|
|
918
919
|
extraGroups = [],
|
|
919
920
|
onQueryChange,
|
|
920
921
|
onFocus,
|
|
@@ -940,9 +941,16 @@ function GenreTreeSearch({
|
|
|
940
941
|
extraGroups: [...extraGroups, ...hostGroups, artistGroup, songGroup].filter((group) => group !== void 0)
|
|
941
942
|
}
|
|
942
943
|
);
|
|
943
|
-
return SearchGroups ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SearchGroups, { query: deferredQuery, focused, children: render }) : render([]);
|
|
944
|
+
return SearchGroups ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SearchGroups, { query: deferredQuery, focused, onSelectGenre, children: render }) : render([]);
|
|
944
945
|
}
|
|
945
946
|
|
|
947
|
+
// src/genre-tree/GenreDetailPanel.tsx
|
|
948
|
+
var import_react14 = require("react");
|
|
949
|
+
var import_genre_tree_view4 = require("@behindthemusictree/genre-tree-view");
|
|
950
|
+
var import_ui7 = require("@behindthemusictree/ui");
|
|
951
|
+
var import_lucide_react8 = require("lucide-react");
|
|
952
|
+
var import_genre_tree19 = require("@behindthemusictree/app-kit/genre-tree");
|
|
953
|
+
|
|
946
954
|
// src/genre-tree/GenreInfoPanelBody.tsx
|
|
947
955
|
var import_genre_tree18 = require("@behindthemusictree/app-kit/genre-tree");
|
|
948
956
|
var import_track_noun3 = require("@behindthemusictree/app-kit/track-noun");
|
|
@@ -1391,6 +1399,7 @@ var nextCursor = (next) => {
|
|
|
1391
1399
|
function GenrePlaylistTracks({
|
|
1392
1400
|
genreUuid,
|
|
1393
1401
|
genrePlaylist,
|
|
1402
|
+
genreName: selectedGenreName,
|
|
1394
1403
|
selection,
|
|
1395
1404
|
scope,
|
|
1396
1405
|
getBackendBaseUrl,
|
|
@@ -1406,14 +1415,18 @@ function GenrePlaylistTracks({
|
|
|
1406
1415
|
const { playerTrackObject, playState, handlePlayPauseAction } = (0, import_player4.usePlayer)();
|
|
1407
1416
|
const { showPopup } = (0, import_popup4.usePopup)();
|
|
1408
1417
|
const trackNoun = (0, import_track_noun.useTrackNoun)();
|
|
1409
|
-
const
|
|
1410
|
-
const
|
|
1418
|
+
const uuid = genrePlaylist?.uuid;
|
|
1419
|
+
const genreName = selectedGenreName ?? genrePlaylist?.name ?? "Genre";
|
|
1420
|
+
const { fetchBound } = (0, import_transport3.usePlaybackFetch)(getBackendBaseUrl);
|
|
1411
1421
|
const playback = (0, import_transport3.usePlaybackContext)();
|
|
1412
1422
|
const { data, isPending, isError, refetch, hasNextPage, isFetchingNextPage, fetchNextPage } = (0, import_react_query.useInfiniteQuery)({
|
|
1413
|
-
queryKey: [...scope === "me" ? import_genre_tree12.genrePlaylistQueryKeys.me.tracks(uuid) : artist ? [...import_genre_tree13.libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid, selection), "artist", artist.uuid] : import_genre_tree13.libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid, selection), ...playback.key ? [playback.key] : []],
|
|
1423
|
+
queryKey: [...scope === "me" ? uuid ? import_genre_tree12.genrePlaylistQueryKeys.me.tracks(uuid) : ["missing-personal-genre-playlist", genreUuid] : artist ? [...import_genre_tree13.libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid, selection), "artist", artist.uuid] : import_genre_tree13.libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid, selection), ...playback.key ? [playback.key] : []],
|
|
1414
1424
|
queryFn: async ({ pageParam }) => {
|
|
1415
|
-
if (scope === "me")
|
|
1416
|
-
|
|
1425
|
+
if (scope === "me") {
|
|
1426
|
+
if (!uuid) throw new Error("Personal genre has no playlist");
|
|
1427
|
+
return fetchGenrePlaylistTracksPage(uuid, scope, Number(pageParam));
|
|
1428
|
+
}
|
|
1429
|
+
const response = await fetchBound(
|
|
1417
1430
|
import_genre_tree13.libraryEndpoints.reference.songs.list(),
|
|
1418
1431
|
true,
|
|
1419
1432
|
false,
|
|
@@ -1425,7 +1438,9 @@ function GenrePlaylistTracks({
|
|
|
1425
1438
|
},
|
|
1426
1439
|
initialPageParam: scope === "me" ? 1 : "",
|
|
1427
1440
|
getNextPageParam: (lastPage) => lastPage.nextPage ?? void 0,
|
|
1428
|
-
enabled: playback.ready && (scope !== "me" || sessionRestored && !!session?.accessToken)
|
|
1441
|
+
enabled: playback.ready && (scope !== "me" || uuid !== void 0 && sessionRestored && !!session?.accessToken),
|
|
1442
|
+
retry: (failureCount, error) => failureCount < 1 && error instanceof DOMException && error.name === "AbortError",
|
|
1443
|
+
retryDelay: 0
|
|
1429
1444
|
});
|
|
1430
1445
|
const [sentinel, setSentinel] = (0, import_react12.useState)(null);
|
|
1431
1446
|
(0, import_react12.useEffect)(() => {
|
|
@@ -1447,7 +1462,7 @@ function GenrePlaylistTracks({
|
|
|
1447
1462
|
ordering: "title",
|
|
1448
1463
|
genreUuid,
|
|
1449
1464
|
...selection && { selection }
|
|
1450
|
-
}) : scope === "reference" ? playNewTrackListFromGenreCatalogue({ uuid: genreUuid, name:
|
|
1465
|
+
}) : 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"));
|
|
1451
1466
|
started.catch((error) => {
|
|
1452
1467
|
console.error("Failed to play genre playlist:", error);
|
|
1453
1468
|
showPopup(/* @__PURE__ */ (0, import_jsx_runtime23.jsx)(InternalErrorPopup, { errorCode: import_transport6.ErrorCode.CLIENT_UNKNOWN }));
|
|
@@ -1455,13 +1470,13 @@ function GenrePlaylistTracks({
|
|
|
1455
1470
|
};
|
|
1456
1471
|
return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ui5.PanelSection, { title: `${(0, import_track_noun.capitalize)(trackNoun.plural)}${total !== void 0 ? ` (${total})` : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { children: [
|
|
1457
1472
|
renderTracksToolbar?.(),
|
|
1458
|
-
isPending ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ui5.Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("p", { role: "alert", children: [
|
|
1473
|
+
scope === "me" && !genrePlaylist ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { children: "\u2014" }) : isPending ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ui5.Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("p", { role: "alert", children: [
|
|
1459
1474
|
"Couldn't load ",
|
|
1460
1475
|
trackNoun.plural,
|
|
1461
1476
|
".",
|
|
1462
1477
|
" ",
|
|
1463
1478
|
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
|
|
1464
|
-
] }) : tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { children: "\u2014" }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("ul", { className: "mt-1 max-h-64 overflow-y-auto text-[0.8125rem] leading-[1.45] [container-type:inline-size]", "aria-label": `${
|
|
1479
|
+
] }) : tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { children: "\u2014" }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("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: [
|
|
1465
1480
|
tracks.map((track, index) => {
|
|
1466
1481
|
const label = track.artists?.length ? `${track.title} \u2014 ${track.artists.map((artist2) => artist2.name).join(", ")}` : track.title;
|
|
1467
1482
|
const isCurrent = playerTrackObject?.track.id === track.uuid;
|
|
@@ -1528,6 +1543,7 @@ var import_jsx_runtime24 = require("react/jsx-runtime");
|
|
|
1528
1543
|
function GenreDetailExtras({
|
|
1529
1544
|
genreUuid,
|
|
1530
1545
|
genrePlaylist,
|
|
1546
|
+
genreName,
|
|
1531
1547
|
selection,
|
|
1532
1548
|
wikidataId,
|
|
1533
1549
|
scope,
|
|
@@ -1595,6 +1611,7 @@ function GenreDetailExtras({
|
|
|
1595
1611
|
{
|
|
1596
1612
|
genreUuid,
|
|
1597
1613
|
genrePlaylist,
|
|
1614
|
+
genreName,
|
|
1598
1615
|
selection,
|
|
1599
1616
|
scope,
|
|
1600
1617
|
getBackendBaseUrl,
|
|
@@ -1636,6 +1653,7 @@ function GenreInfoPanelBody({
|
|
|
1636
1653
|
genreUuid,
|
|
1637
1654
|
genrePlaylist,
|
|
1638
1655
|
catalogue,
|
|
1656
|
+
countsContext,
|
|
1639
1657
|
wikidataId,
|
|
1640
1658
|
scope,
|
|
1641
1659
|
getBackendBaseUrl,
|
|
@@ -1644,7 +1662,7 @@ function GenreInfoPanelBody({
|
|
|
1644
1662
|
extras
|
|
1645
1663
|
}) {
|
|
1646
1664
|
const noun = (0, import_track_noun3.useTrackNoun)();
|
|
1647
|
-
const summary = scope === "reference" ? import_genre_tree18.GenreCatalogueCountsSchema.parse(genrePlaylist) : void 0;
|
|
1665
|
+
const summary = scope === "reference" ? import_genre_tree18.GenreCatalogueCountsSchema.parse(genrePlaylist ?? catalogue) : void 0;
|
|
1648
1666
|
const selection = summary ? (0, import_genre_tree18.defaultGenreMusicSelection)(summary) : void 0;
|
|
1649
1667
|
const describeCounts = (counts) => {
|
|
1650
1668
|
const scopes = counts.genreAndSubgenresTracksCount;
|
|
@@ -1652,17 +1670,19 @@ function GenreInfoPanelBody({
|
|
|
1652
1670
|
};
|
|
1653
1671
|
return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_jsx_runtime25.Fragment, { children: [
|
|
1654
1672
|
toolbar,
|
|
1655
|
-
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(GenreNodeActionBand, { ...band, genrePlaylist, playback }),
|
|
1673
|
+
genrePlaylist ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(GenreNodeActionBand, { ...band, genrePlaylist, playback }) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(GenreActionBand, { ...band }),
|
|
1656
1674
|
summary && catalogue && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "px-3.5 py-3 text-sm text-[var(--color-text)]", children: [
|
|
1657
1675
|
/* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("p", { children: [
|
|
1658
1676
|
"Catalogue ",
|
|
1659
1677
|
noun.plural,
|
|
1678
|
+
countsContext ? ` (${countsContext})` : "",
|
|
1660
1679
|
": ",
|
|
1661
1680
|
describeCounts(catalogue)
|
|
1662
1681
|
] }),
|
|
1663
|
-
/* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("p", { children: [
|
|
1682
|
+
genrePlaylist && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("p", { children: [
|
|
1664
1683
|
"Playlist ",
|
|
1665
1684
|
noun.plural,
|
|
1685
|
+
countsContext ? ` (${countsContext})` : "",
|
|
1666
1686
|
": ",
|
|
1667
1687
|
describeCounts(summary)
|
|
1668
1688
|
] })
|
|
@@ -1675,6 +1695,7 @@ function GenreInfoPanelBody({
|
|
|
1675
1695
|
family,
|
|
1676
1696
|
genreUuid,
|
|
1677
1697
|
genrePlaylist,
|
|
1698
|
+
genreName: band.node.name,
|
|
1678
1699
|
selection,
|
|
1679
1700
|
wikidataId,
|
|
1680
1701
|
scope,
|
|
@@ -1684,10 +1705,160 @@ function GenreInfoPanelBody({
|
|
|
1684
1705
|
] });
|
|
1685
1706
|
}
|
|
1686
1707
|
|
|
1687
|
-
// src/genre-tree/
|
|
1688
|
-
var import_react14 = require("react");
|
|
1689
|
-
var import_genre_tree19 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1708
|
+
// src/genre-tree/GenreDetailPanel.tsx
|
|
1690
1709
|
var import_jsx_runtime26 = require("react/jsx-runtime");
|
|
1710
|
+
var RELATIVE_ITEM_COUNT = 1;
|
|
1711
|
+
function toNode(genre, parentId) {
|
|
1712
|
+
return {
|
|
1713
|
+
id: genre.uuid,
|
|
1714
|
+
parentId,
|
|
1715
|
+
name: genre.name,
|
|
1716
|
+
itemCount: RELATIVE_ITEM_COUNT,
|
|
1717
|
+
side: genre.side ?? void 0,
|
|
1718
|
+
wikidataId: genre.wikidataId
|
|
1719
|
+
};
|
|
1720
|
+
}
|
|
1721
|
+
function useGenreDetailContent({
|
|
1722
|
+
genreUuid,
|
|
1723
|
+
scope,
|
|
1724
|
+
getBackendBaseUrl,
|
|
1725
|
+
onSelectGenre,
|
|
1726
|
+
extras,
|
|
1727
|
+
band,
|
|
1728
|
+
playback: hostPlayback,
|
|
1729
|
+
trailing,
|
|
1730
|
+
genrePlaylist: hostPlaylist,
|
|
1731
|
+
expanded
|
|
1732
|
+
}) {
|
|
1733
|
+
const { data: detail, isPending, isError } = (0, import_genre_tree19.useFetchGenreDetail)(genreUuid, scope, getBackendBaseUrl);
|
|
1734
|
+
const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists: [], artist: extras?.artist });
|
|
1735
|
+
const playback = hostPlayback ?? defaultPlayback;
|
|
1736
|
+
const view = (0, import_react14.useMemo)(() => {
|
|
1737
|
+
if (!detail) return null;
|
|
1738
|
+
const rootColor = (0, import_genre_tree_view4.getGenreTreeColor)(detail.root.wikidataId);
|
|
1739
|
+
const relative = (genre, parentId) => ({
|
|
1740
|
+
node: toNode(genre, parentId),
|
|
1741
|
+
fill: (0, import_genre_tree_view4.tintSurface)(rootColor),
|
|
1742
|
+
textColor: import_genre_tree_view4.TEXT_COLOR
|
|
1743
|
+
});
|
|
1744
|
+
const ancestors2 = [...detail.ascendants].sort((a, b) => b.degree - a.degree);
|
|
1745
|
+
return {
|
|
1746
|
+
node: {
|
|
1747
|
+
id: detail.uuid,
|
|
1748
|
+
parentId: detail.parent?.uuid ?? null,
|
|
1749
|
+
name: detail.name,
|
|
1750
|
+
itemCount: scope === "reference" ? (0, import_genre_tree19.genreMusicCount)(detail.criteriaPlaylist ? import_genre_tree19.GenrePlaylistSummarySchema.parse(detail.criteriaPlaylist) : import_genre_tree19.GenreCatalogueCountsSchema.parse(detail.catalogue)) : detail.tracksCount,
|
|
1751
|
+
wikidataId: detail.wikidataId
|
|
1752
|
+
},
|
|
1753
|
+
fill: rootColor,
|
|
1754
|
+
parentNode: detail.parent ? relative(detail.parent, null) : null,
|
|
1755
|
+
childNodes: detail.children.map((child) => relative(child, detail.uuid)),
|
|
1756
|
+
ancestorNodes: ancestors2.map(
|
|
1757
|
+
({ ascendant }, index) => relative(ascendant, index === 0 ? null : ancestors2[index - 1].ascendant.uuid)
|
|
1758
|
+
)
|
|
1759
|
+
};
|
|
1760
|
+
}, [detail, scope]);
|
|
1761
|
+
if (isError) return {
|
|
1762
|
+
title: "Genre unavailable",
|
|
1763
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." })
|
|
1764
|
+
};
|
|
1765
|
+
if (isPending || !detail || !view) return {
|
|
1766
|
+
title: "Loading genre\u2026",
|
|
1767
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
|
|
1768
|
+
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_ui7.Skeleton, { className: "h-6 w-2/3" }),
|
|
1769
|
+
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_ui7.Skeleton, { className: "h-4 w-full" }),
|
|
1770
|
+
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_ui7.Skeleton, { className: "h-4 w-3/4" })
|
|
1771
|
+
] })
|
|
1772
|
+
};
|
|
1773
|
+
const selectGenre = (nodeId) => onSelectGenre(import_genre_tree19.GenreUuidSchema.parse(nodeId));
|
|
1774
|
+
const family = /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
|
|
1775
|
+
import_genre_tree_view4.GenreNodeFamily,
|
|
1776
|
+
{
|
|
1777
|
+
node: view.node,
|
|
1778
|
+
fill: view.fill,
|
|
1779
|
+
textColor: import_genre_tree_view4.ACCENT_TEXT_COLOR,
|
|
1780
|
+
parentNode: view.parentNode,
|
|
1781
|
+
childNodes: view.childNodes,
|
|
1782
|
+
onSelectNode: selectGenre
|
|
1783
|
+
}
|
|
1784
|
+
);
|
|
1785
|
+
const ancestors = /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_genre_tree_view4.GenreNodeAncestors, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
|
|
1786
|
+
return {
|
|
1787
|
+
title: detail.name,
|
|
1788
|
+
headerAccent: { background: view.fill, color: import_genre_tree_view4.ACCENT_TEXT_COLOR },
|
|
1789
|
+
aside: expanded ? ancestors : void 0,
|
|
1790
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_jsx_runtime26.Fragment, { children: [
|
|
1791
|
+
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
|
|
1792
|
+
GenreInfoPanelBody,
|
|
1793
|
+
{
|
|
1794
|
+
family: /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_jsx_runtime26.Fragment, { children: [
|
|
1795
|
+
family,
|
|
1796
|
+
!expanded && ancestors
|
|
1797
|
+
] }),
|
|
1798
|
+
genreUuid: detail.uuid,
|
|
1799
|
+
genrePlaylist: hostPlaylist ?? (detail.criteriaPlaylist === null ? null : scope === "reference" ? { ...import_genre_tree19.GenrePlaylistSummarySchema.parse(detail.criteriaPlaylist), criteria: import_genre_tree19.CriteriaMinimumSchema.parse(detail) } : { ...detail.criteriaPlaylist, criteria: import_genre_tree19.CriteriaMinimumSchema.parse(detail), tracksCount: detail.tracksCount }),
|
|
1800
|
+
catalogue: detail.catalogue,
|
|
1801
|
+
countsContext: !hostPlaylist && extras?.artist ? "all artists" : void 0,
|
|
1802
|
+
wikidataId: detail.wikidataId,
|
|
1803
|
+
scope,
|
|
1804
|
+
getBackendBaseUrl,
|
|
1805
|
+
playback,
|
|
1806
|
+
band: { node: view.node, ...band, shareTarget: band?.shareTarget?.kind === "genre" ? { ...band.shareTarget, name: detail.name } : band?.shareTarget },
|
|
1807
|
+
extras
|
|
1808
|
+
}
|
|
1809
|
+
),
|
|
1810
|
+
trailing
|
|
1811
|
+
] })
|
|
1812
|
+
};
|
|
1813
|
+
}
|
|
1814
|
+
function GenreDetailPanel({
|
|
1815
|
+
genreUuid,
|
|
1816
|
+
scope,
|
|
1817
|
+
getBackendBaseUrl,
|
|
1818
|
+
onClose,
|
|
1819
|
+
onSelectGenre,
|
|
1820
|
+
onZoomIn,
|
|
1821
|
+
shareUrl,
|
|
1822
|
+
shareTarget,
|
|
1823
|
+
collapsed,
|
|
1824
|
+
expanded,
|
|
1825
|
+
onExpandedChange,
|
|
1826
|
+
edgeTab,
|
|
1827
|
+
extras,
|
|
1828
|
+
headerClassName
|
|
1829
|
+
}) {
|
|
1830
|
+
const panel = useGenreDetailContent({
|
|
1831
|
+
genreUuid,
|
|
1832
|
+
scope,
|
|
1833
|
+
getBackendBaseUrl,
|
|
1834
|
+
onSelectGenre,
|
|
1835
|
+
extras,
|
|
1836
|
+
expanded,
|
|
1837
|
+
band: { onZoomIn, shareUrl, shareTarget: shareUrl ? shareTarget : void 0 }
|
|
1838
|
+
});
|
|
1839
|
+
return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
|
|
1840
|
+
import_ui7.DetailPanel,
|
|
1841
|
+
{
|
|
1842
|
+
side: "left",
|
|
1843
|
+
title: panel.title,
|
|
1844
|
+
headerAccent: panel.headerAccent,
|
|
1845
|
+
onClose,
|
|
1846
|
+
collapsed,
|
|
1847
|
+
expanded,
|
|
1848
|
+
onExpandedChange,
|
|
1849
|
+
edgeTab,
|
|
1850
|
+
aside: panel.aside,
|
|
1851
|
+
headerClassName,
|
|
1852
|
+
headerActions: onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_ui7.IconButton, { "aria-label": expanded ? "Shrink panel" : "Extend panel", onClick: () => onExpandedChange(!expanded), children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react8.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react8.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" }) }) : void 0,
|
|
1853
|
+
children: panel.content
|
|
1854
|
+
}
|
|
1855
|
+
);
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
// src/genre-tree/ChangeParentPopup.tsx
|
|
1859
|
+
var import_react15 = require("react");
|
|
1860
|
+
var import_genre_tree20 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1861
|
+
var import_jsx_runtime27 = require("react/jsx-runtime");
|
|
1691
1862
|
function ChangeParentPopup({
|
|
1692
1863
|
genrePlaylist,
|
|
1693
1864
|
genrePlaylists,
|
|
@@ -1696,9 +1867,9 @@ function ChangeParentPopup({
|
|
|
1696
1867
|
onClose,
|
|
1697
1868
|
onParentChanged
|
|
1698
1869
|
}) {
|
|
1699
|
-
const { mutate, isPending, formErrors } = (0,
|
|
1700
|
-
const candidates = (0,
|
|
1701
|
-
() => (0,
|
|
1870
|
+
const { mutate, isPending, formErrors } = (0, import_genre_tree20.useUpdateGenre)(scope, getBackendBaseUrl);
|
|
1871
|
+
const candidates = (0, import_react15.useMemo)(
|
|
1872
|
+
() => (0, import_genre_tree20.getReparentCandidates)(genrePlaylists, genrePlaylist.uuid),
|
|
1702
1873
|
[genrePlaylists, genrePlaylist.uuid]
|
|
1703
1874
|
);
|
|
1704
1875
|
const handleSelect = (newParent) => {
|
|
@@ -1715,19 +1886,19 @@ function ChangeParentPopup({
|
|
|
1715
1886
|
}
|
|
1716
1887
|
);
|
|
1717
1888
|
};
|
|
1718
|
-
(0,
|
|
1889
|
+
(0, import_react15.useEffect)(() => {
|
|
1719
1890
|
const handleKeyDown = (event) => {
|
|
1720
1891
|
if (event.key === "Escape") onClose();
|
|
1721
1892
|
};
|
|
1722
1893
|
window.addEventListener("keydown", handleKeyDown);
|
|
1723
1894
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
1724
1895
|
}, [onClose]);
|
|
1725
|
-
return /* @__PURE__ */ (0,
|
|
1726
|
-
/* @__PURE__ */ (0,
|
|
1896
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
|
|
1897
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("p", { className: "mb-3 text-sm text-[var(--color-text-muted)]", children: [
|
|
1727
1898
|
"Current parent: ",
|
|
1728
1899
|
genrePlaylist.parent?.name ?? "none"
|
|
1729
1900
|
] }),
|
|
1730
|
-
/* @__PURE__ */ (0,
|
|
1901
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
1731
1902
|
GenreSearch,
|
|
1732
1903
|
{
|
|
1733
1904
|
genrePlaylists: candidates,
|
|
@@ -1735,24 +1906,24 @@ function ChangeParentPopup({
|
|
|
1735
1906
|
placeholder: "Search a new parent\u2026"
|
|
1736
1907
|
}
|
|
1737
1908
|
) }),
|
|
1738
|
-
formErrors.length > 0 && /* @__PURE__ */ (0,
|
|
1909
|
+
formErrors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { role: "alert", className: "mt-3 text-sm text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
|
|
1739
1910
|
] });
|
|
1740
1911
|
}
|
|
1741
1912
|
|
|
1742
1913
|
// src/genre-tree/artist/useArtistPanel.tsx
|
|
1743
|
-
var
|
|
1744
|
-
var
|
|
1914
|
+
var import_ui13 = require("@behindthemusictree/ui");
|
|
1915
|
+
var import_genre_tree25 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1745
1916
|
|
|
1746
1917
|
// src/genre-tree/artist/ArtistHeader.tsx
|
|
1747
|
-
var
|
|
1918
|
+
var import_ui9 = require("@behindthemusictree/ui");
|
|
1748
1919
|
|
|
1749
1920
|
// src/genre-tree/artist/ArtistLinks.tsx
|
|
1750
|
-
var
|
|
1921
|
+
var import_lucide_react9 = require("lucide-react");
|
|
1751
1922
|
var import_si3 = require("react-icons/si");
|
|
1752
|
-
var
|
|
1753
|
-
var
|
|
1923
|
+
var import_ui8 = require("@behindthemusictree/ui");
|
|
1924
|
+
var import_jsx_runtime28 = require("react/jsx-runtime");
|
|
1754
1925
|
var LINK_KINDS = {
|
|
1755
|
-
official: { label: "Official website", Icon:
|
|
1926
|
+
official: { label: "Official website", Icon: import_lucide_react9.Globe },
|
|
1756
1927
|
wikipedia: { label: "Wikipedia", Icon: import_si3.SiWikipedia },
|
|
1757
1928
|
wikidata: { label: "Wikidata", Icon: import_si3.SiWikidata },
|
|
1758
1929
|
spotify: { label: "Spotify", Icon: import_si3.SiSpotify },
|
|
@@ -1760,7 +1931,7 @@ var LINK_KINDS = {
|
|
|
1760
1931
|
bandcamp: { label: "Bandcamp", Icon: import_si3.SiBandcamp },
|
|
1761
1932
|
soundcloud: { label: "SoundCloud", Icon: import_si3.SiSoundcloud },
|
|
1762
1933
|
discogs: { label: "Discogs", Icon: import_si3.SiDiscogs },
|
|
1763
|
-
social: { label: "Social", Icon:
|
|
1934
|
+
social: { label: "Social", Icon: import_lucide_react9.Share2 }
|
|
1764
1935
|
};
|
|
1765
1936
|
function socialLabel(url) {
|
|
1766
1937
|
try {
|
|
@@ -1787,7 +1958,7 @@ function artistLinks(artist) {
|
|
|
1787
1958
|
return links;
|
|
1788
1959
|
}
|
|
1789
1960
|
function ArtistLinks({ links }) {
|
|
1790
|
-
return /* @__PURE__ */ (0,
|
|
1961
|
+
return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_ui8.Chip, { href: url, icon: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
|
|
1791
1962
|
}
|
|
1792
1963
|
|
|
1793
1964
|
// src/genre-tree/artist/formatYears.ts
|
|
@@ -1798,7 +1969,7 @@ function formatYears(beginYear, endYear, ended) {
|
|
|
1798
1969
|
}
|
|
1799
1970
|
|
|
1800
1971
|
// src/genre-tree/artist/ArtistHeader.tsx
|
|
1801
|
-
var
|
|
1972
|
+
var import_jsx_runtime29 = require("react/jsx-runtime");
|
|
1802
1973
|
var countryNames = new Intl.DisplayNames(["en"], { type: "region" });
|
|
1803
1974
|
function ArtistHeader({ artist }) {
|
|
1804
1975
|
const fields = [];
|
|
@@ -1808,35 +1979,35 @@ function ArtistHeader({ artist }) {
|
|
|
1808
1979
|
const years = formatYears(artist.beginYear, artist.endYear, artist.ended);
|
|
1809
1980
|
if (years) fields.push({ label: "Active", value: years });
|
|
1810
1981
|
const links = artistLinks(artist);
|
|
1811
|
-
if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0,
|
|
1982
|
+
if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(ArtistLinks, { links }) });
|
|
1812
1983
|
if (fields.length === 0) return null;
|
|
1813
|
-
return /* @__PURE__ */ (0,
|
|
1984
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui9.FieldList, { items: fields });
|
|
1814
1985
|
}
|
|
1815
1986
|
|
|
1816
1987
|
// src/genre-tree/artist/ArtistPhoto.tsx
|
|
1817
|
-
var
|
|
1988
|
+
var import_jsx_runtime30 = require("react/jsx-runtime");
|
|
1818
1989
|
function ExternalLink({ href, children }) {
|
|
1819
|
-
return /* @__PURE__ */ (0,
|
|
1990
|
+
return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
|
|
1820
1991
|
}
|
|
1821
1992
|
function ArtistPhoto({ image, name }) {
|
|
1822
|
-
return /* @__PURE__ */ (0,
|
|
1823
|
-
/* @__PURE__ */ (0,
|
|
1824
|
-
/* @__PURE__ */ (0,
|
|
1993
|
+
return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
|
|
1994
|
+
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
|
|
1995
|
+
/* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("figcaption", { className: "text-xs text-[var(--color-text-muted)]", children: [
|
|
1825
1996
|
"Photo: ",
|
|
1826
|
-
/* @__PURE__ */ (0,
|
|
1997
|
+
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
|
|
1827
1998
|
",",
|
|
1828
1999
|
" ",
|
|
1829
|
-
image.licenseUrl ? /* @__PURE__ */ (0,
|
|
2000
|
+
image.licenseUrl ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
|
|
1830
2001
|
] })
|
|
1831
2002
|
] });
|
|
1832
2003
|
}
|
|
1833
2004
|
|
|
1834
2005
|
// src/genre-tree/artist/ArtistMembers.tsx
|
|
1835
|
-
var
|
|
1836
|
-
var
|
|
2006
|
+
var import_ui10 = require("@behindthemusictree/ui");
|
|
2007
|
+
var import_jsx_runtime31 = require("react/jsx-runtime");
|
|
1837
2008
|
function MembershipList({ title, memberships, renderArtistLink }) {
|
|
1838
2009
|
if (memberships.length === 0) return null;
|
|
1839
|
-
return /* @__PURE__ */ (0,
|
|
2010
|
+
return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ui10.PanelGroup, { title, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("ul", { children: memberships.map((membership, index) => {
|
|
1840
2011
|
const details = [
|
|
1841
2012
|
formatYears(membership.beginYear, membership.endYear, membership.ended),
|
|
1842
2013
|
...membership.roles
|
|
@@ -1844,9 +2015,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
|
|
|
1844
2015
|
const detailsText = details.filter(Boolean).join(" \xB7 ");
|
|
1845
2016
|
return (
|
|
1846
2017
|
// A member can have several periods in the same group.
|
|
1847
|
-
/* @__PURE__ */ (0,
|
|
1848
|
-
/* @__PURE__ */ (0,
|
|
1849
|
-
detailsText && /* @__PURE__ */ (0,
|
|
2018
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
|
|
2019
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
|
|
2020
|
+
detailsText && /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
|
|
1850
2021
|
" \u2014 ",
|
|
1851
2022
|
detailsText
|
|
1852
2023
|
] })
|
|
@@ -1857,9 +2028,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
|
|
|
1857
2028
|
function ArtistMembers({ title, memberships, renderArtistLink }) {
|
|
1858
2029
|
if (memberships.length === 0) return null;
|
|
1859
2030
|
const isCurrent = (membership) => !membership.ended && membership.endYear === null;
|
|
1860
|
-
return /* @__PURE__ */ (0,
|
|
1861
|
-
/* @__PURE__ */ (0,
|
|
1862
|
-
/* @__PURE__ */ (0,
|
|
2031
|
+
return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_ui10.PanelSection, { title, children: [
|
|
2032
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
|
|
2033
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
|
|
1863
2034
|
MembershipList,
|
|
1864
2035
|
{
|
|
1865
2036
|
title: "Past",
|
|
@@ -1871,8 +2042,8 @@ function ArtistMembers({ title, memberships, renderArtistLink }) {
|
|
|
1871
2042
|
}
|
|
1872
2043
|
|
|
1873
2044
|
// src/genre-tree/artist/ArtistDiscography.tsx
|
|
1874
|
-
var
|
|
1875
|
-
var
|
|
2045
|
+
var import_ui11 = require("@behindthemusictree/ui");
|
|
2046
|
+
var import_jsx_runtime32 = require("react/jsx-runtime");
|
|
1876
2047
|
var GROUPS = ["Albums", "EPs", "Singles", "Live", "Compilations", "Other"];
|
|
1877
2048
|
var PRIMARY_GROUPS = { album: "Albums", ep: "EPs", single: "Singles" };
|
|
1878
2049
|
function discographyGroup({ primaryType, secondaryTypes }) {
|
|
@@ -1892,34 +2063,34 @@ function byYear(a, b) {
|
|
|
1892
2063
|
function ArtistDiscography({ albums }) {
|
|
1893
2064
|
if (albums.length === 0) return null;
|
|
1894
2065
|
const sorted = [...albums].sort(byYear);
|
|
1895
|
-
return /* @__PURE__ */ (0,
|
|
2066
|
+
return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui11.PanelSection, { title: "Discography", children: GROUPS.map((group) => {
|
|
1896
2067
|
const groupAlbums = sorted.filter((album) => discographyGroup(album) === group);
|
|
1897
2068
|
if (groupAlbums.length === 0) return null;
|
|
1898
|
-
return /* @__PURE__ */ (0,
|
|
1899
|
-
/* @__PURE__ */ (0,
|
|
1900
|
-
/* @__PURE__ */ (0,
|
|
2069
|
+
return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui11.PanelGroup, { title: group, children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
|
|
2070
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
|
|
2071
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
|
|
1901
2072
|
] }, album.uuid)) }) }, group);
|
|
1902
2073
|
}) });
|
|
1903
2074
|
}
|
|
1904
2075
|
|
|
1905
2076
|
// src/genre-tree/artist/ArtistSongs.tsx
|
|
1906
|
-
var
|
|
2077
|
+
var import_react16 = require("react");
|
|
1907
2078
|
var import_react_query2 = require("@tanstack/react-query");
|
|
1908
|
-
var
|
|
1909
|
-
var
|
|
2079
|
+
var import_lucide_react10 = require("lucide-react");
|
|
2080
|
+
var import_ui12 = require("@behindthemusictree/ui");
|
|
1910
2081
|
var import_player10 = require("@behindthemusictree/app-kit/player");
|
|
1911
|
-
var
|
|
2082
|
+
var import_genre_tree24 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1912
2083
|
var import_transport7 = require("@behindthemusictree/app-kit/transport");
|
|
1913
2084
|
|
|
1914
2085
|
// src/genre-tree/track-list-sidebar/TrackItem.tsx
|
|
1915
2086
|
var import_player9 = require("@behindthemusictree/app-kit/player");
|
|
1916
|
-
var
|
|
2087
|
+
var import_genre_tree21 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1917
2088
|
|
|
1918
2089
|
// src/genre-tree/TrackPositionPlayPause.tsx
|
|
1919
2090
|
var import_fa3 = require("react-icons/fa");
|
|
1920
2091
|
var import_player7 = require("@behindthemusictree/app-kit/player");
|
|
1921
2092
|
var import_player8 = require("@behindthemusictree/app-kit/player");
|
|
1922
|
-
var
|
|
2093
|
+
var import_jsx_runtime33 = require("react/jsx-runtime");
|
|
1923
2094
|
function TrackPositionPlayPause({
|
|
1924
2095
|
position,
|
|
1925
2096
|
uuid,
|
|
@@ -1933,14 +2104,14 @@ function TrackPositionPlayPause({
|
|
|
1933
2104
|
const isPlaying = isThisTrack && isConfirmedPlayable && playState === import_player8.PlayStates.PLAYING;
|
|
1934
2105
|
const isPaused = isThisTrack && isConfirmedPlayable && playState === import_player8.PlayStates.PAUSED;
|
|
1935
2106
|
const isCurrentTrackActive = isPlaying || isPaused;
|
|
1936
|
-
return /* @__PURE__ */ (0,
|
|
2107
|
+
return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
|
|
1937
2108
|
"div",
|
|
1938
2109
|
{
|
|
1939
2110
|
className: "track-position-play-pause relative flex items-center justify-center gap-2 text-lg w-16 cursor-pointer",
|
|
1940
2111
|
style: { minWidth: "4rem", maxWidth: "4rem" },
|
|
1941
2112
|
onClick: handlePlayPauseClick,
|
|
1942
2113
|
children: [
|
|
1943
|
-
/* @__PURE__ */ (0,
|
|
2114
|
+
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
|
|
1944
2115
|
"span",
|
|
1945
2116
|
{
|
|
1946
2117
|
className: "track-position-play-pause__bars",
|
|
@@ -1948,18 +2119,18 @@ function TrackPositionPlayPause({
|
|
|
1948
2119
|
role: "img",
|
|
1949
2120
|
"aria-label": isPlaying ? "Playing" : "Paused",
|
|
1950
2121
|
children: [
|
|
1951
|
-
/* @__PURE__ */ (0,
|
|
1952
|
-
/* @__PURE__ */ (0,
|
|
1953
|
-
/* @__PURE__ */ (0,
|
|
2122
|
+
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "track-position-play-pause__bar" }),
|
|
2123
|
+
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "track-position-play-pause__bar" }),
|
|
2124
|
+
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "track-position-play-pause__bar" })
|
|
1954
2125
|
]
|
|
1955
2126
|
}
|
|
1956
2127
|
) : position }),
|
|
1957
|
-
/* @__PURE__ */ (0,
|
|
2128
|
+
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
|
|
1958
2129
|
"div",
|
|
1959
2130
|
{
|
|
1960
2131
|
className: `hidden group-hover:flex items-center justify-center ${isCurrentTrackActive ? "" : "absolute inset-0"}`,
|
|
1961
2132
|
"aria-hidden": "true",
|
|
1962
|
-
children: isAwaitingPlayback ? /* @__PURE__ */ (0,
|
|
2133
|
+
children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_fa3.FaPause, {}) : /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_fa3.FaPlay, {})
|
|
1963
2134
|
}
|
|
1964
2135
|
)
|
|
1965
2136
|
]
|
|
@@ -1968,9 +2139,9 @@ function TrackPositionPlayPause({
|
|
|
1968
2139
|
}
|
|
1969
2140
|
|
|
1970
2141
|
// src/genre-tree/track-list-sidebar/TrackItem.tsx
|
|
1971
|
-
var import_genre_tree21 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1972
2142
|
var import_genre_tree22 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1973
|
-
var
|
|
2143
|
+
var import_genre_tree23 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2144
|
+
var import_jsx_runtime34 = require("react/jsx-runtime");
|
|
1974
2145
|
function TrackItem({
|
|
1975
2146
|
track,
|
|
1976
2147
|
position,
|
|
@@ -1980,11 +2151,11 @@ function TrackItem({
|
|
|
1980
2151
|
renderSongLink
|
|
1981
2152
|
}) {
|
|
1982
2153
|
const { handlePlayPauseAction, playerTrackObject, loadTrackForPlayer } = (0, import_player9.usePlayer)();
|
|
1983
|
-
const { trackList, selectedTrack, toTrackAtPosition, runtimeUnplayableTrackIds } = (0,
|
|
2154
|
+
const { trackList, selectedTrack, toTrackAtPosition, runtimeUnplayableTrackIds } = (0, import_genre_tree21.useTrackList)();
|
|
1984
2155
|
const scope = trackList?.origin?.scope ?? null;
|
|
1985
2156
|
const isCurrentTrack = playerTrackObject?.track.id === track.uuid;
|
|
1986
2157
|
const hasPlaybackError = isCurrentTrack && !!playerTrackObject?.loadError;
|
|
1987
|
-
const isUnplayable = hasPlaybackError || runtimeUnplayableTrackIds?.has(track.uuid) === true || (0,
|
|
2158
|
+
const isUnplayable = hasPlaybackError || runtimeUnplayableTrackIds?.has(track.uuid) === true || (0, import_genre_tree22.isSong)(track) && (0, import_genre_tree22.songUnplayableReason)(track) !== null;
|
|
1988
2159
|
const handlePlayPauseClick = (event) => {
|
|
1989
2160
|
event.stopPropagation();
|
|
1990
2161
|
if (isCurrentTrack) {
|
|
@@ -1994,12 +2165,12 @@ function TrackItem({
|
|
|
1994
2165
|
loadTrackForPlayer(track.uuid);
|
|
1995
2166
|
}
|
|
1996
2167
|
};
|
|
1997
|
-
return /* @__PURE__ */ (0,
|
|
2168
|
+
return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
|
|
1998
2169
|
"div",
|
|
1999
2170
|
{
|
|
2000
2171
|
className: `track-item flex h-14 text-[var(--color-text)] ${isUnplayable ? "opacity-50" : "hover:bg-[var(--color-surface-muted)] group"}`,
|
|
2001
2172
|
children: [
|
|
2002
|
-
isUnplayable ? /* @__PURE__ */ (0,
|
|
2173
|
+
isUnplayable ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2003
2174
|
"div",
|
|
2004
2175
|
{
|
|
2005
2176
|
className: "flex items-center justify-center text-lg w-16",
|
|
@@ -2007,7 +2178,7 @@ function TrackItem({
|
|
|
2007
2178
|
"aria-disabled": "true",
|
|
2008
2179
|
children: position
|
|
2009
2180
|
}
|
|
2010
|
-
) : /* @__PURE__ */ (0,
|
|
2181
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2011
2182
|
TrackPositionPlayPause,
|
|
2012
2183
|
{
|
|
2013
2184
|
position,
|
|
@@ -2016,12 +2187,12 @@ function TrackItem({
|
|
|
2016
2187
|
isSelected: selectedTrack?.uuid === track.uuid
|
|
2017
2188
|
}
|
|
2018
2189
|
),
|
|
2019
|
-
/* @__PURE__ */ (0,
|
|
2020
|
-
/* @__PURE__ */ (0,
|
|
2021
|
-
track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0,
|
|
2190
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
|
|
2191
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree22.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
|
|
2192
|
+
track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_genre_tree23.ArtistNames, { artists: track.artists, renderArtistLink }) }) : ""
|
|
2022
2193
|
] }),
|
|
2023
|
-
/* @__PURE__ */ (0,
|
|
2024
|
-
renderDuration && /* @__PURE__ */ (0,
|
|
2194
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "genre-name-container flex items-center justify-start w-[21.6%] min-w-0 shrink-0", children: track.genre ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
|
|
2195
|
+
renderDuration && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2025
2196
|
"div",
|
|
2026
2197
|
{
|
|
2027
2198
|
className: "duration flex text-base w-16 items-center justify-center",
|
|
@@ -2029,28 +2200,28 @@ function TrackItem({
|
|
|
2029
2200
|
children: renderDuration(track)
|
|
2030
2201
|
}
|
|
2031
2202
|
),
|
|
2032
|
-
renderActions && /* @__PURE__ */ (0,
|
|
2203
|
+
renderActions && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
|
|
2033
2204
|
]
|
|
2034
2205
|
}
|
|
2035
2206
|
);
|
|
2036
2207
|
}
|
|
2037
2208
|
|
|
2038
2209
|
// src/genre-tree/artist/ArtistSongs.tsx
|
|
2039
|
-
var
|
|
2040
|
-
var SongPageSchema2 = (0, import_transport7.PaginatedResponseSchema)(
|
|
2210
|
+
var import_jsx_runtime35 = require("react/jsx-runtime");
|
|
2211
|
+
var SongPageSchema2 = (0, import_transport7.PaginatedResponseSchema)(import_genre_tree24.SongDetailedSchema);
|
|
2041
2212
|
var SORTS = [
|
|
2042
2213
|
{ ordering: "title", label: "Title" },
|
|
2043
2214
|
{ ordering: "-albumYear", label: "Year" }
|
|
2044
2215
|
];
|
|
2045
2216
|
function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLink }) {
|
|
2046
|
-
const [page, setPage] = (0,
|
|
2047
|
-
const [ordering, setOrdering] = (0,
|
|
2048
|
-
const {
|
|
2049
|
-
const { playNewTrackListFromArtist } = (0, import_genre_tree23.useTrackList)();
|
|
2217
|
+
const [page, setPage] = (0, import_react16.useState)(1);
|
|
2218
|
+
const [ordering, setOrdering] = (0, import_react16.useState)("title");
|
|
2219
|
+
const { playNewTrackListFromArtist } = (0, import_genre_tree24.useTrackList)();
|
|
2050
2220
|
const { playerTrackObject } = (0, import_player10.usePlayer)();
|
|
2051
|
-
const { data, isPending, isError } = (0, import_transport7.
|
|
2221
|
+
const { data, isPending, isError } = (0, import_transport7.usePlaybackQuery)({
|
|
2222
|
+
getBackendBaseUrl,
|
|
2052
2223
|
queryKey: ["referenceSongs", "artist", artist.uuid, ordering, page],
|
|
2053
|
-
queryFn: () =>
|
|
2224
|
+
queryFn: (fetchBound) => fetchBound(import_genre_tree24.libraryEndpoints.reference.songs.list(), true, false, void 0, {
|
|
2054
2225
|
artist: artist.uuid,
|
|
2055
2226
|
ordering,
|
|
2056
2227
|
page
|
|
@@ -2065,25 +2236,25 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
|
|
|
2065
2236
|
});
|
|
2066
2237
|
const handleRowClickCapture = (event, song) => {
|
|
2067
2238
|
if (!(event.target instanceof Element) || !event.target.closest(".track-position-play-pause")) return;
|
|
2068
|
-
if (playerTrackObject?.track.id === song.uuid || (0,
|
|
2239
|
+
if (playerTrackObject?.track.id === song.uuid || (0, import_genre_tree24.songUnplayableReason)(song) !== null) return;
|
|
2069
2240
|
event.stopPropagation();
|
|
2070
2241
|
void play(song.uuid);
|
|
2071
2242
|
};
|
|
2072
|
-
return /* @__PURE__ */ (0,
|
|
2073
|
-
/* @__PURE__ */ (0,
|
|
2074
|
-
/* @__PURE__ */ (0,
|
|
2243
|
+
return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui12.PanelSection, { title: "Songs", children: isPending ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui12.Skeleton, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
|
|
2244
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
|
|
2245
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
|
|
2075
2246
|
"button",
|
|
2076
2247
|
{
|
|
2077
2248
|
type: "button",
|
|
2078
2249
|
onClick: () => void play(),
|
|
2079
2250
|
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",
|
|
2080
2251
|
children: [
|
|
2081
|
-
/* @__PURE__ */ (0,
|
|
2252
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react10.Play, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
|
|
2082
2253
|
"Play all"
|
|
2083
2254
|
]
|
|
2084
2255
|
}
|
|
2085
2256
|
),
|
|
2086
|
-
/* @__PURE__ */ (0,
|
|
2257
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2087
2258
|
"button",
|
|
2088
2259
|
{
|
|
2089
2260
|
type: "button",
|
|
@@ -2098,7 +2269,7 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
|
|
|
2098
2269
|
sort.ordering
|
|
2099
2270
|
)) })
|
|
2100
2271
|
] }),
|
|
2101
|
-
/* @__PURE__ */ (0,
|
|
2272
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2102
2273
|
TrackItem,
|
|
2103
2274
|
{
|
|
2104
2275
|
track: song,
|
|
@@ -2107,12 +2278,12 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
|
|
|
2107
2278
|
renderSongLink
|
|
2108
2279
|
}
|
|
2109
2280
|
) }, song.uuid)) }),
|
|
2110
|
-
/* @__PURE__ */ (0,
|
|
2281
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui12.Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
|
|
2111
2282
|
] }) });
|
|
2112
2283
|
}
|
|
2113
2284
|
|
|
2114
2285
|
// src/genre-tree/artist/useArtistPanel.tsx
|
|
2115
|
-
var
|
|
2286
|
+
var import_jsx_runtime36 = require("react/jsx-runtime");
|
|
2116
2287
|
function useArtistPanel({
|
|
2117
2288
|
uuid,
|
|
2118
2289
|
scope,
|
|
@@ -2124,7 +2295,7 @@ function useArtistPanel({
|
|
|
2124
2295
|
onExpandedChange,
|
|
2125
2296
|
onClose
|
|
2126
2297
|
}) {
|
|
2127
|
-
const result = (0,
|
|
2298
|
+
const result = (0, import_genre_tree25.useArtist)(uuid, getBackendBaseUrl);
|
|
2128
2299
|
const panel = { expanded, onExpandedChange, onClose };
|
|
2129
2300
|
switch (result.status) {
|
|
2130
2301
|
case "idle":
|
|
@@ -2133,26 +2304,26 @@ function useArtistPanel({
|
|
|
2133
2304
|
return {
|
|
2134
2305
|
...panel,
|
|
2135
2306
|
title: "Artist",
|
|
2136
|
-
content: /* @__PURE__ */ (0,
|
|
2137
|
-
/* @__PURE__ */ (0,
|
|
2138
|
-
/* @__PURE__ */ (0,
|
|
2139
|
-
/* @__PURE__ */ (0,
|
|
2140
|
-
/* @__PURE__ */ (0,
|
|
2141
|
-
/* @__PURE__ */ (0,
|
|
2142
|
-
/* @__PURE__ */ (0,
|
|
2307
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
|
|
2308
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.Skeleton, { className: "h-3 w-16" }),
|
|
2309
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.Skeleton, { className: "h-4 w-32" }),
|
|
2310
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.Skeleton, { className: "h-3 w-16" }),
|
|
2311
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.Skeleton, { className: "h-4 w-24" }),
|
|
2312
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.Skeleton, { className: "h-16 mt-2.5" }),
|
|
2313
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.Skeleton, { className: "h-16" })
|
|
2143
2314
|
] })
|
|
2144
2315
|
};
|
|
2145
2316
|
case "not-found":
|
|
2146
2317
|
return {
|
|
2147
2318
|
...panel,
|
|
2148
2319
|
title: "Artist not found",
|
|
2149
|
-
content: /* @__PURE__ */ (0,
|
|
2320
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This artist does not exist." })
|
|
2150
2321
|
};
|
|
2151
2322
|
case "error":
|
|
2152
2323
|
return {
|
|
2153
2324
|
...panel,
|
|
2154
2325
|
title: "Artist",
|
|
2155
|
-
content: /* @__PURE__ */ (0,
|
|
2326
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
|
|
2156
2327
|
};
|
|
2157
2328
|
case "success": {
|
|
2158
2329
|
const { artist } = result;
|
|
@@ -2161,13 +2332,13 @@ function useArtistPanel({
|
|
|
2161
2332
|
return {
|
|
2162
2333
|
...panel,
|
|
2163
2334
|
title: artist.name,
|
|
2164
|
-
content: /* @__PURE__ */ (0,
|
|
2165
|
-
shareUrl && /* @__PURE__ */ (0,
|
|
2166
|
-
artist.image && /* @__PURE__ */ (0,
|
|
2167
|
-
/* @__PURE__ */ (0,
|
|
2168
|
-
/* @__PURE__ */ (0,
|
|
2169
|
-
/* @__PURE__ */ (0,
|
|
2170
|
-
artist.tracksCount > 0 && /* @__PURE__ */ (0,
|
|
2335
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_jsx_runtime36.Fragment, { children: [
|
|
2336
|
+
shareUrl && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: shareTarget && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl }) }) }),
|
|
2337
|
+
artist.image && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ArtistPhoto, { image: artist.image, name: artist.name }),
|
|
2338
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ArtistHeader, { artist }),
|
|
2339
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
|
|
2340
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
|
|
2341
|
+
artist.tracksCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
|
|
2171
2342
|
ArtistSongs,
|
|
2172
2343
|
{
|
|
2173
2344
|
artist: { uuid: artist.uuid, name: artist.name },
|
|
@@ -2177,56 +2348,56 @@ function useArtistPanel({
|
|
|
2177
2348
|
}
|
|
2178
2349
|
)
|
|
2179
2350
|
] }),
|
|
2180
|
-
extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0,
|
|
2351
|
+
extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ArtistDiscography, { albums: artist.albums }) : void 0
|
|
2181
2352
|
};
|
|
2182
2353
|
}
|
|
2183
2354
|
}
|
|
2184
2355
|
}
|
|
2185
2356
|
|
|
2186
2357
|
// src/genre-tree/song/useSongPanel.tsx
|
|
2187
|
-
var
|
|
2188
|
-
var
|
|
2358
|
+
var import_ui15 = require("@behindthemusictree/ui");
|
|
2359
|
+
var import_genre_tree27 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2189
2360
|
|
|
2190
2361
|
// src/genre-tree/song/SongPanelContent.tsx
|
|
2191
|
-
var
|
|
2192
|
-
var
|
|
2193
|
-
var
|
|
2362
|
+
var import_react17 = require("react");
|
|
2363
|
+
var import_lucide_react11 = require("lucide-react");
|
|
2364
|
+
var import_ui14 = require("@behindthemusictree/ui");
|
|
2194
2365
|
var import_player11 = require("@behindthemusictree/app-kit/player");
|
|
2195
|
-
var
|
|
2196
|
-
var
|
|
2366
|
+
var import_genre_tree26 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2367
|
+
var import_jsx_runtime37 = require("react/jsx-runtime");
|
|
2197
2368
|
var numberFormat = new Intl.NumberFormat("en");
|
|
2198
2369
|
var dateFormat = new Intl.DateTimeFormat("en", { dateStyle: "medium" });
|
|
2199
2370
|
var externalLink = "text-[var(--color-primary)] hover:underline focus-visible:underline";
|
|
2200
2371
|
function SongHistory({ uuid, getBackendBaseUrl }) {
|
|
2201
|
-
const [open, setOpen] = (0,
|
|
2202
|
-
const history = (0,
|
|
2372
|
+
const [open, setOpen] = (0, import_react17.useState)(false);
|
|
2373
|
+
const history = (0, import_genre_tree26.useSongHistory)(uuid, getBackendBaseUrl, open);
|
|
2203
2374
|
let body = null;
|
|
2204
|
-
if (history.status === "loading") body = /* @__PURE__ */ (0,
|
|
2205
|
-
else if (history.status === "error") body = /* @__PURE__ */ (0,
|
|
2375
|
+
if (history.status === "loading") body = /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui14.Skeleton, { className: "h-10", "aria-busy": "true" });
|
|
2376
|
+
else if (history.status === "error") body = /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "text-red-600", children: "Could not load the history." });
|
|
2206
2377
|
else if (history.status === "success")
|
|
2207
|
-
body = history.entries.length === 0 ? /* @__PURE__ */ (0,
|
|
2208
|
-
/* @__PURE__ */ (0,
|
|
2209
|
-
entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ (0,
|
|
2378
|
+
body = history.entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("li", { className: "py-1.5 text-sm", children: [
|
|
2379
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "font-medium", children: entry.action }),
|
|
2380
|
+
entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
|
|
2210
2381
|
" ",
|
|
2211
2382
|
"\u2014 ",
|
|
2212
2383
|
entry.oldValue ?? "none",
|
|
2213
2384
|
" \u2192 ",
|
|
2214
2385
|
entry.newValue ?? "none"
|
|
2215
2386
|
] }) : null,
|
|
2216
|
-
/* @__PURE__ */ (0,
|
|
2387
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { className: "block text-xs text-[var(--color-text-muted)]", children: [
|
|
2217
2388
|
entry.actorPseudo ?? "Anonymous",
|
|
2218
2389
|
" \xB7 ",
|
|
2219
2390
|
dateFormat.format(new Date(entry.createdOn))
|
|
2220
2391
|
] })
|
|
2221
2392
|
] }, entry.uuid)) });
|
|
2222
|
-
return /* @__PURE__ */ (0,
|
|
2393
|
+
return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
|
|
2223
2394
|
"details",
|
|
2224
2395
|
{
|
|
2225
2396
|
onToggle: (event) => setOpen(event.currentTarget.open),
|
|
2226
2397
|
className: "px-6 pb-2.5 mt-2.5 border-t border-[var(--color-border)]",
|
|
2227
2398
|
children: [
|
|
2228
|
-
/* @__PURE__ */ (0,
|
|
2229
|
-
/* @__PURE__ */ (0,
|
|
2399
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)("summary", { className: "mt-2.5 cursor-pointer text-[0.6875rem] font-semibold uppercase text-[var(--color-text-muted)]", children: "History" }),
|
|
2400
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "mt-1.5", children: body })
|
|
2230
2401
|
]
|
|
2231
2402
|
}
|
|
2232
2403
|
);
|
|
@@ -2240,25 +2411,25 @@ function SongPanelContent({
|
|
|
2240
2411
|
genrePath,
|
|
2241
2412
|
onSelectGenre
|
|
2242
2413
|
}) {
|
|
2243
|
-
const { playNewTrackListFromTrackUuid } = (0,
|
|
2414
|
+
const { playNewTrackListFromTrackUuid } = (0, import_genre_tree26.useTrackList)();
|
|
2244
2415
|
const { playerTrackObject, playState, handlePlayPauseAction } = (0, import_player11.usePlayer)();
|
|
2245
|
-
const unplayableReason = (0,
|
|
2416
|
+
const unplayableReason = (0, import_genre_tree26.songUnplayableReason)(song);
|
|
2246
2417
|
const isCurrent = playerTrackObject?.track.id === song.uuid;
|
|
2247
2418
|
const isPlaying = isCurrent && playState === import_player11.PlayStates.PLAYING;
|
|
2248
2419
|
const shareTarget = { kind: "song", uuid: song.uuid, name: song.title, scope };
|
|
2249
|
-
const musicBrainzUrl = (0,
|
|
2420
|
+
const musicBrainzUrl = (0, import_genre_tree26.musicBrainzRecordingUrl)(song);
|
|
2250
2421
|
const fields = [];
|
|
2251
2422
|
if (song.genre) {
|
|
2252
2423
|
const path = genrePath.length > 0 ? genrePath : null;
|
|
2253
2424
|
fields.push({
|
|
2254
2425
|
label: "Genre",
|
|
2255
|
-
value: path ? /* @__PURE__ */ (0,
|
|
2256
|
-
index > 0 && /* @__PURE__ */ (0,
|
|
2257
|
-
/* @__PURE__ */ (0,
|
|
2426
|
+
value: path ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { children: [
|
|
2427
|
+
index > 0 && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { "aria-hidden": "true", children: "\u203A " }),
|
|
2428
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)("button", { type: "button", onClick: () => onSelectGenre(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
|
|
2258
2429
|
] }, genrePlaylist.uuid)) }) : song.genre.name
|
|
2259
2430
|
});
|
|
2260
2431
|
} else {
|
|
2261
|
-
fields.push({ label: "Genre", value: /* @__PURE__ */ (0,
|
|
2432
|
+
fields.push({ label: "Genre", value: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
|
|
2262
2433
|
}
|
|
2263
2434
|
if (song.firstReleaseYear) fields.push({ label: "First released", value: song.firstReleaseYear });
|
|
2264
2435
|
const viewCount = song.youtubeVideo?.viewCount;
|
|
@@ -2268,11 +2439,11 @@ function SongPanelContent({
|
|
|
2268
2439
|
const links = [];
|
|
2269
2440
|
if (musicBrainzUrl)
|
|
2270
2441
|
links.push(
|
|
2271
|
-
/* @__PURE__ */ (0,
|
|
2442
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
|
|
2272
2443
|
);
|
|
2273
2444
|
if (song.youtubeVideo)
|
|
2274
2445
|
links.push(
|
|
2275
|
-
/* @__PURE__ */ (0,
|
|
2446
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
|
|
2276
2447
|
"a",
|
|
2277
2448
|
{
|
|
2278
2449
|
href: `https://www.youtube.com/watch?v=${encodeURIComponent(song.youtubeVideo.id)}`,
|
|
@@ -2287,33 +2458,33 @@ function SongPanelContent({
|
|
|
2287
2458
|
if (links.length > 0)
|
|
2288
2459
|
fields.push({
|
|
2289
2460
|
label: "Links",
|
|
2290
|
-
value: /* @__PURE__ */ (0,
|
|
2461
|
+
value: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "flex gap-3", children: links })
|
|
2291
2462
|
});
|
|
2292
|
-
return /* @__PURE__ */ (0,
|
|
2293
|
-
/* @__PURE__ */ (0,
|
|
2294
|
-
unplayableReason ? /* @__PURE__ */ (0,
|
|
2463
|
+
return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(import_jsx_runtime37.Fragment, { children: [
|
|
2464
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
|
|
2465
|
+
unplayableReason ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "flex items-center px-2.5 text-sm text-[var(--color-text-muted)]", children: unplayableReason }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
|
|
2295
2466
|
"button",
|
|
2296
2467
|
{
|
|
2297
2468
|
type: "button",
|
|
2298
2469
|
onClick: () => isCurrent ? handlePlayPauseAction() : playNewTrackListFromTrackUuid(song, scope),
|
|
2299
2470
|
className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none",
|
|
2300
2471
|
children: [
|
|
2301
|
-
/* @__PURE__ */ (0,
|
|
2472
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)("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__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react11.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react11.Play, { "aria-hidden": "true", className: "w-5 h-5" }) }),
|
|
2302
2473
|
isPlaying ? "Pause" : "Play"
|
|
2303
2474
|
]
|
|
2304
2475
|
}
|
|
2305
2476
|
),
|
|
2306
|
-
shareUrl && /* @__PURE__ */ (0,
|
|
2477
|
+
shareUrl && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
|
|
2307
2478
|
] }) }),
|
|
2308
|
-
song.artists && song.artists.length > 0 && /* @__PURE__ */ (0,
|
|
2309
|
-
/* @__PURE__ */ (0,
|
|
2310
|
-
song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ (0,
|
|
2311
|
-
/* @__PURE__ */ (0,
|
|
2479
|
+
song.artists && song.artists.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "px-6 pt-2.5 text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_genre_tree26.ArtistNames, { artists: song.artists, renderArtistLink }) }),
|
|
2480
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui14.FieldList, { items: fields }),
|
|
2481
|
+
song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui14.PanelSection, { title: "Essential in", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
|
|
2482
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
|
|
2312
2483
|
] });
|
|
2313
2484
|
}
|
|
2314
2485
|
|
|
2315
2486
|
// src/genre-tree/song/useSongPanel.tsx
|
|
2316
|
-
var
|
|
2487
|
+
var import_jsx_runtime38 = require("react/jsx-runtime");
|
|
2317
2488
|
function useSongPanel({
|
|
2318
2489
|
uuid,
|
|
2319
2490
|
scope,
|
|
@@ -2326,7 +2497,7 @@ function useSongPanel({
|
|
|
2326
2497
|
onExpandedChange,
|
|
2327
2498
|
onClose
|
|
2328
2499
|
}) {
|
|
2329
|
-
const result = (0,
|
|
2500
|
+
const result = (0, import_genre_tree27.useSong)(uuid, getBackendBaseUrl);
|
|
2330
2501
|
const panel = { expanded, onExpandedChange, onClose };
|
|
2331
2502
|
switch (result.status) {
|
|
2332
2503
|
case "idle":
|
|
@@ -2335,24 +2506,24 @@ function useSongPanel({
|
|
|
2335
2506
|
return {
|
|
2336
2507
|
...panel,
|
|
2337
2508
|
title: "Song",
|
|
2338
|
-
content: /* @__PURE__ */ (0,
|
|
2339
|
-
/* @__PURE__ */ (0,
|
|
2340
|
-
/* @__PURE__ */ (0,
|
|
2341
|
-
/* @__PURE__ */ (0,
|
|
2342
|
-
/* @__PURE__ */ (0,
|
|
2509
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
|
|
2510
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_ui15.Skeleton, { className: "h-3 w-16" }),
|
|
2511
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_ui15.Skeleton, { className: "h-4 w-32" }),
|
|
2512
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_ui15.Skeleton, { className: "h-3 w-16" }),
|
|
2513
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_ui15.Skeleton, { className: "h-4 w-24" })
|
|
2343
2514
|
] })
|
|
2344
2515
|
};
|
|
2345
2516
|
case "not-found":
|
|
2346
2517
|
return {
|
|
2347
2518
|
...panel,
|
|
2348
2519
|
title: "Song not found",
|
|
2349
|
-
content: /* @__PURE__ */ (0,
|
|
2520
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This song does not exist." })
|
|
2350
2521
|
};
|
|
2351
2522
|
case "error":
|
|
2352
2523
|
return {
|
|
2353
2524
|
...panel,
|
|
2354
2525
|
title: "Song",
|
|
2355
|
-
content: /* @__PURE__ */ (0,
|
|
2526
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this song." })
|
|
2356
2527
|
};
|
|
2357
2528
|
case "success": {
|
|
2358
2529
|
const { song } = result;
|
|
@@ -2360,7 +2531,7 @@ function useSongPanel({
|
|
|
2360
2531
|
return {
|
|
2361
2532
|
...panel,
|
|
2362
2533
|
title: song.title,
|
|
2363
|
-
content: /* @__PURE__ */ (0,
|
|
2534
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
|
|
2364
2535
|
SongPanelContent,
|
|
2365
2536
|
{
|
|
2366
2537
|
song,
|
|
@@ -2379,29 +2550,29 @@ function useSongPanel({
|
|
|
2379
2550
|
|
|
2380
2551
|
// src/genre-tree/SizeMetricToggle.tsx
|
|
2381
2552
|
var import_md = require("react-icons/md");
|
|
2382
|
-
var
|
|
2383
|
-
var
|
|
2553
|
+
var import_ui16 = require("@behindthemusictree/ui");
|
|
2554
|
+
var import_jsx_runtime39 = require("react/jsx-runtime");
|
|
2384
2555
|
function SizeMetricToggle({
|
|
2385
2556
|
value,
|
|
2386
2557
|
onChange
|
|
2387
2558
|
}) {
|
|
2388
|
-
return /* @__PURE__ */ (0,
|
|
2389
|
-
|
|
2559
|
+
return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2560
|
+
import_ui16.SegmentedControl,
|
|
2390
2561
|
{
|
|
2391
2562
|
variant: "floating",
|
|
2392
2563
|
"aria-label": "Size nodes by",
|
|
2393
2564
|
value,
|
|
2394
2565
|
onChange,
|
|
2395
2566
|
options: [
|
|
2396
|
-
{ value: "songs", label: /* @__PURE__ */ (0,
|
|
2397
|
-
{ value: "views", label: /* @__PURE__ */ (0,
|
|
2567
|
+
{ value: "songs", label: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_md.MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
|
|
2568
|
+
{ value: "views", label: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_md.MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
|
|
2398
2569
|
]
|
|
2399
2570
|
}
|
|
2400
2571
|
);
|
|
2401
2572
|
}
|
|
2402
2573
|
|
|
2403
2574
|
// src/genre-tree/GenreTreeView.tsx
|
|
2404
|
-
var
|
|
2575
|
+
var import_jsx_runtime40 = require("react/jsx-runtime");
|
|
2405
2576
|
var NO_POP_ROOT_TITLE = "This genre tree has no 'Mainstream Pop' root yet";
|
|
2406
2577
|
var REGIONS_CLASS = "[--gtv-center-left:var(--layout-center-left)] [--gtv-center-right:var(--layout-center-right)] [--gtv-center-top:var(--layout-band-top)]";
|
|
2407
2578
|
var noop = () => {
|
|
@@ -2414,8 +2585,8 @@ function GenreTreeView({
|
|
|
2414
2585
|
getBackendBaseUrl,
|
|
2415
2586
|
additionalActions,
|
|
2416
2587
|
viewMode: controlledViewMode,
|
|
2417
|
-
|
|
2418
|
-
|
|
2588
|
+
selectedGenreUuid: controlledSelectedGenreUuid,
|
|
2589
|
+
onSelectedGenreUuidChange,
|
|
2419
2590
|
defaultViewport,
|
|
2420
2591
|
viewportRestore,
|
|
2421
2592
|
onViewportChange,
|
|
@@ -2463,21 +2634,21 @@ function GenreTreeView({
|
|
|
2463
2634
|
placement = "default",
|
|
2464
2635
|
...focus
|
|
2465
2636
|
}) {
|
|
2466
|
-
const getShareUrlRef = (0,
|
|
2637
|
+
const getShareUrlRef = (0, import_react18.useRef)(getShareUrl);
|
|
2467
2638
|
getShareUrlRef.current = getShareUrl;
|
|
2468
|
-
const [internalArtistUuid, setInternalArtistUuid] = (0,
|
|
2639
|
+
const [internalArtistUuid, setInternalArtistUuid] = (0, import_react18.useState)(null);
|
|
2469
2640
|
const isArtistControlled = controlledArtistUuid !== void 0;
|
|
2470
2641
|
const artistUuid = isArtistControlled ? controlledArtistUuid : internalArtistUuid;
|
|
2471
|
-
const [internalSongUuid, setInternalSongUuid] = (0,
|
|
2642
|
+
const [internalSongUuid, setInternalSongUuid] = (0, import_react18.useState)(null);
|
|
2472
2643
|
const isSongControlled = controlledSongUuid !== void 0;
|
|
2473
2644
|
const songUuid = isSongControlled ? controlledSongUuid : internalSongUuid;
|
|
2474
2645
|
const changeSong = (uuid) => {
|
|
2475
2646
|
if (!isSongControlled) setInternalSongUuid(uuid);
|
|
2476
2647
|
onSongUuidChange?.(uuid);
|
|
2477
2648
|
};
|
|
2478
|
-
const [internalSizeMetric, setInternalSizeMetric] = (0,
|
|
2649
|
+
const [internalSizeMetric, setInternalSizeMetric] = (0, import_react18.useState)("songs");
|
|
2479
2650
|
const sizeMetric = controlledSizeMetric ?? internalSizeMetric;
|
|
2480
|
-
const deferredSizeMetric = (0,
|
|
2651
|
+
const deferredSizeMetric = (0, import_react18.useDeferredValue)(sizeMetric);
|
|
2481
2652
|
const changeSizeMetric = (metric) => {
|
|
2482
2653
|
if (controlledSizeMetric === void 0) setInternalSizeMetric(metric);
|
|
2483
2654
|
onSizeMetricChange?.(metric);
|
|
@@ -2488,10 +2659,14 @@ function GenreTreeView({
|
|
|
2488
2659
|
};
|
|
2489
2660
|
const {
|
|
2490
2661
|
isLoading,
|
|
2662
|
+
isError,
|
|
2663
|
+
refetch,
|
|
2491
2664
|
isFetching: isTreeFetching,
|
|
2492
2665
|
genrePlaylistByNodeId,
|
|
2493
2666
|
visibleGenrePlaylists,
|
|
2494
2667
|
groupedGenrePlaylistsByRoot,
|
|
2668
|
+
selectedGenreUuid,
|
|
2669
|
+
selectGenre,
|
|
2495
2670
|
selectedNodeId,
|
|
2496
2671
|
selectedGenrePlaylist,
|
|
2497
2672
|
selectNode,
|
|
@@ -2504,17 +2679,17 @@ function GenreTreeView({
|
|
|
2504
2679
|
allowWheelRotation: internalAllowWheelRotation,
|
|
2505
2680
|
toggleWheelRotation,
|
|
2506
2681
|
onNodeHover: handleNodeHover
|
|
2507
|
-
} = (0,
|
|
2682
|
+
} = (0, import_genre_tree28.useGenreTreeViewState)({
|
|
2508
2683
|
scope,
|
|
2509
2684
|
getBackendBaseUrl,
|
|
2510
2685
|
viewMode: controlledViewMode,
|
|
2511
|
-
|
|
2512
|
-
|
|
2686
|
+
selectedGenreUuid: controlledSelectedGenreUuid,
|
|
2687
|
+
onSelectedGenreUuidChange,
|
|
2513
2688
|
criteriaOverviewSchema,
|
|
2514
2689
|
artistUuid
|
|
2515
2690
|
});
|
|
2516
2691
|
const restoreRevision = viewportRestore?.revision;
|
|
2517
|
-
const [stackedRestoreOwner, setStackedRestoreOwner] = (0,
|
|
2692
|
+
const [stackedRestoreOwner, setStackedRestoreOwner] = (0, import_react18.useState)({ revision: restoreRevision, rootUuid: selectedGenrePlaylist?.root.uuid });
|
|
2518
2693
|
let restoreRootUuid = stackedRestoreOwner.rootUuid;
|
|
2519
2694
|
if (stackedRestoreOwner.revision !== restoreRevision) {
|
|
2520
2695
|
restoreRootUuid = selectedGenrePlaylist?.root.uuid;
|
|
@@ -2523,36 +2698,36 @@ function GenreTreeView({
|
|
|
2523
2698
|
restoreRootUuid = selectedGenrePlaylist.root.uuid;
|
|
2524
2699
|
setStackedRestoreOwner({ revision: restoreRevision, rootUuid: restoreRootUuid });
|
|
2525
2700
|
}
|
|
2526
|
-
const isGenreUpdating = (0, import_react_query3.useIsMutating)({ mutationKey:
|
|
2527
|
-
const { mutate: updateGenre } = (0,
|
|
2701
|
+
const isGenreUpdating = (0, import_react_query3.useIsMutating)({ mutationKey: import_genre_tree28.genreUpdateMutationKey }) > 0;
|
|
2702
|
+
const { mutate: updateGenre } = (0, import_genre_tree28.useUpdateGenre)(scope, getBackendBaseUrl);
|
|
2528
2703
|
const busy = deferredSizeMetric !== sizeMetric || isTreeFetching || isGenreUpdating;
|
|
2529
2704
|
const isArtistFiltered = artistUuid !== null;
|
|
2530
2705
|
const canSizeByViews = !isArtistFiltered && hasViewsCount(visibleGenrePlaylists);
|
|
2531
|
-
const artistResult = (0,
|
|
2706
|
+
const artistResult = (0, import_genre_tree28.useArtist)(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
|
|
2532
2707
|
const filterArtistName = artistResult.status === "success" ? artistResult.artist.name : null;
|
|
2533
|
-
const filterArtist = (0,
|
|
2708
|
+
const filterArtist = (0, import_react18.useMemo)(
|
|
2534
2709
|
() => artistUuid !== null && filterArtistName !== null ? { uuid: artistUuid, name: filterArtistName } : null,
|
|
2535
2710
|
[artistUuid, filterArtistName]
|
|
2536
2711
|
);
|
|
2537
2712
|
const hasSelectedGenreNode = selectedGenrePlaylist !== void 0 && visibleGenrePlaylists.some(({ uuid }) => uuid === selectedGenrePlaylist.uuid);
|
|
2538
2713
|
const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: visibleGenrePlaylists, artist: filterArtist });
|
|
2539
2714
|
const openArtist = (uuid) => {
|
|
2540
|
-
if (
|
|
2715
|
+
if (selectedGenreUuid !== null) clearSelection();
|
|
2541
2716
|
if (songUuid !== null) changeSong(null);
|
|
2542
2717
|
changeArtist(uuid);
|
|
2543
2718
|
};
|
|
2544
2719
|
const openSong = (uuid) => {
|
|
2545
|
-
if (
|
|
2720
|
+
if (selectedGenreUuid !== null) clearSelection();
|
|
2546
2721
|
changeSong(uuid);
|
|
2547
2722
|
};
|
|
2548
|
-
const openSongRef = (0,
|
|
2723
|
+
const openSongRef = (0, import_react18.useRef)(openSong);
|
|
2549
2724
|
openSongRef.current = openSong;
|
|
2550
|
-
const resolvedRenderSongLink = (0,
|
|
2551
|
-
(song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ (0,
|
|
2725
|
+
const resolvedRenderSongLink = (0, import_react18.useCallback)(
|
|
2726
|
+
(song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
|
|
2552
2727
|
[renderSongLink]
|
|
2553
2728
|
);
|
|
2554
|
-
const [expandedSongUuid, setExpandedSongUuid] = (0,
|
|
2555
|
-
const [expandedArtistUuid, setExpandedArtistUuid] = (0,
|
|
2729
|
+
const [expandedSongUuid, setExpandedSongUuid] = (0, import_react18.useState)(null);
|
|
2730
|
+
const [expandedArtistUuid, setExpandedArtistUuid] = (0, import_react18.useState)(null);
|
|
2556
2731
|
const artistBaseUrl = artistSearchBaseUrl ?? getBackendBaseUrl;
|
|
2557
2732
|
const songBaseUrl = getBackendBaseUrl;
|
|
2558
2733
|
const artistPanel = useArtistPanel({
|
|
@@ -2560,7 +2735,7 @@ function GenreTreeView({
|
|
|
2560
2735
|
scope,
|
|
2561
2736
|
getBackendBaseUrl: artistBaseUrl,
|
|
2562
2737
|
getShareUrl: getShareUrlRef.current,
|
|
2563
|
-
renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0,
|
|
2738
|
+
renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
|
|
2564
2739
|
expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
|
|
2565
2740
|
onExpandedChange: (on) => {
|
|
2566
2741
|
if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
|
|
@@ -2569,7 +2744,7 @@ function GenreTreeView({
|
|
|
2569
2744
|
renderSongLink: resolvedRenderSongLink,
|
|
2570
2745
|
onClose: () => changeArtist(null)
|
|
2571
2746
|
});
|
|
2572
|
-
const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0,
|
|
2747
|
+
const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
|
|
2573
2748
|
const getGenrePath = (genre) => {
|
|
2574
2749
|
const path = [];
|
|
2575
2750
|
let current = visibleGenrePlaylists.find((playlist) => playlist.criteria?.uuid === genre.uuid);
|
|
@@ -2587,9 +2762,9 @@ function GenreTreeView({
|
|
|
2587
2762
|
getShareUrl: getShareUrlRef.current,
|
|
2588
2763
|
renderArtistLink: resolvedRenderArtistLink,
|
|
2589
2764
|
getGenrePath,
|
|
2590
|
-
onSelectGenre: (
|
|
2765
|
+
onSelectGenre: (genrePlaylist2) => {
|
|
2591
2766
|
changeSong(null);
|
|
2592
|
-
selectGenrePlaylist(
|
|
2767
|
+
selectGenrePlaylist(genrePlaylist2);
|
|
2593
2768
|
},
|
|
2594
2769
|
expanded: controlledSongExpanded ?? (songUuid !== null && expandedSongUuid === songUuid),
|
|
2595
2770
|
onExpandedChange: (on) => {
|
|
@@ -2598,11 +2773,11 @@ function GenreTreeView({
|
|
|
2598
2773
|
},
|
|
2599
2774
|
onClose: () => changeSong(null)
|
|
2600
2775
|
});
|
|
2601
|
-
const builtInSongPanel = overlayPanel ||
|
|
2602
|
-
const builtInArtistPanel = overlayPanel ||
|
|
2776
|
+
const builtInSongPanel = overlayPanel || selectedGenreUuid !== null ? void 0 : songPanel;
|
|
2777
|
+
const builtInArtistPanel = overlayPanel || selectedGenreUuid !== null || builtInSongPanel ? void 0 : artistPanel;
|
|
2603
2778
|
const builtInPanel = builtInSongPanel ?? builtInArtistPanel;
|
|
2604
|
-
const [uncontrolledCollapsed, setUncontrolledCollapsed] = (0,
|
|
2605
|
-
const [uncontrolledExpanded, setUncontrolledExpanded] = (0,
|
|
2779
|
+
const [uncontrolledCollapsed, setUncontrolledCollapsed] = (0, import_react18.useState)(false);
|
|
2780
|
+
const [uncontrolledExpanded, setUncontrolledExpanded] = (0, import_react18.useState)(false);
|
|
2606
2781
|
const isCollapsed = collapsed ?? uncontrolledCollapsed;
|
|
2607
2782
|
const isExpanded = expanded ?? uncontrolledExpanded;
|
|
2608
2783
|
const changeCollapsed = (next) => {
|
|
@@ -2613,155 +2788,114 @@ function GenreTreeView({
|
|
|
2613
2788
|
setUncontrolledExpanded(next);
|
|
2614
2789
|
onExpandedChange?.(next);
|
|
2615
2790
|
};
|
|
2616
|
-
(0,
|
|
2791
|
+
(0, import_react18.useEffect)(() => {
|
|
2617
2792
|
setUncontrolledCollapsed(false);
|
|
2618
|
-
}, [
|
|
2619
|
-
const [isWheelRevealed, setIsWheelRevealed] = (0,
|
|
2793
|
+
}, [selectedGenreUuid, artistUuid, songUuid]);
|
|
2794
|
+
const [isWheelRevealed, setIsWheelRevealed] = (0, import_react18.useState)(false);
|
|
2620
2795
|
const usesHandoff = viewMode === "wheel" || viewMode === "pop-core";
|
|
2621
|
-
const isTreeReady = !isLoading && (!usesHandoff || isWheelRevealed);
|
|
2622
|
-
const [resolveStyle, setResolveStyle] = (0,
|
|
2623
|
-
const handleNodeStyleResolverChange = (0,
|
|
2796
|
+
const isTreeReady = !isLoading && !isError && (!usesHandoff || isWheelRevealed);
|
|
2797
|
+
const [resolveStyle, setResolveStyle] = (0, import_react18.useState)(null);
|
|
2798
|
+
const handleNodeStyleResolverChange = (0, import_react18.useCallback)(
|
|
2624
2799
|
(resolver) => setResolveStyle(() => resolver),
|
|
2625
2800
|
[]
|
|
2626
2801
|
);
|
|
2627
|
-
const panelRef = (0,
|
|
2628
|
-
const obscuredInset = (0,
|
|
2802
|
+
const panelRef = (0, import_react18.useRef)(null);
|
|
2803
|
+
const obscuredInset = (0, import_ui17.useObscuredInset)(panelRef);
|
|
2629
2804
|
const hostPanel = { obscuredInset, onNodeStyleResolverChange: handleNodeStyleResolverChange };
|
|
2630
2805
|
const nodeMetric = isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric;
|
|
2631
|
-
const treeNodes = (0,
|
|
2632
|
-
const relations = (0,
|
|
2806
|
+
const treeNodes = (0, import_react18.useMemo)(() => toTreeNodes(visibleGenrePlaylists, nodeMetric), [visibleGenrePlaylists, nodeMetric]);
|
|
2807
|
+
const relations = (0, import_genre_tree_view5.useGenreNodeRelations)(
|
|
2633
2808
|
treeNodes,
|
|
2634
2809
|
!overlayPanel && hasSelectedGenreNode ? selectedNodeId : null,
|
|
2635
2810
|
resolveStyle
|
|
2636
2811
|
);
|
|
2637
|
-
const [selectionHistory, setSelectionHistory] = (0,
|
|
2638
|
-
|
|
2639
|
-
previous:
|
|
2640
|
-
});
|
|
2641
|
-
if (selectionHistory.current !== selectedNodeId) {
|
|
2642
|
-
setSelectionHistory({
|
|
2643
|
-
current: selectedNodeId,
|
|
2644
|
-
previous: selectionHistory.current ?? selectionHistory.previous
|
|
2645
|
-
});
|
|
2812
|
+
const [selectionHistory, setSelectionHistory] = (0, import_react18.useState)({ current: selectedGenreUuid, previous: null });
|
|
2813
|
+
if (selectionHistory.current !== selectedGenreUuid) {
|
|
2814
|
+
setSelectionHistory({ current: selectedGenreUuid, previous: selectionHistory.current ?? selectionHistory.previous });
|
|
2646
2815
|
}
|
|
2647
|
-
const
|
|
2648
|
-
const handlePrevious = previousPlaylist && visibleGenrePlaylists.includes(previousPlaylist) ? () => handleGenreSearchSelect(previousPlaylist) : void 0;
|
|
2816
|
+
const handlePrevious = selectionHistory.previous ? () => selectGenre(selectionHistory.previous) : void 0;
|
|
2649
2817
|
const { showPopup, hidePopup } = (0, import_popup5.usePopup)();
|
|
2818
|
+
const reopenPanel = () => {
|
|
2819
|
+
setUncontrolledCollapsed(false);
|
|
2820
|
+
onCollapsedChange?.(false);
|
|
2821
|
+
};
|
|
2650
2822
|
const handleSelectedNodeChange = (node) => {
|
|
2823
|
+
reopenPanel();
|
|
2651
2824
|
if (node !== null && songUuid !== null) changeSong(null);
|
|
2652
2825
|
selectNode(node);
|
|
2653
2826
|
};
|
|
2654
2827
|
const handleGenreSearchClear = clearSelection;
|
|
2655
|
-
const handleGenreSearchSelect = (
|
|
2828
|
+
const handleGenreSearchSelect = (genrePlaylist2) => {
|
|
2829
|
+
reopenPanel();
|
|
2656
2830
|
if (songUuid !== null) changeSong(null);
|
|
2657
|
-
selectGenrePlaylist(
|
|
2831
|
+
selectGenrePlaylist(genrePlaylist2);
|
|
2658
2832
|
};
|
|
2659
2833
|
const selectedUuid = selectedGenrePlaylist?.uuid;
|
|
2660
|
-
const selectedChildren = selectedUuid ? visibleGenrePlaylists.filter((
|
|
2834
|
+
const selectedChildren = selectedUuid ? visibleGenrePlaylists.filter((genrePlaylist2) => genrePlaylist2.parent?.uuid === selectedUuid) : [];
|
|
2661
2835
|
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;
|
|
2662
2836
|
const handleGenreSearchZoomIn = () => {
|
|
2663
2837
|
if (selectedUuid) focus.onFocusGenreChange?.(selectedUuid, "zoom-in");
|
|
2664
2838
|
};
|
|
2665
|
-
const zoomInRef = (0,
|
|
2839
|
+
const zoomInRef = (0, import_react18.useRef)(void 0);
|
|
2666
2840
|
zoomInRef.current = canZoomIn ? handleGenreSearchZoomIn : void 0;
|
|
2667
|
-
const
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
const
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
kind: "genre",
|
|
2697
|
-
genrePlaylistUuid: genrePlaylist.uuid,
|
|
2698
|
-
name: criteria.name,
|
|
2699
|
-
scope,
|
|
2700
|
-
artistUuid
|
|
2701
|
-
};
|
|
2702
|
-
const shareUrl = getShareUrlRef.current?.(shareTarget) ?? void 0;
|
|
2703
|
-
const popSibling = canToggleSide && side === "core" ? visibleGenrePlaylists.find(
|
|
2704
|
-
(sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist.uuid && sibling.criteria?.side === "pop"
|
|
2705
|
-
) : void 0;
|
|
2706
|
-
return {
|
|
2707
|
-
primary: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_jsx_runtime39.Fragment, { children: [
|
|
2708
|
-
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2709
|
-
GenreInfoPanelBody,
|
|
2841
|
+
const genrePlaylist = selectedGenrePlaylist;
|
|
2842
|
+
const criteria = genrePlaylist?.criteria;
|
|
2843
|
+
const parentPlaylist = visibleGenrePlaylists.find(({ uuid }) => uuid === genrePlaylist?.parent?.uuid);
|
|
2844
|
+
const canToggleSide = !readOnly && criteria !== void 0 && criteria !== null && parentPlaylist !== void 0 && parentPlaylist.parent === null;
|
|
2845
|
+
const side = criteria?.side ?? "core";
|
|
2846
|
+
const popSibling = canToggleSide && side === "core" ? visibleGenrePlaylists.find((sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist?.uuid && sibling.criteria?.side === "pop") : void 0;
|
|
2847
|
+
const shareTarget = selectedGenreUuid ? { kind: "genre", genreUuid: selectedGenreUuid, name: criteria?.name ?? "Genre", scope, artistUuid } : void 0;
|
|
2848
|
+
const shareUrl = shareTarget ? getShareUrlRef.current?.(shareTarget) ?? void 0 : void 0;
|
|
2849
|
+
const genreContent = useGenreDetailContent({
|
|
2850
|
+
genreUuid: selectedGenreUuid,
|
|
2851
|
+
scope,
|
|
2852
|
+
getBackendBaseUrl,
|
|
2853
|
+
onSelectGenre: (uuid) => {
|
|
2854
|
+
reopenPanel();
|
|
2855
|
+
selectGenre(uuid);
|
|
2856
|
+
},
|
|
2857
|
+
expanded: isExpanded,
|
|
2858
|
+
playback,
|
|
2859
|
+
genrePlaylist: selectedGenrePlaylist,
|
|
2860
|
+
band: {
|
|
2861
|
+
...relations ? { node: relations.node } : {},
|
|
2862
|
+
shareTarget: shareUrl ? shareTarget : void 0,
|
|
2863
|
+
shareUrl,
|
|
2864
|
+
onZoomIn: zoomInRef.current,
|
|
2865
|
+
onAddChild: readOnly || !criteria ? void 0 : () => handleGenreCreationAction(criteria),
|
|
2866
|
+
onRename: readOnly || !criteria ? void 0 : () => handleGenreRenameAction(criteria),
|
|
2867
|
+
onChangeParent: readOnly || !genrePlaylist || !criteria ? void 0 : () => showPopup(
|
|
2868
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2869
|
+
ChangeParentPopup,
|
|
2710
2870
|
{
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
ancestors
|
|
2714
|
-
] }),
|
|
2715
|
-
genreUuid,
|
|
2716
|
-
genrePlaylist,
|
|
2717
|
-
catalogue: genrePlaylist.catalogue,
|
|
2718
|
-
wikidataId: criteria.wikidataId,
|
|
2871
|
+
genrePlaylist: { ...genrePlaylist, criteria },
|
|
2872
|
+
genrePlaylists: visibleGenrePlaylists,
|
|
2719
2873
|
scope,
|
|
2720
2874
|
getBackendBaseUrl,
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
node,
|
|
2724
|
-
shareTarget: shareUrl ? shareTarget : void 0,
|
|
2725
|
-
shareUrl,
|
|
2726
|
-
onZoomIn: zoomInRef.current,
|
|
2727
|
-
onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
|
|
2728
|
-
onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
|
|
2729
|
-
onChangeParent: readOnly ? void 0 : () => showPopup(
|
|
2730
|
-
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2731
|
-
ChangeParentPopup,
|
|
2732
|
-
{
|
|
2733
|
-
genrePlaylist: { ...genrePlaylist, criteria },
|
|
2734
|
-
genrePlaylists: visibleGenrePlaylists,
|
|
2735
|
-
scope,
|
|
2736
|
-
getBackendBaseUrl,
|
|
2737
|
-
onClose: hidePopup,
|
|
2738
|
-
onParentChanged
|
|
2739
|
-
}
|
|
2740
|
-
)
|
|
2741
|
-
),
|
|
2742
|
-
side,
|
|
2743
|
-
onToggleSide: canToggleSide ? () => updateGenre({ uuid: genreUuid, data: { side: side === "pop" ? "core" : "pop" } }) : void 0,
|
|
2744
|
-
toggleSideDisabledReason: popSibling ? `${popSibling.name} is already the pop side of ${parentPlaylist?.name}` : void 0,
|
|
2745
|
-
additionalActions
|
|
2746
|
-
},
|
|
2747
|
-
extras: {
|
|
2748
|
-
criteriaOverviewSchema,
|
|
2749
|
-
renderGenreDetailExtras,
|
|
2750
|
-
renderArtistLink,
|
|
2751
|
-
renderSongLink: resolvedRenderSongLink,
|
|
2752
|
-
artist: filterArtist,
|
|
2753
|
-
renderSummary,
|
|
2754
|
-
renderEssentialTracks,
|
|
2755
|
-
renderSongLeading,
|
|
2756
|
-
renderSongActions,
|
|
2757
|
-
renderTracksToolbar
|
|
2758
|
-
}
|
|
2875
|
+
onClose: hidePopup,
|
|
2876
|
+
onParentChanged
|
|
2759
2877
|
}
|
|
2760
|
-
)
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2878
|
+
)
|
|
2879
|
+
),
|
|
2880
|
+
side,
|
|
2881
|
+
onToggleSide: canToggleSide && criteria ? () => updateGenre({ uuid: criteria.uuid, data: { side: side === "pop" ? "core" : "pop" } }) : void 0,
|
|
2882
|
+
toggleSideDisabledReason: popSibling ? `${popSibling.name} is already the pop side of ${parentPlaylist?.name}` : void 0,
|
|
2883
|
+
additionalActions
|
|
2884
|
+
},
|
|
2885
|
+
extras: {
|
|
2886
|
+
criteriaOverviewSchema,
|
|
2887
|
+
renderGenreDetailExtras,
|
|
2888
|
+
renderArtistLink,
|
|
2889
|
+
renderSongLink: resolvedRenderSongLink,
|
|
2890
|
+
artist: filterArtist,
|
|
2891
|
+
renderSummary,
|
|
2892
|
+
renderEssentialTracks,
|
|
2893
|
+
renderSongLeading,
|
|
2894
|
+
renderSongActions,
|
|
2895
|
+
renderTracksToolbar
|
|
2896
|
+
},
|
|
2897
|
+
trailing: relations ? renderDangerZone?.(relations.node) : void 0
|
|
2898
|
+
});
|
|
2765
2899
|
const isRotationControlled = controlledAllowWheelRotation !== void 0;
|
|
2766
2900
|
const allowWheelRotation = controlledAllowWheelRotation ?? internalAllowWheelRotation;
|
|
2767
2901
|
let activePanel = null;
|
|
@@ -2775,16 +2909,16 @@ function GenreTreeView({
|
|
|
2775
2909
|
onClose: overlayPanel.onClose,
|
|
2776
2910
|
compact: false
|
|
2777
2911
|
};
|
|
2778
|
-
} else if (
|
|
2779
|
-
const { primary } = renderGenreBody(relations);
|
|
2912
|
+
} else if (selectedGenreUuid) {
|
|
2780
2913
|
activePanel = {
|
|
2781
|
-
title:
|
|
2782
|
-
content:
|
|
2914
|
+
title: genreContent.title,
|
|
2915
|
+
content: genreContent.content,
|
|
2916
|
+
aside: genreContent.aside,
|
|
2783
2917
|
expanded: isExpanded,
|
|
2784
2918
|
onExpandedChange: changeExpanded,
|
|
2785
2919
|
onClose: clearSelection,
|
|
2786
|
-
compact:
|
|
2787
|
-
headerAccent: { background: relations.fill, color: relations.textColor }
|
|
2920
|
+
compact: false,
|
|
2921
|
+
headerAccent: relations ? { background: relations.fill, color: relations.textColor } : genreContent.headerAccent
|
|
2788
2922
|
};
|
|
2789
2923
|
} else if (builtInPanel) {
|
|
2790
2924
|
activePanel = {
|
|
@@ -2797,36 +2931,36 @@ function GenreTreeView({
|
|
|
2797
2931
|
compact: true
|
|
2798
2932
|
};
|
|
2799
2933
|
}
|
|
2800
|
-
const isPanelShown =
|
|
2934
|
+
const isPanelShown = activePanel !== null && (selectedGenreUuid !== null || isTreeReady);
|
|
2801
2935
|
const detailTab = placement === "regions" && activePanel?.onExpandedChange ? {
|
|
2802
2936
|
edge: activePanel.expanded ? { label: "Shrink panel", onClick: () => activePanel.onExpandedChange?.(false) } : isCollapsed ? null : { label: "Hide panel", onClick: () => changeCollapsed(true) },
|
|
2803
2937
|
center: activePanel.expanded ? null : isCollapsed ? { label: "Show panel", onClick: () => changeCollapsed(false) } : activePanel.onExpandedChange ? { label: "Extend panel", onClick: () => activePanel.onExpandedChange?.(true) } : null
|
|
2804
2938
|
} : void 0;
|
|
2805
|
-
const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ (0,
|
|
2806
|
-
|
|
2939
|
+
const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2940
|
+
import_ui17.IconButton,
|
|
2807
2941
|
{
|
|
2808
2942
|
"aria-label": activePanel.expanded ? "Shrink panel" : "Extend panel",
|
|
2809
2943
|
style: activePanel.headerAccent && { color: "inherit" },
|
|
2810
2944
|
onClick: () => activePanel.onExpandedChange?.(!activePanel.expanded),
|
|
2811
|
-
children: activePanel.expanded ? /* @__PURE__ */ (0,
|
|
2945
|
+
children: activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react13.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react13.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
2812
2946
|
}
|
|
2813
2947
|
) : null;
|
|
2814
2948
|
const renderArtistFilter = () => {
|
|
2815
2949
|
if (isLoading || !filterArtist) return null;
|
|
2816
|
-
return /* @__PURE__ */ (0,
|
|
2817
|
-
/* @__PURE__ */ (0,
|
|
2950
|
+
return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
|
|
2951
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2818
2952
|
"button",
|
|
2819
2953
|
{
|
|
2820
2954
|
type: "button",
|
|
2821
2955
|
className: "py-1 pl-3 pr-2 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] hover:underline",
|
|
2822
2956
|
"aria-label": `Show artist ${filterArtist.name}`,
|
|
2823
2957
|
onClick: () => {
|
|
2824
|
-
if (
|
|
2958
|
+
if (selectedGenreUuid !== null) clearSelection();
|
|
2825
2959
|
},
|
|
2826
2960
|
children: filterArtist.name
|
|
2827
2961
|
}
|
|
2828
2962
|
),
|
|
2829
|
-
/* @__PURE__ */ (0,
|
|
2963
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2830
2964
|
"button",
|
|
2831
2965
|
{
|
|
2832
2966
|
type: "button",
|
|
@@ -2838,8 +2972,8 @@ function GenreTreeView({
|
|
|
2838
2972
|
)
|
|
2839
2973
|
] });
|
|
2840
2974
|
};
|
|
2841
|
-
const actions = /* @__PURE__ */ (0,
|
|
2842
|
-
!isLoading && /* @__PURE__ */ (0,
|
|
2975
|
+
const actions = /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
|
|
2976
|
+
!isLoading && !isError && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2843
2977
|
GenreTreeSearch,
|
|
2844
2978
|
{
|
|
2845
2979
|
artistBaseUrl,
|
|
@@ -2857,7 +2991,12 @@ function GenreTreeView({
|
|
|
2857
2991
|
SearchGroups,
|
|
2858
2992
|
genrePlaylists: visibleGenrePlaylists,
|
|
2859
2993
|
onSelect: handleGenreSearchSelect,
|
|
2860
|
-
|
|
2994
|
+
onSelectGenre: (uuid) => {
|
|
2995
|
+
reopenPanel();
|
|
2996
|
+
if (songUuid) changeSong(null);
|
|
2997
|
+
selectGenre(uuid);
|
|
2998
|
+
},
|
|
2999
|
+
selectedName: (selectedGenreUuid ? genreContent.title : null) ?? (typeof builtInPanel?.title === "string" ? builtInPanel.title : null),
|
|
2861
3000
|
onClear: builtInSongPanel ? () => changeSong(null) : builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
|
|
2862
3001
|
onPrevious: handlePrevious,
|
|
2863
3002
|
onQueryChange: onSearchQueryChange,
|
|
@@ -2865,10 +3004,10 @@ function GenreTreeView({
|
|
|
2865
3004
|
extraGroups: extraSearchGroups
|
|
2866
3005
|
}
|
|
2867
3006
|
),
|
|
2868
|
-
!isLoading && filterArtist && /* @__PURE__ */ (0,
|
|
2869
|
-
/* @__PURE__ */ (0,
|
|
2870
|
-
!isLoading && !isControlled && /* @__PURE__ */ (0,
|
|
2871
|
-
|
|
3007
|
+
!isLoading && !isError && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
|
|
3008
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("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: [
|
|
3009
|
+
!isLoading && !isError && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
3010
|
+
import_ui17.SegmentedControl,
|
|
2872
3011
|
{
|
|
2873
3012
|
"aria-label": "Tree view mode",
|
|
2874
3013
|
value: viewMode,
|
|
@@ -2881,14 +3020,14 @@ function GenreTreeView({
|
|
|
2881
3020
|
]
|
|
2882
3021
|
}
|
|
2883
3022
|
),
|
|
2884
|
-
!isLoading && showDisplayOptions && /* @__PURE__ */ (0,
|
|
3023
|
+
!isLoading && !isError && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2885
3024
|
"div",
|
|
2886
3025
|
{
|
|
2887
3026
|
className: "flex items-center gap-1",
|
|
2888
3027
|
role: "group",
|
|
2889
3028
|
"aria-label": "Tree display options",
|
|
2890
|
-
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0,
|
|
2891
|
-
|
|
3029
|
+
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
3030
|
+
import_ui17.Button,
|
|
2892
3031
|
{
|
|
2893
3032
|
variant: allowWheelRotation ? "default" : "outline",
|
|
2894
3033
|
size: "sm",
|
|
@@ -2898,44 +3037,48 @@ function GenreTreeView({
|
|
|
2898
3037
|
)
|
|
2899
3038
|
}
|
|
2900
3039
|
),
|
|
2901
|
-
!isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0,
|
|
2902
|
-
|
|
3040
|
+
!isLoading && !isError && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
3041
|
+
import_ui17.IconTextButton,
|
|
2903
3042
|
{
|
|
2904
|
-
icon:
|
|
3043
|
+
icon: import_lucide_react12.Plus,
|
|
2905
3044
|
text: "Add root",
|
|
2906
3045
|
onClick: () => handleGenreCreationAction(null)
|
|
2907
3046
|
}
|
|
2908
3047
|
)
|
|
2909
3048
|
] })
|
|
2910
3049
|
] });
|
|
2911
|
-
return /* @__PURE__ */ (0,
|
|
3050
|
+
return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
|
|
2912
3051
|
"div",
|
|
2913
3052
|
{
|
|
2914
3053
|
"data-left-panel": isPanelShown && !isCollapsed ? void 0 : "hidden",
|
|
2915
3054
|
className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
|
|
2916
3055
|
children: [
|
|
2917
|
-
/* @__PURE__ */ (0,
|
|
3056
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2918
3057
|
"div",
|
|
2919
3058
|
{
|
|
2920
3059
|
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"}`,
|
|
2921
3060
|
children: actions
|
|
2922
3061
|
}
|
|
2923
3062
|
),
|
|
2924
|
-
!isLoading && filterArtist && /* @__PURE__ */ (0,
|
|
3063
|
+
!isLoading && !isError && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2925
3064
|
"div",
|
|
2926
3065
|
{
|
|
2927
3066
|
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"}`,
|
|
2928
3067
|
children: renderArtistFilter()
|
|
2929
3068
|
}
|
|
2930
3069
|
),
|
|
2931
|
-
/* @__PURE__ */ (0,
|
|
2932
|
-
!isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0,
|
|
2933
|
-
isLoading ? /* @__PURE__ */ (0,
|
|
3070
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
|
|
3071
|
+
!isLoading && !isError && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
|
|
3072
|
+
isLoading ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_genre_tree_view5.GenreTreeViewSkeleton, { viewMode }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("p", { role: "alert", className: "m-auto text-center", children: [
|
|
3073
|
+
"Couldn't load the genre tree.",
|
|
3074
|
+
" ",
|
|
3075
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)("button", { type: "button", className: "underline", onClick: refetch, children: "Retry" })
|
|
3076
|
+
] }) : viewMode === "wheel" ? (
|
|
2934
3077
|
// The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
|
|
2935
3078
|
// and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
|
|
2936
3079
|
// Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
|
|
2937
3080
|
// The panel's own Zoom in button is hidden: the search bar carries it.
|
|
2938
|
-
/* @__PURE__ */ (0,
|
|
3081
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_genre_tree_view5.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2939
3082
|
GenrePlaylistTreeWheel,
|
|
2940
3083
|
{
|
|
2941
3084
|
scope,
|
|
@@ -2952,9 +3095,9 @@ function GenreTreeView({
|
|
|
2952
3095
|
allowWheelRotation,
|
|
2953
3096
|
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2954
3097
|
busy,
|
|
2955
|
-
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0,
|
|
3098
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
2956
3099
|
floatingControlsLeading,
|
|
2957
|
-
canSizeByViews && /* @__PURE__ */ (0,
|
|
3100
|
+
canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2958
3101
|
] }) : void 0,
|
|
2959
3102
|
defaultViewport,
|
|
2960
3103
|
viewportRestore,
|
|
@@ -2963,7 +3106,7 @@ function GenreTreeView({
|
|
|
2963
3106
|
) }) })
|
|
2964
3107
|
) : viewMode === "pop-core" ? (
|
|
2965
3108
|
// Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
|
|
2966
|
-
/* @__PURE__ */ (0,
|
|
3109
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_genre_tree_view5.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2967
3110
|
GenrePlaylistTreeWheelRadialPopCore,
|
|
2968
3111
|
{
|
|
2969
3112
|
scope,
|
|
@@ -2980,9 +3123,9 @@ function GenreTreeView({
|
|
|
2980
3123
|
allowWheelRotation,
|
|
2981
3124
|
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2982
3125
|
busy,
|
|
2983
|
-
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0,
|
|
3126
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
2984
3127
|
floatingControlsLeading,
|
|
2985
|
-
canSizeByViews && /* @__PURE__ */ (0,
|
|
3128
|
+
canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2986
3129
|
] }) : void 0,
|
|
2987
3130
|
defaultViewport,
|
|
2988
3131
|
viewportRestore,
|
|
@@ -2990,7 +3133,7 @@ function GenreTreeView({
|
|
|
2990
3133
|
...focus
|
|
2991
3134
|
}
|
|
2992
3135
|
) }) })
|
|
2993
|
-
) : viewMode === "outline" ? /* @__PURE__ */ (0,
|
|
3136
|
+
) : viewMode === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2994
3137
|
GenrePlaylistTreeWheelRadialPopCore,
|
|
2995
3138
|
{
|
|
2996
3139
|
outline: true,
|
|
@@ -3006,13 +3149,13 @@ function GenreTreeView({
|
|
|
3006
3149
|
selectedNodeId,
|
|
3007
3150
|
readOnly
|
|
3008
3151
|
}
|
|
3009
|
-
) }) : /* @__PURE__ */ (0,
|
|
3152
|
+
) }) : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("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(
|
|
3010
3153
|
([uuid, genrePlaylistTreePerRoot]) => {
|
|
3011
|
-
return /* @__PURE__ */ (0,
|
|
3154
|
+
return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
3012
3155
|
"div",
|
|
3013
3156
|
{
|
|
3014
3157
|
className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
|
|
3015
|
-
children: /* @__PURE__ */ (0,
|
|
3158
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
3016
3159
|
GenrePlaylistTreePerRoot,
|
|
3017
3160
|
{
|
|
3018
3161
|
scope,
|
|
@@ -3038,8 +3181,8 @@ function GenreTreeView({
|
|
|
3038
3181
|
);
|
|
3039
3182
|
}
|
|
3040
3183
|
) }),
|
|
3041
|
-
/* @__PURE__ */ (0,
|
|
3042
|
-
|
|
3184
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
3185
|
+
import_ui17.DetailPanel,
|
|
3043
3186
|
{
|
|
3044
3187
|
ref: panelRef,
|
|
3045
3188
|
side: "left",
|
|
@@ -3049,13 +3192,13 @@ function GenreTreeView({
|
|
|
3049
3192
|
headerAccent: activePanel?.headerAccent,
|
|
3050
3193
|
headerActions: expandButton,
|
|
3051
3194
|
onClose: activePanel?.onClose ?? noop,
|
|
3052
|
-
collapsed: isCollapsed || !
|
|
3195
|
+
collapsed: isCollapsed || !isPanelShown,
|
|
3053
3196
|
expanded: activePanel?.expanded,
|
|
3054
3197
|
onExpandedChange: activePanel?.onExpandedChange,
|
|
3055
3198
|
aside: activePanel?.aside,
|
|
3056
3199
|
edgeTab: detailTab,
|
|
3057
|
-
headerClassName:
|
|
3058
|
-
children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ (0,
|
|
3200
|
+
headerClassName: "pt-[calc(var(--layout-search-h)+var(--layout-panel-inset)+0.5rem)]",
|
|
3201
|
+
children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
|
|
3059
3202
|
activePanel.content,
|
|
3060
3203
|
activePanel.aside
|
|
3061
3204
|
] }) : activePanel?.content
|
|
@@ -3067,134 +3210,6 @@ function GenreTreeView({
|
|
|
3067
3210
|
);
|
|
3068
3211
|
}
|
|
3069
3212
|
|
|
3070
|
-
// src/genre-tree/GenreDetailPanel.tsx
|
|
3071
|
-
var import_react18 = require("react");
|
|
3072
|
-
var import_genre_tree_view5 = require("@behindthemusictree/genre-tree-view");
|
|
3073
|
-
var import_ui17 = require("@behindthemusictree/ui");
|
|
3074
|
-
var import_lucide_react13 = require("lucide-react");
|
|
3075
|
-
var import_genre_tree28 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3076
|
-
var import_jsx_runtime40 = require("react/jsx-runtime");
|
|
3077
|
-
var RELATIVE_ITEM_COUNT = 1;
|
|
3078
|
-
function toNode(genre, parentId) {
|
|
3079
|
-
return {
|
|
3080
|
-
id: genre.uuid,
|
|
3081
|
-
parentId,
|
|
3082
|
-
name: genre.name,
|
|
3083
|
-
itemCount: RELATIVE_ITEM_COUNT,
|
|
3084
|
-
side: genre.side ?? void 0,
|
|
3085
|
-
wikidataId: genre.wikidataId
|
|
3086
|
-
};
|
|
3087
|
-
}
|
|
3088
|
-
function GenreDetailPanel({
|
|
3089
|
-
genreUuid,
|
|
3090
|
-
scope,
|
|
3091
|
-
getBackendBaseUrl,
|
|
3092
|
-
onClose,
|
|
3093
|
-
onSelectGenre,
|
|
3094
|
-
onZoomIn,
|
|
3095
|
-
shareUrl,
|
|
3096
|
-
shareTarget,
|
|
3097
|
-
collapsed,
|
|
3098
|
-
expanded,
|
|
3099
|
-
onExpandedChange,
|
|
3100
|
-
edgeTab,
|
|
3101
|
-
extras
|
|
3102
|
-
}) {
|
|
3103
|
-
const { data: detail, isPending, isError } = (0, import_genre_tree28.useFetchGenreDetail)(genreUuid, scope, getBackendBaseUrl);
|
|
3104
|
-
const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: [] });
|
|
3105
|
-
const view = (0, import_react18.useMemo)(() => {
|
|
3106
|
-
if (!detail) return null;
|
|
3107
|
-
const rootColor = (0, import_genre_tree_view5.getGenreTreeColor)(detail.root.wikidataId);
|
|
3108
|
-
const relative = (genre, parentId) => ({
|
|
3109
|
-
node: toNode(genre, parentId),
|
|
3110
|
-
fill: (0, import_genre_tree_view5.tintSurface)(rootColor),
|
|
3111
|
-
textColor: import_genre_tree_view5.TEXT_COLOR
|
|
3112
|
-
});
|
|
3113
|
-
const ancestors2 = [...detail.ascendants].sort((a, b) => b.degree - a.degree);
|
|
3114
|
-
return {
|
|
3115
|
-
node: {
|
|
3116
|
-
id: detail.uuid,
|
|
3117
|
-
parentId: detail.parent?.uuid ?? null,
|
|
3118
|
-
name: detail.name,
|
|
3119
|
-
itemCount: scope === "reference" ? (0, import_genre_tree28.genreMusicCount)(import_genre_tree28.GenrePlaylistSummarySchema.parse(detail.criteriaPlaylist)) : detail.tracksCount,
|
|
3120
|
-
wikidataId: detail.wikidataId
|
|
3121
|
-
},
|
|
3122
|
-
fill: rootColor,
|
|
3123
|
-
parentNode: detail.parent ? relative(detail.parent, null) : null,
|
|
3124
|
-
childNodes: detail.children.map((child) => relative(child, detail.uuid)),
|
|
3125
|
-
ancestorNodes: ancestors2.map(
|
|
3126
|
-
({ ascendant }, index) => relative(ascendant, index === 0 ? null : ancestors2[index - 1].ascendant.uuid)
|
|
3127
|
-
)
|
|
3128
|
-
};
|
|
3129
|
-
}, [detail, scope]);
|
|
3130
|
-
if (isError) {
|
|
3131
|
-
return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
|
|
3132
|
-
}
|
|
3133
|
-
if (isPending || !detail || !view) {
|
|
3134
|
-
return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
|
|
3135
|
-
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui17.Skeleton, { className: "h-6 w-2/3" }),
|
|
3136
|
-
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui17.Skeleton, { className: "h-4 w-full" }),
|
|
3137
|
-
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui17.Skeleton, { className: "h-4 w-3/4" })
|
|
3138
|
-
] });
|
|
3139
|
-
}
|
|
3140
|
-
const selectGenre = (nodeId) => onSelectGenre(nodeId);
|
|
3141
|
-
const family = /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
3142
|
-
import_genre_tree_view5.GenreNodeFamily,
|
|
3143
|
-
{
|
|
3144
|
-
node: view.node,
|
|
3145
|
-
fill: view.fill,
|
|
3146
|
-
textColor: import_genre_tree_view5.ACCENT_TEXT_COLOR,
|
|
3147
|
-
parentNode: view.parentNode,
|
|
3148
|
-
childNodes: view.childNodes,
|
|
3149
|
-
onSelectNode: selectGenre
|
|
3150
|
-
}
|
|
3151
|
-
);
|
|
3152
|
-
const ancestors = /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_genre_tree_view5.GenreNodeAncestors, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
|
|
3153
|
-
return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
|
|
3154
|
-
import_ui17.DetailPanel,
|
|
3155
|
-
{
|
|
3156
|
-
side: "left",
|
|
3157
|
-
title: view.node.name,
|
|
3158
|
-
headerAccent: { background: view.fill, color: import_genre_tree_view5.ACCENT_TEXT_COLOR },
|
|
3159
|
-
headerActions: onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
3160
|
-
import_ui17.IconButton,
|
|
3161
|
-
{
|
|
3162
|
-
"aria-label": expanded ? "Shrink panel" : "Extend panel",
|
|
3163
|
-
style: { color: "inherit" },
|
|
3164
|
-
onClick: () => onExpandedChange(!expanded),
|
|
3165
|
-
children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react13.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react13.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
3166
|
-
}
|
|
3167
|
-
) : void 0,
|
|
3168
|
-
onClose,
|
|
3169
|
-
collapsed,
|
|
3170
|
-
expanded,
|
|
3171
|
-
onExpandedChange,
|
|
3172
|
-
edgeTab,
|
|
3173
|
-
children: [
|
|
3174
|
-
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
3175
|
-
GenreInfoPanelBody,
|
|
3176
|
-
{
|
|
3177
|
-
family: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
|
|
3178
|
-
family,
|
|
3179
|
-
ancestors
|
|
3180
|
-
] }),
|
|
3181
|
-
genreUuid: detail.uuid,
|
|
3182
|
-
genrePlaylist: scope === "reference" ? import_genre_tree28.GenrePlaylistSummarySchema.parse(detail.criteriaPlaylist) : { ...detail.criteriaPlaylist, tracksCount: detail.tracksCount },
|
|
3183
|
-
catalogue: detail.catalogue,
|
|
3184
|
-
wikidataId: detail.wikidataId,
|
|
3185
|
-
scope,
|
|
3186
|
-
getBackendBaseUrl,
|
|
3187
|
-
playback,
|
|
3188
|
-
band: { node: view.node, onZoomIn, shareUrl, shareTarget: shareUrl ? shareTarget : void 0 },
|
|
3189
|
-
extras
|
|
3190
|
-
}
|
|
3191
|
-
),
|
|
3192
|
-
!expanded && ancestors
|
|
3193
|
-
]
|
|
3194
|
-
}
|
|
3195
|
-
);
|
|
3196
|
-
}
|
|
3197
|
-
|
|
3198
3213
|
// src/genre-tree/index.ts
|
|
3199
3214
|
var import_genre_tree_view6 = require("@behindthemusictree/genre-tree-view");
|
|
3200
3215
|
|