@behindthemusictree/app-kit-ui 29.1.0 → 31.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-36YVZ6JR.js → chunk-AQASG5KT.js} +874 -628
- package/dist/chunk-AQASG5KT.js.map +1 -0
- package/dist/genre-tree/index.cjs +892 -662
- package/dist/genre-tree/index.cjs.map +1 -1
- package/dist/genre-tree/index.d.ts +74 -49
- package/dist/genre-tree/index.js +3 -7
- package/dist/index.cjs +889 -659
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -7
- package/package.json +6 -6
- package/dist/chunk-36YVZ6JR.js.map +0 -1
|
@@ -30,27 +30,52 @@ __export(genre_tree_exports, {
|
|
|
30
30
|
GenreTreeView: () => GenreTreeView,
|
|
31
31
|
GenreTreeViewSkeleton: () => import_genre_tree_view6.GenreTreeViewSkeleton,
|
|
32
32
|
GenreTreeWheelSkeleton: () => import_genre_tree_view6.GenreTreeWheelSkeleton,
|
|
33
|
-
PanelEdgeTab: () => PanelEdgeTab,
|
|
34
33
|
PlayerPanel: () => PlayerPanel,
|
|
35
34
|
Rating: () => Rating,
|
|
36
35
|
TrackItem: () => TrackItem,
|
|
37
36
|
TrackListSidebar: () => TrackListSidebar,
|
|
38
37
|
TrackPositionPlayPause: () => TrackPositionPlayPause,
|
|
39
|
-
useGenrePlaylistPlayback: () => useGenrePlaylistPlayback
|
|
40
|
-
useLeftDetailPanelLayout: () => useLeftDetailPanelLayout
|
|
38
|
+
useGenrePlaylistPlayback: () => useGenrePlaylistPlayback
|
|
41
39
|
});
|
|
42
40
|
module.exports = __toCommonJS(genre_tree_exports);
|
|
43
41
|
|
|
44
42
|
// src/genre-tree/GenreTreeView.tsx
|
|
45
|
-
var
|
|
43
|
+
var import_react16 = require("react");
|
|
46
44
|
var import_react_query3 = require("@tanstack/react-query");
|
|
47
45
|
var import_lucide_react11 = require("lucide-react");
|
|
48
|
-
var
|
|
46
|
+
var import_lucide_react12 = require("lucide-react");
|
|
47
|
+
var import_ui15 = require("@behindthemusictree/ui");
|
|
49
48
|
var import_genre_tree_view4 = require("@behindthemusictree/genre-tree-view");
|
|
50
|
-
var
|
|
51
|
-
var
|
|
49
|
+
var import_genre_tree18 = require("@behindthemusictree/app-kit/genre-tree");
|
|
50
|
+
var import_genre_tree19 = require("@behindthemusictree/app-kit/genre-tree");
|
|
52
51
|
var import_popup5 = require("@behindthemusictree/app-kit/popup");
|
|
53
52
|
|
|
53
|
+
// src/genre-tree/playlist-tree/size-metric.ts
|
|
54
|
+
var hasViewsCount = (genrePlaylists) => genrePlaylists.every((genrePlaylist) => genrePlaylist.viewsCount !== void 0);
|
|
55
|
+
var genrePlaylistSize = (genrePlaylist, metric) => {
|
|
56
|
+
if (metric === "songs") return genrePlaylist.tracksCount;
|
|
57
|
+
if (genrePlaylist.viewsCount === void 0) {
|
|
58
|
+
throw new Error(`Genre playlist ${genrePlaylist.uuid} has no viewsCount to size by views`);
|
|
59
|
+
}
|
|
60
|
+
return genrePlaylist.viewsCount;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// src/genre-tree/playlist-tree/toTreeNodes.ts
|
|
64
|
+
function toTreeNodes(genrePlaylists, sizeMetric) {
|
|
65
|
+
return genrePlaylists.map((genrePlaylist) => ({
|
|
66
|
+
id: genrePlaylist.uuid,
|
|
67
|
+
parentId: genrePlaylist.parent?.uuid ?? null,
|
|
68
|
+
name: genrePlaylist.name,
|
|
69
|
+
itemCount: genrePlaylistSize(genrePlaylist, sizeMetric),
|
|
70
|
+
actionable: Boolean(genrePlaylist.criteria),
|
|
71
|
+
detached: !genrePlaylist.criteria,
|
|
72
|
+
side: genrePlaylist.criteria?.side ?? void 0,
|
|
73
|
+
wikidataId: genrePlaylist.criteria?.wikidataId ?? null,
|
|
74
|
+
umbrella: genrePlaylist.isUmbrella,
|
|
75
|
+
scene: genrePlaylist.scene ?? void 0
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
|
|
54
79
|
// src/genre-tree/playlist-tree/TreePerRoot.tsx
|
|
55
80
|
var import_react3 = require("react");
|
|
56
81
|
var import_genre_tree_view = require("@behindthemusictree/genre-tree-view");
|
|
@@ -303,7 +328,6 @@ function useGenrePlaylistPlayback({
|
|
|
303
328
|
}
|
|
304
329
|
|
|
305
330
|
// src/genre-tree/playlist-tree/TreePerRoot.tsx
|
|
306
|
-
var import_track_noun = require("@behindthemusictree/app-kit/track-noun");
|
|
307
331
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
308
332
|
function GenrePlaylistTreePerRoot({
|
|
309
333
|
scope,
|
|
@@ -315,24 +339,19 @@ function GenrePlaylistTreePerRoot({
|
|
|
315
339
|
additionalActions,
|
|
316
340
|
onNodeClick,
|
|
317
341
|
onNodeHover,
|
|
318
|
-
renderInfoPanelBody,
|
|
319
342
|
selectedNodeId,
|
|
320
343
|
onSelectedNodeChange,
|
|
321
|
-
hideInfoPanelClose,
|
|
322
|
-
hideInfoPanelTitle,
|
|
323
344
|
readOnly = false,
|
|
324
|
-
showToolbar,
|
|
325
345
|
wheelZoom,
|
|
326
346
|
defaultViewport,
|
|
327
347
|
viewportRestore,
|
|
328
348
|
onViewportChange,
|
|
329
349
|
playback,
|
|
330
|
-
...
|
|
350
|
+
...hostPanel
|
|
331
351
|
}) {
|
|
332
352
|
const { isPlaying, setIsPlaying } = (0, import_player.usePlayer)();
|
|
333
353
|
const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists: genrePlaylistTreePerRoot });
|
|
334
354
|
const { playingNodeId, play } = playback ?? defaultPlayback;
|
|
335
|
-
const trackNoun = (0, import_track_noun.useTrackNoun)();
|
|
336
355
|
const { showPopup } = (0, import_popup.usePopup)();
|
|
337
356
|
const nodes = (0, import_react3.useMemo)(
|
|
338
357
|
() => genrePlaylistTreePerRoot.map((genrePlaylist) => ({
|
|
@@ -397,7 +416,6 @@ function GenrePlaylistTreePerRoot({
|
|
|
397
416
|
{
|
|
398
417
|
className: className2,
|
|
399
418
|
nodes,
|
|
400
|
-
itemCountLabel: `${(0, import_track_noun.capitalize)(trackNoun.singular)} count`,
|
|
401
419
|
rootColor: (0, import_genre_tree_view.getGenreTreeColor)(rootUuid),
|
|
402
420
|
playingNodeId,
|
|
403
421
|
playState: isPlaying ? "playing" : "paused",
|
|
@@ -408,18 +426,13 @@ function GenrePlaylistTreePerRoot({
|
|
|
408
426
|
additionalActions,
|
|
409
427
|
onNodeClick,
|
|
410
428
|
onNodeHover,
|
|
411
|
-
renderInfoPanelBody,
|
|
412
429
|
selectedNodeId,
|
|
413
430
|
onSelectedNodeChange,
|
|
414
|
-
hideInfoPanelClose,
|
|
415
|
-
hideInfoPanelTitle,
|
|
416
|
-
showToolbar,
|
|
417
431
|
wheelZoom,
|
|
418
432
|
defaultViewport,
|
|
419
433
|
viewportRestore,
|
|
420
434
|
onViewportChange,
|
|
421
|
-
...
|
|
422
|
-
readOnly
|
|
435
|
+
...hostPanel
|
|
423
436
|
}
|
|
424
437
|
);
|
|
425
438
|
}
|
|
@@ -430,19 +443,6 @@ var import_genre_tree_view2 = require("@behindthemusictree/genre-tree-view");
|
|
|
430
443
|
var import_player2 = require("@behindthemusictree/app-kit/player");
|
|
431
444
|
var import_popup2 = require("@behindthemusictree/app-kit/popup");
|
|
432
445
|
var import_transport2 = require("@behindthemusictree/app-kit/transport");
|
|
433
|
-
|
|
434
|
-
// src/genre-tree/playlist-tree/size-metric.ts
|
|
435
|
-
var hasViewsCount = (genrePlaylists) => genrePlaylists.every((genrePlaylist) => genrePlaylist.viewsCount !== void 0);
|
|
436
|
-
var genrePlaylistSize = (genrePlaylist, metric) => {
|
|
437
|
-
if (metric === "songs") return genrePlaylist.tracksCount;
|
|
438
|
-
if (genrePlaylist.viewsCount === void 0) {
|
|
439
|
-
throw new Error(`Genre playlist ${genrePlaylist.uuid} has no viewsCount to size by views`);
|
|
440
|
-
}
|
|
441
|
-
return genrePlaylist.viewsCount;
|
|
442
|
-
};
|
|
443
|
-
|
|
444
|
-
// src/genre-tree/playlist-tree/TreeWheel.tsx
|
|
445
|
-
var import_track_noun2 = require("@behindthemusictree/app-kit/track-noun");
|
|
446
446
|
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
447
447
|
function GenrePlaylistTreeWheel({
|
|
448
448
|
scope,
|
|
@@ -453,14 +453,10 @@ function GenrePlaylistTreeWheel({
|
|
|
453
453
|
additionalActions,
|
|
454
454
|
onNodeClick,
|
|
455
455
|
onNodeHover,
|
|
456
|
-
renderInfoPanelBody,
|
|
457
456
|
selectedNodeId,
|
|
458
457
|
onSelectedNodeChange,
|
|
459
|
-
hideInfoPanelClose,
|
|
460
|
-
hideInfoPanelTitle,
|
|
461
458
|
readOnly = false,
|
|
462
459
|
allowWheelRotation,
|
|
463
|
-
showToolbar,
|
|
464
460
|
sizeMetric = "songs",
|
|
465
461
|
floatingControlsLeading,
|
|
466
462
|
busy,
|
|
@@ -468,12 +464,11 @@ function GenrePlaylistTreeWheel({
|
|
|
468
464
|
viewportRestore,
|
|
469
465
|
onViewportChange,
|
|
470
466
|
playback,
|
|
471
|
-
...
|
|
467
|
+
...hostPanel
|
|
472
468
|
}) {
|
|
473
469
|
const { isPlaying, setIsPlaying } = (0, import_player2.usePlayer)();
|
|
474
470
|
const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists });
|
|
475
471
|
const { playingNodeId, play } = playback ?? defaultPlayback;
|
|
476
|
-
const trackNoun = (0, import_track_noun2.useTrackNoun)();
|
|
477
472
|
const { showPopup } = (0, import_popup2.usePopup)();
|
|
478
473
|
const nodes = (0, import_react4.useMemo)(
|
|
479
474
|
() => genrePlaylists.map((genrePlaylist) => ({
|
|
@@ -537,7 +532,6 @@ function GenrePlaylistTreeWheel({
|
|
|
537
532
|
{
|
|
538
533
|
className: className2,
|
|
539
534
|
nodes,
|
|
540
|
-
itemCountLabel: sizeMetric === "views" ? "Views" : `${(0, import_track_noun2.capitalize)(trackNoun.singular)} count`,
|
|
541
535
|
playingNodeId,
|
|
542
536
|
playState: isPlaying ? "playing" : "paused",
|
|
543
537
|
onPlayPause: handlePlayPause,
|
|
@@ -547,20 +541,15 @@ function GenrePlaylistTreeWheel({
|
|
|
547
541
|
additionalActions,
|
|
548
542
|
onNodeClick,
|
|
549
543
|
onNodeHover,
|
|
550
|
-
renderInfoPanelBody,
|
|
551
544
|
selectedNodeId,
|
|
552
545
|
onSelectedNodeChange,
|
|
553
|
-
hideInfoPanelClose,
|
|
554
|
-
hideInfoPanelTitle,
|
|
555
546
|
allowWheelRotation,
|
|
556
|
-
showToolbar,
|
|
557
547
|
floatingControlsLeading,
|
|
558
548
|
busy,
|
|
559
549
|
defaultViewport,
|
|
560
550
|
viewportRestore,
|
|
561
551
|
onViewportChange,
|
|
562
|
-
...
|
|
563
|
-
readOnly
|
|
552
|
+
...hostPanel
|
|
564
553
|
}
|
|
565
554
|
);
|
|
566
555
|
}
|
|
@@ -569,7 +558,6 @@ function GenrePlaylistTreeWheel({
|
|
|
569
558
|
var import_react5 = require("react");
|
|
570
559
|
var import_genre_tree_view3 = require("@behindthemusictree/genre-tree-view");
|
|
571
560
|
var import_player3 = require("@behindthemusictree/app-kit/player");
|
|
572
|
-
var import_track_noun3 = require("@behindthemusictree/app-kit/track-noun");
|
|
573
561
|
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
574
562
|
function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
575
563
|
const {
|
|
@@ -581,14 +569,10 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
581
569
|
additionalActions,
|
|
582
570
|
onNodeClick,
|
|
583
571
|
onNodeHover,
|
|
584
|
-
renderInfoPanelBody,
|
|
585
572
|
selectedNodeId,
|
|
586
573
|
onSelectedNodeChange,
|
|
587
|
-
hideInfoPanelClose,
|
|
588
|
-
hideInfoPanelTitle,
|
|
589
574
|
readOnly = false,
|
|
590
575
|
allowWheelRotation,
|
|
591
|
-
showToolbar,
|
|
592
576
|
sizeMetric = "songs",
|
|
593
577
|
floatingControlsLeading,
|
|
594
578
|
busy,
|
|
@@ -599,33 +583,14 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
599
583
|
playback
|
|
600
584
|
} = props;
|
|
601
585
|
const focus = props;
|
|
602
|
-
const
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
onExpandedChange: props.onExpandedChange,
|
|
606
|
-
collapsed: props.collapsed,
|
|
607
|
-
hideExpandButton: props.hideExpandButton,
|
|
608
|
-
overlayPanel: props.overlayPanel
|
|
586
|
+
const hostPanel = {
|
|
587
|
+
obscuredInset: props.obscuredInset,
|
|
588
|
+
onNodeStyleResolverChange: props.onNodeStyleResolverChange
|
|
609
589
|
};
|
|
610
590
|
const { isPlaying, setIsPlaying } = (0, import_player3.usePlayer)();
|
|
611
591
|
const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists });
|
|
612
592
|
const { playingNodeId, play } = playback ?? defaultPlayback;
|
|
613
|
-
const
|
|
614
|
-
const nodes = (0, import_react5.useMemo)(
|
|
615
|
-
() => genrePlaylists.map((genrePlaylist) => ({
|
|
616
|
-
id: genrePlaylist.uuid,
|
|
617
|
-
parentId: genrePlaylist.parent?.uuid ?? null,
|
|
618
|
-
name: genrePlaylist.name,
|
|
619
|
-
itemCount: genrePlaylistSize(genrePlaylist, sizeMetric),
|
|
620
|
-
actionable: Boolean(genrePlaylist.criteria),
|
|
621
|
-
detached: !genrePlaylist.criteria,
|
|
622
|
-
side: genrePlaylist.criteria?.side ?? void 0,
|
|
623
|
-
wikidataId: genrePlaylist.criteria?.wikidataId ?? null,
|
|
624
|
-
umbrella: genrePlaylist.isUmbrella,
|
|
625
|
-
scene: genrePlaylist.scene ?? void 0
|
|
626
|
-
})),
|
|
627
|
-
[genrePlaylists, sizeMetric]
|
|
628
|
-
);
|
|
593
|
+
const nodes = (0, import_react5.useMemo)(() => toTreeNodes(genrePlaylists, sizeMetric), [genrePlaylists, sizeMetric]);
|
|
629
594
|
const handlePlayPause = (0, import_react5.useCallback)(
|
|
630
595
|
(nodeId) => {
|
|
631
596
|
const genrePlaylist = genrePlaylists.find((g) => g.uuid === nodeId);
|
|
@@ -666,7 +631,6 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
666
631
|
const treeProps = {
|
|
667
632
|
className: className2,
|
|
668
633
|
nodes,
|
|
669
|
-
itemCountLabel: sizeMetric === "views" ? "Views" : `${(0, import_track_noun3.capitalize)(trackNoun.singular)} count`,
|
|
670
634
|
playingNodeId,
|
|
671
635
|
playState: isPlaying ? "playing" : "paused",
|
|
672
636
|
onPlayPause: handlePlayPause,
|
|
@@ -676,14 +640,9 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
676
640
|
additionalActions,
|
|
677
641
|
onNodeClick,
|
|
678
642
|
onNodeHover,
|
|
679
|
-
renderInfoPanelBody,
|
|
680
643
|
selectedNodeId,
|
|
681
644
|
onSelectedNodeChange,
|
|
682
|
-
|
|
683
|
-
hideInfoPanelTitle,
|
|
684
|
-
showToolbar,
|
|
685
|
-
...infoPanelCuration,
|
|
686
|
-
readOnly
|
|
645
|
+
...hostPanel
|
|
687
646
|
};
|
|
688
647
|
return outline ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_genre_tree_view3.GenreTreeOutline, { ...treeProps }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
689
648
|
import_genre_tree_view3.GenreTreeWheelRadialPopCore,
|
|
@@ -736,6 +695,7 @@ function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
|
|
|
736
695
|
}
|
|
737
696
|
|
|
738
697
|
// src/genre-tree/GenreSearch.tsx
|
|
698
|
+
var import_react7 = require("react");
|
|
739
699
|
var import_lucide_react3 = require("lucide-react");
|
|
740
700
|
var import_ui3 = require("@behindthemusictree/ui");
|
|
741
701
|
var import_genre_tree2 = require("@behindthemusictree/app-kit/genre-tree");
|
|
@@ -748,7 +708,7 @@ function GenreSearch({
|
|
|
748
708
|
onPrevious,
|
|
749
709
|
placeholder,
|
|
750
710
|
onQueryChange,
|
|
751
|
-
|
|
711
|
+
extraGroups = []
|
|
752
712
|
}) {
|
|
753
713
|
const { query, setQuery, results, canClear, clear, select } = (0, import_genre_tree2.useGenreSearch)({
|
|
754
714
|
genrePlaylists,
|
|
@@ -756,11 +716,45 @@ function GenreSearch({
|
|
|
756
716
|
selectedName,
|
|
757
717
|
onClear
|
|
758
718
|
});
|
|
759
|
-
const
|
|
760
|
-
const
|
|
761
|
-
const
|
|
762
|
-
const
|
|
763
|
-
const
|
|
719
|
+
const [open, setOpen] = (0, import_react7.useState)(false);
|
|
720
|
+
const active = query !== "" && query !== selectedName;
|
|
721
|
+
const hasExtras = extraGroups.length > 0;
|
|
722
|
+
const label = hasExtras ? "Search" : "Search a genre";
|
|
723
|
+
const loading = active && extraGroups.some((group) => group.loading);
|
|
724
|
+
const status = active ? extraGroups.map((group) => group.status).filter(Boolean).join(". ") : "";
|
|
725
|
+
const groups = [
|
|
726
|
+
{
|
|
727
|
+
key: "genres",
|
|
728
|
+
label: hasExtras ? "Genres" : void 0,
|
|
729
|
+
options: results.map((genrePlaylist) => ({
|
|
730
|
+
key: genrePlaylist.uuid,
|
|
731
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
|
|
732
|
+
genrePlaylist.name,
|
|
733
|
+
genrePlaylist.isUmbrella && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ml-2 text-xs italic text-[var(--color-text-muted)]", children: "umbrella" })
|
|
734
|
+
] })
|
|
735
|
+
}))
|
|
736
|
+
},
|
|
737
|
+
...extraGroups.map((group) => ({
|
|
738
|
+
key: group.key,
|
|
739
|
+
label: group.label,
|
|
740
|
+
options: active ? group.items.map((item) => ({ key: item.key, content: item.content })) : []
|
|
741
|
+
}))
|
|
742
|
+
];
|
|
743
|
+
const handlePick = (groupKey, optionKey) => {
|
|
744
|
+
setOpen(false);
|
|
745
|
+
if (groupKey === "genres") {
|
|
746
|
+
const genrePlaylist = results.find((result) => result.uuid === optionKey);
|
|
747
|
+
if (!genrePlaylist) return;
|
|
748
|
+
select(genrePlaylist);
|
|
749
|
+
onQueryChange?.("");
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
const item = extraGroups.find((group) => group.key === groupKey)?.items.find((entry) => entry.key === optionKey);
|
|
753
|
+
if (!item) return;
|
|
754
|
+
setQuery("");
|
|
755
|
+
onQueryChange?.("");
|
|
756
|
+
item.onSelect();
|
|
757
|
+
};
|
|
764
758
|
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "genre-search relative w-[calc(var(--layout-detail-panel-w,25.5rem)-2*var(--layout-panel-inset,1rem))] shrink-0 max-md:w-full", children: [
|
|
765
759
|
onPrevious && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
766
760
|
"button",
|
|
@@ -768,53 +762,51 @@ function GenreSearch({
|
|
|
768
762
|
type: "button",
|
|
769
763
|
"aria-label": "Previous",
|
|
770
764
|
title: "Previous",
|
|
771
|
-
className: "genre-search-previous absolute left-2 top-1/2 z-10
|
|
765
|
+
className: "genre-search-previous absolute left-2 top-1/2 z-10 flex items-center justify-center p-1.5 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] [@media(pointer:coarse)]:min-w-[var(--layout-touch-target)] text-[var(--color-text-muted)] rounded-full -translate-y-1/2 hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-text)]",
|
|
772
766
|
onClick: onPrevious,
|
|
773
767
|
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react3.ArrowLeft, { "aria-hidden": "true", className: "w-4 h-4" })
|
|
774
768
|
}
|
|
775
769
|
),
|
|
776
770
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
777
|
-
import_ui3.
|
|
771
|
+
import_ui3.Combobox,
|
|
778
772
|
{
|
|
779
|
-
|
|
773
|
+
ariaLabel: label,
|
|
774
|
+
listLabel: `${label} results`,
|
|
780
775
|
value: query,
|
|
781
|
-
onChange: (event) => {
|
|
782
|
-
setQuery(event.target.value);
|
|
783
|
-
onQueryChange?.(event.target.value);
|
|
784
|
-
},
|
|
785
776
|
placeholder: placeholder ?? `${label}\u2026`,
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
777
|
+
groups,
|
|
778
|
+
open,
|
|
779
|
+
onOpenChange: setOpen,
|
|
780
|
+
onValueChange: (value) => {
|
|
781
|
+
setQuery(value);
|
|
782
|
+
onQueryChange?.(value);
|
|
783
|
+
},
|
|
784
|
+
onPick: handlePick,
|
|
785
|
+
status,
|
|
786
|
+
loading: loading ? "Searching\u2026" : void 0,
|
|
787
|
+
inputClassName: `h-[var(--layout-search-h,3rem)] ${onPrevious ? "pl-11" : "pl-6"} pr-20 text-base transition-[padding] duration-200 ease-out motion-reduce:transition-none bg-[var(--color-surface)] border-[var(--color-border)] rounded-full shadow-md`,
|
|
788
|
+
className: "genre-search-results z-40 rounded-xl shadow-md",
|
|
789
|
+
listClassName: "max-h-[min(20rem,50dvh)]",
|
|
790
|
+
optionClassName: "[@media(pointer:coarse)]:min-h-[var(--layout-touch-target)]"
|
|
792
791
|
}
|
|
793
792
|
),
|
|
794
793
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "absolute right-3 top-1/2 flex items-center -translate-y-1/2", children: [
|
|
795
|
-
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
"aria-hidden": "true",
|
|
812
|
-
className: `absolute inset-2 w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? "opacity-100 delay-300 animate-spin motion-reduce:animate-none" : "opacity-0"}`
|
|
813
|
-
}
|
|
814
|
-
)
|
|
815
|
-
]
|
|
816
|
-
}
|
|
817
|
-
),
|
|
794
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { "data-loading": loading, className: "relative p-2 text-[var(--color-text-muted)] pointer-events-none", children: [
|
|
795
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
796
|
+
import_lucide_react3.Search,
|
|
797
|
+
{
|
|
798
|
+
"aria-hidden": "true",
|
|
799
|
+
className: `w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? "opacity-0" : ""}`
|
|
800
|
+
}
|
|
801
|
+
),
|
|
802
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
803
|
+
import_lucide_react3.LoaderCircle,
|
|
804
|
+
{
|
|
805
|
+
"aria-hidden": "true",
|
|
806
|
+
className: `absolute inset-2 w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? "opacity-100 delay-300 animate-spin motion-reduce:animate-none" : "opacity-0"}`
|
|
807
|
+
}
|
|
808
|
+
)
|
|
809
|
+
] }),
|
|
818
810
|
canClear && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
819
811
|
"button",
|
|
820
812
|
{
|
|
@@ -828,39 +820,6 @@ function GenreSearch({
|
|
|
828
820
|
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react3.X, { "aria-hidden": "true", className: "w-6 h-6" })
|
|
829
821
|
}
|
|
830
822
|
)
|
|
831
|
-
] }),
|
|
832
|
-
extraResults?.status && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { role: "status", "aria-live": "polite", className: "sr-only", children: extraResults.status }),
|
|
833
|
-
showList && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("ul", { className: "genre-search-results absolute z-40 w-full max-h-[min(16rem,50dvh)] mt-1 overflow-y-auto bg-[var(--color-surface)] border border-[var(--color-border)] rounded-xl shadow-md", children: [
|
|
834
|
-
extraResults && results.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("li", { className: heading, children: "Genres" }),
|
|
835
|
-
results.map((genrePlaylist) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
836
|
-
"button",
|
|
837
|
-
{
|
|
838
|
-
type: "button",
|
|
839
|
-
className: "w-full px-3 py-1.5 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] text-left hover:bg-[var(--color-surface-muted)]",
|
|
840
|
-
onClick: () => {
|
|
841
|
-
select(genrePlaylist);
|
|
842
|
-
onQueryChange?.("");
|
|
843
|
-
},
|
|
844
|
-
children: [
|
|
845
|
-
genrePlaylist.name,
|
|
846
|
-
genrePlaylist.isUmbrella && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ml-2 text-xs italic text-[var(--color-text-muted)]", children: "umbrella" })
|
|
847
|
-
]
|
|
848
|
-
}
|
|
849
|
-
) }, genrePlaylist.uuid)),
|
|
850
|
-
extraResults && extraItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("li", { className: heading, children: extraResults.label }),
|
|
851
|
-
extraItems.map((item) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
852
|
-
"button",
|
|
853
|
-
{
|
|
854
|
-
type: "button",
|
|
855
|
-
className: "w-full px-3 py-1.5 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] text-left hover:bg-[var(--color-surface-muted)]",
|
|
856
|
-
onClick: () => {
|
|
857
|
-
setQuery("");
|
|
858
|
-
onQueryChange?.("");
|
|
859
|
-
item.onSelect();
|
|
860
|
-
},
|
|
861
|
-
children: item.content
|
|
862
|
-
}
|
|
863
|
-
) }, item.key))
|
|
864
823
|
] })
|
|
865
824
|
] });
|
|
866
825
|
}
|
|
@@ -876,18 +835,18 @@ var import_lucide_react6 = require("lucide-react");
|
|
|
876
835
|
var import_popup3 = require("@behindthemusictree/app-kit/popup");
|
|
877
836
|
|
|
878
837
|
// src/genre-tree/SharePopup.tsx
|
|
879
|
-
var
|
|
838
|
+
var import_react9 = require("react");
|
|
880
839
|
var import_lucide_react5 = require("lucide-react");
|
|
881
840
|
var import_fa = require("react-icons/fa");
|
|
882
841
|
var import_si = require("react-icons/si");
|
|
883
842
|
|
|
884
843
|
// src/popup/Toast.tsx
|
|
885
|
-
var
|
|
844
|
+
var import_react8 = require("react");
|
|
886
845
|
var import_react_dom2 = require("react-dom");
|
|
887
846
|
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
888
847
|
function Toast({ message, onClose, durationMs = 2200 }) {
|
|
889
|
-
const [mounted, setMounted] = (0,
|
|
890
|
-
(0,
|
|
848
|
+
const [mounted, setMounted] = (0, import_react8.useState)(false);
|
|
849
|
+
(0, import_react8.useEffect)(() => {
|
|
891
850
|
setMounted(true);
|
|
892
851
|
const timeout = window.setTimeout(onClose, durationMs);
|
|
893
852
|
return () => window.clearTimeout(timeout);
|
|
@@ -935,8 +894,8 @@ function ShareDestinationButton({
|
|
|
935
894
|
var import_lucide_react4 = require("lucide-react");
|
|
936
895
|
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
937
896
|
function SharePreviewCard({ target, url }) {
|
|
938
|
-
const Icon =
|
|
939
|
-
const typeLabel =
|
|
897
|
+
const Icon = { genre: import_lucide_react4.Music2, artist: import_lucide_react4.UserRound, song: import_lucide_react4.Music }[target.kind];
|
|
898
|
+
const typeLabel = { genre: "Genre", artist: "Artist", song: "Song" }[target.kind];
|
|
940
899
|
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "rounded-3xl bg-[var(--color-surface-muted)] p-4", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex min-h-32 overflow-hidden rounded-2xl bg-[var(--color-surface)] shadow-lg", children: [
|
|
941
900
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "flex w-28 shrink-0 items-center justify-center bg-[var(--color-primary)]/15 text-[var(--color-primary)]", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Icon, { "aria-hidden": "true", className: "h-10 w-10" }) }),
|
|
942
901
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col justify-center gap-1 p-4", children: [
|
|
@@ -981,20 +940,20 @@ async function copyText(text) {
|
|
|
981
940
|
}
|
|
982
941
|
}
|
|
983
942
|
function SharePopup({ target, url, onClose }) {
|
|
984
|
-
const [absoluteUrl, setAbsoluteUrl] = (0,
|
|
985
|
-
const [copyStatus, setCopyStatus] = (0,
|
|
986
|
-
const [canUseNativeShare, setCanUseNativeShare] = (0,
|
|
987
|
-
const [shareError, setShareError] = (0,
|
|
988
|
-
const [toastId, setToastId] = (0,
|
|
989
|
-
(0,
|
|
943
|
+
const [absoluteUrl, setAbsoluteUrl] = (0, import_react9.useState)(url);
|
|
944
|
+
const [copyStatus, setCopyStatus] = (0, import_react9.useState)("idle");
|
|
945
|
+
const [canUseNativeShare, setCanUseNativeShare] = (0, import_react9.useState)(false);
|
|
946
|
+
const [shareError, setShareError] = (0, import_react9.useState)(null);
|
|
947
|
+
const [toastId, setToastId] = (0, import_react9.useState)(0);
|
|
948
|
+
(0, import_react9.useEffect)(() => {
|
|
990
949
|
setAbsoluteUrl(resolveAbsoluteUrl(url) ?? "");
|
|
991
950
|
setCopyStatus("idle");
|
|
992
951
|
setShareError(null);
|
|
993
952
|
}, [url]);
|
|
994
|
-
(0,
|
|
953
|
+
(0, import_react9.useEffect)(() => {
|
|
995
954
|
setCanUseNativeShare(typeof navigator !== "undefined" && typeof navigator.share === "function");
|
|
996
955
|
}, []);
|
|
997
|
-
const destinationUrls = (0,
|
|
956
|
+
const destinationUrls = (0, import_react9.useMemo)(() => {
|
|
998
957
|
if (!absoluteUrl) return null;
|
|
999
958
|
const message = `${target.name} \u2014 ${absoluteUrl}`;
|
|
1000
959
|
return {
|
|
@@ -1012,7 +971,7 @@ function SharePopup({ target, url, onClose }) {
|
|
|
1012
971
|
setCopyStatus(copied ? "idle" : "failed");
|
|
1013
972
|
if (copied) setToastId((id) => id + 1);
|
|
1014
973
|
};
|
|
1015
|
-
const dismissToast = (0,
|
|
974
|
+
const dismissToast = (0, import_react9.useCallback)(() => setToastId(0), []);
|
|
1016
975
|
const handleNativeShare = async () => {
|
|
1017
976
|
if (!absoluteUrl || !navigator.share) return;
|
|
1018
977
|
setShareError(null);
|
|
@@ -1218,13 +1177,13 @@ var import_ui5 = require("@behindthemusictree/ui");
|
|
|
1218
1177
|
var import_genre_tree9 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1219
1178
|
|
|
1220
1179
|
// src/genre-tree/ClampedText.tsx
|
|
1221
|
-
var
|
|
1180
|
+
var import_react10 = require("react");
|
|
1222
1181
|
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
1223
1182
|
function ClampedText({ children, className: className2 = "" }) {
|
|
1224
|
-
const ref = (0,
|
|
1225
|
-
const [expanded, setExpanded] = (0,
|
|
1226
|
-
const [overflows, setOverflows] = (0,
|
|
1227
|
-
(0,
|
|
1183
|
+
const ref = (0, import_react10.useRef)(null);
|
|
1184
|
+
const [expanded, setExpanded] = (0, import_react10.useState)(false);
|
|
1185
|
+
const [overflows, setOverflows] = (0, import_react10.useState)(false);
|
|
1186
|
+
(0, import_react10.useLayoutEffect)(() => {
|
|
1228
1187
|
const element = ref.current;
|
|
1229
1188
|
if (!element || expanded) return;
|
|
1230
1189
|
setOverflows(element.scrollHeight > element.clientHeight);
|
|
@@ -1245,7 +1204,7 @@ function ClampedText({ children, className: className2 = "" }) {
|
|
|
1245
1204
|
}
|
|
1246
1205
|
|
|
1247
1206
|
// src/genre-tree/GenrePlaylistTracks.tsx
|
|
1248
|
-
var
|
|
1207
|
+
var import_react11 = require("react");
|
|
1249
1208
|
var import_react_query = require("@tanstack/react-query");
|
|
1250
1209
|
var import_zod = require("zod");
|
|
1251
1210
|
var import_fa2 = require("react-icons/fa");
|
|
@@ -1289,7 +1248,7 @@ function MusicBrainzRecordingLink({
|
|
|
1289
1248
|
|
|
1290
1249
|
// src/genre-tree/GenrePlaylistTracks.tsx
|
|
1291
1250
|
var import_genre_tree8 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1292
|
-
var
|
|
1251
|
+
var import_track_noun = require("@behindthemusictree/app-kit/track-noun");
|
|
1293
1252
|
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
1294
1253
|
var CATALOGUE_PAGE_SIZE = 100;
|
|
1295
1254
|
var SongPageSchema = (0, import_transport4.PaginatedResponseSchema)(import_genre_tree6.SongDetailedSchema).extend({
|
|
@@ -1313,6 +1272,7 @@ function GenrePlaylistTracks({
|
|
|
1313
1272
|
scope,
|
|
1314
1273
|
getBackendBaseUrl,
|
|
1315
1274
|
renderArtistLink,
|
|
1275
|
+
renderSongLink,
|
|
1316
1276
|
renderSongLeading,
|
|
1317
1277
|
renderSongActions,
|
|
1318
1278
|
renderTracksToolbar,
|
|
@@ -1322,7 +1282,7 @@ function GenrePlaylistTracks({
|
|
|
1322
1282
|
const { session, sessionRestored } = (0, import_auth.useSession)();
|
|
1323
1283
|
const { playerTrackObject, playState, handlePlayPauseAction } = (0, import_player4.usePlayer)();
|
|
1324
1284
|
const { showPopup } = (0, import_popup4.usePopup)();
|
|
1325
|
-
const trackNoun = (0,
|
|
1285
|
+
const trackNoun = (0, import_track_noun.useTrackNoun)();
|
|
1326
1286
|
const { uuid } = genrePlaylist;
|
|
1327
1287
|
const { fetch } = (0, import_transport3.useFetchWrapper)(getBackendBaseUrl);
|
|
1328
1288
|
const { data, isPending, isError, refetch, hasNextPage, isFetchingNextPage, fetchNextPage } = (0, import_react_query.useInfiniteQuery)({
|
|
@@ -1343,8 +1303,8 @@ function GenrePlaylistTracks({
|
|
|
1343
1303
|
getNextPageParam: (lastPage) => lastPage.nextPage ?? void 0,
|
|
1344
1304
|
enabled: scope !== "me" || sessionRestored && !!session?.accessToken
|
|
1345
1305
|
});
|
|
1346
|
-
const sentinelRef = (0,
|
|
1347
|
-
(0,
|
|
1306
|
+
const sentinelRef = (0, import_react11.useRef)(null);
|
|
1307
|
+
(0, import_react11.useEffect)(() => {
|
|
1348
1308
|
const sentinel = sentinelRef.current;
|
|
1349
1309
|
if (!hasNextPage || isFetchingNextPage || !sentinel) return;
|
|
1350
1310
|
const observer = new IntersectionObserver((entries) => {
|
|
@@ -1367,7 +1327,7 @@ function GenrePlaylistTracks({
|
|
|
1367
1327
|
};
|
|
1368
1328
|
return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "gtv-info-panel-children", children: [
|
|
1369
1329
|
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "gtv-info-panel-children-title", children: [
|
|
1370
|
-
(0,
|
|
1330
|
+
(0, import_track_noun.capitalize)(trackNoun.plural),
|
|
1371
1331
|
total !== void 0 && ` (${total})`
|
|
1372
1332
|
] }),
|
|
1373
1333
|
renderTracksToolbar?.(),
|
|
@@ -1383,7 +1343,7 @@ function GenrePlaylistTracks({
|
|
|
1383
1343
|
const isCurrent = playerTrackObject?.track.id === track.uuid;
|
|
1384
1344
|
const isPlaying = isCurrent && playState === import_player5.PlayStates.PLAYING;
|
|
1385
1345
|
const text = /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: TEXT_CELL, children: [
|
|
1386
|
-
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: TITLE_CELL, children: (0, import_genre_tree6.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
|
|
1346
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: TITLE_CELL, children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree6.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
|
|
1387
1347
|
track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: SEPARATOR, children: " \u2014 " }) : null,
|
|
1388
1348
|
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_genre_tree8.ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
|
|
1389
1349
|
track.album && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: ALBUM_CELL, children: track.album.name })
|
|
@@ -1438,7 +1398,7 @@ function GenrePlaylistTracks({
|
|
|
1438
1398
|
}
|
|
1439
1399
|
|
|
1440
1400
|
// src/genre-tree/GenreDetailExtras.tsx
|
|
1441
|
-
var
|
|
1401
|
+
var import_track_noun2 = require("@behindthemusictree/app-kit/track-noun");
|
|
1442
1402
|
var import_si2 = require("react-icons/si");
|
|
1443
1403
|
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
1444
1404
|
function GenreDetailExtras({
|
|
@@ -1450,12 +1410,13 @@ function GenreDetailExtras({
|
|
|
1450
1410
|
criteriaOverviewSchema,
|
|
1451
1411
|
renderGenreDetailExtras,
|
|
1452
1412
|
renderArtistLink,
|
|
1413
|
+
renderSongLink,
|
|
1453
1414
|
renderSummary,
|
|
1454
1415
|
renderEssentialTracks,
|
|
1455
1416
|
family,
|
|
1456
1417
|
...trackSlots
|
|
1457
1418
|
}) {
|
|
1458
|
-
const trackNoun = (0,
|
|
1419
|
+
const trackNoun = (0, import_track_noun2.useTrackNoun)();
|
|
1459
1420
|
const { data: overview, isPending } = (0, import_genre_tree9.useFetchGenreOverview)(
|
|
1460
1421
|
genreUuid,
|
|
1461
1422
|
scope,
|
|
@@ -1527,6 +1488,7 @@ function GenreDetailExtras({
|
|
|
1527
1488
|
scope,
|
|
1528
1489
|
getBackendBaseUrl,
|
|
1529
1490
|
renderArtistLink,
|
|
1491
|
+
renderSongLink,
|
|
1530
1492
|
...trackSlots
|
|
1531
1493
|
}
|
|
1532
1494
|
)
|
|
@@ -1535,7 +1497,7 @@ function GenreDetailExtras({
|
|
|
1535
1497
|
|
|
1536
1498
|
// src/genre-tree/GenreInfoPanelBody.tsx
|
|
1537
1499
|
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
1538
|
-
var
|
|
1500
|
+
var import_react12 = require("react");
|
|
1539
1501
|
function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
|
|
1540
1502
|
const { isPlaying, setIsPlaying } = (0, import_player6.usePlayer)();
|
|
1541
1503
|
const isCurrentTrackList = playback.playingNodeId === genrePlaylist.uuid;
|
|
@@ -1572,7 +1534,7 @@ function GenreInfoPanelBody({
|
|
|
1572
1534
|
return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
|
|
1573
1535
|
toolbar,
|
|
1574
1536
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(GenreNodeActionBand, { ...band, genrePlaylist, playback }),
|
|
1575
|
-
/* @__PURE__ */ (0,
|
|
1537
|
+
/* @__PURE__ */ (0, import_react12.createElement)(
|
|
1576
1538
|
GenreDetailExtras,
|
|
1577
1539
|
{
|
|
1578
1540
|
...extras,
|
|
@@ -1588,66 +1550,10 @@ function GenreInfoPanelBody({
|
|
|
1588
1550
|
] });
|
|
1589
1551
|
}
|
|
1590
1552
|
|
|
1591
|
-
// src/genre-tree/PanelEdgeTab.tsx
|
|
1592
|
-
var import_lucide_react8 = require("lucide-react");
|
|
1593
|
-
var import_tailwind_merge = require("tailwind-merge");
|
|
1594
|
-
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
1595
|
-
var buttonClassName = "flex h-12 w-full items-center justify-center p-0 text-[var(--color-text-muted)] hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-text)] focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--color-primary)]";
|
|
1596
|
-
function PanelEdgeTab({ side, edge, center, controls, className: className2 }) {
|
|
1597
|
-
if (!edge && !center) return null;
|
|
1598
|
-
const isLeft = side === "left";
|
|
1599
|
-
const EdgeIcon = isLeft ? import_lucide_react8.ChevronLeft : import_lucide_react8.ChevronRight;
|
|
1600
|
-
const CenterIcon = isLeft ? import_lucide_react8.ChevronRight : import_lucide_react8.ChevronLeft;
|
|
1601
|
-
const renderButton = (action, icon) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
1602
|
-
"button",
|
|
1603
|
-
{
|
|
1604
|
-
type: "button",
|
|
1605
|
-
"aria-label": action.label,
|
|
1606
|
-
title: action.label,
|
|
1607
|
-
"aria-controls": controls,
|
|
1608
|
-
"aria-expanded": action.expanded,
|
|
1609
|
-
className: buttonClassName,
|
|
1610
|
-
onClick: action.onClick,
|
|
1611
|
-
children: icon
|
|
1612
|
-
}
|
|
1613
|
-
);
|
|
1614
|
-
const edgeButton = edge && renderButton(edge, /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(EdgeIcon, { "aria-hidden": "true", className: "h-5 w-5" }));
|
|
1615
|
-
const centerButton = center && renderButton(center, /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(CenterIcon, { "aria-hidden": "true", className: "h-5 w-5" }));
|
|
1616
|
-
const buttons = isLeft ? [edgeButton, centerButton] : [centerButton, edgeButton];
|
|
1617
|
-
return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
|
|
1618
|
-
"div",
|
|
1619
|
-
{
|
|
1620
|
-
className: (0, import_tailwind_merge.twMerge)(
|
|
1621
|
-
"panel-edge-tab flex w-[var(--layout-edge-tab-w)] flex-col overflow-hidden bg-[var(--color-surface)] shadow-[0_1px_2px_rgba(60,64,67,0.3),0_2px_6px_2px_rgba(60,64,67,0.15)] [clip-path:inset(-0.5rem_-0.5rem_-0.5rem_0)]",
|
|
1622
|
-
isLeft ? "rounded-r-lg" : "rounded-l-lg [clip-path:inset(-0.5rem_0_-0.5rem_-0.5rem)]",
|
|
1623
|
-
className2
|
|
1624
|
-
),
|
|
1625
|
-
children: [
|
|
1626
|
-
buttons[0],
|
|
1627
|
-
buttons[0] && buttons[1] && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { role: "presentation", className: "h-px shrink-0 bg-[var(--color-border)]" }),
|
|
1628
|
-
buttons[1]
|
|
1629
|
-
]
|
|
1630
|
-
}
|
|
1631
|
-
);
|
|
1632
|
-
}
|
|
1633
|
-
|
|
1634
|
-
// src/genre-tree/useLeftDetailPanelLayout.ts
|
|
1635
|
-
var import_react12 = require("react");
|
|
1636
|
-
function useLeftDetailPanelLayout(isPanelShown, enabled = true) {
|
|
1637
|
-
(0, import_react12.useEffect)(() => {
|
|
1638
|
-
if (!enabled || isPanelShown) return void 0;
|
|
1639
|
-
const root = document.documentElement;
|
|
1640
|
-
root.style.setProperty("--layout-left-w", "0px");
|
|
1641
|
-
return () => {
|
|
1642
|
-
root.style.removeProperty("--layout-left-w");
|
|
1643
|
-
};
|
|
1644
|
-
}, [enabled, isPanelShown]);
|
|
1645
|
-
}
|
|
1646
|
-
|
|
1647
1553
|
// src/genre-tree/ChangeParentPopup.tsx
|
|
1648
1554
|
var import_react13 = require("react");
|
|
1649
1555
|
var import_genre_tree10 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1650
|
-
var
|
|
1556
|
+
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
1651
1557
|
function ChangeParentPopup({
|
|
1652
1558
|
genrePlaylist,
|
|
1653
1559
|
genrePlaylists,
|
|
@@ -1682,12 +1588,12 @@ function ChangeParentPopup({
|
|
|
1682
1588
|
window.addEventListener("keydown", handleKeyDown);
|
|
1683
1589
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
1684
1590
|
}, [onClose]);
|
|
1685
|
-
return /* @__PURE__ */ (0,
|
|
1686
|
-
/* @__PURE__ */ (0,
|
|
1591
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
|
|
1592
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("p", { className: "mb-3 text-sm text-[var(--color-text-muted)]", children: [
|
|
1687
1593
|
"Current parent: ",
|
|
1688
1594
|
genrePlaylist.parent?.name ?? "none"
|
|
1689
1595
|
] }),
|
|
1690
|
-
/* @__PURE__ */ (0,
|
|
1596
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
1691
1597
|
GenreSearch,
|
|
1692
1598
|
{
|
|
1693
1599
|
genrePlaylists: candidates,
|
|
@@ -1695,7 +1601,7 @@ function ChangeParentPopup({
|
|
|
1695
1601
|
placeholder: "Search a new parent\u2026"
|
|
1696
1602
|
}
|
|
1697
1603
|
) }),
|
|
1698
|
-
formErrors.length > 0 && /* @__PURE__ */ (0,
|
|
1604
|
+
formErrors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { role: "alert", className: "mt-3 text-sm text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
|
|
1699
1605
|
] });
|
|
1700
1606
|
}
|
|
1701
1607
|
|
|
@@ -1707,12 +1613,12 @@ var import_genre_tree15 = require("@behindthemusictree/app-kit/genre-tree");
|
|
|
1707
1613
|
var import_ui7 = require("@behindthemusictree/ui");
|
|
1708
1614
|
|
|
1709
1615
|
// src/genre-tree/artist/ArtistLinks.tsx
|
|
1710
|
-
var
|
|
1616
|
+
var import_lucide_react8 = require("lucide-react");
|
|
1711
1617
|
var import_si3 = require("react-icons/si");
|
|
1712
1618
|
var import_ui6 = require("@behindthemusictree/ui");
|
|
1713
|
-
var
|
|
1619
|
+
var import_jsx_runtime22 = require("react/jsx-runtime");
|
|
1714
1620
|
var LINK_KINDS = {
|
|
1715
|
-
official: { label: "Official website", Icon:
|
|
1621
|
+
official: { label: "Official website", Icon: import_lucide_react8.Globe },
|
|
1716
1622
|
wikipedia: { label: "Wikipedia", Icon: import_si3.SiWikipedia },
|
|
1717
1623
|
wikidata: { label: "Wikidata", Icon: import_si3.SiWikidata },
|
|
1718
1624
|
spotify: { label: "Spotify", Icon: import_si3.SiSpotify },
|
|
@@ -1720,7 +1626,7 @@ var LINK_KINDS = {
|
|
|
1720
1626
|
bandcamp: { label: "Bandcamp", Icon: import_si3.SiBandcamp },
|
|
1721
1627
|
soundcloud: { label: "SoundCloud", Icon: import_si3.SiSoundcloud },
|
|
1722
1628
|
discogs: { label: "Discogs", Icon: import_si3.SiDiscogs },
|
|
1723
|
-
social: { label: "Social", Icon:
|
|
1629
|
+
social: { label: "Social", Icon: import_lucide_react8.Share2 }
|
|
1724
1630
|
};
|
|
1725
1631
|
function socialLabel(url) {
|
|
1726
1632
|
try {
|
|
@@ -1747,7 +1653,7 @@ function artistLinks(artist) {
|
|
|
1747
1653
|
return links;
|
|
1748
1654
|
}
|
|
1749
1655
|
function ArtistLinks({ links }) {
|
|
1750
|
-
return /* @__PURE__ */ (0,
|
|
1656
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ui6.Chip, { href: url, icon: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
|
|
1751
1657
|
}
|
|
1752
1658
|
|
|
1753
1659
|
// src/genre-tree/artist/formatYears.ts
|
|
@@ -1758,7 +1664,7 @@ function formatYears(beginYear, endYear, ended) {
|
|
|
1758
1664
|
}
|
|
1759
1665
|
|
|
1760
1666
|
// src/genre-tree/artist/ArtistHeader.tsx
|
|
1761
|
-
var
|
|
1667
|
+
var import_jsx_runtime23 = require("react/jsx-runtime");
|
|
1762
1668
|
var countryNames = new Intl.DisplayNames(["en"], { type: "region" });
|
|
1763
1669
|
function ArtistHeader({ artist }) {
|
|
1764
1670
|
const fields = [];
|
|
@@ -1768,35 +1674,35 @@ function ArtistHeader({ artist }) {
|
|
|
1768
1674
|
const years = formatYears(artist.beginYear, artist.endYear, artist.ended);
|
|
1769
1675
|
if (years) fields.push({ label: "Active", value: years });
|
|
1770
1676
|
const links = artistLinks(artist);
|
|
1771
|
-
if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0,
|
|
1677
|
+
if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ArtistLinks, { links }) });
|
|
1772
1678
|
if (fields.length === 0) return null;
|
|
1773
|
-
return /* @__PURE__ */ (0,
|
|
1679
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ui7.FieldList, { items: fields });
|
|
1774
1680
|
}
|
|
1775
1681
|
|
|
1776
1682
|
// src/genre-tree/artist/ArtistPhoto.tsx
|
|
1777
|
-
var
|
|
1683
|
+
var import_jsx_runtime24 = require("react/jsx-runtime");
|
|
1778
1684
|
function ExternalLink({ href, children }) {
|
|
1779
|
-
return /* @__PURE__ */ (0,
|
|
1685
|
+
return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
|
|
1780
1686
|
}
|
|
1781
1687
|
function ArtistPhoto({ image, name }) {
|
|
1782
|
-
return /* @__PURE__ */ (0,
|
|
1783
|
-
/* @__PURE__ */ (0,
|
|
1784
|
-
/* @__PURE__ */ (0,
|
|
1688
|
+
return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
|
|
1689
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
|
|
1690
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("figcaption", { className: "text-xs text-[var(--color-text-muted)]", children: [
|
|
1785
1691
|
"Photo: ",
|
|
1786
|
-
/* @__PURE__ */ (0,
|
|
1692
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
|
|
1787
1693
|
",",
|
|
1788
1694
|
" ",
|
|
1789
|
-
image.licenseUrl ? /* @__PURE__ */ (0,
|
|
1695
|
+
image.licenseUrl ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
|
|
1790
1696
|
] })
|
|
1791
1697
|
] });
|
|
1792
1698
|
}
|
|
1793
1699
|
|
|
1794
1700
|
// src/genre-tree/artist/ArtistMembers.tsx
|
|
1795
1701
|
var import_ui8 = require("@behindthemusictree/ui");
|
|
1796
|
-
var
|
|
1702
|
+
var import_jsx_runtime25 = require("react/jsx-runtime");
|
|
1797
1703
|
function MembershipList({ title, memberships, renderArtistLink }) {
|
|
1798
1704
|
if (memberships.length === 0) return null;
|
|
1799
|
-
return /* @__PURE__ */ (0,
|
|
1705
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_ui8.PanelGroup, { title, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("ul", { children: memberships.map((membership, index) => {
|
|
1800
1706
|
const details = [
|
|
1801
1707
|
formatYears(membership.beginYear, membership.endYear, membership.ended),
|
|
1802
1708
|
...membership.roles
|
|
@@ -1804,9 +1710,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
|
|
|
1804
1710
|
const detailsText = details.filter(Boolean).join(" \xB7 ");
|
|
1805
1711
|
return (
|
|
1806
1712
|
// A member can have several periods in the same group.
|
|
1807
|
-
/* @__PURE__ */ (0,
|
|
1808
|
-
/* @__PURE__ */ (0,
|
|
1809
|
-
detailsText && /* @__PURE__ */ (0,
|
|
1713
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
|
|
1714
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
|
|
1715
|
+
detailsText && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
|
|
1810
1716
|
" \u2014 ",
|
|
1811
1717
|
detailsText
|
|
1812
1718
|
] })
|
|
@@ -1817,9 +1723,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
|
|
|
1817
1723
|
function ArtistMembers({ title, memberships, renderArtistLink }) {
|
|
1818
1724
|
if (memberships.length === 0) return null;
|
|
1819
1725
|
const isCurrent = (membership) => !membership.ended && membership.endYear === null;
|
|
1820
|
-
return /* @__PURE__ */ (0,
|
|
1821
|
-
/* @__PURE__ */ (0,
|
|
1822
|
-
/* @__PURE__ */ (0,
|
|
1726
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_ui8.PanelSection, { title, children: [
|
|
1727
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
|
|
1728
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
1823
1729
|
MembershipList,
|
|
1824
1730
|
{
|
|
1825
1731
|
title: "Past",
|
|
@@ -1832,7 +1738,7 @@ function ArtistMembers({ title, memberships, renderArtistLink }) {
|
|
|
1832
1738
|
|
|
1833
1739
|
// src/genre-tree/artist/ArtistDiscography.tsx
|
|
1834
1740
|
var import_ui9 = require("@behindthemusictree/ui");
|
|
1835
|
-
var
|
|
1741
|
+
var import_jsx_runtime26 = require("react/jsx-runtime");
|
|
1836
1742
|
var GROUPS = ["Albums", "EPs", "Singles", "Live", "Compilations", "Other"];
|
|
1837
1743
|
var PRIMARY_GROUPS = { album: "Albums", ep: "EPs", single: "Singles" };
|
|
1838
1744
|
function discographyGroup({ primaryType, secondaryTypes }) {
|
|
@@ -1852,12 +1758,12 @@ function byYear(a, b) {
|
|
|
1852
1758
|
function ArtistDiscography({ albums }) {
|
|
1853
1759
|
if (albums.length === 0) return null;
|
|
1854
1760
|
const sorted = [...albums].sort(byYear);
|
|
1855
|
-
return /* @__PURE__ */ (0,
|
|
1761
|
+
return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_ui9.PanelSection, { title: "Discography", children: GROUPS.map((group) => {
|
|
1856
1762
|
const groupAlbums = sorted.filter((album) => discographyGroup(album) === group);
|
|
1857
1763
|
if (groupAlbums.length === 0) return null;
|
|
1858
|
-
return /* @__PURE__ */ (0,
|
|
1859
|
-
/* @__PURE__ */ (0,
|
|
1860
|
-
/* @__PURE__ */ (0,
|
|
1764
|
+
return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_ui9.PanelGroup, { title: group, children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
|
|
1765
|
+
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
|
|
1766
|
+
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
|
|
1861
1767
|
] }, album.uuid)) }) }, group);
|
|
1862
1768
|
}) });
|
|
1863
1769
|
}
|
|
@@ -1865,7 +1771,7 @@ function ArtistDiscography({ albums }) {
|
|
|
1865
1771
|
// src/genre-tree/artist/ArtistSongs.tsx
|
|
1866
1772
|
var import_react14 = require("react");
|
|
1867
1773
|
var import_react_query2 = require("@tanstack/react-query");
|
|
1868
|
-
var
|
|
1774
|
+
var import_lucide_react9 = require("lucide-react");
|
|
1869
1775
|
var import_ui10 = require("@behindthemusictree/ui");
|
|
1870
1776
|
var import_player10 = require("@behindthemusictree/app-kit/player");
|
|
1871
1777
|
var import_genre_tree14 = require("@behindthemusictree/app-kit/genre-tree");
|
|
@@ -1879,7 +1785,7 @@ var import_genre_tree11 = require("@behindthemusictree/app-kit/genre-tree");
|
|
|
1879
1785
|
var import_fa3 = require("react-icons/fa");
|
|
1880
1786
|
var import_player7 = require("@behindthemusictree/app-kit/player");
|
|
1881
1787
|
var import_player8 = require("@behindthemusictree/app-kit/player");
|
|
1882
|
-
var
|
|
1788
|
+
var import_jsx_runtime27 = require("react/jsx-runtime");
|
|
1883
1789
|
function TrackPositionPlayPause({
|
|
1884
1790
|
position,
|
|
1885
1791
|
uuid,
|
|
@@ -1893,14 +1799,14 @@ function TrackPositionPlayPause({
|
|
|
1893
1799
|
const isPlaying = isThisTrack && isConfirmedPlayable && playState === import_player8.PlayStates.PLAYING;
|
|
1894
1800
|
const isPaused = isThisTrack && isConfirmedPlayable && playState === import_player8.PlayStates.PAUSED;
|
|
1895
1801
|
const isCurrentTrackActive = isPlaying || isPaused;
|
|
1896
|
-
return /* @__PURE__ */ (0,
|
|
1802
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
|
|
1897
1803
|
"div",
|
|
1898
1804
|
{
|
|
1899
1805
|
className: "track-position-play-pause relative flex items-center justify-center gap-2 text-lg w-16 cursor-pointer",
|
|
1900
1806
|
style: { minWidth: "4rem", maxWidth: "4rem" },
|
|
1901
1807
|
onClick: handlePlayPauseClick,
|
|
1902
1808
|
children: [
|
|
1903
|
-
/* @__PURE__ */ (0,
|
|
1809
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
|
|
1904
1810
|
"span",
|
|
1905
1811
|
{
|
|
1906
1812
|
className: "track-position-play-pause__bars",
|
|
@@ -1908,18 +1814,18 @@ function TrackPositionPlayPause({
|
|
|
1908
1814
|
role: "img",
|
|
1909
1815
|
"aria-label": isPlaying ? "Playing" : "Paused",
|
|
1910
1816
|
children: [
|
|
1911
|
-
/* @__PURE__ */ (0,
|
|
1912
|
-
/* @__PURE__ */ (0,
|
|
1913
|
-
/* @__PURE__ */ (0,
|
|
1817
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "track-position-play-pause__bar" }),
|
|
1818
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "track-position-play-pause__bar" }),
|
|
1819
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "track-position-play-pause__bar" })
|
|
1914
1820
|
]
|
|
1915
1821
|
}
|
|
1916
1822
|
) : position }),
|
|
1917
|
-
/* @__PURE__ */ (0,
|
|
1823
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
1918
1824
|
"div",
|
|
1919
1825
|
{
|
|
1920
1826
|
className: `hidden group-hover:flex items-center justify-center ${isCurrentTrackActive ? "" : "absolute inset-0"}`,
|
|
1921
1827
|
"aria-hidden": "true",
|
|
1922
|
-
children: isAwaitingPlayback ? /* @__PURE__ */ (0,
|
|
1828
|
+
children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_fa3.FaPause, {}) : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_fa3.FaPlay, {})
|
|
1923
1829
|
}
|
|
1924
1830
|
)
|
|
1925
1831
|
]
|
|
@@ -1930,13 +1836,14 @@ function TrackPositionPlayPause({
|
|
|
1930
1836
|
// src/genre-tree/track-list-sidebar/TrackItem.tsx
|
|
1931
1837
|
var import_genre_tree12 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1932
1838
|
var import_genre_tree13 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1933
|
-
var
|
|
1839
|
+
var import_jsx_runtime28 = require("react/jsx-runtime");
|
|
1934
1840
|
function TrackItem({
|
|
1935
1841
|
track,
|
|
1936
1842
|
position,
|
|
1937
1843
|
renderDuration,
|
|
1938
1844
|
renderActions,
|
|
1939
|
-
renderArtistLink
|
|
1845
|
+
renderArtistLink,
|
|
1846
|
+
renderSongLink
|
|
1940
1847
|
}) {
|
|
1941
1848
|
const { handlePlayPauseAction, playerTrackObject, loadTrackForPlayer } = (0, import_player9.usePlayer)();
|
|
1942
1849
|
const { trackList, selectedTrack, toTrackAtPosition, runtimeUnplayableTrackIds } = (0, import_genre_tree11.useTrackList)();
|
|
@@ -1953,12 +1860,12 @@ function TrackItem({
|
|
|
1953
1860
|
loadTrackForPlayer(track.uuid);
|
|
1954
1861
|
}
|
|
1955
1862
|
};
|
|
1956
|
-
return /* @__PURE__ */ (0,
|
|
1863
|
+
return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
|
|
1957
1864
|
"div",
|
|
1958
1865
|
{
|
|
1959
1866
|
className: `track-item flex h-14 text-[var(--color-text)] ${isUnplayable ? "opacity-50" : "hover:bg-[var(--color-surface-muted)] group"}`,
|
|
1960
1867
|
children: [
|
|
1961
|
-
isUnplayable ? /* @__PURE__ */ (0,
|
|
1868
|
+
isUnplayable ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
|
|
1962
1869
|
"div",
|
|
1963
1870
|
{
|
|
1964
1871
|
className: "flex items-center justify-center text-lg w-16",
|
|
@@ -1966,7 +1873,7 @@ function TrackItem({
|
|
|
1966
1873
|
"aria-disabled": "true",
|
|
1967
1874
|
children: position
|
|
1968
1875
|
}
|
|
1969
|
-
) : /* @__PURE__ */ (0,
|
|
1876
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
|
|
1970
1877
|
TrackPositionPlayPause,
|
|
1971
1878
|
{
|
|
1972
1879
|
position,
|
|
@@ -1975,12 +1882,12 @@ function TrackItem({
|
|
|
1975
1882
|
isSelected: selectedTrack?.uuid === track.uuid
|
|
1976
1883
|
}
|
|
1977
1884
|
),
|
|
1978
|
-
/* @__PURE__ */ (0,
|
|
1979
|
-
/* @__PURE__ */ (0,
|
|
1980
|
-
track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0,
|
|
1885
|
+
/* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
|
|
1886
|
+
/* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree12.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
|
|
1887
|
+
track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_genre_tree13.ArtistNames, { artists: track.artists, renderArtistLink }) }) : ""
|
|
1981
1888
|
] }),
|
|
1982
|
-
/* @__PURE__ */ (0,
|
|
1983
|
-
renderDuration && /* @__PURE__ */ (0,
|
|
1889
|
+
/* @__PURE__ */ (0, import_jsx_runtime28.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_runtime28.jsx)("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
|
|
1890
|
+
renderDuration && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
|
|
1984
1891
|
"div",
|
|
1985
1892
|
{
|
|
1986
1893
|
className: "duration flex text-base w-16 items-center justify-center",
|
|
@@ -1988,20 +1895,20 @@ function TrackItem({
|
|
|
1988
1895
|
children: renderDuration(track)
|
|
1989
1896
|
}
|
|
1990
1897
|
),
|
|
1991
|
-
renderActions && /* @__PURE__ */ (0,
|
|
1898
|
+
renderActions && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
|
|
1992
1899
|
]
|
|
1993
1900
|
}
|
|
1994
1901
|
);
|
|
1995
1902
|
}
|
|
1996
1903
|
|
|
1997
1904
|
// src/genre-tree/artist/ArtistSongs.tsx
|
|
1998
|
-
var
|
|
1905
|
+
var import_jsx_runtime29 = require("react/jsx-runtime");
|
|
1999
1906
|
var SongPageSchema2 = (0, import_transport7.PaginatedResponseSchema)(import_genre_tree14.SongDetailedSchema);
|
|
2000
1907
|
var SORTS = [
|
|
2001
1908
|
{ ordering: "title", label: "Title" },
|
|
2002
1909
|
{ ordering: "-albumYear", label: "Year" }
|
|
2003
1910
|
];
|
|
2004
|
-
function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
1911
|
+
function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLink }) {
|
|
2005
1912
|
const [page, setPage] = (0, import_react14.useState)(1);
|
|
2006
1913
|
const [ordering, setOrdering] = (0, import_react14.useState)("title");
|
|
2007
1914
|
const { fetch } = (0, import_transport7.useFetchWrapper)(getBackendBaseUrl);
|
|
@@ -2028,21 +1935,21 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
|
2028
1935
|
event.stopPropagation();
|
|
2029
1936
|
void play(song.uuid);
|
|
2030
1937
|
};
|
|
2031
|
-
return /* @__PURE__ */ (0,
|
|
2032
|
-
/* @__PURE__ */ (0,
|
|
2033
|
-
/* @__PURE__ */ (0,
|
|
1938
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui10.PanelSection, { title: "Songs", children: isPending ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui10.Skeleton, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(import_jsx_runtime29.Fragment, { children: [
|
|
1939
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
|
|
1940
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
|
|
2034
1941
|
"button",
|
|
2035
1942
|
{
|
|
2036
1943
|
type: "button",
|
|
2037
1944
|
onClick: () => void play(),
|
|
2038
1945
|
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",
|
|
2039
1946
|
children: [
|
|
2040
|
-
/* @__PURE__ */ (0,
|
|
1947
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_lucide_react9.Play, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
|
|
2041
1948
|
"Play all"
|
|
2042
1949
|
]
|
|
2043
1950
|
}
|
|
2044
1951
|
),
|
|
2045
|
-
/* @__PURE__ */ (0,
|
|
1952
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
2046
1953
|
"button",
|
|
2047
1954
|
{
|
|
2048
1955
|
type: "button",
|
|
@@ -2057,26 +1964,28 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
|
2057
1964
|
sort.ordering
|
|
2058
1965
|
)) })
|
|
2059
1966
|
] }),
|
|
2060
|
-
/* @__PURE__ */ (0,
|
|
1967
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
2061
1968
|
TrackItem,
|
|
2062
1969
|
{
|
|
2063
1970
|
track: song,
|
|
2064
1971
|
position: (page - 1) * data.pageSize + index + 1,
|
|
2065
|
-
renderArtistLink
|
|
1972
|
+
renderArtistLink,
|
|
1973
|
+
renderSongLink
|
|
2066
1974
|
}
|
|
2067
1975
|
) }, song.uuid)) }),
|
|
2068
|
-
/* @__PURE__ */ (0,
|
|
1976
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui10.Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
|
|
2069
1977
|
] }) });
|
|
2070
1978
|
}
|
|
2071
1979
|
|
|
2072
1980
|
// src/genre-tree/artist/useArtistPanel.tsx
|
|
2073
|
-
var
|
|
1981
|
+
var import_jsx_runtime30 = require("react/jsx-runtime");
|
|
2074
1982
|
function useArtistPanel({
|
|
2075
1983
|
uuid,
|
|
2076
1984
|
scope,
|
|
2077
1985
|
getBackendBaseUrl,
|
|
2078
1986
|
getShareUrl,
|
|
2079
1987
|
renderArtistLink,
|
|
1988
|
+
renderSongLink,
|
|
2080
1989
|
expanded,
|
|
2081
1990
|
onExpandedChange,
|
|
2082
1991
|
onClose
|
|
@@ -2090,26 +1999,26 @@ function useArtistPanel({
|
|
|
2090
1999
|
return {
|
|
2091
2000
|
...panel,
|
|
2092
2001
|
title: "Artist",
|
|
2093
|
-
content: /* @__PURE__ */ (0,
|
|
2094
|
-
/* @__PURE__ */ (0,
|
|
2095
|
-
/* @__PURE__ */ (0,
|
|
2096
|
-
/* @__PURE__ */ (0,
|
|
2097
|
-
/* @__PURE__ */ (0,
|
|
2098
|
-
/* @__PURE__ */ (0,
|
|
2099
|
-
/* @__PURE__ */ (0,
|
|
2002
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
|
|
2003
|
+
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui11.Skeleton, { className: "h-3 w-16" }),
|
|
2004
|
+
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui11.Skeleton, { className: "h-4 w-32" }),
|
|
2005
|
+
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui11.Skeleton, { className: "h-3 w-16" }),
|
|
2006
|
+
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui11.Skeleton, { className: "h-4 w-24" }),
|
|
2007
|
+
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui11.Skeleton, { className: "h-16 mt-2.5" }),
|
|
2008
|
+
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui11.Skeleton, { className: "h-16" })
|
|
2100
2009
|
] })
|
|
2101
2010
|
};
|
|
2102
2011
|
case "not-found":
|
|
2103
2012
|
return {
|
|
2104
2013
|
...panel,
|
|
2105
2014
|
title: "Artist not found",
|
|
2106
|
-
content: /* @__PURE__ */ (0,
|
|
2015
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This artist does not exist." })
|
|
2107
2016
|
};
|
|
2108
2017
|
case "error":
|
|
2109
2018
|
return {
|
|
2110
2019
|
...panel,
|
|
2111
2020
|
title: "Artist",
|
|
2112
|
-
content: /* @__PURE__ */ (0,
|
|
2021
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
|
|
2113
2022
|
};
|
|
2114
2023
|
case "success": {
|
|
2115
2024
|
const { artist } = result;
|
|
@@ -2118,63 +2027,251 @@ function useArtistPanel({
|
|
|
2118
2027
|
return {
|
|
2119
2028
|
...panel,
|
|
2120
2029
|
title: artist.name,
|
|
2121
|
-
content: /* @__PURE__ */ (0,
|
|
2122
|
-
shareUrl && /* @__PURE__ */ (0,
|
|
2123
|
-
artist.image && /* @__PURE__ */ (0,
|
|
2124
|
-
/* @__PURE__ */ (0,
|
|
2125
|
-
/* @__PURE__ */ (0,
|
|
2126
|
-
/* @__PURE__ */ (0,
|
|
2127
|
-
artist.tracksCount > 0 && /* @__PURE__ */ (0,
|
|
2030
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_jsx_runtime30.Fragment, { children: [
|
|
2031
|
+
shareUrl && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: shareTarget && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl }) }) }),
|
|
2032
|
+
artist.image && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ArtistPhoto, { image: artist.image, name: artist.name }),
|
|
2033
|
+
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ArtistHeader, { artist }),
|
|
2034
|
+
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
|
|
2035
|
+
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
|
|
2036
|
+
artist.tracksCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
|
|
2128
2037
|
ArtistSongs,
|
|
2129
2038
|
{
|
|
2130
2039
|
artist: { uuid: artist.uuid, name: artist.name },
|
|
2131
2040
|
getBackendBaseUrl,
|
|
2132
|
-
renderArtistLink
|
|
2041
|
+
renderArtistLink,
|
|
2042
|
+
renderSongLink
|
|
2133
2043
|
}
|
|
2134
2044
|
)
|
|
2135
2045
|
] }),
|
|
2136
|
-
extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0,
|
|
2046
|
+
extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ArtistDiscography, { albums: artist.albums }) : void 0
|
|
2137
2047
|
};
|
|
2138
2048
|
}
|
|
2139
2049
|
}
|
|
2140
2050
|
}
|
|
2141
2051
|
|
|
2142
|
-
// src/genre-tree/
|
|
2143
|
-
var
|
|
2052
|
+
// src/genre-tree/song/useSongPanel.tsx
|
|
2053
|
+
var import_ui13 = require("@behindthemusictree/ui");
|
|
2054
|
+
var import_genre_tree17 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2055
|
+
|
|
2056
|
+
// src/genre-tree/song/SongPanelContent.tsx
|
|
2057
|
+
var import_react15 = require("react");
|
|
2058
|
+
var import_lucide_react10 = require("lucide-react");
|
|
2144
2059
|
var import_ui12 = require("@behindthemusictree/ui");
|
|
2060
|
+
var import_player11 = require("@behindthemusictree/app-kit/player");
|
|
2061
|
+
var import_genre_tree16 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2062
|
+
var import_jsx_runtime31 = require("react/jsx-runtime");
|
|
2063
|
+
var numberFormat = new Intl.NumberFormat("en");
|
|
2064
|
+
var dateFormat = new Intl.DateTimeFormat("en", { dateStyle: "medium" });
|
|
2065
|
+
var externalLink = "text-[var(--color-primary)] hover:underline focus-visible:underline";
|
|
2066
|
+
function SongHistory({ uuid, getBackendBaseUrl }) {
|
|
2067
|
+
const [open, setOpen] = (0, import_react15.useState)(false);
|
|
2068
|
+
const history = (0, import_genre_tree16.useSongHistory)(uuid, getBackendBaseUrl, open);
|
|
2069
|
+
let body = null;
|
|
2070
|
+
if (history.status === "loading") body = /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ui12.Skeleton, { className: "h-10", "aria-busy": "true" });
|
|
2071
|
+
else if (history.status === "error") body = /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { className: "text-red-600", children: "Could not load the history." });
|
|
2072
|
+
else if (history.status === "success")
|
|
2073
|
+
body = history.entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("li", { className: "py-1.5 text-sm", children: [
|
|
2074
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "font-medium", children: entry.action }),
|
|
2075
|
+
entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
|
|
2076
|
+
" ",
|
|
2077
|
+
"\u2014 ",
|
|
2078
|
+
entry.oldValue ?? "none",
|
|
2079
|
+
" \u2192 ",
|
|
2080
|
+
entry.newValue ?? "none"
|
|
2081
|
+
] }) : null,
|
|
2082
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("span", { className: "block text-xs text-[var(--color-text-muted)]", children: [
|
|
2083
|
+
entry.actorPseudo ?? "Anonymous",
|
|
2084
|
+
" \xB7 ",
|
|
2085
|
+
dateFormat.format(new Date(entry.createdOn))
|
|
2086
|
+
] })
|
|
2087
|
+
] }, entry.uuid)) });
|
|
2088
|
+
return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
|
|
2089
|
+
"details",
|
|
2090
|
+
{
|
|
2091
|
+
onToggle: (event) => setOpen(event.currentTarget.open),
|
|
2092
|
+
className: "px-6 pb-2.5 mt-2.5 border-t border-[var(--color-border)]",
|
|
2093
|
+
children: [
|
|
2094
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)("summary", { className: "mt-2.5 cursor-pointer text-[0.6875rem] font-semibold uppercase text-[var(--color-text-muted)]", children: "History" }),
|
|
2095
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "mt-1.5", children: body })
|
|
2096
|
+
]
|
|
2097
|
+
}
|
|
2098
|
+
);
|
|
2099
|
+
}
|
|
2100
|
+
function SongPanelContent({
|
|
2101
|
+
song,
|
|
2102
|
+
scope,
|
|
2103
|
+
getBackendBaseUrl,
|
|
2104
|
+
shareUrl,
|
|
2105
|
+
renderArtistLink,
|
|
2106
|
+
genrePath,
|
|
2107
|
+
onSelectGenre
|
|
2108
|
+
}) {
|
|
2109
|
+
const { playNewTrackListFromTrackUuid } = (0, import_genre_tree16.useTrackList)();
|
|
2110
|
+
const { playerTrackObject, playState, handlePlayPauseAction } = (0, import_player11.usePlayer)();
|
|
2111
|
+
const unplayableReason = (0, import_genre_tree16.songUnplayableReason)(song);
|
|
2112
|
+
const isCurrent = playerTrackObject?.track.id === song.uuid;
|
|
2113
|
+
const isPlaying = isCurrent && playState === import_player11.PlayStates.PLAYING;
|
|
2114
|
+
const shareTarget = { kind: "song", uuid: song.uuid, name: song.title, scope };
|
|
2115
|
+
const musicBrainzUrl = (0, import_genre_tree16.musicBrainzRecordingUrl)(song);
|
|
2116
|
+
const fields = [];
|
|
2117
|
+
if (song.genre) {
|
|
2118
|
+
const path = genrePath.length > 0 ? genrePath : null;
|
|
2119
|
+
fields.push({
|
|
2120
|
+
label: "Genre",
|
|
2121
|
+
value: path ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("span", { children: [
|
|
2122
|
+
index > 0 && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { "aria-hidden": "true", children: "\u203A " }),
|
|
2123
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)("button", { type: "button", onClick: () => onSelectGenre(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
|
|
2124
|
+
] }, genrePlaylist.uuid)) }) : song.genre.name
|
|
2125
|
+
});
|
|
2126
|
+
} else {
|
|
2127
|
+
fields.push({ label: "Genre", value: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
|
|
2128
|
+
}
|
|
2129
|
+
if (song.firstReleaseYear) fields.push({ label: "First released", value: song.firstReleaseYear });
|
|
2130
|
+
const viewCount = song.youtubeVideo?.viewCount;
|
|
2131
|
+
if (viewCount !== null && viewCount !== void 0)
|
|
2132
|
+
fields.push({ label: "YouTube views", value: numberFormat.format(viewCount) });
|
|
2133
|
+
fields.push({ label: "Plays", value: numberFormat.format(song.playCount) });
|
|
2134
|
+
const links = [];
|
|
2135
|
+
if (musicBrainzUrl)
|
|
2136
|
+
links.push(
|
|
2137
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
|
|
2138
|
+
);
|
|
2139
|
+
if (song.youtubeVideo)
|
|
2140
|
+
links.push(
|
|
2141
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
|
|
2142
|
+
"a",
|
|
2143
|
+
{
|
|
2144
|
+
href: `https://www.youtube.com/watch?v=${encodeURIComponent(song.youtubeVideo.id)}`,
|
|
2145
|
+
target: "_blank",
|
|
2146
|
+
rel: "noopener noreferrer",
|
|
2147
|
+
className: externalLink,
|
|
2148
|
+
children: "YouTube"
|
|
2149
|
+
},
|
|
2150
|
+
"yt"
|
|
2151
|
+
)
|
|
2152
|
+
);
|
|
2153
|
+
if (links.length > 0)
|
|
2154
|
+
fields.push({
|
|
2155
|
+
label: "Links",
|
|
2156
|
+
value: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "flex gap-3", children: links })
|
|
2157
|
+
});
|
|
2158
|
+
return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_jsx_runtime31.Fragment, { children: [
|
|
2159
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
|
|
2160
|
+
unplayableReason ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { className: "flex items-center px-2.5 text-sm text-[var(--color-text-muted)]", children: unplayableReason }) : /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
|
|
2161
|
+
"button",
|
|
2162
|
+
{
|
|
2163
|
+
type: "button",
|
|
2164
|
+
onClick: () => isCurrent ? handlePlayPauseAction() : playNewTrackListFromTrackUuid(song, scope),
|
|
2165
|
+
className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none",
|
|
2166
|
+
children: [
|
|
2167
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.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_runtime31.jsx)(import_lucide_react10.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react10.Play, { "aria-hidden": "true", className: "w-5 h-5" }) }),
|
|
2168
|
+
isPlaying ? "Pause" : "Play"
|
|
2169
|
+
]
|
|
2170
|
+
}
|
|
2171
|
+
),
|
|
2172
|
+
shareUrl && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
|
|
2173
|
+
] }) }),
|
|
2174
|
+
song.artists && song.artists.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { className: "px-6 pt-2.5 text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_genre_tree16.ArtistNames, { artists: song.artists, renderArtistLink }) }),
|
|
2175
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ui12.FieldList, { items: fields }),
|
|
2176
|
+
song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ui12.PanelSection, { title: "Essential in", children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
|
|
2177
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
|
|
2178
|
+
] });
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
// src/genre-tree/song/useSongPanel.tsx
|
|
2145
2182
|
var import_jsx_runtime32 = require("react/jsx-runtime");
|
|
2183
|
+
function useSongPanel({
|
|
2184
|
+
uuid,
|
|
2185
|
+
scope,
|
|
2186
|
+
getBackendBaseUrl,
|
|
2187
|
+
getShareUrl,
|
|
2188
|
+
renderArtistLink,
|
|
2189
|
+
getGenrePath,
|
|
2190
|
+
onSelectGenre,
|
|
2191
|
+
expanded,
|
|
2192
|
+
onExpandedChange,
|
|
2193
|
+
onClose
|
|
2194
|
+
}) {
|
|
2195
|
+
const result = (0, import_genre_tree17.useSong)(uuid, getBackendBaseUrl);
|
|
2196
|
+
const panel = { expanded, onExpandedChange, onClose };
|
|
2197
|
+
switch (result.status) {
|
|
2198
|
+
case "idle":
|
|
2199
|
+
return void 0;
|
|
2200
|
+
case "loading":
|
|
2201
|
+
return {
|
|
2202
|
+
...panel,
|
|
2203
|
+
title: "Song",
|
|
2204
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
|
|
2205
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui13.Skeleton, { className: "h-3 w-16" }),
|
|
2206
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui13.Skeleton, { className: "h-4 w-32" }),
|
|
2207
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui13.Skeleton, { className: "h-3 w-16" }),
|
|
2208
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui13.Skeleton, { className: "h-4 w-24" })
|
|
2209
|
+
] })
|
|
2210
|
+
};
|
|
2211
|
+
case "not-found":
|
|
2212
|
+
return {
|
|
2213
|
+
...panel,
|
|
2214
|
+
title: "Song not found",
|
|
2215
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This song does not exist." })
|
|
2216
|
+
};
|
|
2217
|
+
case "error":
|
|
2218
|
+
return {
|
|
2219
|
+
...panel,
|
|
2220
|
+
title: "Song",
|
|
2221
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this song." })
|
|
2222
|
+
};
|
|
2223
|
+
case "success": {
|
|
2224
|
+
const { song } = result;
|
|
2225
|
+
const shareUrl = getShareUrl?.({ kind: "song", uuid: song.uuid, name: song.title, scope });
|
|
2226
|
+
return {
|
|
2227
|
+
...panel,
|
|
2228
|
+
title: song.title,
|
|
2229
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
|
|
2230
|
+
SongPanelContent,
|
|
2231
|
+
{
|
|
2232
|
+
song,
|
|
2233
|
+
scope,
|
|
2234
|
+
getBackendBaseUrl,
|
|
2235
|
+
shareUrl: shareUrl ?? void 0,
|
|
2236
|
+
renderArtistLink,
|
|
2237
|
+
genrePath: song.genre ? getGenrePath(song.genre) : [],
|
|
2238
|
+
onSelectGenre
|
|
2239
|
+
}
|
|
2240
|
+
)
|
|
2241
|
+
};
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
// src/genre-tree/SizeMetricToggle.tsx
|
|
2247
|
+
var import_md = require("react-icons/md");
|
|
2248
|
+
var import_ui14 = require("@behindthemusictree/ui");
|
|
2249
|
+
var import_jsx_runtime33 = require("react/jsx-runtime");
|
|
2146
2250
|
function SizeMetricToggle({
|
|
2147
2251
|
value,
|
|
2148
2252
|
onChange
|
|
2149
2253
|
}) {
|
|
2150
|
-
return /* @__PURE__ */ (0,
|
|
2151
|
-
|
|
2254
|
+
return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
|
|
2255
|
+
import_ui14.SegmentedControl,
|
|
2152
2256
|
{
|
|
2153
2257
|
variant: "floating",
|
|
2154
2258
|
"aria-label": "Size nodes by",
|
|
2155
2259
|
value,
|
|
2156
2260
|
onChange,
|
|
2157
2261
|
options: [
|
|
2158
|
-
{ value: "songs", label: /* @__PURE__ */ (0,
|
|
2159
|
-
{ value: "views", label: /* @__PURE__ */ (0,
|
|
2262
|
+
{ value: "songs", label: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_md.MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
|
|
2263
|
+
{ value: "views", label: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_md.MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
|
|
2160
2264
|
]
|
|
2161
2265
|
}
|
|
2162
2266
|
);
|
|
2163
2267
|
}
|
|
2164
2268
|
|
|
2165
2269
|
// src/genre-tree/GenreTreeView.tsx
|
|
2166
|
-
var
|
|
2167
|
-
var PREVIOUS_BUTTON_CSS = `
|
|
2168
|
-
.genre-tree-view .search-overlay .gtv-info-panel-previous { display: none; }
|
|
2169
|
-
.genre-tree-view:has(.search-overlay .gtv-info-panel-previous) .genre-search input { padding-left: 2.75rem; }
|
|
2170
|
-
.genre-tree-view:has(.search-overlay .gtv-info-panel-previous) .genre-search-previous { display: flex; }
|
|
2171
|
-
@keyframes genre-tree-view-previous-in { from { opacity: 0; transform: translate(-0.5rem, -50%); } to { opacity: 1; transform: translate(0, -50%); } }
|
|
2172
|
-
@media (prefers-reduced-motion: no-preference) {
|
|
2173
|
-
.genre-search-previous { animation: genre-tree-view-previous-in 200ms ease-out; }
|
|
2174
|
-
}
|
|
2175
|
-
`;
|
|
2270
|
+
var import_jsx_runtime34 = require("react/jsx-runtime");
|
|
2176
2271
|
var NO_POP_ROOT_TITLE = "This genre tree has no 'Mainstream Pop' root yet";
|
|
2177
|
-
var REGIONS_CLASS = "[--gtv-center-left:var(--layout-center-left)] [--gtv-center-right:var(--layout-center-right)] [--gtv-center-top:var(--layout-band-top)]
|
|
2272
|
+
var REGIONS_CLASS = "[--gtv-center-left:var(--layout-center-left)] [--gtv-center-right:var(--layout-center-right)] [--gtv-center-top:var(--layout-band-top)]";
|
|
2273
|
+
var noop = () => {
|
|
2274
|
+
};
|
|
2178
2275
|
function GenreTreeView({
|
|
2179
2276
|
scope,
|
|
2180
2277
|
handleGenreCreationAction,
|
|
@@ -2207,6 +2304,13 @@ function GenreTreeView({
|
|
|
2207
2304
|
artistSearch = true,
|
|
2208
2305
|
onArtistSelect,
|
|
2209
2306
|
artistSearchBaseUrl,
|
|
2307
|
+
songUuid: controlledSongUuid,
|
|
2308
|
+
onSongUuidChange,
|
|
2309
|
+
songExpanded: controlledSongExpanded,
|
|
2310
|
+
onSongExpandedChange,
|
|
2311
|
+
songSearch = true,
|
|
2312
|
+
onSongSelect,
|
|
2313
|
+
renderSongLink,
|
|
2210
2314
|
renderSummary,
|
|
2211
2315
|
renderEssentialTracks,
|
|
2212
2316
|
renderSongLeading,
|
|
@@ -2221,14 +2325,21 @@ function GenreTreeView({
|
|
|
2221
2325
|
placement = "default",
|
|
2222
2326
|
...focus
|
|
2223
2327
|
}) {
|
|
2224
|
-
const getShareUrlRef = (0,
|
|
2328
|
+
const getShareUrlRef = (0, import_react16.useRef)(getShareUrl);
|
|
2225
2329
|
getShareUrlRef.current = getShareUrl;
|
|
2226
|
-
const [internalArtistUuid, setInternalArtistUuid] = (0,
|
|
2330
|
+
const [internalArtistUuid, setInternalArtistUuid] = (0, import_react16.useState)(null);
|
|
2227
2331
|
const isArtistControlled = controlledArtistUuid !== void 0;
|
|
2228
2332
|
const artistUuid = isArtistControlled ? controlledArtistUuid : internalArtistUuid;
|
|
2229
|
-
const [
|
|
2333
|
+
const [internalSongUuid, setInternalSongUuid] = (0, import_react16.useState)(null);
|
|
2334
|
+
const isSongControlled = controlledSongUuid !== void 0;
|
|
2335
|
+
const songUuid = isSongControlled ? controlledSongUuid : internalSongUuid;
|
|
2336
|
+
const changeSong = (uuid) => {
|
|
2337
|
+
if (!isSongControlled) setInternalSongUuid(uuid);
|
|
2338
|
+
onSongUuidChange?.(uuid);
|
|
2339
|
+
};
|
|
2340
|
+
const [internalSizeMetric, setInternalSizeMetric] = (0, import_react16.useState)("songs");
|
|
2230
2341
|
const sizeMetric = controlledSizeMetric ?? internalSizeMetric;
|
|
2231
|
-
const deferredSizeMetric = (0,
|
|
2342
|
+
const deferredSizeMetric = (0, import_react16.useDeferredValue)(sizeMetric);
|
|
2232
2343
|
const changeSizeMetric = (metric) => {
|
|
2233
2344
|
if (controlledSizeMetric === void 0) setInternalSizeMetric(metric);
|
|
2234
2345
|
onSizeMetricChange?.(metric);
|
|
@@ -2255,7 +2366,7 @@ function GenreTreeView({
|
|
|
2255
2366
|
allowWheelRotation: internalAllowWheelRotation,
|
|
2256
2367
|
toggleWheelRotation,
|
|
2257
2368
|
onNodeHover: handleNodeHover
|
|
2258
|
-
} = (0,
|
|
2369
|
+
} = (0, import_genre_tree19.useGenreTreeViewState)({
|
|
2259
2370
|
scope,
|
|
2260
2371
|
getBackendBaseUrl,
|
|
2261
2372
|
viewMode: controlledViewMode,
|
|
@@ -2265,7 +2376,7 @@ function GenreTreeView({
|
|
|
2265
2376
|
artistUuid
|
|
2266
2377
|
});
|
|
2267
2378
|
const restoreRevision = viewportRestore?.revision;
|
|
2268
|
-
const [stackedRestoreOwner, setStackedRestoreOwner] = (0,
|
|
2379
|
+
const [stackedRestoreOwner, setStackedRestoreOwner] = (0, import_react16.useState)({ revision: restoreRevision, rootUuid: selectedGenrePlaylist?.root.uuid });
|
|
2269
2380
|
let restoreRootUuid = stackedRestoreOwner.rootUuid;
|
|
2270
2381
|
if (stackedRestoreOwner.revision !== restoreRevision) {
|
|
2271
2382
|
restoreRootUuid = selectedGenrePlaylist?.root.uuid;
|
|
@@ -2274,14 +2385,14 @@ function GenreTreeView({
|
|
|
2274
2385
|
restoreRootUuid = selectedGenrePlaylist.root.uuid;
|
|
2275
2386
|
setStackedRestoreOwner({ revision: restoreRevision, rootUuid: restoreRootUuid });
|
|
2276
2387
|
}
|
|
2277
|
-
const isGenreUpdating = (0, import_react_query3.useIsMutating)({ mutationKey:
|
|
2278
|
-
const { mutate: updateGenre } = (0,
|
|
2388
|
+
const isGenreUpdating = (0, import_react_query3.useIsMutating)({ mutationKey: import_genre_tree19.genreUpdateMutationKey }) > 0;
|
|
2389
|
+
const { mutate: updateGenre } = (0, import_genre_tree19.useUpdateGenre)(scope, getBackendBaseUrl);
|
|
2279
2390
|
const busy = deferredSizeMetric !== sizeMetric || isTreeFetching || isGenreUpdating;
|
|
2280
2391
|
const isArtistFiltered = artistUuid !== null;
|
|
2281
2392
|
const canSizeByViews = !isArtistFiltered && hasViewsCount(visibleGenrePlaylists);
|
|
2282
|
-
const artistResult = (0,
|
|
2393
|
+
const artistResult = (0, import_genre_tree19.useArtist)(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
|
|
2283
2394
|
const filterArtistName = artistResult.status === "success" ? artistResult.artist.name : null;
|
|
2284
|
-
const filterArtist = (0,
|
|
2395
|
+
const filterArtist = (0, import_react16.useMemo)(
|
|
2285
2396
|
() => artistUuid !== null && filterArtistName !== null ? { uuid: artistUuid, name: filterArtistName } : null,
|
|
2286
2397
|
[artistUuid, filterArtistName]
|
|
2287
2398
|
);
|
|
@@ -2289,27 +2400,71 @@ function GenreTreeView({
|
|
|
2289
2400
|
const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: visibleGenrePlaylists, artist: filterArtist });
|
|
2290
2401
|
const openArtist = (uuid) => {
|
|
2291
2402
|
if (selectedNodeId !== null) clearSelection();
|
|
2403
|
+
if (songUuid !== null) changeSong(null);
|
|
2292
2404
|
changeArtist(uuid);
|
|
2293
2405
|
};
|
|
2294
|
-
const
|
|
2406
|
+
const openSong = (uuid) => {
|
|
2407
|
+
if (selectedNodeId !== null) clearSelection();
|
|
2408
|
+
changeSong(uuid);
|
|
2409
|
+
};
|
|
2410
|
+
const openSongRef = (0, import_react16.useRef)(openSong);
|
|
2411
|
+
openSongRef.current = openSong;
|
|
2412
|
+
const resolvedRenderSongLink = (0, import_react16.useCallback)(
|
|
2413
|
+
(song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
|
|
2414
|
+
[renderSongLink]
|
|
2415
|
+
);
|
|
2416
|
+
const [expandedSongUuid, setExpandedSongUuid] = (0, import_react16.useState)(null);
|
|
2417
|
+
const [expandedArtistUuid, setExpandedArtistUuid] = (0, import_react16.useState)(null);
|
|
2295
2418
|
const artistBaseUrl = artistSearchBaseUrl ?? getBackendBaseUrl;
|
|
2419
|
+
const songBaseUrl = getBackendBaseUrl;
|
|
2296
2420
|
const artistPanel = useArtistPanel({
|
|
2297
2421
|
uuid: artistUuid,
|
|
2298
2422
|
scope,
|
|
2299
2423
|
getBackendBaseUrl: artistBaseUrl,
|
|
2300
2424
|
getShareUrl: getShareUrlRef.current,
|
|
2301
|
-
renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0,
|
|
2425
|
+
renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
|
|
2302
2426
|
expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
|
|
2303
2427
|
onExpandedChange: (on) => {
|
|
2304
2428
|
if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
|
|
2305
2429
|
onArtistExpandedChange?.(on);
|
|
2306
2430
|
},
|
|
2431
|
+
renderSongLink: resolvedRenderSongLink,
|
|
2307
2432
|
onClose: () => changeArtist(null)
|
|
2308
2433
|
});
|
|
2309
|
-
const
|
|
2310
|
-
const
|
|
2311
|
-
|
|
2312
|
-
|
|
2434
|
+
const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
|
|
2435
|
+
const getGenrePath = (genre) => {
|
|
2436
|
+
const path = [];
|
|
2437
|
+
let current = visibleGenrePlaylists.find((playlist) => playlist.criteria?.uuid === genre.uuid);
|
|
2438
|
+
while (current) {
|
|
2439
|
+
path.unshift(current);
|
|
2440
|
+
const parentUuid = current.parent?.uuid;
|
|
2441
|
+
current = parentUuid ? visibleGenrePlaylists.find((playlist) => playlist.uuid === parentUuid) : void 0;
|
|
2442
|
+
}
|
|
2443
|
+
return path;
|
|
2444
|
+
};
|
|
2445
|
+
const songPanel = useSongPanel({
|
|
2446
|
+
uuid: songUuid,
|
|
2447
|
+
scope,
|
|
2448
|
+
getBackendBaseUrl: songBaseUrl,
|
|
2449
|
+
getShareUrl: getShareUrlRef.current,
|
|
2450
|
+
renderArtistLink: resolvedRenderArtistLink,
|
|
2451
|
+
getGenrePath,
|
|
2452
|
+
onSelectGenre: (genrePlaylist) => {
|
|
2453
|
+
changeSong(null);
|
|
2454
|
+
selectGenrePlaylist(genrePlaylist);
|
|
2455
|
+
},
|
|
2456
|
+
expanded: controlledSongExpanded ?? (songUuid !== null && expandedSongUuid === songUuid),
|
|
2457
|
+
onExpandedChange: (on) => {
|
|
2458
|
+
if (controlledSongExpanded === void 0) setExpandedSongUuid(on ? songUuid : null);
|
|
2459
|
+
onSongExpandedChange?.(on);
|
|
2460
|
+
},
|
|
2461
|
+
onClose: () => changeSong(null)
|
|
2462
|
+
});
|
|
2463
|
+
const builtInSongPanel = overlayPanel || hasSelectedGenreNode ? void 0 : songPanel;
|
|
2464
|
+
const builtInArtistPanel = overlayPanel || hasSelectedGenreNode || builtInSongPanel ? void 0 : artistPanel;
|
|
2465
|
+
const builtInPanel = builtInSongPanel ?? builtInArtistPanel;
|
|
2466
|
+
const [uncontrolledCollapsed, setUncontrolledCollapsed] = (0, import_react16.useState)(false);
|
|
2467
|
+
const [uncontrolledExpanded, setUncontrolledExpanded] = (0, import_react16.useState)(false);
|
|
2313
2468
|
const isCollapsed = collapsed ?? uncontrolledCollapsed;
|
|
2314
2469
|
const isExpanded = expanded ?? uncontrolledExpanded;
|
|
2315
2470
|
const changeCollapsed = (next) => {
|
|
@@ -2320,54 +2475,56 @@ function GenreTreeView({
|
|
|
2320
2475
|
setUncontrolledExpanded(next);
|
|
2321
2476
|
onExpandedChange?.(next);
|
|
2322
2477
|
};
|
|
2323
|
-
(0,
|
|
2478
|
+
(0, import_react16.useEffect)(() => {
|
|
2324
2479
|
setUncontrolledCollapsed(false);
|
|
2325
|
-
}, [selectedNodeId, artistUuid]);
|
|
2326
|
-
const
|
|
2327
|
-
useLeftDetailPanelLayout(!isDetailHidden, placement === "regions");
|
|
2328
|
-
const detailOverlay = placement === "regions" && activeOverlayPanel?.onExpandedChange ? activeOverlayPanel : void 0;
|
|
2329
|
-
const detailExpanded = detailOverlay ? !!detailOverlay.expanded : isExpanded;
|
|
2330
|
-
const onDetailExpandedChange = detailOverlay ? detailOverlay.onExpandedChange : changeExpanded;
|
|
2331
|
-
const infoPanelCuration = {
|
|
2332
|
-
renderDangerZone,
|
|
2333
|
-
expanded: placement === "regions" ? isExpanded : expanded,
|
|
2334
|
-
onExpandedChange: placement === "regions" ? changeExpanded : onExpandedChange,
|
|
2335
|
-
collapsed: placement === "regions" ? isCollapsed : void 0,
|
|
2336
|
-
hideExpandButton: placement === "regions" ? true : void 0,
|
|
2337
|
-
overlayPanel: detailOverlay ? { ...detailOverlay, collapsed: isCollapsed, hideExpandButton: true } : activeOverlayPanel
|
|
2338
|
-
};
|
|
2339
|
-
const [isWheelRevealed, setIsWheelRevealed] = (0, import_react15.useState)(false);
|
|
2480
|
+
}, [selectedNodeId, artistUuid, songUuid]);
|
|
2481
|
+
const [isWheelRevealed, setIsWheelRevealed] = (0, import_react16.useState)(false);
|
|
2340
2482
|
const usesHandoff = viewMode === "wheel" || viewMode === "pop-core";
|
|
2341
2483
|
const isTreeReady = !isLoading && (!usesHandoff || isWheelRevealed);
|
|
2342
|
-
const
|
|
2343
|
-
const
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2484
|
+
const [resolveStyle, setResolveStyle] = (0, import_react16.useState)(null);
|
|
2485
|
+
const handleNodeStyleResolverChange = (0, import_react16.useCallback)(
|
|
2486
|
+
(resolver) => setResolveStyle(() => resolver),
|
|
2487
|
+
[]
|
|
2488
|
+
);
|
|
2489
|
+
const panelRef = (0, import_react16.useRef)(null);
|
|
2490
|
+
const obscuredInset = (0, import_ui15.useObscuredInset)(panelRef);
|
|
2491
|
+
const hostPanel = { obscuredInset, onNodeStyleResolverChange: handleNodeStyleResolverChange };
|
|
2492
|
+
const nodeMetric = isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric;
|
|
2493
|
+
const treeNodes = (0, import_react16.useMemo)(() => toTreeNodes(visibleGenrePlaylists, nodeMetric), [visibleGenrePlaylists, nodeMetric]);
|
|
2494
|
+
const relations = (0, import_genre_tree_view4.useGenreNodeRelations)(
|
|
2495
|
+
treeNodes,
|
|
2496
|
+
!overlayPanel && hasSelectedGenreNode ? selectedNodeId : null,
|
|
2497
|
+
resolveStyle
|
|
2498
|
+
);
|
|
2499
|
+
const [selectionHistory, setSelectionHistory] = (0, import_react16.useState)({
|
|
2500
|
+
current: selectedNodeId,
|
|
2501
|
+
previous: null
|
|
2502
|
+
});
|
|
2503
|
+
if (selectionHistory.current !== selectedNodeId) {
|
|
2504
|
+
setSelectionHistory({
|
|
2505
|
+
current: selectedNodeId,
|
|
2506
|
+
previous: selectionHistory.current ?? selectionHistory.previous
|
|
2507
|
+
});
|
|
2508
|
+
}
|
|
2509
|
+
const previousPlaylist = selectionHistory.previous !== null && selectionHistory.previous !== selectedNodeId ? genrePlaylistByNodeId.get(selectionHistory.previous) : void 0;
|
|
2510
|
+
const handlePrevious = previousPlaylist && visibleGenrePlaylists.includes(previousPlaylist) ? () => handleGenreSearchSelect(previousPlaylist) : void 0;
|
|
2355
2511
|
const { showPopup, hidePopup } = (0, import_popup5.usePopup)();
|
|
2356
|
-
const [searchText, setSearchText] = (0,
|
|
2357
|
-
const artistSearchResult = (0,
|
|
2358
|
-
const
|
|
2512
|
+
const [searchText, setSearchText] = (0, import_react16.useState)("");
|
|
2513
|
+
const artistSearchResult = (0, import_genre_tree18.useArtistSearch)(artistBaseUrl, artistSearch ? searchText : "");
|
|
2514
|
+
const artistGroup = artistSearch ? {
|
|
2515
|
+
key: "artists",
|
|
2359
2516
|
label: "Artists",
|
|
2360
2517
|
items: artistSearchResult.artists.map((artist) => ({
|
|
2361
2518
|
key: artist.uuid,
|
|
2362
|
-
content: /* @__PURE__ */ (0,
|
|
2363
|
-
/* @__PURE__ */ (0,
|
|
2519
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
|
|
2520
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "block truncate font-medium", children: [
|
|
2364
2521
|
artist.name,
|
|
2365
|
-
artist.disambiguation && /* @__PURE__ */ (0,
|
|
2522
|
+
artist.disambiguation && /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "font-normal text-[var(--color-neutral-500)]", children: [
|
|
2366
2523
|
" \xB7 ",
|
|
2367
2524
|
artist.disambiguation
|
|
2368
2525
|
] })
|
|
2369
2526
|
] }),
|
|
2370
|
-
artist.matchedAlias && /* @__PURE__ */ (0,
|
|
2527
|
+
artist.matchedAlias && /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [
|
|
2371
2528
|
"also known as ",
|
|
2372
2529
|
artist.matchedAlias
|
|
2373
2530
|
] })
|
|
@@ -2380,11 +2537,32 @@ function GenreTreeView({
|
|
|
2380
2537
|
status: artistSearchResult.isError ? "Search failed" : artistSearchResult.settled && artistSearchResult.artists.length === 0 ? "No artists found" : "",
|
|
2381
2538
|
loading: artistSearchResult.pending
|
|
2382
2539
|
} : void 0;
|
|
2540
|
+
const songSearchResult = (0, import_genre_tree18.useSongSearch)(songBaseUrl, songSearch ? searchText : "");
|
|
2541
|
+
const songGroup = songSearch ? {
|
|
2542
|
+
key: "songs",
|
|
2543
|
+
label: "Songs",
|
|
2544
|
+
items: songSearchResult.songs.map((song) => ({
|
|
2545
|
+
key: song.uuid,
|
|
2546
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: (0, import_genre_tree18.isSongSearchResultPlayable)(song) ? void 0 : "opacity-60", children: [
|
|
2547
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "block truncate font-medium", children: song.title }),
|
|
2548
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [song.artists.map((artist) => artist.name).join(", "), song.genre?.name ?? "No genre"].filter(Boolean).join(" \xB7 ") })
|
|
2549
|
+
] }),
|
|
2550
|
+
onSelect: () => {
|
|
2551
|
+
openSong(song.uuid);
|
|
2552
|
+
onSongSelect?.(song);
|
|
2553
|
+
}
|
|
2554
|
+
})),
|
|
2555
|
+
status: songSearchResult.isError ? "Search failed" : songSearchResult.settled && songSearchResult.songs.length === 0 ? "No songs found" : "",
|
|
2556
|
+
loading: songSearchResult.pending
|
|
2557
|
+
} : void 0;
|
|
2558
|
+
const extraGroups = [artistGroup, songGroup].filter((group) => group !== void 0);
|
|
2383
2559
|
const handleSelectedNodeChange = (node) => {
|
|
2560
|
+
if (node !== null && songUuid !== null) changeSong(null);
|
|
2384
2561
|
selectNode(node);
|
|
2385
2562
|
};
|
|
2386
2563
|
const handleGenreSearchClear = clearSelection;
|
|
2387
2564
|
const handleGenreSearchSelect = (genrePlaylist) => {
|
|
2565
|
+
if (songUuid !== null) changeSong(null);
|
|
2388
2566
|
selectGenrePlaylist(genrePlaylist);
|
|
2389
2567
|
};
|
|
2390
2568
|
const selectedUuid = selectedGenrePlaylist?.uuid;
|
|
@@ -2393,36 +2571,49 @@ function GenreTreeView({
|
|
|
2393
2571
|
const handleGenreSearchZoomIn = () => {
|
|
2394
2572
|
if (selectedUuid) focus.onFocusGenreChange?.(selectedUuid, "zoom-in");
|
|
2395
2573
|
};
|
|
2396
|
-
const zoomInRef = (0,
|
|
2574
|
+
const zoomInRef = (0, import_react16.useRef)(void 0);
|
|
2397
2575
|
zoomInRef.current = canZoomIn ? handleGenreSearchZoomIn : void 0;
|
|
2398
|
-
const
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2576
|
+
const selectGenreFromPanel = (nodeId) => {
|
|
2577
|
+
const genrePlaylist = genrePlaylistByNodeId.get(nodeId);
|
|
2578
|
+
if (genrePlaylist) handleGenreSearchSelect(genrePlaylist);
|
|
2579
|
+
};
|
|
2580
|
+
const renderGenreBody = (relation) => {
|
|
2581
|
+
const { node } = relation;
|
|
2582
|
+
const family = /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2583
|
+
import_genre_tree_view4.GenreNodeFamily,
|
|
2584
|
+
{
|
|
2585
|
+
node,
|
|
2586
|
+
fill: relation.fill,
|
|
2587
|
+
textColor: relation.textColor,
|
|
2588
|
+
parentNode: relation.parentNode,
|
|
2589
|
+
childNodes: relation.childNodes,
|
|
2590
|
+
onSelectNode: selectGenreFromPanel
|
|
2591
|
+
}
|
|
2592
|
+
);
|
|
2593
|
+
const ancestors = /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_genre_tree_view4.GenreNodeAncestors, { ancestorNodes: relation.ancestorNodes, onSelectNode: selectGenreFromPanel });
|
|
2594
|
+
const genrePlaylist = genrePlaylistByNodeId.get(node.id);
|
|
2595
|
+
const criteria = genrePlaylist?.criteria;
|
|
2596
|
+
if (!genrePlaylist || !criteria) return { primary: family, aside: ancestors };
|
|
2597
|
+
const genreUuid = criteria.uuid;
|
|
2598
|
+
const parentPlaylist = visibleGenrePlaylists.find(({ uuid }) => uuid === genrePlaylist.parent?.uuid);
|
|
2599
|
+
const canToggleSide = !readOnly && parentPlaylist !== void 0 && parentPlaylist.parent === null;
|
|
2600
|
+
const side = criteria.side ?? "core";
|
|
2601
|
+
const shareTarget = {
|
|
2602
|
+
kind: "genre",
|
|
2603
|
+
genrePlaylistUuid: genrePlaylist.uuid,
|
|
2604
|
+
name: criteria.name,
|
|
2605
|
+
scope,
|
|
2606
|
+
artistUuid
|
|
2607
|
+
};
|
|
2608
|
+
const shareUrl = getShareUrlRef.current?.(shareTarget) ?? void 0;
|
|
2609
|
+
const popSibling = canToggleSide && side === "core" ? visibleGenrePlaylists.find(
|
|
2610
|
+
(sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist.uuid && sibling.criteria?.side === "pop"
|
|
2611
|
+
) : void 0;
|
|
2612
|
+
return {
|
|
2613
|
+
primary: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
|
|
2614
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2423
2615
|
GenreInfoPanelBody,
|
|
2424
2616
|
{
|
|
2425
|
-
toolbar,
|
|
2426
2617
|
family,
|
|
2427
2618
|
genreUuid,
|
|
2428
2619
|
genrePlaylist,
|
|
@@ -2438,7 +2629,7 @@ function GenreTreeView({
|
|
|
2438
2629
|
onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
|
|
2439
2630
|
onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
|
|
2440
2631
|
onChangeParent: readOnly ? void 0 : () => showPopup(
|
|
2441
|
-
/* @__PURE__ */ (0,
|
|
2632
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2442
2633
|
ChangeParentPopup,
|
|
2443
2634
|
{
|
|
2444
2635
|
genrePlaylist: { ...genrePlaylist, criteria },
|
|
@@ -2459,6 +2650,7 @@ function GenreTreeView({
|
|
|
2459
2650
|
criteriaOverviewSchema,
|
|
2460
2651
|
renderGenreDetailExtras,
|
|
2461
2652
|
renderArtistLink,
|
|
2653
|
+
renderSongLink: resolvedRenderSongLink,
|
|
2462
2654
|
artist: filterArtist,
|
|
2463
2655
|
renderSummary,
|
|
2464
2656
|
renderEssentialTracks,
|
|
@@ -2468,48 +2660,65 @@ function GenreTreeView({
|
|
|
2468
2660
|
}
|
|
2469
2661
|
}
|
|
2470
2662
|
),
|
|
2471
|
-
|
|
2472
|
-
}
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
additionalActions,
|
|
2477
|
-
readOnly,
|
|
2478
|
-
handleGenreCreationAction,
|
|
2479
|
-
handleGenreRenameAction,
|
|
2480
|
-
showPopup,
|
|
2481
|
-
hidePopup,
|
|
2482
|
-
onParentChanged,
|
|
2483
|
-
visibleGenrePlaylists,
|
|
2484
|
-
updateGenre,
|
|
2485
|
-
playback,
|
|
2486
|
-
filterArtist,
|
|
2487
|
-
scope,
|
|
2488
|
-
getBackendBaseUrl,
|
|
2489
|
-
criteriaOverviewSchema,
|
|
2490
|
-
renderGenreDetailExtras,
|
|
2491
|
-
renderArtistLink,
|
|
2492
|
-
renderSummary,
|
|
2493
|
-
renderEssentialTracks,
|
|
2494
|
-
renderSongLeading,
|
|
2495
|
-
renderSongActions,
|
|
2496
|
-
renderTracksToolbar
|
|
2497
|
-
]
|
|
2498
|
-
);
|
|
2663
|
+
renderDangerZone?.(node)
|
|
2664
|
+
] }),
|
|
2665
|
+
aside: ancestors
|
|
2666
|
+
};
|
|
2667
|
+
};
|
|
2499
2668
|
const isRotationControlled = controlledAllowWheelRotation !== void 0;
|
|
2500
2669
|
const allowWheelRotation = controlledAllowWheelRotation ?? internalAllowWheelRotation;
|
|
2501
|
-
|
|
2502
|
-
|
|
2670
|
+
let activePanel = null;
|
|
2671
|
+
if (overlayPanel) {
|
|
2672
|
+
activePanel = {
|
|
2673
|
+
title: overlayPanel.title,
|
|
2674
|
+
content: overlayPanel.content,
|
|
2675
|
+
aside: overlayPanel.extraContent,
|
|
2676
|
+
expanded: overlayPanel.expanded ?? false,
|
|
2677
|
+
onExpandedChange: overlayPanel.onExpandedChange,
|
|
2678
|
+
onClose: overlayPanel.onClose,
|
|
2679
|
+
compact: false
|
|
2680
|
+
};
|
|
2681
|
+
} else if (relations) {
|
|
2682
|
+
const { primary, aside } = renderGenreBody(relations);
|
|
2683
|
+
activePanel = {
|
|
2684
|
+
title: relations.node.name,
|
|
2685
|
+
content: primary,
|
|
2686
|
+
aside,
|
|
2687
|
+
expanded: isExpanded,
|
|
2688
|
+
onExpandedChange: changeExpanded,
|
|
2689
|
+
onClose: clearSelection,
|
|
2690
|
+
compact: true,
|
|
2691
|
+
headerAccent: { background: relations.fill, color: relations.textColor }
|
|
2692
|
+
};
|
|
2693
|
+
} else if (builtInPanel) {
|
|
2694
|
+
activePanel = {
|
|
2695
|
+
title: builtInPanel.title,
|
|
2696
|
+
content: builtInPanel.content,
|
|
2697
|
+
aside: builtInPanel.extraContent,
|
|
2698
|
+
expanded: builtInPanel.expanded,
|
|
2699
|
+
onExpandedChange: builtInPanel.onExpandedChange,
|
|
2700
|
+
onClose: builtInPanel.onClose,
|
|
2701
|
+
compact: true
|
|
2702
|
+
};
|
|
2703
|
+
}
|
|
2704
|
+
const isPanelShown = isTreeReady && activePanel !== null;
|
|
2705
|
+
const detailTab = placement === "regions" && activePanel?.onExpandedChange ? {
|
|
2706
|
+
edge: activePanel.expanded ? { label: "Shrink panel", onClick: () => activePanel.onExpandedChange?.(false) } : isCollapsed ? null : { label: "Hide panel", onClick: () => changeCollapsed(true) },
|
|
2707
|
+
center: activePanel.expanded ? null : isCollapsed ? { label: "Show panel", onClick: () => changeCollapsed(false) } : activePanel.onExpandedChange ? { label: "Extend panel", onClick: () => activePanel.onExpandedChange?.(true) } : null
|
|
2708
|
+
} : void 0;
|
|
2709
|
+
const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2710
|
+
import_ui15.IconButton,
|
|
2503
2711
|
{
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2712
|
+
"aria-label": activePanel.expanded ? "Shrink panel" : "Extend panel",
|
|
2713
|
+
style: activePanel.headerAccent && { color: "inherit" },
|
|
2714
|
+
onClick: () => activePanel.onExpandedChange?.(!activePanel.expanded),
|
|
2715
|
+
children: activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react12.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react12.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
2507
2716
|
}
|
|
2508
2717
|
) : null;
|
|
2509
2718
|
const renderArtistFilter = () => {
|
|
2510
2719
|
if (isLoading || !filterArtist) return null;
|
|
2511
|
-
return /* @__PURE__ */ (0,
|
|
2512
|
-
/* @__PURE__ */ (0,
|
|
2720
|
+
return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
|
|
2721
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2513
2722
|
"button",
|
|
2514
2723
|
{
|
|
2515
2724
|
type: "button",
|
|
@@ -2521,7 +2730,7 @@ function GenreTreeView({
|
|
|
2521
2730
|
children: filterArtist.name
|
|
2522
2731
|
}
|
|
2523
2732
|
),
|
|
2524
|
-
/* @__PURE__ */ (0,
|
|
2733
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2525
2734
|
"button",
|
|
2526
2735
|
{
|
|
2527
2736
|
type: "button",
|
|
@@ -2533,23 +2742,23 @@ function GenreTreeView({
|
|
|
2533
2742
|
)
|
|
2534
2743
|
] });
|
|
2535
2744
|
};
|
|
2536
|
-
const actions = /* @__PURE__ */ (0,
|
|
2537
|
-
!isLoading && /* @__PURE__ */ (0,
|
|
2745
|
+
const actions = /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
|
|
2746
|
+
!isLoading && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2538
2747
|
GenreSearch,
|
|
2539
2748
|
{
|
|
2540
2749
|
genrePlaylists: visibleGenrePlaylists,
|
|
2541
2750
|
onSelect: handleGenreSearchSelect,
|
|
2542
|
-
selectedName: selectedGenrePlaylist?.name ??
|
|
2543
|
-
onClear: builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
|
|
2751
|
+
selectedName: selectedGenrePlaylist?.name ?? (typeof builtInPanel?.title === "string" ? builtInPanel.title : null),
|
|
2752
|
+
onClear: builtInSongPanel ? () => changeSong(null) : builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
|
|
2544
2753
|
onPrevious: handlePrevious,
|
|
2545
|
-
onQueryChange: artistSearch ? setSearchText : void 0,
|
|
2546
|
-
|
|
2754
|
+
onQueryChange: artistSearch || songSearch ? setSearchText : void 0,
|
|
2755
|
+
extraGroups
|
|
2547
2756
|
}
|
|
2548
2757
|
),
|
|
2549
|
-
!isLoading && filterArtist && /* @__PURE__ */ (0,
|
|
2550
|
-
/* @__PURE__ */ (0,
|
|
2551
|
-
!isLoading && !isControlled && /* @__PURE__ */ (0,
|
|
2552
|
-
|
|
2758
|
+
!isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
|
|
2759
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.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: [
|
|
2760
|
+
!isLoading && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2761
|
+
import_ui15.SegmentedControl,
|
|
2553
2762
|
{
|
|
2554
2763
|
"aria-label": "Tree view mode",
|
|
2555
2764
|
value: viewMode,
|
|
@@ -2562,14 +2771,14 @@ function GenreTreeView({
|
|
|
2562
2771
|
]
|
|
2563
2772
|
}
|
|
2564
2773
|
),
|
|
2565
|
-
!isLoading && showDisplayOptions && /* @__PURE__ */ (0,
|
|
2774
|
+
!isLoading && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2566
2775
|
"div",
|
|
2567
2776
|
{
|
|
2568
2777
|
className: "flex items-center gap-1",
|
|
2569
2778
|
role: "group",
|
|
2570
2779
|
"aria-label": "Tree display options",
|
|
2571
|
-
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0,
|
|
2572
|
-
|
|
2780
|
+
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2781
|
+
import_ui15.Button,
|
|
2573
2782
|
{
|
|
2574
2783
|
variant: allowWheelRotation ? "default" : "outline",
|
|
2575
2784
|
size: "sm",
|
|
@@ -2579,8 +2788,8 @@ function GenreTreeView({
|
|
|
2579
2788
|
)
|
|
2580
2789
|
}
|
|
2581
2790
|
),
|
|
2582
|
-
!isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0,
|
|
2583
|
-
|
|
2791
|
+
!isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2792
|
+
import_ui15.IconTextButton,
|
|
2584
2793
|
{
|
|
2585
2794
|
icon: import_lucide_react11.Plus,
|
|
2586
2795
|
text: "Add root",
|
|
@@ -2589,42 +2798,34 @@ function GenreTreeView({
|
|
|
2589
2798
|
)
|
|
2590
2799
|
] })
|
|
2591
2800
|
] });
|
|
2592
|
-
return /* @__PURE__ */ (0,
|
|
2801
|
+
return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
|
|
2593
2802
|
"div",
|
|
2594
2803
|
{
|
|
2595
|
-
|
|
2804
|
+
"data-left-panel": isPanelShown && !isCollapsed ? void 0 : "hidden",
|
|
2596
2805
|
className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
|
|
2597
2806
|
children: [
|
|
2598
|
-
/* @__PURE__ */ (0,
|
|
2599
|
-
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
|
|
2807
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2600
2808
|
"div",
|
|
2601
2809
|
{
|
|
2602
2810
|
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"}`,
|
|
2603
2811
|
children: actions
|
|
2604
2812
|
}
|
|
2605
2813
|
),
|
|
2606
|
-
!isLoading && filterArtist && /* @__PURE__ */ (0,
|
|
2814
|
+
!isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2607
2815
|
"div",
|
|
2608
2816
|
{
|
|
2609
2817
|
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"}`,
|
|
2610
2818
|
children: renderArtistFilter()
|
|
2611
2819
|
}
|
|
2612
2820
|
),
|
|
2613
|
-
|
|
2614
|
-
"div",
|
|
2615
|
-
{
|
|
2616
|
-
className: `absolute top-1/2 z-30 -translate-y-1/2 max-md:hidden transition-[opacity,visibility] duration-150 ease-out ${isTreeReady ? "opacity-100" : "pointer-events-none invisible opacity-0"} ${detailExpanded ? "right-[calc(var(--layout-center-right)-var(--layout-edge-tab-w))]" : isCollapsed ? "left-0" : "left-[calc(var(--layout-gutter)+var(--layout-left-w))]"}`,
|
|
2617
|
-
children: detailTab
|
|
2618
|
-
}
|
|
2619
|
-
),
|
|
2620
|
-
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
|
|
2621
|
-
!isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
|
|
2622
|
-
isLoading ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_genre_tree_view4.GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
|
|
2821
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
|
|
2822
|
+
!isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
|
|
2823
|
+
isLoading ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_genre_tree_view4.GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
|
|
2623
2824
|
// The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
|
|
2624
2825
|
// and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
|
|
2625
2826
|
// Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
|
|
2626
2827
|
// The panel's own Zoom in button is hidden: the search bar carries it.
|
|
2627
|
-
/* @__PURE__ */ (0,
|
|
2828
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2628
2829
|
GenrePlaylistTreeWheel,
|
|
2629
2830
|
{
|
|
2630
2831
|
scope,
|
|
@@ -2633,29 +2834,26 @@ function GenreTreeView({
|
|
|
2633
2834
|
handleGenreRenameAction,
|
|
2634
2835
|
additionalActions,
|
|
2635
2836
|
onSelectedNodeChange: handleSelectedNodeChange,
|
|
2636
|
-
hideInfoPanelClose: true,
|
|
2637
|
-
hideInfoPanelTitle: true,
|
|
2638
2837
|
onNodeHover: handleNodeHover,
|
|
2639
|
-
|
|
2838
|
+
...hostPanel,
|
|
2640
2839
|
playback,
|
|
2641
2840
|
selectedNodeId,
|
|
2642
2841
|
readOnly,
|
|
2643
2842
|
allowWheelRotation,
|
|
2644
2843
|
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2645
2844
|
busy,
|
|
2646
|
-
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0,
|
|
2845
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
2647
2846
|
floatingControlsLeading,
|
|
2648
|
-
canSizeByViews && /* @__PURE__ */ (0,
|
|
2847
|
+
canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2649
2848
|
] }) : void 0,
|
|
2650
2849
|
defaultViewport,
|
|
2651
2850
|
viewportRestore,
|
|
2652
|
-
onViewportChange
|
|
2653
|
-
...infoPanelCuration
|
|
2851
|
+
onViewportChange
|
|
2654
2852
|
}
|
|
2655
2853
|
) }) })
|
|
2656
2854
|
) : viewMode === "pop-core" ? (
|
|
2657
2855
|
// Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
|
|
2658
|
-
/* @__PURE__ */ (0,
|
|
2856
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2659
2857
|
GenrePlaylistTreeWheelRadialPopCore,
|
|
2660
2858
|
{
|
|
2661
2859
|
scope,
|
|
@@ -2664,28 +2862,25 @@ function GenreTreeView({
|
|
|
2664
2862
|
handleGenreRenameAction,
|
|
2665
2863
|
additionalActions,
|
|
2666
2864
|
onSelectedNodeChange: handleSelectedNodeChange,
|
|
2667
|
-
hideInfoPanelClose: true,
|
|
2668
|
-
hideInfoPanelTitle: true,
|
|
2669
2865
|
onNodeHover: handleNodeHover,
|
|
2670
|
-
|
|
2866
|
+
...hostPanel,
|
|
2671
2867
|
playback,
|
|
2672
2868
|
selectedNodeId,
|
|
2673
2869
|
readOnly,
|
|
2674
2870
|
allowWheelRotation,
|
|
2675
2871
|
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2676
2872
|
busy,
|
|
2677
|
-
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0,
|
|
2873
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
2678
2874
|
floatingControlsLeading,
|
|
2679
|
-
canSizeByViews && /* @__PURE__ */ (0,
|
|
2875
|
+
canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2680
2876
|
] }) : void 0,
|
|
2681
2877
|
defaultViewport,
|
|
2682
2878
|
viewportRestore,
|
|
2683
2879
|
onViewportChange,
|
|
2684
|
-
...infoPanelCuration,
|
|
2685
2880
|
...focus
|
|
2686
2881
|
}
|
|
2687
2882
|
) }) })
|
|
2688
|
-
) : viewMode === "outline" ? /* @__PURE__ */ (0,
|
|
2883
|
+
) : viewMode === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2689
2884
|
GenrePlaylistTreeWheelRadialPopCore,
|
|
2690
2885
|
{
|
|
2691
2886
|
outline: true,
|
|
@@ -2695,22 +2890,19 @@ function GenreTreeView({
|
|
|
2695
2890
|
handleGenreRenameAction,
|
|
2696
2891
|
additionalActions,
|
|
2697
2892
|
onSelectedNodeChange: handleSelectedNodeChange,
|
|
2698
|
-
hideInfoPanelClose: true,
|
|
2699
|
-
hideInfoPanelTitle: true,
|
|
2700
2893
|
onNodeHover: handleNodeHover,
|
|
2701
|
-
|
|
2894
|
+
...hostPanel,
|
|
2702
2895
|
playback,
|
|
2703
2896
|
selectedNodeId,
|
|
2704
|
-
readOnly
|
|
2705
|
-
...infoPanelCuration
|
|
2897
|
+
readOnly
|
|
2706
2898
|
}
|
|
2707
|
-
) }) : /* @__PURE__ */ (0,
|
|
2708
|
-
([uuid, genrePlaylistTreePerRoot]
|
|
2709
|
-
return /* @__PURE__ */ (0,
|
|
2899
|
+
) }) : /* @__PURE__ */ (0, import_jsx_runtime34.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(
|
|
2900
|
+
([uuid, genrePlaylistTreePerRoot]) => {
|
|
2901
|
+
return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2710
2902
|
"div",
|
|
2711
2903
|
{
|
|
2712
2904
|
className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
|
|
2713
|
-
children: /* @__PURE__ */ (0,
|
|
2905
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2714
2906
|
GenrePlaylistTreePerRoot,
|
|
2715
2907
|
{
|
|
2716
2908
|
scope,
|
|
@@ -2720,26 +2912,45 @@ function GenreTreeView({
|
|
|
2720
2912
|
handleGenreRenameAction,
|
|
2721
2913
|
additionalActions,
|
|
2722
2914
|
onSelectedNodeChange: handleSelectedNodeChange,
|
|
2723
|
-
hideInfoPanelClose: true,
|
|
2724
|
-
hideInfoPanelTitle: true,
|
|
2725
2915
|
onNodeHover: handleNodeHover,
|
|
2726
|
-
|
|
2916
|
+
...hostPanel,
|
|
2727
2917
|
playback,
|
|
2728
2918
|
selectedNodeId: uuid === selectedGenrePlaylist?.root.uuid ? selectedNodeId : null,
|
|
2729
2919
|
readOnly,
|
|
2730
2920
|
wheelZoom: "modifier",
|
|
2731
2921
|
defaultViewport: uuid === selectedGenrePlaylist?.root.uuid ? defaultViewport : void 0,
|
|
2732
2922
|
viewportRestore: uuid === selectedGenrePlaylist?.root.uuid && uuid === restoreRootUuid ? viewportRestore : void 0,
|
|
2733
|
-
onViewportChange: uuid === selectedGenrePlaylist?.root.uuid ? onViewportChange : void 0
|
|
2734
|
-
...infoPanelCuration,
|
|
2735
|
-
overlayPanel: rootIndex === 0 ? activeOverlayPanel : void 0
|
|
2923
|
+
onViewportChange: uuid === selectedGenrePlaylist?.root.uuid ? onViewportChange : void 0
|
|
2736
2924
|
}
|
|
2737
2925
|
) })
|
|
2738
2926
|
},
|
|
2739
2927
|
uuid
|
|
2740
2928
|
);
|
|
2741
2929
|
}
|
|
2742
|
-
) })
|
|
2930
|
+
) }),
|
|
2931
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2932
|
+
import_ui15.DetailPanel,
|
|
2933
|
+
{
|
|
2934
|
+
ref: panelRef,
|
|
2935
|
+
side: "left",
|
|
2936
|
+
title: activePanel?.title ?? "",
|
|
2937
|
+
hideTitle: activePanel?.compact,
|
|
2938
|
+
hideClose: activePanel?.compact,
|
|
2939
|
+
headerAccent: activePanel?.headerAccent,
|
|
2940
|
+
headerActions: expandButton,
|
|
2941
|
+
onClose: activePanel?.onClose ?? noop,
|
|
2942
|
+
collapsed: isCollapsed || !isTreeReady || !activePanel,
|
|
2943
|
+
expanded: activePanel?.expanded,
|
|
2944
|
+
onExpandedChange: activePanel?.onExpandedChange,
|
|
2945
|
+
aside: activePanel?.aside,
|
|
2946
|
+
edgeTab: detailTab,
|
|
2947
|
+
headerClassName: activePanel?.compact ? "pt-[calc(var(--layout-search-h)+var(--layout-panel-inset)+0.5rem)]" : void 0,
|
|
2948
|
+
children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
|
|
2949
|
+
activePanel.content,
|
|
2950
|
+
activePanel.aside
|
|
2951
|
+
] }) : activePanel?.content
|
|
2952
|
+
}
|
|
2953
|
+
)
|
|
2743
2954
|
] }) })
|
|
2744
2955
|
]
|
|
2745
2956
|
}
|
|
@@ -2747,12 +2958,12 @@ function GenreTreeView({
|
|
|
2747
2958
|
}
|
|
2748
2959
|
|
|
2749
2960
|
// src/genre-tree/GenreDetailPanel.tsx
|
|
2750
|
-
var
|
|
2961
|
+
var import_react17 = require("react");
|
|
2751
2962
|
var import_genre_tree_view5 = require("@behindthemusictree/genre-tree-view");
|
|
2752
|
-
var
|
|
2753
|
-
var
|
|
2754
|
-
var
|
|
2755
|
-
var
|
|
2963
|
+
var import_ui16 = require("@behindthemusictree/ui");
|
|
2964
|
+
var import_lucide_react13 = require("lucide-react");
|
|
2965
|
+
var import_genre_tree20 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2966
|
+
var import_jsx_runtime35 = require("react/jsx-runtime");
|
|
2756
2967
|
var RELATIVE_ITEM_COUNT = 1;
|
|
2757
2968
|
function toNode(genre, parentId) {
|
|
2758
2969
|
return {
|
|
@@ -2776,11 +2987,12 @@ function GenreDetailPanel({
|
|
|
2776
2987
|
collapsed,
|
|
2777
2988
|
expanded,
|
|
2778
2989
|
onExpandedChange,
|
|
2990
|
+
edgeTab,
|
|
2779
2991
|
extras
|
|
2780
2992
|
}) {
|
|
2781
|
-
const { data: detail, isPending, isError } = (0,
|
|
2993
|
+
const { data: detail, isPending, isError } = (0, import_genre_tree20.useFetchGenreDetail)(genreUuid, scope, getBackendBaseUrl);
|
|
2782
2994
|
const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: [] });
|
|
2783
|
-
const view = (0,
|
|
2995
|
+
const view = (0, import_react17.useMemo)(() => {
|
|
2784
2996
|
if (!detail) return null;
|
|
2785
2997
|
const rootColor = (0, import_genre_tree_view5.getGenreTreeColor)(detail.root.wikidataId);
|
|
2786
2998
|
const relative = (genre, parentId) => ({
|
|
@@ -2788,7 +3000,7 @@ function GenreDetailPanel({
|
|
|
2788
3000
|
fill: (0, import_genre_tree_view5.tintSurface)(rootColor),
|
|
2789
3001
|
textColor: import_genre_tree_view5.TEXT_COLOR
|
|
2790
3002
|
});
|
|
2791
|
-
const
|
|
3003
|
+
const ancestors2 = [...detail.ascendants].sort((a, b) => b.degree - a.degree);
|
|
2792
3004
|
return {
|
|
2793
3005
|
node: {
|
|
2794
3006
|
id: detail.uuid,
|
|
@@ -2800,57 +3012,72 @@ function GenreDetailPanel({
|
|
|
2800
3012
|
fill: rootColor,
|
|
2801
3013
|
parentNode: detail.parent ? relative(detail.parent, null) : null,
|
|
2802
3014
|
childNodes: detail.children.map((child) => relative(child, detail.uuid)),
|
|
2803
|
-
ancestorNodes:
|
|
2804
|
-
({ ascendant }, index) => relative(ascendant, index === 0 ? null :
|
|
3015
|
+
ancestorNodes: ancestors2.map(
|
|
3016
|
+
({ ascendant }, index) => relative(ascendant, index === 0 ? null : ancestors2[index - 1].ascendant.uuid)
|
|
2805
3017
|
)
|
|
2806
3018
|
};
|
|
2807
3019
|
}, [detail]);
|
|
2808
3020
|
if (isError) {
|
|
2809
|
-
return /* @__PURE__ */ (0,
|
|
3021
|
+
return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
|
|
2810
3022
|
}
|
|
2811
3023
|
if (isPending || !detail || !view) {
|
|
2812
|
-
return /* @__PURE__ */ (0,
|
|
2813
|
-
/* @__PURE__ */ (0,
|
|
2814
|
-
/* @__PURE__ */ (0,
|
|
2815
|
-
/* @__PURE__ */ (0,
|
|
3024
|
+
return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
|
|
3025
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui16.Skeleton, { className: "h-6 w-2/3" }),
|
|
3026
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui16.Skeleton, { className: "h-4 w-full" }),
|
|
3027
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui16.Skeleton, { className: "h-4 w-3/4" })
|
|
2816
3028
|
] });
|
|
2817
3029
|
}
|
|
2818
|
-
const
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
{
|
|
2822
|
-
toolbar: sections.toolbar,
|
|
2823
|
-
family: sections.family,
|
|
2824
|
-
genreUuid: detail.uuid,
|
|
2825
|
-
genrePlaylist: { ...detail.criteriaPlaylist, tracksCount: detail.tracksCount },
|
|
2826
|
-
wikidataId: detail.wikidataId,
|
|
2827
|
-
scope,
|
|
2828
|
-
getBackendBaseUrl,
|
|
2829
|
-
playback,
|
|
2830
|
-
band: { node, onZoomIn, shareUrl, shareTarget: shareUrl ? shareTarget : void 0 },
|
|
2831
|
-
extras
|
|
2832
|
-
}
|
|
2833
|
-
),
|
|
2834
|
-
aside: null
|
|
2835
|
-
});
|
|
2836
|
-
return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2837
|
-
import_genre_tree_view5.InfoPanel,
|
|
3030
|
+
const selectGenre = (nodeId) => onSelectGenre(nodeId);
|
|
3031
|
+
const family = /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
3032
|
+
import_genre_tree_view5.GenreNodeFamily,
|
|
2838
3033
|
{
|
|
2839
3034
|
node: view.node,
|
|
2840
3035
|
fill: view.fill,
|
|
2841
3036
|
textColor: import_genre_tree_view5.ACCENT_TEXT_COLOR,
|
|
2842
3037
|
parentNode: view.parentNode,
|
|
2843
3038
|
childNodes: view.childNodes,
|
|
2844
|
-
|
|
3039
|
+
onSelectNode: selectGenre
|
|
3040
|
+
}
|
|
3041
|
+
);
|
|
3042
|
+
const ancestors = /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_genre_tree_view5.GenreNodeAncestors, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
|
|
3043
|
+
return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
|
|
3044
|
+
import_ui16.DetailPanel,
|
|
3045
|
+
{
|
|
2845
3046
|
side: "left",
|
|
3047
|
+
title: view.node.name,
|
|
3048
|
+
headerAccent: { background: view.fill, color: import_genre_tree_view5.ACCENT_TEXT_COLOR },
|
|
3049
|
+
headerActions: onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
3050
|
+
import_ui16.IconButton,
|
|
3051
|
+
{
|
|
3052
|
+
"aria-label": expanded ? "Shrink panel" : "Extend panel",
|
|
3053
|
+
style: { color: "inherit" },
|
|
3054
|
+
onClick: () => onExpandedChange(!expanded),
|
|
3055
|
+
children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react13.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react13.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
3056
|
+
}
|
|
3057
|
+
) : void 0,
|
|
2846
3058
|
onClose,
|
|
2847
|
-
|
|
2848
|
-
itemCountLabel: ITEM_COUNT_LABEL,
|
|
2849
|
-
renderInfoPanelBody: renderBody,
|
|
3059
|
+
collapsed,
|
|
2850
3060
|
expanded,
|
|
2851
3061
|
onExpandedChange,
|
|
2852
|
-
|
|
2853
|
-
|
|
3062
|
+
edgeTab,
|
|
3063
|
+
aside: ancestors,
|
|
3064
|
+
children: [
|
|
3065
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
3066
|
+
GenreInfoPanelBody,
|
|
3067
|
+
{
|
|
3068
|
+
family,
|
|
3069
|
+
genreUuid: detail.uuid,
|
|
3070
|
+
genrePlaylist: { ...detail.criteriaPlaylist, tracksCount: detail.tracksCount },
|
|
3071
|
+
wikidataId: detail.wikidataId,
|
|
3072
|
+
scope,
|
|
3073
|
+
getBackendBaseUrl,
|
|
3074
|
+
playback,
|
|
3075
|
+
band: { node: view.node, onZoomIn, shareUrl, shareTarget: shareUrl ? shareTarget : void 0 },
|
|
3076
|
+
extras
|
|
3077
|
+
}
|
|
3078
|
+
),
|
|
3079
|
+
!expanded && ancestors
|
|
3080
|
+
]
|
|
2854
3081
|
}
|
|
2855
3082
|
);
|
|
2856
3083
|
}
|
|
@@ -2859,13 +3086,13 @@ function GenreDetailPanel({
|
|
|
2859
3086
|
var import_genre_tree_view6 = require("@behindthemusictree/genre-tree-view");
|
|
2860
3087
|
|
|
2861
3088
|
// src/genre-tree/track-list-sidebar/TrackListSidebar.tsx
|
|
2862
|
-
var
|
|
2863
|
-
var
|
|
2864
|
-
var
|
|
2865
|
-
var
|
|
2866
|
-
var
|
|
2867
|
-
var
|
|
2868
|
-
var useBrowserLayoutEffect = typeof window === "undefined" ?
|
|
3089
|
+
var import_react18 = require("react");
|
|
3090
|
+
var import_lucide_react14 = require("lucide-react");
|
|
3091
|
+
var import_genre_tree21 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3092
|
+
var import_genre_tree22 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3093
|
+
var import_track_noun3 = require("@behindthemusictree/app-kit/track-noun");
|
|
3094
|
+
var import_jsx_runtime36 = require("react/jsx-runtime");
|
|
3095
|
+
var useBrowserLayoutEffect = typeof window === "undefined" ? import_react18.useEffect : import_react18.useLayoutEffect;
|
|
2869
3096
|
function TrackListSidebar({
|
|
2870
3097
|
className: className2 = "",
|
|
2871
3098
|
renderDuration,
|
|
@@ -2874,20 +3101,21 @@ function TrackListSidebar({
|
|
|
2874
3101
|
isCollapsed = false,
|
|
2875
3102
|
onExpand,
|
|
2876
3103
|
onMinimize,
|
|
2877
|
-
renderArtistLink
|
|
3104
|
+
renderArtistLink,
|
|
3105
|
+
renderSongLink
|
|
2878
3106
|
}) {
|
|
2879
|
-
const { trackList, loadMore, autoAdvanceScrollRequest } = (0,
|
|
2880
|
-
const trackNoun = (0,
|
|
2881
|
-
const trackListRef = (0,
|
|
2882
|
-
const previousOriginRef = (0,
|
|
2883
|
-
const handledAutoAdvanceRequestIdRef = (0,
|
|
2884
|
-
const shouldFollowPlaybackRef = (0,
|
|
2885
|
-
const programmaticScrollTopRef = (0,
|
|
2886
|
-
const userScrollIntentRef = (0,
|
|
2887
|
-
const userScrollIntentTimerRef = (0,
|
|
2888
|
-
const loadMoreSentinelRef = (0,
|
|
3107
|
+
const { trackList, loadMore, autoAdvanceScrollRequest } = (0, import_genre_tree21.useTrackList)();
|
|
3108
|
+
const trackNoun = (0, import_track_noun3.useTrackNoun)();
|
|
3109
|
+
const trackListRef = (0, import_react18.useRef)(null);
|
|
3110
|
+
const previousOriginRef = (0, import_react18.useRef)(trackList?.origin ?? null);
|
|
3111
|
+
const handledAutoAdvanceRequestIdRef = (0, import_react18.useRef)(null);
|
|
3112
|
+
const shouldFollowPlaybackRef = (0, import_react18.useRef)(true);
|
|
3113
|
+
const programmaticScrollTopRef = (0, import_react18.useRef)(null);
|
|
3114
|
+
const userScrollIntentRef = (0, import_react18.useRef)(false);
|
|
3115
|
+
const userScrollIntentTimerRef = (0, import_react18.useRef)(null);
|
|
3116
|
+
const loadMoreSentinelRef = (0, import_react18.useRef)(null);
|
|
2889
3117
|
const hasMore = !!trackList && trackList.nextPage !== null;
|
|
2890
|
-
(0,
|
|
3118
|
+
(0, import_react18.useEffect)(() => {
|
|
2891
3119
|
const sentinel = loadMoreSentinelRef.current;
|
|
2892
3120
|
if (!hasMore || !sentinel) return;
|
|
2893
3121
|
const observer = new IntersectionObserver((entries) => {
|
|
@@ -2907,11 +3135,11 @@ function TrackListSidebar({
|
|
|
2907
3135
|
programmaticScrollTopRef.current = null;
|
|
2908
3136
|
if (trackListRef.current) trackListRef.current.scrollTop = 0;
|
|
2909
3137
|
}, [trackList?.origin]);
|
|
2910
|
-
(0,
|
|
3138
|
+
(0, import_react18.useEffect)(() => {
|
|
2911
3139
|
const list = trackListRef.current;
|
|
2912
3140
|
if (!isCollapsed && list?.scrollTop === 0) shouldFollowPlaybackRef.current = true;
|
|
2913
3141
|
}, [isCollapsed]);
|
|
2914
|
-
(0,
|
|
3142
|
+
(0, import_react18.useEffect)(
|
|
2915
3143
|
() => () => {
|
|
2916
3144
|
if (userScrollIntentTimerRef.current !== null) clearTimeout(userScrollIntentTimerRef.current);
|
|
2917
3145
|
},
|
|
@@ -2965,25 +3193,25 @@ function TrackListSidebar({
|
|
|
2965
3193
|
}
|
|
2966
3194
|
};
|
|
2967
3195
|
const positionClasses = layout === "inline" ? "relative w-full flex h-full flex-col" : "fixed right-0 w-[50rem] flex flex-col";
|
|
2968
|
-
return trackList ? /* @__PURE__ */ (0,
|
|
3196
|
+
return trackList ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
|
|
2969
3197
|
"div",
|
|
2970
3198
|
{
|
|
2971
3199
|
"data-theme": "dark",
|
|
2972
3200
|
className: `track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${className2}`,
|
|
2973
3201
|
style: layout === "fixed" ? { top: "63.5px", maxHeight: "calc(100vh - 63.5px - 79px)" } : void 0,
|
|
2974
3202
|
children: [
|
|
2975
|
-
/* @__PURE__ */ (0,
|
|
2976
|
-
/* @__PURE__ */ (0,
|
|
2977
|
-
/* @__PURE__ */ (0,
|
|
2978
|
-
/* @__PURE__ */ (0,
|
|
2979
|
-
/* @__PURE__ */ (0,
|
|
3203
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
|
|
3204
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
|
|
3205
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "origin flex text-xl", children: [
|
|
3206
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
|
|
3207
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "name-container flex flex-col justify-center items-center text-[var(--color-text)] font-bold pr-2 max-w-[27.5rem]", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
|
|
2980
3208
|
] }),
|
|
2981
|
-
/* @__PURE__ */ (0,
|
|
2982
|
-
trackList.origin.type ===
|
|
3209
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
|
|
3210
|
+
trackList.origin.type === import_genre_tree22.TrackListOriginType.GENRE_PLAYLIST ? "Genre \u2022 " : trackList.origin.type === import_genre_tree22.TrackListOriginType.ARTIST ? "Artist \u2022 " : `${trackNoun.singular} playlist \u2022 `,
|
|
2983
3211
|
`${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`
|
|
2984
3212
|
] })
|
|
2985
3213
|
] }),
|
|
2986
|
-
(isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0,
|
|
3214
|
+
(isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
|
|
2987
3215
|
"button",
|
|
2988
3216
|
{
|
|
2989
3217
|
type: "button",
|
|
@@ -2992,11 +3220,11 @@ function TrackListSidebar({
|
|
|
2992
3220
|
"aria-expanded": !isCollapsed,
|
|
2993
3221
|
className: `ml-2 flex h-8 w-8 shrink-0 items-center justify-center rounded-md hover:bg-[var(--color-surface-muted)] ${isCollapsed ? "text-[var(--color-text-muted)]" : "text-white"}`,
|
|
2994
3222
|
onClick: isCollapsed ? onExpand : onMinimize,
|
|
2995
|
-
children: /* @__PURE__ */ (0,
|
|
3223
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_lucide_react14.ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
|
|
2996
3224
|
}
|
|
2997
3225
|
)
|
|
2998
3226
|
] }),
|
|
2999
|
-
!isCollapsed && /* @__PURE__ */ (0,
|
|
3227
|
+
!isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
|
|
3000
3228
|
"ul",
|
|
3001
3229
|
{
|
|
3002
3230
|
ref: trackListRef,
|
|
@@ -3006,17 +3234,18 @@ function TrackListSidebar({
|
|
|
3006
3234
|
onPointerDown: markUserScrollIntent,
|
|
3007
3235
|
className: layout === "inline" ? "track-list flex-1 min-h-0 overflow-auto list-none p-0 m-0" : "track-list flex-1 min-h-0 overflow-auto list-none p-0 m-0",
|
|
3008
3236
|
children: [
|
|
3009
|
-
trackList.tracks.map((track, index) => /* @__PURE__ */ (0,
|
|
3237
|
+
trackList.tracks.map((track, index) => /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
|
|
3010
3238
|
TrackItem,
|
|
3011
3239
|
{
|
|
3012
3240
|
track,
|
|
3013
3241
|
position: index + 1,
|
|
3014
3242
|
renderDuration,
|
|
3015
3243
|
renderActions,
|
|
3016
|
-
renderArtistLink
|
|
3244
|
+
renderArtistLink,
|
|
3245
|
+
renderSongLink
|
|
3017
3246
|
}
|
|
3018
3247
|
) }, track.uuid)),
|
|
3019
|
-
hasMore && /* @__PURE__ */ (0,
|
|
3248
|
+
hasMore && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
|
|
3020
3249
|
]
|
|
3021
3250
|
}
|
|
3022
3251
|
)
|
|
@@ -3026,16 +3255,17 @@ function TrackListSidebar({
|
|
|
3026
3255
|
}
|
|
3027
3256
|
|
|
3028
3257
|
// src/genre-tree/PlayerPanel.tsx
|
|
3029
|
-
var
|
|
3030
|
-
var
|
|
3031
|
-
var
|
|
3032
|
-
var
|
|
3258
|
+
var import_react19 = require("react");
|
|
3259
|
+
var import_tailwind_merge = require("tailwind-merge");
|
|
3260
|
+
var import_player13 = require("@behindthemusictree/app-kit/player");
|
|
3261
|
+
var import_genre_tree24 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3262
|
+
var import_ui17 = require("@behindthemusictree/ui");
|
|
3033
3263
|
|
|
3034
3264
|
// src/genre-tree/PlayerControls.tsx
|
|
3035
|
-
var
|
|
3036
|
-
var
|
|
3037
|
-
var
|
|
3038
|
-
var
|
|
3265
|
+
var import_lucide_react15 = require("lucide-react");
|
|
3266
|
+
var import_genre_tree23 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3267
|
+
var import_player12 = require("@behindthemusictree/app-kit/player");
|
|
3268
|
+
var import_jsx_runtime37 = require("react/jsx-runtime");
|
|
3039
3269
|
function PlayerControls() {
|
|
3040
3270
|
const {
|
|
3041
3271
|
duration,
|
|
@@ -3046,23 +3276,23 @@ function PlayerControls() {
|
|
|
3046
3276
|
seekTo,
|
|
3047
3277
|
setVolume,
|
|
3048
3278
|
volume
|
|
3049
|
-
} = (0,
|
|
3050
|
-
const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } = (0,
|
|
3051
|
-
const currentTime = (0,
|
|
3052
|
-
const isBusy = isLoading || playState ===
|
|
3279
|
+
} = (0, import_player12.usePlayer)();
|
|
3280
|
+
const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } = (0, import_genre_tree23.useTrackList)();
|
|
3281
|
+
const currentTime = (0, import_player12.useCurrentTime)();
|
|
3282
|
+
const isBusy = isLoading || playState === import_player12.PlayStates.LOADING;
|
|
3053
3283
|
const isReady = !!playerTrackObject?.isReady && !isBusy && !playerTrackObject.loadError;
|
|
3054
3284
|
const safeDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;
|
|
3055
3285
|
const safeCurrentTime = Number.isFinite(currentTime) ? Math.max(0, Math.min(currentTime, safeDuration)) : 0;
|
|
3056
|
-
const isPlaying = playState ===
|
|
3057
|
-
return /* @__PURE__ */ (0,
|
|
3286
|
+
const isPlaying = playState === import_player12.PlayStates.PLAYING;
|
|
3287
|
+
return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
|
|
3058
3288
|
"section",
|
|
3059
3289
|
{
|
|
3060
3290
|
"aria-label": "Player controls",
|
|
3061
3291
|
className: "player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200",
|
|
3062
3292
|
children: [
|
|
3063
|
-
/* @__PURE__ */ (0,
|
|
3064
|
-
/* @__PURE__ */ (0,
|
|
3065
|
-
/* @__PURE__ */ (0,
|
|
3293
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
|
|
3294
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { "aria-label": "Elapsed time", children: (0, import_genre_tree23.formatTime)(safeCurrentTime) }),
|
|
3295
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
|
|
3066
3296
|
"input",
|
|
3067
3297
|
{
|
|
3068
3298
|
"aria-label": "Seek",
|
|
@@ -3076,11 +3306,11 @@ function PlayerControls() {
|
|
|
3076
3306
|
onChange: (event) => seekTo(Number(event.currentTarget.value))
|
|
3077
3307
|
}
|
|
3078
3308
|
),
|
|
3079
|
-
/* @__PURE__ */ (0,
|
|
3309
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { "aria-label": "Duration", children: (0, import_genre_tree23.formatTime)(safeDuration) })
|
|
3080
3310
|
] }),
|
|
3081
|
-
/* @__PURE__ */ (0,
|
|
3082
|
-
/* @__PURE__ */ (0,
|
|
3083
|
-
/* @__PURE__ */ (0,
|
|
3311
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
|
|
3312
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
|
|
3313
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
|
|
3084
3314
|
"button",
|
|
3085
3315
|
{
|
|
3086
3316
|
type: "button",
|
|
@@ -3089,10 +3319,10 @@ function PlayerControls() {
|
|
|
3089
3319
|
disabled: !isReady || !hasPreviousTrack,
|
|
3090
3320
|
onClick: playPreviousTrack,
|
|
3091
3321
|
className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-zinc-300 hover:bg-zinc-800 hover:text-white disabled:cursor-not-allowed disabled:opacity-40",
|
|
3092
|
-
children: /* @__PURE__ */ (0,
|
|
3322
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react15.SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
3093
3323
|
}
|
|
3094
3324
|
),
|
|
3095
|
-
/* @__PURE__ */ (0,
|
|
3325
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
|
|
3096
3326
|
"button",
|
|
3097
3327
|
{
|
|
3098
3328
|
type: "button",
|
|
@@ -3101,10 +3331,10 @@ function PlayerControls() {
|
|
|
3101
3331
|
disabled: !isReady,
|
|
3102
3332
|
onClick: handlePlayPauseAction,
|
|
3103
3333
|
className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-white text-zinc-950 hover:bg-zinc-200 disabled:cursor-not-allowed disabled:opacity-40",
|
|
3104
|
-
children: isPlaying ? /* @__PURE__ */ (0,
|
|
3334
|
+
children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react15.Pause, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react15.Play, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
|
|
3105
3335
|
}
|
|
3106
3336
|
),
|
|
3107
|
-
/* @__PURE__ */ (0,
|
|
3337
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
|
|
3108
3338
|
"button",
|
|
3109
3339
|
{
|
|
3110
3340
|
type: "button",
|
|
@@ -3113,14 +3343,14 @@ function PlayerControls() {
|
|
|
3113
3343
|
disabled: !isReady || !hasNextTrack,
|
|
3114
3344
|
onClick: playNextTrack,
|
|
3115
3345
|
className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-zinc-300 hover:bg-zinc-800 hover:text-white disabled:cursor-not-allowed disabled:opacity-40",
|
|
3116
|
-
children: /* @__PURE__ */ (0,
|
|
3346
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react15.SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
3117
3347
|
}
|
|
3118
3348
|
)
|
|
3119
3349
|
] }),
|
|
3120
|
-
/* @__PURE__ */ (0,
|
|
3121
|
-
volume === 0 ? /* @__PURE__ */ (0,
|
|
3122
|
-
/* @__PURE__ */ (0,
|
|
3123
|
-
/* @__PURE__ */ (0,
|
|
3350
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("label", { className: "col-start-3 flex w-[4.2rem] shrink-0 items-center gap-[0.35rem] justify-self-end text-zinc-400 sm:w-[4.9rem]", children: [
|
|
3351
|
+
volume === 0 ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react15.VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react15.Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
|
|
3352
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "sr-only", children: "Volume" }),
|
|
3353
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
|
|
3124
3354
|
"input",
|
|
3125
3355
|
{
|
|
3126
3356
|
"aria-label": "Volume",
|
|
@@ -3142,7 +3372,7 @@ function PlayerControls() {
|
|
|
3142
3372
|
}
|
|
3143
3373
|
|
|
3144
3374
|
// src/genre-tree/PlayerPanel.tsx
|
|
3145
|
-
var
|
|
3375
|
+
var import_jsx_runtime38 = require("react/jsx-runtime");
|
|
3146
3376
|
var PANEL_ID = "player-panel";
|
|
3147
3377
|
var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
|
|
3148
3378
|
function PlayerPanel({
|
|
@@ -3151,16 +3381,17 @@ function PlayerPanel({
|
|
|
3151
3381
|
expanded,
|
|
3152
3382
|
onExpandedChange,
|
|
3153
3383
|
extendedWidth = DEFAULT_EXTENDED_WIDTH,
|
|
3154
|
-
renderArtistLink
|
|
3384
|
+
renderArtistLink,
|
|
3385
|
+
renderSongLink
|
|
3155
3386
|
}) {
|
|
3156
|
-
const { playerTrackObject } = (0,
|
|
3157
|
-
const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0,
|
|
3158
|
-
const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = (0,
|
|
3387
|
+
const { playerTrackObject } = (0, import_player13.usePlayer)();
|
|
3388
|
+
const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0, import_genre_tree24.useTrackListSidebarVisibility)();
|
|
3389
|
+
const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = (0, import_react19.useState)("");
|
|
3159
3390
|
const loadedTrackTitle = playerTrackObject?.track.title;
|
|
3160
|
-
(0,
|
|
3391
|
+
(0, import_react19.useEffect)(() => {
|
|
3161
3392
|
if (loadedTrackTitle) setLastLoadedTrackTitle(loadedTrackTitle);
|
|
3162
3393
|
}, [loadedTrackTitle]);
|
|
3163
|
-
const [uncontrolledExtended, setUncontrolledExtended] = (0,
|
|
3394
|
+
const [uncontrolledExtended, setUncontrolledExtended] = (0, import_react19.useState)(false);
|
|
3164
3395
|
const isExtendRequested = expanded ?? uncontrolledExtended;
|
|
3165
3396
|
const setIsExtendRequested = (next) => {
|
|
3166
3397
|
setUncontrolledExtended(next);
|
|
@@ -3181,11 +3412,11 @@ function PlayerPanel({
|
|
|
3181
3412
|
onClick: () => setIsExtendRequested(true),
|
|
3182
3413
|
expanded: false
|
|
3183
3414
|
} : null;
|
|
3184
|
-
return /* @__PURE__ */ (0,
|
|
3415
|
+
return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
|
|
3185
3416
|
"div",
|
|
3186
3417
|
{
|
|
3187
3418
|
id: PANEL_ID,
|
|
3188
|
-
className: (0,
|
|
3419
|
+
className: (0, import_tailwind_merge.twMerge)(
|
|
3189
3420
|
"fixed right-[var(--layout-gutter)] top-[var(--layout-band-top)] z-40 max-h-[calc(100vh-var(--layout-band-top)-3.5rem)]",
|
|
3190
3421
|
!isExtended && "w-[var(--layout-right-w)]",
|
|
3191
3422
|
isTrackListSidebarVisible && "h-[calc(100vh-var(--layout-band-top)-3.5rem)]",
|
|
@@ -3194,8 +3425,8 @@ function PlayerPanel({
|
|
|
3194
3425
|
),
|
|
3195
3426
|
style: isExtended ? { width: extendedWidth } : void 0,
|
|
3196
3427
|
children: [
|
|
3197
|
-
/* @__PURE__ */ (0,
|
|
3198
|
-
PanelEdgeTab,
|
|
3428
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
|
|
3429
|
+
import_ui17.PanelEdgeTab,
|
|
3199
3430
|
{
|
|
3200
3431
|
side: "right",
|
|
3201
3432
|
edge: tabEdge,
|
|
@@ -3204,34 +3435,35 @@ function PlayerPanel({
|
|
|
3204
3435
|
className: "absolute right-full top-1/2 -translate-y-1/2"
|
|
3205
3436
|
}
|
|
3206
3437
|
),
|
|
3207
|
-
/* @__PURE__ */ (0,
|
|
3438
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
|
|
3208
3439
|
"div",
|
|
3209
3440
|
{
|
|
3210
|
-
className: (0,
|
|
3441
|
+
className: (0, import_tailwind_merge.twMerge)(
|
|
3211
3442
|
"flex h-full max-h-[inherit] overflow-hidden rounded-[var(--radius-panel,1rem)] bg-zinc-950 text-zinc-100 shadow-[0_1px_2px_rgba(0,0,0,0.5),0_2px_6px_2px_rgba(0,0,0,0.3)]",
|
|
3212
3443
|
isExtended ? "flex-row" : "flex-col"
|
|
3213
3444
|
),
|
|
3214
3445
|
children: [
|
|
3215
|
-
/* @__PURE__ */ (0,
|
|
3216
|
-
/* @__PURE__ */ (0,
|
|
3217
|
-
/* @__PURE__ */ (0,
|
|
3218
|
-
/* @__PURE__ */ (0,
|
|
3219
|
-
playerTrackObject?.loadError && /* @__PURE__ */ (0,
|
|
3446
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
|
|
3447
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
|
|
3448
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "relative w-full shrink-0", children: [
|
|
3449
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_player13.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
|
|
3450
|
+
playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: playerTrackObject.loadError })
|
|
3220
3451
|
] }),
|
|
3221
|
-
/* @__PURE__ */ (0,
|
|
3452
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(PlayerControls, {})
|
|
3222
3453
|
] }),
|
|
3223
|
-
/* @__PURE__ */ (0,
|
|
3454
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
|
|
3224
3455
|
TrackListSidebar,
|
|
3225
3456
|
{
|
|
3226
3457
|
layout: "inline",
|
|
3227
3458
|
isCollapsed: !isTrackListSidebarVisible,
|
|
3228
3459
|
onExpand: toggleTrackListSidebar,
|
|
3229
3460
|
onMinimize: hideSongList,
|
|
3230
|
-
className: (0,
|
|
3461
|
+
className: (0, import_tailwind_merge.twMerge)(
|
|
3231
3462
|
"min-h-0 min-w-0 flex-1",
|
|
3232
3463
|
isExtended && "border-l border-zinc-800"
|
|
3233
3464
|
),
|
|
3234
|
-
renderArtistLink
|
|
3465
|
+
renderArtistLink,
|
|
3466
|
+
renderSongLink
|
|
3235
3467
|
}
|
|
3236
3468
|
)
|
|
3237
3469
|
]
|
|
@@ -3245,33 +3477,33 @@ function PlayerPanel({
|
|
|
3245
3477
|
// src/genre-tree/Rating.tsx
|
|
3246
3478
|
var import_uuid = require("uuid");
|
|
3247
3479
|
var import_ai = require("react-icons/ai");
|
|
3248
|
-
var
|
|
3249
|
-
var
|
|
3480
|
+
var import_genre_tree25 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3481
|
+
var import_jsx_runtime39 = require("react/jsx-runtime");
|
|
3250
3482
|
function Rating({ rating, handleChange }) {
|
|
3251
3483
|
const instanceId = (0, import_uuid.v4)();
|
|
3252
|
-
return /* @__PURE__ */ (0,
|
|
3253
|
-
/* @__PURE__ */ (0,
|
|
3484
|
+
return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
|
|
3485
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
3254
3486
|
"input",
|
|
3255
3487
|
{
|
|
3256
3488
|
id: `star-${instanceId}-${i}`,
|
|
3257
3489
|
className: "w-6 h-6 opacity-0",
|
|
3258
3490
|
type: "radio",
|
|
3259
3491
|
name: "rating",
|
|
3260
|
-
value: i === 0 ?
|
|
3492
|
+
value: i === 0 ? import_genre_tree25.FORM_RATING_NULL_VALUE : i,
|
|
3261
3493
|
checked: rating === (i === 0 ? null : i),
|
|
3262
3494
|
onChange: handleChange
|
|
3263
3495
|
}
|
|
3264
3496
|
),
|
|
3265
|
-
/* @__PURE__ */ (0,
|
|
3266
|
-
/* @__PURE__ */ (0,
|
|
3497
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex", children: [
|
|
3498
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
3267
3499
|
import_ai.AiOutlineStar,
|
|
3268
3500
|
{
|
|
3269
|
-
className: `w-7 h-7 absolute top-0 left-0 ${rating ===
|
|
3501
|
+
className: `w-7 h-7 absolute top-0 left-0 ${rating === import_genre_tree25.FORM_RATING_NULL_VALUE ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
|
|
3270
3502
|
}
|
|
3271
3503
|
),
|
|
3272
|
-
/* @__PURE__ */ (0,
|
|
3504
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "flex-grow w-8" }),
|
|
3273
3505
|
" "
|
|
3274
|
-
] }) : /* @__PURE__ */ (0,
|
|
3506
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
3275
3507
|
import_ai.AiFillStar,
|
|
3276
3508
|
{
|
|
3277
3509
|
className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
|
|
@@ -3291,13 +3523,11 @@ function Rating({ rating, handleChange }) {
|
|
|
3291
3523
|
GenreTreeView,
|
|
3292
3524
|
GenreTreeViewSkeleton,
|
|
3293
3525
|
GenreTreeWheelSkeleton,
|
|
3294
|
-
PanelEdgeTab,
|
|
3295
3526
|
PlayerPanel,
|
|
3296
3527
|
Rating,
|
|
3297
3528
|
TrackItem,
|
|
3298
3529
|
TrackListSidebar,
|
|
3299
3530
|
TrackPositionPlayPause,
|
|
3300
|
-
useGenrePlaylistPlayback
|
|
3301
|
-
useLeftDetailPanelLayout
|
|
3531
|
+
useGenrePlaylistPlayback
|
|
3302
3532
|
});
|
|
3303
3533
|
//# sourceMappingURL=index.cjs.map
|