@behindthemusictree/app-kit-ui 29.0.1 → 30.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-TFLX6C5Q.js → chunk-2HE2HDFA.js} +552 -253
- package/dist/chunk-2HE2HDFA.js.map +1 -0
- package/dist/genre-tree/index.cjs +588 -299
- package/dist/genre-tree/index.cjs.map +1 -1
- package/dist/genre-tree/index.d.ts +40 -12
- package/dist/genre-tree/index.js +1 -1
- package/dist/index.cjs +585 -296
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +6 -6
- package/dist/chunk-TFLX6C5Q.js.map +0 -1
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from "./chunk-DKQQLLMY.js";
|
|
6
6
|
|
|
7
7
|
// src/genre-tree/GenreTreeView.tsx
|
|
8
|
-
import { useCallback as useCallback6, useDeferredValue, useEffect as useEffect6, useMemo as useMemo7, useRef as useRef4, useState as
|
|
8
|
+
import { useCallback as useCallback6, useDeferredValue, useEffect as useEffect6, useMemo as useMemo7, useRef as useRef4, useState as useState7 } from "react";
|
|
9
9
|
import { useIsMutating } from "@tanstack/react-query";
|
|
10
10
|
import { Plus as Plus2 } from "lucide-react";
|
|
11
11
|
import { IconTextButton, Button, SegmentedControl as SegmentedControl2 } from "@behindthemusictree/ui";
|
|
@@ -14,7 +14,9 @@ import {
|
|
|
14
14
|
GenreTreeViewSkeleton
|
|
15
15
|
} from "@behindthemusictree/genre-tree-view";
|
|
16
16
|
import {
|
|
17
|
-
useArtistSearch
|
|
17
|
+
useArtistSearch,
|
|
18
|
+
useSongSearch,
|
|
19
|
+
isSongSearchResultPlayable
|
|
18
20
|
} from "@behindthemusictree/app-kit/genre-tree";
|
|
19
21
|
import { useGenreTreeViewState, useArtist as useArtist2, useUpdateGenre as useUpdateGenre2, genreUpdateMutationKey } from "@behindthemusictree/app-kit/genre-tree";
|
|
20
22
|
import { usePopup as usePopup5 } from "@behindthemusictree/app-kit/popup";
|
|
@@ -499,10 +501,11 @@ function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
|
|
|
499
501
|
}
|
|
500
502
|
|
|
501
503
|
// src/genre-tree/GenreSearch.tsx
|
|
504
|
+
import { useState as useState2 } from "react";
|
|
502
505
|
import { ArrowLeft, LoaderCircle, Search, X } from "lucide-react";
|
|
503
|
-
import {
|
|
506
|
+
import { Combobox } from "@behindthemusictree/ui";
|
|
504
507
|
import { useGenreSearch } from "@behindthemusictree/app-kit/genre-tree";
|
|
505
|
-
import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
508
|
+
import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
506
509
|
function GenreSearch({
|
|
507
510
|
genrePlaylists,
|
|
508
511
|
onSelect,
|
|
@@ -511,7 +514,7 @@ function GenreSearch({
|
|
|
511
514
|
onPrevious,
|
|
512
515
|
placeholder,
|
|
513
516
|
onQueryChange,
|
|
514
|
-
|
|
517
|
+
extraGroups = []
|
|
515
518
|
}) {
|
|
516
519
|
const { query, setQuery, results, canClear, clear, select } = useGenreSearch({
|
|
517
520
|
genrePlaylists,
|
|
@@ -519,11 +522,45 @@ function GenreSearch({
|
|
|
519
522
|
selectedName,
|
|
520
523
|
onClear
|
|
521
524
|
});
|
|
522
|
-
const
|
|
523
|
-
const
|
|
524
|
-
const
|
|
525
|
-
const
|
|
526
|
-
const
|
|
525
|
+
const [open, setOpen] = useState2(false);
|
|
526
|
+
const active = query !== "" && query !== selectedName;
|
|
527
|
+
const hasExtras = extraGroups.length > 0;
|
|
528
|
+
const label = hasExtras ? "Search" : "Search a genre";
|
|
529
|
+
const loading = active && extraGroups.some((group) => group.loading);
|
|
530
|
+
const status = active ? extraGroups.map((group) => group.status).filter(Boolean).join(". ") : "";
|
|
531
|
+
const groups = [
|
|
532
|
+
{
|
|
533
|
+
key: "genres",
|
|
534
|
+
label: hasExtras ? "Genres" : void 0,
|
|
535
|
+
options: results.map((genrePlaylist) => ({
|
|
536
|
+
key: genrePlaylist.uuid,
|
|
537
|
+
content: /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
538
|
+
genrePlaylist.name,
|
|
539
|
+
genrePlaylist.isUmbrella && /* @__PURE__ */ jsx5("span", { className: "ml-2 text-xs italic text-[var(--color-text-muted)]", children: "umbrella" })
|
|
540
|
+
] })
|
|
541
|
+
}))
|
|
542
|
+
},
|
|
543
|
+
...extraGroups.map((group) => ({
|
|
544
|
+
key: group.key,
|
|
545
|
+
label: group.label,
|
|
546
|
+
options: active ? group.items.map((item) => ({ key: item.key, content: item.content })) : []
|
|
547
|
+
}))
|
|
548
|
+
];
|
|
549
|
+
const handlePick = (groupKey, optionKey) => {
|
|
550
|
+
setOpen(false);
|
|
551
|
+
if (groupKey === "genres") {
|
|
552
|
+
const genrePlaylist = results.find((result) => result.uuid === optionKey);
|
|
553
|
+
if (!genrePlaylist) return;
|
|
554
|
+
select(genrePlaylist);
|
|
555
|
+
onQueryChange?.("");
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
const item = extraGroups.find((group) => group.key === groupKey)?.items.find((entry) => entry.key === optionKey);
|
|
559
|
+
if (!item) return;
|
|
560
|
+
setQuery("");
|
|
561
|
+
onQueryChange?.("");
|
|
562
|
+
item.onSelect();
|
|
563
|
+
};
|
|
527
564
|
return /* @__PURE__ */ jsxs2("div", { className: "genre-search relative w-[calc(var(--layout-detail-panel-w,25.5rem)-2*var(--layout-panel-inset,1rem))] shrink-0 max-md:w-full", children: [
|
|
528
565
|
onPrevious && /* @__PURE__ */ jsx5(
|
|
529
566
|
"button",
|
|
@@ -537,47 +574,45 @@ function GenreSearch({
|
|
|
537
574
|
}
|
|
538
575
|
),
|
|
539
576
|
/* @__PURE__ */ jsx5(
|
|
540
|
-
|
|
577
|
+
Combobox,
|
|
541
578
|
{
|
|
542
|
-
|
|
579
|
+
ariaLabel: label,
|
|
580
|
+
listLabel: `${label} results`,
|
|
543
581
|
value: query,
|
|
544
|
-
onChange: (event) => {
|
|
545
|
-
setQuery(event.target.value);
|
|
546
|
-
onQueryChange?.(event.target.value);
|
|
547
|
-
},
|
|
548
582
|
placeholder: placeholder ?? `${label}\u2026`,
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
583
|
+
groups,
|
|
584
|
+
open,
|
|
585
|
+
onOpenChange: setOpen,
|
|
586
|
+
onValueChange: (value) => {
|
|
587
|
+
setQuery(value);
|
|
588
|
+
onQueryChange?.(value);
|
|
589
|
+
},
|
|
590
|
+
onPick: handlePick,
|
|
591
|
+
status,
|
|
592
|
+
loading: loading ? "Searching\u2026" : void 0,
|
|
593
|
+
inputClassName: "h-[var(--layout-search-h,3rem)] pl-6 pr-20 text-base transition-[padding] duration-200 ease-out motion-reduce:transition-none bg-[var(--color-surface)] border-[var(--color-border)] rounded-full shadow-md",
|
|
594
|
+
className: "genre-search-results z-40 rounded-xl shadow-md",
|
|
595
|
+
listClassName: "max-h-[min(20rem,50dvh)]",
|
|
596
|
+
optionClassName: "[@media(pointer:coarse)]:min-h-[var(--layout-touch-target)]"
|
|
555
597
|
}
|
|
556
598
|
),
|
|
557
599
|
/* @__PURE__ */ jsxs2("div", { className: "absolute right-3 top-1/2 flex items-center -translate-y-1/2", children: [
|
|
558
|
-
/* @__PURE__ */ jsxs2(
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
"aria-hidden": "true",
|
|
575
|
-
className: `absolute inset-2 w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? "opacity-100 delay-300 animate-spin motion-reduce:animate-none" : "opacity-0"}`
|
|
576
|
-
}
|
|
577
|
-
)
|
|
578
|
-
]
|
|
579
|
-
}
|
|
580
|
-
),
|
|
600
|
+
/* @__PURE__ */ jsxs2("span", { "data-loading": loading, className: "relative p-2 text-[var(--color-text-muted)] pointer-events-none", children: [
|
|
601
|
+
/* @__PURE__ */ jsx5(
|
|
602
|
+
Search,
|
|
603
|
+
{
|
|
604
|
+
"aria-hidden": "true",
|
|
605
|
+
className: `w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? "opacity-0" : ""}`
|
|
606
|
+
}
|
|
607
|
+
),
|
|
608
|
+
/* @__PURE__ */ jsx5(
|
|
609
|
+
LoaderCircle,
|
|
610
|
+
{
|
|
611
|
+
"aria-hidden": "true",
|
|
612
|
+
className: `absolute inset-2 w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? "opacity-100 delay-300 animate-spin motion-reduce:animate-none" : "opacity-0"}`
|
|
613
|
+
}
|
|
614
|
+
)
|
|
615
|
+
] }),
|
|
581
616
|
canClear && /* @__PURE__ */ jsx5(
|
|
582
617
|
"button",
|
|
583
618
|
{
|
|
@@ -591,39 +626,6 @@ function GenreSearch({
|
|
|
591
626
|
children: /* @__PURE__ */ jsx5(X, { "aria-hidden": "true", className: "w-6 h-6" })
|
|
592
627
|
}
|
|
593
628
|
)
|
|
594
|
-
] }),
|
|
595
|
-
extraResults?.status && /* @__PURE__ */ jsx5("p", { role: "status", "aria-live": "polite", className: "sr-only", children: extraResults.status }),
|
|
596
|
-
showList && /* @__PURE__ */ jsxs2("ul", { className: "genre-search-results absolute z-40 w-full max-h-[min(16rem,50dvh)] mt-1 overflow-y-auto bg-[var(--color-surface)] border border-[var(--color-border)] rounded-xl shadow-md", children: [
|
|
597
|
-
extraResults && results.length > 0 && /* @__PURE__ */ jsx5("li", { className: heading, children: "Genres" }),
|
|
598
|
-
results.map((genrePlaylist) => /* @__PURE__ */ jsx5("li", { children: /* @__PURE__ */ jsxs2(
|
|
599
|
-
"button",
|
|
600
|
-
{
|
|
601
|
-
type: "button",
|
|
602
|
-
className: "w-full px-3 py-1.5 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] text-left hover:bg-[var(--color-surface-muted)]",
|
|
603
|
-
onClick: () => {
|
|
604
|
-
select(genrePlaylist);
|
|
605
|
-
onQueryChange?.("");
|
|
606
|
-
},
|
|
607
|
-
children: [
|
|
608
|
-
genrePlaylist.name,
|
|
609
|
-
genrePlaylist.isUmbrella && /* @__PURE__ */ jsx5("span", { className: "ml-2 text-xs italic text-[var(--color-text-muted)]", children: "umbrella" })
|
|
610
|
-
]
|
|
611
|
-
}
|
|
612
|
-
) }, genrePlaylist.uuid)),
|
|
613
|
-
extraResults && extraItems.length > 0 && /* @__PURE__ */ jsx5("li", { className: heading, children: extraResults.label }),
|
|
614
|
-
extraItems.map((item) => /* @__PURE__ */ jsx5("li", { children: /* @__PURE__ */ jsx5(
|
|
615
|
-
"button",
|
|
616
|
-
{
|
|
617
|
-
type: "button",
|
|
618
|
-
className: "w-full px-3 py-1.5 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] text-left hover:bg-[var(--color-surface-muted)]",
|
|
619
|
-
onClick: () => {
|
|
620
|
-
setQuery("");
|
|
621
|
-
onQueryChange?.("");
|
|
622
|
-
item.onSelect();
|
|
623
|
-
},
|
|
624
|
-
children: item.content
|
|
625
|
-
}
|
|
626
|
-
) }, item.key))
|
|
627
629
|
] })
|
|
628
630
|
] });
|
|
629
631
|
}
|
|
@@ -639,16 +641,16 @@ import { Share2 as Share22 } from "lucide-react";
|
|
|
639
641
|
import { usePopup as usePopup3 } from "@behindthemusictree/app-kit/popup";
|
|
640
642
|
|
|
641
643
|
// src/genre-tree/SharePopup.tsx
|
|
642
|
-
import { useCallback as useCallback5, useEffect as useEffect2, useMemo as useMemo5, useState as
|
|
644
|
+
import { useCallback as useCallback5, useEffect as useEffect2, useMemo as useMemo5, useState as useState3 } from "react";
|
|
643
645
|
import { Link2, Share2 } from "lucide-react";
|
|
644
646
|
import { FaWhatsapp } from "react-icons/fa";
|
|
645
647
|
import { SiGmail } from "react-icons/si";
|
|
646
648
|
|
|
647
649
|
// src/genre-tree/ShareDestinationButton.tsx
|
|
648
|
-
import { Fragment, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
650
|
+
import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
649
651
|
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";
|
|
650
652
|
function Contents({ icon, label }) {
|
|
651
|
-
return /* @__PURE__ */ jsxs3(
|
|
653
|
+
return /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
652
654
|
/* @__PURE__ */ jsx6("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 }),
|
|
653
655
|
/* @__PURE__ */ jsx6("span", { className: "max-w-full truncate", children: label })
|
|
654
656
|
] });
|
|
@@ -668,11 +670,11 @@ function ShareDestinationButton({
|
|
|
668
670
|
}
|
|
669
671
|
|
|
670
672
|
// src/genre-tree/SharePreviewCard.tsx
|
|
671
|
-
import { Music2, UserRound } from "lucide-react";
|
|
673
|
+
import { Music, Music2, UserRound } from "lucide-react";
|
|
672
674
|
import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
673
675
|
function SharePreviewCard({ target, url }) {
|
|
674
|
-
const Icon =
|
|
675
|
-
const typeLabel =
|
|
676
|
+
const Icon = { genre: Music2, artist: UserRound, song: Music }[target.kind];
|
|
677
|
+
const typeLabel = { genre: "Genre", artist: "Artist", song: "Song" }[target.kind];
|
|
676
678
|
return /* @__PURE__ */ jsx7("div", { className: "rounded-3xl bg-[var(--color-surface-muted)] p-4", children: /* @__PURE__ */ jsxs4("div", { className: "flex min-h-32 overflow-hidden rounded-2xl bg-[var(--color-surface)] shadow-lg", children: [
|
|
677
679
|
/* @__PURE__ */ jsx7("div", { className: "flex w-28 shrink-0 items-center justify-center bg-[var(--color-primary)]/15 text-[var(--color-primary)]", children: /* @__PURE__ */ jsx7(Icon, { "aria-hidden": "true", className: "h-10 w-10" }) }),
|
|
678
680
|
/* @__PURE__ */ jsxs4("div", { className: "flex min-w-0 flex-1 flex-col justify-center gap-1 p-4", children: [
|
|
@@ -684,7 +686,7 @@ function SharePreviewCard({ target, url }) {
|
|
|
684
686
|
}
|
|
685
687
|
|
|
686
688
|
// src/genre-tree/SharePopup.tsx
|
|
687
|
-
import { Fragment as
|
|
689
|
+
import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
688
690
|
function resolveAbsoluteUrl(url) {
|
|
689
691
|
try {
|
|
690
692
|
const parsed = new URL(url, window.location.href);
|
|
@@ -717,11 +719,11 @@ async function copyText(text) {
|
|
|
717
719
|
}
|
|
718
720
|
}
|
|
719
721
|
function SharePopup({ target, url, onClose }) {
|
|
720
|
-
const [absoluteUrl, setAbsoluteUrl] =
|
|
721
|
-
const [copyStatus, setCopyStatus] =
|
|
722
|
-
const [canUseNativeShare, setCanUseNativeShare] =
|
|
723
|
-
const [shareError, setShareError] =
|
|
724
|
-
const [toastId, setToastId] =
|
|
722
|
+
const [absoluteUrl, setAbsoluteUrl] = useState3(url);
|
|
723
|
+
const [copyStatus, setCopyStatus] = useState3("idle");
|
|
724
|
+
const [canUseNativeShare, setCanUseNativeShare] = useState3(false);
|
|
725
|
+
const [shareError, setShareError] = useState3(null);
|
|
726
|
+
const [toastId, setToastId] = useState3(0);
|
|
725
727
|
useEffect2(() => {
|
|
726
728
|
setAbsoluteUrl(resolveAbsoluteUrl(url) ?? "");
|
|
727
729
|
setCopyStatus("idle");
|
|
@@ -759,7 +761,7 @@ function SharePopup({ target, url, onClose }) {
|
|
|
759
761
|
setShareError("Could not open the share options.");
|
|
760
762
|
}
|
|
761
763
|
};
|
|
762
|
-
return /* @__PURE__ */ jsxs5(
|
|
764
|
+
return /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
763
765
|
/* @__PURE__ */ jsx8(
|
|
764
766
|
BasePopup_default,
|
|
765
767
|
{
|
|
@@ -954,18 +956,18 @@ import { Skeleton as Skeleton2 } from "@behindthemusictree/ui";
|
|
|
954
956
|
import { useFetchGenreOverview } from "@behindthemusictree/app-kit/genre-tree";
|
|
955
957
|
|
|
956
958
|
// src/genre-tree/ClampedText.tsx
|
|
957
|
-
import { useLayoutEffect, useRef as useRef2, useState as
|
|
958
|
-
import { Fragment as
|
|
959
|
+
import { useLayoutEffect, useRef as useRef2, useState as useState4 } from "react";
|
|
960
|
+
import { Fragment as Fragment4, jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
959
961
|
function ClampedText({ children, className: className2 = "" }) {
|
|
960
962
|
const ref = useRef2(null);
|
|
961
|
-
const [expanded, setExpanded] =
|
|
962
|
-
const [overflows, setOverflows] =
|
|
963
|
+
const [expanded, setExpanded] = useState4(false);
|
|
964
|
+
const [overflows, setOverflows] = useState4(false);
|
|
963
965
|
useLayoutEffect(() => {
|
|
964
966
|
const element = ref.current;
|
|
965
967
|
if (!element || expanded) return;
|
|
966
968
|
setOverflows(element.scrollHeight > element.clientHeight);
|
|
967
969
|
}, [children, expanded]);
|
|
968
|
-
return /* @__PURE__ */ jsxs8(
|
|
970
|
+
return /* @__PURE__ */ jsxs8(Fragment4, { children: [
|
|
969
971
|
/* @__PURE__ */ jsx11("p", { ref, className: `${className2} ${expanded ? "" : "line-clamp-3"}`, children }),
|
|
970
972
|
(overflows || expanded) && /* @__PURE__ */ jsx11(
|
|
971
973
|
"button",
|
|
@@ -1001,14 +1003,14 @@ import { useTrackList as useTrackList2 } from "@behindthemusictree/app-kit/genre
|
|
|
1001
1003
|
|
|
1002
1004
|
// src/genre-tree/MusicBrainzRecordingLink.tsx
|
|
1003
1005
|
import { musicBrainzRecordingUrl } from "@behindthemusictree/app-kit/genre-tree";
|
|
1004
|
-
import { Fragment as
|
|
1006
|
+
import { Fragment as Fragment5, jsx as jsx12 } from "react/jsx-runtime";
|
|
1005
1007
|
function MusicBrainzRecordingLink({
|
|
1006
1008
|
song,
|
|
1007
1009
|
children,
|
|
1008
1010
|
className: className2 = ""
|
|
1009
1011
|
}) {
|
|
1010
1012
|
const url = musicBrainzRecordingUrl(song);
|
|
1011
|
-
if (!url) return /* @__PURE__ */ jsx12(
|
|
1013
|
+
if (!url) return /* @__PURE__ */ jsx12(Fragment5, { children });
|
|
1012
1014
|
return /* @__PURE__ */ jsx12(
|
|
1013
1015
|
"a",
|
|
1014
1016
|
{
|
|
@@ -1049,6 +1051,7 @@ function GenrePlaylistTracks({
|
|
|
1049
1051
|
scope,
|
|
1050
1052
|
getBackendBaseUrl,
|
|
1051
1053
|
renderArtistLink,
|
|
1054
|
+
renderSongLink,
|
|
1052
1055
|
renderSongLeading,
|
|
1053
1056
|
renderSongActions,
|
|
1054
1057
|
renderTracksToolbar,
|
|
@@ -1119,7 +1122,7 @@ function GenrePlaylistTracks({
|
|
|
1119
1122
|
const isCurrent = playerTrackObject?.track.id === track.uuid;
|
|
1120
1123
|
const isPlaying = isCurrent && playState === PlayStates.PLAYING;
|
|
1121
1124
|
const text = /* @__PURE__ */ jsxs9("span", { className: TEXT_CELL, children: [
|
|
1122
|
-
/* @__PURE__ */ jsx13("span", { className: TITLE_CELL, children: isSong(track) ? /* @__PURE__ */ jsx13(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
|
|
1125
|
+
/* @__PURE__ */ jsx13("span", { className: TITLE_CELL, children: renderSongLink ? renderSongLink(track) : isSong(track) ? /* @__PURE__ */ jsx13(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
|
|
1123
1126
|
track.artists?.length ? /* @__PURE__ */ jsx13("span", { className: SEPARATOR, children: " \u2014 " }) : null,
|
|
1124
1127
|
/* @__PURE__ */ jsx13("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ jsx13(ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
|
|
1125
1128
|
track.album && /* @__PURE__ */ jsx13("span", { className: ALBUM_CELL, children: track.album.name })
|
|
@@ -1176,7 +1179,7 @@ function GenrePlaylistTracks({
|
|
|
1176
1179
|
// src/genre-tree/GenreDetailExtras.tsx
|
|
1177
1180
|
import { useTrackNoun as useTrackNoun5 } from "@behindthemusictree/app-kit/track-noun";
|
|
1178
1181
|
import { SiWikidata, SiWikipedia } from "react-icons/si";
|
|
1179
|
-
import { Fragment as
|
|
1182
|
+
import { Fragment as Fragment6, jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1180
1183
|
function GenreDetailExtras({
|
|
1181
1184
|
genreUuid,
|
|
1182
1185
|
genrePlaylist,
|
|
@@ -1186,6 +1189,7 @@ function GenreDetailExtras({
|
|
|
1186
1189
|
criteriaOverviewSchema,
|
|
1187
1190
|
renderGenreDetailExtras,
|
|
1188
1191
|
renderArtistLink,
|
|
1192
|
+
renderSongLink,
|
|
1189
1193
|
renderSummary,
|
|
1190
1194
|
renderEssentialTracks,
|
|
1191
1195
|
family,
|
|
@@ -1199,7 +1203,7 @@ function GenreDetailExtras({
|
|
|
1199
1203
|
criteriaOverviewSchema
|
|
1200
1204
|
);
|
|
1201
1205
|
if (isPending) {
|
|
1202
|
-
return /* @__PURE__ */ jsxs10(
|
|
1206
|
+
return /* @__PURE__ */ jsxs10(Fragment6, { children: [
|
|
1203
1207
|
/* @__PURE__ */ jsxs10("div", { className: "gtv-info-panel-children", "aria-busy": "true", "data-testid": "genre-detail-extras-skeleton", children: [
|
|
1204
1208
|
/* @__PURE__ */ jsx14("span", { className: "gtv-info-panel-children-title", children: "Summary" }),
|
|
1205
1209
|
/* @__PURE__ */ jsx14(Skeleton2, { className: "h-4 w-3/4" })
|
|
@@ -1207,10 +1211,10 @@ function GenreDetailExtras({
|
|
|
1207
1211
|
family
|
|
1208
1212
|
] });
|
|
1209
1213
|
}
|
|
1210
|
-
if (!overview) return /* @__PURE__ */ jsx14(
|
|
1214
|
+
if (!overview) return /* @__PURE__ */ jsx14(Fragment6, { children: family });
|
|
1211
1215
|
const { summary, summarySourceUrl, essentialSongs } = overview;
|
|
1212
1216
|
const wikidataUrl = wikidataId ? `https://www.wikidata.org/wiki/${encodeURIComponent(wikidataId)}` : null;
|
|
1213
|
-
return /* @__PURE__ */ jsxs10(
|
|
1217
|
+
return /* @__PURE__ */ jsxs10(Fragment6, { children: [
|
|
1214
1218
|
(summarySourceUrl || wikidataUrl) && /* @__PURE__ */ jsxs10("div", { className: "gtv-info-panel-children text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: [
|
|
1215
1219
|
/* @__PURE__ */ jsx14("span", { className: "gtv-info-panel-children-title", children: "Links" }),
|
|
1216
1220
|
/* @__PURE__ */ jsxs10("div", { className: "mt-1 flex flex-wrap gap-x-3 gap-y-1 text-xs", children: [
|
|
@@ -1263,6 +1267,7 @@ function GenreDetailExtras({
|
|
|
1263
1267
|
scope,
|
|
1264
1268
|
getBackendBaseUrl,
|
|
1265
1269
|
renderArtistLink,
|
|
1270
|
+
renderSongLink,
|
|
1266
1271
|
...trackSlots
|
|
1267
1272
|
}
|
|
1268
1273
|
)
|
|
@@ -1270,7 +1275,7 @@ function GenreDetailExtras({
|
|
|
1270
1275
|
}
|
|
1271
1276
|
|
|
1272
1277
|
// src/genre-tree/GenreInfoPanelBody.tsx
|
|
1273
|
-
import { Fragment as
|
|
1278
|
+
import { Fragment as Fragment7, jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1274
1279
|
import { createElement } from "react";
|
|
1275
1280
|
function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
|
|
1276
1281
|
const { isPlaying, setIsPlaying } = usePlayer5();
|
|
@@ -1305,7 +1310,7 @@ function GenreInfoPanelBody({
|
|
|
1305
1310
|
band,
|
|
1306
1311
|
extras
|
|
1307
1312
|
}) {
|
|
1308
|
-
return /* @__PURE__ */ jsxs11(
|
|
1313
|
+
return /* @__PURE__ */ jsxs11(Fragment7, { children: [
|
|
1309
1314
|
toolbar,
|
|
1310
1315
|
/* @__PURE__ */ jsx15(GenreNodeActionBand, { ...band, genrePlaylist, playback }),
|
|
1311
1316
|
/* @__PURE__ */ createElement(
|
|
@@ -1611,7 +1616,7 @@ function ArtistDiscography({ albums }) {
|
|
|
1611
1616
|
}
|
|
1612
1617
|
|
|
1613
1618
|
// src/genre-tree/artist/ArtistSongs.tsx
|
|
1614
|
-
import { useState as
|
|
1619
|
+
import { useState as useState5 } from "react";
|
|
1615
1620
|
import { keepPreviousData } from "@tanstack/react-query";
|
|
1616
1621
|
import { Play as Play2 } from "lucide-react";
|
|
1617
1622
|
import { Pagination, PanelSection as PanelSection3, Skeleton as Skeleton3 } from "@behindthemusictree/ui";
|
|
@@ -1689,7 +1694,8 @@ function TrackItem({
|
|
|
1689
1694
|
position,
|
|
1690
1695
|
renderDuration,
|
|
1691
1696
|
renderActions,
|
|
1692
|
-
renderArtistLink
|
|
1697
|
+
renderArtistLink,
|
|
1698
|
+
renderSongLink
|
|
1693
1699
|
}) {
|
|
1694
1700
|
const { handlePlayPauseAction, playerTrackObject, loadTrackForPlayer } = usePlayer7();
|
|
1695
1701
|
const { trackList, selectedTrack, toTrackAtPosition, runtimeUnplayableTrackIds } = useTrackList3();
|
|
@@ -1729,7 +1735,7 @@ function TrackItem({
|
|
|
1729
1735
|
}
|
|
1730
1736
|
),
|
|
1731
1737
|
/* @__PURE__ */ jsxs18("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
|
|
1732
|
-
/* @__PURE__ */ jsx24("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ jsx24("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: isSong2(track) ? /* @__PURE__ */ jsx24(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
|
|
1738
|
+
/* @__PURE__ */ jsx24("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ jsx24("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: renderSongLink ? renderSongLink(track) : isSong2(track) ? /* @__PURE__ */ jsx24(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
|
|
1733
1739
|
track.artists && track.artists.length > 0 ? /* @__PURE__ */ jsx24("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ jsx24(ArtistNames2, { artists: track.artists, renderArtistLink }) }) : ""
|
|
1734
1740
|
] }),
|
|
1735
1741
|
/* @__PURE__ */ jsx24("div", { className: "genre-name-container flex items-center justify-start w-[21.6%] min-w-0 shrink-0", children: track.genre ? /* @__PURE__ */ jsx24("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
|
|
@@ -1748,15 +1754,15 @@ function TrackItem({
|
|
|
1748
1754
|
}
|
|
1749
1755
|
|
|
1750
1756
|
// src/genre-tree/artist/ArtistSongs.tsx
|
|
1751
|
-
import { Fragment as
|
|
1757
|
+
import { Fragment as Fragment8, jsx as jsx25, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
1752
1758
|
var SongPageSchema2 = PaginatedResponseSchema2(SongDetailedSchema2);
|
|
1753
1759
|
var SORTS = [
|
|
1754
1760
|
{ ordering: "title", label: "Title" },
|
|
1755
1761
|
{ ordering: "-albumYear", label: "Year" }
|
|
1756
1762
|
];
|
|
1757
|
-
function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
1758
|
-
const [page, setPage] =
|
|
1759
|
-
const [ordering, setOrdering] =
|
|
1763
|
+
function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLink }) {
|
|
1764
|
+
const [page, setPage] = useState5(1);
|
|
1765
|
+
const [ordering, setOrdering] = useState5("title");
|
|
1760
1766
|
const { fetch } = useFetchWrapper2(getBackendBaseUrl);
|
|
1761
1767
|
const { playNewTrackListFromArtist } = useTrackList4();
|
|
1762
1768
|
const { playerTrackObject } = usePlayer8();
|
|
@@ -1781,7 +1787,7 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
|
1781
1787
|
event.stopPropagation();
|
|
1782
1788
|
void play(song.uuid);
|
|
1783
1789
|
};
|
|
1784
|
-
return /* @__PURE__ */ jsx25(PanelSection3, { title: "Songs", children: isPending ? /* @__PURE__ */ jsx25(Skeleton3, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ jsx25("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ jsx25("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ jsxs19(
|
|
1790
|
+
return /* @__PURE__ */ jsx25(PanelSection3, { title: "Songs", children: isPending ? /* @__PURE__ */ jsx25(Skeleton3, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ jsx25("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ jsx25("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ jsxs19(Fragment8, { children: [
|
|
1785
1791
|
/* @__PURE__ */ jsxs19("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
|
|
1786
1792
|
/* @__PURE__ */ jsxs19(
|
|
1787
1793
|
"button",
|
|
@@ -1815,7 +1821,8 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
|
1815
1821
|
{
|
|
1816
1822
|
track: song,
|
|
1817
1823
|
position: (page - 1) * data.pageSize + index + 1,
|
|
1818
|
-
renderArtistLink
|
|
1824
|
+
renderArtistLink,
|
|
1825
|
+
renderSongLink
|
|
1819
1826
|
}
|
|
1820
1827
|
) }, song.uuid)) }),
|
|
1821
1828
|
/* @__PURE__ */ jsx25(Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
|
|
@@ -1823,13 +1830,14 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
|
1823
1830
|
}
|
|
1824
1831
|
|
|
1825
1832
|
// src/genre-tree/artist/useArtistPanel.tsx
|
|
1826
|
-
import { Fragment as
|
|
1833
|
+
import { Fragment as Fragment9, jsx as jsx26, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
1827
1834
|
function useArtistPanel({
|
|
1828
1835
|
uuid,
|
|
1829
1836
|
scope,
|
|
1830
1837
|
getBackendBaseUrl,
|
|
1831
1838
|
getShareUrl,
|
|
1832
1839
|
renderArtistLink,
|
|
1840
|
+
renderSongLink,
|
|
1833
1841
|
expanded,
|
|
1834
1842
|
onExpandedChange,
|
|
1835
1843
|
onClose
|
|
@@ -1871,7 +1879,7 @@ function useArtistPanel({
|
|
|
1871
1879
|
return {
|
|
1872
1880
|
...panel,
|
|
1873
1881
|
title: artist.name,
|
|
1874
|
-
content: /* @__PURE__ */ jsxs20(
|
|
1882
|
+
content: /* @__PURE__ */ jsxs20(Fragment9, { children: [
|
|
1875
1883
|
shareUrl && /* @__PURE__ */ jsx26("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ jsx26("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: shareTarget && /* @__PURE__ */ jsx26(ShareActionButton, { target: shareTarget, url: shareUrl }) }) }),
|
|
1876
1884
|
artist.image && /* @__PURE__ */ jsx26(ArtistPhoto, { image: artist.image, name: artist.name }),
|
|
1877
1885
|
/* @__PURE__ */ jsx26(ArtistHeader, { artist }),
|
|
@@ -1882,7 +1890,8 @@ function useArtistPanel({
|
|
|
1882
1890
|
{
|
|
1883
1891
|
artist: { uuid: artist.uuid, name: artist.name },
|
|
1884
1892
|
getBackendBaseUrl,
|
|
1885
|
-
renderArtistLink
|
|
1893
|
+
renderArtistLink,
|
|
1894
|
+
renderSongLink
|
|
1886
1895
|
}
|
|
1887
1896
|
)
|
|
1888
1897
|
] }),
|
|
@@ -1892,30 +1901,233 @@ function useArtistPanel({
|
|
|
1892
1901
|
}
|
|
1893
1902
|
}
|
|
1894
1903
|
|
|
1904
|
+
// src/genre-tree/song/useSongPanel.tsx
|
|
1905
|
+
import { Skeleton as Skeleton6 } from "@behindthemusictree/ui";
|
|
1906
|
+
import {
|
|
1907
|
+
useSong
|
|
1908
|
+
} from "@behindthemusictree/app-kit/genre-tree";
|
|
1909
|
+
|
|
1910
|
+
// src/genre-tree/song/SongPanelContent.tsx
|
|
1911
|
+
import { useState as useState6 } from "react";
|
|
1912
|
+
import { Pause as Pause2, Play as Play3 } from "lucide-react";
|
|
1913
|
+
import { FieldList as FieldList2, PanelSection as PanelSection4, Skeleton as Skeleton5 } from "@behindthemusictree/ui";
|
|
1914
|
+
import { PlayStates as PlayStates3, usePlayer as usePlayer9 } from "@behindthemusictree/app-kit/player";
|
|
1915
|
+
import {
|
|
1916
|
+
ArtistNames as ArtistNames3,
|
|
1917
|
+
musicBrainzRecordingUrl as musicBrainzRecordingUrl2,
|
|
1918
|
+
songUnplayableReason as songUnplayableReason4,
|
|
1919
|
+
useSongHistory,
|
|
1920
|
+
useTrackList as useTrackList5
|
|
1921
|
+
} from "@behindthemusictree/app-kit/genre-tree";
|
|
1922
|
+
import { Fragment as Fragment10, jsx as jsx27, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
1923
|
+
var numberFormat = new Intl.NumberFormat("en");
|
|
1924
|
+
var dateFormat = new Intl.DateTimeFormat("en", { dateStyle: "medium" });
|
|
1925
|
+
var externalLink = "text-[var(--color-primary)] hover:underline focus-visible:underline";
|
|
1926
|
+
function SongHistory({ uuid, getBackendBaseUrl }) {
|
|
1927
|
+
const [open, setOpen] = useState6(false);
|
|
1928
|
+
const history = useSongHistory(uuid, getBackendBaseUrl, open);
|
|
1929
|
+
let body = null;
|
|
1930
|
+
if (history.status === "loading") body = /* @__PURE__ */ jsx27(Skeleton5, { className: "h-10", "aria-busy": "true" });
|
|
1931
|
+
else if (history.status === "error") body = /* @__PURE__ */ jsx27("p", { className: "text-red-600", children: "Could not load the history." });
|
|
1932
|
+
else if (history.status === "success")
|
|
1933
|
+
body = history.entries.length === 0 ? /* @__PURE__ */ jsx27("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ jsx27("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ jsxs21("li", { className: "py-1.5 text-sm", children: [
|
|
1934
|
+
/* @__PURE__ */ jsx27("span", { className: "font-medium", children: entry.action }),
|
|
1935
|
+
entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ jsxs21("span", { className: "text-[var(--color-text-muted)]", children: [
|
|
1936
|
+
" ",
|
|
1937
|
+
"\u2014 ",
|
|
1938
|
+
entry.oldValue ?? "none",
|
|
1939
|
+
" \u2192 ",
|
|
1940
|
+
entry.newValue ?? "none"
|
|
1941
|
+
] }) : null,
|
|
1942
|
+
/* @__PURE__ */ jsxs21("span", { className: "block text-xs text-[var(--color-text-muted)]", children: [
|
|
1943
|
+
entry.actorPseudo ?? "Anonymous",
|
|
1944
|
+
" \xB7 ",
|
|
1945
|
+
dateFormat.format(new Date(entry.createdOn))
|
|
1946
|
+
] })
|
|
1947
|
+
] }, entry.uuid)) });
|
|
1948
|
+
return /* @__PURE__ */ jsxs21(
|
|
1949
|
+
"details",
|
|
1950
|
+
{
|
|
1951
|
+
onToggle: (event) => setOpen(event.currentTarget.open),
|
|
1952
|
+
className: "px-6 pb-2.5 mt-2.5 border-t border-[var(--color-border)]",
|
|
1953
|
+
children: [
|
|
1954
|
+
/* @__PURE__ */ jsx27("summary", { className: "mt-2.5 cursor-pointer text-[0.6875rem] font-semibold uppercase text-[var(--color-text-muted)]", children: "History" }),
|
|
1955
|
+
/* @__PURE__ */ jsx27("div", { className: "mt-1.5", children: body })
|
|
1956
|
+
]
|
|
1957
|
+
}
|
|
1958
|
+
);
|
|
1959
|
+
}
|
|
1960
|
+
function SongPanelContent({
|
|
1961
|
+
song,
|
|
1962
|
+
scope,
|
|
1963
|
+
getBackendBaseUrl,
|
|
1964
|
+
shareUrl,
|
|
1965
|
+
renderArtistLink,
|
|
1966
|
+
genrePath,
|
|
1967
|
+
onSelectGenre
|
|
1968
|
+
}) {
|
|
1969
|
+
const { playNewTrackListFromTrackUuid } = useTrackList5();
|
|
1970
|
+
const { playerTrackObject, playState, handlePlayPauseAction } = usePlayer9();
|
|
1971
|
+
const unplayableReason = songUnplayableReason4(song);
|
|
1972
|
+
const isCurrent = playerTrackObject?.track.id === song.uuid;
|
|
1973
|
+
const isPlaying = isCurrent && playState === PlayStates3.PLAYING;
|
|
1974
|
+
const shareTarget = { kind: "song", uuid: song.uuid, name: song.title, scope };
|
|
1975
|
+
const musicBrainzUrl = musicBrainzRecordingUrl2(song);
|
|
1976
|
+
const fields = [];
|
|
1977
|
+
if (song.genre) {
|
|
1978
|
+
const path = genrePath.length > 0 ? genrePath : null;
|
|
1979
|
+
fields.push({
|
|
1980
|
+
label: "Genre",
|
|
1981
|
+
value: path ? /* @__PURE__ */ jsx27("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ jsxs21("span", { children: [
|
|
1982
|
+
index > 0 && /* @__PURE__ */ jsx27("span", { "aria-hidden": "true", children: "\u203A " }),
|
|
1983
|
+
/* @__PURE__ */ jsx27("button", { type: "button", onClick: () => onSelectGenre(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
|
|
1984
|
+
] }, genrePlaylist.uuid)) }) : song.genre.name
|
|
1985
|
+
});
|
|
1986
|
+
} else {
|
|
1987
|
+
fields.push({ label: "Genre", value: /* @__PURE__ */ jsx27("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
|
|
1988
|
+
}
|
|
1989
|
+
if (song.firstReleaseYear) fields.push({ label: "First released", value: song.firstReleaseYear });
|
|
1990
|
+
const viewCount = song.youtubeVideo?.viewCount;
|
|
1991
|
+
if (viewCount !== null && viewCount !== void 0)
|
|
1992
|
+
fields.push({ label: "YouTube views", value: numberFormat.format(viewCount) });
|
|
1993
|
+
fields.push({ label: "Plays", value: numberFormat.format(song.playCount) });
|
|
1994
|
+
const links = [];
|
|
1995
|
+
if (musicBrainzUrl)
|
|
1996
|
+
links.push(
|
|
1997
|
+
/* @__PURE__ */ jsx27("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
|
|
1998
|
+
);
|
|
1999
|
+
if (song.youtubeVideo)
|
|
2000
|
+
links.push(
|
|
2001
|
+
/* @__PURE__ */ jsx27(
|
|
2002
|
+
"a",
|
|
2003
|
+
{
|
|
2004
|
+
href: `https://www.youtube.com/watch?v=${encodeURIComponent(song.youtubeVideo.id)}`,
|
|
2005
|
+
target: "_blank",
|
|
2006
|
+
rel: "noopener noreferrer",
|
|
2007
|
+
className: externalLink,
|
|
2008
|
+
children: "YouTube"
|
|
2009
|
+
},
|
|
2010
|
+
"yt"
|
|
2011
|
+
)
|
|
2012
|
+
);
|
|
2013
|
+
if (links.length > 0)
|
|
2014
|
+
fields.push({
|
|
2015
|
+
label: "Links",
|
|
2016
|
+
value: /* @__PURE__ */ jsx27("span", { className: "flex gap-3", children: links })
|
|
2017
|
+
});
|
|
2018
|
+
return /* @__PURE__ */ jsxs21(Fragment10, { children: [
|
|
2019
|
+
/* @__PURE__ */ jsx27("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ jsxs21("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
|
|
2020
|
+
unplayableReason ? /* @__PURE__ */ jsx27("p", { className: "flex items-center px-2.5 text-sm text-[var(--color-text-muted)]", children: unplayableReason }) : /* @__PURE__ */ jsxs21(
|
|
2021
|
+
"button",
|
|
2022
|
+
{
|
|
2023
|
+
type: "button",
|
|
2024
|
+
onClick: () => isCurrent ? handlePlayPauseAction() : playNewTrackListFromTrackUuid(song, scope),
|
|
2025
|
+
className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none",
|
|
2026
|
+
children: [
|
|
2027
|
+
/* @__PURE__ */ jsx27("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__ */ jsx27(Pause2, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ jsx27(Play3, { "aria-hidden": "true", className: "w-5 h-5" }) }),
|
|
2028
|
+
isPlaying ? "Pause" : "Play"
|
|
2029
|
+
]
|
|
2030
|
+
}
|
|
2031
|
+
),
|
|
2032
|
+
shareUrl && /* @__PURE__ */ jsx27(ShareActionButton, { target: shareTarget, url: shareUrl })
|
|
2033
|
+
] }) }),
|
|
2034
|
+
song.artists && song.artists.length > 0 && /* @__PURE__ */ jsx27("p", { className: "px-6 pt-2.5 text-[var(--color-text-muted)]", children: /* @__PURE__ */ jsx27(ArtistNames3, { artists: song.artists, renderArtistLink }) }),
|
|
2035
|
+
/* @__PURE__ */ jsx27(FieldList2, { items: fields }),
|
|
2036
|
+
song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ jsx27(PanelSection4, { title: "Essential in", children: /* @__PURE__ */ jsx27("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ jsx27("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
|
|
2037
|
+
/* @__PURE__ */ jsx27(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
|
|
2038
|
+
] });
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
// src/genre-tree/song/useSongPanel.tsx
|
|
2042
|
+
import { jsx as jsx28, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
2043
|
+
function useSongPanel({
|
|
2044
|
+
uuid,
|
|
2045
|
+
scope,
|
|
2046
|
+
getBackendBaseUrl,
|
|
2047
|
+
getShareUrl,
|
|
2048
|
+
renderArtistLink,
|
|
2049
|
+
getGenrePath,
|
|
2050
|
+
onSelectGenre,
|
|
2051
|
+
expanded,
|
|
2052
|
+
onExpandedChange,
|
|
2053
|
+
onClose
|
|
2054
|
+
}) {
|
|
2055
|
+
const result = useSong(uuid, getBackendBaseUrl);
|
|
2056
|
+
const panel = { expanded, onExpandedChange, onClose };
|
|
2057
|
+
switch (result.status) {
|
|
2058
|
+
case "idle":
|
|
2059
|
+
return void 0;
|
|
2060
|
+
case "loading":
|
|
2061
|
+
return {
|
|
2062
|
+
...panel,
|
|
2063
|
+
title: "Song",
|
|
2064
|
+
content: /* @__PURE__ */ jsxs22("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
|
|
2065
|
+
/* @__PURE__ */ jsx28(Skeleton6, { className: "h-3 w-16" }),
|
|
2066
|
+
/* @__PURE__ */ jsx28(Skeleton6, { className: "h-4 w-32" }),
|
|
2067
|
+
/* @__PURE__ */ jsx28(Skeleton6, { className: "h-3 w-16" }),
|
|
2068
|
+
/* @__PURE__ */ jsx28(Skeleton6, { className: "h-4 w-24" })
|
|
2069
|
+
] })
|
|
2070
|
+
};
|
|
2071
|
+
case "not-found":
|
|
2072
|
+
return {
|
|
2073
|
+
...panel,
|
|
2074
|
+
title: "Song not found",
|
|
2075
|
+
content: /* @__PURE__ */ jsx28("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This song does not exist." })
|
|
2076
|
+
};
|
|
2077
|
+
case "error":
|
|
2078
|
+
return {
|
|
2079
|
+
...panel,
|
|
2080
|
+
title: "Song",
|
|
2081
|
+
content: /* @__PURE__ */ jsx28("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this song." })
|
|
2082
|
+
};
|
|
2083
|
+
case "success": {
|
|
2084
|
+
const { song } = result;
|
|
2085
|
+
const shareUrl = getShareUrl?.({ kind: "song", uuid: song.uuid, name: song.title, scope });
|
|
2086
|
+
return {
|
|
2087
|
+
...panel,
|
|
2088
|
+
title: song.title,
|
|
2089
|
+
content: /* @__PURE__ */ jsx28(
|
|
2090
|
+
SongPanelContent,
|
|
2091
|
+
{
|
|
2092
|
+
song,
|
|
2093
|
+
scope,
|
|
2094
|
+
getBackendBaseUrl,
|
|
2095
|
+
shareUrl: shareUrl ?? void 0,
|
|
2096
|
+
renderArtistLink,
|
|
2097
|
+
genrePath: song.genre ? getGenrePath(song.genre) : [],
|
|
2098
|
+
onSelectGenre
|
|
2099
|
+
}
|
|
2100
|
+
)
|
|
2101
|
+
};
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
|
|
1895
2106
|
// src/genre-tree/SizeMetricToggle.tsx
|
|
1896
2107
|
import { MdMusicNote, MdVisibility } from "react-icons/md";
|
|
1897
2108
|
import { SegmentedControl } from "@behindthemusictree/ui";
|
|
1898
|
-
import { jsx as
|
|
2109
|
+
import { jsx as jsx29 } from "react/jsx-runtime";
|
|
1899
2110
|
function SizeMetricToggle({
|
|
1900
2111
|
value,
|
|
1901
2112
|
onChange
|
|
1902
2113
|
}) {
|
|
1903
|
-
return /* @__PURE__ */
|
|
2114
|
+
return /* @__PURE__ */ jsx29(
|
|
1904
2115
|
SegmentedControl,
|
|
1905
2116
|
{
|
|
2117
|
+
variant: "floating",
|
|
1906
2118
|
"aria-label": "Size nodes by",
|
|
1907
2119
|
value,
|
|
1908
2120
|
onChange,
|
|
1909
2121
|
options: [
|
|
1910
|
-
{ value: "songs", label: /* @__PURE__ */
|
|
1911
|
-
{ value: "views", label: /* @__PURE__ */
|
|
2122
|
+
{ value: "songs", label: /* @__PURE__ */ jsx29(MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
|
|
2123
|
+
{ value: "views", label: /* @__PURE__ */ jsx29(MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
|
|
1912
2124
|
]
|
|
1913
2125
|
}
|
|
1914
2126
|
);
|
|
1915
2127
|
}
|
|
1916
2128
|
|
|
1917
2129
|
// src/genre-tree/GenreTreeView.tsx
|
|
1918
|
-
import { Fragment as
|
|
2130
|
+
import { Fragment as Fragment11, jsx as jsx30, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
1919
2131
|
var PREVIOUS_BUTTON_CSS = `
|
|
1920
2132
|
.genre-tree-view .search-overlay .gtv-info-panel-previous { display: none; }
|
|
1921
2133
|
.genre-tree-view:has(.search-overlay .gtv-info-panel-previous) .genre-search input { padding-left: 2.75rem; }
|
|
@@ -1959,6 +2171,13 @@ function GenreTreeView({
|
|
|
1959
2171
|
artistSearch = true,
|
|
1960
2172
|
onArtistSelect,
|
|
1961
2173
|
artistSearchBaseUrl,
|
|
2174
|
+
songUuid: controlledSongUuid,
|
|
2175
|
+
onSongUuidChange,
|
|
2176
|
+
songExpanded: controlledSongExpanded,
|
|
2177
|
+
onSongExpandedChange,
|
|
2178
|
+
songSearch = true,
|
|
2179
|
+
onSongSelect,
|
|
2180
|
+
renderSongLink,
|
|
1962
2181
|
renderSummary,
|
|
1963
2182
|
renderEssentialTracks,
|
|
1964
2183
|
renderSongLeading,
|
|
@@ -1975,10 +2194,17 @@ function GenreTreeView({
|
|
|
1975
2194
|
}) {
|
|
1976
2195
|
const getShareUrlRef = useRef4(getShareUrl);
|
|
1977
2196
|
getShareUrlRef.current = getShareUrl;
|
|
1978
|
-
const [internalArtistUuid, setInternalArtistUuid] =
|
|
2197
|
+
const [internalArtistUuid, setInternalArtistUuid] = useState7(null);
|
|
1979
2198
|
const isArtistControlled = controlledArtistUuid !== void 0;
|
|
1980
2199
|
const artistUuid = isArtistControlled ? controlledArtistUuid : internalArtistUuid;
|
|
1981
|
-
const [
|
|
2200
|
+
const [internalSongUuid, setInternalSongUuid] = useState7(null);
|
|
2201
|
+
const isSongControlled = controlledSongUuid !== void 0;
|
|
2202
|
+
const songUuid = isSongControlled ? controlledSongUuid : internalSongUuid;
|
|
2203
|
+
const changeSong = (uuid) => {
|
|
2204
|
+
if (!isSongControlled) setInternalSongUuid(uuid);
|
|
2205
|
+
onSongUuidChange?.(uuid);
|
|
2206
|
+
};
|
|
2207
|
+
const [internalSizeMetric, setInternalSizeMetric] = useState7("songs");
|
|
1982
2208
|
const sizeMetric = controlledSizeMetric ?? internalSizeMetric;
|
|
1983
2209
|
const deferredSizeMetric = useDeferredValue(sizeMetric);
|
|
1984
2210
|
const changeSizeMetric = (metric) => {
|
|
@@ -2017,7 +2243,7 @@ function GenreTreeView({
|
|
|
2017
2243
|
artistUuid
|
|
2018
2244
|
});
|
|
2019
2245
|
const restoreRevision = viewportRestore?.revision;
|
|
2020
|
-
const [stackedRestoreOwner, setStackedRestoreOwner] =
|
|
2246
|
+
const [stackedRestoreOwner, setStackedRestoreOwner] = useState7({ revision: restoreRevision, rootUuid: selectedGenrePlaylist?.root.uuid });
|
|
2021
2247
|
let restoreRootUuid = stackedRestoreOwner.rootUuid;
|
|
2022
2248
|
if (stackedRestoreOwner.revision !== restoreRevision) {
|
|
2023
2249
|
restoreRootUuid = selectedGenrePlaylist?.root.uuid;
|
|
@@ -2041,27 +2267,72 @@ function GenreTreeView({
|
|
|
2041
2267
|
const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: visibleGenrePlaylists, artist: filterArtist });
|
|
2042
2268
|
const openArtist = (uuid) => {
|
|
2043
2269
|
if (selectedNodeId !== null) clearSelection();
|
|
2270
|
+
if (songUuid !== null) changeSong(null);
|
|
2044
2271
|
changeArtist(uuid);
|
|
2045
2272
|
};
|
|
2046
|
-
const
|
|
2273
|
+
const openSong = (uuid) => {
|
|
2274
|
+
if (selectedNodeId !== null) clearSelection();
|
|
2275
|
+
changeSong(uuid);
|
|
2276
|
+
};
|
|
2277
|
+
const openSongRef = useRef4(openSong);
|
|
2278
|
+
openSongRef.current = openSong;
|
|
2279
|
+
const resolvedRenderSongLink = useCallback6(
|
|
2280
|
+
(song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ jsx30("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
|
|
2281
|
+
[renderSongLink]
|
|
2282
|
+
);
|
|
2283
|
+
const [expandedSongUuid, setExpandedSongUuid] = useState7(null);
|
|
2284
|
+
const [expandedArtistUuid, setExpandedArtistUuid] = useState7(null);
|
|
2047
2285
|
const artistBaseUrl = artistSearchBaseUrl ?? getBackendBaseUrl;
|
|
2286
|
+
const songBaseUrl = getBackendBaseUrl;
|
|
2048
2287
|
const artistPanel = useArtistPanel({
|
|
2049
2288
|
uuid: artistUuid,
|
|
2050
2289
|
scope,
|
|
2051
2290
|
getBackendBaseUrl: artistBaseUrl,
|
|
2052
2291
|
getShareUrl: getShareUrlRef.current,
|
|
2053
|
-
renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */
|
|
2292
|
+
renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ jsx30("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
|
|
2054
2293
|
expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
|
|
2055
2294
|
onExpandedChange: (on) => {
|
|
2056
2295
|
if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
|
|
2057
2296
|
onArtistExpandedChange?.(on);
|
|
2058
2297
|
},
|
|
2298
|
+
renderSongLink: resolvedRenderSongLink,
|
|
2059
2299
|
onClose: () => changeArtist(null)
|
|
2060
2300
|
});
|
|
2061
|
-
const
|
|
2062
|
-
const
|
|
2063
|
-
|
|
2064
|
-
|
|
2301
|
+
const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ jsx30("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
|
|
2302
|
+
const getGenrePath = (genre) => {
|
|
2303
|
+
const path = [];
|
|
2304
|
+
let current = visibleGenrePlaylists.find((playlist) => playlist.criteria?.uuid === genre.uuid);
|
|
2305
|
+
while (current) {
|
|
2306
|
+
path.unshift(current);
|
|
2307
|
+
const parentUuid = current.parent?.uuid;
|
|
2308
|
+
current = parentUuid ? visibleGenrePlaylists.find((playlist) => playlist.uuid === parentUuid) : void 0;
|
|
2309
|
+
}
|
|
2310
|
+
return path;
|
|
2311
|
+
};
|
|
2312
|
+
const songPanel = useSongPanel({
|
|
2313
|
+
uuid: songUuid,
|
|
2314
|
+
scope,
|
|
2315
|
+
getBackendBaseUrl: songBaseUrl,
|
|
2316
|
+
getShareUrl: getShareUrlRef.current,
|
|
2317
|
+
renderArtistLink: resolvedRenderArtistLink,
|
|
2318
|
+
getGenrePath,
|
|
2319
|
+
onSelectGenre: (genrePlaylist) => {
|
|
2320
|
+
changeSong(null);
|
|
2321
|
+
selectGenrePlaylist(genrePlaylist);
|
|
2322
|
+
},
|
|
2323
|
+
expanded: controlledSongExpanded ?? (songUuid !== null && expandedSongUuid === songUuid),
|
|
2324
|
+
onExpandedChange: (on) => {
|
|
2325
|
+
if (controlledSongExpanded === void 0) setExpandedSongUuid(on ? songUuid : null);
|
|
2326
|
+
onSongExpandedChange?.(on);
|
|
2327
|
+
},
|
|
2328
|
+
onClose: () => changeSong(null)
|
|
2329
|
+
});
|
|
2330
|
+
const builtInSongPanel = overlayPanel || hasSelectedGenreNode ? void 0 : songPanel;
|
|
2331
|
+
const builtInArtistPanel = overlayPanel || hasSelectedGenreNode || builtInSongPanel ? void 0 : artistPanel;
|
|
2332
|
+
const builtInPanel = builtInSongPanel ?? builtInArtistPanel;
|
|
2333
|
+
const activeOverlayPanel = overlayPanel ?? (builtInPanel && { ...builtInPanel, hideTitle: true, hideClose: true });
|
|
2334
|
+
const [uncontrolledCollapsed, setUncontrolledCollapsed] = useState7(false);
|
|
2335
|
+
const [uncontrolledExpanded, setUncontrolledExpanded] = useState7(false);
|
|
2065
2336
|
const isCollapsed = collapsed ?? uncontrolledCollapsed;
|
|
2066
2337
|
const isExpanded = expanded ?? uncontrolledExpanded;
|
|
2067
2338
|
const changeCollapsed = (next) => {
|
|
@@ -2074,7 +2345,7 @@ function GenreTreeView({
|
|
|
2074
2345
|
};
|
|
2075
2346
|
useEffect6(() => {
|
|
2076
2347
|
setUncontrolledCollapsed(false);
|
|
2077
|
-
}, [selectedNodeId, artistUuid]);
|
|
2348
|
+
}, [selectedNodeId, artistUuid, songUuid]);
|
|
2078
2349
|
const isDetailHidden = isCollapsed || !hasSelectedGenreNode && !activeOverlayPanel;
|
|
2079
2350
|
useLeftDetailPanelLayout(!isDetailHidden, placement === "regions");
|
|
2080
2351
|
const detailOverlay = placement === "regions" && activeOverlayPanel?.onExpandedChange ? activeOverlayPanel : void 0;
|
|
@@ -2088,7 +2359,7 @@ function GenreTreeView({
|
|
|
2088
2359
|
hideExpandButton: placement === "regions" ? true : void 0,
|
|
2089
2360
|
overlayPanel: detailOverlay ? { ...detailOverlay, collapsed: isCollapsed, hideExpandButton: true } : activeOverlayPanel
|
|
2090
2361
|
};
|
|
2091
|
-
const [isWheelRevealed, setIsWheelRevealed] =
|
|
2362
|
+
const [isWheelRevealed, setIsWheelRevealed] = useState7(false);
|
|
2092
2363
|
const usesHandoff = viewMode === "wheel" || viewMode === "pop-core";
|
|
2093
2364
|
const isTreeReady = !isLoading && (!usesHandoff || isWheelRevealed);
|
|
2094
2365
|
const rootRef = useRef4(null);
|
|
@@ -2105,21 +2376,22 @@ function GenreTreeView({
|
|
|
2105
2376
|
centerChip.click();
|
|
2106
2377
|
}, [hasSelectedGenreNode, isTreeReady, selectedGenrePlaylist, viewMode]);
|
|
2107
2378
|
const { showPopup, hidePopup } = usePopup5();
|
|
2108
|
-
const [searchText, setSearchText] =
|
|
2379
|
+
const [searchText, setSearchText] = useState7("");
|
|
2109
2380
|
const artistSearchResult = useArtistSearch(artistBaseUrl, artistSearch ? searchText : "");
|
|
2110
|
-
const
|
|
2381
|
+
const artistGroup = artistSearch ? {
|
|
2382
|
+
key: "artists",
|
|
2111
2383
|
label: "Artists",
|
|
2112
2384
|
items: artistSearchResult.artists.map((artist) => ({
|
|
2113
2385
|
key: artist.uuid,
|
|
2114
|
-
content: /* @__PURE__ */
|
|
2115
|
-
/* @__PURE__ */
|
|
2386
|
+
content: /* @__PURE__ */ jsxs23(Fragment11, { children: [
|
|
2387
|
+
/* @__PURE__ */ jsxs23("span", { className: "block truncate font-medium", children: [
|
|
2116
2388
|
artist.name,
|
|
2117
|
-
artist.disambiguation && /* @__PURE__ */
|
|
2389
|
+
artist.disambiguation && /* @__PURE__ */ jsxs23("span", { className: "font-normal text-[var(--color-neutral-500)]", children: [
|
|
2118
2390
|
" \xB7 ",
|
|
2119
2391
|
artist.disambiguation
|
|
2120
2392
|
] })
|
|
2121
2393
|
] }),
|
|
2122
|
-
artist.matchedAlias && /* @__PURE__ */
|
|
2394
|
+
artist.matchedAlias && /* @__PURE__ */ jsxs23("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [
|
|
2123
2395
|
"also known as ",
|
|
2124
2396
|
artist.matchedAlias
|
|
2125
2397
|
] })
|
|
@@ -2132,11 +2404,32 @@ function GenreTreeView({
|
|
|
2132
2404
|
status: artistSearchResult.isError ? "Search failed" : artistSearchResult.settled && artistSearchResult.artists.length === 0 ? "No artists found" : "",
|
|
2133
2405
|
loading: artistSearchResult.pending
|
|
2134
2406
|
} : void 0;
|
|
2407
|
+
const songSearchResult = useSongSearch(songBaseUrl, songSearch ? searchText : "");
|
|
2408
|
+
const songGroup = songSearch ? {
|
|
2409
|
+
key: "songs",
|
|
2410
|
+
label: "Songs",
|
|
2411
|
+
items: songSearchResult.songs.map((song) => ({
|
|
2412
|
+
key: song.uuid,
|
|
2413
|
+
content: /* @__PURE__ */ jsxs23("span", { className: isSongSearchResultPlayable(song) ? void 0 : "opacity-60", children: [
|
|
2414
|
+
/* @__PURE__ */ jsx30("span", { className: "block truncate font-medium", children: song.title }),
|
|
2415
|
+
/* @__PURE__ */ jsx30("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [song.artists.map((artist) => artist.name).join(", "), song.genre?.name ?? "No genre"].filter(Boolean).join(" \xB7 ") })
|
|
2416
|
+
] }),
|
|
2417
|
+
onSelect: () => {
|
|
2418
|
+
openSong(song.uuid);
|
|
2419
|
+
onSongSelect?.(song);
|
|
2420
|
+
}
|
|
2421
|
+
})),
|
|
2422
|
+
status: songSearchResult.isError ? "Search failed" : songSearchResult.settled && songSearchResult.songs.length === 0 ? "No songs found" : "",
|
|
2423
|
+
loading: songSearchResult.pending
|
|
2424
|
+
} : void 0;
|
|
2425
|
+
const extraGroups = [artistGroup, songGroup].filter((group) => group !== void 0);
|
|
2135
2426
|
const handleSelectedNodeChange = (node) => {
|
|
2427
|
+
if (node !== null && songUuid !== null) changeSong(null);
|
|
2136
2428
|
selectNode(node);
|
|
2137
2429
|
};
|
|
2138
2430
|
const handleGenreSearchClear = clearSelection;
|
|
2139
2431
|
const handleGenreSearchSelect = (genrePlaylist) => {
|
|
2432
|
+
if (songUuid !== null) changeSong(null);
|
|
2140
2433
|
selectGenrePlaylist(genrePlaylist);
|
|
2141
2434
|
};
|
|
2142
2435
|
const selectedUuid = selectedGenrePlaylist?.uuid;
|
|
@@ -2151,7 +2444,7 @@ function GenreTreeView({
|
|
|
2151
2444
|
(node, { toolbar, ancestors, family }) => {
|
|
2152
2445
|
const genrePlaylist = genrePlaylistByNodeId.get(node.id);
|
|
2153
2446
|
const criteria = genrePlaylist?.criteria;
|
|
2154
|
-
if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */
|
|
2447
|
+
if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ jsxs23(Fragment11, { children: [
|
|
2155
2448
|
toolbar,
|
|
2156
2449
|
family
|
|
2157
2450
|
] }), aside: ancestors };
|
|
@@ -2171,7 +2464,7 @@ function GenreTreeView({
|
|
|
2171
2464
|
(sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist.uuid && sibling.criteria?.side === "pop"
|
|
2172
2465
|
) : void 0;
|
|
2173
2466
|
return {
|
|
2174
|
-
primary: /* @__PURE__ */
|
|
2467
|
+
primary: /* @__PURE__ */ jsx30(
|
|
2175
2468
|
GenreInfoPanelBody,
|
|
2176
2469
|
{
|
|
2177
2470
|
toolbar,
|
|
@@ -2190,7 +2483,7 @@ function GenreTreeView({
|
|
|
2190
2483
|
onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
|
|
2191
2484
|
onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
|
|
2192
2485
|
onChangeParent: readOnly ? void 0 : () => showPopup(
|
|
2193
|
-
/* @__PURE__ */
|
|
2486
|
+
/* @__PURE__ */ jsx30(
|
|
2194
2487
|
ChangeParentPopup,
|
|
2195
2488
|
{
|
|
2196
2489
|
genrePlaylist: { ...genrePlaylist, criteria },
|
|
@@ -2211,6 +2504,7 @@ function GenreTreeView({
|
|
|
2211
2504
|
criteriaOverviewSchema,
|
|
2212
2505
|
renderGenreDetailExtras,
|
|
2213
2506
|
renderArtistLink,
|
|
2507
|
+
renderSongLink: resolvedRenderSongLink,
|
|
2214
2508
|
artist: filterArtist,
|
|
2215
2509
|
renderSummary,
|
|
2216
2510
|
renderEssentialTracks,
|
|
@@ -2241,6 +2535,7 @@ function GenreTreeView({
|
|
|
2241
2535
|
criteriaOverviewSchema,
|
|
2242
2536
|
renderGenreDetailExtras,
|
|
2243
2537
|
renderArtistLink,
|
|
2538
|
+
resolvedRenderSongLink,
|
|
2244
2539
|
renderSummary,
|
|
2245
2540
|
renderEssentialTracks,
|
|
2246
2541
|
renderSongLeading,
|
|
@@ -2250,7 +2545,7 @@ function GenreTreeView({
|
|
|
2250
2545
|
);
|
|
2251
2546
|
const isRotationControlled = controlledAllowWheelRotation !== void 0;
|
|
2252
2547
|
const allowWheelRotation = controlledAllowWheelRotation ?? internalAllowWheelRotation;
|
|
2253
|
-
const detailTab = placement === "regions" && (hasSelectedGenreNode || detailOverlay) ? /* @__PURE__ */
|
|
2548
|
+
const detailTab = placement === "regions" && (hasSelectedGenreNode || detailOverlay) ? /* @__PURE__ */ jsx30(
|
|
2254
2549
|
PanelEdgeTab,
|
|
2255
2550
|
{
|
|
2256
2551
|
side: "left",
|
|
@@ -2260,8 +2555,8 @@ function GenreTreeView({
|
|
|
2260
2555
|
) : null;
|
|
2261
2556
|
const renderArtistFilter = () => {
|
|
2262
2557
|
if (isLoading || !filterArtist) return null;
|
|
2263
|
-
return /* @__PURE__ */
|
|
2264
|
-
/* @__PURE__ */
|
|
2558
|
+
return /* @__PURE__ */ jsxs23("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
|
|
2559
|
+
/* @__PURE__ */ jsx30(
|
|
2265
2560
|
"button",
|
|
2266
2561
|
{
|
|
2267
2562
|
type: "button",
|
|
@@ -2273,7 +2568,7 @@ function GenreTreeView({
|
|
|
2273
2568
|
children: filterArtist.name
|
|
2274
2569
|
}
|
|
2275
2570
|
),
|
|
2276
|
-
/* @__PURE__ */
|
|
2571
|
+
/* @__PURE__ */ jsx30(
|
|
2277
2572
|
"button",
|
|
2278
2573
|
{
|
|
2279
2574
|
type: "button",
|
|
@@ -2285,22 +2580,22 @@ function GenreTreeView({
|
|
|
2285
2580
|
)
|
|
2286
2581
|
] });
|
|
2287
2582
|
};
|
|
2288
|
-
const actions = /* @__PURE__ */
|
|
2289
|
-
!isLoading && /* @__PURE__ */
|
|
2583
|
+
const actions = /* @__PURE__ */ jsxs23(Fragment11, { children: [
|
|
2584
|
+
!isLoading && /* @__PURE__ */ jsx30(
|
|
2290
2585
|
GenreSearch,
|
|
2291
2586
|
{
|
|
2292
2587
|
genrePlaylists: visibleGenrePlaylists,
|
|
2293
2588
|
onSelect: handleGenreSearchSelect,
|
|
2294
|
-
selectedName: selectedGenrePlaylist?.name ??
|
|
2295
|
-
onClear: builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
|
|
2589
|
+
selectedName: selectedGenrePlaylist?.name ?? builtInPanel?.title ?? null,
|
|
2590
|
+
onClear: builtInSongPanel ? () => changeSong(null) : builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
|
|
2296
2591
|
onPrevious: handlePrevious,
|
|
2297
|
-
onQueryChange: artistSearch ? setSearchText : void 0,
|
|
2298
|
-
|
|
2592
|
+
onQueryChange: artistSearch || songSearch ? setSearchText : void 0,
|
|
2593
|
+
extraGroups
|
|
2299
2594
|
}
|
|
2300
2595
|
),
|
|
2301
|
-
!isLoading && filterArtist && /* @__PURE__ */
|
|
2302
|
-
/* @__PURE__ */
|
|
2303
|
-
!isLoading && !isControlled && /* @__PURE__ */
|
|
2596
|
+
!isLoading && filterArtist && /* @__PURE__ */ jsx30("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
|
|
2597
|
+
/* @__PURE__ */ jsxs23("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: [
|
|
2598
|
+
!isLoading && !isControlled && /* @__PURE__ */ jsx30(
|
|
2304
2599
|
SegmentedControl2,
|
|
2305
2600
|
{
|
|
2306
2601
|
"aria-label": "Tree view mode",
|
|
@@ -2314,13 +2609,13 @@ function GenreTreeView({
|
|
|
2314
2609
|
]
|
|
2315
2610
|
}
|
|
2316
2611
|
),
|
|
2317
|
-
!isLoading && showDisplayOptions && /* @__PURE__ */
|
|
2612
|
+
!isLoading && showDisplayOptions && /* @__PURE__ */ jsx30(
|
|
2318
2613
|
"div",
|
|
2319
2614
|
{
|
|
2320
2615
|
className: "flex items-center gap-1",
|
|
2321
2616
|
role: "group",
|
|
2322
2617
|
"aria-label": "Tree display options",
|
|
2323
|
-
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */
|
|
2618
|
+
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ jsx30(
|
|
2324
2619
|
Button,
|
|
2325
2620
|
{
|
|
2326
2621
|
variant: allowWheelRotation ? "default" : "outline",
|
|
@@ -2331,7 +2626,7 @@ function GenreTreeView({
|
|
|
2331
2626
|
)
|
|
2332
2627
|
}
|
|
2333
2628
|
),
|
|
2334
|
-
!isLoading && !readOnly && showAddRoot && /* @__PURE__ */
|
|
2629
|
+
!isLoading && !readOnly && showAddRoot && /* @__PURE__ */ jsx30(
|
|
2335
2630
|
IconTextButton,
|
|
2336
2631
|
{
|
|
2337
2632
|
icon: Plus2,
|
|
@@ -2341,42 +2636,42 @@ function GenreTreeView({
|
|
|
2341
2636
|
)
|
|
2342
2637
|
] })
|
|
2343
2638
|
] });
|
|
2344
|
-
return /* @__PURE__ */
|
|
2639
|
+
return /* @__PURE__ */ jsxs23(
|
|
2345
2640
|
"div",
|
|
2346
2641
|
{
|
|
2347
2642
|
ref: rootRef,
|
|
2348
2643
|
className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
|
|
2349
2644
|
children: [
|
|
2350
|
-
/* @__PURE__ */
|
|
2351
|
-
/* @__PURE__ */
|
|
2645
|
+
/* @__PURE__ */ jsx30("style", { children: PREVIOUS_BUTTON_CSS }),
|
|
2646
|
+
/* @__PURE__ */ jsx30(
|
|
2352
2647
|
"div",
|
|
2353
2648
|
{
|
|
2354
2649
|
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"}`,
|
|
2355
2650
|
children: actions
|
|
2356
2651
|
}
|
|
2357
2652
|
),
|
|
2358
|
-
!isLoading && filterArtist && /* @__PURE__ */
|
|
2653
|
+
!isLoading && filterArtist && /* @__PURE__ */ jsx30(
|
|
2359
2654
|
"div",
|
|
2360
2655
|
{
|
|
2361
2656
|
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"}`,
|
|
2362
2657
|
children: renderArtistFilter()
|
|
2363
2658
|
}
|
|
2364
2659
|
),
|
|
2365
|
-
detailTab && /* @__PURE__ */
|
|
2660
|
+
detailTab && /* @__PURE__ */ jsx30(
|
|
2366
2661
|
"div",
|
|
2367
2662
|
{
|
|
2368
2663
|
className: `absolute top-1/2 z-30 -translate-y-1/2 max-md:hidden transition-[opacity,visibility] duration-150 ease-out ${isTreeReady ? "opacity-100" : "pointer-events-none invisible opacity-0"} ${detailExpanded ? "right-[calc(var(--layout-center-right)-var(--layout-edge-tab-w))]" : isCollapsed ? "left-0" : "left-[calc(var(--layout-gutter)+var(--layout-left-w))]"}`,
|
|
2369
2664
|
children: detailTab
|
|
2370
2665
|
}
|
|
2371
2666
|
),
|
|
2372
|
-
/* @__PURE__ */
|
|
2373
|
-
!isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */
|
|
2374
|
-
isLoading ? /* @__PURE__ */
|
|
2667
|
+
/* @__PURE__ */ jsx30("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ jsxs23("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
|
|
2668
|
+
!isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ jsx30("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
|
|
2669
|
+
isLoading ? /* @__PURE__ */ jsx30(GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
|
|
2375
2670
|
// The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
|
|
2376
2671
|
// and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
|
|
2377
2672
|
// Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
|
|
2378
2673
|
// The panel's own Zoom in button is hidden: the search bar carries it.
|
|
2379
|
-
/* @__PURE__ */
|
|
2674
|
+
/* @__PURE__ */ jsx30("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 [--gtv-info-panel-header-inset:calc(var(--layout-search-h,2.5rem)+var(--layout-panel-inset,0.5rem))] [&_.gtv-info-panel-header]:pb-2 [&_.gtv-info-panel-title--hidden]:h-0 [&_.gtv-toolbar]:hidden", children: /* @__PURE__ */ jsx30(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ jsx30(GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ jsx30(
|
|
2380
2675
|
GenrePlaylistTreeWheel,
|
|
2381
2676
|
{
|
|
2382
2677
|
scope,
|
|
@@ -2395,9 +2690,9 @@ function GenreTreeView({
|
|
|
2395
2690
|
allowWheelRotation,
|
|
2396
2691
|
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2397
2692
|
busy,
|
|
2398
|
-
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */
|
|
2693
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ jsxs23("div", { className: "flex items-center gap-2", children: [
|
|
2399
2694
|
floatingControlsLeading,
|
|
2400
|
-
canSizeByViews && /* @__PURE__ */
|
|
2695
|
+
canSizeByViews && /* @__PURE__ */ jsx30(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2401
2696
|
] }) : void 0,
|
|
2402
2697
|
defaultViewport,
|
|
2403
2698
|
viewportRestore,
|
|
@@ -2407,7 +2702,7 @@ function GenreTreeView({
|
|
|
2407
2702
|
) }) })
|
|
2408
2703
|
) : viewMode === "pop-core" ? (
|
|
2409
2704
|
// Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
|
|
2410
|
-
/* @__PURE__ */
|
|
2705
|
+
/* @__PURE__ */ jsx30("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 [--gtv-info-panel-header-inset:calc(var(--layout-search-h,2.5rem)+var(--layout-panel-inset,0.5rem))] [&_.gtv-info-panel-header]:pb-2 [&_.gtv-info-panel-title--hidden]:h-0 [&_.gtv-toolbar]:hidden [&_.gtv-info-panel-zoom-in]:hidden", children: /* @__PURE__ */ jsx30(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ jsx30(GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ jsx30(
|
|
2411
2706
|
GenrePlaylistTreeWheelRadialPopCore,
|
|
2412
2707
|
{
|
|
2413
2708
|
scope,
|
|
@@ -2426,9 +2721,9 @@ function GenreTreeView({
|
|
|
2426
2721
|
allowWheelRotation,
|
|
2427
2722
|
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2428
2723
|
busy,
|
|
2429
|
-
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */
|
|
2724
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ jsxs23("div", { className: "flex items-center gap-2", children: [
|
|
2430
2725
|
floatingControlsLeading,
|
|
2431
|
-
canSizeByViews && /* @__PURE__ */
|
|
2726
|
+
canSizeByViews && /* @__PURE__ */ jsx30(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2432
2727
|
] }) : void 0,
|
|
2433
2728
|
defaultViewport,
|
|
2434
2729
|
viewportRestore,
|
|
@@ -2437,7 +2732,7 @@ function GenreTreeView({
|
|
|
2437
2732
|
...focus
|
|
2438
2733
|
}
|
|
2439
2734
|
) }) })
|
|
2440
|
-
) : viewMode === "outline" ? /* @__PURE__ */
|
|
2735
|
+
) : viewMode === "outline" ? /* @__PURE__ */ jsx30("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative [--gtv-info-panel-header-inset:calc(var(--layout-search-h,2.5rem)+var(--layout-panel-inset,0.5rem)+0.75rem)] [&_.gtv-toolbar]:hidden", children: /* @__PURE__ */ jsx30(
|
|
2441
2736
|
GenrePlaylistTreeWheelRadialPopCore,
|
|
2442
2737
|
{
|
|
2443
2738
|
outline: true,
|
|
@@ -2456,13 +2751,13 @@ function GenreTreeView({
|
|
|
2456
2751
|
readOnly,
|
|
2457
2752
|
...infoPanelCuration
|
|
2458
2753
|
}
|
|
2459
|
-
) }) : /* @__PURE__ */
|
|
2754
|
+
) }) : /* @__PURE__ */ jsx30("div", { className: "tree-container relative flex flex-col gap-4 w-full pt-14 overflow-x-auto overflow-y-auto text-[var(--color-text)] [&_.gtv-toolbar]:hidden", children: Object.entries(groupedGenrePlaylistsByRoot).map(
|
|
2460
2755
|
([uuid, genrePlaylistTreePerRoot], rootIndex) => {
|
|
2461
|
-
return /* @__PURE__ */
|
|
2756
|
+
return /* @__PURE__ */ jsx30(
|
|
2462
2757
|
"div",
|
|
2463
2758
|
{
|
|
2464
2759
|
className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
|
|
2465
|
-
children: /* @__PURE__ */
|
|
2760
|
+
children: /* @__PURE__ */ jsx30("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ jsx30(
|
|
2466
2761
|
GenrePlaylistTreePerRoot,
|
|
2467
2762
|
{
|
|
2468
2763
|
scope,
|
|
@@ -2507,11 +2802,11 @@ import {
|
|
|
2507
2802
|
TEXT_COLOR,
|
|
2508
2803
|
tintSurface
|
|
2509
2804
|
} from "@behindthemusictree/genre-tree-view";
|
|
2510
|
-
import { Skeleton as
|
|
2805
|
+
import { Skeleton as Skeleton7 } from "@behindthemusictree/ui";
|
|
2511
2806
|
import {
|
|
2512
2807
|
useFetchGenreDetail
|
|
2513
2808
|
} from "@behindthemusictree/app-kit/genre-tree";
|
|
2514
|
-
import { jsx as
|
|
2809
|
+
import { jsx as jsx31, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
2515
2810
|
var ITEM_COUNT_LABEL = "Songs";
|
|
2516
2811
|
var RELATIVE_ITEM_COUNT = 1;
|
|
2517
2812
|
function toNode(genre, parentId) {
|
|
@@ -2566,17 +2861,17 @@ function GenreDetailPanel({
|
|
|
2566
2861
|
};
|
|
2567
2862
|
}, [detail]);
|
|
2568
2863
|
if (isError) {
|
|
2569
|
-
return /* @__PURE__ */
|
|
2864
|
+
return /* @__PURE__ */ jsx31("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
|
|
2570
2865
|
}
|
|
2571
2866
|
if (isPending || !detail || !view) {
|
|
2572
|
-
return /* @__PURE__ */
|
|
2573
|
-
/* @__PURE__ */
|
|
2574
|
-
/* @__PURE__ */
|
|
2575
|
-
/* @__PURE__ */
|
|
2867
|
+
return /* @__PURE__ */ jsxs24("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
|
|
2868
|
+
/* @__PURE__ */ jsx31(Skeleton7, { className: "h-6 w-2/3" }),
|
|
2869
|
+
/* @__PURE__ */ jsx31(Skeleton7, { className: "h-4 w-full" }),
|
|
2870
|
+
/* @__PURE__ */ jsx31(Skeleton7, { className: "h-4 w-3/4" })
|
|
2576
2871
|
] });
|
|
2577
2872
|
}
|
|
2578
2873
|
const renderBody = (node, sections) => ({
|
|
2579
|
-
primary: /* @__PURE__ */
|
|
2874
|
+
primary: /* @__PURE__ */ jsx31(
|
|
2580
2875
|
GenreInfoPanelBody,
|
|
2581
2876
|
{
|
|
2582
2877
|
toolbar: sections.toolbar,
|
|
@@ -2593,7 +2888,7 @@ function GenreDetailPanel({
|
|
|
2593
2888
|
),
|
|
2594
2889
|
aside: null
|
|
2595
2890
|
});
|
|
2596
|
-
return /* @__PURE__ */
|
|
2891
|
+
return /* @__PURE__ */ jsx31(
|
|
2597
2892
|
InfoPanel,
|
|
2598
2893
|
{
|
|
2599
2894
|
node: view.node,
|
|
@@ -2626,10 +2921,10 @@ import {
|
|
|
2626
2921
|
// src/genre-tree/track-list-sidebar/TrackListSidebar.tsx
|
|
2627
2922
|
import { useEffect as useEffect7, useLayoutEffect as useLayoutEffect2, useRef as useRef5 } from "react";
|
|
2628
2923
|
import { ListMusic } from "lucide-react";
|
|
2629
|
-
import { useTrackList as
|
|
2924
|
+
import { useTrackList as useTrackList6 } from "@behindthemusictree/app-kit/genre-tree";
|
|
2630
2925
|
import { TrackListOriginType as TrackListOriginType2 } from "@behindthemusictree/app-kit/genre-tree";
|
|
2631
2926
|
import { useTrackNoun as useTrackNoun6 } from "@behindthemusictree/app-kit/track-noun";
|
|
2632
|
-
import { jsx as
|
|
2927
|
+
import { jsx as jsx32, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
2633
2928
|
var useBrowserLayoutEffect = typeof window === "undefined" ? useEffect7 : useLayoutEffect2;
|
|
2634
2929
|
function TrackListSidebar({
|
|
2635
2930
|
className: className2 = "",
|
|
@@ -2639,9 +2934,10 @@ function TrackListSidebar({
|
|
|
2639
2934
|
isCollapsed = false,
|
|
2640
2935
|
onExpand,
|
|
2641
2936
|
onMinimize,
|
|
2642
|
-
renderArtistLink
|
|
2937
|
+
renderArtistLink,
|
|
2938
|
+
renderSongLink
|
|
2643
2939
|
}) {
|
|
2644
|
-
const { trackList, loadMore, autoAdvanceScrollRequest } =
|
|
2940
|
+
const { trackList, loadMore, autoAdvanceScrollRequest } = useTrackList6();
|
|
2645
2941
|
const trackNoun = useTrackNoun6();
|
|
2646
2942
|
const trackListRef = useRef5(null);
|
|
2647
2943
|
const previousOriginRef = useRef5(trackList?.origin ?? null);
|
|
@@ -2730,25 +3026,25 @@ function TrackListSidebar({
|
|
|
2730
3026
|
}
|
|
2731
3027
|
};
|
|
2732
3028
|
const positionClasses = layout === "inline" ? "relative w-full flex h-full flex-col" : "fixed right-0 w-[50rem] flex flex-col";
|
|
2733
|
-
return trackList ? /* @__PURE__ */
|
|
3029
|
+
return trackList ? /* @__PURE__ */ jsxs25(
|
|
2734
3030
|
"div",
|
|
2735
3031
|
{
|
|
2736
3032
|
"data-theme": "dark",
|
|
2737
3033
|
className: `track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${className2}`,
|
|
2738
3034
|
style: layout === "fixed" ? { top: "63.5px", maxHeight: "calc(100vh - 63.5px - 79px)" } : void 0,
|
|
2739
3035
|
children: [
|
|
2740
|
-
/* @__PURE__ */
|
|
2741
|
-
/* @__PURE__ */
|
|
2742
|
-
/* @__PURE__ */
|
|
2743
|
-
/* @__PURE__ */
|
|
2744
|
-
/* @__PURE__ */
|
|
3036
|
+
/* @__PURE__ */ jsxs25("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
|
|
3037
|
+
/* @__PURE__ */ jsxs25("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
|
|
3038
|
+
/* @__PURE__ */ jsxs25("div", { className: "origin flex text-xl", children: [
|
|
3039
|
+
/* @__PURE__ */ jsx32("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
|
|
3040
|
+
/* @__PURE__ */ jsx32("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__ */ jsx32("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
|
|
2745
3041
|
] }),
|
|
2746
|
-
/* @__PURE__ */
|
|
3042
|
+
/* @__PURE__ */ jsxs25("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
|
|
2747
3043
|
trackList.origin.type === TrackListOriginType2.GENRE_PLAYLIST ? "Genre \u2022 " : trackList.origin.type === TrackListOriginType2.ARTIST ? "Artist \u2022 " : `${trackNoun.singular} playlist \u2022 `,
|
|
2748
3044
|
`${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`
|
|
2749
3045
|
] })
|
|
2750
3046
|
] }),
|
|
2751
|
-
(isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */
|
|
3047
|
+
(isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ jsx32(
|
|
2752
3048
|
"button",
|
|
2753
3049
|
{
|
|
2754
3050
|
type: "button",
|
|
@@ -2757,11 +3053,11 @@ function TrackListSidebar({
|
|
|
2757
3053
|
"aria-expanded": !isCollapsed,
|
|
2758
3054
|
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"}`,
|
|
2759
3055
|
onClick: isCollapsed ? onExpand : onMinimize,
|
|
2760
|
-
children: /* @__PURE__ */
|
|
3056
|
+
children: /* @__PURE__ */ jsx32(ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
|
|
2761
3057
|
}
|
|
2762
3058
|
)
|
|
2763
3059
|
] }),
|
|
2764
|
-
!isCollapsed && /* @__PURE__ */
|
|
3060
|
+
!isCollapsed && /* @__PURE__ */ jsxs25(
|
|
2765
3061
|
"ul",
|
|
2766
3062
|
{
|
|
2767
3063
|
ref: trackListRef,
|
|
@@ -2771,17 +3067,18 @@ function TrackListSidebar({
|
|
|
2771
3067
|
onPointerDown: markUserScrollIntent,
|
|
2772
3068
|
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",
|
|
2773
3069
|
children: [
|
|
2774
|
-
trackList.tracks.map((track, index) => /* @__PURE__ */
|
|
3070
|
+
trackList.tracks.map((track, index) => /* @__PURE__ */ jsx32("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ jsx32(
|
|
2775
3071
|
TrackItem,
|
|
2776
3072
|
{
|
|
2777
3073
|
track,
|
|
2778
3074
|
position: index + 1,
|
|
2779
3075
|
renderDuration,
|
|
2780
3076
|
renderActions,
|
|
2781
|
-
renderArtistLink
|
|
3077
|
+
renderArtistLink,
|
|
3078
|
+
renderSongLink
|
|
2782
3079
|
}
|
|
2783
3080
|
) }, track.uuid)),
|
|
2784
|
-
hasMore && /* @__PURE__ */
|
|
3081
|
+
hasMore && /* @__PURE__ */ jsx32("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
|
|
2785
3082
|
]
|
|
2786
3083
|
}
|
|
2787
3084
|
)
|
|
@@ -2791,10 +3088,10 @@ function TrackListSidebar({
|
|
|
2791
3088
|
}
|
|
2792
3089
|
|
|
2793
3090
|
// src/genre-tree/PlayerPanel.tsx
|
|
2794
|
-
import { useEffect as useEffect8, useState as
|
|
3091
|
+
import { useEffect as useEffect8, useState as useState8 } from "react";
|
|
2795
3092
|
import { twMerge as twMerge2 } from "tailwind-merge";
|
|
2796
3093
|
import {
|
|
2797
|
-
usePlayer as
|
|
3094
|
+
usePlayer as usePlayer11,
|
|
2798
3095
|
PlayerVideoSurface
|
|
2799
3096
|
} from "@behindthemusictree/app-kit/player";
|
|
2800
3097
|
import {
|
|
@@ -2802,10 +3099,10 @@ import {
|
|
|
2802
3099
|
} from "@behindthemusictree/app-kit/genre-tree";
|
|
2803
3100
|
|
|
2804
3101
|
// src/genre-tree/PlayerControls.tsx
|
|
2805
|
-
import { Pause as
|
|
2806
|
-
import { formatTime, useTrackList as
|
|
2807
|
-
import { PlayStates as
|
|
2808
|
-
import { jsx as
|
|
3102
|
+
import { Pause as Pause3, Play as Play4, SkipBack, SkipForward, Volume2, VolumeX } from "lucide-react";
|
|
3103
|
+
import { formatTime, useTrackList as useTrackList7 } from "@behindthemusictree/app-kit/genre-tree";
|
|
3104
|
+
import { PlayStates as PlayStates4, useCurrentTime, usePlayer as usePlayer10 } from "@behindthemusictree/app-kit/player";
|
|
3105
|
+
import { jsx as jsx33, jsxs as jsxs26 } from "react/jsx-runtime";
|
|
2809
3106
|
function PlayerControls() {
|
|
2810
3107
|
const {
|
|
2811
3108
|
duration,
|
|
@@ -2816,23 +3113,23 @@ function PlayerControls() {
|
|
|
2816
3113
|
seekTo,
|
|
2817
3114
|
setVolume,
|
|
2818
3115
|
volume
|
|
2819
|
-
} =
|
|
2820
|
-
const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } =
|
|
3116
|
+
} = usePlayer10();
|
|
3117
|
+
const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } = useTrackList7();
|
|
2821
3118
|
const currentTime = useCurrentTime();
|
|
2822
|
-
const isBusy = isLoading || playState ===
|
|
3119
|
+
const isBusy = isLoading || playState === PlayStates4.LOADING;
|
|
2823
3120
|
const isReady = !!playerTrackObject?.isReady && !isBusy && !playerTrackObject.loadError;
|
|
2824
3121
|
const safeDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;
|
|
2825
3122
|
const safeCurrentTime = Number.isFinite(currentTime) ? Math.max(0, Math.min(currentTime, safeDuration)) : 0;
|
|
2826
|
-
const isPlaying = playState ===
|
|
2827
|
-
return /* @__PURE__ */
|
|
3123
|
+
const isPlaying = playState === PlayStates4.PLAYING;
|
|
3124
|
+
return /* @__PURE__ */ jsxs26(
|
|
2828
3125
|
"section",
|
|
2829
3126
|
{
|
|
2830
3127
|
"aria-label": "Player controls",
|
|
2831
3128
|
className: "player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200",
|
|
2832
3129
|
children: [
|
|
2833
|
-
/* @__PURE__ */
|
|
2834
|
-
/* @__PURE__ */
|
|
2835
|
-
/* @__PURE__ */
|
|
3130
|
+
/* @__PURE__ */ jsxs26("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
|
|
3131
|
+
/* @__PURE__ */ jsx33("span", { "aria-label": "Elapsed time", children: formatTime(safeCurrentTime) }),
|
|
3132
|
+
/* @__PURE__ */ jsx33(
|
|
2836
3133
|
"input",
|
|
2837
3134
|
{
|
|
2838
3135
|
"aria-label": "Seek",
|
|
@@ -2846,11 +3143,11 @@ function PlayerControls() {
|
|
|
2846
3143
|
onChange: (event) => seekTo(Number(event.currentTarget.value))
|
|
2847
3144
|
}
|
|
2848
3145
|
),
|
|
2849
|
-
/* @__PURE__ */
|
|
3146
|
+
/* @__PURE__ */ jsx33("span", { "aria-label": "Duration", children: formatTime(safeDuration) })
|
|
2850
3147
|
] }),
|
|
2851
|
-
/* @__PURE__ */
|
|
2852
|
-
/* @__PURE__ */
|
|
2853
|
-
/* @__PURE__ */
|
|
3148
|
+
/* @__PURE__ */ jsxs26("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
|
|
3149
|
+
/* @__PURE__ */ jsxs26("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
|
|
3150
|
+
/* @__PURE__ */ jsx33(
|
|
2854
3151
|
"button",
|
|
2855
3152
|
{
|
|
2856
3153
|
type: "button",
|
|
@@ -2859,10 +3156,10 @@ function PlayerControls() {
|
|
|
2859
3156
|
disabled: !isReady || !hasPreviousTrack,
|
|
2860
3157
|
onClick: playPreviousTrack,
|
|
2861
3158
|
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",
|
|
2862
|
-
children: /* @__PURE__ */
|
|
3159
|
+
children: /* @__PURE__ */ jsx33(SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
2863
3160
|
}
|
|
2864
3161
|
),
|
|
2865
|
-
/* @__PURE__ */
|
|
3162
|
+
/* @__PURE__ */ jsx33(
|
|
2866
3163
|
"button",
|
|
2867
3164
|
{
|
|
2868
3165
|
type: "button",
|
|
@@ -2871,10 +3168,10 @@ function PlayerControls() {
|
|
|
2871
3168
|
disabled: !isReady,
|
|
2872
3169
|
onClick: handlePlayPauseAction,
|
|
2873
3170
|
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",
|
|
2874
|
-
children: isPlaying ? /* @__PURE__ */
|
|
3171
|
+
children: isPlaying ? /* @__PURE__ */ jsx33(Pause3, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ jsx33(Play4, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
|
|
2875
3172
|
}
|
|
2876
3173
|
),
|
|
2877
|
-
/* @__PURE__ */
|
|
3174
|
+
/* @__PURE__ */ jsx33(
|
|
2878
3175
|
"button",
|
|
2879
3176
|
{
|
|
2880
3177
|
type: "button",
|
|
@@ -2883,14 +3180,14 @@ function PlayerControls() {
|
|
|
2883
3180
|
disabled: !isReady || !hasNextTrack,
|
|
2884
3181
|
onClick: playNextTrack,
|
|
2885
3182
|
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",
|
|
2886
|
-
children: /* @__PURE__ */
|
|
3183
|
+
children: /* @__PURE__ */ jsx33(SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
2887
3184
|
}
|
|
2888
3185
|
)
|
|
2889
3186
|
] }),
|
|
2890
|
-
/* @__PURE__ */
|
|
2891
|
-
volume === 0 ? /* @__PURE__ */
|
|
2892
|
-
/* @__PURE__ */
|
|
2893
|
-
/* @__PURE__ */
|
|
3187
|
+
/* @__PURE__ */ jsxs26("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: [
|
|
3188
|
+
volume === 0 ? /* @__PURE__ */ jsx33(VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ jsx33(Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
|
|
3189
|
+
/* @__PURE__ */ jsx33("span", { className: "sr-only", children: "Volume" }),
|
|
3190
|
+
/* @__PURE__ */ jsx33(
|
|
2894
3191
|
"input",
|
|
2895
3192
|
{
|
|
2896
3193
|
"aria-label": "Volume",
|
|
@@ -2912,7 +3209,7 @@ function PlayerControls() {
|
|
|
2912
3209
|
}
|
|
2913
3210
|
|
|
2914
3211
|
// src/genre-tree/PlayerPanel.tsx
|
|
2915
|
-
import { jsx as
|
|
3212
|
+
import { jsx as jsx34, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
2916
3213
|
var PANEL_ID = "player-panel";
|
|
2917
3214
|
var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
|
|
2918
3215
|
function PlayerPanel({
|
|
@@ -2921,16 +3218,17 @@ function PlayerPanel({
|
|
|
2921
3218
|
expanded,
|
|
2922
3219
|
onExpandedChange,
|
|
2923
3220
|
extendedWidth = DEFAULT_EXTENDED_WIDTH,
|
|
2924
|
-
renderArtistLink
|
|
3221
|
+
renderArtistLink,
|
|
3222
|
+
renderSongLink
|
|
2925
3223
|
}) {
|
|
2926
|
-
const { playerTrackObject } =
|
|
3224
|
+
const { playerTrackObject } = usePlayer11();
|
|
2927
3225
|
const { toggleTrackListSidebar, isTrackListSidebarVisible } = useTrackListSidebarVisibility();
|
|
2928
|
-
const [lastLoadedTrackTitle, setLastLoadedTrackTitle] =
|
|
3226
|
+
const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = useState8("");
|
|
2929
3227
|
const loadedTrackTitle = playerTrackObject?.track.title;
|
|
2930
3228
|
useEffect8(() => {
|
|
2931
3229
|
if (loadedTrackTitle) setLastLoadedTrackTitle(loadedTrackTitle);
|
|
2932
3230
|
}, [loadedTrackTitle]);
|
|
2933
|
-
const [uncontrolledExtended, setUncontrolledExtended] =
|
|
3231
|
+
const [uncontrolledExtended, setUncontrolledExtended] = useState8(false);
|
|
2934
3232
|
const isExtendRequested = expanded ?? uncontrolledExtended;
|
|
2935
3233
|
const setIsExtendRequested = (next) => {
|
|
2936
3234
|
setUncontrolledExtended(next);
|
|
@@ -2951,7 +3249,7 @@ function PlayerPanel({
|
|
|
2951
3249
|
onClick: () => setIsExtendRequested(true),
|
|
2952
3250
|
expanded: false
|
|
2953
3251
|
} : null;
|
|
2954
|
-
return /* @__PURE__ */
|
|
3252
|
+
return /* @__PURE__ */ jsxs27(
|
|
2955
3253
|
"div",
|
|
2956
3254
|
{
|
|
2957
3255
|
id: PANEL_ID,
|
|
@@ -2964,7 +3262,7 @@ function PlayerPanel({
|
|
|
2964
3262
|
),
|
|
2965
3263
|
style: isExtended ? { width: extendedWidth } : void 0,
|
|
2966
3264
|
children: [
|
|
2967
|
-
/* @__PURE__ */
|
|
3265
|
+
/* @__PURE__ */ jsx34(
|
|
2968
3266
|
PanelEdgeTab,
|
|
2969
3267
|
{
|
|
2970
3268
|
side: "right",
|
|
@@ -2974,7 +3272,7 @@ function PlayerPanel({
|
|
|
2974
3272
|
className: "absolute right-full top-1/2 -translate-y-1/2"
|
|
2975
3273
|
}
|
|
2976
3274
|
),
|
|
2977
|
-
/* @__PURE__ */
|
|
3275
|
+
/* @__PURE__ */ jsxs27(
|
|
2978
3276
|
"div",
|
|
2979
3277
|
{
|
|
2980
3278
|
className: twMerge2(
|
|
@@ -2982,15 +3280,15 @@ function PlayerPanel({
|
|
|
2982
3280
|
isExtended ? "flex-row" : "flex-col"
|
|
2983
3281
|
),
|
|
2984
3282
|
children: [
|
|
2985
|
-
/* @__PURE__ */
|
|
2986
|
-
/* @__PURE__ */
|
|
2987
|
-
/* @__PURE__ */
|
|
2988
|
-
/* @__PURE__ */
|
|
2989
|
-
playerTrackObject?.loadError && /* @__PURE__ */
|
|
3283
|
+
/* @__PURE__ */ jsxs27("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
|
|
3284
|
+
/* @__PURE__ */ jsx34("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ jsx34("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
|
|
3285
|
+
/* @__PURE__ */ jsxs27("div", { className: "relative w-full shrink-0", children: [
|
|
3286
|
+
/* @__PURE__ */ jsx34(PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
|
|
3287
|
+
playerTrackObject?.loadError && /* @__PURE__ */ jsx34("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: playerTrackObject.loadError })
|
|
2990
3288
|
] }),
|
|
2991
|
-
/* @__PURE__ */
|
|
3289
|
+
/* @__PURE__ */ jsx34(PlayerControls, {})
|
|
2992
3290
|
] }),
|
|
2993
|
-
/* @__PURE__ */
|
|
3291
|
+
/* @__PURE__ */ jsx34(
|
|
2994
3292
|
TrackListSidebar,
|
|
2995
3293
|
{
|
|
2996
3294
|
layout: "inline",
|
|
@@ -3001,7 +3299,8 @@ function PlayerPanel({
|
|
|
3001
3299
|
"min-h-0 min-w-0 flex-1",
|
|
3002
3300
|
isExtended && "border-l border-zinc-800"
|
|
3003
3301
|
),
|
|
3004
|
-
renderArtistLink
|
|
3302
|
+
renderArtistLink,
|
|
3303
|
+
renderSongLink
|
|
3005
3304
|
}
|
|
3006
3305
|
)
|
|
3007
3306
|
]
|
|
@@ -3016,11 +3315,11 @@ function PlayerPanel({
|
|
|
3016
3315
|
import { v4 as uuidv4 } from "uuid";
|
|
3017
3316
|
import { AiFillStar, AiOutlineStar } from "react-icons/ai";
|
|
3018
3317
|
import { FORM_RATING_NULL_VALUE } from "@behindthemusictree/app-kit/genre-tree";
|
|
3019
|
-
import { jsx as
|
|
3318
|
+
import { jsx as jsx35, jsxs as jsxs28 } from "react/jsx-runtime";
|
|
3020
3319
|
function Rating({ rating, handleChange }) {
|
|
3021
3320
|
const instanceId = uuidv4();
|
|
3022
|
-
return /* @__PURE__ */
|
|
3023
|
-
/* @__PURE__ */
|
|
3321
|
+
return /* @__PURE__ */ jsx35("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ jsxs28("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
|
|
3322
|
+
/* @__PURE__ */ jsx35(
|
|
3024
3323
|
"input",
|
|
3025
3324
|
{
|
|
3026
3325
|
id: `star-${instanceId}-${i}`,
|
|
@@ -3032,16 +3331,16 @@ function Rating({ rating, handleChange }) {
|
|
|
3032
3331
|
onChange: handleChange
|
|
3033
3332
|
}
|
|
3034
3333
|
),
|
|
3035
|
-
/* @__PURE__ */
|
|
3036
|
-
/* @__PURE__ */
|
|
3334
|
+
/* @__PURE__ */ jsx35("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ jsxs28("div", { className: "flex", children: [
|
|
3335
|
+
/* @__PURE__ */ jsx35(
|
|
3037
3336
|
AiOutlineStar,
|
|
3038
3337
|
{
|
|
3039
3338
|
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)]"}`
|
|
3040
3339
|
}
|
|
3041
3340
|
),
|
|
3042
|
-
/* @__PURE__ */
|
|
3341
|
+
/* @__PURE__ */ jsx35("span", { className: "flex-grow w-8" }),
|
|
3043
3342
|
" "
|
|
3044
|
-
] }) : /* @__PURE__ */
|
|
3343
|
+
] }) : /* @__PURE__ */ jsx35(
|
|
3045
3344
|
AiFillStar,
|
|
3046
3345
|
{
|
|
3047
3346
|
className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
|
|
@@ -3070,4 +3369,4 @@ export {
|
|
|
3070
3369
|
GenreTreeViewSkeleton2 as GenreTreeViewSkeleton,
|
|
3071
3370
|
GenreTreeOutlineSkeleton
|
|
3072
3371
|
};
|
|
3073
|
-
//# sourceMappingURL=chunk-
|
|
3372
|
+
//# sourceMappingURL=chunk-2HE2HDFA.js.map
|