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