@behindthemusictree/app-kit-ui 29.0.1 → 30.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-TFLX6C5Q.js → chunk-2HE2HDFA.js} +552 -253
- package/dist/chunk-2HE2HDFA.js.map +1 -0
- package/dist/genre-tree/index.cjs +588 -299
- package/dist/genre-tree/index.cjs.map +1 -1
- package/dist/genre-tree/index.d.ts +40 -12
- package/dist/genre-tree/index.js +1 -1
- package/dist/index.cjs +585 -296
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +6 -6
- package/dist/chunk-TFLX6C5Q.js.map +0 -1
|
@@ -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,30 +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
|
{
|
|
2354
|
+
variant: "floating",
|
|
2153
2355
|
"aria-label": "Size nodes by",
|
|
2154
2356
|
value,
|
|
2155
2357
|
onChange,
|
|
2156
2358
|
options: [
|
|
2157
|
-
{ value: "songs", label: /* @__PURE__ */ (0,
|
|
2158
|
-
{ 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" }
|
|
2159
2361
|
]
|
|
2160
2362
|
}
|
|
2161
2363
|
);
|
|
2162
2364
|
}
|
|
2163
2365
|
|
|
2164
2366
|
// src/genre-tree/GenreTreeView.tsx
|
|
2165
|
-
var
|
|
2367
|
+
var import_jsx_runtime35 = require("react/jsx-runtime");
|
|
2166
2368
|
var PREVIOUS_BUTTON_CSS = `
|
|
2167
2369
|
.genre-tree-view .search-overlay .gtv-info-panel-previous { display: none; }
|
|
2168
2370
|
.genre-tree-view:has(.search-overlay .gtv-info-panel-previous) .genre-search input { padding-left: 2.75rem; }
|
|
@@ -2206,6 +2408,13 @@ function GenreTreeView({
|
|
|
2206
2408
|
artistSearch = true,
|
|
2207
2409
|
onArtistSelect,
|
|
2208
2410
|
artistSearchBaseUrl,
|
|
2411
|
+
songUuid: controlledSongUuid,
|
|
2412
|
+
onSongUuidChange,
|
|
2413
|
+
songExpanded: controlledSongExpanded,
|
|
2414
|
+
onSongExpandedChange,
|
|
2415
|
+
songSearch = true,
|
|
2416
|
+
onSongSelect,
|
|
2417
|
+
renderSongLink,
|
|
2209
2418
|
renderSummary,
|
|
2210
2419
|
renderEssentialTracks,
|
|
2211
2420
|
renderSongLeading,
|
|
@@ -2220,14 +2429,21 @@ function GenreTreeView({
|
|
|
2220
2429
|
placement = "default",
|
|
2221
2430
|
...focus
|
|
2222
2431
|
}) {
|
|
2223
|
-
const getShareUrlRef = (0,
|
|
2432
|
+
const getShareUrlRef = (0, import_react17.useRef)(getShareUrl);
|
|
2224
2433
|
getShareUrlRef.current = getShareUrl;
|
|
2225
|
-
const [internalArtistUuid, setInternalArtistUuid] = (0,
|
|
2434
|
+
const [internalArtistUuid, setInternalArtistUuid] = (0, import_react17.useState)(null);
|
|
2226
2435
|
const isArtistControlled = controlledArtistUuid !== void 0;
|
|
2227
2436
|
const artistUuid = isArtistControlled ? controlledArtistUuid : internalArtistUuid;
|
|
2228
|
-
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");
|
|
2229
2445
|
const sizeMetric = controlledSizeMetric ?? internalSizeMetric;
|
|
2230
|
-
const deferredSizeMetric = (0,
|
|
2446
|
+
const deferredSizeMetric = (0, import_react17.useDeferredValue)(sizeMetric);
|
|
2231
2447
|
const changeSizeMetric = (metric) => {
|
|
2232
2448
|
if (controlledSizeMetric === void 0) setInternalSizeMetric(metric);
|
|
2233
2449
|
onSizeMetricChange?.(metric);
|
|
@@ -2254,7 +2470,7 @@ function GenreTreeView({
|
|
|
2254
2470
|
allowWheelRotation: internalAllowWheelRotation,
|
|
2255
2471
|
toggleWheelRotation,
|
|
2256
2472
|
onNodeHover: handleNodeHover
|
|
2257
|
-
} = (0,
|
|
2473
|
+
} = (0, import_genre_tree19.useGenreTreeViewState)({
|
|
2258
2474
|
scope,
|
|
2259
2475
|
getBackendBaseUrl,
|
|
2260
2476
|
viewMode: controlledViewMode,
|
|
@@ -2264,7 +2480,7 @@ function GenreTreeView({
|
|
|
2264
2480
|
artistUuid
|
|
2265
2481
|
});
|
|
2266
2482
|
const restoreRevision = viewportRestore?.revision;
|
|
2267
|
-
const [stackedRestoreOwner, setStackedRestoreOwner] = (0,
|
|
2483
|
+
const [stackedRestoreOwner, setStackedRestoreOwner] = (0, import_react17.useState)({ revision: restoreRevision, rootUuid: selectedGenrePlaylist?.root.uuid });
|
|
2268
2484
|
let restoreRootUuid = stackedRestoreOwner.rootUuid;
|
|
2269
2485
|
if (stackedRestoreOwner.revision !== restoreRevision) {
|
|
2270
2486
|
restoreRootUuid = selectedGenrePlaylist?.root.uuid;
|
|
@@ -2273,14 +2489,14 @@ function GenreTreeView({
|
|
|
2273
2489
|
restoreRootUuid = selectedGenrePlaylist.root.uuid;
|
|
2274
2490
|
setStackedRestoreOwner({ revision: restoreRevision, rootUuid: restoreRootUuid });
|
|
2275
2491
|
}
|
|
2276
|
-
const isGenreUpdating = (0, import_react_query3.useIsMutating)({ mutationKey:
|
|
2277
|
-
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);
|
|
2278
2494
|
const busy = deferredSizeMetric !== sizeMetric || isTreeFetching || isGenreUpdating;
|
|
2279
2495
|
const isArtistFiltered = artistUuid !== null;
|
|
2280
2496
|
const canSizeByViews = !isArtistFiltered && hasViewsCount(visibleGenrePlaylists);
|
|
2281
|
-
const artistResult = (0,
|
|
2497
|
+
const artistResult = (0, import_genre_tree19.useArtist)(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
|
|
2282
2498
|
const filterArtistName = artistResult.status === "success" ? artistResult.artist.name : null;
|
|
2283
|
-
const filterArtist = (0,
|
|
2499
|
+
const filterArtist = (0, import_react17.useMemo)(
|
|
2284
2500
|
() => artistUuid !== null && filterArtistName !== null ? { uuid: artistUuid, name: filterArtistName } : null,
|
|
2285
2501
|
[artistUuid, filterArtistName]
|
|
2286
2502
|
);
|
|
@@ -2288,27 +2504,72 @@ function GenreTreeView({
|
|
|
2288
2504
|
const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: visibleGenrePlaylists, artist: filterArtist });
|
|
2289
2505
|
const openArtist = (uuid) => {
|
|
2290
2506
|
if (selectedNodeId !== null) clearSelection();
|
|
2507
|
+
if (songUuid !== null) changeSong(null);
|
|
2291
2508
|
changeArtist(uuid);
|
|
2292
2509
|
};
|
|
2293
|
-
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);
|
|
2294
2522
|
const artistBaseUrl = artistSearchBaseUrl ?? getBackendBaseUrl;
|
|
2523
|
+
const songBaseUrl = getBackendBaseUrl;
|
|
2295
2524
|
const artistPanel = useArtistPanel({
|
|
2296
2525
|
uuid: artistUuid,
|
|
2297
2526
|
scope,
|
|
2298
2527
|
getBackendBaseUrl: artistBaseUrl,
|
|
2299
2528
|
getShareUrl: getShareUrlRef.current,
|
|
2300
|
-
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 })),
|
|
2301
2530
|
expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
|
|
2302
2531
|
onExpandedChange: (on) => {
|
|
2303
2532
|
if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
|
|
2304
2533
|
onArtistExpandedChange?.(on);
|
|
2305
2534
|
},
|
|
2535
|
+
renderSongLink: resolvedRenderSongLink,
|
|
2306
2536
|
onClose: () => changeArtist(null)
|
|
2307
2537
|
});
|
|
2308
|
-
const
|
|
2309
|
-
const
|
|
2310
|
-
|
|
2311
|
-
|
|
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);
|
|
2312
2573
|
const isCollapsed = collapsed ?? uncontrolledCollapsed;
|
|
2313
2574
|
const isExpanded = expanded ?? uncontrolledExpanded;
|
|
2314
2575
|
const changeCollapsed = (next) => {
|
|
@@ -2319,9 +2580,9 @@ function GenreTreeView({
|
|
|
2319
2580
|
setUncontrolledExpanded(next);
|
|
2320
2581
|
onExpandedChange?.(next);
|
|
2321
2582
|
};
|
|
2322
|
-
(0,
|
|
2583
|
+
(0, import_react17.useEffect)(() => {
|
|
2323
2584
|
setUncontrolledCollapsed(false);
|
|
2324
|
-
}, [selectedNodeId, artistUuid]);
|
|
2585
|
+
}, [selectedNodeId, artistUuid, songUuid]);
|
|
2325
2586
|
const isDetailHidden = isCollapsed || !hasSelectedGenreNode && !activeOverlayPanel;
|
|
2326
2587
|
useLeftDetailPanelLayout(!isDetailHidden, placement === "regions");
|
|
2327
2588
|
const detailOverlay = placement === "regions" && activeOverlayPanel?.onExpandedChange ? activeOverlayPanel : void 0;
|
|
@@ -2335,14 +2596,14 @@ function GenreTreeView({
|
|
|
2335
2596
|
hideExpandButton: placement === "regions" ? true : void 0,
|
|
2336
2597
|
overlayPanel: detailOverlay ? { ...detailOverlay, collapsed: isCollapsed, hideExpandButton: true } : activeOverlayPanel
|
|
2337
2598
|
};
|
|
2338
|
-
const [isWheelRevealed, setIsWheelRevealed] = (0,
|
|
2599
|
+
const [isWheelRevealed, setIsWheelRevealed] = (0, import_react17.useState)(false);
|
|
2339
2600
|
const usesHandoff = viewMode === "wheel" || viewMode === "pop-core";
|
|
2340
2601
|
const isTreeReady = !isLoading && (!usesHandoff || isWheelRevealed);
|
|
2341
|
-
const rootRef = (0,
|
|
2602
|
+
const rootRef = (0, import_react17.useRef)(null);
|
|
2342
2603
|
const handlePrevious = () => {
|
|
2343
2604
|
rootRef.current?.querySelector(".search-overlay .gtv-info-panel-previous")?.click();
|
|
2344
2605
|
};
|
|
2345
|
-
(0,
|
|
2606
|
+
(0, import_react17.useEffect)(() => {
|
|
2346
2607
|
if (!isTreeReady || viewMode !== "pop-core" || !hasSelectedGenreNode || !selectedGenrePlaylist) return;
|
|
2347
2608
|
const root = rootRef.current;
|
|
2348
2609
|
const centerChip = root?.querySelector(".gtv-wheel-center-node .gtv-wheel-chip");
|
|
@@ -2352,21 +2613,22 @@ function GenreTreeView({
|
|
|
2352
2613
|
centerChip.click();
|
|
2353
2614
|
}, [hasSelectedGenreNode, isTreeReady, selectedGenrePlaylist, viewMode]);
|
|
2354
2615
|
const { showPopup, hidePopup } = (0, import_popup5.usePopup)();
|
|
2355
|
-
const [searchText, setSearchText] = (0,
|
|
2356
|
-
const artistSearchResult = (0,
|
|
2357
|
-
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",
|
|
2358
2620
|
label: "Artists",
|
|
2359
2621
|
items: artistSearchResult.artists.map((artist) => ({
|
|
2360
2622
|
key: artist.uuid,
|
|
2361
|
-
content: /* @__PURE__ */ (0,
|
|
2362
|
-
/* @__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: [
|
|
2363
2625
|
artist.name,
|
|
2364
|
-
artist.disambiguation && /* @__PURE__ */ (0,
|
|
2626
|
+
artist.disambiguation && /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "font-normal text-[var(--color-neutral-500)]", children: [
|
|
2365
2627
|
" \xB7 ",
|
|
2366
2628
|
artist.disambiguation
|
|
2367
2629
|
] })
|
|
2368
2630
|
] }),
|
|
2369
|
-
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: [
|
|
2370
2632
|
"also known as ",
|
|
2371
2633
|
artist.matchedAlias
|
|
2372
2634
|
] })
|
|
@@ -2379,11 +2641,32 @@ function GenreTreeView({
|
|
|
2379
2641
|
status: artistSearchResult.isError ? "Search failed" : artistSearchResult.settled && artistSearchResult.artists.length === 0 ? "No artists found" : "",
|
|
2380
2642
|
loading: artistSearchResult.pending
|
|
2381
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);
|
|
2382
2663
|
const handleSelectedNodeChange = (node) => {
|
|
2664
|
+
if (node !== null && songUuid !== null) changeSong(null);
|
|
2383
2665
|
selectNode(node);
|
|
2384
2666
|
};
|
|
2385
2667
|
const handleGenreSearchClear = clearSelection;
|
|
2386
2668
|
const handleGenreSearchSelect = (genrePlaylist) => {
|
|
2669
|
+
if (songUuid !== null) changeSong(null);
|
|
2387
2670
|
selectGenrePlaylist(genrePlaylist);
|
|
2388
2671
|
};
|
|
2389
2672
|
const selectedUuid = selectedGenrePlaylist?.uuid;
|
|
@@ -2392,13 +2675,13 @@ function GenreTreeView({
|
|
|
2392
2675
|
const handleGenreSearchZoomIn = () => {
|
|
2393
2676
|
if (selectedUuid) focus.onFocusGenreChange?.(selectedUuid, "zoom-in");
|
|
2394
2677
|
};
|
|
2395
|
-
const zoomInRef = (0,
|
|
2678
|
+
const zoomInRef = (0, import_react17.useRef)(void 0);
|
|
2396
2679
|
zoomInRef.current = canZoomIn ? handleGenreSearchZoomIn : void 0;
|
|
2397
|
-
const renderInfoPanelBody = (0,
|
|
2680
|
+
const renderInfoPanelBody = (0, import_react17.useCallback)(
|
|
2398
2681
|
(node, { toolbar, ancestors, family }) => {
|
|
2399
2682
|
const genrePlaylist = genrePlaylistByNodeId.get(node.id);
|
|
2400
2683
|
const criteria = genrePlaylist?.criteria;
|
|
2401
|
-
if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ (0,
|
|
2684
|
+
if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
|
|
2402
2685
|
toolbar,
|
|
2403
2686
|
family
|
|
2404
2687
|
] }), aside: ancestors };
|
|
@@ -2418,7 +2701,7 @@ function GenreTreeView({
|
|
|
2418
2701
|
(sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist.uuid && sibling.criteria?.side === "pop"
|
|
2419
2702
|
) : void 0;
|
|
2420
2703
|
return {
|
|
2421
|
-
primary: /* @__PURE__ */ (0,
|
|
2704
|
+
primary: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2422
2705
|
GenreInfoPanelBody,
|
|
2423
2706
|
{
|
|
2424
2707
|
toolbar,
|
|
@@ -2437,7 +2720,7 @@ function GenreTreeView({
|
|
|
2437
2720
|
onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
|
|
2438
2721
|
onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
|
|
2439
2722
|
onChangeParent: readOnly ? void 0 : () => showPopup(
|
|
2440
|
-
/* @__PURE__ */ (0,
|
|
2723
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2441
2724
|
ChangeParentPopup,
|
|
2442
2725
|
{
|
|
2443
2726
|
genrePlaylist: { ...genrePlaylist, criteria },
|
|
@@ -2458,6 +2741,7 @@ function GenreTreeView({
|
|
|
2458
2741
|
criteriaOverviewSchema,
|
|
2459
2742
|
renderGenreDetailExtras,
|
|
2460
2743
|
renderArtistLink,
|
|
2744
|
+
renderSongLink: resolvedRenderSongLink,
|
|
2461
2745
|
artist: filterArtist,
|
|
2462
2746
|
renderSummary,
|
|
2463
2747
|
renderEssentialTracks,
|
|
@@ -2488,6 +2772,7 @@ function GenreTreeView({
|
|
|
2488
2772
|
criteriaOverviewSchema,
|
|
2489
2773
|
renderGenreDetailExtras,
|
|
2490
2774
|
renderArtistLink,
|
|
2775
|
+
resolvedRenderSongLink,
|
|
2491
2776
|
renderSummary,
|
|
2492
2777
|
renderEssentialTracks,
|
|
2493
2778
|
renderSongLeading,
|
|
@@ -2497,7 +2782,7 @@ function GenreTreeView({
|
|
|
2497
2782
|
);
|
|
2498
2783
|
const isRotationControlled = controlledAllowWheelRotation !== void 0;
|
|
2499
2784
|
const allowWheelRotation = controlledAllowWheelRotation ?? internalAllowWheelRotation;
|
|
2500
|
-
const detailTab = placement === "regions" && (hasSelectedGenreNode || detailOverlay) ? /* @__PURE__ */ (0,
|
|
2785
|
+
const detailTab = placement === "regions" && (hasSelectedGenreNode || detailOverlay) ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2501
2786
|
PanelEdgeTab,
|
|
2502
2787
|
{
|
|
2503
2788
|
side: "left",
|
|
@@ -2507,8 +2792,8 @@ function GenreTreeView({
|
|
|
2507
2792
|
) : null;
|
|
2508
2793
|
const renderArtistFilter = () => {
|
|
2509
2794
|
if (isLoading || !filterArtist) return null;
|
|
2510
|
-
return /* @__PURE__ */ (0,
|
|
2511
|
-
/* @__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)(
|
|
2512
2797
|
"button",
|
|
2513
2798
|
{
|
|
2514
2799
|
type: "button",
|
|
@@ -2520,7 +2805,7 @@ function GenreTreeView({
|
|
|
2520
2805
|
children: filterArtist.name
|
|
2521
2806
|
}
|
|
2522
2807
|
),
|
|
2523
|
-
/* @__PURE__ */ (0,
|
|
2808
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2524
2809
|
"button",
|
|
2525
2810
|
{
|
|
2526
2811
|
type: "button",
|
|
@@ -2532,23 +2817,23 @@ function GenreTreeView({
|
|
|
2532
2817
|
)
|
|
2533
2818
|
] });
|
|
2534
2819
|
};
|
|
2535
|
-
const actions = /* @__PURE__ */ (0,
|
|
2536
|
-
!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)(
|
|
2537
2822
|
GenreSearch,
|
|
2538
2823
|
{
|
|
2539
2824
|
genrePlaylists: visibleGenrePlaylists,
|
|
2540
2825
|
onSelect: handleGenreSearchSelect,
|
|
2541
|
-
selectedName: selectedGenrePlaylist?.name ??
|
|
2542
|
-
onClear: builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
|
|
2826
|
+
selectedName: selectedGenrePlaylist?.name ?? builtInPanel?.title ?? null,
|
|
2827
|
+
onClear: builtInSongPanel ? () => changeSong(null) : builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
|
|
2543
2828
|
onPrevious: handlePrevious,
|
|
2544
|
-
onQueryChange: artistSearch ? setSearchText : void 0,
|
|
2545
|
-
|
|
2829
|
+
onQueryChange: artistSearch || songSearch ? setSearchText : void 0,
|
|
2830
|
+
extraGroups
|
|
2546
2831
|
}
|
|
2547
2832
|
),
|
|
2548
|
-
!isLoading && filterArtist && /* @__PURE__ */ (0,
|
|
2549
|
-
/* @__PURE__ */ (0,
|
|
2550
|
-
!isLoading && !isControlled && /* @__PURE__ */ (0,
|
|
2551
|
-
|
|
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,
|
|
2552
2837
|
{
|
|
2553
2838
|
"aria-label": "Tree view mode",
|
|
2554
2839
|
value: viewMode,
|
|
@@ -2561,14 +2846,14 @@ function GenreTreeView({
|
|
|
2561
2846
|
]
|
|
2562
2847
|
}
|
|
2563
2848
|
),
|
|
2564
|
-
!isLoading && showDisplayOptions && /* @__PURE__ */ (0,
|
|
2849
|
+
!isLoading && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2565
2850
|
"div",
|
|
2566
2851
|
{
|
|
2567
2852
|
className: "flex items-center gap-1",
|
|
2568
2853
|
role: "group",
|
|
2569
2854
|
"aria-label": "Tree display options",
|
|
2570
|
-
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0,
|
|
2571
|
-
|
|
2855
|
+
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2856
|
+
import_ui15.Button,
|
|
2572
2857
|
{
|
|
2573
2858
|
variant: allowWheelRotation ? "default" : "outline",
|
|
2574
2859
|
size: "sm",
|
|
@@ -2578,52 +2863,52 @@ function GenreTreeView({
|
|
|
2578
2863
|
)
|
|
2579
2864
|
}
|
|
2580
2865
|
),
|
|
2581
|
-
!isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0,
|
|
2582
|
-
|
|
2866
|
+
!isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2867
|
+
import_ui15.IconTextButton,
|
|
2583
2868
|
{
|
|
2584
|
-
icon:
|
|
2869
|
+
icon: import_lucide_react12.Plus,
|
|
2585
2870
|
text: "Add root",
|
|
2586
2871
|
onClick: () => handleGenreCreationAction(null)
|
|
2587
2872
|
}
|
|
2588
2873
|
)
|
|
2589
2874
|
] })
|
|
2590
2875
|
] });
|
|
2591
|
-
return /* @__PURE__ */ (0,
|
|
2876
|
+
return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
|
|
2592
2877
|
"div",
|
|
2593
2878
|
{
|
|
2594
2879
|
ref: rootRef,
|
|
2595
2880
|
className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
|
|
2596
2881
|
children: [
|
|
2597
|
-
/* @__PURE__ */ (0,
|
|
2598
|
-
/* @__PURE__ */ (0,
|
|
2882
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)("style", { children: PREVIOUS_BUTTON_CSS }),
|
|
2883
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2599
2884
|
"div",
|
|
2600
2885
|
{
|
|
2601
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"}`,
|
|
2602
2887
|
children: actions
|
|
2603
2888
|
}
|
|
2604
2889
|
),
|
|
2605
|
-
!isLoading && filterArtist && /* @__PURE__ */ (0,
|
|
2890
|
+
!isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2606
2891
|
"div",
|
|
2607
2892
|
{
|
|
2608
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"}`,
|
|
2609
2894
|
children: renderArtistFilter()
|
|
2610
2895
|
}
|
|
2611
2896
|
),
|
|
2612
|
-
detailTab && /* @__PURE__ */ (0,
|
|
2897
|
+
detailTab && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2613
2898
|
"div",
|
|
2614
2899
|
{
|
|
2615
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))]"}`,
|
|
2616
2901
|
children: detailTab
|
|
2617
2902
|
}
|
|
2618
2903
|
),
|
|
2619
|
-
/* @__PURE__ */ (0,
|
|
2620
|
-
!isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0,
|
|
2621
|
-
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" ? (
|
|
2622
2907
|
// The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
|
|
2623
2908
|
// and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
|
|
2624
2909
|
// Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
|
|
2625
2910
|
// The panel's own Zoom in button is hidden: the search bar carries it.
|
|
2626
|
-
/* @__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)(
|
|
2627
2912
|
GenrePlaylistTreeWheel,
|
|
2628
2913
|
{
|
|
2629
2914
|
scope,
|
|
@@ -2642,9 +2927,9 @@ function GenreTreeView({
|
|
|
2642
2927
|
allowWheelRotation,
|
|
2643
2928
|
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2644
2929
|
busy,
|
|
2645
|
-
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0,
|
|
2930
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
2646
2931
|
floatingControlsLeading,
|
|
2647
|
-
canSizeByViews && /* @__PURE__ */ (0,
|
|
2932
|
+
canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2648
2933
|
] }) : void 0,
|
|
2649
2934
|
defaultViewport,
|
|
2650
2935
|
viewportRestore,
|
|
@@ -2654,7 +2939,7 @@ function GenreTreeView({
|
|
|
2654
2939
|
) }) })
|
|
2655
2940
|
) : viewMode === "pop-core" ? (
|
|
2656
2941
|
// Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
|
|
2657
|
-
/* @__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)(
|
|
2658
2943
|
GenrePlaylistTreeWheelRadialPopCore,
|
|
2659
2944
|
{
|
|
2660
2945
|
scope,
|
|
@@ -2673,9 +2958,9 @@ function GenreTreeView({
|
|
|
2673
2958
|
allowWheelRotation,
|
|
2674
2959
|
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2675
2960
|
busy,
|
|
2676
|
-
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0,
|
|
2961
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
2677
2962
|
floatingControlsLeading,
|
|
2678
|
-
canSizeByViews && /* @__PURE__ */ (0,
|
|
2963
|
+
canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2679
2964
|
] }) : void 0,
|
|
2680
2965
|
defaultViewport,
|
|
2681
2966
|
viewportRestore,
|
|
@@ -2684,7 +2969,7 @@ function GenreTreeView({
|
|
|
2684
2969
|
...focus
|
|
2685
2970
|
}
|
|
2686
2971
|
) }) })
|
|
2687
|
-
) : 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)(
|
|
2688
2973
|
GenrePlaylistTreeWheelRadialPopCore,
|
|
2689
2974
|
{
|
|
2690
2975
|
outline: true,
|
|
@@ -2703,13 +2988,13 @@ function GenreTreeView({
|
|
|
2703
2988
|
readOnly,
|
|
2704
2989
|
...infoPanelCuration
|
|
2705
2990
|
}
|
|
2706
|
-
) }) : /* @__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(
|
|
2707
2992
|
([uuid, genrePlaylistTreePerRoot], rootIndex) => {
|
|
2708
|
-
return /* @__PURE__ */ (0,
|
|
2993
|
+
return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2709
2994
|
"div",
|
|
2710
2995
|
{
|
|
2711
2996
|
className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
|
|
2712
|
-
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)(
|
|
2713
2998
|
GenrePlaylistTreePerRoot,
|
|
2714
2999
|
{
|
|
2715
3000
|
scope,
|
|
@@ -2746,11 +3031,11 @@ function GenreTreeView({
|
|
|
2746
3031
|
}
|
|
2747
3032
|
|
|
2748
3033
|
// src/genre-tree/GenreDetailPanel.tsx
|
|
2749
|
-
var
|
|
3034
|
+
var import_react18 = require("react");
|
|
2750
3035
|
var import_genre_tree_view5 = require("@behindthemusictree/genre-tree-view");
|
|
2751
|
-
var
|
|
2752
|
-
var
|
|
2753
|
-
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");
|
|
2754
3039
|
var ITEM_COUNT_LABEL = "Songs";
|
|
2755
3040
|
var RELATIVE_ITEM_COUNT = 1;
|
|
2756
3041
|
function toNode(genre, parentId) {
|
|
@@ -2777,9 +3062,9 @@ function GenreDetailPanel({
|
|
|
2777
3062
|
onExpandedChange,
|
|
2778
3063
|
extras
|
|
2779
3064
|
}) {
|
|
2780
|
-
const { data: detail, isPending, isError } = (0,
|
|
3065
|
+
const { data: detail, isPending, isError } = (0, import_genre_tree20.useFetchGenreDetail)(genreUuid, scope, getBackendBaseUrl);
|
|
2781
3066
|
const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: [] });
|
|
2782
|
-
const view = (0,
|
|
3067
|
+
const view = (0, import_react18.useMemo)(() => {
|
|
2783
3068
|
if (!detail) return null;
|
|
2784
3069
|
const rootColor = (0, import_genre_tree_view5.getGenreTreeColor)(detail.root.wikidataId);
|
|
2785
3070
|
const relative = (genre, parentId) => ({
|
|
@@ -2805,17 +3090,17 @@ function GenreDetailPanel({
|
|
|
2805
3090
|
};
|
|
2806
3091
|
}, [detail]);
|
|
2807
3092
|
if (isError) {
|
|
2808
|
-
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." });
|
|
2809
3094
|
}
|
|
2810
3095
|
if (isPending || !detail || !view) {
|
|
2811
|
-
return /* @__PURE__ */ (0,
|
|
2812
|
-
/* @__PURE__ */ (0,
|
|
2813
|
-
/* @__PURE__ */ (0,
|
|
2814
|
-
/* @__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" })
|
|
2815
3100
|
] });
|
|
2816
3101
|
}
|
|
2817
3102
|
const renderBody = (node, sections) => ({
|
|
2818
|
-
primary: /* @__PURE__ */ (0,
|
|
3103
|
+
primary: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
|
|
2819
3104
|
GenreInfoPanelBody,
|
|
2820
3105
|
{
|
|
2821
3106
|
toolbar: sections.toolbar,
|
|
@@ -2832,7 +3117,7 @@ function GenreDetailPanel({
|
|
|
2832
3117
|
),
|
|
2833
3118
|
aside: null
|
|
2834
3119
|
});
|
|
2835
|
-
return /* @__PURE__ */ (0,
|
|
3120
|
+
return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
|
|
2836
3121
|
import_genre_tree_view5.InfoPanel,
|
|
2837
3122
|
{
|
|
2838
3123
|
node: view.node,
|
|
@@ -2858,13 +3143,13 @@ function GenreDetailPanel({
|
|
|
2858
3143
|
var import_genre_tree_view6 = require("@behindthemusictree/genre-tree-view");
|
|
2859
3144
|
|
|
2860
3145
|
// src/genre-tree/track-list-sidebar/TrackListSidebar.tsx
|
|
2861
|
-
var
|
|
2862
|
-
var
|
|
2863
|
-
var
|
|
2864
|
-
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");
|
|
2865
3150
|
var import_track_noun6 = require("@behindthemusictree/app-kit/track-noun");
|
|
2866
|
-
var
|
|
2867
|
-
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;
|
|
2868
3153
|
function TrackListSidebar({
|
|
2869
3154
|
className: className2 = "",
|
|
2870
3155
|
renderDuration,
|
|
@@ -2873,20 +3158,21 @@ function TrackListSidebar({
|
|
|
2873
3158
|
isCollapsed = false,
|
|
2874
3159
|
onExpand,
|
|
2875
3160
|
onMinimize,
|
|
2876
|
-
renderArtistLink
|
|
3161
|
+
renderArtistLink,
|
|
3162
|
+
renderSongLink
|
|
2877
3163
|
}) {
|
|
2878
|
-
const { trackList, loadMore, autoAdvanceScrollRequest } = (0,
|
|
3164
|
+
const { trackList, loadMore, autoAdvanceScrollRequest } = (0, import_genre_tree21.useTrackList)();
|
|
2879
3165
|
const trackNoun = (0, import_track_noun6.useTrackNoun)();
|
|
2880
|
-
const trackListRef = (0,
|
|
2881
|
-
const previousOriginRef = (0,
|
|
2882
|
-
const handledAutoAdvanceRequestIdRef = (0,
|
|
2883
|
-
const shouldFollowPlaybackRef = (0,
|
|
2884
|
-
const programmaticScrollTopRef = (0,
|
|
2885
|
-
const userScrollIntentRef = (0,
|
|
2886
|
-
const userScrollIntentTimerRef = (0,
|
|
2887
|
-
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);
|
|
2888
3174
|
const hasMore = !!trackList && trackList.nextPage !== null;
|
|
2889
|
-
(0,
|
|
3175
|
+
(0, import_react19.useEffect)(() => {
|
|
2890
3176
|
const sentinel = loadMoreSentinelRef.current;
|
|
2891
3177
|
if (!hasMore || !sentinel) return;
|
|
2892
3178
|
const observer = new IntersectionObserver((entries) => {
|
|
@@ -2906,11 +3192,11 @@ function TrackListSidebar({
|
|
|
2906
3192
|
programmaticScrollTopRef.current = null;
|
|
2907
3193
|
if (trackListRef.current) trackListRef.current.scrollTop = 0;
|
|
2908
3194
|
}, [trackList?.origin]);
|
|
2909
|
-
(0,
|
|
3195
|
+
(0, import_react19.useEffect)(() => {
|
|
2910
3196
|
const list = trackListRef.current;
|
|
2911
3197
|
if (!isCollapsed && list?.scrollTop === 0) shouldFollowPlaybackRef.current = true;
|
|
2912
3198
|
}, [isCollapsed]);
|
|
2913
|
-
(0,
|
|
3199
|
+
(0, import_react19.useEffect)(
|
|
2914
3200
|
() => () => {
|
|
2915
3201
|
if (userScrollIntentTimerRef.current !== null) clearTimeout(userScrollIntentTimerRef.current);
|
|
2916
3202
|
},
|
|
@@ -2964,25 +3250,25 @@ function TrackListSidebar({
|
|
|
2964
3250
|
}
|
|
2965
3251
|
};
|
|
2966
3252
|
const positionClasses = layout === "inline" ? "relative w-full flex h-full flex-col" : "fixed right-0 w-[50rem] flex flex-col";
|
|
2967
|
-
return trackList ? /* @__PURE__ */ (0,
|
|
3253
|
+
return trackList ? /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
|
|
2968
3254
|
"div",
|
|
2969
3255
|
{
|
|
2970
3256
|
"data-theme": "dark",
|
|
2971
3257
|
className: `track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${className2}`,
|
|
2972
3258
|
style: layout === "fixed" ? { top: "63.5px", maxHeight: "calc(100vh - 63.5px - 79px)" } : void 0,
|
|
2973
3259
|
children: [
|
|
2974
|
-
/* @__PURE__ */ (0,
|
|
2975
|
-
/* @__PURE__ */ (0,
|
|
2976
|
-
/* @__PURE__ */ (0,
|
|
2977
|
-
/* @__PURE__ */ (0,
|
|
2978
|
-
/* @__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 }) })
|
|
2979
3265
|
] }),
|
|
2980
|
-
/* @__PURE__ */ (0,
|
|
2981
|
-
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 `,
|
|
2982
3268
|
`${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`
|
|
2983
3269
|
] })
|
|
2984
3270
|
] }),
|
|
2985
|
-
(isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0,
|
|
3271
|
+
(isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
|
|
2986
3272
|
"button",
|
|
2987
3273
|
{
|
|
2988
3274
|
type: "button",
|
|
@@ -2991,11 +3277,11 @@ function TrackListSidebar({
|
|
|
2991
3277
|
"aria-expanded": !isCollapsed,
|
|
2992
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"}`,
|
|
2993
3279
|
onClick: isCollapsed ? onExpand : onMinimize,
|
|
2994
|
-
children: /* @__PURE__ */ (0,
|
|
3280
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react13.ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
|
|
2995
3281
|
}
|
|
2996
3282
|
)
|
|
2997
3283
|
] }),
|
|
2998
|
-
!isCollapsed && /* @__PURE__ */ (0,
|
|
3284
|
+
!isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
|
|
2999
3285
|
"ul",
|
|
3000
3286
|
{
|
|
3001
3287
|
ref: trackListRef,
|
|
@@ -3005,17 +3291,18 @@ function TrackListSidebar({
|
|
|
3005
3291
|
onPointerDown: markUserScrollIntent,
|
|
3006
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",
|
|
3007
3293
|
children: [
|
|
3008
|
-
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)(
|
|
3009
3295
|
TrackItem,
|
|
3010
3296
|
{
|
|
3011
3297
|
track,
|
|
3012
3298
|
position: index + 1,
|
|
3013
3299
|
renderDuration,
|
|
3014
3300
|
renderActions,
|
|
3015
|
-
renderArtistLink
|
|
3301
|
+
renderArtistLink,
|
|
3302
|
+
renderSongLink
|
|
3016
3303
|
}
|
|
3017
3304
|
) }, track.uuid)),
|
|
3018
|
-
hasMore && /* @__PURE__ */ (0,
|
|
3305
|
+
hasMore && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
|
|
3019
3306
|
]
|
|
3020
3307
|
}
|
|
3021
3308
|
)
|
|
@@ -3025,16 +3312,16 @@ function TrackListSidebar({
|
|
|
3025
3312
|
}
|
|
3026
3313
|
|
|
3027
3314
|
// src/genre-tree/PlayerPanel.tsx
|
|
3028
|
-
var
|
|
3315
|
+
var import_react20 = require("react");
|
|
3029
3316
|
var import_tailwind_merge2 = require("tailwind-merge");
|
|
3030
|
-
var
|
|
3031
|
-
var
|
|
3317
|
+
var import_player13 = require("@behindthemusictree/app-kit/player");
|
|
3318
|
+
var import_genre_tree24 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3032
3319
|
|
|
3033
3320
|
// src/genre-tree/PlayerControls.tsx
|
|
3034
|
-
var
|
|
3035
|
-
var
|
|
3036
|
-
var
|
|
3037
|
-
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");
|
|
3038
3325
|
function PlayerControls() {
|
|
3039
3326
|
const {
|
|
3040
3327
|
duration,
|
|
@@ -3045,23 +3332,23 @@ function PlayerControls() {
|
|
|
3045
3332
|
seekTo,
|
|
3046
3333
|
setVolume,
|
|
3047
3334
|
volume
|
|
3048
|
-
} = (0,
|
|
3049
|
-
const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } = (0,
|
|
3050
|
-
const currentTime = (0,
|
|
3051
|
-
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;
|
|
3052
3339
|
const isReady = !!playerTrackObject?.isReady && !isBusy && !playerTrackObject.loadError;
|
|
3053
3340
|
const safeDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;
|
|
3054
3341
|
const safeCurrentTime = Number.isFinite(currentTime) ? Math.max(0, Math.min(currentTime, safeDuration)) : 0;
|
|
3055
|
-
const isPlaying = playState ===
|
|
3056
|
-
return /* @__PURE__ */ (0,
|
|
3342
|
+
const isPlaying = playState === import_player12.PlayStates.PLAYING;
|
|
3343
|
+
return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
|
|
3057
3344
|
"section",
|
|
3058
3345
|
{
|
|
3059
3346
|
"aria-label": "Player controls",
|
|
3060
3347
|
className: "player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200",
|
|
3061
3348
|
children: [
|
|
3062
|
-
/* @__PURE__ */ (0,
|
|
3063
|
-
/* @__PURE__ */ (0,
|
|
3064
|
-
/* @__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)(
|
|
3065
3352
|
"input",
|
|
3066
3353
|
{
|
|
3067
3354
|
"aria-label": "Seek",
|
|
@@ -3075,11 +3362,11 @@ function PlayerControls() {
|
|
|
3075
3362
|
onChange: (event) => seekTo(Number(event.currentTarget.value))
|
|
3076
3363
|
}
|
|
3077
3364
|
),
|
|
3078
|
-
/* @__PURE__ */ (0,
|
|
3365
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { "aria-label": "Duration", children: (0, import_genre_tree23.formatTime)(safeDuration) })
|
|
3079
3366
|
] }),
|
|
3080
|
-
/* @__PURE__ */ (0,
|
|
3081
|
-
/* @__PURE__ */ (0,
|
|
3082
|
-
/* @__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)(
|
|
3083
3370
|
"button",
|
|
3084
3371
|
{
|
|
3085
3372
|
type: "button",
|
|
@@ -3088,10 +3375,10 @@ function PlayerControls() {
|
|
|
3088
3375
|
disabled: !isReady || !hasPreviousTrack,
|
|
3089
3376
|
onClick: playPreviousTrack,
|
|
3090
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",
|
|
3091
|
-
children: /* @__PURE__ */ (0,
|
|
3378
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_lucide_react14.SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
3092
3379
|
}
|
|
3093
3380
|
),
|
|
3094
|
-
/* @__PURE__ */ (0,
|
|
3381
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
|
|
3095
3382
|
"button",
|
|
3096
3383
|
{
|
|
3097
3384
|
type: "button",
|
|
@@ -3100,10 +3387,10 @@ function PlayerControls() {
|
|
|
3100
3387
|
disabled: !isReady,
|
|
3101
3388
|
onClick: handlePlayPauseAction,
|
|
3102
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",
|
|
3103
|
-
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" })
|
|
3104
3391
|
}
|
|
3105
3392
|
),
|
|
3106
|
-
/* @__PURE__ */ (0,
|
|
3393
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
|
|
3107
3394
|
"button",
|
|
3108
3395
|
{
|
|
3109
3396
|
type: "button",
|
|
@@ -3112,14 +3399,14 @@ function PlayerControls() {
|
|
|
3112
3399
|
disabled: !isReady || !hasNextTrack,
|
|
3113
3400
|
onClick: playNextTrack,
|
|
3114
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",
|
|
3115
|
-
children: /* @__PURE__ */ (0,
|
|
3402
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_lucide_react14.SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
3116
3403
|
}
|
|
3117
3404
|
)
|
|
3118
3405
|
] }),
|
|
3119
|
-
/* @__PURE__ */ (0,
|
|
3120
|
-
volume === 0 ? /* @__PURE__ */ (0,
|
|
3121
|
-
/* @__PURE__ */ (0,
|
|
3122
|
-
/* @__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)(
|
|
3123
3410
|
"input",
|
|
3124
3411
|
{
|
|
3125
3412
|
"aria-label": "Volume",
|
|
@@ -3141,7 +3428,7 @@ function PlayerControls() {
|
|
|
3141
3428
|
}
|
|
3142
3429
|
|
|
3143
3430
|
// src/genre-tree/PlayerPanel.tsx
|
|
3144
|
-
var
|
|
3431
|
+
var import_jsx_runtime39 = require("react/jsx-runtime");
|
|
3145
3432
|
var PANEL_ID = "player-panel";
|
|
3146
3433
|
var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
|
|
3147
3434
|
function PlayerPanel({
|
|
@@ -3150,16 +3437,17 @@ function PlayerPanel({
|
|
|
3150
3437
|
expanded,
|
|
3151
3438
|
onExpandedChange,
|
|
3152
3439
|
extendedWidth = DEFAULT_EXTENDED_WIDTH,
|
|
3153
|
-
renderArtistLink
|
|
3440
|
+
renderArtistLink,
|
|
3441
|
+
renderSongLink
|
|
3154
3442
|
}) {
|
|
3155
|
-
const { playerTrackObject } = (0,
|
|
3156
|
-
const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0,
|
|
3157
|
-
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)("");
|
|
3158
3446
|
const loadedTrackTitle = playerTrackObject?.track.title;
|
|
3159
|
-
(0,
|
|
3447
|
+
(0, import_react20.useEffect)(() => {
|
|
3160
3448
|
if (loadedTrackTitle) setLastLoadedTrackTitle(loadedTrackTitle);
|
|
3161
3449
|
}, [loadedTrackTitle]);
|
|
3162
|
-
const [uncontrolledExtended, setUncontrolledExtended] = (0,
|
|
3450
|
+
const [uncontrolledExtended, setUncontrolledExtended] = (0, import_react20.useState)(false);
|
|
3163
3451
|
const isExtendRequested = expanded ?? uncontrolledExtended;
|
|
3164
3452
|
const setIsExtendRequested = (next) => {
|
|
3165
3453
|
setUncontrolledExtended(next);
|
|
@@ -3180,7 +3468,7 @@ function PlayerPanel({
|
|
|
3180
3468
|
onClick: () => setIsExtendRequested(true),
|
|
3181
3469
|
expanded: false
|
|
3182
3470
|
} : null;
|
|
3183
|
-
return /* @__PURE__ */ (0,
|
|
3471
|
+
return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
|
|
3184
3472
|
"div",
|
|
3185
3473
|
{
|
|
3186
3474
|
id: PANEL_ID,
|
|
@@ -3193,7 +3481,7 @@ function PlayerPanel({
|
|
|
3193
3481
|
),
|
|
3194
3482
|
style: isExtended ? { width: extendedWidth } : void 0,
|
|
3195
3483
|
children: [
|
|
3196
|
-
/* @__PURE__ */ (0,
|
|
3484
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
3197
3485
|
PanelEdgeTab,
|
|
3198
3486
|
{
|
|
3199
3487
|
side: "right",
|
|
@@ -3203,7 +3491,7 @@ function PlayerPanel({
|
|
|
3203
3491
|
className: "absolute right-full top-1/2 -translate-y-1/2"
|
|
3204
3492
|
}
|
|
3205
3493
|
),
|
|
3206
|
-
/* @__PURE__ */ (0,
|
|
3494
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
|
|
3207
3495
|
"div",
|
|
3208
3496
|
{
|
|
3209
3497
|
className: (0, import_tailwind_merge2.twMerge)(
|
|
@@ -3211,15 +3499,15 @@ function PlayerPanel({
|
|
|
3211
3499
|
isExtended ? "flex-row" : "flex-col"
|
|
3212
3500
|
),
|
|
3213
3501
|
children: [
|
|
3214
|
-
/* @__PURE__ */ (0,
|
|
3215
|
-
/* @__PURE__ */ (0,
|
|
3216
|
-
/* @__PURE__ */ (0,
|
|
3217
|
-
/* @__PURE__ */ (0,
|
|
3218
|
-
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 })
|
|
3219
3507
|
] }),
|
|
3220
|
-
/* @__PURE__ */ (0,
|
|
3508
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(PlayerControls, {})
|
|
3221
3509
|
] }),
|
|
3222
|
-
/* @__PURE__ */ (0,
|
|
3510
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
3223
3511
|
TrackListSidebar,
|
|
3224
3512
|
{
|
|
3225
3513
|
layout: "inline",
|
|
@@ -3230,7 +3518,8 @@ function PlayerPanel({
|
|
|
3230
3518
|
"min-h-0 min-w-0 flex-1",
|
|
3231
3519
|
isExtended && "border-l border-zinc-800"
|
|
3232
3520
|
),
|
|
3233
|
-
renderArtistLink
|
|
3521
|
+
renderArtistLink,
|
|
3522
|
+
renderSongLink
|
|
3234
3523
|
}
|
|
3235
3524
|
)
|
|
3236
3525
|
]
|
|
@@ -3244,33 +3533,33 @@ function PlayerPanel({
|
|
|
3244
3533
|
// src/genre-tree/Rating.tsx
|
|
3245
3534
|
var import_uuid = require("uuid");
|
|
3246
3535
|
var import_ai = require("react-icons/ai");
|
|
3247
|
-
var
|
|
3248
|
-
var
|
|
3536
|
+
var import_genre_tree25 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3537
|
+
var import_jsx_runtime40 = require("react/jsx-runtime");
|
|
3249
3538
|
function Rating({ rating, handleChange }) {
|
|
3250
3539
|
const instanceId = (0, import_uuid.v4)();
|
|
3251
|
-
return /* @__PURE__ */ (0,
|
|
3252
|
-
/* @__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)(
|
|
3253
3542
|
"input",
|
|
3254
3543
|
{
|
|
3255
3544
|
id: `star-${instanceId}-${i}`,
|
|
3256
3545
|
className: "w-6 h-6 opacity-0",
|
|
3257
3546
|
type: "radio",
|
|
3258
3547
|
name: "rating",
|
|
3259
|
-
value: i === 0 ?
|
|
3548
|
+
value: i === 0 ? import_genre_tree25.FORM_RATING_NULL_VALUE : i,
|
|
3260
3549
|
checked: rating === (i === 0 ? null : i),
|
|
3261
3550
|
onChange: handleChange
|
|
3262
3551
|
}
|
|
3263
3552
|
),
|
|
3264
|
-
/* @__PURE__ */ (0,
|
|
3265
|
-
/* @__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)(
|
|
3266
3555
|
import_ai.AiOutlineStar,
|
|
3267
3556
|
{
|
|
3268
|
-
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)]"}`
|
|
3269
3558
|
}
|
|
3270
3559
|
),
|
|
3271
|
-
/* @__PURE__ */ (0,
|
|
3560
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "flex-grow w-8" }),
|
|
3272
3561
|
" "
|
|
3273
|
-
] }) : /* @__PURE__ */ (0,
|
|
3562
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
3274
3563
|
import_ai.AiFillStar,
|
|
3275
3564
|
{
|
|
3276
3565
|
className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
|