@behindthemusictree/app-kit-ui 26.1.0 → 29.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-DKQQLLMY.js +246 -0
- package/dist/chunk-DKQQLLMY.js.map +1 -0
- package/dist/{chunk-KPWV4LEJ.js → chunk-TFLX6C5Q.js} +1551 -863
- package/dist/chunk-TFLX6C5Q.js.map +1 -0
- package/dist/{chunk-ZLCRD7OK.js → chunk-XUC4UJDQ.js} +2 -2
- package/dist/genre-tree/index.cjs +1954 -1164
- package/dist/genre-tree/index.cjs.map +1 -1
- package/dist/genre-tree/index.d.ts +151 -17
- package/dist/genre-tree/index.js +14 -4
- package/dist/genre-tree/styles.css +80 -0
- package/dist/index.cjs +1855 -1065
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +18 -6
- package/dist/popup/index.cjs +179 -73
- package/dist/popup/index.cjs.map +1 -1
- package/dist/popup/index.d.ts +22 -2
- package/dist/popup/index.js +5 -3
- package/package.json +7 -7
- package/dist/chunk-KPWV4LEJ.js.map +0 -1
- package/dist/chunk-VDXUW7PP.js +0 -141
- package/dist/chunk-VDXUW7PP.js.map +0 -1
- /package/dist/{chunk-ZLCRD7OK.js.map → chunk-XUC4UJDQ.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -24,13 +24,16 @@ __export(src_exports, {
|
|
|
24
24
|
AuthErrorPopup: () => AuthErrorPopup,
|
|
25
25
|
AuthPopup: () => AuthPopup,
|
|
26
26
|
BasePopup: () => BasePopup,
|
|
27
|
+
GenreDetailPanel: () => GenreDetailPanel,
|
|
28
|
+
GenreInfoPanelBody: () => GenreInfoPanelBody,
|
|
29
|
+
GenreNodeActionBand: () => GenreNodeActionBand,
|
|
27
30
|
GenrePlaylistTreePerRoot: () => GenrePlaylistTreePerRoot,
|
|
28
31
|
GenreSearch: () => GenreSearch,
|
|
29
|
-
GenreTreeOutlineSkeleton: () =>
|
|
30
|
-
GenreTreeSkeleton: () =>
|
|
32
|
+
GenreTreeOutlineSkeleton: () => import_genre_tree_view6.GenreTreeOutlineSkeleton,
|
|
33
|
+
GenreTreeSkeleton: () => import_genre_tree_view6.GenreTreeSkeleton,
|
|
31
34
|
GenreTreeView: () => GenreTreeView,
|
|
32
|
-
GenreTreeViewSkeleton: () =>
|
|
33
|
-
GenreTreeWheelSkeleton: () =>
|
|
35
|
+
GenreTreeViewSkeleton: () => import_genre_tree_view6.GenreTreeViewSkeleton,
|
|
36
|
+
GenreTreeWheelSkeleton: () => import_genre_tree_view6.GenreTreeWheelSkeleton,
|
|
34
37
|
InternalErrorPopup: () => InternalErrorPopup,
|
|
35
38
|
NetworkErrorPopup: () => NetworkErrorPopup,
|
|
36
39
|
PanelEdgeTab: () => PanelEdgeTab,
|
|
@@ -39,10 +42,13 @@ __export(src_exports, {
|
|
|
39
42
|
PopupTitle: () => PopupTitle,
|
|
40
43
|
Rating: () => Rating,
|
|
41
44
|
SpotifyAuthErrorPopup: () => SpotifyAuthErrorPopup,
|
|
45
|
+
Toast: () => Toast,
|
|
42
46
|
TrackItem: () => TrackItem,
|
|
43
47
|
TrackListSidebar: () => TrackListSidebar,
|
|
44
48
|
TrackPositionPlayPause: () => TrackPositionPlayPause,
|
|
45
|
-
TrackUploadPopup: () => TrackUploadPopup
|
|
49
|
+
TrackUploadPopup: () => TrackUploadPopup,
|
|
50
|
+
useGenrePlaylistPlayback: () => useGenrePlaylistPlayback,
|
|
51
|
+
useLeftDetailPanelLayout: () => useLeftDetailPanelLayout
|
|
46
52
|
});
|
|
47
53
|
module.exports = __toCommonJS(src_exports);
|
|
48
54
|
|
|
@@ -50,29 +56,46 @@ module.exports = __toCommonJS(src_exports);
|
|
|
50
56
|
var import_lucide_react = require("lucide-react");
|
|
51
57
|
var import_ui = require("@behindthemusictree/ui");
|
|
52
58
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
53
|
-
function PopupTitle({
|
|
54
|
-
|
|
59
|
+
function PopupTitle({
|
|
60
|
+
title,
|
|
61
|
+
onClose,
|
|
62
|
+
isDismissable,
|
|
63
|
+
icon: Icon,
|
|
64
|
+
className: className2 = "",
|
|
65
|
+
titleClassName = "",
|
|
66
|
+
closeButtonClassName = ""
|
|
67
|
+
}) {
|
|
68
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `popup-title flex items-center justify-between bg-black p-4 rounded-t-lg ${className2}`, children: [
|
|
55
69
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
56
70
|
Icon && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { className: "h-5 w-5 text-white" }),
|
|
57
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { id: "popup-title", className:
|
|
71
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { id: "popup-title", className: `text-white font-semibold ${titleClassName}`, children: title })
|
|
58
72
|
] }),
|
|
59
|
-
isDismissable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
73
|
+
isDismissable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
74
|
+
import_ui.Button,
|
|
75
|
+
{
|
|
76
|
+
onClick: onClose,
|
|
77
|
+
className: `h-8 w-8 text-white hover:text-white/80 ${closeButtonClassName}`,
|
|
78
|
+
"aria-label": "Close popup",
|
|
79
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.X, { className: "h-4 w-4" })
|
|
80
|
+
}
|
|
81
|
+
)
|
|
60
82
|
] });
|
|
61
83
|
}
|
|
62
84
|
|
|
63
85
|
// src/popup/PopupButtons.tsx
|
|
64
86
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
65
|
-
function PopupButtons({ children, alignment = "center", className = "" }) {
|
|
87
|
+
function PopupButtons({ children, alignment = "center", className: className2 = "" }) {
|
|
66
88
|
const alignmentClasses = {
|
|
67
89
|
left: "justify-start",
|
|
68
90
|
center: "justify-center",
|
|
69
91
|
right: "justify-end"
|
|
70
92
|
};
|
|
71
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: `flex gap-3 ${alignmentClasses[alignment]} ${
|
|
93
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: `flex gap-3 ${alignmentClasses[alignment]} ${className2}`, children });
|
|
72
94
|
}
|
|
73
95
|
|
|
74
96
|
// src/popup/BasePopup.tsx
|
|
75
97
|
var import_react_dom = require("react-dom");
|
|
98
|
+
var import_react = require("react");
|
|
76
99
|
var import_ui2 = require("@behindthemusictree/ui");
|
|
77
100
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
78
101
|
var typeClasses = {
|
|
@@ -96,8 +119,13 @@ function BasePopup(props) {
|
|
|
96
119
|
type = "default",
|
|
97
120
|
icon,
|
|
98
121
|
isDismissable = true,
|
|
99
|
-
className,
|
|
122
|
+
className: className2,
|
|
123
|
+
overlayClassName = "",
|
|
124
|
+
titleClassName = "",
|
|
125
|
+
titleTextClassName = "",
|
|
126
|
+
closeButtonClassName = "",
|
|
100
127
|
fitContent = false,
|
|
128
|
+
contentClassName = "",
|
|
101
129
|
showOkButton = false,
|
|
102
130
|
showCancelButton = false,
|
|
103
131
|
okButtonText = "OK",
|
|
@@ -106,8 +134,49 @@ function BasePopup(props) {
|
|
|
106
134
|
okButtonDisabled = false,
|
|
107
135
|
onCancel,
|
|
108
136
|
buttonAlignment = "center",
|
|
109
|
-
topOffset = 0
|
|
137
|
+
topOffset = 0,
|
|
138
|
+
trapFocus = false,
|
|
139
|
+
closeOnEscape = false,
|
|
140
|
+
closeOnBackdrop = false
|
|
110
141
|
} = props;
|
|
142
|
+
const dialogRef = (0, import_react.useRef)(null);
|
|
143
|
+
(0, import_react.useEffect)(() => {
|
|
144
|
+
const previousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
145
|
+
const dialog = dialogRef.current;
|
|
146
|
+
const focusableSelector = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
147
|
+
const getFocusable = () => Array.from(dialog?.querySelectorAll(focusableSelector) ?? []).filter(
|
|
148
|
+
(element) => !element.hasAttribute("hidden") && element.getAttribute("aria-hidden") !== "true"
|
|
149
|
+
);
|
|
150
|
+
if (trapFocus) (getFocusable()[0] ?? dialog)?.focus();
|
|
151
|
+
const handleKeyDown = (event) => {
|
|
152
|
+
if (event.key === "Escape" && closeOnEscape && isDismissable) {
|
|
153
|
+
event.preventDefault();
|
|
154
|
+
onClose?.();
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (!trapFocus || event.key !== "Tab") return;
|
|
158
|
+
const focusable = getFocusable();
|
|
159
|
+
if (focusable.length === 0) {
|
|
160
|
+
event.preventDefault();
|
|
161
|
+
dialog?.focus();
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const first = focusable[0];
|
|
165
|
+
const last = focusable[focusable.length - 1];
|
|
166
|
+
if (event.shiftKey && (document.activeElement === first || !dialog?.contains(document.activeElement))) {
|
|
167
|
+
event.preventDefault();
|
|
168
|
+
last.focus();
|
|
169
|
+
} else if (!event.shiftKey && (document.activeElement === last || !dialog?.contains(document.activeElement))) {
|
|
170
|
+
event.preventDefault();
|
|
171
|
+
first.focus();
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
if (trapFocus || closeOnEscape) document.addEventListener("keydown", handleKeyDown);
|
|
175
|
+
return () => {
|
|
176
|
+
if (trapFocus || closeOnEscape) document.removeEventListener("keydown", handleKeyDown);
|
|
177
|
+
if (trapFocus && previousFocus?.isConnected) previousFocus.focus();
|
|
178
|
+
};
|
|
179
|
+
}, [closeOnEscape, isDismissable, onClose, trapFocus]);
|
|
111
180
|
const baseClasses = "bg-white rounded-lg";
|
|
112
181
|
const renderButtons = () => {
|
|
113
182
|
if (!showOkButton && !showCancelButton) return null;
|
|
@@ -121,7 +190,7 @@ function BasePopup(props) {
|
|
|
121
190
|
const popupComponent = /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
122
191
|
"div",
|
|
123
192
|
{
|
|
124
|
-
className:
|
|
193
|
+
className: `fixed z-50 flex items-center justify-center bg-transparent ${overlayClassName}`,
|
|
125
194
|
style: {
|
|
126
195
|
top: topOffset,
|
|
127
196
|
left: 0,
|
|
@@ -131,15 +200,29 @@ function BasePopup(props) {
|
|
|
131
200
|
role: "dialog",
|
|
132
201
|
"aria-modal": "true",
|
|
133
202
|
"aria-labelledby": "popup-title",
|
|
203
|
+
onClick: closeOnBackdrop && isDismissable ? onClose : void 0,
|
|
134
204
|
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
135
205
|
"div",
|
|
136
206
|
{
|
|
137
|
-
|
|
207
|
+
ref: dialogRef,
|
|
208
|
+
tabIndex: trapFocus ? -1 : void 0,
|
|
209
|
+
className: `${baseClasses} ${fitContent ? typeClasses[type].replace("w-full", "w-fit") : typeClasses[type]} ${className2 || ""}`,
|
|
138
210
|
onClick: (e) => e.stopPropagation(),
|
|
139
211
|
children: [
|
|
140
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
141
|
-
|
|
142
|
-
|
|
212
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
213
|
+
PopupTitle,
|
|
214
|
+
{
|
|
215
|
+
title,
|
|
216
|
+
onClose: onClose ?? (() => {
|
|
217
|
+
}),
|
|
218
|
+
isDismissable,
|
|
219
|
+
icon,
|
|
220
|
+
className: titleClassName,
|
|
221
|
+
titleClassName: titleTextClassName,
|
|
222
|
+
closeButtonClassName
|
|
223
|
+
}
|
|
224
|
+
),
|
|
225
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `popup-content rounded-b-lg p-4 ${contentBgClasses[type] ?? ""} ${contentClassName}`, children: [
|
|
143
226
|
children,
|
|
144
227
|
renderButtons()
|
|
145
228
|
] })
|
|
@@ -152,21 +235,48 @@ function BasePopup(props) {
|
|
|
152
235
|
}
|
|
153
236
|
var BasePopup_default = BasePopup;
|
|
154
237
|
|
|
238
|
+
// src/popup/Toast.tsx
|
|
239
|
+
var import_react2 = require("react");
|
|
240
|
+
var import_react_dom2 = require("react-dom");
|
|
241
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
242
|
+
function Toast({ message, onClose, durationMs = 2200 }) {
|
|
243
|
+
const [mounted, setMounted] = (0, import_react2.useState)(false);
|
|
244
|
+
(0, import_react2.useEffect)(() => {
|
|
245
|
+
setMounted(true);
|
|
246
|
+
const timeout = window.setTimeout(onClose, durationMs);
|
|
247
|
+
return () => window.clearTimeout(timeout);
|
|
248
|
+
}, [durationMs, onClose]);
|
|
249
|
+
if (!mounted) return null;
|
|
250
|
+
return (0, import_react_dom2.createPortal)(
|
|
251
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "pointer-events-none fixed inset-x-0 bottom-8 z-[60] flex justify-center px-4", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
252
|
+
"div",
|
|
253
|
+
{
|
|
254
|
+
role: "status",
|
|
255
|
+
"aria-live": "polite",
|
|
256
|
+
"aria-atomic": "true",
|
|
257
|
+
className: "pointer-events-auto w-full max-w-md rounded-md bg-[var(--color-surface)] px-4 py-4 text-sm text-[var(--color-text)] shadow-lg ring-1 ring-black/10",
|
|
258
|
+
children: message
|
|
259
|
+
}
|
|
260
|
+
) }),
|
|
261
|
+
document.body
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
155
265
|
// src/popup/TrackUploadPopup.tsx
|
|
156
266
|
var import_md = require("react-icons/md");
|
|
157
267
|
var import_popup = require("@behindthemusictree/app-kit/popup");
|
|
158
268
|
var import_track_noun = require("@behindthemusictree/app-kit/track-noun");
|
|
159
|
-
var
|
|
269
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
160
270
|
function getStatusIcon(status) {
|
|
161
271
|
switch (status) {
|
|
162
272
|
case "success":
|
|
163
|
-
return /* @__PURE__ */ (0,
|
|
273
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_md.MdCheckCircle, { className: "text-[var(--color-green-500)]", size: 20 });
|
|
164
274
|
case "error":
|
|
165
|
-
return /* @__PURE__ */ (0,
|
|
275
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_md.MdError, { className: "text-[var(--color-red-500)]", size: 20 });
|
|
166
276
|
case "uploading":
|
|
167
|
-
return /* @__PURE__ */ (0,
|
|
277
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_md.MdUpload, { className: "text-[var(--color-blue-500)] animate-pulse", size: 20 });
|
|
168
278
|
default:
|
|
169
|
-
return /* @__PURE__ */ (0,
|
|
279
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "w-5 h-5 rounded-full border-2 border-[var(--color-neutral-300)]" });
|
|
170
280
|
}
|
|
171
281
|
}
|
|
172
282
|
function getStatusText(status) {
|
|
@@ -199,7 +309,7 @@ function TrackUploadPopup({
|
|
|
199
309
|
});
|
|
200
310
|
const displayTotal = items.length > 0 ? items.length : files.length;
|
|
201
311
|
const trackNoun = (0, import_track_noun.useTrackNoun)();
|
|
202
|
-
return /* @__PURE__ */ (0,
|
|
312
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
203
313
|
BasePopup,
|
|
204
314
|
{
|
|
205
315
|
...rest,
|
|
@@ -210,31 +320,31 @@ function TrackUploadPopup({
|
|
|
210
320
|
okButtonText: "OK",
|
|
211
321
|
okButtonDisabled: isUploading || !allComplete,
|
|
212
322
|
onOk: onClose,
|
|
213
|
-
children: /* @__PURE__ */ (0,
|
|
214
|
-
items.map((item) => /* @__PURE__ */ (0,
|
|
215
|
-
/* @__PURE__ */ (0,
|
|
216
|
-
/* @__PURE__ */ (0,
|
|
323
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "space-y-4", children: [
|
|
324
|
+
items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "border rounded-lg p-4", children: [
|
|
325
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center justify-between mb-2", children: [
|
|
326
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center space-x-3", children: [
|
|
217
327
|
getStatusIcon(item.status),
|
|
218
|
-
/* @__PURE__ */ (0,
|
|
219
|
-
/* @__PURE__ */ (0,
|
|
220
|
-
/* @__PURE__ */ (0,
|
|
328
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [
|
|
329
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "font-medium text-sm truncate max-w-xs", title: item.file.name, children: item.file.name }),
|
|
330
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "text-xs text-[var(--color-neutral-500)]", children: [
|
|
221
331
|
(item.file.size / 1024 / 1024).toFixed(1),
|
|
222
332
|
" MB"
|
|
223
333
|
] })
|
|
224
334
|
] })
|
|
225
335
|
] }),
|
|
226
|
-
/* @__PURE__ */ (0,
|
|
336
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "text-sm text-[var(--color-neutral-600)]", children: getStatusText(item.status) })
|
|
227
337
|
] }),
|
|
228
|
-
item.status === "uploading" && /* @__PURE__ */ (0,
|
|
338
|
+
item.status === "uploading" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "w-full bg-[var(--color-neutral-200)] rounded-full h-2", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
229
339
|
"div",
|
|
230
340
|
{
|
|
231
341
|
className: "bg-[var(--color-blue-500)] h-2 rounded-full transition-all duration-300",
|
|
232
342
|
style: { width: `${item.progress}%` }
|
|
233
343
|
}
|
|
234
344
|
) }),
|
|
235
|
-
item.status === "error" && item.error && /* @__PURE__ */ (0,
|
|
345
|
+
item.status === "error" && item.error && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "text-[var(--color-red-500)] text-sm mt-2", children: item.error.name === "InvalidInputError" ? "Upload failed due to invalid file data. Please check your file and try again." : item.error.name === "ZodError" ? "Upload failed due to a server error. Please try again later." : item.error.message })
|
|
236
346
|
] }, item.id)),
|
|
237
|
-
allComplete && /* @__PURE__ */ (0,
|
|
347
|
+
allComplete && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "flex justify-between items-center", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "text-sm text-[var(--color-neutral-600)]", children: [
|
|
238
348
|
successfulCount,
|
|
239
349
|
" successful, ",
|
|
240
350
|
errorCount,
|
|
@@ -248,18 +358,18 @@ function TrackUploadPopup({
|
|
|
248
358
|
// src/popup/AuthErrorPopup.tsx
|
|
249
359
|
var import_lucide_react2 = require("lucide-react");
|
|
250
360
|
var import_ui3 = require("@behindthemusictree/ui");
|
|
251
|
-
var
|
|
361
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
252
362
|
function AuthErrorPopup({ message, onClose, ...rest }) {
|
|
253
|
-
return /* @__PURE__ */ (0,
|
|
363
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
254
364
|
BasePopup,
|
|
255
365
|
{
|
|
256
366
|
...rest,
|
|
257
367
|
title: "Sign-in error",
|
|
258
368
|
isDismissable: true,
|
|
259
369
|
icon: import_lucide_react2.AlertCircle,
|
|
260
|
-
children: /* @__PURE__ */ (0,
|
|
261
|
-
/* @__PURE__ */ (0,
|
|
262
|
-
/* @__PURE__ */ (0,
|
|
370
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-col items-center space-y-6 py-4", children: [
|
|
371
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-center text-lg font-medium text-[var(--color-text)]", children: message }),
|
|
372
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ui3.Button, { onClick: onClose, variant: "secondary", className: "w-full max-w-xs", children: "Close" })
|
|
263
373
|
] })
|
|
264
374
|
}
|
|
265
375
|
);
|
|
@@ -267,23 +377,23 @@ function AuthErrorPopup({ message, onClose, ...rest }) {
|
|
|
267
377
|
|
|
268
378
|
// src/popup/InternalErrorPopup.tsx
|
|
269
379
|
var import_lucide_react3 = require("lucide-react");
|
|
270
|
-
var
|
|
380
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
271
381
|
function InternalErrorPopup({ errorCode, contactEmail, ...rest }) {
|
|
272
|
-
return /* @__PURE__ */ (0,
|
|
382
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
273
383
|
BasePopup,
|
|
274
384
|
{
|
|
275
385
|
...rest,
|
|
276
386
|
title: "Internal Error",
|
|
277
387
|
isDismissable: false,
|
|
278
388
|
icon: import_lucide_react3.AlertTriangle,
|
|
279
|
-
children: /* @__PURE__ */ (0,
|
|
280
|
-
/* @__PURE__ */ (0,
|
|
281
|
-
/* @__PURE__ */ (0,
|
|
389
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col items-center space-y-6 py-4", children: [
|
|
390
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react3.AlertCircle, { className: "h-16 w-16 text-[var(--color-red-500)]", strokeWidth: 1.5 }),
|
|
391
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("p", { className: "text-center text-[var(--color-neutral-600)]", children: [
|
|
282
392
|
"Please try again. If the problem persists, contact us at",
|
|
283
393
|
" ",
|
|
284
|
-
/* @__PURE__ */ (0,
|
|
394
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("a", { href: `mailto:${contactEmail}`, className: "text-[var(--color-primary)] hover:text-[var(--color-primary-hover)]", children: contactEmail })
|
|
285
395
|
] }) }),
|
|
286
|
-
errorCode && /* @__PURE__ */ (0,
|
|
396
|
+
errorCode && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "text-sm text-[var(--color-neutral-500)] bg-[var(--color-neutral-100)] p-3 rounded-lg border border-[var(--color-neutral-100)]", children: [
|
|
287
397
|
"Error Code: ",
|
|
288
398
|
errorCode
|
|
289
399
|
] })
|
|
@@ -294,18 +404,18 @@ function InternalErrorPopup({ errorCode, contactEmail, ...rest }) {
|
|
|
294
404
|
|
|
295
405
|
// src/popup/NetworkErrorPopup.tsx
|
|
296
406
|
var import_lucide_react4 = require("lucide-react");
|
|
297
|
-
var
|
|
407
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
298
408
|
function NetworkErrorPopup(props) {
|
|
299
|
-
return /* @__PURE__ */ (0,
|
|
409
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
300
410
|
BasePopup,
|
|
301
411
|
{
|
|
302
412
|
...props,
|
|
303
413
|
title: "Network Error",
|
|
304
414
|
isDismissable: false,
|
|
305
415
|
icon: import_lucide_react4.AlertTriangle,
|
|
306
|
-
children: /* @__PURE__ */ (0,
|
|
307
|
-
/* @__PURE__ */ (0,
|
|
308
|
-
/* @__PURE__ */ (0,
|
|
416
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col items-center space-y-6 py-4", children: [
|
|
417
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.AlertTriangle, { className: "h-16 w-16 text-[var(--color-neutral-600)]", strokeWidth: 1.5 }),
|
|
418
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-center text-[var(--color-neutral-700)]", children: "It seems that you are not connected to the internet. Please check your connection and try again." })
|
|
309
419
|
] })
|
|
310
420
|
}
|
|
311
421
|
);
|
|
@@ -316,7 +426,7 @@ var import_lucide_react5 = require("lucide-react");
|
|
|
316
426
|
var import_ui4 = require("@behindthemusictree/ui");
|
|
317
427
|
var import_fa = require("react-icons/fa");
|
|
318
428
|
var import_fc = require("react-icons/fc");
|
|
319
|
-
var
|
|
429
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
320
430
|
function AuthPopup({
|
|
321
431
|
handleSpotifyOAuth,
|
|
322
432
|
handleGoogleOAuth,
|
|
@@ -328,7 +438,7 @@ function AuthPopup({
|
|
|
328
438
|
...rest
|
|
329
439
|
}) {
|
|
330
440
|
const showGoogle = !spotifyOnly && handleGoogleOAuth;
|
|
331
|
-
return /* @__PURE__ */ (0,
|
|
441
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
332
442
|
BasePopup,
|
|
333
443
|
{
|
|
334
444
|
...rest,
|
|
@@ -336,32 +446,32 @@ function AuthPopup({
|
|
|
336
446
|
isDismissable: false,
|
|
337
447
|
icon: import_lucide_react5.User,
|
|
338
448
|
type: "auth",
|
|
339
|
-
children: /* @__PURE__ */ (0,
|
|
340
|
-
/* @__PURE__ */ (0,
|
|
341
|
-
message && /* @__PURE__ */ (0,
|
|
342
|
-
/* @__PURE__ */ (0,
|
|
449
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col items-center space-y-7", children: [
|
|
450
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "px-2 text-center", children: [
|
|
451
|
+
message && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "mb-3 text-base font-medium leading-relaxed text-amber-200", children: message }),
|
|
452
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "text-lg font-medium leading-relaxed text-white", children: spotifyOnly ? spotifyOnlyDescription : defaultDescription })
|
|
343
453
|
] }),
|
|
344
|
-
/* @__PURE__ */ (0,
|
|
345
|
-
/* @__PURE__ */ (0,
|
|
454
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex w-full flex-col items-center gap-3", children: [
|
|
455
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
346
456
|
import_ui4.Button,
|
|
347
457
|
{
|
|
348
458
|
onClick: () => handleSpotifyOAuth(redirectAfterAuthPath),
|
|
349
459
|
className: "relative mb-2 w-fit transform bg-green-600 px-8 py-3 text-white shadow-lg transition-all duration-200 hover:scale-[1.02] hover:bg-green-700 hover:shadow-xl",
|
|
350
|
-
children: /* @__PURE__ */ (0,
|
|
351
|
-
/* @__PURE__ */ (0,
|
|
352
|
-
/* @__PURE__ */ (0,
|
|
460
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "flex items-center justify-center", children: [
|
|
461
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_fa.FaSpotify, { className: "absolute left-6 text-3xl" }),
|
|
462
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "w-full pl-14 pr-8 text-center text-lg font-medium", children: "Sign in with Spotify" })
|
|
353
463
|
] })
|
|
354
464
|
}
|
|
355
465
|
),
|
|
356
|
-
showGoogle && /* @__PURE__ */ (0,
|
|
466
|
+
showGoogle && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
357
467
|
import_ui4.Button,
|
|
358
468
|
{
|
|
359
469
|
variant: "outline",
|
|
360
470
|
onClick: () => handleGoogleOAuth(redirectAfterAuthPath),
|
|
361
471
|
className: "relative w-fit transform border-white bg-white px-8 py-3 text-black shadow-lg transition-all duration-200 hover:scale-[1.02] hover:bg-[var(--color-neutral-100)] hover:shadow-xl",
|
|
362
|
-
children: /* @__PURE__ */ (0,
|
|
363
|
-
/* @__PURE__ */ (0,
|
|
364
|
-
/* @__PURE__ */ (0,
|
|
472
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "flex items-center justify-center", children: [
|
|
473
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_fc.FcGoogle, { className: "absolute left-6 text-2xl" }),
|
|
474
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "w-full pl-14 pr-8 text-center text-lg font-medium text-black", children: "Sign in with Google" })
|
|
365
475
|
] })
|
|
366
476
|
}
|
|
367
477
|
)
|
|
@@ -376,7 +486,7 @@ var import_fa2 = require("react-icons/fa");
|
|
|
376
486
|
var import_ui5 = require("@behindthemusictree/ui");
|
|
377
487
|
var import_lucide_react6 = require("lucide-react");
|
|
378
488
|
var import_transport = require("@behindthemusictree/app-kit/transport");
|
|
379
|
-
var
|
|
489
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
380
490
|
function SpotifyAuthErrorPopup({
|
|
381
491
|
message,
|
|
382
492
|
details,
|
|
@@ -385,24 +495,24 @@ function SpotifyAuthErrorPopup({
|
|
|
385
495
|
...rest
|
|
386
496
|
}) {
|
|
387
497
|
const requestAccessHref = (0, import_transport.getSpotifyAllowlistMailtoHref)(contactEmail);
|
|
388
|
-
return /* @__PURE__ */ (0,
|
|
498
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
389
499
|
BasePopup,
|
|
390
500
|
{
|
|
391
501
|
...rest,
|
|
392
502
|
title: "Authentication Failed",
|
|
393
503
|
isDismissable: true,
|
|
394
504
|
icon: import_lucide_react6.User,
|
|
395
|
-
children: /* @__PURE__ */ (0,
|
|
396
|
-
/* @__PURE__ */ (0,
|
|
397
|
-
/* @__PURE__ */ (0,
|
|
398
|
-
/* @__PURE__ */ (0,
|
|
399
|
-
details && /* @__PURE__ */ (0,
|
|
400
|
-
/* @__PURE__ */ (0,
|
|
401
|
-
/* @__PURE__ */ (0,
|
|
402
|
-
requestAccessHref && contactEmail ? /* @__PURE__ */ (0,
|
|
505
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col items-center space-y-8 py-4", children: [
|
|
506
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_fa2.FaSpotify, { className: "text-[#1DB954] text-7xl" }),
|
|
507
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "space-y-3 text-center", children: [
|
|
508
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-xl font-medium text-[var(--color-text)]", children: message }),
|
|
509
|
+
details && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-sm text-[var(--color-neutral-500)] bg-[var(--color-neutral-100)] p-3 rounded-lg border border-[var(--color-neutral-100)]", children: details }),
|
|
510
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "space-y-2 text-center", children: [
|
|
511
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-base text-[var(--color-neutral-700)]", children: "The Spotify app is in development mode, so only allowlisted accounts can connect." }),
|
|
512
|
+
requestAccessHref && contactEmail ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("p", { className: "text-base text-[var(--color-neutral-700)]", children: [
|
|
403
513
|
"Request access by emailing your",
|
|
404
514
|
" ",
|
|
405
|
-
/* @__PURE__ */ (0,
|
|
515
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
406
516
|
"a",
|
|
407
517
|
{
|
|
408
518
|
href: requestAccessHref,
|
|
@@ -413,7 +523,7 @@ function SpotifyAuthErrorPopup({
|
|
|
413
523
|
" ",
|
|
414
524
|
"to",
|
|
415
525
|
" ",
|
|
416
|
-
/* @__PURE__ */ (0,
|
|
526
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
417
527
|
"a",
|
|
418
528
|
{
|
|
419
529
|
href: requestAccessHref,
|
|
@@ -422,16 +532,16 @@ function SpotifyAuthErrorPopup({
|
|
|
422
532
|
}
|
|
423
533
|
),
|
|
424
534
|
"."
|
|
425
|
-
] }) : /* @__PURE__ */ (0,
|
|
535
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-base text-[var(--color-neutral-700)]", children: "To request access, contact the app owner." })
|
|
426
536
|
] })
|
|
427
537
|
] }),
|
|
428
|
-
/* @__PURE__ */ (0,
|
|
538
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
429
539
|
import_ui5.Button,
|
|
430
540
|
{
|
|
431
541
|
onClick: onClose,
|
|
432
542
|
className: "bg-[#1DB954] hover:bg-[#1DB954]/90 text-white w-full max-w-xs transform transition-all duration-200 hover:scale-[1.02] shadow-lg hover:shadow-xl",
|
|
433
543
|
children: [
|
|
434
|
-
/* @__PURE__ */ (0,
|
|
544
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_fa2.FaSpotify, { className: "mr-2 text-lg" }),
|
|
435
545
|
"Try Again"
|
|
436
546
|
]
|
|
437
547
|
}
|
|
@@ -443,16 +553,16 @@ function SpotifyAuthErrorPopup({
|
|
|
443
553
|
|
|
444
554
|
// src/auth/AuthCallbackHandler.tsx
|
|
445
555
|
var import_auth = require("@behindthemusictree/app-kit/auth");
|
|
446
|
-
var
|
|
556
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
447
557
|
function AuthCallbackHandler({ renderStatus, ...options }) {
|
|
448
558
|
const state = (0, import_auth.useAuthCallback)(options);
|
|
449
559
|
if (state.status === null) return null;
|
|
450
|
-
if (renderStatus) return /* @__PURE__ */ (0,
|
|
560
|
+
if (renderStatus) return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_jsx_runtime11.Fragment, { children: renderStatus(state) });
|
|
451
561
|
if (state.status === "error") {
|
|
452
|
-
return /* @__PURE__ */ (0,
|
|
453
|
-
/* @__PURE__ */ (0,
|
|
454
|
-
/* @__PURE__ */ (0,
|
|
455
|
-
/* @__PURE__ */ (0,
|
|
562
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "fixed inset-0 z-[100] flex flex-col items-center justify-center bg-[var(--color-neutral-100)] p-4", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "w-full max-w-md rounded-lg border border-[color-mix(in_srgb,var(--color-red-500)_20%,transparent)] bg-[color-mix(in_srgb,var(--color-red-500)_10%,transparent)] p-4", children: [
|
|
563
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h2", { className: "mb-2 font-semibold text-[var(--color-red-500)]", children: "Authentication Error" }),
|
|
564
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "text-[color-mix(in_srgb,var(--color-red-500)_80%,transparent)]", children: state.message }),
|
|
565
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
456
566
|
"button",
|
|
457
567
|
{
|
|
458
568
|
type: "button",
|
|
@@ -463,439 +573,116 @@ function AuthCallbackHandler({ renderStatus, ...options }) {
|
|
|
463
573
|
)
|
|
464
574
|
] }) });
|
|
465
575
|
}
|
|
466
|
-
return /* @__PURE__ */ (0,
|
|
467
|
-
/* @__PURE__ */ (0,
|
|
468
|
-
/* @__PURE__ */ (0,
|
|
576
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "fixed inset-0 z-[100] flex flex-col items-center justify-center bg-[var(--color-neutral-100)]", "data-page": "auth-callback-handler", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "text-center", children: [
|
|
577
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h1", { className: "mb-4 text-2xl font-bold", children: "Connecting\u2026" }),
|
|
578
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "text-[var(--color-neutral-600)]", children: "Completing sign-in." })
|
|
469
579
|
] }) });
|
|
470
580
|
}
|
|
471
581
|
|
|
472
582
|
// src/genre-tree/GenreTreeView.tsx
|
|
473
|
-
var
|
|
583
|
+
var import_react15 = require("react");
|
|
474
584
|
var import_react_query3 = require("@tanstack/react-query");
|
|
475
|
-
var
|
|
476
|
-
var
|
|
585
|
+
var import_lucide_react15 = require("lucide-react");
|
|
586
|
+
var import_ui16 = require("@behindthemusictree/ui");
|
|
477
587
|
var import_genre_tree_view4 = require("@behindthemusictree/genre-tree-view");
|
|
588
|
+
var import_genre_tree16 = require("@behindthemusictree/app-kit/genre-tree");
|
|
478
589
|
var import_genre_tree17 = require("@behindthemusictree/app-kit/genre-tree");
|
|
479
|
-
var
|
|
480
|
-
var import_player10 = require("@behindthemusictree/app-kit/player");
|
|
481
|
-
var import_popup5 = require("@behindthemusictree/app-kit/popup");
|
|
482
|
-
|
|
483
|
-
// src/genre-tree/GenreDetailExtras.tsx
|
|
484
|
-
var import_ui7 = require("@behindthemusictree/ui");
|
|
485
|
-
var import_genre_tree8 = require("@behindthemusictree/app-kit/genre-tree");
|
|
486
|
-
|
|
487
|
-
// src/genre-tree/ClampedText.tsx
|
|
488
|
-
var import_react = require("react");
|
|
489
|
-
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
490
|
-
function ClampedText({ children, className = "" }) {
|
|
491
|
-
const ref = (0, import_react.useRef)(null);
|
|
492
|
-
const [expanded, setExpanded] = (0, import_react.useState)(false);
|
|
493
|
-
const [overflows, setOverflows] = (0, import_react.useState)(false);
|
|
494
|
-
(0, import_react.useLayoutEffect)(() => {
|
|
495
|
-
const element = ref.current;
|
|
496
|
-
if (!element || expanded) return;
|
|
497
|
-
setOverflows(element.scrollHeight > element.clientHeight);
|
|
498
|
-
}, [children, expanded]);
|
|
499
|
-
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
|
|
500
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { ref, className: `${className} ${expanded ? "" : "line-clamp-3"}`, children }),
|
|
501
|
-
(overflows || expanded) && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
502
|
-
"button",
|
|
503
|
-
{
|
|
504
|
-
type: "button",
|
|
505
|
-
"aria-expanded": expanded,
|
|
506
|
-
onClick: () => setExpanded((value) => !value),
|
|
507
|
-
className: "mt-1 text-xs text-[var(--color-text-muted)] underline hover:text-[var(--color-text)]",
|
|
508
|
-
children: expanded ? "Show less" : "Show more"
|
|
509
|
-
}
|
|
510
|
-
)
|
|
511
|
-
] });
|
|
512
|
-
}
|
|
590
|
+
var import_popup6 = require("@behindthemusictree/app-kit/popup");
|
|
513
591
|
|
|
514
|
-
// src/genre-tree/
|
|
515
|
-
var
|
|
516
|
-
var
|
|
517
|
-
var import_zod = require("zod");
|
|
518
|
-
var import_fa5 = require("react-icons/fa");
|
|
519
|
-
var import_ui6 = require("@behindthemusictree/ui");
|
|
520
|
-
var import_auth2 = require("@behindthemusictree/app-kit/auth");
|
|
521
|
-
var import_transport2 = require("@behindthemusictree/app-kit/transport");
|
|
522
|
-
var import_transport3 = require("@behindthemusictree/app-kit/transport");
|
|
523
|
-
var import_transport4 = require("@behindthemusictree/app-kit/transport");
|
|
592
|
+
// src/genre-tree/playlist-tree/TreePerRoot.tsx
|
|
593
|
+
var import_react4 = require("react");
|
|
594
|
+
var import_genre_tree_view = require("@behindthemusictree/genre-tree-view");
|
|
524
595
|
var import_player = require("@behindthemusictree/app-kit/player");
|
|
525
|
-
var import_player2 = require("@behindthemusictree/app-kit/player");
|
|
526
596
|
var import_popup2 = require("@behindthemusictree/app-kit/popup");
|
|
597
|
+
var import_transport2 = require("@behindthemusictree/app-kit/transport");
|
|
527
598
|
|
|
528
|
-
// src/genre-tree/
|
|
529
|
-
var
|
|
599
|
+
// src/genre-tree/playlist-tree/useGenrePlaylistPlayback.ts
|
|
600
|
+
var import_react3 = require("react");
|
|
530
601
|
var import_genre_tree = require("@behindthemusictree/app-kit/genre-tree");
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
const url = (0, import_genre_tree2.musicBrainzRecordingUrl)(song);
|
|
553
|
-
if (!url) return null;
|
|
554
|
-
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
555
|
-
"a",
|
|
556
|
-
{
|
|
557
|
-
href: url,
|
|
558
|
-
target: "_blank",
|
|
559
|
-
rel: "noopener noreferrer",
|
|
560
|
-
"aria-label": `View ${song.title} on MusicBrainz`,
|
|
561
|
-
title: "View on MusicBrainz",
|
|
562
|
-
className: "inline-flex shrink-0 items-center opacity-50 hover:opacity-100 focus-visible:opacity-100",
|
|
563
|
-
onClick: (event) => event.stopPropagation(),
|
|
564
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_fa4.FaExternalLinkAlt, { size: 10 })
|
|
565
|
-
}
|
|
602
|
+
function useGenrePlaylistPlayback({
|
|
603
|
+
scope,
|
|
604
|
+
genrePlaylists,
|
|
605
|
+
artist = null
|
|
606
|
+
}) {
|
|
607
|
+
const { trackList, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist } = (0, import_genre_tree.useTrackList)();
|
|
608
|
+
const origin = trackList?.origin;
|
|
609
|
+
const playingNodeId = (0, import_react3.useMemo)(() => {
|
|
610
|
+
if (!origin) return null;
|
|
611
|
+
if (!artist) return origin.type === import_genre_tree.TrackListOriginType.GENRE_PLAYLIST ? origin.uuid : null;
|
|
612
|
+
if (origin.type !== import_genre_tree.TrackListOriginType.ARTIST || origin.uuid !== artist.uuid) return null;
|
|
613
|
+
const { genreUuid } = origin;
|
|
614
|
+
return genreUuid ? genrePlaylists.find((gp) => gp.criteria?.uuid === genreUuid)?.uuid ?? null : null;
|
|
615
|
+
}, [origin, artist, genrePlaylists]);
|
|
616
|
+
const play = (0, import_react3.useCallback)(
|
|
617
|
+
async (genrePlaylist) => {
|
|
618
|
+
if (!artist) return playNewTrackListFromGenrePlaylist(genrePlaylist, scope);
|
|
619
|
+
if (!genrePlaylist.criteria) return;
|
|
620
|
+
return playNewTrackListFromArtist(artist, scope, { ordering: "title", genreUuid: genrePlaylist.criteria.uuid });
|
|
621
|
+
},
|
|
622
|
+
[artist, scope, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist]
|
|
566
623
|
);
|
|
624
|
+
return (0, import_react3.useMemo)(() => ({ playingNodeId, play }), [playingNodeId, play]);
|
|
567
625
|
}
|
|
568
626
|
|
|
569
|
-
// src/genre-tree/
|
|
570
|
-
var import_genre_tree7 = require("@behindthemusictree/app-kit/genre-tree");
|
|
627
|
+
// src/genre-tree/playlist-tree/TreePerRoot.tsx
|
|
571
628
|
var import_track_noun2 = require("@behindthemusictree/app-kit/track-noun");
|
|
572
|
-
var
|
|
573
|
-
|
|
574
|
-
var SongPageSchema = (0, import_transport3.PaginatedResponseSchema)(import_genre_tree5.SongDetailedSchema).extend({
|
|
575
|
-
overallTotal: import_zod.z.number().nullable(),
|
|
576
|
-
page: import_zod.z.number().nullable(),
|
|
577
|
-
totalPages: import_zod.z.number().nullable()
|
|
578
|
-
});
|
|
579
|
-
var TEXT_CELL = "min-w-0 flex-1 truncate [@container(min-width:40rem)]:flex [@container(min-width:40rem)]:gap-2";
|
|
580
|
-
var TITLE_CELL = "[@container(min-width:40rem)]:flex-[2] [@container(min-width:40rem)]:truncate";
|
|
581
|
-
var COLUMN_CELL = "[@container(min-width:40rem)]:flex-1 [@container(min-width:40rem)]:truncate";
|
|
582
|
-
var ALBUM_CELL = `hidden [@container(min-width:40rem)]:block ${COLUMN_CELL}`;
|
|
583
|
-
var SEPARATOR = "[@container(min-width:40rem)]:hidden";
|
|
584
|
-
var nextCursor = (next) => {
|
|
585
|
-
const cursor = new URL(next).searchParams.get("cursor");
|
|
586
|
-
if (!cursor) throw new Error(`Songs next link has no cursor: ${next}`);
|
|
587
|
-
return cursor;
|
|
588
|
-
};
|
|
589
|
-
function GenrePlaylistTracks({
|
|
590
|
-
genreUuid,
|
|
591
|
-
genrePlaylist,
|
|
629
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
630
|
+
function GenrePlaylistTreePerRoot({
|
|
592
631
|
scope,
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
632
|
+
className: className2,
|
|
633
|
+
rootUuid,
|
|
634
|
+
genrePlaylistTreePerRoot,
|
|
635
|
+
handleGenreCreationAction,
|
|
636
|
+
handleGenreRenameAction,
|
|
637
|
+
additionalActions,
|
|
638
|
+
onNodeClick,
|
|
639
|
+
onNodeHover,
|
|
640
|
+
renderInfoPanelBody,
|
|
641
|
+
selectedNodeId,
|
|
642
|
+
onSelectedNodeChange,
|
|
643
|
+
hideInfoPanelClose,
|
|
644
|
+
hideInfoPanelTitle,
|
|
645
|
+
readOnly = false,
|
|
646
|
+
showToolbar,
|
|
647
|
+
wheelZoom,
|
|
648
|
+
defaultViewport,
|
|
649
|
+
viewportRestore,
|
|
650
|
+
onViewportChange,
|
|
651
|
+
playback,
|
|
652
|
+
...infoPanelCuration
|
|
599
653
|
}) {
|
|
600
|
-
const {
|
|
601
|
-
const {
|
|
602
|
-
const {
|
|
603
|
-
const { showPopup } = (0, import_popup2.usePopup)();
|
|
654
|
+
const { isPlaying, setIsPlaying } = (0, import_player.usePlayer)();
|
|
655
|
+
const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists: genrePlaylistTreePerRoot });
|
|
656
|
+
const { playingNodeId, play } = playback ?? defaultPlayback;
|
|
604
657
|
const trackNoun = (0, import_track_noun2.useTrackNoun)();
|
|
605
|
-
const {
|
|
606
|
-
const
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
(0, import_react2.useEffect)(() => {
|
|
627
|
-
const sentinel = sentinelRef.current;
|
|
628
|
-
if (!hasNextPage || isFetchingNextPage || !sentinel) return;
|
|
629
|
-
const observer = new IntersectionObserver((entries) => {
|
|
630
|
-
if (entries.some((entry) => entry.isIntersecting)) {
|
|
631
|
-
void fetchNextPage();
|
|
632
|
-
}
|
|
633
|
-
});
|
|
634
|
-
observer.observe(sentinel);
|
|
635
|
-
return () => observer.disconnect();
|
|
636
|
-
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);
|
|
637
|
-
const pages = data?.pages ?? [];
|
|
638
|
-
const tracks = pages.flatMap((page) => page.tracks);
|
|
639
|
-
const total = pages[0]?.total ?? void 0;
|
|
640
|
-
const play = (startSongUuid) => {
|
|
641
|
-
const started = artist ? playNewTrackListFromArtist(artist, scope, { startSongUuid, ordering: "title", genreUuid }) : playNewTrackListFromGenrePlaylist(genrePlaylist, scope, { startSongUuid });
|
|
642
|
-
started.catch((error) => {
|
|
643
|
-
console.error("Failed to play genre playlist:", error);
|
|
644
|
-
showPopup(/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(InternalErrorPopup, { errorCode: import_transport5.ErrorCode.CLIENT_UNKNOWN }));
|
|
645
|
-
});
|
|
646
|
-
};
|
|
647
|
-
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "gtv-info-panel-children", children: [
|
|
648
|
-
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "gtv-info-panel-children-title", children: [
|
|
649
|
-
(0, import_track_noun2.capitalize)(trackNoun.plural),
|
|
650
|
-
total !== void 0 && ` (${total})`
|
|
651
|
-
] }),
|
|
652
|
-
renderTracksToolbar?.(),
|
|
653
|
-
isPending ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_ui6.Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("p", { role: "alert", children: [
|
|
654
|
-
"Couldn't load ",
|
|
655
|
-
trackNoun.plural,
|
|
656
|
-
".",
|
|
657
|
-
" ",
|
|
658
|
-
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
|
|
659
|
-
] }) : tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { children: "\u2014" }) : /* @__PURE__ */ (0, import_jsx_runtime14.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: [
|
|
660
|
-
tracks.map((track, index) => {
|
|
661
|
-
const label = track.artists?.length ? `${track.title} \u2014 ${track.artists.map((artist2) => artist2.name).join(", ")}` : track.title;
|
|
662
|
-
const isCurrent = playerTrackObject?.track.id === track.uuid;
|
|
663
|
-
const isPlaying = isCurrent && playState === import_player2.PlayStates.PLAYING;
|
|
664
|
-
const text = /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: TEXT_CELL, children: [
|
|
665
|
-
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: TITLE_CELL, children: track.title }),
|
|
666
|
-
track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: SEPARATOR, children: " \u2014 " }) : null,
|
|
667
|
-
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_genre_tree7.ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
|
|
668
|
-
track.album && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: ALBUM_CELL, children: track.album.name })
|
|
669
|
-
] });
|
|
670
|
-
const leading = renderSongLeading?.(track, index);
|
|
671
|
-
const actions = renderSongActions?.(track);
|
|
672
|
-
const unplayableReason = (0, import_genre_tree5.isSong)(track) ? (0, import_genre_tree5.songUnplayableReason)(track) : null;
|
|
673
|
-
if (unplayableReason) {
|
|
674
|
-
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("li", { className: "flex min-h-8 items-center gap-1 px-1 opacity-50", title: unplayableReason, children: [
|
|
675
|
-
leading,
|
|
676
|
-
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
677
|
-
"button",
|
|
678
|
-
{
|
|
679
|
-
type: "button",
|
|
680
|
-
disabled: true,
|
|
681
|
-
"aria-disabled": "true",
|
|
682
|
-
"aria-label": `${label} \u2014 ${unplayableReason}`,
|
|
683
|
-
className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full opacity-0 cursor-not-allowed",
|
|
684
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_fa5.FaPlay, { size: 10 })
|
|
685
|
-
}
|
|
686
|
-
),
|
|
687
|
-
text,
|
|
688
|
-
(0, import_genre_tree5.isSong)(track) && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(MusicBrainzRecordingLink, { song: track }),
|
|
689
|
-
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "shrink-0 text-xs", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(UnplayableReason, { reason: unplayableReason }) }),
|
|
690
|
-
actions
|
|
691
|
-
] }, track.uuid);
|
|
692
|
-
}
|
|
693
|
-
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
|
|
694
|
-
"li",
|
|
695
|
-
{
|
|
696
|
-
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)]" : ""}`,
|
|
697
|
-
children: [
|
|
698
|
-
leading,
|
|
699
|
-
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
700
|
-
"button",
|
|
701
|
-
{
|
|
702
|
-
type: "button",
|
|
703
|
-
"aria-label": `${isPlaying ? "Pause" : "Play"} ${label}`,
|
|
704
|
-
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_player2.PlayStates.STOPPED ? "opacity-100" : "opacity-0 [@media(pointer:coarse)]:opacity-100"}`,
|
|
705
|
-
onClick: () => isCurrent ? handlePlayPauseAction() : play(track.uuid),
|
|
706
|
-
children: isCurrent && playState === import_player2.PlayStates.LOADING ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_ui6.RingLoader, { size: 10 }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_fa5.FaPause, { size: 10 }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_fa5.FaPlay, { size: 10 })
|
|
707
|
-
}
|
|
708
|
-
),
|
|
709
|
-
text,
|
|
710
|
-
(0, import_genre_tree5.isSong)(track) && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(MusicBrainzRecordingLink, { song: track }),
|
|
711
|
-
actions
|
|
712
|
-
]
|
|
713
|
-
},
|
|
714
|
-
track.uuid
|
|
715
|
-
);
|
|
716
|
-
}),
|
|
717
|
-
hasNextPage && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("li", { ref: sentinelRef, "aria-hidden": "true", className: "h-4" })
|
|
718
|
-
] })
|
|
719
|
-
] });
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
// src/genre-tree/GenreDetailExtras.tsx
|
|
723
|
-
var import_track_noun3 = require("@behindthemusictree/app-kit/track-noun");
|
|
724
|
-
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
725
|
-
function GenreDetailExtras({
|
|
726
|
-
genreUuid,
|
|
727
|
-
genrePlaylist,
|
|
728
|
-
scope,
|
|
729
|
-
getBackendBaseUrl,
|
|
730
|
-
criteriaOverviewSchema,
|
|
731
|
-
renderGenreDetailExtras,
|
|
732
|
-
renderArtistLink,
|
|
733
|
-
renderSummary,
|
|
734
|
-
renderEssentialTracks,
|
|
735
|
-
family,
|
|
736
|
-
...trackSlots
|
|
737
|
-
}) {
|
|
738
|
-
const trackNoun = (0, import_track_noun3.useTrackNoun)();
|
|
739
|
-
const { data: overview, isPending } = (0, import_genre_tree8.useFetchGenreOverview)(
|
|
740
|
-
genreUuid,
|
|
741
|
-
scope,
|
|
742
|
-
getBackendBaseUrl,
|
|
743
|
-
criteriaOverviewSchema
|
|
744
|
-
);
|
|
745
|
-
if (isPending) {
|
|
746
|
-
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
|
|
747
|
-
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "gtv-info-panel-children", "aria-busy": "true", "data-testid": "genre-detail-extras-skeleton", children: [
|
|
748
|
-
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "gtv-info-panel-children-title", children: "Summary" }),
|
|
749
|
-
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ui7.Skeleton, { className: "h-4 w-3/4" })
|
|
750
|
-
] }),
|
|
751
|
-
family
|
|
752
|
-
] });
|
|
753
|
-
}
|
|
754
|
-
if (!overview) return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_jsx_runtime15.Fragment, { children: family });
|
|
755
|
-
const { summary, description, summarySourceUrl, essentialSongs } = overview;
|
|
756
|
-
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
|
|
757
|
-
description && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "px-3.5 py-2.5", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ClampedText, { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text-muted)]", children: description }, genreUuid) }),
|
|
758
|
-
renderSummary ? renderSummary(overview) : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "gtv-info-panel-children text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: [
|
|
759
|
-
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "gtv-info-panel-children-title", children: "Summary" }),
|
|
760
|
-
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ClampedText, { className: "mt-1", children: summary ?? "\u2014" }, genreUuid),
|
|
761
|
-
summarySourceUrl && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
762
|
-
"a",
|
|
763
|
-
{
|
|
764
|
-
href: summarySourceUrl,
|
|
765
|
-
target: "_blank",
|
|
766
|
-
rel: "noopener noreferrer",
|
|
767
|
-
className: "mt-1 inline-block text-xs text-[var(--color-text-muted)] underline hover:text-[var(--color-text)]",
|
|
768
|
-
children: "Source: Wikipedia"
|
|
769
|
-
}
|
|
770
|
-
)
|
|
771
|
-
] }),
|
|
772
|
-
renderSummary && summarySourceUrl && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
773
|
-
"a",
|
|
774
|
-
{
|
|
775
|
-
href: summarySourceUrl,
|
|
776
|
-
target: "_blank",
|
|
777
|
-
rel: "noopener noreferrer",
|
|
778
|
-
className: "mx-3.5 mb-2.5 inline-block text-xs text-[var(--color-text-muted)] underline hover:text-[var(--color-text)]",
|
|
779
|
-
children: "Source: Wikipedia"
|
|
780
|
-
}
|
|
781
|
-
),
|
|
782
|
-
family,
|
|
783
|
-
renderGenreDetailExtras?.(overview),
|
|
784
|
-
renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "gtv-info-panel-children text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: [
|
|
785
|
-
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "gtv-info-panel-children-title", children: [
|
|
786
|
-
"Essential ",
|
|
787
|
-
trackNoun.plural
|
|
788
|
-
] }),
|
|
789
|
-
essentialSongs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("ul", { className: "mt-1 divide-y divide-[var(--color-border)]", children: essentialSongs.map((track) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("li", { className: "py-1", children: track.title }, track.uuid)) }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "mt-1", children: "\u2014" })
|
|
790
|
-
] }),
|
|
791
|
-
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
792
|
-
GenrePlaylistTracks,
|
|
793
|
-
{
|
|
794
|
-
genreUuid,
|
|
795
|
-
genrePlaylist,
|
|
796
|
-
scope,
|
|
797
|
-
getBackendBaseUrl,
|
|
798
|
-
renderArtistLink,
|
|
799
|
-
...trackSlots
|
|
800
|
-
}
|
|
801
|
-
)
|
|
802
|
-
] });
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
// src/genre-tree/playlist-tree/TreePerRoot.tsx
|
|
806
|
-
var import_react4 = require("react");
|
|
807
|
-
var import_genre_tree_view = require("@behindthemusictree/genre-tree-view");
|
|
808
|
-
var import_player3 = require("@behindthemusictree/app-kit/player");
|
|
809
|
-
var import_popup3 = require("@behindthemusictree/app-kit/popup");
|
|
810
|
-
var import_transport6 = require("@behindthemusictree/app-kit/transport");
|
|
811
|
-
|
|
812
|
-
// src/genre-tree/playlist-tree/useGenrePlaylistPlayback.ts
|
|
813
|
-
var import_react3 = require("react");
|
|
814
|
-
var import_genre_tree9 = require("@behindthemusictree/app-kit/genre-tree");
|
|
815
|
-
function useGenrePlaylistPlayback({
|
|
816
|
-
scope,
|
|
817
|
-
genrePlaylists,
|
|
818
|
-
artist = null
|
|
819
|
-
}) {
|
|
820
|
-
const { trackList, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist } = (0, import_genre_tree9.useTrackList)();
|
|
821
|
-
const origin = trackList?.origin;
|
|
822
|
-
const playingNodeId = (0, import_react3.useMemo)(() => {
|
|
823
|
-
if (!origin) return null;
|
|
824
|
-
if (!artist) return origin.type === import_genre_tree9.TrackListOriginType.GENRE_PLAYLIST ? origin.uuid : null;
|
|
825
|
-
if (origin.type !== import_genre_tree9.TrackListOriginType.ARTIST || origin.uuid !== artist.uuid) return null;
|
|
826
|
-
const { genreUuid } = origin;
|
|
827
|
-
return genreUuid ? genrePlaylists.find((gp) => gp.criteria?.uuid === genreUuid)?.uuid ?? null : null;
|
|
828
|
-
}, [origin, artist, genrePlaylists]);
|
|
829
|
-
const play = (0, import_react3.useCallback)(
|
|
830
|
-
async (genrePlaylist) => {
|
|
831
|
-
if (!artist) return playNewTrackListFromGenrePlaylist(genrePlaylist, scope);
|
|
832
|
-
if (!genrePlaylist.criteria) return;
|
|
833
|
-
return playNewTrackListFromArtist(artist, scope, { ordering: "title", genreUuid: genrePlaylist.criteria.uuid });
|
|
834
|
-
},
|
|
835
|
-
[artist, scope, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist]
|
|
836
|
-
);
|
|
837
|
-
return (0, import_react3.useMemo)(() => ({ playingNodeId, play }), [playingNodeId, play]);
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
// src/genre-tree/playlist-tree/TreePerRoot.tsx
|
|
841
|
-
var import_track_noun4 = require("@behindthemusictree/app-kit/track-noun");
|
|
842
|
-
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
843
|
-
function GenrePlaylistTreePerRoot({
|
|
844
|
-
scope,
|
|
845
|
-
className,
|
|
846
|
-
rootUuid,
|
|
847
|
-
genrePlaylistTreePerRoot,
|
|
848
|
-
handleGenreCreationAction,
|
|
849
|
-
handleGenreRenameAction,
|
|
850
|
-
additionalActions,
|
|
851
|
-
onNodeClick,
|
|
852
|
-
onNodeHover,
|
|
853
|
-
renderInfoPanelBody,
|
|
854
|
-
selectedNodeId,
|
|
855
|
-
onSelectedNodeChange,
|
|
856
|
-
hideInfoPanelClose,
|
|
857
|
-
hideInfoPanelTitle,
|
|
858
|
-
readOnly = false,
|
|
859
|
-
showToolbar,
|
|
860
|
-
wheelZoom,
|
|
861
|
-
controlsOrientation,
|
|
862
|
-
defaultViewport,
|
|
863
|
-
onViewportChange,
|
|
864
|
-
playback,
|
|
865
|
-
...infoPanelCuration
|
|
866
|
-
}) {
|
|
867
|
-
const { isPlaying, setIsPlaying } = (0, import_player3.usePlayer)();
|
|
868
|
-
const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists: genrePlaylistTreePerRoot });
|
|
869
|
-
const { playingNodeId, play } = playback ?? defaultPlayback;
|
|
870
|
-
const trackNoun = (0, import_track_noun4.useTrackNoun)();
|
|
871
|
-
const { showPopup } = (0, import_popup3.usePopup)();
|
|
872
|
-
const nodes = (0, import_react4.useMemo)(
|
|
873
|
-
() => genrePlaylistTreePerRoot.map((genrePlaylist) => ({
|
|
874
|
-
id: genrePlaylist.uuid,
|
|
875
|
-
parentId: genrePlaylist.parent?.uuid ?? null,
|
|
876
|
-
name: genrePlaylist.name,
|
|
877
|
-
itemCount: genrePlaylist.tracksCount,
|
|
878
|
-
actionable: Boolean(genrePlaylist.criteria),
|
|
879
|
-
side: genrePlaylist.criteria?.side ?? void 0,
|
|
880
|
-
wikidataId: genrePlaylist.criteria?.wikidataId ?? null,
|
|
881
|
-
umbrella: genrePlaylist.isUmbrella
|
|
882
|
-
})),
|
|
883
|
-
[genrePlaylistTreePerRoot]
|
|
884
|
-
);
|
|
885
|
-
const handlePlayPause = (0, import_react4.useCallback)(
|
|
886
|
-
(nodeId) => {
|
|
887
|
-
const genrePlaylist = genrePlaylistTreePerRoot.find((g) => g.uuid === nodeId);
|
|
888
|
-
if (!genrePlaylist) return;
|
|
889
|
-
if (nodeId === playingNodeId) {
|
|
890
|
-
setIsPlaying(!isPlaying);
|
|
891
|
-
return;
|
|
658
|
+
const { showPopup } = (0, import_popup2.usePopup)();
|
|
659
|
+
const nodes = (0, import_react4.useMemo)(
|
|
660
|
+
() => genrePlaylistTreePerRoot.map((genrePlaylist) => ({
|
|
661
|
+
id: genrePlaylist.uuid,
|
|
662
|
+
parentId: genrePlaylist.parent?.uuid ?? null,
|
|
663
|
+
name: genrePlaylist.name,
|
|
664
|
+
itemCount: genrePlaylist.tracksCount,
|
|
665
|
+
actionable: Boolean(genrePlaylist.criteria),
|
|
666
|
+
side: genrePlaylist.criteria?.side ?? void 0,
|
|
667
|
+
wikidataId: genrePlaylist.criteria?.wikidataId ?? null,
|
|
668
|
+
umbrella: genrePlaylist.isUmbrella
|
|
669
|
+
})),
|
|
670
|
+
[genrePlaylistTreePerRoot]
|
|
671
|
+
);
|
|
672
|
+
const handlePlayPause = (0, import_react4.useCallback)(
|
|
673
|
+
(nodeId) => {
|
|
674
|
+
const genrePlaylist = genrePlaylistTreePerRoot.find((g) => g.uuid === nodeId);
|
|
675
|
+
if (!genrePlaylist) return;
|
|
676
|
+
if (nodeId === playingNodeId) {
|
|
677
|
+
setIsPlaying(!isPlaying);
|
|
678
|
+
return;
|
|
892
679
|
}
|
|
893
680
|
if (genrePlaylist.tracksCount === 0) {
|
|
894
681
|
return;
|
|
895
682
|
}
|
|
896
683
|
play(genrePlaylist).catch((error) => {
|
|
897
684
|
console.error("Failed to load genre playlist tracks:", error);
|
|
898
|
-
showPopup(/* @__PURE__ */ (0,
|
|
685
|
+
showPopup(/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(InternalErrorPopup, { errorCode: import_transport2.ErrorCode.CLIENT_UNKNOWN }));
|
|
899
686
|
});
|
|
900
687
|
},
|
|
901
688
|
[
|
|
@@ -927,12 +714,12 @@ function GenrePlaylistTreePerRoot({
|
|
|
927
714
|
if (confirm(`Are you sure you want to delete "${node.name}"?`)) {
|
|
928
715
|
}
|
|
929
716
|
}, []);
|
|
930
|
-
return /* @__PURE__ */ (0,
|
|
717
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
931
718
|
import_genre_tree_view.GenreTree,
|
|
932
719
|
{
|
|
933
|
-
className,
|
|
720
|
+
className: className2,
|
|
934
721
|
nodes,
|
|
935
|
-
itemCountLabel: `${(0,
|
|
722
|
+
itemCountLabel: `${(0, import_track_noun2.capitalize)(trackNoun.singular)} count`,
|
|
936
723
|
rootColor: (0, import_genre_tree_view.getGenreTreeColor)(rootUuid),
|
|
937
724
|
playingNodeId,
|
|
938
725
|
playState: isPlaying ? "playing" : "paused",
|
|
@@ -950,8 +737,8 @@ function GenrePlaylistTreePerRoot({
|
|
|
950
737
|
hideInfoPanelTitle,
|
|
951
738
|
showToolbar,
|
|
952
739
|
wheelZoom,
|
|
953
|
-
controlsOrientation,
|
|
954
740
|
defaultViewport,
|
|
741
|
+
viewportRestore,
|
|
955
742
|
onViewportChange,
|
|
956
743
|
...infoPanelCuration,
|
|
957
744
|
readOnly
|
|
@@ -962,19 +749,26 @@ function GenrePlaylistTreePerRoot({
|
|
|
962
749
|
// src/genre-tree/playlist-tree/TreeWheel.tsx
|
|
963
750
|
var import_react5 = require("react");
|
|
964
751
|
var import_genre_tree_view2 = require("@behindthemusictree/genre-tree-view");
|
|
965
|
-
var
|
|
966
|
-
var
|
|
967
|
-
var
|
|
752
|
+
var import_player2 = require("@behindthemusictree/app-kit/player");
|
|
753
|
+
var import_popup3 = require("@behindthemusictree/app-kit/popup");
|
|
754
|
+
var import_transport3 = require("@behindthemusictree/app-kit/transport");
|
|
968
755
|
|
|
969
756
|
// src/genre-tree/playlist-tree/size-metric.ts
|
|
970
|
-
var
|
|
757
|
+
var hasViewsCount = (genrePlaylists) => genrePlaylists.every((genrePlaylist) => genrePlaylist.viewsCount !== void 0);
|
|
758
|
+
var genrePlaylistSize = (genrePlaylist, metric) => {
|
|
759
|
+
if (metric === "songs") return genrePlaylist.tracksCount;
|
|
760
|
+
if (genrePlaylist.viewsCount === void 0) {
|
|
761
|
+
throw new Error(`Genre playlist ${genrePlaylist.uuid} has no viewsCount to size by views`);
|
|
762
|
+
}
|
|
763
|
+
return genrePlaylist.viewsCount;
|
|
764
|
+
};
|
|
971
765
|
|
|
972
766
|
// src/genre-tree/playlist-tree/TreeWheel.tsx
|
|
973
|
-
var
|
|
974
|
-
var
|
|
767
|
+
var import_track_noun3 = require("@behindthemusictree/app-kit/track-noun");
|
|
768
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
975
769
|
function GenrePlaylistTreeWheel({
|
|
976
770
|
scope,
|
|
977
|
-
className,
|
|
771
|
+
className: className2,
|
|
978
772
|
genrePlaylists,
|
|
979
773
|
handleGenreCreationAction,
|
|
980
774
|
handleGenreRenameAction,
|
|
@@ -989,20 +783,20 @@ function GenrePlaylistTreeWheel({
|
|
|
989
783
|
readOnly = false,
|
|
990
784
|
allowWheelRotation,
|
|
991
785
|
showToolbar,
|
|
992
|
-
controlsOrientation,
|
|
993
786
|
sizeMetric = "songs",
|
|
994
787
|
floatingControlsLeading,
|
|
995
788
|
busy,
|
|
996
789
|
defaultViewport,
|
|
790
|
+
viewportRestore,
|
|
997
791
|
onViewportChange,
|
|
998
792
|
playback,
|
|
999
793
|
...infoPanelCuration
|
|
1000
794
|
}) {
|
|
1001
|
-
const { isPlaying, setIsPlaying } = (0,
|
|
795
|
+
const { isPlaying, setIsPlaying } = (0, import_player2.usePlayer)();
|
|
1002
796
|
const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists });
|
|
1003
797
|
const { playingNodeId, play } = playback ?? defaultPlayback;
|
|
1004
|
-
const trackNoun = (0,
|
|
1005
|
-
const { showPopup } = (0,
|
|
798
|
+
const trackNoun = (0, import_track_noun3.useTrackNoun)();
|
|
799
|
+
const { showPopup } = (0, import_popup3.usePopup)();
|
|
1006
800
|
const nodes = (0, import_react5.useMemo)(
|
|
1007
801
|
() => genrePlaylists.map((genrePlaylist) => ({
|
|
1008
802
|
id: genrePlaylist.uuid,
|
|
@@ -1028,7 +822,7 @@ function GenrePlaylistTreeWheel({
|
|
|
1028
822
|
}
|
|
1029
823
|
play(genrePlaylist).catch((error) => {
|
|
1030
824
|
console.error("Failed to load genre playlist tracks:", error);
|
|
1031
|
-
showPopup(/* @__PURE__ */ (0,
|
|
825
|
+
showPopup(/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(InternalErrorPopup, { errorCode: import_transport3.ErrorCode.CLIENT_UNKNOWN }));
|
|
1032
826
|
});
|
|
1033
827
|
},
|
|
1034
828
|
[
|
|
@@ -1060,12 +854,12 @@ function GenrePlaylistTreeWheel({
|
|
|
1060
854
|
if (confirm(`Are you sure you want to delete "${node.name}"?`)) {
|
|
1061
855
|
}
|
|
1062
856
|
}, []);
|
|
1063
|
-
return /* @__PURE__ */ (0,
|
|
857
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1064
858
|
import_genre_tree_view2.GenreTreeWheel,
|
|
1065
859
|
{
|
|
1066
|
-
className,
|
|
860
|
+
className: className2,
|
|
1067
861
|
nodes,
|
|
1068
|
-
itemCountLabel: sizeMetric === "views" ? "Views" : `${(0,
|
|
862
|
+
itemCountLabel: sizeMetric === "views" ? "Views" : `${(0, import_track_noun3.capitalize)(trackNoun.singular)} count`,
|
|
1069
863
|
playingNodeId,
|
|
1070
864
|
playState: isPlaying ? "playing" : "paused",
|
|
1071
865
|
onPlayPause: handlePlayPause,
|
|
@@ -1082,10 +876,10 @@ function GenrePlaylistTreeWheel({
|
|
|
1082
876
|
hideInfoPanelTitle,
|
|
1083
877
|
allowWheelRotation,
|
|
1084
878
|
showToolbar,
|
|
1085
|
-
controlsOrientation,
|
|
1086
879
|
floatingControlsLeading,
|
|
1087
880
|
busy,
|
|
1088
881
|
defaultViewport,
|
|
882
|
+
viewportRestore,
|
|
1089
883
|
onViewportChange,
|
|
1090
884
|
...infoPanelCuration,
|
|
1091
885
|
readOnly
|
|
@@ -1096,13 +890,13 @@ function GenrePlaylistTreeWheel({
|
|
|
1096
890
|
// src/genre-tree/playlist-tree/TreeWheelRadialPopCore.tsx
|
|
1097
891
|
var import_react6 = require("react");
|
|
1098
892
|
var import_genre_tree_view3 = require("@behindthemusictree/genre-tree-view");
|
|
1099
|
-
var
|
|
1100
|
-
var
|
|
1101
|
-
var
|
|
893
|
+
var import_player3 = require("@behindthemusictree/app-kit/player");
|
|
894
|
+
var import_track_noun4 = require("@behindthemusictree/app-kit/track-noun");
|
|
895
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
1102
896
|
function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
1103
897
|
const {
|
|
1104
898
|
scope,
|
|
1105
|
-
className,
|
|
899
|
+
className: className2,
|
|
1106
900
|
genrePlaylists,
|
|
1107
901
|
handleGenreCreationAction,
|
|
1108
902
|
handleGenreRenameAction,
|
|
@@ -1117,12 +911,12 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
1117
911
|
readOnly = false,
|
|
1118
912
|
allowWheelRotation,
|
|
1119
913
|
showToolbar,
|
|
1120
|
-
controlsOrientation,
|
|
1121
914
|
sizeMetric = "songs",
|
|
1122
915
|
floatingControlsLeading,
|
|
1123
916
|
busy,
|
|
1124
917
|
outline = false,
|
|
1125
918
|
defaultViewport,
|
|
919
|
+
viewportRestore,
|
|
1126
920
|
onViewportChange,
|
|
1127
921
|
playback
|
|
1128
922
|
} = props;
|
|
@@ -1135,10 +929,10 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
1135
929
|
hideExpandButton: props.hideExpandButton,
|
|
1136
930
|
overlayPanel: props.overlayPanel
|
|
1137
931
|
};
|
|
1138
|
-
const { isPlaying, setIsPlaying } = (0,
|
|
932
|
+
const { isPlaying, setIsPlaying } = (0, import_player3.usePlayer)();
|
|
1139
933
|
const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists });
|
|
1140
934
|
const { playingNodeId, play } = playback ?? defaultPlayback;
|
|
1141
|
-
const trackNoun = (0,
|
|
935
|
+
const trackNoun = (0, import_track_noun4.useTrackNoun)();
|
|
1142
936
|
const nodes = (0, import_react6.useMemo)(
|
|
1143
937
|
() => genrePlaylists.map((genrePlaylist) => ({
|
|
1144
938
|
id: genrePlaylist.uuid,
|
|
@@ -1192,9 +986,9 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
1192
986
|
}
|
|
1193
987
|
}, []);
|
|
1194
988
|
const treeProps = {
|
|
1195
|
-
className,
|
|
989
|
+
className: className2,
|
|
1196
990
|
nodes,
|
|
1197
|
-
itemCountLabel: sizeMetric === "views" ? "Views" : `${(0,
|
|
991
|
+
itemCountLabel: sizeMetric === "views" ? "Views" : `${(0, import_track_noun4.capitalize)(trackNoun.singular)} count`,
|
|
1198
992
|
playingNodeId,
|
|
1199
993
|
playState: isPlaying ? "playing" : "paused",
|
|
1200
994
|
onPlayPause: handlePlayPause,
|
|
@@ -1213,15 +1007,15 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
1213
1007
|
...infoPanelCuration,
|
|
1214
1008
|
readOnly
|
|
1215
1009
|
};
|
|
1216
|
-
return outline ? /* @__PURE__ */ (0,
|
|
1010
|
+
return outline ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_genre_tree_view3.GenreTreeOutline, { ...treeProps }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1217
1011
|
import_genre_tree_view3.GenreTreeWheelRadialPopCore,
|
|
1218
1012
|
{
|
|
1219
1013
|
...treeProps,
|
|
1220
1014
|
allowWheelRotation,
|
|
1221
|
-
controlsOrientation,
|
|
1222
1015
|
floatingControlsLeading,
|
|
1223
1016
|
busy,
|
|
1224
1017
|
defaultViewport,
|
|
1018
|
+
viewportRestore,
|
|
1225
1019
|
onViewportChange,
|
|
1226
1020
|
...focus.onFocusGenreChange ? { focusNodeId: focus.focusGenreUuid, onFocusChange: focus.onFocusGenreChange } : { focusNodeId: void 0, onFocusChange: void 0 }
|
|
1227
1021
|
}
|
|
@@ -1230,7 +1024,7 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
1230
1024
|
|
|
1231
1025
|
// src/genre-tree/GenreTreeWheelHandoff.tsx
|
|
1232
1026
|
var import_react7 = require("react");
|
|
1233
|
-
var
|
|
1027
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
1234
1028
|
function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
|
|
1235
1029
|
const [revealed, setRevealed] = (0, import_react7.useState)(false);
|
|
1236
1030
|
const onRevealedChangeRef = (0, import_react7.useRef)(onRevealedChange);
|
|
@@ -1249,8 +1043,8 @@ function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
|
|
|
1249
1043
|
cancelAnimationFrame(innerFrame);
|
|
1250
1044
|
};
|
|
1251
1045
|
}, []);
|
|
1252
|
-
return /* @__PURE__ */ (0,
|
|
1253
|
-
/* @__PURE__ */ (0,
|
|
1046
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "relative w-full h-full", children: [
|
|
1047
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
1254
1048
|
"div",
|
|
1255
1049
|
{
|
|
1256
1050
|
className: "absolute inset-0 transition-opacity duration-150 ease-out",
|
|
@@ -1259,15 +1053,15 @@ function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
|
|
|
1259
1053
|
children
|
|
1260
1054
|
}
|
|
1261
1055
|
),
|
|
1262
|
-
!revealed && /* @__PURE__ */ (0,
|
|
1056
|
+
!revealed && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "absolute inset-0", "aria-hidden": "true", children: skeleton })
|
|
1263
1057
|
] });
|
|
1264
1058
|
}
|
|
1265
1059
|
|
|
1266
1060
|
// src/genre-tree/GenreSearch.tsx
|
|
1267
1061
|
var import_lucide_react7 = require("lucide-react");
|
|
1268
|
-
var
|
|
1269
|
-
var
|
|
1270
|
-
var
|
|
1062
|
+
var import_ui6 = require("@behindthemusictree/ui");
|
|
1063
|
+
var import_genre_tree2 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1064
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
1271
1065
|
function GenreSearch({
|
|
1272
1066
|
genrePlaylists,
|
|
1273
1067
|
onSelect,
|
|
@@ -1278,7 +1072,7 @@ function GenreSearch({
|
|
|
1278
1072
|
onQueryChange,
|
|
1279
1073
|
extraResults
|
|
1280
1074
|
}) {
|
|
1281
|
-
const { query, setQuery, results, canClear, clear, select } = (0,
|
|
1075
|
+
const { query, setQuery, results, canClear, clear, select } = (0, import_genre_tree2.useGenreSearch)({
|
|
1282
1076
|
genrePlaylists,
|
|
1283
1077
|
onSelect,
|
|
1284
1078
|
selectedName,
|
|
@@ -1289,20 +1083,20 @@ function GenreSearch({
|
|
|
1289
1083
|
const loading = query !== "" && query !== selectedName && (extraResults?.loading ?? false);
|
|
1290
1084
|
const showList = results.length > 0 || extraItems.length > 0;
|
|
1291
1085
|
const heading = "px-3 pt-2 pb-1 text-xs font-medium uppercase text-[var(--color-text-muted)]";
|
|
1292
|
-
return /* @__PURE__ */ (0,
|
|
1293
|
-
onPrevious && /* @__PURE__ */ (0,
|
|
1086
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "genre-search relative w-[calc(var(--layout-detail-panel-w,25.5rem)-2*var(--layout-panel-inset,1rem))] shrink-0 max-md:w-full", children: [
|
|
1087
|
+
onPrevious && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1294
1088
|
"button",
|
|
1295
1089
|
{
|
|
1296
1090
|
type: "button",
|
|
1297
1091
|
"aria-label": "Previous",
|
|
1298
1092
|
title: "Previous",
|
|
1299
|
-
className: "genre-search-previous absolute left-2 top-1/2 z-10 hidden p-1.5 text-[var(--color-text-muted)] rounded-full -translate-y-1/2 hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-text)]",
|
|
1093
|
+
className: "genre-search-previous absolute left-2 top-1/2 z-10 hidden items-center justify-center p-1.5 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] [@media(pointer:coarse)]:min-w-[var(--layout-touch-target)] text-[var(--color-text-muted)] rounded-full -translate-y-1/2 hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-text)]",
|
|
1300
1094
|
onClick: onPrevious,
|
|
1301
|
-
children: /* @__PURE__ */ (0,
|
|
1095
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react7.ArrowLeft, { "aria-hidden": "true", className: "w-4 h-4" })
|
|
1302
1096
|
}
|
|
1303
1097
|
),
|
|
1304
|
-
/* @__PURE__ */ (0,
|
|
1305
|
-
|
|
1098
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1099
|
+
import_ui6.Input,
|
|
1306
1100
|
{
|
|
1307
1101
|
type: "text",
|
|
1308
1102
|
value: query,
|
|
@@ -1313,24 +1107,27 @@ function GenreSearch({
|
|
|
1313
1107
|
placeholder: placeholder ?? `${label}\u2026`,
|
|
1314
1108
|
"aria-label": label,
|
|
1315
1109
|
"aria-busy": loading,
|
|
1110
|
+
inputMode: "search",
|
|
1111
|
+
enterKeyHint: "search",
|
|
1112
|
+
autoComplete: "off",
|
|
1316
1113
|
className: "h-[var(--layout-search-h,3rem)] pl-6 pr-20 text-base transition-[padding] duration-200 ease-out motion-reduce:transition-none bg-[var(--color-surface)] border-[var(--color-border)] rounded-full shadow-md"
|
|
1317
1114
|
}
|
|
1318
1115
|
),
|
|
1319
|
-
/* @__PURE__ */ (0,
|
|
1320
|
-
/* @__PURE__ */ (0,
|
|
1116
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "absolute right-3 top-1/2 flex items-center -translate-y-1/2", children: [
|
|
1117
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
1321
1118
|
"span",
|
|
1322
1119
|
{
|
|
1323
1120
|
"data-loading": loading,
|
|
1324
1121
|
className: "relative p-2 text-[var(--color-text-muted)] pointer-events-none",
|
|
1325
1122
|
children: [
|
|
1326
|
-
/* @__PURE__ */ (0,
|
|
1123
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1327
1124
|
import_lucide_react7.Search,
|
|
1328
1125
|
{
|
|
1329
1126
|
"aria-hidden": "true",
|
|
1330
1127
|
className: `w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? "opacity-0" : ""}`
|
|
1331
1128
|
}
|
|
1332
1129
|
),
|
|
1333
|
-
/* @__PURE__ */ (0,
|
|
1130
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1334
1131
|
import_lucide_react7.LoaderCircle,
|
|
1335
1132
|
{
|
|
1336
1133
|
"aria-hidden": "true",
|
|
@@ -1340,44 +1137,44 @@ function GenreSearch({
|
|
|
1340
1137
|
]
|
|
1341
1138
|
}
|
|
1342
1139
|
),
|
|
1343
|
-
canClear && /* @__PURE__ */ (0,
|
|
1140
|
+
canClear && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1344
1141
|
"button",
|
|
1345
1142
|
{
|
|
1346
1143
|
type: "button",
|
|
1347
1144
|
"aria-label": "Clear search",
|
|
1348
|
-
className: "p-2 text-[var(--color-text-muted)] rounded-full hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-text)]",
|
|
1145
|
+
className: "flex items-center justify-center p-2 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] [@media(pointer:coarse)]:min-w-[var(--layout-touch-target)] text-[var(--color-text-muted)] rounded-full hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-text)]",
|
|
1349
1146
|
onClick: () => {
|
|
1350
1147
|
clear();
|
|
1351
1148
|
onQueryChange?.("");
|
|
1352
1149
|
},
|
|
1353
|
-
children: /* @__PURE__ */ (0,
|
|
1150
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react7.X, { "aria-hidden": "true", className: "w-6 h-6" })
|
|
1354
1151
|
}
|
|
1355
1152
|
)
|
|
1356
1153
|
] }),
|
|
1357
|
-
extraResults?.status && /* @__PURE__ */ (0,
|
|
1358
|
-
showList && /* @__PURE__ */ (0,
|
|
1359
|
-
extraResults && results.length > 0 && /* @__PURE__ */ (0,
|
|
1360
|
-
results.map((genrePlaylist) => /* @__PURE__ */ (0,
|
|
1154
|
+
extraResults?.status && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { role: "status", "aria-live": "polite", className: "sr-only", children: extraResults.status }),
|
|
1155
|
+
showList && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("ul", { className: "genre-search-results absolute z-40 w-full max-h-[min(16rem,50dvh)] mt-1 overflow-y-auto bg-[var(--color-surface)] border border-[var(--color-border)] rounded-xl shadow-md", children: [
|
|
1156
|
+
extraResults && results.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("li", { className: heading, children: "Genres" }),
|
|
1157
|
+
results.map((genrePlaylist) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
1361
1158
|
"button",
|
|
1362
1159
|
{
|
|
1363
1160
|
type: "button",
|
|
1364
|
-
className: "w-full px-3 py-1.5 text-left hover:bg-[var(--color-surface-muted)]",
|
|
1161
|
+
className: "w-full px-3 py-1.5 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] text-left hover:bg-[var(--color-surface-muted)]",
|
|
1365
1162
|
onClick: () => {
|
|
1366
1163
|
select(genrePlaylist);
|
|
1367
1164
|
onQueryChange?.("");
|
|
1368
1165
|
},
|
|
1369
1166
|
children: [
|
|
1370
1167
|
genrePlaylist.name,
|
|
1371
|
-
genrePlaylist.isUmbrella && /* @__PURE__ */ (0,
|
|
1168
|
+
genrePlaylist.isUmbrella && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "ml-2 text-xs italic text-[var(--color-text-muted)]", children: "umbrella" })
|
|
1372
1169
|
]
|
|
1373
1170
|
}
|
|
1374
1171
|
) }, genrePlaylist.uuid)),
|
|
1375
|
-
extraResults && extraItems.length > 0 && /* @__PURE__ */ (0,
|
|
1376
|
-
extraItems.map((item) => /* @__PURE__ */ (0,
|
|
1172
|
+
extraResults && extraItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("li", { className: heading, children: extraResults.label }),
|
|
1173
|
+
extraItems.map((item) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1377
1174
|
"button",
|
|
1378
1175
|
{
|
|
1379
1176
|
type: "button",
|
|
1380
|
-
className: "w-full px-3 py-1.5 text-left hover:bg-[var(--color-surface-muted)]",
|
|
1177
|
+
className: "w-full px-3 py-1.5 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] text-left hover:bg-[var(--color-surface-muted)]",
|
|
1381
1178
|
onClick: () => {
|
|
1382
1179
|
setQuery("");
|
|
1383
1180
|
onQueryChange?.("");
|
|
@@ -1390,45 +1187,282 @@ function GenreSearch({
|
|
|
1390
1187
|
] });
|
|
1391
1188
|
}
|
|
1392
1189
|
|
|
1190
|
+
// src/genre-tree/GenreInfoPanelBody.tsx
|
|
1191
|
+
var import_player6 = require("@behindthemusictree/app-kit/player");
|
|
1192
|
+
|
|
1393
1193
|
// src/genre-tree/GenreActionBand.tsx
|
|
1394
|
-
var
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1194
|
+
var import_lucide_react11 = require("lucide-react");
|
|
1195
|
+
|
|
1196
|
+
// src/genre-tree/ShareActionButton.tsx
|
|
1197
|
+
var import_lucide_react10 = require("lucide-react");
|
|
1198
|
+
var import_popup4 = require("@behindthemusictree/app-kit/popup");
|
|
1199
|
+
|
|
1200
|
+
// src/genre-tree/SharePopup.tsx
|
|
1201
|
+
var import_react8 = require("react");
|
|
1202
|
+
var import_lucide_react9 = require("lucide-react");
|
|
1203
|
+
var import_fa3 = require("react-icons/fa");
|
|
1204
|
+
var import_si = require("react-icons/si");
|
|
1205
|
+
|
|
1206
|
+
// src/genre-tree/ShareDestinationButton.tsx
|
|
1207
|
+
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
1208
|
+
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";
|
|
1209
|
+
function Contents({ icon, label }) {
|
|
1210
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
|
|
1211
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.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 }),
|
|
1212
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "max-w-full truncate", children: label })
|
|
1213
|
+
] });
|
|
1214
|
+
}
|
|
1215
|
+
function ShareDestinationButton({
|
|
1216
|
+
label,
|
|
1217
|
+
icon,
|
|
1218
|
+
href,
|
|
1219
|
+
onClick,
|
|
1220
|
+
disabled = false,
|
|
1221
|
+
ariaLabel
|
|
1405
1222
|
}) {
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1223
|
+
if (href) {
|
|
1224
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", "aria-label": ariaLabel, className, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Contents, { icon, label }) });
|
|
1225
|
+
}
|
|
1226
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", onClick, disabled, "aria-label": ariaLabel, className, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Contents, { icon, label }) });
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
// src/genre-tree/SharePreviewCard.tsx
|
|
1230
|
+
var import_lucide_react8 = require("lucide-react");
|
|
1231
|
+
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
1232
|
+
function SharePreviewCard({ target, url }) {
|
|
1233
|
+
const Icon = target.kind === "genre" ? import_lucide_react8.Music2 : import_lucide_react8.UserRound;
|
|
1234
|
+
const typeLabel = target.kind === "genre" ? "Genre" : "Artist";
|
|
1235
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "rounded-3xl bg-[var(--color-surface-muted)] p-4", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex min-h-32 overflow-hidden rounded-2xl bg-[var(--color-surface)] shadow-lg", children: [
|
|
1236
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.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_runtime18.jsx)(Icon, { "aria-hidden": "true", className: "h-10 w-10" }) }),
|
|
1237
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col justify-center gap-1 p-4", children: [
|
|
1238
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "text-xs font-medium uppercase tracking-wide text-[var(--color-text-muted)]", children: typeLabel }),
|
|
1239
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "truncate text-base font-semibold text-[var(--color-text)]", title: target.name, children: target.name }),
|
|
1240
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "truncate text-sm text-[var(--color-primary)]", title: url, children: url })
|
|
1241
|
+
] })
|
|
1242
|
+
] }) });
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
// src/genre-tree/SharePopup.tsx
|
|
1246
|
+
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
1247
|
+
function resolveAbsoluteUrl(url) {
|
|
1248
|
+
try {
|
|
1249
|
+
const parsed = new URL(url, window.location.href);
|
|
1250
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:" ? parsed.href : void 0;
|
|
1251
|
+
} catch {
|
|
1252
|
+
return void 0;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
async function copyText(text) {
|
|
1256
|
+
try {
|
|
1257
|
+
if (navigator.clipboard?.writeText) {
|
|
1258
|
+
await navigator.clipboard.writeText(text);
|
|
1259
|
+
return true;
|
|
1260
|
+
}
|
|
1261
|
+
} catch {
|
|
1262
|
+
}
|
|
1263
|
+
const input = document.createElement("textarea");
|
|
1264
|
+
input.value = text;
|
|
1265
|
+
input.setAttribute("readonly", "");
|
|
1266
|
+
input.style.position = "fixed";
|
|
1267
|
+
input.style.opacity = "0";
|
|
1268
|
+
document.body.append(input);
|
|
1269
|
+
input.select();
|
|
1270
|
+
try {
|
|
1271
|
+
return document.execCommand("copy");
|
|
1272
|
+
} catch {
|
|
1273
|
+
return false;
|
|
1274
|
+
} finally {
|
|
1275
|
+
input.remove();
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
function SharePopup({ target, url, onClose }) {
|
|
1279
|
+
const [absoluteUrl, setAbsoluteUrl] = (0, import_react8.useState)(url);
|
|
1280
|
+
const [copyStatus, setCopyStatus] = (0, import_react8.useState)("idle");
|
|
1281
|
+
const [canUseNativeShare, setCanUseNativeShare] = (0, import_react8.useState)(false);
|
|
1282
|
+
const [shareError, setShareError] = (0, import_react8.useState)(null);
|
|
1283
|
+
const [toastId, setToastId] = (0, import_react8.useState)(0);
|
|
1284
|
+
(0, import_react8.useEffect)(() => {
|
|
1285
|
+
setAbsoluteUrl(resolveAbsoluteUrl(url) ?? "");
|
|
1286
|
+
setCopyStatus("idle");
|
|
1287
|
+
setShareError(null);
|
|
1288
|
+
}, [url]);
|
|
1289
|
+
(0, import_react8.useEffect)(() => {
|
|
1290
|
+
setCanUseNativeShare(typeof navigator !== "undefined" && typeof navigator.share === "function");
|
|
1291
|
+
}, []);
|
|
1292
|
+
const destinationUrls = (0, import_react8.useMemo)(() => {
|
|
1293
|
+
if (!absoluteUrl) return null;
|
|
1294
|
+
const message = `${target.name} \u2014 ${absoluteUrl}`;
|
|
1295
|
+
return {
|
|
1296
|
+
whatsapp: `https://wa.me/?text=${encodeURIComponent(message)}`,
|
|
1297
|
+
gmail: `https://mail.google.com/mail/?view=cm&fs=1&su=${encodeURIComponent(`Share: ${target.name}`)}&body=${encodeURIComponent(message)}`
|
|
1298
|
+
};
|
|
1299
|
+
}, [absoluteUrl, target.name]);
|
|
1300
|
+
const handleCopy = async () => {
|
|
1301
|
+
if (!absoluteUrl) {
|
|
1302
|
+
setCopyStatus("failed");
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
setShareError(null);
|
|
1306
|
+
const copied = await copyText(absoluteUrl);
|
|
1307
|
+
setCopyStatus(copied ? "idle" : "failed");
|
|
1308
|
+
if (copied) setToastId((id) => id + 1);
|
|
1309
|
+
};
|
|
1310
|
+
const dismissToast = (0, import_react8.useCallback)(() => setToastId(0), []);
|
|
1311
|
+
const handleNativeShare = async () => {
|
|
1312
|
+
if (!absoluteUrl || !navigator.share) return;
|
|
1313
|
+
setShareError(null);
|
|
1314
|
+
try {
|
|
1315
|
+
await navigator.share({ title: target.name, url: absoluteUrl });
|
|
1316
|
+
} catch (error) {
|
|
1317
|
+
if (error instanceof Error && error.name === "AbortError") return;
|
|
1318
|
+
setShareError("Could not open the share options.");
|
|
1319
|
+
}
|
|
1320
|
+
};
|
|
1321
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
|
|
1322
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
1323
|
+
BasePopup_default,
|
|
1324
|
+
{
|
|
1325
|
+
title: "Share",
|
|
1326
|
+
onClose,
|
|
1327
|
+
isDismissable: true,
|
|
1328
|
+
trapFocus: true,
|
|
1329
|
+
closeOnEscape: true,
|
|
1330
|
+
closeOnBackdrop: true,
|
|
1331
|
+
className: "!w-full !max-w-[28rem] overflow-hidden !rounded-[2rem] !bg-[var(--color-surface)] shadow-2xl",
|
|
1332
|
+
overlayClassName: "!bg-black/50 p-4 backdrop-blur-[2px]",
|
|
1333
|
+
titleClassName: "!rounded-none !bg-transparent !px-6 !pb-2 !pt-6",
|
|
1334
|
+
titleTextClassName: "!text-xl !font-semibold !text-[var(--color-text)]",
|
|
1335
|
+
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)]",
|
|
1336
|
+
contentClassName: "!rounded-none !bg-transparent !px-6 !pb-6 !pt-2",
|
|
1337
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex flex-col gap-4", children: [
|
|
1338
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(SharePreviewCard, { target, url: absoluteUrl || url }),
|
|
1339
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "grid grid-cols-3 gap-1 rounded-3xl bg-[var(--color-surface)] p-3 sm:grid-cols-4", children: [
|
|
1340
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
1341
|
+
ShareDestinationButton,
|
|
1342
|
+
{
|
|
1343
|
+
label: "Copy Link",
|
|
1344
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react9.Link2, { "aria-hidden": "true", className: "h-6 w-6" }),
|
|
1345
|
+
onClick: () => void handleCopy(),
|
|
1346
|
+
ariaLabel: "Copy link",
|
|
1347
|
+
disabled: !absoluteUrl
|
|
1348
|
+
}
|
|
1349
|
+
),
|
|
1350
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
1351
|
+
ShareDestinationButton,
|
|
1352
|
+
{
|
|
1353
|
+
label: "WhatsApp",
|
|
1354
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_fa3.FaWhatsapp, { "aria-hidden": "true", className: "h-9 w-9 text-[#25D366]" }),
|
|
1355
|
+
href: destinationUrls?.whatsapp,
|
|
1356
|
+
ariaLabel: `Share ${target.name} via WhatsApp`,
|
|
1357
|
+
disabled: !absoluteUrl
|
|
1358
|
+
}
|
|
1359
|
+
),
|
|
1360
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
1361
|
+
ShareDestinationButton,
|
|
1362
|
+
{
|
|
1363
|
+
label: "Gmail",
|
|
1364
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_si.SiGmail, { "aria-hidden": "true", className: "h-8 w-8 text-[#EA4335]" }),
|
|
1365
|
+
href: destinationUrls?.gmail,
|
|
1366
|
+
ariaLabel: `Email ${target.name} with Gmail`,
|
|
1367
|
+
disabled: !absoluteUrl
|
|
1368
|
+
}
|
|
1369
|
+
),
|
|
1370
|
+
canUseNativeShare && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
1371
|
+
ShareDestinationButton,
|
|
1372
|
+
{
|
|
1373
|
+
label: "More",
|
|
1374
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react9.Share2, { "aria-hidden": "true", className: "h-6 w-6" }),
|
|
1375
|
+
onClick: () => void handleNativeShare(),
|
|
1376
|
+
ariaLabel: "More sharing options",
|
|
1377
|
+
disabled: !absoluteUrl
|
|
1378
|
+
}
|
|
1379
|
+
)
|
|
1380
|
+
] }),
|
|
1381
|
+
copyStatus === "failed" && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: "Could not copy the link." }),
|
|
1382
|
+
shareError && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: shareError })
|
|
1383
|
+
] })
|
|
1384
|
+
}
|
|
1385
|
+
),
|
|
1386
|
+
toastId > 0 && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Toast, { message: "Link copied", onClose: dismissToast }, toastId)
|
|
1387
|
+
] });
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
// src/genre-tree/ShareActionButton.tsx
|
|
1391
|
+
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
1392
|
+
function ShareActionButton({ target, url }) {
|
|
1393
|
+
const { showPopup, hidePopup } = (0, import_popup4.usePopup)();
|
|
1394
|
+
const openSharePopup = () => {
|
|
1395
|
+
showPopup(/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(SharePopup, { target, url, onClose: hidePopup }));
|
|
1396
|
+
};
|
|
1397
|
+
return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
|
|
1398
|
+
"button",
|
|
1399
|
+
{
|
|
1400
|
+
type: "button",
|
|
1401
|
+
onClick: openSharePopup,
|
|
1402
|
+
className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none",
|
|
1403
|
+
"aria-label": `Share ${target.name}`,
|
|
1404
|
+
title: `Share ${target.name}`,
|
|
1405
|
+
children: [
|
|
1406
|
+
/* @__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)]", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react10.Share2, { "aria-hidden": "true", className: "w-5 h-5" }) }),
|
|
1407
|
+
"Share"
|
|
1408
|
+
]
|
|
1409
|
+
}
|
|
1410
|
+
);
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
// src/genre-tree/GenreActionBand.tsx
|
|
1414
|
+
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
1415
|
+
function GenreActionBand({
|
|
1416
|
+
node,
|
|
1417
|
+
onPlayPause,
|
|
1418
|
+
isPlaying = false,
|
|
1419
|
+
onZoomIn,
|
|
1420
|
+
onAddChild,
|
|
1421
|
+
onRename,
|
|
1422
|
+
onChangeParent,
|
|
1423
|
+
side,
|
|
1424
|
+
onToggleSide,
|
|
1425
|
+
toggleSideDisabledReason,
|
|
1426
|
+
additionalActions,
|
|
1427
|
+
shareTarget,
|
|
1428
|
+
shareUrl
|
|
1429
|
+
}) {
|
|
1430
|
+
const items = [];
|
|
1431
|
+
const adminItems = [];
|
|
1432
|
+
if (onPlayPause) {
|
|
1433
|
+
items.push({
|
|
1434
|
+
key: "play-pause",
|
|
1435
|
+
label: isPlaying ? "Pause" : "Play",
|
|
1436
|
+
icon: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react11.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react11.Play, { "aria-hidden": "true", className: "w-5 h-5" }),
|
|
1437
|
+
onClick: onPlayPause
|
|
1438
|
+
});
|
|
1415
1439
|
}
|
|
1416
1440
|
if (onZoomIn) {
|
|
1417
1441
|
items.push({
|
|
1418
1442
|
key: "zoom-in",
|
|
1419
1443
|
label: "Focus",
|
|
1420
|
-
icon: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
1444
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react11.ZoomIn, { "aria-hidden": "true", className: "w-5 h-5" }),
|
|
1421
1445
|
onClick: onZoomIn
|
|
1422
1446
|
});
|
|
1423
1447
|
}
|
|
1424
1448
|
if (onAddChild) {
|
|
1425
|
-
adminItems.push({ key: "add-child", label: "Add", icon: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
1449
|
+
adminItems.push({ key: "add-child", label: "Add", icon: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react11.Plus, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onAddChild });
|
|
1426
1450
|
}
|
|
1427
1451
|
if (onRename) {
|
|
1428
|
-
adminItems.push({ key: "rename", label: "Rename", icon: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
1452
|
+
adminItems.push({ key: "rename", label: "Rename", icon: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react11.Pencil, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onRename });
|
|
1429
1453
|
}
|
|
1430
1454
|
if (onChangeParent) {
|
|
1431
|
-
adminItems.push({ key: "change-parent", label: "Parent", icon: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
1455
|
+
adminItems.push({ key: "change-parent", label: "Parent", icon: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react11.MoveUpRight, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onChangeParent });
|
|
1456
|
+
}
|
|
1457
|
+
if (onToggleSide) {
|
|
1458
|
+
adminItems.push({
|
|
1459
|
+
key: "toggle-side",
|
|
1460
|
+
label: side === "pop" ? "Make core" : "Make pop",
|
|
1461
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react11.ArrowLeftRight, { "aria-hidden": "true", className: "w-5 h-5" }),
|
|
1462
|
+
onClick: onToggleSide,
|
|
1463
|
+
disabled: !!toggleSideDisabledReason,
|
|
1464
|
+
title: toggleSideDisabledReason
|
|
1465
|
+
});
|
|
1432
1466
|
}
|
|
1433
1467
|
for (const action of additionalActions?.(node) ?? []) {
|
|
1434
1468
|
items.push({
|
|
@@ -1439,12 +1473,13 @@ function GenreActionBand({
|
|
|
1439
1473
|
disabled: action.enabled ? !action.enabled(node) : false
|
|
1440
1474
|
});
|
|
1441
1475
|
}
|
|
1442
|
-
if (items.length === 0 && adminItems.length === 0) return null;
|
|
1476
|
+
if (items.length === 0 && (!shareTarget || !shareUrl) && adminItems.length === 0) return null;
|
|
1443
1477
|
const renderItems = (list) => list.map((item) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
|
|
1444
1478
|
"button",
|
|
1445
1479
|
{
|
|
1446
1480
|
type: "button",
|
|
1447
1481
|
disabled: item.disabled,
|
|
1482
|
+
title: item.title,
|
|
1448
1483
|
onClick: item.onClick,
|
|
1449
1484
|
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",
|
|
1450
1485
|
children: [
|
|
@@ -1455,7 +1490,10 @@ function GenreActionBand({
|
|
|
1455
1490
|
item.key
|
|
1456
1491
|
));
|
|
1457
1492
|
return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: [
|
|
1458
|
-
items.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime21.
|
|
1493
|
+
(items.length > 0 || shareTarget && shareUrl) && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
|
|
1494
|
+
renderItems(items),
|
|
1495
|
+
shareTarget && shareUrl && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
|
|
1496
|
+
] }),
|
|
1459
1497
|
adminItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
|
|
1460
1498
|
"section",
|
|
1461
1499
|
{
|
|
@@ -1470,17 +1508,392 @@ function GenreActionBand({
|
|
|
1470
1508
|
] });
|
|
1471
1509
|
}
|
|
1472
1510
|
|
|
1511
|
+
// src/genre-tree/GenreDetailExtras.tsx
|
|
1512
|
+
var import_ui8 = require("@behindthemusictree/ui");
|
|
1513
|
+
var import_genre_tree9 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1514
|
+
|
|
1515
|
+
// src/genre-tree/ClampedText.tsx
|
|
1516
|
+
var import_react9 = require("react");
|
|
1517
|
+
var import_jsx_runtime22 = require("react/jsx-runtime");
|
|
1518
|
+
function ClampedText({ children, className: className2 = "" }) {
|
|
1519
|
+
const ref = (0, import_react9.useRef)(null);
|
|
1520
|
+
const [expanded, setExpanded] = (0, import_react9.useState)(false);
|
|
1521
|
+
const [overflows, setOverflows] = (0, import_react9.useState)(false);
|
|
1522
|
+
(0, import_react9.useLayoutEffect)(() => {
|
|
1523
|
+
const element = ref.current;
|
|
1524
|
+
if (!element || expanded) return;
|
|
1525
|
+
setOverflows(element.scrollHeight > element.clientHeight);
|
|
1526
|
+
}, [children, expanded]);
|
|
1527
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
|
|
1528
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { ref, className: `${className2} ${expanded ? "" : "line-clamp-3"}`, children }),
|
|
1529
|
+
(overflows || expanded) && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
1530
|
+
"button",
|
|
1531
|
+
{
|
|
1532
|
+
type: "button",
|
|
1533
|
+
"aria-expanded": expanded,
|
|
1534
|
+
onClick: () => setExpanded((value) => !value),
|
|
1535
|
+
className: "mt-1 text-xs text-[var(--color-text-muted)] underline hover:text-[var(--color-text)]",
|
|
1536
|
+
children: expanded ? "Show less" : "Show more"
|
|
1537
|
+
}
|
|
1538
|
+
)
|
|
1539
|
+
] });
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
// src/genre-tree/GenrePlaylistTracks.tsx
|
|
1543
|
+
var import_react10 = require("react");
|
|
1544
|
+
var import_react_query = require("@tanstack/react-query");
|
|
1545
|
+
var import_zod = require("zod");
|
|
1546
|
+
var import_fa4 = require("react-icons/fa");
|
|
1547
|
+
var import_ui7 = require("@behindthemusictree/ui");
|
|
1548
|
+
var import_auth2 = require("@behindthemusictree/app-kit/auth");
|
|
1549
|
+
var import_transport4 = require("@behindthemusictree/app-kit/transport");
|
|
1550
|
+
var import_transport5 = require("@behindthemusictree/app-kit/transport");
|
|
1551
|
+
var import_transport6 = require("@behindthemusictree/app-kit/transport");
|
|
1552
|
+
var import_player4 = require("@behindthemusictree/app-kit/player");
|
|
1553
|
+
var import_player5 = require("@behindthemusictree/app-kit/player");
|
|
1554
|
+
var import_popup5 = require("@behindthemusictree/app-kit/popup");
|
|
1555
|
+
var import_transport7 = require("@behindthemusictree/app-kit/transport");
|
|
1556
|
+
var import_genre_tree4 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1557
|
+
var import_genre_tree5 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1558
|
+
var import_genre_tree6 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1559
|
+
var import_genre_tree7 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1560
|
+
|
|
1561
|
+
// src/genre-tree/MusicBrainzRecordingLink.tsx
|
|
1562
|
+
var import_genre_tree3 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1563
|
+
var import_jsx_runtime23 = require("react/jsx-runtime");
|
|
1564
|
+
function MusicBrainzRecordingLink({
|
|
1565
|
+
song,
|
|
1566
|
+
children,
|
|
1567
|
+
className: className2 = ""
|
|
1568
|
+
}) {
|
|
1569
|
+
const url = (0, import_genre_tree3.musicBrainzRecordingUrl)(song);
|
|
1570
|
+
if (!url) return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_jsx_runtime23.Fragment, { children });
|
|
1571
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
1572
|
+
"a",
|
|
1573
|
+
{
|
|
1574
|
+
href: url,
|
|
1575
|
+
target: "_blank",
|
|
1576
|
+
rel: "noopener noreferrer",
|
|
1577
|
+
"aria-label": `View ${song.title} on MusicBrainz`,
|
|
1578
|
+
className: className2,
|
|
1579
|
+
onClick: (event) => event.stopPropagation(),
|
|
1580
|
+
children
|
|
1581
|
+
}
|
|
1582
|
+
);
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
// src/genre-tree/GenrePlaylistTracks.tsx
|
|
1586
|
+
var import_genre_tree8 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1587
|
+
var import_track_noun5 = require("@behindthemusictree/app-kit/track-noun");
|
|
1588
|
+
var import_jsx_runtime24 = require("react/jsx-runtime");
|
|
1589
|
+
var CATALOGUE_PAGE_SIZE = 100;
|
|
1590
|
+
var SongPageSchema = (0, import_transport5.PaginatedResponseSchema)(import_genre_tree6.SongDetailedSchema).extend({
|
|
1591
|
+
overallTotal: import_zod.z.number().nullable(),
|
|
1592
|
+
page: import_zod.z.number().nullable(),
|
|
1593
|
+
totalPages: import_zod.z.number().nullable()
|
|
1594
|
+
});
|
|
1595
|
+
var TEXT_CELL = "min-w-0 flex-1 truncate [@container(min-width:40rem)]:flex [@container(min-width:40rem)]:gap-2";
|
|
1596
|
+
var TITLE_CELL = "[@container(min-width:40rem)]:flex-[2] [@container(min-width:40rem)]:truncate";
|
|
1597
|
+
var COLUMN_CELL = "[@container(min-width:40rem)]:flex-1 [@container(min-width:40rem)]:truncate";
|
|
1598
|
+
var ALBUM_CELL = `hidden [@container(min-width:40rem)]:block ${COLUMN_CELL}`;
|
|
1599
|
+
var SEPARATOR = "[@container(min-width:40rem)]:hidden";
|
|
1600
|
+
var nextCursor = (next) => {
|
|
1601
|
+
const cursor = new URL(next).searchParams.get("cursor");
|
|
1602
|
+
if (!cursor) throw new Error(`Songs next link has no cursor: ${next}`);
|
|
1603
|
+
return cursor;
|
|
1604
|
+
};
|
|
1605
|
+
function GenrePlaylistTracks({
|
|
1606
|
+
genreUuid,
|
|
1607
|
+
genrePlaylist,
|
|
1608
|
+
scope,
|
|
1609
|
+
getBackendBaseUrl,
|
|
1610
|
+
renderArtistLink,
|
|
1611
|
+
renderSongLeading,
|
|
1612
|
+
renderSongActions,
|
|
1613
|
+
renderTracksToolbar,
|
|
1614
|
+
artist = null
|
|
1615
|
+
}) {
|
|
1616
|
+
const { fetchGenrePlaylistTracksPage, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist } = (0, import_genre_tree7.useTrackList)();
|
|
1617
|
+
const { session, sessionRestored } = (0, import_auth2.useSession)();
|
|
1618
|
+
const { playerTrackObject, playState, handlePlayPauseAction } = (0, import_player4.usePlayer)();
|
|
1619
|
+
const { showPopup } = (0, import_popup5.usePopup)();
|
|
1620
|
+
const trackNoun = (0, import_track_noun5.useTrackNoun)();
|
|
1621
|
+
const { uuid } = genrePlaylist;
|
|
1622
|
+
const { fetch } = (0, import_transport4.useFetchWrapper)(getBackendBaseUrl);
|
|
1623
|
+
const { data, isPending, isError, refetch, hasNextPage, isFetchingNextPage, fetchNextPage } = (0, import_react_query.useInfiniteQuery)({
|
|
1624
|
+
queryKey: scope === "me" ? import_genre_tree4.genrePlaylistQueryKeys.me.tracks(uuid) : artist ? [...import_genre_tree5.libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid), "artist", artist.uuid] : import_genre_tree5.libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid),
|
|
1625
|
+
queryFn: async ({ pageParam }) => {
|
|
1626
|
+
if (scope === "me") return fetchGenrePlaylistTracksPage(uuid, scope, Number(pageParam));
|
|
1627
|
+
const response = await fetch(
|
|
1628
|
+
import_genre_tree5.libraryEndpoints.reference.songs.list(),
|
|
1629
|
+
true,
|
|
1630
|
+
false,
|
|
1631
|
+
{},
|
|
1632
|
+
{ genre: genreUuid, ...artist && { artist: artist.uuid }, pageSize: CATALOGUE_PAGE_SIZE, withTotal: false, ...pageParam && { cursor: pageParam } }
|
|
1633
|
+
);
|
|
1634
|
+
const parsed = (0, import_transport6.parseWithLog)(SongPageSchema, response, "GenrePlaylistTracks");
|
|
1635
|
+
return { tracks: parsed.results, total: parsed.overallTotal, nextPage: parsed.next ? nextCursor(parsed.next) : null };
|
|
1636
|
+
},
|
|
1637
|
+
initialPageParam: scope === "me" ? 1 : "",
|
|
1638
|
+
getNextPageParam: (lastPage) => lastPage.nextPage ?? void 0,
|
|
1639
|
+
enabled: scope !== "me" || sessionRestored && !!session?.accessToken
|
|
1640
|
+
});
|
|
1641
|
+
const sentinelRef = (0, import_react10.useRef)(null);
|
|
1642
|
+
(0, import_react10.useEffect)(() => {
|
|
1643
|
+
const sentinel = sentinelRef.current;
|
|
1644
|
+
if (!hasNextPage || isFetchingNextPage || !sentinel) return;
|
|
1645
|
+
const observer = new IntersectionObserver((entries) => {
|
|
1646
|
+
if (entries.some((entry) => entry.isIntersecting)) {
|
|
1647
|
+
void fetchNextPage();
|
|
1648
|
+
}
|
|
1649
|
+
});
|
|
1650
|
+
observer.observe(sentinel);
|
|
1651
|
+
return () => observer.disconnect();
|
|
1652
|
+
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);
|
|
1653
|
+
const pages = data?.pages ?? [];
|
|
1654
|
+
const tracks = pages.flatMap((page) => page.tracks);
|
|
1655
|
+
const total = pages[0]?.total ?? void 0;
|
|
1656
|
+
const play = (startSongUuid) => {
|
|
1657
|
+
const started = artist ? playNewTrackListFromArtist(artist, scope, { startSongUuid, ordering: "title", genreUuid }) : playNewTrackListFromGenrePlaylist(genrePlaylist, scope, { startSongUuid });
|
|
1658
|
+
started.catch((error) => {
|
|
1659
|
+
console.error("Failed to play genre playlist:", error);
|
|
1660
|
+
showPopup(/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(InternalErrorPopup, { errorCode: import_transport7.ErrorCode.CLIENT_UNKNOWN }));
|
|
1661
|
+
});
|
|
1662
|
+
};
|
|
1663
|
+
return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "gtv-info-panel-children", children: [
|
|
1664
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { className: "gtv-info-panel-children-title", children: [
|
|
1665
|
+
(0, import_track_noun5.capitalize)(trackNoun.plural),
|
|
1666
|
+
total !== void 0 && ` (${total})`
|
|
1667
|
+
] }),
|
|
1668
|
+
renderTracksToolbar?.(),
|
|
1669
|
+
isPending ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ui7.Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("p", { role: "alert", children: [
|
|
1670
|
+
"Couldn't load ",
|
|
1671
|
+
trackNoun.plural,
|
|
1672
|
+
".",
|
|
1673
|
+
" ",
|
|
1674
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
|
|
1675
|
+
] }) : tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { children: "\u2014" }) : /* @__PURE__ */ (0, import_jsx_runtime24.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: [
|
|
1676
|
+
tracks.map((track, index) => {
|
|
1677
|
+
const label = track.artists?.length ? `${track.title} \u2014 ${track.artists.map((artist2) => artist2.name).join(", ")}` : track.title;
|
|
1678
|
+
const isCurrent = playerTrackObject?.track.id === track.uuid;
|
|
1679
|
+
const isPlaying = isCurrent && playState === import_player5.PlayStates.PLAYING;
|
|
1680
|
+
const text = /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { className: TEXT_CELL, children: [
|
|
1681
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: TITLE_CELL, children: (0, import_genre_tree6.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
|
|
1682
|
+
track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: SEPARATOR, children: " \u2014 " }) : null,
|
|
1683
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_genre_tree8.ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
|
|
1684
|
+
track.album && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: ALBUM_CELL, children: track.album.name })
|
|
1685
|
+
] });
|
|
1686
|
+
const leading = renderSongLeading?.(track, index);
|
|
1687
|
+
const actions = renderSongActions?.(track);
|
|
1688
|
+
const isUnplayable = (0, import_genre_tree6.isSong)(track) && (0, import_genre_tree6.songUnplayableReason)(track) !== null;
|
|
1689
|
+
if (isUnplayable) {
|
|
1690
|
+
return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("li", { className: "flex min-h-8 items-center gap-1 px-1 opacity-50", children: [
|
|
1691
|
+
leading,
|
|
1692
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
1693
|
+
"button",
|
|
1694
|
+
{
|
|
1695
|
+
type: "button",
|
|
1696
|
+
disabled: true,
|
|
1697
|
+
"aria-disabled": "true",
|
|
1698
|
+
"aria-label": label,
|
|
1699
|
+
className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full opacity-0 cursor-not-allowed",
|
|
1700
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_fa4.FaPlay, { size: 10 })
|
|
1701
|
+
}
|
|
1702
|
+
),
|
|
1703
|
+
text,
|
|
1704
|
+
actions
|
|
1705
|
+
] }, track.uuid);
|
|
1706
|
+
}
|
|
1707
|
+
return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
|
|
1708
|
+
"li",
|
|
1709
|
+
{
|
|
1710
|
+
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)]" : ""}`,
|
|
1711
|
+
children: [
|
|
1712
|
+
leading,
|
|
1713
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
1714
|
+
"button",
|
|
1715
|
+
{
|
|
1716
|
+
type: "button",
|
|
1717
|
+
"aria-label": `${isPlaying ? "Pause" : "Play"} ${label}`,
|
|
1718
|
+
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"}`,
|
|
1719
|
+
onClick: () => isCurrent ? handlePlayPauseAction() : play(track.uuid),
|
|
1720
|
+
children: isCurrent && playState === import_player5.PlayStates.LOADING ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ui7.RingLoader, { size: 10 }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_fa4.FaPause, { size: 10 }) : /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_fa4.FaPlay, { size: 10 })
|
|
1721
|
+
}
|
|
1722
|
+
),
|
|
1723
|
+
text,
|
|
1724
|
+
actions
|
|
1725
|
+
]
|
|
1726
|
+
},
|
|
1727
|
+
track.uuid
|
|
1728
|
+
);
|
|
1729
|
+
}),
|
|
1730
|
+
hasNextPage && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("li", { ref: sentinelRef, "aria-hidden": "true", className: "h-4" })
|
|
1731
|
+
] })
|
|
1732
|
+
] });
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
// src/genre-tree/GenreDetailExtras.tsx
|
|
1736
|
+
var import_track_noun6 = require("@behindthemusictree/app-kit/track-noun");
|
|
1737
|
+
var import_si2 = require("react-icons/si");
|
|
1738
|
+
var import_jsx_runtime25 = require("react/jsx-runtime");
|
|
1739
|
+
function GenreDetailExtras({
|
|
1740
|
+
genreUuid,
|
|
1741
|
+
genrePlaylist,
|
|
1742
|
+
wikidataId,
|
|
1743
|
+
scope,
|
|
1744
|
+
getBackendBaseUrl,
|
|
1745
|
+
criteriaOverviewSchema,
|
|
1746
|
+
renderGenreDetailExtras,
|
|
1747
|
+
renderArtistLink,
|
|
1748
|
+
renderSummary,
|
|
1749
|
+
renderEssentialTracks,
|
|
1750
|
+
family,
|
|
1751
|
+
...trackSlots
|
|
1752
|
+
}) {
|
|
1753
|
+
const trackNoun = (0, import_track_noun6.useTrackNoun)();
|
|
1754
|
+
const { data: overview, isPending } = (0, import_genre_tree9.useFetchGenreOverview)(
|
|
1755
|
+
genreUuid,
|
|
1756
|
+
scope,
|
|
1757
|
+
getBackendBaseUrl,
|
|
1758
|
+
criteriaOverviewSchema
|
|
1759
|
+
);
|
|
1760
|
+
if (isPending) {
|
|
1761
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_jsx_runtime25.Fragment, { children: [
|
|
1762
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "gtv-info-panel-children", "aria-busy": "true", "data-testid": "genre-detail-extras-skeleton", children: [
|
|
1763
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "gtv-info-panel-children-title", children: "Summary" }),
|
|
1764
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_ui8.Skeleton, { className: "h-4 w-3/4" })
|
|
1765
|
+
] }),
|
|
1766
|
+
family
|
|
1767
|
+
] });
|
|
1768
|
+
}
|
|
1769
|
+
if (!overview) return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_jsx_runtime25.Fragment, { children: family });
|
|
1770
|
+
const { summary, summarySourceUrl, essentialSongs } = overview;
|
|
1771
|
+
const wikidataUrl = wikidataId ? `https://www.wikidata.org/wiki/${encodeURIComponent(wikidataId)}` : null;
|
|
1772
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_jsx_runtime25.Fragment, { children: [
|
|
1773
|
+
(summarySourceUrl || wikidataUrl) && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "gtv-info-panel-children text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: [
|
|
1774
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "gtv-info-panel-children-title", children: "Links" }),
|
|
1775
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "mt-1 flex flex-wrap gap-x-3 gap-y-1 text-xs", children: [
|
|
1776
|
+
summarySourceUrl && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
|
|
1777
|
+
"a",
|
|
1778
|
+
{
|
|
1779
|
+
href: summarySourceUrl,
|
|
1780
|
+
target: "_blank",
|
|
1781
|
+
rel: "noopener noreferrer",
|
|
1782
|
+
className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
|
|
1783
|
+
children: [
|
|
1784
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_si2.SiWikipedia, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
|
|
1785
|
+
"Wikipedia"
|
|
1786
|
+
]
|
|
1787
|
+
}
|
|
1788
|
+
),
|
|
1789
|
+
wikidataUrl && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
|
|
1790
|
+
"a",
|
|
1791
|
+
{
|
|
1792
|
+
href: wikidataUrl,
|
|
1793
|
+
target: "_blank",
|
|
1794
|
+
rel: "noopener noreferrer",
|
|
1795
|
+
className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
|
|
1796
|
+
children: [
|
|
1797
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_si2.SiWikidata, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
|
|
1798
|
+
"Wikidata"
|
|
1799
|
+
]
|
|
1800
|
+
}
|
|
1801
|
+
)
|
|
1802
|
+
] })
|
|
1803
|
+
] }),
|
|
1804
|
+
renderSummary ? renderSummary(overview) : /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "gtv-info-panel-children text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: [
|
|
1805
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "gtv-info-panel-children-title", children: "Summary" }),
|
|
1806
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ClampedText, { className: "mt-1", children: summary ?? "\u2014" }, genreUuid)
|
|
1807
|
+
] }),
|
|
1808
|
+
family,
|
|
1809
|
+
renderGenreDetailExtras?.(overview),
|
|
1810
|
+
renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "gtv-info-panel-children text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: [
|
|
1811
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("span", { className: "gtv-info-panel-children-title", children: [
|
|
1812
|
+
"Essential ",
|
|
1813
|
+
trackNoun.plural
|
|
1814
|
+
] }),
|
|
1815
|
+
essentialSongs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("ul", { className: "mt-1 divide-y divide-[var(--color-border)]", children: essentialSongs.map((track) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("li", { className: "py-1", children: track.title }, track.uuid)) }) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("p", { className: "mt-1", children: "\u2014" })
|
|
1816
|
+
] }),
|
|
1817
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
1818
|
+
GenrePlaylistTracks,
|
|
1819
|
+
{
|
|
1820
|
+
genreUuid,
|
|
1821
|
+
genrePlaylist,
|
|
1822
|
+
scope,
|
|
1823
|
+
getBackendBaseUrl,
|
|
1824
|
+
renderArtistLink,
|
|
1825
|
+
...trackSlots
|
|
1826
|
+
}
|
|
1827
|
+
)
|
|
1828
|
+
] });
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
// src/genre-tree/GenreInfoPanelBody.tsx
|
|
1832
|
+
var import_jsx_runtime26 = require("react/jsx-runtime");
|
|
1833
|
+
var import_react11 = require("react");
|
|
1834
|
+
function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
|
|
1835
|
+
const { isPlaying, setIsPlaying } = (0, import_player6.usePlayer)();
|
|
1836
|
+
const isCurrentTrackList = playback.playingNodeId === genrePlaylist.uuid;
|
|
1837
|
+
const handlePlayPause = () => {
|
|
1838
|
+
if (isCurrentTrackList) {
|
|
1839
|
+
setIsPlaying(!isPlaying);
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1842
|
+
playback.play(genrePlaylist).catch((error) => {
|
|
1843
|
+
console.error("Failed to load genre playlist tracks:", error);
|
|
1844
|
+
});
|
|
1845
|
+
};
|
|
1846
|
+
return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
|
|
1847
|
+
GenreActionBand,
|
|
1848
|
+
{
|
|
1849
|
+
...bandProps,
|
|
1850
|
+
onPlayPause: genrePlaylist.tracksCount > 0 ? handlePlayPause : void 0,
|
|
1851
|
+
isPlaying: isCurrentTrackList && isPlaying
|
|
1852
|
+
}
|
|
1853
|
+
);
|
|
1854
|
+
}
|
|
1855
|
+
function GenreInfoPanelBody({
|
|
1856
|
+
toolbar,
|
|
1857
|
+
family,
|
|
1858
|
+
genreUuid,
|
|
1859
|
+
genrePlaylist,
|
|
1860
|
+
wikidataId,
|
|
1861
|
+
scope,
|
|
1862
|
+
getBackendBaseUrl,
|
|
1863
|
+
playback,
|
|
1864
|
+
band,
|
|
1865
|
+
extras
|
|
1866
|
+
}) {
|
|
1867
|
+
return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_jsx_runtime26.Fragment, { children: [
|
|
1868
|
+
toolbar,
|
|
1869
|
+
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)(GenreNodeActionBand, { ...band, genrePlaylist, playback }),
|
|
1870
|
+
/* @__PURE__ */ (0, import_react11.createElement)(
|
|
1871
|
+
GenreDetailExtras,
|
|
1872
|
+
{
|
|
1873
|
+
...extras,
|
|
1874
|
+
key: genreUuid,
|
|
1875
|
+
family,
|
|
1876
|
+
genreUuid,
|
|
1877
|
+
genrePlaylist,
|
|
1878
|
+
wikidataId,
|
|
1879
|
+
scope,
|
|
1880
|
+
getBackendBaseUrl
|
|
1881
|
+
}
|
|
1882
|
+
)
|
|
1883
|
+
] });
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1473
1886
|
// src/genre-tree/PanelEdgeTab.tsx
|
|
1474
|
-
var
|
|
1887
|
+
var import_lucide_react12 = require("lucide-react");
|
|
1475
1888
|
var import_tailwind_merge = require("tailwind-merge");
|
|
1476
|
-
var
|
|
1889
|
+
var import_jsx_runtime27 = require("react/jsx-runtime");
|
|
1477
1890
|
var buttonClassName = "flex h-12 w-full items-center justify-center p-0 text-[var(--color-text-muted)] hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-text)] focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--color-primary)]";
|
|
1478
|
-
function PanelEdgeTab({ side, edge, center, controls, className }) {
|
|
1891
|
+
function PanelEdgeTab({ side, edge, center, controls, className: className2 }) {
|
|
1479
1892
|
if (!edge && !center) return null;
|
|
1480
1893
|
const isLeft = side === "left";
|
|
1481
|
-
const EdgeIcon = isLeft ?
|
|
1482
|
-
const CenterIcon = isLeft ?
|
|
1483
|
-
const renderButton = (action, icon) => /* @__PURE__ */ (0,
|
|
1894
|
+
const EdgeIcon = isLeft ? import_lucide_react12.ChevronLeft : import_lucide_react12.ChevronRight;
|
|
1895
|
+
const CenterIcon = isLeft ? import_lucide_react12.ChevronRight : import_lucide_react12.ChevronLeft;
|
|
1896
|
+
const renderButton = (action, icon) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
1484
1897
|
"button",
|
|
1485
1898
|
{
|
|
1486
1899
|
type: "button",
|
|
@@ -1493,30 +1906,43 @@ function PanelEdgeTab({ side, edge, center, controls, className }) {
|
|
|
1493
1906
|
children: icon
|
|
1494
1907
|
}
|
|
1495
1908
|
);
|
|
1496
|
-
const edgeButton = edge && renderButton(edge, /* @__PURE__ */ (0,
|
|
1497
|
-
const centerButton = center && renderButton(center, /* @__PURE__ */ (0,
|
|
1909
|
+
const edgeButton = edge && renderButton(edge, /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(EdgeIcon, { "aria-hidden": "true", className: "h-5 w-5" }));
|
|
1910
|
+
const centerButton = center && renderButton(center, /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(CenterIcon, { "aria-hidden": "true", className: "h-5 w-5" }));
|
|
1498
1911
|
const buttons = isLeft ? [edgeButton, centerButton] : [centerButton, edgeButton];
|
|
1499
|
-
return /* @__PURE__ */ (0,
|
|
1912
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
|
|
1500
1913
|
"div",
|
|
1501
1914
|
{
|
|
1502
1915
|
className: (0, import_tailwind_merge.twMerge)(
|
|
1503
1916
|
"panel-edge-tab flex w-[var(--layout-edge-tab-w)] flex-col overflow-hidden bg-[var(--color-surface)] shadow-[0_1px_2px_rgba(60,64,67,0.3),0_2px_6px_2px_rgba(60,64,67,0.15)] [clip-path:inset(-0.5rem_-0.5rem_-0.5rem_0)]",
|
|
1504
1917
|
isLeft ? "rounded-r-lg" : "rounded-l-lg [clip-path:inset(-0.5rem_0_-0.5rem_-0.5rem)]",
|
|
1505
|
-
|
|
1918
|
+
className2
|
|
1506
1919
|
),
|
|
1507
1920
|
children: [
|
|
1508
1921
|
buttons[0],
|
|
1509
|
-
buttons[0] && buttons[1] && /* @__PURE__ */ (0,
|
|
1922
|
+
buttons[0] && buttons[1] && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { role: "presentation", className: "h-px shrink-0 bg-[var(--color-border)]" }),
|
|
1510
1923
|
buttons[1]
|
|
1511
1924
|
]
|
|
1512
1925
|
}
|
|
1513
1926
|
);
|
|
1514
1927
|
}
|
|
1515
1928
|
|
|
1929
|
+
// src/genre-tree/useLeftDetailPanelLayout.ts
|
|
1930
|
+
var import_react12 = require("react");
|
|
1931
|
+
function useLeftDetailPanelLayout(isPanelShown, enabled = true) {
|
|
1932
|
+
(0, import_react12.useEffect)(() => {
|
|
1933
|
+
if (!enabled || isPanelShown) return void 0;
|
|
1934
|
+
const root = document.documentElement;
|
|
1935
|
+
root.style.setProperty("--layout-left-w", "0px");
|
|
1936
|
+
return () => {
|
|
1937
|
+
root.style.removeProperty("--layout-left-w");
|
|
1938
|
+
};
|
|
1939
|
+
}, [enabled, isPanelShown]);
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1516
1942
|
// src/genre-tree/ChangeParentPopup.tsx
|
|
1517
|
-
var
|
|
1518
|
-
var
|
|
1519
|
-
var
|
|
1943
|
+
var import_react13 = require("react");
|
|
1944
|
+
var import_genre_tree10 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1945
|
+
var import_jsx_runtime28 = require("react/jsx-runtime");
|
|
1520
1946
|
function ChangeParentPopup({
|
|
1521
1947
|
genrePlaylist,
|
|
1522
1948
|
genrePlaylists,
|
|
@@ -1525,9 +1951,9 @@ function ChangeParentPopup({
|
|
|
1525
1951
|
onClose,
|
|
1526
1952
|
onParentChanged
|
|
1527
1953
|
}) {
|
|
1528
|
-
const { mutate, isPending, formErrors } = (0,
|
|
1529
|
-
const candidates = (0,
|
|
1530
|
-
() => (0,
|
|
1954
|
+
const { mutate, isPending, formErrors } = (0, import_genre_tree10.useUpdateGenre)(scope, getBackendBaseUrl);
|
|
1955
|
+
const candidates = (0, import_react13.useMemo)(
|
|
1956
|
+
() => (0, import_genre_tree10.getReparentCandidates)(genrePlaylists, genrePlaylist.uuid),
|
|
1531
1957
|
[genrePlaylists, genrePlaylist.uuid]
|
|
1532
1958
|
);
|
|
1533
1959
|
const handleSelect = (newParent) => {
|
|
@@ -1544,19 +1970,19 @@ function ChangeParentPopup({
|
|
|
1544
1970
|
}
|
|
1545
1971
|
);
|
|
1546
1972
|
};
|
|
1547
|
-
(0,
|
|
1973
|
+
(0, import_react13.useEffect)(() => {
|
|
1548
1974
|
const handleKeyDown = (event) => {
|
|
1549
1975
|
if (event.key === "Escape") onClose();
|
|
1550
1976
|
};
|
|
1551
1977
|
window.addEventListener("keydown", handleKeyDown);
|
|
1552
1978
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
1553
1979
|
}, [onClose]);
|
|
1554
|
-
return /* @__PURE__ */ (0,
|
|
1555
|
-
/* @__PURE__ */ (0,
|
|
1980
|
+
return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
|
|
1981
|
+
/* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("p", { className: "mb-3 text-sm text-[var(--color-text-muted)]", children: [
|
|
1556
1982
|
"Current parent: ",
|
|
1557
1983
|
genrePlaylist.parent?.name ?? "none"
|
|
1558
1984
|
] }),
|
|
1559
|
-
/* @__PURE__ */ (0,
|
|
1985
|
+
/* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
|
|
1560
1986
|
GenreSearch,
|
|
1561
1987
|
{
|
|
1562
1988
|
genrePlaylists: candidates,
|
|
@@ -1564,32 +1990,32 @@ function ChangeParentPopup({
|
|
|
1564
1990
|
placeholder: "Search a new parent\u2026"
|
|
1565
1991
|
}
|
|
1566
1992
|
) }),
|
|
1567
|
-
formErrors.length > 0 && /* @__PURE__ */ (0,
|
|
1993
|
+
formErrors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("p", { role: "alert", className: "mt-3 text-sm text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
|
|
1568
1994
|
] });
|
|
1569
1995
|
}
|
|
1570
1996
|
|
|
1571
1997
|
// src/genre-tree/artist/useArtistPanel.tsx
|
|
1572
|
-
var
|
|
1573
|
-
var
|
|
1998
|
+
var import_ui14 = require("@behindthemusictree/ui");
|
|
1999
|
+
var import_genre_tree15 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1574
2000
|
|
|
1575
2001
|
// src/genre-tree/artist/ArtistHeader.tsx
|
|
1576
2002
|
var import_ui10 = require("@behindthemusictree/ui");
|
|
1577
2003
|
|
|
1578
2004
|
// src/genre-tree/artist/ArtistLinks.tsx
|
|
1579
|
-
var
|
|
1580
|
-
var
|
|
2005
|
+
var import_lucide_react13 = require("lucide-react");
|
|
2006
|
+
var import_si3 = require("react-icons/si");
|
|
1581
2007
|
var import_ui9 = require("@behindthemusictree/ui");
|
|
1582
|
-
var
|
|
2008
|
+
var import_jsx_runtime29 = require("react/jsx-runtime");
|
|
1583
2009
|
var LINK_KINDS = {
|
|
1584
|
-
official: { label: "Official website", Icon:
|
|
1585
|
-
wikipedia: { label: "Wikipedia", Icon:
|
|
1586
|
-
wikidata: { label: "Wikidata", Icon:
|
|
1587
|
-
spotify: { label: "Spotify", Icon:
|
|
1588
|
-
youtube: { label: "YouTube", Icon:
|
|
1589
|
-
bandcamp: { label: "Bandcamp", Icon:
|
|
1590
|
-
soundcloud: { label: "SoundCloud", Icon:
|
|
1591
|
-
discogs: { label: "Discogs", Icon:
|
|
1592
|
-
social: { label: "Social", Icon:
|
|
2010
|
+
official: { label: "Official website", Icon: import_lucide_react13.Globe },
|
|
2011
|
+
wikipedia: { label: "Wikipedia", Icon: import_si3.SiWikipedia },
|
|
2012
|
+
wikidata: { label: "Wikidata", Icon: import_si3.SiWikidata },
|
|
2013
|
+
spotify: { label: "Spotify", Icon: import_si3.SiSpotify },
|
|
2014
|
+
youtube: { label: "YouTube", Icon: import_si3.SiYoutube },
|
|
2015
|
+
bandcamp: { label: "Bandcamp", Icon: import_si3.SiBandcamp },
|
|
2016
|
+
soundcloud: { label: "SoundCloud", Icon: import_si3.SiSoundcloud },
|
|
2017
|
+
discogs: { label: "Discogs", Icon: import_si3.SiDiscogs },
|
|
2018
|
+
social: { label: "Social", Icon: import_lucide_react13.Share2 }
|
|
1593
2019
|
};
|
|
1594
2020
|
function socialLabel(url) {
|
|
1595
2021
|
try {
|
|
@@ -1609,14 +2035,14 @@ function artistLinks(artist) {
|
|
|
1609
2035
|
links.push({
|
|
1610
2036
|
key: "musicbrainz",
|
|
1611
2037
|
url: `https://musicbrainz.org/artist/${artist.musicbrainzArtistId}`,
|
|
1612
|
-
Icon:
|
|
2038
|
+
Icon: import_si3.SiMusicbrainz,
|
|
1613
2039
|
label: "MusicBrainz"
|
|
1614
2040
|
});
|
|
1615
2041
|
}
|
|
1616
2042
|
return links;
|
|
1617
2043
|
}
|
|
1618
2044
|
function ArtistLinks({ links }) {
|
|
1619
|
-
return /* @__PURE__ */ (0,
|
|
2045
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui9.Chip, { href: url, icon: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
|
|
1620
2046
|
}
|
|
1621
2047
|
|
|
1622
2048
|
// src/genre-tree/artist/formatYears.ts
|
|
@@ -1627,7 +2053,7 @@ function formatYears(beginYear, endYear, ended) {
|
|
|
1627
2053
|
}
|
|
1628
2054
|
|
|
1629
2055
|
// src/genre-tree/artist/ArtistHeader.tsx
|
|
1630
|
-
var
|
|
2056
|
+
var import_jsx_runtime30 = require("react/jsx-runtime");
|
|
1631
2057
|
var countryNames = new Intl.DisplayNames(["en"], { type: "region" });
|
|
1632
2058
|
function ArtistHeader({ artist }) {
|
|
1633
2059
|
const fields = [];
|
|
@@ -1637,35 +2063,35 @@ function ArtistHeader({ artist }) {
|
|
|
1637
2063
|
const years = formatYears(artist.beginYear, artist.endYear, artist.ended);
|
|
1638
2064
|
if (years) fields.push({ label: "Active", value: years });
|
|
1639
2065
|
const links = artistLinks(artist);
|
|
1640
|
-
if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0,
|
|
2066
|
+
if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ArtistLinks, { links }) });
|
|
1641
2067
|
if (fields.length === 0) return null;
|
|
1642
|
-
return /* @__PURE__ */ (0,
|
|
2068
|
+
return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui10.FieldList, { items: fields });
|
|
1643
2069
|
}
|
|
1644
2070
|
|
|
1645
2071
|
// src/genre-tree/artist/ArtistPhoto.tsx
|
|
1646
|
-
var
|
|
2072
|
+
var import_jsx_runtime31 = require("react/jsx-runtime");
|
|
1647
2073
|
function ExternalLink({ href, children }) {
|
|
1648
|
-
return /* @__PURE__ */ (0,
|
|
2074
|
+
return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
|
|
1649
2075
|
}
|
|
1650
2076
|
function ArtistPhoto({ image, name }) {
|
|
1651
|
-
return /* @__PURE__ */ (0,
|
|
1652
|
-
/* @__PURE__ */ (0,
|
|
1653
|
-
/* @__PURE__ */ (0,
|
|
2077
|
+
return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
|
|
2078
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
|
|
2079
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("figcaption", { className: "text-xs text-[var(--color-text-muted)]", children: [
|
|
1654
2080
|
"Photo: ",
|
|
1655
|
-
/* @__PURE__ */ (0,
|
|
2081
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
|
|
1656
2082
|
",",
|
|
1657
2083
|
" ",
|
|
1658
|
-
image.licenseUrl ? /* @__PURE__ */ (0,
|
|
2084
|
+
image.licenseUrl ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
|
|
1659
2085
|
] })
|
|
1660
2086
|
] });
|
|
1661
2087
|
}
|
|
1662
2088
|
|
|
1663
2089
|
// src/genre-tree/artist/ArtistMembers.tsx
|
|
1664
2090
|
var import_ui11 = require("@behindthemusictree/ui");
|
|
1665
|
-
var
|
|
2091
|
+
var import_jsx_runtime32 = require("react/jsx-runtime");
|
|
1666
2092
|
function MembershipList({ title, memberships, renderArtistLink }) {
|
|
1667
2093
|
if (memberships.length === 0) return null;
|
|
1668
|
-
return /* @__PURE__ */ (0,
|
|
2094
|
+
return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui11.PanelGroup, { title, children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("ul", { children: memberships.map((membership, index) => {
|
|
1669
2095
|
const details = [
|
|
1670
2096
|
formatYears(membership.beginYear, membership.endYear, membership.ended),
|
|
1671
2097
|
...membership.roles
|
|
@@ -1673,9 +2099,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
|
|
|
1673
2099
|
const detailsText = details.filter(Boolean).join(" \xB7 ");
|
|
1674
2100
|
return (
|
|
1675
2101
|
// A member can have several periods in the same group.
|
|
1676
|
-
/* @__PURE__ */ (0,
|
|
1677
|
-
/* @__PURE__ */ (0,
|
|
1678
|
-
detailsText && /* @__PURE__ */ (0,
|
|
2102
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
|
|
2103
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
|
|
2104
|
+
detailsText && /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
|
|
1679
2105
|
" \u2014 ",
|
|
1680
2106
|
detailsText
|
|
1681
2107
|
] })
|
|
@@ -1686,9 +2112,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
|
|
|
1686
2112
|
function ArtistMembers({ title, memberships, renderArtistLink }) {
|
|
1687
2113
|
if (memberships.length === 0) return null;
|
|
1688
2114
|
const isCurrent = (membership) => !membership.ended && membership.endYear === null;
|
|
1689
|
-
return /* @__PURE__ */ (0,
|
|
1690
|
-
/* @__PURE__ */ (0,
|
|
1691
|
-
/* @__PURE__ */ (0,
|
|
2115
|
+
return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_ui11.PanelSection, { title, children: [
|
|
2116
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
|
|
2117
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
|
|
1692
2118
|
MembershipList,
|
|
1693
2119
|
{
|
|
1694
2120
|
title: "Past",
|
|
@@ -1701,7 +2127,7 @@ function ArtistMembers({ title, memberships, renderArtistLink }) {
|
|
|
1701
2127
|
|
|
1702
2128
|
// src/genre-tree/artist/ArtistDiscography.tsx
|
|
1703
2129
|
var import_ui12 = require("@behindthemusictree/ui");
|
|
1704
|
-
var
|
|
2130
|
+
var import_jsx_runtime33 = require("react/jsx-runtime");
|
|
1705
2131
|
var GROUPS = ["Albums", "EPs", "Singles", "Live", "Compilations", "Other"];
|
|
1706
2132
|
var PRIMARY_GROUPS = { album: "Albums", ep: "EPs", single: "Singles" };
|
|
1707
2133
|
function discographyGroup({ primaryType, secondaryTypes }) {
|
|
@@ -1721,77 +2147,85 @@ function byYear(a, b) {
|
|
|
1721
2147
|
function ArtistDiscography({ albums }) {
|
|
1722
2148
|
if (albums.length === 0) return null;
|
|
1723
2149
|
const sorted = [...albums].sort(byYear);
|
|
1724
|
-
return /* @__PURE__ */ (0,
|
|
2150
|
+
return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ui12.PanelSection, { title: "Discography", children: GROUPS.map((group) => {
|
|
1725
2151
|
const groupAlbums = sorted.filter((album) => discographyGroup(album) === group);
|
|
1726
2152
|
if (groupAlbums.length === 0) return null;
|
|
1727
|
-
return /* @__PURE__ */ (0,
|
|
1728
|
-
/* @__PURE__ */ (0,
|
|
1729
|
-
/* @__PURE__ */ (0,
|
|
2153
|
+
return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ui12.PanelGroup, { title: group, children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
|
|
2154
|
+
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
|
|
2155
|
+
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
|
|
1730
2156
|
] }, album.uuid)) }) }, group);
|
|
1731
2157
|
}) });
|
|
1732
2158
|
}
|
|
1733
2159
|
|
|
1734
2160
|
// src/genre-tree/artist/ArtistSongs.tsx
|
|
1735
|
-
var
|
|
2161
|
+
var import_react14 = require("react");
|
|
1736
2162
|
var import_react_query2 = require("@tanstack/react-query");
|
|
1737
|
-
var
|
|
1738
|
-
var
|
|
1739
|
-
var
|
|
1740
|
-
var
|
|
2163
|
+
var import_lucide_react14 = require("lucide-react");
|
|
2164
|
+
var import_ui13 = require("@behindthemusictree/ui");
|
|
2165
|
+
var import_player10 = require("@behindthemusictree/app-kit/player");
|
|
2166
|
+
var import_genre_tree14 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1741
2167
|
var import_transport8 = require("@behindthemusictree/app-kit/transport");
|
|
1742
2168
|
|
|
1743
2169
|
// src/genre-tree/track-list-sidebar/TrackItem.tsx
|
|
1744
|
-
var
|
|
1745
|
-
var
|
|
2170
|
+
var import_player9 = require("@behindthemusictree/app-kit/player");
|
|
2171
|
+
var import_genre_tree11 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1746
2172
|
|
|
1747
2173
|
// src/genre-tree/TrackPositionPlayPause.tsx
|
|
1748
|
-
var
|
|
1749
|
-
var import_player6 = require("@behindthemusictree/app-kit/player");
|
|
2174
|
+
var import_fa5 = require("react-icons/fa");
|
|
1750
2175
|
var import_player7 = require("@behindthemusictree/app-kit/player");
|
|
1751
|
-
var
|
|
1752
|
-
var
|
|
1753
|
-
function TrackPositionPlayPause({
|
|
1754
|
-
|
|
2176
|
+
var import_player8 = require("@behindthemusictree/app-kit/player");
|
|
2177
|
+
var import_jsx_runtime34 = require("react/jsx-runtime");
|
|
2178
|
+
function TrackPositionPlayPause({
|
|
2179
|
+
position,
|
|
2180
|
+
uuid,
|
|
2181
|
+
handlePlayPauseClick,
|
|
2182
|
+
isSelected = false
|
|
2183
|
+
}) {
|
|
2184
|
+
const { playerTrackObject, playbackConfirmedTrackId, playState, isLoading: isPlayerLoading } = (0, import_player7.usePlayer)();
|
|
1755
2185
|
const isThisTrack = playerTrackObject?.track.id === uuid;
|
|
1756
|
-
|
|
2186
|
+
const isConfirmedPlayable = playbackConfirmedTrackId === uuid;
|
|
2187
|
+
const isAwaitingPlayback = (isSelected || isThisTrack) && !isConfirmedPlayable && !(isThisTrack && playerTrackObject?.loadError) && (!isThisTrack || isPlayerLoading || playState === import_player8.PlayStates.LOADING || playState === import_player8.PlayStates.PLAYING);
|
|
2188
|
+
const isPlaying = isThisTrack && isConfirmedPlayable && playState === import_player8.PlayStates.PLAYING;
|
|
2189
|
+
const isPaused = isThisTrack && isConfirmedPlayable && playState === import_player8.PlayStates.PAUSED;
|
|
2190
|
+
const isCurrentTrackActive = isPlaying || isPaused;
|
|
2191
|
+
return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
|
|
1757
2192
|
"div",
|
|
1758
2193
|
{
|
|
1759
|
-
className: "track-position-play-pause flex items-center justify-center text-lg w-16 cursor-pointer",
|
|
2194
|
+
className: "track-position-play-pause relative flex items-center justify-center gap-2 text-lg w-16 cursor-pointer",
|
|
1760
2195
|
style: { minWidth: "4rem", maxWidth: "4rem" },
|
|
1761
2196
|
onClick: handlePlayPauseClick,
|
|
1762
2197
|
children: [
|
|
1763
|
-
/* @__PURE__ */ (0,
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
}
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "hidden group-hover:flex items-center justify-center", children: isThisTrack && playState === import_player7.PlayStates.LOADING ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui13.RingLoader, { size: 16 }) : isThisTrack && playState === import_player7.PlayStates.PLAYING ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_fa6.FaPause, {}) : /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_fa6.FaPlay, {}) })
|
|
2198
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
|
|
2199
|
+
"span",
|
|
2200
|
+
{
|
|
2201
|
+
className: "track-position-play-pause__bars",
|
|
2202
|
+
"data-playing": isPlaying,
|
|
2203
|
+
role: "img",
|
|
2204
|
+
"aria-label": isPlaying ? "Playing" : "Paused",
|
|
2205
|
+
children: [
|
|
2206
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "track-position-play-pause__bar" }),
|
|
2207
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "track-position-play-pause__bar" }),
|
|
2208
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "track-position-play-pause__bar" })
|
|
2209
|
+
]
|
|
2210
|
+
}
|
|
2211
|
+
) : position }),
|
|
2212
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2213
|
+
"div",
|
|
2214
|
+
{
|
|
2215
|
+
className: `hidden group-hover:flex items-center justify-center ${isCurrentTrackActive ? "" : "absolute inset-0"}`,
|
|
2216
|
+
"aria-hidden": "true",
|
|
2217
|
+
children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_fa5.FaPause, {}) : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_fa5.FaPlay, {})
|
|
2218
|
+
}
|
|
2219
|
+
)
|
|
1786
2220
|
]
|
|
1787
2221
|
}
|
|
1788
2222
|
);
|
|
1789
2223
|
}
|
|
1790
2224
|
|
|
1791
2225
|
// src/genre-tree/track-list-sidebar/TrackItem.tsx
|
|
2226
|
+
var import_genre_tree12 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1792
2227
|
var import_genre_tree13 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1793
|
-
var
|
|
1794
|
-
var import_jsx_runtime30 = require("react/jsx-runtime");
|
|
2228
|
+
var import_jsx_runtime35 = require("react/jsx-runtime");
|
|
1795
2229
|
function TrackItem({
|
|
1796
2230
|
track,
|
|
1797
2231
|
position,
|
|
@@ -1799,26 +2233,27 @@ function TrackItem({
|
|
|
1799
2233
|
renderActions,
|
|
1800
2234
|
renderArtistLink
|
|
1801
2235
|
}) {
|
|
1802
|
-
const { handlePlayPauseAction, playerTrackObject, loadTrackForPlayer } = (0,
|
|
1803
|
-
const { trackList, toTrackAtPosition } = (0,
|
|
2236
|
+
const { handlePlayPauseAction, playerTrackObject, loadTrackForPlayer } = (0, import_player9.usePlayer)();
|
|
2237
|
+
const { trackList, selectedTrack, toTrackAtPosition, runtimeUnplayableTrackIds } = (0, import_genre_tree11.useTrackList)();
|
|
1804
2238
|
const scope = trackList?.origin?.scope ?? null;
|
|
1805
|
-
const
|
|
2239
|
+
const isCurrentTrack = playerTrackObject?.track.id === track.uuid;
|
|
2240
|
+
const hasPlaybackError = isCurrentTrack && !!playerTrackObject?.loadError;
|
|
2241
|
+
const isUnplayable = hasPlaybackError || runtimeUnplayableTrackIds?.has(track.uuid) === true || (0, import_genre_tree12.isSong)(track) && (0, import_genre_tree12.songUnplayableReason)(track) !== null;
|
|
1806
2242
|
const handlePlayPauseClick = (event) => {
|
|
1807
2243
|
event.stopPropagation();
|
|
1808
|
-
if (
|
|
2244
|
+
if (isCurrentTrack) {
|
|
1809
2245
|
handlePlayPauseAction();
|
|
1810
2246
|
} else if (scope != null) {
|
|
1811
2247
|
toTrackAtPosition(position);
|
|
1812
2248
|
loadTrackForPlayer(track.uuid);
|
|
1813
2249
|
}
|
|
1814
2250
|
};
|
|
1815
|
-
return /* @__PURE__ */ (0,
|
|
2251
|
+
return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
|
|
1816
2252
|
"div",
|
|
1817
2253
|
{
|
|
1818
|
-
className: `track-item flex h-14 text-[var(--color-text)] ${
|
|
1819
|
-
title: unplayableReason ?? void 0,
|
|
2254
|
+
className: `track-item flex h-14 text-[var(--color-text)] ${isUnplayable ? "opacity-50" : "hover:bg-[var(--color-surface-muted)] group"}`,
|
|
1820
2255
|
children: [
|
|
1821
|
-
|
|
2256
|
+
isUnplayable ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
1822
2257
|
"div",
|
|
1823
2258
|
{
|
|
1824
2259
|
className: "flex items-center justify-center text-lg w-16",
|
|
@@ -1826,18 +2261,21 @@ function TrackItem({
|
|
|
1826
2261
|
"aria-disabled": "true",
|
|
1827
2262
|
children: position
|
|
1828
2263
|
}
|
|
1829
|
-
) : /* @__PURE__ */ (0,
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
2264
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
2265
|
+
TrackPositionPlayPause,
|
|
2266
|
+
{
|
|
2267
|
+
position,
|
|
2268
|
+
uuid: track.uuid,
|
|
2269
|
+
handlePlayPauseClick,
|
|
2270
|
+
isSelected: selectedTrack?.uuid === track.uuid
|
|
2271
|
+
}
|
|
2272
|
+
),
|
|
2273
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
|
|
2274
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: (0, import_genre_tree12.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
|
|
2275
|
+
track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_genre_tree13.ArtistNames, { artists: track.artists, renderArtistLink }) }) : ""
|
|
1837
2276
|
] }),
|
|
1838
|
-
/* @__PURE__ */ (0,
|
|
1839
|
-
|
|
1840
|
-
renderDuration && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
|
|
2277
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.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_runtime35.jsx)("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
|
|
2278
|
+
renderDuration && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
1841
2279
|
"div",
|
|
1842
2280
|
{
|
|
1843
2281
|
className: "duration flex text-base w-16 items-center justify-center",
|
|
@@ -1845,28 +2283,28 @@ function TrackItem({
|
|
|
1845
2283
|
children: renderDuration(track)
|
|
1846
2284
|
}
|
|
1847
2285
|
),
|
|
1848
|
-
renderActions && /* @__PURE__ */ (0,
|
|
2286
|
+
renderActions && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
|
|
1849
2287
|
]
|
|
1850
2288
|
}
|
|
1851
2289
|
);
|
|
1852
2290
|
}
|
|
1853
2291
|
|
|
1854
2292
|
// src/genre-tree/artist/ArtistSongs.tsx
|
|
1855
|
-
var
|
|
1856
|
-
var SongPageSchema2 = (0, import_transport8.PaginatedResponseSchema)(
|
|
2293
|
+
var import_jsx_runtime36 = require("react/jsx-runtime");
|
|
2294
|
+
var SongPageSchema2 = (0, import_transport8.PaginatedResponseSchema)(import_genre_tree14.SongDetailedSchema);
|
|
1857
2295
|
var SORTS = [
|
|
1858
2296
|
{ ordering: "title", label: "Title" },
|
|
1859
2297
|
{ ordering: "-albumYear", label: "Year" }
|
|
1860
2298
|
];
|
|
1861
2299
|
function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
1862
|
-
const [page, setPage] = (0,
|
|
1863
|
-
const [ordering, setOrdering] = (0,
|
|
2300
|
+
const [page, setPage] = (0, import_react14.useState)(1);
|
|
2301
|
+
const [ordering, setOrdering] = (0, import_react14.useState)("title");
|
|
1864
2302
|
const { fetch } = (0, import_transport8.useFetchWrapper)(getBackendBaseUrl);
|
|
1865
|
-
const { playNewTrackListFromArtist } = (0,
|
|
1866
|
-
const { playerTrackObject } = (0,
|
|
2303
|
+
const { playNewTrackListFromArtist } = (0, import_genre_tree14.useTrackList)();
|
|
2304
|
+
const { playerTrackObject } = (0, import_player10.usePlayer)();
|
|
1867
2305
|
const { data, isPending, isError } = (0, import_transport8.useQueryWithParse)({
|
|
1868
2306
|
queryKey: ["referenceSongs", "artist", artist.uuid, ordering, page],
|
|
1869
|
-
queryFn: () => fetch(
|
|
2307
|
+
queryFn: () => fetch(import_genre_tree14.libraryEndpoints.reference.songs.list(), true, false, void 0, {
|
|
1870
2308
|
artist: artist.uuid,
|
|
1871
2309
|
ordering,
|
|
1872
2310
|
page
|
|
@@ -1881,25 +2319,25 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
|
1881
2319
|
});
|
|
1882
2320
|
const handleRowClickCapture = (event, song) => {
|
|
1883
2321
|
if (!(event.target instanceof Element) || !event.target.closest(".track-position-play-pause")) return;
|
|
1884
|
-
if (playerTrackObject?.track.id === song.uuid || (0,
|
|
2322
|
+
if (playerTrackObject?.track.id === song.uuid || (0, import_genre_tree14.songUnplayableReason)(song) !== null) return;
|
|
1885
2323
|
event.stopPropagation();
|
|
1886
2324
|
void play(song.uuid);
|
|
1887
2325
|
};
|
|
1888
|
-
return /* @__PURE__ */ (0,
|
|
1889
|
-
/* @__PURE__ */ (0,
|
|
1890
|
-
/* @__PURE__ */ (0,
|
|
2326
|
+
return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.PanelSection, { title: "Songs", children: isPending ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.Skeleton, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_jsx_runtime36.Fragment, { children: [
|
|
2327
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
|
|
2328
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
|
|
1891
2329
|
"button",
|
|
1892
2330
|
{
|
|
1893
2331
|
type: "button",
|
|
1894
2332
|
onClick: () => void play(),
|
|
1895
2333
|
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",
|
|
1896
2334
|
children: [
|
|
1897
|
-
/* @__PURE__ */ (0,
|
|
2335
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_lucide_react14.Play, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
|
|
1898
2336
|
"Play all"
|
|
1899
2337
|
]
|
|
1900
2338
|
}
|
|
1901
2339
|
),
|
|
1902
|
-
/* @__PURE__ */ (0,
|
|
2340
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
|
|
1903
2341
|
"button",
|
|
1904
2342
|
{
|
|
1905
2343
|
type: "button",
|
|
@@ -1914,7 +2352,7 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
|
1914
2352
|
sort.ordering
|
|
1915
2353
|
)) })
|
|
1916
2354
|
] }),
|
|
1917
|
-
/* @__PURE__ */ (0,
|
|
2355
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
|
|
1918
2356
|
TrackItem,
|
|
1919
2357
|
{
|
|
1920
2358
|
track: song,
|
|
@@ -1922,21 +2360,23 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
|
1922
2360
|
renderArtistLink
|
|
1923
2361
|
}
|
|
1924
2362
|
) }, song.uuid)) }),
|
|
1925
|
-
/* @__PURE__ */ (0,
|
|
2363
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
|
|
1926
2364
|
] }) });
|
|
1927
2365
|
}
|
|
1928
2366
|
|
|
1929
2367
|
// src/genre-tree/artist/useArtistPanel.tsx
|
|
1930
|
-
var
|
|
2368
|
+
var import_jsx_runtime37 = require("react/jsx-runtime");
|
|
1931
2369
|
function useArtistPanel({
|
|
1932
2370
|
uuid,
|
|
2371
|
+
scope,
|
|
1933
2372
|
getBackendBaseUrl,
|
|
2373
|
+
getShareUrl,
|
|
1934
2374
|
renderArtistLink,
|
|
1935
2375
|
expanded,
|
|
1936
2376
|
onExpandedChange,
|
|
1937
2377
|
onClose
|
|
1938
2378
|
}) {
|
|
1939
|
-
const result = (0,
|
|
2379
|
+
const result = (0, import_genre_tree15.useArtist)(uuid, getBackendBaseUrl);
|
|
1940
2380
|
const panel = { expanded, onExpandedChange, onClose };
|
|
1941
2381
|
switch (result.status) {
|
|
1942
2382
|
case "idle":
|
|
@@ -1945,38 +2385,41 @@ function useArtistPanel({
|
|
|
1945
2385
|
return {
|
|
1946
2386
|
...panel,
|
|
1947
2387
|
title: "Artist",
|
|
1948
|
-
content: /* @__PURE__ */ (0,
|
|
1949
|
-
/* @__PURE__ */ (0,
|
|
1950
|
-
/* @__PURE__ */ (0,
|
|
1951
|
-
/* @__PURE__ */ (0,
|
|
1952
|
-
/* @__PURE__ */ (0,
|
|
1953
|
-
/* @__PURE__ */ (0,
|
|
1954
|
-
/* @__PURE__ */ (0,
|
|
2388
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
|
|
2389
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui14.Skeleton, { className: "h-3 w-16" }),
|
|
2390
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui14.Skeleton, { className: "h-4 w-32" }),
|
|
2391
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui14.Skeleton, { className: "h-3 w-16" }),
|
|
2392
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui14.Skeleton, { className: "h-4 w-24" }),
|
|
2393
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui14.Skeleton, { className: "h-16 mt-2.5" }),
|
|
2394
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui14.Skeleton, { className: "h-16" })
|
|
1955
2395
|
] })
|
|
1956
2396
|
};
|
|
1957
2397
|
case "not-found":
|
|
1958
2398
|
return {
|
|
1959
2399
|
...panel,
|
|
1960
2400
|
title: "Artist not found",
|
|
1961
|
-
content: /* @__PURE__ */ (0,
|
|
2401
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This artist does not exist." })
|
|
1962
2402
|
};
|
|
1963
2403
|
case "error":
|
|
1964
2404
|
return {
|
|
1965
2405
|
...panel,
|
|
1966
2406
|
title: "Artist",
|
|
1967
|
-
content: /* @__PURE__ */ (0,
|
|
2407
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
|
|
1968
2408
|
};
|
|
1969
2409
|
case "success": {
|
|
1970
2410
|
const { artist } = result;
|
|
2411
|
+
const shareTarget = scope ? { kind: "artist", uuid: artist.uuid, name: artist.name, scope } : void 0;
|
|
2412
|
+
const shareUrl = shareTarget ? getShareUrl?.(shareTarget) : void 0;
|
|
1971
2413
|
return {
|
|
1972
2414
|
...panel,
|
|
1973
2415
|
title: artist.name,
|
|
1974
|
-
content: /* @__PURE__ */ (0,
|
|
1975
|
-
|
|
1976
|
-
/* @__PURE__ */ (0,
|
|
1977
|
-
/* @__PURE__ */ (0,
|
|
1978
|
-
/* @__PURE__ */ (0,
|
|
1979
|
-
|
|
2416
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(import_jsx_runtime37.Fragment, { children: [
|
|
2417
|
+
shareUrl && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: shareTarget && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl }) }) }),
|
|
2418
|
+
artist.image && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ArtistPhoto, { image: artist.image, name: artist.name }),
|
|
2419
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ArtistHeader, { artist }),
|
|
2420
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
|
|
2421
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
|
|
2422
|
+
artist.tracksCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
|
|
1980
2423
|
ArtistSongs,
|
|
1981
2424
|
{
|
|
1982
2425
|
artist: { uuid: artist.uuid, name: artist.name },
|
|
@@ -1985,7 +2428,7 @@ function useArtistPanel({
|
|
|
1985
2428
|
}
|
|
1986
2429
|
)
|
|
1987
2430
|
] }),
|
|
1988
|
-
extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0,
|
|
2431
|
+
extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(ArtistDiscography, { albums: artist.albums }) : void 0
|
|
1989
2432
|
};
|
|
1990
2433
|
}
|
|
1991
2434
|
}
|
|
@@ -1993,28 +2436,28 @@ function useArtistPanel({
|
|
|
1993
2436
|
|
|
1994
2437
|
// src/genre-tree/SizeMetricToggle.tsx
|
|
1995
2438
|
var import_md2 = require("react-icons/md");
|
|
1996
|
-
var
|
|
1997
|
-
var
|
|
2439
|
+
var import_ui15 = require("@behindthemusictree/ui");
|
|
2440
|
+
var import_jsx_runtime38 = require("react/jsx-runtime");
|
|
1998
2441
|
function SizeMetricToggle({
|
|
1999
2442
|
value,
|
|
2000
2443
|
onChange
|
|
2001
2444
|
}) {
|
|
2002
|
-
return /* @__PURE__ */ (0,
|
|
2003
|
-
|
|
2445
|
+
return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
|
|
2446
|
+
import_ui15.SegmentedControl,
|
|
2004
2447
|
{
|
|
2005
2448
|
"aria-label": "Size nodes by",
|
|
2006
2449
|
value,
|
|
2007
2450
|
onChange,
|
|
2008
2451
|
options: [
|
|
2009
|
-
{ value: "songs", label: /* @__PURE__ */ (0,
|
|
2010
|
-
{ value: "views", label: /* @__PURE__ */ (0,
|
|
2452
|
+
{ value: "songs", label: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_md2.MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
|
|
2453
|
+
{ value: "views", label: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_md2.MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
|
|
2011
2454
|
]
|
|
2012
2455
|
}
|
|
2013
2456
|
);
|
|
2014
2457
|
}
|
|
2015
2458
|
|
|
2016
2459
|
// src/genre-tree/GenreTreeView.tsx
|
|
2017
|
-
var
|
|
2460
|
+
var import_jsx_runtime39 = require("react/jsx-runtime");
|
|
2018
2461
|
var PREVIOUS_BUTTON_CSS = `
|
|
2019
2462
|
.genre-tree-view .search-overlay .gtv-info-panel-previous { display: none; }
|
|
2020
2463
|
.genre-tree-view:has(.search-overlay .gtv-info-panel-previous) .genre-search input { padding-left: 2.75rem; }
|
|
@@ -2024,27 +2467,6 @@ var PREVIOUS_BUTTON_CSS = `
|
|
|
2024
2467
|
.genre-search-previous { animation: genre-tree-view-previous-in 200ms ease-out; }
|
|
2025
2468
|
}
|
|
2026
2469
|
`;
|
|
2027
|
-
function GenreNodeActionBand({ genrePlaylist, scope, playback, ...bandProps }) {
|
|
2028
|
-
const { isPlaying, setIsPlaying } = (0, import_player10.usePlayer)();
|
|
2029
|
-
const isCurrentTrackList = playback.playingNodeId === genrePlaylist.uuid;
|
|
2030
|
-
const handlePlayPause = () => {
|
|
2031
|
-
if (isCurrentTrackList) {
|
|
2032
|
-
setIsPlaying(!isPlaying);
|
|
2033
|
-
return;
|
|
2034
|
-
}
|
|
2035
|
-
playback.play(genrePlaylist).catch((error) => {
|
|
2036
|
-
console.error("Failed to load genre playlist tracks:", error);
|
|
2037
|
-
});
|
|
2038
|
-
};
|
|
2039
|
-
return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2040
|
-
GenreActionBand,
|
|
2041
|
-
{
|
|
2042
|
-
...bandProps,
|
|
2043
|
-
onPlayPause: genrePlaylist.tracksCount > 0 ? handlePlayPause : void 0,
|
|
2044
|
-
isPlaying: isCurrentTrackList && isPlaying
|
|
2045
|
-
}
|
|
2046
|
-
);
|
|
2047
|
-
}
|
|
2048
2470
|
var NO_POP_ROOT_TITLE = "This genre tree has no 'Mainstream Pop' root yet";
|
|
2049
2471
|
var REGIONS_CLASS = "[--gtv-center-left:var(--layout-center-left)] [--gtv-center-right:var(--layout-center-right)] [--gtv-center-top:var(--layout-band-top)] [&_.gtv-info-panel--overlay]:[--gtv-info-panel-header-inset:0px]";
|
|
2050
2472
|
function GenreTreeView({
|
|
@@ -2058,15 +2480,16 @@ function GenreTreeView({
|
|
|
2058
2480
|
selectedNodeId: controlledSelectedNodeId,
|
|
2059
2481
|
onSelectedNodeIdChange,
|
|
2060
2482
|
defaultViewport,
|
|
2483
|
+
viewportRestore,
|
|
2061
2484
|
onViewportChange,
|
|
2062
2485
|
readOnly = false,
|
|
2063
|
-
className,
|
|
2486
|
+
className: className2,
|
|
2064
2487
|
showDisplayOptions = true,
|
|
2065
2488
|
showAddRoot = true,
|
|
2066
2489
|
allowWheelRotation: controlledAllowWheelRotation,
|
|
2067
2490
|
criteriaOverviewSchema,
|
|
2068
2491
|
renderGenreDetailExtras,
|
|
2069
|
-
|
|
2492
|
+
floatingControlsLeading,
|
|
2070
2493
|
sizeMetric: controlledSizeMetric,
|
|
2071
2494
|
onSizeMetricChange,
|
|
2072
2495
|
renderArtistLink,
|
|
@@ -2074,6 +2497,7 @@ function GenreTreeView({
|
|
|
2074
2497
|
onArtistUuidChange,
|
|
2075
2498
|
artistExpanded: controlledArtistExpanded,
|
|
2076
2499
|
onArtistExpandedChange,
|
|
2500
|
+
getShareUrl,
|
|
2077
2501
|
artistSearch = true,
|
|
2078
2502
|
onArtistSelect,
|
|
2079
2503
|
artistSearchBaseUrl,
|
|
@@ -2091,12 +2515,14 @@ function GenreTreeView({
|
|
|
2091
2515
|
placement = "default",
|
|
2092
2516
|
...focus
|
|
2093
2517
|
}) {
|
|
2094
|
-
const
|
|
2518
|
+
const getShareUrlRef = (0, import_react15.useRef)(getShareUrl);
|
|
2519
|
+
getShareUrlRef.current = getShareUrl;
|
|
2520
|
+
const [internalArtistUuid, setInternalArtistUuid] = (0, import_react15.useState)(null);
|
|
2095
2521
|
const isArtistControlled = controlledArtistUuid !== void 0;
|
|
2096
2522
|
const artistUuid = isArtistControlled ? controlledArtistUuid : internalArtistUuid;
|
|
2097
|
-
const [internalSizeMetric, setInternalSizeMetric] = (0,
|
|
2523
|
+
const [internalSizeMetric, setInternalSizeMetric] = (0, import_react15.useState)("songs");
|
|
2098
2524
|
const sizeMetric = controlledSizeMetric ?? internalSizeMetric;
|
|
2099
|
-
const deferredSizeMetric = (0,
|
|
2525
|
+
const deferredSizeMetric = (0, import_react15.useDeferredValue)(sizeMetric);
|
|
2100
2526
|
const changeSizeMetric = (metric) => {
|
|
2101
2527
|
if (controlledSizeMetric === void 0) setInternalSizeMetric(metric);
|
|
2102
2528
|
onSizeMetricChange?.(metric);
|
|
@@ -2123,7 +2549,7 @@ function GenreTreeView({
|
|
|
2123
2549
|
allowWheelRotation: internalAllowWheelRotation,
|
|
2124
2550
|
toggleWheelRotation,
|
|
2125
2551
|
onNodeHover: handleNodeHover
|
|
2126
|
-
} = (0,
|
|
2552
|
+
} = (0, import_genre_tree17.useGenreTreeViewState)({
|
|
2127
2553
|
scope,
|
|
2128
2554
|
getBackendBaseUrl,
|
|
2129
2555
|
viewMode: controlledViewMode,
|
|
@@ -2132,25 +2558,41 @@ function GenreTreeView({
|
|
|
2132
2558
|
criteriaOverviewSchema,
|
|
2133
2559
|
artistUuid
|
|
2134
2560
|
});
|
|
2135
|
-
const
|
|
2561
|
+
const restoreRevision = viewportRestore?.revision;
|
|
2562
|
+
const [stackedRestoreOwner, setStackedRestoreOwner] = (0, import_react15.useState)({ revision: restoreRevision, rootUuid: selectedGenrePlaylist?.root.uuid });
|
|
2563
|
+
let restoreRootUuid = stackedRestoreOwner.rootUuid;
|
|
2564
|
+
if (stackedRestoreOwner.revision !== restoreRevision) {
|
|
2565
|
+
restoreRootUuid = selectedGenrePlaylist?.root.uuid;
|
|
2566
|
+
setStackedRestoreOwner({ revision: restoreRevision, rootUuid: restoreRootUuid });
|
|
2567
|
+
} else if (restoreRootUuid === void 0 && selectedGenrePlaylist) {
|
|
2568
|
+
restoreRootUuid = selectedGenrePlaylist.root.uuid;
|
|
2569
|
+
setStackedRestoreOwner({ revision: restoreRevision, rootUuid: restoreRootUuid });
|
|
2570
|
+
}
|
|
2571
|
+
const isGenreUpdating = (0, import_react_query3.useIsMutating)({ mutationKey: import_genre_tree17.genreUpdateMutationKey }) > 0;
|
|
2572
|
+
const { mutate: updateGenre } = (0, import_genre_tree17.useUpdateGenre)(scope, getBackendBaseUrl);
|
|
2136
2573
|
const busy = deferredSizeMetric !== sizeMetric || isTreeFetching || isGenreUpdating;
|
|
2137
|
-
const
|
|
2574
|
+
const isArtistFiltered = artistUuid !== null;
|
|
2575
|
+
const canSizeByViews = !isArtistFiltered && hasViewsCount(visibleGenrePlaylists);
|
|
2576
|
+
const artistResult = (0, import_genre_tree17.useArtist)(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
|
|
2138
2577
|
const filterArtistName = artistResult.status === "success" ? artistResult.artist.name : null;
|
|
2139
|
-
const filterArtist = (0,
|
|
2578
|
+
const filterArtist = (0, import_react15.useMemo)(
|
|
2140
2579
|
() => artistUuid !== null && filterArtistName !== null ? { uuid: artistUuid, name: filterArtistName } : null,
|
|
2141
2580
|
[artistUuid, filterArtistName]
|
|
2142
2581
|
);
|
|
2582
|
+
const hasSelectedGenreNode = selectedGenrePlaylist !== void 0 && visibleGenrePlaylists.some(({ uuid }) => uuid === selectedGenrePlaylist.uuid);
|
|
2143
2583
|
const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: visibleGenrePlaylists, artist: filterArtist });
|
|
2144
2584
|
const openArtist = (uuid) => {
|
|
2145
2585
|
if (selectedNodeId !== null) clearSelection();
|
|
2146
2586
|
changeArtist(uuid);
|
|
2147
2587
|
};
|
|
2148
|
-
const [expandedArtistUuid, setExpandedArtistUuid] = (0,
|
|
2588
|
+
const [expandedArtistUuid, setExpandedArtistUuid] = (0, import_react15.useState)(null);
|
|
2149
2589
|
const artistBaseUrl = artistSearchBaseUrl ?? getBackendBaseUrl;
|
|
2150
2590
|
const artistPanel = useArtistPanel({
|
|
2151
2591
|
uuid: artistUuid,
|
|
2592
|
+
scope,
|
|
2152
2593
|
getBackendBaseUrl: artistBaseUrl,
|
|
2153
|
-
|
|
2594
|
+
getShareUrl: getShareUrlRef.current,
|
|
2595
|
+
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 })),
|
|
2154
2596
|
expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
|
|
2155
2597
|
onExpandedChange: (on) => {
|
|
2156
2598
|
if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
|
|
@@ -2158,10 +2600,10 @@ function GenreTreeView({
|
|
|
2158
2600
|
},
|
|
2159
2601
|
onClose: () => changeArtist(null)
|
|
2160
2602
|
});
|
|
2161
|
-
const builtInArtistPanel = overlayPanel ||
|
|
2603
|
+
const builtInArtistPanel = overlayPanel || hasSelectedGenreNode ? void 0 : artistPanel;
|
|
2162
2604
|
const activeOverlayPanel = overlayPanel ?? (builtInArtistPanel && { ...builtInArtistPanel, hideTitle: true, hideClose: true });
|
|
2163
|
-
const [uncontrolledCollapsed, setUncontrolledCollapsed] = (0,
|
|
2164
|
-
const [uncontrolledExpanded, setUncontrolledExpanded] = (0,
|
|
2605
|
+
const [uncontrolledCollapsed, setUncontrolledCollapsed] = (0, import_react15.useState)(false);
|
|
2606
|
+
const [uncontrolledExpanded, setUncontrolledExpanded] = (0, import_react15.useState)(false);
|
|
2165
2607
|
const isCollapsed = collapsed ?? uncontrolledCollapsed;
|
|
2166
2608
|
const isExpanded = expanded ?? uncontrolledExpanded;
|
|
2167
2609
|
const changeCollapsed = (next) => {
|
|
@@ -2172,18 +2614,11 @@ function GenreTreeView({
|
|
|
2172
2614
|
setUncontrolledExpanded(next);
|
|
2173
2615
|
onExpandedChange?.(next);
|
|
2174
2616
|
};
|
|
2175
|
-
(0,
|
|
2617
|
+
(0, import_react15.useEffect)(() => {
|
|
2176
2618
|
setUncontrolledCollapsed(false);
|
|
2177
2619
|
}, [selectedNodeId, artistUuid]);
|
|
2178
|
-
const isDetailHidden = isCollapsed || !
|
|
2179
|
-
(
|
|
2180
|
-
if (placement !== "regions" || !isDetailHidden) return void 0;
|
|
2181
|
-
const root = document.documentElement;
|
|
2182
|
-
root.style.setProperty("--layout-left-w", "0px");
|
|
2183
|
-
return () => {
|
|
2184
|
-
root.style.removeProperty("--layout-left-w");
|
|
2185
|
-
};
|
|
2186
|
-
}, [placement, isDetailHidden]);
|
|
2620
|
+
const isDetailHidden = isCollapsed || !hasSelectedGenreNode && !activeOverlayPanel;
|
|
2621
|
+
useLeftDetailPanelLayout(!isDetailHidden, placement === "regions");
|
|
2187
2622
|
const detailOverlay = placement === "regions" && activeOverlayPanel?.onExpandedChange ? activeOverlayPanel : void 0;
|
|
2188
2623
|
const detailExpanded = detailOverlay ? !!detailOverlay.expanded : isExpanded;
|
|
2189
2624
|
const onDetailExpandedChange = detailOverlay ? detailOverlay.onExpandedChange : changeExpanded;
|
|
@@ -2195,29 +2630,38 @@ function GenreTreeView({
|
|
|
2195
2630
|
hideExpandButton: placement === "regions" ? true : void 0,
|
|
2196
2631
|
overlayPanel: detailOverlay ? { ...detailOverlay, collapsed: isCollapsed, hideExpandButton: true } : activeOverlayPanel
|
|
2197
2632
|
};
|
|
2198
|
-
const [isWheelRevealed, setIsWheelRevealed] = (0,
|
|
2633
|
+
const [isWheelRevealed, setIsWheelRevealed] = (0, import_react15.useState)(false);
|
|
2199
2634
|
const usesHandoff = viewMode === "wheel" || viewMode === "pop-core";
|
|
2200
2635
|
const isTreeReady = !isLoading && (!usesHandoff || isWheelRevealed);
|
|
2201
|
-
const rootRef = (0,
|
|
2636
|
+
const rootRef = (0, import_react15.useRef)(null);
|
|
2202
2637
|
const handlePrevious = () => {
|
|
2203
2638
|
rootRef.current?.querySelector(".search-overlay .gtv-info-panel-previous")?.click();
|
|
2204
2639
|
};
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2640
|
+
(0, import_react15.useEffect)(() => {
|
|
2641
|
+
if (!isTreeReady || viewMode !== "pop-core" || !hasSelectedGenreNode || !selectedGenrePlaylist) return;
|
|
2642
|
+
const root = rootRef.current;
|
|
2643
|
+
const centerChip = root?.querySelector(".gtv-wheel-center-node .gtv-wheel-chip");
|
|
2644
|
+
if (!centerChip || centerChip.getAttribute("aria-label") !== selectedGenrePlaylist.name) return;
|
|
2645
|
+
const panelTitle = root?.querySelector(".gtv-info-panel-title");
|
|
2646
|
+
if (panelTitle?.textContent?.trim().startsWith(selectedGenrePlaylist.name)) return;
|
|
2647
|
+
centerChip.click();
|
|
2648
|
+
}, [hasSelectedGenreNode, isTreeReady, selectedGenrePlaylist, viewMode]);
|
|
2649
|
+
const { showPopup, hidePopup } = (0, import_popup6.usePopup)();
|
|
2650
|
+
const [searchText, setSearchText] = (0, import_react15.useState)("");
|
|
2651
|
+
const artistSearchResult = (0, import_genre_tree16.useArtistSearch)(artistBaseUrl, artistSearch ? searchText : "");
|
|
2208
2652
|
const artistExtraResults = artistSearch ? {
|
|
2209
2653
|
label: "Artists",
|
|
2210
2654
|
items: artistSearchResult.artists.map((artist) => ({
|
|
2211
2655
|
key: artist.uuid,
|
|
2212
|
-
content: /* @__PURE__ */ (0,
|
|
2213
|
-
/* @__PURE__ */ (0,
|
|
2656
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_jsx_runtime39.Fragment, { children: [
|
|
2657
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("span", { className: "block truncate font-medium", children: [
|
|
2214
2658
|
artist.name,
|
|
2215
|
-
artist.disambiguation && /* @__PURE__ */ (0,
|
|
2659
|
+
artist.disambiguation && /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("span", { className: "font-normal text-[var(--color-neutral-500)]", children: [
|
|
2216
2660
|
" \xB7 ",
|
|
2217
2661
|
artist.disambiguation
|
|
2218
2662
|
] })
|
|
2219
2663
|
] }),
|
|
2220
|
-
artist.matchedAlias && /* @__PURE__ */ (0,
|
|
2664
|
+
artist.matchedAlias && /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [
|
|
2221
2665
|
"also known as ",
|
|
2222
2666
|
artist.matchedAlias
|
|
2223
2667
|
] })
|
|
@@ -2243,31 +2687,52 @@ function GenreTreeView({
|
|
|
2243
2687
|
const handleGenreSearchZoomIn = () => {
|
|
2244
2688
|
if (selectedUuid) focus.onFocusGenreChange?.(selectedUuid, "zoom-in");
|
|
2245
2689
|
};
|
|
2246
|
-
const zoomInRef = (0,
|
|
2690
|
+
const zoomInRef = (0, import_react15.useRef)(void 0);
|
|
2247
2691
|
zoomInRef.current = canZoomIn ? handleGenreSearchZoomIn : void 0;
|
|
2248
|
-
const renderInfoPanelBody = (0,
|
|
2692
|
+
const renderInfoPanelBody = (0, import_react15.useCallback)(
|
|
2249
2693
|
(node, { toolbar, ancestors, family }) => {
|
|
2250
2694
|
const genrePlaylist = genrePlaylistByNodeId.get(node.id);
|
|
2251
2695
|
const criteria = genrePlaylist?.criteria;
|
|
2252
|
-
if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ (0,
|
|
2696
|
+
if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_jsx_runtime39.Fragment, { children: [
|
|
2253
2697
|
toolbar,
|
|
2254
2698
|
family
|
|
2255
2699
|
] }), aside: ancestors };
|
|
2256
2700
|
const genreUuid = criteria.uuid;
|
|
2701
|
+
const parentPlaylist = visibleGenrePlaylists.find(({ uuid }) => uuid === genrePlaylist.parent?.uuid);
|
|
2702
|
+
const canToggleSide = !readOnly && parentPlaylist !== void 0 && parentPlaylist.parent === null;
|
|
2703
|
+
const side = criteria.side ?? "core";
|
|
2704
|
+
const shareTarget = {
|
|
2705
|
+
kind: "genre",
|
|
2706
|
+
genrePlaylistUuid: genrePlaylist.uuid,
|
|
2707
|
+
name: criteria.name,
|
|
2708
|
+
scope,
|
|
2709
|
+
artistUuid
|
|
2710
|
+
};
|
|
2711
|
+
const shareUrl = getShareUrlRef.current?.(shareTarget) ?? void 0;
|
|
2712
|
+
const popSibling = canToggleSide && side === "core" ? visibleGenrePlaylists.find(
|
|
2713
|
+
(sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist.uuid && sibling.criteria?.side === "pop"
|
|
2714
|
+
) : void 0;
|
|
2257
2715
|
return {
|
|
2258
|
-
primary: /* @__PURE__ */ (0,
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2716
|
+
primary: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2717
|
+
GenreInfoPanelBody,
|
|
2718
|
+
{
|
|
2719
|
+
toolbar,
|
|
2720
|
+
family,
|
|
2721
|
+
genreUuid,
|
|
2722
|
+
genrePlaylist,
|
|
2723
|
+
wikidataId: criteria.wikidataId,
|
|
2724
|
+
scope,
|
|
2725
|
+
getBackendBaseUrl,
|
|
2726
|
+
playback,
|
|
2727
|
+
band: {
|
|
2263
2728
|
node,
|
|
2264
|
-
|
|
2265
|
-
|
|
2729
|
+
shareTarget: shareUrl ? shareTarget : void 0,
|
|
2730
|
+
shareUrl,
|
|
2266
2731
|
onZoomIn: zoomInRef.current,
|
|
2267
2732
|
onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
|
|
2268
2733
|
onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
|
|
2269
2734
|
onChangeParent: readOnly ? void 0 : () => showPopup(
|
|
2270
|
-
/* @__PURE__ */ (0,
|
|
2735
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2271
2736
|
ChangeParentPopup,
|
|
2272
2737
|
{
|
|
2273
2738
|
genrePlaylist: { ...genrePlaylist, criteria },
|
|
@@ -2279,18 +2744,12 @@ function GenreTreeView({
|
|
|
2279
2744
|
}
|
|
2280
2745
|
)
|
|
2281
2746
|
),
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
{
|
|
2289
|
-
family,
|
|
2290
|
-
genreUuid,
|
|
2291
|
-
genrePlaylist,
|
|
2292
|
-
scope,
|
|
2293
|
-
getBackendBaseUrl,
|
|
2747
|
+
side,
|
|
2748
|
+
onToggleSide: canToggleSide ? () => updateGenre({ uuid: genreUuid, data: { side: side === "pop" ? "core" : "pop" } }) : void 0,
|
|
2749
|
+
toggleSideDisabledReason: popSibling ? `${popSibling.name} is already the pop side of ${parentPlaylist?.name}` : void 0,
|
|
2750
|
+
additionalActions
|
|
2751
|
+
},
|
|
2752
|
+
extras: {
|
|
2294
2753
|
criteriaOverviewSchema,
|
|
2295
2754
|
renderGenreDetailExtras,
|
|
2296
2755
|
renderArtistLink,
|
|
@@ -2300,10 +2759,9 @@ function GenreTreeView({
|
|
|
2300
2759
|
renderSongLeading,
|
|
2301
2760
|
renderSongActions,
|
|
2302
2761
|
renderTracksToolbar
|
|
2303
|
-
}
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
] }),
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
),
|
|
2307
2765
|
aside: ancestors
|
|
2308
2766
|
};
|
|
2309
2767
|
},
|
|
@@ -2317,6 +2775,7 @@ function GenreTreeView({
|
|
|
2317
2775
|
hidePopup,
|
|
2318
2776
|
onParentChanged,
|
|
2319
2777
|
visibleGenrePlaylists,
|
|
2778
|
+
updateGenre,
|
|
2320
2779
|
playback,
|
|
2321
2780
|
filterArtist,
|
|
2322
2781
|
scope,
|
|
@@ -2333,7 +2792,7 @@ function GenreTreeView({
|
|
|
2333
2792
|
);
|
|
2334
2793
|
const isRotationControlled = controlledAllowWheelRotation !== void 0;
|
|
2335
2794
|
const allowWheelRotation = controlledAllowWheelRotation ?? internalAllowWheelRotation;
|
|
2336
|
-
const detailTab = placement === "regions" && (
|
|
2795
|
+
const detailTab = placement === "regions" && (hasSelectedGenreNode || detailOverlay) ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2337
2796
|
PanelEdgeTab,
|
|
2338
2797
|
{
|
|
2339
2798
|
side: "left",
|
|
@@ -2341,25 +2800,14 @@ function GenreTreeView({
|
|
|
2341
2800
|
center: detailExpanded ? null : isCollapsed ? { label: "Show panel", onClick: () => changeCollapsed(false) } : onDetailExpandedChange ? { label: "Extend panel", onClick: () => onDetailExpandedChange(true) } : null
|
|
2342
2801
|
}
|
|
2343
2802
|
) : null;
|
|
2344
|
-
const
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
genrePlaylists: visibleGenrePlaylists,
|
|
2349
|
-
onSelect: handleGenreSearchSelect,
|
|
2350
|
-
selectedName: selectedGenrePlaylist?.name ?? builtInArtistPanel?.title ?? null,
|
|
2351
|
-
onClear: builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
|
|
2352
|
-
onPrevious: handlePrevious,
|
|
2353
|
-
onQueryChange: artistSearch ? setSearchText : void 0,
|
|
2354
|
-
extraResults: artistExtraResults
|
|
2355
|
-
}
|
|
2356
|
-
),
|
|
2357
|
-
!isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
|
|
2358
|
-
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2803
|
+
const renderArtistFilter = () => {
|
|
2804
|
+
if (isLoading || !filterArtist) return null;
|
|
2805
|
+
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: [
|
|
2806
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2359
2807
|
"button",
|
|
2360
2808
|
{
|
|
2361
2809
|
type: "button",
|
|
2362
|
-
className: "py-1 pl-3 pr-2 hover:underline",
|
|
2810
|
+
className: "py-1 pl-3 pr-2 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] hover:underline",
|
|
2363
2811
|
"aria-label": `Show artist ${filterArtist.name}`,
|
|
2364
2812
|
onClick: () => {
|
|
2365
2813
|
if (selectedNodeId !== null) clearSelection();
|
|
@@ -2367,86 +2815,174 @@ function GenreTreeView({
|
|
|
2367
2815
|
children: filterArtist.name
|
|
2368
2816
|
}
|
|
2369
2817
|
),
|
|
2370
|
-
/* @__PURE__ */ (0,
|
|
2818
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2371
2819
|
"button",
|
|
2372
2820
|
{
|
|
2373
2821
|
type: "button",
|
|
2374
|
-
className: "py-1 pl-1 pr-3 hover:text-[var(--color-text)]",
|
|
2822
|
+
className: "py-1 pl-1 pr-3 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] hover:text-[var(--color-text)]",
|
|
2375
2823
|
"aria-label": `Clear artist filter ${filterArtist.name}`,
|
|
2376
2824
|
onClick: () => changeArtist(null),
|
|
2377
2825
|
children: "\xD7"
|
|
2378
2826
|
}
|
|
2379
2827
|
)
|
|
2380
|
-
] })
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
value: viewMode,
|
|
2386
|
-
onChange: setViewMode,
|
|
2387
|
-
options: [
|
|
2388
|
-
{ value: "pop-core", label: "Pop/Core", disabled: !canShowPopCore, title: canShowPopCore ? void 0 : NO_POP_ROOT_TITLE },
|
|
2389
|
-
{ value: "wheel", label: "Wheel" },
|
|
2390
|
-
{ value: "stacked", label: "Stacked" },
|
|
2391
|
-
{ value: "outline", label: "Outline", disabled: !canShowPopCore, title: canShowPopCore ? void 0 : NO_POP_ROOT_TITLE }
|
|
2392
|
-
]
|
|
2393
|
-
}
|
|
2394
|
-
),
|
|
2395
|
-
!isLoading && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2396
|
-
"div",
|
|
2828
|
+
] });
|
|
2829
|
+
};
|
|
2830
|
+
const actions = /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_jsx_runtime39.Fragment, { children: [
|
|
2831
|
+
!isLoading && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2832
|
+
GenreSearch,
|
|
2397
2833
|
{
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
size: "sm",
|
|
2406
|
-
onClick: toggleWheelRotation,
|
|
2407
|
-
children: "Rotation"
|
|
2408
|
-
}
|
|
2409
|
-
)
|
|
2834
|
+
genrePlaylists: visibleGenrePlaylists,
|
|
2835
|
+
onSelect: handleGenreSearchSelect,
|
|
2836
|
+
selectedName: selectedGenrePlaylist?.name ?? builtInArtistPanel?.title ?? null,
|
|
2837
|
+
onClear: builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
|
|
2838
|
+
onPrevious: handlePrevious,
|
|
2839
|
+
onQueryChange: artistSearch ? setSearchText : void 0,
|
|
2840
|
+
extraResults: artistExtraResults
|
|
2410
2841
|
}
|
|
2411
2842
|
),
|
|
2412
|
-
!isLoading &&
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2843
|
+
!isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
|
|
2844
|
+
/* @__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: [
|
|
2845
|
+
!isLoading && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2846
|
+
import_ui16.SegmentedControl,
|
|
2847
|
+
{
|
|
2848
|
+
"aria-label": "Tree view mode",
|
|
2849
|
+
value: viewMode,
|
|
2850
|
+
onChange: setViewMode,
|
|
2851
|
+
options: [
|
|
2852
|
+
{ value: "pop-core", label: "Pop/Core", disabled: !canShowPopCore, title: canShowPopCore ? void 0 : NO_POP_ROOT_TITLE },
|
|
2853
|
+
{ value: "wheel", label: "Wheel" },
|
|
2854
|
+
{ value: "stacked", label: "Stacked" },
|
|
2855
|
+
{ value: "outline", label: "Outline", disabled: !canShowPopCore, title: canShowPopCore ? void 0 : NO_POP_ROOT_TITLE }
|
|
2856
|
+
]
|
|
2857
|
+
}
|
|
2858
|
+
),
|
|
2859
|
+
!isLoading && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2860
|
+
"div",
|
|
2861
|
+
{
|
|
2862
|
+
className: "flex items-center gap-1",
|
|
2863
|
+
role: "group",
|
|
2864
|
+
"aria-label": "Tree display options",
|
|
2865
|
+
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2866
|
+
import_ui16.Button,
|
|
2867
|
+
{
|
|
2868
|
+
variant: allowWheelRotation ? "default" : "outline",
|
|
2869
|
+
size: "sm",
|
|
2870
|
+
onClick: toggleWheelRotation,
|
|
2871
|
+
children: "Rotation"
|
|
2872
|
+
}
|
|
2873
|
+
)
|
|
2874
|
+
}
|
|
2875
|
+
),
|
|
2876
|
+
!isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2877
|
+
import_ui16.IconTextButton,
|
|
2878
|
+
{
|
|
2879
|
+
icon: import_lucide_react15.Plus,
|
|
2880
|
+
text: "Add root",
|
|
2881
|
+
onClick: () => handleGenreCreationAction(null)
|
|
2882
|
+
}
|
|
2883
|
+
)
|
|
2884
|
+
] })
|
|
2420
2885
|
] });
|
|
2421
|
-
return /* @__PURE__ */ (0,
|
|
2886
|
+
return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
|
|
2422
2887
|
"div",
|
|
2423
2888
|
{
|
|
2424
2889
|
ref: rootRef,
|
|
2425
|
-
className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS,
|
|
2890
|
+
className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
|
|
2426
2891
|
children: [
|
|
2427
|
-
/* @__PURE__ */ (0,
|
|
2428
|
-
/* @__PURE__ */ (0,
|
|
2892
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("style", { children: PREVIOUS_BUTTON_CSS }),
|
|
2893
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2429
2894
|
"div",
|
|
2430
2895
|
{
|
|
2431
|
-
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))]" : "left-5 top-5"}`,
|
|
2896
|
+
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"}`,
|
|
2432
2897
|
children: actions
|
|
2433
2898
|
}
|
|
2434
2899
|
),
|
|
2435
|
-
|
|
2900
|
+
!isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2901
|
+
"div",
|
|
2902
|
+
{
|
|
2903
|
+
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"}`,
|
|
2904
|
+
children: renderArtistFilter()
|
|
2905
|
+
}
|
|
2906
|
+
),
|
|
2907
|
+
detailTab && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2436
2908
|
"div",
|
|
2437
2909
|
{
|
|
2438
|
-
className: `absolute top-1/2 z-30 -translate-y-1/2 transition-[opacity,visibility] duration-150 ease-out ${isTreeReady ? "opacity-100" : "pointer-events-none invisible opacity-0"} ${detailExpanded ? "right-[calc(var(--layout-center-right)-var(--layout-edge-tab-w))]" : isCollapsed ? "left-0" : "left-[calc(var(--layout-gutter)+var(--layout-left-w))]"}`,
|
|
2910
|
+
className: `absolute top-1/2 z-30 -translate-y-1/2 max-md:hidden transition-[opacity,visibility] duration-150 ease-out ${isTreeReady ? "opacity-100" : "pointer-events-none invisible opacity-0"} ${detailExpanded ? "right-[calc(var(--layout-center-right)-var(--layout-edge-tab-w))]" : isCollapsed ? "left-0" : "left-[calc(var(--layout-gutter)+var(--layout-left-w))]"}`,
|
|
2439
2911
|
children: detailTab
|
|
2440
2912
|
}
|
|
2441
2913
|
),
|
|
2442
|
-
/* @__PURE__ */ (0,
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2914
|
+
/* @__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: [
|
|
2915
|
+
!isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
|
|
2916
|
+
isLoading ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_genre_tree_view4.GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
|
|
2917
|
+
// The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
|
|
2918
|
+
// and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
|
|
2919
|
+
// Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
|
|
2920
|
+
// The panel's own Zoom in button is hidden: the search bar carries it.
|
|
2921
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 [--gtv-info-panel-header-inset:calc(var(--layout-search-h,2.5rem)+var(--layout-panel-inset,0.5rem))] [&_.gtv-info-panel-header]:pb-2 [&_.gtv-info-panel-title--hidden]:h-0 [&_.gtv-toolbar]:hidden", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2922
|
+
GenrePlaylistTreeWheel,
|
|
2923
|
+
{
|
|
2924
|
+
scope,
|
|
2925
|
+
genrePlaylists: visibleGenrePlaylists,
|
|
2926
|
+
handleGenreCreationAction,
|
|
2927
|
+
handleGenreRenameAction,
|
|
2928
|
+
additionalActions,
|
|
2929
|
+
onSelectedNodeChange: handleSelectedNodeChange,
|
|
2930
|
+
hideInfoPanelClose: true,
|
|
2931
|
+
hideInfoPanelTitle: true,
|
|
2932
|
+
onNodeHover: handleNodeHover,
|
|
2933
|
+
renderInfoPanelBody,
|
|
2934
|
+
playback,
|
|
2935
|
+
selectedNodeId,
|
|
2936
|
+
readOnly,
|
|
2937
|
+
allowWheelRotation,
|
|
2938
|
+
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2939
|
+
busy,
|
|
2940
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
2941
|
+
floatingControlsLeading,
|
|
2942
|
+
canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2943
|
+
] }) : void 0,
|
|
2944
|
+
defaultViewport,
|
|
2945
|
+
viewportRestore,
|
|
2946
|
+
onViewportChange,
|
|
2947
|
+
...infoPanelCuration
|
|
2948
|
+
}
|
|
2949
|
+
) }) })
|
|
2950
|
+
) : viewMode === "pop-core" ? (
|
|
2951
|
+
// Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
|
|
2952
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 [--gtv-info-panel-header-inset:calc(var(--layout-search-h,2.5rem)+var(--layout-panel-inset,0.5rem))] [&_.gtv-info-panel-header]:pb-2 [&_.gtv-info-panel-title--hidden]:h-0 [&_.gtv-toolbar]:hidden [&_.gtv-info-panel-zoom-in]:hidden", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2953
|
+
GenrePlaylistTreeWheelRadialPopCore,
|
|
2954
|
+
{
|
|
2955
|
+
scope,
|
|
2956
|
+
genrePlaylists: visibleGenrePlaylists,
|
|
2957
|
+
handleGenreCreationAction,
|
|
2958
|
+
handleGenreRenameAction,
|
|
2959
|
+
additionalActions,
|
|
2960
|
+
onSelectedNodeChange: handleSelectedNodeChange,
|
|
2961
|
+
hideInfoPanelClose: true,
|
|
2962
|
+
hideInfoPanelTitle: true,
|
|
2963
|
+
onNodeHover: handleNodeHover,
|
|
2964
|
+
renderInfoPanelBody,
|
|
2965
|
+
playback,
|
|
2966
|
+
selectedNodeId,
|
|
2967
|
+
readOnly,
|
|
2968
|
+
allowWheelRotation,
|
|
2969
|
+
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2970
|
+
busy,
|
|
2971
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
2972
|
+
floatingControlsLeading,
|
|
2973
|
+
canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2974
|
+
] }) : void 0,
|
|
2975
|
+
defaultViewport,
|
|
2976
|
+
viewportRestore,
|
|
2977
|
+
onViewportChange,
|
|
2978
|
+
...infoPanelCuration,
|
|
2979
|
+
...focus
|
|
2980
|
+
}
|
|
2981
|
+
) }) })
|
|
2982
|
+
) : viewMode === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative [--gtv-info-panel-header-inset:calc(var(--layout-search-h,2.5rem)+var(--layout-panel-inset,0.5rem)+0.75rem)] [&_.gtv-toolbar]:hidden", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2983
|
+
GenrePlaylistTreeWheelRadialPopCore,
|
|
2449
2984
|
{
|
|
2985
|
+
outline: true,
|
|
2450
2986
|
scope,
|
|
2451
2987
|
genrePlaylists: visibleGenrePlaylists,
|
|
2452
2988
|
handleGenreCreationAction,
|
|
@@ -2460,127 +2996,192 @@ function GenreTreeView({
|
|
|
2460
2996
|
playback,
|
|
2461
2997
|
selectedNodeId,
|
|
2462
2998
|
readOnly,
|
|
2463
|
-
allowWheelRotation,
|
|
2464
|
-
controlsOrientation,
|
|
2465
|
-
sizeMetric: filterArtist ? "songs" : deferredSizeMetric,
|
|
2466
|
-
busy,
|
|
2467
|
-
floatingControlsLeading: filterArtist ? void 0 : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric }),
|
|
2468
|
-
defaultViewport,
|
|
2469
|
-
onViewportChange,
|
|
2470
2999
|
...infoPanelCuration
|
|
2471
3000
|
}
|
|
2472
|
-
) })
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
3001
|
+
) }) : /* @__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)] [&_.gtv-toolbar]:hidden", children: Object.entries(groupedGenrePlaylistsByRoot).map(
|
|
3002
|
+
([uuid, genrePlaylistTreePerRoot], rootIndex) => {
|
|
3003
|
+
return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
3004
|
+
"div",
|
|
3005
|
+
{
|
|
3006
|
+
className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
|
|
3007
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
3008
|
+
GenrePlaylistTreePerRoot,
|
|
3009
|
+
{
|
|
3010
|
+
scope,
|
|
3011
|
+
rootUuid: uuid,
|
|
3012
|
+
genrePlaylistTreePerRoot,
|
|
3013
|
+
handleGenreCreationAction,
|
|
3014
|
+
handleGenreRenameAction,
|
|
3015
|
+
additionalActions,
|
|
3016
|
+
onSelectedNodeChange: handleSelectedNodeChange,
|
|
3017
|
+
hideInfoPanelClose: true,
|
|
3018
|
+
hideInfoPanelTitle: true,
|
|
3019
|
+
onNodeHover: handleNodeHover,
|
|
3020
|
+
renderInfoPanelBody,
|
|
3021
|
+
playback,
|
|
3022
|
+
selectedNodeId: uuid === selectedGenrePlaylist?.root.uuid ? selectedNodeId : null,
|
|
3023
|
+
readOnly,
|
|
3024
|
+
wheelZoom: "modifier",
|
|
3025
|
+
defaultViewport: uuid === selectedGenrePlaylist?.root.uuid ? defaultViewport : void 0,
|
|
3026
|
+
viewportRestore: uuid === selectedGenrePlaylist?.root.uuid && uuid === restoreRootUuid ? viewportRestore : void 0,
|
|
3027
|
+
onViewportChange: uuid === selectedGenrePlaylist?.root.uuid ? onViewportChange : void 0,
|
|
3028
|
+
...infoPanelCuration,
|
|
3029
|
+
overlayPanel: rootIndex === 0 ? activeOverlayPanel : void 0
|
|
3030
|
+
}
|
|
3031
|
+
) })
|
|
3032
|
+
},
|
|
3033
|
+
uuid
|
|
3034
|
+
);
|
|
2500
3035
|
}
|
|
2501
|
-
) })
|
|
2502
|
-
|
|
2503
|
-
GenrePlaylistTreeWheelRadialPopCore,
|
|
2504
|
-
{
|
|
2505
|
-
outline: true,
|
|
2506
|
-
scope,
|
|
2507
|
-
genrePlaylists: visibleGenrePlaylists,
|
|
2508
|
-
handleGenreCreationAction,
|
|
2509
|
-
handleGenreRenameAction,
|
|
2510
|
-
additionalActions,
|
|
2511
|
-
onSelectedNodeChange: handleSelectedNodeChange,
|
|
2512
|
-
hideInfoPanelClose: true,
|
|
2513
|
-
hideInfoPanelTitle: true,
|
|
2514
|
-
onNodeHover: handleNodeHover,
|
|
2515
|
-
renderInfoPanelBody,
|
|
2516
|
-
playback,
|
|
2517
|
-
selectedNodeId,
|
|
2518
|
-
readOnly,
|
|
2519
|
-
...infoPanelCuration
|
|
2520
|
-
}
|
|
2521
|
-
) }) : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "tree-container relative flex flex-col gap-4 w-full pt-14 overflow-x-auto overflow-y-auto text-[var(--color-text)] [&_.gtv-toolbar]:hidden", children: Object.entries(groupedGenrePlaylistsByRoot).map(
|
|
2522
|
-
([uuid, genrePlaylistTreePerRoot], rootIndex) => {
|
|
2523
|
-
return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2524
|
-
"div",
|
|
2525
|
-
{
|
|
2526
|
-
className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
|
|
2527
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
2528
|
-
GenrePlaylistTreePerRoot,
|
|
2529
|
-
{
|
|
2530
|
-
scope,
|
|
2531
|
-
rootUuid: uuid,
|
|
2532
|
-
genrePlaylistTreePerRoot,
|
|
2533
|
-
handleGenreCreationAction,
|
|
2534
|
-
handleGenreRenameAction,
|
|
2535
|
-
additionalActions,
|
|
2536
|
-
onSelectedNodeChange: handleSelectedNodeChange,
|
|
2537
|
-
hideInfoPanelClose: true,
|
|
2538
|
-
hideInfoPanelTitle: true,
|
|
2539
|
-
onNodeHover: handleNodeHover,
|
|
2540
|
-
renderInfoPanelBody,
|
|
2541
|
-
playback,
|
|
2542
|
-
selectedNodeId: uuid === selectedGenrePlaylist?.root.uuid ? selectedNodeId : null,
|
|
2543
|
-
readOnly,
|
|
2544
|
-
wheelZoom: "modifier",
|
|
2545
|
-
controlsOrientation,
|
|
2546
|
-
defaultViewport: uuid === selectedGenrePlaylist?.root.uuid ? defaultViewport : void 0,
|
|
2547
|
-
onViewportChange: uuid === selectedGenrePlaylist?.root.uuid ? onViewportChange : void 0,
|
|
2548
|
-
...infoPanelCuration,
|
|
2549
|
-
overlayPanel: rootIndex === 0 ? activeOverlayPanel : void 0
|
|
2550
|
-
}
|
|
2551
|
-
) })
|
|
2552
|
-
},
|
|
2553
|
-
uuid
|
|
2554
|
-
);
|
|
2555
|
-
}
|
|
2556
|
-
) }) }) })
|
|
3036
|
+
) })
|
|
3037
|
+
] }) })
|
|
2557
3038
|
]
|
|
2558
3039
|
}
|
|
2559
3040
|
);
|
|
2560
3041
|
}
|
|
2561
3042
|
|
|
2562
|
-
// src/genre-tree/
|
|
3043
|
+
// src/genre-tree/GenreDetailPanel.tsx
|
|
3044
|
+
var import_react16 = require("react");
|
|
2563
3045
|
var import_genre_tree_view5 = require("@behindthemusictree/genre-tree-view");
|
|
3046
|
+
var import_ui17 = require("@behindthemusictree/ui");
|
|
3047
|
+
var import_genre_tree18 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3048
|
+
var import_jsx_runtime40 = require("react/jsx-runtime");
|
|
3049
|
+
var ITEM_COUNT_LABEL = "Songs";
|
|
3050
|
+
var RELATIVE_ITEM_COUNT = 1;
|
|
3051
|
+
function toNode(genre, parentId) {
|
|
3052
|
+
return {
|
|
3053
|
+
id: genre.uuid,
|
|
3054
|
+
parentId,
|
|
3055
|
+
name: genre.name,
|
|
3056
|
+
itemCount: RELATIVE_ITEM_COUNT,
|
|
3057
|
+
side: genre.side ?? void 0,
|
|
3058
|
+
wikidataId: genre.wikidataId
|
|
3059
|
+
};
|
|
3060
|
+
}
|
|
3061
|
+
function GenreDetailPanel({
|
|
3062
|
+
genreUuid,
|
|
3063
|
+
scope,
|
|
3064
|
+
getBackendBaseUrl,
|
|
3065
|
+
onClose,
|
|
3066
|
+
onSelectGenre,
|
|
3067
|
+
onZoomIn,
|
|
3068
|
+
shareUrl,
|
|
3069
|
+
shareTarget,
|
|
3070
|
+
collapsed,
|
|
3071
|
+
expanded,
|
|
3072
|
+
onExpandedChange,
|
|
3073
|
+
extras
|
|
3074
|
+
}) {
|
|
3075
|
+
const { data: detail, isPending, isError } = (0, import_genre_tree18.useFetchGenreDetail)(genreUuid, scope, getBackendBaseUrl);
|
|
3076
|
+
const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: [] });
|
|
3077
|
+
const view = (0, import_react16.useMemo)(() => {
|
|
3078
|
+
if (!detail) return null;
|
|
3079
|
+
const rootColor = (0, import_genre_tree_view5.getGenreTreeColor)(detail.root.wikidataId);
|
|
3080
|
+
const relative = (genre, parentId) => ({
|
|
3081
|
+
node: toNode(genre, parentId),
|
|
3082
|
+
fill: (0, import_genre_tree_view5.tintSurface)(rootColor),
|
|
3083
|
+
textColor: import_genre_tree_view5.TEXT_COLOR
|
|
3084
|
+
});
|
|
3085
|
+
const ancestors = [...detail.ascendants].sort((a, b) => b.degree - a.degree);
|
|
3086
|
+
return {
|
|
3087
|
+
node: {
|
|
3088
|
+
id: detail.uuid,
|
|
3089
|
+
parentId: detail.parent?.uuid ?? null,
|
|
3090
|
+
name: detail.name,
|
|
3091
|
+
itemCount: detail.tracksCount,
|
|
3092
|
+
wikidataId: detail.wikidataId
|
|
3093
|
+
},
|
|
3094
|
+
fill: rootColor,
|
|
3095
|
+
parentNode: detail.parent ? relative(detail.parent, null) : null,
|
|
3096
|
+
childNodes: detail.children.map((child) => relative(child, detail.uuid)),
|
|
3097
|
+
ancestorNodes: ancestors.map(
|
|
3098
|
+
({ ascendant }, index) => relative(ascendant, index === 0 ? null : ancestors[index - 1].ascendant.uuid)
|
|
3099
|
+
)
|
|
3100
|
+
};
|
|
3101
|
+
}, [detail]);
|
|
3102
|
+
if (isError) {
|
|
3103
|
+
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." });
|
|
3104
|
+
}
|
|
3105
|
+
if (isPending || !detail || !view) {
|
|
3106
|
+
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: [
|
|
3107
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui17.Skeleton, { className: "h-6 w-2/3" }),
|
|
3108
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui17.Skeleton, { className: "h-4 w-full" }),
|
|
3109
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui17.Skeleton, { className: "h-4 w-3/4" })
|
|
3110
|
+
] });
|
|
3111
|
+
}
|
|
3112
|
+
const renderBody = (node, sections) => ({
|
|
3113
|
+
primary: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
3114
|
+
GenreInfoPanelBody,
|
|
3115
|
+
{
|
|
3116
|
+
toolbar: sections.toolbar,
|
|
3117
|
+
family: sections.family,
|
|
3118
|
+
genreUuid: detail.uuid,
|
|
3119
|
+
genrePlaylist: { ...detail.criteriaPlaylist, tracksCount: detail.tracksCount },
|
|
3120
|
+
wikidataId: detail.wikidataId,
|
|
3121
|
+
scope,
|
|
3122
|
+
getBackendBaseUrl,
|
|
3123
|
+
playback,
|
|
3124
|
+
band: { node, onZoomIn, shareUrl, shareTarget: shareUrl ? shareTarget : void 0 },
|
|
3125
|
+
extras
|
|
3126
|
+
}
|
|
3127
|
+
),
|
|
3128
|
+
aside: null
|
|
3129
|
+
});
|
|
3130
|
+
return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
3131
|
+
import_genre_tree_view5.InfoPanel,
|
|
3132
|
+
{
|
|
3133
|
+
node: view.node,
|
|
3134
|
+
fill: view.fill,
|
|
3135
|
+
textColor: import_genre_tree_view5.ACCENT_TEXT_COLOR,
|
|
3136
|
+
parentNode: view.parentNode,
|
|
3137
|
+
childNodes: view.childNodes,
|
|
3138
|
+
ancestorNodes: view.ancestorNodes,
|
|
3139
|
+
side: "left",
|
|
3140
|
+
onClose,
|
|
3141
|
+
onSelectNode: (nodeId) => onSelectGenre(nodeId),
|
|
3142
|
+
itemCountLabel: ITEM_COUNT_LABEL,
|
|
3143
|
+
renderInfoPanelBody: renderBody,
|
|
3144
|
+
expanded,
|
|
3145
|
+
onExpandedChange,
|
|
3146
|
+
collapsed,
|
|
3147
|
+
hideExpandButton: onExpandedChange === void 0
|
|
3148
|
+
}
|
|
3149
|
+
);
|
|
3150
|
+
}
|
|
3151
|
+
|
|
3152
|
+
// src/genre-tree/index.ts
|
|
3153
|
+
var import_genre_tree_view6 = require("@behindthemusictree/genre-tree-view");
|
|
2564
3154
|
|
|
2565
3155
|
// src/genre-tree/track-list-sidebar/TrackListSidebar.tsx
|
|
2566
|
-
var
|
|
3156
|
+
var import_react17 = require("react");
|
|
3157
|
+
var import_lucide_react16 = require("lucide-react");
|
|
2567
3158
|
var import_genre_tree19 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2568
3159
|
var import_genre_tree20 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2569
|
-
var import_genre_tree21 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2570
3160
|
var import_track_noun7 = require("@behindthemusictree/app-kit/track-noun");
|
|
2571
|
-
var
|
|
3161
|
+
var import_jsx_runtime41 = require("react/jsx-runtime");
|
|
3162
|
+
var useBrowserLayoutEffect = typeof window === "undefined" ? import_react17.useEffect : import_react17.useLayoutEffect;
|
|
2572
3163
|
function TrackListSidebar({
|
|
2573
|
-
className = "",
|
|
3164
|
+
className: className2 = "",
|
|
2574
3165
|
renderDuration,
|
|
2575
3166
|
renderActions,
|
|
2576
3167
|
layout = "fixed",
|
|
3168
|
+
isCollapsed = false,
|
|
3169
|
+
onExpand,
|
|
3170
|
+
onMinimize,
|
|
2577
3171
|
renderArtistLink
|
|
2578
3172
|
}) {
|
|
2579
|
-
const { trackList, loadMore } = (0, import_genre_tree19.useTrackList)();
|
|
3173
|
+
const { trackList, loadMore, autoAdvanceScrollRequest } = (0, import_genre_tree19.useTrackList)();
|
|
2580
3174
|
const trackNoun = (0, import_track_noun7.useTrackNoun)();
|
|
2581
|
-
const
|
|
3175
|
+
const trackListRef = (0, import_react17.useRef)(null);
|
|
3176
|
+
const previousOriginRef = (0, import_react17.useRef)(trackList?.origin ?? null);
|
|
3177
|
+
const handledAutoAdvanceRequestIdRef = (0, import_react17.useRef)(null);
|
|
3178
|
+
const shouldFollowPlaybackRef = (0, import_react17.useRef)(true);
|
|
3179
|
+
const programmaticScrollTopRef = (0, import_react17.useRef)(null);
|
|
3180
|
+
const userScrollIntentRef = (0, import_react17.useRef)(false);
|
|
3181
|
+
const userScrollIntentTimerRef = (0, import_react17.useRef)(null);
|
|
3182
|
+
const loadMoreSentinelRef = (0, import_react17.useRef)(null);
|
|
2582
3183
|
const hasMore = !!trackList && trackList.nextPage !== null;
|
|
2583
|
-
(0,
|
|
3184
|
+
(0, import_react17.useEffect)(() => {
|
|
2584
3185
|
const sentinel = loadMoreSentinelRef.current;
|
|
2585
3186
|
if (!hasMore || !sentinel) return;
|
|
2586
3187
|
const observer = new IntersectionObserver((entries) => {
|
|
@@ -2591,44 +3192,115 @@ function TrackListSidebar({
|
|
|
2591
3192
|
observer.observe(sentinel);
|
|
2592
3193
|
return () => observer.disconnect();
|
|
2593
3194
|
}, [hasMore, loadMore]);
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
3195
|
+
useBrowserLayoutEffect(() => {
|
|
3196
|
+
const origin = trackList?.origin ?? null;
|
|
3197
|
+
if (previousOriginRef.current === origin) return;
|
|
3198
|
+
previousOriginRef.current = origin;
|
|
3199
|
+
handledAutoAdvanceRequestIdRef.current = null;
|
|
3200
|
+
shouldFollowPlaybackRef.current = true;
|
|
3201
|
+
programmaticScrollTopRef.current = null;
|
|
3202
|
+
if (trackListRef.current) trackListRef.current.scrollTop = 0;
|
|
3203
|
+
}, [trackList?.origin]);
|
|
3204
|
+
(0, import_react17.useEffect)(() => {
|
|
3205
|
+
const list = trackListRef.current;
|
|
3206
|
+
if (!isCollapsed && list?.scrollTop === 0) shouldFollowPlaybackRef.current = true;
|
|
3207
|
+
}, [isCollapsed]);
|
|
3208
|
+
(0, import_react17.useEffect)(
|
|
3209
|
+
() => () => {
|
|
3210
|
+
if (userScrollIntentTimerRef.current !== null) clearTimeout(userScrollIntentTimerRef.current);
|
|
3211
|
+
},
|
|
3212
|
+
[]
|
|
3213
|
+
);
|
|
3214
|
+
useBrowserLayoutEffect(() => {
|
|
3215
|
+
if (!autoAdvanceScrollRequest || handledAutoAdvanceRequestIdRef.current === autoAdvanceScrollRequest.id) {
|
|
3216
|
+
return;
|
|
3217
|
+
}
|
|
3218
|
+
if (isCollapsed) return;
|
|
3219
|
+
const list = trackListRef.current;
|
|
3220
|
+
if (!list) return;
|
|
3221
|
+
if (list.scrollTop <= 1) shouldFollowPlaybackRef.current = true;
|
|
3222
|
+
if (!shouldFollowPlaybackRef.current) {
|
|
3223
|
+
handledAutoAdvanceRequestIdRef.current = autoAdvanceScrollRequest.id;
|
|
3224
|
+
return;
|
|
3225
|
+
}
|
|
3226
|
+
const row = Array.from(list.querySelectorAll("[data-track-uuid]")).find(
|
|
3227
|
+
(element) => element.dataset.trackUuid === autoAdvanceScrollRequest.trackUuid
|
|
3228
|
+
);
|
|
3229
|
+
if (!row) return;
|
|
3230
|
+
handledAutoAdvanceRequestIdRef.current = autoAdvanceScrollRequest.id;
|
|
3231
|
+
const rowTop = row.getBoundingClientRect().top - list.getBoundingClientRect().top;
|
|
3232
|
+
const targetScrollTop = list.scrollTop + rowTop;
|
|
3233
|
+
programmaticScrollTopRef.current = targetScrollTop;
|
|
3234
|
+
list.scrollTop = targetScrollTop;
|
|
3235
|
+
programmaticScrollTopRef.current = list.scrollTop;
|
|
3236
|
+
}, [autoAdvanceScrollRequest, isCollapsed, trackList]);
|
|
3237
|
+
const markUserScrollIntent = () => {
|
|
3238
|
+
userScrollIntentRef.current = true;
|
|
3239
|
+
if (userScrollIntentTimerRef.current !== null) clearTimeout(userScrollIntentTimerRef.current);
|
|
3240
|
+
userScrollIntentTimerRef.current = setTimeout(() => {
|
|
3241
|
+
userScrollIntentRef.current = false;
|
|
3242
|
+
userScrollIntentTimerRef.current = null;
|
|
3243
|
+
}, 250);
|
|
3244
|
+
};
|
|
3245
|
+
const handleTrackListScroll = (event) => {
|
|
3246
|
+
const list = event.currentTarget;
|
|
3247
|
+
if (userScrollIntentRef.current) {
|
|
3248
|
+
userScrollIntentRef.current = false;
|
|
3249
|
+
if (userScrollIntentTimerRef.current !== null) clearTimeout(userScrollIntentTimerRef.current);
|
|
3250
|
+
userScrollIntentTimerRef.current = null;
|
|
3251
|
+
programmaticScrollTopRef.current = null;
|
|
3252
|
+
shouldFollowPlaybackRef.current = list.scrollTop <= 1;
|
|
3253
|
+
return;
|
|
3254
|
+
}
|
|
3255
|
+
const programmaticScrollTop = programmaticScrollTopRef.current;
|
|
3256
|
+
if (programmaticScrollTop !== null) {
|
|
3257
|
+
if (Math.abs(list.scrollTop - programmaticScrollTop) <= 1) programmaticScrollTopRef.current = null;
|
|
3258
|
+
return;
|
|
3259
|
+
}
|
|
3260
|
+
};
|
|
3261
|
+
const positionClasses = layout === "inline" ? "relative w-full flex h-full flex-col" : "fixed right-0 w-[50rem] flex flex-col";
|
|
3262
|
+
return trackList ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
|
|
2597
3263
|
"div",
|
|
2598
3264
|
{
|
|
2599
3265
|
"data-theme": "dark",
|
|
2600
|
-
className: `track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${
|
|
2601
|
-
style: layout === "fixed" ? {
|
|
2602
|
-
bottom: "79px"
|
|
2603
|
-
/* Match player height exactly */
|
|
2604
|
-
} : void 0,
|
|
3266
|
+
className: `track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${className2}`,
|
|
3267
|
+
style: layout === "fixed" ? { top: "63.5px", maxHeight: "calc(100vh - 63.5px - 79px)" } : void 0,
|
|
2605
3268
|
children: [
|
|
2606
|
-
/* @__PURE__ */ (0,
|
|
2607
|
-
/* @__PURE__ */ (0,
|
|
2608
|
-
/* @__PURE__ */ (0,
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
3269
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
|
|
3270
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
|
|
3271
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "origin flex text-xl", children: [
|
|
3272
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
|
|
3273
|
+
/* @__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 }) })
|
|
3274
|
+
] }),
|
|
3275
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
|
|
3276
|
+
trackList.origin.type === import_genre_tree20.TrackListOriginType.GENRE_PLAYLIST ? "Genre \u2022 " : trackList.origin.type === import_genre_tree20.TrackListOriginType.ARTIST ? "Artist \u2022 " : `${trackNoun.singular} playlist \u2022 `,
|
|
3277
|
+
`${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`
|
|
3278
|
+
] })
|
|
2614
3279
|
] }),
|
|
2615
|
-
/* @__PURE__ */ (0,
|
|
3280
|
+
(isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
2616
3281
|
"button",
|
|
2617
3282
|
{
|
|
2618
3283
|
type: "button",
|
|
2619
|
-
"aria-label": "
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
3284
|
+
"aria-label": isCollapsed ? "Show tracks" : "Hide tracks",
|
|
3285
|
+
title: isCollapsed ? "Show tracks" : "Hide tracks",
|
|
3286
|
+
"aria-expanded": !isCollapsed,
|
|
3287
|
+
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"}`,
|
|
3288
|
+
onClick: isCollapsed ? onExpand : onMinimize,
|
|
3289
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_lucide_react16.ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
|
|
2623
3290
|
}
|
|
2624
3291
|
)
|
|
2625
3292
|
] }),
|
|
2626
|
-
/* @__PURE__ */ (0,
|
|
3293
|
+
!isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
|
|
2627
3294
|
"ul",
|
|
2628
3295
|
{
|
|
2629
|
-
|
|
3296
|
+
ref: trackListRef,
|
|
3297
|
+
onScroll: handleTrackListScroll,
|
|
3298
|
+
onWheel: markUserScrollIntent,
|
|
3299
|
+
onTouchMove: markUserScrollIntent,
|
|
3300
|
+
onPointerDown: markUserScrollIntent,
|
|
3301
|
+
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",
|
|
2630
3302
|
children: [
|
|
2631
|
-
trackList.tracks.map((track, index) => /* @__PURE__ */ (0,
|
|
3303
|
+
trackList.tracks.map((track, index) => /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
2632
3304
|
TrackItem,
|
|
2633
3305
|
{
|
|
2634
3306
|
track,
|
|
@@ -2638,7 +3310,7 @@ function TrackListSidebar({
|
|
|
2638
3310
|
renderArtistLink
|
|
2639
3311
|
}
|
|
2640
3312
|
) }, track.uuid)),
|
|
2641
|
-
hasMore && /* @__PURE__ */ (0,
|
|
3313
|
+
hasMore && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
|
|
2642
3314
|
]
|
|
2643
3315
|
}
|
|
2644
3316
|
)
|
|
@@ -2648,42 +3320,162 @@ function TrackListSidebar({
|
|
|
2648
3320
|
}
|
|
2649
3321
|
|
|
2650
3322
|
// src/genre-tree/PlayerPanel.tsx
|
|
2651
|
-
var
|
|
2652
|
-
var import_lucide_react13 = require("lucide-react");
|
|
3323
|
+
var import_react18 = require("react");
|
|
2653
3324
|
var import_tailwind_merge2 = require("tailwind-merge");
|
|
2654
|
-
var
|
|
3325
|
+
var import_player12 = require("@behindthemusictree/app-kit/player");
|
|
2655
3326
|
var import_genre_tree22 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2656
|
-
|
|
3327
|
+
|
|
3328
|
+
// src/genre-tree/PlayerControls.tsx
|
|
3329
|
+
var import_lucide_react17 = require("lucide-react");
|
|
3330
|
+
var import_genre_tree21 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3331
|
+
var import_player11 = require("@behindthemusictree/app-kit/player");
|
|
3332
|
+
var import_jsx_runtime42 = require("react/jsx-runtime");
|
|
3333
|
+
function PlayerControls() {
|
|
3334
|
+
const {
|
|
3335
|
+
duration,
|
|
3336
|
+
handlePlayPauseAction,
|
|
3337
|
+
isLoading,
|
|
3338
|
+
playState,
|
|
3339
|
+
playerTrackObject,
|
|
3340
|
+
seekTo,
|
|
3341
|
+
setVolume,
|
|
3342
|
+
volume
|
|
3343
|
+
} = (0, import_player11.usePlayer)();
|
|
3344
|
+
const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } = (0, import_genre_tree21.useTrackList)();
|
|
3345
|
+
const currentTime = (0, import_player11.useCurrentTime)();
|
|
3346
|
+
const isBusy = isLoading || playState === import_player11.PlayStates.LOADING;
|
|
3347
|
+
const isReady = !!playerTrackObject?.isReady && !isBusy && !playerTrackObject.loadError;
|
|
3348
|
+
const safeDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;
|
|
3349
|
+
const safeCurrentTime = Number.isFinite(currentTime) ? Math.max(0, Math.min(currentTime, safeDuration)) : 0;
|
|
3350
|
+
const isPlaying = playState === import_player11.PlayStates.PLAYING;
|
|
3351
|
+
return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
|
|
3352
|
+
"section",
|
|
3353
|
+
{
|
|
3354
|
+
"aria-label": "Player controls",
|
|
3355
|
+
className: "player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200",
|
|
3356
|
+
children: [
|
|
3357
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
|
|
3358
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { "aria-label": "Elapsed time", children: (0, import_genre_tree21.formatTime)(safeCurrentTime) }),
|
|
3359
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
3360
|
+
"input",
|
|
3361
|
+
{
|
|
3362
|
+
"aria-label": "Seek",
|
|
3363
|
+
className: "h-1 min-w-0 flex-1 cursor-pointer accent-white disabled:cursor-not-allowed disabled:opacity-40",
|
|
3364
|
+
type: "range",
|
|
3365
|
+
min: 0,
|
|
3366
|
+
max: safeDuration,
|
|
3367
|
+
step: 0.1,
|
|
3368
|
+
value: safeCurrentTime,
|
|
3369
|
+
disabled: !isReady || safeDuration <= 0,
|
|
3370
|
+
onChange: (event) => seekTo(Number(event.currentTarget.value))
|
|
3371
|
+
}
|
|
3372
|
+
),
|
|
3373
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { "aria-label": "Duration", children: (0, import_genre_tree21.formatTime)(safeDuration) })
|
|
3374
|
+
] }),
|
|
3375
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
|
|
3376
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
|
|
3377
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
3378
|
+
"button",
|
|
3379
|
+
{
|
|
3380
|
+
type: "button",
|
|
3381
|
+
"aria-label": "Previous track",
|
|
3382
|
+
title: "Previous track",
|
|
3383
|
+
disabled: !isReady || !hasPreviousTrack,
|
|
3384
|
+
onClick: playPreviousTrack,
|
|
3385
|
+
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",
|
|
3386
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react17.SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
3387
|
+
}
|
|
3388
|
+
),
|
|
3389
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
3390
|
+
"button",
|
|
3391
|
+
{
|
|
3392
|
+
type: "button",
|
|
3393
|
+
"aria-label": isPlaying ? "Pause" : "Play",
|
|
3394
|
+
title: isPlaying ? "Pause" : "Play",
|
|
3395
|
+
disabled: !isReady,
|
|
3396
|
+
onClick: handlePlayPauseAction,
|
|
3397
|
+
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",
|
|
3398
|
+
children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react17.Pause, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react17.Play, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
|
|
3399
|
+
}
|
|
3400
|
+
),
|
|
3401
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
3402
|
+
"button",
|
|
3403
|
+
{
|
|
3404
|
+
type: "button",
|
|
3405
|
+
"aria-label": "Next track",
|
|
3406
|
+
title: "Next track",
|
|
3407
|
+
disabled: !isReady || !hasNextTrack,
|
|
3408
|
+
onClick: playNextTrack,
|
|
3409
|
+
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",
|
|
3410
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react17.SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
3411
|
+
}
|
|
3412
|
+
)
|
|
3413
|
+
] }),
|
|
3414
|
+
/* @__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: [
|
|
3415
|
+
volume === 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react17.VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_lucide_react17.Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
|
|
3416
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "sr-only", children: "Volume" }),
|
|
3417
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
3418
|
+
"input",
|
|
3419
|
+
{
|
|
3420
|
+
"aria-label": "Volume",
|
|
3421
|
+
className: "h-[3px] min-w-0 flex-1 cursor-pointer accent-white disabled:cursor-not-allowed disabled:opacity-40",
|
|
3422
|
+
type: "range",
|
|
3423
|
+
min: 0,
|
|
3424
|
+
max: 100,
|
|
3425
|
+
step: 1,
|
|
3426
|
+
value: volume,
|
|
3427
|
+
disabled: !isReady,
|
|
3428
|
+
onChange: (event) => setVolume(Number(event.currentTarget.value))
|
|
3429
|
+
}
|
|
3430
|
+
)
|
|
3431
|
+
] })
|
|
3432
|
+
] })
|
|
3433
|
+
]
|
|
3434
|
+
}
|
|
3435
|
+
);
|
|
3436
|
+
}
|
|
3437
|
+
|
|
3438
|
+
// src/genre-tree/PlayerPanel.tsx
|
|
3439
|
+
var import_jsx_runtime43 = require("react/jsx-runtime");
|
|
2657
3440
|
var PANEL_ID = "player-panel";
|
|
2658
|
-
var iconButtonClassName = "flex h-6 w-6 shrink-0 items-center justify-center p-0 rounded-md hover:bg-white/10";
|
|
2659
3441
|
var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
|
|
2660
3442
|
function PlayerPanel({
|
|
2661
|
-
className,
|
|
3443
|
+
className: className2,
|
|
2662
3444
|
isVisible = true,
|
|
2663
|
-
onClose,
|
|
2664
3445
|
expanded,
|
|
2665
3446
|
onExpandedChange,
|
|
2666
3447
|
extendedWidth = DEFAULT_EXTENDED_WIDTH,
|
|
2667
3448
|
renderArtistLink
|
|
2668
3449
|
}) {
|
|
2669
|
-
const { playerTrackObject } = (0,
|
|
3450
|
+
const { playerTrackObject } = (0, import_player12.usePlayer)();
|
|
2670
3451
|
const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0, import_genre_tree22.useTrackListSidebarVisibility)();
|
|
2671
|
-
const [
|
|
3452
|
+
const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = (0, import_react18.useState)("");
|
|
3453
|
+
const loadedTrackTitle = playerTrackObject?.track.title;
|
|
3454
|
+
(0, import_react18.useEffect)(() => {
|
|
3455
|
+
if (loadedTrackTitle) setLastLoadedTrackTitle(loadedTrackTitle);
|
|
3456
|
+
}, [loadedTrackTitle]);
|
|
3457
|
+
const [uncontrolledExtended, setUncontrolledExtended] = (0, import_react18.useState)(false);
|
|
2672
3458
|
const isExtendRequested = expanded ?? uncontrolledExtended;
|
|
2673
3459
|
const setIsExtendRequested = (next) => {
|
|
2674
3460
|
setUncontrolledExtended(next);
|
|
2675
3461
|
onExpandedChange?.(next);
|
|
2676
3462
|
};
|
|
2677
3463
|
const isExtended = isExtendRequested && isTrackListSidebarVisible;
|
|
2678
|
-
const
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
} : { label: "Hide song list", onClick: toggleTrackListSidebar };
|
|
2682
|
-
const tabCenter = !isTrackListSidebarVisible ? { label: "Show song list", onClick: toggleTrackListSidebar } : isExtended ? null : {
|
|
2683
|
-
label: "Extend song list panel",
|
|
2684
|
-
onClick: () => setIsExtendRequested(true)
|
|
3464
|
+
const hideSongList = () => {
|
|
3465
|
+
setIsExtendRequested(false);
|
|
3466
|
+
if (isTrackListSidebarVisible) toggleTrackListSidebar();
|
|
2685
3467
|
};
|
|
2686
|
-
|
|
3468
|
+
const tabEdge = isTrackListSidebarVisible && isExtended ? {
|
|
3469
|
+
label: "Shrink player panel",
|
|
3470
|
+
onClick: () => setIsExtendRequested(false),
|
|
3471
|
+
expanded: true
|
|
3472
|
+
} : null;
|
|
3473
|
+
const tabCenter = isTrackListSidebarVisible && !isExtended ? {
|
|
3474
|
+
label: "Extend player panel",
|
|
3475
|
+
onClick: () => setIsExtendRequested(true),
|
|
3476
|
+
expanded: false
|
|
3477
|
+
} : null;
|
|
3478
|
+
return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(
|
|
2687
3479
|
"div",
|
|
2688
3480
|
{
|
|
2689
3481
|
id: PANEL_ID,
|
|
@@ -2692,11 +3484,11 @@ function PlayerPanel({
|
|
|
2692
3484
|
!isExtended && "w-[var(--layout-right-w)]",
|
|
2693
3485
|
isTrackListSidebarVisible && "h-[calc(100vh-var(--layout-band-top)-3.5rem)]",
|
|
2694
3486
|
!isVisible && "hidden",
|
|
2695
|
-
|
|
3487
|
+
className2
|
|
2696
3488
|
),
|
|
2697
3489
|
style: isExtended ? { width: extendedWidth } : void 0,
|
|
2698
3490
|
children: [
|
|
2699
|
-
/* @__PURE__ */ (0,
|
|
3491
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
2700
3492
|
PanelEdgeTab,
|
|
2701
3493
|
{
|
|
2702
3494
|
side: "right",
|
|
@@ -2706,7 +3498,7 @@ function PlayerPanel({
|
|
|
2706
3498
|
className: "absolute right-full top-1/2 -translate-y-1/2"
|
|
2707
3499
|
}
|
|
2708
3500
|
),
|
|
2709
|
-
/* @__PURE__ */ (0,
|
|
3501
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(
|
|
2710
3502
|
"div",
|
|
2711
3503
|
{
|
|
2712
3504
|
className: (0, import_tailwind_merge2.twMerge)(
|
|
@@ -2714,29 +3506,21 @@ function PlayerPanel({
|
|
|
2714
3506
|
isExtended ? "flex-row" : "flex-col"
|
|
2715
3507
|
),
|
|
2716
3508
|
children: [
|
|
2717
|
-
/* @__PURE__ */ (0,
|
|
2718
|
-
/* @__PURE__ */ (0,
|
|
2719
|
-
|
|
2720
|
-
/* @__PURE__ */ (0,
|
|
2721
|
-
|
|
2722
|
-
{
|
|
2723
|
-
type: "button",
|
|
2724
|
-
onClick: onClose,
|
|
2725
|
-
className: iconButtonClassName,
|
|
2726
|
-
"aria-label": "Close player",
|
|
2727
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_lucide_react13.X, { size: 16 })
|
|
2728
|
-
}
|
|
2729
|
-
) })
|
|
3509
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
|
|
3510
|
+
/* @__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 }) }),
|
|
3511
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "relative w-full shrink-0", children: [
|
|
3512
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_player12.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
|
|
3513
|
+
playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: playerTrackObject.loadError })
|
|
2730
3514
|
] }),
|
|
2731
|
-
/* @__PURE__ */ (0,
|
|
2732
|
-
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_player11.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
|
|
2733
|
-
playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: playerTrackObject.loadError })
|
|
2734
|
-
] })
|
|
3515
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)(PlayerControls, {})
|
|
2735
3516
|
] }),
|
|
2736
|
-
|
|
3517
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
2737
3518
|
TrackListSidebar,
|
|
2738
3519
|
{
|
|
2739
3520
|
layout: "inline",
|
|
3521
|
+
isCollapsed: !isTrackListSidebarVisible,
|
|
3522
|
+
onExpand: toggleTrackListSidebar,
|
|
3523
|
+
onMinimize: hideSongList,
|
|
2740
3524
|
className: (0, import_tailwind_merge2.twMerge)(
|
|
2741
3525
|
"min-h-0 min-w-0 flex-1",
|
|
2742
3526
|
isExtended && "border-l border-zinc-800"
|
|
@@ -2756,11 +3540,11 @@ function PlayerPanel({
|
|
|
2756
3540
|
var import_uuid = require("uuid");
|
|
2757
3541
|
var import_ai = require("react-icons/ai");
|
|
2758
3542
|
var import_genre_tree23 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2759
|
-
var
|
|
3543
|
+
var import_jsx_runtime44 = require("react/jsx-runtime");
|
|
2760
3544
|
function Rating({ rating, handleChange }) {
|
|
2761
3545
|
const instanceId = (0, import_uuid.v4)();
|
|
2762
|
-
return /* @__PURE__ */ (0,
|
|
2763
|
-
/* @__PURE__ */ (0,
|
|
3546
|
+
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: [
|
|
3547
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
2764
3548
|
"input",
|
|
2765
3549
|
{
|
|
2766
3550
|
id: `star-${instanceId}-${i}`,
|
|
@@ -2772,16 +3556,16 @@ function Rating({ rating, handleChange }) {
|
|
|
2772
3556
|
onChange: handleChange
|
|
2773
3557
|
}
|
|
2774
3558
|
),
|
|
2775
|
-
/* @__PURE__ */ (0,
|
|
2776
|
-
/* @__PURE__ */ (0,
|
|
3559
|
+
/* @__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: [
|
|
3560
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
2777
3561
|
import_ai.AiOutlineStar,
|
|
2778
3562
|
{
|
|
2779
3563
|
className: `w-7 h-7 absolute top-0 left-0 ${rating === import_genre_tree23.FORM_RATING_NULL_VALUE ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
|
|
2780
3564
|
}
|
|
2781
3565
|
),
|
|
2782
|
-
/* @__PURE__ */ (0,
|
|
3566
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { className: "flex-grow w-8" }),
|
|
2783
3567
|
" "
|
|
2784
|
-
] }) : /* @__PURE__ */ (0,
|
|
3568
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
2785
3569
|
import_ai.AiFillStar,
|
|
2786
3570
|
{
|
|
2787
3571
|
className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
|
|
@@ -2795,6 +3579,9 @@ function Rating({ rating, handleChange }) {
|
|
|
2795
3579
|
AuthErrorPopup,
|
|
2796
3580
|
AuthPopup,
|
|
2797
3581
|
BasePopup,
|
|
3582
|
+
GenreDetailPanel,
|
|
3583
|
+
GenreInfoPanelBody,
|
|
3584
|
+
GenreNodeActionBand,
|
|
2798
3585
|
GenrePlaylistTreePerRoot,
|
|
2799
3586
|
GenreSearch,
|
|
2800
3587
|
GenreTreeOutlineSkeleton,
|
|
@@ -2810,9 +3597,12 @@ function Rating({ rating, handleChange }) {
|
|
|
2810
3597
|
PopupTitle,
|
|
2811
3598
|
Rating,
|
|
2812
3599
|
SpotifyAuthErrorPopup,
|
|
3600
|
+
Toast,
|
|
2813
3601
|
TrackItem,
|
|
2814
3602
|
TrackListSidebar,
|
|
2815
3603
|
TrackPositionPlayPause,
|
|
2816
|
-
TrackUploadPopup
|
|
3604
|
+
TrackUploadPopup,
|
|
3605
|
+
useGenrePlaylistPlayback,
|
|
3606
|
+
useLeftDetailPanelLayout
|
|
2817
3607
|
});
|
|
2818
3608
|
//# sourceMappingURL=index.cjs.map
|