@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
|
@@ -42,13 +42,13 @@ __export(genre_tree_exports, {
|
|
|
42
42
|
module.exports = __toCommonJS(genre_tree_exports);
|
|
43
43
|
|
|
44
44
|
// src/genre-tree/GenreTreeView.tsx
|
|
45
|
-
var
|
|
45
|
+
var import_react17 = require("react");
|
|
46
46
|
var import_react_query3 = require("@tanstack/react-query");
|
|
47
|
-
var
|
|
48
|
-
var
|
|
47
|
+
var import_lucide_react12 = require("lucide-react");
|
|
48
|
+
var import_ui15 = require("@behindthemusictree/ui");
|
|
49
49
|
var import_genre_tree_view4 = require("@behindthemusictree/genre-tree-view");
|
|
50
|
-
var
|
|
51
|
-
var
|
|
50
|
+
var import_genre_tree18 = require("@behindthemusictree/app-kit/genre-tree");
|
|
51
|
+
var import_genre_tree19 = require("@behindthemusictree/app-kit/genre-tree");
|
|
52
52
|
var import_popup5 = require("@behindthemusictree/app-kit/popup");
|
|
53
53
|
|
|
54
54
|
// src/genre-tree/playlist-tree/TreePerRoot.tsx
|
|
@@ -736,6 +736,7 @@ function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
|
|
|
736
736
|
}
|
|
737
737
|
|
|
738
738
|
// src/genre-tree/GenreSearch.tsx
|
|
739
|
+
var import_react7 = require("react");
|
|
739
740
|
var import_lucide_react3 = require("lucide-react");
|
|
740
741
|
var import_ui3 = require("@behindthemusictree/ui");
|
|
741
742
|
var import_genre_tree2 = require("@behindthemusictree/app-kit/genre-tree");
|
|
@@ -748,7 +749,7 @@ function GenreSearch({
|
|
|
748
749
|
onPrevious,
|
|
749
750
|
placeholder,
|
|
750
751
|
onQueryChange,
|
|
751
|
-
|
|
752
|
+
extraGroups = []
|
|
752
753
|
}) {
|
|
753
754
|
const { query, setQuery, results, canClear, clear, select } = (0, import_genre_tree2.useGenreSearch)({
|
|
754
755
|
genrePlaylists,
|
|
@@ -756,11 +757,45 @@ function GenreSearch({
|
|
|
756
757
|
selectedName,
|
|
757
758
|
onClear
|
|
758
759
|
});
|
|
759
|
-
const
|
|
760
|
-
const
|
|
761
|
-
const
|
|
762
|
-
const
|
|
763
|
-
const
|
|
760
|
+
const [open, setOpen] = (0, import_react7.useState)(false);
|
|
761
|
+
const active = query !== "" && query !== selectedName;
|
|
762
|
+
const hasExtras = extraGroups.length > 0;
|
|
763
|
+
const label = hasExtras ? "Search" : "Search a genre";
|
|
764
|
+
const loading = active && extraGroups.some((group) => group.loading);
|
|
765
|
+
const status = active ? extraGroups.map((group) => group.status).filter(Boolean).join(". ") : "";
|
|
766
|
+
const groups = [
|
|
767
|
+
{
|
|
768
|
+
key: "genres",
|
|
769
|
+
label: hasExtras ? "Genres" : void 0,
|
|
770
|
+
options: results.map((genrePlaylist) => ({
|
|
771
|
+
key: genrePlaylist.uuid,
|
|
772
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
|
|
773
|
+
genrePlaylist.name,
|
|
774
|
+
genrePlaylist.isUmbrella && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ml-2 text-xs italic text-[var(--color-text-muted)]", children: "umbrella" })
|
|
775
|
+
] })
|
|
776
|
+
}))
|
|
777
|
+
},
|
|
778
|
+
...extraGroups.map((group) => ({
|
|
779
|
+
key: group.key,
|
|
780
|
+
label: group.label,
|
|
781
|
+
options: active ? group.items.map((item) => ({ key: item.key, content: item.content })) : []
|
|
782
|
+
}))
|
|
783
|
+
];
|
|
784
|
+
const handlePick = (groupKey, optionKey) => {
|
|
785
|
+
setOpen(false);
|
|
786
|
+
if (groupKey === "genres") {
|
|
787
|
+
const genrePlaylist = results.find((result) => result.uuid === optionKey);
|
|
788
|
+
if (!genrePlaylist) return;
|
|
789
|
+
select(genrePlaylist);
|
|
790
|
+
onQueryChange?.("");
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
const item = extraGroups.find((group) => group.key === groupKey)?.items.find((entry) => entry.key === optionKey);
|
|
794
|
+
if (!item) return;
|
|
795
|
+
setQuery("");
|
|
796
|
+
onQueryChange?.("");
|
|
797
|
+
item.onSelect();
|
|
798
|
+
};
|
|
764
799
|
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "genre-search relative w-[calc(var(--layout-detail-panel-w,25.5rem)-2*var(--layout-panel-inset,1rem))] shrink-0 max-md:w-full", children: [
|
|
765
800
|
onPrevious && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
766
801
|
"button",
|
|
@@ -774,47 +809,45 @@ function GenreSearch({
|
|
|
774
809
|
}
|
|
775
810
|
),
|
|
776
811
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
777
|
-
import_ui3.
|
|
812
|
+
import_ui3.Combobox,
|
|
778
813
|
{
|
|
779
|
-
|
|
814
|
+
ariaLabel: label,
|
|
815
|
+
listLabel: `${label} results`,
|
|
780
816
|
value: query,
|
|
781
|
-
onChange: (event) => {
|
|
782
|
-
setQuery(event.target.value);
|
|
783
|
-
onQueryChange?.(event.target.value);
|
|
784
|
-
},
|
|
785
817
|
placeholder: placeholder ?? `${label}\u2026`,
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
818
|
+
groups,
|
|
819
|
+
open,
|
|
820
|
+
onOpenChange: setOpen,
|
|
821
|
+
onValueChange: (value) => {
|
|
822
|
+
setQuery(value);
|
|
823
|
+
onQueryChange?.(value);
|
|
824
|
+
},
|
|
825
|
+
onPick: handlePick,
|
|
826
|
+
status,
|
|
827
|
+
loading: loading ? "Searching\u2026" : void 0,
|
|
828
|
+
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",
|
|
829
|
+
className: "genre-search-results z-40 rounded-xl shadow-md",
|
|
830
|
+
listClassName: "max-h-[min(20rem,50dvh)]",
|
|
831
|
+
optionClassName: "[@media(pointer:coarse)]:min-h-[var(--layout-touch-target)]"
|
|
792
832
|
}
|
|
793
833
|
),
|
|
794
834
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "absolute right-3 top-1/2 flex items-center -translate-y-1/2", children: [
|
|
795
|
-
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
"aria-hidden": "true",
|
|
812
|
-
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"}`
|
|
813
|
-
}
|
|
814
|
-
)
|
|
815
|
-
]
|
|
816
|
-
}
|
|
817
|
-
),
|
|
835
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { "data-loading": loading, className: "relative p-2 text-[var(--color-text-muted)] pointer-events-none", children: [
|
|
836
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
837
|
+
import_lucide_react3.Search,
|
|
838
|
+
{
|
|
839
|
+
"aria-hidden": "true",
|
|
840
|
+
className: `w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? "opacity-0" : ""}`
|
|
841
|
+
}
|
|
842
|
+
),
|
|
843
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
844
|
+
import_lucide_react3.LoaderCircle,
|
|
845
|
+
{
|
|
846
|
+
"aria-hidden": "true",
|
|
847
|
+
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"}`
|
|
848
|
+
}
|
|
849
|
+
)
|
|
850
|
+
] }),
|
|
818
851
|
canClear && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
819
852
|
"button",
|
|
820
853
|
{
|
|
@@ -828,39 +861,6 @@ function GenreSearch({
|
|
|
828
861
|
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react3.X, { "aria-hidden": "true", className: "w-6 h-6" })
|
|
829
862
|
}
|
|
830
863
|
)
|
|
831
|
-
] }),
|
|
832
|
-
extraResults?.status && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { role: "status", "aria-live": "polite", className: "sr-only", children: extraResults.status }),
|
|
833
|
-
showList && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("ul", { className: "genre-search-results absolute z-40 w-full max-h-[min(16rem,50dvh)] mt-1 overflow-y-auto bg-[var(--color-surface)] border border-[var(--color-border)] rounded-xl shadow-md", children: [
|
|
834
|
-
extraResults && results.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("li", { className: heading, children: "Genres" }),
|
|
835
|
-
results.map((genrePlaylist) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
836
|
-
"button",
|
|
837
|
-
{
|
|
838
|
-
type: "button",
|
|
839
|
-
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)]",
|
|
840
|
-
onClick: () => {
|
|
841
|
-
select(genrePlaylist);
|
|
842
|
-
onQueryChange?.("");
|
|
843
|
-
},
|
|
844
|
-
children: [
|
|
845
|
-
genrePlaylist.name,
|
|
846
|
-
genrePlaylist.isUmbrella && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ml-2 text-xs italic text-[var(--color-text-muted)]", children: "umbrella" })
|
|
847
|
-
]
|
|
848
|
-
}
|
|
849
|
-
) }, genrePlaylist.uuid)),
|
|
850
|
-
extraResults && extraItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("li", { className: heading, children: extraResults.label }),
|
|
851
|
-
extraItems.map((item) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
852
|
-
"button",
|
|
853
|
-
{
|
|
854
|
-
type: "button",
|
|
855
|
-
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)]",
|
|
856
|
-
onClick: () => {
|
|
857
|
-
setQuery("");
|
|
858
|
-
onQueryChange?.("");
|
|
859
|
-
item.onSelect();
|
|
860
|
-
},
|
|
861
|
-
children: item.content
|
|
862
|
-
}
|
|
863
|
-
) }, item.key))
|
|
864
864
|
] })
|
|
865
865
|
] });
|
|
866
866
|
}
|
|
@@ -876,18 +876,18 @@ var import_lucide_react6 = require("lucide-react");
|
|
|
876
876
|
var import_popup3 = require("@behindthemusictree/app-kit/popup");
|
|
877
877
|
|
|
878
878
|
// src/genre-tree/SharePopup.tsx
|
|
879
|
-
var
|
|
879
|
+
var import_react9 = require("react");
|
|
880
880
|
var import_lucide_react5 = require("lucide-react");
|
|
881
881
|
var import_fa = require("react-icons/fa");
|
|
882
882
|
var import_si = require("react-icons/si");
|
|
883
883
|
|
|
884
884
|
// src/popup/Toast.tsx
|
|
885
|
-
var
|
|
885
|
+
var import_react8 = require("react");
|
|
886
886
|
var import_react_dom2 = require("react-dom");
|
|
887
887
|
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
888
888
|
function Toast({ message, onClose, durationMs = 2200 }) {
|
|
889
|
-
const [mounted, setMounted] = (0,
|
|
890
|
-
(0,
|
|
889
|
+
const [mounted, setMounted] = (0, import_react8.useState)(false);
|
|
890
|
+
(0, import_react8.useEffect)(() => {
|
|
891
891
|
setMounted(true);
|
|
892
892
|
const timeout = window.setTimeout(onClose, durationMs);
|
|
893
893
|
return () => window.clearTimeout(timeout);
|
|
@@ -935,8 +935,8 @@ function ShareDestinationButton({
|
|
|
935
935
|
var import_lucide_react4 = require("lucide-react");
|
|
936
936
|
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
937
937
|
function SharePreviewCard({ target, url }) {
|
|
938
|
-
const Icon =
|
|
939
|
-
const typeLabel =
|
|
938
|
+
const Icon = { genre: import_lucide_react4.Music2, artist: import_lucide_react4.UserRound, song: import_lucide_react4.Music }[target.kind];
|
|
939
|
+
const typeLabel = { genre: "Genre", artist: "Artist", song: "Song" }[target.kind];
|
|
940
940
|
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "rounded-3xl bg-[var(--color-surface-muted)] p-4", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex min-h-32 overflow-hidden rounded-2xl bg-[var(--color-surface)] shadow-lg", children: [
|
|
941
941
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "flex w-28 shrink-0 items-center justify-center bg-[var(--color-primary)]/15 text-[var(--color-primary)]", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Icon, { "aria-hidden": "true", className: "h-10 w-10" }) }),
|
|
942
942
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col justify-center gap-1 p-4", children: [
|
|
@@ -981,20 +981,20 @@ async function copyText(text) {
|
|
|
981
981
|
}
|
|
982
982
|
}
|
|
983
983
|
function SharePopup({ target, url, onClose }) {
|
|
984
|
-
const [absoluteUrl, setAbsoluteUrl] = (0,
|
|
985
|
-
const [copyStatus, setCopyStatus] = (0,
|
|
986
|
-
const [canUseNativeShare, setCanUseNativeShare] = (0,
|
|
987
|
-
const [shareError, setShareError] = (0,
|
|
988
|
-
const [toastId, setToastId] = (0,
|
|
989
|
-
(0,
|
|
984
|
+
const [absoluteUrl, setAbsoluteUrl] = (0, import_react9.useState)(url);
|
|
985
|
+
const [copyStatus, setCopyStatus] = (0, import_react9.useState)("idle");
|
|
986
|
+
const [canUseNativeShare, setCanUseNativeShare] = (0, import_react9.useState)(false);
|
|
987
|
+
const [shareError, setShareError] = (0, import_react9.useState)(null);
|
|
988
|
+
const [toastId, setToastId] = (0, import_react9.useState)(0);
|
|
989
|
+
(0, import_react9.useEffect)(() => {
|
|
990
990
|
setAbsoluteUrl(resolveAbsoluteUrl(url) ?? "");
|
|
991
991
|
setCopyStatus("idle");
|
|
992
992
|
setShareError(null);
|
|
993
993
|
}, [url]);
|
|
994
|
-
(0,
|
|
994
|
+
(0, import_react9.useEffect)(() => {
|
|
995
995
|
setCanUseNativeShare(typeof navigator !== "undefined" && typeof navigator.share === "function");
|
|
996
996
|
}, []);
|
|
997
|
-
const destinationUrls = (0,
|
|
997
|
+
const destinationUrls = (0, import_react9.useMemo)(() => {
|
|
998
998
|
if (!absoluteUrl) return null;
|
|
999
999
|
const message = `${target.name} \u2014 ${absoluteUrl}`;
|
|
1000
1000
|
return {
|
|
@@ -1012,7 +1012,7 @@ function SharePopup({ target, url, onClose }) {
|
|
|
1012
1012
|
setCopyStatus(copied ? "idle" : "failed");
|
|
1013
1013
|
if (copied) setToastId((id) => id + 1);
|
|
1014
1014
|
};
|
|
1015
|
-
const dismissToast = (0,
|
|
1015
|
+
const dismissToast = (0, import_react9.useCallback)(() => setToastId(0), []);
|
|
1016
1016
|
const handleNativeShare = async () => {
|
|
1017
1017
|
if (!absoluteUrl || !navigator.share) return;
|
|
1018
1018
|
setShareError(null);
|
|
@@ -1218,13 +1218,13 @@ var import_ui5 = require("@behindthemusictree/ui");
|
|
|
1218
1218
|
var import_genre_tree9 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1219
1219
|
|
|
1220
1220
|
// src/genre-tree/ClampedText.tsx
|
|
1221
|
-
var
|
|
1221
|
+
var import_react10 = require("react");
|
|
1222
1222
|
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
1223
1223
|
function ClampedText({ children, className: className2 = "" }) {
|
|
1224
|
-
const ref = (0,
|
|
1225
|
-
const [expanded, setExpanded] = (0,
|
|
1226
|
-
const [overflows, setOverflows] = (0,
|
|
1227
|
-
(0,
|
|
1224
|
+
const ref = (0, import_react10.useRef)(null);
|
|
1225
|
+
const [expanded, setExpanded] = (0, import_react10.useState)(false);
|
|
1226
|
+
const [overflows, setOverflows] = (0, import_react10.useState)(false);
|
|
1227
|
+
(0, import_react10.useLayoutEffect)(() => {
|
|
1228
1228
|
const element = ref.current;
|
|
1229
1229
|
if (!element || expanded) return;
|
|
1230
1230
|
setOverflows(element.scrollHeight > element.clientHeight);
|
|
@@ -1245,7 +1245,7 @@ function ClampedText({ children, className: className2 = "" }) {
|
|
|
1245
1245
|
}
|
|
1246
1246
|
|
|
1247
1247
|
// src/genre-tree/GenrePlaylistTracks.tsx
|
|
1248
|
-
var
|
|
1248
|
+
var import_react11 = require("react");
|
|
1249
1249
|
var import_react_query = require("@tanstack/react-query");
|
|
1250
1250
|
var import_zod = require("zod");
|
|
1251
1251
|
var import_fa2 = require("react-icons/fa");
|
|
@@ -1313,6 +1313,7 @@ function GenrePlaylistTracks({
|
|
|
1313
1313
|
scope,
|
|
1314
1314
|
getBackendBaseUrl,
|
|
1315
1315
|
renderArtistLink,
|
|
1316
|
+
renderSongLink,
|
|
1316
1317
|
renderSongLeading,
|
|
1317
1318
|
renderSongActions,
|
|
1318
1319
|
renderTracksToolbar,
|
|
@@ -1343,8 +1344,8 @@ function GenrePlaylistTracks({
|
|
|
1343
1344
|
getNextPageParam: (lastPage) => lastPage.nextPage ?? void 0,
|
|
1344
1345
|
enabled: scope !== "me" || sessionRestored && !!session?.accessToken
|
|
1345
1346
|
});
|
|
1346
|
-
const sentinelRef = (0,
|
|
1347
|
-
(0,
|
|
1347
|
+
const sentinelRef = (0, import_react11.useRef)(null);
|
|
1348
|
+
(0, import_react11.useEffect)(() => {
|
|
1348
1349
|
const sentinel = sentinelRef.current;
|
|
1349
1350
|
if (!hasNextPage || isFetchingNextPage || !sentinel) return;
|
|
1350
1351
|
const observer = new IntersectionObserver((entries) => {
|
|
@@ -1383,7 +1384,7 @@ function GenrePlaylistTracks({
|
|
|
1383
1384
|
const isCurrent = playerTrackObject?.track.id === track.uuid;
|
|
1384
1385
|
const isPlaying = isCurrent && playState === import_player5.PlayStates.PLAYING;
|
|
1385
1386
|
const text = /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: TEXT_CELL, children: [
|
|
1386
|
-
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: TITLE_CELL, children: (0, import_genre_tree6.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
|
|
1387
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: TITLE_CELL, children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree6.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
|
|
1387
1388
|
track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: SEPARATOR, children: " \u2014 " }) : null,
|
|
1388
1389
|
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_genre_tree8.ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
|
|
1389
1390
|
track.album && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: ALBUM_CELL, children: track.album.name })
|
|
@@ -1450,6 +1451,7 @@ function GenreDetailExtras({
|
|
|
1450
1451
|
criteriaOverviewSchema,
|
|
1451
1452
|
renderGenreDetailExtras,
|
|
1452
1453
|
renderArtistLink,
|
|
1454
|
+
renderSongLink,
|
|
1453
1455
|
renderSummary,
|
|
1454
1456
|
renderEssentialTracks,
|
|
1455
1457
|
family,
|
|
@@ -1527,6 +1529,7 @@ function GenreDetailExtras({
|
|
|
1527
1529
|
scope,
|
|
1528
1530
|
getBackendBaseUrl,
|
|
1529
1531
|
renderArtistLink,
|
|
1532
|
+
renderSongLink,
|
|
1530
1533
|
...trackSlots
|
|
1531
1534
|
}
|
|
1532
1535
|
)
|
|
@@ -1535,7 +1538,7 @@ function GenreDetailExtras({
|
|
|
1535
1538
|
|
|
1536
1539
|
// src/genre-tree/GenreInfoPanelBody.tsx
|
|
1537
1540
|
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
1538
|
-
var
|
|
1541
|
+
var import_react12 = require("react");
|
|
1539
1542
|
function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
|
|
1540
1543
|
const { isPlaying, setIsPlaying } = (0, import_player6.usePlayer)();
|
|
1541
1544
|
const isCurrentTrackList = playback.playingNodeId === genrePlaylist.uuid;
|
|
@@ -1572,7 +1575,7 @@ function GenreInfoPanelBody({
|
|
|
1572
1575
|
return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
|
|
1573
1576
|
toolbar,
|
|
1574
1577
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(GenreNodeActionBand, { ...band, genrePlaylist, playback }),
|
|
1575
|
-
/* @__PURE__ */ (0,
|
|
1578
|
+
/* @__PURE__ */ (0, import_react12.createElement)(
|
|
1576
1579
|
GenreDetailExtras,
|
|
1577
1580
|
{
|
|
1578
1581
|
...extras,
|
|
@@ -1632,9 +1635,9 @@ function PanelEdgeTab({ side, edge, center, controls, className: className2 }) {
|
|
|
1632
1635
|
}
|
|
1633
1636
|
|
|
1634
1637
|
// src/genre-tree/useLeftDetailPanelLayout.ts
|
|
1635
|
-
var
|
|
1638
|
+
var import_react13 = require("react");
|
|
1636
1639
|
function useLeftDetailPanelLayout(isPanelShown, enabled = true) {
|
|
1637
|
-
(0,
|
|
1640
|
+
(0, import_react13.useEffect)(() => {
|
|
1638
1641
|
if (!enabled || isPanelShown) return void 0;
|
|
1639
1642
|
const root = document.documentElement;
|
|
1640
1643
|
root.style.setProperty("--layout-left-w", "0px");
|
|
@@ -1645,7 +1648,7 @@ function useLeftDetailPanelLayout(isPanelShown, enabled = true) {
|
|
|
1645
1648
|
}
|
|
1646
1649
|
|
|
1647
1650
|
// src/genre-tree/ChangeParentPopup.tsx
|
|
1648
|
-
var
|
|
1651
|
+
var import_react14 = require("react");
|
|
1649
1652
|
var import_genre_tree10 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1650
1653
|
var import_jsx_runtime22 = require("react/jsx-runtime");
|
|
1651
1654
|
function ChangeParentPopup({
|
|
@@ -1657,7 +1660,7 @@ function ChangeParentPopup({
|
|
|
1657
1660
|
onParentChanged
|
|
1658
1661
|
}) {
|
|
1659
1662
|
const { mutate, isPending, formErrors } = (0, import_genre_tree10.useUpdateGenre)(scope, getBackendBaseUrl);
|
|
1660
|
-
const candidates = (0,
|
|
1663
|
+
const candidates = (0, import_react14.useMemo)(
|
|
1661
1664
|
() => (0, import_genre_tree10.getReparentCandidates)(genrePlaylists, genrePlaylist.uuid),
|
|
1662
1665
|
[genrePlaylists, genrePlaylist.uuid]
|
|
1663
1666
|
);
|
|
@@ -1675,7 +1678,7 @@ function ChangeParentPopup({
|
|
|
1675
1678
|
}
|
|
1676
1679
|
);
|
|
1677
1680
|
};
|
|
1678
|
-
(0,
|
|
1681
|
+
(0, import_react14.useEffect)(() => {
|
|
1679
1682
|
const handleKeyDown = (event) => {
|
|
1680
1683
|
if (event.key === "Escape") onClose();
|
|
1681
1684
|
};
|
|
@@ -1863,7 +1866,7 @@ function ArtistDiscography({ albums }) {
|
|
|
1863
1866
|
}
|
|
1864
1867
|
|
|
1865
1868
|
// src/genre-tree/artist/ArtistSongs.tsx
|
|
1866
|
-
var
|
|
1869
|
+
var import_react15 = require("react");
|
|
1867
1870
|
var import_react_query2 = require("@tanstack/react-query");
|
|
1868
1871
|
var import_lucide_react10 = require("lucide-react");
|
|
1869
1872
|
var import_ui10 = require("@behindthemusictree/ui");
|
|
@@ -1936,7 +1939,8 @@ function TrackItem({
|
|
|
1936
1939
|
position,
|
|
1937
1940
|
renderDuration,
|
|
1938
1941
|
renderActions,
|
|
1939
|
-
renderArtistLink
|
|
1942
|
+
renderArtistLink,
|
|
1943
|
+
renderSongLink
|
|
1940
1944
|
}) {
|
|
1941
1945
|
const { handlePlayPauseAction, playerTrackObject, loadTrackForPlayer } = (0, import_player9.usePlayer)();
|
|
1942
1946
|
const { trackList, selectedTrack, toTrackAtPosition, runtimeUnplayableTrackIds } = (0, import_genre_tree11.useTrackList)();
|
|
@@ -1976,7 +1980,7 @@ function TrackItem({
|
|
|
1976
1980
|
}
|
|
1977
1981
|
),
|
|
1978
1982
|
/* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
|
|
1979
|
-
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: (0, import_genre_tree12.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
|
|
1983
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree12.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
|
|
1980
1984
|
track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_genre_tree13.ArtistNames, { artists: track.artists, renderArtistLink }) }) : ""
|
|
1981
1985
|
] }),
|
|
1982
1986
|
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "genre-name-container flex items-center justify-start w-[21.6%] min-w-0 shrink-0", children: track.genre ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
|
|
@@ -2001,9 +2005,9 @@ var SORTS = [
|
|
|
2001
2005
|
{ ordering: "title", label: "Title" },
|
|
2002
2006
|
{ ordering: "-albumYear", label: "Year" }
|
|
2003
2007
|
];
|
|
2004
|
-
function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
2005
|
-
const [page, setPage] = (0,
|
|
2006
|
-
const [ordering, setOrdering] = (0,
|
|
2008
|
+
function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLink }) {
|
|
2009
|
+
const [page, setPage] = (0, import_react15.useState)(1);
|
|
2010
|
+
const [ordering, setOrdering] = (0, import_react15.useState)("title");
|
|
2007
2011
|
const { fetch } = (0, import_transport7.useFetchWrapper)(getBackendBaseUrl);
|
|
2008
2012
|
const { playNewTrackListFromArtist } = (0, import_genre_tree14.useTrackList)();
|
|
2009
2013
|
const { playerTrackObject } = (0, import_player10.usePlayer)();
|
|
@@ -2062,7 +2066,8 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
|
2062
2066
|
{
|
|
2063
2067
|
track: song,
|
|
2064
2068
|
position: (page - 1) * data.pageSize + index + 1,
|
|
2065
|
-
renderArtistLink
|
|
2069
|
+
renderArtistLink,
|
|
2070
|
+
renderSongLink
|
|
2066
2071
|
}
|
|
2067
2072
|
) }, song.uuid)) }),
|
|
2068
2073
|
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui10.Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
|
|
@@ -2077,6 +2082,7 @@ function useArtistPanel({
|
|
|
2077
2082
|
getBackendBaseUrl,
|
|
2078
2083
|
getShareUrl,
|
|
2079
2084
|
renderArtistLink,
|
|
2085
|
+
renderSongLink,
|
|
2080
2086
|
expanded,
|
|
2081
2087
|
onExpandedChange,
|
|
2082
2088
|
onClose
|
|
@@ -2129,7 +2135,8 @@ function useArtistPanel({
|
|
|
2129
2135
|
{
|
|
2130
2136
|
artist: { uuid: artist.uuid, name: artist.name },
|
|
2131
2137
|
getBackendBaseUrl,
|
|
2132
|
-
renderArtistLink
|
|
2138
|
+
renderArtistLink,
|
|
2139
|
+
renderSongLink
|
|
2133
2140
|
}
|
|
2134
2141
|
)
|
|
2135
2142
|
] }),
|
|
@@ -2139,31 +2146,225 @@ function useArtistPanel({
|
|
|
2139
2146
|
}
|
|
2140
2147
|
}
|
|
2141
2148
|
|
|
2142
|
-
// src/genre-tree/
|
|
2143
|
-
var
|
|
2149
|
+
// src/genre-tree/song/useSongPanel.tsx
|
|
2150
|
+
var import_ui13 = require("@behindthemusictree/ui");
|
|
2151
|
+
var import_genre_tree17 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2152
|
+
|
|
2153
|
+
// src/genre-tree/song/SongPanelContent.tsx
|
|
2154
|
+
var import_react16 = require("react");
|
|
2155
|
+
var import_lucide_react11 = require("lucide-react");
|
|
2144
2156
|
var import_ui12 = require("@behindthemusictree/ui");
|
|
2157
|
+
var import_player11 = require("@behindthemusictree/app-kit/player");
|
|
2158
|
+
var import_genre_tree16 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2145
2159
|
var import_jsx_runtime32 = require("react/jsx-runtime");
|
|
2160
|
+
var numberFormat = new Intl.NumberFormat("en");
|
|
2161
|
+
var dateFormat = new Intl.DateTimeFormat("en", { dateStyle: "medium" });
|
|
2162
|
+
var externalLink = "text-[var(--color-primary)] hover:underline focus-visible:underline";
|
|
2163
|
+
function SongHistory({ uuid, getBackendBaseUrl }) {
|
|
2164
|
+
const [open, setOpen] = (0, import_react16.useState)(false);
|
|
2165
|
+
const history = (0, import_genre_tree16.useSongHistory)(uuid, getBackendBaseUrl, open);
|
|
2166
|
+
let body = null;
|
|
2167
|
+
if (history.status === "loading") body = /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui12.Skeleton, { className: "h-10", "aria-busy": "true" });
|
|
2168
|
+
else if (history.status === "error") body = /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "text-red-600", children: "Could not load the history." });
|
|
2169
|
+
else if (history.status === "success")
|
|
2170
|
+
body = history.entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("li", { className: "py-1.5 text-sm", children: [
|
|
2171
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "font-medium", children: entry.action }),
|
|
2172
|
+
entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
|
|
2173
|
+
" ",
|
|
2174
|
+
"\u2014 ",
|
|
2175
|
+
entry.oldValue ?? "none",
|
|
2176
|
+
" \u2192 ",
|
|
2177
|
+
entry.newValue ?? "none"
|
|
2178
|
+
] }) : null,
|
|
2179
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("span", { className: "block text-xs text-[var(--color-text-muted)]", children: [
|
|
2180
|
+
entry.actorPseudo ?? "Anonymous",
|
|
2181
|
+
" \xB7 ",
|
|
2182
|
+
dateFormat.format(new Date(entry.createdOn))
|
|
2183
|
+
] })
|
|
2184
|
+
] }, entry.uuid)) });
|
|
2185
|
+
return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
|
|
2186
|
+
"details",
|
|
2187
|
+
{
|
|
2188
|
+
onToggle: (event) => setOpen(event.currentTarget.open),
|
|
2189
|
+
className: "px-6 pb-2.5 mt-2.5 border-t border-[var(--color-border)]",
|
|
2190
|
+
children: [
|
|
2191
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)("summary", { className: "mt-2.5 cursor-pointer text-[0.6875rem] font-semibold uppercase text-[var(--color-text-muted)]", children: "History" }),
|
|
2192
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "mt-1.5", children: body })
|
|
2193
|
+
]
|
|
2194
|
+
}
|
|
2195
|
+
);
|
|
2196
|
+
}
|
|
2197
|
+
function SongPanelContent({
|
|
2198
|
+
song,
|
|
2199
|
+
scope,
|
|
2200
|
+
getBackendBaseUrl,
|
|
2201
|
+
shareUrl,
|
|
2202
|
+
renderArtistLink,
|
|
2203
|
+
genrePath,
|
|
2204
|
+
onSelectGenre
|
|
2205
|
+
}) {
|
|
2206
|
+
const { playNewTrackListFromTrackUuid } = (0, import_genre_tree16.useTrackList)();
|
|
2207
|
+
const { playerTrackObject, playState, handlePlayPauseAction } = (0, import_player11.usePlayer)();
|
|
2208
|
+
const unplayableReason = (0, import_genre_tree16.songUnplayableReason)(song);
|
|
2209
|
+
const isCurrent = playerTrackObject?.track.id === song.uuid;
|
|
2210
|
+
const isPlaying = isCurrent && playState === import_player11.PlayStates.PLAYING;
|
|
2211
|
+
const shareTarget = { kind: "song", uuid: song.uuid, name: song.title, scope };
|
|
2212
|
+
const musicBrainzUrl = (0, import_genre_tree16.musicBrainzRecordingUrl)(song);
|
|
2213
|
+
const fields = [];
|
|
2214
|
+
if (song.genre) {
|
|
2215
|
+
const path = genrePath.length > 0 ? genrePath : null;
|
|
2216
|
+
fields.push({
|
|
2217
|
+
label: "Genre",
|
|
2218
|
+
value: path ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("span", { children: [
|
|
2219
|
+
index > 0 && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { "aria-hidden": "true", children: "\u203A " }),
|
|
2220
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)("button", { type: "button", onClick: () => onSelectGenre(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
|
|
2221
|
+
] }, genrePlaylist.uuid)) }) : song.genre.name
|
|
2222
|
+
});
|
|
2223
|
+
} else {
|
|
2224
|
+
fields.push({ label: "Genre", value: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
|
|
2225
|
+
}
|
|
2226
|
+
if (song.firstReleaseYear) fields.push({ label: "First released", value: song.firstReleaseYear });
|
|
2227
|
+
const viewCount = song.youtubeVideo?.viewCount;
|
|
2228
|
+
if (viewCount !== null && viewCount !== void 0)
|
|
2229
|
+
fields.push({ label: "YouTube views", value: numberFormat.format(viewCount) });
|
|
2230
|
+
fields.push({ label: "Plays", value: numberFormat.format(song.playCount) });
|
|
2231
|
+
const links = [];
|
|
2232
|
+
if (musicBrainzUrl)
|
|
2233
|
+
links.push(
|
|
2234
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
|
|
2235
|
+
);
|
|
2236
|
+
if (song.youtubeVideo)
|
|
2237
|
+
links.push(
|
|
2238
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
|
|
2239
|
+
"a",
|
|
2240
|
+
{
|
|
2241
|
+
href: `https://www.youtube.com/watch?v=${encodeURIComponent(song.youtubeVideo.id)}`,
|
|
2242
|
+
target: "_blank",
|
|
2243
|
+
rel: "noopener noreferrer",
|
|
2244
|
+
className: externalLink,
|
|
2245
|
+
children: "YouTube"
|
|
2246
|
+
},
|
|
2247
|
+
"yt"
|
|
2248
|
+
)
|
|
2249
|
+
);
|
|
2250
|
+
if (links.length > 0)
|
|
2251
|
+
fields.push({
|
|
2252
|
+
label: "Links",
|
|
2253
|
+
value: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "flex gap-3", children: links })
|
|
2254
|
+
});
|
|
2255
|
+
return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_jsx_runtime32.Fragment, { children: [
|
|
2256
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
|
|
2257
|
+
unplayableReason ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "flex items-center px-2.5 text-sm text-[var(--color-text-muted)]", children: unplayableReason }) : /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
|
|
2258
|
+
"button",
|
|
2259
|
+
{
|
|
2260
|
+
type: "button",
|
|
2261
|
+
onClick: () => isCurrent ? handlePlayPauseAction() : playNewTrackListFromTrackUuid(song, scope),
|
|
2262
|
+
className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none",
|
|
2263
|
+
children: [
|
|
2264
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "flex items-center justify-center w-10 h-10 rounded-full group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)] bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]", children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react11.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react11.Play, { "aria-hidden": "true", className: "w-5 h-5" }) }),
|
|
2265
|
+
isPlaying ? "Pause" : "Play"
|
|
2266
|
+
]
|
|
2267
|
+
}
|
|
2268
|
+
),
|
|
2269
|
+
shareUrl && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
|
|
2270
|
+
] }) }),
|
|
2271
|
+
song.artists && song.artists.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "px-6 pt-2.5 text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_genre_tree16.ArtistNames, { artists: song.artists, renderArtistLink }) }),
|
|
2272
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui12.FieldList, { items: fields }),
|
|
2273
|
+
song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui12.PanelSection, { title: "Essential in", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
|
|
2274
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
|
|
2275
|
+
] });
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
// src/genre-tree/song/useSongPanel.tsx
|
|
2279
|
+
var import_jsx_runtime33 = require("react/jsx-runtime");
|
|
2280
|
+
function useSongPanel({
|
|
2281
|
+
uuid,
|
|
2282
|
+
scope,
|
|
2283
|
+
getBackendBaseUrl,
|
|
2284
|
+
getShareUrl,
|
|
2285
|
+
renderArtistLink,
|
|
2286
|
+
getGenrePath,
|
|
2287
|
+
onSelectGenre,
|
|
2288
|
+
expanded,
|
|
2289
|
+
onExpandedChange,
|
|
2290
|
+
onClose
|
|
2291
|
+
}) {
|
|
2292
|
+
const result = (0, import_genre_tree17.useSong)(uuid, getBackendBaseUrl);
|
|
2293
|
+
const panel = { expanded, onExpandedChange, onClose };
|
|
2294
|
+
switch (result.status) {
|
|
2295
|
+
case "idle":
|
|
2296
|
+
return void 0;
|
|
2297
|
+
case "loading":
|
|
2298
|
+
return {
|
|
2299
|
+
...panel,
|
|
2300
|
+
title: "Song",
|
|
2301
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
|
|
2302
|
+
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ui13.Skeleton, { className: "h-3 w-16" }),
|
|
2303
|
+
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ui13.Skeleton, { className: "h-4 w-32" }),
|
|
2304
|
+
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ui13.Skeleton, { className: "h-3 w-16" }),
|
|
2305
|
+
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ui13.Skeleton, { className: "h-4 w-24" })
|
|
2306
|
+
] })
|
|
2307
|
+
};
|
|
2308
|
+
case "not-found":
|
|
2309
|
+
return {
|
|
2310
|
+
...panel,
|
|
2311
|
+
title: "Song not found",
|
|
2312
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This song does not exist." })
|
|
2313
|
+
};
|
|
2314
|
+
case "error":
|
|
2315
|
+
return {
|
|
2316
|
+
...panel,
|
|
2317
|
+
title: "Song",
|
|
2318
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this song." })
|
|
2319
|
+
};
|
|
2320
|
+
case "success": {
|
|
2321
|
+
const { song } = result;
|
|
2322
|
+
const shareUrl = getShareUrl?.({ kind: "song", uuid: song.uuid, name: song.title, scope });
|
|
2323
|
+
return {
|
|
2324
|
+
...panel,
|
|
2325
|
+
title: song.title,
|
|
2326
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
|
|
2327
|
+
SongPanelContent,
|
|
2328
|
+
{
|
|
2329
|
+
song,
|
|
2330
|
+
scope,
|
|
2331
|
+
getBackendBaseUrl,
|
|
2332
|
+
shareUrl: shareUrl ?? void 0,
|
|
2333
|
+
renderArtistLink,
|
|
2334
|
+
genrePath: song.genre ? getGenrePath(song.genre) : [],
|
|
2335
|
+
onSelectGenre
|
|
2336
|
+
}
|
|
2337
|
+
)
|
|
2338
|
+
};
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
// src/genre-tree/SizeMetricToggle.tsx
|
|
2344
|
+
var import_md = require("react-icons/md");
|
|
2345
|
+
var import_ui14 = require("@behindthemusictree/ui");
|
|
2346
|
+
var import_jsx_runtime34 = require("react/jsx-runtime");
|
|
2146
2347
|
function SizeMetricToggle({
|
|
2147
2348
|
value,
|
|
2148
2349
|
onChange
|
|
2149
2350
|
}) {
|
|
2150
|
-
return /* @__PURE__ */ (0,
|
|
2151
|
-
|
|
2351
|
+
return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2352
|
+
import_ui14.SegmentedControl,
|
|
2152
2353
|
{
|
|
2153
2354
|
variant: "floating",
|
|
2154
2355
|
"aria-label": "Size nodes by",
|
|
2155
2356
|
value,
|
|
2156
2357
|
onChange,
|
|
2157
2358
|
options: [
|
|
2158
|
-
{ value: "songs", label: /* @__PURE__ */ (0,
|
|
2159
|
-
{ value: "views", label: /* @__PURE__ */ (0,
|
|
2359
|
+
{ value: "songs", label: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_md.MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
|
|
2360
|
+
{ value: "views", label: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_md.MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
|
|
2160
2361
|
]
|
|
2161
2362
|
}
|
|
2162
2363
|
);
|
|
2163
2364
|
}
|
|
2164
2365
|
|
|
2165
2366
|
// src/genre-tree/GenreTreeView.tsx
|
|
2166
|
-
var
|
|
2367
|
+
var import_jsx_runtime35 = require("react/jsx-runtime");
|
|
2167
2368
|
var PREVIOUS_BUTTON_CSS = `
|
|
2168
2369
|
.genre-tree-view .search-overlay .gtv-info-panel-previous { display: none; }
|
|
2169
2370
|
.genre-tree-view:has(.search-overlay .gtv-info-panel-previous) .genre-search input { padding-left: 2.75rem; }
|
|
@@ -2207,6 +2408,13 @@ function GenreTreeView({
|
|
|
2207
2408
|
artistSearch = true,
|
|
2208
2409
|
onArtistSelect,
|
|
2209
2410
|
artistSearchBaseUrl,
|
|
2411
|
+
songUuid: controlledSongUuid,
|
|
2412
|
+
onSongUuidChange,
|
|
2413
|
+
songExpanded: controlledSongExpanded,
|
|
2414
|
+
onSongExpandedChange,
|
|
2415
|
+
songSearch = true,
|
|
2416
|
+
onSongSelect,
|
|
2417
|
+
renderSongLink,
|
|
2210
2418
|
renderSummary,
|
|
2211
2419
|
renderEssentialTracks,
|
|
2212
2420
|
renderSongLeading,
|
|
@@ -2221,14 +2429,21 @@ function GenreTreeView({
|
|
|
2221
2429
|
placement = "default",
|
|
2222
2430
|
...focus
|
|
2223
2431
|
}) {
|
|
2224
|
-
const getShareUrlRef = (0,
|
|
2432
|
+
const getShareUrlRef = (0, import_react17.useRef)(getShareUrl);
|
|
2225
2433
|
getShareUrlRef.current = getShareUrl;
|
|
2226
|
-
const [internalArtistUuid, setInternalArtistUuid] = (0,
|
|
2434
|
+
const [internalArtistUuid, setInternalArtistUuid] = (0, import_react17.useState)(null);
|
|
2227
2435
|
const isArtistControlled = controlledArtistUuid !== void 0;
|
|
2228
2436
|
const artistUuid = isArtistControlled ? controlledArtistUuid : internalArtistUuid;
|
|
2229
|
-
const [
|
|
2437
|
+
const [internalSongUuid, setInternalSongUuid] = (0, import_react17.useState)(null);
|
|
2438
|
+
const isSongControlled = controlledSongUuid !== void 0;
|
|
2439
|
+
const songUuid = isSongControlled ? controlledSongUuid : internalSongUuid;
|
|
2440
|
+
const changeSong = (uuid) => {
|
|
2441
|
+
if (!isSongControlled) setInternalSongUuid(uuid);
|
|
2442
|
+
onSongUuidChange?.(uuid);
|
|
2443
|
+
};
|
|
2444
|
+
const [internalSizeMetric, setInternalSizeMetric] = (0, import_react17.useState)("songs");
|
|
2230
2445
|
const sizeMetric = controlledSizeMetric ?? internalSizeMetric;
|
|
2231
|
-
const deferredSizeMetric = (0,
|
|
2446
|
+
const deferredSizeMetric = (0, import_react17.useDeferredValue)(sizeMetric);
|
|
2232
2447
|
const changeSizeMetric = (metric) => {
|
|
2233
2448
|
if (controlledSizeMetric === void 0) setInternalSizeMetric(metric);
|
|
2234
2449
|
onSizeMetricChange?.(metric);
|
|
@@ -2255,7 +2470,7 @@ function GenreTreeView({
|
|
|
2255
2470
|
allowWheelRotation: internalAllowWheelRotation,
|
|
2256
2471
|
toggleWheelRotation,
|
|
2257
2472
|
onNodeHover: handleNodeHover
|
|
2258
|
-
} = (0,
|
|
2473
|
+
} = (0, import_genre_tree19.useGenreTreeViewState)({
|
|
2259
2474
|
scope,
|
|
2260
2475
|
getBackendBaseUrl,
|
|
2261
2476
|
viewMode: controlledViewMode,
|
|
@@ -2265,7 +2480,7 @@ function GenreTreeView({
|
|
|
2265
2480
|
artistUuid
|
|
2266
2481
|
});
|
|
2267
2482
|
const restoreRevision = viewportRestore?.revision;
|
|
2268
|
-
const [stackedRestoreOwner, setStackedRestoreOwner] = (0,
|
|
2483
|
+
const [stackedRestoreOwner, setStackedRestoreOwner] = (0, import_react17.useState)({ revision: restoreRevision, rootUuid: selectedGenrePlaylist?.root.uuid });
|
|
2269
2484
|
let restoreRootUuid = stackedRestoreOwner.rootUuid;
|
|
2270
2485
|
if (stackedRestoreOwner.revision !== restoreRevision) {
|
|
2271
2486
|
restoreRootUuid = selectedGenrePlaylist?.root.uuid;
|
|
@@ -2274,14 +2489,14 @@ function GenreTreeView({
|
|
|
2274
2489
|
restoreRootUuid = selectedGenrePlaylist.root.uuid;
|
|
2275
2490
|
setStackedRestoreOwner({ revision: restoreRevision, rootUuid: restoreRootUuid });
|
|
2276
2491
|
}
|
|
2277
|
-
const isGenreUpdating = (0, import_react_query3.useIsMutating)({ mutationKey:
|
|
2278
|
-
const { mutate: updateGenre } = (0,
|
|
2492
|
+
const isGenreUpdating = (0, import_react_query3.useIsMutating)({ mutationKey: import_genre_tree19.genreUpdateMutationKey }) > 0;
|
|
2493
|
+
const { mutate: updateGenre } = (0, import_genre_tree19.useUpdateGenre)(scope, getBackendBaseUrl);
|
|
2279
2494
|
const busy = deferredSizeMetric !== sizeMetric || isTreeFetching || isGenreUpdating;
|
|
2280
2495
|
const isArtistFiltered = artistUuid !== null;
|
|
2281
2496
|
const canSizeByViews = !isArtistFiltered && hasViewsCount(visibleGenrePlaylists);
|
|
2282
|
-
const artistResult = (0,
|
|
2497
|
+
const artistResult = (0, import_genre_tree19.useArtist)(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
|
|
2283
2498
|
const filterArtistName = artistResult.status === "success" ? artistResult.artist.name : null;
|
|
2284
|
-
const filterArtist = (0,
|
|
2499
|
+
const filterArtist = (0, import_react17.useMemo)(
|
|
2285
2500
|
() => artistUuid !== null && filterArtistName !== null ? { uuid: artistUuid, name: filterArtistName } : null,
|
|
2286
2501
|
[artistUuid, filterArtistName]
|
|
2287
2502
|
);
|
|
@@ -2289,27 +2504,72 @@ function GenreTreeView({
|
|
|
2289
2504
|
const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: visibleGenrePlaylists, artist: filterArtist });
|
|
2290
2505
|
const openArtist = (uuid) => {
|
|
2291
2506
|
if (selectedNodeId !== null) clearSelection();
|
|
2507
|
+
if (songUuid !== null) changeSong(null);
|
|
2292
2508
|
changeArtist(uuid);
|
|
2293
2509
|
};
|
|
2294
|
-
const
|
|
2510
|
+
const openSong = (uuid) => {
|
|
2511
|
+
if (selectedNodeId !== null) clearSelection();
|
|
2512
|
+
changeSong(uuid);
|
|
2513
|
+
};
|
|
2514
|
+
const openSongRef = (0, import_react17.useRef)(openSong);
|
|
2515
|
+
openSongRef.current = openSong;
|
|
2516
|
+
const resolvedRenderSongLink = (0, import_react17.useCallback)(
|
|
2517
|
+
(song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
|
|
2518
|
+
[renderSongLink]
|
|
2519
|
+
);
|
|
2520
|
+
const [expandedSongUuid, setExpandedSongUuid] = (0, import_react17.useState)(null);
|
|
2521
|
+
const [expandedArtistUuid, setExpandedArtistUuid] = (0, import_react17.useState)(null);
|
|
2295
2522
|
const artistBaseUrl = artistSearchBaseUrl ?? getBackendBaseUrl;
|
|
2523
|
+
const songBaseUrl = getBackendBaseUrl;
|
|
2296
2524
|
const artistPanel = useArtistPanel({
|
|
2297
2525
|
uuid: artistUuid,
|
|
2298
2526
|
scope,
|
|
2299
2527
|
getBackendBaseUrl: artistBaseUrl,
|
|
2300
2528
|
getShareUrl: getShareUrlRef.current,
|
|
2301
|
-
renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0,
|
|
2529
|
+
renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
|
|
2302
2530
|
expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
|
|
2303
2531
|
onExpandedChange: (on) => {
|
|
2304
2532
|
if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
|
|
2305
2533
|
onArtistExpandedChange?.(on);
|
|
2306
2534
|
},
|
|
2535
|
+
renderSongLink: resolvedRenderSongLink,
|
|
2307
2536
|
onClose: () => changeArtist(null)
|
|
2308
2537
|
});
|
|
2309
|
-
const
|
|
2310
|
-
const
|
|
2311
|
-
|
|
2312
|
-
|
|
2538
|
+
const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
|
|
2539
|
+
const getGenrePath = (genre) => {
|
|
2540
|
+
const path = [];
|
|
2541
|
+
let current = visibleGenrePlaylists.find((playlist) => playlist.criteria?.uuid === genre.uuid);
|
|
2542
|
+
while (current) {
|
|
2543
|
+
path.unshift(current);
|
|
2544
|
+
const parentUuid = current.parent?.uuid;
|
|
2545
|
+
current = parentUuid ? visibleGenrePlaylists.find((playlist) => playlist.uuid === parentUuid) : void 0;
|
|
2546
|
+
}
|
|
2547
|
+
return path;
|
|
2548
|
+
};
|
|
2549
|
+
const songPanel = useSongPanel({
|
|
2550
|
+
uuid: songUuid,
|
|
2551
|
+
scope,
|
|
2552
|
+
getBackendBaseUrl: songBaseUrl,
|
|
2553
|
+
getShareUrl: getShareUrlRef.current,
|
|
2554
|
+
renderArtistLink: resolvedRenderArtistLink,
|
|
2555
|
+
getGenrePath,
|
|
2556
|
+
onSelectGenre: (genrePlaylist) => {
|
|
2557
|
+
changeSong(null);
|
|
2558
|
+
selectGenrePlaylist(genrePlaylist);
|
|
2559
|
+
},
|
|
2560
|
+
expanded: controlledSongExpanded ?? (songUuid !== null && expandedSongUuid === songUuid),
|
|
2561
|
+
onExpandedChange: (on) => {
|
|
2562
|
+
if (controlledSongExpanded === void 0) setExpandedSongUuid(on ? songUuid : null);
|
|
2563
|
+
onSongExpandedChange?.(on);
|
|
2564
|
+
},
|
|
2565
|
+
onClose: () => changeSong(null)
|
|
2566
|
+
});
|
|
2567
|
+
const builtInSongPanel = overlayPanel || hasSelectedGenreNode ? void 0 : songPanel;
|
|
2568
|
+
const builtInArtistPanel = overlayPanel || hasSelectedGenreNode || builtInSongPanel ? void 0 : artistPanel;
|
|
2569
|
+
const builtInPanel = builtInSongPanel ?? builtInArtistPanel;
|
|
2570
|
+
const activeOverlayPanel = overlayPanel ?? (builtInPanel && { ...builtInPanel, hideTitle: true, hideClose: true });
|
|
2571
|
+
const [uncontrolledCollapsed, setUncontrolledCollapsed] = (0, import_react17.useState)(false);
|
|
2572
|
+
const [uncontrolledExpanded, setUncontrolledExpanded] = (0, import_react17.useState)(false);
|
|
2313
2573
|
const isCollapsed = collapsed ?? uncontrolledCollapsed;
|
|
2314
2574
|
const isExpanded = expanded ?? uncontrolledExpanded;
|
|
2315
2575
|
const changeCollapsed = (next) => {
|
|
@@ -2320,9 +2580,9 @@ function GenreTreeView({
|
|
|
2320
2580
|
setUncontrolledExpanded(next);
|
|
2321
2581
|
onExpandedChange?.(next);
|
|
2322
2582
|
};
|
|
2323
|
-
(0,
|
|
2583
|
+
(0, import_react17.useEffect)(() => {
|
|
2324
2584
|
setUncontrolledCollapsed(false);
|
|
2325
|
-
}, [selectedNodeId, artistUuid]);
|
|
2585
|
+
}, [selectedNodeId, artistUuid, songUuid]);
|
|
2326
2586
|
const isDetailHidden = isCollapsed || !hasSelectedGenreNode && !activeOverlayPanel;
|
|
2327
2587
|
useLeftDetailPanelLayout(!isDetailHidden, placement === "regions");
|
|
2328
2588
|
const detailOverlay = placement === "regions" && activeOverlayPanel?.onExpandedChange ? activeOverlayPanel : void 0;
|
|
@@ -2336,14 +2596,14 @@ function GenreTreeView({
|
|
|
2336
2596
|
hideExpandButton: placement === "regions" ? true : void 0,
|
|
2337
2597
|
overlayPanel: detailOverlay ? { ...detailOverlay, collapsed: isCollapsed, hideExpandButton: true } : activeOverlayPanel
|
|
2338
2598
|
};
|
|
2339
|
-
const [isWheelRevealed, setIsWheelRevealed] = (0,
|
|
2599
|
+
const [isWheelRevealed, setIsWheelRevealed] = (0, import_react17.useState)(false);
|
|
2340
2600
|
const usesHandoff = viewMode === "wheel" || viewMode === "pop-core";
|
|
2341
2601
|
const isTreeReady = !isLoading && (!usesHandoff || isWheelRevealed);
|
|
2342
|
-
const rootRef = (0,
|
|
2602
|
+
const rootRef = (0, import_react17.useRef)(null);
|
|
2343
2603
|
const handlePrevious = () => {
|
|
2344
2604
|
rootRef.current?.querySelector(".search-overlay .gtv-info-panel-previous")?.click();
|
|
2345
2605
|
};
|
|
2346
|
-
(0,
|
|
2606
|
+
(0, import_react17.useEffect)(() => {
|
|
2347
2607
|
if (!isTreeReady || viewMode !== "pop-core" || !hasSelectedGenreNode || !selectedGenrePlaylist) return;
|
|
2348
2608
|
const root = rootRef.current;
|
|
2349
2609
|
const centerChip = root?.querySelector(".gtv-wheel-center-node .gtv-wheel-chip");
|
|
@@ -2353,21 +2613,22 @@ function GenreTreeView({
|
|
|
2353
2613
|
centerChip.click();
|
|
2354
2614
|
}, [hasSelectedGenreNode, isTreeReady, selectedGenrePlaylist, viewMode]);
|
|
2355
2615
|
const { showPopup, hidePopup } = (0, import_popup5.usePopup)();
|
|
2356
|
-
const [searchText, setSearchText] = (0,
|
|
2357
|
-
const artistSearchResult = (0,
|
|
2358
|
-
const
|
|
2616
|
+
const [searchText, setSearchText] = (0, import_react17.useState)("");
|
|
2617
|
+
const artistSearchResult = (0, import_genre_tree18.useArtistSearch)(artistBaseUrl, artistSearch ? searchText : "");
|
|
2618
|
+
const artistGroup = artistSearch ? {
|
|
2619
|
+
key: "artists",
|
|
2359
2620
|
label: "Artists",
|
|
2360
2621
|
items: artistSearchResult.artists.map((artist) => ({
|
|
2361
2622
|
key: artist.uuid,
|
|
2362
|
-
content: /* @__PURE__ */ (0,
|
|
2363
|
-
/* @__PURE__ */ (0,
|
|
2623
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
|
|
2624
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "block truncate font-medium", children: [
|
|
2364
2625
|
artist.name,
|
|
2365
|
-
artist.disambiguation && /* @__PURE__ */ (0,
|
|
2626
|
+
artist.disambiguation && /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "font-normal text-[var(--color-neutral-500)]", children: [
|
|
2366
2627
|
" \xB7 ",
|
|
2367
2628
|
artist.disambiguation
|
|
2368
2629
|
] })
|
|
2369
2630
|
] }),
|
|
2370
|
-
artist.matchedAlias && /* @__PURE__ */ (0,
|
|
2631
|
+
artist.matchedAlias && /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [
|
|
2371
2632
|
"also known as ",
|
|
2372
2633
|
artist.matchedAlias
|
|
2373
2634
|
] })
|
|
@@ -2380,11 +2641,32 @@ function GenreTreeView({
|
|
|
2380
2641
|
status: artistSearchResult.isError ? "Search failed" : artistSearchResult.settled && artistSearchResult.artists.length === 0 ? "No artists found" : "",
|
|
2381
2642
|
loading: artistSearchResult.pending
|
|
2382
2643
|
} : void 0;
|
|
2644
|
+
const songSearchResult = (0, import_genre_tree18.useSongSearch)(songBaseUrl, songSearch ? searchText : "");
|
|
2645
|
+
const songGroup = songSearch ? {
|
|
2646
|
+
key: "songs",
|
|
2647
|
+
label: "Songs",
|
|
2648
|
+
items: songSearchResult.songs.map((song) => ({
|
|
2649
|
+
key: song.uuid,
|
|
2650
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: (0, import_genre_tree18.isSongSearchResultPlayable)(song) ? void 0 : "opacity-60", children: [
|
|
2651
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "block truncate font-medium", children: song.title }),
|
|
2652
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [song.artists.map((artist) => artist.name).join(", "), song.genre?.name ?? "No genre"].filter(Boolean).join(" \xB7 ") })
|
|
2653
|
+
] }),
|
|
2654
|
+
onSelect: () => {
|
|
2655
|
+
openSong(song.uuid);
|
|
2656
|
+
onSongSelect?.(song);
|
|
2657
|
+
}
|
|
2658
|
+
})),
|
|
2659
|
+
status: songSearchResult.isError ? "Search failed" : songSearchResult.settled && songSearchResult.songs.length === 0 ? "No songs found" : "",
|
|
2660
|
+
loading: songSearchResult.pending
|
|
2661
|
+
} : void 0;
|
|
2662
|
+
const extraGroups = [artistGroup, songGroup].filter((group) => group !== void 0);
|
|
2383
2663
|
const handleSelectedNodeChange = (node) => {
|
|
2664
|
+
if (node !== null && songUuid !== null) changeSong(null);
|
|
2384
2665
|
selectNode(node);
|
|
2385
2666
|
};
|
|
2386
2667
|
const handleGenreSearchClear = clearSelection;
|
|
2387
2668
|
const handleGenreSearchSelect = (genrePlaylist) => {
|
|
2669
|
+
if (songUuid !== null) changeSong(null);
|
|
2388
2670
|
selectGenrePlaylist(genrePlaylist);
|
|
2389
2671
|
};
|
|
2390
2672
|
const selectedUuid = selectedGenrePlaylist?.uuid;
|
|
@@ -2393,13 +2675,13 @@ function GenreTreeView({
|
|
|
2393
2675
|
const handleGenreSearchZoomIn = () => {
|
|
2394
2676
|
if (selectedUuid) focus.onFocusGenreChange?.(selectedUuid, "zoom-in");
|
|
2395
2677
|
};
|
|
2396
|
-
const zoomInRef = (0,
|
|
2678
|
+
const zoomInRef = (0, import_react17.useRef)(void 0);
|
|
2397
2679
|
zoomInRef.current = canZoomIn ? handleGenreSearchZoomIn : void 0;
|
|
2398
|
-
const renderInfoPanelBody = (0,
|
|
2680
|
+
const renderInfoPanelBody = (0, import_react17.useCallback)(
|
|
2399
2681
|
(node, { toolbar, ancestors, family }) => {
|
|
2400
2682
|
const genrePlaylist = genrePlaylistByNodeId.get(node.id);
|
|
2401
2683
|
const criteria = genrePlaylist?.criteria;
|
|
2402
|
-
if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ (0,
|
|
2684
|
+
if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
|
|
2403
2685
|
toolbar,
|
|
2404
2686
|
family
|
|
2405
2687
|
] }), aside: ancestors };
|
|
@@ -2419,7 +2701,7 @@ function GenreTreeView({
|
|
|
2419
2701
|
(sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist.uuid && sibling.criteria?.side === "pop"
|
|
2420
2702
|
) : void 0;
|
|
2421
2703
|
return {
|
|
2422
|
-
primary: /* @__PURE__ */ (0,
|
|
2704
|
+
primary: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2423
2705
|
GenreInfoPanelBody,
|
|
2424
2706
|
{
|
|
2425
2707
|
toolbar,
|
|
@@ -2438,7 +2720,7 @@ function GenreTreeView({
|
|
|
2438
2720
|
onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
|
|
2439
2721
|
onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
|
|
2440
2722
|
onChangeParent: readOnly ? void 0 : () => showPopup(
|
|
2441
|
-
/* @__PURE__ */ (0,
|
|
2723
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2442
2724
|
ChangeParentPopup,
|
|
2443
2725
|
{
|
|
2444
2726
|
genrePlaylist: { ...genrePlaylist, criteria },
|
|
@@ -2459,6 +2741,7 @@ function GenreTreeView({
|
|
|
2459
2741
|
criteriaOverviewSchema,
|
|
2460
2742
|
renderGenreDetailExtras,
|
|
2461
2743
|
renderArtistLink,
|
|
2744
|
+
renderSongLink: resolvedRenderSongLink,
|
|
2462
2745
|
artist: filterArtist,
|
|
2463
2746
|
renderSummary,
|
|
2464
2747
|
renderEssentialTracks,
|
|
@@ -2489,6 +2772,7 @@ function GenreTreeView({
|
|
|
2489
2772
|
criteriaOverviewSchema,
|
|
2490
2773
|
renderGenreDetailExtras,
|
|
2491
2774
|
renderArtistLink,
|
|
2775
|
+
resolvedRenderSongLink,
|
|
2492
2776
|
renderSummary,
|
|
2493
2777
|
renderEssentialTracks,
|
|
2494
2778
|
renderSongLeading,
|
|
@@ -2498,7 +2782,7 @@ function GenreTreeView({
|
|
|
2498
2782
|
);
|
|
2499
2783
|
const isRotationControlled = controlledAllowWheelRotation !== void 0;
|
|
2500
2784
|
const allowWheelRotation = controlledAllowWheelRotation ?? internalAllowWheelRotation;
|
|
2501
|
-
const detailTab = placement === "regions" && (hasSelectedGenreNode || detailOverlay) ? /* @__PURE__ */ (0,
|
|
2785
|
+
const detailTab = placement === "regions" && (hasSelectedGenreNode || detailOverlay) ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2502
2786
|
PanelEdgeTab,
|
|
2503
2787
|
{
|
|
2504
2788
|
side: "left",
|
|
@@ -2508,8 +2792,8 @@ function GenreTreeView({
|
|
|
2508
2792
|
) : null;
|
|
2509
2793
|
const renderArtistFilter = () => {
|
|
2510
2794
|
if (isLoading || !filterArtist) return null;
|
|
2511
|
-
return /* @__PURE__ */ (0,
|
|
2512
|
-
/* @__PURE__ */ (0,
|
|
2795
|
+
return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
|
|
2796
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2513
2797
|
"button",
|
|
2514
2798
|
{
|
|
2515
2799
|
type: "button",
|
|
@@ -2521,7 +2805,7 @@ function GenreTreeView({
|
|
|
2521
2805
|
children: filterArtist.name
|
|
2522
2806
|
}
|
|
2523
2807
|
),
|
|
2524
|
-
/* @__PURE__ */ (0,
|
|
2808
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2525
2809
|
"button",
|
|
2526
2810
|
{
|
|
2527
2811
|
type: "button",
|
|
@@ -2533,23 +2817,23 @@ function GenreTreeView({
|
|
|
2533
2817
|
)
|
|
2534
2818
|
] });
|
|
2535
2819
|
};
|
|
2536
|
-
const actions = /* @__PURE__ */ (0,
|
|
2537
|
-
!isLoading && /* @__PURE__ */ (0,
|
|
2820
|
+
const actions = /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
|
|
2821
|
+
!isLoading && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2538
2822
|
GenreSearch,
|
|
2539
2823
|
{
|
|
2540
2824
|
genrePlaylists: visibleGenrePlaylists,
|
|
2541
2825
|
onSelect: handleGenreSearchSelect,
|
|
2542
|
-
selectedName: selectedGenrePlaylist?.name ??
|
|
2543
|
-
onClear: builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
|
|
2826
|
+
selectedName: selectedGenrePlaylist?.name ?? builtInPanel?.title ?? null,
|
|
2827
|
+
onClear: builtInSongPanel ? () => changeSong(null) : builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
|
|
2544
2828
|
onPrevious: handlePrevious,
|
|
2545
|
-
onQueryChange: artistSearch ? setSearchText : void 0,
|
|
2546
|
-
|
|
2829
|
+
onQueryChange: artistSearch || songSearch ? setSearchText : void 0,
|
|
2830
|
+
extraGroups
|
|
2547
2831
|
}
|
|
2548
2832
|
),
|
|
2549
|
-
!isLoading && filterArtist && /* @__PURE__ */ (0,
|
|
2550
|
-
/* @__PURE__ */ (0,
|
|
2551
|
-
!isLoading && !isControlled && /* @__PURE__ */ (0,
|
|
2552
|
-
|
|
2833
|
+
!isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
|
|
2834
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "contents max-md:flex max-md:w-full max-md:items-center max-md:gap-2 max-md:overflow-x-auto max-md:[scrollbar-width:none] max-md:[&>*]:shrink-0", children: [
|
|
2835
|
+
!isLoading && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2836
|
+
import_ui15.SegmentedControl,
|
|
2553
2837
|
{
|
|
2554
2838
|
"aria-label": "Tree view mode",
|
|
2555
2839
|
value: viewMode,
|
|
@@ -2562,14 +2846,14 @@ function GenreTreeView({
|
|
|
2562
2846
|
]
|
|
2563
2847
|
}
|
|
2564
2848
|
),
|
|
2565
|
-
!isLoading && showDisplayOptions && /* @__PURE__ */ (0,
|
|
2849
|
+
!isLoading && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2566
2850
|
"div",
|
|
2567
2851
|
{
|
|
2568
2852
|
className: "flex items-center gap-1",
|
|
2569
2853
|
role: "group",
|
|
2570
2854
|
"aria-label": "Tree display options",
|
|
2571
|
-
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0,
|
|
2572
|
-
|
|
2855
|
+
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2856
|
+
import_ui15.Button,
|
|
2573
2857
|
{
|
|
2574
2858
|
variant: allowWheelRotation ? "default" : "outline",
|
|
2575
2859
|
size: "sm",
|
|
@@ -2579,52 +2863,52 @@ function GenreTreeView({
|
|
|
2579
2863
|
)
|
|
2580
2864
|
}
|
|
2581
2865
|
),
|
|
2582
|
-
!isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0,
|
|
2583
|
-
|
|
2866
|
+
!isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2867
|
+
import_ui15.IconTextButton,
|
|
2584
2868
|
{
|
|
2585
|
-
icon:
|
|
2869
|
+
icon: import_lucide_react12.Plus,
|
|
2586
2870
|
text: "Add root",
|
|
2587
2871
|
onClick: () => handleGenreCreationAction(null)
|
|
2588
2872
|
}
|
|
2589
2873
|
)
|
|
2590
2874
|
] })
|
|
2591
2875
|
] });
|
|
2592
|
-
return /* @__PURE__ */ (0,
|
|
2876
|
+
return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
|
|
2593
2877
|
"div",
|
|
2594
2878
|
{
|
|
2595
2879
|
ref: rootRef,
|
|
2596
2880
|
className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
|
|
2597
2881
|
children: [
|
|
2598
|
-
/* @__PURE__ */ (0,
|
|
2599
|
-
/* @__PURE__ */ (0,
|
|
2882
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)("style", { children: PREVIOUS_BUTTON_CSS }),
|
|
2883
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2600
2884
|
"div",
|
|
2601
2885
|
{
|
|
2602
2886
|
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"}`,
|
|
2603
2887
|
children: actions
|
|
2604
2888
|
}
|
|
2605
2889
|
),
|
|
2606
|
-
!isLoading && filterArtist && /* @__PURE__ */ (0,
|
|
2890
|
+
!isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2607
2891
|
"div",
|
|
2608
2892
|
{
|
|
2609
2893
|
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"}`,
|
|
2610
2894
|
children: renderArtistFilter()
|
|
2611
2895
|
}
|
|
2612
2896
|
),
|
|
2613
|
-
detailTab && /* @__PURE__ */ (0,
|
|
2897
|
+
detailTab && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2614
2898
|
"div",
|
|
2615
2899
|
{
|
|
2616
2900
|
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))]"}`,
|
|
2617
2901
|
children: detailTab
|
|
2618
2902
|
}
|
|
2619
2903
|
),
|
|
2620
|
-
/* @__PURE__ */ (0,
|
|
2621
|
-
!isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0,
|
|
2622
|
-
isLoading ? /* @__PURE__ */ (0,
|
|
2904
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
|
|
2905
|
+
!isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
|
|
2906
|
+
isLoading ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_genre_tree_view4.GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
|
|
2623
2907
|
// The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
|
|
2624
2908
|
// and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
|
|
2625
2909
|
// Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
|
|
2626
2910
|
// The panel's own Zoom in button is hidden: the search bar carries it.
|
|
2627
|
-
/* @__PURE__ */ (0,
|
|
2911
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 [--gtv-info-panel-header-inset:calc(var(--layout-search-h,2.5rem)+var(--layout-panel-inset,0.5rem))] [&_.gtv-info-panel-header]:pb-2 [&_.gtv-info-panel-title--hidden]:h-0 [&_.gtv-toolbar]:hidden", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2628
2912
|
GenrePlaylistTreeWheel,
|
|
2629
2913
|
{
|
|
2630
2914
|
scope,
|
|
@@ -2643,9 +2927,9 @@ function GenreTreeView({
|
|
|
2643
2927
|
allowWheelRotation,
|
|
2644
2928
|
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2645
2929
|
busy,
|
|
2646
|
-
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0,
|
|
2930
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
2647
2931
|
floatingControlsLeading,
|
|
2648
|
-
canSizeByViews && /* @__PURE__ */ (0,
|
|
2932
|
+
canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2649
2933
|
] }) : void 0,
|
|
2650
2934
|
defaultViewport,
|
|
2651
2935
|
viewportRestore,
|
|
@@ -2655,7 +2939,7 @@ function GenreTreeView({
|
|
|
2655
2939
|
) }) })
|
|
2656
2940
|
) : viewMode === "pop-core" ? (
|
|
2657
2941
|
// Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
|
|
2658
|
-
/* @__PURE__ */ (0,
|
|
2942
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 [--gtv-info-panel-header-inset:calc(var(--layout-search-h,2.5rem)+var(--layout-panel-inset,0.5rem))] [&_.gtv-info-panel-header]:pb-2 [&_.gtv-info-panel-title--hidden]:h-0 [&_.gtv-toolbar]:hidden [&_.gtv-info-panel-zoom-in]:hidden", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2659
2943
|
GenrePlaylistTreeWheelRadialPopCore,
|
|
2660
2944
|
{
|
|
2661
2945
|
scope,
|
|
@@ -2674,9 +2958,9 @@ function GenreTreeView({
|
|
|
2674
2958
|
allowWheelRotation,
|
|
2675
2959
|
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2676
2960
|
busy,
|
|
2677
|
-
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0,
|
|
2961
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
2678
2962
|
floatingControlsLeading,
|
|
2679
|
-
canSizeByViews && /* @__PURE__ */ (0,
|
|
2963
|
+
canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2680
2964
|
] }) : void 0,
|
|
2681
2965
|
defaultViewport,
|
|
2682
2966
|
viewportRestore,
|
|
@@ -2685,7 +2969,7 @@ function GenreTreeView({
|
|
|
2685
2969
|
...focus
|
|
2686
2970
|
}
|
|
2687
2971
|
) }) })
|
|
2688
|
-
) : viewMode === "outline" ? /* @__PURE__ */ (0,
|
|
2972
|
+
) : viewMode === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative [--gtv-info-panel-header-inset:calc(var(--layout-search-h,2.5rem)+var(--layout-panel-inset,0.5rem)+0.75rem)] [&_.gtv-toolbar]:hidden", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2689
2973
|
GenrePlaylistTreeWheelRadialPopCore,
|
|
2690
2974
|
{
|
|
2691
2975
|
outline: true,
|
|
@@ -2704,13 +2988,13 @@ function GenreTreeView({
|
|
|
2704
2988
|
readOnly,
|
|
2705
2989
|
...infoPanelCuration
|
|
2706
2990
|
}
|
|
2707
|
-
) }) : /* @__PURE__ */ (0,
|
|
2991
|
+
) }) : /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "tree-container relative flex flex-col gap-4 w-full pt-14 overflow-x-auto overflow-y-auto text-[var(--color-text)] [&_.gtv-toolbar]:hidden", children: Object.entries(groupedGenrePlaylistsByRoot).map(
|
|
2708
2992
|
([uuid, genrePlaylistTreePerRoot], rootIndex) => {
|
|
2709
|
-
return /* @__PURE__ */ (0,
|
|
2993
|
+
return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2710
2994
|
"div",
|
|
2711
2995
|
{
|
|
2712
2996
|
className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
|
|
2713
|
-
children: /* @__PURE__ */ (0,
|
|
2997
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2714
2998
|
GenrePlaylistTreePerRoot,
|
|
2715
2999
|
{
|
|
2716
3000
|
scope,
|
|
@@ -2747,11 +3031,11 @@ function GenreTreeView({
|
|
|
2747
3031
|
}
|
|
2748
3032
|
|
|
2749
3033
|
// src/genre-tree/GenreDetailPanel.tsx
|
|
2750
|
-
var
|
|
3034
|
+
var import_react18 = require("react");
|
|
2751
3035
|
var import_genre_tree_view5 = require("@behindthemusictree/genre-tree-view");
|
|
2752
|
-
var
|
|
2753
|
-
var
|
|
2754
|
-
var
|
|
3036
|
+
var import_ui16 = require("@behindthemusictree/ui");
|
|
3037
|
+
var import_genre_tree20 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3038
|
+
var import_jsx_runtime36 = require("react/jsx-runtime");
|
|
2755
3039
|
var ITEM_COUNT_LABEL = "Songs";
|
|
2756
3040
|
var RELATIVE_ITEM_COUNT = 1;
|
|
2757
3041
|
function toNode(genre, parentId) {
|
|
@@ -2778,9 +3062,9 @@ function GenreDetailPanel({
|
|
|
2778
3062
|
onExpandedChange,
|
|
2779
3063
|
extras
|
|
2780
3064
|
}) {
|
|
2781
|
-
const { data: detail, isPending, isError } = (0,
|
|
3065
|
+
const { data: detail, isPending, isError } = (0, import_genre_tree20.useFetchGenreDetail)(genreUuid, scope, getBackendBaseUrl);
|
|
2782
3066
|
const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: [] });
|
|
2783
|
-
const view = (0,
|
|
3067
|
+
const view = (0, import_react18.useMemo)(() => {
|
|
2784
3068
|
if (!detail) return null;
|
|
2785
3069
|
const rootColor = (0, import_genre_tree_view5.getGenreTreeColor)(detail.root.wikidataId);
|
|
2786
3070
|
const relative = (genre, parentId) => ({
|
|
@@ -2806,17 +3090,17 @@ function GenreDetailPanel({
|
|
|
2806
3090
|
};
|
|
2807
3091
|
}, [detail]);
|
|
2808
3092
|
if (isError) {
|
|
2809
|
-
return /* @__PURE__ */ (0,
|
|
3093
|
+
return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
|
|
2810
3094
|
}
|
|
2811
3095
|
if (isPending || !detail || !view) {
|
|
2812
|
-
return /* @__PURE__ */ (0,
|
|
2813
|
-
/* @__PURE__ */ (0,
|
|
2814
|
-
/* @__PURE__ */ (0,
|
|
2815
|
-
/* @__PURE__ */ (0,
|
|
3096
|
+
return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
|
|
3097
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui16.Skeleton, { className: "h-6 w-2/3" }),
|
|
3098
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui16.Skeleton, { className: "h-4 w-full" }),
|
|
3099
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui16.Skeleton, { className: "h-4 w-3/4" })
|
|
2816
3100
|
] });
|
|
2817
3101
|
}
|
|
2818
3102
|
const renderBody = (node, sections) => ({
|
|
2819
|
-
primary: /* @__PURE__ */ (0,
|
|
3103
|
+
primary: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
|
|
2820
3104
|
GenreInfoPanelBody,
|
|
2821
3105
|
{
|
|
2822
3106
|
toolbar: sections.toolbar,
|
|
@@ -2833,7 +3117,7 @@ function GenreDetailPanel({
|
|
|
2833
3117
|
),
|
|
2834
3118
|
aside: null
|
|
2835
3119
|
});
|
|
2836
|
-
return /* @__PURE__ */ (0,
|
|
3120
|
+
return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
|
|
2837
3121
|
import_genre_tree_view5.InfoPanel,
|
|
2838
3122
|
{
|
|
2839
3123
|
node: view.node,
|
|
@@ -2859,13 +3143,13 @@ function GenreDetailPanel({
|
|
|
2859
3143
|
var import_genre_tree_view6 = require("@behindthemusictree/genre-tree-view");
|
|
2860
3144
|
|
|
2861
3145
|
// src/genre-tree/track-list-sidebar/TrackListSidebar.tsx
|
|
2862
|
-
var
|
|
2863
|
-
var
|
|
2864
|
-
var
|
|
2865
|
-
var
|
|
3146
|
+
var import_react19 = require("react");
|
|
3147
|
+
var import_lucide_react13 = require("lucide-react");
|
|
3148
|
+
var import_genre_tree21 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3149
|
+
var import_genre_tree22 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2866
3150
|
var import_track_noun6 = require("@behindthemusictree/app-kit/track-noun");
|
|
2867
|
-
var
|
|
2868
|
-
var useBrowserLayoutEffect = typeof window === "undefined" ?
|
|
3151
|
+
var import_jsx_runtime37 = require("react/jsx-runtime");
|
|
3152
|
+
var useBrowserLayoutEffect = typeof window === "undefined" ? import_react19.useEffect : import_react19.useLayoutEffect;
|
|
2869
3153
|
function TrackListSidebar({
|
|
2870
3154
|
className: className2 = "",
|
|
2871
3155
|
renderDuration,
|
|
@@ -2874,20 +3158,21 @@ function TrackListSidebar({
|
|
|
2874
3158
|
isCollapsed = false,
|
|
2875
3159
|
onExpand,
|
|
2876
3160
|
onMinimize,
|
|
2877
|
-
renderArtistLink
|
|
3161
|
+
renderArtistLink,
|
|
3162
|
+
renderSongLink
|
|
2878
3163
|
}) {
|
|
2879
|
-
const { trackList, loadMore, autoAdvanceScrollRequest } = (0,
|
|
3164
|
+
const { trackList, loadMore, autoAdvanceScrollRequest } = (0, import_genre_tree21.useTrackList)();
|
|
2880
3165
|
const trackNoun = (0, import_track_noun6.useTrackNoun)();
|
|
2881
|
-
const trackListRef = (0,
|
|
2882
|
-
const previousOriginRef = (0,
|
|
2883
|
-
const handledAutoAdvanceRequestIdRef = (0,
|
|
2884
|
-
const shouldFollowPlaybackRef = (0,
|
|
2885
|
-
const programmaticScrollTopRef = (0,
|
|
2886
|
-
const userScrollIntentRef = (0,
|
|
2887
|
-
const userScrollIntentTimerRef = (0,
|
|
2888
|
-
const loadMoreSentinelRef = (0,
|
|
3166
|
+
const trackListRef = (0, import_react19.useRef)(null);
|
|
3167
|
+
const previousOriginRef = (0, import_react19.useRef)(trackList?.origin ?? null);
|
|
3168
|
+
const handledAutoAdvanceRequestIdRef = (0, import_react19.useRef)(null);
|
|
3169
|
+
const shouldFollowPlaybackRef = (0, import_react19.useRef)(true);
|
|
3170
|
+
const programmaticScrollTopRef = (0, import_react19.useRef)(null);
|
|
3171
|
+
const userScrollIntentRef = (0, import_react19.useRef)(false);
|
|
3172
|
+
const userScrollIntentTimerRef = (0, import_react19.useRef)(null);
|
|
3173
|
+
const loadMoreSentinelRef = (0, import_react19.useRef)(null);
|
|
2889
3174
|
const hasMore = !!trackList && trackList.nextPage !== null;
|
|
2890
|
-
(0,
|
|
3175
|
+
(0, import_react19.useEffect)(() => {
|
|
2891
3176
|
const sentinel = loadMoreSentinelRef.current;
|
|
2892
3177
|
if (!hasMore || !sentinel) return;
|
|
2893
3178
|
const observer = new IntersectionObserver((entries) => {
|
|
@@ -2907,11 +3192,11 @@ function TrackListSidebar({
|
|
|
2907
3192
|
programmaticScrollTopRef.current = null;
|
|
2908
3193
|
if (trackListRef.current) trackListRef.current.scrollTop = 0;
|
|
2909
3194
|
}, [trackList?.origin]);
|
|
2910
|
-
(0,
|
|
3195
|
+
(0, import_react19.useEffect)(() => {
|
|
2911
3196
|
const list = trackListRef.current;
|
|
2912
3197
|
if (!isCollapsed && list?.scrollTop === 0) shouldFollowPlaybackRef.current = true;
|
|
2913
3198
|
}, [isCollapsed]);
|
|
2914
|
-
(0,
|
|
3199
|
+
(0, import_react19.useEffect)(
|
|
2915
3200
|
() => () => {
|
|
2916
3201
|
if (userScrollIntentTimerRef.current !== null) clearTimeout(userScrollIntentTimerRef.current);
|
|
2917
3202
|
},
|
|
@@ -2965,25 +3250,25 @@ function TrackListSidebar({
|
|
|
2965
3250
|
}
|
|
2966
3251
|
};
|
|
2967
3252
|
const positionClasses = layout === "inline" ? "relative w-full flex h-full flex-col" : "fixed right-0 w-[50rem] flex flex-col";
|
|
2968
|
-
return trackList ? /* @__PURE__ */ (0,
|
|
3253
|
+
return trackList ? /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
|
|
2969
3254
|
"div",
|
|
2970
3255
|
{
|
|
2971
3256
|
"data-theme": "dark",
|
|
2972
3257
|
className: `track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${className2}`,
|
|
2973
3258
|
style: layout === "fixed" ? { top: "63.5px", maxHeight: "calc(100vh - 63.5px - 79px)" } : void 0,
|
|
2974
3259
|
children: [
|
|
2975
|
-
/* @__PURE__ */ (0,
|
|
2976
|
-
/* @__PURE__ */ (0,
|
|
2977
|
-
/* @__PURE__ */ (0,
|
|
2978
|
-
/* @__PURE__ */ (0,
|
|
2979
|
-
/* @__PURE__ */ (0,
|
|
3260
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
|
|
3261
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
|
|
3262
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "origin flex text-xl", children: [
|
|
3263
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
|
|
3264
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "name-container flex flex-col justify-center items-center text-[var(--color-text)] font-bold pr-2 max-w-[27.5rem]", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
|
|
2980
3265
|
] }),
|
|
2981
|
-
/* @__PURE__ */ (0,
|
|
2982
|
-
trackList.origin.type ===
|
|
3266
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
|
|
3267
|
+
trackList.origin.type === import_genre_tree22.TrackListOriginType.GENRE_PLAYLIST ? "Genre \u2022 " : trackList.origin.type === import_genre_tree22.TrackListOriginType.ARTIST ? "Artist \u2022 " : `${trackNoun.singular} playlist \u2022 `,
|
|
2983
3268
|
`${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`
|
|
2984
3269
|
] })
|
|
2985
3270
|
] }),
|
|
2986
|
-
(isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0,
|
|
3271
|
+
(isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
|
|
2987
3272
|
"button",
|
|
2988
3273
|
{
|
|
2989
3274
|
type: "button",
|
|
@@ -2992,11 +3277,11 @@ function TrackListSidebar({
|
|
|
2992
3277
|
"aria-expanded": !isCollapsed,
|
|
2993
3278
|
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"}`,
|
|
2994
3279
|
onClick: isCollapsed ? onExpand : onMinimize,
|
|
2995
|
-
children: /* @__PURE__ */ (0,
|
|
3280
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react13.ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
|
|
2996
3281
|
}
|
|
2997
3282
|
)
|
|
2998
3283
|
] }),
|
|
2999
|
-
!isCollapsed && /* @__PURE__ */ (0,
|
|
3284
|
+
!isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
|
|
3000
3285
|
"ul",
|
|
3001
3286
|
{
|
|
3002
3287
|
ref: trackListRef,
|
|
@@ -3006,17 +3291,18 @@ function TrackListSidebar({
|
|
|
3006
3291
|
onPointerDown: markUserScrollIntent,
|
|
3007
3292
|
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",
|
|
3008
3293
|
children: [
|
|
3009
|
-
trackList.tracks.map((track, index) => /* @__PURE__ */ (0,
|
|
3294
|
+
trackList.tracks.map((track, index) => /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
|
|
3010
3295
|
TrackItem,
|
|
3011
3296
|
{
|
|
3012
3297
|
track,
|
|
3013
3298
|
position: index + 1,
|
|
3014
3299
|
renderDuration,
|
|
3015
3300
|
renderActions,
|
|
3016
|
-
renderArtistLink
|
|
3301
|
+
renderArtistLink,
|
|
3302
|
+
renderSongLink
|
|
3017
3303
|
}
|
|
3018
3304
|
) }, track.uuid)),
|
|
3019
|
-
hasMore && /* @__PURE__ */ (0,
|
|
3305
|
+
hasMore && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
|
|
3020
3306
|
]
|
|
3021
3307
|
}
|
|
3022
3308
|
)
|
|
@@ -3026,16 +3312,16 @@ function TrackListSidebar({
|
|
|
3026
3312
|
}
|
|
3027
3313
|
|
|
3028
3314
|
// src/genre-tree/PlayerPanel.tsx
|
|
3029
|
-
var
|
|
3315
|
+
var import_react20 = require("react");
|
|
3030
3316
|
var import_tailwind_merge2 = require("tailwind-merge");
|
|
3031
|
-
var
|
|
3032
|
-
var
|
|
3317
|
+
var import_player13 = require("@behindthemusictree/app-kit/player");
|
|
3318
|
+
var import_genre_tree24 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3033
3319
|
|
|
3034
3320
|
// src/genre-tree/PlayerControls.tsx
|
|
3035
|
-
var
|
|
3036
|
-
var
|
|
3037
|
-
var
|
|
3038
|
-
var
|
|
3321
|
+
var import_lucide_react14 = require("lucide-react");
|
|
3322
|
+
var import_genre_tree23 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3323
|
+
var import_player12 = require("@behindthemusictree/app-kit/player");
|
|
3324
|
+
var import_jsx_runtime38 = require("react/jsx-runtime");
|
|
3039
3325
|
function PlayerControls() {
|
|
3040
3326
|
const {
|
|
3041
3327
|
duration,
|
|
@@ -3046,23 +3332,23 @@ function PlayerControls() {
|
|
|
3046
3332
|
seekTo,
|
|
3047
3333
|
setVolume,
|
|
3048
3334
|
volume
|
|
3049
|
-
} = (0,
|
|
3050
|
-
const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } = (0,
|
|
3051
|
-
const currentTime = (0,
|
|
3052
|
-
const isBusy = isLoading || playState ===
|
|
3335
|
+
} = (0, import_player12.usePlayer)();
|
|
3336
|
+
const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } = (0, import_genre_tree23.useTrackList)();
|
|
3337
|
+
const currentTime = (0, import_player12.useCurrentTime)();
|
|
3338
|
+
const isBusy = isLoading || playState === import_player12.PlayStates.LOADING;
|
|
3053
3339
|
const isReady = !!playerTrackObject?.isReady && !isBusy && !playerTrackObject.loadError;
|
|
3054
3340
|
const safeDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;
|
|
3055
3341
|
const safeCurrentTime = Number.isFinite(currentTime) ? Math.max(0, Math.min(currentTime, safeDuration)) : 0;
|
|
3056
|
-
const isPlaying = playState ===
|
|
3057
|
-
return /* @__PURE__ */ (0,
|
|
3342
|
+
const isPlaying = playState === import_player12.PlayStates.PLAYING;
|
|
3343
|
+
return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
|
|
3058
3344
|
"section",
|
|
3059
3345
|
{
|
|
3060
3346
|
"aria-label": "Player controls",
|
|
3061
3347
|
className: "player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200",
|
|
3062
3348
|
children: [
|
|
3063
|
-
/* @__PURE__ */ (0,
|
|
3064
|
-
/* @__PURE__ */ (0,
|
|
3065
|
-
/* @__PURE__ */ (0,
|
|
3349
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
|
|
3350
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { "aria-label": "Elapsed time", children: (0, import_genre_tree23.formatTime)(safeCurrentTime) }),
|
|
3351
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
|
|
3066
3352
|
"input",
|
|
3067
3353
|
{
|
|
3068
3354
|
"aria-label": "Seek",
|
|
@@ -3076,11 +3362,11 @@ function PlayerControls() {
|
|
|
3076
3362
|
onChange: (event) => seekTo(Number(event.currentTarget.value))
|
|
3077
3363
|
}
|
|
3078
3364
|
),
|
|
3079
|
-
/* @__PURE__ */ (0,
|
|
3365
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { "aria-label": "Duration", children: (0, import_genre_tree23.formatTime)(safeDuration) })
|
|
3080
3366
|
] }),
|
|
3081
|
-
/* @__PURE__ */ (0,
|
|
3082
|
-
/* @__PURE__ */ (0,
|
|
3083
|
-
/* @__PURE__ */ (0,
|
|
3367
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
|
|
3368
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
|
|
3369
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
|
|
3084
3370
|
"button",
|
|
3085
3371
|
{
|
|
3086
3372
|
type: "button",
|
|
@@ -3089,10 +3375,10 @@ function PlayerControls() {
|
|
|
3089
3375
|
disabled: !isReady || !hasPreviousTrack,
|
|
3090
3376
|
onClick: playPreviousTrack,
|
|
3091
3377
|
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",
|
|
3092
|
-
children: /* @__PURE__ */ (0,
|
|
3378
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_lucide_react14.SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
3093
3379
|
}
|
|
3094
3380
|
),
|
|
3095
|
-
/* @__PURE__ */ (0,
|
|
3381
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
|
|
3096
3382
|
"button",
|
|
3097
3383
|
{
|
|
3098
3384
|
type: "button",
|
|
@@ -3101,10 +3387,10 @@ function PlayerControls() {
|
|
|
3101
3387
|
disabled: !isReady,
|
|
3102
3388
|
onClick: handlePlayPauseAction,
|
|
3103
3389
|
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",
|
|
3104
|
-
children: isPlaying ? /* @__PURE__ */ (0,
|
|
3390
|
+
children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_lucide_react14.Pause, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_lucide_react14.Play, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
|
|
3105
3391
|
}
|
|
3106
3392
|
),
|
|
3107
|
-
/* @__PURE__ */ (0,
|
|
3393
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
|
|
3108
3394
|
"button",
|
|
3109
3395
|
{
|
|
3110
3396
|
type: "button",
|
|
@@ -3113,14 +3399,14 @@ function PlayerControls() {
|
|
|
3113
3399
|
disabled: !isReady || !hasNextTrack,
|
|
3114
3400
|
onClick: playNextTrack,
|
|
3115
3401
|
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",
|
|
3116
|
-
children: /* @__PURE__ */ (0,
|
|
3402
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_lucide_react14.SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
3117
3403
|
}
|
|
3118
3404
|
)
|
|
3119
3405
|
] }),
|
|
3120
|
-
/* @__PURE__ */ (0,
|
|
3121
|
-
volume === 0 ? /* @__PURE__ */ (0,
|
|
3122
|
-
/* @__PURE__ */ (0,
|
|
3123
|
-
/* @__PURE__ */ (0,
|
|
3406
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("label", { className: "col-start-3 flex w-[4.2rem] shrink-0 items-center gap-[0.35rem] justify-self-end text-zinc-400 sm:w-[4.9rem]", children: [
|
|
3407
|
+
volume === 0 ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_lucide_react14.VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_lucide_react14.Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
|
|
3408
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "sr-only", children: "Volume" }),
|
|
3409
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
|
|
3124
3410
|
"input",
|
|
3125
3411
|
{
|
|
3126
3412
|
"aria-label": "Volume",
|
|
@@ -3142,7 +3428,7 @@ function PlayerControls() {
|
|
|
3142
3428
|
}
|
|
3143
3429
|
|
|
3144
3430
|
// src/genre-tree/PlayerPanel.tsx
|
|
3145
|
-
var
|
|
3431
|
+
var import_jsx_runtime39 = require("react/jsx-runtime");
|
|
3146
3432
|
var PANEL_ID = "player-panel";
|
|
3147
3433
|
var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
|
|
3148
3434
|
function PlayerPanel({
|
|
@@ -3151,16 +3437,17 @@ function PlayerPanel({
|
|
|
3151
3437
|
expanded,
|
|
3152
3438
|
onExpandedChange,
|
|
3153
3439
|
extendedWidth = DEFAULT_EXTENDED_WIDTH,
|
|
3154
|
-
renderArtistLink
|
|
3440
|
+
renderArtistLink,
|
|
3441
|
+
renderSongLink
|
|
3155
3442
|
}) {
|
|
3156
|
-
const { playerTrackObject } = (0,
|
|
3157
|
-
const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0,
|
|
3158
|
-
const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = (0,
|
|
3443
|
+
const { playerTrackObject } = (0, import_player13.usePlayer)();
|
|
3444
|
+
const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0, import_genre_tree24.useTrackListSidebarVisibility)();
|
|
3445
|
+
const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = (0, import_react20.useState)("");
|
|
3159
3446
|
const loadedTrackTitle = playerTrackObject?.track.title;
|
|
3160
|
-
(0,
|
|
3447
|
+
(0, import_react20.useEffect)(() => {
|
|
3161
3448
|
if (loadedTrackTitle) setLastLoadedTrackTitle(loadedTrackTitle);
|
|
3162
3449
|
}, [loadedTrackTitle]);
|
|
3163
|
-
const [uncontrolledExtended, setUncontrolledExtended] = (0,
|
|
3450
|
+
const [uncontrolledExtended, setUncontrolledExtended] = (0, import_react20.useState)(false);
|
|
3164
3451
|
const isExtendRequested = expanded ?? uncontrolledExtended;
|
|
3165
3452
|
const setIsExtendRequested = (next) => {
|
|
3166
3453
|
setUncontrolledExtended(next);
|
|
@@ -3181,7 +3468,7 @@ function PlayerPanel({
|
|
|
3181
3468
|
onClick: () => setIsExtendRequested(true),
|
|
3182
3469
|
expanded: false
|
|
3183
3470
|
} : null;
|
|
3184
|
-
return /* @__PURE__ */ (0,
|
|
3471
|
+
return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
|
|
3185
3472
|
"div",
|
|
3186
3473
|
{
|
|
3187
3474
|
id: PANEL_ID,
|
|
@@ -3194,7 +3481,7 @@ function PlayerPanel({
|
|
|
3194
3481
|
),
|
|
3195
3482
|
style: isExtended ? { width: extendedWidth } : void 0,
|
|
3196
3483
|
children: [
|
|
3197
|
-
/* @__PURE__ */ (0,
|
|
3484
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
3198
3485
|
PanelEdgeTab,
|
|
3199
3486
|
{
|
|
3200
3487
|
side: "right",
|
|
@@ -3204,7 +3491,7 @@ function PlayerPanel({
|
|
|
3204
3491
|
className: "absolute right-full top-1/2 -translate-y-1/2"
|
|
3205
3492
|
}
|
|
3206
3493
|
),
|
|
3207
|
-
/* @__PURE__ */ (0,
|
|
3494
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
|
|
3208
3495
|
"div",
|
|
3209
3496
|
{
|
|
3210
3497
|
className: (0, import_tailwind_merge2.twMerge)(
|
|
@@ -3212,15 +3499,15 @@ function PlayerPanel({
|
|
|
3212
3499
|
isExtended ? "flex-row" : "flex-col"
|
|
3213
3500
|
),
|
|
3214
3501
|
children: [
|
|
3215
|
-
/* @__PURE__ */ (0,
|
|
3216
|
-
/* @__PURE__ */ (0,
|
|
3217
|
-
/* @__PURE__ */ (0,
|
|
3218
|
-
/* @__PURE__ */ (0,
|
|
3219
|
-
playerTrackObject?.loadError && /* @__PURE__ */ (0,
|
|
3502
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
|
|
3503
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
|
|
3504
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "relative w-full shrink-0", children: [
|
|
3505
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_player13.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
|
|
3506
|
+
playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: playerTrackObject.loadError })
|
|
3220
3507
|
] }),
|
|
3221
|
-
/* @__PURE__ */ (0,
|
|
3508
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(PlayerControls, {})
|
|
3222
3509
|
] }),
|
|
3223
|
-
/* @__PURE__ */ (0,
|
|
3510
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
3224
3511
|
TrackListSidebar,
|
|
3225
3512
|
{
|
|
3226
3513
|
layout: "inline",
|
|
@@ -3231,7 +3518,8 @@ function PlayerPanel({
|
|
|
3231
3518
|
"min-h-0 min-w-0 flex-1",
|
|
3232
3519
|
isExtended && "border-l border-zinc-800"
|
|
3233
3520
|
),
|
|
3234
|
-
renderArtistLink
|
|
3521
|
+
renderArtistLink,
|
|
3522
|
+
renderSongLink
|
|
3235
3523
|
}
|
|
3236
3524
|
)
|
|
3237
3525
|
]
|
|
@@ -3245,33 +3533,33 @@ function PlayerPanel({
|
|
|
3245
3533
|
// src/genre-tree/Rating.tsx
|
|
3246
3534
|
var import_uuid = require("uuid");
|
|
3247
3535
|
var import_ai = require("react-icons/ai");
|
|
3248
|
-
var
|
|
3249
|
-
var
|
|
3536
|
+
var import_genre_tree25 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3537
|
+
var import_jsx_runtime40 = require("react/jsx-runtime");
|
|
3250
3538
|
function Rating({ rating, handleChange }) {
|
|
3251
3539
|
const instanceId = (0, import_uuid.v4)();
|
|
3252
|
-
return /* @__PURE__ */ (0,
|
|
3253
|
-
/* @__PURE__ */ (0,
|
|
3540
|
+
return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
|
|
3541
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
3254
3542
|
"input",
|
|
3255
3543
|
{
|
|
3256
3544
|
id: `star-${instanceId}-${i}`,
|
|
3257
3545
|
className: "w-6 h-6 opacity-0",
|
|
3258
3546
|
type: "radio",
|
|
3259
3547
|
name: "rating",
|
|
3260
|
-
value: i === 0 ?
|
|
3548
|
+
value: i === 0 ? import_genre_tree25.FORM_RATING_NULL_VALUE : i,
|
|
3261
3549
|
checked: rating === (i === 0 ? null : i),
|
|
3262
3550
|
onChange: handleChange
|
|
3263
3551
|
}
|
|
3264
3552
|
),
|
|
3265
|
-
/* @__PURE__ */ (0,
|
|
3266
|
-
/* @__PURE__ */ (0,
|
|
3553
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "flex", children: [
|
|
3554
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
3267
3555
|
import_ai.AiOutlineStar,
|
|
3268
3556
|
{
|
|
3269
|
-
className: `w-7 h-7 absolute top-0 left-0 ${rating ===
|
|
3557
|
+
className: `w-7 h-7 absolute top-0 left-0 ${rating === import_genre_tree25.FORM_RATING_NULL_VALUE ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
|
|
3270
3558
|
}
|
|
3271
3559
|
),
|
|
3272
|
-
/* @__PURE__ */ (0,
|
|
3560
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "flex-grow w-8" }),
|
|
3273
3561
|
" "
|
|
3274
|
-
] }) : /* @__PURE__ */ (0,
|
|
3562
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
3275
3563
|
import_ai.AiFillStar,
|
|
3276
3564
|
{
|
|
3277
3565
|
className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
|