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