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