@behindthemusictree/app-kit-ui 34.0.0 → 35.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-A7OWCEIK.js → chunk-YC3WBUVO.js} +643 -605
- package/dist/chunk-YC3WBUVO.js.map +1 -0
- package/dist/genre-tree/index.cjs +744 -707
- package/dist/genre-tree/index.cjs.map +1 -1
- package/dist/genre-tree/index.d.ts +51 -39
- package/dist/genre-tree/index.js +1 -1
- package/dist/index.cjs +739 -702
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +3 -3
- package/dist/chunk-A7OWCEIK.js.map +0 -1
|
@@ -46,13 +46,13 @@ __export(genre_tree_exports, {
|
|
|
46
46
|
module.exports = __toCommonJS(genre_tree_exports);
|
|
47
47
|
|
|
48
48
|
// src/genre-tree/GenreTreeView.tsx
|
|
49
|
-
var
|
|
49
|
+
var import_react18 = require("react");
|
|
50
50
|
var import_react_query3 = require("@tanstack/react-query");
|
|
51
|
-
var import_lucide_react11 = require("lucide-react");
|
|
52
51
|
var import_lucide_react12 = require("lucide-react");
|
|
53
|
-
var
|
|
54
|
-
var
|
|
55
|
-
var
|
|
52
|
+
var import_lucide_react13 = require("lucide-react");
|
|
53
|
+
var import_ui17 = require("@behindthemusictree/ui");
|
|
54
|
+
var import_genre_tree_view5 = require("@behindthemusictree/genre-tree-view");
|
|
55
|
+
var import_genre_tree28 = require("@behindthemusictree/app-kit/genre-tree");
|
|
56
56
|
var import_popup5 = require("@behindthemusictree/app-kit/popup");
|
|
57
57
|
|
|
58
58
|
// src/genre-tree/playlist-tree/size-metric.ts
|
|
@@ -702,6 +702,9 @@ function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
|
|
|
702
702
|
] });
|
|
703
703
|
}
|
|
704
704
|
|
|
705
|
+
// src/genre-tree/GenreTreeSearch.tsx
|
|
706
|
+
var import_react8 = require("react");
|
|
707
|
+
|
|
705
708
|
// src/genre-tree/GenreSearch.tsx
|
|
706
709
|
var import_lucide_react3 = require("lucide-react");
|
|
707
710
|
var import_ui4 = require("@behindthemusictree/ui");
|
|
@@ -902,6 +905,52 @@ function GenreSearch({
|
|
|
902
905
|
) });
|
|
903
906
|
}
|
|
904
907
|
|
|
908
|
+
// src/genre-tree/GenreTreeSearch.tsx
|
|
909
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
910
|
+
function GenreTreeSearch({
|
|
911
|
+
artistBaseUrl,
|
|
912
|
+
songBaseUrl,
|
|
913
|
+
onArtistSelect,
|
|
914
|
+
onSongSelect,
|
|
915
|
+
artistSearch,
|
|
916
|
+
songSearch,
|
|
917
|
+
SearchGroups,
|
|
918
|
+
onSelectGenre,
|
|
919
|
+
extraGroups = [],
|
|
920
|
+
onQueryChange,
|
|
921
|
+
onFocus,
|
|
922
|
+
...props
|
|
923
|
+
}) {
|
|
924
|
+
const [query, setQuery] = (0, import_react8.useState)("");
|
|
925
|
+
const [focused, setFocused] = (0, import_react8.useState)(false);
|
|
926
|
+
const deferredQuery = (0, import_react8.useDeferredValue)(query);
|
|
927
|
+
const artistGroup = useArtistSearchGroup(artistBaseUrl, deferredQuery, onArtistSelect, artistSearch);
|
|
928
|
+
const songGroup = useSongSearchGroup(songBaseUrl, deferredQuery, onSongSelect, songSearch);
|
|
929
|
+
const render = (hostGroups) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
930
|
+
GenreSearch,
|
|
931
|
+
{
|
|
932
|
+
...props,
|
|
933
|
+
onQueryChange: (text) => {
|
|
934
|
+
setQuery(text);
|
|
935
|
+
onQueryChange?.(text);
|
|
936
|
+
},
|
|
937
|
+
onFocus: () => {
|
|
938
|
+
setFocused(true);
|
|
939
|
+
onFocus?.();
|
|
940
|
+
},
|
|
941
|
+
extraGroups: [...extraGroups, ...hostGroups, artistGroup, songGroup].filter((group) => group !== void 0)
|
|
942
|
+
}
|
|
943
|
+
);
|
|
944
|
+
return SearchGroups ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SearchGroups, { query: deferredQuery, focused, onSelectGenre, children: render }) : render([]);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// src/genre-tree/GenreDetailPanel.tsx
|
|
948
|
+
var import_react14 = require("react");
|
|
949
|
+
var import_genre_tree_view4 = require("@behindthemusictree/genre-tree-view");
|
|
950
|
+
var import_ui7 = require("@behindthemusictree/ui");
|
|
951
|
+
var import_lucide_react8 = require("lucide-react");
|
|
952
|
+
var import_genre_tree19 = require("@behindthemusictree/app-kit/genre-tree");
|
|
953
|
+
|
|
905
954
|
// src/genre-tree/GenreInfoPanelBody.tsx
|
|
906
955
|
var import_genre_tree18 = require("@behindthemusictree/app-kit/genre-tree");
|
|
907
956
|
var import_track_noun3 = require("@behindthemusictree/app-kit/track-noun");
|
|
@@ -915,25 +964,25 @@ var import_lucide_react6 = require("lucide-react");
|
|
|
915
964
|
var import_popup3 = require("@behindthemusictree/app-kit/popup");
|
|
916
965
|
|
|
917
966
|
// src/genre-tree/SharePopup.tsx
|
|
918
|
-
var
|
|
967
|
+
var import_react10 = require("react");
|
|
919
968
|
var import_lucide_react5 = require("lucide-react");
|
|
920
969
|
var import_fa = require("react-icons/fa");
|
|
921
970
|
var import_si = require("react-icons/si");
|
|
922
971
|
|
|
923
972
|
// src/popup/Toast.tsx
|
|
924
|
-
var
|
|
973
|
+
var import_react9 = require("react");
|
|
925
974
|
var import_react_dom2 = require("react-dom");
|
|
926
|
-
var
|
|
975
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
927
976
|
function Toast({ message, onClose, durationMs = 2200 }) {
|
|
928
|
-
const [mounted, setMounted] = (0,
|
|
929
|
-
(0,
|
|
977
|
+
const [mounted, setMounted] = (0, import_react9.useState)(false);
|
|
978
|
+
(0, import_react9.useEffect)(() => {
|
|
930
979
|
setMounted(true);
|
|
931
980
|
const timeout = window.setTimeout(onClose, durationMs);
|
|
932
981
|
return () => window.clearTimeout(timeout);
|
|
933
982
|
}, [durationMs, onClose]);
|
|
934
983
|
if (!mounted) return null;
|
|
935
984
|
return (0, import_react_dom2.createPortal)(
|
|
936
|
-
/* @__PURE__ */ (0,
|
|
985
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "pointer-events-none fixed inset-x-0 bottom-8 z-[60] flex justify-center px-4", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
937
986
|
"div",
|
|
938
987
|
{
|
|
939
988
|
role: "status",
|
|
@@ -948,12 +997,12 @@ function Toast({ message, onClose, durationMs = 2200 }) {
|
|
|
948
997
|
}
|
|
949
998
|
|
|
950
999
|
// src/genre-tree/ShareDestinationButton.tsx
|
|
951
|
-
var
|
|
1000
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
952
1001
|
var className = "group flex min-w-0 flex-1 flex-col items-center justify-center gap-1.5 rounded-xl px-2 py-2 text-xs text-[var(--color-text)] transition-colors hover:bg-[var(--color-surface-muted)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] disabled:cursor-not-allowed disabled:opacity-50";
|
|
953
1002
|
function Contents({ icon, label }) {
|
|
954
|
-
return /* @__PURE__ */ (0,
|
|
955
|
-
/* @__PURE__ */ (0,
|
|
956
|
-
/* @__PURE__ */ (0,
|
|
1003
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
|
|
1004
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]", children: icon }),
|
|
1005
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "max-w-full truncate", children: label })
|
|
957
1006
|
] });
|
|
958
1007
|
}
|
|
959
1008
|
function ShareDestinationButton({
|
|
@@ -965,29 +1014,29 @@ function ShareDestinationButton({
|
|
|
965
1014
|
ariaLabel
|
|
966
1015
|
}) {
|
|
967
1016
|
if (href) {
|
|
968
|
-
return /* @__PURE__ */ (0,
|
|
1017
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", "aria-label": ariaLabel, className, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Contents, { icon, label }) });
|
|
969
1018
|
}
|
|
970
|
-
return /* @__PURE__ */ (0,
|
|
1019
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", onClick, disabled, "aria-label": ariaLabel, className, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Contents, { icon, label }) });
|
|
971
1020
|
}
|
|
972
1021
|
|
|
973
1022
|
// src/genre-tree/SharePreviewCard.tsx
|
|
974
1023
|
var import_lucide_react4 = require("lucide-react");
|
|
975
|
-
var
|
|
1024
|
+
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
976
1025
|
function SharePreviewCard({ target, url }) {
|
|
977
1026
|
const Icon = { genre: import_lucide_react4.Music2, artist: import_lucide_react4.UserRound, song: import_lucide_react4.Music }[target.kind];
|
|
978
1027
|
const typeLabel = { genre: "Genre", artist: "Artist", song: "Song" }[target.kind];
|
|
979
|
-
return /* @__PURE__ */ (0,
|
|
980
|
-
/* @__PURE__ */ (0,
|
|
981
|
-
/* @__PURE__ */ (0,
|
|
982
|
-
/* @__PURE__ */ (0,
|
|
983
|
-
/* @__PURE__ */ (0,
|
|
984
|
-
/* @__PURE__ */ (0,
|
|
1028
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "rounded-3xl bg-[var(--color-surface-muted)] p-4", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex min-h-32 overflow-hidden rounded-2xl bg-[var(--color-surface)] shadow-lg", children: [
|
|
1029
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.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_runtime17.jsx)(Icon, { "aria-hidden": "true", className: "h-10 w-10" }) }),
|
|
1030
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col justify-center gap-1 p-4", children: [
|
|
1031
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-xs font-medium uppercase tracking-wide text-[var(--color-text-muted)]", children: typeLabel }),
|
|
1032
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "truncate text-base font-semibold text-[var(--color-text)]", title: target.name, children: target.name }),
|
|
1033
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "truncate text-sm text-[var(--color-primary)]", title: url, children: url })
|
|
985
1034
|
] })
|
|
986
1035
|
] }) });
|
|
987
1036
|
}
|
|
988
1037
|
|
|
989
1038
|
// src/genre-tree/SharePopup.tsx
|
|
990
|
-
var
|
|
1039
|
+
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
991
1040
|
function resolveAbsoluteUrl(url) {
|
|
992
1041
|
try {
|
|
993
1042
|
const parsed = new URL(url, window.location.href);
|
|
@@ -1020,20 +1069,20 @@ async function copyText(text) {
|
|
|
1020
1069
|
}
|
|
1021
1070
|
}
|
|
1022
1071
|
function SharePopup({ target, url, onClose }) {
|
|
1023
|
-
const [absoluteUrl, setAbsoluteUrl] = (0,
|
|
1024
|
-
const [copyStatus, setCopyStatus] = (0,
|
|
1025
|
-
const [canUseNativeShare, setCanUseNativeShare] = (0,
|
|
1026
|
-
const [shareError, setShareError] = (0,
|
|
1027
|
-
const [toastId, setToastId] = (0,
|
|
1028
|
-
(0,
|
|
1072
|
+
const [absoluteUrl, setAbsoluteUrl] = (0, import_react10.useState)(url);
|
|
1073
|
+
const [copyStatus, setCopyStatus] = (0, import_react10.useState)("idle");
|
|
1074
|
+
const [canUseNativeShare, setCanUseNativeShare] = (0, import_react10.useState)(false);
|
|
1075
|
+
const [shareError, setShareError] = (0, import_react10.useState)(null);
|
|
1076
|
+
const [toastId, setToastId] = (0, import_react10.useState)(0);
|
|
1077
|
+
(0, import_react10.useEffect)(() => {
|
|
1029
1078
|
setAbsoluteUrl(resolveAbsoluteUrl(url) ?? "");
|
|
1030
1079
|
setCopyStatus("idle");
|
|
1031
1080
|
setShareError(null);
|
|
1032
1081
|
}, [url]);
|
|
1033
|
-
(0,
|
|
1082
|
+
(0, import_react10.useEffect)(() => {
|
|
1034
1083
|
setCanUseNativeShare(typeof navigator !== "undefined" && typeof navigator.share === "function");
|
|
1035
1084
|
}, []);
|
|
1036
|
-
const destinationUrls = (0,
|
|
1085
|
+
const destinationUrls = (0, import_react10.useMemo)(() => {
|
|
1037
1086
|
if (!absoluteUrl) return null;
|
|
1038
1087
|
const message = `${target.name} \u2014 ${absoluteUrl}`;
|
|
1039
1088
|
return {
|
|
@@ -1051,7 +1100,7 @@ function SharePopup({ target, url, onClose }) {
|
|
|
1051
1100
|
setCopyStatus(copied ? "idle" : "failed");
|
|
1052
1101
|
if (copied) setToastId((id) => id + 1);
|
|
1053
1102
|
};
|
|
1054
|
-
const dismissToast = (0,
|
|
1103
|
+
const dismissToast = (0, import_react10.useCallback)(() => setToastId(0), []);
|
|
1055
1104
|
const handleNativeShare = async () => {
|
|
1056
1105
|
if (!absoluteUrl || !navigator.share) return;
|
|
1057
1106
|
setShareError(null);
|
|
@@ -1062,8 +1111,8 @@ function SharePopup({ target, url, onClose }) {
|
|
|
1062
1111
|
setShareError("Could not open the share options.");
|
|
1063
1112
|
}
|
|
1064
1113
|
};
|
|
1065
|
-
return /* @__PURE__ */ (0,
|
|
1066
|
-
/* @__PURE__ */ (0,
|
|
1114
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
|
|
1115
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
1067
1116
|
BasePopup_default,
|
|
1068
1117
|
{
|
|
1069
1118
|
title: "Share",
|
|
@@ -1078,67 +1127,67 @@ function SharePopup({ target, url, onClose }) {
|
|
|
1078
1127
|
titleTextClassName: "!text-xl !font-semibold !text-[var(--color-text)]",
|
|
1079
1128
|
closeButtonClassName: "!h-10 !w-10 !rounded-none !border-0 !bg-transparent !p-0 !text-[var(--color-text)] hover:!bg-transparent hover:!text-[var(--color-text)]",
|
|
1080
1129
|
contentClassName: "!rounded-none !bg-transparent !px-6 !pb-6 !pt-2",
|
|
1081
|
-
children: /* @__PURE__ */ (0,
|
|
1082
|
-
/* @__PURE__ */ (0,
|
|
1083
|
-
/* @__PURE__ */ (0,
|
|
1084
|
-
/* @__PURE__ */ (0,
|
|
1130
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex flex-col gap-4", children: [
|
|
1131
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(SharePreviewCard, { target, url: absoluteUrl || url }),
|
|
1132
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "grid grid-cols-3 gap-1 rounded-3xl bg-[var(--color-surface)] p-3 sm:grid-cols-4", children: [
|
|
1133
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
1085
1134
|
ShareDestinationButton,
|
|
1086
1135
|
{
|
|
1087
1136
|
label: "Copy Link",
|
|
1088
|
-
icon: /* @__PURE__ */ (0,
|
|
1137
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react5.Link2, { "aria-hidden": "true", className: "h-6 w-6" }),
|
|
1089
1138
|
onClick: () => void handleCopy(),
|
|
1090
1139
|
ariaLabel: "Copy link",
|
|
1091
1140
|
disabled: !absoluteUrl
|
|
1092
1141
|
}
|
|
1093
1142
|
),
|
|
1094
|
-
/* @__PURE__ */ (0,
|
|
1143
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
1095
1144
|
ShareDestinationButton,
|
|
1096
1145
|
{
|
|
1097
1146
|
label: "WhatsApp",
|
|
1098
|
-
icon: /* @__PURE__ */ (0,
|
|
1147
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_fa.FaWhatsapp, { "aria-hidden": "true", className: "h-9 w-9 text-[#25D366]" }),
|
|
1099
1148
|
href: destinationUrls?.whatsapp,
|
|
1100
1149
|
ariaLabel: `Share ${target.name} via WhatsApp`,
|
|
1101
1150
|
disabled: !absoluteUrl
|
|
1102
1151
|
}
|
|
1103
1152
|
),
|
|
1104
|
-
/* @__PURE__ */ (0,
|
|
1153
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
1105
1154
|
ShareDestinationButton,
|
|
1106
1155
|
{
|
|
1107
1156
|
label: "Gmail",
|
|
1108
|
-
icon: /* @__PURE__ */ (0,
|
|
1157
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_si.SiGmail, { "aria-hidden": "true", className: "h-8 w-8 text-[#EA4335]" }),
|
|
1109
1158
|
href: destinationUrls?.gmail,
|
|
1110
1159
|
ariaLabel: `Email ${target.name} with Gmail`,
|
|
1111
1160
|
disabled: !absoluteUrl
|
|
1112
1161
|
}
|
|
1113
1162
|
),
|
|
1114
|
-
canUseNativeShare && /* @__PURE__ */ (0,
|
|
1163
|
+
canUseNativeShare && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
1115
1164
|
ShareDestinationButton,
|
|
1116
1165
|
{
|
|
1117
1166
|
label: "More",
|
|
1118
|
-
icon: /* @__PURE__ */ (0,
|
|
1167
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react5.Share2, { "aria-hidden": "true", className: "h-6 w-6" }),
|
|
1119
1168
|
onClick: () => void handleNativeShare(),
|
|
1120
1169
|
ariaLabel: "More sharing options",
|
|
1121
1170
|
disabled: !absoluteUrl
|
|
1122
1171
|
}
|
|
1123
1172
|
)
|
|
1124
1173
|
] }),
|
|
1125
|
-
copyStatus === "failed" && /* @__PURE__ */ (0,
|
|
1126
|
-
shareError && /* @__PURE__ */ (0,
|
|
1174
|
+
copyStatus === "failed" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: "Could not copy the link." }),
|
|
1175
|
+
shareError && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: shareError })
|
|
1127
1176
|
] })
|
|
1128
1177
|
}
|
|
1129
1178
|
),
|
|
1130
|
-
toastId > 0 && /* @__PURE__ */ (0,
|
|
1179
|
+
toastId > 0 && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Toast, { message: "Link copied", onClose: dismissToast }, toastId)
|
|
1131
1180
|
] });
|
|
1132
1181
|
}
|
|
1133
1182
|
|
|
1134
1183
|
// src/genre-tree/ShareActionButton.tsx
|
|
1135
|
-
var
|
|
1184
|
+
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
1136
1185
|
function ShareActionButton({ target, url }) {
|
|
1137
1186
|
const { showPopup, hidePopup } = (0, import_popup3.usePopup)();
|
|
1138
1187
|
const openSharePopup = () => {
|
|
1139
|
-
showPopup(/* @__PURE__ */ (0,
|
|
1188
|
+
showPopup(/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(SharePopup, { target, url, onClose: hidePopup }));
|
|
1140
1189
|
};
|
|
1141
|
-
return /* @__PURE__ */ (0,
|
|
1190
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
1142
1191
|
"button",
|
|
1143
1192
|
{
|
|
1144
1193
|
type: "button",
|
|
@@ -1147,7 +1196,7 @@ function ShareActionButton({ target, url }) {
|
|
|
1147
1196
|
"aria-label": `Share ${target.name}`,
|
|
1148
1197
|
title: `Share ${target.name}`,
|
|
1149
1198
|
children: [
|
|
1150
|
-
/* @__PURE__ */ (0,
|
|
1199
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.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: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react6.Share2, { "aria-hidden": "true", className: "w-5 h-5" }) }),
|
|
1151
1200
|
"Share"
|
|
1152
1201
|
]
|
|
1153
1202
|
}
|
|
@@ -1155,7 +1204,7 @@ function ShareActionButton({ target, url }) {
|
|
|
1155
1204
|
}
|
|
1156
1205
|
|
|
1157
1206
|
// src/genre-tree/GenreActionBand.tsx
|
|
1158
|
-
var
|
|
1207
|
+
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
1159
1208
|
function GenreActionBand({
|
|
1160
1209
|
node,
|
|
1161
1210
|
onPlayPause,
|
|
@@ -1177,7 +1226,7 @@ function GenreActionBand({
|
|
|
1177
1226
|
items.push({
|
|
1178
1227
|
key: "play-pause",
|
|
1179
1228
|
label: isPlaying ? "Pause" : "Play",
|
|
1180
|
-
icon: isPlaying ? /* @__PURE__ */ (0,
|
|
1229
|
+
icon: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react7.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react7.Play, { "aria-hidden": "true", className: "w-5 h-5" }),
|
|
1181
1230
|
onClick: onPlayPause
|
|
1182
1231
|
});
|
|
1183
1232
|
}
|
|
@@ -1185,24 +1234,24 @@ function GenreActionBand({
|
|
|
1185
1234
|
items.push({
|
|
1186
1235
|
key: "zoom-in",
|
|
1187
1236
|
label: "Focus",
|
|
1188
|
-
icon: /* @__PURE__ */ (0,
|
|
1237
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react7.ZoomIn, { "aria-hidden": "true", className: "w-5 h-5" }),
|
|
1189
1238
|
onClick: onZoomIn
|
|
1190
1239
|
});
|
|
1191
1240
|
}
|
|
1192
1241
|
if (onAddChild) {
|
|
1193
|
-
adminItems.push({ key: "add-child", label: "Add", icon: /* @__PURE__ */ (0,
|
|
1242
|
+
adminItems.push({ key: "add-child", label: "Add", icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react7.Plus, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onAddChild });
|
|
1194
1243
|
}
|
|
1195
1244
|
if (onRename) {
|
|
1196
|
-
adminItems.push({ key: "rename", label: "Rename", icon: /* @__PURE__ */ (0,
|
|
1245
|
+
adminItems.push({ key: "rename", label: "Rename", icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react7.Pencil, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onRename });
|
|
1197
1246
|
}
|
|
1198
1247
|
if (onChangeParent) {
|
|
1199
|
-
adminItems.push({ key: "change-parent", label: "Parent", icon: /* @__PURE__ */ (0,
|
|
1248
|
+
adminItems.push({ key: "change-parent", label: "Parent", icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react7.MoveUpRight, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onChangeParent });
|
|
1200
1249
|
}
|
|
1201
1250
|
if (onToggleSide) {
|
|
1202
1251
|
adminItems.push({
|
|
1203
1252
|
key: "toggle-side",
|
|
1204
1253
|
label: side === "pop" ? "Make core" : "Make pop",
|
|
1205
|
-
icon: /* @__PURE__ */ (0,
|
|
1254
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react7.ArrowLeftRight, { "aria-hidden": "true", className: "w-5 h-5" }),
|
|
1206
1255
|
onClick: onToggleSide,
|
|
1207
1256
|
disabled: !!toggleSideDisabledReason,
|
|
1208
1257
|
title: toggleSideDisabledReason
|
|
@@ -1218,7 +1267,7 @@ function GenreActionBand({
|
|
|
1218
1267
|
});
|
|
1219
1268
|
}
|
|
1220
1269
|
if (items.length === 0 && (!shareTarget || !shareUrl) && adminItems.length === 0) return null;
|
|
1221
|
-
const renderItems = (list) => list.map((item) => /* @__PURE__ */ (0,
|
|
1270
|
+
const renderItems = (list) => list.map((item) => /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
|
|
1222
1271
|
"button",
|
|
1223
1272
|
{
|
|
1224
1273
|
type: "button",
|
|
@@ -1227,25 +1276,25 @@ function GenreActionBand({
|
|
|
1227
1276
|
onClick: item.onClick,
|
|
1228
1277
|
className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none disabled:opacity-50",
|
|
1229
1278
|
children: [
|
|
1230
|
-
/* @__PURE__ */ (0,
|
|
1279
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.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)] group-disabled:group-hover:bg-[var(--color-surface-muted)]", children: item.icon }),
|
|
1231
1280
|
item.label
|
|
1232
1281
|
]
|
|
1233
1282
|
},
|
|
1234
1283
|
item.key
|
|
1235
1284
|
));
|
|
1236
|
-
return /* @__PURE__ */ (0,
|
|
1237
|
-
(items.length > 0 || shareTarget && shareUrl) && /* @__PURE__ */ (0,
|
|
1285
|
+
return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: [
|
|
1286
|
+
(items.length > 0 || shareTarget && shareUrl) && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
|
|
1238
1287
|
renderItems(items),
|
|
1239
|
-
shareTarget && shareUrl && /* @__PURE__ */ (0,
|
|
1288
|
+
shareTarget && shareUrl && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
|
|
1240
1289
|
] }),
|
|
1241
|
-
adminItems.length > 0 && /* @__PURE__ */ (0,
|
|
1290
|
+
adminItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
|
|
1242
1291
|
"section",
|
|
1243
1292
|
{
|
|
1244
1293
|
"aria-label": "Admin actions",
|
|
1245
1294
|
className: `px-3.5 py-3 ${items.length > 0 ? "border-t border-[var(--color-border)]" : ""}`,
|
|
1246
1295
|
children: [
|
|
1247
|
-
/* @__PURE__ */ (0,
|
|
1248
|
-
/* @__PURE__ */ (0,
|
|
1296
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("h3", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-[var(--color-text-muted)]", children: "Admin" }),
|
|
1297
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "flex flex-wrap gap-1", children: renderItems(adminItems) })
|
|
1249
1298
|
]
|
|
1250
1299
|
}
|
|
1251
1300
|
)
|
|
@@ -1257,20 +1306,20 @@ var import_ui6 = require("@behindthemusictree/ui");
|
|
|
1257
1306
|
var import_genre_tree17 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1258
1307
|
|
|
1259
1308
|
// src/genre-tree/ClampedText.tsx
|
|
1260
|
-
var
|
|
1261
|
-
var
|
|
1309
|
+
var import_react11 = require("react");
|
|
1310
|
+
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
1262
1311
|
function ClampedText({ children, className: className2 = "" }) {
|
|
1263
|
-
const ref = (0,
|
|
1264
|
-
const [expanded, setExpanded] = (0,
|
|
1265
|
-
const [overflows, setOverflows] = (0,
|
|
1266
|
-
(0,
|
|
1312
|
+
const ref = (0, import_react11.useRef)(null);
|
|
1313
|
+
const [expanded, setExpanded] = (0, import_react11.useState)(false);
|
|
1314
|
+
const [overflows, setOverflows] = (0, import_react11.useState)(false);
|
|
1315
|
+
(0, import_react11.useLayoutEffect)(() => {
|
|
1267
1316
|
const element = ref.current;
|
|
1268
1317
|
if (!element || expanded) return;
|
|
1269
1318
|
setOverflows(element.scrollHeight > element.clientHeight);
|
|
1270
1319
|
}, [children, expanded]);
|
|
1271
|
-
return /* @__PURE__ */ (0,
|
|
1272
|
-
/* @__PURE__ */ (0,
|
|
1273
|
-
(overflows || expanded) && /* @__PURE__ */ (0,
|
|
1320
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
|
|
1321
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { ref, className: `${className2} ${expanded ? "" : "line-clamp-3"}`, children }),
|
|
1322
|
+
(overflows || expanded) && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
1274
1323
|
"button",
|
|
1275
1324
|
{
|
|
1276
1325
|
type: "button",
|
|
@@ -1284,7 +1333,7 @@ function ClampedText({ children, className: className2 = "" }) {
|
|
|
1284
1333
|
}
|
|
1285
1334
|
|
|
1286
1335
|
// src/genre-tree/GenrePlaylistTracks.tsx
|
|
1287
|
-
var
|
|
1336
|
+
var import_react12 = require("react");
|
|
1288
1337
|
var import_react_query = require("@tanstack/react-query");
|
|
1289
1338
|
var import_zod = require("zod");
|
|
1290
1339
|
var import_fa2 = require("react-icons/fa");
|
|
@@ -1305,15 +1354,15 @@ var import_genre_tree15 = require("@behindthemusictree/app-kit/genre-tree");
|
|
|
1305
1354
|
|
|
1306
1355
|
// src/genre-tree/MusicBrainzRecordingLink.tsx
|
|
1307
1356
|
var import_genre_tree10 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1308
|
-
var
|
|
1357
|
+
var import_jsx_runtime22 = require("react/jsx-runtime");
|
|
1309
1358
|
function MusicBrainzRecordingLink({
|
|
1310
1359
|
song,
|
|
1311
1360
|
children,
|
|
1312
1361
|
className: className2 = ""
|
|
1313
1362
|
}) {
|
|
1314
1363
|
const url = (0, import_genre_tree10.musicBrainzRecordingUrl)(song);
|
|
1315
|
-
if (!url) return /* @__PURE__ */ (0,
|
|
1316
|
-
return /* @__PURE__ */ (0,
|
|
1364
|
+
if (!url) return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_jsx_runtime22.Fragment, { children });
|
|
1365
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
1317
1366
|
"a",
|
|
1318
1367
|
{
|
|
1319
1368
|
href: url,
|
|
@@ -1330,7 +1379,7 @@ function MusicBrainzRecordingLink({
|
|
|
1330
1379
|
// src/genre-tree/GenrePlaylistTracks.tsx
|
|
1331
1380
|
var import_genre_tree16 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1332
1381
|
var import_track_noun = require("@behindthemusictree/app-kit/track-noun");
|
|
1333
|
-
var
|
|
1382
|
+
var import_jsx_runtime23 = require("react/jsx-runtime");
|
|
1334
1383
|
var CATALOGUE_PAGE_SIZE = 100;
|
|
1335
1384
|
var SongPageSchema = (0, import_transport4.PaginatedResponseSchema)(import_genre_tree14.SongDetailedSchema).extend({
|
|
1336
1385
|
overallTotal: import_zod.z.number().nullable(),
|
|
@@ -1350,6 +1399,7 @@ var nextCursor = (next) => {
|
|
|
1350
1399
|
function GenrePlaylistTracks({
|
|
1351
1400
|
genreUuid,
|
|
1352
1401
|
genrePlaylist,
|
|
1402
|
+
genreName: selectedGenreName,
|
|
1353
1403
|
selection,
|
|
1354
1404
|
scope,
|
|
1355
1405
|
getBackendBaseUrl,
|
|
@@ -1365,13 +1415,17 @@ function GenrePlaylistTracks({
|
|
|
1365
1415
|
const { playerTrackObject, playState, handlePlayPauseAction } = (0, import_player4.usePlayer)();
|
|
1366
1416
|
const { showPopup } = (0, import_popup4.usePopup)();
|
|
1367
1417
|
const trackNoun = (0, import_track_noun.useTrackNoun)();
|
|
1368
|
-
const
|
|
1418
|
+
const uuid = genrePlaylist?.uuid;
|
|
1419
|
+
const genreName = selectedGenreName ?? genrePlaylist?.name ?? "Genre";
|
|
1369
1420
|
const { fetch } = (0, import_transport3.useFetchWrapper)(getBackendBaseUrl);
|
|
1370
1421
|
const playback = (0, import_transport3.usePlaybackContext)();
|
|
1371
1422
|
const { data, isPending, isError, refetch, hasNextPage, isFetchingNextPage, fetchNextPage } = (0, import_react_query.useInfiniteQuery)({
|
|
1372
|
-
queryKey: [...scope === "me" ? import_genre_tree12.genrePlaylistQueryKeys.me.tracks(uuid) : artist ? [...import_genre_tree13.libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid, selection), "artist", artist.uuid] : import_genre_tree13.libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid, selection), ...playback.key ? [playback.key] : []],
|
|
1423
|
+
queryKey: [...scope === "me" ? uuid ? import_genre_tree12.genrePlaylistQueryKeys.me.tracks(uuid) : ["missing-personal-genre-playlist", genreUuid] : artist ? [...import_genre_tree13.libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid, selection), "artist", artist.uuid] : import_genre_tree13.libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid, selection), ...playback.key ? [playback.key] : []],
|
|
1373
1424
|
queryFn: async ({ pageParam }) => {
|
|
1374
|
-
if (scope === "me")
|
|
1425
|
+
if (scope === "me") {
|
|
1426
|
+
if (!uuid) throw new Error("Personal genre has no playlist");
|
|
1427
|
+
return fetchGenrePlaylistTracksPage(uuid, scope, Number(pageParam));
|
|
1428
|
+
}
|
|
1375
1429
|
const response = await fetch(
|
|
1376
1430
|
import_genre_tree13.libraryEndpoints.reference.songs.list(),
|
|
1377
1431
|
true,
|
|
@@ -1384,10 +1438,10 @@ function GenrePlaylistTracks({
|
|
|
1384
1438
|
},
|
|
1385
1439
|
initialPageParam: scope === "me" ? 1 : "",
|
|
1386
1440
|
getNextPageParam: (lastPage) => lastPage.nextPage ?? void 0,
|
|
1387
|
-
enabled: playback.ready && (scope !== "me" || sessionRestored && !!session?.accessToken)
|
|
1441
|
+
enabled: playback.ready && (scope !== "me" || uuid !== void 0 && sessionRestored && !!session?.accessToken)
|
|
1388
1442
|
});
|
|
1389
|
-
const [sentinel, setSentinel] = (0,
|
|
1390
|
-
(0,
|
|
1443
|
+
const [sentinel, setSentinel] = (0, import_react12.useState)(null);
|
|
1444
|
+
(0, import_react12.useEffect)(() => {
|
|
1391
1445
|
if (!hasNextPage || isFetchingNextPage || !sentinel) return;
|
|
1392
1446
|
const observer = new IntersectionObserver((entries) => {
|
|
1393
1447
|
if (entries.some((entry) => entry.isIntersecting)) {
|
|
@@ -1406,38 +1460,38 @@ function GenrePlaylistTracks({
|
|
|
1406
1460
|
ordering: "title",
|
|
1407
1461
|
genreUuid,
|
|
1408
1462
|
...selection && { selection }
|
|
1409
|
-
}) : scope === "reference" ? playNewTrackListFromGenreCatalogue({ uuid: genreUuid, name:
|
|
1463
|
+
}) : scope === "reference" ? playNewTrackListFromGenreCatalogue({ uuid: genreUuid, name: genreName }, scope, { startSongUuid, ...selection && { selection } }) : genrePlaylist ? playNewTrackListFromGenrePlaylist(genrePlaylist, scope, { startSongUuid }) : Promise.reject(new Error("Personal genre has no playlist"));
|
|
1410
1464
|
started.catch((error) => {
|
|
1411
1465
|
console.error("Failed to play genre playlist:", error);
|
|
1412
|
-
showPopup(/* @__PURE__ */ (0,
|
|
1466
|
+
showPopup(/* @__PURE__ */ (0, import_jsx_runtime23.jsx)(InternalErrorPopup, { errorCode: import_transport6.ErrorCode.CLIENT_UNKNOWN }));
|
|
1413
1467
|
});
|
|
1414
1468
|
};
|
|
1415
|
-
return /* @__PURE__ */ (0,
|
|
1469
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ui5.PanelSection, { title: `${(0, import_track_noun.capitalize)(trackNoun.plural)}${total !== void 0 ? ` (${total})` : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { children: [
|
|
1416
1470
|
renderTracksToolbar?.(),
|
|
1417
|
-
isPending ? /* @__PURE__ */ (0,
|
|
1471
|
+
scope === "me" && !genrePlaylist ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { children: "\u2014" }) : isPending ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ui5.Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("p", { role: "alert", children: [
|
|
1418
1472
|
"Couldn't load ",
|
|
1419
1473
|
trackNoun.plural,
|
|
1420
1474
|
".",
|
|
1421
1475
|
" ",
|
|
1422
|
-
/* @__PURE__ */ (0,
|
|
1423
|
-
] }) : tracks.length === 0 ? /* @__PURE__ */ (0,
|
|
1476
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
|
|
1477
|
+
] }) : tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { children: "\u2014" }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("ul", { className: "mt-1 max-h-64 overflow-y-auto text-[0.8125rem] leading-[1.45] [container-type:inline-size]", "aria-label": `${genreName} ${trackNoun.plural}`, children: [
|
|
1424
1478
|
tracks.map((track, index) => {
|
|
1425
1479
|
const label = track.artists?.length ? `${track.title} \u2014 ${track.artists.map((artist2) => artist2.name).join(", ")}` : track.title;
|
|
1426
1480
|
const isCurrent = playerTrackObject?.track.id === track.uuid;
|
|
1427
1481
|
const isPlaying = isCurrent && playState === import_player5.PlayStates.PLAYING;
|
|
1428
|
-
const text = /* @__PURE__ */ (0,
|
|
1429
|
-
/* @__PURE__ */ (0,
|
|
1430
|
-
track.artists?.length ? /* @__PURE__ */ (0,
|
|
1431
|
-
/* @__PURE__ */ (0,
|
|
1432
|
-
track.album && /* @__PURE__ */ (0,
|
|
1482
|
+
const text = /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { className: TEXT_CELL, children: [
|
|
1483
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: TITLE_CELL, children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree14.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
|
|
1484
|
+
track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: SEPARATOR, children: " \u2014 " }) : null,
|
|
1485
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_genre_tree16.ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
|
|
1486
|
+
track.album && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: ALBUM_CELL, children: track.album.name })
|
|
1433
1487
|
] });
|
|
1434
1488
|
const leading = renderSongLeading?.(track, index);
|
|
1435
1489
|
const actions = renderSongActions?.(track);
|
|
1436
1490
|
const isUnplayable = (0, import_genre_tree14.isSong)(track) && (0, import_genre_tree14.songUnplayableReason)(track) !== null;
|
|
1437
1491
|
if (isUnplayable) {
|
|
1438
|
-
return /* @__PURE__ */ (0,
|
|
1492
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("li", { className: "flex min-h-8 items-center gap-1 px-1 opacity-50", children: [
|
|
1439
1493
|
leading,
|
|
1440
|
-
/* @__PURE__ */ (0,
|
|
1494
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
1441
1495
|
"button",
|
|
1442
1496
|
{
|
|
1443
1497
|
type: "button",
|
|
@@ -1445,27 +1499,27 @@ function GenrePlaylistTracks({
|
|
|
1445
1499
|
"aria-disabled": "true",
|
|
1446
1500
|
"aria-label": label,
|
|
1447
1501
|
className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full opacity-0 cursor-not-allowed",
|
|
1448
|
-
children: /* @__PURE__ */ (0,
|
|
1502
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_fa2.FaPlay, { size: 10 })
|
|
1449
1503
|
}
|
|
1450
1504
|
),
|
|
1451
1505
|
text,
|
|
1452
1506
|
actions
|
|
1453
1507
|
] }, track.uuid);
|
|
1454
1508
|
}
|
|
1455
|
-
return /* @__PURE__ */ (0,
|
|
1509
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
|
|
1456
1510
|
"li",
|
|
1457
1511
|
{
|
|
1458
1512
|
className: `group flex min-h-8 items-center gap-1 rounded px-1 hover:bg-[var(--color-neutral-100)] ${isCurrent ? "font-medium text-[var(--color-primary)]" : ""}`,
|
|
1459
1513
|
children: [
|
|
1460
1514
|
leading,
|
|
1461
|
-
/* @__PURE__ */ (0,
|
|
1515
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
1462
1516
|
"button",
|
|
1463
1517
|
{
|
|
1464
1518
|
type: "button",
|
|
1465
1519
|
"aria-label": `${isPlaying ? "Pause" : "Play"} ${label}`,
|
|
1466
1520
|
className: `flex h-6 w-6 shrink-0 items-center justify-center rounded-full cursor-pointer group-hover:opacity-100 focus-visible:opacity-100 ${isCurrent && playState !== import_player5.PlayStates.STOPPED ? "opacity-100" : "opacity-0 [@media(pointer:coarse)]:opacity-100"}`,
|
|
1467
1521
|
onClick: () => isCurrent ? handlePlayPauseAction() : play(track.uuid),
|
|
1468
|
-
children: isCurrent && playState === import_player5.PlayStates.LOADING ? /* @__PURE__ */ (0,
|
|
1522
|
+
children: isCurrent && playState === import_player5.PlayStates.LOADING ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ui5.RingLoader, { size: 10 }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_fa2.FaPause, { size: 10 }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_fa2.FaPlay, { size: 10 })
|
|
1469
1523
|
}
|
|
1470
1524
|
),
|
|
1471
1525
|
text,
|
|
@@ -1475,7 +1529,7 @@ function GenrePlaylistTracks({
|
|
|
1475
1529
|
track.uuid
|
|
1476
1530
|
);
|
|
1477
1531
|
}),
|
|
1478
|
-
hasNextPage && /* @__PURE__ */ (0,
|
|
1532
|
+
hasNextPage && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("li", { ref: setSentinel, "aria-hidden": "true", className: "h-4" })
|
|
1479
1533
|
] })
|
|
1480
1534
|
] }) });
|
|
1481
1535
|
}
|
|
@@ -1483,10 +1537,11 @@ function GenrePlaylistTracks({
|
|
|
1483
1537
|
// src/genre-tree/GenreDetailExtras.tsx
|
|
1484
1538
|
var import_track_noun2 = require("@behindthemusictree/app-kit/track-noun");
|
|
1485
1539
|
var import_si2 = require("react-icons/si");
|
|
1486
|
-
var
|
|
1540
|
+
var import_jsx_runtime24 = require("react/jsx-runtime");
|
|
1487
1541
|
function GenreDetailExtras({
|
|
1488
1542
|
genreUuid,
|
|
1489
1543
|
genrePlaylist,
|
|
1544
|
+
genreName,
|
|
1490
1545
|
selection,
|
|
1491
1546
|
wikidataId,
|
|
1492
1547
|
scope,
|
|
@@ -1508,17 +1563,17 @@ function GenreDetailExtras({
|
|
|
1508
1563
|
criteriaOverviewSchema
|
|
1509
1564
|
);
|
|
1510
1565
|
if (isPending) {
|
|
1511
|
-
return /* @__PURE__ */ (0,
|
|
1512
|
-
/* @__PURE__ */ (0,
|
|
1566
|
+
return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
|
|
1567
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ui6.PanelSection, { title: "Summary", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { "aria-busy": "true", "data-testid": "genre-detail-extras-skeleton", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ui6.Skeleton, { className: "h-4 w-3/4" }) }) }),
|
|
1513
1568
|
family
|
|
1514
1569
|
] });
|
|
1515
1570
|
}
|
|
1516
|
-
if (!overview) return /* @__PURE__ */ (0,
|
|
1571
|
+
if (!overview) return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_jsx_runtime24.Fragment, { children: family });
|
|
1517
1572
|
const { summary, summarySourceUrl, essentialSongs } = overview;
|
|
1518
1573
|
const wikidataUrl = wikidataId ? `https://www.wikidata.org/wiki/${encodeURIComponent(wikidataId)}` : null;
|
|
1519
|
-
return /* @__PURE__ */ (0,
|
|
1520
|
-
(summarySourceUrl || wikidataUrl) && /* @__PURE__ */ (0,
|
|
1521
|
-
summarySourceUrl && /* @__PURE__ */ (0,
|
|
1574
|
+
return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
|
|
1575
|
+
(summarySourceUrl || wikidataUrl) && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ui6.PanelSection, { title: "Links", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex flex-wrap gap-x-3 gap-y-1 text-xs text-[var(--color-text)]", children: [
|
|
1576
|
+
summarySourceUrl && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
|
|
1522
1577
|
"a",
|
|
1523
1578
|
{
|
|
1524
1579
|
href: summarySourceUrl,
|
|
@@ -1526,12 +1581,12 @@ function GenreDetailExtras({
|
|
|
1526
1581
|
rel: "noopener noreferrer",
|
|
1527
1582
|
className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
|
|
1528
1583
|
children: [
|
|
1529
|
-
/* @__PURE__ */ (0,
|
|
1584
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_si2.SiWikipedia, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
|
|
1530
1585
|
"Wikipedia"
|
|
1531
1586
|
]
|
|
1532
1587
|
}
|
|
1533
1588
|
),
|
|
1534
|
-
wikidataUrl && /* @__PURE__ */ (0,
|
|
1589
|
+
wikidataUrl && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
|
|
1535
1590
|
"a",
|
|
1536
1591
|
{
|
|
1537
1592
|
href: wikidataUrl,
|
|
@@ -1539,21 +1594,22 @@ function GenreDetailExtras({
|
|
|
1539
1594
|
rel: "noopener noreferrer",
|
|
1540
1595
|
className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
|
|
1541
1596
|
children: [
|
|
1542
|
-
/* @__PURE__ */ (0,
|
|
1597
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_si2.SiWikidata, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
|
|
1543
1598
|
"Wikidata"
|
|
1544
1599
|
]
|
|
1545
1600
|
}
|
|
1546
1601
|
)
|
|
1547
1602
|
] }) }),
|
|
1548
|
-
/* @__PURE__ */ (0,
|
|
1603
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ui6.PanelSection, { title: "Summary", children: renderSummary ? renderSummary(overview) : /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ClampedText, { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: summary ?? "\u2014" }, genreUuid) }),
|
|
1549
1604
|
family,
|
|
1550
1605
|
renderGenreDetailExtras?.(overview),
|
|
1551
|
-
(renderEssentialTracks || essentialSongs) && /* @__PURE__ */ (0,
|
|
1552
|
-
/* @__PURE__ */ (0,
|
|
1606
|
+
(renderEssentialTracks || essentialSongs) && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ui6.PanelSection, { title: `Essential ${trackNoun.plural}`, children: renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && essentialSongs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("ul", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: essentialSongs.map((track) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("li", { className: "py-1", children: track.title }, track.uuid)) }) : /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: "\u2014" }) }),
|
|
1607
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
1553
1608
|
GenrePlaylistTracks,
|
|
1554
1609
|
{
|
|
1555
1610
|
genreUuid,
|
|
1556
1611
|
genrePlaylist,
|
|
1612
|
+
genreName,
|
|
1557
1613
|
selection,
|
|
1558
1614
|
scope,
|
|
1559
1615
|
getBackendBaseUrl,
|
|
@@ -1566,8 +1622,8 @@ function GenreDetailExtras({
|
|
|
1566
1622
|
}
|
|
1567
1623
|
|
|
1568
1624
|
// src/genre-tree/GenreInfoPanelBody.tsx
|
|
1569
|
-
var
|
|
1570
|
-
var
|
|
1625
|
+
var import_jsx_runtime25 = require("react/jsx-runtime");
|
|
1626
|
+
var import_react13 = require("react");
|
|
1571
1627
|
function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
|
|
1572
1628
|
const { isPlaying, setIsPlaying } = (0, import_player6.usePlayer)();
|
|
1573
1629
|
const isCurrentTrackList = playback.playingNodeId === genrePlaylist.uuid;
|
|
@@ -1580,7 +1636,7 @@ function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
|
|
|
1580
1636
|
console.error("Failed to load genre playlist tracks:", error);
|
|
1581
1637
|
});
|
|
1582
1638
|
};
|
|
1583
|
-
return /* @__PURE__ */ (0,
|
|
1639
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
1584
1640
|
GenreActionBand,
|
|
1585
1641
|
{
|
|
1586
1642
|
...bandProps,
|
|
@@ -1595,6 +1651,7 @@ function GenreInfoPanelBody({
|
|
|
1595
1651
|
genreUuid,
|
|
1596
1652
|
genrePlaylist,
|
|
1597
1653
|
catalogue,
|
|
1654
|
+
countsContext,
|
|
1598
1655
|
wikidataId,
|
|
1599
1656
|
scope,
|
|
1600
1657
|
getBackendBaseUrl,
|
|
@@ -1603,30 +1660,32 @@ function GenreInfoPanelBody({
|
|
|
1603
1660
|
extras
|
|
1604
1661
|
}) {
|
|
1605
1662
|
const noun = (0, import_track_noun3.useTrackNoun)();
|
|
1606
|
-
const summary = scope === "reference" ? import_genre_tree18.GenreCatalogueCountsSchema.parse(genrePlaylist) : void 0;
|
|
1663
|
+
const summary = scope === "reference" ? import_genre_tree18.GenreCatalogueCountsSchema.parse(genrePlaylist ?? catalogue) : void 0;
|
|
1607
1664
|
const selection = summary ? (0, import_genre_tree18.defaultGenreMusicSelection)(summary) : void 0;
|
|
1608
1665
|
const describeCounts = (counts) => {
|
|
1609
1666
|
const scopes = counts.genreAndSubgenresTracksCount;
|
|
1610
1667
|
return "canonical" in scopes ? `${counts.genreOnlyTracksCount} this genre only \xB7 ${scopes.canonical} with canonical subgenres \xB7 ${scopes.regional} with regional subgenres \xB7 ${scopes.all} with all subgenres` : `${counts.genreOnlyTracksCount} this genre only \xB7 ${scopes.all} with subgenres`;
|
|
1611
1668
|
};
|
|
1612
|
-
return /* @__PURE__ */ (0,
|
|
1669
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_jsx_runtime25.Fragment, { children: [
|
|
1613
1670
|
toolbar,
|
|
1614
|
-
/* @__PURE__ */ (0,
|
|
1615
|
-
summary && catalogue && /* @__PURE__ */ (0,
|
|
1616
|
-
/* @__PURE__ */ (0,
|
|
1671
|
+
genrePlaylist ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(GenreNodeActionBand, { ...band, genrePlaylist, playback }) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(GenreActionBand, { ...band }),
|
|
1672
|
+
summary && catalogue && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "px-3.5 py-3 text-sm text-[var(--color-text)]", children: [
|
|
1673
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("p", { children: [
|
|
1617
1674
|
"Catalogue ",
|
|
1618
1675
|
noun.plural,
|
|
1676
|
+
countsContext ? ` (${countsContext})` : "",
|
|
1619
1677
|
": ",
|
|
1620
1678
|
describeCounts(catalogue)
|
|
1621
1679
|
] }),
|
|
1622
|
-
/* @__PURE__ */ (0,
|
|
1680
|
+
genrePlaylist && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("p", { children: [
|
|
1623
1681
|
"Playlist ",
|
|
1624
1682
|
noun.plural,
|
|
1683
|
+
countsContext ? ` (${countsContext})` : "",
|
|
1625
1684
|
": ",
|
|
1626
1685
|
describeCounts(summary)
|
|
1627
1686
|
] })
|
|
1628
1687
|
] }),
|
|
1629
|
-
/* @__PURE__ */ (0,
|
|
1688
|
+
/* @__PURE__ */ (0, import_react13.createElement)(
|
|
1630
1689
|
GenreDetailExtras,
|
|
1631
1690
|
{
|
|
1632
1691
|
...extras,
|
|
@@ -1634,6 +1693,7 @@ function GenreInfoPanelBody({
|
|
|
1634
1693
|
family,
|
|
1635
1694
|
genreUuid,
|
|
1636
1695
|
genrePlaylist,
|
|
1696
|
+
genreName: band.node.name,
|
|
1637
1697
|
selection,
|
|
1638
1698
|
wikidataId,
|
|
1639
1699
|
scope,
|
|
@@ -1643,10 +1703,160 @@ function GenreInfoPanelBody({
|
|
|
1643
1703
|
] });
|
|
1644
1704
|
}
|
|
1645
1705
|
|
|
1706
|
+
// src/genre-tree/GenreDetailPanel.tsx
|
|
1707
|
+
var import_jsx_runtime26 = require("react/jsx-runtime");
|
|
1708
|
+
var RELATIVE_ITEM_COUNT = 1;
|
|
1709
|
+
function toNode(genre, parentId) {
|
|
1710
|
+
return {
|
|
1711
|
+
id: genre.uuid,
|
|
1712
|
+
parentId,
|
|
1713
|
+
name: genre.name,
|
|
1714
|
+
itemCount: RELATIVE_ITEM_COUNT,
|
|
1715
|
+
side: genre.side ?? void 0,
|
|
1716
|
+
wikidataId: genre.wikidataId
|
|
1717
|
+
};
|
|
1718
|
+
}
|
|
1719
|
+
function useGenreDetailContent({
|
|
1720
|
+
genreUuid,
|
|
1721
|
+
scope,
|
|
1722
|
+
getBackendBaseUrl,
|
|
1723
|
+
onSelectGenre,
|
|
1724
|
+
extras,
|
|
1725
|
+
band,
|
|
1726
|
+
playback: hostPlayback,
|
|
1727
|
+
trailing,
|
|
1728
|
+
genrePlaylist: hostPlaylist,
|
|
1729
|
+
expanded
|
|
1730
|
+
}) {
|
|
1731
|
+
const { data: detail, isPending, isError } = (0, import_genre_tree19.useFetchGenreDetail)(genreUuid, scope, getBackendBaseUrl);
|
|
1732
|
+
const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists: [], artist: extras?.artist });
|
|
1733
|
+
const playback = hostPlayback ?? defaultPlayback;
|
|
1734
|
+
const view = (0, import_react14.useMemo)(() => {
|
|
1735
|
+
if (!detail) return null;
|
|
1736
|
+
const rootColor = (0, import_genre_tree_view4.getGenreTreeColor)(detail.root.wikidataId);
|
|
1737
|
+
const relative = (genre, parentId) => ({
|
|
1738
|
+
node: toNode(genre, parentId),
|
|
1739
|
+
fill: (0, import_genre_tree_view4.tintSurface)(rootColor),
|
|
1740
|
+
textColor: import_genre_tree_view4.TEXT_COLOR
|
|
1741
|
+
});
|
|
1742
|
+
const ancestors2 = [...detail.ascendants].sort((a, b) => b.degree - a.degree);
|
|
1743
|
+
return {
|
|
1744
|
+
node: {
|
|
1745
|
+
id: detail.uuid,
|
|
1746
|
+
parentId: detail.parent?.uuid ?? null,
|
|
1747
|
+
name: detail.name,
|
|
1748
|
+
itemCount: scope === "reference" ? (0, import_genre_tree19.genreMusicCount)(detail.criteriaPlaylist ? import_genre_tree19.GenrePlaylistSummarySchema.parse(detail.criteriaPlaylist) : import_genre_tree19.GenreCatalogueCountsSchema.parse(detail.catalogue)) : detail.tracksCount,
|
|
1749
|
+
wikidataId: detail.wikidataId
|
|
1750
|
+
},
|
|
1751
|
+
fill: rootColor,
|
|
1752
|
+
parentNode: detail.parent ? relative(detail.parent, null) : null,
|
|
1753
|
+
childNodes: detail.children.map((child) => relative(child, detail.uuid)),
|
|
1754
|
+
ancestorNodes: ancestors2.map(
|
|
1755
|
+
({ ascendant }, index) => relative(ascendant, index === 0 ? null : ancestors2[index - 1].ascendant.uuid)
|
|
1756
|
+
)
|
|
1757
|
+
};
|
|
1758
|
+
}, [detail, scope]);
|
|
1759
|
+
if (isError) return {
|
|
1760
|
+
title: "Genre unavailable",
|
|
1761
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." })
|
|
1762
|
+
};
|
|
1763
|
+
if (isPending || !detail || !view) return {
|
|
1764
|
+
title: "Loading genre\u2026",
|
|
1765
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
|
|
1766
|
+
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_ui7.Skeleton, { className: "h-6 w-2/3" }),
|
|
1767
|
+
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_ui7.Skeleton, { className: "h-4 w-full" }),
|
|
1768
|
+
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_ui7.Skeleton, { className: "h-4 w-3/4" })
|
|
1769
|
+
] })
|
|
1770
|
+
};
|
|
1771
|
+
const selectGenre = (nodeId) => onSelectGenre(import_genre_tree19.GenreUuidSchema.parse(nodeId));
|
|
1772
|
+
const family = /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
|
|
1773
|
+
import_genre_tree_view4.GenreNodeFamily,
|
|
1774
|
+
{
|
|
1775
|
+
node: view.node,
|
|
1776
|
+
fill: view.fill,
|
|
1777
|
+
textColor: import_genre_tree_view4.ACCENT_TEXT_COLOR,
|
|
1778
|
+
parentNode: view.parentNode,
|
|
1779
|
+
childNodes: view.childNodes,
|
|
1780
|
+
onSelectNode: selectGenre
|
|
1781
|
+
}
|
|
1782
|
+
);
|
|
1783
|
+
const ancestors = /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_genre_tree_view4.GenreNodeAncestors, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
|
|
1784
|
+
return {
|
|
1785
|
+
title: detail.name,
|
|
1786
|
+
headerAccent: { background: view.fill, color: import_genre_tree_view4.ACCENT_TEXT_COLOR },
|
|
1787
|
+
aside: expanded ? ancestors : void 0,
|
|
1788
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_jsx_runtime26.Fragment, { children: [
|
|
1789
|
+
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
|
|
1790
|
+
GenreInfoPanelBody,
|
|
1791
|
+
{
|
|
1792
|
+
family: /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_jsx_runtime26.Fragment, { children: [
|
|
1793
|
+
family,
|
|
1794
|
+
!expanded && ancestors
|
|
1795
|
+
] }),
|
|
1796
|
+
genreUuid: detail.uuid,
|
|
1797
|
+
genrePlaylist: hostPlaylist ?? (detail.criteriaPlaylist === null ? null : scope === "reference" ? { ...import_genre_tree19.GenrePlaylistSummarySchema.parse(detail.criteriaPlaylist), criteria: import_genre_tree19.CriteriaMinimumSchema.parse(detail) } : { ...detail.criteriaPlaylist, criteria: import_genre_tree19.CriteriaMinimumSchema.parse(detail), tracksCount: detail.tracksCount }),
|
|
1798
|
+
catalogue: detail.catalogue,
|
|
1799
|
+
countsContext: !hostPlaylist && extras?.artist ? "all artists" : void 0,
|
|
1800
|
+
wikidataId: detail.wikidataId,
|
|
1801
|
+
scope,
|
|
1802
|
+
getBackendBaseUrl,
|
|
1803
|
+
playback,
|
|
1804
|
+
band: { node: view.node, ...band, shareTarget: band?.shareTarget?.kind === "genre" ? { ...band.shareTarget, name: detail.name } : band?.shareTarget },
|
|
1805
|
+
extras
|
|
1806
|
+
}
|
|
1807
|
+
),
|
|
1808
|
+
trailing
|
|
1809
|
+
] })
|
|
1810
|
+
};
|
|
1811
|
+
}
|
|
1812
|
+
function GenreDetailPanel({
|
|
1813
|
+
genreUuid,
|
|
1814
|
+
scope,
|
|
1815
|
+
getBackendBaseUrl,
|
|
1816
|
+
onClose,
|
|
1817
|
+
onSelectGenre,
|
|
1818
|
+
onZoomIn,
|
|
1819
|
+
shareUrl,
|
|
1820
|
+
shareTarget,
|
|
1821
|
+
collapsed,
|
|
1822
|
+
expanded,
|
|
1823
|
+
onExpandedChange,
|
|
1824
|
+
edgeTab,
|
|
1825
|
+
extras,
|
|
1826
|
+
headerClassName
|
|
1827
|
+
}) {
|
|
1828
|
+
const panel = useGenreDetailContent({
|
|
1829
|
+
genreUuid,
|
|
1830
|
+
scope,
|
|
1831
|
+
getBackendBaseUrl,
|
|
1832
|
+
onSelectGenre,
|
|
1833
|
+
extras,
|
|
1834
|
+
expanded,
|
|
1835
|
+
band: { onZoomIn, shareUrl, shareTarget: shareUrl ? shareTarget : void 0 }
|
|
1836
|
+
});
|
|
1837
|
+
return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
|
|
1838
|
+
import_ui7.DetailPanel,
|
|
1839
|
+
{
|
|
1840
|
+
side: "left",
|
|
1841
|
+
title: panel.title,
|
|
1842
|
+
headerAccent: panel.headerAccent,
|
|
1843
|
+
onClose,
|
|
1844
|
+
collapsed,
|
|
1845
|
+
expanded,
|
|
1846
|
+
onExpandedChange,
|
|
1847
|
+
edgeTab,
|
|
1848
|
+
aside: panel.aside,
|
|
1849
|
+
headerClassName,
|
|
1850
|
+
headerActions: onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_ui7.IconButton, { "aria-label": expanded ? "Shrink panel" : "Extend panel", onClick: () => onExpandedChange(!expanded), children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react8.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react8.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" }) }) : void 0,
|
|
1851
|
+
children: panel.content
|
|
1852
|
+
}
|
|
1853
|
+
);
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1646
1856
|
// src/genre-tree/ChangeParentPopup.tsx
|
|
1647
|
-
var
|
|
1648
|
-
var
|
|
1649
|
-
var
|
|
1857
|
+
var import_react15 = require("react");
|
|
1858
|
+
var import_genre_tree20 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1859
|
+
var import_jsx_runtime27 = require("react/jsx-runtime");
|
|
1650
1860
|
function ChangeParentPopup({
|
|
1651
1861
|
genrePlaylist,
|
|
1652
1862
|
genrePlaylists,
|
|
@@ -1655,9 +1865,9 @@ function ChangeParentPopup({
|
|
|
1655
1865
|
onClose,
|
|
1656
1866
|
onParentChanged
|
|
1657
1867
|
}) {
|
|
1658
|
-
const { mutate, isPending, formErrors } = (0,
|
|
1659
|
-
const candidates = (0,
|
|
1660
|
-
() => (0,
|
|
1868
|
+
const { mutate, isPending, formErrors } = (0, import_genre_tree20.useUpdateGenre)(scope, getBackendBaseUrl);
|
|
1869
|
+
const candidates = (0, import_react15.useMemo)(
|
|
1870
|
+
() => (0, import_genre_tree20.getReparentCandidates)(genrePlaylists, genrePlaylist.uuid),
|
|
1661
1871
|
[genrePlaylists, genrePlaylist.uuid]
|
|
1662
1872
|
);
|
|
1663
1873
|
const handleSelect = (newParent) => {
|
|
@@ -1674,19 +1884,19 @@ function ChangeParentPopup({
|
|
|
1674
1884
|
}
|
|
1675
1885
|
);
|
|
1676
1886
|
};
|
|
1677
|
-
(0,
|
|
1887
|
+
(0, import_react15.useEffect)(() => {
|
|
1678
1888
|
const handleKeyDown = (event) => {
|
|
1679
1889
|
if (event.key === "Escape") onClose();
|
|
1680
1890
|
};
|
|
1681
1891
|
window.addEventListener("keydown", handleKeyDown);
|
|
1682
1892
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
1683
1893
|
}, [onClose]);
|
|
1684
|
-
return /* @__PURE__ */ (0,
|
|
1685
|
-
/* @__PURE__ */ (0,
|
|
1894
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
|
|
1895
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("p", { className: "mb-3 text-sm text-[var(--color-text-muted)]", children: [
|
|
1686
1896
|
"Current parent: ",
|
|
1687
1897
|
genrePlaylist.parent?.name ?? "none"
|
|
1688
1898
|
] }),
|
|
1689
|
-
/* @__PURE__ */ (0,
|
|
1899
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
1690
1900
|
GenreSearch,
|
|
1691
1901
|
{
|
|
1692
1902
|
genrePlaylists: candidates,
|
|
@@ -1694,24 +1904,24 @@ function ChangeParentPopup({
|
|
|
1694
1904
|
placeholder: "Search a new parent\u2026"
|
|
1695
1905
|
}
|
|
1696
1906
|
) }),
|
|
1697
|
-
formErrors.length > 0 && /* @__PURE__ */ (0,
|
|
1907
|
+
formErrors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { role: "alert", className: "mt-3 text-sm text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
|
|
1698
1908
|
] });
|
|
1699
1909
|
}
|
|
1700
1910
|
|
|
1701
1911
|
// src/genre-tree/artist/useArtistPanel.tsx
|
|
1702
|
-
var
|
|
1703
|
-
var
|
|
1912
|
+
var import_ui13 = require("@behindthemusictree/ui");
|
|
1913
|
+
var import_genre_tree25 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1704
1914
|
|
|
1705
1915
|
// src/genre-tree/artist/ArtistHeader.tsx
|
|
1706
|
-
var
|
|
1916
|
+
var import_ui9 = require("@behindthemusictree/ui");
|
|
1707
1917
|
|
|
1708
1918
|
// src/genre-tree/artist/ArtistLinks.tsx
|
|
1709
|
-
var
|
|
1919
|
+
var import_lucide_react9 = require("lucide-react");
|
|
1710
1920
|
var import_si3 = require("react-icons/si");
|
|
1711
|
-
var
|
|
1712
|
-
var
|
|
1921
|
+
var import_ui8 = require("@behindthemusictree/ui");
|
|
1922
|
+
var import_jsx_runtime28 = require("react/jsx-runtime");
|
|
1713
1923
|
var LINK_KINDS = {
|
|
1714
|
-
official: { label: "Official website", Icon:
|
|
1924
|
+
official: { label: "Official website", Icon: import_lucide_react9.Globe },
|
|
1715
1925
|
wikipedia: { label: "Wikipedia", Icon: import_si3.SiWikipedia },
|
|
1716
1926
|
wikidata: { label: "Wikidata", Icon: import_si3.SiWikidata },
|
|
1717
1927
|
spotify: { label: "Spotify", Icon: import_si3.SiSpotify },
|
|
@@ -1719,7 +1929,7 @@ var LINK_KINDS = {
|
|
|
1719
1929
|
bandcamp: { label: "Bandcamp", Icon: import_si3.SiBandcamp },
|
|
1720
1930
|
soundcloud: { label: "SoundCloud", Icon: import_si3.SiSoundcloud },
|
|
1721
1931
|
discogs: { label: "Discogs", Icon: import_si3.SiDiscogs },
|
|
1722
|
-
social: { label: "Social", Icon:
|
|
1932
|
+
social: { label: "Social", Icon: import_lucide_react9.Share2 }
|
|
1723
1933
|
};
|
|
1724
1934
|
function socialLabel(url) {
|
|
1725
1935
|
try {
|
|
@@ -1746,7 +1956,7 @@ function artistLinks(artist) {
|
|
|
1746
1956
|
return links;
|
|
1747
1957
|
}
|
|
1748
1958
|
function ArtistLinks({ links }) {
|
|
1749
|
-
return /* @__PURE__ */ (0,
|
|
1959
|
+
return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_ui8.Chip, { href: url, icon: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
|
|
1750
1960
|
}
|
|
1751
1961
|
|
|
1752
1962
|
// src/genre-tree/artist/formatYears.ts
|
|
@@ -1757,7 +1967,7 @@ function formatYears(beginYear, endYear, ended) {
|
|
|
1757
1967
|
}
|
|
1758
1968
|
|
|
1759
1969
|
// src/genre-tree/artist/ArtistHeader.tsx
|
|
1760
|
-
var
|
|
1970
|
+
var import_jsx_runtime29 = require("react/jsx-runtime");
|
|
1761
1971
|
var countryNames = new Intl.DisplayNames(["en"], { type: "region" });
|
|
1762
1972
|
function ArtistHeader({ artist }) {
|
|
1763
1973
|
const fields = [];
|
|
@@ -1767,35 +1977,35 @@ function ArtistHeader({ artist }) {
|
|
|
1767
1977
|
const years = formatYears(artist.beginYear, artist.endYear, artist.ended);
|
|
1768
1978
|
if (years) fields.push({ label: "Active", value: years });
|
|
1769
1979
|
const links = artistLinks(artist);
|
|
1770
|
-
if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0,
|
|
1980
|
+
if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(ArtistLinks, { links }) });
|
|
1771
1981
|
if (fields.length === 0) return null;
|
|
1772
|
-
return /* @__PURE__ */ (0,
|
|
1982
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui9.FieldList, { items: fields });
|
|
1773
1983
|
}
|
|
1774
1984
|
|
|
1775
1985
|
// src/genre-tree/artist/ArtistPhoto.tsx
|
|
1776
|
-
var
|
|
1986
|
+
var import_jsx_runtime30 = require("react/jsx-runtime");
|
|
1777
1987
|
function ExternalLink({ href, children }) {
|
|
1778
|
-
return /* @__PURE__ */ (0,
|
|
1988
|
+
return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
|
|
1779
1989
|
}
|
|
1780
1990
|
function ArtistPhoto({ image, name }) {
|
|
1781
|
-
return /* @__PURE__ */ (0,
|
|
1782
|
-
/* @__PURE__ */ (0,
|
|
1783
|
-
/* @__PURE__ */ (0,
|
|
1991
|
+
return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
|
|
1992
|
+
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
|
|
1993
|
+
/* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("figcaption", { className: "text-xs text-[var(--color-text-muted)]", children: [
|
|
1784
1994
|
"Photo: ",
|
|
1785
|
-
/* @__PURE__ */ (0,
|
|
1995
|
+
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
|
|
1786
1996
|
",",
|
|
1787
1997
|
" ",
|
|
1788
|
-
image.licenseUrl ? /* @__PURE__ */ (0,
|
|
1998
|
+
image.licenseUrl ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
|
|
1789
1999
|
] })
|
|
1790
2000
|
] });
|
|
1791
2001
|
}
|
|
1792
2002
|
|
|
1793
2003
|
// src/genre-tree/artist/ArtistMembers.tsx
|
|
1794
|
-
var
|
|
1795
|
-
var
|
|
2004
|
+
var import_ui10 = require("@behindthemusictree/ui");
|
|
2005
|
+
var import_jsx_runtime31 = require("react/jsx-runtime");
|
|
1796
2006
|
function MembershipList({ title, memberships, renderArtistLink }) {
|
|
1797
2007
|
if (memberships.length === 0) return null;
|
|
1798
|
-
return /* @__PURE__ */ (0,
|
|
2008
|
+
return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_ui10.PanelGroup, { title, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("ul", { children: memberships.map((membership, index) => {
|
|
1799
2009
|
const details = [
|
|
1800
2010
|
formatYears(membership.beginYear, membership.endYear, membership.ended),
|
|
1801
2011
|
...membership.roles
|
|
@@ -1803,9 +2013,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
|
|
|
1803
2013
|
const detailsText = details.filter(Boolean).join(" \xB7 ");
|
|
1804
2014
|
return (
|
|
1805
2015
|
// A member can have several periods in the same group.
|
|
1806
|
-
/* @__PURE__ */ (0,
|
|
1807
|
-
/* @__PURE__ */ (0,
|
|
1808
|
-
detailsText && /* @__PURE__ */ (0,
|
|
2016
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
|
|
2017
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
|
|
2018
|
+
detailsText && /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
|
|
1809
2019
|
" \u2014 ",
|
|
1810
2020
|
detailsText
|
|
1811
2021
|
] })
|
|
@@ -1816,9 +2026,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
|
|
|
1816
2026
|
function ArtistMembers({ title, memberships, renderArtistLink }) {
|
|
1817
2027
|
if (memberships.length === 0) return null;
|
|
1818
2028
|
const isCurrent = (membership) => !membership.ended && membership.endYear === null;
|
|
1819
|
-
return /* @__PURE__ */ (0,
|
|
1820
|
-
/* @__PURE__ */ (0,
|
|
1821
|
-
/* @__PURE__ */ (0,
|
|
2029
|
+
return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_ui10.PanelSection, { title, children: [
|
|
2030
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
|
|
2031
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
|
|
1822
2032
|
MembershipList,
|
|
1823
2033
|
{
|
|
1824
2034
|
title: "Past",
|
|
@@ -1830,8 +2040,8 @@ function ArtistMembers({ title, memberships, renderArtistLink }) {
|
|
|
1830
2040
|
}
|
|
1831
2041
|
|
|
1832
2042
|
// src/genre-tree/artist/ArtistDiscography.tsx
|
|
1833
|
-
var
|
|
1834
|
-
var
|
|
2043
|
+
var import_ui11 = require("@behindthemusictree/ui");
|
|
2044
|
+
var import_jsx_runtime32 = require("react/jsx-runtime");
|
|
1835
2045
|
var GROUPS = ["Albums", "EPs", "Singles", "Live", "Compilations", "Other"];
|
|
1836
2046
|
var PRIMARY_GROUPS = { album: "Albums", ep: "EPs", single: "Singles" };
|
|
1837
2047
|
function discographyGroup({ primaryType, secondaryTypes }) {
|
|
@@ -1851,34 +2061,34 @@ function byYear(a, b) {
|
|
|
1851
2061
|
function ArtistDiscography({ albums }) {
|
|
1852
2062
|
if (albums.length === 0) return null;
|
|
1853
2063
|
const sorted = [...albums].sort(byYear);
|
|
1854
|
-
return /* @__PURE__ */ (0,
|
|
2064
|
+
return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui11.PanelSection, { title: "Discography", children: GROUPS.map((group) => {
|
|
1855
2065
|
const groupAlbums = sorted.filter((album) => discographyGroup(album) === group);
|
|
1856
2066
|
if (groupAlbums.length === 0) return null;
|
|
1857
|
-
return /* @__PURE__ */ (0,
|
|
1858
|
-
/* @__PURE__ */ (0,
|
|
1859
|
-
/* @__PURE__ */ (0,
|
|
2067
|
+
return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui11.PanelGroup, { title: group, children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
|
|
2068
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
|
|
2069
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
|
|
1860
2070
|
] }, album.uuid)) }) }, group);
|
|
1861
2071
|
}) });
|
|
1862
2072
|
}
|
|
1863
2073
|
|
|
1864
2074
|
// src/genre-tree/artist/ArtistSongs.tsx
|
|
1865
|
-
var
|
|
2075
|
+
var import_react16 = require("react");
|
|
1866
2076
|
var import_react_query2 = require("@tanstack/react-query");
|
|
1867
|
-
var
|
|
1868
|
-
var
|
|
2077
|
+
var import_lucide_react10 = require("lucide-react");
|
|
2078
|
+
var import_ui12 = require("@behindthemusictree/ui");
|
|
1869
2079
|
var import_player10 = require("@behindthemusictree/app-kit/player");
|
|
1870
|
-
var
|
|
2080
|
+
var import_genre_tree24 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1871
2081
|
var import_transport7 = require("@behindthemusictree/app-kit/transport");
|
|
1872
2082
|
|
|
1873
2083
|
// src/genre-tree/track-list-sidebar/TrackItem.tsx
|
|
1874
2084
|
var import_player9 = require("@behindthemusictree/app-kit/player");
|
|
1875
|
-
var
|
|
2085
|
+
var import_genre_tree21 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1876
2086
|
|
|
1877
2087
|
// src/genre-tree/TrackPositionPlayPause.tsx
|
|
1878
2088
|
var import_fa3 = require("react-icons/fa");
|
|
1879
2089
|
var import_player7 = require("@behindthemusictree/app-kit/player");
|
|
1880
2090
|
var import_player8 = require("@behindthemusictree/app-kit/player");
|
|
1881
|
-
var
|
|
2091
|
+
var import_jsx_runtime33 = require("react/jsx-runtime");
|
|
1882
2092
|
function TrackPositionPlayPause({
|
|
1883
2093
|
position,
|
|
1884
2094
|
uuid,
|
|
@@ -1892,14 +2102,14 @@ function TrackPositionPlayPause({
|
|
|
1892
2102
|
const isPlaying = isThisTrack && isConfirmedPlayable && playState === import_player8.PlayStates.PLAYING;
|
|
1893
2103
|
const isPaused = isThisTrack && isConfirmedPlayable && playState === import_player8.PlayStates.PAUSED;
|
|
1894
2104
|
const isCurrentTrackActive = isPlaying || isPaused;
|
|
1895
|
-
return /* @__PURE__ */ (0,
|
|
2105
|
+
return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
|
|
1896
2106
|
"div",
|
|
1897
2107
|
{
|
|
1898
2108
|
className: "track-position-play-pause relative flex items-center justify-center gap-2 text-lg w-16 cursor-pointer",
|
|
1899
2109
|
style: { minWidth: "4rem", maxWidth: "4rem" },
|
|
1900
2110
|
onClick: handlePlayPauseClick,
|
|
1901
2111
|
children: [
|
|
1902
|
-
/* @__PURE__ */ (0,
|
|
2112
|
+
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
|
|
1903
2113
|
"span",
|
|
1904
2114
|
{
|
|
1905
2115
|
className: "track-position-play-pause__bars",
|
|
@@ -1907,18 +2117,18 @@ function TrackPositionPlayPause({
|
|
|
1907
2117
|
role: "img",
|
|
1908
2118
|
"aria-label": isPlaying ? "Playing" : "Paused",
|
|
1909
2119
|
children: [
|
|
1910
|
-
/* @__PURE__ */ (0,
|
|
1911
|
-
/* @__PURE__ */ (0,
|
|
1912
|
-
/* @__PURE__ */ (0,
|
|
2120
|
+
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "track-position-play-pause__bar" }),
|
|
2121
|
+
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "track-position-play-pause__bar" }),
|
|
2122
|
+
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "track-position-play-pause__bar" })
|
|
1913
2123
|
]
|
|
1914
2124
|
}
|
|
1915
2125
|
) : position }),
|
|
1916
|
-
/* @__PURE__ */ (0,
|
|
2126
|
+
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
|
|
1917
2127
|
"div",
|
|
1918
2128
|
{
|
|
1919
2129
|
className: `hidden group-hover:flex items-center justify-center ${isCurrentTrackActive ? "" : "absolute inset-0"}`,
|
|
1920
2130
|
"aria-hidden": "true",
|
|
1921
|
-
children: isAwaitingPlayback ? /* @__PURE__ */ (0,
|
|
2131
|
+
children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_fa3.FaPause, {}) : /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_fa3.FaPlay, {})
|
|
1922
2132
|
}
|
|
1923
2133
|
)
|
|
1924
2134
|
]
|
|
@@ -1927,9 +2137,9 @@ function TrackPositionPlayPause({
|
|
|
1927
2137
|
}
|
|
1928
2138
|
|
|
1929
2139
|
// src/genre-tree/track-list-sidebar/TrackItem.tsx
|
|
1930
|
-
var import_genre_tree21 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1931
2140
|
var import_genre_tree22 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1932
|
-
var
|
|
2141
|
+
var import_genre_tree23 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2142
|
+
var import_jsx_runtime34 = require("react/jsx-runtime");
|
|
1933
2143
|
function TrackItem({
|
|
1934
2144
|
track,
|
|
1935
2145
|
position,
|
|
@@ -1939,11 +2149,11 @@ function TrackItem({
|
|
|
1939
2149
|
renderSongLink
|
|
1940
2150
|
}) {
|
|
1941
2151
|
const { handlePlayPauseAction, playerTrackObject, loadTrackForPlayer } = (0, import_player9.usePlayer)();
|
|
1942
|
-
const { trackList, selectedTrack, toTrackAtPosition, runtimeUnplayableTrackIds } = (0,
|
|
2152
|
+
const { trackList, selectedTrack, toTrackAtPosition, runtimeUnplayableTrackIds } = (0, import_genre_tree21.useTrackList)();
|
|
1943
2153
|
const scope = trackList?.origin?.scope ?? null;
|
|
1944
2154
|
const isCurrentTrack = playerTrackObject?.track.id === track.uuid;
|
|
1945
2155
|
const hasPlaybackError = isCurrentTrack && !!playerTrackObject?.loadError;
|
|
1946
|
-
const isUnplayable = hasPlaybackError || runtimeUnplayableTrackIds?.has(track.uuid) === true || (0,
|
|
2156
|
+
const isUnplayable = hasPlaybackError || runtimeUnplayableTrackIds?.has(track.uuid) === true || (0, import_genre_tree22.isSong)(track) && (0, import_genre_tree22.songUnplayableReason)(track) !== null;
|
|
1947
2157
|
const handlePlayPauseClick = (event) => {
|
|
1948
2158
|
event.stopPropagation();
|
|
1949
2159
|
if (isCurrentTrack) {
|
|
@@ -1953,12 +2163,12 @@ function TrackItem({
|
|
|
1953
2163
|
loadTrackForPlayer(track.uuid);
|
|
1954
2164
|
}
|
|
1955
2165
|
};
|
|
1956
|
-
return /* @__PURE__ */ (0,
|
|
2166
|
+
return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
|
|
1957
2167
|
"div",
|
|
1958
2168
|
{
|
|
1959
2169
|
className: `track-item flex h-14 text-[var(--color-text)] ${isUnplayable ? "opacity-50" : "hover:bg-[var(--color-surface-muted)] group"}`,
|
|
1960
2170
|
children: [
|
|
1961
|
-
isUnplayable ? /* @__PURE__ */ (0,
|
|
2171
|
+
isUnplayable ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
1962
2172
|
"div",
|
|
1963
2173
|
{
|
|
1964
2174
|
className: "flex items-center justify-center text-lg w-16",
|
|
@@ -1966,7 +2176,7 @@ function TrackItem({
|
|
|
1966
2176
|
"aria-disabled": "true",
|
|
1967
2177
|
children: position
|
|
1968
2178
|
}
|
|
1969
|
-
) : /* @__PURE__ */ (0,
|
|
2179
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
1970
2180
|
TrackPositionPlayPause,
|
|
1971
2181
|
{
|
|
1972
2182
|
position,
|
|
@@ -1975,12 +2185,12 @@ function TrackItem({
|
|
|
1975
2185
|
isSelected: selectedTrack?.uuid === track.uuid
|
|
1976
2186
|
}
|
|
1977
2187
|
),
|
|
1978
|
-
/* @__PURE__ */ (0,
|
|
1979
|
-
/* @__PURE__ */ (0,
|
|
1980
|
-
track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0,
|
|
2188
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
|
|
2189
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree22.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
|
|
2190
|
+
track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_genre_tree23.ArtistNames, { artists: track.artists, renderArtistLink }) }) : ""
|
|
1981
2191
|
] }),
|
|
1982
|
-
/* @__PURE__ */ (0,
|
|
1983
|
-
renderDuration && /* @__PURE__ */ (0,
|
|
2192
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.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_runtime34.jsx)("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
|
|
2193
|
+
renderDuration && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
1984
2194
|
"div",
|
|
1985
2195
|
{
|
|
1986
2196
|
className: "duration flex text-base w-16 items-center justify-center",
|
|
@@ -1988,28 +2198,28 @@ function TrackItem({
|
|
|
1988
2198
|
children: renderDuration(track)
|
|
1989
2199
|
}
|
|
1990
2200
|
),
|
|
1991
|
-
renderActions && /* @__PURE__ */ (0,
|
|
2201
|
+
renderActions && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
|
|
1992
2202
|
]
|
|
1993
2203
|
}
|
|
1994
2204
|
);
|
|
1995
2205
|
}
|
|
1996
2206
|
|
|
1997
2207
|
// src/genre-tree/artist/ArtistSongs.tsx
|
|
1998
|
-
var
|
|
1999
|
-
var SongPageSchema2 = (0, import_transport7.PaginatedResponseSchema)(
|
|
2208
|
+
var import_jsx_runtime35 = require("react/jsx-runtime");
|
|
2209
|
+
var SongPageSchema2 = (0, import_transport7.PaginatedResponseSchema)(import_genre_tree24.SongDetailedSchema);
|
|
2000
2210
|
var SORTS = [
|
|
2001
2211
|
{ ordering: "title", label: "Title" },
|
|
2002
2212
|
{ ordering: "-albumYear", label: "Year" }
|
|
2003
2213
|
];
|
|
2004
2214
|
function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLink }) {
|
|
2005
|
-
const [page, setPage] = (0,
|
|
2006
|
-
const [ordering, setOrdering] = (0,
|
|
2215
|
+
const [page, setPage] = (0, import_react16.useState)(1);
|
|
2216
|
+
const [ordering, setOrdering] = (0, import_react16.useState)("title");
|
|
2007
2217
|
const { fetch } = (0, import_transport7.useFetchWrapper)(getBackendBaseUrl);
|
|
2008
|
-
const { playNewTrackListFromArtist } = (0,
|
|
2218
|
+
const { playNewTrackListFromArtist } = (0, import_genre_tree24.useTrackList)();
|
|
2009
2219
|
const { playerTrackObject } = (0, import_player10.usePlayer)();
|
|
2010
2220
|
const { data, isPending, isError } = (0, import_transport7.useQueryWithParse)({
|
|
2011
2221
|
queryKey: ["referenceSongs", "artist", artist.uuid, ordering, page],
|
|
2012
|
-
queryFn: () => fetch(
|
|
2222
|
+
queryFn: () => fetch(import_genre_tree24.libraryEndpoints.reference.songs.list(), true, false, void 0, {
|
|
2013
2223
|
artist: artist.uuid,
|
|
2014
2224
|
ordering,
|
|
2015
2225
|
page
|
|
@@ -2024,25 +2234,25 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
|
|
|
2024
2234
|
});
|
|
2025
2235
|
const handleRowClickCapture = (event, song) => {
|
|
2026
2236
|
if (!(event.target instanceof Element) || !event.target.closest(".track-position-play-pause")) return;
|
|
2027
|
-
if (playerTrackObject?.track.id === song.uuid || (0,
|
|
2237
|
+
if (playerTrackObject?.track.id === song.uuid || (0, import_genre_tree24.songUnplayableReason)(song) !== null) return;
|
|
2028
2238
|
event.stopPropagation();
|
|
2029
2239
|
void play(song.uuid);
|
|
2030
2240
|
};
|
|
2031
|
-
return /* @__PURE__ */ (0,
|
|
2032
|
-
/* @__PURE__ */ (0,
|
|
2033
|
-
/* @__PURE__ */ (0,
|
|
2241
|
+
return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui12.PanelSection, { title: "Songs", children: isPending ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui12.Skeleton, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
|
|
2242
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
|
|
2243
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
|
|
2034
2244
|
"button",
|
|
2035
2245
|
{
|
|
2036
2246
|
type: "button",
|
|
2037
2247
|
onClick: () => void play(),
|
|
2038
2248
|
className: "flex w-fit items-center gap-1.5 px-3 py-1 text-xs font-medium bg-gray-900 text-white rounded-full hover:bg-gray-700",
|
|
2039
2249
|
children: [
|
|
2040
|
-
/* @__PURE__ */ (0,
|
|
2250
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react10.Play, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
|
|
2041
2251
|
"Play all"
|
|
2042
2252
|
]
|
|
2043
2253
|
}
|
|
2044
2254
|
),
|
|
2045
|
-
/* @__PURE__ */ (0,
|
|
2255
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2046
2256
|
"button",
|
|
2047
2257
|
{
|
|
2048
2258
|
type: "button",
|
|
@@ -2057,7 +2267,7 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
|
|
|
2057
2267
|
sort.ordering
|
|
2058
2268
|
)) })
|
|
2059
2269
|
] }),
|
|
2060
|
-
/* @__PURE__ */ (0,
|
|
2270
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2061
2271
|
TrackItem,
|
|
2062
2272
|
{
|
|
2063
2273
|
track: song,
|
|
@@ -2066,12 +2276,12 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
|
|
|
2066
2276
|
renderSongLink
|
|
2067
2277
|
}
|
|
2068
2278
|
) }, song.uuid)) }),
|
|
2069
|
-
/* @__PURE__ */ (0,
|
|
2279
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui12.Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
|
|
2070
2280
|
] }) });
|
|
2071
2281
|
}
|
|
2072
2282
|
|
|
2073
2283
|
// src/genre-tree/artist/useArtistPanel.tsx
|
|
2074
|
-
var
|
|
2284
|
+
var import_jsx_runtime36 = require("react/jsx-runtime");
|
|
2075
2285
|
function useArtistPanel({
|
|
2076
2286
|
uuid,
|
|
2077
2287
|
scope,
|
|
@@ -2083,7 +2293,7 @@ function useArtistPanel({
|
|
|
2083
2293
|
onExpandedChange,
|
|
2084
2294
|
onClose
|
|
2085
2295
|
}) {
|
|
2086
|
-
const result = (0,
|
|
2296
|
+
const result = (0, import_genre_tree25.useArtist)(uuid, getBackendBaseUrl);
|
|
2087
2297
|
const panel = { expanded, onExpandedChange, onClose };
|
|
2088
2298
|
switch (result.status) {
|
|
2089
2299
|
case "idle":
|
|
@@ -2092,26 +2302,26 @@ function useArtistPanel({
|
|
|
2092
2302
|
return {
|
|
2093
2303
|
...panel,
|
|
2094
2304
|
title: "Artist",
|
|
2095
|
-
content: /* @__PURE__ */ (0,
|
|
2096
|
-
/* @__PURE__ */ (0,
|
|
2097
|
-
/* @__PURE__ */ (0,
|
|
2098
|
-
/* @__PURE__ */ (0,
|
|
2099
|
-
/* @__PURE__ */ (0,
|
|
2100
|
-
/* @__PURE__ */ (0,
|
|
2101
|
-
/* @__PURE__ */ (0,
|
|
2305
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
|
|
2306
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.Skeleton, { className: "h-3 w-16" }),
|
|
2307
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.Skeleton, { className: "h-4 w-32" }),
|
|
2308
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.Skeleton, { className: "h-3 w-16" }),
|
|
2309
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.Skeleton, { className: "h-4 w-24" }),
|
|
2310
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.Skeleton, { className: "h-16 mt-2.5" }),
|
|
2311
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.Skeleton, { className: "h-16" })
|
|
2102
2312
|
] })
|
|
2103
2313
|
};
|
|
2104
2314
|
case "not-found":
|
|
2105
2315
|
return {
|
|
2106
2316
|
...panel,
|
|
2107
2317
|
title: "Artist not found",
|
|
2108
|
-
content: /* @__PURE__ */ (0,
|
|
2318
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This artist does not exist." })
|
|
2109
2319
|
};
|
|
2110
2320
|
case "error":
|
|
2111
2321
|
return {
|
|
2112
2322
|
...panel,
|
|
2113
2323
|
title: "Artist",
|
|
2114
|
-
content: /* @__PURE__ */ (0,
|
|
2324
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
|
|
2115
2325
|
};
|
|
2116
2326
|
case "success": {
|
|
2117
2327
|
const { artist } = result;
|
|
@@ -2120,13 +2330,13 @@ function useArtistPanel({
|
|
|
2120
2330
|
return {
|
|
2121
2331
|
...panel,
|
|
2122
2332
|
title: artist.name,
|
|
2123
|
-
content: /* @__PURE__ */ (0,
|
|
2124
|
-
shareUrl && /* @__PURE__ */ (0,
|
|
2125
|
-
artist.image && /* @__PURE__ */ (0,
|
|
2126
|
-
/* @__PURE__ */ (0,
|
|
2127
|
-
/* @__PURE__ */ (0,
|
|
2128
|
-
/* @__PURE__ */ (0,
|
|
2129
|
-
artist.tracksCount > 0 && /* @__PURE__ */ (0,
|
|
2333
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_jsx_runtime36.Fragment, { children: [
|
|
2334
|
+
shareUrl && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: shareTarget && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl }) }) }),
|
|
2335
|
+
artist.image && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ArtistPhoto, { image: artist.image, name: artist.name }),
|
|
2336
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ArtistHeader, { artist }),
|
|
2337
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
|
|
2338
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
|
|
2339
|
+
artist.tracksCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
|
|
2130
2340
|
ArtistSongs,
|
|
2131
2341
|
{
|
|
2132
2342
|
artist: { uuid: artist.uuid, name: artist.name },
|
|
@@ -2136,56 +2346,56 @@ function useArtistPanel({
|
|
|
2136
2346
|
}
|
|
2137
2347
|
)
|
|
2138
2348
|
] }),
|
|
2139
|
-
extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0,
|
|
2349
|
+
extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ArtistDiscography, { albums: artist.albums }) : void 0
|
|
2140
2350
|
};
|
|
2141
2351
|
}
|
|
2142
2352
|
}
|
|
2143
2353
|
}
|
|
2144
2354
|
|
|
2145
2355
|
// src/genre-tree/song/useSongPanel.tsx
|
|
2146
|
-
var
|
|
2147
|
-
var
|
|
2356
|
+
var import_ui15 = require("@behindthemusictree/ui");
|
|
2357
|
+
var import_genre_tree27 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2148
2358
|
|
|
2149
2359
|
// src/genre-tree/song/SongPanelContent.tsx
|
|
2150
|
-
var
|
|
2151
|
-
var
|
|
2152
|
-
var
|
|
2360
|
+
var import_react17 = require("react");
|
|
2361
|
+
var import_lucide_react11 = require("lucide-react");
|
|
2362
|
+
var import_ui14 = require("@behindthemusictree/ui");
|
|
2153
2363
|
var import_player11 = require("@behindthemusictree/app-kit/player");
|
|
2154
|
-
var
|
|
2155
|
-
var
|
|
2364
|
+
var import_genre_tree26 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2365
|
+
var import_jsx_runtime37 = require("react/jsx-runtime");
|
|
2156
2366
|
var numberFormat = new Intl.NumberFormat("en");
|
|
2157
2367
|
var dateFormat = new Intl.DateTimeFormat("en", { dateStyle: "medium" });
|
|
2158
2368
|
var externalLink = "text-[var(--color-primary)] hover:underline focus-visible:underline";
|
|
2159
2369
|
function SongHistory({ uuid, getBackendBaseUrl }) {
|
|
2160
|
-
const [open, setOpen] = (0,
|
|
2161
|
-
const history = (0,
|
|
2370
|
+
const [open, setOpen] = (0, import_react17.useState)(false);
|
|
2371
|
+
const history = (0, import_genre_tree26.useSongHistory)(uuid, getBackendBaseUrl, open);
|
|
2162
2372
|
let body = null;
|
|
2163
|
-
if (history.status === "loading") body = /* @__PURE__ */ (0,
|
|
2164
|
-
else if (history.status === "error") body = /* @__PURE__ */ (0,
|
|
2373
|
+
if (history.status === "loading") body = /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui14.Skeleton, { className: "h-10", "aria-busy": "true" });
|
|
2374
|
+
else if (history.status === "error") body = /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "text-red-600", children: "Could not load the history." });
|
|
2165
2375
|
else if (history.status === "success")
|
|
2166
|
-
body = history.entries.length === 0 ? /* @__PURE__ */ (0,
|
|
2167
|
-
/* @__PURE__ */ (0,
|
|
2168
|
-
entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ (0,
|
|
2376
|
+
body = history.entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("li", { className: "py-1.5 text-sm", children: [
|
|
2377
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "font-medium", children: entry.action }),
|
|
2378
|
+
entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
|
|
2169
2379
|
" ",
|
|
2170
2380
|
"\u2014 ",
|
|
2171
2381
|
entry.oldValue ?? "none",
|
|
2172
2382
|
" \u2192 ",
|
|
2173
2383
|
entry.newValue ?? "none"
|
|
2174
2384
|
] }) : null,
|
|
2175
|
-
/* @__PURE__ */ (0,
|
|
2385
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { className: "block text-xs text-[var(--color-text-muted)]", children: [
|
|
2176
2386
|
entry.actorPseudo ?? "Anonymous",
|
|
2177
2387
|
" \xB7 ",
|
|
2178
2388
|
dateFormat.format(new Date(entry.createdOn))
|
|
2179
2389
|
] })
|
|
2180
2390
|
] }, entry.uuid)) });
|
|
2181
|
-
return /* @__PURE__ */ (0,
|
|
2391
|
+
return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
|
|
2182
2392
|
"details",
|
|
2183
2393
|
{
|
|
2184
2394
|
onToggle: (event) => setOpen(event.currentTarget.open),
|
|
2185
2395
|
className: "px-6 pb-2.5 mt-2.5 border-t border-[var(--color-border)]",
|
|
2186
2396
|
children: [
|
|
2187
|
-
/* @__PURE__ */ (0,
|
|
2188
|
-
/* @__PURE__ */ (0,
|
|
2397
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)("summary", { className: "mt-2.5 cursor-pointer text-[0.6875rem] font-semibold uppercase text-[var(--color-text-muted)]", children: "History" }),
|
|
2398
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "mt-1.5", children: body })
|
|
2189
2399
|
]
|
|
2190
2400
|
}
|
|
2191
2401
|
);
|
|
@@ -2199,25 +2409,25 @@ function SongPanelContent({
|
|
|
2199
2409
|
genrePath,
|
|
2200
2410
|
onSelectGenre
|
|
2201
2411
|
}) {
|
|
2202
|
-
const { playNewTrackListFromTrackUuid } = (0,
|
|
2412
|
+
const { playNewTrackListFromTrackUuid } = (0, import_genre_tree26.useTrackList)();
|
|
2203
2413
|
const { playerTrackObject, playState, handlePlayPauseAction } = (0, import_player11.usePlayer)();
|
|
2204
|
-
const unplayableReason = (0,
|
|
2414
|
+
const unplayableReason = (0, import_genre_tree26.songUnplayableReason)(song);
|
|
2205
2415
|
const isCurrent = playerTrackObject?.track.id === song.uuid;
|
|
2206
2416
|
const isPlaying = isCurrent && playState === import_player11.PlayStates.PLAYING;
|
|
2207
2417
|
const shareTarget = { kind: "song", uuid: song.uuid, name: song.title, scope };
|
|
2208
|
-
const musicBrainzUrl = (0,
|
|
2418
|
+
const musicBrainzUrl = (0, import_genre_tree26.musicBrainzRecordingUrl)(song);
|
|
2209
2419
|
const fields = [];
|
|
2210
2420
|
if (song.genre) {
|
|
2211
2421
|
const path = genrePath.length > 0 ? genrePath : null;
|
|
2212
2422
|
fields.push({
|
|
2213
2423
|
label: "Genre",
|
|
2214
|
-
value: path ? /* @__PURE__ */ (0,
|
|
2215
|
-
index > 0 && /* @__PURE__ */ (0,
|
|
2216
|
-
/* @__PURE__ */ (0,
|
|
2424
|
+
value: path ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { children: [
|
|
2425
|
+
index > 0 && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { "aria-hidden": "true", children: "\u203A " }),
|
|
2426
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)("button", { type: "button", onClick: () => onSelectGenre(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
|
|
2217
2427
|
] }, genrePlaylist.uuid)) }) : song.genre.name
|
|
2218
2428
|
});
|
|
2219
2429
|
} else {
|
|
2220
|
-
fields.push({ label: "Genre", value: /* @__PURE__ */ (0,
|
|
2430
|
+
fields.push({ label: "Genre", value: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
|
|
2221
2431
|
}
|
|
2222
2432
|
if (song.firstReleaseYear) fields.push({ label: "First released", value: song.firstReleaseYear });
|
|
2223
2433
|
const viewCount = song.youtubeVideo?.viewCount;
|
|
@@ -2227,11 +2437,11 @@ function SongPanelContent({
|
|
|
2227
2437
|
const links = [];
|
|
2228
2438
|
if (musicBrainzUrl)
|
|
2229
2439
|
links.push(
|
|
2230
|
-
/* @__PURE__ */ (0,
|
|
2440
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
|
|
2231
2441
|
);
|
|
2232
2442
|
if (song.youtubeVideo)
|
|
2233
2443
|
links.push(
|
|
2234
|
-
/* @__PURE__ */ (0,
|
|
2444
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
|
|
2235
2445
|
"a",
|
|
2236
2446
|
{
|
|
2237
2447
|
href: `https://www.youtube.com/watch?v=${encodeURIComponent(song.youtubeVideo.id)}`,
|
|
@@ -2246,33 +2456,33 @@ function SongPanelContent({
|
|
|
2246
2456
|
if (links.length > 0)
|
|
2247
2457
|
fields.push({
|
|
2248
2458
|
label: "Links",
|
|
2249
|
-
value: /* @__PURE__ */ (0,
|
|
2459
|
+
value: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "flex gap-3", children: links })
|
|
2250
2460
|
});
|
|
2251
|
-
return /* @__PURE__ */ (0,
|
|
2252
|
-
/* @__PURE__ */ (0,
|
|
2253
|
-
unplayableReason ? /* @__PURE__ */ (0,
|
|
2461
|
+
return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(import_jsx_runtime37.Fragment, { children: [
|
|
2462
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
|
|
2463
|
+
unplayableReason ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "flex items-center px-2.5 text-sm text-[var(--color-text-muted)]", children: unplayableReason }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
|
|
2254
2464
|
"button",
|
|
2255
2465
|
{
|
|
2256
2466
|
type: "button",
|
|
2257
2467
|
onClick: () => isCurrent ? handlePlayPauseAction() : playNewTrackListFromTrackUuid(song, scope),
|
|
2258
2468
|
className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none",
|
|
2259
2469
|
children: [
|
|
2260
|
-
/* @__PURE__ */ (0,
|
|
2470
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.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_runtime37.jsx)(import_lucide_react11.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react11.Play, { "aria-hidden": "true", className: "w-5 h-5" }) }),
|
|
2261
2471
|
isPlaying ? "Pause" : "Play"
|
|
2262
2472
|
]
|
|
2263
2473
|
}
|
|
2264
2474
|
),
|
|
2265
|
-
shareUrl && /* @__PURE__ */ (0,
|
|
2475
|
+
shareUrl && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
|
|
2266
2476
|
] }) }),
|
|
2267
|
-
song.artists && song.artists.length > 0 && /* @__PURE__ */ (0,
|
|
2268
|
-
/* @__PURE__ */ (0,
|
|
2269
|
-
song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ (0,
|
|
2270
|
-
/* @__PURE__ */ (0,
|
|
2477
|
+
song.artists && song.artists.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "px-6 pt-2.5 text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_genre_tree26.ArtistNames, { artists: song.artists, renderArtistLink }) }),
|
|
2478
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui14.FieldList, { items: fields }),
|
|
2479
|
+
song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui14.PanelSection, { title: "Essential in", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
|
|
2480
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
|
|
2271
2481
|
] });
|
|
2272
2482
|
}
|
|
2273
2483
|
|
|
2274
2484
|
// src/genre-tree/song/useSongPanel.tsx
|
|
2275
|
-
var
|
|
2485
|
+
var import_jsx_runtime38 = require("react/jsx-runtime");
|
|
2276
2486
|
function useSongPanel({
|
|
2277
2487
|
uuid,
|
|
2278
2488
|
scope,
|
|
@@ -2285,7 +2495,7 @@ function useSongPanel({
|
|
|
2285
2495
|
onExpandedChange,
|
|
2286
2496
|
onClose
|
|
2287
2497
|
}) {
|
|
2288
|
-
const result = (0,
|
|
2498
|
+
const result = (0, import_genre_tree27.useSong)(uuid, getBackendBaseUrl);
|
|
2289
2499
|
const panel = { expanded, onExpandedChange, onClose };
|
|
2290
2500
|
switch (result.status) {
|
|
2291
2501
|
case "idle":
|
|
@@ -2294,24 +2504,24 @@ function useSongPanel({
|
|
|
2294
2504
|
return {
|
|
2295
2505
|
...panel,
|
|
2296
2506
|
title: "Song",
|
|
2297
|
-
content: /* @__PURE__ */ (0,
|
|
2298
|
-
/* @__PURE__ */ (0,
|
|
2299
|
-
/* @__PURE__ */ (0,
|
|
2300
|
-
/* @__PURE__ */ (0,
|
|
2301
|
-
/* @__PURE__ */ (0,
|
|
2507
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
|
|
2508
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_ui15.Skeleton, { className: "h-3 w-16" }),
|
|
2509
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_ui15.Skeleton, { className: "h-4 w-32" }),
|
|
2510
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_ui15.Skeleton, { className: "h-3 w-16" }),
|
|
2511
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_ui15.Skeleton, { className: "h-4 w-24" })
|
|
2302
2512
|
] })
|
|
2303
2513
|
};
|
|
2304
2514
|
case "not-found":
|
|
2305
2515
|
return {
|
|
2306
2516
|
...panel,
|
|
2307
2517
|
title: "Song not found",
|
|
2308
|
-
content: /* @__PURE__ */ (0,
|
|
2518
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This song does not exist." })
|
|
2309
2519
|
};
|
|
2310
2520
|
case "error":
|
|
2311
2521
|
return {
|
|
2312
2522
|
...panel,
|
|
2313
2523
|
title: "Song",
|
|
2314
|
-
content: /* @__PURE__ */ (0,
|
|
2524
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this song." })
|
|
2315
2525
|
};
|
|
2316
2526
|
case "success": {
|
|
2317
2527
|
const { song } = result;
|
|
@@ -2319,7 +2529,7 @@ function useSongPanel({
|
|
|
2319
2529
|
return {
|
|
2320
2530
|
...panel,
|
|
2321
2531
|
title: song.title,
|
|
2322
|
-
content: /* @__PURE__ */ (0,
|
|
2532
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
|
|
2323
2533
|
SongPanelContent,
|
|
2324
2534
|
{
|
|
2325
2535
|
song,
|
|
@@ -2338,29 +2548,29 @@ function useSongPanel({
|
|
|
2338
2548
|
|
|
2339
2549
|
// src/genre-tree/SizeMetricToggle.tsx
|
|
2340
2550
|
var import_md = require("react-icons/md");
|
|
2341
|
-
var
|
|
2342
|
-
var
|
|
2551
|
+
var import_ui16 = require("@behindthemusictree/ui");
|
|
2552
|
+
var import_jsx_runtime39 = require("react/jsx-runtime");
|
|
2343
2553
|
function SizeMetricToggle({
|
|
2344
2554
|
value,
|
|
2345
2555
|
onChange
|
|
2346
2556
|
}) {
|
|
2347
|
-
return /* @__PURE__ */ (0,
|
|
2348
|
-
|
|
2557
|
+
return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2558
|
+
import_ui16.SegmentedControl,
|
|
2349
2559
|
{
|
|
2350
2560
|
variant: "floating",
|
|
2351
2561
|
"aria-label": "Size nodes by",
|
|
2352
2562
|
value,
|
|
2353
2563
|
onChange,
|
|
2354
2564
|
options: [
|
|
2355
|
-
{ value: "songs", label: /* @__PURE__ */ (0,
|
|
2356
|
-
{ value: "views", label: /* @__PURE__ */ (0,
|
|
2565
|
+
{ value: "songs", label: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_md.MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
|
|
2566
|
+
{ value: "views", label: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_md.MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
|
|
2357
2567
|
]
|
|
2358
2568
|
}
|
|
2359
2569
|
);
|
|
2360
2570
|
}
|
|
2361
2571
|
|
|
2362
2572
|
// src/genre-tree/GenreTreeView.tsx
|
|
2363
|
-
var
|
|
2573
|
+
var import_jsx_runtime40 = require("react/jsx-runtime");
|
|
2364
2574
|
var NO_POP_ROOT_TITLE = "This genre tree has no 'Mainstream Pop' root yet";
|
|
2365
2575
|
var REGIONS_CLASS = "[--gtv-center-left:var(--layout-center-left)] [--gtv-center-right:var(--layout-center-right)] [--gtv-center-top:var(--layout-band-top)]";
|
|
2366
2576
|
var noop = () => {
|
|
@@ -2373,8 +2583,8 @@ function GenreTreeView({
|
|
|
2373
2583
|
getBackendBaseUrl,
|
|
2374
2584
|
additionalActions,
|
|
2375
2585
|
viewMode: controlledViewMode,
|
|
2376
|
-
|
|
2377
|
-
|
|
2586
|
+
selectedGenreUuid: controlledSelectedGenreUuid,
|
|
2587
|
+
onSelectedGenreUuidChange,
|
|
2378
2588
|
defaultViewport,
|
|
2379
2589
|
viewportRestore,
|
|
2380
2590
|
onViewportChange,
|
|
@@ -2395,6 +2605,7 @@ function GenreTreeView({
|
|
|
2395
2605
|
onArtistExpandedChange,
|
|
2396
2606
|
getShareUrl,
|
|
2397
2607
|
extraSearchGroups = [],
|
|
2608
|
+
SearchGroups,
|
|
2398
2609
|
onSearchQueryChange,
|
|
2399
2610
|
onSearchFocus,
|
|
2400
2611
|
artistSearch = true,
|
|
@@ -2421,21 +2632,21 @@ function GenreTreeView({
|
|
|
2421
2632
|
placement = "default",
|
|
2422
2633
|
...focus
|
|
2423
2634
|
}) {
|
|
2424
|
-
const getShareUrlRef = (0,
|
|
2635
|
+
const getShareUrlRef = (0, import_react18.useRef)(getShareUrl);
|
|
2425
2636
|
getShareUrlRef.current = getShareUrl;
|
|
2426
|
-
const [internalArtistUuid, setInternalArtistUuid] = (0,
|
|
2637
|
+
const [internalArtistUuid, setInternalArtistUuid] = (0, import_react18.useState)(null);
|
|
2427
2638
|
const isArtistControlled = controlledArtistUuid !== void 0;
|
|
2428
2639
|
const artistUuid = isArtistControlled ? controlledArtistUuid : internalArtistUuid;
|
|
2429
|
-
const [internalSongUuid, setInternalSongUuid] = (0,
|
|
2640
|
+
const [internalSongUuid, setInternalSongUuid] = (0, import_react18.useState)(null);
|
|
2430
2641
|
const isSongControlled = controlledSongUuid !== void 0;
|
|
2431
2642
|
const songUuid = isSongControlled ? controlledSongUuid : internalSongUuid;
|
|
2432
2643
|
const changeSong = (uuid) => {
|
|
2433
2644
|
if (!isSongControlled) setInternalSongUuid(uuid);
|
|
2434
2645
|
onSongUuidChange?.(uuid);
|
|
2435
2646
|
};
|
|
2436
|
-
const [internalSizeMetric, setInternalSizeMetric] = (0,
|
|
2647
|
+
const [internalSizeMetric, setInternalSizeMetric] = (0, import_react18.useState)("songs");
|
|
2437
2648
|
const sizeMetric = controlledSizeMetric ?? internalSizeMetric;
|
|
2438
|
-
const deferredSizeMetric = (0,
|
|
2649
|
+
const deferredSizeMetric = (0, import_react18.useDeferredValue)(sizeMetric);
|
|
2439
2650
|
const changeSizeMetric = (metric) => {
|
|
2440
2651
|
if (controlledSizeMetric === void 0) setInternalSizeMetric(metric);
|
|
2441
2652
|
onSizeMetricChange?.(metric);
|
|
@@ -2450,6 +2661,8 @@ function GenreTreeView({
|
|
|
2450
2661
|
genrePlaylistByNodeId,
|
|
2451
2662
|
visibleGenrePlaylists,
|
|
2452
2663
|
groupedGenrePlaylistsByRoot,
|
|
2664
|
+
selectedGenreUuid,
|
|
2665
|
+
selectGenre,
|
|
2453
2666
|
selectedNodeId,
|
|
2454
2667
|
selectedGenrePlaylist,
|
|
2455
2668
|
selectNode,
|
|
@@ -2462,17 +2675,17 @@ function GenreTreeView({
|
|
|
2462
2675
|
allowWheelRotation: internalAllowWheelRotation,
|
|
2463
2676
|
toggleWheelRotation,
|
|
2464
2677
|
onNodeHover: handleNodeHover
|
|
2465
|
-
} = (0,
|
|
2678
|
+
} = (0, import_genre_tree28.useGenreTreeViewState)({
|
|
2466
2679
|
scope,
|
|
2467
2680
|
getBackendBaseUrl,
|
|
2468
2681
|
viewMode: controlledViewMode,
|
|
2469
|
-
|
|
2470
|
-
|
|
2682
|
+
selectedGenreUuid: controlledSelectedGenreUuid,
|
|
2683
|
+
onSelectedGenreUuidChange,
|
|
2471
2684
|
criteriaOverviewSchema,
|
|
2472
2685
|
artistUuid
|
|
2473
2686
|
});
|
|
2474
2687
|
const restoreRevision = viewportRestore?.revision;
|
|
2475
|
-
const [stackedRestoreOwner, setStackedRestoreOwner] = (0,
|
|
2688
|
+
const [stackedRestoreOwner, setStackedRestoreOwner] = (0, import_react18.useState)({ revision: restoreRevision, rootUuid: selectedGenrePlaylist?.root.uuid });
|
|
2476
2689
|
let restoreRootUuid = stackedRestoreOwner.rootUuid;
|
|
2477
2690
|
if (stackedRestoreOwner.revision !== restoreRevision) {
|
|
2478
2691
|
restoreRootUuid = selectedGenrePlaylist?.root.uuid;
|
|
@@ -2481,36 +2694,36 @@ function GenreTreeView({
|
|
|
2481
2694
|
restoreRootUuid = selectedGenrePlaylist.root.uuid;
|
|
2482
2695
|
setStackedRestoreOwner({ revision: restoreRevision, rootUuid: restoreRootUuid });
|
|
2483
2696
|
}
|
|
2484
|
-
const isGenreUpdating = (0, import_react_query3.useIsMutating)({ mutationKey:
|
|
2485
|
-
const { mutate: updateGenre } = (0,
|
|
2697
|
+
const isGenreUpdating = (0, import_react_query3.useIsMutating)({ mutationKey: import_genre_tree28.genreUpdateMutationKey }) > 0;
|
|
2698
|
+
const { mutate: updateGenre } = (0, import_genre_tree28.useUpdateGenre)(scope, getBackendBaseUrl);
|
|
2486
2699
|
const busy = deferredSizeMetric !== sizeMetric || isTreeFetching || isGenreUpdating;
|
|
2487
2700
|
const isArtistFiltered = artistUuid !== null;
|
|
2488
2701
|
const canSizeByViews = !isArtistFiltered && hasViewsCount(visibleGenrePlaylists);
|
|
2489
|
-
const artistResult = (0,
|
|
2702
|
+
const artistResult = (0, import_genre_tree28.useArtist)(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
|
|
2490
2703
|
const filterArtistName = artistResult.status === "success" ? artistResult.artist.name : null;
|
|
2491
|
-
const filterArtist = (0,
|
|
2704
|
+
const filterArtist = (0, import_react18.useMemo)(
|
|
2492
2705
|
() => artistUuid !== null && filterArtistName !== null ? { uuid: artistUuid, name: filterArtistName } : null,
|
|
2493
2706
|
[artistUuid, filterArtistName]
|
|
2494
2707
|
);
|
|
2495
2708
|
const hasSelectedGenreNode = selectedGenrePlaylist !== void 0 && visibleGenrePlaylists.some(({ uuid }) => uuid === selectedGenrePlaylist.uuid);
|
|
2496
2709
|
const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: visibleGenrePlaylists, artist: filterArtist });
|
|
2497
2710
|
const openArtist = (uuid) => {
|
|
2498
|
-
if (
|
|
2711
|
+
if (selectedGenreUuid !== null) clearSelection();
|
|
2499
2712
|
if (songUuid !== null) changeSong(null);
|
|
2500
2713
|
changeArtist(uuid);
|
|
2501
2714
|
};
|
|
2502
2715
|
const openSong = (uuid) => {
|
|
2503
|
-
if (
|
|
2716
|
+
if (selectedGenreUuid !== null) clearSelection();
|
|
2504
2717
|
changeSong(uuid);
|
|
2505
2718
|
};
|
|
2506
|
-
const openSongRef = (0,
|
|
2719
|
+
const openSongRef = (0, import_react18.useRef)(openSong);
|
|
2507
2720
|
openSongRef.current = openSong;
|
|
2508
|
-
const resolvedRenderSongLink = (0,
|
|
2509
|
-
(song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ (0,
|
|
2721
|
+
const resolvedRenderSongLink = (0, import_react18.useCallback)(
|
|
2722
|
+
(song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
|
|
2510
2723
|
[renderSongLink]
|
|
2511
2724
|
);
|
|
2512
|
-
const [expandedSongUuid, setExpandedSongUuid] = (0,
|
|
2513
|
-
const [expandedArtistUuid, setExpandedArtistUuid] = (0,
|
|
2725
|
+
const [expandedSongUuid, setExpandedSongUuid] = (0, import_react18.useState)(null);
|
|
2726
|
+
const [expandedArtistUuid, setExpandedArtistUuid] = (0, import_react18.useState)(null);
|
|
2514
2727
|
const artistBaseUrl = artistSearchBaseUrl ?? getBackendBaseUrl;
|
|
2515
2728
|
const songBaseUrl = getBackendBaseUrl;
|
|
2516
2729
|
const artistPanel = useArtistPanel({
|
|
@@ -2518,7 +2731,7 @@ function GenreTreeView({
|
|
|
2518
2731
|
scope,
|
|
2519
2732
|
getBackendBaseUrl: artistBaseUrl,
|
|
2520
2733
|
getShareUrl: getShareUrlRef.current,
|
|
2521
|
-
renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0,
|
|
2734
|
+
renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
|
|
2522
2735
|
expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
|
|
2523
2736
|
onExpandedChange: (on) => {
|
|
2524
2737
|
if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
|
|
@@ -2527,7 +2740,7 @@ function GenreTreeView({
|
|
|
2527
2740
|
renderSongLink: resolvedRenderSongLink,
|
|
2528
2741
|
onClose: () => changeArtist(null)
|
|
2529
2742
|
});
|
|
2530
|
-
const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0,
|
|
2743
|
+
const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
|
|
2531
2744
|
const getGenrePath = (genre) => {
|
|
2532
2745
|
const path = [];
|
|
2533
2746
|
let current = visibleGenrePlaylists.find((playlist) => playlist.criteria?.uuid === genre.uuid);
|
|
@@ -2545,9 +2758,9 @@ function GenreTreeView({
|
|
|
2545
2758
|
getShareUrl: getShareUrlRef.current,
|
|
2546
2759
|
renderArtistLink: resolvedRenderArtistLink,
|
|
2547
2760
|
getGenrePath,
|
|
2548
|
-
onSelectGenre: (
|
|
2761
|
+
onSelectGenre: (genrePlaylist2) => {
|
|
2549
2762
|
changeSong(null);
|
|
2550
|
-
selectGenrePlaylist(
|
|
2763
|
+
selectGenrePlaylist(genrePlaylist2);
|
|
2551
2764
|
},
|
|
2552
2765
|
expanded: controlledSongExpanded ?? (songUuid !== null && expandedSongUuid === songUuid),
|
|
2553
2766
|
onExpandedChange: (on) => {
|
|
@@ -2556,11 +2769,11 @@ function GenreTreeView({
|
|
|
2556
2769
|
},
|
|
2557
2770
|
onClose: () => changeSong(null)
|
|
2558
2771
|
});
|
|
2559
|
-
const builtInSongPanel = overlayPanel ||
|
|
2560
|
-
const builtInArtistPanel = overlayPanel ||
|
|
2772
|
+
const builtInSongPanel = overlayPanel || selectedGenreUuid !== null ? void 0 : songPanel;
|
|
2773
|
+
const builtInArtistPanel = overlayPanel || selectedGenreUuid !== null || builtInSongPanel ? void 0 : artistPanel;
|
|
2561
2774
|
const builtInPanel = builtInSongPanel ?? builtInArtistPanel;
|
|
2562
|
-
const [uncontrolledCollapsed, setUncontrolledCollapsed] = (0,
|
|
2563
|
-
const [uncontrolledExpanded, setUncontrolledExpanded] = (0,
|
|
2775
|
+
const [uncontrolledCollapsed, setUncontrolledCollapsed] = (0, import_react18.useState)(false);
|
|
2776
|
+
const [uncontrolledExpanded, setUncontrolledExpanded] = (0, import_react18.useState)(false);
|
|
2564
2777
|
const isCollapsed = collapsed ?? uncontrolledCollapsed;
|
|
2565
2778
|
const isExpanded = expanded ?? uncontrolledExpanded;
|
|
2566
2779
|
const changeCollapsed = (next) => {
|
|
@@ -2571,177 +2784,114 @@ function GenreTreeView({
|
|
|
2571
2784
|
setUncontrolledExpanded(next);
|
|
2572
2785
|
onExpandedChange?.(next);
|
|
2573
2786
|
};
|
|
2574
|
-
(0,
|
|
2787
|
+
(0, import_react18.useEffect)(() => {
|
|
2575
2788
|
setUncontrolledCollapsed(false);
|
|
2576
|
-
}, [
|
|
2577
|
-
const [isWheelRevealed, setIsWheelRevealed] = (0,
|
|
2789
|
+
}, [selectedGenreUuid, artistUuid, songUuid]);
|
|
2790
|
+
const [isWheelRevealed, setIsWheelRevealed] = (0, import_react18.useState)(false);
|
|
2578
2791
|
const usesHandoff = viewMode === "wheel" || viewMode === "pop-core";
|
|
2579
2792
|
const isTreeReady = !isLoading && (!usesHandoff || isWheelRevealed);
|
|
2580
|
-
const [resolveStyle, setResolveStyle] = (0,
|
|
2581
|
-
const handleNodeStyleResolverChange = (0,
|
|
2793
|
+
const [resolveStyle, setResolveStyle] = (0, import_react18.useState)(null);
|
|
2794
|
+
const handleNodeStyleResolverChange = (0, import_react18.useCallback)(
|
|
2582
2795
|
(resolver) => setResolveStyle(() => resolver),
|
|
2583
2796
|
[]
|
|
2584
2797
|
);
|
|
2585
|
-
const panelRef = (0,
|
|
2586
|
-
const obscuredInset = (0,
|
|
2798
|
+
const panelRef = (0, import_react18.useRef)(null);
|
|
2799
|
+
const obscuredInset = (0, import_ui17.useObscuredInset)(panelRef);
|
|
2587
2800
|
const hostPanel = { obscuredInset, onNodeStyleResolverChange: handleNodeStyleResolverChange };
|
|
2588
2801
|
const nodeMetric = isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric;
|
|
2589
|
-
const treeNodes = (0,
|
|
2590
|
-
const relations = (0,
|
|
2802
|
+
const treeNodes = (0, import_react18.useMemo)(() => toTreeNodes(visibleGenrePlaylists, nodeMetric), [visibleGenrePlaylists, nodeMetric]);
|
|
2803
|
+
const relations = (0, import_genre_tree_view5.useGenreNodeRelations)(
|
|
2591
2804
|
treeNodes,
|
|
2592
2805
|
!overlayPanel && hasSelectedGenreNode ? selectedNodeId : null,
|
|
2593
2806
|
resolveStyle
|
|
2594
2807
|
);
|
|
2595
|
-
const [selectionHistory, setSelectionHistory] = (0,
|
|
2596
|
-
|
|
2597
|
-
previous:
|
|
2598
|
-
});
|
|
2599
|
-
if (selectionHistory.current !== selectedNodeId) {
|
|
2600
|
-
setSelectionHistory({
|
|
2601
|
-
current: selectedNodeId,
|
|
2602
|
-
previous: selectionHistory.current ?? selectionHistory.previous
|
|
2603
|
-
});
|
|
2808
|
+
const [selectionHistory, setSelectionHistory] = (0, import_react18.useState)({ current: selectedGenreUuid, previous: null });
|
|
2809
|
+
if (selectionHistory.current !== selectedGenreUuid) {
|
|
2810
|
+
setSelectionHistory({ current: selectedGenreUuid, previous: selectionHistory.current ?? selectionHistory.previous });
|
|
2604
2811
|
}
|
|
2605
|
-
const
|
|
2606
|
-
const handlePrevious = previousPlaylist && visibleGenrePlaylists.includes(previousPlaylist) ? () => handleGenreSearchSelect(previousPlaylist) : void 0;
|
|
2812
|
+
const handlePrevious = selectionHistory.previous ? () => selectGenre(selectionHistory.previous) : void 0;
|
|
2607
2813
|
const { showPopup, hidePopup } = (0, import_popup5.usePopup)();
|
|
2608
|
-
const
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
(artist) => {
|
|
2613
|
-
openArtist(artist.uuid);
|
|
2614
|
-
onArtistSelect?.(artist);
|
|
2615
|
-
},
|
|
2616
|
-
Boolean(artistSearch)
|
|
2617
|
-
);
|
|
2618
|
-
const songGroup = useSongSearchGroup(
|
|
2619
|
-
songBaseUrl,
|
|
2620
|
-
searchText,
|
|
2621
|
-
(song) => {
|
|
2622
|
-
openSong(song.uuid);
|
|
2623
|
-
onSongSelect?.(song);
|
|
2624
|
-
},
|
|
2625
|
-
Boolean(songSearch)
|
|
2626
|
-
);
|
|
2627
|
-
const extraGroups = [...extraSearchGroups, artistGroup, songGroup].filter(
|
|
2628
|
-
(group) => group !== void 0
|
|
2629
|
-
);
|
|
2814
|
+
const reopenPanel = () => {
|
|
2815
|
+
setUncontrolledCollapsed(false);
|
|
2816
|
+
onCollapsedChange?.(false);
|
|
2817
|
+
};
|
|
2630
2818
|
const handleSelectedNodeChange = (node) => {
|
|
2819
|
+
reopenPanel();
|
|
2631
2820
|
if (node !== null && songUuid !== null) changeSong(null);
|
|
2632
2821
|
selectNode(node);
|
|
2633
2822
|
};
|
|
2634
2823
|
const handleGenreSearchClear = clearSelection;
|
|
2635
|
-
const handleGenreSearchSelect = (
|
|
2824
|
+
const handleGenreSearchSelect = (genrePlaylist2) => {
|
|
2825
|
+
reopenPanel();
|
|
2636
2826
|
if (songUuid !== null) changeSong(null);
|
|
2637
|
-
selectGenrePlaylist(
|
|
2827
|
+
selectGenrePlaylist(genrePlaylist2);
|
|
2638
2828
|
};
|
|
2639
2829
|
const selectedUuid = selectedGenrePlaylist?.uuid;
|
|
2640
|
-
const selectedChildren = selectedUuid ? visibleGenrePlaylists.filter((
|
|
2830
|
+
const selectedChildren = selectedUuid ? visibleGenrePlaylists.filter((genrePlaylist2) => genrePlaylist2.parent?.uuid === selectedUuid) : [];
|
|
2641
2831
|
const canZoomIn = viewMode === "pop-core" && focus.onFocusGenreChange !== void 0 && selectedUuid !== void 0 && focus.focusGenreUuid !== selectedUuid && selectedChildren.length > 0 && selectedChildren.filter((child) => child.criteria?.side === "pop").length <= 1;
|
|
2642
2832
|
const handleGenreSearchZoomIn = () => {
|
|
2643
2833
|
if (selectedUuid) focus.onFocusGenreChange?.(selectedUuid, "zoom-in");
|
|
2644
2834
|
};
|
|
2645
|
-
const zoomInRef = (0,
|
|
2835
|
+
const zoomInRef = (0, import_react18.useRef)(void 0);
|
|
2646
2836
|
zoomInRef.current = canZoomIn ? handleGenreSearchZoomIn : void 0;
|
|
2647
|
-
const
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
const
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
kind: "genre",
|
|
2677
|
-
genrePlaylistUuid: genrePlaylist.uuid,
|
|
2678
|
-
name: criteria.name,
|
|
2679
|
-
scope,
|
|
2680
|
-
artistUuid
|
|
2681
|
-
};
|
|
2682
|
-
const shareUrl = getShareUrlRef.current?.(shareTarget) ?? void 0;
|
|
2683
|
-
const popSibling = canToggleSide && side === "core" ? visibleGenrePlaylists.find(
|
|
2684
|
-
(sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist.uuid && sibling.criteria?.side === "pop"
|
|
2685
|
-
) : void 0;
|
|
2686
|
-
return {
|
|
2687
|
-
primary: /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_jsx_runtime38.Fragment, { children: [
|
|
2688
|
-
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
|
|
2689
|
-
GenreInfoPanelBody,
|
|
2837
|
+
const genrePlaylist = selectedGenrePlaylist;
|
|
2838
|
+
const criteria = genrePlaylist?.criteria;
|
|
2839
|
+
const parentPlaylist = visibleGenrePlaylists.find(({ uuid }) => uuid === genrePlaylist?.parent?.uuid);
|
|
2840
|
+
const canToggleSide = !readOnly && criteria !== void 0 && criteria !== null && parentPlaylist !== void 0 && parentPlaylist.parent === null;
|
|
2841
|
+
const side = criteria?.side ?? "core";
|
|
2842
|
+
const popSibling = canToggleSide && side === "core" ? visibleGenrePlaylists.find((sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist?.uuid && sibling.criteria?.side === "pop") : void 0;
|
|
2843
|
+
const shareTarget = selectedGenreUuid ? { kind: "genre", genreUuid: selectedGenreUuid, name: criteria?.name ?? "Genre", scope, artistUuid } : void 0;
|
|
2844
|
+
const shareUrl = shareTarget ? getShareUrlRef.current?.(shareTarget) ?? void 0 : void 0;
|
|
2845
|
+
const genreContent = useGenreDetailContent({
|
|
2846
|
+
genreUuid: selectedGenreUuid,
|
|
2847
|
+
scope,
|
|
2848
|
+
getBackendBaseUrl,
|
|
2849
|
+
onSelectGenre: (uuid) => {
|
|
2850
|
+
reopenPanel();
|
|
2851
|
+
selectGenre(uuid);
|
|
2852
|
+
},
|
|
2853
|
+
expanded: isExpanded,
|
|
2854
|
+
playback,
|
|
2855
|
+
genrePlaylist: selectedGenrePlaylist,
|
|
2856
|
+
band: {
|
|
2857
|
+
...relations ? { node: relations.node } : {},
|
|
2858
|
+
shareTarget: shareUrl ? shareTarget : void 0,
|
|
2859
|
+
shareUrl,
|
|
2860
|
+
onZoomIn: zoomInRef.current,
|
|
2861
|
+
onAddChild: readOnly || !criteria ? void 0 : () => handleGenreCreationAction(criteria),
|
|
2862
|
+
onRename: readOnly || !criteria ? void 0 : () => handleGenreRenameAction(criteria),
|
|
2863
|
+
onChangeParent: readOnly || !genrePlaylist || !criteria ? void 0 : () => showPopup(
|
|
2864
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2865
|
+
ChangeParentPopup,
|
|
2690
2866
|
{
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
ancestors
|
|
2694
|
-
] }),
|
|
2695
|
-
genreUuid,
|
|
2696
|
-
genrePlaylist,
|
|
2697
|
-
catalogue: genrePlaylist.catalogue,
|
|
2698
|
-
wikidataId: criteria.wikidataId,
|
|
2867
|
+
genrePlaylist: { ...genrePlaylist, criteria },
|
|
2868
|
+
genrePlaylists: visibleGenrePlaylists,
|
|
2699
2869
|
scope,
|
|
2700
2870
|
getBackendBaseUrl,
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
node,
|
|
2704
|
-
shareTarget: shareUrl ? shareTarget : void 0,
|
|
2705
|
-
shareUrl,
|
|
2706
|
-
onZoomIn: zoomInRef.current,
|
|
2707
|
-
onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
|
|
2708
|
-
onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
|
|
2709
|
-
onChangeParent: readOnly ? void 0 : () => showPopup(
|
|
2710
|
-
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
|
|
2711
|
-
ChangeParentPopup,
|
|
2712
|
-
{
|
|
2713
|
-
genrePlaylist: { ...genrePlaylist, criteria },
|
|
2714
|
-
genrePlaylists: visibleGenrePlaylists,
|
|
2715
|
-
scope,
|
|
2716
|
-
getBackendBaseUrl,
|
|
2717
|
-
onClose: hidePopup,
|
|
2718
|
-
onParentChanged
|
|
2719
|
-
}
|
|
2720
|
-
)
|
|
2721
|
-
),
|
|
2722
|
-
side,
|
|
2723
|
-
onToggleSide: canToggleSide ? () => updateGenre({ uuid: genreUuid, data: { side: side === "pop" ? "core" : "pop" } }) : void 0,
|
|
2724
|
-
toggleSideDisabledReason: popSibling ? `${popSibling.name} is already the pop side of ${parentPlaylist?.name}` : void 0,
|
|
2725
|
-
additionalActions
|
|
2726
|
-
},
|
|
2727
|
-
extras: {
|
|
2728
|
-
criteriaOverviewSchema,
|
|
2729
|
-
renderGenreDetailExtras,
|
|
2730
|
-
renderArtistLink,
|
|
2731
|
-
renderSongLink: resolvedRenderSongLink,
|
|
2732
|
-
artist: filterArtist,
|
|
2733
|
-
renderSummary,
|
|
2734
|
-
renderEssentialTracks,
|
|
2735
|
-
renderSongLeading,
|
|
2736
|
-
renderSongActions,
|
|
2737
|
-
renderTracksToolbar
|
|
2738
|
-
}
|
|
2871
|
+
onClose: hidePopup,
|
|
2872
|
+
onParentChanged
|
|
2739
2873
|
}
|
|
2740
|
-
)
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2874
|
+
)
|
|
2875
|
+
),
|
|
2876
|
+
side,
|
|
2877
|
+
onToggleSide: canToggleSide && criteria ? () => updateGenre({ uuid: criteria.uuid, data: { side: side === "pop" ? "core" : "pop" } }) : void 0,
|
|
2878
|
+
toggleSideDisabledReason: popSibling ? `${popSibling.name} is already the pop side of ${parentPlaylist?.name}` : void 0,
|
|
2879
|
+
additionalActions
|
|
2880
|
+
},
|
|
2881
|
+
extras: {
|
|
2882
|
+
criteriaOverviewSchema,
|
|
2883
|
+
renderGenreDetailExtras,
|
|
2884
|
+
renderArtistLink,
|
|
2885
|
+
renderSongLink: resolvedRenderSongLink,
|
|
2886
|
+
artist: filterArtist,
|
|
2887
|
+
renderSummary,
|
|
2888
|
+
renderEssentialTracks,
|
|
2889
|
+
renderSongLeading,
|
|
2890
|
+
renderSongActions,
|
|
2891
|
+
renderTracksToolbar
|
|
2892
|
+
},
|
|
2893
|
+
trailing: relations ? renderDangerZone?.(relations.node) : void 0
|
|
2894
|
+
});
|
|
2745
2895
|
const isRotationControlled = controlledAllowWheelRotation !== void 0;
|
|
2746
2896
|
const allowWheelRotation = controlledAllowWheelRotation ?? internalAllowWheelRotation;
|
|
2747
2897
|
let activePanel = null;
|
|
@@ -2755,16 +2905,16 @@ function GenreTreeView({
|
|
|
2755
2905
|
onClose: overlayPanel.onClose,
|
|
2756
2906
|
compact: false
|
|
2757
2907
|
};
|
|
2758
|
-
} else if (
|
|
2759
|
-
const { primary } = renderGenreBody(relations);
|
|
2908
|
+
} else if (selectedGenreUuid) {
|
|
2760
2909
|
activePanel = {
|
|
2761
|
-
title:
|
|
2762
|
-
content:
|
|
2910
|
+
title: genreContent.title,
|
|
2911
|
+
content: genreContent.content,
|
|
2912
|
+
aside: genreContent.aside,
|
|
2763
2913
|
expanded: isExpanded,
|
|
2764
2914
|
onExpandedChange: changeExpanded,
|
|
2765
2915
|
onClose: clearSelection,
|
|
2766
|
-
compact:
|
|
2767
|
-
headerAccent: { background: relations.fill, color: relations.textColor }
|
|
2916
|
+
compact: false,
|
|
2917
|
+
headerAccent: relations ? { background: relations.fill, color: relations.textColor } : genreContent.headerAccent
|
|
2768
2918
|
};
|
|
2769
2919
|
} else if (builtInPanel) {
|
|
2770
2920
|
activePanel = {
|
|
@@ -2777,36 +2927,36 @@ function GenreTreeView({
|
|
|
2777
2927
|
compact: true
|
|
2778
2928
|
};
|
|
2779
2929
|
}
|
|
2780
|
-
const isPanelShown =
|
|
2930
|
+
const isPanelShown = activePanel !== null && (selectedGenreUuid !== null || isTreeReady);
|
|
2781
2931
|
const detailTab = placement === "regions" && activePanel?.onExpandedChange ? {
|
|
2782
2932
|
edge: activePanel.expanded ? { label: "Shrink panel", onClick: () => activePanel.onExpandedChange?.(false) } : isCollapsed ? null : { label: "Hide panel", onClick: () => changeCollapsed(true) },
|
|
2783
2933
|
center: activePanel.expanded ? null : isCollapsed ? { label: "Show panel", onClick: () => changeCollapsed(false) } : activePanel.onExpandedChange ? { label: "Extend panel", onClick: () => activePanel.onExpandedChange?.(true) } : null
|
|
2784
2934
|
} : void 0;
|
|
2785
|
-
const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ (0,
|
|
2786
|
-
|
|
2935
|
+
const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2936
|
+
import_ui17.IconButton,
|
|
2787
2937
|
{
|
|
2788
2938
|
"aria-label": activePanel.expanded ? "Shrink panel" : "Extend panel",
|
|
2789
2939
|
style: activePanel.headerAccent && { color: "inherit" },
|
|
2790
2940
|
onClick: () => activePanel.onExpandedChange?.(!activePanel.expanded),
|
|
2791
|
-
children: activePanel.expanded ? /* @__PURE__ */ (0,
|
|
2941
|
+
children: activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react13.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_lucide_react13.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
2792
2942
|
}
|
|
2793
2943
|
) : null;
|
|
2794
2944
|
const renderArtistFilter = () => {
|
|
2795
2945
|
if (isLoading || !filterArtist) return null;
|
|
2796
|
-
return /* @__PURE__ */ (0,
|
|
2797
|
-
/* @__PURE__ */ (0,
|
|
2946
|
+
return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
|
|
2947
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2798
2948
|
"button",
|
|
2799
2949
|
{
|
|
2800
2950
|
type: "button",
|
|
2801
2951
|
className: "py-1 pl-3 pr-2 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] hover:underline",
|
|
2802
2952
|
"aria-label": `Show artist ${filterArtist.name}`,
|
|
2803
2953
|
onClick: () => {
|
|
2804
|
-
if (
|
|
2954
|
+
if (selectedGenreUuid !== null) clearSelection();
|
|
2805
2955
|
},
|
|
2806
2956
|
children: filterArtist.name
|
|
2807
2957
|
}
|
|
2808
2958
|
),
|
|
2809
|
-
/* @__PURE__ */ (0,
|
|
2959
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2810
2960
|
"button",
|
|
2811
2961
|
{
|
|
2812
2962
|
type: "button",
|
|
@@ -2818,27 +2968,42 @@ function GenreTreeView({
|
|
|
2818
2968
|
)
|
|
2819
2969
|
] });
|
|
2820
2970
|
};
|
|
2821
|
-
const actions = /* @__PURE__ */ (0,
|
|
2822
|
-
!isLoading && /* @__PURE__ */ (0,
|
|
2823
|
-
|
|
2971
|
+
const actions = /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
|
|
2972
|
+
!isLoading && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2973
|
+
GenreTreeSearch,
|
|
2824
2974
|
{
|
|
2975
|
+
artistBaseUrl,
|
|
2976
|
+
songBaseUrl,
|
|
2977
|
+
artistSearch: Boolean(artistSearch),
|
|
2978
|
+
songSearch: Boolean(songSearch),
|
|
2979
|
+
onArtistSelect: (artist) => {
|
|
2980
|
+
openArtist(artist.uuid);
|
|
2981
|
+
onArtistSelect?.(artist);
|
|
2982
|
+
},
|
|
2983
|
+
onSongSelect: (song) => {
|
|
2984
|
+
openSong(song.uuid);
|
|
2985
|
+
onSongSelect?.(song);
|
|
2986
|
+
},
|
|
2987
|
+
SearchGroups,
|
|
2825
2988
|
genrePlaylists: visibleGenrePlaylists,
|
|
2826
2989
|
onSelect: handleGenreSearchSelect,
|
|
2827
|
-
|
|
2990
|
+
onSelectGenre: (uuid) => {
|
|
2991
|
+
reopenPanel();
|
|
2992
|
+
if (songUuid) changeSong(null);
|
|
2993
|
+
selectGenre(uuid);
|
|
2994
|
+
},
|
|
2995
|
+
selectedName: (selectedGenreUuid ? genreContent.title : null) ?? (typeof builtInPanel?.title === "string" ? builtInPanel.title : null),
|
|
2828
2996
|
onClear: builtInSongPanel ? () => changeSong(null) : builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
|
|
2829
2997
|
onPrevious: handlePrevious,
|
|
2830
|
-
onQueryChange:
|
|
2831
|
-
setSearchText(text);
|
|
2832
|
-
onSearchQueryChange?.(text);
|
|
2833
|
-
},
|
|
2998
|
+
onQueryChange: onSearchQueryChange,
|
|
2834
2999
|
onFocus: onSearchFocus,
|
|
2835
|
-
extraGroups
|
|
3000
|
+
extraGroups: extraSearchGroups
|
|
2836
3001
|
}
|
|
2837
3002
|
),
|
|
2838
|
-
!isLoading && filterArtist && /* @__PURE__ */ (0,
|
|
2839
|
-
/* @__PURE__ */ (0,
|
|
2840
|
-
!isLoading && !isControlled && /* @__PURE__ */ (0,
|
|
2841
|
-
|
|
3003
|
+
!isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
|
|
3004
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.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: [
|
|
3005
|
+
!isLoading && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
3006
|
+
import_ui17.SegmentedControl,
|
|
2842
3007
|
{
|
|
2843
3008
|
"aria-label": "Tree view mode",
|
|
2844
3009
|
value: viewMode,
|
|
@@ -2851,14 +3016,14 @@ function GenreTreeView({
|
|
|
2851
3016
|
]
|
|
2852
3017
|
}
|
|
2853
3018
|
),
|
|
2854
|
-
!isLoading && showDisplayOptions && /* @__PURE__ */ (0,
|
|
3019
|
+
!isLoading && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2855
3020
|
"div",
|
|
2856
3021
|
{
|
|
2857
3022
|
className: "flex items-center gap-1",
|
|
2858
3023
|
role: "group",
|
|
2859
3024
|
"aria-label": "Tree display options",
|
|
2860
|
-
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0,
|
|
2861
|
-
|
|
3025
|
+
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
3026
|
+
import_ui17.Button,
|
|
2862
3027
|
{
|
|
2863
3028
|
variant: allowWheelRotation ? "default" : "outline",
|
|
2864
3029
|
size: "sm",
|
|
@@ -2868,44 +3033,44 @@ function GenreTreeView({
|
|
|
2868
3033
|
)
|
|
2869
3034
|
}
|
|
2870
3035
|
),
|
|
2871
|
-
!isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0,
|
|
2872
|
-
|
|
3036
|
+
!isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
3037
|
+
import_ui17.IconTextButton,
|
|
2873
3038
|
{
|
|
2874
|
-
icon:
|
|
3039
|
+
icon: import_lucide_react12.Plus,
|
|
2875
3040
|
text: "Add root",
|
|
2876
3041
|
onClick: () => handleGenreCreationAction(null)
|
|
2877
3042
|
}
|
|
2878
3043
|
)
|
|
2879
3044
|
] })
|
|
2880
3045
|
] });
|
|
2881
|
-
return /* @__PURE__ */ (0,
|
|
3046
|
+
return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
|
|
2882
3047
|
"div",
|
|
2883
3048
|
{
|
|
2884
3049
|
"data-left-panel": isPanelShown && !isCollapsed ? void 0 : "hidden",
|
|
2885
3050
|
className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
|
|
2886
3051
|
children: [
|
|
2887
|
-
/* @__PURE__ */ (0,
|
|
3052
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2888
3053
|
"div",
|
|
2889
3054
|
{
|
|
2890
3055
|
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"}`,
|
|
2891
3056
|
children: actions
|
|
2892
3057
|
}
|
|
2893
3058
|
),
|
|
2894
|
-
!isLoading && filterArtist && /* @__PURE__ */ (0,
|
|
3059
|
+
!isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2895
3060
|
"div",
|
|
2896
3061
|
{
|
|
2897
3062
|
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"}`,
|
|
2898
3063
|
children: renderArtistFilter()
|
|
2899
3064
|
}
|
|
2900
3065
|
),
|
|
2901
|
-
/* @__PURE__ */ (0,
|
|
2902
|
-
!isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0,
|
|
2903
|
-
isLoading ? /* @__PURE__ */ (0,
|
|
3066
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
|
|
3067
|
+
!isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
|
|
3068
|
+
isLoading ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_genre_tree_view5.GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
|
|
2904
3069
|
// The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
|
|
2905
3070
|
// and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
|
|
2906
3071
|
// Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
|
|
2907
3072
|
// The panel's own Zoom in button is hidden: the search bar carries it.
|
|
2908
|
-
/* @__PURE__ */ (0,
|
|
3073
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_genre_tree_view5.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2909
3074
|
GenrePlaylistTreeWheel,
|
|
2910
3075
|
{
|
|
2911
3076
|
scope,
|
|
@@ -2922,9 +3087,9 @@ function GenreTreeView({
|
|
|
2922
3087
|
allowWheelRotation,
|
|
2923
3088
|
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2924
3089
|
busy,
|
|
2925
|
-
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0,
|
|
3090
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
2926
3091
|
floatingControlsLeading,
|
|
2927
|
-
canSizeByViews && /* @__PURE__ */ (0,
|
|
3092
|
+
canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2928
3093
|
] }) : void 0,
|
|
2929
3094
|
defaultViewport,
|
|
2930
3095
|
viewportRestore,
|
|
@@ -2933,7 +3098,7 @@ function GenreTreeView({
|
|
|
2933
3098
|
) }) })
|
|
2934
3099
|
) : viewMode === "pop-core" ? (
|
|
2935
3100
|
// Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
|
|
2936
|
-
/* @__PURE__ */ (0,
|
|
3101
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_genre_tree_view5.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2937
3102
|
GenrePlaylistTreeWheelRadialPopCore,
|
|
2938
3103
|
{
|
|
2939
3104
|
scope,
|
|
@@ -2950,9 +3115,9 @@ function GenreTreeView({
|
|
|
2950
3115
|
allowWheelRotation,
|
|
2951
3116
|
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2952
3117
|
busy,
|
|
2953
|
-
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0,
|
|
3118
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
2954
3119
|
floatingControlsLeading,
|
|
2955
|
-
canSizeByViews && /* @__PURE__ */ (0,
|
|
3120
|
+
canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2956
3121
|
] }) : void 0,
|
|
2957
3122
|
defaultViewport,
|
|
2958
3123
|
viewportRestore,
|
|
@@ -2960,7 +3125,7 @@ function GenreTreeView({
|
|
|
2960
3125
|
...focus
|
|
2961
3126
|
}
|
|
2962
3127
|
) }) })
|
|
2963
|
-
) : viewMode === "outline" ? /* @__PURE__ */ (0,
|
|
3128
|
+
) : viewMode === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2964
3129
|
GenrePlaylistTreeWheelRadialPopCore,
|
|
2965
3130
|
{
|
|
2966
3131
|
outline: true,
|
|
@@ -2976,13 +3141,13 @@ function GenreTreeView({
|
|
|
2976
3141
|
selectedNodeId,
|
|
2977
3142
|
readOnly
|
|
2978
3143
|
}
|
|
2979
|
-
) }) : /* @__PURE__ */ (0,
|
|
3144
|
+
) }) : /* @__PURE__ */ (0, import_jsx_runtime40.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)]", children: Object.entries(groupedGenrePlaylistsByRoot).map(
|
|
2980
3145
|
([uuid, genrePlaylistTreePerRoot]) => {
|
|
2981
|
-
return /* @__PURE__ */ (0,
|
|
3146
|
+
return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2982
3147
|
"div",
|
|
2983
3148
|
{
|
|
2984
3149
|
className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
|
|
2985
|
-
children: /* @__PURE__ */ (0,
|
|
3150
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2986
3151
|
GenrePlaylistTreePerRoot,
|
|
2987
3152
|
{
|
|
2988
3153
|
scope,
|
|
@@ -3008,8 +3173,8 @@ function GenreTreeView({
|
|
|
3008
3173
|
);
|
|
3009
3174
|
}
|
|
3010
3175
|
) }),
|
|
3011
|
-
/* @__PURE__ */ (0,
|
|
3012
|
-
|
|
3176
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
3177
|
+
import_ui17.DetailPanel,
|
|
3013
3178
|
{
|
|
3014
3179
|
ref: panelRef,
|
|
3015
3180
|
side: "left",
|
|
@@ -3019,13 +3184,13 @@ function GenreTreeView({
|
|
|
3019
3184
|
headerAccent: activePanel?.headerAccent,
|
|
3020
3185
|
headerActions: expandButton,
|
|
3021
3186
|
onClose: activePanel?.onClose ?? noop,
|
|
3022
|
-
collapsed: isCollapsed || !
|
|
3187
|
+
collapsed: isCollapsed || !isPanelShown,
|
|
3023
3188
|
expanded: activePanel?.expanded,
|
|
3024
3189
|
onExpandedChange: activePanel?.onExpandedChange,
|
|
3025
3190
|
aside: activePanel?.aside,
|
|
3026
3191
|
edgeTab: detailTab,
|
|
3027
|
-
headerClassName:
|
|
3028
|
-
children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ (0,
|
|
3192
|
+
headerClassName: "pt-[calc(var(--layout-search-h)+var(--layout-panel-inset)+0.5rem)]",
|
|
3193
|
+
children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(import_jsx_runtime40.Fragment, { children: [
|
|
3029
3194
|
activePanel.content,
|
|
3030
3195
|
activePanel.aside
|
|
3031
3196
|
] }) : activePanel?.content
|
|
@@ -3037,145 +3202,17 @@ function GenreTreeView({
|
|
|
3037
3202
|
);
|
|
3038
3203
|
}
|
|
3039
3204
|
|
|
3040
|
-
// src/genre-tree/GenreDetailPanel.tsx
|
|
3041
|
-
var import_react17 = require("react");
|
|
3042
|
-
var import_genre_tree_view5 = require("@behindthemusictree/genre-tree-view");
|
|
3043
|
-
var import_ui17 = require("@behindthemusictree/ui");
|
|
3044
|
-
var import_lucide_react13 = require("lucide-react");
|
|
3045
|
-
var import_genre_tree28 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3046
|
-
var import_jsx_runtime39 = require("react/jsx-runtime");
|
|
3047
|
-
var RELATIVE_ITEM_COUNT = 1;
|
|
3048
|
-
function toNode(genre, parentId) {
|
|
3049
|
-
return {
|
|
3050
|
-
id: genre.uuid,
|
|
3051
|
-
parentId,
|
|
3052
|
-
name: genre.name,
|
|
3053
|
-
itemCount: RELATIVE_ITEM_COUNT,
|
|
3054
|
-
side: genre.side ?? void 0,
|
|
3055
|
-
wikidataId: genre.wikidataId
|
|
3056
|
-
};
|
|
3057
|
-
}
|
|
3058
|
-
function GenreDetailPanel({
|
|
3059
|
-
genreUuid,
|
|
3060
|
-
scope,
|
|
3061
|
-
getBackendBaseUrl,
|
|
3062
|
-
onClose,
|
|
3063
|
-
onSelectGenre,
|
|
3064
|
-
onZoomIn,
|
|
3065
|
-
shareUrl,
|
|
3066
|
-
shareTarget,
|
|
3067
|
-
collapsed,
|
|
3068
|
-
expanded,
|
|
3069
|
-
onExpandedChange,
|
|
3070
|
-
edgeTab,
|
|
3071
|
-
extras
|
|
3072
|
-
}) {
|
|
3073
|
-
const { data: detail, isPending, isError } = (0, import_genre_tree28.useFetchGenreDetail)(genreUuid, scope, getBackendBaseUrl);
|
|
3074
|
-
const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: [] });
|
|
3075
|
-
const view = (0, import_react17.useMemo)(() => {
|
|
3076
|
-
if (!detail) return null;
|
|
3077
|
-
const rootColor = (0, import_genre_tree_view5.getGenreTreeColor)(detail.root.wikidataId);
|
|
3078
|
-
const relative = (genre, parentId) => ({
|
|
3079
|
-
node: toNode(genre, parentId),
|
|
3080
|
-
fill: (0, import_genre_tree_view5.tintSurface)(rootColor),
|
|
3081
|
-
textColor: import_genre_tree_view5.TEXT_COLOR
|
|
3082
|
-
});
|
|
3083
|
-
const ancestors2 = [...detail.ascendants].sort((a, b) => b.degree - a.degree);
|
|
3084
|
-
return {
|
|
3085
|
-
node: {
|
|
3086
|
-
id: detail.uuid,
|
|
3087
|
-
parentId: detail.parent?.uuid ?? null,
|
|
3088
|
-
name: detail.name,
|
|
3089
|
-
itemCount: scope === "reference" ? (0, import_genre_tree28.genreMusicCount)(import_genre_tree28.GenrePlaylistSummarySchema.parse(detail.criteriaPlaylist)) : detail.tracksCount,
|
|
3090
|
-
wikidataId: detail.wikidataId
|
|
3091
|
-
},
|
|
3092
|
-
fill: rootColor,
|
|
3093
|
-
parentNode: detail.parent ? relative(detail.parent, null) : null,
|
|
3094
|
-
childNodes: detail.children.map((child) => relative(child, detail.uuid)),
|
|
3095
|
-
ancestorNodes: ancestors2.map(
|
|
3096
|
-
({ ascendant }, index) => relative(ascendant, index === 0 ? null : ancestors2[index - 1].ascendant.uuid)
|
|
3097
|
-
)
|
|
3098
|
-
};
|
|
3099
|
-
}, [detail, scope]);
|
|
3100
|
-
if (isError) {
|
|
3101
|
-
return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
|
|
3102
|
-
}
|
|
3103
|
-
if (isPending || !detail || !view) {
|
|
3104
|
-
return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
|
|
3105
|
-
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_ui17.Skeleton, { className: "h-6 w-2/3" }),
|
|
3106
|
-
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_ui17.Skeleton, { className: "h-4 w-full" }),
|
|
3107
|
-
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_ui17.Skeleton, { className: "h-4 w-3/4" })
|
|
3108
|
-
] });
|
|
3109
|
-
}
|
|
3110
|
-
const selectGenre = (nodeId) => onSelectGenre(nodeId);
|
|
3111
|
-
const family = /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
3112
|
-
import_genre_tree_view5.GenreNodeFamily,
|
|
3113
|
-
{
|
|
3114
|
-
node: view.node,
|
|
3115
|
-
fill: view.fill,
|
|
3116
|
-
textColor: import_genre_tree_view5.ACCENT_TEXT_COLOR,
|
|
3117
|
-
parentNode: view.parentNode,
|
|
3118
|
-
childNodes: view.childNodes,
|
|
3119
|
-
onSelectNode: selectGenre
|
|
3120
|
-
}
|
|
3121
|
-
);
|
|
3122
|
-
const ancestors = /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_genre_tree_view5.GenreNodeAncestors, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
|
|
3123
|
-
return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
|
|
3124
|
-
import_ui17.DetailPanel,
|
|
3125
|
-
{
|
|
3126
|
-
side: "left",
|
|
3127
|
-
title: view.node.name,
|
|
3128
|
-
headerAccent: { background: view.fill, color: import_genre_tree_view5.ACCENT_TEXT_COLOR },
|
|
3129
|
-
headerActions: onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
3130
|
-
import_ui17.IconButton,
|
|
3131
|
-
{
|
|
3132
|
-
"aria-label": expanded ? "Shrink panel" : "Extend panel",
|
|
3133
|
-
style: { color: "inherit" },
|
|
3134
|
-
onClick: () => onExpandedChange(!expanded),
|
|
3135
|
-
children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_lucide_react13.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_lucide_react13.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
3136
|
-
}
|
|
3137
|
-
) : void 0,
|
|
3138
|
-
onClose,
|
|
3139
|
-
collapsed,
|
|
3140
|
-
expanded,
|
|
3141
|
-
onExpandedChange,
|
|
3142
|
-
edgeTab,
|
|
3143
|
-
children: [
|
|
3144
|
-
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
3145
|
-
GenreInfoPanelBody,
|
|
3146
|
-
{
|
|
3147
|
-
family: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_jsx_runtime39.Fragment, { children: [
|
|
3148
|
-
family,
|
|
3149
|
-
ancestors
|
|
3150
|
-
] }),
|
|
3151
|
-
genreUuid: detail.uuid,
|
|
3152
|
-
genrePlaylist: scope === "reference" ? import_genre_tree28.GenrePlaylistSummarySchema.parse(detail.criteriaPlaylist) : { ...detail.criteriaPlaylist, tracksCount: detail.tracksCount },
|
|
3153
|
-
catalogue: detail.catalogue,
|
|
3154
|
-
wikidataId: detail.wikidataId,
|
|
3155
|
-
scope,
|
|
3156
|
-
getBackendBaseUrl,
|
|
3157
|
-
playback,
|
|
3158
|
-
band: { node: view.node, onZoomIn, shareUrl, shareTarget: shareUrl ? shareTarget : void 0 },
|
|
3159
|
-
extras
|
|
3160
|
-
}
|
|
3161
|
-
),
|
|
3162
|
-
!expanded && ancestors
|
|
3163
|
-
]
|
|
3164
|
-
}
|
|
3165
|
-
);
|
|
3166
|
-
}
|
|
3167
|
-
|
|
3168
3205
|
// src/genre-tree/index.ts
|
|
3169
3206
|
var import_genre_tree_view6 = require("@behindthemusictree/genre-tree-view");
|
|
3170
3207
|
|
|
3171
3208
|
// src/genre-tree/track-list-sidebar/TrackListSidebar.tsx
|
|
3172
|
-
var
|
|
3209
|
+
var import_react19 = require("react");
|
|
3173
3210
|
var import_lucide_react14 = require("lucide-react");
|
|
3174
3211
|
var import_genre_tree29 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3175
3212
|
var import_genre_tree30 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3176
3213
|
var import_track_noun4 = require("@behindthemusictree/app-kit/track-noun");
|
|
3177
|
-
var
|
|
3178
|
-
var useBrowserLayoutEffect = typeof window === "undefined" ?
|
|
3214
|
+
var import_jsx_runtime41 = require("react/jsx-runtime");
|
|
3215
|
+
var useBrowserLayoutEffect = typeof window === "undefined" ? import_react19.useEffect : import_react19.useLayoutEffect;
|
|
3179
3216
|
function TrackListSidebar({
|
|
3180
3217
|
className: className2 = "",
|
|
3181
3218
|
renderDuration,
|
|
@@ -3189,16 +3226,16 @@ function TrackListSidebar({
|
|
|
3189
3226
|
}) {
|
|
3190
3227
|
const { trackList, loadMore, autoAdvanceScrollRequest } = (0, import_genre_tree29.useTrackList)();
|
|
3191
3228
|
const trackNoun = (0, import_track_noun4.useTrackNoun)();
|
|
3192
|
-
const trackListRef = (0,
|
|
3193
|
-
const previousOriginRef = (0,
|
|
3194
|
-
const handledAutoAdvanceRequestIdRef = (0,
|
|
3195
|
-
const shouldFollowPlaybackRef = (0,
|
|
3196
|
-
const programmaticScrollTopRef = (0,
|
|
3197
|
-
const userScrollIntentRef = (0,
|
|
3198
|
-
const userScrollIntentTimerRef = (0,
|
|
3199
|
-
const loadMoreSentinelRef = (0,
|
|
3229
|
+
const trackListRef = (0, import_react19.useRef)(null);
|
|
3230
|
+
const previousOriginRef = (0, import_react19.useRef)(trackList?.origin ?? null);
|
|
3231
|
+
const handledAutoAdvanceRequestIdRef = (0, import_react19.useRef)(null);
|
|
3232
|
+
const shouldFollowPlaybackRef = (0, import_react19.useRef)(true);
|
|
3233
|
+
const programmaticScrollTopRef = (0, import_react19.useRef)(null);
|
|
3234
|
+
const userScrollIntentRef = (0, import_react19.useRef)(false);
|
|
3235
|
+
const userScrollIntentTimerRef = (0, import_react19.useRef)(null);
|
|
3236
|
+
const loadMoreSentinelRef = (0, import_react19.useRef)(null);
|
|
3200
3237
|
const hasMore = !!trackList && trackList.nextPage !== null;
|
|
3201
|
-
(0,
|
|
3238
|
+
(0, import_react19.useEffect)(() => {
|
|
3202
3239
|
const sentinel = loadMoreSentinelRef.current;
|
|
3203
3240
|
if (!hasMore || !sentinel) return;
|
|
3204
3241
|
const observer = new IntersectionObserver((entries) => {
|
|
@@ -3218,11 +3255,11 @@ function TrackListSidebar({
|
|
|
3218
3255
|
programmaticScrollTopRef.current = null;
|
|
3219
3256
|
if (trackListRef.current) trackListRef.current.scrollTop = 0;
|
|
3220
3257
|
}, [trackList?.origin]);
|
|
3221
|
-
(0,
|
|
3258
|
+
(0, import_react19.useEffect)(() => {
|
|
3222
3259
|
const list = trackListRef.current;
|
|
3223
3260
|
if (!isCollapsed && list?.scrollTop === 0) shouldFollowPlaybackRef.current = true;
|
|
3224
3261
|
}, [isCollapsed]);
|
|
3225
|
-
(0,
|
|
3262
|
+
(0, import_react19.useEffect)(
|
|
3226
3263
|
() => () => {
|
|
3227
3264
|
if (userScrollIntentTimerRef.current !== null) clearTimeout(userScrollIntentTimerRef.current);
|
|
3228
3265
|
},
|
|
@@ -3276,25 +3313,25 @@ function TrackListSidebar({
|
|
|
3276
3313
|
}
|
|
3277
3314
|
};
|
|
3278
3315
|
const positionClasses = layout === "inline" ? "relative w-full flex h-full flex-col" : "fixed right-0 w-[50rem] flex flex-col";
|
|
3279
|
-
return trackList ? /* @__PURE__ */ (0,
|
|
3316
|
+
return trackList ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
|
|
3280
3317
|
"div",
|
|
3281
3318
|
{
|
|
3282
3319
|
"data-theme": "dark",
|
|
3283
3320
|
className: `track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${className2}`,
|
|
3284
3321
|
style: layout === "fixed" ? { top: "63.5px", maxHeight: "calc(100vh - 63.5px - 79px)" } : void 0,
|
|
3285
3322
|
children: [
|
|
3286
|
-
/* @__PURE__ */ (0,
|
|
3287
|
-
/* @__PURE__ */ (0,
|
|
3288
|
-
/* @__PURE__ */ (0,
|
|
3289
|
-
/* @__PURE__ */ (0,
|
|
3290
|
-
/* @__PURE__ */ (0,
|
|
3323
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
|
|
3324
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
|
|
3325
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "origin flex text-xl", children: [
|
|
3326
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
|
|
3327
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.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_runtime41.jsx)("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
|
|
3291
3328
|
] }),
|
|
3292
|
-
/* @__PURE__ */ (0,
|
|
3329
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
|
|
3293
3330
|
trackList.origin.type === import_genre_tree30.TrackListOriginType.GENRE_PLAYLIST ? "Genre \u2022 " : trackList.origin.type === import_genre_tree30.TrackListOriginType.ARTIST ? "Artist \u2022 " : `${trackNoun.singular} playlist \u2022 `,
|
|
3294
3331
|
`${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`
|
|
3295
3332
|
] })
|
|
3296
3333
|
] }),
|
|
3297
|
-
(isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0,
|
|
3334
|
+
(isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
3298
3335
|
"button",
|
|
3299
3336
|
{
|
|
3300
3337
|
type: "button",
|
|
@@ -3303,11 +3340,11 @@ function TrackListSidebar({
|
|
|
3303
3340
|
"aria-expanded": !isCollapsed,
|
|
3304
3341
|
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"}`,
|
|
3305
3342
|
onClick: isCollapsed ? onExpand : onMinimize,
|
|
3306
|
-
children: /* @__PURE__ */ (0,
|
|
3343
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_lucide_react14.ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
|
|
3307
3344
|
}
|
|
3308
3345
|
)
|
|
3309
3346
|
] }),
|
|
3310
|
-
!isCollapsed && /* @__PURE__ */ (0,
|
|
3347
|
+
!isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
|
|
3311
3348
|
"ul",
|
|
3312
3349
|
{
|
|
3313
3350
|
ref: trackListRef,
|
|
@@ -3317,7 +3354,7 @@ function TrackListSidebar({
|
|
|
3317
3354
|
onPointerDown: markUserScrollIntent,
|
|
3318
3355
|
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",
|
|
3319
3356
|
children: [
|
|
3320
|
-
trackList.tracks.map((track, index) => /* @__PURE__ */ (0,
|
|
3357
|
+
trackList.tracks.map((track, index) => /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
3321
3358
|
TrackItem,
|
|
3322
3359
|
{
|
|
3323
3360
|
track,
|
|
@@ -3328,7 +3365,7 @@ function TrackListSidebar({
|
|
|
3328
3365
|
renderSongLink
|
|
3329
3366
|
}
|
|
3330
3367
|
) }, track.uuid)),
|
|
3331
|
-
hasMore && /* @__PURE__ */ (0,
|
|
3368
|
+
hasMore && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
|
|
3332
3369
|
]
|
|
3333
3370
|
}
|
|
3334
3371
|
)
|
|
@@ -3338,7 +3375,7 @@ function TrackListSidebar({
|
|
|
3338
3375
|
}
|
|
3339
3376
|
|
|
3340
3377
|
// src/genre-tree/PlayerPanel.tsx
|
|
3341
|
-
var
|
|
3378
|
+
var import_react20 = require("react");
|
|
3342
3379
|
var import_tailwind_merge = require("tailwind-merge");
|
|
3343
3380
|
var import_player13 = require("@behindthemusictree/app-kit/player");
|
|
3344
3381
|
var import_genre_tree32 = require("@behindthemusictree/app-kit/genre-tree");
|
|
@@ -3348,7 +3385,7 @@ var import_ui18 = require("@behindthemusictree/ui");
|
|
|
3348
3385
|
var import_lucide_react15 = require("lucide-react");
|
|
3349
3386
|
var import_genre_tree31 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3350
3387
|
var import_player12 = require("@behindthemusictree/app-kit/player");
|
|
3351
|
-
var
|
|
3388
|
+
var import_jsx_runtime42 = require("react/jsx-runtime");
|
|
3352
3389
|
function PlayerControls() {
|
|
3353
3390
|
const {
|
|
3354
3391
|
duration,
|
|
@@ -3367,15 +3404,15 @@ function PlayerControls() {
|
|
|
3367
3404
|
const safeDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;
|
|
3368
3405
|
const safeCurrentTime = Number.isFinite(currentTime) ? Math.max(0, Math.min(currentTime, safeDuration)) : 0;
|
|
3369
3406
|
const isPlaying = playState === import_player12.PlayStates.PLAYING;
|
|
3370
|
-
return /* @__PURE__ */ (0,
|
|
3407
|
+
return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
|
|
3371
3408
|
"section",
|
|
3372
3409
|
{
|
|
3373
3410
|
"aria-label": "Player controls",
|
|
3374
3411
|
className: "player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200",
|
|
3375
3412
|
children: [
|
|
3376
|
-
/* @__PURE__ */ (0,
|
|
3377
|
-
/* @__PURE__ */ (0,
|
|
3378
|
-
/* @__PURE__ */ (0,
|
|
3413
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
|
|
3414
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { "aria-label": "Elapsed time", children: (0, import_genre_tree31.formatTime)(safeCurrentTime) }),
|
|
3415
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
3379
3416
|
"input",
|
|
3380
3417
|
{
|
|
3381
3418
|
"aria-label": "Seek",
|
|
@@ -3389,11 +3426,11 @@ function PlayerControls() {
|
|
|
3389
3426
|
onChange: (event) => seekTo(Number(event.currentTarget.value))
|
|
3390
3427
|
}
|
|
3391
3428
|
),
|
|
3392
|
-
/* @__PURE__ */ (0,
|
|
3429
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { "aria-label": "Duration", children: (0, import_genre_tree31.formatTime)(safeDuration) })
|
|
3393
3430
|
] }),
|
|
3394
|
-
/* @__PURE__ */ (0,
|
|
3395
|
-
/* @__PURE__ */ (0,
|
|
3396
|
-
/* @__PURE__ */ (0,
|
|
3431
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
|
|
3432
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
|
|
3433
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
3397
3434
|
"button",
|
|
3398
3435
|
{
|
|
3399
3436
|
type: "button",
|
|
@@ -3402,10 +3439,10 @@ function PlayerControls() {
|
|
|
3402
3439
|
disabled: !isReady || !hasPreviousTrack,
|
|
3403
3440
|
onClick: playPreviousTrack,
|
|
3404
3441
|
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",
|
|
3405
|
-
children: /* @__PURE__ */ (0,
|
|
3442
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react15.SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
3406
3443
|
}
|
|
3407
3444
|
),
|
|
3408
|
-
/* @__PURE__ */ (0,
|
|
3445
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
3409
3446
|
"button",
|
|
3410
3447
|
{
|
|
3411
3448
|
type: "button",
|
|
@@ -3414,10 +3451,10 @@ function PlayerControls() {
|
|
|
3414
3451
|
disabled: !isReady,
|
|
3415
3452
|
onClick: handlePlayPauseAction,
|
|
3416
3453
|
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",
|
|
3417
|
-
children: isPlaying ? /* @__PURE__ */ (0,
|
|
3454
|
+
children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react15.Pause, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react15.Play, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
|
|
3418
3455
|
}
|
|
3419
3456
|
),
|
|
3420
|
-
/* @__PURE__ */ (0,
|
|
3457
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
3421
3458
|
"button",
|
|
3422
3459
|
{
|
|
3423
3460
|
type: "button",
|
|
@@ -3426,14 +3463,14 @@ function PlayerControls() {
|
|
|
3426
3463
|
disabled: !isReady || !hasNextTrack,
|
|
3427
3464
|
onClick: playNextTrack,
|
|
3428
3465
|
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",
|
|
3429
|
-
children: /* @__PURE__ */ (0,
|
|
3466
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react15.SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
3430
3467
|
}
|
|
3431
3468
|
)
|
|
3432
3469
|
] }),
|
|
3433
|
-
/* @__PURE__ */ (0,
|
|
3434
|
-
volume === 0 ? /* @__PURE__ */ (0,
|
|
3435
|
-
/* @__PURE__ */ (0,
|
|
3436
|
-
/* @__PURE__ */ (0,
|
|
3470
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.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: [
|
|
3471
|
+
volume === 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react15.VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react15.Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
|
|
3472
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "sr-only", children: "Volume" }),
|
|
3473
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
3437
3474
|
"input",
|
|
3438
3475
|
{
|
|
3439
3476
|
"aria-label": "Volume",
|
|
@@ -3455,7 +3492,7 @@ function PlayerControls() {
|
|
|
3455
3492
|
}
|
|
3456
3493
|
|
|
3457
3494
|
// src/genre-tree/PlayerPanel.tsx
|
|
3458
|
-
var
|
|
3495
|
+
var import_jsx_runtime43 = require("react/jsx-runtime");
|
|
3459
3496
|
var PANEL_ID = "player-panel";
|
|
3460
3497
|
var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
|
|
3461
3498
|
function PlayerPanel({
|
|
@@ -3469,12 +3506,12 @@ function PlayerPanel({
|
|
|
3469
3506
|
}) {
|
|
3470
3507
|
const { playerTrackObject, retryPlayback } = (0, import_player13.usePlayer)();
|
|
3471
3508
|
const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0, import_genre_tree32.useTrackListSidebarVisibility)();
|
|
3472
|
-
const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = (0,
|
|
3509
|
+
const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = (0, import_react20.useState)("");
|
|
3473
3510
|
const loadedTrackTitle = playerTrackObject?.track.title;
|
|
3474
|
-
(0,
|
|
3511
|
+
(0, import_react20.useEffect)(() => {
|
|
3475
3512
|
if (loadedTrackTitle) setLastLoadedTrackTitle(loadedTrackTitle);
|
|
3476
3513
|
}, [loadedTrackTitle]);
|
|
3477
|
-
const [uncontrolledExtended, setUncontrolledExtended] = (0,
|
|
3514
|
+
const [uncontrolledExtended, setUncontrolledExtended] = (0, import_react20.useState)(false);
|
|
3478
3515
|
const isExtendRequested = expanded ?? uncontrolledExtended;
|
|
3479
3516
|
const setIsExtendRequested = (next) => {
|
|
3480
3517
|
setUncontrolledExtended(next);
|
|
@@ -3495,7 +3532,7 @@ function PlayerPanel({
|
|
|
3495
3532
|
onClick: () => setIsExtendRequested(true),
|
|
3496
3533
|
expanded: false
|
|
3497
3534
|
} : null;
|
|
3498
|
-
return /* @__PURE__ */ (0,
|
|
3535
|
+
return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(
|
|
3499
3536
|
"div",
|
|
3500
3537
|
{
|
|
3501
3538
|
id: PANEL_ID,
|
|
@@ -3508,7 +3545,7 @@ function PlayerPanel({
|
|
|
3508
3545
|
),
|
|
3509
3546
|
style: isExtended ? { width: extendedWidth } : void 0,
|
|
3510
3547
|
children: [
|
|
3511
|
-
/* @__PURE__ */ (0,
|
|
3548
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
3512
3549
|
import_ui18.PanelEdgeTab,
|
|
3513
3550
|
{
|
|
3514
3551
|
side: "right",
|
|
@@ -3518,7 +3555,7 @@ function PlayerPanel({
|
|
|
3518
3555
|
className: "absolute right-full top-1/2 -translate-y-1/2"
|
|
3519
3556
|
}
|
|
3520
3557
|
),
|
|
3521
|
-
/* @__PURE__ */ (0,
|
|
3558
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(
|
|
3522
3559
|
"div",
|
|
3523
3560
|
{
|
|
3524
3561
|
className: (0, import_tailwind_merge.twMerge)(
|
|
@@ -3526,20 +3563,20 @@ function PlayerPanel({
|
|
|
3526
3563
|
isExtended ? "flex-row" : "flex-col"
|
|
3527
3564
|
),
|
|
3528
3565
|
children: [
|
|
3529
|
-
/* @__PURE__ */ (0,
|
|
3530
|
-
/* @__PURE__ */ (0,
|
|
3531
|
-
/* @__PURE__ */ (0,
|
|
3532
|
-
/* @__PURE__ */ (0,
|
|
3533
|
-
playerTrackObject?.loadError && /* @__PURE__ */ (0,
|
|
3566
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
|
|
3567
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
|
|
3568
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "relative w-full shrink-0", children: [
|
|
3569
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_player13.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
|
|
3570
|
+
playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: [
|
|
3534
3571
|
playerTrackObject.loadError,
|
|
3535
|
-
/* @__PURE__ */ (0,
|
|
3572
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)("button", { type: "button", className: "ml-2 underline", onClick: () => {
|
|
3536
3573
|
void retryPlayback();
|
|
3537
3574
|
}, children: "Retry" })
|
|
3538
3575
|
] })
|
|
3539
3576
|
] }),
|
|
3540
|
-
/* @__PURE__ */ (0,
|
|
3577
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)(PlayerControls, {})
|
|
3541
3578
|
] }),
|
|
3542
|
-
/* @__PURE__ */ (0,
|
|
3579
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
3543
3580
|
TrackListSidebar,
|
|
3544
3581
|
{
|
|
3545
3582
|
layout: "inline",
|
|
@@ -3566,11 +3603,11 @@ function PlayerPanel({
|
|
|
3566
3603
|
var import_uuid = require("uuid");
|
|
3567
3604
|
var import_ai = require("react-icons/ai");
|
|
3568
3605
|
var import_genre_tree33 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3569
|
-
var
|
|
3606
|
+
var import_jsx_runtime44 = require("react/jsx-runtime");
|
|
3570
3607
|
function Rating({ rating, handleChange }) {
|
|
3571
3608
|
const instanceId = (0, import_uuid.v4)();
|
|
3572
|
-
return /* @__PURE__ */ (0,
|
|
3573
|
-
/* @__PURE__ */ (0,
|
|
3609
|
+
return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
|
|
3610
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
3574
3611
|
"input",
|
|
3575
3612
|
{
|
|
3576
3613
|
id: `star-${instanceId}-${i}`,
|
|
@@ -3582,16 +3619,16 @@ function Rating({ rating, handleChange }) {
|
|
|
3582
3619
|
onChange: handleChange
|
|
3583
3620
|
}
|
|
3584
3621
|
),
|
|
3585
|
-
/* @__PURE__ */ (0,
|
|
3586
|
-
/* @__PURE__ */ (0,
|
|
3622
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "flex", children: [
|
|
3623
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
3587
3624
|
import_ai.AiOutlineStar,
|
|
3588
3625
|
{
|
|
3589
3626
|
className: `w-7 h-7 absolute top-0 left-0 ${rating === import_genre_tree33.FORM_RATING_NULL_VALUE ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
|
|
3590
3627
|
}
|
|
3591
3628
|
),
|
|
3592
|
-
/* @__PURE__ */ (0,
|
|
3629
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { className: "flex-grow w-8" }),
|
|
3593
3630
|
" "
|
|
3594
|
-
] }) : /* @__PURE__ */ (0,
|
|
3631
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
3595
3632
|
import_ai.AiFillStar,
|
|
3596
3633
|
{
|
|
3597
3634
|
className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
|