@behindthemusictree/app-kit-ui 28.0.2 → 29.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-5PQWJRDZ.js → chunk-TFLX6C5Q.js} +1018 -850
- package/dist/chunk-TFLX6C5Q.js.map +1 -0
- package/dist/genre-tree/index.cjs +1106 -941
- package/dist/genre-tree/index.cjs.map +1 -1
- package/dist/genre-tree/index.d.ts +119 -12
- package/dist/genre-tree/index.js +13 -3
- package/dist/index.cjs +1187 -1022
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +13 -3
- package/package.json +6 -6
- package/dist/chunk-5PQWJRDZ.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -24,13 +24,16 @@ __export(src_exports, {
|
|
|
24
24
|
AuthErrorPopup: () => AuthErrorPopup,
|
|
25
25
|
AuthPopup: () => AuthPopup,
|
|
26
26
|
BasePopup: () => BasePopup,
|
|
27
|
+
GenreDetailPanel: () => GenreDetailPanel,
|
|
28
|
+
GenreInfoPanelBody: () => GenreInfoPanelBody,
|
|
29
|
+
GenreNodeActionBand: () => GenreNodeActionBand,
|
|
27
30
|
GenrePlaylistTreePerRoot: () => GenrePlaylistTreePerRoot,
|
|
28
31
|
GenreSearch: () => GenreSearch,
|
|
29
|
-
GenreTreeOutlineSkeleton: () =>
|
|
30
|
-
GenreTreeSkeleton: () =>
|
|
32
|
+
GenreTreeOutlineSkeleton: () => import_genre_tree_view6.GenreTreeOutlineSkeleton,
|
|
33
|
+
GenreTreeSkeleton: () => import_genre_tree_view6.GenreTreeSkeleton,
|
|
31
34
|
GenreTreeView: () => GenreTreeView,
|
|
32
|
-
GenreTreeViewSkeleton: () =>
|
|
33
|
-
GenreTreeWheelSkeleton: () =>
|
|
35
|
+
GenreTreeViewSkeleton: () => import_genre_tree_view6.GenreTreeViewSkeleton,
|
|
36
|
+
GenreTreeWheelSkeleton: () => import_genre_tree_view6.GenreTreeWheelSkeleton,
|
|
34
37
|
InternalErrorPopup: () => InternalErrorPopup,
|
|
35
38
|
NetworkErrorPopup: () => NetworkErrorPopup,
|
|
36
39
|
PanelEdgeTab: () => PanelEdgeTab,
|
|
@@ -43,7 +46,9 @@ __export(src_exports, {
|
|
|
43
46
|
TrackItem: () => TrackItem,
|
|
44
47
|
TrackListSidebar: () => TrackListSidebar,
|
|
45
48
|
TrackPositionPlayPause: () => TrackPositionPlayPause,
|
|
46
|
-
TrackUploadPopup: () => TrackUploadPopup
|
|
49
|
+
TrackUploadPopup: () => TrackUploadPopup,
|
|
50
|
+
useGenrePlaylistPlayback: () => useGenrePlaylistPlayback,
|
|
51
|
+
useLeftDetailPanelLayout: () => useLeftDetailPanelLayout
|
|
47
52
|
});
|
|
48
53
|
module.exports = __toCommonJS(src_exports);
|
|
49
54
|
|
|
@@ -575,419 +580,238 @@ function AuthCallbackHandler({ renderStatus, ...options }) {
|
|
|
575
580
|
}
|
|
576
581
|
|
|
577
582
|
// src/genre-tree/GenreTreeView.tsx
|
|
578
|
-
var
|
|
583
|
+
var import_react15 = require("react");
|
|
579
584
|
var import_react_query3 = require("@tanstack/react-query");
|
|
580
585
|
var import_lucide_react15 = require("lucide-react");
|
|
581
586
|
var import_ui16 = require("@behindthemusictree/ui");
|
|
582
587
|
var import_genre_tree_view4 = require("@behindthemusictree/genre-tree-view");
|
|
583
588
|
var import_genre_tree16 = require("@behindthemusictree/app-kit/genre-tree");
|
|
584
589
|
var import_genre_tree17 = require("@behindthemusictree/app-kit/genre-tree");
|
|
585
|
-
var import_player10 = require("@behindthemusictree/app-kit/player");
|
|
586
590
|
var import_popup6 = require("@behindthemusictree/app-kit/popup");
|
|
587
591
|
|
|
588
|
-
// src/genre-tree/
|
|
589
|
-
var import_ui7 = require("@behindthemusictree/ui");
|
|
590
|
-
var import_genre_tree7 = require("@behindthemusictree/app-kit/genre-tree");
|
|
591
|
-
|
|
592
|
-
// src/genre-tree/ClampedText.tsx
|
|
593
|
-
var import_react3 = require("react");
|
|
594
|
-
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
595
|
-
function ClampedText({ children, className: className2 = "" }) {
|
|
596
|
-
const ref = (0, import_react3.useRef)(null);
|
|
597
|
-
const [expanded, setExpanded] = (0, import_react3.useState)(false);
|
|
598
|
-
const [overflows, setOverflows] = (0, import_react3.useState)(false);
|
|
599
|
-
(0, import_react3.useLayoutEffect)(() => {
|
|
600
|
-
const element = ref.current;
|
|
601
|
-
if (!element || expanded) return;
|
|
602
|
-
setOverflows(element.scrollHeight > element.clientHeight);
|
|
603
|
-
}, [children, expanded]);
|
|
604
|
-
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
|
|
605
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { ref, className: `${className2} ${expanded ? "" : "line-clamp-3"}`, children }),
|
|
606
|
-
(overflows || expanded) && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
607
|
-
"button",
|
|
608
|
-
{
|
|
609
|
-
type: "button",
|
|
610
|
-
"aria-expanded": expanded,
|
|
611
|
-
onClick: () => setExpanded((value) => !value),
|
|
612
|
-
className: "mt-1 text-xs text-[var(--color-text-muted)] underline hover:text-[var(--color-text)]",
|
|
613
|
-
children: expanded ? "Show less" : "Show more"
|
|
614
|
-
}
|
|
615
|
-
)
|
|
616
|
-
] });
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
// src/genre-tree/GenrePlaylistTracks.tsx
|
|
592
|
+
// src/genre-tree/playlist-tree/TreePerRoot.tsx
|
|
620
593
|
var import_react4 = require("react");
|
|
621
|
-
var
|
|
622
|
-
var import_zod = require("zod");
|
|
623
|
-
var import_fa3 = require("react-icons/fa");
|
|
624
|
-
var import_ui6 = require("@behindthemusictree/ui");
|
|
625
|
-
var import_auth2 = require("@behindthemusictree/app-kit/auth");
|
|
626
|
-
var import_transport2 = require("@behindthemusictree/app-kit/transport");
|
|
627
|
-
var import_transport3 = require("@behindthemusictree/app-kit/transport");
|
|
628
|
-
var import_transport4 = require("@behindthemusictree/app-kit/transport");
|
|
594
|
+
var import_genre_tree_view = require("@behindthemusictree/genre-tree-view");
|
|
629
595
|
var import_player = require("@behindthemusictree/app-kit/player");
|
|
630
|
-
var import_player2 = require("@behindthemusictree/app-kit/player");
|
|
631
596
|
var import_popup2 = require("@behindthemusictree/app-kit/popup");
|
|
632
|
-
var
|
|
633
|
-
var import_genre_tree2 = require("@behindthemusictree/app-kit/genre-tree");
|
|
634
|
-
var import_genre_tree3 = require("@behindthemusictree/app-kit/genre-tree");
|
|
635
|
-
var import_genre_tree4 = require("@behindthemusictree/app-kit/genre-tree");
|
|
636
|
-
var import_genre_tree5 = require("@behindthemusictree/app-kit/genre-tree");
|
|
597
|
+
var import_transport2 = require("@behindthemusictree/app-kit/transport");
|
|
637
598
|
|
|
638
|
-
// src/genre-tree/
|
|
599
|
+
// src/genre-tree/playlist-tree/useGenrePlaylistPlayback.ts
|
|
600
|
+
var import_react3 = require("react");
|
|
639
601
|
var import_genre_tree = require("@behindthemusictree/app-kit/genre-tree");
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
className: className2 = ""
|
|
602
|
+
function useGenrePlaylistPlayback({
|
|
603
|
+
scope,
|
|
604
|
+
genrePlaylists,
|
|
605
|
+
artist = null
|
|
645
606
|
}) {
|
|
646
|
-
const
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
607
|
+
const { trackList, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist } = (0, import_genre_tree.useTrackList)();
|
|
608
|
+
const origin = trackList?.origin;
|
|
609
|
+
const playingNodeId = (0, import_react3.useMemo)(() => {
|
|
610
|
+
if (!origin) return null;
|
|
611
|
+
if (!artist) return origin.type === import_genre_tree.TrackListOriginType.GENRE_PLAYLIST ? origin.uuid : null;
|
|
612
|
+
if (origin.type !== import_genre_tree.TrackListOriginType.ARTIST || origin.uuid !== artist.uuid) return null;
|
|
613
|
+
const { genreUuid } = origin;
|
|
614
|
+
return genreUuid ? genrePlaylists.find((gp) => gp.criteria?.uuid === genreUuid)?.uuid ?? null : null;
|
|
615
|
+
}, [origin, artist, genrePlaylists]);
|
|
616
|
+
const play = (0, import_react3.useCallback)(
|
|
617
|
+
async (genrePlaylist) => {
|
|
618
|
+
if (!artist) return playNewTrackListFromGenrePlaylist(genrePlaylist, scope);
|
|
619
|
+
if (!genrePlaylist.criteria) return;
|
|
620
|
+
return playNewTrackListFromArtist(artist, scope, { ordering: "title", genreUuid: genrePlaylist.criteria.uuid });
|
|
621
|
+
},
|
|
622
|
+
[artist, scope, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist]
|
|
623
|
+
);
|
|
624
|
+
return (0, import_react3.useMemo)(() => ({ playingNodeId, play }), [playingNodeId, play]);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// src/genre-tree/playlist-tree/TreePerRoot.tsx
|
|
628
|
+
var import_track_noun2 = require("@behindthemusictree/app-kit/track-noun");
|
|
629
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
630
|
+
function GenrePlaylistTreePerRoot({
|
|
631
|
+
scope,
|
|
632
|
+
className: className2,
|
|
633
|
+
rootUuid,
|
|
634
|
+
genrePlaylistTreePerRoot,
|
|
635
|
+
handleGenreCreationAction,
|
|
636
|
+
handleGenreRenameAction,
|
|
637
|
+
additionalActions,
|
|
638
|
+
onNodeClick,
|
|
639
|
+
onNodeHover,
|
|
640
|
+
renderInfoPanelBody,
|
|
641
|
+
selectedNodeId,
|
|
642
|
+
onSelectedNodeChange,
|
|
643
|
+
hideInfoPanelClose,
|
|
644
|
+
hideInfoPanelTitle,
|
|
645
|
+
readOnly = false,
|
|
646
|
+
showToolbar,
|
|
647
|
+
wheelZoom,
|
|
648
|
+
defaultViewport,
|
|
649
|
+
viewportRestore,
|
|
650
|
+
onViewportChange,
|
|
651
|
+
playback,
|
|
652
|
+
...infoPanelCuration
|
|
653
|
+
}) {
|
|
654
|
+
const { isPlaying, setIsPlaying } = (0, import_player.usePlayer)();
|
|
655
|
+
const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists: genrePlaylistTreePerRoot });
|
|
656
|
+
const { playingNodeId, play } = playback ?? defaultPlayback;
|
|
657
|
+
const trackNoun = (0, import_track_noun2.useTrackNoun)();
|
|
658
|
+
const { showPopup } = (0, import_popup2.usePopup)();
|
|
659
|
+
const nodes = (0, import_react4.useMemo)(
|
|
660
|
+
() => genrePlaylistTreePerRoot.map((genrePlaylist) => ({
|
|
661
|
+
id: genrePlaylist.uuid,
|
|
662
|
+
parentId: genrePlaylist.parent?.uuid ?? null,
|
|
663
|
+
name: genrePlaylist.name,
|
|
664
|
+
itemCount: genrePlaylist.tracksCount,
|
|
665
|
+
actionable: Boolean(genrePlaylist.criteria),
|
|
666
|
+
side: genrePlaylist.criteria?.side ?? void 0,
|
|
667
|
+
wikidataId: genrePlaylist.criteria?.wikidataId ?? null,
|
|
668
|
+
umbrella: genrePlaylist.isUmbrella
|
|
669
|
+
})),
|
|
670
|
+
[genrePlaylistTreePerRoot]
|
|
671
|
+
);
|
|
672
|
+
const handlePlayPause = (0, import_react4.useCallback)(
|
|
673
|
+
(nodeId) => {
|
|
674
|
+
const genrePlaylist = genrePlaylistTreePerRoot.find((g) => g.uuid === nodeId);
|
|
675
|
+
if (!genrePlaylist) return;
|
|
676
|
+
if (nodeId === playingNodeId) {
|
|
677
|
+
setIsPlaying(!isPlaying);
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
if (genrePlaylist.tracksCount === 0) {
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
play(genrePlaylist).catch((error) => {
|
|
684
|
+
console.error("Failed to load genre playlist tracks:", error);
|
|
685
|
+
showPopup(/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(InternalErrorPopup, { errorCode: import_transport2.ErrorCode.CLIENT_UNKNOWN }));
|
|
686
|
+
});
|
|
687
|
+
},
|
|
688
|
+
[
|
|
689
|
+
genrePlaylistTreePerRoot,
|
|
690
|
+
playingNodeId,
|
|
691
|
+
isPlaying,
|
|
692
|
+
setIsPlaying,
|
|
693
|
+
play,
|
|
694
|
+
showPopup
|
|
695
|
+
]
|
|
696
|
+
);
|
|
697
|
+
const handleAddChild = (0, import_react4.useCallback)(
|
|
698
|
+
(parentId) => {
|
|
699
|
+
const genrePlaylist = genrePlaylistTreePerRoot.find((g) => g.uuid === parentId);
|
|
700
|
+
if (!genrePlaylist?.criteria) return;
|
|
701
|
+
handleGenreCreationAction(genrePlaylist.criteria);
|
|
702
|
+
},
|
|
703
|
+
[genrePlaylistTreePerRoot, handleGenreCreationAction]
|
|
704
|
+
);
|
|
705
|
+
const handleRenameRequest = (0, import_react4.useCallback)(
|
|
706
|
+
(node) => {
|
|
707
|
+
const genrePlaylist = genrePlaylistTreePerRoot.find((g) => g.uuid === node.id);
|
|
708
|
+
if (!genrePlaylist?.criteria) return;
|
|
709
|
+
handleGenreRenameAction(genrePlaylist.criteria);
|
|
710
|
+
},
|
|
711
|
+
[genrePlaylistTreePerRoot, handleGenreRenameAction]
|
|
712
|
+
);
|
|
713
|
+
const handleDeleteRequest = (0, import_react4.useCallback)((node) => {
|
|
714
|
+
if (confirm(`Are you sure you want to delete "${node.name}"?`)) {
|
|
715
|
+
}
|
|
716
|
+
}, []);
|
|
717
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
718
|
+
import_genre_tree_view.GenreTree,
|
|
650
719
|
{
|
|
651
|
-
href: url,
|
|
652
|
-
target: "_blank",
|
|
653
|
-
rel: "noopener noreferrer",
|
|
654
|
-
"aria-label": `View ${song.title} on MusicBrainz`,
|
|
655
720
|
className: className2,
|
|
656
|
-
|
|
657
|
-
|
|
721
|
+
nodes,
|
|
722
|
+
itemCountLabel: `${(0, import_track_noun2.capitalize)(trackNoun.singular)} count`,
|
|
723
|
+
rootColor: (0, import_genre_tree_view.getGenreTreeColor)(rootUuid),
|
|
724
|
+
playingNodeId,
|
|
725
|
+
playState: isPlaying ? "playing" : "paused",
|
|
726
|
+
onPlayPause: handlePlayPause,
|
|
727
|
+
onAddChild: readOnly ? void 0 : handleAddChild,
|
|
728
|
+
onRenameRequest: readOnly ? void 0 : handleRenameRequest,
|
|
729
|
+
onDeleteRequest: readOnly ? void 0 : handleDeleteRequest,
|
|
730
|
+
additionalActions,
|
|
731
|
+
onNodeClick,
|
|
732
|
+
onNodeHover,
|
|
733
|
+
renderInfoPanelBody,
|
|
734
|
+
selectedNodeId,
|
|
735
|
+
onSelectedNodeChange,
|
|
736
|
+
hideInfoPanelClose,
|
|
737
|
+
hideInfoPanelTitle,
|
|
738
|
+
showToolbar,
|
|
739
|
+
wheelZoom,
|
|
740
|
+
defaultViewport,
|
|
741
|
+
viewportRestore,
|
|
742
|
+
onViewportChange,
|
|
743
|
+
...infoPanelCuration,
|
|
744
|
+
readOnly
|
|
658
745
|
}
|
|
659
746
|
);
|
|
660
747
|
}
|
|
661
748
|
|
|
662
|
-
// src/genre-tree/
|
|
663
|
-
var
|
|
664
|
-
var
|
|
665
|
-
var
|
|
666
|
-
var
|
|
667
|
-
var
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
var nextCursor = (next) => {
|
|
678
|
-
const cursor = new URL(next).searchParams.get("cursor");
|
|
679
|
-
if (!cursor) throw new Error(`Songs next link has no cursor: ${next}`);
|
|
680
|
-
return cursor;
|
|
749
|
+
// src/genre-tree/playlist-tree/TreeWheel.tsx
|
|
750
|
+
var import_react5 = require("react");
|
|
751
|
+
var import_genre_tree_view2 = require("@behindthemusictree/genre-tree-view");
|
|
752
|
+
var import_player2 = require("@behindthemusictree/app-kit/player");
|
|
753
|
+
var import_popup3 = require("@behindthemusictree/app-kit/popup");
|
|
754
|
+
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;
|
|
681
764
|
};
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
765
|
+
|
|
766
|
+
// src/genre-tree/playlist-tree/TreeWheel.tsx
|
|
767
|
+
var import_track_noun3 = require("@behindthemusictree/app-kit/track-noun");
|
|
768
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
769
|
+
function GenrePlaylistTreeWheel({
|
|
685
770
|
scope,
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
771
|
+
className: className2,
|
|
772
|
+
genrePlaylists,
|
|
773
|
+
handleGenreCreationAction,
|
|
774
|
+
handleGenreRenameAction,
|
|
775
|
+
additionalActions,
|
|
776
|
+
onNodeClick,
|
|
777
|
+
onNodeHover,
|
|
778
|
+
renderInfoPanelBody,
|
|
779
|
+
selectedNodeId,
|
|
780
|
+
onSelectedNodeChange,
|
|
781
|
+
hideInfoPanelClose,
|
|
782
|
+
hideInfoPanelTitle,
|
|
783
|
+
readOnly = false,
|
|
784
|
+
allowWheelRotation,
|
|
785
|
+
showToolbar,
|
|
786
|
+
sizeMetric = "songs",
|
|
787
|
+
floatingControlsLeading,
|
|
788
|
+
busy,
|
|
789
|
+
defaultViewport,
|
|
790
|
+
viewportRestore,
|
|
791
|
+
onViewportChange,
|
|
792
|
+
playback,
|
|
793
|
+
...infoPanelCuration
|
|
692
794
|
}) {
|
|
693
|
-
const {
|
|
694
|
-
const {
|
|
695
|
-
const { playerTrackObject, playState, handlePlayPauseAction } = (0, import_player.usePlayer)();
|
|
696
|
-
const { showPopup } = (0, import_popup2.usePopup)();
|
|
697
|
-
const trackNoun = (0, import_track_noun2.useTrackNoun)();
|
|
698
|
-
const { uuid } = genrePlaylist;
|
|
699
|
-
const { fetch } = (0, import_transport2.useFetchWrapper)(getBackendBaseUrl);
|
|
700
|
-
const { data, isPending, isError, refetch, hasNextPage, isFetchingNextPage, fetchNextPage } = (0, import_react_query.useInfiniteQuery)({
|
|
701
|
-
queryKey: scope === "me" ? import_genre_tree2.genrePlaylistQueryKeys.me.tracks(uuid) : artist ? [...import_genre_tree3.libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid), "artist", artist.uuid] : import_genre_tree3.libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid),
|
|
702
|
-
queryFn: async ({ pageParam }) => {
|
|
703
|
-
if (scope === "me") return fetchGenrePlaylistTracksPage(uuid, scope, Number(pageParam));
|
|
704
|
-
const response = await fetch(
|
|
705
|
-
import_genre_tree3.libraryEndpoints.reference.songs.list(),
|
|
706
|
-
true,
|
|
707
|
-
false,
|
|
708
|
-
{},
|
|
709
|
-
{ genre: genreUuid, ...artist && { artist: artist.uuid }, pageSize: CATALOGUE_PAGE_SIZE, withTotal: false, ...pageParam && { cursor: pageParam } }
|
|
710
|
-
);
|
|
711
|
-
const parsed = (0, import_transport4.parseWithLog)(SongPageSchema, response, "GenrePlaylistTracks");
|
|
712
|
-
return { tracks: parsed.results, total: parsed.overallTotal, nextPage: parsed.next ? nextCursor(parsed.next) : null };
|
|
713
|
-
},
|
|
714
|
-
initialPageParam: scope === "me" ? 1 : "",
|
|
715
|
-
getNextPageParam: (lastPage) => lastPage.nextPage ?? void 0,
|
|
716
|
-
enabled: scope !== "me" || sessionRestored && !!session?.accessToken
|
|
717
|
-
});
|
|
718
|
-
const sentinelRef = (0, import_react4.useRef)(null);
|
|
719
|
-
(0, import_react4.useEffect)(() => {
|
|
720
|
-
const sentinel = sentinelRef.current;
|
|
721
|
-
if (!hasNextPage || isFetchingNextPage || !sentinel) return;
|
|
722
|
-
const observer = new IntersectionObserver((entries) => {
|
|
723
|
-
if (entries.some((entry) => entry.isIntersecting)) {
|
|
724
|
-
void fetchNextPage();
|
|
725
|
-
}
|
|
726
|
-
});
|
|
727
|
-
observer.observe(sentinel);
|
|
728
|
-
return () => observer.disconnect();
|
|
729
|
-
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);
|
|
730
|
-
const pages = data?.pages ?? [];
|
|
731
|
-
const tracks = pages.flatMap((page) => page.tracks);
|
|
732
|
-
const total = pages[0]?.total ?? void 0;
|
|
733
|
-
const play = (startSongUuid) => {
|
|
734
|
-
const started = artist ? playNewTrackListFromArtist(artist, scope, { startSongUuid, ordering: "title", genreUuid }) : playNewTrackListFromGenrePlaylist(genrePlaylist, scope, { startSongUuid });
|
|
735
|
-
started.catch((error) => {
|
|
736
|
-
console.error("Failed to play genre playlist:", error);
|
|
737
|
-
showPopup(/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(InternalErrorPopup, { errorCode: import_transport5.ErrorCode.CLIENT_UNKNOWN }));
|
|
738
|
-
});
|
|
739
|
-
};
|
|
740
|
-
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "gtv-info-panel-children", children: [
|
|
741
|
-
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "gtv-info-panel-children-title", children: [
|
|
742
|
-
(0, import_track_noun2.capitalize)(trackNoun.plural),
|
|
743
|
-
total !== void 0 && ` (${total})`
|
|
744
|
-
] }),
|
|
745
|
-
renderTracksToolbar?.(),
|
|
746
|
-
isPending ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_ui6.Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("p", { role: "alert", children: [
|
|
747
|
-
"Couldn't load ",
|
|
748
|
-
trackNoun.plural,
|
|
749
|
-
".",
|
|
750
|
-
" ",
|
|
751
|
-
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
|
|
752
|
-
] }) : tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { children: "\u2014" }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("ul", { className: "mt-1 max-h-64 overflow-y-auto text-[0.8125rem] leading-[1.45] [container-type:inline-size]", "aria-label": `${genrePlaylist.name} ${trackNoun.plural}`, children: [
|
|
753
|
-
tracks.map((track, index) => {
|
|
754
|
-
const label = track.artists?.length ? `${track.title} \u2014 ${track.artists.map((artist2) => artist2.name).join(", ")}` : track.title;
|
|
755
|
-
const isCurrent = playerTrackObject?.track.id === track.uuid;
|
|
756
|
-
const isPlaying = isCurrent && playState === import_player2.PlayStates.PLAYING;
|
|
757
|
-
const text = /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: TEXT_CELL, children: [
|
|
758
|
-
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: TITLE_CELL, children: (0, import_genre_tree4.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
|
|
759
|
-
track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: SEPARATOR, children: " \u2014 " }) : null,
|
|
760
|
-
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_genre_tree6.ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
|
|
761
|
-
track.album && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: ALBUM_CELL, children: track.album.name })
|
|
762
|
-
] });
|
|
763
|
-
const leading = renderSongLeading?.(track, index);
|
|
764
|
-
const actions = renderSongActions?.(track);
|
|
765
|
-
const isUnplayable = (0, import_genre_tree4.isSong)(track) && (0, import_genre_tree4.songUnplayableReason)(track) !== null;
|
|
766
|
-
if (isUnplayable) {
|
|
767
|
-
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("li", { className: "flex min-h-8 items-center gap-1 px-1 opacity-50", children: [
|
|
768
|
-
leading,
|
|
769
|
-
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
770
|
-
"button",
|
|
771
|
-
{
|
|
772
|
-
type: "button",
|
|
773
|
-
disabled: true,
|
|
774
|
-
"aria-disabled": "true",
|
|
775
|
-
"aria-label": label,
|
|
776
|
-
className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full opacity-0 cursor-not-allowed",
|
|
777
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_fa3.FaPlay, { size: 10 })
|
|
778
|
-
}
|
|
779
|
-
),
|
|
780
|
-
text,
|
|
781
|
-
actions
|
|
782
|
-
] }, track.uuid);
|
|
783
|
-
}
|
|
784
|
-
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
|
|
785
|
-
"li",
|
|
786
|
-
{
|
|
787
|
-
className: `group flex min-h-8 items-center gap-1 rounded px-1 hover:bg-[var(--color-neutral-100)] ${isCurrent ? "font-medium text-[var(--color-primary)]" : ""}`,
|
|
788
|
-
children: [
|
|
789
|
-
leading,
|
|
790
|
-
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
791
|
-
"button",
|
|
792
|
-
{
|
|
793
|
-
type: "button",
|
|
794
|
-
"aria-label": `${isPlaying ? "Pause" : "Play"} ${label}`,
|
|
795
|
-
className: `flex h-6 w-6 shrink-0 items-center justify-center rounded-full cursor-pointer group-hover:opacity-100 focus-visible:opacity-100 ${isCurrent && playState !== import_player2.PlayStates.STOPPED ? "opacity-100" : "opacity-0 [@media(pointer:coarse)]:opacity-100"}`,
|
|
796
|
-
onClick: () => isCurrent ? handlePlayPauseAction() : play(track.uuid),
|
|
797
|
-
children: isCurrent && playState === import_player2.PlayStates.LOADING ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_ui6.RingLoader, { size: 10 }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_fa3.FaPause, { size: 10 }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_fa3.FaPlay, { size: 10 })
|
|
798
|
-
}
|
|
799
|
-
),
|
|
800
|
-
text,
|
|
801
|
-
actions
|
|
802
|
-
]
|
|
803
|
-
},
|
|
804
|
-
track.uuid
|
|
805
|
-
);
|
|
806
|
-
}),
|
|
807
|
-
hasNextPage && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("li", { ref: sentinelRef, "aria-hidden": "true", className: "h-4" })
|
|
808
|
-
] })
|
|
809
|
-
] });
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
// src/genre-tree/GenreDetailExtras.tsx
|
|
813
|
-
var import_track_noun3 = require("@behindthemusictree/app-kit/track-noun");
|
|
814
|
-
var import_si = require("react-icons/si");
|
|
815
|
-
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
816
|
-
function GenreDetailExtras({
|
|
817
|
-
genreUuid,
|
|
818
|
-
genrePlaylist,
|
|
819
|
-
wikidataId,
|
|
820
|
-
scope,
|
|
821
|
-
getBackendBaseUrl,
|
|
822
|
-
criteriaOverviewSchema,
|
|
823
|
-
renderGenreDetailExtras,
|
|
824
|
-
renderArtistLink,
|
|
825
|
-
renderSummary,
|
|
826
|
-
renderEssentialTracks,
|
|
827
|
-
family,
|
|
828
|
-
...trackSlots
|
|
829
|
-
}) {
|
|
830
|
-
const trackNoun = (0, import_track_noun3.useTrackNoun)();
|
|
831
|
-
const { data: overview, isPending } = (0, import_genre_tree7.useFetchGenreOverview)(
|
|
832
|
-
genreUuid,
|
|
833
|
-
scope,
|
|
834
|
-
getBackendBaseUrl,
|
|
835
|
-
criteriaOverviewSchema
|
|
836
|
-
);
|
|
837
|
-
if (isPending) {
|
|
838
|
-
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
|
|
839
|
-
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "gtv-info-panel-children", "aria-busy": "true", "data-testid": "genre-detail-extras-skeleton", children: [
|
|
840
|
-
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "gtv-info-panel-children-title", children: "Summary" }),
|
|
841
|
-
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ui7.Skeleton, { className: "h-4 w-3/4" })
|
|
842
|
-
] }),
|
|
843
|
-
family
|
|
844
|
-
] });
|
|
845
|
-
}
|
|
846
|
-
if (!overview) return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_jsx_runtime15.Fragment, { children: family });
|
|
847
|
-
const { summary, summarySourceUrl, essentialSongs } = overview;
|
|
848
|
-
const wikidataUrl = wikidataId ? `https://www.wikidata.org/wiki/${encodeURIComponent(wikidataId)}` : null;
|
|
849
|
-
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
|
|
850
|
-
(summarySourceUrl || wikidataUrl) && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "gtv-info-panel-children text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: [
|
|
851
|
-
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "gtv-info-panel-children-title", children: "Links" }),
|
|
852
|
-
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "mt-1 flex flex-wrap gap-x-3 gap-y-1 text-xs", children: [
|
|
853
|
-
summarySourceUrl && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
854
|
-
"a",
|
|
855
|
-
{
|
|
856
|
-
href: summarySourceUrl,
|
|
857
|
-
target: "_blank",
|
|
858
|
-
rel: "noopener noreferrer",
|
|
859
|
-
className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
|
|
860
|
-
children: [
|
|
861
|
-
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_si.SiWikipedia, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
|
|
862
|
-
"Wikipedia"
|
|
863
|
-
]
|
|
864
|
-
}
|
|
865
|
-
),
|
|
866
|
-
wikidataUrl && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
867
|
-
"a",
|
|
868
|
-
{
|
|
869
|
-
href: wikidataUrl,
|
|
870
|
-
target: "_blank",
|
|
871
|
-
rel: "noopener noreferrer",
|
|
872
|
-
className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
|
|
873
|
-
children: [
|
|
874
|
-
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_si.SiWikidata, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
|
|
875
|
-
"Wikidata"
|
|
876
|
-
]
|
|
877
|
-
}
|
|
878
|
-
)
|
|
879
|
-
] })
|
|
880
|
-
] }),
|
|
881
|
-
renderSummary ? renderSummary(overview) : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "gtv-info-panel-children text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: [
|
|
882
|
-
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "gtv-info-panel-children-title", children: "Summary" }),
|
|
883
|
-
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ClampedText, { className: "mt-1", children: summary ?? "\u2014" }, genreUuid)
|
|
884
|
-
] }),
|
|
885
|
-
family,
|
|
886
|
-
renderGenreDetailExtras?.(overview),
|
|
887
|
-
renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "gtv-info-panel-children text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: [
|
|
888
|
-
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "gtv-info-panel-children-title", children: [
|
|
889
|
-
"Essential ",
|
|
890
|
-
trackNoun.plural
|
|
891
|
-
] }),
|
|
892
|
-
essentialSongs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("ul", { className: "mt-1 divide-y divide-[var(--color-border)]", children: essentialSongs.map((track) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("li", { className: "py-1", children: track.title }, track.uuid)) }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "mt-1", children: "\u2014" })
|
|
893
|
-
] }),
|
|
894
|
-
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
895
|
-
GenrePlaylistTracks,
|
|
896
|
-
{
|
|
897
|
-
genreUuid,
|
|
898
|
-
genrePlaylist,
|
|
899
|
-
scope,
|
|
900
|
-
getBackendBaseUrl,
|
|
901
|
-
renderArtistLink,
|
|
902
|
-
...trackSlots
|
|
903
|
-
}
|
|
904
|
-
)
|
|
905
|
-
] });
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
// src/genre-tree/playlist-tree/TreePerRoot.tsx
|
|
909
|
-
var import_react6 = require("react");
|
|
910
|
-
var import_genre_tree_view = require("@behindthemusictree/genre-tree-view");
|
|
911
|
-
var import_player3 = require("@behindthemusictree/app-kit/player");
|
|
912
|
-
var import_popup3 = require("@behindthemusictree/app-kit/popup");
|
|
913
|
-
var import_transport6 = require("@behindthemusictree/app-kit/transport");
|
|
914
|
-
|
|
915
|
-
// src/genre-tree/playlist-tree/useGenrePlaylistPlayback.ts
|
|
916
|
-
var import_react5 = require("react");
|
|
917
|
-
var import_genre_tree8 = require("@behindthemusictree/app-kit/genre-tree");
|
|
918
|
-
function useGenrePlaylistPlayback({
|
|
919
|
-
scope,
|
|
920
|
-
genrePlaylists,
|
|
921
|
-
artist = null
|
|
922
|
-
}) {
|
|
923
|
-
const { trackList, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist } = (0, import_genre_tree8.useTrackList)();
|
|
924
|
-
const origin = trackList?.origin;
|
|
925
|
-
const playingNodeId = (0, import_react5.useMemo)(() => {
|
|
926
|
-
if (!origin) return null;
|
|
927
|
-
if (!artist) return origin.type === import_genre_tree8.TrackListOriginType.GENRE_PLAYLIST ? origin.uuid : null;
|
|
928
|
-
if (origin.type !== import_genre_tree8.TrackListOriginType.ARTIST || origin.uuid !== artist.uuid) return null;
|
|
929
|
-
const { genreUuid } = origin;
|
|
930
|
-
return genreUuid ? genrePlaylists.find((gp) => gp.criteria?.uuid === genreUuid)?.uuid ?? null : null;
|
|
931
|
-
}, [origin, artist, genrePlaylists]);
|
|
932
|
-
const play = (0, import_react5.useCallback)(
|
|
933
|
-
async (genrePlaylist) => {
|
|
934
|
-
if (!artist) return playNewTrackListFromGenrePlaylist(genrePlaylist, scope);
|
|
935
|
-
if (!genrePlaylist.criteria) return;
|
|
936
|
-
return playNewTrackListFromArtist(artist, scope, { ordering: "title", genreUuid: genrePlaylist.criteria.uuid });
|
|
937
|
-
},
|
|
938
|
-
[artist, scope, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist]
|
|
939
|
-
);
|
|
940
|
-
return (0, import_react5.useMemo)(() => ({ playingNodeId, play }), [playingNodeId, play]);
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
// src/genre-tree/playlist-tree/TreePerRoot.tsx
|
|
944
|
-
var import_track_noun4 = require("@behindthemusictree/app-kit/track-noun");
|
|
945
|
-
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
946
|
-
function GenrePlaylistTreePerRoot({
|
|
947
|
-
scope,
|
|
948
|
-
className: className2,
|
|
949
|
-
rootUuid,
|
|
950
|
-
genrePlaylistTreePerRoot,
|
|
951
|
-
handleGenreCreationAction,
|
|
952
|
-
handleGenreRenameAction,
|
|
953
|
-
additionalActions,
|
|
954
|
-
onNodeClick,
|
|
955
|
-
onNodeHover,
|
|
956
|
-
renderInfoPanelBody,
|
|
957
|
-
selectedNodeId,
|
|
958
|
-
onSelectedNodeChange,
|
|
959
|
-
hideInfoPanelClose,
|
|
960
|
-
hideInfoPanelTitle,
|
|
961
|
-
readOnly = false,
|
|
962
|
-
showToolbar,
|
|
963
|
-
wheelZoom,
|
|
964
|
-
controlsOrientation,
|
|
965
|
-
defaultViewport,
|
|
966
|
-
onViewportChange,
|
|
967
|
-
playback,
|
|
968
|
-
...infoPanelCuration
|
|
969
|
-
}) {
|
|
970
|
-
const { isPlaying, setIsPlaying } = (0, import_player3.usePlayer)();
|
|
971
|
-
const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists: genrePlaylistTreePerRoot });
|
|
795
|
+
const { isPlaying, setIsPlaying } = (0, import_player2.usePlayer)();
|
|
796
|
+
const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists });
|
|
972
797
|
const { playingNodeId, play } = playback ?? defaultPlayback;
|
|
973
|
-
const trackNoun = (0,
|
|
798
|
+
const trackNoun = (0, import_track_noun3.useTrackNoun)();
|
|
974
799
|
const { showPopup } = (0, import_popup3.usePopup)();
|
|
975
|
-
const nodes = (0,
|
|
976
|
-
() =>
|
|
800
|
+
const nodes = (0, import_react5.useMemo)(
|
|
801
|
+
() => genrePlaylists.map((genrePlaylist) => ({
|
|
977
802
|
id: genrePlaylist.uuid,
|
|
978
803
|
parentId: genrePlaylist.parent?.uuid ?? null,
|
|
979
804
|
name: genrePlaylist.name,
|
|
980
|
-
itemCount: genrePlaylist
|
|
805
|
+
itemCount: genrePlaylistSize(genrePlaylist, sizeMetric),
|
|
981
806
|
actionable: Boolean(genrePlaylist.criteria),
|
|
982
807
|
side: genrePlaylist.criteria?.side ?? void 0,
|
|
983
|
-
wikidataId: genrePlaylist.criteria?.wikidataId ?? null
|
|
984
|
-
umbrella: genrePlaylist.isUmbrella
|
|
808
|
+
wikidataId: genrePlaylist.criteria?.wikidataId ?? null
|
|
985
809
|
})),
|
|
986
|
-
[
|
|
810
|
+
[genrePlaylists, sizeMetric]
|
|
987
811
|
);
|
|
988
|
-
const handlePlayPause = (0,
|
|
812
|
+
const handlePlayPause = (0, import_react5.useCallback)(
|
|
989
813
|
(nodeId) => {
|
|
990
|
-
const genrePlaylist =
|
|
814
|
+
const genrePlaylist = genrePlaylists.find((g) => g.uuid === nodeId);
|
|
991
815
|
if (!genrePlaylist) return;
|
|
992
816
|
if (nodeId === playingNodeId) {
|
|
993
817
|
setIsPlaying(!isPlaying);
|
|
@@ -998,11 +822,11 @@ function GenrePlaylistTreePerRoot({
|
|
|
998
822
|
}
|
|
999
823
|
play(genrePlaylist).catch((error) => {
|
|
1000
824
|
console.error("Failed to load genre playlist tracks:", error);
|
|
1001
|
-
showPopup(/* @__PURE__ */ (0,
|
|
825
|
+
showPopup(/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(InternalErrorPopup, { errorCode: import_transport3.ErrorCode.CLIENT_UNKNOWN }));
|
|
1002
826
|
});
|
|
1003
827
|
},
|
|
1004
828
|
[
|
|
1005
|
-
|
|
829
|
+
genrePlaylists,
|
|
1006
830
|
playingNodeId,
|
|
1007
831
|
isPlaying,
|
|
1008
832
|
setIsPlaying,
|
|
@@ -1010,33 +834,32 @@ function GenrePlaylistTreePerRoot({
|
|
|
1010
834
|
showPopup
|
|
1011
835
|
]
|
|
1012
836
|
);
|
|
1013
|
-
const handleAddChild = (0,
|
|
837
|
+
const handleAddChild = (0, import_react5.useCallback)(
|
|
1014
838
|
(parentId) => {
|
|
1015
|
-
const genrePlaylist =
|
|
839
|
+
const genrePlaylist = genrePlaylists.find((g) => g.uuid === parentId);
|
|
1016
840
|
if (!genrePlaylist?.criteria) return;
|
|
1017
841
|
handleGenreCreationAction(genrePlaylist.criteria);
|
|
1018
842
|
},
|
|
1019
|
-
[
|
|
843
|
+
[genrePlaylists, handleGenreCreationAction]
|
|
1020
844
|
);
|
|
1021
|
-
const handleRenameRequest = (0,
|
|
845
|
+
const handleRenameRequest = (0, import_react5.useCallback)(
|
|
1022
846
|
(node) => {
|
|
1023
|
-
const genrePlaylist =
|
|
847
|
+
const genrePlaylist = genrePlaylists.find((g) => g.uuid === node.id);
|
|
1024
848
|
if (!genrePlaylist?.criteria) return;
|
|
1025
849
|
handleGenreRenameAction(genrePlaylist.criteria);
|
|
1026
850
|
},
|
|
1027
|
-
[
|
|
851
|
+
[genrePlaylists, handleGenreRenameAction]
|
|
1028
852
|
);
|
|
1029
|
-
const handleDeleteRequest = (0,
|
|
853
|
+
const handleDeleteRequest = (0, import_react5.useCallback)((node) => {
|
|
1030
854
|
if (confirm(`Are you sure you want to delete "${node.name}"?`)) {
|
|
1031
855
|
}
|
|
1032
856
|
}, []);
|
|
1033
|
-
return /* @__PURE__ */ (0,
|
|
1034
|
-
|
|
857
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
858
|
+
import_genre_tree_view2.GenreTreeWheel,
|
|
1035
859
|
{
|
|
1036
860
|
className: className2,
|
|
1037
861
|
nodes,
|
|
1038
|
-
itemCountLabel: `${(0,
|
|
1039
|
-
rootColor: (0, import_genre_tree_view.getGenreTreeColor)(rootUuid),
|
|
862
|
+
itemCountLabel: sizeMetric === "views" ? "Views" : `${(0, import_track_noun3.capitalize)(trackNoun.singular)} count`,
|
|
1040
863
|
playingNodeId,
|
|
1041
864
|
playState: isPlaying ? "playing" : "paused",
|
|
1042
865
|
onPlayPause: handlePlayPause,
|
|
@@ -1051,151 +874,12 @@ function GenrePlaylistTreePerRoot({
|
|
|
1051
874
|
onSelectedNodeChange,
|
|
1052
875
|
hideInfoPanelClose,
|
|
1053
876
|
hideInfoPanelTitle,
|
|
877
|
+
allowWheelRotation,
|
|
1054
878
|
showToolbar,
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
defaultViewport,
|
|
1058
|
-
onViewportChange,
|
|
1059
|
-
...infoPanelCuration,
|
|
1060
|
-
readOnly
|
|
1061
|
-
}
|
|
1062
|
-
);
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
// src/genre-tree/playlist-tree/TreeWheel.tsx
|
|
1066
|
-
var import_react7 = require("react");
|
|
1067
|
-
var import_genre_tree_view2 = require("@behindthemusictree/genre-tree-view");
|
|
1068
|
-
var import_player4 = require("@behindthemusictree/app-kit/player");
|
|
1069
|
-
var import_popup4 = require("@behindthemusictree/app-kit/popup");
|
|
1070
|
-
var import_transport7 = require("@behindthemusictree/app-kit/transport");
|
|
1071
|
-
|
|
1072
|
-
// src/genre-tree/playlist-tree/size-metric.ts
|
|
1073
|
-
var hasViewsCount = (genrePlaylists) => genrePlaylists.every((genrePlaylist) => genrePlaylist.viewsCount !== void 0);
|
|
1074
|
-
var genrePlaylistSize = (genrePlaylist, metric) => {
|
|
1075
|
-
if (metric === "songs") return genrePlaylist.tracksCount;
|
|
1076
|
-
if (genrePlaylist.viewsCount === void 0) {
|
|
1077
|
-
throw new Error(`Genre playlist ${genrePlaylist.uuid} has no viewsCount to size by views`);
|
|
1078
|
-
}
|
|
1079
|
-
return genrePlaylist.viewsCount;
|
|
1080
|
-
};
|
|
1081
|
-
|
|
1082
|
-
// src/genre-tree/playlist-tree/TreeWheel.tsx
|
|
1083
|
-
var import_track_noun5 = require("@behindthemusictree/app-kit/track-noun");
|
|
1084
|
-
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
1085
|
-
function GenrePlaylistTreeWheel({
|
|
1086
|
-
scope,
|
|
1087
|
-
className: className2,
|
|
1088
|
-
genrePlaylists,
|
|
1089
|
-
handleGenreCreationAction,
|
|
1090
|
-
handleGenreRenameAction,
|
|
1091
|
-
additionalActions,
|
|
1092
|
-
onNodeClick,
|
|
1093
|
-
onNodeHover,
|
|
1094
|
-
renderInfoPanelBody,
|
|
1095
|
-
selectedNodeId,
|
|
1096
|
-
onSelectedNodeChange,
|
|
1097
|
-
hideInfoPanelClose,
|
|
1098
|
-
hideInfoPanelTitle,
|
|
1099
|
-
readOnly = false,
|
|
1100
|
-
allowWheelRotation,
|
|
1101
|
-
showToolbar,
|
|
1102
|
-
controlsOrientation,
|
|
1103
|
-
sizeMetric = "songs",
|
|
1104
|
-
floatingControlsLeading,
|
|
1105
|
-
busy,
|
|
1106
|
-
defaultViewport,
|
|
1107
|
-
onViewportChange,
|
|
1108
|
-
playback,
|
|
1109
|
-
...infoPanelCuration
|
|
1110
|
-
}) {
|
|
1111
|
-
const { isPlaying, setIsPlaying } = (0, import_player4.usePlayer)();
|
|
1112
|
-
const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists });
|
|
1113
|
-
const { playingNodeId, play } = playback ?? defaultPlayback;
|
|
1114
|
-
const trackNoun = (0, import_track_noun5.useTrackNoun)();
|
|
1115
|
-
const { showPopup } = (0, import_popup4.usePopup)();
|
|
1116
|
-
const nodes = (0, import_react7.useMemo)(
|
|
1117
|
-
() => genrePlaylists.map((genrePlaylist) => ({
|
|
1118
|
-
id: genrePlaylist.uuid,
|
|
1119
|
-
parentId: genrePlaylist.parent?.uuid ?? null,
|
|
1120
|
-
name: genrePlaylist.name,
|
|
1121
|
-
itemCount: genrePlaylistSize(genrePlaylist, sizeMetric),
|
|
1122
|
-
actionable: Boolean(genrePlaylist.criteria),
|
|
1123
|
-
side: genrePlaylist.criteria?.side ?? void 0,
|
|
1124
|
-
wikidataId: genrePlaylist.criteria?.wikidataId ?? null
|
|
1125
|
-
})),
|
|
1126
|
-
[genrePlaylists, sizeMetric]
|
|
1127
|
-
);
|
|
1128
|
-
const handlePlayPause = (0, import_react7.useCallback)(
|
|
1129
|
-
(nodeId) => {
|
|
1130
|
-
const genrePlaylist = genrePlaylists.find((g) => g.uuid === nodeId);
|
|
1131
|
-
if (!genrePlaylist) return;
|
|
1132
|
-
if (nodeId === playingNodeId) {
|
|
1133
|
-
setIsPlaying(!isPlaying);
|
|
1134
|
-
return;
|
|
1135
|
-
}
|
|
1136
|
-
if (genrePlaylist.tracksCount === 0) {
|
|
1137
|
-
return;
|
|
1138
|
-
}
|
|
1139
|
-
play(genrePlaylist).catch((error) => {
|
|
1140
|
-
console.error("Failed to load genre playlist tracks:", error);
|
|
1141
|
-
showPopup(/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(InternalErrorPopup, { errorCode: import_transport7.ErrorCode.CLIENT_UNKNOWN }));
|
|
1142
|
-
});
|
|
1143
|
-
},
|
|
1144
|
-
[
|
|
1145
|
-
genrePlaylists,
|
|
1146
|
-
playingNodeId,
|
|
1147
|
-
isPlaying,
|
|
1148
|
-
setIsPlaying,
|
|
1149
|
-
play,
|
|
1150
|
-
showPopup
|
|
1151
|
-
]
|
|
1152
|
-
);
|
|
1153
|
-
const handleAddChild = (0, import_react7.useCallback)(
|
|
1154
|
-
(parentId) => {
|
|
1155
|
-
const genrePlaylist = genrePlaylists.find((g) => g.uuid === parentId);
|
|
1156
|
-
if (!genrePlaylist?.criteria) return;
|
|
1157
|
-
handleGenreCreationAction(genrePlaylist.criteria);
|
|
1158
|
-
},
|
|
1159
|
-
[genrePlaylists, handleGenreCreationAction]
|
|
1160
|
-
);
|
|
1161
|
-
const handleRenameRequest = (0, import_react7.useCallback)(
|
|
1162
|
-
(node) => {
|
|
1163
|
-
const genrePlaylist = genrePlaylists.find((g) => g.uuid === node.id);
|
|
1164
|
-
if (!genrePlaylist?.criteria) return;
|
|
1165
|
-
handleGenreRenameAction(genrePlaylist.criteria);
|
|
1166
|
-
},
|
|
1167
|
-
[genrePlaylists, handleGenreRenameAction]
|
|
1168
|
-
);
|
|
1169
|
-
const handleDeleteRequest = (0, import_react7.useCallback)((node) => {
|
|
1170
|
-
if (confirm(`Are you sure you want to delete "${node.name}"?`)) {
|
|
1171
|
-
}
|
|
1172
|
-
}, []);
|
|
1173
|
-
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
1174
|
-
import_genre_tree_view2.GenreTreeWheel,
|
|
1175
|
-
{
|
|
1176
|
-
className: className2,
|
|
1177
|
-
nodes,
|
|
1178
|
-
itemCountLabel: sizeMetric === "views" ? "Views" : `${(0, import_track_noun5.capitalize)(trackNoun.singular)} count`,
|
|
1179
|
-
playingNodeId,
|
|
1180
|
-
playState: isPlaying ? "playing" : "paused",
|
|
1181
|
-
onPlayPause: handlePlayPause,
|
|
1182
|
-
onAddChild: readOnly ? void 0 : handleAddChild,
|
|
1183
|
-
onRenameRequest: readOnly ? void 0 : handleRenameRequest,
|
|
1184
|
-
onDeleteRequest: readOnly ? void 0 : handleDeleteRequest,
|
|
1185
|
-
additionalActions,
|
|
1186
|
-
onNodeClick,
|
|
1187
|
-
onNodeHover,
|
|
1188
|
-
renderInfoPanelBody,
|
|
1189
|
-
selectedNodeId,
|
|
1190
|
-
onSelectedNodeChange,
|
|
1191
|
-
hideInfoPanelClose,
|
|
1192
|
-
hideInfoPanelTitle,
|
|
1193
|
-
allowWheelRotation,
|
|
1194
|
-
showToolbar,
|
|
1195
|
-
controlsOrientation,
|
|
1196
|
-
floatingControlsLeading,
|
|
1197
|
-
busy,
|
|
879
|
+
floatingControlsLeading,
|
|
880
|
+
busy,
|
|
1198
881
|
defaultViewport,
|
|
882
|
+
viewportRestore,
|
|
1199
883
|
onViewportChange,
|
|
1200
884
|
...infoPanelCuration,
|
|
1201
885
|
readOnly
|
|
@@ -1204,11 +888,11 @@ function GenrePlaylistTreeWheel({
|
|
|
1204
888
|
}
|
|
1205
889
|
|
|
1206
890
|
// src/genre-tree/playlist-tree/TreeWheelRadialPopCore.tsx
|
|
1207
|
-
var
|
|
891
|
+
var import_react6 = require("react");
|
|
1208
892
|
var import_genre_tree_view3 = require("@behindthemusictree/genre-tree-view");
|
|
1209
|
-
var
|
|
1210
|
-
var
|
|
1211
|
-
var
|
|
893
|
+
var import_player3 = require("@behindthemusictree/app-kit/player");
|
|
894
|
+
var import_track_noun4 = require("@behindthemusictree/app-kit/track-noun");
|
|
895
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
1212
896
|
function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
1213
897
|
const {
|
|
1214
898
|
scope,
|
|
@@ -1227,12 +911,12 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
1227
911
|
readOnly = false,
|
|
1228
912
|
allowWheelRotation,
|
|
1229
913
|
showToolbar,
|
|
1230
|
-
controlsOrientation,
|
|
1231
914
|
sizeMetric = "songs",
|
|
1232
915
|
floatingControlsLeading,
|
|
1233
916
|
busy,
|
|
1234
917
|
outline = false,
|
|
1235
918
|
defaultViewport,
|
|
919
|
+
viewportRestore,
|
|
1236
920
|
onViewportChange,
|
|
1237
921
|
playback
|
|
1238
922
|
} = props;
|
|
@@ -1245,11 +929,11 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
1245
929
|
hideExpandButton: props.hideExpandButton,
|
|
1246
930
|
overlayPanel: props.overlayPanel
|
|
1247
931
|
};
|
|
1248
|
-
const { isPlaying, setIsPlaying } = (0,
|
|
932
|
+
const { isPlaying, setIsPlaying } = (0, import_player3.usePlayer)();
|
|
1249
933
|
const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists });
|
|
1250
934
|
const { playingNodeId, play } = playback ?? defaultPlayback;
|
|
1251
|
-
const trackNoun = (0,
|
|
1252
|
-
const nodes = (0,
|
|
935
|
+
const trackNoun = (0, import_track_noun4.useTrackNoun)();
|
|
936
|
+
const nodes = (0, import_react6.useMemo)(
|
|
1253
937
|
() => genrePlaylists.map((genrePlaylist) => ({
|
|
1254
938
|
id: genrePlaylist.uuid,
|
|
1255
939
|
parentId: genrePlaylist.parent?.uuid ?? null,
|
|
@@ -1264,7 +948,7 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
1264
948
|
})),
|
|
1265
949
|
[genrePlaylists, sizeMetric]
|
|
1266
950
|
);
|
|
1267
|
-
const handlePlayPause = (0,
|
|
951
|
+
const handlePlayPause = (0, import_react6.useCallback)(
|
|
1268
952
|
(nodeId) => {
|
|
1269
953
|
const genrePlaylist = genrePlaylists.find((g) => g.uuid === nodeId);
|
|
1270
954
|
if (!genrePlaylist) return;
|
|
@@ -1281,7 +965,7 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
1281
965
|
},
|
|
1282
966
|
[genrePlaylists, playingNodeId, isPlaying, setIsPlaying, play]
|
|
1283
967
|
);
|
|
1284
|
-
const handleAddChild = (0,
|
|
968
|
+
const handleAddChild = (0, import_react6.useCallback)(
|
|
1285
969
|
(parentId) => {
|
|
1286
970
|
const genrePlaylist = genrePlaylists.find((g) => g.uuid === parentId);
|
|
1287
971
|
if (!genrePlaylist?.criteria) return;
|
|
@@ -1289,7 +973,7 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
1289
973
|
},
|
|
1290
974
|
[genrePlaylists, handleGenreCreationAction]
|
|
1291
975
|
);
|
|
1292
|
-
const handleRenameRequest = (0,
|
|
976
|
+
const handleRenameRequest = (0, import_react6.useCallback)(
|
|
1293
977
|
(node) => {
|
|
1294
978
|
const genrePlaylist = genrePlaylists.find((g) => g.uuid === node.id);
|
|
1295
979
|
if (!genrePlaylist?.criteria) return;
|
|
@@ -1297,14 +981,14 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
1297
981
|
},
|
|
1298
982
|
[genrePlaylists, handleGenreRenameAction]
|
|
1299
983
|
);
|
|
1300
|
-
const handleDeleteRequest = (0,
|
|
984
|
+
const handleDeleteRequest = (0, import_react6.useCallback)((node) => {
|
|
1301
985
|
if (confirm(`Are you sure you want to delete "${node.name}"?`)) {
|
|
1302
986
|
}
|
|
1303
987
|
}, []);
|
|
1304
988
|
const treeProps = {
|
|
1305
989
|
className: className2,
|
|
1306
990
|
nodes,
|
|
1307
|
-
itemCountLabel: sizeMetric === "views" ? "Views" : `${(0,
|
|
991
|
+
itemCountLabel: sizeMetric === "views" ? "Views" : `${(0, import_track_noun4.capitalize)(trackNoun.singular)} count`,
|
|
1308
992
|
playingNodeId,
|
|
1309
993
|
playState: isPlaying ? "playing" : "paused",
|
|
1310
994
|
onPlayPause: handlePlayPause,
|
|
@@ -1323,15 +1007,15 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
1323
1007
|
...infoPanelCuration,
|
|
1324
1008
|
readOnly
|
|
1325
1009
|
};
|
|
1326
|
-
return outline ? /* @__PURE__ */ (0,
|
|
1010
|
+
return outline ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_genre_tree_view3.GenreTreeOutline, { ...treeProps }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1327
1011
|
import_genre_tree_view3.GenreTreeWheelRadialPopCore,
|
|
1328
1012
|
{
|
|
1329
1013
|
...treeProps,
|
|
1330
1014
|
allowWheelRotation,
|
|
1331
|
-
controlsOrientation,
|
|
1332
1015
|
floatingControlsLeading,
|
|
1333
1016
|
busy,
|
|
1334
1017
|
defaultViewport,
|
|
1018
|
+
viewportRestore,
|
|
1335
1019
|
onViewportChange,
|
|
1336
1020
|
...focus.onFocusGenreChange ? { focusNodeId: focus.focusGenreUuid, onFocusChange: focus.onFocusGenreChange } : { focusNodeId: void 0, onFocusChange: void 0 }
|
|
1337
1021
|
}
|
|
@@ -1339,13 +1023,13 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
1339
1023
|
}
|
|
1340
1024
|
|
|
1341
1025
|
// src/genre-tree/GenreTreeWheelHandoff.tsx
|
|
1342
|
-
var
|
|
1343
|
-
var
|
|
1026
|
+
var import_react7 = require("react");
|
|
1027
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
1344
1028
|
function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
|
|
1345
|
-
const [revealed, setRevealed] = (0,
|
|
1346
|
-
const onRevealedChangeRef = (0,
|
|
1029
|
+
const [revealed, setRevealed] = (0, import_react7.useState)(false);
|
|
1030
|
+
const onRevealedChangeRef = (0, import_react7.useRef)(onRevealedChange);
|
|
1347
1031
|
onRevealedChangeRef.current = onRevealedChange;
|
|
1348
|
-
(0,
|
|
1032
|
+
(0, import_react7.useEffect)(() => {
|
|
1349
1033
|
let innerFrame = 0;
|
|
1350
1034
|
const outerFrame = requestAnimationFrame(() => {
|
|
1351
1035
|
innerFrame = requestAnimationFrame(() => {
|
|
@@ -1359,8 +1043,8 @@ function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
|
|
|
1359
1043
|
cancelAnimationFrame(innerFrame);
|
|
1360
1044
|
};
|
|
1361
1045
|
}, []);
|
|
1362
|
-
return /* @__PURE__ */ (0,
|
|
1363
|
-
/* @__PURE__ */ (0,
|
|
1046
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "relative w-full h-full", children: [
|
|
1047
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
1364
1048
|
"div",
|
|
1365
1049
|
{
|
|
1366
1050
|
className: "absolute inset-0 transition-opacity duration-150 ease-out",
|
|
@@ -1369,15 +1053,15 @@ function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
|
|
|
1369
1053
|
children
|
|
1370
1054
|
}
|
|
1371
1055
|
),
|
|
1372
|
-
!revealed && /* @__PURE__ */ (0,
|
|
1056
|
+
!revealed && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "absolute inset-0", "aria-hidden": "true", children: skeleton })
|
|
1373
1057
|
] });
|
|
1374
1058
|
}
|
|
1375
1059
|
|
|
1376
1060
|
// src/genre-tree/GenreSearch.tsx
|
|
1377
1061
|
var import_lucide_react7 = require("lucide-react");
|
|
1378
|
-
var
|
|
1379
|
-
var
|
|
1380
|
-
var
|
|
1062
|
+
var import_ui6 = require("@behindthemusictree/ui");
|
|
1063
|
+
var import_genre_tree2 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1064
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
1381
1065
|
function GenreSearch({
|
|
1382
1066
|
genrePlaylists,
|
|
1383
1067
|
onSelect,
|
|
@@ -1388,7 +1072,7 @@ function GenreSearch({
|
|
|
1388
1072
|
onQueryChange,
|
|
1389
1073
|
extraResults
|
|
1390
1074
|
}) {
|
|
1391
|
-
const { query, setQuery, results, canClear, clear, select } = (0,
|
|
1075
|
+
const { query, setQuery, results, canClear, clear, select } = (0, import_genre_tree2.useGenreSearch)({
|
|
1392
1076
|
genrePlaylists,
|
|
1393
1077
|
onSelect,
|
|
1394
1078
|
selectedName,
|
|
@@ -1399,8 +1083,8 @@ function GenreSearch({
|
|
|
1399
1083
|
const loading = query !== "" && query !== selectedName && (extraResults?.loading ?? false);
|
|
1400
1084
|
const showList = results.length > 0 || extraItems.length > 0;
|
|
1401
1085
|
const heading = "px-3 pt-2 pb-1 text-xs font-medium uppercase text-[var(--color-text-muted)]";
|
|
1402
|
-
return /* @__PURE__ */ (0,
|
|
1403
|
-
onPrevious && /* @__PURE__ */ (0,
|
|
1086
|
+
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
|
+
onPrevious && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1404
1088
|
"button",
|
|
1405
1089
|
{
|
|
1406
1090
|
type: "button",
|
|
@@ -1408,11 +1092,11 @@ function GenreSearch({
|
|
|
1408
1092
|
title: "Previous",
|
|
1409
1093
|
className: "genre-search-previous absolute left-2 top-1/2 z-10 hidden 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)]",
|
|
1410
1094
|
onClick: onPrevious,
|
|
1411
|
-
children: /* @__PURE__ */ (0,
|
|
1095
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react7.ArrowLeft, { "aria-hidden": "true", className: "w-4 h-4" })
|
|
1412
1096
|
}
|
|
1413
1097
|
),
|
|
1414
|
-
/* @__PURE__ */ (0,
|
|
1415
|
-
|
|
1098
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1099
|
+
import_ui6.Input,
|
|
1416
1100
|
{
|
|
1417
1101
|
type: "text",
|
|
1418
1102
|
value: query,
|
|
@@ -1429,21 +1113,21 @@ function GenreSearch({
|
|
|
1429
1113
|
className: "h-[var(--layout-search-h,3rem)] pl-6 pr-20 text-base transition-[padding] duration-200 ease-out motion-reduce:transition-none bg-[var(--color-surface)] border-[var(--color-border)] rounded-full shadow-md"
|
|
1430
1114
|
}
|
|
1431
1115
|
),
|
|
1432
|
-
/* @__PURE__ */ (0,
|
|
1433
|
-
/* @__PURE__ */ (0,
|
|
1116
|
+
/* @__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)(
|
|
1434
1118
|
"span",
|
|
1435
1119
|
{
|
|
1436
1120
|
"data-loading": loading,
|
|
1437
1121
|
className: "relative p-2 text-[var(--color-text-muted)] pointer-events-none",
|
|
1438
1122
|
children: [
|
|
1439
|
-
/* @__PURE__ */ (0,
|
|
1123
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1440
1124
|
import_lucide_react7.Search,
|
|
1441
1125
|
{
|
|
1442
1126
|
"aria-hidden": "true",
|
|
1443
1127
|
className: `w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? "opacity-0" : ""}`
|
|
1444
1128
|
}
|
|
1445
1129
|
),
|
|
1446
|
-
/* @__PURE__ */ (0,
|
|
1130
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1447
1131
|
import_lucide_react7.LoaderCircle,
|
|
1448
1132
|
{
|
|
1449
1133
|
"aria-hidden": "true",
|
|
@@ -1453,7 +1137,7 @@ function GenreSearch({
|
|
|
1453
1137
|
]
|
|
1454
1138
|
}
|
|
1455
1139
|
),
|
|
1456
|
-
canClear && /* @__PURE__ */ (0,
|
|
1140
|
+
canClear && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1457
1141
|
"button",
|
|
1458
1142
|
{
|
|
1459
1143
|
type: "button",
|
|
@@ -1463,14 +1147,14 @@ function GenreSearch({
|
|
|
1463
1147
|
clear();
|
|
1464
1148
|
onQueryChange?.("");
|
|
1465
1149
|
},
|
|
1466
|
-
children: /* @__PURE__ */ (0,
|
|
1150
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react7.X, { "aria-hidden": "true", className: "w-6 h-6" })
|
|
1467
1151
|
}
|
|
1468
1152
|
)
|
|
1469
1153
|
] }),
|
|
1470
|
-
extraResults?.status && /* @__PURE__ */ (0,
|
|
1471
|
-
showList && /* @__PURE__ */ (0,
|
|
1472
|
-
extraResults && results.length > 0 && /* @__PURE__ */ (0,
|
|
1473
|
-
results.map((genrePlaylist) => /* @__PURE__ */ (0,
|
|
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)(
|
|
1474
1158
|
"button",
|
|
1475
1159
|
{
|
|
1476
1160
|
type: "button",
|
|
@@ -1481,12 +1165,12 @@ function GenreSearch({
|
|
|
1481
1165
|
},
|
|
1482
1166
|
children: [
|
|
1483
1167
|
genrePlaylist.name,
|
|
1484
|
-
genrePlaylist.isUmbrella && /* @__PURE__ */ (0,
|
|
1168
|
+
genrePlaylist.isUmbrella && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "ml-2 text-xs italic text-[var(--color-text-muted)]", children: "umbrella" })
|
|
1485
1169
|
]
|
|
1486
1170
|
}
|
|
1487
1171
|
) }, genrePlaylist.uuid)),
|
|
1488
|
-
extraResults && extraItems.length > 0 && /* @__PURE__ */ (0,
|
|
1489
|
-
extraItems.map((item) => /* @__PURE__ */ (0,
|
|
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)(
|
|
1490
1174
|
"button",
|
|
1491
1175
|
{
|
|
1492
1176
|
type: "button",
|
|
@@ -1503,26 +1187,29 @@ function GenreSearch({
|
|
|
1503
1187
|
] });
|
|
1504
1188
|
}
|
|
1505
1189
|
|
|
1190
|
+
// src/genre-tree/GenreInfoPanelBody.tsx
|
|
1191
|
+
var import_player6 = require("@behindthemusictree/app-kit/player");
|
|
1192
|
+
|
|
1506
1193
|
// src/genre-tree/GenreActionBand.tsx
|
|
1507
1194
|
var import_lucide_react11 = require("lucide-react");
|
|
1508
1195
|
|
|
1509
1196
|
// src/genre-tree/ShareActionButton.tsx
|
|
1510
1197
|
var import_lucide_react10 = require("lucide-react");
|
|
1511
|
-
var
|
|
1198
|
+
var import_popup4 = require("@behindthemusictree/app-kit/popup");
|
|
1512
1199
|
|
|
1513
1200
|
// src/genre-tree/SharePopup.tsx
|
|
1514
|
-
var
|
|
1201
|
+
var import_react8 = require("react");
|
|
1515
1202
|
var import_lucide_react9 = require("lucide-react");
|
|
1516
|
-
var
|
|
1517
|
-
var
|
|
1203
|
+
var import_fa3 = require("react-icons/fa");
|
|
1204
|
+
var import_si = require("react-icons/si");
|
|
1518
1205
|
|
|
1519
1206
|
// src/genre-tree/ShareDestinationButton.tsx
|
|
1520
|
-
var
|
|
1207
|
+
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
1521
1208
|
var className = "group flex min-w-0 flex-1 flex-col items-center justify-center gap-1.5 rounded-xl px-2 py-2 text-xs text-[var(--color-text)] transition-colors hover:bg-[var(--color-surface-muted)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] disabled:cursor-not-allowed disabled:opacity-50";
|
|
1522
1209
|
function Contents({ icon, label }) {
|
|
1523
|
-
return /* @__PURE__ */ (0,
|
|
1524
|
-
/* @__PURE__ */ (0,
|
|
1525
|
-
/* @__PURE__ */ (0,
|
|
1210
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
|
|
1211
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]", children: icon }),
|
|
1212
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "max-w-full truncate", children: label })
|
|
1526
1213
|
] });
|
|
1527
1214
|
}
|
|
1528
1215
|
function ShareDestinationButton({
|
|
@@ -1534,29 +1221,29 @@ function ShareDestinationButton({
|
|
|
1534
1221
|
ariaLabel
|
|
1535
1222
|
}) {
|
|
1536
1223
|
if (href) {
|
|
1537
|
-
return /* @__PURE__ */ (0,
|
|
1224
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", "aria-label": ariaLabel, className, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Contents, { icon, label }) });
|
|
1538
1225
|
}
|
|
1539
|
-
return /* @__PURE__ */ (0,
|
|
1226
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", onClick, disabled, "aria-label": ariaLabel, className, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Contents, { icon, label }) });
|
|
1540
1227
|
}
|
|
1541
1228
|
|
|
1542
1229
|
// src/genre-tree/SharePreviewCard.tsx
|
|
1543
1230
|
var import_lucide_react8 = require("lucide-react");
|
|
1544
|
-
var
|
|
1231
|
+
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
1545
1232
|
function SharePreviewCard({ target, url }) {
|
|
1546
1233
|
const Icon = target.kind === "genre" ? import_lucide_react8.Music2 : import_lucide_react8.UserRound;
|
|
1547
1234
|
const typeLabel = target.kind === "genre" ? "Genre" : "Artist";
|
|
1548
|
-
return /* @__PURE__ */ (0,
|
|
1549
|
-
/* @__PURE__ */ (0,
|
|
1550
|
-
/* @__PURE__ */ (0,
|
|
1551
|
-
/* @__PURE__ */ (0,
|
|
1552
|
-
/* @__PURE__ */ (0,
|
|
1553
|
-
/* @__PURE__ */ (0,
|
|
1235
|
+
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
|
+
/* @__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
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col justify-center gap-1 p-4", children: [
|
|
1238
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "text-xs font-medium uppercase tracking-wide text-[var(--color-text-muted)]", children: typeLabel }),
|
|
1239
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "truncate text-base font-semibold text-[var(--color-text)]", title: target.name, children: target.name }),
|
|
1240
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "truncate text-sm text-[var(--color-primary)]", title: url, children: url })
|
|
1554
1241
|
] })
|
|
1555
1242
|
] }) });
|
|
1556
1243
|
}
|
|
1557
1244
|
|
|
1558
1245
|
// src/genre-tree/SharePopup.tsx
|
|
1559
|
-
var
|
|
1246
|
+
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
1560
1247
|
function resolveAbsoluteUrl(url) {
|
|
1561
1248
|
try {
|
|
1562
1249
|
const parsed = new URL(url, window.location.href);
|
|
@@ -1589,20 +1276,20 @@ async function copyText(text) {
|
|
|
1589
1276
|
}
|
|
1590
1277
|
}
|
|
1591
1278
|
function SharePopup({ target, url, onClose }) {
|
|
1592
|
-
const [absoluteUrl, setAbsoluteUrl] = (0,
|
|
1593
|
-
const [copyStatus, setCopyStatus] = (0,
|
|
1594
|
-
const [canUseNativeShare, setCanUseNativeShare] = (0,
|
|
1595
|
-
const [shareError, setShareError] = (0,
|
|
1596
|
-
const [toastId, setToastId] = (0,
|
|
1597
|
-
(0,
|
|
1279
|
+
const [absoluteUrl, setAbsoluteUrl] = (0, import_react8.useState)(url);
|
|
1280
|
+
const [copyStatus, setCopyStatus] = (0, import_react8.useState)("idle");
|
|
1281
|
+
const [canUseNativeShare, setCanUseNativeShare] = (0, import_react8.useState)(false);
|
|
1282
|
+
const [shareError, setShareError] = (0, import_react8.useState)(null);
|
|
1283
|
+
const [toastId, setToastId] = (0, import_react8.useState)(0);
|
|
1284
|
+
(0, import_react8.useEffect)(() => {
|
|
1598
1285
|
setAbsoluteUrl(resolveAbsoluteUrl(url) ?? "");
|
|
1599
1286
|
setCopyStatus("idle");
|
|
1600
1287
|
setShareError(null);
|
|
1601
1288
|
}, [url]);
|
|
1602
|
-
(0,
|
|
1289
|
+
(0, import_react8.useEffect)(() => {
|
|
1603
1290
|
setCanUseNativeShare(typeof navigator !== "undefined" && typeof navigator.share === "function");
|
|
1604
1291
|
}, []);
|
|
1605
|
-
const destinationUrls = (0,
|
|
1292
|
+
const destinationUrls = (0, import_react8.useMemo)(() => {
|
|
1606
1293
|
if (!absoluteUrl) return null;
|
|
1607
1294
|
const message = `${target.name} \u2014 ${absoluteUrl}`;
|
|
1608
1295
|
return {
|
|
@@ -1620,7 +1307,7 @@ function SharePopup({ target, url, onClose }) {
|
|
|
1620
1307
|
setCopyStatus(copied ? "idle" : "failed");
|
|
1621
1308
|
if (copied) setToastId((id) => id + 1);
|
|
1622
1309
|
};
|
|
1623
|
-
const dismissToast = (0,
|
|
1310
|
+
const dismissToast = (0, import_react8.useCallback)(() => setToastId(0), []);
|
|
1624
1311
|
const handleNativeShare = async () => {
|
|
1625
1312
|
if (!absoluteUrl || !navigator.share) return;
|
|
1626
1313
|
setShareError(null);
|
|
@@ -1631,8 +1318,8 @@ function SharePopup({ target, url, onClose }) {
|
|
|
1631
1318
|
setShareError("Could not open the share options.");
|
|
1632
1319
|
}
|
|
1633
1320
|
};
|
|
1634
|
-
return /* @__PURE__ */ (0,
|
|
1635
|
-
/* @__PURE__ */ (0,
|
|
1321
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
|
|
1322
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
1636
1323
|
BasePopup_default,
|
|
1637
1324
|
{
|
|
1638
1325
|
title: "Share",
|
|
@@ -1647,175 +1334,550 @@ function SharePopup({ target, url, onClose }) {
|
|
|
1647
1334
|
titleTextClassName: "!text-xl !font-semibold !text-[var(--color-text)]",
|
|
1648
1335
|
closeButtonClassName: "!h-10 !w-10 !rounded-none !border-0 !bg-transparent !p-0 !text-[var(--color-text)] hover:!bg-transparent hover:!text-[var(--color-text)]",
|
|
1649
1336
|
contentClassName: "!rounded-none !bg-transparent !px-6 !pb-6 !pt-2",
|
|
1650
|
-
children: /* @__PURE__ */ (0,
|
|
1651
|
-
/* @__PURE__ */ (0,
|
|
1652
|
-
/* @__PURE__ */ (0,
|
|
1653
|
-
/* @__PURE__ */ (0,
|
|
1337
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex flex-col gap-4", children: [
|
|
1338
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(SharePreviewCard, { target, url: absoluteUrl || url }),
|
|
1339
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "grid grid-cols-3 gap-1 rounded-3xl bg-[var(--color-surface)] p-3 sm:grid-cols-4", children: [
|
|
1340
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
1654
1341
|
ShareDestinationButton,
|
|
1655
1342
|
{
|
|
1656
1343
|
label: "Copy Link",
|
|
1657
|
-
icon: /* @__PURE__ */ (0,
|
|
1344
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react9.Link2, { "aria-hidden": "true", className: "h-6 w-6" }),
|
|
1658
1345
|
onClick: () => void handleCopy(),
|
|
1659
1346
|
ariaLabel: "Copy link",
|
|
1660
1347
|
disabled: !absoluteUrl
|
|
1661
1348
|
}
|
|
1662
1349
|
),
|
|
1663
|
-
/* @__PURE__ */ (0,
|
|
1350
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
1664
1351
|
ShareDestinationButton,
|
|
1665
1352
|
{
|
|
1666
1353
|
label: "WhatsApp",
|
|
1667
|
-
icon: /* @__PURE__ */ (0,
|
|
1354
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_fa3.FaWhatsapp, { "aria-hidden": "true", className: "h-9 w-9 text-[#25D366]" }),
|
|
1668
1355
|
href: destinationUrls?.whatsapp,
|
|
1669
1356
|
ariaLabel: `Share ${target.name} via WhatsApp`,
|
|
1670
1357
|
disabled: !absoluteUrl
|
|
1671
1358
|
}
|
|
1672
1359
|
),
|
|
1673
|
-
/* @__PURE__ */ (0,
|
|
1360
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
1674
1361
|
ShareDestinationButton,
|
|
1675
1362
|
{
|
|
1676
1363
|
label: "Gmail",
|
|
1677
|
-
icon: /* @__PURE__ */ (0,
|
|
1364
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_si.SiGmail, { "aria-hidden": "true", className: "h-8 w-8 text-[#EA4335]" }),
|
|
1678
1365
|
href: destinationUrls?.gmail,
|
|
1679
1366
|
ariaLabel: `Email ${target.name} with Gmail`,
|
|
1680
1367
|
disabled: !absoluteUrl
|
|
1681
1368
|
}
|
|
1682
1369
|
),
|
|
1683
|
-
canUseNativeShare && /* @__PURE__ */ (0,
|
|
1370
|
+
canUseNativeShare && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
1684
1371
|
ShareDestinationButton,
|
|
1685
1372
|
{
|
|
1686
1373
|
label: "More",
|
|
1687
|
-
icon: /* @__PURE__ */ (0,
|
|
1374
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react9.Share2, { "aria-hidden": "true", className: "h-6 w-6" }),
|
|
1688
1375
|
onClick: () => void handleNativeShare(),
|
|
1689
1376
|
ariaLabel: "More sharing options",
|
|
1690
1377
|
disabled: !absoluteUrl
|
|
1691
1378
|
}
|
|
1692
1379
|
)
|
|
1693
1380
|
] }),
|
|
1694
|
-
copyStatus === "failed" && /* @__PURE__ */ (0,
|
|
1695
|
-
shareError && /* @__PURE__ */ (0,
|
|
1381
|
+
copyStatus === "failed" && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: "Could not copy the link." }),
|
|
1382
|
+
shareError && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: shareError })
|
|
1696
1383
|
] })
|
|
1697
1384
|
}
|
|
1698
1385
|
),
|
|
1699
|
-
toastId > 0 && /* @__PURE__ */ (0,
|
|
1386
|
+
toastId > 0 && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Toast, { message: "Link copied", onClose: dismissToast }, toastId)
|
|
1700
1387
|
] });
|
|
1701
1388
|
}
|
|
1702
1389
|
|
|
1703
1390
|
// src/genre-tree/ShareActionButton.tsx
|
|
1704
|
-
var
|
|
1391
|
+
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
1705
1392
|
function ShareActionButton({ target, url }) {
|
|
1706
|
-
const { showPopup, hidePopup } = (0,
|
|
1393
|
+
const { showPopup, hidePopup } = (0, import_popup4.usePopup)();
|
|
1707
1394
|
const openSharePopup = () => {
|
|
1708
|
-
showPopup(/* @__PURE__ */ (0,
|
|
1395
|
+
showPopup(/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(SharePopup, { target, url, onClose: hidePopup }));
|
|
1396
|
+
};
|
|
1397
|
+
return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
|
|
1398
|
+
"button",
|
|
1399
|
+
{
|
|
1400
|
+
type: "button",
|
|
1401
|
+
onClick: openSharePopup,
|
|
1402
|
+
className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none",
|
|
1403
|
+
"aria-label": `Share ${target.name}`,
|
|
1404
|
+
title: `Share ${target.name}`,
|
|
1405
|
+
children: [
|
|
1406
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.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: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react10.Share2, { "aria-hidden": "true", className: "w-5 h-5" }) }),
|
|
1407
|
+
"Share"
|
|
1408
|
+
]
|
|
1409
|
+
}
|
|
1410
|
+
);
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
// src/genre-tree/GenreActionBand.tsx
|
|
1414
|
+
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
1415
|
+
function GenreActionBand({
|
|
1416
|
+
node,
|
|
1417
|
+
onPlayPause,
|
|
1418
|
+
isPlaying = false,
|
|
1419
|
+
onZoomIn,
|
|
1420
|
+
onAddChild,
|
|
1421
|
+
onRename,
|
|
1422
|
+
onChangeParent,
|
|
1423
|
+
side,
|
|
1424
|
+
onToggleSide,
|
|
1425
|
+
toggleSideDisabledReason,
|
|
1426
|
+
additionalActions,
|
|
1427
|
+
shareTarget,
|
|
1428
|
+
shareUrl
|
|
1429
|
+
}) {
|
|
1430
|
+
const items = [];
|
|
1431
|
+
const adminItems = [];
|
|
1432
|
+
if (onPlayPause) {
|
|
1433
|
+
items.push({
|
|
1434
|
+
key: "play-pause",
|
|
1435
|
+
label: isPlaying ? "Pause" : "Play",
|
|
1436
|
+
icon: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react11.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react11.Play, { "aria-hidden": "true", className: "w-5 h-5" }),
|
|
1437
|
+
onClick: onPlayPause
|
|
1438
|
+
});
|
|
1439
|
+
}
|
|
1440
|
+
if (onZoomIn) {
|
|
1441
|
+
items.push({
|
|
1442
|
+
key: "zoom-in",
|
|
1443
|
+
label: "Focus",
|
|
1444
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react11.ZoomIn, { "aria-hidden": "true", className: "w-5 h-5" }),
|
|
1445
|
+
onClick: onZoomIn
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
if (onAddChild) {
|
|
1449
|
+
adminItems.push({ key: "add-child", label: "Add", icon: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react11.Plus, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onAddChild });
|
|
1450
|
+
}
|
|
1451
|
+
if (onRename) {
|
|
1452
|
+
adminItems.push({ key: "rename", label: "Rename", icon: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react11.Pencil, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onRename });
|
|
1453
|
+
}
|
|
1454
|
+
if (onChangeParent) {
|
|
1455
|
+
adminItems.push({ key: "change-parent", label: "Parent", icon: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react11.MoveUpRight, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onChangeParent });
|
|
1456
|
+
}
|
|
1457
|
+
if (onToggleSide) {
|
|
1458
|
+
adminItems.push({
|
|
1459
|
+
key: "toggle-side",
|
|
1460
|
+
label: side === "pop" ? "Make core" : "Make pop",
|
|
1461
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react11.ArrowLeftRight, { "aria-hidden": "true", className: "w-5 h-5" }),
|
|
1462
|
+
onClick: onToggleSide,
|
|
1463
|
+
disabled: !!toggleSideDisabledReason,
|
|
1464
|
+
title: toggleSideDisabledReason
|
|
1465
|
+
});
|
|
1466
|
+
}
|
|
1467
|
+
for (const action of additionalActions?.(node) ?? []) {
|
|
1468
|
+
items.push({
|
|
1469
|
+
key: action.key,
|
|
1470
|
+
label: action.label(node),
|
|
1471
|
+
icon: action.icon(node),
|
|
1472
|
+
onClick: (event) => action.onClick(event, node),
|
|
1473
|
+
disabled: action.enabled ? !action.enabled(node) : false
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1476
|
+
if (items.length === 0 && (!shareTarget || !shareUrl) && adminItems.length === 0) return null;
|
|
1477
|
+
const renderItems = (list) => list.map((item) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
|
|
1478
|
+
"button",
|
|
1479
|
+
{
|
|
1480
|
+
type: "button",
|
|
1481
|
+
disabled: item.disabled,
|
|
1482
|
+
title: item.title,
|
|
1483
|
+
onClick: item.onClick,
|
|
1484
|
+
className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none disabled:opacity-50",
|
|
1485
|
+
children: [
|
|
1486
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.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)] group-disabled:group-hover:bg-[var(--color-surface-muted)]", children: item.icon }),
|
|
1487
|
+
item.label
|
|
1488
|
+
]
|
|
1489
|
+
},
|
|
1490
|
+
item.key
|
|
1491
|
+
));
|
|
1492
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: [
|
|
1493
|
+
(items.length > 0 || shareTarget && shareUrl) && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
|
|
1494
|
+
renderItems(items),
|
|
1495
|
+
shareTarget && shareUrl && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
|
|
1496
|
+
] }),
|
|
1497
|
+
adminItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
|
|
1498
|
+
"section",
|
|
1499
|
+
{
|
|
1500
|
+
"aria-label": "Admin actions",
|
|
1501
|
+
className: `px-3.5 py-3 ${items.length > 0 ? "border-t border-[var(--color-border)]" : ""}`,
|
|
1502
|
+
children: [
|
|
1503
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("h3", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-[var(--color-text-muted)]", children: "Admin" }),
|
|
1504
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "flex flex-wrap gap-1", children: renderItems(adminItems) })
|
|
1505
|
+
]
|
|
1506
|
+
}
|
|
1507
|
+
)
|
|
1508
|
+
] });
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
// src/genre-tree/GenreDetailExtras.tsx
|
|
1512
|
+
var import_ui8 = require("@behindthemusictree/ui");
|
|
1513
|
+
var import_genre_tree9 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1514
|
+
|
|
1515
|
+
// src/genre-tree/ClampedText.tsx
|
|
1516
|
+
var import_react9 = require("react");
|
|
1517
|
+
var import_jsx_runtime22 = require("react/jsx-runtime");
|
|
1518
|
+
function ClampedText({ children, className: className2 = "" }) {
|
|
1519
|
+
const ref = (0, import_react9.useRef)(null);
|
|
1520
|
+
const [expanded, setExpanded] = (0, import_react9.useState)(false);
|
|
1521
|
+
const [overflows, setOverflows] = (0, import_react9.useState)(false);
|
|
1522
|
+
(0, import_react9.useLayoutEffect)(() => {
|
|
1523
|
+
const element = ref.current;
|
|
1524
|
+
if (!element || expanded) return;
|
|
1525
|
+
setOverflows(element.scrollHeight > element.clientHeight);
|
|
1526
|
+
}, [children, expanded]);
|
|
1527
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
|
|
1528
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { ref, className: `${className2} ${expanded ? "" : "line-clamp-3"}`, children }),
|
|
1529
|
+
(overflows || expanded) && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
1530
|
+
"button",
|
|
1531
|
+
{
|
|
1532
|
+
type: "button",
|
|
1533
|
+
"aria-expanded": expanded,
|
|
1534
|
+
onClick: () => setExpanded((value) => !value),
|
|
1535
|
+
className: "mt-1 text-xs text-[var(--color-text-muted)] underline hover:text-[var(--color-text)]",
|
|
1536
|
+
children: expanded ? "Show less" : "Show more"
|
|
1537
|
+
}
|
|
1538
|
+
)
|
|
1539
|
+
] });
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
// src/genre-tree/GenrePlaylistTracks.tsx
|
|
1543
|
+
var import_react10 = require("react");
|
|
1544
|
+
var import_react_query = require("@tanstack/react-query");
|
|
1545
|
+
var import_zod = require("zod");
|
|
1546
|
+
var import_fa4 = require("react-icons/fa");
|
|
1547
|
+
var import_ui7 = require("@behindthemusictree/ui");
|
|
1548
|
+
var import_auth2 = require("@behindthemusictree/app-kit/auth");
|
|
1549
|
+
var import_transport4 = require("@behindthemusictree/app-kit/transport");
|
|
1550
|
+
var import_transport5 = require("@behindthemusictree/app-kit/transport");
|
|
1551
|
+
var import_transport6 = require("@behindthemusictree/app-kit/transport");
|
|
1552
|
+
var import_player4 = require("@behindthemusictree/app-kit/player");
|
|
1553
|
+
var import_player5 = require("@behindthemusictree/app-kit/player");
|
|
1554
|
+
var import_popup5 = require("@behindthemusictree/app-kit/popup");
|
|
1555
|
+
var import_transport7 = require("@behindthemusictree/app-kit/transport");
|
|
1556
|
+
var import_genre_tree4 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1557
|
+
var import_genre_tree5 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1558
|
+
var import_genre_tree6 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1559
|
+
var import_genre_tree7 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1560
|
+
|
|
1561
|
+
// src/genre-tree/MusicBrainzRecordingLink.tsx
|
|
1562
|
+
var import_genre_tree3 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1563
|
+
var import_jsx_runtime23 = require("react/jsx-runtime");
|
|
1564
|
+
function MusicBrainzRecordingLink({
|
|
1565
|
+
song,
|
|
1566
|
+
children,
|
|
1567
|
+
className: className2 = ""
|
|
1568
|
+
}) {
|
|
1569
|
+
const url = (0, import_genre_tree3.musicBrainzRecordingUrl)(song);
|
|
1570
|
+
if (!url) return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_jsx_runtime23.Fragment, { children });
|
|
1571
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
1572
|
+
"a",
|
|
1573
|
+
{
|
|
1574
|
+
href: url,
|
|
1575
|
+
target: "_blank",
|
|
1576
|
+
rel: "noopener noreferrer",
|
|
1577
|
+
"aria-label": `View ${song.title} on MusicBrainz`,
|
|
1578
|
+
className: className2,
|
|
1579
|
+
onClick: (event) => event.stopPropagation(),
|
|
1580
|
+
children
|
|
1581
|
+
}
|
|
1582
|
+
);
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
// src/genre-tree/GenrePlaylistTracks.tsx
|
|
1586
|
+
var import_genre_tree8 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1587
|
+
var import_track_noun5 = require("@behindthemusictree/app-kit/track-noun");
|
|
1588
|
+
var import_jsx_runtime24 = require("react/jsx-runtime");
|
|
1589
|
+
var CATALOGUE_PAGE_SIZE = 100;
|
|
1590
|
+
var SongPageSchema = (0, import_transport5.PaginatedResponseSchema)(import_genre_tree6.SongDetailedSchema).extend({
|
|
1591
|
+
overallTotal: import_zod.z.number().nullable(),
|
|
1592
|
+
page: import_zod.z.number().nullable(),
|
|
1593
|
+
totalPages: import_zod.z.number().nullable()
|
|
1594
|
+
});
|
|
1595
|
+
var TEXT_CELL = "min-w-0 flex-1 truncate [@container(min-width:40rem)]:flex [@container(min-width:40rem)]:gap-2";
|
|
1596
|
+
var TITLE_CELL = "[@container(min-width:40rem)]:flex-[2] [@container(min-width:40rem)]:truncate";
|
|
1597
|
+
var COLUMN_CELL = "[@container(min-width:40rem)]:flex-1 [@container(min-width:40rem)]:truncate";
|
|
1598
|
+
var ALBUM_CELL = `hidden [@container(min-width:40rem)]:block ${COLUMN_CELL}`;
|
|
1599
|
+
var SEPARATOR = "[@container(min-width:40rem)]:hidden";
|
|
1600
|
+
var nextCursor = (next) => {
|
|
1601
|
+
const cursor = new URL(next).searchParams.get("cursor");
|
|
1602
|
+
if (!cursor) throw new Error(`Songs next link has no cursor: ${next}`);
|
|
1603
|
+
return cursor;
|
|
1604
|
+
};
|
|
1605
|
+
function GenrePlaylistTracks({
|
|
1606
|
+
genreUuid,
|
|
1607
|
+
genrePlaylist,
|
|
1608
|
+
scope,
|
|
1609
|
+
getBackendBaseUrl,
|
|
1610
|
+
renderArtistLink,
|
|
1611
|
+
renderSongLeading,
|
|
1612
|
+
renderSongActions,
|
|
1613
|
+
renderTracksToolbar,
|
|
1614
|
+
artist = null
|
|
1615
|
+
}) {
|
|
1616
|
+
const { fetchGenrePlaylistTracksPage, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist } = (0, import_genre_tree7.useTrackList)();
|
|
1617
|
+
const { session, sessionRestored } = (0, import_auth2.useSession)();
|
|
1618
|
+
const { playerTrackObject, playState, handlePlayPauseAction } = (0, import_player4.usePlayer)();
|
|
1619
|
+
const { showPopup } = (0, import_popup5.usePopup)();
|
|
1620
|
+
const trackNoun = (0, import_track_noun5.useTrackNoun)();
|
|
1621
|
+
const { uuid } = genrePlaylist;
|
|
1622
|
+
const { fetch } = (0, import_transport4.useFetchWrapper)(getBackendBaseUrl);
|
|
1623
|
+
const { data, isPending, isError, refetch, hasNextPage, isFetchingNextPage, fetchNextPage } = (0, import_react_query.useInfiniteQuery)({
|
|
1624
|
+
queryKey: scope === "me" ? import_genre_tree4.genrePlaylistQueryKeys.me.tracks(uuid) : artist ? [...import_genre_tree5.libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid), "artist", artist.uuid] : import_genre_tree5.libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid),
|
|
1625
|
+
queryFn: async ({ pageParam }) => {
|
|
1626
|
+
if (scope === "me") return fetchGenrePlaylistTracksPage(uuid, scope, Number(pageParam));
|
|
1627
|
+
const response = await fetch(
|
|
1628
|
+
import_genre_tree5.libraryEndpoints.reference.songs.list(),
|
|
1629
|
+
true,
|
|
1630
|
+
false,
|
|
1631
|
+
{},
|
|
1632
|
+
{ genre: genreUuid, ...artist && { artist: artist.uuid }, pageSize: CATALOGUE_PAGE_SIZE, withTotal: false, ...pageParam && { cursor: pageParam } }
|
|
1633
|
+
);
|
|
1634
|
+
const parsed = (0, import_transport6.parseWithLog)(SongPageSchema, response, "GenrePlaylistTracks");
|
|
1635
|
+
return { tracks: parsed.results, total: parsed.overallTotal, nextPage: parsed.next ? nextCursor(parsed.next) : null };
|
|
1636
|
+
},
|
|
1637
|
+
initialPageParam: scope === "me" ? 1 : "",
|
|
1638
|
+
getNextPageParam: (lastPage) => lastPage.nextPage ?? void 0,
|
|
1639
|
+
enabled: scope !== "me" || sessionRestored && !!session?.accessToken
|
|
1640
|
+
});
|
|
1641
|
+
const sentinelRef = (0, import_react10.useRef)(null);
|
|
1642
|
+
(0, import_react10.useEffect)(() => {
|
|
1643
|
+
const sentinel = sentinelRef.current;
|
|
1644
|
+
if (!hasNextPage || isFetchingNextPage || !sentinel) return;
|
|
1645
|
+
const observer = new IntersectionObserver((entries) => {
|
|
1646
|
+
if (entries.some((entry) => entry.isIntersecting)) {
|
|
1647
|
+
void fetchNextPage();
|
|
1648
|
+
}
|
|
1649
|
+
});
|
|
1650
|
+
observer.observe(sentinel);
|
|
1651
|
+
return () => observer.disconnect();
|
|
1652
|
+
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);
|
|
1653
|
+
const pages = data?.pages ?? [];
|
|
1654
|
+
const tracks = pages.flatMap((page) => page.tracks);
|
|
1655
|
+
const total = pages[0]?.total ?? void 0;
|
|
1656
|
+
const play = (startSongUuid) => {
|
|
1657
|
+
const started = artist ? playNewTrackListFromArtist(artist, scope, { startSongUuid, ordering: "title", genreUuid }) : playNewTrackListFromGenrePlaylist(genrePlaylist, scope, { startSongUuid });
|
|
1658
|
+
started.catch((error) => {
|
|
1659
|
+
console.error("Failed to play genre playlist:", error);
|
|
1660
|
+
showPopup(/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(InternalErrorPopup, { errorCode: import_transport7.ErrorCode.CLIENT_UNKNOWN }));
|
|
1661
|
+
});
|
|
1662
|
+
};
|
|
1663
|
+
return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "gtv-info-panel-children", children: [
|
|
1664
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { className: "gtv-info-panel-children-title", children: [
|
|
1665
|
+
(0, import_track_noun5.capitalize)(trackNoun.plural),
|
|
1666
|
+
total !== void 0 && ` (${total})`
|
|
1667
|
+
] }),
|
|
1668
|
+
renderTracksToolbar?.(),
|
|
1669
|
+
isPending ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ui7.Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("p", { role: "alert", children: [
|
|
1670
|
+
"Couldn't load ",
|
|
1671
|
+
trackNoun.plural,
|
|
1672
|
+
".",
|
|
1673
|
+
" ",
|
|
1674
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
|
|
1675
|
+
] }) : tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { children: "\u2014" }) : /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("ul", { className: "mt-1 max-h-64 overflow-y-auto text-[0.8125rem] leading-[1.45] [container-type:inline-size]", "aria-label": `${genrePlaylist.name} ${trackNoun.plural}`, children: [
|
|
1676
|
+
tracks.map((track, index) => {
|
|
1677
|
+
const label = track.artists?.length ? `${track.title} \u2014 ${track.artists.map((artist2) => artist2.name).join(", ")}` : track.title;
|
|
1678
|
+
const isCurrent = playerTrackObject?.track.id === track.uuid;
|
|
1679
|
+
const isPlaying = isCurrent && playState === import_player5.PlayStates.PLAYING;
|
|
1680
|
+
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 }),
|
|
1682
|
+
track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: SEPARATOR, children: " \u2014 " }) : null,
|
|
1683
|
+
/* @__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
|
+
track.album && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: ALBUM_CELL, children: track.album.name })
|
|
1685
|
+
] });
|
|
1686
|
+
const leading = renderSongLeading?.(track, index);
|
|
1687
|
+
const actions = renderSongActions?.(track);
|
|
1688
|
+
const isUnplayable = (0, import_genre_tree6.isSong)(track) && (0, import_genre_tree6.songUnplayableReason)(track) !== null;
|
|
1689
|
+
if (isUnplayable) {
|
|
1690
|
+
return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("li", { className: "flex min-h-8 items-center gap-1 px-1 opacity-50", children: [
|
|
1691
|
+
leading,
|
|
1692
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
1693
|
+
"button",
|
|
1694
|
+
{
|
|
1695
|
+
type: "button",
|
|
1696
|
+
disabled: true,
|
|
1697
|
+
"aria-disabled": "true",
|
|
1698
|
+
"aria-label": label,
|
|
1699
|
+
className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full opacity-0 cursor-not-allowed",
|
|
1700
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_fa4.FaPlay, { size: 10 })
|
|
1701
|
+
}
|
|
1702
|
+
),
|
|
1703
|
+
text,
|
|
1704
|
+
actions
|
|
1705
|
+
] }, track.uuid);
|
|
1706
|
+
}
|
|
1707
|
+
return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
|
|
1708
|
+
"li",
|
|
1709
|
+
{
|
|
1710
|
+
className: `group flex min-h-8 items-center gap-1 rounded px-1 hover:bg-[var(--color-neutral-100)] ${isCurrent ? "font-medium text-[var(--color-primary)]" : ""}`,
|
|
1711
|
+
children: [
|
|
1712
|
+
leading,
|
|
1713
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
1714
|
+
"button",
|
|
1715
|
+
{
|
|
1716
|
+
type: "button",
|
|
1717
|
+
"aria-label": `${isPlaying ? "Pause" : "Play"} ${label}`,
|
|
1718
|
+
className: `flex h-6 w-6 shrink-0 items-center justify-center rounded-full cursor-pointer group-hover:opacity-100 focus-visible:opacity-100 ${isCurrent && playState !== import_player5.PlayStates.STOPPED ? "opacity-100" : "opacity-0 [@media(pointer:coarse)]:opacity-100"}`,
|
|
1719
|
+
onClick: () => isCurrent ? handlePlayPauseAction() : play(track.uuid),
|
|
1720
|
+
children: isCurrent && playState === import_player5.PlayStates.LOADING ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ui7.RingLoader, { size: 10 }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_fa4.FaPause, { size: 10 }) : /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_fa4.FaPlay, { size: 10 })
|
|
1721
|
+
}
|
|
1722
|
+
),
|
|
1723
|
+
text,
|
|
1724
|
+
actions
|
|
1725
|
+
]
|
|
1726
|
+
},
|
|
1727
|
+
track.uuid
|
|
1728
|
+
);
|
|
1729
|
+
}),
|
|
1730
|
+
hasNextPage && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("li", { ref: sentinelRef, "aria-hidden": "true", className: "h-4" })
|
|
1731
|
+
] })
|
|
1732
|
+
] });
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
// src/genre-tree/GenreDetailExtras.tsx
|
|
1736
|
+
var import_track_noun6 = require("@behindthemusictree/app-kit/track-noun");
|
|
1737
|
+
var import_si2 = require("react-icons/si");
|
|
1738
|
+
var import_jsx_runtime25 = require("react/jsx-runtime");
|
|
1739
|
+
function GenreDetailExtras({
|
|
1740
|
+
genreUuid,
|
|
1741
|
+
genrePlaylist,
|
|
1742
|
+
wikidataId,
|
|
1743
|
+
scope,
|
|
1744
|
+
getBackendBaseUrl,
|
|
1745
|
+
criteriaOverviewSchema,
|
|
1746
|
+
renderGenreDetailExtras,
|
|
1747
|
+
renderArtistLink,
|
|
1748
|
+
renderSummary,
|
|
1749
|
+
renderEssentialTracks,
|
|
1750
|
+
family,
|
|
1751
|
+
...trackSlots
|
|
1752
|
+
}) {
|
|
1753
|
+
const trackNoun = (0, import_track_noun6.useTrackNoun)();
|
|
1754
|
+
const { data: overview, isPending } = (0, import_genre_tree9.useFetchGenreOverview)(
|
|
1755
|
+
genreUuid,
|
|
1756
|
+
scope,
|
|
1757
|
+
getBackendBaseUrl,
|
|
1758
|
+
criteriaOverviewSchema
|
|
1759
|
+
);
|
|
1760
|
+
if (isPending) {
|
|
1761
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_jsx_runtime25.Fragment, { children: [
|
|
1762
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "gtv-info-panel-children", "aria-busy": "true", "data-testid": "genre-detail-extras-skeleton", children: [
|
|
1763
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "gtv-info-panel-children-title", children: "Summary" }),
|
|
1764
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_ui8.Skeleton, { className: "h-4 w-3/4" })
|
|
1765
|
+
] }),
|
|
1766
|
+
family
|
|
1767
|
+
] });
|
|
1768
|
+
}
|
|
1769
|
+
if (!overview) return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_jsx_runtime25.Fragment, { children: family });
|
|
1770
|
+
const { summary, summarySourceUrl, essentialSongs } = overview;
|
|
1771
|
+
const wikidataUrl = wikidataId ? `https://www.wikidata.org/wiki/${encodeURIComponent(wikidataId)}` : null;
|
|
1772
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_jsx_runtime25.Fragment, { children: [
|
|
1773
|
+
(summarySourceUrl || wikidataUrl) && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "gtv-info-panel-children text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: [
|
|
1774
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "gtv-info-panel-children-title", children: "Links" }),
|
|
1775
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "mt-1 flex flex-wrap gap-x-3 gap-y-1 text-xs", children: [
|
|
1776
|
+
summarySourceUrl && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
|
|
1777
|
+
"a",
|
|
1778
|
+
{
|
|
1779
|
+
href: summarySourceUrl,
|
|
1780
|
+
target: "_blank",
|
|
1781
|
+
rel: "noopener noreferrer",
|
|
1782
|
+
className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
|
|
1783
|
+
children: [
|
|
1784
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_si2.SiWikipedia, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
|
|
1785
|
+
"Wikipedia"
|
|
1786
|
+
]
|
|
1787
|
+
}
|
|
1788
|
+
),
|
|
1789
|
+
wikidataUrl && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
|
|
1790
|
+
"a",
|
|
1791
|
+
{
|
|
1792
|
+
href: wikidataUrl,
|
|
1793
|
+
target: "_blank",
|
|
1794
|
+
rel: "noopener noreferrer",
|
|
1795
|
+
className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
|
|
1796
|
+
children: [
|
|
1797
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_si2.SiWikidata, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
|
|
1798
|
+
"Wikidata"
|
|
1799
|
+
]
|
|
1800
|
+
}
|
|
1801
|
+
)
|
|
1802
|
+
] })
|
|
1803
|
+
] }),
|
|
1804
|
+
renderSummary ? renderSummary(overview) : /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "gtv-info-panel-children text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: [
|
|
1805
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "gtv-info-panel-children-title", children: "Summary" }),
|
|
1806
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ClampedText, { className: "mt-1", children: summary ?? "\u2014" }, genreUuid)
|
|
1807
|
+
] }),
|
|
1808
|
+
family,
|
|
1809
|
+
renderGenreDetailExtras?.(overview),
|
|
1810
|
+
renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "gtv-info-panel-children text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: [
|
|
1811
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("span", { className: "gtv-info-panel-children-title", children: [
|
|
1812
|
+
"Essential ",
|
|
1813
|
+
trackNoun.plural
|
|
1814
|
+
] }),
|
|
1815
|
+
essentialSongs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("ul", { className: "mt-1 divide-y divide-[var(--color-border)]", children: essentialSongs.map((track) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("li", { className: "py-1", children: track.title }, track.uuid)) }) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("p", { className: "mt-1", children: "\u2014" })
|
|
1816
|
+
] }),
|
|
1817
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
1818
|
+
GenrePlaylistTracks,
|
|
1819
|
+
{
|
|
1820
|
+
genreUuid,
|
|
1821
|
+
genrePlaylist,
|
|
1822
|
+
scope,
|
|
1823
|
+
getBackendBaseUrl,
|
|
1824
|
+
renderArtistLink,
|
|
1825
|
+
...trackSlots
|
|
1826
|
+
}
|
|
1827
|
+
)
|
|
1828
|
+
] });
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
// src/genre-tree/GenreInfoPanelBody.tsx
|
|
1832
|
+
var import_jsx_runtime26 = require("react/jsx-runtime");
|
|
1833
|
+
var import_react11 = require("react");
|
|
1834
|
+
function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
|
|
1835
|
+
const { isPlaying, setIsPlaying } = (0, import_player6.usePlayer)();
|
|
1836
|
+
const isCurrentTrackList = playback.playingNodeId === genrePlaylist.uuid;
|
|
1837
|
+
const handlePlayPause = () => {
|
|
1838
|
+
if (isCurrentTrackList) {
|
|
1839
|
+
setIsPlaying(!isPlaying);
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1842
|
+
playback.play(genrePlaylist).catch((error) => {
|
|
1843
|
+
console.error("Failed to load genre playlist tracks:", error);
|
|
1844
|
+
});
|
|
1709
1845
|
};
|
|
1710
|
-
return /* @__PURE__ */ (0,
|
|
1711
|
-
|
|
1846
|
+
return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
|
|
1847
|
+
GenreActionBand,
|
|
1712
1848
|
{
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
"aria-label": `Share ${target.name}`,
|
|
1717
|
-
title: `Share ${target.name}`,
|
|
1718
|
-
children: [
|
|
1719
|
-
/* @__PURE__ */ (0, import_jsx_runtime24.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: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react10.Share2, { "aria-hidden": "true", className: "w-5 h-5" }) }),
|
|
1720
|
-
"Share"
|
|
1721
|
-
]
|
|
1849
|
+
...bandProps,
|
|
1850
|
+
onPlayPause: genrePlaylist.tracksCount > 0 ? handlePlayPause : void 0,
|
|
1851
|
+
isPlaying: isCurrentTrackList && isPlaying
|
|
1722
1852
|
}
|
|
1723
1853
|
);
|
|
1724
1854
|
}
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
side,
|
|
1737
|
-
onToggleSide,
|
|
1738
|
-
toggleSideDisabledReason,
|
|
1739
|
-
additionalActions,
|
|
1740
|
-
shareTarget,
|
|
1741
|
-
shareUrl
|
|
1855
|
+
function GenreInfoPanelBody({
|
|
1856
|
+
toolbar,
|
|
1857
|
+
family,
|
|
1858
|
+
genreUuid,
|
|
1859
|
+
genrePlaylist,
|
|
1860
|
+
wikidataId,
|
|
1861
|
+
scope,
|
|
1862
|
+
getBackendBaseUrl,
|
|
1863
|
+
playback,
|
|
1864
|
+
band,
|
|
1865
|
+
extras
|
|
1742
1866
|
}) {
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
label: isPlaying ? "Pause" : "Play",
|
|
1749
|
-
icon: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react11.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react11.Play, { "aria-hidden": "true", className: "w-5 h-5" }),
|
|
1750
|
-
onClick: onPlayPause
|
|
1751
|
-
});
|
|
1752
|
-
}
|
|
1753
|
-
if (onZoomIn) {
|
|
1754
|
-
items.push({
|
|
1755
|
-
key: "zoom-in",
|
|
1756
|
-
label: "Focus",
|
|
1757
|
-
icon: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react11.ZoomIn, { "aria-hidden": "true", className: "w-5 h-5" }),
|
|
1758
|
-
onClick: onZoomIn
|
|
1759
|
-
});
|
|
1760
|
-
}
|
|
1761
|
-
if (onAddChild) {
|
|
1762
|
-
adminItems.push({ key: "add-child", label: "Add", icon: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react11.Plus, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onAddChild });
|
|
1763
|
-
}
|
|
1764
|
-
if (onRename) {
|
|
1765
|
-
adminItems.push({ key: "rename", label: "Rename", icon: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react11.Pencil, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onRename });
|
|
1766
|
-
}
|
|
1767
|
-
if (onChangeParent) {
|
|
1768
|
-
adminItems.push({ key: "change-parent", label: "Parent", icon: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react11.MoveUpRight, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onChangeParent });
|
|
1769
|
-
}
|
|
1770
|
-
if (onToggleSide) {
|
|
1771
|
-
adminItems.push({
|
|
1772
|
-
key: "toggle-side",
|
|
1773
|
-
label: side === "pop" ? "Make core" : "Make pop",
|
|
1774
|
-
icon: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react11.ArrowLeftRight, { "aria-hidden": "true", className: "w-5 h-5" }),
|
|
1775
|
-
onClick: onToggleSide,
|
|
1776
|
-
disabled: !!toggleSideDisabledReason,
|
|
1777
|
-
title: toggleSideDisabledReason
|
|
1778
|
-
});
|
|
1779
|
-
}
|
|
1780
|
-
for (const action of additionalActions?.(node) ?? []) {
|
|
1781
|
-
items.push({
|
|
1782
|
-
key: action.key,
|
|
1783
|
-
label: action.label(node),
|
|
1784
|
-
icon: action.icon(node),
|
|
1785
|
-
onClick: (event) => action.onClick(event, node),
|
|
1786
|
-
disabled: action.enabled ? !action.enabled(node) : false
|
|
1787
|
-
});
|
|
1788
|
-
}
|
|
1789
|
-
if (items.length === 0 && (!shareTarget || !shareUrl) && adminItems.length === 0) return null;
|
|
1790
|
-
const renderItems = (list) => list.map((item) => /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
|
|
1791
|
-
"button",
|
|
1792
|
-
{
|
|
1793
|
-
type: "button",
|
|
1794
|
-
disabled: item.disabled,
|
|
1795
|
-
title: item.title,
|
|
1796
|
-
onClick: item.onClick,
|
|
1797
|
-
className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none disabled:opacity-50",
|
|
1798
|
-
children: [
|
|
1799
|
-
/* @__PURE__ */ (0, import_jsx_runtime25.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)] group-disabled:group-hover:bg-[var(--color-surface-muted)]", children: item.icon }),
|
|
1800
|
-
item.label
|
|
1801
|
-
]
|
|
1802
|
-
},
|
|
1803
|
-
item.key
|
|
1804
|
-
));
|
|
1805
|
-
return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: [
|
|
1806
|
-
(items.length > 0 || shareTarget && shareUrl) && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
|
|
1807
|
-
renderItems(items),
|
|
1808
|
-
shareTarget && shareUrl && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
|
|
1809
|
-
] }),
|
|
1810
|
-
adminItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
|
|
1811
|
-
"section",
|
|
1867
|
+
return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_jsx_runtime26.Fragment, { children: [
|
|
1868
|
+
toolbar,
|
|
1869
|
+
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)(GenreNodeActionBand, { ...band, genrePlaylist, playback }),
|
|
1870
|
+
/* @__PURE__ */ (0, import_react11.createElement)(
|
|
1871
|
+
GenreDetailExtras,
|
|
1812
1872
|
{
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1873
|
+
...extras,
|
|
1874
|
+
key: genreUuid,
|
|
1875
|
+
family,
|
|
1876
|
+
genreUuid,
|
|
1877
|
+
genrePlaylist,
|
|
1878
|
+
wikidataId,
|
|
1879
|
+
scope,
|
|
1880
|
+
getBackendBaseUrl
|
|
1819
1881
|
}
|
|
1820
1882
|
)
|
|
1821
1883
|
] });
|
|
@@ -1824,14 +1886,14 @@ function GenreActionBand({
|
|
|
1824
1886
|
// src/genre-tree/PanelEdgeTab.tsx
|
|
1825
1887
|
var import_lucide_react12 = require("lucide-react");
|
|
1826
1888
|
var import_tailwind_merge = require("tailwind-merge");
|
|
1827
|
-
var
|
|
1889
|
+
var import_jsx_runtime27 = require("react/jsx-runtime");
|
|
1828
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)]";
|
|
1829
1891
|
function PanelEdgeTab({ side, edge, center, controls, className: className2 }) {
|
|
1830
1892
|
if (!edge && !center) return null;
|
|
1831
1893
|
const isLeft = side === "left";
|
|
1832
1894
|
const EdgeIcon = isLeft ? import_lucide_react12.ChevronLeft : import_lucide_react12.ChevronRight;
|
|
1833
1895
|
const CenterIcon = isLeft ? import_lucide_react12.ChevronRight : import_lucide_react12.ChevronLeft;
|
|
1834
|
-
const renderButton = (action, icon) => /* @__PURE__ */ (0,
|
|
1896
|
+
const renderButton = (action, icon) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
1835
1897
|
"button",
|
|
1836
1898
|
{
|
|
1837
1899
|
type: "button",
|
|
@@ -1844,10 +1906,10 @@ function PanelEdgeTab({ side, edge, center, controls, className: className2 }) {
|
|
|
1844
1906
|
children: icon
|
|
1845
1907
|
}
|
|
1846
1908
|
);
|
|
1847
|
-
const edgeButton = edge && renderButton(edge, /* @__PURE__ */ (0,
|
|
1848
|
-
const centerButton = center && renderButton(center, /* @__PURE__ */ (0,
|
|
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" }));
|
|
1849
1911
|
const buttons = isLeft ? [edgeButton, centerButton] : [centerButton, edgeButton];
|
|
1850
|
-
return /* @__PURE__ */ (0,
|
|
1912
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
|
|
1851
1913
|
"div",
|
|
1852
1914
|
{
|
|
1853
1915
|
className: (0, import_tailwind_merge.twMerge)(
|
|
@@ -1857,17 +1919,30 @@ function PanelEdgeTab({ side, edge, center, controls, className: className2 }) {
|
|
|
1857
1919
|
),
|
|
1858
1920
|
children: [
|
|
1859
1921
|
buttons[0],
|
|
1860
|
-
buttons[0] && buttons[1] && /* @__PURE__ */ (0,
|
|
1922
|
+
buttons[0] && buttons[1] && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { role: "presentation", className: "h-px shrink-0 bg-[var(--color-border)]" }),
|
|
1861
1923
|
buttons[1]
|
|
1862
1924
|
]
|
|
1863
1925
|
}
|
|
1864
1926
|
);
|
|
1865
1927
|
}
|
|
1866
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
|
+
|
|
1867
1942
|
// src/genre-tree/ChangeParentPopup.tsx
|
|
1868
|
-
var
|
|
1943
|
+
var import_react13 = require("react");
|
|
1869
1944
|
var import_genre_tree10 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1870
|
-
var
|
|
1945
|
+
var import_jsx_runtime28 = require("react/jsx-runtime");
|
|
1871
1946
|
function ChangeParentPopup({
|
|
1872
1947
|
genrePlaylist,
|
|
1873
1948
|
genrePlaylists,
|
|
@@ -1877,7 +1952,7 @@ function ChangeParentPopup({
|
|
|
1877
1952
|
onParentChanged
|
|
1878
1953
|
}) {
|
|
1879
1954
|
const { mutate, isPending, formErrors } = (0, import_genre_tree10.useUpdateGenre)(scope, getBackendBaseUrl);
|
|
1880
|
-
const candidates = (0,
|
|
1955
|
+
const candidates = (0, import_react13.useMemo)(
|
|
1881
1956
|
() => (0, import_genre_tree10.getReparentCandidates)(genrePlaylists, genrePlaylist.uuid),
|
|
1882
1957
|
[genrePlaylists, genrePlaylist.uuid]
|
|
1883
1958
|
);
|
|
@@ -1895,19 +1970,19 @@ function ChangeParentPopup({
|
|
|
1895
1970
|
}
|
|
1896
1971
|
);
|
|
1897
1972
|
};
|
|
1898
|
-
(0,
|
|
1973
|
+
(0, import_react13.useEffect)(() => {
|
|
1899
1974
|
const handleKeyDown = (event) => {
|
|
1900
1975
|
if (event.key === "Escape") onClose();
|
|
1901
1976
|
};
|
|
1902
1977
|
window.addEventListener("keydown", handleKeyDown);
|
|
1903
1978
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
1904
1979
|
}, [onClose]);
|
|
1905
|
-
return /* @__PURE__ */ (0,
|
|
1906
|
-
/* @__PURE__ */ (0,
|
|
1980
|
+
return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
|
|
1981
|
+
/* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("p", { className: "mb-3 text-sm text-[var(--color-text-muted)]", children: [
|
|
1907
1982
|
"Current parent: ",
|
|
1908
1983
|
genrePlaylist.parent?.name ?? "none"
|
|
1909
1984
|
] }),
|
|
1910
|
-
/* @__PURE__ */ (0,
|
|
1985
|
+
/* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
|
|
1911
1986
|
GenreSearch,
|
|
1912
1987
|
{
|
|
1913
1988
|
genrePlaylists: candidates,
|
|
@@ -1915,7 +1990,7 @@ function ChangeParentPopup({
|
|
|
1915
1990
|
placeholder: "Search a new parent\u2026"
|
|
1916
1991
|
}
|
|
1917
1992
|
) }),
|
|
1918
|
-
formErrors.length > 0 && /* @__PURE__ */ (0,
|
|
1993
|
+
formErrors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("p", { role: "alert", className: "mt-3 text-sm text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
|
|
1919
1994
|
] });
|
|
1920
1995
|
}
|
|
1921
1996
|
|
|
@@ -1930,7 +2005,7 @@ var import_ui10 = require("@behindthemusictree/ui");
|
|
|
1930
2005
|
var import_lucide_react13 = require("lucide-react");
|
|
1931
2006
|
var import_si3 = require("react-icons/si");
|
|
1932
2007
|
var import_ui9 = require("@behindthemusictree/ui");
|
|
1933
|
-
var
|
|
2008
|
+
var import_jsx_runtime29 = require("react/jsx-runtime");
|
|
1934
2009
|
var LINK_KINDS = {
|
|
1935
2010
|
official: { label: "Official website", Icon: import_lucide_react13.Globe },
|
|
1936
2011
|
wikipedia: { label: "Wikipedia", Icon: import_si3.SiWikipedia },
|
|
@@ -1967,7 +2042,7 @@ function artistLinks(artist) {
|
|
|
1967
2042
|
return links;
|
|
1968
2043
|
}
|
|
1969
2044
|
function ArtistLinks({ links }) {
|
|
1970
|
-
return /* @__PURE__ */ (0,
|
|
2045
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui9.Chip, { href: url, icon: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
|
|
1971
2046
|
}
|
|
1972
2047
|
|
|
1973
2048
|
// src/genre-tree/artist/formatYears.ts
|
|
@@ -1978,7 +2053,7 @@ function formatYears(beginYear, endYear, ended) {
|
|
|
1978
2053
|
}
|
|
1979
2054
|
|
|
1980
2055
|
// src/genre-tree/artist/ArtistHeader.tsx
|
|
1981
|
-
var
|
|
2056
|
+
var import_jsx_runtime30 = require("react/jsx-runtime");
|
|
1982
2057
|
var countryNames = new Intl.DisplayNames(["en"], { type: "region" });
|
|
1983
2058
|
function ArtistHeader({ artist }) {
|
|
1984
2059
|
const fields = [];
|
|
@@ -1988,35 +2063,35 @@ function ArtistHeader({ artist }) {
|
|
|
1988
2063
|
const years = formatYears(artist.beginYear, artist.endYear, artist.ended);
|
|
1989
2064
|
if (years) fields.push({ label: "Active", value: years });
|
|
1990
2065
|
const links = artistLinks(artist);
|
|
1991
|
-
if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0,
|
|
2066
|
+
if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ArtistLinks, { links }) });
|
|
1992
2067
|
if (fields.length === 0) return null;
|
|
1993
|
-
return /* @__PURE__ */ (0,
|
|
2068
|
+
return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui10.FieldList, { items: fields });
|
|
1994
2069
|
}
|
|
1995
2070
|
|
|
1996
2071
|
// src/genre-tree/artist/ArtistPhoto.tsx
|
|
1997
|
-
var
|
|
2072
|
+
var import_jsx_runtime31 = require("react/jsx-runtime");
|
|
1998
2073
|
function ExternalLink({ href, children }) {
|
|
1999
|
-
return /* @__PURE__ */ (0,
|
|
2074
|
+
return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
|
|
2000
2075
|
}
|
|
2001
2076
|
function ArtistPhoto({ image, name }) {
|
|
2002
|
-
return /* @__PURE__ */ (0,
|
|
2003
|
-
/* @__PURE__ */ (0,
|
|
2004
|
-
/* @__PURE__ */ (0,
|
|
2077
|
+
return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
|
|
2078
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
|
|
2079
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("figcaption", { className: "text-xs text-[var(--color-text-muted)]", children: [
|
|
2005
2080
|
"Photo: ",
|
|
2006
|
-
/* @__PURE__ */ (0,
|
|
2081
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
|
|
2007
2082
|
",",
|
|
2008
2083
|
" ",
|
|
2009
|
-
image.licenseUrl ? /* @__PURE__ */ (0,
|
|
2084
|
+
image.licenseUrl ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
|
|
2010
2085
|
] })
|
|
2011
2086
|
] });
|
|
2012
2087
|
}
|
|
2013
2088
|
|
|
2014
2089
|
// src/genre-tree/artist/ArtistMembers.tsx
|
|
2015
2090
|
var import_ui11 = require("@behindthemusictree/ui");
|
|
2016
|
-
var
|
|
2091
|
+
var import_jsx_runtime32 = require("react/jsx-runtime");
|
|
2017
2092
|
function MembershipList({ title, memberships, renderArtistLink }) {
|
|
2018
2093
|
if (memberships.length === 0) return null;
|
|
2019
|
-
return /* @__PURE__ */ (0,
|
|
2094
|
+
return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui11.PanelGroup, { title, children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("ul", { children: memberships.map((membership, index) => {
|
|
2020
2095
|
const details = [
|
|
2021
2096
|
formatYears(membership.beginYear, membership.endYear, membership.ended),
|
|
2022
2097
|
...membership.roles
|
|
@@ -2024,9 +2099,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
|
|
|
2024
2099
|
const detailsText = details.filter(Boolean).join(" \xB7 ");
|
|
2025
2100
|
return (
|
|
2026
2101
|
// A member can have several periods in the same group.
|
|
2027
|
-
/* @__PURE__ */ (0,
|
|
2028
|
-
/* @__PURE__ */ (0,
|
|
2029
|
-
detailsText && /* @__PURE__ */ (0,
|
|
2102
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
|
|
2103
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
|
|
2104
|
+
detailsText && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
|
|
2030
2105
|
" \u2014 ",
|
|
2031
2106
|
detailsText
|
|
2032
2107
|
] })
|
|
@@ -2037,9 +2112,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
|
|
|
2037
2112
|
function ArtistMembers({ title, memberships, renderArtistLink }) {
|
|
2038
2113
|
if (memberships.length === 0) return null;
|
|
2039
2114
|
const isCurrent = (membership) => !membership.ended && membership.endYear === null;
|
|
2040
|
-
return /* @__PURE__ */ (0,
|
|
2041
|
-
/* @__PURE__ */ (0,
|
|
2042
|
-
/* @__PURE__ */ (0,
|
|
2115
|
+
return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_ui11.PanelSection, { title, children: [
|
|
2116
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
|
|
2117
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
|
|
2043
2118
|
MembershipList,
|
|
2044
2119
|
{
|
|
2045
2120
|
title: "Past",
|
|
@@ -2052,7 +2127,7 @@ function ArtistMembers({ title, memberships, renderArtistLink }) {
|
|
|
2052
2127
|
|
|
2053
2128
|
// src/genre-tree/artist/ArtistDiscography.tsx
|
|
2054
2129
|
var import_ui12 = require("@behindthemusictree/ui");
|
|
2055
|
-
var
|
|
2130
|
+
var import_jsx_runtime33 = require("react/jsx-runtime");
|
|
2056
2131
|
var GROUPS = ["Albums", "EPs", "Singles", "Live", "Compilations", "Other"];
|
|
2057
2132
|
var PRIMARY_GROUPS = { album: "Albums", ep: "EPs", single: "Singles" };
|
|
2058
2133
|
function discographyGroup({ primaryType, secondaryTypes }) {
|
|
@@ -2072,55 +2147,55 @@ function byYear(a, b) {
|
|
|
2072
2147
|
function ArtistDiscography({ albums }) {
|
|
2073
2148
|
if (albums.length === 0) return null;
|
|
2074
2149
|
const sorted = [...albums].sort(byYear);
|
|
2075
|
-
return /* @__PURE__ */ (0,
|
|
2150
|
+
return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ui12.PanelSection, { title: "Discography", children: GROUPS.map((group) => {
|
|
2076
2151
|
const groupAlbums = sorted.filter((album) => discographyGroup(album) === group);
|
|
2077
2152
|
if (groupAlbums.length === 0) return null;
|
|
2078
|
-
return /* @__PURE__ */ (0,
|
|
2079
|
-
/* @__PURE__ */ (0,
|
|
2080
|
-
/* @__PURE__ */ (0,
|
|
2153
|
+
return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ui12.PanelGroup, { title: group, children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
|
|
2154
|
+
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
|
|
2155
|
+
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
|
|
2081
2156
|
] }, album.uuid)) }) }, group);
|
|
2082
2157
|
}) });
|
|
2083
2158
|
}
|
|
2084
2159
|
|
|
2085
2160
|
// src/genre-tree/artist/ArtistSongs.tsx
|
|
2086
|
-
var
|
|
2161
|
+
var import_react14 = require("react");
|
|
2087
2162
|
var import_react_query2 = require("@tanstack/react-query");
|
|
2088
2163
|
var import_lucide_react14 = require("lucide-react");
|
|
2089
2164
|
var import_ui13 = require("@behindthemusictree/ui");
|
|
2090
|
-
var
|
|
2165
|
+
var import_player10 = require("@behindthemusictree/app-kit/player");
|
|
2091
2166
|
var import_genre_tree14 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2092
2167
|
var import_transport8 = require("@behindthemusictree/app-kit/transport");
|
|
2093
2168
|
|
|
2094
2169
|
// src/genre-tree/track-list-sidebar/TrackItem.tsx
|
|
2095
|
-
var
|
|
2170
|
+
var import_player9 = require("@behindthemusictree/app-kit/player");
|
|
2096
2171
|
var import_genre_tree11 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2097
2172
|
|
|
2098
2173
|
// src/genre-tree/TrackPositionPlayPause.tsx
|
|
2099
2174
|
var import_fa5 = require("react-icons/fa");
|
|
2100
|
-
var import_player6 = require("@behindthemusictree/app-kit/player");
|
|
2101
2175
|
var import_player7 = require("@behindthemusictree/app-kit/player");
|
|
2102
|
-
var
|
|
2176
|
+
var import_player8 = require("@behindthemusictree/app-kit/player");
|
|
2177
|
+
var import_jsx_runtime34 = require("react/jsx-runtime");
|
|
2103
2178
|
function TrackPositionPlayPause({
|
|
2104
2179
|
position,
|
|
2105
2180
|
uuid,
|
|
2106
2181
|
handlePlayPauseClick,
|
|
2107
2182
|
isSelected = false
|
|
2108
2183
|
}) {
|
|
2109
|
-
const { playerTrackObject, playbackConfirmedTrackId, playState, isLoading: isPlayerLoading } = (0,
|
|
2184
|
+
const { playerTrackObject, playbackConfirmedTrackId, playState, isLoading: isPlayerLoading } = (0, import_player7.usePlayer)();
|
|
2110
2185
|
const isThisTrack = playerTrackObject?.track.id === uuid;
|
|
2111
2186
|
const isConfirmedPlayable = playbackConfirmedTrackId === uuid;
|
|
2112
|
-
const isAwaitingPlayback = (isSelected || isThisTrack) && !isConfirmedPlayable && !(isThisTrack && playerTrackObject?.loadError) && (!isThisTrack || isPlayerLoading || playState ===
|
|
2113
|
-
const isPlaying = isThisTrack && isConfirmedPlayable && playState ===
|
|
2114
|
-
const isPaused = isThisTrack && isConfirmedPlayable && playState ===
|
|
2187
|
+
const isAwaitingPlayback = (isSelected || isThisTrack) && !isConfirmedPlayable && !(isThisTrack && playerTrackObject?.loadError) && (!isThisTrack || isPlayerLoading || playState === import_player8.PlayStates.LOADING || playState === import_player8.PlayStates.PLAYING);
|
|
2188
|
+
const isPlaying = isThisTrack && isConfirmedPlayable && playState === import_player8.PlayStates.PLAYING;
|
|
2189
|
+
const isPaused = isThisTrack && isConfirmedPlayable && playState === import_player8.PlayStates.PAUSED;
|
|
2115
2190
|
const isCurrentTrackActive = isPlaying || isPaused;
|
|
2116
|
-
return /* @__PURE__ */ (0,
|
|
2191
|
+
return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
|
|
2117
2192
|
"div",
|
|
2118
2193
|
{
|
|
2119
2194
|
className: "track-position-play-pause relative flex items-center justify-center gap-2 text-lg w-16 cursor-pointer",
|
|
2120
2195
|
style: { minWidth: "4rem", maxWidth: "4rem" },
|
|
2121
2196
|
onClick: handlePlayPauseClick,
|
|
2122
2197
|
children: [
|
|
2123
|
-
/* @__PURE__ */ (0,
|
|
2198
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
|
|
2124
2199
|
"span",
|
|
2125
2200
|
{
|
|
2126
2201
|
className: "track-position-play-pause__bars",
|
|
@@ -2128,18 +2203,18 @@ function TrackPositionPlayPause({
|
|
|
2128
2203
|
role: "img",
|
|
2129
2204
|
"aria-label": isPlaying ? "Playing" : "Paused",
|
|
2130
2205
|
children: [
|
|
2131
|
-
/* @__PURE__ */ (0,
|
|
2132
|
-
/* @__PURE__ */ (0,
|
|
2133
|
-
/* @__PURE__ */ (0,
|
|
2206
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "track-position-play-pause__bar" }),
|
|
2207
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "track-position-play-pause__bar" }),
|
|
2208
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "track-position-play-pause__bar" })
|
|
2134
2209
|
]
|
|
2135
2210
|
}
|
|
2136
2211
|
) : position }),
|
|
2137
|
-
/* @__PURE__ */ (0,
|
|
2212
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2138
2213
|
"div",
|
|
2139
2214
|
{
|
|
2140
2215
|
className: `hidden group-hover:flex items-center justify-center ${isCurrentTrackActive ? "" : "absolute inset-0"}`,
|
|
2141
2216
|
"aria-hidden": "true",
|
|
2142
|
-
children: isAwaitingPlayback ? /* @__PURE__ */ (0,
|
|
2217
|
+
children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_fa5.FaPause, {}) : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_fa5.FaPlay, {})
|
|
2143
2218
|
}
|
|
2144
2219
|
)
|
|
2145
2220
|
]
|
|
@@ -2150,7 +2225,7 @@ function TrackPositionPlayPause({
|
|
|
2150
2225
|
// src/genre-tree/track-list-sidebar/TrackItem.tsx
|
|
2151
2226
|
var import_genre_tree12 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2152
2227
|
var import_genre_tree13 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2153
|
-
var
|
|
2228
|
+
var import_jsx_runtime35 = require("react/jsx-runtime");
|
|
2154
2229
|
function TrackItem({
|
|
2155
2230
|
track,
|
|
2156
2231
|
position,
|
|
@@ -2158,7 +2233,7 @@ function TrackItem({
|
|
|
2158
2233
|
renderActions,
|
|
2159
2234
|
renderArtistLink
|
|
2160
2235
|
}) {
|
|
2161
|
-
const { handlePlayPauseAction, playerTrackObject, loadTrackForPlayer } = (0,
|
|
2236
|
+
const { handlePlayPauseAction, playerTrackObject, loadTrackForPlayer } = (0, import_player9.usePlayer)();
|
|
2162
2237
|
const { trackList, selectedTrack, toTrackAtPosition, runtimeUnplayableTrackIds } = (0, import_genre_tree11.useTrackList)();
|
|
2163
2238
|
const scope = trackList?.origin?.scope ?? null;
|
|
2164
2239
|
const isCurrentTrack = playerTrackObject?.track.id === track.uuid;
|
|
@@ -2173,12 +2248,12 @@ function TrackItem({
|
|
|
2173
2248
|
loadTrackForPlayer(track.uuid);
|
|
2174
2249
|
}
|
|
2175
2250
|
};
|
|
2176
|
-
return /* @__PURE__ */ (0,
|
|
2251
|
+
return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
|
|
2177
2252
|
"div",
|
|
2178
2253
|
{
|
|
2179
2254
|
className: `track-item flex h-14 text-[var(--color-text)] ${isUnplayable ? "opacity-50" : "hover:bg-[var(--color-surface-muted)] group"}`,
|
|
2180
2255
|
children: [
|
|
2181
|
-
isUnplayable ? /* @__PURE__ */ (0,
|
|
2256
|
+
isUnplayable ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2182
2257
|
"div",
|
|
2183
2258
|
{
|
|
2184
2259
|
className: "flex items-center justify-center text-lg w-16",
|
|
@@ -2186,7 +2261,7 @@ function TrackItem({
|
|
|
2186
2261
|
"aria-disabled": "true",
|
|
2187
2262
|
children: position
|
|
2188
2263
|
}
|
|
2189
|
-
) : /* @__PURE__ */ (0,
|
|
2264
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2190
2265
|
TrackPositionPlayPause,
|
|
2191
2266
|
{
|
|
2192
2267
|
position,
|
|
@@ -2195,12 +2270,12 @@ function TrackItem({
|
|
|
2195
2270
|
isSelected: selectedTrack?.uuid === track.uuid
|
|
2196
2271
|
}
|
|
2197
2272
|
),
|
|
2198
|
-
/* @__PURE__ */ (0,
|
|
2199
|
-
/* @__PURE__ */ (0,
|
|
2200
|
-
track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0,
|
|
2273
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
|
|
2274
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: (0, import_genre_tree12.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
|
|
2275
|
+
track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_genre_tree13.ArtistNames, { artists: track.artists, renderArtistLink }) }) : ""
|
|
2201
2276
|
] }),
|
|
2202
|
-
/* @__PURE__ */ (0,
|
|
2203
|
-
renderDuration && /* @__PURE__ */ (0,
|
|
2277
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.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_runtime35.jsx)("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
|
|
2278
|
+
renderDuration && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2204
2279
|
"div",
|
|
2205
2280
|
{
|
|
2206
2281
|
className: "duration flex text-base w-16 items-center justify-center",
|
|
@@ -2208,25 +2283,25 @@ function TrackItem({
|
|
|
2208
2283
|
children: renderDuration(track)
|
|
2209
2284
|
}
|
|
2210
2285
|
),
|
|
2211
|
-
renderActions && /* @__PURE__ */ (0,
|
|
2286
|
+
renderActions && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
|
|
2212
2287
|
]
|
|
2213
2288
|
}
|
|
2214
2289
|
);
|
|
2215
2290
|
}
|
|
2216
2291
|
|
|
2217
2292
|
// src/genre-tree/artist/ArtistSongs.tsx
|
|
2218
|
-
var
|
|
2293
|
+
var import_jsx_runtime36 = require("react/jsx-runtime");
|
|
2219
2294
|
var SongPageSchema2 = (0, import_transport8.PaginatedResponseSchema)(import_genre_tree14.SongDetailedSchema);
|
|
2220
2295
|
var SORTS = [
|
|
2221
2296
|
{ ordering: "title", label: "Title" },
|
|
2222
2297
|
{ ordering: "-albumYear", label: "Year" }
|
|
2223
2298
|
];
|
|
2224
2299
|
function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
2225
|
-
const [page, setPage] = (0,
|
|
2226
|
-
const [ordering, setOrdering] = (0,
|
|
2300
|
+
const [page, setPage] = (0, import_react14.useState)(1);
|
|
2301
|
+
const [ordering, setOrdering] = (0, import_react14.useState)("title");
|
|
2227
2302
|
const { fetch } = (0, import_transport8.useFetchWrapper)(getBackendBaseUrl);
|
|
2228
2303
|
const { playNewTrackListFromArtist } = (0, import_genre_tree14.useTrackList)();
|
|
2229
|
-
const { playerTrackObject } = (0,
|
|
2304
|
+
const { playerTrackObject } = (0, import_player10.usePlayer)();
|
|
2230
2305
|
const { data, isPending, isError } = (0, import_transport8.useQueryWithParse)({
|
|
2231
2306
|
queryKey: ["referenceSongs", "artist", artist.uuid, ordering, page],
|
|
2232
2307
|
queryFn: () => fetch(import_genre_tree14.libraryEndpoints.reference.songs.list(), true, false, void 0, {
|
|
@@ -2248,21 +2323,21 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
|
2248
2323
|
event.stopPropagation();
|
|
2249
2324
|
void play(song.uuid);
|
|
2250
2325
|
};
|
|
2251
|
-
return /* @__PURE__ */ (0,
|
|
2252
|
-
/* @__PURE__ */ (0,
|
|
2253
|
-
/* @__PURE__ */ (0,
|
|
2326
|
+
return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.PanelSection, { title: "Songs", children: isPending ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.Skeleton, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_jsx_runtime36.Fragment, { children: [
|
|
2327
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
|
|
2328
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
|
|
2254
2329
|
"button",
|
|
2255
2330
|
{
|
|
2256
2331
|
type: "button",
|
|
2257
2332
|
onClick: () => void play(),
|
|
2258
2333
|
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",
|
|
2259
2334
|
children: [
|
|
2260
|
-
/* @__PURE__ */ (0,
|
|
2335
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_lucide_react14.Play, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
|
|
2261
2336
|
"Play all"
|
|
2262
2337
|
]
|
|
2263
2338
|
}
|
|
2264
2339
|
),
|
|
2265
|
-
/* @__PURE__ */ (0,
|
|
2340
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
|
|
2266
2341
|
"button",
|
|
2267
2342
|
{
|
|
2268
2343
|
type: "button",
|
|
@@ -2277,7 +2352,7 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
|
2277
2352
|
sort.ordering
|
|
2278
2353
|
)) })
|
|
2279
2354
|
] }),
|
|
2280
|
-
/* @__PURE__ */ (0,
|
|
2355
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
|
|
2281
2356
|
TrackItem,
|
|
2282
2357
|
{
|
|
2283
2358
|
track: song,
|
|
@@ -2285,12 +2360,12 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
|
2285
2360
|
renderArtistLink
|
|
2286
2361
|
}
|
|
2287
2362
|
) }, song.uuid)) }),
|
|
2288
|
-
/* @__PURE__ */ (0,
|
|
2363
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
|
|
2289
2364
|
] }) });
|
|
2290
2365
|
}
|
|
2291
2366
|
|
|
2292
2367
|
// src/genre-tree/artist/useArtistPanel.tsx
|
|
2293
|
-
var
|
|
2368
|
+
var import_jsx_runtime37 = require("react/jsx-runtime");
|
|
2294
2369
|
function useArtistPanel({
|
|
2295
2370
|
uuid,
|
|
2296
2371
|
scope,
|
|
@@ -2310,26 +2385,26 @@ function useArtistPanel({
|
|
|
2310
2385
|
return {
|
|
2311
2386
|
...panel,
|
|
2312
2387
|
title: "Artist",
|
|
2313
|
-
content: /* @__PURE__ */ (0,
|
|
2314
|
-
/* @__PURE__ */ (0,
|
|
2315
|
-
/* @__PURE__ */ (0,
|
|
2316
|
-
/* @__PURE__ */ (0,
|
|
2317
|
-
/* @__PURE__ */ (0,
|
|
2318
|
-
/* @__PURE__ */ (0,
|
|
2319
|
-
/* @__PURE__ */ (0,
|
|
2388
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
|
|
2389
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui14.Skeleton, { className: "h-3 w-16" }),
|
|
2390
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui14.Skeleton, { className: "h-4 w-32" }),
|
|
2391
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui14.Skeleton, { className: "h-3 w-16" }),
|
|
2392
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui14.Skeleton, { className: "h-4 w-24" }),
|
|
2393
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui14.Skeleton, { className: "h-16 mt-2.5" }),
|
|
2394
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui14.Skeleton, { className: "h-16" })
|
|
2320
2395
|
] })
|
|
2321
2396
|
};
|
|
2322
2397
|
case "not-found":
|
|
2323
2398
|
return {
|
|
2324
2399
|
...panel,
|
|
2325
2400
|
title: "Artist not found",
|
|
2326
|
-
content: /* @__PURE__ */ (0,
|
|
2401
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This artist does not exist." })
|
|
2327
2402
|
};
|
|
2328
2403
|
case "error":
|
|
2329
2404
|
return {
|
|
2330
2405
|
...panel,
|
|
2331
2406
|
title: "Artist",
|
|
2332
|
-
content: /* @__PURE__ */ (0,
|
|
2407
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
|
|
2333
2408
|
};
|
|
2334
2409
|
case "success": {
|
|
2335
2410
|
const { artist } = result;
|
|
@@ -2338,13 +2413,13 @@ function useArtistPanel({
|
|
|
2338
2413
|
return {
|
|
2339
2414
|
...panel,
|
|
2340
2415
|
title: artist.name,
|
|
2341
|
-
content: /* @__PURE__ */ (0,
|
|
2342
|
-
shareUrl && /* @__PURE__ */ (0,
|
|
2343
|
-
artist.image && /* @__PURE__ */ (0,
|
|
2344
|
-
/* @__PURE__ */ (0,
|
|
2345
|
-
/* @__PURE__ */ (0,
|
|
2346
|
-
/* @__PURE__ */ (0,
|
|
2347
|
-
artist.tracksCount > 0 && /* @__PURE__ */ (0,
|
|
2416
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(import_jsx_runtime37.Fragment, { children: [
|
|
2417
|
+
shareUrl && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: shareTarget && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl }) }) }),
|
|
2418
|
+
artist.image && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ArtistPhoto, { image: artist.image, name: artist.name }),
|
|
2419
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ArtistHeader, { artist }),
|
|
2420
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
|
|
2421
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
|
|
2422
|
+
artist.tracksCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
|
|
2348
2423
|
ArtistSongs,
|
|
2349
2424
|
{
|
|
2350
2425
|
artist: { uuid: artist.uuid, name: artist.name },
|
|
@@ -2353,7 +2428,7 @@ function useArtistPanel({
|
|
|
2353
2428
|
}
|
|
2354
2429
|
)
|
|
2355
2430
|
] }),
|
|
2356
|
-
extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0,
|
|
2431
|
+
extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ArtistDiscography, { albums: artist.albums }) : void 0
|
|
2357
2432
|
};
|
|
2358
2433
|
}
|
|
2359
2434
|
}
|
|
@@ -2362,27 +2437,27 @@ function useArtistPanel({
|
|
|
2362
2437
|
// src/genre-tree/SizeMetricToggle.tsx
|
|
2363
2438
|
var import_md2 = require("react-icons/md");
|
|
2364
2439
|
var import_ui15 = require("@behindthemusictree/ui");
|
|
2365
|
-
var
|
|
2440
|
+
var import_jsx_runtime38 = require("react/jsx-runtime");
|
|
2366
2441
|
function SizeMetricToggle({
|
|
2367
2442
|
value,
|
|
2368
2443
|
onChange
|
|
2369
2444
|
}) {
|
|
2370
|
-
return /* @__PURE__ */ (0,
|
|
2445
|
+
return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
|
|
2371
2446
|
import_ui15.SegmentedControl,
|
|
2372
2447
|
{
|
|
2373
2448
|
"aria-label": "Size nodes by",
|
|
2374
2449
|
value,
|
|
2375
2450
|
onChange,
|
|
2376
2451
|
options: [
|
|
2377
|
-
{ value: "songs", label: /* @__PURE__ */ (0,
|
|
2378
|
-
{ value: "views", label: /* @__PURE__ */ (0,
|
|
2452
|
+
{ value: "songs", label: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_md2.MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
|
|
2453
|
+
{ value: "views", label: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_md2.MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
|
|
2379
2454
|
]
|
|
2380
2455
|
}
|
|
2381
2456
|
);
|
|
2382
2457
|
}
|
|
2383
2458
|
|
|
2384
2459
|
// src/genre-tree/GenreTreeView.tsx
|
|
2385
|
-
var
|
|
2460
|
+
var import_jsx_runtime39 = require("react/jsx-runtime");
|
|
2386
2461
|
var PREVIOUS_BUTTON_CSS = `
|
|
2387
2462
|
.genre-tree-view .search-overlay .gtv-info-panel-previous { display: none; }
|
|
2388
2463
|
.genre-tree-view:has(.search-overlay .gtv-info-panel-previous) .genre-search input { padding-left: 2.75rem; }
|
|
@@ -2392,27 +2467,6 @@ var PREVIOUS_BUTTON_CSS = `
|
|
|
2392
2467
|
.genre-search-previous { animation: genre-tree-view-previous-in 200ms ease-out; }
|
|
2393
2468
|
}
|
|
2394
2469
|
`;
|
|
2395
|
-
function GenreNodeActionBand({ genrePlaylist, scope, playback, ...bandProps }) {
|
|
2396
|
-
const { isPlaying, setIsPlaying } = (0, import_player10.usePlayer)();
|
|
2397
|
-
const isCurrentTrackList = playback.playingNodeId === genrePlaylist.uuid;
|
|
2398
|
-
const handlePlayPause = () => {
|
|
2399
|
-
if (isCurrentTrackList) {
|
|
2400
|
-
setIsPlaying(!isPlaying);
|
|
2401
|
-
return;
|
|
2402
|
-
}
|
|
2403
|
-
playback.play(genrePlaylist).catch((error) => {
|
|
2404
|
-
console.error("Failed to load genre playlist tracks:", error);
|
|
2405
|
-
});
|
|
2406
|
-
};
|
|
2407
|
-
return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
|
|
2408
|
-
GenreActionBand,
|
|
2409
|
-
{
|
|
2410
|
-
...bandProps,
|
|
2411
|
-
onPlayPause: genrePlaylist.tracksCount > 0 ? handlePlayPause : void 0,
|
|
2412
|
-
isPlaying: isCurrentTrackList && isPlaying
|
|
2413
|
-
}
|
|
2414
|
-
);
|
|
2415
|
-
}
|
|
2416
2470
|
var NO_POP_ROOT_TITLE = "This genre tree has no 'Mainstream Pop' root yet";
|
|
2417
2471
|
var REGIONS_CLASS = "[--gtv-center-left:var(--layout-center-left)] [--gtv-center-right:var(--layout-center-right)] [--gtv-center-top:var(--layout-band-top)] [&_.gtv-info-panel--overlay]:[--gtv-info-panel-header-inset:0px]";
|
|
2418
2472
|
function GenreTreeView({
|
|
@@ -2426,6 +2480,7 @@ function GenreTreeView({
|
|
|
2426
2480
|
selectedNodeId: controlledSelectedNodeId,
|
|
2427
2481
|
onSelectedNodeIdChange,
|
|
2428
2482
|
defaultViewport,
|
|
2483
|
+
viewportRestore,
|
|
2429
2484
|
onViewportChange,
|
|
2430
2485
|
readOnly = false,
|
|
2431
2486
|
className: className2,
|
|
@@ -2434,7 +2489,6 @@ function GenreTreeView({
|
|
|
2434
2489
|
allowWheelRotation: controlledAllowWheelRotation,
|
|
2435
2490
|
criteriaOverviewSchema,
|
|
2436
2491
|
renderGenreDetailExtras,
|
|
2437
|
-
controlsOrientation = "horizontal",
|
|
2438
2492
|
floatingControlsLeading,
|
|
2439
2493
|
sizeMetric: controlledSizeMetric,
|
|
2440
2494
|
onSizeMetricChange,
|
|
@@ -2461,14 +2515,14 @@ function GenreTreeView({
|
|
|
2461
2515
|
placement = "default",
|
|
2462
2516
|
...focus
|
|
2463
2517
|
}) {
|
|
2464
|
-
const getShareUrlRef = (0,
|
|
2518
|
+
const getShareUrlRef = (0, import_react15.useRef)(getShareUrl);
|
|
2465
2519
|
getShareUrlRef.current = getShareUrl;
|
|
2466
|
-
const [internalArtistUuid, setInternalArtistUuid] = (0,
|
|
2520
|
+
const [internalArtistUuid, setInternalArtistUuid] = (0, import_react15.useState)(null);
|
|
2467
2521
|
const isArtistControlled = controlledArtistUuid !== void 0;
|
|
2468
2522
|
const artistUuid = isArtistControlled ? controlledArtistUuid : internalArtistUuid;
|
|
2469
|
-
const [internalSizeMetric, setInternalSizeMetric] = (0,
|
|
2523
|
+
const [internalSizeMetric, setInternalSizeMetric] = (0, import_react15.useState)("songs");
|
|
2470
2524
|
const sizeMetric = controlledSizeMetric ?? internalSizeMetric;
|
|
2471
|
-
const deferredSizeMetric = (0,
|
|
2525
|
+
const deferredSizeMetric = (0, import_react15.useDeferredValue)(sizeMetric);
|
|
2472
2526
|
const changeSizeMetric = (metric) => {
|
|
2473
2527
|
if (controlledSizeMetric === void 0) setInternalSizeMetric(metric);
|
|
2474
2528
|
onSizeMetricChange?.(metric);
|
|
@@ -2504,6 +2558,16 @@ function GenreTreeView({
|
|
|
2504
2558
|
criteriaOverviewSchema,
|
|
2505
2559
|
artistUuid
|
|
2506
2560
|
});
|
|
2561
|
+
const restoreRevision = viewportRestore?.revision;
|
|
2562
|
+
const [stackedRestoreOwner, setStackedRestoreOwner] = (0, import_react15.useState)({ revision: restoreRevision, rootUuid: selectedGenrePlaylist?.root.uuid });
|
|
2563
|
+
let restoreRootUuid = stackedRestoreOwner.rootUuid;
|
|
2564
|
+
if (stackedRestoreOwner.revision !== restoreRevision) {
|
|
2565
|
+
restoreRootUuid = selectedGenrePlaylist?.root.uuid;
|
|
2566
|
+
setStackedRestoreOwner({ revision: restoreRevision, rootUuid: restoreRootUuid });
|
|
2567
|
+
} else if (restoreRootUuid === void 0 && selectedGenrePlaylist) {
|
|
2568
|
+
restoreRootUuid = selectedGenrePlaylist.root.uuid;
|
|
2569
|
+
setStackedRestoreOwner({ revision: restoreRevision, rootUuid: restoreRootUuid });
|
|
2570
|
+
}
|
|
2507
2571
|
const isGenreUpdating = (0, import_react_query3.useIsMutating)({ mutationKey: import_genre_tree17.genreUpdateMutationKey }) > 0;
|
|
2508
2572
|
const { mutate: updateGenre } = (0, import_genre_tree17.useUpdateGenre)(scope, getBackendBaseUrl);
|
|
2509
2573
|
const busy = deferredSizeMetric !== sizeMetric || isTreeFetching || isGenreUpdating;
|
|
@@ -2511,7 +2575,7 @@ function GenreTreeView({
|
|
|
2511
2575
|
const canSizeByViews = !isArtistFiltered && hasViewsCount(visibleGenrePlaylists);
|
|
2512
2576
|
const artistResult = (0, import_genre_tree17.useArtist)(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
|
|
2513
2577
|
const filterArtistName = artistResult.status === "success" ? artistResult.artist.name : null;
|
|
2514
|
-
const filterArtist = (0,
|
|
2578
|
+
const filterArtist = (0, import_react15.useMemo)(
|
|
2515
2579
|
() => artistUuid !== null && filterArtistName !== null ? { uuid: artistUuid, name: filterArtistName } : null,
|
|
2516
2580
|
[artistUuid, filterArtistName]
|
|
2517
2581
|
);
|
|
@@ -2521,14 +2585,14 @@ function GenreTreeView({
|
|
|
2521
2585
|
if (selectedNodeId !== null) clearSelection();
|
|
2522
2586
|
changeArtist(uuid);
|
|
2523
2587
|
};
|
|
2524
|
-
const [expandedArtistUuid, setExpandedArtistUuid] = (0,
|
|
2588
|
+
const [expandedArtistUuid, setExpandedArtistUuid] = (0, import_react15.useState)(null);
|
|
2525
2589
|
const artistBaseUrl = artistSearchBaseUrl ?? getBackendBaseUrl;
|
|
2526
2590
|
const artistPanel = useArtistPanel({
|
|
2527
2591
|
uuid: artistUuid,
|
|
2528
2592
|
scope,
|
|
2529
2593
|
getBackendBaseUrl: artistBaseUrl,
|
|
2530
2594
|
getShareUrl: getShareUrlRef.current,
|
|
2531
|
-
renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0,
|
|
2595
|
+
renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
|
|
2532
2596
|
expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
|
|
2533
2597
|
onExpandedChange: (on) => {
|
|
2534
2598
|
if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
|
|
@@ -2538,8 +2602,8 @@ function GenreTreeView({
|
|
|
2538
2602
|
});
|
|
2539
2603
|
const builtInArtistPanel = overlayPanel || hasSelectedGenreNode ? void 0 : artistPanel;
|
|
2540
2604
|
const activeOverlayPanel = overlayPanel ?? (builtInArtistPanel && { ...builtInArtistPanel, hideTitle: true, hideClose: true });
|
|
2541
|
-
const [uncontrolledCollapsed, setUncontrolledCollapsed] = (0,
|
|
2542
|
-
const [uncontrolledExpanded, setUncontrolledExpanded] = (0,
|
|
2605
|
+
const [uncontrolledCollapsed, setUncontrolledCollapsed] = (0, import_react15.useState)(false);
|
|
2606
|
+
const [uncontrolledExpanded, setUncontrolledExpanded] = (0, import_react15.useState)(false);
|
|
2543
2607
|
const isCollapsed = collapsed ?? uncontrolledCollapsed;
|
|
2544
2608
|
const isExpanded = expanded ?? uncontrolledExpanded;
|
|
2545
2609
|
const changeCollapsed = (next) => {
|
|
@@ -2550,18 +2614,11 @@ function GenreTreeView({
|
|
|
2550
2614
|
setUncontrolledExpanded(next);
|
|
2551
2615
|
onExpandedChange?.(next);
|
|
2552
2616
|
};
|
|
2553
|
-
(0,
|
|
2617
|
+
(0, import_react15.useEffect)(() => {
|
|
2554
2618
|
setUncontrolledCollapsed(false);
|
|
2555
2619
|
}, [selectedNodeId, artistUuid]);
|
|
2556
2620
|
const isDetailHidden = isCollapsed || !hasSelectedGenreNode && !activeOverlayPanel;
|
|
2557
|
-
(
|
|
2558
|
-
if (placement !== "regions" || !isDetailHidden) return void 0;
|
|
2559
|
-
const root = document.documentElement;
|
|
2560
|
-
root.style.setProperty("--layout-left-w", "0px");
|
|
2561
|
-
return () => {
|
|
2562
|
-
root.style.removeProperty("--layout-left-w");
|
|
2563
|
-
};
|
|
2564
|
-
}, [placement, isDetailHidden]);
|
|
2621
|
+
useLeftDetailPanelLayout(!isDetailHidden, placement === "regions");
|
|
2565
2622
|
const detailOverlay = placement === "regions" && activeOverlayPanel?.onExpandedChange ? activeOverlayPanel : void 0;
|
|
2566
2623
|
const detailExpanded = detailOverlay ? !!detailOverlay.expanded : isExpanded;
|
|
2567
2624
|
const onDetailExpandedChange = detailOverlay ? detailOverlay.onExpandedChange : changeExpanded;
|
|
@@ -2573,14 +2630,14 @@ function GenreTreeView({
|
|
|
2573
2630
|
hideExpandButton: placement === "regions" ? true : void 0,
|
|
2574
2631
|
overlayPanel: detailOverlay ? { ...detailOverlay, collapsed: isCollapsed, hideExpandButton: true } : activeOverlayPanel
|
|
2575
2632
|
};
|
|
2576
|
-
const [isWheelRevealed, setIsWheelRevealed] = (0,
|
|
2633
|
+
const [isWheelRevealed, setIsWheelRevealed] = (0, import_react15.useState)(false);
|
|
2577
2634
|
const usesHandoff = viewMode === "wheel" || viewMode === "pop-core";
|
|
2578
2635
|
const isTreeReady = !isLoading && (!usesHandoff || isWheelRevealed);
|
|
2579
|
-
const rootRef = (0,
|
|
2636
|
+
const rootRef = (0, import_react15.useRef)(null);
|
|
2580
2637
|
const handlePrevious = () => {
|
|
2581
2638
|
rootRef.current?.querySelector(".search-overlay .gtv-info-panel-previous")?.click();
|
|
2582
2639
|
};
|
|
2583
|
-
(0,
|
|
2640
|
+
(0, import_react15.useEffect)(() => {
|
|
2584
2641
|
if (!isTreeReady || viewMode !== "pop-core" || !hasSelectedGenreNode || !selectedGenrePlaylist) return;
|
|
2585
2642
|
const root = rootRef.current;
|
|
2586
2643
|
const centerChip = root?.querySelector(".gtv-wheel-center-node .gtv-wheel-chip");
|
|
@@ -2590,21 +2647,21 @@ function GenreTreeView({
|
|
|
2590
2647
|
centerChip.click();
|
|
2591
2648
|
}, [hasSelectedGenreNode, isTreeReady, selectedGenrePlaylist, viewMode]);
|
|
2592
2649
|
const { showPopup, hidePopup } = (0, import_popup6.usePopup)();
|
|
2593
|
-
const [searchText, setSearchText] = (0,
|
|
2650
|
+
const [searchText, setSearchText] = (0, import_react15.useState)("");
|
|
2594
2651
|
const artistSearchResult = (0, import_genre_tree16.useArtistSearch)(artistBaseUrl, artistSearch ? searchText : "");
|
|
2595
2652
|
const artistExtraResults = artistSearch ? {
|
|
2596
2653
|
label: "Artists",
|
|
2597
2654
|
items: artistSearchResult.artists.map((artist) => ({
|
|
2598
2655
|
key: artist.uuid,
|
|
2599
|
-
content: /* @__PURE__ */ (0,
|
|
2600
|
-
/* @__PURE__ */ (0,
|
|
2656
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_jsx_runtime39.Fragment, { children: [
|
|
2657
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("span", { className: "block truncate font-medium", children: [
|
|
2601
2658
|
artist.name,
|
|
2602
|
-
artist.disambiguation && /* @__PURE__ */ (0,
|
|
2659
|
+
artist.disambiguation && /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("span", { className: "font-normal text-[var(--color-neutral-500)]", children: [
|
|
2603
2660
|
" \xB7 ",
|
|
2604
2661
|
artist.disambiguation
|
|
2605
2662
|
] })
|
|
2606
2663
|
] }),
|
|
2607
|
-
artist.matchedAlias && /* @__PURE__ */ (0,
|
|
2664
|
+
artist.matchedAlias && /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [
|
|
2608
2665
|
"also known as ",
|
|
2609
2666
|
artist.matchedAlias
|
|
2610
2667
|
] })
|
|
@@ -2630,13 +2687,13 @@ function GenreTreeView({
|
|
|
2630
2687
|
const handleGenreSearchZoomIn = () => {
|
|
2631
2688
|
if (selectedUuid) focus.onFocusGenreChange?.(selectedUuid, "zoom-in");
|
|
2632
2689
|
};
|
|
2633
|
-
const zoomInRef = (0,
|
|
2690
|
+
const zoomInRef = (0, import_react15.useRef)(void 0);
|
|
2634
2691
|
zoomInRef.current = canZoomIn ? handleGenreSearchZoomIn : void 0;
|
|
2635
|
-
const renderInfoPanelBody = (0,
|
|
2692
|
+
const renderInfoPanelBody = (0, import_react15.useCallback)(
|
|
2636
2693
|
(node, { toolbar, ancestors, family }) => {
|
|
2637
2694
|
const genrePlaylist = genrePlaylistByNodeId.get(node.id);
|
|
2638
2695
|
const criteria = genrePlaylist?.criteria;
|
|
2639
|
-
if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ (0,
|
|
2696
|
+
if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_jsx_runtime39.Fragment, { children: [
|
|
2640
2697
|
toolbar,
|
|
2641
2698
|
family
|
|
2642
2699
|
] }), aside: ancestors };
|
|
@@ -2656,21 +2713,26 @@ function GenreTreeView({
|
|
|
2656
2713
|
(sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist.uuid && sibling.criteria?.side === "pop"
|
|
2657
2714
|
) : void 0;
|
|
2658
2715
|
return {
|
|
2659
|
-
primary: /* @__PURE__ */ (0,
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2716
|
+
primary: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2717
|
+
GenreInfoPanelBody,
|
|
2718
|
+
{
|
|
2719
|
+
toolbar,
|
|
2720
|
+
family,
|
|
2721
|
+
genreUuid,
|
|
2722
|
+
genrePlaylist,
|
|
2723
|
+
wikidataId: criteria.wikidataId,
|
|
2724
|
+
scope,
|
|
2725
|
+
getBackendBaseUrl,
|
|
2726
|
+
playback,
|
|
2727
|
+
band: {
|
|
2664
2728
|
node,
|
|
2665
|
-
genrePlaylist,
|
|
2666
|
-
scope,
|
|
2667
2729
|
shareTarget: shareUrl ? shareTarget : void 0,
|
|
2668
2730
|
shareUrl,
|
|
2669
2731
|
onZoomIn: zoomInRef.current,
|
|
2670
2732
|
onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
|
|
2671
2733
|
onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
|
|
2672
2734
|
onChangeParent: readOnly ? void 0 : () => showPopup(
|
|
2673
|
-
/* @__PURE__ */ (0,
|
|
2735
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2674
2736
|
ChangeParentPopup,
|
|
2675
2737
|
{
|
|
2676
2738
|
genrePlaylist: { ...genrePlaylist, criteria },
|
|
@@ -2685,19 +2747,9 @@ function GenreTreeView({
|
|
|
2685
2747
|
side,
|
|
2686
2748
|
onToggleSide: canToggleSide ? () => updateGenre({ uuid: genreUuid, data: { side: side === "pop" ? "core" : "pop" } }) : void 0,
|
|
2687
2749
|
toggleSideDisabledReason: popSibling ? `${popSibling.name} is already the pop side of ${parentPlaylist?.name}` : void 0,
|
|
2688
|
-
additionalActions
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
),
|
|
2692
|
-
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
|
|
2693
|
-
GenreDetailExtras,
|
|
2694
|
-
{
|
|
2695
|
-
family,
|
|
2696
|
-
genreUuid,
|
|
2697
|
-
genrePlaylist,
|
|
2698
|
-
wikidataId: criteria.wikidataId,
|
|
2699
|
-
scope,
|
|
2700
|
-
getBackendBaseUrl,
|
|
2750
|
+
additionalActions
|
|
2751
|
+
},
|
|
2752
|
+
extras: {
|
|
2701
2753
|
criteriaOverviewSchema,
|
|
2702
2754
|
renderGenreDetailExtras,
|
|
2703
2755
|
renderArtistLink,
|
|
@@ -2707,10 +2759,9 @@ function GenreTreeView({
|
|
|
2707
2759
|
renderSongLeading,
|
|
2708
2760
|
renderSongActions,
|
|
2709
2761
|
renderTracksToolbar
|
|
2710
|
-
}
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
] }),
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
),
|
|
2714
2765
|
aside: ancestors
|
|
2715
2766
|
};
|
|
2716
2767
|
},
|
|
@@ -2741,7 +2792,7 @@ function GenreTreeView({
|
|
|
2741
2792
|
);
|
|
2742
2793
|
const isRotationControlled = controlledAllowWheelRotation !== void 0;
|
|
2743
2794
|
const allowWheelRotation = controlledAllowWheelRotation ?? internalAllowWheelRotation;
|
|
2744
|
-
const detailTab = placement === "regions" && (hasSelectedGenreNode || detailOverlay) ? /* @__PURE__ */ (0,
|
|
2795
|
+
const detailTab = placement === "regions" && (hasSelectedGenreNode || detailOverlay) ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2745
2796
|
PanelEdgeTab,
|
|
2746
2797
|
{
|
|
2747
2798
|
side: "left",
|
|
@@ -2751,8 +2802,8 @@ function GenreTreeView({
|
|
|
2751
2802
|
) : null;
|
|
2752
2803
|
const renderArtistFilter = () => {
|
|
2753
2804
|
if (isLoading || !filterArtist) return null;
|
|
2754
|
-
return /* @__PURE__ */ (0,
|
|
2755
|
-
/* @__PURE__ */ (0,
|
|
2805
|
+
return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
|
|
2806
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2756
2807
|
"button",
|
|
2757
2808
|
{
|
|
2758
2809
|
type: "button",
|
|
@@ -2764,7 +2815,7 @@ function GenreTreeView({
|
|
|
2764
2815
|
children: filterArtist.name
|
|
2765
2816
|
}
|
|
2766
2817
|
),
|
|
2767
|
-
/* @__PURE__ */ (0,
|
|
2818
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2768
2819
|
"button",
|
|
2769
2820
|
{
|
|
2770
2821
|
type: "button",
|
|
@@ -2776,8 +2827,8 @@ function GenreTreeView({
|
|
|
2776
2827
|
)
|
|
2777
2828
|
] });
|
|
2778
2829
|
};
|
|
2779
|
-
const actions = /* @__PURE__ */ (0,
|
|
2780
|
-
!isLoading && /* @__PURE__ */ (0,
|
|
2830
|
+
const actions = /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_jsx_runtime39.Fragment, { children: [
|
|
2831
|
+
!isLoading && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2781
2832
|
GenreSearch,
|
|
2782
2833
|
{
|
|
2783
2834
|
genrePlaylists: visibleGenrePlaylists,
|
|
@@ -2789,9 +2840,9 @@ function GenreTreeView({
|
|
|
2789
2840
|
extraResults: artistExtraResults
|
|
2790
2841
|
}
|
|
2791
2842
|
),
|
|
2792
|
-
!isLoading && filterArtist && /* @__PURE__ */ (0,
|
|
2793
|
-
/* @__PURE__ */ (0,
|
|
2794
|
-
!isLoading && !isControlled && /* @__PURE__ */ (0,
|
|
2843
|
+
!isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
|
|
2844
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.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: [
|
|
2845
|
+
!isLoading && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2795
2846
|
import_ui16.SegmentedControl,
|
|
2796
2847
|
{
|
|
2797
2848
|
"aria-label": "Tree view mode",
|
|
@@ -2805,13 +2856,13 @@ function GenreTreeView({
|
|
|
2805
2856
|
]
|
|
2806
2857
|
}
|
|
2807
2858
|
),
|
|
2808
|
-
!isLoading && showDisplayOptions && /* @__PURE__ */ (0,
|
|
2859
|
+
!isLoading && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2809
2860
|
"div",
|
|
2810
2861
|
{
|
|
2811
2862
|
className: "flex items-center gap-1",
|
|
2812
2863
|
role: "group",
|
|
2813
2864
|
"aria-label": "Tree display options",
|
|
2814
|
-
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0,
|
|
2865
|
+
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2815
2866
|
import_ui16.Button,
|
|
2816
2867
|
{
|
|
2817
2868
|
variant: allowWheelRotation ? "default" : "outline",
|
|
@@ -2822,7 +2873,7 @@ function GenreTreeView({
|
|
|
2822
2873
|
)
|
|
2823
2874
|
}
|
|
2824
2875
|
),
|
|
2825
|
-
!isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0,
|
|
2876
|
+
!isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2826
2877
|
import_ui16.IconTextButton,
|
|
2827
2878
|
{
|
|
2828
2879
|
icon: import_lucide_react15.Plus,
|
|
@@ -2832,42 +2883,42 @@ function GenreTreeView({
|
|
|
2832
2883
|
)
|
|
2833
2884
|
] })
|
|
2834
2885
|
] });
|
|
2835
|
-
return /* @__PURE__ */ (0,
|
|
2886
|
+
return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
|
|
2836
2887
|
"div",
|
|
2837
2888
|
{
|
|
2838
2889
|
ref: rootRef,
|
|
2839
2890
|
className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
|
|
2840
2891
|
children: [
|
|
2841
|
-
/* @__PURE__ */ (0,
|
|
2842
|
-
/* @__PURE__ */ (0,
|
|
2892
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("style", { children: PREVIOUS_BUTTON_CSS }),
|
|
2893
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2843
2894
|
"div",
|
|
2844
2895
|
{
|
|
2845
2896
|
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"}`,
|
|
2846
2897
|
children: actions
|
|
2847
2898
|
}
|
|
2848
2899
|
),
|
|
2849
|
-
!isLoading && filterArtist && /* @__PURE__ */ (0,
|
|
2900
|
+
!isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2850
2901
|
"div",
|
|
2851
2902
|
{
|
|
2852
2903
|
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"}`,
|
|
2853
2904
|
children: renderArtistFilter()
|
|
2854
2905
|
}
|
|
2855
2906
|
),
|
|
2856
|
-
detailTab && /* @__PURE__ */ (0,
|
|
2907
|
+
detailTab && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2857
2908
|
"div",
|
|
2858
2909
|
{
|
|
2859
2910
|
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))]"}`,
|
|
2860
2911
|
children: detailTab
|
|
2861
2912
|
}
|
|
2862
2913
|
),
|
|
2863
|
-
/* @__PURE__ */ (0,
|
|
2864
|
-
!isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0,
|
|
2865
|
-
isLoading ? /* @__PURE__ */ (0,
|
|
2914
|
+
/* @__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: [
|
|
2915
|
+
!isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
|
|
2916
|
+
isLoading ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_genre_tree_view4.GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
|
|
2866
2917
|
// The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
|
|
2867
2918
|
// and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
|
|
2868
2919
|
// Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
|
|
2869
2920
|
// The panel's own Zoom in button is hidden: the search bar carries it.
|
|
2870
|
-
/* @__PURE__ */ (0,
|
|
2921
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 [--gtv-info-panel-header-inset:calc(var(--layout-search-h,2.5rem)+var(--layout-panel-inset,0.5rem))] [&_.gtv-info-panel-header]:pb-2 [&_.gtv-info-panel-title--hidden]:h-0 [&_.gtv-toolbar]:hidden", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2871
2922
|
GenrePlaylistTreeWheel,
|
|
2872
2923
|
{
|
|
2873
2924
|
scope,
|
|
@@ -2884,21 +2935,21 @@ function GenreTreeView({
|
|
|
2884
2935
|
selectedNodeId,
|
|
2885
2936
|
readOnly,
|
|
2886
2937
|
allowWheelRotation,
|
|
2887
|
-
controlsOrientation,
|
|
2888
2938
|
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2889
2939
|
busy,
|
|
2890
|
-
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0,
|
|
2940
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
2891
2941
|
floatingControlsLeading,
|
|
2892
|
-
canSizeByViews && /* @__PURE__ */ (0,
|
|
2942
|
+
canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2893
2943
|
] }) : void 0,
|
|
2894
2944
|
defaultViewport,
|
|
2945
|
+
viewportRestore,
|
|
2895
2946
|
onViewportChange,
|
|
2896
2947
|
...infoPanelCuration
|
|
2897
2948
|
}
|
|
2898
2949
|
) }) })
|
|
2899
2950
|
) : viewMode === "pop-core" ? (
|
|
2900
2951
|
// Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
|
|
2901
|
-
/* @__PURE__ */ (0,
|
|
2952
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 [--gtv-info-panel-header-inset:calc(var(--layout-search-h,2.5rem)+var(--layout-panel-inset,0.5rem))] [&_.gtv-info-panel-header]:pb-2 [&_.gtv-info-panel-title--hidden]:h-0 [&_.gtv-toolbar]:hidden [&_.gtv-info-panel-zoom-in]:hidden", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2902
2953
|
GenrePlaylistTreeWheelRadialPopCore,
|
|
2903
2954
|
{
|
|
2904
2955
|
scope,
|
|
@@ -2915,20 +2966,20 @@ function GenreTreeView({
|
|
|
2915
2966
|
selectedNodeId,
|
|
2916
2967
|
readOnly,
|
|
2917
2968
|
allowWheelRotation,
|
|
2918
|
-
controlsOrientation,
|
|
2919
2969
|
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2920
2970
|
busy,
|
|
2921
|
-
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0,
|
|
2971
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
2922
2972
|
floatingControlsLeading,
|
|
2923
|
-
canSizeByViews && /* @__PURE__ */ (0,
|
|
2973
|
+
canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2924
2974
|
] }) : void 0,
|
|
2925
2975
|
defaultViewport,
|
|
2976
|
+
viewportRestore,
|
|
2926
2977
|
onViewportChange,
|
|
2927
2978
|
...infoPanelCuration,
|
|
2928
2979
|
...focus
|
|
2929
2980
|
}
|
|
2930
2981
|
) }) })
|
|
2931
|
-
) : viewMode === "outline" ? /* @__PURE__ */ (0,
|
|
2982
|
+
) : viewMode === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative [--gtv-info-panel-header-inset:calc(var(--layout-search-h,2.5rem)+var(--layout-panel-inset,0.5rem)+0.75rem)] [&_.gtv-toolbar]:hidden", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2932
2983
|
GenrePlaylistTreeWheelRadialPopCore,
|
|
2933
2984
|
{
|
|
2934
2985
|
outline: true,
|
|
@@ -2947,13 +2998,13 @@ function GenreTreeView({
|
|
|
2947
2998
|
readOnly,
|
|
2948
2999
|
...infoPanelCuration
|
|
2949
3000
|
}
|
|
2950
|
-
) }) : /* @__PURE__ */ (0,
|
|
3001
|
+
) }) : /* @__PURE__ */ (0, import_jsx_runtime39.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)] [&_.gtv-toolbar]:hidden", children: Object.entries(groupedGenrePlaylistsByRoot).map(
|
|
2951
3002
|
([uuid, genrePlaylistTreePerRoot], rootIndex) => {
|
|
2952
|
-
return /* @__PURE__ */ (0,
|
|
3003
|
+
return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2953
3004
|
"div",
|
|
2954
3005
|
{
|
|
2955
3006
|
className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
|
|
2956
|
-
children: /* @__PURE__ */ (0,
|
|
3007
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2957
3008
|
GenrePlaylistTreePerRoot,
|
|
2958
3009
|
{
|
|
2959
3010
|
scope,
|
|
@@ -2971,8 +3022,8 @@ function GenreTreeView({
|
|
|
2971
3022
|
selectedNodeId: uuid === selectedGenrePlaylist?.root.uuid ? selectedNodeId : null,
|
|
2972
3023
|
readOnly,
|
|
2973
3024
|
wheelZoom: "modifier",
|
|
2974
|
-
controlsOrientation,
|
|
2975
3025
|
defaultViewport: uuid === selectedGenrePlaylist?.root.uuid ? defaultViewport : void 0,
|
|
3026
|
+
viewportRestore: uuid === selectedGenrePlaylist?.root.uuid && uuid === restoreRootUuid ? viewportRestore : void 0,
|
|
2976
3027
|
onViewportChange: uuid === selectedGenrePlaylist?.root.uuid ? onViewportChange : void 0,
|
|
2977
3028
|
...infoPanelCuration,
|
|
2978
3029
|
overlayPanel: rootIndex === 0 ? activeOverlayPanel : void 0
|
|
@@ -2989,17 +3040,126 @@ function GenreTreeView({
|
|
|
2989
3040
|
);
|
|
2990
3041
|
}
|
|
2991
3042
|
|
|
2992
|
-
// src/genre-tree/
|
|
3043
|
+
// src/genre-tree/GenreDetailPanel.tsx
|
|
3044
|
+
var import_react16 = require("react");
|
|
2993
3045
|
var import_genre_tree_view5 = require("@behindthemusictree/genre-tree-view");
|
|
3046
|
+
var import_ui17 = require("@behindthemusictree/ui");
|
|
3047
|
+
var import_genre_tree18 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3048
|
+
var import_jsx_runtime40 = require("react/jsx-runtime");
|
|
3049
|
+
var ITEM_COUNT_LABEL = "Songs";
|
|
3050
|
+
var RELATIVE_ITEM_COUNT = 1;
|
|
3051
|
+
function toNode(genre, parentId) {
|
|
3052
|
+
return {
|
|
3053
|
+
id: genre.uuid,
|
|
3054
|
+
parentId,
|
|
3055
|
+
name: genre.name,
|
|
3056
|
+
itemCount: RELATIVE_ITEM_COUNT,
|
|
3057
|
+
side: genre.side ?? void 0,
|
|
3058
|
+
wikidataId: genre.wikidataId
|
|
3059
|
+
};
|
|
3060
|
+
}
|
|
3061
|
+
function GenreDetailPanel({
|
|
3062
|
+
genreUuid,
|
|
3063
|
+
scope,
|
|
3064
|
+
getBackendBaseUrl,
|
|
3065
|
+
onClose,
|
|
3066
|
+
onSelectGenre,
|
|
3067
|
+
onZoomIn,
|
|
3068
|
+
shareUrl,
|
|
3069
|
+
shareTarget,
|
|
3070
|
+
collapsed,
|
|
3071
|
+
expanded,
|
|
3072
|
+
onExpandedChange,
|
|
3073
|
+
extras
|
|
3074
|
+
}) {
|
|
3075
|
+
const { data: detail, isPending, isError } = (0, import_genre_tree18.useFetchGenreDetail)(genreUuid, scope, getBackendBaseUrl);
|
|
3076
|
+
const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: [] });
|
|
3077
|
+
const view = (0, import_react16.useMemo)(() => {
|
|
3078
|
+
if (!detail) return null;
|
|
3079
|
+
const rootColor = (0, import_genre_tree_view5.getGenreTreeColor)(detail.root.wikidataId);
|
|
3080
|
+
const relative = (genre, parentId) => ({
|
|
3081
|
+
node: toNode(genre, parentId),
|
|
3082
|
+
fill: (0, import_genre_tree_view5.tintSurface)(rootColor),
|
|
3083
|
+
textColor: import_genre_tree_view5.TEXT_COLOR
|
|
3084
|
+
});
|
|
3085
|
+
const ancestors = [...detail.ascendants].sort((a, b) => b.degree - a.degree);
|
|
3086
|
+
return {
|
|
3087
|
+
node: {
|
|
3088
|
+
id: detail.uuid,
|
|
3089
|
+
parentId: detail.parent?.uuid ?? null,
|
|
3090
|
+
name: detail.name,
|
|
3091
|
+
itemCount: detail.tracksCount,
|
|
3092
|
+
wikidataId: detail.wikidataId
|
|
3093
|
+
},
|
|
3094
|
+
fill: rootColor,
|
|
3095
|
+
parentNode: detail.parent ? relative(detail.parent, null) : null,
|
|
3096
|
+
childNodes: detail.children.map((child) => relative(child, detail.uuid)),
|
|
3097
|
+
ancestorNodes: ancestors.map(
|
|
3098
|
+
({ ascendant }, index) => relative(ascendant, index === 0 ? null : ancestors[index - 1].ascendant.uuid)
|
|
3099
|
+
)
|
|
3100
|
+
};
|
|
3101
|
+
}, [detail]);
|
|
3102
|
+
if (isError) {
|
|
3103
|
+
return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
|
|
3104
|
+
}
|
|
3105
|
+
if (isPending || !detail || !view) {
|
|
3106
|
+
return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
|
|
3107
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui17.Skeleton, { className: "h-6 w-2/3" }),
|
|
3108
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui17.Skeleton, { className: "h-4 w-full" }),
|
|
3109
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui17.Skeleton, { className: "h-4 w-3/4" })
|
|
3110
|
+
] });
|
|
3111
|
+
}
|
|
3112
|
+
const renderBody = (node, sections) => ({
|
|
3113
|
+
primary: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
3114
|
+
GenreInfoPanelBody,
|
|
3115
|
+
{
|
|
3116
|
+
toolbar: sections.toolbar,
|
|
3117
|
+
family: sections.family,
|
|
3118
|
+
genreUuid: detail.uuid,
|
|
3119
|
+
genrePlaylist: { ...detail.criteriaPlaylist, tracksCount: detail.tracksCount },
|
|
3120
|
+
wikidataId: detail.wikidataId,
|
|
3121
|
+
scope,
|
|
3122
|
+
getBackendBaseUrl,
|
|
3123
|
+
playback,
|
|
3124
|
+
band: { node, onZoomIn, shareUrl, shareTarget: shareUrl ? shareTarget : void 0 },
|
|
3125
|
+
extras
|
|
3126
|
+
}
|
|
3127
|
+
),
|
|
3128
|
+
aside: null
|
|
3129
|
+
});
|
|
3130
|
+
return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
3131
|
+
import_genre_tree_view5.InfoPanel,
|
|
3132
|
+
{
|
|
3133
|
+
node: view.node,
|
|
3134
|
+
fill: view.fill,
|
|
3135
|
+
textColor: import_genre_tree_view5.ACCENT_TEXT_COLOR,
|
|
3136
|
+
parentNode: view.parentNode,
|
|
3137
|
+
childNodes: view.childNodes,
|
|
3138
|
+
ancestorNodes: view.ancestorNodes,
|
|
3139
|
+
side: "left",
|
|
3140
|
+
onClose,
|
|
3141
|
+
onSelectNode: (nodeId) => onSelectGenre(nodeId),
|
|
3142
|
+
itemCountLabel: ITEM_COUNT_LABEL,
|
|
3143
|
+
renderInfoPanelBody: renderBody,
|
|
3144
|
+
expanded,
|
|
3145
|
+
onExpandedChange,
|
|
3146
|
+
collapsed,
|
|
3147
|
+
hideExpandButton: onExpandedChange === void 0
|
|
3148
|
+
}
|
|
3149
|
+
);
|
|
3150
|
+
}
|
|
3151
|
+
|
|
3152
|
+
// src/genre-tree/index.ts
|
|
3153
|
+
var import_genre_tree_view6 = require("@behindthemusictree/genre-tree-view");
|
|
2994
3154
|
|
|
2995
3155
|
// src/genre-tree/track-list-sidebar/TrackListSidebar.tsx
|
|
2996
|
-
var
|
|
3156
|
+
var import_react17 = require("react");
|
|
2997
3157
|
var import_lucide_react16 = require("lucide-react");
|
|
2998
|
-
var import_genre_tree18 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2999
3158
|
var import_genre_tree19 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3159
|
+
var import_genre_tree20 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3000
3160
|
var import_track_noun7 = require("@behindthemusictree/app-kit/track-noun");
|
|
3001
|
-
var
|
|
3002
|
-
var useBrowserLayoutEffect = typeof window === "undefined" ?
|
|
3161
|
+
var import_jsx_runtime41 = require("react/jsx-runtime");
|
|
3162
|
+
var useBrowserLayoutEffect = typeof window === "undefined" ? import_react17.useEffect : import_react17.useLayoutEffect;
|
|
3003
3163
|
function TrackListSidebar({
|
|
3004
3164
|
className: className2 = "",
|
|
3005
3165
|
renderDuration,
|
|
@@ -3010,18 +3170,18 @@ function TrackListSidebar({
|
|
|
3010
3170
|
onMinimize,
|
|
3011
3171
|
renderArtistLink
|
|
3012
3172
|
}) {
|
|
3013
|
-
const { trackList, loadMore, autoAdvanceScrollRequest } = (0,
|
|
3173
|
+
const { trackList, loadMore, autoAdvanceScrollRequest } = (0, import_genre_tree19.useTrackList)();
|
|
3014
3174
|
const trackNoun = (0, import_track_noun7.useTrackNoun)();
|
|
3015
|
-
const trackListRef = (0,
|
|
3016
|
-
const previousOriginRef = (0,
|
|
3017
|
-
const handledAutoAdvanceRequestIdRef = (0,
|
|
3018
|
-
const shouldFollowPlaybackRef = (0,
|
|
3019
|
-
const programmaticScrollTopRef = (0,
|
|
3020
|
-
const userScrollIntentRef = (0,
|
|
3021
|
-
const userScrollIntentTimerRef = (0,
|
|
3022
|
-
const loadMoreSentinelRef = (0,
|
|
3175
|
+
const trackListRef = (0, import_react17.useRef)(null);
|
|
3176
|
+
const previousOriginRef = (0, import_react17.useRef)(trackList?.origin ?? null);
|
|
3177
|
+
const handledAutoAdvanceRequestIdRef = (0, import_react17.useRef)(null);
|
|
3178
|
+
const shouldFollowPlaybackRef = (0, import_react17.useRef)(true);
|
|
3179
|
+
const programmaticScrollTopRef = (0, import_react17.useRef)(null);
|
|
3180
|
+
const userScrollIntentRef = (0, import_react17.useRef)(false);
|
|
3181
|
+
const userScrollIntentTimerRef = (0, import_react17.useRef)(null);
|
|
3182
|
+
const loadMoreSentinelRef = (0, import_react17.useRef)(null);
|
|
3023
3183
|
const hasMore = !!trackList && trackList.nextPage !== null;
|
|
3024
|
-
(0,
|
|
3184
|
+
(0, import_react17.useEffect)(() => {
|
|
3025
3185
|
const sentinel = loadMoreSentinelRef.current;
|
|
3026
3186
|
if (!hasMore || !sentinel) return;
|
|
3027
3187
|
const observer = new IntersectionObserver((entries) => {
|
|
@@ -3041,11 +3201,11 @@ function TrackListSidebar({
|
|
|
3041
3201
|
programmaticScrollTopRef.current = null;
|
|
3042
3202
|
if (trackListRef.current) trackListRef.current.scrollTop = 0;
|
|
3043
3203
|
}, [trackList?.origin]);
|
|
3044
|
-
(0,
|
|
3204
|
+
(0, import_react17.useEffect)(() => {
|
|
3045
3205
|
const list = trackListRef.current;
|
|
3046
3206
|
if (!isCollapsed && list?.scrollTop === 0) shouldFollowPlaybackRef.current = true;
|
|
3047
3207
|
}, [isCollapsed]);
|
|
3048
|
-
(0,
|
|
3208
|
+
(0, import_react17.useEffect)(
|
|
3049
3209
|
() => () => {
|
|
3050
3210
|
if (userScrollIntentTimerRef.current !== null) clearTimeout(userScrollIntentTimerRef.current);
|
|
3051
3211
|
},
|
|
@@ -3099,25 +3259,25 @@ function TrackListSidebar({
|
|
|
3099
3259
|
}
|
|
3100
3260
|
};
|
|
3101
3261
|
const positionClasses = layout === "inline" ? "relative w-full flex h-full flex-col" : "fixed right-0 w-[50rem] flex flex-col";
|
|
3102
|
-
return trackList ? /* @__PURE__ */ (0,
|
|
3262
|
+
return trackList ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
|
|
3103
3263
|
"div",
|
|
3104
3264
|
{
|
|
3105
3265
|
"data-theme": "dark",
|
|
3106
3266
|
className: `track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${className2}`,
|
|
3107
3267
|
style: layout === "fixed" ? { top: "63.5px", maxHeight: "calc(100vh - 63.5px - 79px)" } : void 0,
|
|
3108
3268
|
children: [
|
|
3109
|
-
/* @__PURE__ */ (0,
|
|
3110
|
-
/* @__PURE__ */ (0,
|
|
3111
|
-
/* @__PURE__ */ (0,
|
|
3112
|
-
/* @__PURE__ */ (0,
|
|
3113
|
-
/* @__PURE__ */ (0,
|
|
3269
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
|
|
3270
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
|
|
3271
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "origin flex text-xl", children: [
|
|
3272
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
|
|
3273
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.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_runtime41.jsx)("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
|
|
3114
3274
|
] }),
|
|
3115
|
-
/* @__PURE__ */ (0,
|
|
3116
|
-
trackList.origin.type ===
|
|
3275
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
|
|
3276
|
+
trackList.origin.type === import_genre_tree20.TrackListOriginType.GENRE_PLAYLIST ? "Genre \u2022 " : trackList.origin.type === import_genre_tree20.TrackListOriginType.ARTIST ? "Artist \u2022 " : `${trackNoun.singular} playlist \u2022 `,
|
|
3117
3277
|
`${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`
|
|
3118
3278
|
] })
|
|
3119
3279
|
] }),
|
|
3120
|
-
(isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0,
|
|
3280
|
+
(isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
3121
3281
|
"button",
|
|
3122
3282
|
{
|
|
3123
3283
|
type: "button",
|
|
@@ -3126,11 +3286,11 @@ function TrackListSidebar({
|
|
|
3126
3286
|
"aria-expanded": !isCollapsed,
|
|
3127
3287
|
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"}`,
|
|
3128
3288
|
onClick: isCollapsed ? onExpand : onMinimize,
|
|
3129
|
-
children: /* @__PURE__ */ (0,
|
|
3289
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_lucide_react16.ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
|
|
3130
3290
|
}
|
|
3131
3291
|
)
|
|
3132
3292
|
] }),
|
|
3133
|
-
!isCollapsed && /* @__PURE__ */ (0,
|
|
3293
|
+
!isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
|
|
3134
3294
|
"ul",
|
|
3135
3295
|
{
|
|
3136
3296
|
ref: trackListRef,
|
|
@@ -3140,7 +3300,7 @@ function TrackListSidebar({
|
|
|
3140
3300
|
onPointerDown: markUserScrollIntent,
|
|
3141
3301
|
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",
|
|
3142
3302
|
children: [
|
|
3143
|
-
trackList.tracks.map((track, index) => /* @__PURE__ */ (0,
|
|
3303
|
+
trackList.tracks.map((track, index) => /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
3144
3304
|
TrackItem,
|
|
3145
3305
|
{
|
|
3146
3306
|
track,
|
|
@@ -3150,7 +3310,7 @@ function TrackListSidebar({
|
|
|
3150
3310
|
renderArtistLink
|
|
3151
3311
|
}
|
|
3152
3312
|
) }, track.uuid)),
|
|
3153
|
-
hasMore && /* @__PURE__ */ (0,
|
|
3313
|
+
hasMore && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
|
|
3154
3314
|
]
|
|
3155
3315
|
}
|
|
3156
3316
|
)
|
|
@@ -3160,16 +3320,16 @@ function TrackListSidebar({
|
|
|
3160
3320
|
}
|
|
3161
3321
|
|
|
3162
3322
|
// src/genre-tree/PlayerPanel.tsx
|
|
3163
|
-
var
|
|
3323
|
+
var import_react18 = require("react");
|
|
3164
3324
|
var import_tailwind_merge2 = require("tailwind-merge");
|
|
3165
3325
|
var import_player12 = require("@behindthemusictree/app-kit/player");
|
|
3166
|
-
var
|
|
3326
|
+
var import_genre_tree22 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3167
3327
|
|
|
3168
3328
|
// src/genre-tree/PlayerControls.tsx
|
|
3169
3329
|
var import_lucide_react17 = require("lucide-react");
|
|
3170
|
-
var
|
|
3330
|
+
var import_genre_tree21 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3171
3331
|
var import_player11 = require("@behindthemusictree/app-kit/player");
|
|
3172
|
-
var
|
|
3332
|
+
var import_jsx_runtime42 = require("react/jsx-runtime");
|
|
3173
3333
|
function PlayerControls() {
|
|
3174
3334
|
const {
|
|
3175
3335
|
duration,
|
|
@@ -3181,22 +3341,22 @@ function PlayerControls() {
|
|
|
3181
3341
|
setVolume,
|
|
3182
3342
|
volume
|
|
3183
3343
|
} = (0, import_player11.usePlayer)();
|
|
3184
|
-
const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } = (0,
|
|
3344
|
+
const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } = (0, import_genre_tree21.useTrackList)();
|
|
3185
3345
|
const currentTime = (0, import_player11.useCurrentTime)();
|
|
3186
3346
|
const isBusy = isLoading || playState === import_player11.PlayStates.LOADING;
|
|
3187
3347
|
const isReady = !!playerTrackObject?.isReady && !isBusy && !playerTrackObject.loadError;
|
|
3188
3348
|
const safeDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;
|
|
3189
3349
|
const safeCurrentTime = Number.isFinite(currentTime) ? Math.max(0, Math.min(currentTime, safeDuration)) : 0;
|
|
3190
3350
|
const isPlaying = playState === import_player11.PlayStates.PLAYING;
|
|
3191
|
-
return /* @__PURE__ */ (0,
|
|
3351
|
+
return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
|
|
3192
3352
|
"section",
|
|
3193
3353
|
{
|
|
3194
3354
|
"aria-label": "Player controls",
|
|
3195
3355
|
className: "player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200",
|
|
3196
3356
|
children: [
|
|
3197
|
-
/* @__PURE__ */ (0,
|
|
3198
|
-
/* @__PURE__ */ (0,
|
|
3199
|
-
/* @__PURE__ */ (0,
|
|
3357
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
|
|
3358
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { "aria-label": "Elapsed time", children: (0, import_genre_tree21.formatTime)(safeCurrentTime) }),
|
|
3359
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
3200
3360
|
"input",
|
|
3201
3361
|
{
|
|
3202
3362
|
"aria-label": "Seek",
|
|
@@ -3210,11 +3370,11 @@ function PlayerControls() {
|
|
|
3210
3370
|
onChange: (event) => seekTo(Number(event.currentTarget.value))
|
|
3211
3371
|
}
|
|
3212
3372
|
),
|
|
3213
|
-
/* @__PURE__ */ (0,
|
|
3373
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { "aria-label": "Duration", children: (0, import_genre_tree21.formatTime)(safeDuration) })
|
|
3214
3374
|
] }),
|
|
3215
|
-
/* @__PURE__ */ (0,
|
|
3216
|
-
/* @__PURE__ */ (0,
|
|
3217
|
-
/* @__PURE__ */ (0,
|
|
3375
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
|
|
3376
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
|
|
3377
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
3218
3378
|
"button",
|
|
3219
3379
|
{
|
|
3220
3380
|
type: "button",
|
|
@@ -3223,10 +3383,10 @@ function PlayerControls() {
|
|
|
3223
3383
|
disabled: !isReady || !hasPreviousTrack,
|
|
3224
3384
|
onClick: playPreviousTrack,
|
|
3225
3385
|
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",
|
|
3226
|
-
children: /* @__PURE__ */ (0,
|
|
3386
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react17.SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
3227
3387
|
}
|
|
3228
3388
|
),
|
|
3229
|
-
/* @__PURE__ */ (0,
|
|
3389
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
3230
3390
|
"button",
|
|
3231
3391
|
{
|
|
3232
3392
|
type: "button",
|
|
@@ -3235,10 +3395,10 @@ function PlayerControls() {
|
|
|
3235
3395
|
disabled: !isReady,
|
|
3236
3396
|
onClick: handlePlayPauseAction,
|
|
3237
3397
|
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",
|
|
3238
|
-
children: isPlaying ? /* @__PURE__ */ (0,
|
|
3398
|
+
children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react17.Pause, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react17.Play, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
|
|
3239
3399
|
}
|
|
3240
3400
|
),
|
|
3241
|
-
/* @__PURE__ */ (0,
|
|
3401
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
3242
3402
|
"button",
|
|
3243
3403
|
{
|
|
3244
3404
|
type: "button",
|
|
@@ -3247,14 +3407,14 @@ function PlayerControls() {
|
|
|
3247
3407
|
disabled: !isReady || !hasNextTrack,
|
|
3248
3408
|
onClick: playNextTrack,
|
|
3249
3409
|
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",
|
|
3250
|
-
children: /* @__PURE__ */ (0,
|
|
3410
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react17.SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
3251
3411
|
}
|
|
3252
3412
|
)
|
|
3253
3413
|
] }),
|
|
3254
|
-
/* @__PURE__ */ (0,
|
|
3255
|
-
volume === 0 ? /* @__PURE__ */ (0,
|
|
3256
|
-
/* @__PURE__ */ (0,
|
|
3257
|
-
/* @__PURE__ */ (0,
|
|
3414
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.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: [
|
|
3415
|
+
volume === 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react17.VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react17.Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
|
|
3416
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "sr-only", children: "Volume" }),
|
|
3417
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
3258
3418
|
"input",
|
|
3259
3419
|
{
|
|
3260
3420
|
"aria-label": "Volume",
|
|
@@ -3276,7 +3436,7 @@ function PlayerControls() {
|
|
|
3276
3436
|
}
|
|
3277
3437
|
|
|
3278
3438
|
// src/genre-tree/PlayerPanel.tsx
|
|
3279
|
-
var
|
|
3439
|
+
var import_jsx_runtime43 = require("react/jsx-runtime");
|
|
3280
3440
|
var PANEL_ID = "player-panel";
|
|
3281
3441
|
var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
|
|
3282
3442
|
function PlayerPanel({
|
|
@@ -3288,13 +3448,13 @@ function PlayerPanel({
|
|
|
3288
3448
|
renderArtistLink
|
|
3289
3449
|
}) {
|
|
3290
3450
|
const { playerTrackObject } = (0, import_player12.usePlayer)();
|
|
3291
|
-
const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0,
|
|
3292
|
-
const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = (0,
|
|
3451
|
+
const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0, import_genre_tree22.useTrackListSidebarVisibility)();
|
|
3452
|
+
const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = (0, import_react18.useState)("");
|
|
3293
3453
|
const loadedTrackTitle = playerTrackObject?.track.title;
|
|
3294
|
-
(0,
|
|
3454
|
+
(0, import_react18.useEffect)(() => {
|
|
3295
3455
|
if (loadedTrackTitle) setLastLoadedTrackTitle(loadedTrackTitle);
|
|
3296
3456
|
}, [loadedTrackTitle]);
|
|
3297
|
-
const [uncontrolledExtended, setUncontrolledExtended] = (0,
|
|
3457
|
+
const [uncontrolledExtended, setUncontrolledExtended] = (0, import_react18.useState)(false);
|
|
3298
3458
|
const isExtendRequested = expanded ?? uncontrolledExtended;
|
|
3299
3459
|
const setIsExtendRequested = (next) => {
|
|
3300
3460
|
setUncontrolledExtended(next);
|
|
@@ -3315,7 +3475,7 @@ function PlayerPanel({
|
|
|
3315
3475
|
onClick: () => setIsExtendRequested(true),
|
|
3316
3476
|
expanded: false
|
|
3317
3477
|
} : null;
|
|
3318
|
-
return /* @__PURE__ */ (0,
|
|
3478
|
+
return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(
|
|
3319
3479
|
"div",
|
|
3320
3480
|
{
|
|
3321
3481
|
id: PANEL_ID,
|
|
@@ -3328,7 +3488,7 @@ function PlayerPanel({
|
|
|
3328
3488
|
),
|
|
3329
3489
|
style: isExtended ? { width: extendedWidth } : void 0,
|
|
3330
3490
|
children: [
|
|
3331
|
-
/* @__PURE__ */ (0,
|
|
3491
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
3332
3492
|
PanelEdgeTab,
|
|
3333
3493
|
{
|
|
3334
3494
|
side: "right",
|
|
@@ -3338,7 +3498,7 @@ function PlayerPanel({
|
|
|
3338
3498
|
className: "absolute right-full top-1/2 -translate-y-1/2"
|
|
3339
3499
|
}
|
|
3340
3500
|
),
|
|
3341
|
-
/* @__PURE__ */ (0,
|
|
3501
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(
|
|
3342
3502
|
"div",
|
|
3343
3503
|
{
|
|
3344
3504
|
className: (0, import_tailwind_merge2.twMerge)(
|
|
@@ -3346,15 +3506,15 @@ function PlayerPanel({
|
|
|
3346
3506
|
isExtended ? "flex-row" : "flex-col"
|
|
3347
3507
|
),
|
|
3348
3508
|
children: [
|
|
3349
|
-
/* @__PURE__ */ (0,
|
|
3350
|
-
/* @__PURE__ */ (0,
|
|
3351
|
-
/* @__PURE__ */ (0,
|
|
3352
|
-
/* @__PURE__ */ (0,
|
|
3353
|
-
playerTrackObject?.loadError && /* @__PURE__ */ (0,
|
|
3509
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
|
|
3510
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
|
|
3511
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "relative w-full shrink-0", children: [
|
|
3512
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_player12.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
|
|
3513
|
+
playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: playerTrackObject.loadError })
|
|
3354
3514
|
] }),
|
|
3355
|
-
/* @__PURE__ */ (0,
|
|
3515
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)(PlayerControls, {})
|
|
3356
3516
|
] }),
|
|
3357
|
-
/* @__PURE__ */ (0,
|
|
3517
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
3358
3518
|
TrackListSidebar,
|
|
3359
3519
|
{
|
|
3360
3520
|
layout: "inline",
|
|
@@ -3379,33 +3539,33 @@ function PlayerPanel({
|
|
|
3379
3539
|
// src/genre-tree/Rating.tsx
|
|
3380
3540
|
var import_uuid = require("uuid");
|
|
3381
3541
|
var import_ai = require("react-icons/ai");
|
|
3382
|
-
var
|
|
3383
|
-
var
|
|
3542
|
+
var import_genre_tree23 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3543
|
+
var import_jsx_runtime44 = require("react/jsx-runtime");
|
|
3384
3544
|
function Rating({ rating, handleChange }) {
|
|
3385
3545
|
const instanceId = (0, import_uuid.v4)();
|
|
3386
|
-
return /* @__PURE__ */ (0,
|
|
3387
|
-
/* @__PURE__ */ (0,
|
|
3546
|
+
return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
|
|
3547
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
3388
3548
|
"input",
|
|
3389
3549
|
{
|
|
3390
3550
|
id: `star-${instanceId}-${i}`,
|
|
3391
3551
|
className: "w-6 h-6 opacity-0",
|
|
3392
3552
|
type: "radio",
|
|
3393
3553
|
name: "rating",
|
|
3394
|
-
value: i === 0 ?
|
|
3554
|
+
value: i === 0 ? import_genre_tree23.FORM_RATING_NULL_VALUE : i,
|
|
3395
3555
|
checked: rating === (i === 0 ? null : i),
|
|
3396
3556
|
onChange: handleChange
|
|
3397
3557
|
}
|
|
3398
3558
|
),
|
|
3399
|
-
/* @__PURE__ */ (0,
|
|
3400
|
-
/* @__PURE__ */ (0,
|
|
3559
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "flex", children: [
|
|
3560
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
3401
3561
|
import_ai.AiOutlineStar,
|
|
3402
3562
|
{
|
|
3403
|
-
className: `w-7 h-7 absolute top-0 left-0 ${rating ===
|
|
3563
|
+
className: `w-7 h-7 absolute top-0 left-0 ${rating === import_genre_tree23.FORM_RATING_NULL_VALUE ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
|
|
3404
3564
|
}
|
|
3405
3565
|
),
|
|
3406
|
-
/* @__PURE__ */ (0,
|
|
3566
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { className: "flex-grow w-8" }),
|
|
3407
3567
|
" "
|
|
3408
|
-
] }) : /* @__PURE__ */ (0,
|
|
3568
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
3409
3569
|
import_ai.AiFillStar,
|
|
3410
3570
|
{
|
|
3411
3571
|
className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
|
|
@@ -3419,6 +3579,9 @@ function Rating({ rating, handleChange }) {
|
|
|
3419
3579
|
AuthErrorPopup,
|
|
3420
3580
|
AuthPopup,
|
|
3421
3581
|
BasePopup,
|
|
3582
|
+
GenreDetailPanel,
|
|
3583
|
+
GenreInfoPanelBody,
|
|
3584
|
+
GenreNodeActionBand,
|
|
3422
3585
|
GenrePlaylistTreePerRoot,
|
|
3423
3586
|
GenreSearch,
|
|
3424
3587
|
GenreTreeOutlineSkeleton,
|
|
@@ -3438,6 +3601,8 @@ function Rating({ rating, handleChange }) {
|
|
|
3438
3601
|
TrackItem,
|
|
3439
3602
|
TrackListSidebar,
|
|
3440
3603
|
TrackPositionPlayPause,
|
|
3441
|
-
TrackUploadPopup
|
|
3604
|
+
TrackUploadPopup,
|
|
3605
|
+
useGenrePlaylistPlayback,
|
|
3606
|
+
useLeftDetailPanelLayout
|
|
3442
3607
|
});
|
|
3443
3608
|
//# sourceMappingURL=index.cjs.map
|