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