@behindthemusictree/app-kit-ui 29.1.0 → 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-36YVZ6JR.js → chunk-2HE2HDFA.js} +551 -253
- package/dist/chunk-2HE2HDFA.js.map +1 -0
- package/dist/genre-tree/index.cjs +587 -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 +584 -296
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +5 -5
- package/dist/chunk-36YVZ6JR.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,15 +1901,217 @@ 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
|
{
|
|
1906
2117
|
variant: "floating",
|
|
@@ -1908,15 +2119,15 @@ function SizeMetricToggle({
|
|
|
1908
2119
|
value,
|
|
1909
2120
|
onChange,
|
|
1910
2121
|
options: [
|
|
1911
|
-
{ value: "songs", label: /* @__PURE__ */
|
|
1912
|
-
{ 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" }
|
|
1913
2124
|
]
|
|
1914
2125
|
}
|
|
1915
2126
|
);
|
|
1916
2127
|
}
|
|
1917
2128
|
|
|
1918
2129
|
// src/genre-tree/GenreTreeView.tsx
|
|
1919
|
-
import { Fragment as
|
|
2130
|
+
import { Fragment as Fragment11, jsx as jsx30, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
1920
2131
|
var PREVIOUS_BUTTON_CSS = `
|
|
1921
2132
|
.genre-tree-view .search-overlay .gtv-info-panel-previous { display: none; }
|
|
1922
2133
|
.genre-tree-view:has(.search-overlay .gtv-info-panel-previous) .genre-search input { padding-left: 2.75rem; }
|
|
@@ -1960,6 +2171,13 @@ function GenreTreeView({
|
|
|
1960
2171
|
artistSearch = true,
|
|
1961
2172
|
onArtistSelect,
|
|
1962
2173
|
artistSearchBaseUrl,
|
|
2174
|
+
songUuid: controlledSongUuid,
|
|
2175
|
+
onSongUuidChange,
|
|
2176
|
+
songExpanded: controlledSongExpanded,
|
|
2177
|
+
onSongExpandedChange,
|
|
2178
|
+
songSearch = true,
|
|
2179
|
+
onSongSelect,
|
|
2180
|
+
renderSongLink,
|
|
1963
2181
|
renderSummary,
|
|
1964
2182
|
renderEssentialTracks,
|
|
1965
2183
|
renderSongLeading,
|
|
@@ -1976,10 +2194,17 @@ function GenreTreeView({
|
|
|
1976
2194
|
}) {
|
|
1977
2195
|
const getShareUrlRef = useRef4(getShareUrl);
|
|
1978
2196
|
getShareUrlRef.current = getShareUrl;
|
|
1979
|
-
const [internalArtistUuid, setInternalArtistUuid] =
|
|
2197
|
+
const [internalArtistUuid, setInternalArtistUuid] = useState7(null);
|
|
1980
2198
|
const isArtistControlled = controlledArtistUuid !== void 0;
|
|
1981
2199
|
const artistUuid = isArtistControlled ? controlledArtistUuid : internalArtistUuid;
|
|
1982
|
-
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");
|
|
1983
2208
|
const sizeMetric = controlledSizeMetric ?? internalSizeMetric;
|
|
1984
2209
|
const deferredSizeMetric = useDeferredValue(sizeMetric);
|
|
1985
2210
|
const changeSizeMetric = (metric) => {
|
|
@@ -2018,7 +2243,7 @@ function GenreTreeView({
|
|
|
2018
2243
|
artistUuid
|
|
2019
2244
|
});
|
|
2020
2245
|
const restoreRevision = viewportRestore?.revision;
|
|
2021
|
-
const [stackedRestoreOwner, setStackedRestoreOwner] =
|
|
2246
|
+
const [stackedRestoreOwner, setStackedRestoreOwner] = useState7({ revision: restoreRevision, rootUuid: selectedGenrePlaylist?.root.uuid });
|
|
2022
2247
|
let restoreRootUuid = stackedRestoreOwner.rootUuid;
|
|
2023
2248
|
if (stackedRestoreOwner.revision !== restoreRevision) {
|
|
2024
2249
|
restoreRootUuid = selectedGenrePlaylist?.root.uuid;
|
|
@@ -2042,27 +2267,72 @@ function GenreTreeView({
|
|
|
2042
2267
|
const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: visibleGenrePlaylists, artist: filterArtist });
|
|
2043
2268
|
const openArtist = (uuid) => {
|
|
2044
2269
|
if (selectedNodeId !== null) clearSelection();
|
|
2270
|
+
if (songUuid !== null) changeSong(null);
|
|
2045
2271
|
changeArtist(uuid);
|
|
2046
2272
|
};
|
|
2047
|
-
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);
|
|
2048
2285
|
const artistBaseUrl = artistSearchBaseUrl ?? getBackendBaseUrl;
|
|
2286
|
+
const songBaseUrl = getBackendBaseUrl;
|
|
2049
2287
|
const artistPanel = useArtistPanel({
|
|
2050
2288
|
uuid: artistUuid,
|
|
2051
2289
|
scope,
|
|
2052
2290
|
getBackendBaseUrl: artistBaseUrl,
|
|
2053
2291
|
getShareUrl: getShareUrlRef.current,
|
|
2054
|
-
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 })),
|
|
2055
2293
|
expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
|
|
2056
2294
|
onExpandedChange: (on) => {
|
|
2057
2295
|
if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
|
|
2058
2296
|
onArtistExpandedChange?.(on);
|
|
2059
2297
|
},
|
|
2298
|
+
renderSongLink: resolvedRenderSongLink,
|
|
2060
2299
|
onClose: () => changeArtist(null)
|
|
2061
2300
|
});
|
|
2062
|
-
const
|
|
2063
|
-
const
|
|
2064
|
-
|
|
2065
|
-
|
|
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);
|
|
2066
2336
|
const isCollapsed = collapsed ?? uncontrolledCollapsed;
|
|
2067
2337
|
const isExpanded = expanded ?? uncontrolledExpanded;
|
|
2068
2338
|
const changeCollapsed = (next) => {
|
|
@@ -2075,7 +2345,7 @@ function GenreTreeView({
|
|
|
2075
2345
|
};
|
|
2076
2346
|
useEffect6(() => {
|
|
2077
2347
|
setUncontrolledCollapsed(false);
|
|
2078
|
-
}, [selectedNodeId, artistUuid]);
|
|
2348
|
+
}, [selectedNodeId, artistUuid, songUuid]);
|
|
2079
2349
|
const isDetailHidden = isCollapsed || !hasSelectedGenreNode && !activeOverlayPanel;
|
|
2080
2350
|
useLeftDetailPanelLayout(!isDetailHidden, placement === "regions");
|
|
2081
2351
|
const detailOverlay = placement === "regions" && activeOverlayPanel?.onExpandedChange ? activeOverlayPanel : void 0;
|
|
@@ -2089,7 +2359,7 @@ function GenreTreeView({
|
|
|
2089
2359
|
hideExpandButton: placement === "regions" ? true : void 0,
|
|
2090
2360
|
overlayPanel: detailOverlay ? { ...detailOverlay, collapsed: isCollapsed, hideExpandButton: true } : activeOverlayPanel
|
|
2091
2361
|
};
|
|
2092
|
-
const [isWheelRevealed, setIsWheelRevealed] =
|
|
2362
|
+
const [isWheelRevealed, setIsWheelRevealed] = useState7(false);
|
|
2093
2363
|
const usesHandoff = viewMode === "wheel" || viewMode === "pop-core";
|
|
2094
2364
|
const isTreeReady = !isLoading && (!usesHandoff || isWheelRevealed);
|
|
2095
2365
|
const rootRef = useRef4(null);
|
|
@@ -2106,21 +2376,22 @@ function GenreTreeView({
|
|
|
2106
2376
|
centerChip.click();
|
|
2107
2377
|
}, [hasSelectedGenreNode, isTreeReady, selectedGenrePlaylist, viewMode]);
|
|
2108
2378
|
const { showPopup, hidePopup } = usePopup5();
|
|
2109
|
-
const [searchText, setSearchText] =
|
|
2379
|
+
const [searchText, setSearchText] = useState7("");
|
|
2110
2380
|
const artistSearchResult = useArtistSearch(artistBaseUrl, artistSearch ? searchText : "");
|
|
2111
|
-
const
|
|
2381
|
+
const artistGroup = artistSearch ? {
|
|
2382
|
+
key: "artists",
|
|
2112
2383
|
label: "Artists",
|
|
2113
2384
|
items: artistSearchResult.artists.map((artist) => ({
|
|
2114
2385
|
key: artist.uuid,
|
|
2115
|
-
content: /* @__PURE__ */
|
|
2116
|
-
/* @__PURE__ */
|
|
2386
|
+
content: /* @__PURE__ */ jsxs23(Fragment11, { children: [
|
|
2387
|
+
/* @__PURE__ */ jsxs23("span", { className: "block truncate font-medium", children: [
|
|
2117
2388
|
artist.name,
|
|
2118
|
-
artist.disambiguation && /* @__PURE__ */
|
|
2389
|
+
artist.disambiguation && /* @__PURE__ */ jsxs23("span", { className: "font-normal text-[var(--color-neutral-500)]", children: [
|
|
2119
2390
|
" \xB7 ",
|
|
2120
2391
|
artist.disambiguation
|
|
2121
2392
|
] })
|
|
2122
2393
|
] }),
|
|
2123
|
-
artist.matchedAlias && /* @__PURE__ */
|
|
2394
|
+
artist.matchedAlias && /* @__PURE__ */ jsxs23("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [
|
|
2124
2395
|
"also known as ",
|
|
2125
2396
|
artist.matchedAlias
|
|
2126
2397
|
] })
|
|
@@ -2133,11 +2404,32 @@ function GenreTreeView({
|
|
|
2133
2404
|
status: artistSearchResult.isError ? "Search failed" : artistSearchResult.settled && artistSearchResult.artists.length === 0 ? "No artists found" : "",
|
|
2134
2405
|
loading: artistSearchResult.pending
|
|
2135
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);
|
|
2136
2426
|
const handleSelectedNodeChange = (node) => {
|
|
2427
|
+
if (node !== null && songUuid !== null) changeSong(null);
|
|
2137
2428
|
selectNode(node);
|
|
2138
2429
|
};
|
|
2139
2430
|
const handleGenreSearchClear = clearSelection;
|
|
2140
2431
|
const handleGenreSearchSelect = (genrePlaylist) => {
|
|
2432
|
+
if (songUuid !== null) changeSong(null);
|
|
2141
2433
|
selectGenrePlaylist(genrePlaylist);
|
|
2142
2434
|
};
|
|
2143
2435
|
const selectedUuid = selectedGenrePlaylist?.uuid;
|
|
@@ -2152,7 +2444,7 @@ function GenreTreeView({
|
|
|
2152
2444
|
(node, { toolbar, ancestors, family }) => {
|
|
2153
2445
|
const genrePlaylist = genrePlaylistByNodeId.get(node.id);
|
|
2154
2446
|
const criteria = genrePlaylist?.criteria;
|
|
2155
|
-
if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */
|
|
2447
|
+
if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ jsxs23(Fragment11, { children: [
|
|
2156
2448
|
toolbar,
|
|
2157
2449
|
family
|
|
2158
2450
|
] }), aside: ancestors };
|
|
@@ -2172,7 +2464,7 @@ function GenreTreeView({
|
|
|
2172
2464
|
(sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist.uuid && sibling.criteria?.side === "pop"
|
|
2173
2465
|
) : void 0;
|
|
2174
2466
|
return {
|
|
2175
|
-
primary: /* @__PURE__ */
|
|
2467
|
+
primary: /* @__PURE__ */ jsx30(
|
|
2176
2468
|
GenreInfoPanelBody,
|
|
2177
2469
|
{
|
|
2178
2470
|
toolbar,
|
|
@@ -2191,7 +2483,7 @@ function GenreTreeView({
|
|
|
2191
2483
|
onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
|
|
2192
2484
|
onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
|
|
2193
2485
|
onChangeParent: readOnly ? void 0 : () => showPopup(
|
|
2194
|
-
/* @__PURE__ */
|
|
2486
|
+
/* @__PURE__ */ jsx30(
|
|
2195
2487
|
ChangeParentPopup,
|
|
2196
2488
|
{
|
|
2197
2489
|
genrePlaylist: { ...genrePlaylist, criteria },
|
|
@@ -2212,6 +2504,7 @@ function GenreTreeView({
|
|
|
2212
2504
|
criteriaOverviewSchema,
|
|
2213
2505
|
renderGenreDetailExtras,
|
|
2214
2506
|
renderArtistLink,
|
|
2507
|
+
renderSongLink: resolvedRenderSongLink,
|
|
2215
2508
|
artist: filterArtist,
|
|
2216
2509
|
renderSummary,
|
|
2217
2510
|
renderEssentialTracks,
|
|
@@ -2242,6 +2535,7 @@ function GenreTreeView({
|
|
|
2242
2535
|
criteriaOverviewSchema,
|
|
2243
2536
|
renderGenreDetailExtras,
|
|
2244
2537
|
renderArtistLink,
|
|
2538
|
+
resolvedRenderSongLink,
|
|
2245
2539
|
renderSummary,
|
|
2246
2540
|
renderEssentialTracks,
|
|
2247
2541
|
renderSongLeading,
|
|
@@ -2251,7 +2545,7 @@ function GenreTreeView({
|
|
|
2251
2545
|
);
|
|
2252
2546
|
const isRotationControlled = controlledAllowWheelRotation !== void 0;
|
|
2253
2547
|
const allowWheelRotation = controlledAllowWheelRotation ?? internalAllowWheelRotation;
|
|
2254
|
-
const detailTab = placement === "regions" && (hasSelectedGenreNode || detailOverlay) ? /* @__PURE__ */
|
|
2548
|
+
const detailTab = placement === "regions" && (hasSelectedGenreNode || detailOverlay) ? /* @__PURE__ */ jsx30(
|
|
2255
2549
|
PanelEdgeTab,
|
|
2256
2550
|
{
|
|
2257
2551
|
side: "left",
|
|
@@ -2261,8 +2555,8 @@ function GenreTreeView({
|
|
|
2261
2555
|
) : null;
|
|
2262
2556
|
const renderArtistFilter = () => {
|
|
2263
2557
|
if (isLoading || !filterArtist) return null;
|
|
2264
|
-
return /* @__PURE__ */
|
|
2265
|
-
/* @__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(
|
|
2266
2560
|
"button",
|
|
2267
2561
|
{
|
|
2268
2562
|
type: "button",
|
|
@@ -2274,7 +2568,7 @@ function GenreTreeView({
|
|
|
2274
2568
|
children: filterArtist.name
|
|
2275
2569
|
}
|
|
2276
2570
|
),
|
|
2277
|
-
/* @__PURE__ */
|
|
2571
|
+
/* @__PURE__ */ jsx30(
|
|
2278
2572
|
"button",
|
|
2279
2573
|
{
|
|
2280
2574
|
type: "button",
|
|
@@ -2286,22 +2580,22 @@ function GenreTreeView({
|
|
|
2286
2580
|
)
|
|
2287
2581
|
] });
|
|
2288
2582
|
};
|
|
2289
|
-
const actions = /* @__PURE__ */
|
|
2290
|
-
!isLoading && /* @__PURE__ */
|
|
2583
|
+
const actions = /* @__PURE__ */ jsxs23(Fragment11, { children: [
|
|
2584
|
+
!isLoading && /* @__PURE__ */ jsx30(
|
|
2291
2585
|
GenreSearch,
|
|
2292
2586
|
{
|
|
2293
2587
|
genrePlaylists: visibleGenrePlaylists,
|
|
2294
2588
|
onSelect: handleGenreSearchSelect,
|
|
2295
|
-
selectedName: selectedGenrePlaylist?.name ??
|
|
2296
|
-
onClear: builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
|
|
2589
|
+
selectedName: selectedGenrePlaylist?.name ?? builtInPanel?.title ?? null,
|
|
2590
|
+
onClear: builtInSongPanel ? () => changeSong(null) : builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
|
|
2297
2591
|
onPrevious: handlePrevious,
|
|
2298
|
-
onQueryChange: artistSearch ? setSearchText : void 0,
|
|
2299
|
-
|
|
2592
|
+
onQueryChange: artistSearch || songSearch ? setSearchText : void 0,
|
|
2593
|
+
extraGroups
|
|
2300
2594
|
}
|
|
2301
2595
|
),
|
|
2302
|
-
!isLoading && filterArtist && /* @__PURE__ */
|
|
2303
|
-
/* @__PURE__ */
|
|
2304
|
-
!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(
|
|
2305
2599
|
SegmentedControl2,
|
|
2306
2600
|
{
|
|
2307
2601
|
"aria-label": "Tree view mode",
|
|
@@ -2315,13 +2609,13 @@ function GenreTreeView({
|
|
|
2315
2609
|
]
|
|
2316
2610
|
}
|
|
2317
2611
|
),
|
|
2318
|
-
!isLoading && showDisplayOptions && /* @__PURE__ */
|
|
2612
|
+
!isLoading && showDisplayOptions && /* @__PURE__ */ jsx30(
|
|
2319
2613
|
"div",
|
|
2320
2614
|
{
|
|
2321
2615
|
className: "flex items-center gap-1",
|
|
2322
2616
|
role: "group",
|
|
2323
2617
|
"aria-label": "Tree display options",
|
|
2324
|
-
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */
|
|
2618
|
+
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ jsx30(
|
|
2325
2619
|
Button,
|
|
2326
2620
|
{
|
|
2327
2621
|
variant: allowWheelRotation ? "default" : "outline",
|
|
@@ -2332,7 +2626,7 @@ function GenreTreeView({
|
|
|
2332
2626
|
)
|
|
2333
2627
|
}
|
|
2334
2628
|
),
|
|
2335
|
-
!isLoading && !readOnly && showAddRoot && /* @__PURE__ */
|
|
2629
|
+
!isLoading && !readOnly && showAddRoot && /* @__PURE__ */ jsx30(
|
|
2336
2630
|
IconTextButton,
|
|
2337
2631
|
{
|
|
2338
2632
|
icon: Plus2,
|
|
@@ -2342,42 +2636,42 @@ function GenreTreeView({
|
|
|
2342
2636
|
)
|
|
2343
2637
|
] })
|
|
2344
2638
|
] });
|
|
2345
|
-
return /* @__PURE__ */
|
|
2639
|
+
return /* @__PURE__ */ jsxs23(
|
|
2346
2640
|
"div",
|
|
2347
2641
|
{
|
|
2348
2642
|
ref: rootRef,
|
|
2349
2643
|
className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
|
|
2350
2644
|
children: [
|
|
2351
|
-
/* @__PURE__ */
|
|
2352
|
-
/* @__PURE__ */
|
|
2645
|
+
/* @__PURE__ */ jsx30("style", { children: PREVIOUS_BUTTON_CSS }),
|
|
2646
|
+
/* @__PURE__ */ jsx30(
|
|
2353
2647
|
"div",
|
|
2354
2648
|
{
|
|
2355
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"}`,
|
|
2356
2650
|
children: actions
|
|
2357
2651
|
}
|
|
2358
2652
|
),
|
|
2359
|
-
!isLoading && filterArtist && /* @__PURE__ */
|
|
2653
|
+
!isLoading && filterArtist && /* @__PURE__ */ jsx30(
|
|
2360
2654
|
"div",
|
|
2361
2655
|
{
|
|
2362
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"}`,
|
|
2363
2657
|
children: renderArtistFilter()
|
|
2364
2658
|
}
|
|
2365
2659
|
),
|
|
2366
|
-
detailTab && /* @__PURE__ */
|
|
2660
|
+
detailTab && /* @__PURE__ */ jsx30(
|
|
2367
2661
|
"div",
|
|
2368
2662
|
{
|
|
2369
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))]"}`,
|
|
2370
2664
|
children: detailTab
|
|
2371
2665
|
}
|
|
2372
2666
|
),
|
|
2373
|
-
/* @__PURE__ */
|
|
2374
|
-
!isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */
|
|
2375
|
-
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" ? (
|
|
2376
2670
|
// The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
|
|
2377
2671
|
// and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
|
|
2378
2672
|
// Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
|
|
2379
2673
|
// The panel's own Zoom in button is hidden: the search bar carries it.
|
|
2380
|
-
/* @__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(
|
|
2381
2675
|
GenrePlaylistTreeWheel,
|
|
2382
2676
|
{
|
|
2383
2677
|
scope,
|
|
@@ -2396,9 +2690,9 @@ function GenreTreeView({
|
|
|
2396
2690
|
allowWheelRotation,
|
|
2397
2691
|
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2398
2692
|
busy,
|
|
2399
|
-
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */
|
|
2693
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ jsxs23("div", { className: "flex items-center gap-2", children: [
|
|
2400
2694
|
floatingControlsLeading,
|
|
2401
|
-
canSizeByViews && /* @__PURE__ */
|
|
2695
|
+
canSizeByViews && /* @__PURE__ */ jsx30(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2402
2696
|
] }) : void 0,
|
|
2403
2697
|
defaultViewport,
|
|
2404
2698
|
viewportRestore,
|
|
@@ -2408,7 +2702,7 @@ function GenreTreeView({
|
|
|
2408
2702
|
) }) })
|
|
2409
2703
|
) : viewMode === "pop-core" ? (
|
|
2410
2704
|
// Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
|
|
2411
|
-
/* @__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(
|
|
2412
2706
|
GenrePlaylistTreeWheelRadialPopCore,
|
|
2413
2707
|
{
|
|
2414
2708
|
scope,
|
|
@@ -2427,9 +2721,9 @@ function GenreTreeView({
|
|
|
2427
2721
|
allowWheelRotation,
|
|
2428
2722
|
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2429
2723
|
busy,
|
|
2430
|
-
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */
|
|
2724
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ jsxs23("div", { className: "flex items-center gap-2", children: [
|
|
2431
2725
|
floatingControlsLeading,
|
|
2432
|
-
canSizeByViews && /* @__PURE__ */
|
|
2726
|
+
canSizeByViews && /* @__PURE__ */ jsx30(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2433
2727
|
] }) : void 0,
|
|
2434
2728
|
defaultViewport,
|
|
2435
2729
|
viewportRestore,
|
|
@@ -2438,7 +2732,7 @@ function GenreTreeView({
|
|
|
2438
2732
|
...focus
|
|
2439
2733
|
}
|
|
2440
2734
|
) }) })
|
|
2441
|
-
) : 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(
|
|
2442
2736
|
GenrePlaylistTreeWheelRadialPopCore,
|
|
2443
2737
|
{
|
|
2444
2738
|
outline: true,
|
|
@@ -2457,13 +2751,13 @@ function GenreTreeView({
|
|
|
2457
2751
|
readOnly,
|
|
2458
2752
|
...infoPanelCuration
|
|
2459
2753
|
}
|
|
2460
|
-
) }) : /* @__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(
|
|
2461
2755
|
([uuid, genrePlaylistTreePerRoot], rootIndex) => {
|
|
2462
|
-
return /* @__PURE__ */
|
|
2756
|
+
return /* @__PURE__ */ jsx30(
|
|
2463
2757
|
"div",
|
|
2464
2758
|
{
|
|
2465
2759
|
className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
|
|
2466
|
-
children: /* @__PURE__ */
|
|
2760
|
+
children: /* @__PURE__ */ jsx30("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ jsx30(
|
|
2467
2761
|
GenrePlaylistTreePerRoot,
|
|
2468
2762
|
{
|
|
2469
2763
|
scope,
|
|
@@ -2508,11 +2802,11 @@ import {
|
|
|
2508
2802
|
TEXT_COLOR,
|
|
2509
2803
|
tintSurface
|
|
2510
2804
|
} from "@behindthemusictree/genre-tree-view";
|
|
2511
|
-
import { Skeleton as
|
|
2805
|
+
import { Skeleton as Skeleton7 } from "@behindthemusictree/ui";
|
|
2512
2806
|
import {
|
|
2513
2807
|
useFetchGenreDetail
|
|
2514
2808
|
} from "@behindthemusictree/app-kit/genre-tree";
|
|
2515
|
-
import { jsx as
|
|
2809
|
+
import { jsx as jsx31, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
2516
2810
|
var ITEM_COUNT_LABEL = "Songs";
|
|
2517
2811
|
var RELATIVE_ITEM_COUNT = 1;
|
|
2518
2812
|
function toNode(genre, parentId) {
|
|
@@ -2567,17 +2861,17 @@ function GenreDetailPanel({
|
|
|
2567
2861
|
};
|
|
2568
2862
|
}, [detail]);
|
|
2569
2863
|
if (isError) {
|
|
2570
|
-
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." });
|
|
2571
2865
|
}
|
|
2572
2866
|
if (isPending || !detail || !view) {
|
|
2573
|
-
return /* @__PURE__ */
|
|
2574
|
-
/* @__PURE__ */
|
|
2575
|
-
/* @__PURE__ */
|
|
2576
|
-
/* @__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" })
|
|
2577
2871
|
] });
|
|
2578
2872
|
}
|
|
2579
2873
|
const renderBody = (node, sections) => ({
|
|
2580
|
-
primary: /* @__PURE__ */
|
|
2874
|
+
primary: /* @__PURE__ */ jsx31(
|
|
2581
2875
|
GenreInfoPanelBody,
|
|
2582
2876
|
{
|
|
2583
2877
|
toolbar: sections.toolbar,
|
|
@@ -2594,7 +2888,7 @@ function GenreDetailPanel({
|
|
|
2594
2888
|
),
|
|
2595
2889
|
aside: null
|
|
2596
2890
|
});
|
|
2597
|
-
return /* @__PURE__ */
|
|
2891
|
+
return /* @__PURE__ */ jsx31(
|
|
2598
2892
|
InfoPanel,
|
|
2599
2893
|
{
|
|
2600
2894
|
node: view.node,
|
|
@@ -2627,10 +2921,10 @@ import {
|
|
|
2627
2921
|
// src/genre-tree/track-list-sidebar/TrackListSidebar.tsx
|
|
2628
2922
|
import { useEffect as useEffect7, useLayoutEffect as useLayoutEffect2, useRef as useRef5 } from "react";
|
|
2629
2923
|
import { ListMusic } from "lucide-react";
|
|
2630
|
-
import { useTrackList as
|
|
2924
|
+
import { useTrackList as useTrackList6 } from "@behindthemusictree/app-kit/genre-tree";
|
|
2631
2925
|
import { TrackListOriginType as TrackListOriginType2 } from "@behindthemusictree/app-kit/genre-tree";
|
|
2632
2926
|
import { useTrackNoun as useTrackNoun6 } from "@behindthemusictree/app-kit/track-noun";
|
|
2633
|
-
import { jsx as
|
|
2927
|
+
import { jsx as jsx32, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
2634
2928
|
var useBrowserLayoutEffect = typeof window === "undefined" ? useEffect7 : useLayoutEffect2;
|
|
2635
2929
|
function TrackListSidebar({
|
|
2636
2930
|
className: className2 = "",
|
|
@@ -2640,9 +2934,10 @@ function TrackListSidebar({
|
|
|
2640
2934
|
isCollapsed = false,
|
|
2641
2935
|
onExpand,
|
|
2642
2936
|
onMinimize,
|
|
2643
|
-
renderArtistLink
|
|
2937
|
+
renderArtistLink,
|
|
2938
|
+
renderSongLink
|
|
2644
2939
|
}) {
|
|
2645
|
-
const { trackList, loadMore, autoAdvanceScrollRequest } =
|
|
2940
|
+
const { trackList, loadMore, autoAdvanceScrollRequest } = useTrackList6();
|
|
2646
2941
|
const trackNoun = useTrackNoun6();
|
|
2647
2942
|
const trackListRef = useRef5(null);
|
|
2648
2943
|
const previousOriginRef = useRef5(trackList?.origin ?? null);
|
|
@@ -2731,25 +3026,25 @@ function TrackListSidebar({
|
|
|
2731
3026
|
}
|
|
2732
3027
|
};
|
|
2733
3028
|
const positionClasses = layout === "inline" ? "relative w-full flex h-full flex-col" : "fixed right-0 w-[50rem] flex flex-col";
|
|
2734
|
-
return trackList ? /* @__PURE__ */
|
|
3029
|
+
return trackList ? /* @__PURE__ */ jsxs25(
|
|
2735
3030
|
"div",
|
|
2736
3031
|
{
|
|
2737
3032
|
"data-theme": "dark",
|
|
2738
3033
|
className: `track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${className2}`,
|
|
2739
3034
|
style: layout === "fixed" ? { top: "63.5px", maxHeight: "calc(100vh - 63.5px - 79px)" } : void 0,
|
|
2740
3035
|
children: [
|
|
2741
|
-
/* @__PURE__ */
|
|
2742
|
-
/* @__PURE__ */
|
|
2743
|
-
/* @__PURE__ */
|
|
2744
|
-
/* @__PURE__ */
|
|
2745
|
-
/* @__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 }) })
|
|
2746
3041
|
] }),
|
|
2747
|
-
/* @__PURE__ */
|
|
3042
|
+
/* @__PURE__ */ jsxs25("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
|
|
2748
3043
|
trackList.origin.type === TrackListOriginType2.GENRE_PLAYLIST ? "Genre \u2022 " : trackList.origin.type === TrackListOriginType2.ARTIST ? "Artist \u2022 " : `${trackNoun.singular} playlist \u2022 `,
|
|
2749
3044
|
`${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`
|
|
2750
3045
|
] })
|
|
2751
3046
|
] }),
|
|
2752
|
-
(isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */
|
|
3047
|
+
(isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ jsx32(
|
|
2753
3048
|
"button",
|
|
2754
3049
|
{
|
|
2755
3050
|
type: "button",
|
|
@@ -2758,11 +3053,11 @@ function TrackListSidebar({
|
|
|
2758
3053
|
"aria-expanded": !isCollapsed,
|
|
2759
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"}`,
|
|
2760
3055
|
onClick: isCollapsed ? onExpand : onMinimize,
|
|
2761
|
-
children: /* @__PURE__ */
|
|
3056
|
+
children: /* @__PURE__ */ jsx32(ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
|
|
2762
3057
|
}
|
|
2763
3058
|
)
|
|
2764
3059
|
] }),
|
|
2765
|
-
!isCollapsed && /* @__PURE__ */
|
|
3060
|
+
!isCollapsed && /* @__PURE__ */ jsxs25(
|
|
2766
3061
|
"ul",
|
|
2767
3062
|
{
|
|
2768
3063
|
ref: trackListRef,
|
|
@@ -2772,17 +3067,18 @@ function TrackListSidebar({
|
|
|
2772
3067
|
onPointerDown: markUserScrollIntent,
|
|
2773
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",
|
|
2774
3069
|
children: [
|
|
2775
|
-
trackList.tracks.map((track, index) => /* @__PURE__ */
|
|
3070
|
+
trackList.tracks.map((track, index) => /* @__PURE__ */ jsx32("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ jsx32(
|
|
2776
3071
|
TrackItem,
|
|
2777
3072
|
{
|
|
2778
3073
|
track,
|
|
2779
3074
|
position: index + 1,
|
|
2780
3075
|
renderDuration,
|
|
2781
3076
|
renderActions,
|
|
2782
|
-
renderArtistLink
|
|
3077
|
+
renderArtistLink,
|
|
3078
|
+
renderSongLink
|
|
2783
3079
|
}
|
|
2784
3080
|
) }, track.uuid)),
|
|
2785
|
-
hasMore && /* @__PURE__ */
|
|
3081
|
+
hasMore && /* @__PURE__ */ jsx32("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
|
|
2786
3082
|
]
|
|
2787
3083
|
}
|
|
2788
3084
|
)
|
|
@@ -2792,10 +3088,10 @@ function TrackListSidebar({
|
|
|
2792
3088
|
}
|
|
2793
3089
|
|
|
2794
3090
|
// src/genre-tree/PlayerPanel.tsx
|
|
2795
|
-
import { useEffect as useEffect8, useState as
|
|
3091
|
+
import { useEffect as useEffect8, useState as useState8 } from "react";
|
|
2796
3092
|
import { twMerge as twMerge2 } from "tailwind-merge";
|
|
2797
3093
|
import {
|
|
2798
|
-
usePlayer as
|
|
3094
|
+
usePlayer as usePlayer11,
|
|
2799
3095
|
PlayerVideoSurface
|
|
2800
3096
|
} from "@behindthemusictree/app-kit/player";
|
|
2801
3097
|
import {
|
|
@@ -2803,10 +3099,10 @@ import {
|
|
|
2803
3099
|
} from "@behindthemusictree/app-kit/genre-tree";
|
|
2804
3100
|
|
|
2805
3101
|
// src/genre-tree/PlayerControls.tsx
|
|
2806
|
-
import { Pause as
|
|
2807
|
-
import { formatTime, useTrackList as
|
|
2808
|
-
import { PlayStates as
|
|
2809
|
-
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";
|
|
2810
3106
|
function PlayerControls() {
|
|
2811
3107
|
const {
|
|
2812
3108
|
duration,
|
|
@@ -2817,23 +3113,23 @@ function PlayerControls() {
|
|
|
2817
3113
|
seekTo,
|
|
2818
3114
|
setVolume,
|
|
2819
3115
|
volume
|
|
2820
|
-
} =
|
|
2821
|
-
const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } =
|
|
3116
|
+
} = usePlayer10();
|
|
3117
|
+
const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } = useTrackList7();
|
|
2822
3118
|
const currentTime = useCurrentTime();
|
|
2823
|
-
const isBusy = isLoading || playState ===
|
|
3119
|
+
const isBusy = isLoading || playState === PlayStates4.LOADING;
|
|
2824
3120
|
const isReady = !!playerTrackObject?.isReady && !isBusy && !playerTrackObject.loadError;
|
|
2825
3121
|
const safeDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;
|
|
2826
3122
|
const safeCurrentTime = Number.isFinite(currentTime) ? Math.max(0, Math.min(currentTime, safeDuration)) : 0;
|
|
2827
|
-
const isPlaying = playState ===
|
|
2828
|
-
return /* @__PURE__ */
|
|
3123
|
+
const isPlaying = playState === PlayStates4.PLAYING;
|
|
3124
|
+
return /* @__PURE__ */ jsxs26(
|
|
2829
3125
|
"section",
|
|
2830
3126
|
{
|
|
2831
3127
|
"aria-label": "Player controls",
|
|
2832
3128
|
className: "player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200",
|
|
2833
3129
|
children: [
|
|
2834
|
-
/* @__PURE__ */
|
|
2835
|
-
/* @__PURE__ */
|
|
2836
|
-
/* @__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(
|
|
2837
3133
|
"input",
|
|
2838
3134
|
{
|
|
2839
3135
|
"aria-label": "Seek",
|
|
@@ -2847,11 +3143,11 @@ function PlayerControls() {
|
|
|
2847
3143
|
onChange: (event) => seekTo(Number(event.currentTarget.value))
|
|
2848
3144
|
}
|
|
2849
3145
|
),
|
|
2850
|
-
/* @__PURE__ */
|
|
3146
|
+
/* @__PURE__ */ jsx33("span", { "aria-label": "Duration", children: formatTime(safeDuration) })
|
|
2851
3147
|
] }),
|
|
2852
|
-
/* @__PURE__ */
|
|
2853
|
-
/* @__PURE__ */
|
|
2854
|
-
/* @__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(
|
|
2855
3151
|
"button",
|
|
2856
3152
|
{
|
|
2857
3153
|
type: "button",
|
|
@@ -2860,10 +3156,10 @@ function PlayerControls() {
|
|
|
2860
3156
|
disabled: !isReady || !hasPreviousTrack,
|
|
2861
3157
|
onClick: playPreviousTrack,
|
|
2862
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",
|
|
2863
|
-
children: /* @__PURE__ */
|
|
3159
|
+
children: /* @__PURE__ */ jsx33(SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
2864
3160
|
}
|
|
2865
3161
|
),
|
|
2866
|
-
/* @__PURE__ */
|
|
3162
|
+
/* @__PURE__ */ jsx33(
|
|
2867
3163
|
"button",
|
|
2868
3164
|
{
|
|
2869
3165
|
type: "button",
|
|
@@ -2872,10 +3168,10 @@ function PlayerControls() {
|
|
|
2872
3168
|
disabled: !isReady,
|
|
2873
3169
|
onClick: handlePlayPauseAction,
|
|
2874
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",
|
|
2875
|
-
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" })
|
|
2876
3172
|
}
|
|
2877
3173
|
),
|
|
2878
|
-
/* @__PURE__ */
|
|
3174
|
+
/* @__PURE__ */ jsx33(
|
|
2879
3175
|
"button",
|
|
2880
3176
|
{
|
|
2881
3177
|
type: "button",
|
|
@@ -2884,14 +3180,14 @@ function PlayerControls() {
|
|
|
2884
3180
|
disabled: !isReady || !hasNextTrack,
|
|
2885
3181
|
onClick: playNextTrack,
|
|
2886
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",
|
|
2887
|
-
children: /* @__PURE__ */
|
|
3183
|
+
children: /* @__PURE__ */ jsx33(SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
2888
3184
|
}
|
|
2889
3185
|
)
|
|
2890
3186
|
] }),
|
|
2891
|
-
/* @__PURE__ */
|
|
2892
|
-
volume === 0 ? /* @__PURE__ */
|
|
2893
|
-
/* @__PURE__ */
|
|
2894
|
-
/* @__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(
|
|
2895
3191
|
"input",
|
|
2896
3192
|
{
|
|
2897
3193
|
"aria-label": "Volume",
|
|
@@ -2913,7 +3209,7 @@ function PlayerControls() {
|
|
|
2913
3209
|
}
|
|
2914
3210
|
|
|
2915
3211
|
// src/genre-tree/PlayerPanel.tsx
|
|
2916
|
-
import { jsx as
|
|
3212
|
+
import { jsx as jsx34, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
2917
3213
|
var PANEL_ID = "player-panel";
|
|
2918
3214
|
var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
|
|
2919
3215
|
function PlayerPanel({
|
|
@@ -2922,16 +3218,17 @@ function PlayerPanel({
|
|
|
2922
3218
|
expanded,
|
|
2923
3219
|
onExpandedChange,
|
|
2924
3220
|
extendedWidth = DEFAULT_EXTENDED_WIDTH,
|
|
2925
|
-
renderArtistLink
|
|
3221
|
+
renderArtistLink,
|
|
3222
|
+
renderSongLink
|
|
2926
3223
|
}) {
|
|
2927
|
-
const { playerTrackObject } =
|
|
3224
|
+
const { playerTrackObject } = usePlayer11();
|
|
2928
3225
|
const { toggleTrackListSidebar, isTrackListSidebarVisible } = useTrackListSidebarVisibility();
|
|
2929
|
-
const [lastLoadedTrackTitle, setLastLoadedTrackTitle] =
|
|
3226
|
+
const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = useState8("");
|
|
2930
3227
|
const loadedTrackTitle = playerTrackObject?.track.title;
|
|
2931
3228
|
useEffect8(() => {
|
|
2932
3229
|
if (loadedTrackTitle) setLastLoadedTrackTitle(loadedTrackTitle);
|
|
2933
3230
|
}, [loadedTrackTitle]);
|
|
2934
|
-
const [uncontrolledExtended, setUncontrolledExtended] =
|
|
3231
|
+
const [uncontrolledExtended, setUncontrolledExtended] = useState8(false);
|
|
2935
3232
|
const isExtendRequested = expanded ?? uncontrolledExtended;
|
|
2936
3233
|
const setIsExtendRequested = (next) => {
|
|
2937
3234
|
setUncontrolledExtended(next);
|
|
@@ -2952,7 +3249,7 @@ function PlayerPanel({
|
|
|
2952
3249
|
onClick: () => setIsExtendRequested(true),
|
|
2953
3250
|
expanded: false
|
|
2954
3251
|
} : null;
|
|
2955
|
-
return /* @__PURE__ */
|
|
3252
|
+
return /* @__PURE__ */ jsxs27(
|
|
2956
3253
|
"div",
|
|
2957
3254
|
{
|
|
2958
3255
|
id: PANEL_ID,
|
|
@@ -2965,7 +3262,7 @@ function PlayerPanel({
|
|
|
2965
3262
|
),
|
|
2966
3263
|
style: isExtended ? { width: extendedWidth } : void 0,
|
|
2967
3264
|
children: [
|
|
2968
|
-
/* @__PURE__ */
|
|
3265
|
+
/* @__PURE__ */ jsx34(
|
|
2969
3266
|
PanelEdgeTab,
|
|
2970
3267
|
{
|
|
2971
3268
|
side: "right",
|
|
@@ -2975,7 +3272,7 @@ function PlayerPanel({
|
|
|
2975
3272
|
className: "absolute right-full top-1/2 -translate-y-1/2"
|
|
2976
3273
|
}
|
|
2977
3274
|
),
|
|
2978
|
-
/* @__PURE__ */
|
|
3275
|
+
/* @__PURE__ */ jsxs27(
|
|
2979
3276
|
"div",
|
|
2980
3277
|
{
|
|
2981
3278
|
className: twMerge2(
|
|
@@ -2983,15 +3280,15 @@ function PlayerPanel({
|
|
|
2983
3280
|
isExtended ? "flex-row" : "flex-col"
|
|
2984
3281
|
),
|
|
2985
3282
|
children: [
|
|
2986
|
-
/* @__PURE__ */
|
|
2987
|
-
/* @__PURE__ */
|
|
2988
|
-
/* @__PURE__ */
|
|
2989
|
-
/* @__PURE__ */
|
|
2990
|
-
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 })
|
|
2991
3288
|
] }),
|
|
2992
|
-
/* @__PURE__ */
|
|
3289
|
+
/* @__PURE__ */ jsx34(PlayerControls, {})
|
|
2993
3290
|
] }),
|
|
2994
|
-
/* @__PURE__ */
|
|
3291
|
+
/* @__PURE__ */ jsx34(
|
|
2995
3292
|
TrackListSidebar,
|
|
2996
3293
|
{
|
|
2997
3294
|
layout: "inline",
|
|
@@ -3002,7 +3299,8 @@ function PlayerPanel({
|
|
|
3002
3299
|
"min-h-0 min-w-0 flex-1",
|
|
3003
3300
|
isExtended && "border-l border-zinc-800"
|
|
3004
3301
|
),
|
|
3005
|
-
renderArtistLink
|
|
3302
|
+
renderArtistLink,
|
|
3303
|
+
renderSongLink
|
|
3006
3304
|
}
|
|
3007
3305
|
)
|
|
3008
3306
|
]
|
|
@@ -3017,11 +3315,11 @@ function PlayerPanel({
|
|
|
3017
3315
|
import { v4 as uuidv4 } from "uuid";
|
|
3018
3316
|
import { AiFillStar, AiOutlineStar } from "react-icons/ai";
|
|
3019
3317
|
import { FORM_RATING_NULL_VALUE } from "@behindthemusictree/app-kit/genre-tree";
|
|
3020
|
-
import { jsx as
|
|
3318
|
+
import { jsx as jsx35, jsxs as jsxs28 } from "react/jsx-runtime";
|
|
3021
3319
|
function Rating({ rating, handleChange }) {
|
|
3022
3320
|
const instanceId = uuidv4();
|
|
3023
|
-
return /* @__PURE__ */
|
|
3024
|
-
/* @__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(
|
|
3025
3323
|
"input",
|
|
3026
3324
|
{
|
|
3027
3325
|
id: `star-${instanceId}-${i}`,
|
|
@@ -3033,16 +3331,16 @@ function Rating({ rating, handleChange }) {
|
|
|
3033
3331
|
onChange: handleChange
|
|
3034
3332
|
}
|
|
3035
3333
|
),
|
|
3036
|
-
/* @__PURE__ */
|
|
3037
|
-
/* @__PURE__ */
|
|
3334
|
+
/* @__PURE__ */ jsx35("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ jsxs28("div", { className: "flex", children: [
|
|
3335
|
+
/* @__PURE__ */ jsx35(
|
|
3038
3336
|
AiOutlineStar,
|
|
3039
3337
|
{
|
|
3040
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)]"}`
|
|
3041
3339
|
}
|
|
3042
3340
|
),
|
|
3043
|
-
/* @__PURE__ */
|
|
3341
|
+
/* @__PURE__ */ jsx35("span", { className: "flex-grow w-8" }),
|
|
3044
3342
|
" "
|
|
3045
|
-
] }) : /* @__PURE__ */
|
|
3343
|
+
] }) : /* @__PURE__ */ jsx35(
|
|
3046
3344
|
AiFillStar,
|
|
3047
3345
|
{
|
|
3048
3346
|
className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
|
|
@@ -3071,4 +3369,4 @@ export {
|
|
|
3071
3369
|
GenreTreeViewSkeleton2 as GenreTreeViewSkeleton,
|
|
3072
3370
|
GenreTreeOutlineSkeleton
|
|
3073
3371
|
};
|
|
3074
|
-
//# sourceMappingURL=chunk-
|
|
3372
|
+
//# sourceMappingURL=chunk-2HE2HDFA.js.map
|