@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
package/dist/index.cjs
CHANGED
|
@@ -36,7 +36,6 @@ __export(src_exports, {
|
|
|
36
36
|
GenreTreeWheelSkeleton: () => import_genre_tree_view6.GenreTreeWheelSkeleton,
|
|
37
37
|
InternalErrorPopup: () => InternalErrorPopup,
|
|
38
38
|
NetworkErrorPopup: () => NetworkErrorPopup,
|
|
39
|
-
PanelEdgeTab: () => PanelEdgeTab,
|
|
40
39
|
PlayerPanel: () => PlayerPanel,
|
|
41
40
|
PopupButtons: () => PopupButtons,
|
|
42
41
|
PopupTitle: () => PopupTitle,
|
|
@@ -47,8 +46,7 @@ __export(src_exports, {
|
|
|
47
46
|
TrackListSidebar: () => TrackListSidebar,
|
|
48
47
|
TrackPositionPlayPause: () => TrackPositionPlayPause,
|
|
49
48
|
TrackUploadPopup: () => TrackUploadPopup,
|
|
50
|
-
useGenrePlaylistPlayback: () => useGenrePlaylistPlayback
|
|
51
|
-
useLeftDetailPanelLayout: () => useLeftDetailPanelLayout
|
|
49
|
+
useGenrePlaylistPlayback: () => useGenrePlaylistPlayback
|
|
52
50
|
});
|
|
53
51
|
module.exports = __toCommonJS(src_exports);
|
|
54
52
|
|
|
@@ -580,15 +578,42 @@ function AuthCallbackHandler({ renderStatus, ...options }) {
|
|
|
580
578
|
}
|
|
581
579
|
|
|
582
580
|
// src/genre-tree/GenreTreeView.tsx
|
|
583
|
-
var
|
|
581
|
+
var import_react16 = require("react");
|
|
584
582
|
var import_react_query3 = require("@tanstack/react-query");
|
|
585
583
|
var import_lucide_react15 = require("lucide-react");
|
|
586
|
-
var
|
|
584
|
+
var import_lucide_react16 = require("lucide-react");
|
|
585
|
+
var import_ui18 = require("@behindthemusictree/ui");
|
|
587
586
|
var import_genre_tree_view4 = require("@behindthemusictree/genre-tree-view");
|
|
588
|
-
var
|
|
589
|
-
var
|
|
587
|
+
var import_genre_tree18 = require("@behindthemusictree/app-kit/genre-tree");
|
|
588
|
+
var import_genre_tree19 = require("@behindthemusictree/app-kit/genre-tree");
|
|
590
589
|
var import_popup6 = require("@behindthemusictree/app-kit/popup");
|
|
591
590
|
|
|
591
|
+
// src/genre-tree/playlist-tree/size-metric.ts
|
|
592
|
+
var hasViewsCount = (genrePlaylists) => genrePlaylists.every((genrePlaylist) => genrePlaylist.viewsCount !== void 0);
|
|
593
|
+
var genrePlaylistSize = (genrePlaylist, metric) => {
|
|
594
|
+
if (metric === "songs") return genrePlaylist.tracksCount;
|
|
595
|
+
if (genrePlaylist.viewsCount === void 0) {
|
|
596
|
+
throw new Error(`Genre playlist ${genrePlaylist.uuid} has no viewsCount to size by views`);
|
|
597
|
+
}
|
|
598
|
+
return genrePlaylist.viewsCount;
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
// src/genre-tree/playlist-tree/toTreeNodes.ts
|
|
602
|
+
function toTreeNodes(genrePlaylists, sizeMetric) {
|
|
603
|
+
return genrePlaylists.map((genrePlaylist) => ({
|
|
604
|
+
id: genrePlaylist.uuid,
|
|
605
|
+
parentId: genrePlaylist.parent?.uuid ?? null,
|
|
606
|
+
name: genrePlaylist.name,
|
|
607
|
+
itemCount: genrePlaylistSize(genrePlaylist, sizeMetric),
|
|
608
|
+
actionable: Boolean(genrePlaylist.criteria),
|
|
609
|
+
detached: !genrePlaylist.criteria,
|
|
610
|
+
side: genrePlaylist.criteria?.side ?? void 0,
|
|
611
|
+
wikidataId: genrePlaylist.criteria?.wikidataId ?? null,
|
|
612
|
+
umbrella: genrePlaylist.isUmbrella,
|
|
613
|
+
scene: genrePlaylist.scene ?? void 0
|
|
614
|
+
}));
|
|
615
|
+
}
|
|
616
|
+
|
|
592
617
|
// src/genre-tree/playlist-tree/TreePerRoot.tsx
|
|
593
618
|
var import_react4 = require("react");
|
|
594
619
|
var import_genre_tree_view = require("@behindthemusictree/genre-tree-view");
|
|
@@ -625,7 +650,6 @@ function useGenrePlaylistPlayback({
|
|
|
625
650
|
}
|
|
626
651
|
|
|
627
652
|
// src/genre-tree/playlist-tree/TreePerRoot.tsx
|
|
628
|
-
var import_track_noun2 = require("@behindthemusictree/app-kit/track-noun");
|
|
629
653
|
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
630
654
|
function GenrePlaylistTreePerRoot({
|
|
631
655
|
scope,
|
|
@@ -637,24 +661,19 @@ function GenrePlaylistTreePerRoot({
|
|
|
637
661
|
additionalActions,
|
|
638
662
|
onNodeClick,
|
|
639
663
|
onNodeHover,
|
|
640
|
-
renderInfoPanelBody,
|
|
641
664
|
selectedNodeId,
|
|
642
665
|
onSelectedNodeChange,
|
|
643
|
-
hideInfoPanelClose,
|
|
644
|
-
hideInfoPanelTitle,
|
|
645
666
|
readOnly = false,
|
|
646
|
-
showToolbar,
|
|
647
667
|
wheelZoom,
|
|
648
668
|
defaultViewport,
|
|
649
669
|
viewportRestore,
|
|
650
670
|
onViewportChange,
|
|
651
671
|
playback,
|
|
652
|
-
...
|
|
672
|
+
...hostPanel
|
|
653
673
|
}) {
|
|
654
674
|
const { isPlaying, setIsPlaying } = (0, import_player.usePlayer)();
|
|
655
675
|
const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists: genrePlaylistTreePerRoot });
|
|
656
676
|
const { playingNodeId, play } = playback ?? defaultPlayback;
|
|
657
|
-
const trackNoun = (0, import_track_noun2.useTrackNoun)();
|
|
658
677
|
const { showPopup } = (0, import_popup2.usePopup)();
|
|
659
678
|
const nodes = (0, import_react4.useMemo)(
|
|
660
679
|
() => genrePlaylistTreePerRoot.map((genrePlaylist) => ({
|
|
@@ -719,7 +738,6 @@ function GenrePlaylistTreePerRoot({
|
|
|
719
738
|
{
|
|
720
739
|
className: className2,
|
|
721
740
|
nodes,
|
|
722
|
-
itemCountLabel: `${(0, import_track_noun2.capitalize)(trackNoun.singular)} count`,
|
|
723
741
|
rootColor: (0, import_genre_tree_view.getGenreTreeColor)(rootUuid),
|
|
724
742
|
playingNodeId,
|
|
725
743
|
playState: isPlaying ? "playing" : "paused",
|
|
@@ -730,18 +748,13 @@ function GenrePlaylistTreePerRoot({
|
|
|
730
748
|
additionalActions,
|
|
731
749
|
onNodeClick,
|
|
732
750
|
onNodeHover,
|
|
733
|
-
renderInfoPanelBody,
|
|
734
751
|
selectedNodeId,
|
|
735
752
|
onSelectedNodeChange,
|
|
736
|
-
hideInfoPanelClose,
|
|
737
|
-
hideInfoPanelTitle,
|
|
738
|
-
showToolbar,
|
|
739
753
|
wheelZoom,
|
|
740
754
|
defaultViewport,
|
|
741
755
|
viewportRestore,
|
|
742
756
|
onViewportChange,
|
|
743
|
-
...
|
|
744
|
-
readOnly
|
|
757
|
+
...hostPanel
|
|
745
758
|
}
|
|
746
759
|
);
|
|
747
760
|
}
|
|
@@ -752,19 +765,6 @@ var import_genre_tree_view2 = require("@behindthemusictree/genre-tree-view");
|
|
|
752
765
|
var import_player2 = require("@behindthemusictree/app-kit/player");
|
|
753
766
|
var import_popup3 = require("@behindthemusictree/app-kit/popup");
|
|
754
767
|
var import_transport3 = require("@behindthemusictree/app-kit/transport");
|
|
755
|
-
|
|
756
|
-
// src/genre-tree/playlist-tree/size-metric.ts
|
|
757
|
-
var hasViewsCount = (genrePlaylists) => genrePlaylists.every((genrePlaylist) => genrePlaylist.viewsCount !== void 0);
|
|
758
|
-
var genrePlaylistSize = (genrePlaylist, metric) => {
|
|
759
|
-
if (metric === "songs") return genrePlaylist.tracksCount;
|
|
760
|
-
if (genrePlaylist.viewsCount === void 0) {
|
|
761
|
-
throw new Error(`Genre playlist ${genrePlaylist.uuid} has no viewsCount to size by views`);
|
|
762
|
-
}
|
|
763
|
-
return genrePlaylist.viewsCount;
|
|
764
|
-
};
|
|
765
|
-
|
|
766
|
-
// src/genre-tree/playlist-tree/TreeWheel.tsx
|
|
767
|
-
var import_track_noun3 = require("@behindthemusictree/app-kit/track-noun");
|
|
768
768
|
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
769
769
|
function GenrePlaylistTreeWheel({
|
|
770
770
|
scope,
|
|
@@ -775,14 +775,10 @@ function GenrePlaylistTreeWheel({
|
|
|
775
775
|
additionalActions,
|
|
776
776
|
onNodeClick,
|
|
777
777
|
onNodeHover,
|
|
778
|
-
renderInfoPanelBody,
|
|
779
778
|
selectedNodeId,
|
|
780
779
|
onSelectedNodeChange,
|
|
781
|
-
hideInfoPanelClose,
|
|
782
|
-
hideInfoPanelTitle,
|
|
783
780
|
readOnly = false,
|
|
784
781
|
allowWheelRotation,
|
|
785
|
-
showToolbar,
|
|
786
782
|
sizeMetric = "songs",
|
|
787
783
|
floatingControlsLeading,
|
|
788
784
|
busy,
|
|
@@ -790,12 +786,11 @@ function GenrePlaylistTreeWheel({
|
|
|
790
786
|
viewportRestore,
|
|
791
787
|
onViewportChange,
|
|
792
788
|
playback,
|
|
793
|
-
...
|
|
789
|
+
...hostPanel
|
|
794
790
|
}) {
|
|
795
791
|
const { isPlaying, setIsPlaying } = (0, import_player2.usePlayer)();
|
|
796
792
|
const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists });
|
|
797
793
|
const { playingNodeId, play } = playback ?? defaultPlayback;
|
|
798
|
-
const trackNoun = (0, import_track_noun3.useTrackNoun)();
|
|
799
794
|
const { showPopup } = (0, import_popup3.usePopup)();
|
|
800
795
|
const nodes = (0, import_react5.useMemo)(
|
|
801
796
|
() => genrePlaylists.map((genrePlaylist) => ({
|
|
@@ -859,7 +854,6 @@ function GenrePlaylistTreeWheel({
|
|
|
859
854
|
{
|
|
860
855
|
className: className2,
|
|
861
856
|
nodes,
|
|
862
|
-
itemCountLabel: sizeMetric === "views" ? "Views" : `${(0, import_track_noun3.capitalize)(trackNoun.singular)} count`,
|
|
863
857
|
playingNodeId,
|
|
864
858
|
playState: isPlaying ? "playing" : "paused",
|
|
865
859
|
onPlayPause: handlePlayPause,
|
|
@@ -869,20 +863,15 @@ function GenrePlaylistTreeWheel({
|
|
|
869
863
|
additionalActions,
|
|
870
864
|
onNodeClick,
|
|
871
865
|
onNodeHover,
|
|
872
|
-
renderInfoPanelBody,
|
|
873
866
|
selectedNodeId,
|
|
874
867
|
onSelectedNodeChange,
|
|
875
|
-
hideInfoPanelClose,
|
|
876
|
-
hideInfoPanelTitle,
|
|
877
868
|
allowWheelRotation,
|
|
878
|
-
showToolbar,
|
|
879
869
|
floatingControlsLeading,
|
|
880
870
|
busy,
|
|
881
871
|
defaultViewport,
|
|
882
872
|
viewportRestore,
|
|
883
873
|
onViewportChange,
|
|
884
|
-
...
|
|
885
|
-
readOnly
|
|
874
|
+
...hostPanel
|
|
886
875
|
}
|
|
887
876
|
);
|
|
888
877
|
}
|
|
@@ -891,7 +880,6 @@ function GenrePlaylistTreeWheel({
|
|
|
891
880
|
var import_react6 = require("react");
|
|
892
881
|
var import_genre_tree_view3 = require("@behindthemusictree/genre-tree-view");
|
|
893
882
|
var import_player3 = require("@behindthemusictree/app-kit/player");
|
|
894
|
-
var import_track_noun4 = require("@behindthemusictree/app-kit/track-noun");
|
|
895
883
|
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
896
884
|
function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
897
885
|
const {
|
|
@@ -903,14 +891,10 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
903
891
|
additionalActions,
|
|
904
892
|
onNodeClick,
|
|
905
893
|
onNodeHover,
|
|
906
|
-
renderInfoPanelBody,
|
|
907
894
|
selectedNodeId,
|
|
908
895
|
onSelectedNodeChange,
|
|
909
|
-
hideInfoPanelClose,
|
|
910
|
-
hideInfoPanelTitle,
|
|
911
896
|
readOnly = false,
|
|
912
897
|
allowWheelRotation,
|
|
913
|
-
showToolbar,
|
|
914
898
|
sizeMetric = "songs",
|
|
915
899
|
floatingControlsLeading,
|
|
916
900
|
busy,
|
|
@@ -921,33 +905,14 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
921
905
|
playback
|
|
922
906
|
} = props;
|
|
923
907
|
const focus = props;
|
|
924
|
-
const
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
onExpandedChange: props.onExpandedChange,
|
|
928
|
-
collapsed: props.collapsed,
|
|
929
|
-
hideExpandButton: props.hideExpandButton,
|
|
930
|
-
overlayPanel: props.overlayPanel
|
|
908
|
+
const hostPanel = {
|
|
909
|
+
obscuredInset: props.obscuredInset,
|
|
910
|
+
onNodeStyleResolverChange: props.onNodeStyleResolverChange
|
|
931
911
|
};
|
|
932
912
|
const { isPlaying, setIsPlaying } = (0, import_player3.usePlayer)();
|
|
933
913
|
const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists });
|
|
934
914
|
const { playingNodeId, play } = playback ?? defaultPlayback;
|
|
935
|
-
const
|
|
936
|
-
const nodes = (0, import_react6.useMemo)(
|
|
937
|
-
() => genrePlaylists.map((genrePlaylist) => ({
|
|
938
|
-
id: genrePlaylist.uuid,
|
|
939
|
-
parentId: genrePlaylist.parent?.uuid ?? null,
|
|
940
|
-
name: genrePlaylist.name,
|
|
941
|
-
itemCount: genrePlaylistSize(genrePlaylist, sizeMetric),
|
|
942
|
-
actionable: Boolean(genrePlaylist.criteria),
|
|
943
|
-
detached: !genrePlaylist.criteria,
|
|
944
|
-
side: genrePlaylist.criteria?.side ?? void 0,
|
|
945
|
-
wikidataId: genrePlaylist.criteria?.wikidataId ?? null,
|
|
946
|
-
umbrella: genrePlaylist.isUmbrella,
|
|
947
|
-
scene: genrePlaylist.scene ?? void 0
|
|
948
|
-
})),
|
|
949
|
-
[genrePlaylists, sizeMetric]
|
|
950
|
-
);
|
|
915
|
+
const nodes = (0, import_react6.useMemo)(() => toTreeNodes(genrePlaylists, sizeMetric), [genrePlaylists, sizeMetric]);
|
|
951
916
|
const handlePlayPause = (0, import_react6.useCallback)(
|
|
952
917
|
(nodeId) => {
|
|
953
918
|
const genrePlaylist = genrePlaylists.find((g) => g.uuid === nodeId);
|
|
@@ -988,7 +953,6 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
988
953
|
const treeProps = {
|
|
989
954
|
className: className2,
|
|
990
955
|
nodes,
|
|
991
|
-
itemCountLabel: sizeMetric === "views" ? "Views" : `${(0, import_track_noun4.capitalize)(trackNoun.singular)} count`,
|
|
992
956
|
playingNodeId,
|
|
993
957
|
playState: isPlaying ? "playing" : "paused",
|
|
994
958
|
onPlayPause: handlePlayPause,
|
|
@@ -998,14 +962,9 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
998
962
|
additionalActions,
|
|
999
963
|
onNodeClick,
|
|
1000
964
|
onNodeHover,
|
|
1001
|
-
renderInfoPanelBody,
|
|
1002
965
|
selectedNodeId,
|
|
1003
966
|
onSelectedNodeChange,
|
|
1004
|
-
|
|
1005
|
-
hideInfoPanelTitle,
|
|
1006
|
-
showToolbar,
|
|
1007
|
-
...infoPanelCuration,
|
|
1008
|
-
readOnly
|
|
967
|
+
...hostPanel
|
|
1009
968
|
};
|
|
1010
969
|
return outline ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_genre_tree_view3.GenreTreeOutline, { ...treeProps }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1011
970
|
import_genre_tree_view3.GenreTreeWheelRadialPopCore,
|
|
@@ -1058,6 +1017,7 @@ function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
|
|
|
1058
1017
|
}
|
|
1059
1018
|
|
|
1060
1019
|
// src/genre-tree/GenreSearch.tsx
|
|
1020
|
+
var import_react8 = require("react");
|
|
1061
1021
|
var import_lucide_react7 = require("lucide-react");
|
|
1062
1022
|
var import_ui6 = require("@behindthemusictree/ui");
|
|
1063
1023
|
var import_genre_tree2 = require("@behindthemusictree/app-kit/genre-tree");
|
|
@@ -1070,7 +1030,7 @@ function GenreSearch({
|
|
|
1070
1030
|
onPrevious,
|
|
1071
1031
|
placeholder,
|
|
1072
1032
|
onQueryChange,
|
|
1073
|
-
|
|
1033
|
+
extraGroups = []
|
|
1074
1034
|
}) {
|
|
1075
1035
|
const { query, setQuery, results, canClear, clear, select } = (0, import_genre_tree2.useGenreSearch)({
|
|
1076
1036
|
genrePlaylists,
|
|
@@ -1078,11 +1038,45 @@ function GenreSearch({
|
|
|
1078
1038
|
selectedName,
|
|
1079
1039
|
onClear
|
|
1080
1040
|
});
|
|
1081
|
-
const
|
|
1082
|
-
const
|
|
1083
|
-
const
|
|
1084
|
-
const
|
|
1085
|
-
const
|
|
1041
|
+
const [open, setOpen] = (0, import_react8.useState)(false);
|
|
1042
|
+
const active = query !== "" && query !== selectedName;
|
|
1043
|
+
const hasExtras = extraGroups.length > 0;
|
|
1044
|
+
const label = hasExtras ? "Search" : "Search a genre";
|
|
1045
|
+
const loading = active && extraGroups.some((group) => group.loading);
|
|
1046
|
+
const status = active ? extraGroups.map((group) => group.status).filter(Boolean).join(". ") : "";
|
|
1047
|
+
const groups = [
|
|
1048
|
+
{
|
|
1049
|
+
key: "genres",
|
|
1050
|
+
label: hasExtras ? "Genres" : void 0,
|
|
1051
|
+
options: results.map((genrePlaylist) => ({
|
|
1052
|
+
key: genrePlaylist.uuid,
|
|
1053
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
|
|
1054
|
+
genrePlaylist.name,
|
|
1055
|
+
genrePlaylist.isUmbrella && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "ml-2 text-xs italic text-[var(--color-text-muted)]", children: "umbrella" })
|
|
1056
|
+
] })
|
|
1057
|
+
}))
|
|
1058
|
+
},
|
|
1059
|
+
...extraGroups.map((group) => ({
|
|
1060
|
+
key: group.key,
|
|
1061
|
+
label: group.label,
|
|
1062
|
+
options: active ? group.items.map((item) => ({ key: item.key, content: item.content })) : []
|
|
1063
|
+
}))
|
|
1064
|
+
];
|
|
1065
|
+
const handlePick = (groupKey, optionKey) => {
|
|
1066
|
+
setOpen(false);
|
|
1067
|
+
if (groupKey === "genres") {
|
|
1068
|
+
const genrePlaylist = results.find((result) => result.uuid === optionKey);
|
|
1069
|
+
if (!genrePlaylist) return;
|
|
1070
|
+
select(genrePlaylist);
|
|
1071
|
+
onQueryChange?.("");
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
const item = extraGroups.find((group) => group.key === groupKey)?.items.find((entry) => entry.key === optionKey);
|
|
1075
|
+
if (!item) return;
|
|
1076
|
+
setQuery("");
|
|
1077
|
+
onQueryChange?.("");
|
|
1078
|
+
item.onSelect();
|
|
1079
|
+
};
|
|
1086
1080
|
return /* @__PURE__ */ (0, import_jsx_runtime16.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: [
|
|
1087
1081
|
onPrevious && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1088
1082
|
"button",
|
|
@@ -1090,53 +1084,51 @@ function GenreSearch({
|
|
|
1090
1084
|
type: "button",
|
|
1091
1085
|
"aria-label": "Previous",
|
|
1092
1086
|
title: "Previous",
|
|
1093
|
-
className: "genre-search-previous absolute left-2 top-1/2 z-10
|
|
1087
|
+
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)]",
|
|
1094
1088
|
onClick: onPrevious,
|
|
1095
1089
|
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react7.ArrowLeft, { "aria-hidden": "true", className: "w-4 h-4" })
|
|
1096
1090
|
}
|
|
1097
1091
|
),
|
|
1098
1092
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1099
|
-
import_ui6.
|
|
1093
|
+
import_ui6.Combobox,
|
|
1100
1094
|
{
|
|
1101
|
-
|
|
1095
|
+
ariaLabel: label,
|
|
1096
|
+
listLabel: `${label} results`,
|
|
1102
1097
|
value: query,
|
|
1103
|
-
onChange: (event) => {
|
|
1104
|
-
setQuery(event.target.value);
|
|
1105
|
-
onQueryChange?.(event.target.value);
|
|
1106
|
-
},
|
|
1107
1098
|
placeholder: placeholder ?? `${label}\u2026`,
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1099
|
+
groups,
|
|
1100
|
+
open,
|
|
1101
|
+
onOpenChange: setOpen,
|
|
1102
|
+
onValueChange: (value) => {
|
|
1103
|
+
setQuery(value);
|
|
1104
|
+
onQueryChange?.(value);
|
|
1105
|
+
},
|
|
1106
|
+
onPick: handlePick,
|
|
1107
|
+
status,
|
|
1108
|
+
loading: loading ? "Searching\u2026" : void 0,
|
|
1109
|
+
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`,
|
|
1110
|
+
className: "genre-search-results z-40 rounded-xl shadow-md",
|
|
1111
|
+
listClassName: "max-h-[min(20rem,50dvh)]",
|
|
1112
|
+
optionClassName: "[@media(pointer:coarse)]:min-h-[var(--layout-touch-target)]"
|
|
1114
1113
|
}
|
|
1115
1114
|
),
|
|
1116
1115
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "absolute right-3 top-1/2 flex items-center -translate-y-1/2", children: [
|
|
1117
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
"aria-hidden": "true",
|
|
1134
|
-
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"}`
|
|
1135
|
-
}
|
|
1136
|
-
)
|
|
1137
|
-
]
|
|
1138
|
-
}
|
|
1139
|
-
),
|
|
1116
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { "data-loading": loading, className: "relative p-2 text-[var(--color-text-muted)] pointer-events-none", children: [
|
|
1117
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1118
|
+
import_lucide_react7.Search,
|
|
1119
|
+
{
|
|
1120
|
+
"aria-hidden": "true",
|
|
1121
|
+
className: `w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? "opacity-0" : ""}`
|
|
1122
|
+
}
|
|
1123
|
+
),
|
|
1124
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1125
|
+
import_lucide_react7.LoaderCircle,
|
|
1126
|
+
{
|
|
1127
|
+
"aria-hidden": "true",
|
|
1128
|
+
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"}`
|
|
1129
|
+
}
|
|
1130
|
+
)
|
|
1131
|
+
] }),
|
|
1140
1132
|
canClear && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1141
1133
|
"button",
|
|
1142
1134
|
{
|
|
@@ -1150,39 +1142,6 @@ function GenreSearch({
|
|
|
1150
1142
|
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react7.X, { "aria-hidden": "true", className: "w-6 h-6" })
|
|
1151
1143
|
}
|
|
1152
1144
|
)
|
|
1153
|
-
] }),
|
|
1154
|
-
extraResults?.status && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { role: "status", "aria-live": "polite", className: "sr-only", children: extraResults.status }),
|
|
1155
|
-
showList && /* @__PURE__ */ (0, import_jsx_runtime16.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: [
|
|
1156
|
-
extraResults && results.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("li", { className: heading, children: "Genres" }),
|
|
1157
|
-
results.map((genrePlaylist) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
1158
|
-
"button",
|
|
1159
|
-
{
|
|
1160
|
-
type: "button",
|
|
1161
|
-
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)]",
|
|
1162
|
-
onClick: () => {
|
|
1163
|
-
select(genrePlaylist);
|
|
1164
|
-
onQueryChange?.("");
|
|
1165
|
-
},
|
|
1166
|
-
children: [
|
|
1167
|
-
genrePlaylist.name,
|
|
1168
|
-
genrePlaylist.isUmbrella && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "ml-2 text-xs italic text-[var(--color-text-muted)]", children: "umbrella" })
|
|
1169
|
-
]
|
|
1170
|
-
}
|
|
1171
|
-
) }, genrePlaylist.uuid)),
|
|
1172
|
-
extraResults && extraItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("li", { className: heading, children: extraResults.label }),
|
|
1173
|
-
extraItems.map((item) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1174
|
-
"button",
|
|
1175
|
-
{
|
|
1176
|
-
type: "button",
|
|
1177
|
-
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)]",
|
|
1178
|
-
onClick: () => {
|
|
1179
|
-
setQuery("");
|
|
1180
|
-
onQueryChange?.("");
|
|
1181
|
-
item.onSelect();
|
|
1182
|
-
},
|
|
1183
|
-
children: item.content
|
|
1184
|
-
}
|
|
1185
|
-
) }, item.key))
|
|
1186
1145
|
] })
|
|
1187
1146
|
] });
|
|
1188
1147
|
}
|
|
@@ -1198,7 +1157,7 @@ var import_lucide_react10 = require("lucide-react");
|
|
|
1198
1157
|
var import_popup4 = require("@behindthemusictree/app-kit/popup");
|
|
1199
1158
|
|
|
1200
1159
|
// src/genre-tree/SharePopup.tsx
|
|
1201
|
-
var
|
|
1160
|
+
var import_react9 = require("react");
|
|
1202
1161
|
var import_lucide_react9 = require("lucide-react");
|
|
1203
1162
|
var import_fa3 = require("react-icons/fa");
|
|
1204
1163
|
var import_si = require("react-icons/si");
|
|
@@ -1230,8 +1189,8 @@ function ShareDestinationButton({
|
|
|
1230
1189
|
var import_lucide_react8 = require("lucide-react");
|
|
1231
1190
|
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
1232
1191
|
function SharePreviewCard({ target, url }) {
|
|
1233
|
-
const Icon =
|
|
1234
|
-
const typeLabel =
|
|
1192
|
+
const Icon = { genre: import_lucide_react8.Music2, artist: import_lucide_react8.UserRound, song: import_lucide_react8.Music }[target.kind];
|
|
1193
|
+
const typeLabel = { genre: "Genre", artist: "Artist", song: "Song" }[target.kind];
|
|
1235
1194
|
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "rounded-3xl bg-[var(--color-surface-muted)] p-4", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex min-h-32 overflow-hidden rounded-2xl bg-[var(--color-surface)] shadow-lg", children: [
|
|
1236
1195
|
/* @__PURE__ */ (0, import_jsx_runtime18.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_runtime18.jsx)(Icon, { "aria-hidden": "true", className: "h-10 w-10" }) }),
|
|
1237
1196
|
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col justify-center gap-1 p-4", children: [
|
|
@@ -1276,20 +1235,20 @@ async function copyText(text) {
|
|
|
1276
1235
|
}
|
|
1277
1236
|
}
|
|
1278
1237
|
function SharePopup({ target, url, onClose }) {
|
|
1279
|
-
const [absoluteUrl, setAbsoluteUrl] = (0,
|
|
1280
|
-
const [copyStatus, setCopyStatus] = (0,
|
|
1281
|
-
const [canUseNativeShare, setCanUseNativeShare] = (0,
|
|
1282
|
-
const [shareError, setShareError] = (0,
|
|
1283
|
-
const [toastId, setToastId] = (0,
|
|
1284
|
-
(0,
|
|
1238
|
+
const [absoluteUrl, setAbsoluteUrl] = (0, import_react9.useState)(url);
|
|
1239
|
+
const [copyStatus, setCopyStatus] = (0, import_react9.useState)("idle");
|
|
1240
|
+
const [canUseNativeShare, setCanUseNativeShare] = (0, import_react9.useState)(false);
|
|
1241
|
+
const [shareError, setShareError] = (0, import_react9.useState)(null);
|
|
1242
|
+
const [toastId, setToastId] = (0, import_react9.useState)(0);
|
|
1243
|
+
(0, import_react9.useEffect)(() => {
|
|
1285
1244
|
setAbsoluteUrl(resolveAbsoluteUrl(url) ?? "");
|
|
1286
1245
|
setCopyStatus("idle");
|
|
1287
1246
|
setShareError(null);
|
|
1288
1247
|
}, [url]);
|
|
1289
|
-
(0,
|
|
1248
|
+
(0, import_react9.useEffect)(() => {
|
|
1290
1249
|
setCanUseNativeShare(typeof navigator !== "undefined" && typeof navigator.share === "function");
|
|
1291
1250
|
}, []);
|
|
1292
|
-
const destinationUrls = (0,
|
|
1251
|
+
const destinationUrls = (0, import_react9.useMemo)(() => {
|
|
1293
1252
|
if (!absoluteUrl) return null;
|
|
1294
1253
|
const message = `${target.name} \u2014 ${absoluteUrl}`;
|
|
1295
1254
|
return {
|
|
@@ -1307,7 +1266,7 @@ function SharePopup({ target, url, onClose }) {
|
|
|
1307
1266
|
setCopyStatus(copied ? "idle" : "failed");
|
|
1308
1267
|
if (copied) setToastId((id) => id + 1);
|
|
1309
1268
|
};
|
|
1310
|
-
const dismissToast = (0,
|
|
1269
|
+
const dismissToast = (0, import_react9.useCallback)(() => setToastId(0), []);
|
|
1311
1270
|
const handleNativeShare = async () => {
|
|
1312
1271
|
if (!absoluteUrl || !navigator.share) return;
|
|
1313
1272
|
setShareError(null);
|
|
@@ -1513,13 +1472,13 @@ var import_ui8 = require("@behindthemusictree/ui");
|
|
|
1513
1472
|
var import_genre_tree9 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1514
1473
|
|
|
1515
1474
|
// src/genre-tree/ClampedText.tsx
|
|
1516
|
-
var
|
|
1475
|
+
var import_react10 = require("react");
|
|
1517
1476
|
var import_jsx_runtime22 = require("react/jsx-runtime");
|
|
1518
1477
|
function ClampedText({ children, className: className2 = "" }) {
|
|
1519
|
-
const ref = (0,
|
|
1520
|
-
const [expanded, setExpanded] = (0,
|
|
1521
|
-
const [overflows, setOverflows] = (0,
|
|
1522
|
-
(0,
|
|
1478
|
+
const ref = (0, import_react10.useRef)(null);
|
|
1479
|
+
const [expanded, setExpanded] = (0, import_react10.useState)(false);
|
|
1480
|
+
const [overflows, setOverflows] = (0, import_react10.useState)(false);
|
|
1481
|
+
(0, import_react10.useLayoutEffect)(() => {
|
|
1523
1482
|
const element = ref.current;
|
|
1524
1483
|
if (!element || expanded) return;
|
|
1525
1484
|
setOverflows(element.scrollHeight > element.clientHeight);
|
|
@@ -1540,7 +1499,7 @@ function ClampedText({ children, className: className2 = "" }) {
|
|
|
1540
1499
|
}
|
|
1541
1500
|
|
|
1542
1501
|
// src/genre-tree/GenrePlaylistTracks.tsx
|
|
1543
|
-
var
|
|
1502
|
+
var import_react11 = require("react");
|
|
1544
1503
|
var import_react_query = require("@tanstack/react-query");
|
|
1545
1504
|
var import_zod = require("zod");
|
|
1546
1505
|
var import_fa4 = require("react-icons/fa");
|
|
@@ -1584,7 +1543,7 @@ function MusicBrainzRecordingLink({
|
|
|
1584
1543
|
|
|
1585
1544
|
// src/genre-tree/GenrePlaylistTracks.tsx
|
|
1586
1545
|
var import_genre_tree8 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1587
|
-
var
|
|
1546
|
+
var import_track_noun2 = require("@behindthemusictree/app-kit/track-noun");
|
|
1588
1547
|
var import_jsx_runtime24 = require("react/jsx-runtime");
|
|
1589
1548
|
var CATALOGUE_PAGE_SIZE = 100;
|
|
1590
1549
|
var SongPageSchema = (0, import_transport5.PaginatedResponseSchema)(import_genre_tree6.SongDetailedSchema).extend({
|
|
@@ -1608,6 +1567,7 @@ function GenrePlaylistTracks({
|
|
|
1608
1567
|
scope,
|
|
1609
1568
|
getBackendBaseUrl,
|
|
1610
1569
|
renderArtistLink,
|
|
1570
|
+
renderSongLink,
|
|
1611
1571
|
renderSongLeading,
|
|
1612
1572
|
renderSongActions,
|
|
1613
1573
|
renderTracksToolbar,
|
|
@@ -1617,7 +1577,7 @@ function GenrePlaylistTracks({
|
|
|
1617
1577
|
const { session, sessionRestored } = (0, import_auth2.useSession)();
|
|
1618
1578
|
const { playerTrackObject, playState, handlePlayPauseAction } = (0, import_player4.usePlayer)();
|
|
1619
1579
|
const { showPopup } = (0, import_popup5.usePopup)();
|
|
1620
|
-
const trackNoun = (0,
|
|
1580
|
+
const trackNoun = (0, import_track_noun2.useTrackNoun)();
|
|
1621
1581
|
const { uuid } = genrePlaylist;
|
|
1622
1582
|
const { fetch } = (0, import_transport4.useFetchWrapper)(getBackendBaseUrl);
|
|
1623
1583
|
const { data, isPending, isError, refetch, hasNextPage, isFetchingNextPage, fetchNextPage } = (0, import_react_query.useInfiniteQuery)({
|
|
@@ -1638,8 +1598,8 @@ function GenrePlaylistTracks({
|
|
|
1638
1598
|
getNextPageParam: (lastPage) => lastPage.nextPage ?? void 0,
|
|
1639
1599
|
enabled: scope !== "me" || sessionRestored && !!session?.accessToken
|
|
1640
1600
|
});
|
|
1641
|
-
const sentinelRef = (0,
|
|
1642
|
-
(0,
|
|
1601
|
+
const sentinelRef = (0, import_react11.useRef)(null);
|
|
1602
|
+
(0, import_react11.useEffect)(() => {
|
|
1643
1603
|
const sentinel = sentinelRef.current;
|
|
1644
1604
|
if (!hasNextPage || isFetchingNextPage || !sentinel) return;
|
|
1645
1605
|
const observer = new IntersectionObserver((entries) => {
|
|
@@ -1662,7 +1622,7 @@ function GenrePlaylistTracks({
|
|
|
1662
1622
|
};
|
|
1663
1623
|
return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "gtv-info-panel-children", children: [
|
|
1664
1624
|
/* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { className: "gtv-info-panel-children-title", children: [
|
|
1665
|
-
(0,
|
|
1625
|
+
(0, import_track_noun2.capitalize)(trackNoun.plural),
|
|
1666
1626
|
total !== void 0 && ` (${total})`
|
|
1667
1627
|
] }),
|
|
1668
1628
|
renderTracksToolbar?.(),
|
|
@@ -1678,7 +1638,7 @@ function GenrePlaylistTracks({
|
|
|
1678
1638
|
const isCurrent = playerTrackObject?.track.id === track.uuid;
|
|
1679
1639
|
const isPlaying = isCurrent && playState === import_player5.PlayStates.PLAYING;
|
|
1680
1640
|
const text = /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { className: TEXT_CELL, children: [
|
|
1681
|
-
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: TITLE_CELL, children: (0, import_genre_tree6.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
|
|
1641
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: TITLE_CELL, children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree6.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
|
|
1682
1642
|
track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: SEPARATOR, children: " \u2014 " }) : null,
|
|
1683
1643
|
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_genre_tree8.ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
|
|
1684
1644
|
track.album && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: ALBUM_CELL, children: track.album.name })
|
|
@@ -1733,7 +1693,7 @@ function GenrePlaylistTracks({
|
|
|
1733
1693
|
}
|
|
1734
1694
|
|
|
1735
1695
|
// src/genre-tree/GenreDetailExtras.tsx
|
|
1736
|
-
var
|
|
1696
|
+
var import_track_noun3 = require("@behindthemusictree/app-kit/track-noun");
|
|
1737
1697
|
var import_si2 = require("react-icons/si");
|
|
1738
1698
|
var import_jsx_runtime25 = require("react/jsx-runtime");
|
|
1739
1699
|
function GenreDetailExtras({
|
|
@@ -1745,12 +1705,13 @@ function GenreDetailExtras({
|
|
|
1745
1705
|
criteriaOverviewSchema,
|
|
1746
1706
|
renderGenreDetailExtras,
|
|
1747
1707
|
renderArtistLink,
|
|
1708
|
+
renderSongLink,
|
|
1748
1709
|
renderSummary,
|
|
1749
1710
|
renderEssentialTracks,
|
|
1750
1711
|
family,
|
|
1751
1712
|
...trackSlots
|
|
1752
1713
|
}) {
|
|
1753
|
-
const trackNoun = (0,
|
|
1714
|
+
const trackNoun = (0, import_track_noun3.useTrackNoun)();
|
|
1754
1715
|
const { data: overview, isPending } = (0, import_genre_tree9.useFetchGenreOverview)(
|
|
1755
1716
|
genreUuid,
|
|
1756
1717
|
scope,
|
|
@@ -1822,6 +1783,7 @@ function GenreDetailExtras({
|
|
|
1822
1783
|
scope,
|
|
1823
1784
|
getBackendBaseUrl,
|
|
1824
1785
|
renderArtistLink,
|
|
1786
|
+
renderSongLink,
|
|
1825
1787
|
...trackSlots
|
|
1826
1788
|
}
|
|
1827
1789
|
)
|
|
@@ -1830,7 +1792,7 @@ function GenreDetailExtras({
|
|
|
1830
1792
|
|
|
1831
1793
|
// src/genre-tree/GenreInfoPanelBody.tsx
|
|
1832
1794
|
var import_jsx_runtime26 = require("react/jsx-runtime");
|
|
1833
|
-
var
|
|
1795
|
+
var import_react12 = require("react");
|
|
1834
1796
|
function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
|
|
1835
1797
|
const { isPlaying, setIsPlaying } = (0, import_player6.usePlayer)();
|
|
1836
1798
|
const isCurrentTrackList = playback.playingNodeId === genrePlaylist.uuid;
|
|
@@ -1867,7 +1829,7 @@ function GenreInfoPanelBody({
|
|
|
1867
1829
|
return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_jsx_runtime26.Fragment, { children: [
|
|
1868
1830
|
toolbar,
|
|
1869
1831
|
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)(GenreNodeActionBand, { ...band, genrePlaylist, playback }),
|
|
1870
|
-
/* @__PURE__ */ (0,
|
|
1832
|
+
/* @__PURE__ */ (0, import_react12.createElement)(
|
|
1871
1833
|
GenreDetailExtras,
|
|
1872
1834
|
{
|
|
1873
1835
|
...extras,
|
|
@@ -1883,66 +1845,10 @@ function GenreInfoPanelBody({
|
|
|
1883
1845
|
] });
|
|
1884
1846
|
}
|
|
1885
1847
|
|
|
1886
|
-
// src/genre-tree/PanelEdgeTab.tsx
|
|
1887
|
-
var import_lucide_react12 = require("lucide-react");
|
|
1888
|
-
var import_tailwind_merge = require("tailwind-merge");
|
|
1889
|
-
var import_jsx_runtime27 = require("react/jsx-runtime");
|
|
1890
|
-
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)]";
|
|
1891
|
-
function PanelEdgeTab({ side, edge, center, controls, className: className2 }) {
|
|
1892
|
-
if (!edge && !center) return null;
|
|
1893
|
-
const isLeft = side === "left";
|
|
1894
|
-
const EdgeIcon = isLeft ? import_lucide_react12.ChevronLeft : import_lucide_react12.ChevronRight;
|
|
1895
|
-
const CenterIcon = isLeft ? import_lucide_react12.ChevronRight : import_lucide_react12.ChevronLeft;
|
|
1896
|
-
const renderButton = (action, icon) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
1897
|
-
"button",
|
|
1898
|
-
{
|
|
1899
|
-
type: "button",
|
|
1900
|
-
"aria-label": action.label,
|
|
1901
|
-
title: action.label,
|
|
1902
|
-
"aria-controls": controls,
|
|
1903
|
-
"aria-expanded": action.expanded,
|
|
1904
|
-
className: buttonClassName,
|
|
1905
|
-
onClick: action.onClick,
|
|
1906
|
-
children: icon
|
|
1907
|
-
}
|
|
1908
|
-
);
|
|
1909
|
-
const edgeButton = edge && renderButton(edge, /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(EdgeIcon, { "aria-hidden": "true", className: "h-5 w-5" }));
|
|
1910
|
-
const centerButton = center && renderButton(center, /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(CenterIcon, { "aria-hidden": "true", className: "h-5 w-5" }));
|
|
1911
|
-
const buttons = isLeft ? [edgeButton, centerButton] : [centerButton, edgeButton];
|
|
1912
|
-
return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
|
|
1913
|
-
"div",
|
|
1914
|
-
{
|
|
1915
|
-
className: (0, import_tailwind_merge.twMerge)(
|
|
1916
|
-
"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)]",
|
|
1917
|
-
isLeft ? "rounded-r-lg" : "rounded-l-lg [clip-path:inset(-0.5rem_0_-0.5rem_-0.5rem)]",
|
|
1918
|
-
className2
|
|
1919
|
-
),
|
|
1920
|
-
children: [
|
|
1921
|
-
buttons[0],
|
|
1922
|
-
buttons[0] && buttons[1] && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { role: "presentation", className: "h-px shrink-0 bg-[var(--color-border)]" }),
|
|
1923
|
-
buttons[1]
|
|
1924
|
-
]
|
|
1925
|
-
}
|
|
1926
|
-
);
|
|
1927
|
-
}
|
|
1928
|
-
|
|
1929
|
-
// src/genre-tree/useLeftDetailPanelLayout.ts
|
|
1930
|
-
var import_react12 = require("react");
|
|
1931
|
-
function useLeftDetailPanelLayout(isPanelShown, enabled = true) {
|
|
1932
|
-
(0, import_react12.useEffect)(() => {
|
|
1933
|
-
if (!enabled || isPanelShown) return void 0;
|
|
1934
|
-
const root = document.documentElement;
|
|
1935
|
-
root.style.setProperty("--layout-left-w", "0px");
|
|
1936
|
-
return () => {
|
|
1937
|
-
root.style.removeProperty("--layout-left-w");
|
|
1938
|
-
};
|
|
1939
|
-
}, [enabled, isPanelShown]);
|
|
1940
|
-
}
|
|
1941
|
-
|
|
1942
1848
|
// src/genre-tree/ChangeParentPopup.tsx
|
|
1943
1849
|
var import_react13 = require("react");
|
|
1944
1850
|
var import_genre_tree10 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1945
|
-
var
|
|
1851
|
+
var import_jsx_runtime27 = require("react/jsx-runtime");
|
|
1946
1852
|
function ChangeParentPopup({
|
|
1947
1853
|
genrePlaylist,
|
|
1948
1854
|
genrePlaylists,
|
|
@@ -1977,12 +1883,12 @@ function ChangeParentPopup({
|
|
|
1977
1883
|
window.addEventListener("keydown", handleKeyDown);
|
|
1978
1884
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
1979
1885
|
}, [onClose]);
|
|
1980
|
-
return /* @__PURE__ */ (0,
|
|
1981
|
-
/* @__PURE__ */ (0,
|
|
1886
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
|
|
1887
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("p", { className: "mb-3 text-sm text-[var(--color-text-muted)]", children: [
|
|
1982
1888
|
"Current parent: ",
|
|
1983
1889
|
genrePlaylist.parent?.name ?? "none"
|
|
1984
1890
|
] }),
|
|
1985
|
-
/* @__PURE__ */ (0,
|
|
1891
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
1986
1892
|
GenreSearch,
|
|
1987
1893
|
{
|
|
1988
1894
|
genrePlaylists: candidates,
|
|
@@ -1990,7 +1896,7 @@ function ChangeParentPopup({
|
|
|
1990
1896
|
placeholder: "Search a new parent\u2026"
|
|
1991
1897
|
}
|
|
1992
1898
|
) }),
|
|
1993
|
-
formErrors.length > 0 && /* @__PURE__ */ (0,
|
|
1899
|
+
formErrors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { role: "alert", className: "mt-3 text-sm text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
|
|
1994
1900
|
] });
|
|
1995
1901
|
}
|
|
1996
1902
|
|
|
@@ -2002,12 +1908,12 @@ var import_genre_tree15 = require("@behindthemusictree/app-kit/genre-tree");
|
|
|
2002
1908
|
var import_ui10 = require("@behindthemusictree/ui");
|
|
2003
1909
|
|
|
2004
1910
|
// src/genre-tree/artist/ArtistLinks.tsx
|
|
2005
|
-
var
|
|
1911
|
+
var import_lucide_react12 = require("lucide-react");
|
|
2006
1912
|
var import_si3 = require("react-icons/si");
|
|
2007
1913
|
var import_ui9 = require("@behindthemusictree/ui");
|
|
2008
|
-
var
|
|
1914
|
+
var import_jsx_runtime28 = require("react/jsx-runtime");
|
|
2009
1915
|
var LINK_KINDS = {
|
|
2010
|
-
official: { label: "Official website", Icon:
|
|
1916
|
+
official: { label: "Official website", Icon: import_lucide_react12.Globe },
|
|
2011
1917
|
wikipedia: { label: "Wikipedia", Icon: import_si3.SiWikipedia },
|
|
2012
1918
|
wikidata: { label: "Wikidata", Icon: import_si3.SiWikidata },
|
|
2013
1919
|
spotify: { label: "Spotify", Icon: import_si3.SiSpotify },
|
|
@@ -2015,7 +1921,7 @@ var LINK_KINDS = {
|
|
|
2015
1921
|
bandcamp: { label: "Bandcamp", Icon: import_si3.SiBandcamp },
|
|
2016
1922
|
soundcloud: { label: "SoundCloud", Icon: import_si3.SiSoundcloud },
|
|
2017
1923
|
discogs: { label: "Discogs", Icon: import_si3.SiDiscogs },
|
|
2018
|
-
social: { label: "Social", Icon:
|
|
1924
|
+
social: { label: "Social", Icon: import_lucide_react12.Share2 }
|
|
2019
1925
|
};
|
|
2020
1926
|
function socialLabel(url) {
|
|
2021
1927
|
try {
|
|
@@ -2042,7 +1948,7 @@ function artistLinks(artist) {
|
|
|
2042
1948
|
return links;
|
|
2043
1949
|
}
|
|
2044
1950
|
function ArtistLinks({ links }) {
|
|
2045
|
-
return /* @__PURE__ */ (0,
|
|
1951
|
+
return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_ui9.Chip, { href: url, icon: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
|
|
2046
1952
|
}
|
|
2047
1953
|
|
|
2048
1954
|
// src/genre-tree/artist/formatYears.ts
|
|
@@ -2053,7 +1959,7 @@ function formatYears(beginYear, endYear, ended) {
|
|
|
2053
1959
|
}
|
|
2054
1960
|
|
|
2055
1961
|
// src/genre-tree/artist/ArtistHeader.tsx
|
|
2056
|
-
var
|
|
1962
|
+
var import_jsx_runtime29 = require("react/jsx-runtime");
|
|
2057
1963
|
var countryNames = new Intl.DisplayNames(["en"], { type: "region" });
|
|
2058
1964
|
function ArtistHeader({ artist }) {
|
|
2059
1965
|
const fields = [];
|
|
@@ -2063,35 +1969,35 @@ function ArtistHeader({ artist }) {
|
|
|
2063
1969
|
const years = formatYears(artist.beginYear, artist.endYear, artist.ended);
|
|
2064
1970
|
if (years) fields.push({ label: "Active", value: years });
|
|
2065
1971
|
const links = artistLinks(artist);
|
|
2066
|
-
if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0,
|
|
1972
|
+
if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(ArtistLinks, { links }) });
|
|
2067
1973
|
if (fields.length === 0) return null;
|
|
2068
|
-
return /* @__PURE__ */ (0,
|
|
1974
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui10.FieldList, { items: fields });
|
|
2069
1975
|
}
|
|
2070
1976
|
|
|
2071
1977
|
// src/genre-tree/artist/ArtistPhoto.tsx
|
|
2072
|
-
var
|
|
1978
|
+
var import_jsx_runtime30 = require("react/jsx-runtime");
|
|
2073
1979
|
function ExternalLink({ href, children }) {
|
|
2074
|
-
return /* @__PURE__ */ (0,
|
|
1980
|
+
return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
|
|
2075
1981
|
}
|
|
2076
1982
|
function ArtistPhoto({ image, name }) {
|
|
2077
|
-
return /* @__PURE__ */ (0,
|
|
2078
|
-
/* @__PURE__ */ (0,
|
|
2079
|
-
/* @__PURE__ */ (0,
|
|
1983
|
+
return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
|
|
1984
|
+
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
|
|
1985
|
+
/* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("figcaption", { className: "text-xs text-[var(--color-text-muted)]", children: [
|
|
2080
1986
|
"Photo: ",
|
|
2081
|
-
/* @__PURE__ */ (0,
|
|
1987
|
+
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
|
|
2082
1988
|
",",
|
|
2083
1989
|
" ",
|
|
2084
|
-
image.licenseUrl ? /* @__PURE__ */ (0,
|
|
1990
|
+
image.licenseUrl ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
|
|
2085
1991
|
] })
|
|
2086
1992
|
] });
|
|
2087
1993
|
}
|
|
2088
1994
|
|
|
2089
1995
|
// src/genre-tree/artist/ArtistMembers.tsx
|
|
2090
1996
|
var import_ui11 = require("@behindthemusictree/ui");
|
|
2091
|
-
var
|
|
1997
|
+
var import_jsx_runtime31 = require("react/jsx-runtime");
|
|
2092
1998
|
function MembershipList({ title, memberships, renderArtistLink }) {
|
|
2093
1999
|
if (memberships.length === 0) return null;
|
|
2094
|
-
return /* @__PURE__ */ (0,
|
|
2000
|
+
return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ui11.PanelGroup, { title, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("ul", { children: memberships.map((membership, index) => {
|
|
2095
2001
|
const details = [
|
|
2096
2002
|
formatYears(membership.beginYear, membership.endYear, membership.ended),
|
|
2097
2003
|
...membership.roles
|
|
@@ -2099,9 +2005,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
|
|
|
2099
2005
|
const detailsText = details.filter(Boolean).join(" \xB7 ");
|
|
2100
2006
|
return (
|
|
2101
2007
|
// A member can have several periods in the same group.
|
|
2102
|
-
/* @__PURE__ */ (0,
|
|
2103
|
-
/* @__PURE__ */ (0,
|
|
2104
|
-
detailsText && /* @__PURE__ */ (0,
|
|
2008
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
|
|
2009
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
|
|
2010
|
+
detailsText && /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
|
|
2105
2011
|
" \u2014 ",
|
|
2106
2012
|
detailsText
|
|
2107
2013
|
] })
|
|
@@ -2112,9 +2018,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
|
|
|
2112
2018
|
function ArtistMembers({ title, memberships, renderArtistLink }) {
|
|
2113
2019
|
if (memberships.length === 0) return null;
|
|
2114
2020
|
const isCurrent = (membership) => !membership.ended && membership.endYear === null;
|
|
2115
|
-
return /* @__PURE__ */ (0,
|
|
2116
|
-
/* @__PURE__ */ (0,
|
|
2117
|
-
/* @__PURE__ */ (0,
|
|
2021
|
+
return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_ui11.PanelSection, { title, children: [
|
|
2022
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
|
|
2023
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
|
|
2118
2024
|
MembershipList,
|
|
2119
2025
|
{
|
|
2120
2026
|
title: "Past",
|
|
@@ -2127,7 +2033,7 @@ function ArtistMembers({ title, memberships, renderArtistLink }) {
|
|
|
2127
2033
|
|
|
2128
2034
|
// src/genre-tree/artist/ArtistDiscography.tsx
|
|
2129
2035
|
var import_ui12 = require("@behindthemusictree/ui");
|
|
2130
|
-
var
|
|
2036
|
+
var import_jsx_runtime32 = require("react/jsx-runtime");
|
|
2131
2037
|
var GROUPS = ["Albums", "EPs", "Singles", "Live", "Compilations", "Other"];
|
|
2132
2038
|
var PRIMARY_GROUPS = { album: "Albums", ep: "EPs", single: "Singles" };
|
|
2133
2039
|
function discographyGroup({ primaryType, secondaryTypes }) {
|
|
@@ -2147,12 +2053,12 @@ function byYear(a, b) {
|
|
|
2147
2053
|
function ArtistDiscography({ albums }) {
|
|
2148
2054
|
if (albums.length === 0) return null;
|
|
2149
2055
|
const sorted = [...albums].sort(byYear);
|
|
2150
|
-
return /* @__PURE__ */ (0,
|
|
2056
|
+
return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui12.PanelSection, { title: "Discography", children: GROUPS.map((group) => {
|
|
2151
2057
|
const groupAlbums = sorted.filter((album) => discographyGroup(album) === group);
|
|
2152
2058
|
if (groupAlbums.length === 0) return null;
|
|
2153
|
-
return /* @__PURE__ */ (0,
|
|
2154
|
-
/* @__PURE__ */ (0,
|
|
2155
|
-
/* @__PURE__ */ (0,
|
|
2059
|
+
return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui12.PanelGroup, { title: group, children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
|
|
2060
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
|
|
2061
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
|
|
2156
2062
|
] }, album.uuid)) }) }, group);
|
|
2157
2063
|
}) });
|
|
2158
2064
|
}
|
|
@@ -2160,7 +2066,7 @@ function ArtistDiscography({ albums }) {
|
|
|
2160
2066
|
// src/genre-tree/artist/ArtistSongs.tsx
|
|
2161
2067
|
var import_react14 = require("react");
|
|
2162
2068
|
var import_react_query2 = require("@tanstack/react-query");
|
|
2163
|
-
var
|
|
2069
|
+
var import_lucide_react13 = require("lucide-react");
|
|
2164
2070
|
var import_ui13 = require("@behindthemusictree/ui");
|
|
2165
2071
|
var import_player10 = require("@behindthemusictree/app-kit/player");
|
|
2166
2072
|
var import_genre_tree14 = require("@behindthemusictree/app-kit/genre-tree");
|
|
@@ -2174,7 +2080,7 @@ var import_genre_tree11 = require("@behindthemusictree/app-kit/genre-tree");
|
|
|
2174
2080
|
var import_fa5 = require("react-icons/fa");
|
|
2175
2081
|
var import_player7 = require("@behindthemusictree/app-kit/player");
|
|
2176
2082
|
var import_player8 = require("@behindthemusictree/app-kit/player");
|
|
2177
|
-
var
|
|
2083
|
+
var import_jsx_runtime33 = require("react/jsx-runtime");
|
|
2178
2084
|
function TrackPositionPlayPause({
|
|
2179
2085
|
position,
|
|
2180
2086
|
uuid,
|
|
@@ -2188,14 +2094,14 @@ function TrackPositionPlayPause({
|
|
|
2188
2094
|
const isPlaying = isThisTrack && isConfirmedPlayable && playState === import_player8.PlayStates.PLAYING;
|
|
2189
2095
|
const isPaused = isThisTrack && isConfirmedPlayable && playState === import_player8.PlayStates.PAUSED;
|
|
2190
2096
|
const isCurrentTrackActive = isPlaying || isPaused;
|
|
2191
|
-
return /* @__PURE__ */ (0,
|
|
2097
|
+
return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
|
|
2192
2098
|
"div",
|
|
2193
2099
|
{
|
|
2194
2100
|
className: "track-position-play-pause relative flex items-center justify-center gap-2 text-lg w-16 cursor-pointer",
|
|
2195
2101
|
style: { minWidth: "4rem", maxWidth: "4rem" },
|
|
2196
2102
|
onClick: handlePlayPauseClick,
|
|
2197
2103
|
children: [
|
|
2198
|
-
/* @__PURE__ */ (0,
|
|
2104
|
+
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
|
|
2199
2105
|
"span",
|
|
2200
2106
|
{
|
|
2201
2107
|
className: "track-position-play-pause__bars",
|
|
@@ -2203,18 +2109,18 @@ function TrackPositionPlayPause({
|
|
|
2203
2109
|
role: "img",
|
|
2204
2110
|
"aria-label": isPlaying ? "Playing" : "Paused",
|
|
2205
2111
|
children: [
|
|
2206
|
-
/* @__PURE__ */ (0,
|
|
2207
|
-
/* @__PURE__ */ (0,
|
|
2208
|
-
/* @__PURE__ */ (0,
|
|
2112
|
+
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "track-position-play-pause__bar" }),
|
|
2113
|
+
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "track-position-play-pause__bar" }),
|
|
2114
|
+
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "track-position-play-pause__bar" })
|
|
2209
2115
|
]
|
|
2210
2116
|
}
|
|
2211
2117
|
) : position }),
|
|
2212
|
-
/* @__PURE__ */ (0,
|
|
2118
|
+
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
|
|
2213
2119
|
"div",
|
|
2214
2120
|
{
|
|
2215
2121
|
className: `hidden group-hover:flex items-center justify-center ${isCurrentTrackActive ? "" : "absolute inset-0"}`,
|
|
2216
2122
|
"aria-hidden": "true",
|
|
2217
|
-
children: isAwaitingPlayback ? /* @__PURE__ */ (0,
|
|
2123
|
+
children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_fa5.FaPause, {}) : /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_fa5.FaPlay, {})
|
|
2218
2124
|
}
|
|
2219
2125
|
)
|
|
2220
2126
|
]
|
|
@@ -2225,13 +2131,14 @@ function TrackPositionPlayPause({
|
|
|
2225
2131
|
// src/genre-tree/track-list-sidebar/TrackItem.tsx
|
|
2226
2132
|
var import_genre_tree12 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2227
2133
|
var import_genre_tree13 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2228
|
-
var
|
|
2134
|
+
var import_jsx_runtime34 = require("react/jsx-runtime");
|
|
2229
2135
|
function TrackItem({
|
|
2230
2136
|
track,
|
|
2231
2137
|
position,
|
|
2232
2138
|
renderDuration,
|
|
2233
2139
|
renderActions,
|
|
2234
|
-
renderArtistLink
|
|
2140
|
+
renderArtistLink,
|
|
2141
|
+
renderSongLink
|
|
2235
2142
|
}) {
|
|
2236
2143
|
const { handlePlayPauseAction, playerTrackObject, loadTrackForPlayer } = (0, import_player9.usePlayer)();
|
|
2237
2144
|
const { trackList, selectedTrack, toTrackAtPosition, runtimeUnplayableTrackIds } = (0, import_genre_tree11.useTrackList)();
|
|
@@ -2248,12 +2155,12 @@ function TrackItem({
|
|
|
2248
2155
|
loadTrackForPlayer(track.uuid);
|
|
2249
2156
|
}
|
|
2250
2157
|
};
|
|
2251
|
-
return /* @__PURE__ */ (0,
|
|
2158
|
+
return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
|
|
2252
2159
|
"div",
|
|
2253
2160
|
{
|
|
2254
2161
|
className: `track-item flex h-14 text-[var(--color-text)] ${isUnplayable ? "opacity-50" : "hover:bg-[var(--color-surface-muted)] group"}`,
|
|
2255
2162
|
children: [
|
|
2256
|
-
isUnplayable ? /* @__PURE__ */ (0,
|
|
2163
|
+
isUnplayable ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2257
2164
|
"div",
|
|
2258
2165
|
{
|
|
2259
2166
|
className: "flex items-center justify-center text-lg w-16",
|
|
@@ -2261,7 +2168,7 @@ function TrackItem({
|
|
|
2261
2168
|
"aria-disabled": "true",
|
|
2262
2169
|
children: position
|
|
2263
2170
|
}
|
|
2264
|
-
) : /* @__PURE__ */ (0,
|
|
2171
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2265
2172
|
TrackPositionPlayPause,
|
|
2266
2173
|
{
|
|
2267
2174
|
position,
|
|
@@ -2270,12 +2177,12 @@ function TrackItem({
|
|
|
2270
2177
|
isSelected: selectedTrack?.uuid === track.uuid
|
|
2271
2178
|
}
|
|
2272
2179
|
),
|
|
2273
|
-
/* @__PURE__ */ (0,
|
|
2274
|
-
/* @__PURE__ */ (0,
|
|
2275
|
-
track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0,
|
|
2180
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
|
|
2181
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree12.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
|
|
2182
|
+
track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_genre_tree13.ArtistNames, { artists: track.artists, renderArtistLink }) }) : ""
|
|
2276
2183
|
] }),
|
|
2277
|
-
/* @__PURE__ */ (0,
|
|
2278
|
-
renderDuration && /* @__PURE__ */ (0,
|
|
2184
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "genre-name-container flex items-center justify-start w-[21.6%] min-w-0 shrink-0", children: track.genre ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
|
|
2185
|
+
renderDuration && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2279
2186
|
"div",
|
|
2280
2187
|
{
|
|
2281
2188
|
className: "duration flex text-base w-16 items-center justify-center",
|
|
@@ -2283,20 +2190,20 @@ function TrackItem({
|
|
|
2283
2190
|
children: renderDuration(track)
|
|
2284
2191
|
}
|
|
2285
2192
|
),
|
|
2286
|
-
renderActions && /* @__PURE__ */ (0,
|
|
2193
|
+
renderActions && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
|
|
2287
2194
|
]
|
|
2288
2195
|
}
|
|
2289
2196
|
);
|
|
2290
2197
|
}
|
|
2291
2198
|
|
|
2292
2199
|
// src/genre-tree/artist/ArtistSongs.tsx
|
|
2293
|
-
var
|
|
2200
|
+
var import_jsx_runtime35 = require("react/jsx-runtime");
|
|
2294
2201
|
var SongPageSchema2 = (0, import_transport8.PaginatedResponseSchema)(import_genre_tree14.SongDetailedSchema);
|
|
2295
2202
|
var SORTS = [
|
|
2296
2203
|
{ ordering: "title", label: "Title" },
|
|
2297
2204
|
{ ordering: "-albumYear", label: "Year" }
|
|
2298
2205
|
];
|
|
2299
|
-
function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
2206
|
+
function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLink }) {
|
|
2300
2207
|
const [page, setPage] = (0, import_react14.useState)(1);
|
|
2301
2208
|
const [ordering, setOrdering] = (0, import_react14.useState)("title");
|
|
2302
2209
|
const { fetch } = (0, import_transport8.useFetchWrapper)(getBackendBaseUrl);
|
|
@@ -2323,21 +2230,21 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
|
2323
2230
|
event.stopPropagation();
|
|
2324
2231
|
void play(song.uuid);
|
|
2325
2232
|
};
|
|
2326
|
-
return /* @__PURE__ */ (0,
|
|
2327
|
-
/* @__PURE__ */ (0,
|
|
2328
|
-
/* @__PURE__ */ (0,
|
|
2233
|
+
return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui13.PanelSection, { title: "Songs", children: isPending ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui13.Skeleton, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
|
|
2234
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
|
|
2235
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
|
|
2329
2236
|
"button",
|
|
2330
2237
|
{
|
|
2331
2238
|
type: "button",
|
|
2332
2239
|
onClick: () => void play(),
|
|
2333
2240
|
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",
|
|
2334
2241
|
children: [
|
|
2335
|
-
/* @__PURE__ */ (0,
|
|
2242
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react13.Play, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
|
|
2336
2243
|
"Play all"
|
|
2337
2244
|
]
|
|
2338
2245
|
}
|
|
2339
2246
|
),
|
|
2340
|
-
/* @__PURE__ */ (0,
|
|
2247
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2341
2248
|
"button",
|
|
2342
2249
|
{
|
|
2343
2250
|
type: "button",
|
|
@@ -2352,26 +2259,28 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
|
2352
2259
|
sort.ordering
|
|
2353
2260
|
)) })
|
|
2354
2261
|
] }),
|
|
2355
|
-
/* @__PURE__ */ (0,
|
|
2262
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2356
2263
|
TrackItem,
|
|
2357
2264
|
{
|
|
2358
2265
|
track: song,
|
|
2359
2266
|
position: (page - 1) * data.pageSize + index + 1,
|
|
2360
|
-
renderArtistLink
|
|
2267
|
+
renderArtistLink,
|
|
2268
|
+
renderSongLink
|
|
2361
2269
|
}
|
|
2362
2270
|
) }, song.uuid)) }),
|
|
2363
|
-
/* @__PURE__ */ (0,
|
|
2271
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui13.Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
|
|
2364
2272
|
] }) });
|
|
2365
2273
|
}
|
|
2366
2274
|
|
|
2367
2275
|
// src/genre-tree/artist/useArtistPanel.tsx
|
|
2368
|
-
var
|
|
2276
|
+
var import_jsx_runtime36 = require("react/jsx-runtime");
|
|
2369
2277
|
function useArtistPanel({
|
|
2370
2278
|
uuid,
|
|
2371
2279
|
scope,
|
|
2372
2280
|
getBackendBaseUrl,
|
|
2373
2281
|
getShareUrl,
|
|
2374
2282
|
renderArtistLink,
|
|
2283
|
+
renderSongLink,
|
|
2375
2284
|
expanded,
|
|
2376
2285
|
onExpandedChange,
|
|
2377
2286
|
onClose
|
|
@@ -2385,26 +2294,26 @@ function useArtistPanel({
|
|
|
2385
2294
|
return {
|
|
2386
2295
|
...panel,
|
|
2387
2296
|
title: "Artist",
|
|
2388
|
-
content: /* @__PURE__ */ (0,
|
|
2389
|
-
/* @__PURE__ */ (0,
|
|
2390
|
-
/* @__PURE__ */ (0,
|
|
2391
|
-
/* @__PURE__ */ (0,
|
|
2392
|
-
/* @__PURE__ */ (0,
|
|
2393
|
-
/* @__PURE__ */ (0,
|
|
2394
|
-
/* @__PURE__ */ (0,
|
|
2297
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
|
|
2298
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui14.Skeleton, { className: "h-3 w-16" }),
|
|
2299
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui14.Skeleton, { className: "h-4 w-32" }),
|
|
2300
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui14.Skeleton, { className: "h-3 w-16" }),
|
|
2301
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui14.Skeleton, { className: "h-4 w-24" }),
|
|
2302
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui14.Skeleton, { className: "h-16 mt-2.5" }),
|
|
2303
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui14.Skeleton, { className: "h-16" })
|
|
2395
2304
|
] })
|
|
2396
2305
|
};
|
|
2397
2306
|
case "not-found":
|
|
2398
2307
|
return {
|
|
2399
2308
|
...panel,
|
|
2400
2309
|
title: "Artist not found",
|
|
2401
|
-
content: /* @__PURE__ */ (0,
|
|
2310
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This artist does not exist." })
|
|
2402
2311
|
};
|
|
2403
2312
|
case "error":
|
|
2404
2313
|
return {
|
|
2405
2314
|
...panel,
|
|
2406
2315
|
title: "Artist",
|
|
2407
|
-
content: /* @__PURE__ */ (0,
|
|
2316
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
|
|
2408
2317
|
};
|
|
2409
2318
|
case "success": {
|
|
2410
2319
|
const { artist } = result;
|
|
@@ -2413,63 +2322,251 @@ function useArtistPanel({
|
|
|
2413
2322
|
return {
|
|
2414
2323
|
...panel,
|
|
2415
2324
|
title: artist.name,
|
|
2416
|
-
content: /* @__PURE__ */ (0,
|
|
2417
|
-
shareUrl && /* @__PURE__ */ (0,
|
|
2418
|
-
artist.image && /* @__PURE__ */ (0,
|
|
2419
|
-
/* @__PURE__ */ (0,
|
|
2420
|
-
/* @__PURE__ */ (0,
|
|
2421
|
-
/* @__PURE__ */ (0,
|
|
2422
|
-
artist.tracksCount > 0 && /* @__PURE__ */ (0,
|
|
2325
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_jsx_runtime36.Fragment, { children: [
|
|
2326
|
+
shareUrl && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: shareTarget && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl }) }) }),
|
|
2327
|
+
artist.image && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ArtistPhoto, { image: artist.image, name: artist.name }),
|
|
2328
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ArtistHeader, { artist }),
|
|
2329
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
|
|
2330
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
|
|
2331
|
+
artist.tracksCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
|
|
2423
2332
|
ArtistSongs,
|
|
2424
2333
|
{
|
|
2425
2334
|
artist: { uuid: artist.uuid, name: artist.name },
|
|
2426
2335
|
getBackendBaseUrl,
|
|
2427
|
-
renderArtistLink
|
|
2336
|
+
renderArtistLink,
|
|
2337
|
+
renderSongLink
|
|
2428
2338
|
}
|
|
2429
2339
|
)
|
|
2430
2340
|
] }),
|
|
2431
|
-
extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0,
|
|
2341
|
+
extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ArtistDiscography, { albums: artist.albums }) : void 0
|
|
2432
2342
|
};
|
|
2433
2343
|
}
|
|
2434
2344
|
}
|
|
2435
2345
|
}
|
|
2436
2346
|
|
|
2437
|
-
// src/genre-tree/
|
|
2438
|
-
var
|
|
2347
|
+
// src/genre-tree/song/useSongPanel.tsx
|
|
2348
|
+
var import_ui16 = require("@behindthemusictree/ui");
|
|
2349
|
+
var import_genre_tree17 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2350
|
+
|
|
2351
|
+
// src/genre-tree/song/SongPanelContent.tsx
|
|
2352
|
+
var import_react15 = require("react");
|
|
2353
|
+
var import_lucide_react14 = require("lucide-react");
|
|
2439
2354
|
var import_ui15 = require("@behindthemusictree/ui");
|
|
2355
|
+
var import_player11 = require("@behindthemusictree/app-kit/player");
|
|
2356
|
+
var import_genre_tree16 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2357
|
+
var import_jsx_runtime37 = require("react/jsx-runtime");
|
|
2358
|
+
var numberFormat = new Intl.NumberFormat("en");
|
|
2359
|
+
var dateFormat = new Intl.DateTimeFormat("en", { dateStyle: "medium" });
|
|
2360
|
+
var externalLink = "text-[var(--color-primary)] hover:underline focus-visible:underline";
|
|
2361
|
+
function SongHistory({ uuid, getBackendBaseUrl }) {
|
|
2362
|
+
const [open, setOpen] = (0, import_react15.useState)(false);
|
|
2363
|
+
const history = (0, import_genre_tree16.useSongHistory)(uuid, getBackendBaseUrl, open);
|
|
2364
|
+
let body = null;
|
|
2365
|
+
if (history.status === "loading") body = /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui15.Skeleton, { className: "h-10", "aria-busy": "true" });
|
|
2366
|
+
else if (history.status === "error") body = /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "text-red-600", children: "Could not load the history." });
|
|
2367
|
+
else if (history.status === "success")
|
|
2368
|
+
body = history.entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("li", { className: "py-1.5 text-sm", children: [
|
|
2369
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "font-medium", children: entry.action }),
|
|
2370
|
+
entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
|
|
2371
|
+
" ",
|
|
2372
|
+
"\u2014 ",
|
|
2373
|
+
entry.oldValue ?? "none",
|
|
2374
|
+
" \u2192 ",
|
|
2375
|
+
entry.newValue ?? "none"
|
|
2376
|
+
] }) : null,
|
|
2377
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { className: "block text-xs text-[var(--color-text-muted)]", children: [
|
|
2378
|
+
entry.actorPseudo ?? "Anonymous",
|
|
2379
|
+
" \xB7 ",
|
|
2380
|
+
dateFormat.format(new Date(entry.createdOn))
|
|
2381
|
+
] })
|
|
2382
|
+
] }, entry.uuid)) });
|
|
2383
|
+
return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
|
|
2384
|
+
"details",
|
|
2385
|
+
{
|
|
2386
|
+
onToggle: (event) => setOpen(event.currentTarget.open),
|
|
2387
|
+
className: "px-6 pb-2.5 mt-2.5 border-t border-[var(--color-border)]",
|
|
2388
|
+
children: [
|
|
2389
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)("summary", { className: "mt-2.5 cursor-pointer text-[0.6875rem] font-semibold uppercase text-[var(--color-text-muted)]", children: "History" }),
|
|
2390
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "mt-1.5", children: body })
|
|
2391
|
+
]
|
|
2392
|
+
}
|
|
2393
|
+
);
|
|
2394
|
+
}
|
|
2395
|
+
function SongPanelContent({
|
|
2396
|
+
song,
|
|
2397
|
+
scope,
|
|
2398
|
+
getBackendBaseUrl,
|
|
2399
|
+
shareUrl,
|
|
2400
|
+
renderArtistLink,
|
|
2401
|
+
genrePath,
|
|
2402
|
+
onSelectGenre
|
|
2403
|
+
}) {
|
|
2404
|
+
const { playNewTrackListFromTrackUuid } = (0, import_genre_tree16.useTrackList)();
|
|
2405
|
+
const { playerTrackObject, playState, handlePlayPauseAction } = (0, import_player11.usePlayer)();
|
|
2406
|
+
const unplayableReason = (0, import_genre_tree16.songUnplayableReason)(song);
|
|
2407
|
+
const isCurrent = playerTrackObject?.track.id === song.uuid;
|
|
2408
|
+
const isPlaying = isCurrent && playState === import_player11.PlayStates.PLAYING;
|
|
2409
|
+
const shareTarget = { kind: "song", uuid: song.uuid, name: song.title, scope };
|
|
2410
|
+
const musicBrainzUrl = (0, import_genre_tree16.musicBrainzRecordingUrl)(song);
|
|
2411
|
+
const fields = [];
|
|
2412
|
+
if (song.genre) {
|
|
2413
|
+
const path = genrePath.length > 0 ? genrePath : null;
|
|
2414
|
+
fields.push({
|
|
2415
|
+
label: "Genre",
|
|
2416
|
+
value: path ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { children: [
|
|
2417
|
+
index > 0 && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { "aria-hidden": "true", children: "\u203A " }),
|
|
2418
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)("button", { type: "button", onClick: () => onSelectGenre(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
|
|
2419
|
+
] }, genrePlaylist.uuid)) }) : song.genre.name
|
|
2420
|
+
});
|
|
2421
|
+
} else {
|
|
2422
|
+
fields.push({ label: "Genre", value: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
|
|
2423
|
+
}
|
|
2424
|
+
if (song.firstReleaseYear) fields.push({ label: "First released", value: song.firstReleaseYear });
|
|
2425
|
+
const viewCount = song.youtubeVideo?.viewCount;
|
|
2426
|
+
if (viewCount !== null && viewCount !== void 0)
|
|
2427
|
+
fields.push({ label: "YouTube views", value: numberFormat.format(viewCount) });
|
|
2428
|
+
fields.push({ label: "Plays", value: numberFormat.format(song.playCount) });
|
|
2429
|
+
const links = [];
|
|
2430
|
+
if (musicBrainzUrl)
|
|
2431
|
+
links.push(
|
|
2432
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
|
|
2433
|
+
);
|
|
2434
|
+
if (song.youtubeVideo)
|
|
2435
|
+
links.push(
|
|
2436
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
|
|
2437
|
+
"a",
|
|
2438
|
+
{
|
|
2439
|
+
href: `https://www.youtube.com/watch?v=${encodeURIComponent(song.youtubeVideo.id)}`,
|
|
2440
|
+
target: "_blank",
|
|
2441
|
+
rel: "noopener noreferrer",
|
|
2442
|
+
className: externalLink,
|
|
2443
|
+
children: "YouTube"
|
|
2444
|
+
},
|
|
2445
|
+
"yt"
|
|
2446
|
+
)
|
|
2447
|
+
);
|
|
2448
|
+
if (links.length > 0)
|
|
2449
|
+
fields.push({
|
|
2450
|
+
label: "Links",
|
|
2451
|
+
value: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "flex gap-3", children: links })
|
|
2452
|
+
});
|
|
2453
|
+
return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(import_jsx_runtime37.Fragment, { children: [
|
|
2454
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
|
|
2455
|
+
unplayableReason ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "flex items-center px-2.5 text-sm text-[var(--color-text-muted)]", children: unplayableReason }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
|
|
2456
|
+
"button",
|
|
2457
|
+
{
|
|
2458
|
+
type: "button",
|
|
2459
|
+
onClick: () => isCurrent ? handlePlayPauseAction() : playNewTrackListFromTrackUuid(song, scope),
|
|
2460
|
+
className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none",
|
|
2461
|
+
children: [
|
|
2462
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "flex items-center justify-center w-10 h-10 rounded-full group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)] bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]", children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react14.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react14.Play, { "aria-hidden": "true", className: "w-5 h-5" }) }),
|
|
2463
|
+
isPlaying ? "Pause" : "Play"
|
|
2464
|
+
]
|
|
2465
|
+
}
|
|
2466
|
+
),
|
|
2467
|
+
shareUrl && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
|
|
2468
|
+
] }) }),
|
|
2469
|
+
song.artists && song.artists.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "px-6 pt-2.5 text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_genre_tree16.ArtistNames, { artists: song.artists, renderArtistLink }) }),
|
|
2470
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui15.FieldList, { items: fields }),
|
|
2471
|
+
song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui15.PanelSection, { title: "Essential in", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
|
|
2472
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
|
|
2473
|
+
] });
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2476
|
+
// src/genre-tree/song/useSongPanel.tsx
|
|
2440
2477
|
var import_jsx_runtime38 = require("react/jsx-runtime");
|
|
2478
|
+
function useSongPanel({
|
|
2479
|
+
uuid,
|
|
2480
|
+
scope,
|
|
2481
|
+
getBackendBaseUrl,
|
|
2482
|
+
getShareUrl,
|
|
2483
|
+
renderArtistLink,
|
|
2484
|
+
getGenrePath,
|
|
2485
|
+
onSelectGenre,
|
|
2486
|
+
expanded,
|
|
2487
|
+
onExpandedChange,
|
|
2488
|
+
onClose
|
|
2489
|
+
}) {
|
|
2490
|
+
const result = (0, import_genre_tree17.useSong)(uuid, getBackendBaseUrl);
|
|
2491
|
+
const panel = { expanded, onExpandedChange, onClose };
|
|
2492
|
+
switch (result.status) {
|
|
2493
|
+
case "idle":
|
|
2494
|
+
return void 0;
|
|
2495
|
+
case "loading":
|
|
2496
|
+
return {
|
|
2497
|
+
...panel,
|
|
2498
|
+
title: "Song",
|
|
2499
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
|
|
2500
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_ui16.Skeleton, { className: "h-3 w-16" }),
|
|
2501
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_ui16.Skeleton, { className: "h-4 w-32" }),
|
|
2502
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_ui16.Skeleton, { className: "h-3 w-16" }),
|
|
2503
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_ui16.Skeleton, { className: "h-4 w-24" })
|
|
2504
|
+
] })
|
|
2505
|
+
};
|
|
2506
|
+
case "not-found":
|
|
2507
|
+
return {
|
|
2508
|
+
...panel,
|
|
2509
|
+
title: "Song not found",
|
|
2510
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This song does not exist." })
|
|
2511
|
+
};
|
|
2512
|
+
case "error":
|
|
2513
|
+
return {
|
|
2514
|
+
...panel,
|
|
2515
|
+
title: "Song",
|
|
2516
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this song." })
|
|
2517
|
+
};
|
|
2518
|
+
case "success": {
|
|
2519
|
+
const { song } = result;
|
|
2520
|
+
const shareUrl = getShareUrl?.({ kind: "song", uuid: song.uuid, name: song.title, scope });
|
|
2521
|
+
return {
|
|
2522
|
+
...panel,
|
|
2523
|
+
title: song.title,
|
|
2524
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
|
|
2525
|
+
SongPanelContent,
|
|
2526
|
+
{
|
|
2527
|
+
song,
|
|
2528
|
+
scope,
|
|
2529
|
+
getBackendBaseUrl,
|
|
2530
|
+
shareUrl: shareUrl ?? void 0,
|
|
2531
|
+
renderArtistLink,
|
|
2532
|
+
genrePath: song.genre ? getGenrePath(song.genre) : [],
|
|
2533
|
+
onSelectGenre
|
|
2534
|
+
}
|
|
2535
|
+
)
|
|
2536
|
+
};
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2541
|
+
// src/genre-tree/SizeMetricToggle.tsx
|
|
2542
|
+
var import_md2 = require("react-icons/md");
|
|
2543
|
+
var import_ui17 = require("@behindthemusictree/ui");
|
|
2544
|
+
var import_jsx_runtime39 = require("react/jsx-runtime");
|
|
2441
2545
|
function SizeMetricToggle({
|
|
2442
2546
|
value,
|
|
2443
2547
|
onChange
|
|
2444
2548
|
}) {
|
|
2445
|
-
return /* @__PURE__ */ (0,
|
|
2446
|
-
|
|
2549
|
+
return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2550
|
+
import_ui17.SegmentedControl,
|
|
2447
2551
|
{
|
|
2448
2552
|
variant: "floating",
|
|
2449
2553
|
"aria-label": "Size nodes by",
|
|
2450
2554
|
value,
|
|
2451
2555
|
onChange,
|
|
2452
2556
|
options: [
|
|
2453
|
-
{ value: "songs", label: /* @__PURE__ */ (0,
|
|
2454
|
-
{ value: "views", label: /* @__PURE__ */ (0,
|
|
2557
|
+
{ value: "songs", label: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_md2.MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
|
|
2558
|
+
{ value: "views", label: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_md2.MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
|
|
2455
2559
|
]
|
|
2456
2560
|
}
|
|
2457
2561
|
);
|
|
2458
2562
|
}
|
|
2459
2563
|
|
|
2460
2564
|
// src/genre-tree/GenreTreeView.tsx
|
|
2461
|
-
var
|
|
2462
|
-
var PREVIOUS_BUTTON_CSS = `
|
|
2463
|
-
.genre-tree-view .search-overlay .gtv-info-panel-previous { display: none; }
|
|
2464
|
-
.genre-tree-view:has(.search-overlay .gtv-info-panel-previous) .genre-search input { padding-left: 2.75rem; }
|
|
2465
|
-
.genre-tree-view:has(.search-overlay .gtv-info-panel-previous) .genre-search-previous { display: flex; }
|
|
2466
|
-
@keyframes genre-tree-view-previous-in { from { opacity: 0; transform: translate(-0.5rem, -50%); } to { opacity: 1; transform: translate(0, -50%); } }
|
|
2467
|
-
@media (prefers-reduced-motion: no-preference) {
|
|
2468
|
-
.genre-search-previous { animation: genre-tree-view-previous-in 200ms ease-out; }
|
|
2469
|
-
}
|
|
2470
|
-
`;
|
|
2565
|
+
var import_jsx_runtime40 = require("react/jsx-runtime");
|
|
2471
2566
|
var NO_POP_ROOT_TITLE = "This genre tree has no 'Mainstream Pop' root yet";
|
|
2472
|
-
var REGIONS_CLASS = "[--gtv-center-left:var(--layout-center-left)] [--gtv-center-right:var(--layout-center-right)] [--gtv-center-top:var(--layout-band-top)]
|
|
2567
|
+
var REGIONS_CLASS = "[--gtv-center-left:var(--layout-center-left)] [--gtv-center-right:var(--layout-center-right)] [--gtv-center-top:var(--layout-band-top)]";
|
|
2568
|
+
var noop = () => {
|
|
2569
|
+
};
|
|
2473
2570
|
function GenreTreeView({
|
|
2474
2571
|
scope,
|
|
2475
2572
|
handleGenreCreationAction,
|
|
@@ -2502,6 +2599,13 @@ function GenreTreeView({
|
|
|
2502
2599
|
artistSearch = true,
|
|
2503
2600
|
onArtistSelect,
|
|
2504
2601
|
artistSearchBaseUrl,
|
|
2602
|
+
songUuid: controlledSongUuid,
|
|
2603
|
+
onSongUuidChange,
|
|
2604
|
+
songExpanded: controlledSongExpanded,
|
|
2605
|
+
onSongExpandedChange,
|
|
2606
|
+
songSearch = true,
|
|
2607
|
+
onSongSelect,
|
|
2608
|
+
renderSongLink,
|
|
2505
2609
|
renderSummary,
|
|
2506
2610
|
renderEssentialTracks,
|
|
2507
2611
|
renderSongLeading,
|
|
@@ -2516,14 +2620,21 @@ function GenreTreeView({
|
|
|
2516
2620
|
placement = "default",
|
|
2517
2621
|
...focus
|
|
2518
2622
|
}) {
|
|
2519
|
-
const getShareUrlRef = (0,
|
|
2623
|
+
const getShareUrlRef = (0, import_react16.useRef)(getShareUrl);
|
|
2520
2624
|
getShareUrlRef.current = getShareUrl;
|
|
2521
|
-
const [internalArtistUuid, setInternalArtistUuid] = (0,
|
|
2625
|
+
const [internalArtistUuid, setInternalArtistUuid] = (0, import_react16.useState)(null);
|
|
2522
2626
|
const isArtistControlled = controlledArtistUuid !== void 0;
|
|
2523
2627
|
const artistUuid = isArtistControlled ? controlledArtistUuid : internalArtistUuid;
|
|
2524
|
-
const [
|
|
2628
|
+
const [internalSongUuid, setInternalSongUuid] = (0, import_react16.useState)(null);
|
|
2629
|
+
const isSongControlled = controlledSongUuid !== void 0;
|
|
2630
|
+
const songUuid = isSongControlled ? controlledSongUuid : internalSongUuid;
|
|
2631
|
+
const changeSong = (uuid) => {
|
|
2632
|
+
if (!isSongControlled) setInternalSongUuid(uuid);
|
|
2633
|
+
onSongUuidChange?.(uuid);
|
|
2634
|
+
};
|
|
2635
|
+
const [internalSizeMetric, setInternalSizeMetric] = (0, import_react16.useState)("songs");
|
|
2525
2636
|
const sizeMetric = controlledSizeMetric ?? internalSizeMetric;
|
|
2526
|
-
const deferredSizeMetric = (0,
|
|
2637
|
+
const deferredSizeMetric = (0, import_react16.useDeferredValue)(sizeMetric);
|
|
2527
2638
|
const changeSizeMetric = (metric) => {
|
|
2528
2639
|
if (controlledSizeMetric === void 0) setInternalSizeMetric(metric);
|
|
2529
2640
|
onSizeMetricChange?.(metric);
|
|
@@ -2550,7 +2661,7 @@ function GenreTreeView({
|
|
|
2550
2661
|
allowWheelRotation: internalAllowWheelRotation,
|
|
2551
2662
|
toggleWheelRotation,
|
|
2552
2663
|
onNodeHover: handleNodeHover
|
|
2553
|
-
} = (0,
|
|
2664
|
+
} = (0, import_genre_tree19.useGenreTreeViewState)({
|
|
2554
2665
|
scope,
|
|
2555
2666
|
getBackendBaseUrl,
|
|
2556
2667
|
viewMode: controlledViewMode,
|
|
@@ -2560,7 +2671,7 @@ function GenreTreeView({
|
|
|
2560
2671
|
artistUuid
|
|
2561
2672
|
});
|
|
2562
2673
|
const restoreRevision = viewportRestore?.revision;
|
|
2563
|
-
const [stackedRestoreOwner, setStackedRestoreOwner] = (0,
|
|
2674
|
+
const [stackedRestoreOwner, setStackedRestoreOwner] = (0, import_react16.useState)({ revision: restoreRevision, rootUuid: selectedGenrePlaylist?.root.uuid });
|
|
2564
2675
|
let restoreRootUuid = stackedRestoreOwner.rootUuid;
|
|
2565
2676
|
if (stackedRestoreOwner.revision !== restoreRevision) {
|
|
2566
2677
|
restoreRootUuid = selectedGenrePlaylist?.root.uuid;
|
|
@@ -2569,14 +2680,14 @@ function GenreTreeView({
|
|
|
2569
2680
|
restoreRootUuid = selectedGenrePlaylist.root.uuid;
|
|
2570
2681
|
setStackedRestoreOwner({ revision: restoreRevision, rootUuid: restoreRootUuid });
|
|
2571
2682
|
}
|
|
2572
|
-
const isGenreUpdating = (0, import_react_query3.useIsMutating)({ mutationKey:
|
|
2573
|
-
const { mutate: updateGenre } = (0,
|
|
2683
|
+
const isGenreUpdating = (0, import_react_query3.useIsMutating)({ mutationKey: import_genre_tree19.genreUpdateMutationKey }) > 0;
|
|
2684
|
+
const { mutate: updateGenre } = (0, import_genre_tree19.useUpdateGenre)(scope, getBackendBaseUrl);
|
|
2574
2685
|
const busy = deferredSizeMetric !== sizeMetric || isTreeFetching || isGenreUpdating;
|
|
2575
2686
|
const isArtistFiltered = artistUuid !== null;
|
|
2576
2687
|
const canSizeByViews = !isArtistFiltered && hasViewsCount(visibleGenrePlaylists);
|
|
2577
|
-
const artistResult = (0,
|
|
2688
|
+
const artistResult = (0, import_genre_tree19.useArtist)(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
|
|
2578
2689
|
const filterArtistName = artistResult.status === "success" ? artistResult.artist.name : null;
|
|
2579
|
-
const filterArtist = (0,
|
|
2690
|
+
const filterArtist = (0, import_react16.useMemo)(
|
|
2580
2691
|
() => artistUuid !== null && filterArtistName !== null ? { uuid: artistUuid, name: filterArtistName } : null,
|
|
2581
2692
|
[artistUuid, filterArtistName]
|
|
2582
2693
|
);
|
|
@@ -2584,27 +2695,71 @@ function GenreTreeView({
|
|
|
2584
2695
|
const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: visibleGenrePlaylists, artist: filterArtist });
|
|
2585
2696
|
const openArtist = (uuid) => {
|
|
2586
2697
|
if (selectedNodeId !== null) clearSelection();
|
|
2698
|
+
if (songUuid !== null) changeSong(null);
|
|
2587
2699
|
changeArtist(uuid);
|
|
2588
2700
|
};
|
|
2589
|
-
const
|
|
2701
|
+
const openSong = (uuid) => {
|
|
2702
|
+
if (selectedNodeId !== null) clearSelection();
|
|
2703
|
+
changeSong(uuid);
|
|
2704
|
+
};
|
|
2705
|
+
const openSongRef = (0, import_react16.useRef)(openSong);
|
|
2706
|
+
openSongRef.current = openSong;
|
|
2707
|
+
const resolvedRenderSongLink = (0, import_react16.useCallback)(
|
|
2708
|
+
(song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
|
|
2709
|
+
[renderSongLink]
|
|
2710
|
+
);
|
|
2711
|
+
const [expandedSongUuid, setExpandedSongUuid] = (0, import_react16.useState)(null);
|
|
2712
|
+
const [expandedArtistUuid, setExpandedArtistUuid] = (0, import_react16.useState)(null);
|
|
2590
2713
|
const artistBaseUrl = artistSearchBaseUrl ?? getBackendBaseUrl;
|
|
2714
|
+
const songBaseUrl = getBackendBaseUrl;
|
|
2591
2715
|
const artistPanel = useArtistPanel({
|
|
2592
2716
|
uuid: artistUuid,
|
|
2593
2717
|
scope,
|
|
2594
2718
|
getBackendBaseUrl: artistBaseUrl,
|
|
2595
2719
|
getShareUrl: getShareUrlRef.current,
|
|
2596
|
-
renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0,
|
|
2720
|
+
renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
|
|
2597
2721
|
expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
|
|
2598
2722
|
onExpandedChange: (on) => {
|
|
2599
2723
|
if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
|
|
2600
2724
|
onArtistExpandedChange?.(on);
|
|
2601
2725
|
},
|
|
2726
|
+
renderSongLink: resolvedRenderSongLink,
|
|
2602
2727
|
onClose: () => changeArtist(null)
|
|
2603
2728
|
});
|
|
2604
|
-
const
|
|
2605
|
-
const
|
|
2606
|
-
|
|
2607
|
-
|
|
2729
|
+
const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
|
|
2730
|
+
const getGenrePath = (genre) => {
|
|
2731
|
+
const path = [];
|
|
2732
|
+
let current = visibleGenrePlaylists.find((playlist) => playlist.criteria?.uuid === genre.uuid);
|
|
2733
|
+
while (current) {
|
|
2734
|
+
path.unshift(current);
|
|
2735
|
+
const parentUuid = current.parent?.uuid;
|
|
2736
|
+
current = parentUuid ? visibleGenrePlaylists.find((playlist) => playlist.uuid === parentUuid) : void 0;
|
|
2737
|
+
}
|
|
2738
|
+
return path;
|
|
2739
|
+
};
|
|
2740
|
+
const songPanel = useSongPanel({
|
|
2741
|
+
uuid: songUuid,
|
|
2742
|
+
scope,
|
|
2743
|
+
getBackendBaseUrl: songBaseUrl,
|
|
2744
|
+
getShareUrl: getShareUrlRef.current,
|
|
2745
|
+
renderArtistLink: resolvedRenderArtistLink,
|
|
2746
|
+
getGenrePath,
|
|
2747
|
+
onSelectGenre: (genrePlaylist) => {
|
|
2748
|
+
changeSong(null);
|
|
2749
|
+
selectGenrePlaylist(genrePlaylist);
|
|
2750
|
+
},
|
|
2751
|
+
expanded: controlledSongExpanded ?? (songUuid !== null && expandedSongUuid === songUuid),
|
|
2752
|
+
onExpandedChange: (on) => {
|
|
2753
|
+
if (controlledSongExpanded === void 0) setExpandedSongUuid(on ? songUuid : null);
|
|
2754
|
+
onSongExpandedChange?.(on);
|
|
2755
|
+
},
|
|
2756
|
+
onClose: () => changeSong(null)
|
|
2757
|
+
});
|
|
2758
|
+
const builtInSongPanel = overlayPanel || hasSelectedGenreNode ? void 0 : songPanel;
|
|
2759
|
+
const builtInArtistPanel = overlayPanel || hasSelectedGenreNode || builtInSongPanel ? void 0 : artistPanel;
|
|
2760
|
+
const builtInPanel = builtInSongPanel ?? builtInArtistPanel;
|
|
2761
|
+
const [uncontrolledCollapsed, setUncontrolledCollapsed] = (0, import_react16.useState)(false);
|
|
2762
|
+
const [uncontrolledExpanded, setUncontrolledExpanded] = (0, import_react16.useState)(false);
|
|
2608
2763
|
const isCollapsed = collapsed ?? uncontrolledCollapsed;
|
|
2609
2764
|
const isExpanded = expanded ?? uncontrolledExpanded;
|
|
2610
2765
|
const changeCollapsed = (next) => {
|
|
@@ -2615,54 +2770,56 @@ function GenreTreeView({
|
|
|
2615
2770
|
setUncontrolledExpanded(next);
|
|
2616
2771
|
onExpandedChange?.(next);
|
|
2617
2772
|
};
|
|
2618
|
-
(0,
|
|
2773
|
+
(0, import_react16.useEffect)(() => {
|
|
2619
2774
|
setUncontrolledCollapsed(false);
|
|
2620
|
-
}, [selectedNodeId, artistUuid]);
|
|
2621
|
-
const
|
|
2622
|
-
useLeftDetailPanelLayout(!isDetailHidden, placement === "regions");
|
|
2623
|
-
const detailOverlay = placement === "regions" && activeOverlayPanel?.onExpandedChange ? activeOverlayPanel : void 0;
|
|
2624
|
-
const detailExpanded = detailOverlay ? !!detailOverlay.expanded : isExpanded;
|
|
2625
|
-
const onDetailExpandedChange = detailOverlay ? detailOverlay.onExpandedChange : changeExpanded;
|
|
2626
|
-
const infoPanelCuration = {
|
|
2627
|
-
renderDangerZone,
|
|
2628
|
-
expanded: placement === "regions" ? isExpanded : expanded,
|
|
2629
|
-
onExpandedChange: placement === "regions" ? changeExpanded : onExpandedChange,
|
|
2630
|
-
collapsed: placement === "regions" ? isCollapsed : void 0,
|
|
2631
|
-
hideExpandButton: placement === "regions" ? true : void 0,
|
|
2632
|
-
overlayPanel: detailOverlay ? { ...detailOverlay, collapsed: isCollapsed, hideExpandButton: true } : activeOverlayPanel
|
|
2633
|
-
};
|
|
2634
|
-
const [isWheelRevealed, setIsWheelRevealed] = (0, import_react15.useState)(false);
|
|
2775
|
+
}, [selectedNodeId, artistUuid, songUuid]);
|
|
2776
|
+
const [isWheelRevealed, setIsWheelRevealed] = (0, import_react16.useState)(false);
|
|
2635
2777
|
const usesHandoff = viewMode === "wheel" || viewMode === "pop-core";
|
|
2636
2778
|
const isTreeReady = !isLoading && (!usesHandoff || isWheelRevealed);
|
|
2637
|
-
const
|
|
2638
|
-
const
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2779
|
+
const [resolveStyle, setResolveStyle] = (0, import_react16.useState)(null);
|
|
2780
|
+
const handleNodeStyleResolverChange = (0, import_react16.useCallback)(
|
|
2781
|
+
(resolver) => setResolveStyle(() => resolver),
|
|
2782
|
+
[]
|
|
2783
|
+
);
|
|
2784
|
+
const panelRef = (0, import_react16.useRef)(null);
|
|
2785
|
+
const obscuredInset = (0, import_ui18.useObscuredInset)(panelRef);
|
|
2786
|
+
const hostPanel = { obscuredInset, onNodeStyleResolverChange: handleNodeStyleResolverChange };
|
|
2787
|
+
const nodeMetric = isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric;
|
|
2788
|
+
const treeNodes = (0, import_react16.useMemo)(() => toTreeNodes(visibleGenrePlaylists, nodeMetric), [visibleGenrePlaylists, nodeMetric]);
|
|
2789
|
+
const relations = (0, import_genre_tree_view4.useGenreNodeRelations)(
|
|
2790
|
+
treeNodes,
|
|
2791
|
+
!overlayPanel && hasSelectedGenreNode ? selectedNodeId : null,
|
|
2792
|
+
resolveStyle
|
|
2793
|
+
);
|
|
2794
|
+
const [selectionHistory, setSelectionHistory] = (0, import_react16.useState)({
|
|
2795
|
+
current: selectedNodeId,
|
|
2796
|
+
previous: null
|
|
2797
|
+
});
|
|
2798
|
+
if (selectionHistory.current !== selectedNodeId) {
|
|
2799
|
+
setSelectionHistory({
|
|
2800
|
+
current: selectedNodeId,
|
|
2801
|
+
previous: selectionHistory.current ?? selectionHistory.previous
|
|
2802
|
+
});
|
|
2803
|
+
}
|
|
2804
|
+
const previousPlaylist = selectionHistory.previous !== null && selectionHistory.previous !== selectedNodeId ? genrePlaylistByNodeId.get(selectionHistory.previous) : void 0;
|
|
2805
|
+
const handlePrevious = previousPlaylist && visibleGenrePlaylists.includes(previousPlaylist) ? () => handleGenreSearchSelect(previousPlaylist) : void 0;
|
|
2650
2806
|
const { showPopup, hidePopup } = (0, import_popup6.usePopup)();
|
|
2651
|
-
const [searchText, setSearchText] = (0,
|
|
2652
|
-
const artistSearchResult = (0,
|
|
2653
|
-
const
|
|
2807
|
+
const [searchText, setSearchText] = (0, import_react16.useState)("");
|
|
2808
|
+
const artistSearchResult = (0, import_genre_tree18.useArtistSearch)(artistBaseUrl, artistSearch ? searchText : "");
|
|
2809
|
+
const artistGroup = artistSearch ? {
|
|
2810
|
+
key: "artists",
|
|
2654
2811
|
label: "Artists",
|
|
2655
2812
|
items: artistSearchResult.artists.map((artist) => ({
|
|
2656
2813
|
key: artist.uuid,
|
|
2657
|
-
content: /* @__PURE__ */ (0,
|
|
2658
|
-
/* @__PURE__ */ (0,
|
|
2814
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
|
|
2815
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "block truncate font-medium", children: [
|
|
2659
2816
|
artist.name,
|
|
2660
|
-
artist.disambiguation && /* @__PURE__ */ (0,
|
|
2817
|
+
artist.disambiguation && /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "font-normal text-[var(--color-neutral-500)]", children: [
|
|
2661
2818
|
" \xB7 ",
|
|
2662
2819
|
artist.disambiguation
|
|
2663
2820
|
] })
|
|
2664
2821
|
] }),
|
|
2665
|
-
artist.matchedAlias && /* @__PURE__ */ (0,
|
|
2822
|
+
artist.matchedAlias && /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [
|
|
2666
2823
|
"also known as ",
|
|
2667
2824
|
artist.matchedAlias
|
|
2668
2825
|
] })
|
|
@@ -2675,11 +2832,32 @@ function GenreTreeView({
|
|
|
2675
2832
|
status: artistSearchResult.isError ? "Search failed" : artistSearchResult.settled && artistSearchResult.artists.length === 0 ? "No artists found" : "",
|
|
2676
2833
|
loading: artistSearchResult.pending
|
|
2677
2834
|
} : void 0;
|
|
2835
|
+
const songSearchResult = (0, import_genre_tree18.useSongSearch)(songBaseUrl, songSearch ? searchText : "");
|
|
2836
|
+
const songGroup = songSearch ? {
|
|
2837
|
+
key: "songs",
|
|
2838
|
+
label: "Songs",
|
|
2839
|
+
items: songSearchResult.songs.map((song) => ({
|
|
2840
|
+
key: song.uuid,
|
|
2841
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: (0, import_genre_tree18.isSongSearchResultPlayable)(song) ? void 0 : "opacity-60", children: [
|
|
2842
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "block truncate font-medium", children: song.title }),
|
|
2843
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.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 ") })
|
|
2844
|
+
] }),
|
|
2845
|
+
onSelect: () => {
|
|
2846
|
+
openSong(song.uuid);
|
|
2847
|
+
onSongSelect?.(song);
|
|
2848
|
+
}
|
|
2849
|
+
})),
|
|
2850
|
+
status: songSearchResult.isError ? "Search failed" : songSearchResult.settled && songSearchResult.songs.length === 0 ? "No songs found" : "",
|
|
2851
|
+
loading: songSearchResult.pending
|
|
2852
|
+
} : void 0;
|
|
2853
|
+
const extraGroups = [artistGroup, songGroup].filter((group) => group !== void 0);
|
|
2678
2854
|
const handleSelectedNodeChange = (node) => {
|
|
2855
|
+
if (node !== null && songUuid !== null) changeSong(null);
|
|
2679
2856
|
selectNode(node);
|
|
2680
2857
|
};
|
|
2681
2858
|
const handleGenreSearchClear = clearSelection;
|
|
2682
2859
|
const handleGenreSearchSelect = (genrePlaylist) => {
|
|
2860
|
+
if (songUuid !== null) changeSong(null);
|
|
2683
2861
|
selectGenrePlaylist(genrePlaylist);
|
|
2684
2862
|
};
|
|
2685
2863
|
const selectedUuid = selectedGenrePlaylist?.uuid;
|
|
@@ -2688,36 +2866,49 @@ function GenreTreeView({
|
|
|
2688
2866
|
const handleGenreSearchZoomIn = () => {
|
|
2689
2867
|
if (selectedUuid) focus.onFocusGenreChange?.(selectedUuid, "zoom-in");
|
|
2690
2868
|
};
|
|
2691
|
-
const zoomInRef = (0,
|
|
2869
|
+
const zoomInRef = (0, import_react16.useRef)(void 0);
|
|
2692
2870
|
zoomInRef.current = canZoomIn ? handleGenreSearchZoomIn : void 0;
|
|
2693
|
-
const
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2871
|
+
const selectGenreFromPanel = (nodeId) => {
|
|
2872
|
+
const genrePlaylist = genrePlaylistByNodeId.get(nodeId);
|
|
2873
|
+
if (genrePlaylist) handleGenreSearchSelect(genrePlaylist);
|
|
2874
|
+
};
|
|
2875
|
+
const renderGenreBody = (relation) => {
|
|
2876
|
+
const { node } = relation;
|
|
2877
|
+
const family = /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2878
|
+
import_genre_tree_view4.GenreNodeFamily,
|
|
2879
|
+
{
|
|
2880
|
+
node,
|
|
2881
|
+
fill: relation.fill,
|
|
2882
|
+
textColor: relation.textColor,
|
|
2883
|
+
parentNode: relation.parentNode,
|
|
2884
|
+
childNodes: relation.childNodes,
|
|
2885
|
+
onSelectNode: selectGenreFromPanel
|
|
2886
|
+
}
|
|
2887
|
+
);
|
|
2888
|
+
const ancestors = /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_genre_tree_view4.GenreNodeAncestors, { ancestorNodes: relation.ancestorNodes, onSelectNode: selectGenreFromPanel });
|
|
2889
|
+
const genrePlaylist = genrePlaylistByNodeId.get(node.id);
|
|
2890
|
+
const criteria = genrePlaylist?.criteria;
|
|
2891
|
+
if (!genrePlaylist || !criteria) return { primary: family, aside: ancestors };
|
|
2892
|
+
const genreUuid = criteria.uuid;
|
|
2893
|
+
const parentPlaylist = visibleGenrePlaylists.find(({ uuid }) => uuid === genrePlaylist.parent?.uuid);
|
|
2894
|
+
const canToggleSide = !readOnly && parentPlaylist !== void 0 && parentPlaylist.parent === null;
|
|
2895
|
+
const side = criteria.side ?? "core";
|
|
2896
|
+
const shareTarget = {
|
|
2897
|
+
kind: "genre",
|
|
2898
|
+
genrePlaylistUuid: genrePlaylist.uuid,
|
|
2899
|
+
name: criteria.name,
|
|
2900
|
+
scope,
|
|
2901
|
+
artistUuid
|
|
2902
|
+
};
|
|
2903
|
+
const shareUrl = getShareUrlRef.current?.(shareTarget) ?? void 0;
|
|
2904
|
+
const popSibling = canToggleSide && side === "core" ? visibleGenrePlaylists.find(
|
|
2905
|
+
(sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist.uuid && sibling.criteria?.side === "pop"
|
|
2906
|
+
) : void 0;
|
|
2907
|
+
return {
|
|
2908
|
+
primary: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
|
|
2909
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2718
2910
|
GenreInfoPanelBody,
|
|
2719
2911
|
{
|
|
2720
|
-
toolbar,
|
|
2721
2912
|
family,
|
|
2722
2913
|
genreUuid,
|
|
2723
2914
|
genrePlaylist,
|
|
@@ -2733,7 +2924,7 @@ function GenreTreeView({
|
|
|
2733
2924
|
onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
|
|
2734
2925
|
onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
|
|
2735
2926
|
onChangeParent: readOnly ? void 0 : () => showPopup(
|
|
2736
|
-
/* @__PURE__ */ (0,
|
|
2927
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2737
2928
|
ChangeParentPopup,
|
|
2738
2929
|
{
|
|
2739
2930
|
genrePlaylist: { ...genrePlaylist, criteria },
|
|
@@ -2754,6 +2945,7 @@ function GenreTreeView({
|
|
|
2754
2945
|
criteriaOverviewSchema,
|
|
2755
2946
|
renderGenreDetailExtras,
|
|
2756
2947
|
renderArtistLink,
|
|
2948
|
+
renderSongLink: resolvedRenderSongLink,
|
|
2757
2949
|
artist: filterArtist,
|
|
2758
2950
|
renderSummary,
|
|
2759
2951
|
renderEssentialTracks,
|
|
@@ -2763,48 +2955,65 @@ function GenreTreeView({
|
|
|
2763
2955
|
}
|
|
2764
2956
|
}
|
|
2765
2957
|
),
|
|
2766
|
-
|
|
2767
|
-
}
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
additionalActions,
|
|
2772
|
-
readOnly,
|
|
2773
|
-
handleGenreCreationAction,
|
|
2774
|
-
handleGenreRenameAction,
|
|
2775
|
-
showPopup,
|
|
2776
|
-
hidePopup,
|
|
2777
|
-
onParentChanged,
|
|
2778
|
-
visibleGenrePlaylists,
|
|
2779
|
-
updateGenre,
|
|
2780
|
-
playback,
|
|
2781
|
-
filterArtist,
|
|
2782
|
-
scope,
|
|
2783
|
-
getBackendBaseUrl,
|
|
2784
|
-
criteriaOverviewSchema,
|
|
2785
|
-
renderGenreDetailExtras,
|
|
2786
|
-
renderArtistLink,
|
|
2787
|
-
renderSummary,
|
|
2788
|
-
renderEssentialTracks,
|
|
2789
|
-
renderSongLeading,
|
|
2790
|
-
renderSongActions,
|
|
2791
|
-
renderTracksToolbar
|
|
2792
|
-
]
|
|
2793
|
-
);
|
|
2958
|
+
renderDangerZone?.(node)
|
|
2959
|
+
] }),
|
|
2960
|
+
aside: ancestors
|
|
2961
|
+
};
|
|
2962
|
+
};
|
|
2794
2963
|
const isRotationControlled = controlledAllowWheelRotation !== void 0;
|
|
2795
2964
|
const allowWheelRotation = controlledAllowWheelRotation ?? internalAllowWheelRotation;
|
|
2796
|
-
|
|
2797
|
-
|
|
2965
|
+
let activePanel = null;
|
|
2966
|
+
if (overlayPanel) {
|
|
2967
|
+
activePanel = {
|
|
2968
|
+
title: overlayPanel.title,
|
|
2969
|
+
content: overlayPanel.content,
|
|
2970
|
+
aside: overlayPanel.extraContent,
|
|
2971
|
+
expanded: overlayPanel.expanded ?? false,
|
|
2972
|
+
onExpandedChange: overlayPanel.onExpandedChange,
|
|
2973
|
+
onClose: overlayPanel.onClose,
|
|
2974
|
+
compact: false
|
|
2975
|
+
};
|
|
2976
|
+
} else if (relations) {
|
|
2977
|
+
const { primary, aside } = renderGenreBody(relations);
|
|
2978
|
+
activePanel = {
|
|
2979
|
+
title: relations.node.name,
|
|
2980
|
+
content: primary,
|
|
2981
|
+
aside,
|
|
2982
|
+
expanded: isExpanded,
|
|
2983
|
+
onExpandedChange: changeExpanded,
|
|
2984
|
+
onClose: clearSelection,
|
|
2985
|
+
compact: true,
|
|
2986
|
+
headerAccent: { background: relations.fill, color: relations.textColor }
|
|
2987
|
+
};
|
|
2988
|
+
} else if (builtInPanel) {
|
|
2989
|
+
activePanel = {
|
|
2990
|
+
title: builtInPanel.title,
|
|
2991
|
+
content: builtInPanel.content,
|
|
2992
|
+
aside: builtInPanel.extraContent,
|
|
2993
|
+
expanded: builtInPanel.expanded,
|
|
2994
|
+
onExpandedChange: builtInPanel.onExpandedChange,
|
|
2995
|
+
onClose: builtInPanel.onClose,
|
|
2996
|
+
compact: true
|
|
2997
|
+
};
|
|
2998
|
+
}
|
|
2999
|
+
const isPanelShown = isTreeReady && activePanel !== null;
|
|
3000
|
+
const detailTab = placement === "regions" && activePanel?.onExpandedChange ? {
|
|
3001
|
+
edge: activePanel.expanded ? { label: "Shrink panel", onClick: () => activePanel.onExpandedChange?.(false) } : isCollapsed ? null : { label: "Hide panel", onClick: () => changeCollapsed(true) },
|
|
3002
|
+
center: activePanel.expanded ? null : isCollapsed ? { label: "Show panel", onClick: () => changeCollapsed(false) } : activePanel.onExpandedChange ? { label: "Extend panel", onClick: () => activePanel.onExpandedChange?.(true) } : null
|
|
3003
|
+
} : void 0;
|
|
3004
|
+
const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
3005
|
+
import_ui18.IconButton,
|
|
2798
3006
|
{
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
3007
|
+
"aria-label": activePanel.expanded ? "Shrink panel" : "Extend panel",
|
|
3008
|
+
style: activePanel.headerAccent && { color: "inherit" },
|
|
3009
|
+
onClick: () => activePanel.onExpandedChange?.(!activePanel.expanded),
|
|
3010
|
+
children: activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react16.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react16.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
2802
3011
|
}
|
|
2803
3012
|
) : null;
|
|
2804
3013
|
const renderArtistFilter = () => {
|
|
2805
3014
|
if (isLoading || !filterArtist) return null;
|
|
2806
|
-
return /* @__PURE__ */ (0,
|
|
2807
|
-
/* @__PURE__ */ (0,
|
|
3015
|
+
return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
|
|
3016
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2808
3017
|
"button",
|
|
2809
3018
|
{
|
|
2810
3019
|
type: "button",
|
|
@@ -2816,7 +3025,7 @@ function GenreTreeView({
|
|
|
2816
3025
|
children: filterArtist.name
|
|
2817
3026
|
}
|
|
2818
3027
|
),
|
|
2819
|
-
/* @__PURE__ */ (0,
|
|
3028
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2820
3029
|
"button",
|
|
2821
3030
|
{
|
|
2822
3031
|
type: "button",
|
|
@@ -2828,23 +3037,23 @@ function GenreTreeView({
|
|
|
2828
3037
|
)
|
|
2829
3038
|
] });
|
|
2830
3039
|
};
|
|
2831
|
-
const actions = /* @__PURE__ */ (0,
|
|
2832
|
-
!isLoading && /* @__PURE__ */ (0,
|
|
3040
|
+
const actions = /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
|
|
3041
|
+
!isLoading && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2833
3042
|
GenreSearch,
|
|
2834
3043
|
{
|
|
2835
3044
|
genrePlaylists: visibleGenrePlaylists,
|
|
2836
3045
|
onSelect: handleGenreSearchSelect,
|
|
2837
|
-
selectedName: selectedGenrePlaylist?.name ??
|
|
2838
|
-
onClear: builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
|
|
3046
|
+
selectedName: selectedGenrePlaylist?.name ?? (typeof builtInPanel?.title === "string" ? builtInPanel.title : null),
|
|
3047
|
+
onClear: builtInSongPanel ? () => changeSong(null) : builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
|
|
2839
3048
|
onPrevious: handlePrevious,
|
|
2840
|
-
onQueryChange: artistSearch ? setSearchText : void 0,
|
|
2841
|
-
|
|
3049
|
+
onQueryChange: artistSearch || songSearch ? setSearchText : void 0,
|
|
3050
|
+
extraGroups
|
|
2842
3051
|
}
|
|
2843
3052
|
),
|
|
2844
|
-
!isLoading && filterArtist && /* @__PURE__ */ (0,
|
|
2845
|
-
/* @__PURE__ */ (0,
|
|
2846
|
-
!isLoading && !isControlled && /* @__PURE__ */ (0,
|
|
2847
|
-
|
|
3053
|
+
!isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
|
|
3054
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "contents max-md:flex max-md:w-full max-md:items-center max-md:gap-2 max-md:overflow-x-auto max-md:[scrollbar-width:none] max-md:[&>*]:shrink-0", children: [
|
|
3055
|
+
!isLoading && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
3056
|
+
import_ui18.SegmentedControl,
|
|
2848
3057
|
{
|
|
2849
3058
|
"aria-label": "Tree view mode",
|
|
2850
3059
|
value: viewMode,
|
|
@@ -2857,14 +3066,14 @@ function GenreTreeView({
|
|
|
2857
3066
|
]
|
|
2858
3067
|
}
|
|
2859
3068
|
),
|
|
2860
|
-
!isLoading && showDisplayOptions && /* @__PURE__ */ (0,
|
|
3069
|
+
!isLoading && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2861
3070
|
"div",
|
|
2862
3071
|
{
|
|
2863
3072
|
className: "flex items-center gap-1",
|
|
2864
3073
|
role: "group",
|
|
2865
3074
|
"aria-label": "Tree display options",
|
|
2866
|
-
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0,
|
|
2867
|
-
|
|
3075
|
+
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
3076
|
+
import_ui18.Button,
|
|
2868
3077
|
{
|
|
2869
3078
|
variant: allowWheelRotation ? "default" : "outline",
|
|
2870
3079
|
size: "sm",
|
|
@@ -2874,8 +3083,8 @@ function GenreTreeView({
|
|
|
2874
3083
|
)
|
|
2875
3084
|
}
|
|
2876
3085
|
),
|
|
2877
|
-
!isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0,
|
|
2878
|
-
|
|
3086
|
+
!isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
3087
|
+
import_ui18.IconTextButton,
|
|
2879
3088
|
{
|
|
2880
3089
|
icon: import_lucide_react15.Plus,
|
|
2881
3090
|
text: "Add root",
|
|
@@ -2884,42 +3093,34 @@ function GenreTreeView({
|
|
|
2884
3093
|
)
|
|
2885
3094
|
] })
|
|
2886
3095
|
] });
|
|
2887
|
-
return /* @__PURE__ */ (0,
|
|
3096
|
+
return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
|
|
2888
3097
|
"div",
|
|
2889
3098
|
{
|
|
2890
|
-
|
|
3099
|
+
"data-left-panel": isPanelShown && !isCollapsed ? void 0 : "hidden",
|
|
2891
3100
|
className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
|
|
2892
3101
|
children: [
|
|
2893
|
-
/* @__PURE__ */ (0,
|
|
2894
|
-
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
3102
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2895
3103
|
"div",
|
|
2896
3104
|
{
|
|
2897
3105
|
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"}`,
|
|
2898
3106
|
children: actions
|
|
2899
3107
|
}
|
|
2900
3108
|
),
|
|
2901
|
-
!isLoading && filterArtist && /* @__PURE__ */ (0,
|
|
3109
|
+
!isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2902
3110
|
"div",
|
|
2903
3111
|
{
|
|
2904
3112
|
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"}`,
|
|
2905
3113
|
children: renderArtistFilter()
|
|
2906
3114
|
}
|
|
2907
3115
|
),
|
|
2908
|
-
|
|
2909
|
-
"div",
|
|
2910
|
-
{
|
|
2911
|
-
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))]"}`,
|
|
2912
|
-
children: detailTab
|
|
2913
|
-
}
|
|
2914
|
-
),
|
|
2915
|
-
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
|
|
2916
|
-
!isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
|
|
2917
|
-
isLoading ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_genre_tree_view4.GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
|
|
3116
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
|
|
3117
|
+
!isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
|
|
3118
|
+
isLoading ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_genre_tree_view4.GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
|
|
2918
3119
|
// The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
|
|
2919
3120
|
// and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
|
|
2920
3121
|
// Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
|
|
2921
3122
|
// The panel's own Zoom in button is hidden: the search bar carries it.
|
|
2922
|
-
/* @__PURE__ */ (0,
|
|
3123
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2923
3124
|
GenrePlaylistTreeWheel,
|
|
2924
3125
|
{
|
|
2925
3126
|
scope,
|
|
@@ -2928,29 +3129,26 @@ function GenreTreeView({
|
|
|
2928
3129
|
handleGenreRenameAction,
|
|
2929
3130
|
additionalActions,
|
|
2930
3131
|
onSelectedNodeChange: handleSelectedNodeChange,
|
|
2931
|
-
hideInfoPanelClose: true,
|
|
2932
|
-
hideInfoPanelTitle: true,
|
|
2933
3132
|
onNodeHover: handleNodeHover,
|
|
2934
|
-
|
|
3133
|
+
...hostPanel,
|
|
2935
3134
|
playback,
|
|
2936
3135
|
selectedNodeId,
|
|
2937
3136
|
readOnly,
|
|
2938
3137
|
allowWheelRotation,
|
|
2939
3138
|
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2940
3139
|
busy,
|
|
2941
|
-
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0,
|
|
3140
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
2942
3141
|
floatingControlsLeading,
|
|
2943
|
-
canSizeByViews && /* @__PURE__ */ (0,
|
|
3142
|
+
canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2944
3143
|
] }) : void 0,
|
|
2945
3144
|
defaultViewport,
|
|
2946
3145
|
viewportRestore,
|
|
2947
|
-
onViewportChange
|
|
2948
|
-
...infoPanelCuration
|
|
3146
|
+
onViewportChange
|
|
2949
3147
|
}
|
|
2950
3148
|
) }) })
|
|
2951
3149
|
) : viewMode === "pop-core" ? (
|
|
2952
3150
|
// Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
|
|
2953
|
-
/* @__PURE__ */ (0,
|
|
3151
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2954
3152
|
GenrePlaylistTreeWheelRadialPopCore,
|
|
2955
3153
|
{
|
|
2956
3154
|
scope,
|
|
@@ -2959,28 +3157,25 @@ function GenreTreeView({
|
|
|
2959
3157
|
handleGenreRenameAction,
|
|
2960
3158
|
additionalActions,
|
|
2961
3159
|
onSelectedNodeChange: handleSelectedNodeChange,
|
|
2962
|
-
hideInfoPanelClose: true,
|
|
2963
|
-
hideInfoPanelTitle: true,
|
|
2964
3160
|
onNodeHover: handleNodeHover,
|
|
2965
|
-
|
|
3161
|
+
...hostPanel,
|
|
2966
3162
|
playback,
|
|
2967
3163
|
selectedNodeId,
|
|
2968
3164
|
readOnly,
|
|
2969
3165
|
allowWheelRotation,
|
|
2970
3166
|
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2971
3167
|
busy,
|
|
2972
|
-
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0,
|
|
3168
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
2973
3169
|
floatingControlsLeading,
|
|
2974
|
-
canSizeByViews && /* @__PURE__ */ (0,
|
|
3170
|
+
canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2975
3171
|
] }) : void 0,
|
|
2976
3172
|
defaultViewport,
|
|
2977
3173
|
viewportRestore,
|
|
2978
3174
|
onViewportChange,
|
|
2979
|
-
...infoPanelCuration,
|
|
2980
3175
|
...focus
|
|
2981
3176
|
}
|
|
2982
3177
|
) }) })
|
|
2983
|
-
) : viewMode === "outline" ? /* @__PURE__ */ (0,
|
|
3178
|
+
) : viewMode === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2984
3179
|
GenrePlaylistTreeWheelRadialPopCore,
|
|
2985
3180
|
{
|
|
2986
3181
|
outline: true,
|
|
@@ -2990,22 +3185,19 @@ function GenreTreeView({
|
|
|
2990
3185
|
handleGenreRenameAction,
|
|
2991
3186
|
additionalActions,
|
|
2992
3187
|
onSelectedNodeChange: handleSelectedNodeChange,
|
|
2993
|
-
hideInfoPanelClose: true,
|
|
2994
|
-
hideInfoPanelTitle: true,
|
|
2995
3188
|
onNodeHover: handleNodeHover,
|
|
2996
|
-
|
|
3189
|
+
...hostPanel,
|
|
2997
3190
|
playback,
|
|
2998
3191
|
selectedNodeId,
|
|
2999
|
-
readOnly
|
|
3000
|
-
...infoPanelCuration
|
|
3192
|
+
readOnly
|
|
3001
3193
|
}
|
|
3002
|
-
) }) : /* @__PURE__ */ (0,
|
|
3003
|
-
([uuid, genrePlaylistTreePerRoot]
|
|
3004
|
-
return /* @__PURE__ */ (0,
|
|
3194
|
+
) }) : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "tree-container relative flex flex-col gap-4 w-full pt-14 overflow-x-auto overflow-y-auto text-[var(--color-text)]", children: Object.entries(groupedGenrePlaylistsByRoot).map(
|
|
3195
|
+
([uuid, genrePlaylistTreePerRoot]) => {
|
|
3196
|
+
return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
3005
3197
|
"div",
|
|
3006
3198
|
{
|
|
3007
3199
|
className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
|
|
3008
|
-
children: /* @__PURE__ */ (0,
|
|
3200
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
3009
3201
|
GenrePlaylistTreePerRoot,
|
|
3010
3202
|
{
|
|
3011
3203
|
scope,
|
|
@@ -3015,26 +3207,45 @@ function GenreTreeView({
|
|
|
3015
3207
|
handleGenreRenameAction,
|
|
3016
3208
|
additionalActions,
|
|
3017
3209
|
onSelectedNodeChange: handleSelectedNodeChange,
|
|
3018
|
-
hideInfoPanelClose: true,
|
|
3019
|
-
hideInfoPanelTitle: true,
|
|
3020
3210
|
onNodeHover: handleNodeHover,
|
|
3021
|
-
|
|
3211
|
+
...hostPanel,
|
|
3022
3212
|
playback,
|
|
3023
3213
|
selectedNodeId: uuid === selectedGenrePlaylist?.root.uuid ? selectedNodeId : null,
|
|
3024
3214
|
readOnly,
|
|
3025
3215
|
wheelZoom: "modifier",
|
|
3026
3216
|
defaultViewport: uuid === selectedGenrePlaylist?.root.uuid ? defaultViewport : void 0,
|
|
3027
3217
|
viewportRestore: uuid === selectedGenrePlaylist?.root.uuid && uuid === restoreRootUuid ? viewportRestore : void 0,
|
|
3028
|
-
onViewportChange: uuid === selectedGenrePlaylist?.root.uuid ? onViewportChange : void 0
|
|
3029
|
-
...infoPanelCuration,
|
|
3030
|
-
overlayPanel: rootIndex === 0 ? activeOverlayPanel : void 0
|
|
3218
|
+
onViewportChange: uuid === selectedGenrePlaylist?.root.uuid ? onViewportChange : void 0
|
|
3031
3219
|
}
|
|
3032
3220
|
) })
|
|
3033
3221
|
},
|
|
3034
3222
|
uuid
|
|
3035
3223
|
);
|
|
3036
3224
|
}
|
|
3037
|
-
) })
|
|
3225
|
+
) }),
|
|
3226
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
3227
|
+
import_ui18.DetailPanel,
|
|
3228
|
+
{
|
|
3229
|
+
ref: panelRef,
|
|
3230
|
+
side: "left",
|
|
3231
|
+
title: activePanel?.title ?? "",
|
|
3232
|
+
hideTitle: activePanel?.compact,
|
|
3233
|
+
hideClose: activePanel?.compact,
|
|
3234
|
+
headerAccent: activePanel?.headerAccent,
|
|
3235
|
+
headerActions: expandButton,
|
|
3236
|
+
onClose: activePanel?.onClose ?? noop,
|
|
3237
|
+
collapsed: isCollapsed || !isTreeReady || !activePanel,
|
|
3238
|
+
expanded: activePanel?.expanded,
|
|
3239
|
+
onExpandedChange: activePanel?.onExpandedChange,
|
|
3240
|
+
aside: activePanel?.aside,
|
|
3241
|
+
edgeTab: detailTab,
|
|
3242
|
+
headerClassName: activePanel?.compact ? "pt-[calc(var(--layout-search-h)+var(--layout-panel-inset)+0.5rem)]" : void 0,
|
|
3243
|
+
children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
|
|
3244
|
+
activePanel.content,
|
|
3245
|
+
activePanel.aside
|
|
3246
|
+
] }) : activePanel?.content
|
|
3247
|
+
}
|
|
3248
|
+
)
|
|
3038
3249
|
] }) })
|
|
3039
3250
|
]
|
|
3040
3251
|
}
|
|
@@ -3042,12 +3253,12 @@ function GenreTreeView({
|
|
|
3042
3253
|
}
|
|
3043
3254
|
|
|
3044
3255
|
// src/genre-tree/GenreDetailPanel.tsx
|
|
3045
|
-
var
|
|
3256
|
+
var import_react17 = require("react");
|
|
3046
3257
|
var import_genre_tree_view5 = require("@behindthemusictree/genre-tree-view");
|
|
3047
|
-
var
|
|
3048
|
-
var
|
|
3049
|
-
var
|
|
3050
|
-
var
|
|
3258
|
+
var import_ui19 = require("@behindthemusictree/ui");
|
|
3259
|
+
var import_lucide_react17 = require("lucide-react");
|
|
3260
|
+
var import_genre_tree20 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3261
|
+
var import_jsx_runtime41 = require("react/jsx-runtime");
|
|
3051
3262
|
var RELATIVE_ITEM_COUNT = 1;
|
|
3052
3263
|
function toNode(genre, parentId) {
|
|
3053
3264
|
return {
|
|
@@ -3071,11 +3282,12 @@ function GenreDetailPanel({
|
|
|
3071
3282
|
collapsed,
|
|
3072
3283
|
expanded,
|
|
3073
3284
|
onExpandedChange,
|
|
3285
|
+
edgeTab,
|
|
3074
3286
|
extras
|
|
3075
3287
|
}) {
|
|
3076
|
-
const { data: detail, isPending, isError } = (0,
|
|
3288
|
+
const { data: detail, isPending, isError } = (0, import_genre_tree20.useFetchGenreDetail)(genreUuid, scope, getBackendBaseUrl);
|
|
3077
3289
|
const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: [] });
|
|
3078
|
-
const view = (0,
|
|
3290
|
+
const view = (0, import_react17.useMemo)(() => {
|
|
3079
3291
|
if (!detail) return null;
|
|
3080
3292
|
const rootColor = (0, import_genre_tree_view5.getGenreTreeColor)(detail.root.wikidataId);
|
|
3081
3293
|
const relative = (genre, parentId) => ({
|
|
@@ -3083,7 +3295,7 @@ function GenreDetailPanel({
|
|
|
3083
3295
|
fill: (0, import_genre_tree_view5.tintSurface)(rootColor),
|
|
3084
3296
|
textColor: import_genre_tree_view5.TEXT_COLOR
|
|
3085
3297
|
});
|
|
3086
|
-
const
|
|
3298
|
+
const ancestors2 = [...detail.ascendants].sort((a, b) => b.degree - a.degree);
|
|
3087
3299
|
return {
|
|
3088
3300
|
node: {
|
|
3089
3301
|
id: detail.uuid,
|
|
@@ -3095,57 +3307,72 @@ function GenreDetailPanel({
|
|
|
3095
3307
|
fill: rootColor,
|
|
3096
3308
|
parentNode: detail.parent ? relative(detail.parent, null) : null,
|
|
3097
3309
|
childNodes: detail.children.map((child) => relative(child, detail.uuid)),
|
|
3098
|
-
ancestorNodes:
|
|
3099
|
-
({ ascendant }, index) => relative(ascendant, index === 0 ? null :
|
|
3310
|
+
ancestorNodes: ancestors2.map(
|
|
3311
|
+
({ ascendant }, index) => relative(ascendant, index === 0 ? null : ancestors2[index - 1].ascendant.uuid)
|
|
3100
3312
|
)
|
|
3101
3313
|
};
|
|
3102
3314
|
}, [detail]);
|
|
3103
3315
|
if (isError) {
|
|
3104
|
-
return /* @__PURE__ */ (0,
|
|
3316
|
+
return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
|
|
3105
3317
|
}
|
|
3106
3318
|
if (isPending || !detail || !view) {
|
|
3107
|
-
return /* @__PURE__ */ (0,
|
|
3108
|
-
/* @__PURE__ */ (0,
|
|
3109
|
-
/* @__PURE__ */ (0,
|
|
3110
|
-
/* @__PURE__ */ (0,
|
|
3319
|
+
return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
|
|
3320
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui19.Skeleton, { className: "h-6 w-2/3" }),
|
|
3321
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui19.Skeleton, { className: "h-4 w-full" }),
|
|
3322
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui19.Skeleton, { className: "h-4 w-3/4" })
|
|
3111
3323
|
] });
|
|
3112
3324
|
}
|
|
3113
|
-
const
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
{
|
|
3117
|
-
toolbar: sections.toolbar,
|
|
3118
|
-
family: sections.family,
|
|
3119
|
-
genreUuid: detail.uuid,
|
|
3120
|
-
genrePlaylist: { ...detail.criteriaPlaylist, tracksCount: detail.tracksCount },
|
|
3121
|
-
wikidataId: detail.wikidataId,
|
|
3122
|
-
scope,
|
|
3123
|
-
getBackendBaseUrl,
|
|
3124
|
-
playback,
|
|
3125
|
-
band: { node, onZoomIn, shareUrl, shareTarget: shareUrl ? shareTarget : void 0 },
|
|
3126
|
-
extras
|
|
3127
|
-
}
|
|
3128
|
-
),
|
|
3129
|
-
aside: null
|
|
3130
|
-
});
|
|
3131
|
-
return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
3132
|
-
import_genre_tree_view5.InfoPanel,
|
|
3325
|
+
const selectGenre = (nodeId) => onSelectGenre(nodeId);
|
|
3326
|
+
const family = /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
3327
|
+
import_genre_tree_view5.GenreNodeFamily,
|
|
3133
3328
|
{
|
|
3134
3329
|
node: view.node,
|
|
3135
3330
|
fill: view.fill,
|
|
3136
3331
|
textColor: import_genre_tree_view5.ACCENT_TEXT_COLOR,
|
|
3137
3332
|
parentNode: view.parentNode,
|
|
3138
3333
|
childNodes: view.childNodes,
|
|
3139
|
-
|
|
3334
|
+
onSelectNode: selectGenre
|
|
3335
|
+
}
|
|
3336
|
+
);
|
|
3337
|
+
const ancestors = /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_genre_tree_view5.GenreNodeAncestors, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
|
|
3338
|
+
return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
|
|
3339
|
+
import_ui19.DetailPanel,
|
|
3340
|
+
{
|
|
3140
3341
|
side: "left",
|
|
3342
|
+
title: view.node.name,
|
|
3343
|
+
headerAccent: { background: view.fill, color: import_genre_tree_view5.ACCENT_TEXT_COLOR },
|
|
3344
|
+
headerActions: onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
3345
|
+
import_ui19.IconButton,
|
|
3346
|
+
{
|
|
3347
|
+
"aria-label": expanded ? "Shrink panel" : "Extend panel",
|
|
3348
|
+
style: { color: "inherit" },
|
|
3349
|
+
onClick: () => onExpandedChange(!expanded),
|
|
3350
|
+
children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_lucide_react17.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_lucide_react17.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
3351
|
+
}
|
|
3352
|
+
) : void 0,
|
|
3141
3353
|
onClose,
|
|
3142
|
-
|
|
3143
|
-
itemCountLabel: ITEM_COUNT_LABEL,
|
|
3144
|
-
renderInfoPanelBody: renderBody,
|
|
3354
|
+
collapsed,
|
|
3145
3355
|
expanded,
|
|
3146
3356
|
onExpandedChange,
|
|
3147
|
-
|
|
3148
|
-
|
|
3357
|
+
edgeTab,
|
|
3358
|
+
aside: ancestors,
|
|
3359
|
+
children: [
|
|
3360
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
3361
|
+
GenreInfoPanelBody,
|
|
3362
|
+
{
|
|
3363
|
+
family,
|
|
3364
|
+
genreUuid: detail.uuid,
|
|
3365
|
+
genrePlaylist: { ...detail.criteriaPlaylist, tracksCount: detail.tracksCount },
|
|
3366
|
+
wikidataId: detail.wikidataId,
|
|
3367
|
+
scope,
|
|
3368
|
+
getBackendBaseUrl,
|
|
3369
|
+
playback,
|
|
3370
|
+
band: { node: view.node, onZoomIn, shareUrl, shareTarget: shareUrl ? shareTarget : void 0 },
|
|
3371
|
+
extras
|
|
3372
|
+
}
|
|
3373
|
+
),
|
|
3374
|
+
!expanded && ancestors
|
|
3375
|
+
]
|
|
3149
3376
|
}
|
|
3150
3377
|
);
|
|
3151
3378
|
}
|
|
@@ -3154,13 +3381,13 @@ function GenreDetailPanel({
|
|
|
3154
3381
|
var import_genre_tree_view6 = require("@behindthemusictree/genre-tree-view");
|
|
3155
3382
|
|
|
3156
3383
|
// src/genre-tree/track-list-sidebar/TrackListSidebar.tsx
|
|
3157
|
-
var
|
|
3158
|
-
var
|
|
3159
|
-
var
|
|
3160
|
-
var
|
|
3161
|
-
var
|
|
3162
|
-
var
|
|
3163
|
-
var useBrowserLayoutEffect = typeof window === "undefined" ?
|
|
3384
|
+
var import_react18 = require("react");
|
|
3385
|
+
var import_lucide_react18 = require("lucide-react");
|
|
3386
|
+
var import_genre_tree21 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3387
|
+
var import_genre_tree22 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3388
|
+
var import_track_noun4 = require("@behindthemusictree/app-kit/track-noun");
|
|
3389
|
+
var import_jsx_runtime42 = require("react/jsx-runtime");
|
|
3390
|
+
var useBrowserLayoutEffect = typeof window === "undefined" ? import_react18.useEffect : import_react18.useLayoutEffect;
|
|
3164
3391
|
function TrackListSidebar({
|
|
3165
3392
|
className: className2 = "",
|
|
3166
3393
|
renderDuration,
|
|
@@ -3169,20 +3396,21 @@ function TrackListSidebar({
|
|
|
3169
3396
|
isCollapsed = false,
|
|
3170
3397
|
onExpand,
|
|
3171
3398
|
onMinimize,
|
|
3172
|
-
renderArtistLink
|
|
3399
|
+
renderArtistLink,
|
|
3400
|
+
renderSongLink
|
|
3173
3401
|
}) {
|
|
3174
|
-
const { trackList, loadMore, autoAdvanceScrollRequest } = (0,
|
|
3175
|
-
const trackNoun = (0,
|
|
3176
|
-
const trackListRef = (0,
|
|
3177
|
-
const previousOriginRef = (0,
|
|
3178
|
-
const handledAutoAdvanceRequestIdRef = (0,
|
|
3179
|
-
const shouldFollowPlaybackRef = (0,
|
|
3180
|
-
const programmaticScrollTopRef = (0,
|
|
3181
|
-
const userScrollIntentRef = (0,
|
|
3182
|
-
const userScrollIntentTimerRef = (0,
|
|
3183
|
-
const loadMoreSentinelRef = (0,
|
|
3402
|
+
const { trackList, loadMore, autoAdvanceScrollRequest } = (0, import_genre_tree21.useTrackList)();
|
|
3403
|
+
const trackNoun = (0, import_track_noun4.useTrackNoun)();
|
|
3404
|
+
const trackListRef = (0, import_react18.useRef)(null);
|
|
3405
|
+
const previousOriginRef = (0, import_react18.useRef)(trackList?.origin ?? null);
|
|
3406
|
+
const handledAutoAdvanceRequestIdRef = (0, import_react18.useRef)(null);
|
|
3407
|
+
const shouldFollowPlaybackRef = (0, import_react18.useRef)(true);
|
|
3408
|
+
const programmaticScrollTopRef = (0, import_react18.useRef)(null);
|
|
3409
|
+
const userScrollIntentRef = (0, import_react18.useRef)(false);
|
|
3410
|
+
const userScrollIntentTimerRef = (0, import_react18.useRef)(null);
|
|
3411
|
+
const loadMoreSentinelRef = (0, import_react18.useRef)(null);
|
|
3184
3412
|
const hasMore = !!trackList && trackList.nextPage !== null;
|
|
3185
|
-
(0,
|
|
3413
|
+
(0, import_react18.useEffect)(() => {
|
|
3186
3414
|
const sentinel = loadMoreSentinelRef.current;
|
|
3187
3415
|
if (!hasMore || !sentinel) return;
|
|
3188
3416
|
const observer = new IntersectionObserver((entries) => {
|
|
@@ -3202,11 +3430,11 @@ function TrackListSidebar({
|
|
|
3202
3430
|
programmaticScrollTopRef.current = null;
|
|
3203
3431
|
if (trackListRef.current) trackListRef.current.scrollTop = 0;
|
|
3204
3432
|
}, [trackList?.origin]);
|
|
3205
|
-
(0,
|
|
3433
|
+
(0, import_react18.useEffect)(() => {
|
|
3206
3434
|
const list = trackListRef.current;
|
|
3207
3435
|
if (!isCollapsed && list?.scrollTop === 0) shouldFollowPlaybackRef.current = true;
|
|
3208
3436
|
}, [isCollapsed]);
|
|
3209
|
-
(0,
|
|
3437
|
+
(0, import_react18.useEffect)(
|
|
3210
3438
|
() => () => {
|
|
3211
3439
|
if (userScrollIntentTimerRef.current !== null) clearTimeout(userScrollIntentTimerRef.current);
|
|
3212
3440
|
},
|
|
@@ -3260,25 +3488,25 @@ function TrackListSidebar({
|
|
|
3260
3488
|
}
|
|
3261
3489
|
};
|
|
3262
3490
|
const positionClasses = layout === "inline" ? "relative w-full flex h-full flex-col" : "fixed right-0 w-[50rem] flex flex-col";
|
|
3263
|
-
return trackList ? /* @__PURE__ */ (0,
|
|
3491
|
+
return trackList ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
|
|
3264
3492
|
"div",
|
|
3265
3493
|
{
|
|
3266
3494
|
"data-theme": "dark",
|
|
3267
3495
|
className: `track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${className2}`,
|
|
3268
3496
|
style: layout === "fixed" ? { top: "63.5px", maxHeight: "calc(100vh - 63.5px - 79px)" } : void 0,
|
|
3269
3497
|
children: [
|
|
3270
|
-
/* @__PURE__ */ (0,
|
|
3271
|
-
/* @__PURE__ */ (0,
|
|
3272
|
-
/* @__PURE__ */ (0,
|
|
3273
|
-
/* @__PURE__ */ (0,
|
|
3274
|
-
/* @__PURE__ */ (0,
|
|
3498
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
|
|
3499
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
|
|
3500
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "origin flex text-xl", children: [
|
|
3501
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
|
|
3502
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.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_runtime42.jsx)("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
|
|
3275
3503
|
] }),
|
|
3276
|
-
/* @__PURE__ */ (0,
|
|
3277
|
-
trackList.origin.type ===
|
|
3504
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
|
|
3505
|
+
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 `,
|
|
3278
3506
|
`${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`
|
|
3279
3507
|
] })
|
|
3280
3508
|
] }),
|
|
3281
|
-
(isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0,
|
|
3509
|
+
(isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
3282
3510
|
"button",
|
|
3283
3511
|
{
|
|
3284
3512
|
type: "button",
|
|
@@ -3287,11 +3515,11 @@ function TrackListSidebar({
|
|
|
3287
3515
|
"aria-expanded": !isCollapsed,
|
|
3288
3516
|
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"}`,
|
|
3289
3517
|
onClick: isCollapsed ? onExpand : onMinimize,
|
|
3290
|
-
children: /* @__PURE__ */ (0,
|
|
3518
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react18.ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
|
|
3291
3519
|
}
|
|
3292
3520
|
)
|
|
3293
3521
|
] }),
|
|
3294
|
-
!isCollapsed && /* @__PURE__ */ (0,
|
|
3522
|
+
!isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
|
|
3295
3523
|
"ul",
|
|
3296
3524
|
{
|
|
3297
3525
|
ref: trackListRef,
|
|
@@ -3301,17 +3529,18 @@ function TrackListSidebar({
|
|
|
3301
3529
|
onPointerDown: markUserScrollIntent,
|
|
3302
3530
|
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",
|
|
3303
3531
|
children: [
|
|
3304
|
-
trackList.tracks.map((track, index) => /* @__PURE__ */ (0,
|
|
3532
|
+
trackList.tracks.map((track, index) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
3305
3533
|
TrackItem,
|
|
3306
3534
|
{
|
|
3307
3535
|
track,
|
|
3308
3536
|
position: index + 1,
|
|
3309
3537
|
renderDuration,
|
|
3310
3538
|
renderActions,
|
|
3311
|
-
renderArtistLink
|
|
3539
|
+
renderArtistLink,
|
|
3540
|
+
renderSongLink
|
|
3312
3541
|
}
|
|
3313
3542
|
) }, track.uuid)),
|
|
3314
|
-
hasMore && /* @__PURE__ */ (0,
|
|
3543
|
+
hasMore && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
|
|
3315
3544
|
]
|
|
3316
3545
|
}
|
|
3317
3546
|
)
|
|
@@ -3321,16 +3550,17 @@ function TrackListSidebar({
|
|
|
3321
3550
|
}
|
|
3322
3551
|
|
|
3323
3552
|
// src/genre-tree/PlayerPanel.tsx
|
|
3324
|
-
var
|
|
3325
|
-
var
|
|
3326
|
-
var
|
|
3327
|
-
var
|
|
3553
|
+
var import_react19 = require("react");
|
|
3554
|
+
var import_tailwind_merge = require("tailwind-merge");
|
|
3555
|
+
var import_player13 = require("@behindthemusictree/app-kit/player");
|
|
3556
|
+
var import_genre_tree24 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3557
|
+
var import_ui20 = require("@behindthemusictree/ui");
|
|
3328
3558
|
|
|
3329
3559
|
// src/genre-tree/PlayerControls.tsx
|
|
3330
|
-
var
|
|
3331
|
-
var
|
|
3332
|
-
var
|
|
3333
|
-
var
|
|
3560
|
+
var import_lucide_react19 = require("lucide-react");
|
|
3561
|
+
var import_genre_tree23 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3562
|
+
var import_player12 = require("@behindthemusictree/app-kit/player");
|
|
3563
|
+
var import_jsx_runtime43 = require("react/jsx-runtime");
|
|
3334
3564
|
function PlayerControls() {
|
|
3335
3565
|
const {
|
|
3336
3566
|
duration,
|
|
@@ -3341,23 +3571,23 @@ function PlayerControls() {
|
|
|
3341
3571
|
seekTo,
|
|
3342
3572
|
setVolume,
|
|
3343
3573
|
volume
|
|
3344
|
-
} = (0,
|
|
3345
|
-
const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } = (0,
|
|
3346
|
-
const currentTime = (0,
|
|
3347
|
-
const isBusy = isLoading || playState ===
|
|
3574
|
+
} = (0, import_player12.usePlayer)();
|
|
3575
|
+
const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } = (0, import_genre_tree23.useTrackList)();
|
|
3576
|
+
const currentTime = (0, import_player12.useCurrentTime)();
|
|
3577
|
+
const isBusy = isLoading || playState === import_player12.PlayStates.LOADING;
|
|
3348
3578
|
const isReady = !!playerTrackObject?.isReady && !isBusy && !playerTrackObject.loadError;
|
|
3349
3579
|
const safeDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;
|
|
3350
3580
|
const safeCurrentTime = Number.isFinite(currentTime) ? Math.max(0, Math.min(currentTime, safeDuration)) : 0;
|
|
3351
|
-
const isPlaying = playState ===
|
|
3352
|
-
return /* @__PURE__ */ (0,
|
|
3581
|
+
const isPlaying = playState === import_player12.PlayStates.PLAYING;
|
|
3582
|
+
return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(
|
|
3353
3583
|
"section",
|
|
3354
3584
|
{
|
|
3355
3585
|
"aria-label": "Player controls",
|
|
3356
3586
|
className: "player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200",
|
|
3357
3587
|
children: [
|
|
3358
|
-
/* @__PURE__ */ (0,
|
|
3359
|
-
/* @__PURE__ */ (0,
|
|
3360
|
-
/* @__PURE__ */ (0,
|
|
3588
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
|
|
3589
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { "aria-label": "Elapsed time", children: (0, import_genre_tree23.formatTime)(safeCurrentTime) }),
|
|
3590
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
3361
3591
|
"input",
|
|
3362
3592
|
{
|
|
3363
3593
|
"aria-label": "Seek",
|
|
@@ -3371,11 +3601,11 @@ function PlayerControls() {
|
|
|
3371
3601
|
onChange: (event) => seekTo(Number(event.currentTarget.value))
|
|
3372
3602
|
}
|
|
3373
3603
|
),
|
|
3374
|
-
/* @__PURE__ */ (0,
|
|
3604
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { "aria-label": "Duration", children: (0, import_genre_tree23.formatTime)(safeDuration) })
|
|
3375
3605
|
] }),
|
|
3376
|
-
/* @__PURE__ */ (0,
|
|
3377
|
-
/* @__PURE__ */ (0,
|
|
3378
|
-
/* @__PURE__ */ (0,
|
|
3606
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
|
|
3607
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
|
|
3608
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
3379
3609
|
"button",
|
|
3380
3610
|
{
|
|
3381
3611
|
type: "button",
|
|
@@ -3384,10 +3614,10 @@ function PlayerControls() {
|
|
|
3384
3614
|
disabled: !isReady || !hasPreviousTrack,
|
|
3385
3615
|
onClick: playPreviousTrack,
|
|
3386
3616
|
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",
|
|
3387
|
-
children: /* @__PURE__ */ (0,
|
|
3617
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react19.SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
3388
3618
|
}
|
|
3389
3619
|
),
|
|
3390
|
-
/* @__PURE__ */ (0,
|
|
3620
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
3391
3621
|
"button",
|
|
3392
3622
|
{
|
|
3393
3623
|
type: "button",
|
|
@@ -3396,10 +3626,10 @@ function PlayerControls() {
|
|
|
3396
3626
|
disabled: !isReady,
|
|
3397
3627
|
onClick: handlePlayPauseAction,
|
|
3398
3628
|
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",
|
|
3399
|
-
children: isPlaying ? /* @__PURE__ */ (0,
|
|
3629
|
+
children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react19.Pause, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react19.Play, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
|
|
3400
3630
|
}
|
|
3401
3631
|
),
|
|
3402
|
-
/* @__PURE__ */ (0,
|
|
3632
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
3403
3633
|
"button",
|
|
3404
3634
|
{
|
|
3405
3635
|
type: "button",
|
|
@@ -3408,14 +3638,14 @@ function PlayerControls() {
|
|
|
3408
3638
|
disabled: !isReady || !hasNextTrack,
|
|
3409
3639
|
onClick: playNextTrack,
|
|
3410
3640
|
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",
|
|
3411
|
-
children: /* @__PURE__ */ (0,
|
|
3641
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react19.SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
3412
3642
|
}
|
|
3413
3643
|
)
|
|
3414
3644
|
] }),
|
|
3415
|
-
/* @__PURE__ */ (0,
|
|
3416
|
-
volume === 0 ? /* @__PURE__ */ (0,
|
|
3417
|
-
/* @__PURE__ */ (0,
|
|
3418
|
-
/* @__PURE__ */ (0,
|
|
3645
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.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: [
|
|
3646
|
+
volume === 0 ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react19.VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react19.Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
|
|
3647
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "sr-only", children: "Volume" }),
|
|
3648
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
3419
3649
|
"input",
|
|
3420
3650
|
{
|
|
3421
3651
|
"aria-label": "Volume",
|
|
@@ -3437,7 +3667,7 @@ function PlayerControls() {
|
|
|
3437
3667
|
}
|
|
3438
3668
|
|
|
3439
3669
|
// src/genre-tree/PlayerPanel.tsx
|
|
3440
|
-
var
|
|
3670
|
+
var import_jsx_runtime44 = require("react/jsx-runtime");
|
|
3441
3671
|
var PANEL_ID = "player-panel";
|
|
3442
3672
|
var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
|
|
3443
3673
|
function PlayerPanel({
|
|
@@ -3446,16 +3676,17 @@ function PlayerPanel({
|
|
|
3446
3676
|
expanded,
|
|
3447
3677
|
onExpandedChange,
|
|
3448
3678
|
extendedWidth = DEFAULT_EXTENDED_WIDTH,
|
|
3449
|
-
renderArtistLink
|
|
3679
|
+
renderArtistLink,
|
|
3680
|
+
renderSongLink
|
|
3450
3681
|
}) {
|
|
3451
|
-
const { playerTrackObject } = (0,
|
|
3452
|
-
const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0,
|
|
3453
|
-
const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = (0,
|
|
3682
|
+
const { playerTrackObject } = (0, import_player13.usePlayer)();
|
|
3683
|
+
const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0, import_genre_tree24.useTrackListSidebarVisibility)();
|
|
3684
|
+
const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = (0, import_react19.useState)("");
|
|
3454
3685
|
const loadedTrackTitle = playerTrackObject?.track.title;
|
|
3455
|
-
(0,
|
|
3686
|
+
(0, import_react19.useEffect)(() => {
|
|
3456
3687
|
if (loadedTrackTitle) setLastLoadedTrackTitle(loadedTrackTitle);
|
|
3457
3688
|
}, [loadedTrackTitle]);
|
|
3458
|
-
const [uncontrolledExtended, setUncontrolledExtended] = (0,
|
|
3689
|
+
const [uncontrolledExtended, setUncontrolledExtended] = (0, import_react19.useState)(false);
|
|
3459
3690
|
const isExtendRequested = expanded ?? uncontrolledExtended;
|
|
3460
3691
|
const setIsExtendRequested = (next) => {
|
|
3461
3692
|
setUncontrolledExtended(next);
|
|
@@ -3476,11 +3707,11 @@ function PlayerPanel({
|
|
|
3476
3707
|
onClick: () => setIsExtendRequested(true),
|
|
3477
3708
|
expanded: false
|
|
3478
3709
|
} : null;
|
|
3479
|
-
return /* @__PURE__ */ (0,
|
|
3710
|
+
return /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(
|
|
3480
3711
|
"div",
|
|
3481
3712
|
{
|
|
3482
3713
|
id: PANEL_ID,
|
|
3483
|
-
className: (0,
|
|
3714
|
+
className: (0, import_tailwind_merge.twMerge)(
|
|
3484
3715
|
"fixed right-[var(--layout-gutter)] top-[var(--layout-band-top)] z-40 max-h-[calc(100vh-var(--layout-band-top)-3.5rem)]",
|
|
3485
3716
|
!isExtended && "w-[var(--layout-right-w)]",
|
|
3486
3717
|
isTrackListSidebarVisible && "h-[calc(100vh-var(--layout-band-top)-3.5rem)]",
|
|
@@ -3489,8 +3720,8 @@ function PlayerPanel({
|
|
|
3489
3720
|
),
|
|
3490
3721
|
style: isExtended ? { width: extendedWidth } : void 0,
|
|
3491
3722
|
children: [
|
|
3492
|
-
/* @__PURE__ */ (0,
|
|
3493
|
-
PanelEdgeTab,
|
|
3723
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
3724
|
+
import_ui20.PanelEdgeTab,
|
|
3494
3725
|
{
|
|
3495
3726
|
side: "right",
|
|
3496
3727
|
edge: tabEdge,
|
|
@@ -3499,34 +3730,35 @@ function PlayerPanel({
|
|
|
3499
3730
|
className: "absolute right-full top-1/2 -translate-y-1/2"
|
|
3500
3731
|
}
|
|
3501
3732
|
),
|
|
3502
|
-
/* @__PURE__ */ (0,
|
|
3733
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(
|
|
3503
3734
|
"div",
|
|
3504
3735
|
{
|
|
3505
|
-
className: (0,
|
|
3736
|
+
className: (0, import_tailwind_merge.twMerge)(
|
|
3506
3737
|
"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)]",
|
|
3507
3738
|
isExtended ? "flex-row" : "flex-col"
|
|
3508
3739
|
),
|
|
3509
3740
|
children: [
|
|
3510
|
-
/* @__PURE__ */ (0,
|
|
3511
|
-
/* @__PURE__ */ (0,
|
|
3512
|
-
/* @__PURE__ */ (0,
|
|
3513
|
-
/* @__PURE__ */ (0,
|
|
3514
|
-
playerTrackObject?.loadError && /* @__PURE__ */ (0,
|
|
3741
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
|
|
3742
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
|
|
3743
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "relative w-full shrink-0", children: [
|
|
3744
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_player13.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
|
|
3745
|
+
playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: playerTrackObject.loadError })
|
|
3515
3746
|
] }),
|
|
3516
|
-
/* @__PURE__ */ (0,
|
|
3747
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(PlayerControls, {})
|
|
3517
3748
|
] }),
|
|
3518
|
-
/* @__PURE__ */ (0,
|
|
3749
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
3519
3750
|
TrackListSidebar,
|
|
3520
3751
|
{
|
|
3521
3752
|
layout: "inline",
|
|
3522
3753
|
isCollapsed: !isTrackListSidebarVisible,
|
|
3523
3754
|
onExpand: toggleTrackListSidebar,
|
|
3524
3755
|
onMinimize: hideSongList,
|
|
3525
|
-
className: (0,
|
|
3756
|
+
className: (0, import_tailwind_merge.twMerge)(
|
|
3526
3757
|
"min-h-0 min-w-0 flex-1",
|
|
3527
3758
|
isExtended && "border-l border-zinc-800"
|
|
3528
3759
|
),
|
|
3529
|
-
renderArtistLink
|
|
3760
|
+
renderArtistLink,
|
|
3761
|
+
renderSongLink
|
|
3530
3762
|
}
|
|
3531
3763
|
)
|
|
3532
3764
|
]
|
|
@@ -3540,33 +3772,33 @@ function PlayerPanel({
|
|
|
3540
3772
|
// src/genre-tree/Rating.tsx
|
|
3541
3773
|
var import_uuid = require("uuid");
|
|
3542
3774
|
var import_ai = require("react-icons/ai");
|
|
3543
|
-
var
|
|
3544
|
-
var
|
|
3775
|
+
var import_genre_tree25 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3776
|
+
var import_jsx_runtime45 = require("react/jsx-runtime");
|
|
3545
3777
|
function Rating({ rating, handleChange }) {
|
|
3546
3778
|
const instanceId = (0, import_uuid.v4)();
|
|
3547
|
-
return /* @__PURE__ */ (0,
|
|
3548
|
-
/* @__PURE__ */ (0,
|
|
3779
|
+
return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
|
|
3780
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
|
|
3549
3781
|
"input",
|
|
3550
3782
|
{
|
|
3551
3783
|
id: `star-${instanceId}-${i}`,
|
|
3552
3784
|
className: "w-6 h-6 opacity-0",
|
|
3553
3785
|
type: "radio",
|
|
3554
3786
|
name: "rating",
|
|
3555
|
-
value: i === 0 ?
|
|
3787
|
+
value: i === 0 ? import_genre_tree25.FORM_RATING_NULL_VALUE : i,
|
|
3556
3788
|
checked: rating === (i === 0 ? null : i),
|
|
3557
3789
|
onChange: handleChange
|
|
3558
3790
|
}
|
|
3559
3791
|
),
|
|
3560
|
-
/* @__PURE__ */ (0,
|
|
3561
|
-
/* @__PURE__ */ (0,
|
|
3792
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("div", { className: "flex", children: [
|
|
3793
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
|
|
3562
3794
|
import_ai.AiOutlineStar,
|
|
3563
3795
|
{
|
|
3564
|
-
className: `w-7 h-7 absolute top-0 left-0 ${rating ===
|
|
3796
|
+
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)]"}`
|
|
3565
3797
|
}
|
|
3566
3798
|
),
|
|
3567
|
-
/* @__PURE__ */ (0,
|
|
3799
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "flex-grow w-8" }),
|
|
3568
3800
|
" "
|
|
3569
|
-
] }) : /* @__PURE__ */ (0,
|
|
3801
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
|
|
3570
3802
|
import_ai.AiFillStar,
|
|
3571
3803
|
{
|
|
3572
3804
|
className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
|
|
@@ -3592,7 +3824,6 @@ function Rating({ rating, handleChange }) {
|
|
|
3592
3824
|
GenreTreeWheelSkeleton,
|
|
3593
3825
|
InternalErrorPopup,
|
|
3594
3826
|
NetworkErrorPopup,
|
|
3595
|
-
PanelEdgeTab,
|
|
3596
3827
|
PlayerPanel,
|
|
3597
3828
|
PopupButtons,
|
|
3598
3829
|
PopupTitle,
|
|
@@ -3603,7 +3834,6 @@ function Rating({ rating, handleChange }) {
|
|
|
3603
3834
|
TrackListSidebar,
|
|
3604
3835
|
TrackPositionPlayPause,
|
|
3605
3836
|
TrackUploadPopup,
|
|
3606
|
-
useGenrePlaylistPlayback
|
|
3607
|
-
useLeftDetailPanelLayout
|
|
3837
|
+
useGenrePlaylistPlayback
|
|
3608
3838
|
});
|
|
3609
3839
|
//# sourceMappingURL=index.cjs.map
|