@behindthemusictree/app-kit-ui 34.0.0 → 35.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-A7OWCEIK.js → chunk-YC3WBUVO.js} +643 -605
- package/dist/chunk-YC3WBUVO.js.map +1 -0
- package/dist/genre-tree/index.cjs +744 -707
- package/dist/genre-tree/index.cjs.map +1 -1
- package/dist/genre-tree/index.d.ts +51 -39
- package/dist/genre-tree/index.js +1 -1
- package/dist/index.cjs +739 -702
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +3 -3
- package/dist/chunk-A7OWCEIK.js.map +0 -1
|
@@ -5,14 +5,12 @@ import {
|
|
|
5
5
|
} from "./chunk-DKQQLLMY.js";
|
|
6
6
|
|
|
7
7
|
// src/genre-tree/GenreTreeView.tsx
|
|
8
|
-
import { useCallback as useCallback6, useDeferredValue, useEffect as useEffect5, useMemo as
|
|
8
|
+
import { useCallback as useCallback6, useDeferredValue as useDeferredValue2, useEffect as useEffect5, useMemo as useMemo8, useRef as useRef3, useState as useState9 } from "react";
|
|
9
9
|
import { useIsMutating } from "@tanstack/react-query";
|
|
10
10
|
import { Plus as Plus2 } from "lucide-react";
|
|
11
|
-
import { Maximize2, Minimize2 } from "lucide-react";
|
|
12
|
-
import { IconTextButton, IconButton, Button, SegmentedControl as SegmentedControl2, DetailPanel, useObscuredInset } from "@behindthemusictree/ui";
|
|
11
|
+
import { Maximize2 as Maximize22, Minimize2 as Minimize22 } from "lucide-react";
|
|
12
|
+
import { IconTextButton, IconButton as IconButton2, Button, SegmentedControl as SegmentedControl2, DetailPanel as DetailPanel2, useObscuredInset } from "@behindthemusictree/ui";
|
|
13
13
|
import {
|
|
14
|
-
GenreNodeAncestors,
|
|
15
|
-
GenreNodeFamily,
|
|
16
14
|
GenreTreeWheelSkeleton,
|
|
17
15
|
GenreTreeViewSkeleton,
|
|
18
16
|
useGenreNodeRelations
|
|
@@ -465,6 +463,9 @@ function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
|
|
|
465
463
|
] });
|
|
466
464
|
}
|
|
467
465
|
|
|
466
|
+
// src/genre-tree/GenreTreeSearch.tsx
|
|
467
|
+
import { useDeferredValue, useState as useState3 } from "react";
|
|
468
|
+
|
|
468
469
|
// src/genre-tree/GenreSearch.tsx
|
|
469
470
|
import { ArrowLeft } from "lucide-react";
|
|
470
471
|
import { SEARCH_LEADING_BUTTON_CLASS } from "@behindthemusictree/ui";
|
|
@@ -670,6 +671,66 @@ function GenreSearch({
|
|
|
670
671
|
) });
|
|
671
672
|
}
|
|
672
673
|
|
|
674
|
+
// src/genre-tree/GenreTreeSearch.tsx
|
|
675
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
676
|
+
function GenreTreeSearch({
|
|
677
|
+
artistBaseUrl,
|
|
678
|
+
songBaseUrl,
|
|
679
|
+
onArtistSelect,
|
|
680
|
+
onSongSelect,
|
|
681
|
+
artistSearch,
|
|
682
|
+
songSearch,
|
|
683
|
+
SearchGroups,
|
|
684
|
+
onSelectGenre,
|
|
685
|
+
extraGroups = [],
|
|
686
|
+
onQueryChange,
|
|
687
|
+
onFocus,
|
|
688
|
+
...props
|
|
689
|
+
}) {
|
|
690
|
+
const [query, setQuery] = useState3("");
|
|
691
|
+
const [focused, setFocused] = useState3(false);
|
|
692
|
+
const deferredQuery = useDeferredValue(query);
|
|
693
|
+
const artistGroup = useArtistSearchGroup(artistBaseUrl, deferredQuery, onArtistSelect, artistSearch);
|
|
694
|
+
const songGroup = useSongSearchGroup(songBaseUrl, deferredQuery, onSongSelect, songSearch);
|
|
695
|
+
const render = (hostGroups) => /* @__PURE__ */ jsx9(
|
|
696
|
+
GenreSearch,
|
|
697
|
+
{
|
|
698
|
+
...props,
|
|
699
|
+
onQueryChange: (text) => {
|
|
700
|
+
setQuery(text);
|
|
701
|
+
onQueryChange?.(text);
|
|
702
|
+
},
|
|
703
|
+
onFocus: () => {
|
|
704
|
+
setFocused(true);
|
|
705
|
+
onFocus?.();
|
|
706
|
+
},
|
|
707
|
+
extraGroups: [...extraGroups, ...hostGroups, artistGroup, songGroup].filter((group) => group !== void 0)
|
|
708
|
+
}
|
|
709
|
+
);
|
|
710
|
+
return SearchGroups ? /* @__PURE__ */ jsx9(SearchGroups, { query: deferredQuery, focused, onSelectGenre, children: render }) : render([]);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// src/genre-tree/GenreDetailPanel.tsx
|
|
714
|
+
import { useMemo as useMemo6 } from "react";
|
|
715
|
+
import {
|
|
716
|
+
ACCENT_TEXT_COLOR,
|
|
717
|
+
GenreNodeAncestors,
|
|
718
|
+
GenreNodeFamily,
|
|
719
|
+
getGenreTreeColor as getGenreTreeColor2,
|
|
720
|
+
TEXT_COLOR,
|
|
721
|
+
tintSurface
|
|
722
|
+
} from "@behindthemusictree/genre-tree-view";
|
|
723
|
+
import { DetailPanel, IconButton, Skeleton as Skeleton3 } from "@behindthemusictree/ui";
|
|
724
|
+
import { Maximize2, Minimize2 } from "lucide-react";
|
|
725
|
+
import {
|
|
726
|
+
useFetchGenreDetail,
|
|
727
|
+
genreMusicCount as genreMusicCount2,
|
|
728
|
+
GenrePlaylistSummarySchema,
|
|
729
|
+
GenreCatalogueCountsSchema as GenreCatalogueCountsSchema3,
|
|
730
|
+
CriteriaMinimumSchema,
|
|
731
|
+
GenreUuidSchema
|
|
732
|
+
} from "@behindthemusictree/app-kit/genre-tree";
|
|
733
|
+
|
|
673
734
|
// src/genre-tree/GenreInfoPanelBody.tsx
|
|
674
735
|
import {
|
|
675
736
|
genreMusicCount,
|
|
@@ -687,18 +748,18 @@ import { Share2 as Share22 } from "lucide-react";
|
|
|
687
748
|
import { usePopup as usePopup3 } from "@behindthemusictree/app-kit/popup";
|
|
688
749
|
|
|
689
750
|
// src/genre-tree/SharePopup.tsx
|
|
690
|
-
import { useCallback as useCallback5, useEffect as useEffect2, useMemo as useMemo5, useState as
|
|
751
|
+
import { useCallback as useCallback5, useEffect as useEffect2, useMemo as useMemo5, useState as useState4 } from "react";
|
|
691
752
|
import { Link2, Share2 } from "lucide-react";
|
|
692
753
|
import { FaWhatsapp } from "react-icons/fa";
|
|
693
754
|
import { SiGmail } from "react-icons/si";
|
|
694
755
|
|
|
695
756
|
// src/genre-tree/ShareDestinationButton.tsx
|
|
696
|
-
import { Fragment as Fragment4, jsx as
|
|
757
|
+
import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
697
758
|
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";
|
|
698
759
|
function Contents({ icon, label }) {
|
|
699
760
|
return /* @__PURE__ */ jsxs6(Fragment4, { children: [
|
|
700
|
-
/* @__PURE__ */
|
|
701
|
-
/* @__PURE__ */
|
|
761
|
+
/* @__PURE__ */ jsx10("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 }),
|
|
762
|
+
/* @__PURE__ */ jsx10("span", { className: "max-w-full truncate", children: label })
|
|
702
763
|
] });
|
|
703
764
|
}
|
|
704
765
|
function ShareDestinationButton({
|
|
@@ -710,29 +771,29 @@ function ShareDestinationButton({
|
|
|
710
771
|
ariaLabel
|
|
711
772
|
}) {
|
|
712
773
|
if (href) {
|
|
713
|
-
return /* @__PURE__ */
|
|
774
|
+
return /* @__PURE__ */ jsx10("a", { href, target: "_blank", rel: "noopener noreferrer", "aria-label": ariaLabel, className, children: /* @__PURE__ */ jsx10(Contents, { icon, label }) });
|
|
714
775
|
}
|
|
715
|
-
return /* @__PURE__ */
|
|
776
|
+
return /* @__PURE__ */ jsx10("button", { type: "button", onClick, disabled, "aria-label": ariaLabel, className, children: /* @__PURE__ */ jsx10(Contents, { icon, label }) });
|
|
716
777
|
}
|
|
717
778
|
|
|
718
779
|
// src/genre-tree/SharePreviewCard.tsx
|
|
719
780
|
import { Music, Music2, UserRound } from "lucide-react";
|
|
720
|
-
import { jsx as
|
|
781
|
+
import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
721
782
|
function SharePreviewCard({ target, url }) {
|
|
722
783
|
const Icon = { genre: Music2, artist: UserRound, song: Music }[target.kind];
|
|
723
784
|
const typeLabel = { genre: "Genre", artist: "Artist", song: "Song" }[target.kind];
|
|
724
|
-
return /* @__PURE__ */
|
|
725
|
-
/* @__PURE__ */
|
|
785
|
+
return /* @__PURE__ */ jsx11("div", { className: "rounded-3xl bg-[var(--color-surface-muted)] p-4", children: /* @__PURE__ */ jsxs7("div", { className: "flex min-h-32 overflow-hidden rounded-2xl bg-[var(--color-surface)] shadow-lg", children: [
|
|
786
|
+
/* @__PURE__ */ jsx11("div", { className: "flex w-28 shrink-0 items-center justify-center bg-[var(--color-primary)]/15 text-[var(--color-primary)]", children: /* @__PURE__ */ jsx11(Icon, { "aria-hidden": "true", className: "h-10 w-10" }) }),
|
|
726
787
|
/* @__PURE__ */ jsxs7("div", { className: "flex min-w-0 flex-1 flex-col justify-center gap-1 p-4", children: [
|
|
727
|
-
/* @__PURE__ */
|
|
728
|
-
/* @__PURE__ */
|
|
729
|
-
/* @__PURE__ */
|
|
788
|
+
/* @__PURE__ */ jsx11("span", { className: "text-xs font-medium uppercase tracking-wide text-[var(--color-text-muted)]", children: typeLabel }),
|
|
789
|
+
/* @__PURE__ */ jsx11("span", { className: "truncate text-base font-semibold text-[var(--color-text)]", title: target.name, children: target.name }),
|
|
790
|
+
/* @__PURE__ */ jsx11("span", { className: "truncate text-sm text-[var(--color-primary)]", title: url, children: url })
|
|
730
791
|
] })
|
|
731
792
|
] }) });
|
|
732
793
|
}
|
|
733
794
|
|
|
734
795
|
// src/genre-tree/SharePopup.tsx
|
|
735
|
-
import { Fragment as Fragment5, jsx as
|
|
796
|
+
import { Fragment as Fragment5, jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
736
797
|
function resolveAbsoluteUrl(url) {
|
|
737
798
|
try {
|
|
738
799
|
const parsed = new URL(url, window.location.href);
|
|
@@ -765,11 +826,11 @@ async function copyText(text) {
|
|
|
765
826
|
}
|
|
766
827
|
}
|
|
767
828
|
function SharePopup({ target, url, onClose }) {
|
|
768
|
-
const [absoluteUrl, setAbsoluteUrl] =
|
|
769
|
-
const [copyStatus, setCopyStatus] =
|
|
770
|
-
const [canUseNativeShare, setCanUseNativeShare] =
|
|
771
|
-
const [shareError, setShareError] =
|
|
772
|
-
const [toastId, setToastId] =
|
|
829
|
+
const [absoluteUrl, setAbsoluteUrl] = useState4(url);
|
|
830
|
+
const [copyStatus, setCopyStatus] = useState4("idle");
|
|
831
|
+
const [canUseNativeShare, setCanUseNativeShare] = useState4(false);
|
|
832
|
+
const [shareError, setShareError] = useState4(null);
|
|
833
|
+
const [toastId, setToastId] = useState4(0);
|
|
773
834
|
useEffect2(() => {
|
|
774
835
|
setAbsoluteUrl(resolveAbsoluteUrl(url) ?? "");
|
|
775
836
|
setCopyStatus("idle");
|
|
@@ -808,7 +869,7 @@ function SharePopup({ target, url, onClose }) {
|
|
|
808
869
|
}
|
|
809
870
|
};
|
|
810
871
|
return /* @__PURE__ */ jsxs8(Fragment5, { children: [
|
|
811
|
-
/* @__PURE__ */
|
|
872
|
+
/* @__PURE__ */ jsx12(
|
|
812
873
|
BasePopup_default,
|
|
813
874
|
{
|
|
814
875
|
title: "Share",
|
|
@@ -824,64 +885,64 @@ function SharePopup({ target, url, onClose }) {
|
|
|
824
885
|
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)]",
|
|
825
886
|
contentClassName: "!rounded-none !bg-transparent !px-6 !pb-6 !pt-2",
|
|
826
887
|
children: /* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-4", children: [
|
|
827
|
-
/* @__PURE__ */
|
|
888
|
+
/* @__PURE__ */ jsx12(SharePreviewCard, { target, url: absoluteUrl || url }),
|
|
828
889
|
/* @__PURE__ */ jsxs8("div", { className: "grid grid-cols-3 gap-1 rounded-3xl bg-[var(--color-surface)] p-3 sm:grid-cols-4", children: [
|
|
829
|
-
/* @__PURE__ */
|
|
890
|
+
/* @__PURE__ */ jsx12(
|
|
830
891
|
ShareDestinationButton,
|
|
831
892
|
{
|
|
832
893
|
label: "Copy Link",
|
|
833
|
-
icon: /* @__PURE__ */
|
|
894
|
+
icon: /* @__PURE__ */ jsx12(Link2, { "aria-hidden": "true", className: "h-6 w-6" }),
|
|
834
895
|
onClick: () => void handleCopy(),
|
|
835
896
|
ariaLabel: "Copy link",
|
|
836
897
|
disabled: !absoluteUrl
|
|
837
898
|
}
|
|
838
899
|
),
|
|
839
|
-
/* @__PURE__ */
|
|
900
|
+
/* @__PURE__ */ jsx12(
|
|
840
901
|
ShareDestinationButton,
|
|
841
902
|
{
|
|
842
903
|
label: "WhatsApp",
|
|
843
|
-
icon: /* @__PURE__ */
|
|
904
|
+
icon: /* @__PURE__ */ jsx12(FaWhatsapp, { "aria-hidden": "true", className: "h-9 w-9 text-[#25D366]" }),
|
|
844
905
|
href: destinationUrls?.whatsapp,
|
|
845
906
|
ariaLabel: `Share ${target.name} via WhatsApp`,
|
|
846
907
|
disabled: !absoluteUrl
|
|
847
908
|
}
|
|
848
909
|
),
|
|
849
|
-
/* @__PURE__ */
|
|
910
|
+
/* @__PURE__ */ jsx12(
|
|
850
911
|
ShareDestinationButton,
|
|
851
912
|
{
|
|
852
913
|
label: "Gmail",
|
|
853
|
-
icon: /* @__PURE__ */
|
|
914
|
+
icon: /* @__PURE__ */ jsx12(SiGmail, { "aria-hidden": "true", className: "h-8 w-8 text-[#EA4335]" }),
|
|
854
915
|
href: destinationUrls?.gmail,
|
|
855
916
|
ariaLabel: `Email ${target.name} with Gmail`,
|
|
856
917
|
disabled: !absoluteUrl
|
|
857
918
|
}
|
|
858
919
|
),
|
|
859
|
-
canUseNativeShare && /* @__PURE__ */
|
|
920
|
+
canUseNativeShare && /* @__PURE__ */ jsx12(
|
|
860
921
|
ShareDestinationButton,
|
|
861
922
|
{
|
|
862
923
|
label: "More",
|
|
863
|
-
icon: /* @__PURE__ */
|
|
924
|
+
icon: /* @__PURE__ */ jsx12(Share2, { "aria-hidden": "true", className: "h-6 w-6" }),
|
|
864
925
|
onClick: () => void handleNativeShare(),
|
|
865
926
|
ariaLabel: "More sharing options",
|
|
866
927
|
disabled: !absoluteUrl
|
|
867
928
|
}
|
|
868
929
|
)
|
|
869
930
|
] }),
|
|
870
|
-
copyStatus === "failed" && /* @__PURE__ */
|
|
871
|
-
shareError && /* @__PURE__ */
|
|
931
|
+
copyStatus === "failed" && /* @__PURE__ */ jsx12("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: "Could not copy the link." }),
|
|
932
|
+
shareError && /* @__PURE__ */ jsx12("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: shareError })
|
|
872
933
|
] })
|
|
873
934
|
}
|
|
874
935
|
),
|
|
875
|
-
toastId > 0 && /* @__PURE__ */
|
|
936
|
+
toastId > 0 && /* @__PURE__ */ jsx12(Toast, { message: "Link copied", onClose: dismissToast }, toastId)
|
|
876
937
|
] });
|
|
877
938
|
}
|
|
878
939
|
|
|
879
940
|
// src/genre-tree/ShareActionButton.tsx
|
|
880
|
-
import { jsx as
|
|
941
|
+
import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
881
942
|
function ShareActionButton({ target, url }) {
|
|
882
943
|
const { showPopup, hidePopup } = usePopup3();
|
|
883
944
|
const openSharePopup = () => {
|
|
884
|
-
showPopup(/* @__PURE__ */
|
|
945
|
+
showPopup(/* @__PURE__ */ jsx13(SharePopup, { target, url, onClose: hidePopup }));
|
|
885
946
|
};
|
|
886
947
|
return /* @__PURE__ */ jsxs9(
|
|
887
948
|
"button",
|
|
@@ -892,7 +953,7 @@ function ShareActionButton({ target, url }) {
|
|
|
892
953
|
"aria-label": `Share ${target.name}`,
|
|
893
954
|
title: `Share ${target.name}`,
|
|
894
955
|
children: [
|
|
895
|
-
/* @__PURE__ */
|
|
956
|
+
/* @__PURE__ */ jsx13("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__ */ jsx13(Share22, { "aria-hidden": "true", className: "w-5 h-5" }) }),
|
|
896
957
|
"Share"
|
|
897
958
|
]
|
|
898
959
|
}
|
|
@@ -900,7 +961,7 @@ function ShareActionButton({ target, url }) {
|
|
|
900
961
|
}
|
|
901
962
|
|
|
902
963
|
// src/genre-tree/GenreActionBand.tsx
|
|
903
|
-
import { jsx as
|
|
964
|
+
import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
904
965
|
function GenreActionBand({
|
|
905
966
|
node,
|
|
906
967
|
onPlayPause,
|
|
@@ -922,7 +983,7 @@ function GenreActionBand({
|
|
|
922
983
|
items.push({
|
|
923
984
|
key: "play-pause",
|
|
924
985
|
label: isPlaying ? "Pause" : "Play",
|
|
925
|
-
icon: isPlaying ? /* @__PURE__ */
|
|
986
|
+
icon: isPlaying ? /* @__PURE__ */ jsx14(Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ jsx14(Play, { "aria-hidden": "true", className: "w-5 h-5" }),
|
|
926
987
|
onClick: onPlayPause
|
|
927
988
|
});
|
|
928
989
|
}
|
|
@@ -930,24 +991,24 @@ function GenreActionBand({
|
|
|
930
991
|
items.push({
|
|
931
992
|
key: "zoom-in",
|
|
932
993
|
label: "Focus",
|
|
933
|
-
icon: /* @__PURE__ */
|
|
994
|
+
icon: /* @__PURE__ */ jsx14(ZoomIn, { "aria-hidden": "true", className: "w-5 h-5" }),
|
|
934
995
|
onClick: onZoomIn
|
|
935
996
|
});
|
|
936
997
|
}
|
|
937
998
|
if (onAddChild) {
|
|
938
|
-
adminItems.push({ key: "add-child", label: "Add", icon: /* @__PURE__ */
|
|
999
|
+
adminItems.push({ key: "add-child", label: "Add", icon: /* @__PURE__ */ jsx14(Plus, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onAddChild });
|
|
939
1000
|
}
|
|
940
1001
|
if (onRename) {
|
|
941
|
-
adminItems.push({ key: "rename", label: "Rename", icon: /* @__PURE__ */
|
|
1002
|
+
adminItems.push({ key: "rename", label: "Rename", icon: /* @__PURE__ */ jsx14(Pencil, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onRename });
|
|
942
1003
|
}
|
|
943
1004
|
if (onChangeParent) {
|
|
944
|
-
adminItems.push({ key: "change-parent", label: "Parent", icon: /* @__PURE__ */
|
|
1005
|
+
adminItems.push({ key: "change-parent", label: "Parent", icon: /* @__PURE__ */ jsx14(MoveUpRight, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onChangeParent });
|
|
945
1006
|
}
|
|
946
1007
|
if (onToggleSide) {
|
|
947
1008
|
adminItems.push({
|
|
948
1009
|
key: "toggle-side",
|
|
949
1010
|
label: side === "pop" ? "Make core" : "Make pop",
|
|
950
|
-
icon: /* @__PURE__ */
|
|
1011
|
+
icon: /* @__PURE__ */ jsx14(ArrowLeftRight, { "aria-hidden": "true", className: "w-5 h-5" }),
|
|
951
1012
|
onClick: onToggleSide,
|
|
952
1013
|
disabled: !!toggleSideDisabledReason,
|
|
953
1014
|
title: toggleSideDisabledReason
|
|
@@ -972,7 +1033,7 @@ function GenreActionBand({
|
|
|
972
1033
|
onClick: item.onClick,
|
|
973
1034
|
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",
|
|
974
1035
|
children: [
|
|
975
|
-
/* @__PURE__ */
|
|
1036
|
+
/* @__PURE__ */ jsx14("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 }),
|
|
976
1037
|
item.label
|
|
977
1038
|
]
|
|
978
1039
|
},
|
|
@@ -981,7 +1042,7 @@ function GenreActionBand({
|
|
|
981
1042
|
return /* @__PURE__ */ jsxs10("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: [
|
|
982
1043
|
(items.length > 0 || shareTarget && shareUrl) && /* @__PURE__ */ jsxs10("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
|
|
983
1044
|
renderItems(items),
|
|
984
|
-
shareTarget && shareUrl && /* @__PURE__ */
|
|
1045
|
+
shareTarget && shareUrl && /* @__PURE__ */ jsx14(ShareActionButton, { target: shareTarget, url: shareUrl })
|
|
985
1046
|
] }),
|
|
986
1047
|
adminItems.length > 0 && /* @__PURE__ */ jsxs10(
|
|
987
1048
|
"section",
|
|
@@ -989,8 +1050,8 @@ function GenreActionBand({
|
|
|
989
1050
|
"aria-label": "Admin actions",
|
|
990
1051
|
className: `px-3.5 py-3 ${items.length > 0 ? "border-t border-[var(--color-border)]" : ""}`,
|
|
991
1052
|
children: [
|
|
992
|
-
/* @__PURE__ */
|
|
993
|
-
/* @__PURE__ */
|
|
1053
|
+
/* @__PURE__ */ jsx14("h3", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-[var(--color-text-muted)]", children: "Admin" }),
|
|
1054
|
+
/* @__PURE__ */ jsx14("div", { className: "flex flex-wrap gap-1", children: renderItems(adminItems) })
|
|
994
1055
|
]
|
|
995
1056
|
}
|
|
996
1057
|
)
|
|
@@ -1002,20 +1063,20 @@ import { PanelSection as PanelSection2, Skeleton as Skeleton2 } from "@behindthe
|
|
|
1002
1063
|
import { useFetchGenreOverview } from "@behindthemusictree/app-kit/genre-tree";
|
|
1003
1064
|
|
|
1004
1065
|
// src/genre-tree/ClampedText.tsx
|
|
1005
|
-
import { useLayoutEffect, useRef as useRef2, useState as
|
|
1006
|
-
import { Fragment as Fragment6, jsx as
|
|
1066
|
+
import { useLayoutEffect, useRef as useRef2, useState as useState5 } from "react";
|
|
1067
|
+
import { Fragment as Fragment6, jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1007
1068
|
function ClampedText({ children, className: className2 = "" }) {
|
|
1008
1069
|
const ref = useRef2(null);
|
|
1009
|
-
const [expanded, setExpanded] =
|
|
1010
|
-
const [overflows, setOverflows] =
|
|
1070
|
+
const [expanded, setExpanded] = useState5(false);
|
|
1071
|
+
const [overflows, setOverflows] = useState5(false);
|
|
1011
1072
|
useLayoutEffect(() => {
|
|
1012
1073
|
const element = ref.current;
|
|
1013
1074
|
if (!element || expanded) return;
|
|
1014
1075
|
setOverflows(element.scrollHeight > element.clientHeight);
|
|
1015
1076
|
}, [children, expanded]);
|
|
1016
1077
|
return /* @__PURE__ */ jsxs11(Fragment6, { children: [
|
|
1017
|
-
/* @__PURE__ */
|
|
1018
|
-
(overflows || expanded) && /* @__PURE__ */
|
|
1078
|
+
/* @__PURE__ */ jsx15("p", { ref, className: `${className2} ${expanded ? "" : "line-clamp-3"}`, children }),
|
|
1079
|
+
(overflows || expanded) && /* @__PURE__ */ jsx15(
|
|
1019
1080
|
"button",
|
|
1020
1081
|
{
|
|
1021
1082
|
type: "button",
|
|
@@ -1029,7 +1090,7 @@ function ClampedText({ children, className: className2 = "" }) {
|
|
|
1029
1090
|
}
|
|
1030
1091
|
|
|
1031
1092
|
// src/genre-tree/GenrePlaylistTracks.tsx
|
|
1032
|
-
import { useEffect as useEffect3, useState as
|
|
1093
|
+
import { useEffect as useEffect3, useState as useState6 } from "react";
|
|
1033
1094
|
import { useInfiniteQuery } from "@tanstack/react-query";
|
|
1034
1095
|
import { z } from "zod";
|
|
1035
1096
|
import { FaPlay, FaPause } from "react-icons/fa";
|
|
@@ -1050,15 +1111,15 @@ import { useTrackList as useTrackList2 } from "@behindthemusictree/app-kit/genre
|
|
|
1050
1111
|
|
|
1051
1112
|
// src/genre-tree/MusicBrainzRecordingLink.tsx
|
|
1052
1113
|
import { musicBrainzRecordingUrl } from "@behindthemusictree/app-kit/genre-tree";
|
|
1053
|
-
import { Fragment as Fragment7, jsx as
|
|
1114
|
+
import { Fragment as Fragment7, jsx as jsx16 } from "react/jsx-runtime";
|
|
1054
1115
|
function MusicBrainzRecordingLink({
|
|
1055
1116
|
song,
|
|
1056
1117
|
children,
|
|
1057
1118
|
className: className2 = ""
|
|
1058
1119
|
}) {
|
|
1059
1120
|
const url = musicBrainzRecordingUrl(song);
|
|
1060
|
-
if (!url) return /* @__PURE__ */
|
|
1061
|
-
return /* @__PURE__ */
|
|
1121
|
+
if (!url) return /* @__PURE__ */ jsx16(Fragment7, { children });
|
|
1122
|
+
return /* @__PURE__ */ jsx16(
|
|
1062
1123
|
"a",
|
|
1063
1124
|
{
|
|
1064
1125
|
href: url,
|
|
@@ -1075,7 +1136,7 @@ function MusicBrainzRecordingLink({
|
|
|
1075
1136
|
// src/genre-tree/GenrePlaylistTracks.tsx
|
|
1076
1137
|
import { ArtistNames } from "@behindthemusictree/app-kit/genre-tree";
|
|
1077
1138
|
import { capitalize, useTrackNoun } from "@behindthemusictree/app-kit/track-noun";
|
|
1078
|
-
import { jsx as
|
|
1139
|
+
import { jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1079
1140
|
var CATALOGUE_PAGE_SIZE = 100;
|
|
1080
1141
|
var SongPageSchema = PaginatedResponseSchema(SongDetailedSchema).extend({
|
|
1081
1142
|
overallTotal: z.number().nullable(),
|
|
@@ -1095,6 +1156,7 @@ var nextCursor = (next) => {
|
|
|
1095
1156
|
function GenrePlaylistTracks({
|
|
1096
1157
|
genreUuid,
|
|
1097
1158
|
genrePlaylist,
|
|
1159
|
+
genreName: selectedGenreName,
|
|
1098
1160
|
selection,
|
|
1099
1161
|
scope,
|
|
1100
1162
|
getBackendBaseUrl,
|
|
@@ -1110,13 +1172,17 @@ function GenrePlaylistTracks({
|
|
|
1110
1172
|
const { playerTrackObject, playState, handlePlayPauseAction } = usePlayer4();
|
|
1111
1173
|
const { showPopup } = usePopup4();
|
|
1112
1174
|
const trackNoun = useTrackNoun();
|
|
1113
|
-
const
|
|
1175
|
+
const uuid = genrePlaylist?.uuid;
|
|
1176
|
+
const genreName = selectedGenreName ?? genrePlaylist?.name ?? "Genre";
|
|
1114
1177
|
const { fetch } = useFetchWrapper(getBackendBaseUrl);
|
|
1115
1178
|
const playback = usePlaybackContext();
|
|
1116
1179
|
const { data, isPending, isError, refetch, hasNextPage, isFetchingNextPage, fetchNextPage } = useInfiniteQuery({
|
|
1117
|
-
queryKey: [...scope === "me" ? genrePlaylistQueryKeys.me.tracks(uuid) : artist ? [...libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid, selection), "artist", artist.uuid] : libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid, selection), ...playback.key ? [playback.key] : []],
|
|
1180
|
+
queryKey: [...scope === "me" ? uuid ? genrePlaylistQueryKeys.me.tracks(uuid) : ["missing-personal-genre-playlist", genreUuid] : artist ? [...libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid, selection), "artist", artist.uuid] : libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid, selection), ...playback.key ? [playback.key] : []],
|
|
1118
1181
|
queryFn: async ({ pageParam }) => {
|
|
1119
|
-
if (scope === "me")
|
|
1182
|
+
if (scope === "me") {
|
|
1183
|
+
if (!uuid) throw new Error("Personal genre has no playlist");
|
|
1184
|
+
return fetchGenrePlaylistTracksPage(uuid, scope, Number(pageParam));
|
|
1185
|
+
}
|
|
1120
1186
|
const response = await fetch(
|
|
1121
1187
|
libraryEndpoints.reference.songs.list(),
|
|
1122
1188
|
true,
|
|
@@ -1129,9 +1195,9 @@ function GenrePlaylistTracks({
|
|
|
1129
1195
|
},
|
|
1130
1196
|
initialPageParam: scope === "me" ? 1 : "",
|
|
1131
1197
|
getNextPageParam: (lastPage) => lastPage.nextPage ?? void 0,
|
|
1132
|
-
enabled: playback.ready && (scope !== "me" || sessionRestored && !!session?.accessToken)
|
|
1198
|
+
enabled: playback.ready && (scope !== "me" || uuid !== void 0 && sessionRestored && !!session?.accessToken)
|
|
1133
1199
|
});
|
|
1134
|
-
const [sentinel, setSentinel] =
|
|
1200
|
+
const [sentinel, setSentinel] = useState6(null);
|
|
1135
1201
|
useEffect3(() => {
|
|
1136
1202
|
if (!hasNextPage || isFetchingNextPage || !sentinel) return;
|
|
1137
1203
|
const observer = new IntersectionObserver((entries) => {
|
|
@@ -1151,30 +1217,30 @@ function GenrePlaylistTracks({
|
|
|
1151
1217
|
ordering: "title",
|
|
1152
1218
|
genreUuid,
|
|
1153
1219
|
...selection && { selection }
|
|
1154
|
-
}) : scope === "reference" ? playNewTrackListFromGenreCatalogue({ uuid: genreUuid, name:
|
|
1220
|
+
}) : scope === "reference" ? playNewTrackListFromGenreCatalogue({ uuid: genreUuid, name: genreName }, scope, { startSongUuid, ...selection && { selection } }) : genrePlaylist ? playNewTrackListFromGenrePlaylist(genrePlaylist, scope, { startSongUuid }) : Promise.reject(new Error("Personal genre has no playlist"));
|
|
1155
1221
|
started.catch((error) => {
|
|
1156
1222
|
console.error("Failed to play genre playlist:", error);
|
|
1157
|
-
showPopup(/* @__PURE__ */
|
|
1223
|
+
showPopup(/* @__PURE__ */ jsx17(InternalErrorPopup, { errorCode: ErrorCode3.CLIENT_UNKNOWN }));
|
|
1158
1224
|
});
|
|
1159
1225
|
};
|
|
1160
|
-
return /* @__PURE__ */
|
|
1226
|
+
return /* @__PURE__ */ jsx17(PanelSection, { title: `${capitalize(trackNoun.plural)}${total !== void 0 ? ` (${total})` : ""}`, children: /* @__PURE__ */ jsxs12("div", { children: [
|
|
1161
1227
|
renderTracksToolbar?.(),
|
|
1162
|
-
isPending ? /* @__PURE__ */
|
|
1228
|
+
scope === "me" && !genrePlaylist ? /* @__PURE__ */ jsx17("p", { children: "\u2014" }) : isPending ? /* @__PURE__ */ jsx17(Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ jsxs12("p", { role: "alert", children: [
|
|
1163
1229
|
"Couldn't load ",
|
|
1164
1230
|
trackNoun.plural,
|
|
1165
1231
|
".",
|
|
1166
1232
|
" ",
|
|
1167
|
-
/* @__PURE__ */
|
|
1168
|
-
] }) : tracks.length === 0 ? /* @__PURE__ */
|
|
1233
|
+
/* @__PURE__ */ jsx17("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
|
|
1234
|
+
] }) : tracks.length === 0 ? /* @__PURE__ */ jsx17("p", { children: "\u2014" }) : /* @__PURE__ */ jsxs12("ul", { className: "mt-1 max-h-64 overflow-y-auto text-[0.8125rem] leading-[1.45] [container-type:inline-size]", "aria-label": `${genreName} ${trackNoun.plural}`, children: [
|
|
1169
1235
|
tracks.map((track, index) => {
|
|
1170
1236
|
const label = track.artists?.length ? `${track.title} \u2014 ${track.artists.map((artist2) => artist2.name).join(", ")}` : track.title;
|
|
1171
1237
|
const isCurrent = playerTrackObject?.track.id === track.uuid;
|
|
1172
1238
|
const isPlaying = isCurrent && playState === PlayStates.PLAYING;
|
|
1173
1239
|
const text = /* @__PURE__ */ jsxs12("span", { className: TEXT_CELL, children: [
|
|
1174
|
-
/* @__PURE__ */
|
|
1175
|
-
track.artists?.length ? /* @__PURE__ */
|
|
1176
|
-
/* @__PURE__ */
|
|
1177
|
-
track.album && /* @__PURE__ */
|
|
1240
|
+
/* @__PURE__ */ jsx17("span", { className: TITLE_CELL, children: renderSongLink ? renderSongLink(track) : isSong(track) ? /* @__PURE__ */ jsx17(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
|
|
1241
|
+
track.artists?.length ? /* @__PURE__ */ jsx17("span", { className: SEPARATOR, children: " \u2014 " }) : null,
|
|
1242
|
+
/* @__PURE__ */ jsx17("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ jsx17(ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
|
|
1243
|
+
track.album && /* @__PURE__ */ jsx17("span", { className: ALBUM_CELL, children: track.album.name })
|
|
1178
1244
|
] });
|
|
1179
1245
|
const leading = renderSongLeading?.(track, index);
|
|
1180
1246
|
const actions = renderSongActions?.(track);
|
|
@@ -1182,7 +1248,7 @@ function GenrePlaylistTracks({
|
|
|
1182
1248
|
if (isUnplayable) {
|
|
1183
1249
|
return /* @__PURE__ */ jsxs12("li", { className: "flex min-h-8 items-center gap-1 px-1 opacity-50", children: [
|
|
1184
1250
|
leading,
|
|
1185
|
-
/* @__PURE__ */
|
|
1251
|
+
/* @__PURE__ */ jsx17(
|
|
1186
1252
|
"button",
|
|
1187
1253
|
{
|
|
1188
1254
|
type: "button",
|
|
@@ -1190,7 +1256,7 @@ function GenrePlaylistTracks({
|
|
|
1190
1256
|
"aria-disabled": "true",
|
|
1191
1257
|
"aria-label": label,
|
|
1192
1258
|
className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full opacity-0 cursor-not-allowed",
|
|
1193
|
-
children: /* @__PURE__ */
|
|
1259
|
+
children: /* @__PURE__ */ jsx17(FaPlay, { size: 10 })
|
|
1194
1260
|
}
|
|
1195
1261
|
),
|
|
1196
1262
|
text,
|
|
@@ -1203,14 +1269,14 @@ function GenrePlaylistTracks({
|
|
|
1203
1269
|
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)]" : ""}`,
|
|
1204
1270
|
children: [
|
|
1205
1271
|
leading,
|
|
1206
|
-
/* @__PURE__ */
|
|
1272
|
+
/* @__PURE__ */ jsx17(
|
|
1207
1273
|
"button",
|
|
1208
1274
|
{
|
|
1209
1275
|
type: "button",
|
|
1210
1276
|
"aria-label": `${isPlaying ? "Pause" : "Play"} ${label}`,
|
|
1211
1277
|
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 !== PlayStates.STOPPED ? "opacity-100" : "opacity-0 [@media(pointer:coarse)]:opacity-100"}`,
|
|
1212
1278
|
onClick: () => isCurrent ? handlePlayPauseAction() : play(track.uuid),
|
|
1213
|
-
children: isCurrent && playState === PlayStates.LOADING ? /* @__PURE__ */
|
|
1279
|
+
children: isCurrent && playState === PlayStates.LOADING ? /* @__PURE__ */ jsx17(RingLoader, { size: 10 }) : isPlaying ? /* @__PURE__ */ jsx17(FaPause, { size: 10 }) : /* @__PURE__ */ jsx17(FaPlay, { size: 10 })
|
|
1214
1280
|
}
|
|
1215
1281
|
),
|
|
1216
1282
|
text,
|
|
@@ -1220,7 +1286,7 @@ function GenrePlaylistTracks({
|
|
|
1220
1286
|
track.uuid
|
|
1221
1287
|
);
|
|
1222
1288
|
}),
|
|
1223
|
-
hasNextPage && /* @__PURE__ */
|
|
1289
|
+
hasNextPage && /* @__PURE__ */ jsx17("li", { ref: setSentinel, "aria-hidden": "true", className: "h-4" })
|
|
1224
1290
|
] })
|
|
1225
1291
|
] }) });
|
|
1226
1292
|
}
|
|
@@ -1228,10 +1294,11 @@ function GenrePlaylistTracks({
|
|
|
1228
1294
|
// src/genre-tree/GenreDetailExtras.tsx
|
|
1229
1295
|
import { useTrackNoun as useTrackNoun2 } from "@behindthemusictree/app-kit/track-noun";
|
|
1230
1296
|
import { SiWikidata, SiWikipedia } from "react-icons/si";
|
|
1231
|
-
import { Fragment as Fragment8, jsx as
|
|
1297
|
+
import { Fragment as Fragment8, jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1232
1298
|
function GenreDetailExtras({
|
|
1233
1299
|
genreUuid,
|
|
1234
1300
|
genrePlaylist,
|
|
1301
|
+
genreName,
|
|
1235
1302
|
selection,
|
|
1236
1303
|
wikidataId,
|
|
1237
1304
|
scope,
|
|
@@ -1254,15 +1321,15 @@ function GenreDetailExtras({
|
|
|
1254
1321
|
);
|
|
1255
1322
|
if (isPending) {
|
|
1256
1323
|
return /* @__PURE__ */ jsxs13(Fragment8, { children: [
|
|
1257
|
-
/* @__PURE__ */
|
|
1324
|
+
/* @__PURE__ */ jsx18(PanelSection2, { title: "Summary", children: /* @__PURE__ */ jsx18("div", { "aria-busy": "true", "data-testid": "genre-detail-extras-skeleton", children: /* @__PURE__ */ jsx18(Skeleton2, { className: "h-4 w-3/4" }) }) }),
|
|
1258
1325
|
family
|
|
1259
1326
|
] });
|
|
1260
1327
|
}
|
|
1261
|
-
if (!overview) return /* @__PURE__ */
|
|
1328
|
+
if (!overview) return /* @__PURE__ */ jsx18(Fragment8, { children: family });
|
|
1262
1329
|
const { summary, summarySourceUrl, essentialSongs } = overview;
|
|
1263
1330
|
const wikidataUrl = wikidataId ? `https://www.wikidata.org/wiki/${encodeURIComponent(wikidataId)}` : null;
|
|
1264
1331
|
return /* @__PURE__ */ jsxs13(Fragment8, { children: [
|
|
1265
|
-
(summarySourceUrl || wikidataUrl) && /* @__PURE__ */
|
|
1332
|
+
(summarySourceUrl || wikidataUrl) && /* @__PURE__ */ jsx18(PanelSection2, { title: "Links", children: /* @__PURE__ */ jsxs13("div", { className: "flex flex-wrap gap-x-3 gap-y-1 text-xs text-[var(--color-text)]", children: [
|
|
1266
1333
|
summarySourceUrl && /* @__PURE__ */ jsxs13(
|
|
1267
1334
|
"a",
|
|
1268
1335
|
{
|
|
@@ -1271,7 +1338,7 @@ function GenreDetailExtras({
|
|
|
1271
1338
|
rel: "noopener noreferrer",
|
|
1272
1339
|
className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
|
|
1273
1340
|
children: [
|
|
1274
|
-
/* @__PURE__ */
|
|
1341
|
+
/* @__PURE__ */ jsx18(SiWikipedia, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
|
|
1275
1342
|
"Wikipedia"
|
|
1276
1343
|
]
|
|
1277
1344
|
}
|
|
@@ -1284,21 +1351,22 @@ function GenreDetailExtras({
|
|
|
1284
1351
|
rel: "noopener noreferrer",
|
|
1285
1352
|
className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
|
|
1286
1353
|
children: [
|
|
1287
|
-
/* @__PURE__ */
|
|
1354
|
+
/* @__PURE__ */ jsx18(SiWikidata, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
|
|
1288
1355
|
"Wikidata"
|
|
1289
1356
|
]
|
|
1290
1357
|
}
|
|
1291
1358
|
)
|
|
1292
1359
|
] }) }),
|
|
1293
|
-
/* @__PURE__ */
|
|
1360
|
+
/* @__PURE__ */ jsx18(PanelSection2, { title: "Summary", children: renderSummary ? renderSummary(overview) : /* @__PURE__ */ jsx18(ClampedText, { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: summary ?? "\u2014" }, genreUuid) }),
|
|
1294
1361
|
family,
|
|
1295
1362
|
renderGenreDetailExtras?.(overview),
|
|
1296
|
-
(renderEssentialTracks || essentialSongs) && /* @__PURE__ */
|
|
1297
|
-
/* @__PURE__ */
|
|
1363
|
+
(renderEssentialTracks || essentialSongs) && /* @__PURE__ */ jsx18(PanelSection2, { title: `Essential ${trackNoun.plural}`, children: renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && essentialSongs.length > 0 ? /* @__PURE__ */ jsx18("ul", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: essentialSongs.map((track) => /* @__PURE__ */ jsx18("li", { className: "py-1", children: track.title }, track.uuid)) }) : /* @__PURE__ */ jsx18("p", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: "\u2014" }) }),
|
|
1364
|
+
/* @__PURE__ */ jsx18(
|
|
1298
1365
|
GenrePlaylistTracks,
|
|
1299
1366
|
{
|
|
1300
1367
|
genreUuid,
|
|
1301
1368
|
genrePlaylist,
|
|
1369
|
+
genreName,
|
|
1302
1370
|
selection,
|
|
1303
1371
|
scope,
|
|
1304
1372
|
getBackendBaseUrl,
|
|
@@ -1311,7 +1379,7 @@ function GenreDetailExtras({
|
|
|
1311
1379
|
}
|
|
1312
1380
|
|
|
1313
1381
|
// src/genre-tree/GenreInfoPanelBody.tsx
|
|
1314
|
-
import { Fragment as Fragment9, jsx as
|
|
1382
|
+
import { Fragment as Fragment9, jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1315
1383
|
import { createElement } from "react";
|
|
1316
1384
|
function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
|
|
1317
1385
|
const { isPlaying, setIsPlaying } = usePlayer5();
|
|
@@ -1325,7 +1393,7 @@ function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
|
|
|
1325
1393
|
console.error("Failed to load genre playlist tracks:", error);
|
|
1326
1394
|
});
|
|
1327
1395
|
};
|
|
1328
|
-
return /* @__PURE__ */
|
|
1396
|
+
return /* @__PURE__ */ jsx19(
|
|
1329
1397
|
GenreActionBand,
|
|
1330
1398
|
{
|
|
1331
1399
|
...bandProps,
|
|
@@ -1340,6 +1408,7 @@ function GenreInfoPanelBody({
|
|
|
1340
1408
|
genreUuid,
|
|
1341
1409
|
genrePlaylist,
|
|
1342
1410
|
catalogue,
|
|
1411
|
+
countsContext,
|
|
1343
1412
|
wikidataId,
|
|
1344
1413
|
scope,
|
|
1345
1414
|
getBackendBaseUrl,
|
|
@@ -1348,7 +1417,7 @@ function GenreInfoPanelBody({
|
|
|
1348
1417
|
extras
|
|
1349
1418
|
}) {
|
|
1350
1419
|
const noun = useTrackNoun3();
|
|
1351
|
-
const summary = scope === "reference" ? GenreCatalogueCountsSchema2.parse(genrePlaylist) : void 0;
|
|
1420
|
+
const summary = scope === "reference" ? GenreCatalogueCountsSchema2.parse(genrePlaylist ?? catalogue) : void 0;
|
|
1352
1421
|
const selection = summary ? defaultGenreMusicSelection2(summary) : void 0;
|
|
1353
1422
|
const describeCounts = (counts) => {
|
|
1354
1423
|
const scopes = counts.genreAndSubgenresTracksCount;
|
|
@@ -1356,17 +1425,19 @@ function GenreInfoPanelBody({
|
|
|
1356
1425
|
};
|
|
1357
1426
|
return /* @__PURE__ */ jsxs14(Fragment9, { children: [
|
|
1358
1427
|
toolbar,
|
|
1359
|
-
/* @__PURE__ */
|
|
1428
|
+
genrePlaylist ? /* @__PURE__ */ jsx19(GenreNodeActionBand, { ...band, genrePlaylist, playback }) : /* @__PURE__ */ jsx19(GenreActionBand, { ...band }),
|
|
1360
1429
|
summary && catalogue && /* @__PURE__ */ jsxs14("div", { className: "px-3.5 py-3 text-sm text-[var(--color-text)]", children: [
|
|
1361
1430
|
/* @__PURE__ */ jsxs14("p", { children: [
|
|
1362
1431
|
"Catalogue ",
|
|
1363
1432
|
noun.plural,
|
|
1433
|
+
countsContext ? ` (${countsContext})` : "",
|
|
1364
1434
|
": ",
|
|
1365
1435
|
describeCounts(catalogue)
|
|
1366
1436
|
] }),
|
|
1367
|
-
/* @__PURE__ */ jsxs14("p", { children: [
|
|
1437
|
+
genrePlaylist && /* @__PURE__ */ jsxs14("p", { children: [
|
|
1368
1438
|
"Playlist ",
|
|
1369
1439
|
noun.plural,
|
|
1440
|
+
countsContext ? ` (${countsContext})` : "",
|
|
1370
1441
|
": ",
|
|
1371
1442
|
describeCounts(summary)
|
|
1372
1443
|
] })
|
|
@@ -1379,6 +1450,7 @@ function GenreInfoPanelBody({
|
|
|
1379
1450
|
family,
|
|
1380
1451
|
genreUuid,
|
|
1381
1452
|
genrePlaylist,
|
|
1453
|
+
genreName: band.node.name,
|
|
1382
1454
|
selection,
|
|
1383
1455
|
wikidataId,
|
|
1384
1456
|
scope,
|
|
@@ -1388,13 +1460,163 @@ function GenreInfoPanelBody({
|
|
|
1388
1460
|
] });
|
|
1389
1461
|
}
|
|
1390
1462
|
|
|
1463
|
+
// src/genre-tree/GenreDetailPanel.tsx
|
|
1464
|
+
import { Fragment as Fragment10, jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1465
|
+
var RELATIVE_ITEM_COUNT = 1;
|
|
1466
|
+
function toNode(genre, parentId) {
|
|
1467
|
+
return {
|
|
1468
|
+
id: genre.uuid,
|
|
1469
|
+
parentId,
|
|
1470
|
+
name: genre.name,
|
|
1471
|
+
itemCount: RELATIVE_ITEM_COUNT,
|
|
1472
|
+
side: genre.side ?? void 0,
|
|
1473
|
+
wikidataId: genre.wikidataId
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
function useGenreDetailContent({
|
|
1477
|
+
genreUuid,
|
|
1478
|
+
scope,
|
|
1479
|
+
getBackendBaseUrl,
|
|
1480
|
+
onSelectGenre,
|
|
1481
|
+
extras,
|
|
1482
|
+
band,
|
|
1483
|
+
playback: hostPlayback,
|
|
1484
|
+
trailing,
|
|
1485
|
+
genrePlaylist: hostPlaylist,
|
|
1486
|
+
expanded
|
|
1487
|
+
}) {
|
|
1488
|
+
const { data: detail, isPending, isError } = useFetchGenreDetail(genreUuid, scope, getBackendBaseUrl);
|
|
1489
|
+
const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists: [], artist: extras?.artist });
|
|
1490
|
+
const playback = hostPlayback ?? defaultPlayback;
|
|
1491
|
+
const view = useMemo6(() => {
|
|
1492
|
+
if (!detail) return null;
|
|
1493
|
+
const rootColor = getGenreTreeColor2(detail.root.wikidataId);
|
|
1494
|
+
const relative = (genre, parentId) => ({
|
|
1495
|
+
node: toNode(genre, parentId),
|
|
1496
|
+
fill: tintSurface(rootColor),
|
|
1497
|
+
textColor: TEXT_COLOR
|
|
1498
|
+
});
|
|
1499
|
+
const ancestors2 = [...detail.ascendants].sort((a, b) => b.degree - a.degree);
|
|
1500
|
+
return {
|
|
1501
|
+
node: {
|
|
1502
|
+
id: detail.uuid,
|
|
1503
|
+
parentId: detail.parent?.uuid ?? null,
|
|
1504
|
+
name: detail.name,
|
|
1505
|
+
itemCount: scope === "reference" ? genreMusicCount2(detail.criteriaPlaylist ? GenrePlaylistSummarySchema.parse(detail.criteriaPlaylist) : GenreCatalogueCountsSchema3.parse(detail.catalogue)) : detail.tracksCount,
|
|
1506
|
+
wikidataId: detail.wikidataId
|
|
1507
|
+
},
|
|
1508
|
+
fill: rootColor,
|
|
1509
|
+
parentNode: detail.parent ? relative(detail.parent, null) : null,
|
|
1510
|
+
childNodes: detail.children.map((child) => relative(child, detail.uuid)),
|
|
1511
|
+
ancestorNodes: ancestors2.map(
|
|
1512
|
+
({ ascendant }, index) => relative(ascendant, index === 0 ? null : ancestors2[index - 1].ascendant.uuid)
|
|
1513
|
+
)
|
|
1514
|
+
};
|
|
1515
|
+
}, [detail, scope]);
|
|
1516
|
+
if (isError) return {
|
|
1517
|
+
title: "Genre unavailable",
|
|
1518
|
+
content: /* @__PURE__ */ jsx20("p", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." })
|
|
1519
|
+
};
|
|
1520
|
+
if (isPending || !detail || !view) return {
|
|
1521
|
+
title: "Loading genre\u2026",
|
|
1522
|
+
content: /* @__PURE__ */ jsxs15("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
|
|
1523
|
+
/* @__PURE__ */ jsx20(Skeleton3, { className: "h-6 w-2/3" }),
|
|
1524
|
+
/* @__PURE__ */ jsx20(Skeleton3, { className: "h-4 w-full" }),
|
|
1525
|
+
/* @__PURE__ */ jsx20(Skeleton3, { className: "h-4 w-3/4" })
|
|
1526
|
+
] })
|
|
1527
|
+
};
|
|
1528
|
+
const selectGenre = (nodeId) => onSelectGenre(GenreUuidSchema.parse(nodeId));
|
|
1529
|
+
const family = /* @__PURE__ */ jsx20(
|
|
1530
|
+
GenreNodeFamily,
|
|
1531
|
+
{
|
|
1532
|
+
node: view.node,
|
|
1533
|
+
fill: view.fill,
|
|
1534
|
+
textColor: ACCENT_TEXT_COLOR,
|
|
1535
|
+
parentNode: view.parentNode,
|
|
1536
|
+
childNodes: view.childNodes,
|
|
1537
|
+
onSelectNode: selectGenre
|
|
1538
|
+
}
|
|
1539
|
+
);
|
|
1540
|
+
const ancestors = /* @__PURE__ */ jsx20(GenreNodeAncestors, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
|
|
1541
|
+
return {
|
|
1542
|
+
title: detail.name,
|
|
1543
|
+
headerAccent: { background: view.fill, color: ACCENT_TEXT_COLOR },
|
|
1544
|
+
aside: expanded ? ancestors : void 0,
|
|
1545
|
+
content: /* @__PURE__ */ jsxs15(Fragment10, { children: [
|
|
1546
|
+
/* @__PURE__ */ jsx20(
|
|
1547
|
+
GenreInfoPanelBody,
|
|
1548
|
+
{
|
|
1549
|
+
family: /* @__PURE__ */ jsxs15(Fragment10, { children: [
|
|
1550
|
+
family,
|
|
1551
|
+
!expanded && ancestors
|
|
1552
|
+
] }),
|
|
1553
|
+
genreUuid: detail.uuid,
|
|
1554
|
+
genrePlaylist: hostPlaylist ?? (detail.criteriaPlaylist === null ? null : scope === "reference" ? { ...GenrePlaylistSummarySchema.parse(detail.criteriaPlaylist), criteria: CriteriaMinimumSchema.parse(detail) } : { ...detail.criteriaPlaylist, criteria: CriteriaMinimumSchema.parse(detail), tracksCount: detail.tracksCount }),
|
|
1555
|
+
catalogue: detail.catalogue,
|
|
1556
|
+
countsContext: !hostPlaylist && extras?.artist ? "all artists" : void 0,
|
|
1557
|
+
wikidataId: detail.wikidataId,
|
|
1558
|
+
scope,
|
|
1559
|
+
getBackendBaseUrl,
|
|
1560
|
+
playback,
|
|
1561
|
+
band: { node: view.node, ...band, shareTarget: band?.shareTarget?.kind === "genre" ? { ...band.shareTarget, name: detail.name } : band?.shareTarget },
|
|
1562
|
+
extras
|
|
1563
|
+
}
|
|
1564
|
+
),
|
|
1565
|
+
trailing
|
|
1566
|
+
] })
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
function GenreDetailPanel({
|
|
1570
|
+
genreUuid,
|
|
1571
|
+
scope,
|
|
1572
|
+
getBackendBaseUrl,
|
|
1573
|
+
onClose,
|
|
1574
|
+
onSelectGenre,
|
|
1575
|
+
onZoomIn,
|
|
1576
|
+
shareUrl,
|
|
1577
|
+
shareTarget,
|
|
1578
|
+
collapsed,
|
|
1579
|
+
expanded,
|
|
1580
|
+
onExpandedChange,
|
|
1581
|
+
edgeTab,
|
|
1582
|
+
extras,
|
|
1583
|
+
headerClassName
|
|
1584
|
+
}) {
|
|
1585
|
+
const panel = useGenreDetailContent({
|
|
1586
|
+
genreUuid,
|
|
1587
|
+
scope,
|
|
1588
|
+
getBackendBaseUrl,
|
|
1589
|
+
onSelectGenre,
|
|
1590
|
+
extras,
|
|
1591
|
+
expanded,
|
|
1592
|
+
band: { onZoomIn, shareUrl, shareTarget: shareUrl ? shareTarget : void 0 }
|
|
1593
|
+
});
|
|
1594
|
+
return /* @__PURE__ */ jsx20(
|
|
1595
|
+
DetailPanel,
|
|
1596
|
+
{
|
|
1597
|
+
side: "left",
|
|
1598
|
+
title: panel.title,
|
|
1599
|
+
headerAccent: panel.headerAccent,
|
|
1600
|
+
onClose,
|
|
1601
|
+
collapsed,
|
|
1602
|
+
expanded,
|
|
1603
|
+
onExpandedChange,
|
|
1604
|
+
edgeTab,
|
|
1605
|
+
aside: panel.aside,
|
|
1606
|
+
headerClassName,
|
|
1607
|
+
headerActions: onExpandedChange ? /* @__PURE__ */ jsx20(IconButton, { "aria-label": expanded ? "Shrink panel" : "Extend panel", onClick: () => onExpandedChange(!expanded), children: expanded ? /* @__PURE__ */ jsx20(Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ jsx20(Maximize2, { "aria-hidden": "true", className: "h-4 w-4" }) }) : void 0,
|
|
1608
|
+
children: panel.content
|
|
1609
|
+
}
|
|
1610
|
+
);
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1391
1613
|
// src/genre-tree/ChangeParentPopup.tsx
|
|
1392
|
-
import { useEffect as useEffect4, useMemo as
|
|
1614
|
+
import { useEffect as useEffect4, useMemo as useMemo7 } from "react";
|
|
1393
1615
|
import {
|
|
1394
1616
|
getReparentCandidates,
|
|
1395
1617
|
useUpdateGenre
|
|
1396
1618
|
} from "@behindthemusictree/app-kit/genre-tree";
|
|
1397
|
-
import { jsx as
|
|
1619
|
+
import { jsx as jsx21, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1398
1620
|
function ChangeParentPopup({
|
|
1399
1621
|
genrePlaylist,
|
|
1400
1622
|
genrePlaylists,
|
|
@@ -1404,7 +1626,7 @@ function ChangeParentPopup({
|
|
|
1404
1626
|
onParentChanged
|
|
1405
1627
|
}) {
|
|
1406
1628
|
const { mutate, isPending, formErrors } = useUpdateGenre(scope, getBackendBaseUrl);
|
|
1407
|
-
const candidates =
|
|
1629
|
+
const candidates = useMemo7(
|
|
1408
1630
|
() => getReparentCandidates(genrePlaylists, genrePlaylist.uuid),
|
|
1409
1631
|
[genrePlaylists, genrePlaylist.uuid]
|
|
1410
1632
|
);
|
|
@@ -1429,12 +1651,12 @@ function ChangeParentPopup({
|
|
|
1429
1651
|
window.addEventListener("keydown", handleKeyDown);
|
|
1430
1652
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
1431
1653
|
}, [onClose]);
|
|
1432
|
-
return /* @__PURE__ */
|
|
1433
|
-
/* @__PURE__ */
|
|
1654
|
+
return /* @__PURE__ */ jsxs16(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
|
|
1655
|
+
/* @__PURE__ */ jsxs16("p", { className: "mb-3 text-sm text-[var(--color-text-muted)]", children: [
|
|
1434
1656
|
"Current parent: ",
|
|
1435
1657
|
genrePlaylist.parent?.name ?? "none"
|
|
1436
1658
|
] }),
|
|
1437
|
-
/* @__PURE__ */
|
|
1659
|
+
/* @__PURE__ */ jsx21("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ jsx21(
|
|
1438
1660
|
GenreSearch,
|
|
1439
1661
|
{
|
|
1440
1662
|
genrePlaylists: candidates,
|
|
@@ -1442,12 +1664,12 @@ function ChangeParentPopup({
|
|
|
1442
1664
|
placeholder: "Search a new parent\u2026"
|
|
1443
1665
|
}
|
|
1444
1666
|
) }),
|
|
1445
|
-
formErrors.length > 0 && /* @__PURE__ */
|
|
1667
|
+
formErrors.length > 0 && /* @__PURE__ */ jsx21("p", { role: "alert", className: "mt-3 text-sm text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
|
|
1446
1668
|
] });
|
|
1447
1669
|
}
|
|
1448
1670
|
|
|
1449
1671
|
// src/genre-tree/artist/useArtistPanel.tsx
|
|
1450
|
-
import { Skeleton as
|
|
1672
|
+
import { Skeleton as Skeleton5 } from "@behindthemusictree/ui";
|
|
1451
1673
|
import { useArtist } from "@behindthemusictree/app-kit/genre-tree";
|
|
1452
1674
|
|
|
1453
1675
|
// src/genre-tree/artist/ArtistHeader.tsx
|
|
@@ -1466,7 +1688,7 @@ import {
|
|
|
1466
1688
|
SiYoutube
|
|
1467
1689
|
} from "react-icons/si";
|
|
1468
1690
|
import { Chip } from "@behindthemusictree/ui";
|
|
1469
|
-
import { jsx as
|
|
1691
|
+
import { jsx as jsx22 } from "react/jsx-runtime";
|
|
1470
1692
|
var LINK_KINDS = {
|
|
1471
1693
|
official: { label: "Official website", Icon: Globe },
|
|
1472
1694
|
wikipedia: { label: "Wikipedia", Icon: SiWikipedia2 },
|
|
@@ -1503,7 +1725,7 @@ function artistLinks(artist) {
|
|
|
1503
1725
|
return links;
|
|
1504
1726
|
}
|
|
1505
1727
|
function ArtistLinks({ links }) {
|
|
1506
|
-
return /* @__PURE__ */
|
|
1728
|
+
return /* @__PURE__ */ jsx22("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ jsx22("li", { children: /* @__PURE__ */ jsx22(Chip, { href: url, icon: /* @__PURE__ */ jsx22(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
|
|
1507
1729
|
}
|
|
1508
1730
|
|
|
1509
1731
|
// src/genre-tree/artist/formatYears.ts
|
|
@@ -1514,7 +1736,7 @@ function formatYears(beginYear, endYear, ended) {
|
|
|
1514
1736
|
}
|
|
1515
1737
|
|
|
1516
1738
|
// src/genre-tree/artist/ArtistHeader.tsx
|
|
1517
|
-
import { jsx as
|
|
1739
|
+
import { jsx as jsx23 } from "react/jsx-runtime";
|
|
1518
1740
|
var countryNames = new Intl.DisplayNames(["en"], { type: "region" });
|
|
1519
1741
|
function ArtistHeader({ artist }) {
|
|
1520
1742
|
const fields = [];
|
|
@@ -1524,35 +1746,35 @@ function ArtistHeader({ artist }) {
|
|
|
1524
1746
|
const years = formatYears(artist.beginYear, artist.endYear, artist.ended);
|
|
1525
1747
|
if (years) fields.push({ label: "Active", value: years });
|
|
1526
1748
|
const links = artistLinks(artist);
|
|
1527
|
-
if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */
|
|
1749
|
+
if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ jsx23(ArtistLinks, { links }) });
|
|
1528
1750
|
if (fields.length === 0) return null;
|
|
1529
|
-
return /* @__PURE__ */
|
|
1751
|
+
return /* @__PURE__ */ jsx23(FieldList, { items: fields });
|
|
1530
1752
|
}
|
|
1531
1753
|
|
|
1532
1754
|
// src/genre-tree/artist/ArtistPhoto.tsx
|
|
1533
|
-
import { jsx as
|
|
1755
|
+
import { jsx as jsx24, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
1534
1756
|
function ExternalLink({ href, children }) {
|
|
1535
|
-
return /* @__PURE__ */
|
|
1757
|
+
return /* @__PURE__ */ jsx24("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
|
|
1536
1758
|
}
|
|
1537
1759
|
function ArtistPhoto({ image, name }) {
|
|
1538
|
-
return /* @__PURE__ */
|
|
1539
|
-
/* @__PURE__ */
|
|
1540
|
-
/* @__PURE__ */
|
|
1760
|
+
return /* @__PURE__ */ jsxs17("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
|
|
1761
|
+
/* @__PURE__ */ jsx24("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
|
|
1762
|
+
/* @__PURE__ */ jsxs17("figcaption", { className: "text-xs text-[var(--color-text-muted)]", children: [
|
|
1541
1763
|
"Photo: ",
|
|
1542
|
-
/* @__PURE__ */
|
|
1764
|
+
/* @__PURE__ */ jsx24(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
|
|
1543
1765
|
",",
|
|
1544
1766
|
" ",
|
|
1545
|
-
image.licenseUrl ? /* @__PURE__ */
|
|
1767
|
+
image.licenseUrl ? /* @__PURE__ */ jsx24(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
|
|
1546
1768
|
] })
|
|
1547
1769
|
] });
|
|
1548
1770
|
}
|
|
1549
1771
|
|
|
1550
1772
|
// src/genre-tree/artist/ArtistMembers.tsx
|
|
1551
1773
|
import { PanelGroup, PanelSection as PanelSection3 } from "@behindthemusictree/ui";
|
|
1552
|
-
import { jsx as
|
|
1774
|
+
import { jsx as jsx25, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
1553
1775
|
function MembershipList({ title, memberships, renderArtistLink }) {
|
|
1554
1776
|
if (memberships.length === 0) return null;
|
|
1555
|
-
return /* @__PURE__ */
|
|
1777
|
+
return /* @__PURE__ */ jsx25(PanelGroup, { title, children: /* @__PURE__ */ jsx25("ul", { children: memberships.map((membership, index) => {
|
|
1556
1778
|
const details = [
|
|
1557
1779
|
formatYears(membership.beginYear, membership.endYear, membership.ended),
|
|
1558
1780
|
...membership.roles
|
|
@@ -1560,9 +1782,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
|
|
|
1560
1782
|
const detailsText = details.filter(Boolean).join(" \xB7 ");
|
|
1561
1783
|
return (
|
|
1562
1784
|
// A member can have several periods in the same group.
|
|
1563
|
-
/* @__PURE__ */
|
|
1564
|
-
/* @__PURE__ */
|
|
1565
|
-
detailsText && /* @__PURE__ */
|
|
1785
|
+
/* @__PURE__ */ jsxs18("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
|
|
1786
|
+
/* @__PURE__ */ jsx25("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
|
|
1787
|
+
detailsText && /* @__PURE__ */ jsxs18("span", { className: "text-[var(--color-text-muted)]", children: [
|
|
1566
1788
|
" \u2014 ",
|
|
1567
1789
|
detailsText
|
|
1568
1790
|
] })
|
|
@@ -1573,9 +1795,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
|
|
|
1573
1795
|
function ArtistMembers({ title, memberships, renderArtistLink }) {
|
|
1574
1796
|
if (memberships.length === 0) return null;
|
|
1575
1797
|
const isCurrent = (membership) => !membership.ended && membership.endYear === null;
|
|
1576
|
-
return /* @__PURE__ */
|
|
1577
|
-
/* @__PURE__ */
|
|
1578
|
-
/* @__PURE__ */
|
|
1798
|
+
return /* @__PURE__ */ jsxs18(PanelSection3, { title, children: [
|
|
1799
|
+
/* @__PURE__ */ jsx25(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
|
|
1800
|
+
/* @__PURE__ */ jsx25(
|
|
1579
1801
|
MembershipList,
|
|
1580
1802
|
{
|
|
1581
1803
|
title: "Past",
|
|
@@ -1588,7 +1810,7 @@ function ArtistMembers({ title, memberships, renderArtistLink }) {
|
|
|
1588
1810
|
|
|
1589
1811
|
// src/genre-tree/artist/ArtistDiscography.tsx
|
|
1590
1812
|
import { PanelGroup as PanelGroup2, PanelSection as PanelSection4 } from "@behindthemusictree/ui";
|
|
1591
|
-
import { jsx as
|
|
1813
|
+
import { jsx as jsx26, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
1592
1814
|
var GROUPS = ["Albums", "EPs", "Singles", "Live", "Compilations", "Other"];
|
|
1593
1815
|
var PRIMARY_GROUPS = { album: "Albums", ep: "EPs", single: "Singles" };
|
|
1594
1816
|
function discographyGroup({ primaryType, secondaryTypes }) {
|
|
@@ -1608,21 +1830,21 @@ function byYear(a, b) {
|
|
|
1608
1830
|
function ArtistDiscography({ albums }) {
|
|
1609
1831
|
if (albums.length === 0) return null;
|
|
1610
1832
|
const sorted = [...albums].sort(byYear);
|
|
1611
|
-
return /* @__PURE__ */
|
|
1833
|
+
return /* @__PURE__ */ jsx26(PanelSection4, { title: "Discography", children: GROUPS.map((group) => {
|
|
1612
1834
|
const groupAlbums = sorted.filter((album) => discographyGroup(album) === group);
|
|
1613
1835
|
if (groupAlbums.length === 0) return null;
|
|
1614
|
-
return /* @__PURE__ */
|
|
1615
|
-
/* @__PURE__ */
|
|
1616
|
-
/* @__PURE__ */
|
|
1836
|
+
return /* @__PURE__ */ jsx26(PanelGroup2, { title: group, children: /* @__PURE__ */ jsx26("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ jsxs19("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
|
|
1837
|
+
/* @__PURE__ */ jsx26("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
|
|
1838
|
+
/* @__PURE__ */ jsx26("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
|
|
1617
1839
|
] }, album.uuid)) }) }, group);
|
|
1618
1840
|
}) });
|
|
1619
1841
|
}
|
|
1620
1842
|
|
|
1621
1843
|
// src/genre-tree/artist/ArtistSongs.tsx
|
|
1622
|
-
import { useState as
|
|
1844
|
+
import { useState as useState7 } from "react";
|
|
1623
1845
|
import { keepPreviousData } from "@tanstack/react-query";
|
|
1624
1846
|
import { Play as Play2 } from "lucide-react";
|
|
1625
|
-
import { Pagination, PanelSection as PanelSection5, Skeleton as
|
|
1847
|
+
import { Pagination, PanelSection as PanelSection5, Skeleton as Skeleton4 } from "@behindthemusictree/ui";
|
|
1626
1848
|
import { usePlayer as usePlayer8 } from "@behindthemusictree/app-kit/player";
|
|
1627
1849
|
import {
|
|
1628
1850
|
SongDetailedSchema as SongDetailedSchema2,
|
|
@@ -1640,7 +1862,7 @@ import { useTrackList as useTrackList3 } from "@behindthemusictree/app-kit/genre
|
|
|
1640
1862
|
import { FaPlay as FaPlay2, FaPause as FaPause2 } from "react-icons/fa";
|
|
1641
1863
|
import { usePlayer as usePlayer6 } from "@behindthemusictree/app-kit/player";
|
|
1642
1864
|
import { PlayStates as PlayStates2 } from "@behindthemusictree/app-kit/player";
|
|
1643
|
-
import { jsx as
|
|
1865
|
+
import { jsx as jsx27, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
1644
1866
|
function TrackPositionPlayPause({
|
|
1645
1867
|
position,
|
|
1646
1868
|
uuid,
|
|
@@ -1654,14 +1876,14 @@ function TrackPositionPlayPause({
|
|
|
1654
1876
|
const isPlaying = isThisTrack && isConfirmedPlayable && playState === PlayStates2.PLAYING;
|
|
1655
1877
|
const isPaused = isThisTrack && isConfirmedPlayable && playState === PlayStates2.PAUSED;
|
|
1656
1878
|
const isCurrentTrackActive = isPlaying || isPaused;
|
|
1657
|
-
return /* @__PURE__ */
|
|
1879
|
+
return /* @__PURE__ */ jsxs20(
|
|
1658
1880
|
"div",
|
|
1659
1881
|
{
|
|
1660
1882
|
className: "track-position-play-pause relative flex items-center justify-center gap-2 text-lg w-16 cursor-pointer",
|
|
1661
1883
|
style: { minWidth: "4rem", maxWidth: "4rem" },
|
|
1662
1884
|
onClick: handlePlayPauseClick,
|
|
1663
1885
|
children: [
|
|
1664
|
-
/* @__PURE__ */
|
|
1886
|
+
/* @__PURE__ */ jsx27("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ jsx27("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ jsxs20(
|
|
1665
1887
|
"span",
|
|
1666
1888
|
{
|
|
1667
1889
|
className: "track-position-play-pause__bars",
|
|
@@ -1669,18 +1891,18 @@ function TrackPositionPlayPause({
|
|
|
1669
1891
|
role: "img",
|
|
1670
1892
|
"aria-label": isPlaying ? "Playing" : "Paused",
|
|
1671
1893
|
children: [
|
|
1672
|
-
/* @__PURE__ */
|
|
1673
|
-
/* @__PURE__ */
|
|
1674
|
-
/* @__PURE__ */
|
|
1894
|
+
/* @__PURE__ */ jsx27("span", { className: "track-position-play-pause__bar" }),
|
|
1895
|
+
/* @__PURE__ */ jsx27("span", { className: "track-position-play-pause__bar" }),
|
|
1896
|
+
/* @__PURE__ */ jsx27("span", { className: "track-position-play-pause__bar" })
|
|
1675
1897
|
]
|
|
1676
1898
|
}
|
|
1677
1899
|
) : position }),
|
|
1678
|
-
/* @__PURE__ */
|
|
1900
|
+
/* @__PURE__ */ jsx27(
|
|
1679
1901
|
"div",
|
|
1680
1902
|
{
|
|
1681
1903
|
className: `hidden group-hover:flex items-center justify-center ${isCurrentTrackActive ? "" : "absolute inset-0"}`,
|
|
1682
1904
|
"aria-hidden": "true",
|
|
1683
|
-
children: isAwaitingPlayback ? /* @__PURE__ */
|
|
1905
|
+
children: isAwaitingPlayback ? /* @__PURE__ */ jsx27("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ jsx27(FaPause2, {}) : /* @__PURE__ */ jsx27(FaPlay2, {})
|
|
1684
1906
|
}
|
|
1685
1907
|
)
|
|
1686
1908
|
]
|
|
@@ -1691,7 +1913,7 @@ function TrackPositionPlayPause({
|
|
|
1691
1913
|
// src/genre-tree/track-list-sidebar/TrackItem.tsx
|
|
1692
1914
|
import { isSong as isSong2, songUnplayableReason as songUnplayableReason2 } from "@behindthemusictree/app-kit/genre-tree";
|
|
1693
1915
|
import { ArtistNames as ArtistNames2 } from "@behindthemusictree/app-kit/genre-tree";
|
|
1694
|
-
import { jsx as
|
|
1916
|
+
import { jsx as jsx28, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
1695
1917
|
function TrackItem({
|
|
1696
1918
|
track,
|
|
1697
1919
|
position,
|
|
@@ -1715,12 +1937,12 @@ function TrackItem({
|
|
|
1715
1937
|
loadTrackForPlayer(track.uuid);
|
|
1716
1938
|
}
|
|
1717
1939
|
};
|
|
1718
|
-
return /* @__PURE__ */
|
|
1940
|
+
return /* @__PURE__ */ jsxs21(
|
|
1719
1941
|
"div",
|
|
1720
1942
|
{
|
|
1721
1943
|
className: `track-item flex h-14 text-[var(--color-text)] ${isUnplayable ? "opacity-50" : "hover:bg-[var(--color-surface-muted)] group"}`,
|
|
1722
1944
|
children: [
|
|
1723
|
-
isUnplayable ? /* @__PURE__ */
|
|
1945
|
+
isUnplayable ? /* @__PURE__ */ jsx28(
|
|
1724
1946
|
"div",
|
|
1725
1947
|
{
|
|
1726
1948
|
className: "flex items-center justify-center text-lg w-16",
|
|
@@ -1728,7 +1950,7 @@ function TrackItem({
|
|
|
1728
1950
|
"aria-disabled": "true",
|
|
1729
1951
|
children: position
|
|
1730
1952
|
}
|
|
1731
|
-
) : /* @__PURE__ */
|
|
1953
|
+
) : /* @__PURE__ */ jsx28(
|
|
1732
1954
|
TrackPositionPlayPause,
|
|
1733
1955
|
{
|
|
1734
1956
|
position,
|
|
@@ -1737,12 +1959,12 @@ function TrackItem({
|
|
|
1737
1959
|
isSelected: selectedTrack?.uuid === track.uuid
|
|
1738
1960
|
}
|
|
1739
1961
|
),
|
|
1740
|
-
/* @__PURE__ */
|
|
1741
|
-
/* @__PURE__ */
|
|
1742
|
-
track.artists && track.artists.length > 0 ? /* @__PURE__ */
|
|
1962
|
+
/* @__PURE__ */ jsxs21("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
|
|
1963
|
+
/* @__PURE__ */ jsx28("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ jsx28("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: renderSongLink ? renderSongLink(track) : isSong2(track) ? /* @__PURE__ */ jsx28(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
|
|
1964
|
+
track.artists && track.artists.length > 0 ? /* @__PURE__ */ jsx28("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ jsx28(ArtistNames2, { artists: track.artists, renderArtistLink }) }) : ""
|
|
1743
1965
|
] }),
|
|
1744
|
-
/* @__PURE__ */
|
|
1745
|
-
renderDuration && /* @__PURE__ */
|
|
1966
|
+
/* @__PURE__ */ jsx28("div", { className: "genre-name-container flex items-center justify-start w-[21.6%] min-w-0 shrink-0", children: track.genre ? /* @__PURE__ */ jsx28("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
|
|
1967
|
+
renderDuration && /* @__PURE__ */ jsx28(
|
|
1746
1968
|
"div",
|
|
1747
1969
|
{
|
|
1748
1970
|
className: "duration flex text-base w-16 items-center justify-center",
|
|
@@ -1750,22 +1972,22 @@ function TrackItem({
|
|
|
1750
1972
|
children: renderDuration(track)
|
|
1751
1973
|
}
|
|
1752
1974
|
),
|
|
1753
|
-
renderActions && /* @__PURE__ */
|
|
1975
|
+
renderActions && /* @__PURE__ */ jsx28("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
|
|
1754
1976
|
]
|
|
1755
1977
|
}
|
|
1756
1978
|
);
|
|
1757
1979
|
}
|
|
1758
1980
|
|
|
1759
1981
|
// src/genre-tree/artist/ArtistSongs.tsx
|
|
1760
|
-
import { Fragment as
|
|
1982
|
+
import { Fragment as Fragment11, jsx as jsx29, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
1761
1983
|
var SongPageSchema2 = PaginatedResponseSchema2(SongDetailedSchema2);
|
|
1762
1984
|
var SORTS = [
|
|
1763
1985
|
{ ordering: "title", label: "Title" },
|
|
1764
1986
|
{ ordering: "-albumYear", label: "Year" }
|
|
1765
1987
|
];
|
|
1766
1988
|
function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLink }) {
|
|
1767
|
-
const [page, setPage] =
|
|
1768
|
-
const [ordering, setOrdering] =
|
|
1989
|
+
const [page, setPage] = useState7(1);
|
|
1990
|
+
const [ordering, setOrdering] = useState7("title");
|
|
1769
1991
|
const { fetch } = useFetchWrapper2(getBackendBaseUrl);
|
|
1770
1992
|
const { playNewTrackListFromArtist } = useTrackList4();
|
|
1771
1993
|
const { playerTrackObject } = usePlayer8();
|
|
@@ -1790,21 +2012,21 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
|
|
|
1790
2012
|
event.stopPropagation();
|
|
1791
2013
|
void play(song.uuid);
|
|
1792
2014
|
};
|
|
1793
|
-
return /* @__PURE__ */
|
|
1794
|
-
/* @__PURE__ */
|
|
1795
|
-
/* @__PURE__ */
|
|
2015
|
+
return /* @__PURE__ */ jsx29(PanelSection5, { title: "Songs", children: isPending ? /* @__PURE__ */ jsx29(Skeleton4, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ jsx29("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ jsx29("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ jsxs22(Fragment11, { children: [
|
|
2016
|
+
/* @__PURE__ */ jsxs22("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
|
|
2017
|
+
/* @__PURE__ */ jsxs22(
|
|
1796
2018
|
"button",
|
|
1797
2019
|
{
|
|
1798
2020
|
type: "button",
|
|
1799
2021
|
onClick: () => void play(),
|
|
1800
2022
|
className: "flex w-fit items-center gap-1.5 px-3 py-1 text-xs font-medium bg-gray-900 text-white rounded-full hover:bg-gray-700",
|
|
1801
2023
|
children: [
|
|
1802
|
-
/* @__PURE__ */
|
|
2024
|
+
/* @__PURE__ */ jsx29(Play2, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
|
|
1803
2025
|
"Play all"
|
|
1804
2026
|
]
|
|
1805
2027
|
}
|
|
1806
2028
|
),
|
|
1807
|
-
/* @__PURE__ */
|
|
2029
|
+
/* @__PURE__ */ jsx29("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ jsx29(
|
|
1808
2030
|
"button",
|
|
1809
2031
|
{
|
|
1810
2032
|
type: "button",
|
|
@@ -1819,7 +2041,7 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
|
|
|
1819
2041
|
sort.ordering
|
|
1820
2042
|
)) })
|
|
1821
2043
|
] }),
|
|
1822
|
-
/* @__PURE__ */
|
|
2044
|
+
/* @__PURE__ */ jsx29("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ jsx29("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ jsx29(
|
|
1823
2045
|
TrackItem,
|
|
1824
2046
|
{
|
|
1825
2047
|
track: song,
|
|
@@ -1828,12 +2050,12 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
|
|
|
1828
2050
|
renderSongLink
|
|
1829
2051
|
}
|
|
1830
2052
|
) }, song.uuid)) }),
|
|
1831
|
-
/* @__PURE__ */
|
|
2053
|
+
/* @__PURE__ */ jsx29(Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
|
|
1832
2054
|
] }) });
|
|
1833
2055
|
}
|
|
1834
2056
|
|
|
1835
2057
|
// src/genre-tree/artist/useArtistPanel.tsx
|
|
1836
|
-
import { Fragment as
|
|
2058
|
+
import { Fragment as Fragment12, jsx as jsx30, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
1837
2059
|
function useArtistPanel({
|
|
1838
2060
|
uuid,
|
|
1839
2061
|
scope,
|
|
@@ -1854,26 +2076,26 @@ function useArtistPanel({
|
|
|
1854
2076
|
return {
|
|
1855
2077
|
...panel,
|
|
1856
2078
|
title: "Artist",
|
|
1857
|
-
content: /* @__PURE__ */
|
|
1858
|
-
/* @__PURE__ */
|
|
1859
|
-
/* @__PURE__ */
|
|
1860
|
-
/* @__PURE__ */
|
|
1861
|
-
/* @__PURE__ */
|
|
1862
|
-
/* @__PURE__ */
|
|
1863
|
-
/* @__PURE__ */
|
|
2079
|
+
content: /* @__PURE__ */ jsxs23("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
|
|
2080
|
+
/* @__PURE__ */ jsx30(Skeleton5, { className: "h-3 w-16" }),
|
|
2081
|
+
/* @__PURE__ */ jsx30(Skeleton5, { className: "h-4 w-32" }),
|
|
2082
|
+
/* @__PURE__ */ jsx30(Skeleton5, { className: "h-3 w-16" }),
|
|
2083
|
+
/* @__PURE__ */ jsx30(Skeleton5, { className: "h-4 w-24" }),
|
|
2084
|
+
/* @__PURE__ */ jsx30(Skeleton5, { className: "h-16 mt-2.5" }),
|
|
2085
|
+
/* @__PURE__ */ jsx30(Skeleton5, { className: "h-16" })
|
|
1864
2086
|
] })
|
|
1865
2087
|
};
|
|
1866
2088
|
case "not-found":
|
|
1867
2089
|
return {
|
|
1868
2090
|
...panel,
|
|
1869
2091
|
title: "Artist not found",
|
|
1870
|
-
content: /* @__PURE__ */
|
|
2092
|
+
content: /* @__PURE__ */ jsx30("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This artist does not exist." })
|
|
1871
2093
|
};
|
|
1872
2094
|
case "error":
|
|
1873
2095
|
return {
|
|
1874
2096
|
...panel,
|
|
1875
2097
|
title: "Artist",
|
|
1876
|
-
content: /* @__PURE__ */
|
|
2098
|
+
content: /* @__PURE__ */ jsx30("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
|
|
1877
2099
|
};
|
|
1878
2100
|
case "success": {
|
|
1879
2101
|
const { artist } = result;
|
|
@@ -1882,13 +2104,13 @@ function useArtistPanel({
|
|
|
1882
2104
|
return {
|
|
1883
2105
|
...panel,
|
|
1884
2106
|
title: artist.name,
|
|
1885
|
-
content: /* @__PURE__ */
|
|
1886
|
-
shareUrl && /* @__PURE__ */
|
|
1887
|
-
artist.image && /* @__PURE__ */
|
|
1888
|
-
/* @__PURE__ */
|
|
1889
|
-
/* @__PURE__ */
|
|
1890
|
-
/* @__PURE__ */
|
|
1891
|
-
artist.tracksCount > 0 && /* @__PURE__ */
|
|
2107
|
+
content: /* @__PURE__ */ jsxs23(Fragment12, { children: [
|
|
2108
|
+
shareUrl && /* @__PURE__ */ jsx30("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ jsx30("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: shareTarget && /* @__PURE__ */ jsx30(ShareActionButton, { target: shareTarget, url: shareUrl }) }) }),
|
|
2109
|
+
artist.image && /* @__PURE__ */ jsx30(ArtistPhoto, { image: artist.image, name: artist.name }),
|
|
2110
|
+
/* @__PURE__ */ jsx30(ArtistHeader, { artist }),
|
|
2111
|
+
/* @__PURE__ */ jsx30(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
|
|
2112
|
+
/* @__PURE__ */ jsx30(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
|
|
2113
|
+
artist.tracksCount > 0 && /* @__PURE__ */ jsx30(
|
|
1892
2114
|
ArtistSongs,
|
|
1893
2115
|
{
|
|
1894
2116
|
artist: { uuid: artist.uuid, name: artist.name },
|
|
@@ -1898,22 +2120,22 @@ function useArtistPanel({
|
|
|
1898
2120
|
}
|
|
1899
2121
|
)
|
|
1900
2122
|
] }),
|
|
1901
|
-
extraContent: artist.albums.length > 0 ? /* @__PURE__ */
|
|
2123
|
+
extraContent: artist.albums.length > 0 ? /* @__PURE__ */ jsx30(ArtistDiscography, { albums: artist.albums }) : void 0
|
|
1902
2124
|
};
|
|
1903
2125
|
}
|
|
1904
2126
|
}
|
|
1905
2127
|
}
|
|
1906
2128
|
|
|
1907
2129
|
// src/genre-tree/song/useSongPanel.tsx
|
|
1908
|
-
import { Skeleton as
|
|
2130
|
+
import { Skeleton as Skeleton7 } from "@behindthemusictree/ui";
|
|
1909
2131
|
import {
|
|
1910
2132
|
useSong
|
|
1911
2133
|
} from "@behindthemusictree/app-kit/genre-tree";
|
|
1912
2134
|
|
|
1913
2135
|
// src/genre-tree/song/SongPanelContent.tsx
|
|
1914
|
-
import { useState as
|
|
2136
|
+
import { useState as useState8 } from "react";
|
|
1915
2137
|
import { Pause as Pause2, Play as Play3 } from "lucide-react";
|
|
1916
|
-
import { FieldList as FieldList2, PanelSection as PanelSection6, Skeleton as
|
|
2138
|
+
import { FieldList as FieldList2, PanelSection as PanelSection6, Skeleton as Skeleton6 } from "@behindthemusictree/ui";
|
|
1917
2139
|
import { PlayStates as PlayStates3, usePlayer as usePlayer9 } from "@behindthemusictree/app-kit/player";
|
|
1918
2140
|
import {
|
|
1919
2141
|
ArtistNames as ArtistNames3,
|
|
@@ -1922,40 +2144,40 @@ import {
|
|
|
1922
2144
|
useSongHistory,
|
|
1923
2145
|
useTrackList as useTrackList5
|
|
1924
2146
|
} from "@behindthemusictree/app-kit/genre-tree";
|
|
1925
|
-
import { Fragment as
|
|
2147
|
+
import { Fragment as Fragment13, jsx as jsx31, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
1926
2148
|
var numberFormat = new Intl.NumberFormat("en");
|
|
1927
2149
|
var dateFormat = new Intl.DateTimeFormat("en", { dateStyle: "medium" });
|
|
1928
2150
|
var externalLink = "text-[var(--color-primary)] hover:underline focus-visible:underline";
|
|
1929
2151
|
function SongHistory({ uuid, getBackendBaseUrl }) {
|
|
1930
|
-
const [open, setOpen] =
|
|
2152
|
+
const [open, setOpen] = useState8(false);
|
|
1931
2153
|
const history = useSongHistory(uuid, getBackendBaseUrl, open);
|
|
1932
2154
|
let body = null;
|
|
1933
|
-
if (history.status === "loading") body = /* @__PURE__ */
|
|
1934
|
-
else if (history.status === "error") body = /* @__PURE__ */
|
|
2155
|
+
if (history.status === "loading") body = /* @__PURE__ */ jsx31(Skeleton6, { className: "h-10", "aria-busy": "true" });
|
|
2156
|
+
else if (history.status === "error") body = /* @__PURE__ */ jsx31("p", { className: "text-red-600", children: "Could not load the history." });
|
|
1935
2157
|
else if (history.status === "success")
|
|
1936
|
-
body = history.entries.length === 0 ? /* @__PURE__ */
|
|
1937
|
-
/* @__PURE__ */
|
|
1938
|
-
entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */
|
|
2158
|
+
body = history.entries.length === 0 ? /* @__PURE__ */ jsx31("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ jsx31("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ jsxs24("li", { className: "py-1.5 text-sm", children: [
|
|
2159
|
+
/* @__PURE__ */ jsx31("span", { className: "font-medium", children: entry.action }),
|
|
2160
|
+
entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ jsxs24("span", { className: "text-[var(--color-text-muted)]", children: [
|
|
1939
2161
|
" ",
|
|
1940
2162
|
"\u2014 ",
|
|
1941
2163
|
entry.oldValue ?? "none",
|
|
1942
2164
|
" \u2192 ",
|
|
1943
2165
|
entry.newValue ?? "none"
|
|
1944
2166
|
] }) : null,
|
|
1945
|
-
/* @__PURE__ */
|
|
2167
|
+
/* @__PURE__ */ jsxs24("span", { className: "block text-xs text-[var(--color-text-muted)]", children: [
|
|
1946
2168
|
entry.actorPseudo ?? "Anonymous",
|
|
1947
2169
|
" \xB7 ",
|
|
1948
2170
|
dateFormat.format(new Date(entry.createdOn))
|
|
1949
2171
|
] })
|
|
1950
2172
|
] }, entry.uuid)) });
|
|
1951
|
-
return /* @__PURE__ */
|
|
2173
|
+
return /* @__PURE__ */ jsxs24(
|
|
1952
2174
|
"details",
|
|
1953
2175
|
{
|
|
1954
2176
|
onToggle: (event) => setOpen(event.currentTarget.open),
|
|
1955
2177
|
className: "px-6 pb-2.5 mt-2.5 border-t border-[var(--color-border)]",
|
|
1956
2178
|
children: [
|
|
1957
|
-
/* @__PURE__ */
|
|
1958
|
-
/* @__PURE__ */
|
|
2179
|
+
/* @__PURE__ */ jsx31("summary", { className: "mt-2.5 cursor-pointer text-[0.6875rem] font-semibold uppercase text-[var(--color-text-muted)]", children: "History" }),
|
|
2180
|
+
/* @__PURE__ */ jsx31("div", { className: "mt-1.5", children: body })
|
|
1959
2181
|
]
|
|
1960
2182
|
}
|
|
1961
2183
|
);
|
|
@@ -1981,13 +2203,13 @@ function SongPanelContent({
|
|
|
1981
2203
|
const path = genrePath.length > 0 ? genrePath : null;
|
|
1982
2204
|
fields.push({
|
|
1983
2205
|
label: "Genre",
|
|
1984
|
-
value: path ? /* @__PURE__ */
|
|
1985
|
-
index > 0 && /* @__PURE__ */
|
|
1986
|
-
/* @__PURE__ */
|
|
2206
|
+
value: path ? /* @__PURE__ */ jsx31("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ jsxs24("span", { children: [
|
|
2207
|
+
index > 0 && /* @__PURE__ */ jsx31("span", { "aria-hidden": "true", children: "\u203A " }),
|
|
2208
|
+
/* @__PURE__ */ jsx31("button", { type: "button", onClick: () => onSelectGenre(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
|
|
1987
2209
|
] }, genrePlaylist.uuid)) }) : song.genre.name
|
|
1988
2210
|
});
|
|
1989
2211
|
} else {
|
|
1990
|
-
fields.push({ label: "Genre", value: /* @__PURE__ */
|
|
2212
|
+
fields.push({ label: "Genre", value: /* @__PURE__ */ jsx31("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
|
|
1991
2213
|
}
|
|
1992
2214
|
if (song.firstReleaseYear) fields.push({ label: "First released", value: song.firstReleaseYear });
|
|
1993
2215
|
const viewCount = song.youtubeVideo?.viewCount;
|
|
@@ -1997,11 +2219,11 @@ function SongPanelContent({
|
|
|
1997
2219
|
const links = [];
|
|
1998
2220
|
if (musicBrainzUrl)
|
|
1999
2221
|
links.push(
|
|
2000
|
-
/* @__PURE__ */
|
|
2222
|
+
/* @__PURE__ */ jsx31("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
|
|
2001
2223
|
);
|
|
2002
2224
|
if (song.youtubeVideo)
|
|
2003
2225
|
links.push(
|
|
2004
|
-
/* @__PURE__ */
|
|
2226
|
+
/* @__PURE__ */ jsx31(
|
|
2005
2227
|
"a",
|
|
2006
2228
|
{
|
|
2007
2229
|
href: `https://www.youtube.com/watch?v=${encodeURIComponent(song.youtubeVideo.id)}`,
|
|
@@ -2016,33 +2238,33 @@ function SongPanelContent({
|
|
|
2016
2238
|
if (links.length > 0)
|
|
2017
2239
|
fields.push({
|
|
2018
2240
|
label: "Links",
|
|
2019
|
-
value: /* @__PURE__ */
|
|
2241
|
+
value: /* @__PURE__ */ jsx31("span", { className: "flex gap-3", children: links })
|
|
2020
2242
|
});
|
|
2021
|
-
return /* @__PURE__ */
|
|
2022
|
-
/* @__PURE__ */
|
|
2023
|
-
unplayableReason ? /* @__PURE__ */
|
|
2243
|
+
return /* @__PURE__ */ jsxs24(Fragment13, { children: [
|
|
2244
|
+
/* @__PURE__ */ jsx31("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ jsxs24("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
|
|
2245
|
+
unplayableReason ? /* @__PURE__ */ jsx31("p", { className: "flex items-center px-2.5 text-sm text-[var(--color-text-muted)]", children: unplayableReason }) : /* @__PURE__ */ jsxs24(
|
|
2024
2246
|
"button",
|
|
2025
2247
|
{
|
|
2026
2248
|
type: "button",
|
|
2027
2249
|
onClick: () => isCurrent ? handlePlayPauseAction() : playNewTrackListFromTrackUuid(song, scope),
|
|
2028
2250
|
className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none",
|
|
2029
2251
|
children: [
|
|
2030
|
-
/* @__PURE__ */
|
|
2252
|
+
/* @__PURE__ */ jsx31("span", { className: "flex items-center justify-center w-10 h-10 rounded-full group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)] bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]", children: isPlaying ? /* @__PURE__ */ jsx31(Pause2, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ jsx31(Play3, { "aria-hidden": "true", className: "w-5 h-5" }) }),
|
|
2031
2253
|
isPlaying ? "Pause" : "Play"
|
|
2032
2254
|
]
|
|
2033
2255
|
}
|
|
2034
2256
|
),
|
|
2035
|
-
shareUrl && /* @__PURE__ */
|
|
2257
|
+
shareUrl && /* @__PURE__ */ jsx31(ShareActionButton, { target: shareTarget, url: shareUrl })
|
|
2036
2258
|
] }) }),
|
|
2037
|
-
song.artists && song.artists.length > 0 && /* @__PURE__ */
|
|
2038
|
-
/* @__PURE__ */
|
|
2039
|
-
song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */
|
|
2040
|
-
/* @__PURE__ */
|
|
2259
|
+
song.artists && song.artists.length > 0 && /* @__PURE__ */ jsx31("p", { className: "px-6 pt-2.5 text-[var(--color-text-muted)]", children: /* @__PURE__ */ jsx31(ArtistNames3, { artists: song.artists, renderArtistLink }) }),
|
|
2260
|
+
/* @__PURE__ */ jsx31(FieldList2, { items: fields }),
|
|
2261
|
+
song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ jsx31(PanelSection6, { title: "Essential in", children: /* @__PURE__ */ jsx31("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ jsx31("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
|
|
2262
|
+
/* @__PURE__ */ jsx31(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
|
|
2041
2263
|
] });
|
|
2042
2264
|
}
|
|
2043
2265
|
|
|
2044
2266
|
// src/genre-tree/song/useSongPanel.tsx
|
|
2045
|
-
import { jsx as
|
|
2267
|
+
import { jsx as jsx32, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
2046
2268
|
function useSongPanel({
|
|
2047
2269
|
uuid,
|
|
2048
2270
|
scope,
|
|
@@ -2064,24 +2286,24 @@ function useSongPanel({
|
|
|
2064
2286
|
return {
|
|
2065
2287
|
...panel,
|
|
2066
2288
|
title: "Song",
|
|
2067
|
-
content: /* @__PURE__ */
|
|
2068
|
-
/* @__PURE__ */
|
|
2069
|
-
/* @__PURE__ */
|
|
2070
|
-
/* @__PURE__ */
|
|
2071
|
-
/* @__PURE__ */
|
|
2289
|
+
content: /* @__PURE__ */ jsxs25("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
|
|
2290
|
+
/* @__PURE__ */ jsx32(Skeleton7, { className: "h-3 w-16" }),
|
|
2291
|
+
/* @__PURE__ */ jsx32(Skeleton7, { className: "h-4 w-32" }),
|
|
2292
|
+
/* @__PURE__ */ jsx32(Skeleton7, { className: "h-3 w-16" }),
|
|
2293
|
+
/* @__PURE__ */ jsx32(Skeleton7, { className: "h-4 w-24" })
|
|
2072
2294
|
] })
|
|
2073
2295
|
};
|
|
2074
2296
|
case "not-found":
|
|
2075
2297
|
return {
|
|
2076
2298
|
...panel,
|
|
2077
2299
|
title: "Song not found",
|
|
2078
|
-
content: /* @__PURE__ */
|
|
2300
|
+
content: /* @__PURE__ */ jsx32("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This song does not exist." })
|
|
2079
2301
|
};
|
|
2080
2302
|
case "error":
|
|
2081
2303
|
return {
|
|
2082
2304
|
...panel,
|
|
2083
2305
|
title: "Song",
|
|
2084
|
-
content: /* @__PURE__ */
|
|
2306
|
+
content: /* @__PURE__ */ jsx32("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this song." })
|
|
2085
2307
|
};
|
|
2086
2308
|
case "success": {
|
|
2087
2309
|
const { song } = result;
|
|
@@ -2089,7 +2311,7 @@ function useSongPanel({
|
|
|
2089
2311
|
return {
|
|
2090
2312
|
...panel,
|
|
2091
2313
|
title: song.title,
|
|
2092
|
-
content: /* @__PURE__ */
|
|
2314
|
+
content: /* @__PURE__ */ jsx32(
|
|
2093
2315
|
SongPanelContent,
|
|
2094
2316
|
{
|
|
2095
2317
|
song,
|
|
@@ -2109,12 +2331,12 @@ function useSongPanel({
|
|
|
2109
2331
|
// src/genre-tree/SizeMetricToggle.tsx
|
|
2110
2332
|
import { MdMusicNote, MdVisibility } from "react-icons/md";
|
|
2111
2333
|
import { SegmentedControl } from "@behindthemusictree/ui";
|
|
2112
|
-
import { jsx as
|
|
2334
|
+
import { jsx as jsx33 } from "react/jsx-runtime";
|
|
2113
2335
|
function SizeMetricToggle({
|
|
2114
2336
|
value,
|
|
2115
2337
|
onChange
|
|
2116
2338
|
}) {
|
|
2117
|
-
return /* @__PURE__ */
|
|
2339
|
+
return /* @__PURE__ */ jsx33(
|
|
2118
2340
|
SegmentedControl,
|
|
2119
2341
|
{
|
|
2120
2342
|
variant: "floating",
|
|
@@ -2122,15 +2344,15 @@ function SizeMetricToggle({
|
|
|
2122
2344
|
value,
|
|
2123
2345
|
onChange,
|
|
2124
2346
|
options: [
|
|
2125
|
-
{ value: "songs", label: /* @__PURE__ */
|
|
2126
|
-
{ value: "views", label: /* @__PURE__ */
|
|
2347
|
+
{ value: "songs", label: /* @__PURE__ */ jsx33(MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
|
|
2348
|
+
{ value: "views", label: /* @__PURE__ */ jsx33(MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
|
|
2127
2349
|
]
|
|
2128
2350
|
}
|
|
2129
2351
|
);
|
|
2130
2352
|
}
|
|
2131
2353
|
|
|
2132
2354
|
// src/genre-tree/GenreTreeView.tsx
|
|
2133
|
-
import { Fragment as
|
|
2355
|
+
import { Fragment as Fragment14, jsx as jsx34, jsxs as jsxs26 } from "react/jsx-runtime";
|
|
2134
2356
|
var NO_POP_ROOT_TITLE = "This genre tree has no 'Mainstream Pop' root yet";
|
|
2135
2357
|
var REGIONS_CLASS = "[--gtv-center-left:var(--layout-center-left)] [--gtv-center-right:var(--layout-center-right)] [--gtv-center-top:var(--layout-band-top)]";
|
|
2136
2358
|
var noop = () => {
|
|
@@ -2143,8 +2365,8 @@ function GenreTreeView({
|
|
|
2143
2365
|
getBackendBaseUrl,
|
|
2144
2366
|
additionalActions,
|
|
2145
2367
|
viewMode: controlledViewMode,
|
|
2146
|
-
|
|
2147
|
-
|
|
2368
|
+
selectedGenreUuid: controlledSelectedGenreUuid,
|
|
2369
|
+
onSelectedGenreUuidChange,
|
|
2148
2370
|
defaultViewport,
|
|
2149
2371
|
viewportRestore,
|
|
2150
2372
|
onViewportChange,
|
|
@@ -2165,6 +2387,7 @@ function GenreTreeView({
|
|
|
2165
2387
|
onArtistExpandedChange,
|
|
2166
2388
|
getShareUrl,
|
|
2167
2389
|
extraSearchGroups = [],
|
|
2390
|
+
SearchGroups,
|
|
2168
2391
|
onSearchQueryChange,
|
|
2169
2392
|
onSearchFocus,
|
|
2170
2393
|
artistSearch = true,
|
|
@@ -2193,19 +2416,19 @@ function GenreTreeView({
|
|
|
2193
2416
|
}) {
|
|
2194
2417
|
const getShareUrlRef = useRef3(getShareUrl);
|
|
2195
2418
|
getShareUrlRef.current = getShareUrl;
|
|
2196
|
-
const [internalArtistUuid, setInternalArtistUuid] =
|
|
2419
|
+
const [internalArtistUuid, setInternalArtistUuid] = useState9(null);
|
|
2197
2420
|
const isArtistControlled = controlledArtistUuid !== void 0;
|
|
2198
2421
|
const artistUuid = isArtistControlled ? controlledArtistUuid : internalArtistUuid;
|
|
2199
|
-
const [internalSongUuid, setInternalSongUuid] =
|
|
2422
|
+
const [internalSongUuid, setInternalSongUuid] = useState9(null);
|
|
2200
2423
|
const isSongControlled = controlledSongUuid !== void 0;
|
|
2201
2424
|
const songUuid = isSongControlled ? controlledSongUuid : internalSongUuid;
|
|
2202
2425
|
const changeSong = (uuid) => {
|
|
2203
2426
|
if (!isSongControlled) setInternalSongUuid(uuid);
|
|
2204
2427
|
onSongUuidChange?.(uuid);
|
|
2205
2428
|
};
|
|
2206
|
-
const [internalSizeMetric, setInternalSizeMetric] =
|
|
2429
|
+
const [internalSizeMetric, setInternalSizeMetric] = useState9("songs");
|
|
2207
2430
|
const sizeMetric = controlledSizeMetric ?? internalSizeMetric;
|
|
2208
|
-
const deferredSizeMetric =
|
|
2431
|
+
const deferredSizeMetric = useDeferredValue2(sizeMetric);
|
|
2209
2432
|
const changeSizeMetric = (metric) => {
|
|
2210
2433
|
if (controlledSizeMetric === void 0) setInternalSizeMetric(metric);
|
|
2211
2434
|
onSizeMetricChange?.(metric);
|
|
@@ -2220,6 +2443,8 @@ function GenreTreeView({
|
|
|
2220
2443
|
genrePlaylistByNodeId,
|
|
2221
2444
|
visibleGenrePlaylists,
|
|
2222
2445
|
groupedGenrePlaylistsByRoot,
|
|
2446
|
+
selectedGenreUuid,
|
|
2447
|
+
selectGenre,
|
|
2223
2448
|
selectedNodeId,
|
|
2224
2449
|
selectedGenrePlaylist,
|
|
2225
2450
|
selectNode,
|
|
@@ -2236,13 +2461,13 @@ function GenreTreeView({
|
|
|
2236
2461
|
scope,
|
|
2237
2462
|
getBackendBaseUrl,
|
|
2238
2463
|
viewMode: controlledViewMode,
|
|
2239
|
-
|
|
2240
|
-
|
|
2464
|
+
selectedGenreUuid: controlledSelectedGenreUuid,
|
|
2465
|
+
onSelectedGenreUuidChange,
|
|
2241
2466
|
criteriaOverviewSchema,
|
|
2242
2467
|
artistUuid
|
|
2243
2468
|
});
|
|
2244
2469
|
const restoreRevision = viewportRestore?.revision;
|
|
2245
|
-
const [stackedRestoreOwner, setStackedRestoreOwner] =
|
|
2470
|
+
const [stackedRestoreOwner, setStackedRestoreOwner] = useState9({ revision: restoreRevision, rootUuid: selectedGenrePlaylist?.root.uuid });
|
|
2246
2471
|
let restoreRootUuid = stackedRestoreOwner.rootUuid;
|
|
2247
2472
|
if (stackedRestoreOwner.revision !== restoreRevision) {
|
|
2248
2473
|
restoreRootUuid = selectedGenrePlaylist?.root.uuid;
|
|
@@ -2258,29 +2483,29 @@ function GenreTreeView({
|
|
|
2258
2483
|
const canSizeByViews = !isArtistFiltered && hasViewsCount(visibleGenrePlaylists);
|
|
2259
2484
|
const artistResult = useArtist2(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
|
|
2260
2485
|
const filterArtistName = artistResult.status === "success" ? artistResult.artist.name : null;
|
|
2261
|
-
const filterArtist =
|
|
2486
|
+
const filterArtist = useMemo8(
|
|
2262
2487
|
() => artistUuid !== null && filterArtistName !== null ? { uuid: artistUuid, name: filterArtistName } : null,
|
|
2263
2488
|
[artistUuid, filterArtistName]
|
|
2264
2489
|
);
|
|
2265
2490
|
const hasSelectedGenreNode = selectedGenrePlaylist !== void 0 && visibleGenrePlaylists.some(({ uuid }) => uuid === selectedGenrePlaylist.uuid);
|
|
2266
2491
|
const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: visibleGenrePlaylists, artist: filterArtist });
|
|
2267
2492
|
const openArtist = (uuid) => {
|
|
2268
|
-
if (
|
|
2493
|
+
if (selectedGenreUuid !== null) clearSelection();
|
|
2269
2494
|
if (songUuid !== null) changeSong(null);
|
|
2270
2495
|
changeArtist(uuid);
|
|
2271
2496
|
};
|
|
2272
2497
|
const openSong = (uuid) => {
|
|
2273
|
-
if (
|
|
2498
|
+
if (selectedGenreUuid !== null) clearSelection();
|
|
2274
2499
|
changeSong(uuid);
|
|
2275
2500
|
};
|
|
2276
2501
|
const openSongRef = useRef3(openSong);
|
|
2277
2502
|
openSongRef.current = openSong;
|
|
2278
2503
|
const resolvedRenderSongLink = useCallback6(
|
|
2279
|
-
(song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */
|
|
2504
|
+
(song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ jsx34("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
|
|
2280
2505
|
[renderSongLink]
|
|
2281
2506
|
);
|
|
2282
|
-
const [expandedSongUuid, setExpandedSongUuid] =
|
|
2283
|
-
const [expandedArtistUuid, setExpandedArtistUuid] =
|
|
2507
|
+
const [expandedSongUuid, setExpandedSongUuid] = useState9(null);
|
|
2508
|
+
const [expandedArtistUuid, setExpandedArtistUuid] = useState9(null);
|
|
2284
2509
|
const artistBaseUrl = artistSearchBaseUrl ?? getBackendBaseUrl;
|
|
2285
2510
|
const songBaseUrl = getBackendBaseUrl;
|
|
2286
2511
|
const artistPanel = useArtistPanel({
|
|
@@ -2288,7 +2513,7 @@ function GenreTreeView({
|
|
|
2288
2513
|
scope,
|
|
2289
2514
|
getBackendBaseUrl: artistBaseUrl,
|
|
2290
2515
|
getShareUrl: getShareUrlRef.current,
|
|
2291
|
-
renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */
|
|
2516
|
+
renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ jsx34("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
|
|
2292
2517
|
expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
|
|
2293
2518
|
onExpandedChange: (on) => {
|
|
2294
2519
|
if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
|
|
@@ -2297,7 +2522,7 @@ function GenreTreeView({
|
|
|
2297
2522
|
renderSongLink: resolvedRenderSongLink,
|
|
2298
2523
|
onClose: () => changeArtist(null)
|
|
2299
2524
|
});
|
|
2300
|
-
const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */
|
|
2525
|
+
const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ jsx34("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
|
|
2301
2526
|
const getGenrePath = (genre) => {
|
|
2302
2527
|
const path = [];
|
|
2303
2528
|
let current = visibleGenrePlaylists.find((playlist) => playlist.criteria?.uuid === genre.uuid);
|
|
@@ -2315,9 +2540,9 @@ function GenreTreeView({
|
|
|
2315
2540
|
getShareUrl: getShareUrlRef.current,
|
|
2316
2541
|
renderArtistLink: resolvedRenderArtistLink,
|
|
2317
2542
|
getGenrePath,
|
|
2318
|
-
onSelectGenre: (
|
|
2543
|
+
onSelectGenre: (genrePlaylist2) => {
|
|
2319
2544
|
changeSong(null);
|
|
2320
|
-
selectGenrePlaylist(
|
|
2545
|
+
selectGenrePlaylist(genrePlaylist2);
|
|
2321
2546
|
},
|
|
2322
2547
|
expanded: controlledSongExpanded ?? (songUuid !== null && expandedSongUuid === songUuid),
|
|
2323
2548
|
onExpandedChange: (on) => {
|
|
@@ -2326,11 +2551,11 @@ function GenreTreeView({
|
|
|
2326
2551
|
},
|
|
2327
2552
|
onClose: () => changeSong(null)
|
|
2328
2553
|
});
|
|
2329
|
-
const builtInSongPanel = overlayPanel ||
|
|
2330
|
-
const builtInArtistPanel = overlayPanel ||
|
|
2554
|
+
const builtInSongPanel = overlayPanel || selectedGenreUuid !== null ? void 0 : songPanel;
|
|
2555
|
+
const builtInArtistPanel = overlayPanel || selectedGenreUuid !== null || builtInSongPanel ? void 0 : artistPanel;
|
|
2331
2556
|
const builtInPanel = builtInSongPanel ?? builtInArtistPanel;
|
|
2332
|
-
const [uncontrolledCollapsed, setUncontrolledCollapsed] =
|
|
2333
|
-
const [uncontrolledExpanded, setUncontrolledExpanded] =
|
|
2557
|
+
const [uncontrolledCollapsed, setUncontrolledCollapsed] = useState9(false);
|
|
2558
|
+
const [uncontrolledExpanded, setUncontrolledExpanded] = useState9(false);
|
|
2334
2559
|
const isCollapsed = collapsed ?? uncontrolledCollapsed;
|
|
2335
2560
|
const isExpanded = expanded ?? uncontrolledExpanded;
|
|
2336
2561
|
const changeCollapsed = (next) => {
|
|
@@ -2343,11 +2568,11 @@ function GenreTreeView({
|
|
|
2343
2568
|
};
|
|
2344
2569
|
useEffect5(() => {
|
|
2345
2570
|
setUncontrolledCollapsed(false);
|
|
2346
|
-
}, [
|
|
2347
|
-
const [isWheelRevealed, setIsWheelRevealed] =
|
|
2571
|
+
}, [selectedGenreUuid, artistUuid, songUuid]);
|
|
2572
|
+
const [isWheelRevealed, setIsWheelRevealed] = useState9(false);
|
|
2348
2573
|
const usesHandoff = viewMode === "wheel" || viewMode === "pop-core";
|
|
2349
2574
|
const isTreeReady = !isLoading && (!usesHandoff || isWheelRevealed);
|
|
2350
|
-
const [resolveStyle, setResolveStyle] =
|
|
2575
|
+
const [resolveStyle, setResolveStyle] = useState9(null);
|
|
2351
2576
|
const handleNodeStyleResolverChange = useCallback6(
|
|
2352
2577
|
(resolver) => setResolveStyle(() => resolver),
|
|
2353
2578
|
[]
|
|
@@ -2356,162 +2581,99 @@ function GenreTreeView({
|
|
|
2356
2581
|
const obscuredInset = useObscuredInset(panelRef);
|
|
2357
2582
|
const hostPanel = { obscuredInset, onNodeStyleResolverChange: handleNodeStyleResolverChange };
|
|
2358
2583
|
const nodeMetric = isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric;
|
|
2359
|
-
const treeNodes =
|
|
2584
|
+
const treeNodes = useMemo8(() => toTreeNodes(visibleGenrePlaylists, nodeMetric), [visibleGenrePlaylists, nodeMetric]);
|
|
2360
2585
|
const relations = useGenreNodeRelations(
|
|
2361
2586
|
treeNodes,
|
|
2362
2587
|
!overlayPanel && hasSelectedGenreNode ? selectedNodeId : null,
|
|
2363
2588
|
resolveStyle
|
|
2364
2589
|
);
|
|
2365
|
-
const [selectionHistory, setSelectionHistory] =
|
|
2366
|
-
|
|
2367
|
-
previous:
|
|
2368
|
-
});
|
|
2369
|
-
if (selectionHistory.current !== selectedNodeId) {
|
|
2370
|
-
setSelectionHistory({
|
|
2371
|
-
current: selectedNodeId,
|
|
2372
|
-
previous: selectionHistory.current ?? selectionHistory.previous
|
|
2373
|
-
});
|
|
2590
|
+
const [selectionHistory, setSelectionHistory] = useState9({ current: selectedGenreUuid, previous: null });
|
|
2591
|
+
if (selectionHistory.current !== selectedGenreUuid) {
|
|
2592
|
+
setSelectionHistory({ current: selectedGenreUuid, previous: selectionHistory.current ?? selectionHistory.previous });
|
|
2374
2593
|
}
|
|
2375
|
-
const
|
|
2376
|
-
const handlePrevious = previousPlaylist && visibleGenrePlaylists.includes(previousPlaylist) ? () => handleGenreSearchSelect(previousPlaylist) : void 0;
|
|
2594
|
+
const handlePrevious = selectionHistory.previous ? () => selectGenre(selectionHistory.previous) : void 0;
|
|
2377
2595
|
const { showPopup, hidePopup } = usePopup5();
|
|
2378
|
-
const
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
(artist) => {
|
|
2383
|
-
openArtist(artist.uuid);
|
|
2384
|
-
onArtistSelect?.(artist);
|
|
2385
|
-
},
|
|
2386
|
-
Boolean(artistSearch)
|
|
2387
|
-
);
|
|
2388
|
-
const songGroup = useSongSearchGroup(
|
|
2389
|
-
songBaseUrl,
|
|
2390
|
-
searchText,
|
|
2391
|
-
(song) => {
|
|
2392
|
-
openSong(song.uuid);
|
|
2393
|
-
onSongSelect?.(song);
|
|
2394
|
-
},
|
|
2395
|
-
Boolean(songSearch)
|
|
2396
|
-
);
|
|
2397
|
-
const extraGroups = [...extraSearchGroups, artistGroup, songGroup].filter(
|
|
2398
|
-
(group) => group !== void 0
|
|
2399
|
-
);
|
|
2596
|
+
const reopenPanel = () => {
|
|
2597
|
+
setUncontrolledCollapsed(false);
|
|
2598
|
+
onCollapsedChange?.(false);
|
|
2599
|
+
};
|
|
2400
2600
|
const handleSelectedNodeChange = (node) => {
|
|
2601
|
+
reopenPanel();
|
|
2401
2602
|
if (node !== null && songUuid !== null) changeSong(null);
|
|
2402
2603
|
selectNode(node);
|
|
2403
2604
|
};
|
|
2404
2605
|
const handleGenreSearchClear = clearSelection;
|
|
2405
|
-
const handleGenreSearchSelect = (
|
|
2606
|
+
const handleGenreSearchSelect = (genrePlaylist2) => {
|
|
2607
|
+
reopenPanel();
|
|
2406
2608
|
if (songUuid !== null) changeSong(null);
|
|
2407
|
-
selectGenrePlaylist(
|
|
2609
|
+
selectGenrePlaylist(genrePlaylist2);
|
|
2408
2610
|
};
|
|
2409
2611
|
const selectedUuid = selectedGenrePlaylist?.uuid;
|
|
2410
|
-
const selectedChildren = selectedUuid ? visibleGenrePlaylists.filter((
|
|
2612
|
+
const selectedChildren = selectedUuid ? visibleGenrePlaylists.filter((genrePlaylist2) => genrePlaylist2.parent?.uuid === selectedUuid) : [];
|
|
2411
2613
|
const canZoomIn = viewMode === "pop-core" && focus.onFocusGenreChange !== void 0 && selectedUuid !== void 0 && focus.focusGenreUuid !== selectedUuid && selectedChildren.length > 0 && selectedChildren.filter((child) => child.criteria?.side === "pop").length <= 1;
|
|
2412
2614
|
const handleGenreSearchZoomIn = () => {
|
|
2413
2615
|
if (selectedUuid) focus.onFocusGenreChange?.(selectedUuid, "zoom-in");
|
|
2414
2616
|
};
|
|
2415
2617
|
const zoomInRef = useRef3(void 0);
|
|
2416
2618
|
zoomInRef.current = canZoomIn ? handleGenreSearchZoomIn : void 0;
|
|
2417
|
-
const
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
const
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
kind: "genre",
|
|
2447
|
-
genrePlaylistUuid: genrePlaylist.uuid,
|
|
2448
|
-
name: criteria.name,
|
|
2449
|
-
scope,
|
|
2450
|
-
artistUuid
|
|
2451
|
-
};
|
|
2452
|
-
const shareUrl = getShareUrlRef.current?.(shareTarget) ?? void 0;
|
|
2453
|
-
const popSibling = canToggleSide && side === "core" ? visibleGenrePlaylists.find(
|
|
2454
|
-
(sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist.uuid && sibling.criteria?.side === "pop"
|
|
2455
|
-
) : void 0;
|
|
2456
|
-
return {
|
|
2457
|
-
primary: /* @__PURE__ */ jsxs25(Fragment13, { children: [
|
|
2458
|
-
/* @__PURE__ */ jsx32(
|
|
2459
|
-
GenreInfoPanelBody,
|
|
2619
|
+
const genrePlaylist = selectedGenrePlaylist;
|
|
2620
|
+
const criteria = genrePlaylist?.criteria;
|
|
2621
|
+
const parentPlaylist = visibleGenrePlaylists.find(({ uuid }) => uuid === genrePlaylist?.parent?.uuid);
|
|
2622
|
+
const canToggleSide = !readOnly && criteria !== void 0 && criteria !== null && parentPlaylist !== void 0 && parentPlaylist.parent === null;
|
|
2623
|
+
const side = criteria?.side ?? "core";
|
|
2624
|
+
const popSibling = canToggleSide && side === "core" ? visibleGenrePlaylists.find((sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist?.uuid && sibling.criteria?.side === "pop") : void 0;
|
|
2625
|
+
const shareTarget = selectedGenreUuid ? { kind: "genre", genreUuid: selectedGenreUuid, name: criteria?.name ?? "Genre", scope, artistUuid } : void 0;
|
|
2626
|
+
const shareUrl = shareTarget ? getShareUrlRef.current?.(shareTarget) ?? void 0 : void 0;
|
|
2627
|
+
const genreContent = useGenreDetailContent({
|
|
2628
|
+
genreUuid: selectedGenreUuid,
|
|
2629
|
+
scope,
|
|
2630
|
+
getBackendBaseUrl,
|
|
2631
|
+
onSelectGenre: (uuid) => {
|
|
2632
|
+
reopenPanel();
|
|
2633
|
+
selectGenre(uuid);
|
|
2634
|
+
},
|
|
2635
|
+
expanded: isExpanded,
|
|
2636
|
+
playback,
|
|
2637
|
+
genrePlaylist: selectedGenrePlaylist,
|
|
2638
|
+
band: {
|
|
2639
|
+
...relations ? { node: relations.node } : {},
|
|
2640
|
+
shareTarget: shareUrl ? shareTarget : void 0,
|
|
2641
|
+
shareUrl,
|
|
2642
|
+
onZoomIn: zoomInRef.current,
|
|
2643
|
+
onAddChild: readOnly || !criteria ? void 0 : () => handleGenreCreationAction(criteria),
|
|
2644
|
+
onRename: readOnly || !criteria ? void 0 : () => handleGenreRenameAction(criteria),
|
|
2645
|
+
onChangeParent: readOnly || !genrePlaylist || !criteria ? void 0 : () => showPopup(
|
|
2646
|
+
/* @__PURE__ */ jsx34(
|
|
2647
|
+
ChangeParentPopup,
|
|
2460
2648
|
{
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
ancestors
|
|
2464
|
-
] }),
|
|
2465
|
-
genreUuid,
|
|
2466
|
-
genrePlaylist,
|
|
2467
|
-
catalogue: genrePlaylist.catalogue,
|
|
2468
|
-
wikidataId: criteria.wikidataId,
|
|
2649
|
+
genrePlaylist: { ...genrePlaylist, criteria },
|
|
2650
|
+
genrePlaylists: visibleGenrePlaylists,
|
|
2469
2651
|
scope,
|
|
2470
2652
|
getBackendBaseUrl,
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
node,
|
|
2474
|
-
shareTarget: shareUrl ? shareTarget : void 0,
|
|
2475
|
-
shareUrl,
|
|
2476
|
-
onZoomIn: zoomInRef.current,
|
|
2477
|
-
onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
|
|
2478
|
-
onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
|
|
2479
|
-
onChangeParent: readOnly ? void 0 : () => showPopup(
|
|
2480
|
-
/* @__PURE__ */ jsx32(
|
|
2481
|
-
ChangeParentPopup,
|
|
2482
|
-
{
|
|
2483
|
-
genrePlaylist: { ...genrePlaylist, criteria },
|
|
2484
|
-
genrePlaylists: visibleGenrePlaylists,
|
|
2485
|
-
scope,
|
|
2486
|
-
getBackendBaseUrl,
|
|
2487
|
-
onClose: hidePopup,
|
|
2488
|
-
onParentChanged
|
|
2489
|
-
}
|
|
2490
|
-
)
|
|
2491
|
-
),
|
|
2492
|
-
side,
|
|
2493
|
-
onToggleSide: canToggleSide ? () => updateGenre({ uuid: genreUuid, data: { side: side === "pop" ? "core" : "pop" } }) : void 0,
|
|
2494
|
-
toggleSideDisabledReason: popSibling ? `${popSibling.name} is already the pop side of ${parentPlaylist?.name}` : void 0,
|
|
2495
|
-
additionalActions
|
|
2496
|
-
},
|
|
2497
|
-
extras: {
|
|
2498
|
-
criteriaOverviewSchema,
|
|
2499
|
-
renderGenreDetailExtras,
|
|
2500
|
-
renderArtistLink,
|
|
2501
|
-
renderSongLink: resolvedRenderSongLink,
|
|
2502
|
-
artist: filterArtist,
|
|
2503
|
-
renderSummary,
|
|
2504
|
-
renderEssentialTracks,
|
|
2505
|
-
renderSongLeading,
|
|
2506
|
-
renderSongActions,
|
|
2507
|
-
renderTracksToolbar
|
|
2508
|
-
}
|
|
2653
|
+
onClose: hidePopup,
|
|
2654
|
+
onParentChanged
|
|
2509
2655
|
}
|
|
2510
|
-
)
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2656
|
+
)
|
|
2657
|
+
),
|
|
2658
|
+
side,
|
|
2659
|
+
onToggleSide: canToggleSide && criteria ? () => updateGenre({ uuid: criteria.uuid, data: { side: side === "pop" ? "core" : "pop" } }) : void 0,
|
|
2660
|
+
toggleSideDisabledReason: popSibling ? `${popSibling.name} is already the pop side of ${parentPlaylist?.name}` : void 0,
|
|
2661
|
+
additionalActions
|
|
2662
|
+
},
|
|
2663
|
+
extras: {
|
|
2664
|
+
criteriaOverviewSchema,
|
|
2665
|
+
renderGenreDetailExtras,
|
|
2666
|
+
renderArtistLink,
|
|
2667
|
+
renderSongLink: resolvedRenderSongLink,
|
|
2668
|
+
artist: filterArtist,
|
|
2669
|
+
renderSummary,
|
|
2670
|
+
renderEssentialTracks,
|
|
2671
|
+
renderSongLeading,
|
|
2672
|
+
renderSongActions,
|
|
2673
|
+
renderTracksToolbar
|
|
2674
|
+
},
|
|
2675
|
+
trailing: relations ? renderDangerZone?.(relations.node) : void 0
|
|
2676
|
+
});
|
|
2515
2677
|
const isRotationControlled = controlledAllowWheelRotation !== void 0;
|
|
2516
2678
|
const allowWheelRotation = controlledAllowWheelRotation ?? internalAllowWheelRotation;
|
|
2517
2679
|
let activePanel = null;
|
|
@@ -2525,16 +2687,16 @@ function GenreTreeView({
|
|
|
2525
2687
|
onClose: overlayPanel.onClose,
|
|
2526
2688
|
compact: false
|
|
2527
2689
|
};
|
|
2528
|
-
} else if (
|
|
2529
|
-
const { primary } = renderGenreBody(relations);
|
|
2690
|
+
} else if (selectedGenreUuid) {
|
|
2530
2691
|
activePanel = {
|
|
2531
|
-
title:
|
|
2532
|
-
content:
|
|
2692
|
+
title: genreContent.title,
|
|
2693
|
+
content: genreContent.content,
|
|
2694
|
+
aside: genreContent.aside,
|
|
2533
2695
|
expanded: isExpanded,
|
|
2534
2696
|
onExpandedChange: changeExpanded,
|
|
2535
2697
|
onClose: clearSelection,
|
|
2536
|
-
compact:
|
|
2537
|
-
headerAccent: { background: relations.fill, color: relations.textColor }
|
|
2698
|
+
compact: false,
|
|
2699
|
+
headerAccent: relations ? { background: relations.fill, color: relations.textColor } : genreContent.headerAccent
|
|
2538
2700
|
};
|
|
2539
2701
|
} else if (builtInPanel) {
|
|
2540
2702
|
activePanel = {
|
|
@@ -2547,36 +2709,36 @@ function GenreTreeView({
|
|
|
2547
2709
|
compact: true
|
|
2548
2710
|
};
|
|
2549
2711
|
}
|
|
2550
|
-
const isPanelShown =
|
|
2712
|
+
const isPanelShown = activePanel !== null && (selectedGenreUuid !== null || isTreeReady);
|
|
2551
2713
|
const detailTab = placement === "regions" && activePanel?.onExpandedChange ? {
|
|
2552
2714
|
edge: activePanel.expanded ? { label: "Shrink panel", onClick: () => activePanel.onExpandedChange?.(false) } : isCollapsed ? null : { label: "Hide panel", onClick: () => changeCollapsed(true) },
|
|
2553
2715
|
center: activePanel.expanded ? null : isCollapsed ? { label: "Show panel", onClick: () => changeCollapsed(false) } : activePanel.onExpandedChange ? { label: "Extend panel", onClick: () => activePanel.onExpandedChange?.(true) } : null
|
|
2554
2716
|
} : void 0;
|
|
2555
|
-
const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */
|
|
2556
|
-
|
|
2717
|
+
const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ jsx34(
|
|
2718
|
+
IconButton2,
|
|
2557
2719
|
{
|
|
2558
2720
|
"aria-label": activePanel.expanded ? "Shrink panel" : "Extend panel",
|
|
2559
2721
|
style: activePanel.headerAccent && { color: "inherit" },
|
|
2560
2722
|
onClick: () => activePanel.onExpandedChange?.(!activePanel.expanded),
|
|
2561
|
-
children: activePanel.expanded ? /* @__PURE__ */
|
|
2723
|
+
children: activePanel.expanded ? /* @__PURE__ */ jsx34(Minimize22, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ jsx34(Maximize22, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
2562
2724
|
}
|
|
2563
2725
|
) : null;
|
|
2564
2726
|
const renderArtistFilter = () => {
|
|
2565
2727
|
if (isLoading || !filterArtist) return null;
|
|
2566
|
-
return /* @__PURE__ */
|
|
2567
|
-
/* @__PURE__ */
|
|
2728
|
+
return /* @__PURE__ */ jsxs26("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
|
|
2729
|
+
/* @__PURE__ */ jsx34(
|
|
2568
2730
|
"button",
|
|
2569
2731
|
{
|
|
2570
2732
|
type: "button",
|
|
2571
2733
|
className: "py-1 pl-3 pr-2 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] hover:underline",
|
|
2572
2734
|
"aria-label": `Show artist ${filterArtist.name}`,
|
|
2573
2735
|
onClick: () => {
|
|
2574
|
-
if (
|
|
2736
|
+
if (selectedGenreUuid !== null) clearSelection();
|
|
2575
2737
|
},
|
|
2576
2738
|
children: filterArtist.name
|
|
2577
2739
|
}
|
|
2578
2740
|
),
|
|
2579
|
-
/* @__PURE__ */
|
|
2741
|
+
/* @__PURE__ */ jsx34(
|
|
2580
2742
|
"button",
|
|
2581
2743
|
{
|
|
2582
2744
|
type: "button",
|
|
@@ -2588,26 +2750,41 @@ function GenreTreeView({
|
|
|
2588
2750
|
)
|
|
2589
2751
|
] });
|
|
2590
2752
|
};
|
|
2591
|
-
const actions = /* @__PURE__ */
|
|
2592
|
-
!isLoading && /* @__PURE__ */
|
|
2593
|
-
|
|
2753
|
+
const actions = /* @__PURE__ */ jsxs26(Fragment14, { children: [
|
|
2754
|
+
!isLoading && /* @__PURE__ */ jsx34(
|
|
2755
|
+
GenreTreeSearch,
|
|
2594
2756
|
{
|
|
2757
|
+
artistBaseUrl,
|
|
2758
|
+
songBaseUrl,
|
|
2759
|
+
artistSearch: Boolean(artistSearch),
|
|
2760
|
+
songSearch: Boolean(songSearch),
|
|
2761
|
+
onArtistSelect: (artist) => {
|
|
2762
|
+
openArtist(artist.uuid);
|
|
2763
|
+
onArtistSelect?.(artist);
|
|
2764
|
+
},
|
|
2765
|
+
onSongSelect: (song) => {
|
|
2766
|
+
openSong(song.uuid);
|
|
2767
|
+
onSongSelect?.(song);
|
|
2768
|
+
},
|
|
2769
|
+
SearchGroups,
|
|
2595
2770
|
genrePlaylists: visibleGenrePlaylists,
|
|
2596
2771
|
onSelect: handleGenreSearchSelect,
|
|
2597
|
-
|
|
2772
|
+
onSelectGenre: (uuid) => {
|
|
2773
|
+
reopenPanel();
|
|
2774
|
+
if (songUuid) changeSong(null);
|
|
2775
|
+
selectGenre(uuid);
|
|
2776
|
+
},
|
|
2777
|
+
selectedName: (selectedGenreUuid ? genreContent.title : null) ?? (typeof builtInPanel?.title === "string" ? builtInPanel.title : null),
|
|
2598
2778
|
onClear: builtInSongPanel ? () => changeSong(null) : builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
|
|
2599
2779
|
onPrevious: handlePrevious,
|
|
2600
|
-
onQueryChange:
|
|
2601
|
-
setSearchText(text);
|
|
2602
|
-
onSearchQueryChange?.(text);
|
|
2603
|
-
},
|
|
2780
|
+
onQueryChange: onSearchQueryChange,
|
|
2604
2781
|
onFocus: onSearchFocus,
|
|
2605
|
-
extraGroups
|
|
2782
|
+
extraGroups: extraSearchGroups
|
|
2606
2783
|
}
|
|
2607
2784
|
),
|
|
2608
|
-
!isLoading && filterArtist && /* @__PURE__ */
|
|
2609
|
-
/* @__PURE__ */
|
|
2610
|
-
!isLoading && !isControlled && /* @__PURE__ */
|
|
2785
|
+
!isLoading && filterArtist && /* @__PURE__ */ jsx34("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
|
|
2786
|
+
/* @__PURE__ */ jsxs26("div", { className: "contents max-md:flex max-md:w-full max-md:items-center max-md:gap-2 max-md:overflow-x-auto max-md:[scrollbar-width:none] max-md:[&>*]:shrink-0", children: [
|
|
2787
|
+
!isLoading && !isControlled && /* @__PURE__ */ jsx34(
|
|
2611
2788
|
SegmentedControl2,
|
|
2612
2789
|
{
|
|
2613
2790
|
"aria-label": "Tree view mode",
|
|
@@ -2621,13 +2798,13 @@ function GenreTreeView({
|
|
|
2621
2798
|
]
|
|
2622
2799
|
}
|
|
2623
2800
|
),
|
|
2624
|
-
!isLoading && showDisplayOptions && /* @__PURE__ */
|
|
2801
|
+
!isLoading && showDisplayOptions && /* @__PURE__ */ jsx34(
|
|
2625
2802
|
"div",
|
|
2626
2803
|
{
|
|
2627
2804
|
className: "flex items-center gap-1",
|
|
2628
2805
|
role: "group",
|
|
2629
2806
|
"aria-label": "Tree display options",
|
|
2630
|
-
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */
|
|
2807
|
+
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ jsx34(
|
|
2631
2808
|
Button,
|
|
2632
2809
|
{
|
|
2633
2810
|
variant: allowWheelRotation ? "default" : "outline",
|
|
@@ -2638,7 +2815,7 @@ function GenreTreeView({
|
|
|
2638
2815
|
)
|
|
2639
2816
|
}
|
|
2640
2817
|
),
|
|
2641
|
-
!isLoading && !readOnly && showAddRoot && /* @__PURE__ */
|
|
2818
|
+
!isLoading && !readOnly && showAddRoot && /* @__PURE__ */ jsx34(
|
|
2642
2819
|
IconTextButton,
|
|
2643
2820
|
{
|
|
2644
2821
|
icon: Plus2,
|
|
@@ -2648,34 +2825,34 @@ function GenreTreeView({
|
|
|
2648
2825
|
)
|
|
2649
2826
|
] })
|
|
2650
2827
|
] });
|
|
2651
|
-
return /* @__PURE__ */
|
|
2828
|
+
return /* @__PURE__ */ jsxs26(
|
|
2652
2829
|
"div",
|
|
2653
2830
|
{
|
|
2654
2831
|
"data-left-panel": isPanelShown && !isCollapsed ? void 0 : "hidden",
|
|
2655
2832
|
className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
|
|
2656
2833
|
children: [
|
|
2657
|
-
/* @__PURE__ */
|
|
2834
|
+
/* @__PURE__ */ jsx34(
|
|
2658
2835
|
"div",
|
|
2659
2836
|
{
|
|
2660
2837
|
className: `actions-container absolute z-30 flex items-center gap-2 ${placement === "regions" ? "left-[calc(var(--layout-gutter)+var(--layout-panel-inset))] top-[calc(var(--layout-gutter)+var(--layout-panel-inset))] max-md:right-[calc(var(--layout-gutter)+var(--layout-panel-inset))] max-md:top-[var(--layout-band-top)] max-md:flex-col max-md:items-stretch" : "left-5 top-5"}`,
|
|
2661
2838
|
children: actions
|
|
2662
2839
|
}
|
|
2663
2840
|
),
|
|
2664
|
-
!isLoading && filterArtist && /* @__PURE__ */
|
|
2841
|
+
!isLoading && filterArtist && /* @__PURE__ */ jsx34(
|
|
2665
2842
|
"div",
|
|
2666
2843
|
{
|
|
2667
2844
|
className: `absolute left-1/2 z-30 hidden -translate-x-1/2 md:block ${placement === "regions" ? "top-[calc(var(--layout-gutter)+var(--layout-panel-inset))]" : "top-5"}`,
|
|
2668
2845
|
children: renderArtistFilter()
|
|
2669
2846
|
}
|
|
2670
2847
|
),
|
|
2671
|
-
/* @__PURE__ */
|
|
2672
|
-
!isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */
|
|
2673
|
-
isLoading ? /* @__PURE__ */
|
|
2848
|
+
/* @__PURE__ */ jsx34("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ jsxs26("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
|
|
2849
|
+
!isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ jsx34("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
|
|
2850
|
+
isLoading ? /* @__PURE__ */ jsx34(GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
|
|
2674
2851
|
// The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
|
|
2675
2852
|
// and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
|
|
2676
2853
|
// Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
|
|
2677
2854
|
// The panel's own Zoom in button is hidden: the search bar carries it.
|
|
2678
|
-
/* @__PURE__ */
|
|
2855
|
+
/* @__PURE__ */ jsx34("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ jsx34(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ jsx34(GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ jsx34(
|
|
2679
2856
|
GenrePlaylistTreeWheel,
|
|
2680
2857
|
{
|
|
2681
2858
|
scope,
|
|
@@ -2692,9 +2869,9 @@ function GenreTreeView({
|
|
|
2692
2869
|
allowWheelRotation,
|
|
2693
2870
|
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2694
2871
|
busy,
|
|
2695
|
-
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */
|
|
2872
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ jsxs26("div", { className: "flex items-center gap-2", children: [
|
|
2696
2873
|
floatingControlsLeading,
|
|
2697
|
-
canSizeByViews && /* @__PURE__ */
|
|
2874
|
+
canSizeByViews && /* @__PURE__ */ jsx34(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2698
2875
|
] }) : void 0,
|
|
2699
2876
|
defaultViewport,
|
|
2700
2877
|
viewportRestore,
|
|
@@ -2703,7 +2880,7 @@ function GenreTreeView({
|
|
|
2703
2880
|
) }) })
|
|
2704
2881
|
) : viewMode === "pop-core" ? (
|
|
2705
2882
|
// Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
|
|
2706
|
-
/* @__PURE__ */
|
|
2883
|
+
/* @__PURE__ */ jsx34("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ jsx34(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ jsx34(GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ jsx34(
|
|
2707
2884
|
GenrePlaylistTreeWheelRadialPopCore,
|
|
2708
2885
|
{
|
|
2709
2886
|
scope,
|
|
@@ -2720,9 +2897,9 @@ function GenreTreeView({
|
|
|
2720
2897
|
allowWheelRotation,
|
|
2721
2898
|
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2722
2899
|
busy,
|
|
2723
|
-
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */
|
|
2900
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ jsxs26("div", { className: "flex items-center gap-2", children: [
|
|
2724
2901
|
floatingControlsLeading,
|
|
2725
|
-
canSizeByViews && /* @__PURE__ */
|
|
2902
|
+
canSizeByViews && /* @__PURE__ */ jsx34(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2726
2903
|
] }) : void 0,
|
|
2727
2904
|
defaultViewport,
|
|
2728
2905
|
viewportRestore,
|
|
@@ -2730,7 +2907,7 @@ function GenreTreeView({
|
|
|
2730
2907
|
...focus
|
|
2731
2908
|
}
|
|
2732
2909
|
) }) })
|
|
2733
|
-
) : viewMode === "outline" ? /* @__PURE__ */
|
|
2910
|
+
) : viewMode === "outline" ? /* @__PURE__ */ jsx34("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ jsx34(
|
|
2734
2911
|
GenrePlaylistTreeWheelRadialPopCore,
|
|
2735
2912
|
{
|
|
2736
2913
|
outline: true,
|
|
@@ -2746,13 +2923,13 @@ function GenreTreeView({
|
|
|
2746
2923
|
selectedNodeId,
|
|
2747
2924
|
readOnly
|
|
2748
2925
|
}
|
|
2749
|
-
) }) : /* @__PURE__ */
|
|
2926
|
+
) }) : /* @__PURE__ */ jsx34("div", { className: "tree-container relative flex flex-col gap-4 w-full pt-14 overflow-x-auto overflow-y-auto text-[var(--color-text)]", children: Object.entries(groupedGenrePlaylistsByRoot).map(
|
|
2750
2927
|
([uuid, genrePlaylistTreePerRoot]) => {
|
|
2751
|
-
return /* @__PURE__ */
|
|
2928
|
+
return /* @__PURE__ */ jsx34(
|
|
2752
2929
|
"div",
|
|
2753
2930
|
{
|
|
2754
2931
|
className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
|
|
2755
|
-
children: /* @__PURE__ */
|
|
2932
|
+
children: /* @__PURE__ */ jsx34("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ jsx34(
|
|
2756
2933
|
GenrePlaylistTreePerRoot,
|
|
2757
2934
|
{
|
|
2758
2935
|
scope,
|
|
@@ -2778,8 +2955,8 @@ function GenreTreeView({
|
|
|
2778
2955
|
);
|
|
2779
2956
|
}
|
|
2780
2957
|
) }),
|
|
2781
|
-
/* @__PURE__ */
|
|
2782
|
-
|
|
2958
|
+
/* @__PURE__ */ jsx34(
|
|
2959
|
+
DetailPanel2,
|
|
2783
2960
|
{
|
|
2784
2961
|
ref: panelRef,
|
|
2785
2962
|
side: "left",
|
|
@@ -2789,13 +2966,13 @@ function GenreTreeView({
|
|
|
2789
2966
|
headerAccent: activePanel?.headerAccent,
|
|
2790
2967
|
headerActions: expandButton,
|
|
2791
2968
|
onClose: activePanel?.onClose ?? noop,
|
|
2792
|
-
collapsed: isCollapsed || !
|
|
2969
|
+
collapsed: isCollapsed || !isPanelShown,
|
|
2793
2970
|
expanded: activePanel?.expanded,
|
|
2794
2971
|
onExpandedChange: activePanel?.onExpandedChange,
|
|
2795
2972
|
aside: activePanel?.aside,
|
|
2796
2973
|
edgeTab: detailTab,
|
|
2797
|
-
headerClassName:
|
|
2798
|
-
children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */
|
|
2974
|
+
headerClassName: "pt-[calc(var(--layout-search-h)+var(--layout-panel-inset)+0.5rem)]",
|
|
2975
|
+
children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ jsxs26(Fragment14, { children: [
|
|
2799
2976
|
activePanel.content,
|
|
2800
2977
|
activePanel.aside
|
|
2801
2978
|
] }) : activePanel?.content
|
|
@@ -2807,145 +2984,6 @@ function GenreTreeView({
|
|
|
2807
2984
|
);
|
|
2808
2985
|
}
|
|
2809
2986
|
|
|
2810
|
-
// src/genre-tree/GenreDetailPanel.tsx
|
|
2811
|
-
import { useMemo as useMemo8 } from "react";
|
|
2812
|
-
import {
|
|
2813
|
-
ACCENT_TEXT_COLOR,
|
|
2814
|
-
GenreNodeAncestors as GenreNodeAncestors2,
|
|
2815
|
-
GenreNodeFamily as GenreNodeFamily2,
|
|
2816
|
-
getGenreTreeColor as getGenreTreeColor2,
|
|
2817
|
-
TEXT_COLOR,
|
|
2818
|
-
tintSurface
|
|
2819
|
-
} from "@behindthemusictree/genre-tree-view";
|
|
2820
|
-
import { DetailPanel as DetailPanel2, IconButton as IconButton2, Skeleton as Skeleton7 } from "@behindthemusictree/ui";
|
|
2821
|
-
import { Maximize2 as Maximize22, Minimize2 as Minimize22 } from "lucide-react";
|
|
2822
|
-
import {
|
|
2823
|
-
useFetchGenreDetail,
|
|
2824
|
-
genreMusicCount as genreMusicCount2,
|
|
2825
|
-
GenrePlaylistSummarySchema
|
|
2826
|
-
} from "@behindthemusictree/app-kit/genre-tree";
|
|
2827
|
-
import { Fragment as Fragment14, jsx as jsx33, jsxs as jsxs26 } from "react/jsx-runtime";
|
|
2828
|
-
var RELATIVE_ITEM_COUNT = 1;
|
|
2829
|
-
function toNode(genre, parentId) {
|
|
2830
|
-
return {
|
|
2831
|
-
id: genre.uuid,
|
|
2832
|
-
parentId,
|
|
2833
|
-
name: genre.name,
|
|
2834
|
-
itemCount: RELATIVE_ITEM_COUNT,
|
|
2835
|
-
side: genre.side ?? void 0,
|
|
2836
|
-
wikidataId: genre.wikidataId
|
|
2837
|
-
};
|
|
2838
|
-
}
|
|
2839
|
-
function GenreDetailPanel({
|
|
2840
|
-
genreUuid,
|
|
2841
|
-
scope,
|
|
2842
|
-
getBackendBaseUrl,
|
|
2843
|
-
onClose,
|
|
2844
|
-
onSelectGenre,
|
|
2845
|
-
onZoomIn,
|
|
2846
|
-
shareUrl,
|
|
2847
|
-
shareTarget,
|
|
2848
|
-
collapsed,
|
|
2849
|
-
expanded,
|
|
2850
|
-
onExpandedChange,
|
|
2851
|
-
edgeTab,
|
|
2852
|
-
extras
|
|
2853
|
-
}) {
|
|
2854
|
-
const { data: detail, isPending, isError } = useFetchGenreDetail(genreUuid, scope, getBackendBaseUrl);
|
|
2855
|
-
const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: [] });
|
|
2856
|
-
const view = useMemo8(() => {
|
|
2857
|
-
if (!detail) return null;
|
|
2858
|
-
const rootColor = getGenreTreeColor2(detail.root.wikidataId);
|
|
2859
|
-
const relative = (genre, parentId) => ({
|
|
2860
|
-
node: toNode(genre, parentId),
|
|
2861
|
-
fill: tintSurface(rootColor),
|
|
2862
|
-
textColor: TEXT_COLOR
|
|
2863
|
-
});
|
|
2864
|
-
const ancestors2 = [...detail.ascendants].sort((a, b) => b.degree - a.degree);
|
|
2865
|
-
return {
|
|
2866
|
-
node: {
|
|
2867
|
-
id: detail.uuid,
|
|
2868
|
-
parentId: detail.parent?.uuid ?? null,
|
|
2869
|
-
name: detail.name,
|
|
2870
|
-
itemCount: scope === "reference" ? genreMusicCount2(GenrePlaylistSummarySchema.parse(detail.criteriaPlaylist)) : detail.tracksCount,
|
|
2871
|
-
wikidataId: detail.wikidataId
|
|
2872
|
-
},
|
|
2873
|
-
fill: rootColor,
|
|
2874
|
-
parentNode: detail.parent ? relative(detail.parent, null) : null,
|
|
2875
|
-
childNodes: detail.children.map((child) => relative(child, detail.uuid)),
|
|
2876
|
-
ancestorNodes: ancestors2.map(
|
|
2877
|
-
({ ascendant }, index) => relative(ascendant, index === 0 ? null : ancestors2[index - 1].ascendant.uuid)
|
|
2878
|
-
)
|
|
2879
|
-
};
|
|
2880
|
-
}, [detail, scope]);
|
|
2881
|
-
if (isError) {
|
|
2882
|
-
return /* @__PURE__ */ jsx33("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
|
|
2883
|
-
}
|
|
2884
|
-
if (isPending || !detail || !view) {
|
|
2885
|
-
return /* @__PURE__ */ jsxs26("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
|
|
2886
|
-
/* @__PURE__ */ jsx33(Skeleton7, { className: "h-6 w-2/3" }),
|
|
2887
|
-
/* @__PURE__ */ jsx33(Skeleton7, { className: "h-4 w-full" }),
|
|
2888
|
-
/* @__PURE__ */ jsx33(Skeleton7, { className: "h-4 w-3/4" })
|
|
2889
|
-
] });
|
|
2890
|
-
}
|
|
2891
|
-
const selectGenre = (nodeId) => onSelectGenre(nodeId);
|
|
2892
|
-
const family = /* @__PURE__ */ jsx33(
|
|
2893
|
-
GenreNodeFamily2,
|
|
2894
|
-
{
|
|
2895
|
-
node: view.node,
|
|
2896
|
-
fill: view.fill,
|
|
2897
|
-
textColor: ACCENT_TEXT_COLOR,
|
|
2898
|
-
parentNode: view.parentNode,
|
|
2899
|
-
childNodes: view.childNodes,
|
|
2900
|
-
onSelectNode: selectGenre
|
|
2901
|
-
}
|
|
2902
|
-
);
|
|
2903
|
-
const ancestors = /* @__PURE__ */ jsx33(GenreNodeAncestors2, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
|
|
2904
|
-
return /* @__PURE__ */ jsxs26(
|
|
2905
|
-
DetailPanel2,
|
|
2906
|
-
{
|
|
2907
|
-
side: "left",
|
|
2908
|
-
title: view.node.name,
|
|
2909
|
-
headerAccent: { background: view.fill, color: ACCENT_TEXT_COLOR },
|
|
2910
|
-
headerActions: onExpandedChange ? /* @__PURE__ */ jsx33(
|
|
2911
|
-
IconButton2,
|
|
2912
|
-
{
|
|
2913
|
-
"aria-label": expanded ? "Shrink panel" : "Extend panel",
|
|
2914
|
-
style: { color: "inherit" },
|
|
2915
|
-
onClick: () => onExpandedChange(!expanded),
|
|
2916
|
-
children: expanded ? /* @__PURE__ */ jsx33(Minimize22, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ jsx33(Maximize22, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
2917
|
-
}
|
|
2918
|
-
) : void 0,
|
|
2919
|
-
onClose,
|
|
2920
|
-
collapsed,
|
|
2921
|
-
expanded,
|
|
2922
|
-
onExpandedChange,
|
|
2923
|
-
edgeTab,
|
|
2924
|
-
children: [
|
|
2925
|
-
/* @__PURE__ */ jsx33(
|
|
2926
|
-
GenreInfoPanelBody,
|
|
2927
|
-
{
|
|
2928
|
-
family: /* @__PURE__ */ jsxs26(Fragment14, { children: [
|
|
2929
|
-
family,
|
|
2930
|
-
ancestors
|
|
2931
|
-
] }),
|
|
2932
|
-
genreUuid: detail.uuid,
|
|
2933
|
-
genrePlaylist: scope === "reference" ? GenrePlaylistSummarySchema.parse(detail.criteriaPlaylist) : { ...detail.criteriaPlaylist, tracksCount: detail.tracksCount },
|
|
2934
|
-
catalogue: detail.catalogue,
|
|
2935
|
-
wikidataId: detail.wikidataId,
|
|
2936
|
-
scope,
|
|
2937
|
-
getBackendBaseUrl,
|
|
2938
|
-
playback,
|
|
2939
|
-
band: { node: view.node, onZoomIn, shareUrl, shareTarget: shareUrl ? shareTarget : void 0 },
|
|
2940
|
-
extras
|
|
2941
|
-
}
|
|
2942
|
-
),
|
|
2943
|
-
!expanded && ancestors
|
|
2944
|
-
]
|
|
2945
|
-
}
|
|
2946
|
-
);
|
|
2947
|
-
}
|
|
2948
|
-
|
|
2949
2987
|
// src/genre-tree/index.ts
|
|
2950
2988
|
import {
|
|
2951
2989
|
GenreTreeSkeleton,
|
|
@@ -2960,7 +2998,7 @@ import { ListMusic } from "lucide-react";
|
|
|
2960
2998
|
import { useTrackList as useTrackList6 } from "@behindthemusictree/app-kit/genre-tree";
|
|
2961
2999
|
import { TrackListOriginType as TrackListOriginType2 } from "@behindthemusictree/app-kit/genre-tree";
|
|
2962
3000
|
import { useTrackNoun as useTrackNoun4 } from "@behindthemusictree/app-kit/track-noun";
|
|
2963
|
-
import { jsx as
|
|
3001
|
+
import { jsx as jsx35, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
2964
3002
|
var useBrowserLayoutEffect = typeof window === "undefined" ? useEffect6 : useLayoutEffect2;
|
|
2965
3003
|
function TrackListSidebar({
|
|
2966
3004
|
className: className2 = "",
|
|
@@ -3072,15 +3110,15 @@ function TrackListSidebar({
|
|
|
3072
3110
|
/* @__PURE__ */ jsxs27("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
|
|
3073
3111
|
/* @__PURE__ */ jsxs27("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
|
|
3074
3112
|
/* @__PURE__ */ jsxs27("div", { className: "origin flex text-xl", children: [
|
|
3075
|
-
/* @__PURE__ */
|
|
3076
|
-
/* @__PURE__ */
|
|
3113
|
+
/* @__PURE__ */ jsx35("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
|
|
3114
|
+
/* @__PURE__ */ jsx35("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__ */ jsx35("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
|
|
3077
3115
|
] }),
|
|
3078
3116
|
/* @__PURE__ */ jsxs27("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
|
|
3079
3117
|
trackList.origin.type === TrackListOriginType2.GENRE_PLAYLIST ? "Genre \u2022 " : trackList.origin.type === TrackListOriginType2.ARTIST ? "Artist \u2022 " : `${trackNoun.singular} playlist \u2022 `,
|
|
3080
3118
|
`${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`
|
|
3081
3119
|
] })
|
|
3082
3120
|
] }),
|
|
3083
|
-
(isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */
|
|
3121
|
+
(isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ jsx35(
|
|
3084
3122
|
"button",
|
|
3085
3123
|
{
|
|
3086
3124
|
type: "button",
|
|
@@ -3089,7 +3127,7 @@ function TrackListSidebar({
|
|
|
3089
3127
|
"aria-expanded": !isCollapsed,
|
|
3090
3128
|
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"}`,
|
|
3091
3129
|
onClick: isCollapsed ? onExpand : onMinimize,
|
|
3092
|
-
children: /* @__PURE__ */
|
|
3130
|
+
children: /* @__PURE__ */ jsx35(ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
|
|
3093
3131
|
}
|
|
3094
3132
|
)
|
|
3095
3133
|
] }),
|
|
@@ -3103,7 +3141,7 @@ function TrackListSidebar({
|
|
|
3103
3141
|
onPointerDown: markUserScrollIntent,
|
|
3104
3142
|
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",
|
|
3105
3143
|
children: [
|
|
3106
|
-
trackList.tracks.map((track, index) => /* @__PURE__ */
|
|
3144
|
+
trackList.tracks.map((track, index) => /* @__PURE__ */ jsx35("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ jsx35(
|
|
3107
3145
|
TrackItem,
|
|
3108
3146
|
{
|
|
3109
3147
|
track,
|
|
@@ -3114,7 +3152,7 @@ function TrackListSidebar({
|
|
|
3114
3152
|
renderSongLink
|
|
3115
3153
|
}
|
|
3116
3154
|
) }, track.uuid)),
|
|
3117
|
-
hasMore && /* @__PURE__ */
|
|
3155
|
+
hasMore && /* @__PURE__ */ jsx35("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
|
|
3118
3156
|
]
|
|
3119
3157
|
}
|
|
3120
3158
|
)
|
|
@@ -3124,7 +3162,7 @@ function TrackListSidebar({
|
|
|
3124
3162
|
}
|
|
3125
3163
|
|
|
3126
3164
|
// src/genre-tree/PlayerPanel.tsx
|
|
3127
|
-
import { useEffect as useEffect7, useState as
|
|
3165
|
+
import { useEffect as useEffect7, useState as useState10 } from "react";
|
|
3128
3166
|
import { twMerge } from "tailwind-merge";
|
|
3129
3167
|
import {
|
|
3130
3168
|
usePlayer as usePlayer11,
|
|
@@ -3139,7 +3177,7 @@ import { PanelEdgeTab } from "@behindthemusictree/ui";
|
|
|
3139
3177
|
import { Pause as Pause3, Play as Play4, SkipBack, SkipForward, Volume2, VolumeX } from "lucide-react";
|
|
3140
3178
|
import { formatTime, useTrackList as useTrackList7 } from "@behindthemusictree/app-kit/genre-tree";
|
|
3141
3179
|
import { PlayStates as PlayStates4, useCurrentTime, usePlayer as usePlayer10 } from "@behindthemusictree/app-kit/player";
|
|
3142
|
-
import { jsx as
|
|
3180
|
+
import { jsx as jsx36, jsxs as jsxs28 } from "react/jsx-runtime";
|
|
3143
3181
|
function PlayerControls() {
|
|
3144
3182
|
const {
|
|
3145
3183
|
duration,
|
|
@@ -3165,8 +3203,8 @@ function PlayerControls() {
|
|
|
3165
3203
|
className: "player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200",
|
|
3166
3204
|
children: [
|
|
3167
3205
|
/* @__PURE__ */ jsxs28("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
|
|
3168
|
-
/* @__PURE__ */
|
|
3169
|
-
/* @__PURE__ */
|
|
3206
|
+
/* @__PURE__ */ jsx36("span", { "aria-label": "Elapsed time", children: formatTime(safeCurrentTime) }),
|
|
3207
|
+
/* @__PURE__ */ jsx36(
|
|
3170
3208
|
"input",
|
|
3171
3209
|
{
|
|
3172
3210
|
"aria-label": "Seek",
|
|
@@ -3180,11 +3218,11 @@ function PlayerControls() {
|
|
|
3180
3218
|
onChange: (event) => seekTo(Number(event.currentTarget.value))
|
|
3181
3219
|
}
|
|
3182
3220
|
),
|
|
3183
|
-
/* @__PURE__ */
|
|
3221
|
+
/* @__PURE__ */ jsx36("span", { "aria-label": "Duration", children: formatTime(safeDuration) })
|
|
3184
3222
|
] }),
|
|
3185
3223
|
/* @__PURE__ */ jsxs28("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
|
|
3186
3224
|
/* @__PURE__ */ jsxs28("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
|
|
3187
|
-
/* @__PURE__ */
|
|
3225
|
+
/* @__PURE__ */ jsx36(
|
|
3188
3226
|
"button",
|
|
3189
3227
|
{
|
|
3190
3228
|
type: "button",
|
|
@@ -3193,10 +3231,10 @@ function PlayerControls() {
|
|
|
3193
3231
|
disabled: !isReady || !hasPreviousTrack,
|
|
3194
3232
|
onClick: playPreviousTrack,
|
|
3195
3233
|
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",
|
|
3196
|
-
children: /* @__PURE__ */
|
|
3234
|
+
children: /* @__PURE__ */ jsx36(SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
3197
3235
|
}
|
|
3198
3236
|
),
|
|
3199
|
-
/* @__PURE__ */
|
|
3237
|
+
/* @__PURE__ */ jsx36(
|
|
3200
3238
|
"button",
|
|
3201
3239
|
{
|
|
3202
3240
|
type: "button",
|
|
@@ -3205,10 +3243,10 @@ function PlayerControls() {
|
|
|
3205
3243
|
disabled: !isReady,
|
|
3206
3244
|
onClick: handlePlayPauseAction,
|
|
3207
3245
|
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",
|
|
3208
|
-
children: isPlaying ? /* @__PURE__ */
|
|
3246
|
+
children: isPlaying ? /* @__PURE__ */ jsx36(Pause3, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ jsx36(Play4, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
|
|
3209
3247
|
}
|
|
3210
3248
|
),
|
|
3211
|
-
/* @__PURE__ */
|
|
3249
|
+
/* @__PURE__ */ jsx36(
|
|
3212
3250
|
"button",
|
|
3213
3251
|
{
|
|
3214
3252
|
type: "button",
|
|
@@ -3217,14 +3255,14 @@ function PlayerControls() {
|
|
|
3217
3255
|
disabled: !isReady || !hasNextTrack,
|
|
3218
3256
|
onClick: playNextTrack,
|
|
3219
3257
|
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",
|
|
3220
|
-
children: /* @__PURE__ */
|
|
3258
|
+
children: /* @__PURE__ */ jsx36(SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
3221
3259
|
}
|
|
3222
3260
|
)
|
|
3223
3261
|
] }),
|
|
3224
3262
|
/* @__PURE__ */ jsxs28("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: [
|
|
3225
|
-
volume === 0 ? /* @__PURE__ */
|
|
3226
|
-
/* @__PURE__ */
|
|
3227
|
-
/* @__PURE__ */
|
|
3263
|
+
volume === 0 ? /* @__PURE__ */ jsx36(VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ jsx36(Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
|
|
3264
|
+
/* @__PURE__ */ jsx36("span", { className: "sr-only", children: "Volume" }),
|
|
3265
|
+
/* @__PURE__ */ jsx36(
|
|
3228
3266
|
"input",
|
|
3229
3267
|
{
|
|
3230
3268
|
"aria-label": "Volume",
|
|
@@ -3246,7 +3284,7 @@ function PlayerControls() {
|
|
|
3246
3284
|
}
|
|
3247
3285
|
|
|
3248
3286
|
// src/genre-tree/PlayerPanel.tsx
|
|
3249
|
-
import { jsx as
|
|
3287
|
+
import { jsx as jsx37, jsxs as jsxs29 } from "react/jsx-runtime";
|
|
3250
3288
|
var PANEL_ID = "player-panel";
|
|
3251
3289
|
var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
|
|
3252
3290
|
function PlayerPanel({
|
|
@@ -3260,12 +3298,12 @@ function PlayerPanel({
|
|
|
3260
3298
|
}) {
|
|
3261
3299
|
const { playerTrackObject, retryPlayback } = usePlayer11();
|
|
3262
3300
|
const { toggleTrackListSidebar, isTrackListSidebarVisible } = useTrackListSidebarVisibility();
|
|
3263
|
-
const [lastLoadedTrackTitle, setLastLoadedTrackTitle] =
|
|
3301
|
+
const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = useState10("");
|
|
3264
3302
|
const loadedTrackTitle = playerTrackObject?.track.title;
|
|
3265
3303
|
useEffect7(() => {
|
|
3266
3304
|
if (loadedTrackTitle) setLastLoadedTrackTitle(loadedTrackTitle);
|
|
3267
3305
|
}, [loadedTrackTitle]);
|
|
3268
|
-
const [uncontrolledExtended, setUncontrolledExtended] =
|
|
3306
|
+
const [uncontrolledExtended, setUncontrolledExtended] = useState10(false);
|
|
3269
3307
|
const isExtendRequested = expanded ?? uncontrolledExtended;
|
|
3270
3308
|
const setIsExtendRequested = (next) => {
|
|
3271
3309
|
setUncontrolledExtended(next);
|
|
@@ -3299,7 +3337,7 @@ function PlayerPanel({
|
|
|
3299
3337
|
),
|
|
3300
3338
|
style: isExtended ? { width: extendedWidth } : void 0,
|
|
3301
3339
|
children: [
|
|
3302
|
-
/* @__PURE__ */
|
|
3340
|
+
/* @__PURE__ */ jsx37(
|
|
3303
3341
|
PanelEdgeTab,
|
|
3304
3342
|
{
|
|
3305
3343
|
side: "right",
|
|
@@ -3318,19 +3356,19 @@ function PlayerPanel({
|
|
|
3318
3356
|
),
|
|
3319
3357
|
children: [
|
|
3320
3358
|
/* @__PURE__ */ jsxs29("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
|
|
3321
|
-
/* @__PURE__ */
|
|
3359
|
+
/* @__PURE__ */ jsx37("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ jsx37("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
|
|
3322
3360
|
/* @__PURE__ */ jsxs29("div", { className: "relative w-full shrink-0", children: [
|
|
3323
|
-
/* @__PURE__ */
|
|
3361
|
+
/* @__PURE__ */ jsx37(PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
|
|
3324
3362
|
playerTrackObject?.loadError && /* @__PURE__ */ jsxs29("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: [
|
|
3325
3363
|
playerTrackObject.loadError,
|
|
3326
|
-
/* @__PURE__ */
|
|
3364
|
+
/* @__PURE__ */ jsx37("button", { type: "button", className: "ml-2 underline", onClick: () => {
|
|
3327
3365
|
void retryPlayback();
|
|
3328
3366
|
}, children: "Retry" })
|
|
3329
3367
|
] })
|
|
3330
3368
|
] }),
|
|
3331
|
-
/* @__PURE__ */
|
|
3369
|
+
/* @__PURE__ */ jsx37(PlayerControls, {})
|
|
3332
3370
|
] }),
|
|
3333
|
-
/* @__PURE__ */
|
|
3371
|
+
/* @__PURE__ */ jsx37(
|
|
3334
3372
|
TrackListSidebar,
|
|
3335
3373
|
{
|
|
3336
3374
|
layout: "inline",
|
|
@@ -3357,11 +3395,11 @@ function PlayerPanel({
|
|
|
3357
3395
|
import { v4 as uuidv4 } from "uuid";
|
|
3358
3396
|
import { AiFillStar, AiOutlineStar } from "react-icons/ai";
|
|
3359
3397
|
import { FORM_RATING_NULL_VALUE } from "@behindthemusictree/app-kit/genre-tree";
|
|
3360
|
-
import { jsx as
|
|
3398
|
+
import { jsx as jsx38, jsxs as jsxs30 } from "react/jsx-runtime";
|
|
3361
3399
|
function Rating({ rating, handleChange }) {
|
|
3362
3400
|
const instanceId = uuidv4();
|
|
3363
|
-
return /* @__PURE__ */
|
|
3364
|
-
/* @__PURE__ */
|
|
3401
|
+
return /* @__PURE__ */ jsx38("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ jsxs30("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
|
|
3402
|
+
/* @__PURE__ */ jsx38(
|
|
3365
3403
|
"input",
|
|
3366
3404
|
{
|
|
3367
3405
|
id: `star-${instanceId}-${i}`,
|
|
@@ -3373,16 +3411,16 @@ function Rating({ rating, handleChange }) {
|
|
|
3373
3411
|
onChange: handleChange
|
|
3374
3412
|
}
|
|
3375
3413
|
),
|
|
3376
|
-
/* @__PURE__ */
|
|
3377
|
-
/* @__PURE__ */
|
|
3414
|
+
/* @__PURE__ */ jsx38("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ jsxs30("div", { className: "flex", children: [
|
|
3415
|
+
/* @__PURE__ */ jsx38(
|
|
3378
3416
|
AiOutlineStar,
|
|
3379
3417
|
{
|
|
3380
3418
|
className: `w-7 h-7 absolute top-0 left-0 ${rating === FORM_RATING_NULL_VALUE ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
|
|
3381
3419
|
}
|
|
3382
3420
|
),
|
|
3383
|
-
/* @__PURE__ */
|
|
3421
|
+
/* @__PURE__ */ jsx38("span", { className: "flex-grow w-8" }),
|
|
3384
3422
|
" "
|
|
3385
|
-
] }) : /* @__PURE__ */
|
|
3423
|
+
] }) : /* @__PURE__ */ jsx38(
|
|
3386
3424
|
AiFillStar,
|
|
3387
3425
|
{
|
|
3388
3426
|
className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
|
|
@@ -3403,10 +3441,10 @@ export {
|
|
|
3403
3441
|
GenreSearch,
|
|
3404
3442
|
GenreNodeActionBand,
|
|
3405
3443
|
GenreInfoPanelBody,
|
|
3444
|
+
GenreDetailPanel,
|
|
3406
3445
|
TrackPositionPlayPause,
|
|
3407
3446
|
TrackItem,
|
|
3408
3447
|
GenreTreeView,
|
|
3409
|
-
GenreDetailPanel,
|
|
3410
3448
|
TrackListSidebar,
|
|
3411
3449
|
PlayerPanel,
|
|
3412
3450
|
Rating,
|
|
@@ -3415,4 +3453,4 @@ export {
|
|
|
3415
3453
|
GenreTreeViewSkeleton2 as GenreTreeViewSkeleton,
|
|
3416
3454
|
GenreTreeOutlineSkeleton
|
|
3417
3455
|
};
|
|
3418
|
-
//# sourceMappingURL=chunk-
|
|
3456
|
+
//# sourceMappingURL=chunk-YC3WBUVO.js.map
|