@behindthemusictree/app-kit-ui 29.1.0 → 31.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-36YVZ6JR.js → chunk-AQASG5KT.js} +874 -628
- package/dist/chunk-AQASG5KT.js.map +1 -0
- package/dist/genre-tree/index.cjs +892 -662
- package/dist/genre-tree/index.cjs.map +1 -1
- package/dist/genre-tree/index.d.ts +74 -49
- package/dist/genre-tree/index.js +3 -7
- package/dist/index.cjs +889 -659
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -7
- package/package.json +6 -6
- package/dist/chunk-36YVZ6JR.js.map +0 -1
|
@@ -5,20 +5,52 @@ import {
|
|
|
5
5
|
} from "./chunk-DKQQLLMY.js";
|
|
6
6
|
|
|
7
7
|
// src/genre-tree/GenreTreeView.tsx
|
|
8
|
-
import { useCallback as useCallback6, useDeferredValue, useEffect as
|
|
8
|
+
import { useCallback as useCallback6, useDeferredValue, useEffect as useEffect5, useMemo as useMemo7, useRef as useRef4, useState as useState7 } from "react";
|
|
9
9
|
import { useIsMutating } from "@tanstack/react-query";
|
|
10
10
|
import { Plus as Plus2 } from "lucide-react";
|
|
11
|
-
import {
|
|
11
|
+
import { Maximize2, Minimize2 } from "lucide-react";
|
|
12
|
+
import { IconTextButton, IconButton, Button, SegmentedControl as SegmentedControl2, DetailPanel, useObscuredInset } from "@behindthemusictree/ui";
|
|
12
13
|
import {
|
|
14
|
+
GenreNodeAncestors,
|
|
15
|
+
GenreNodeFamily,
|
|
13
16
|
GenreTreeWheelSkeleton,
|
|
14
|
-
GenreTreeViewSkeleton
|
|
17
|
+
GenreTreeViewSkeleton,
|
|
18
|
+
useGenreNodeRelations
|
|
15
19
|
} from "@behindthemusictree/genre-tree-view";
|
|
16
20
|
import {
|
|
17
|
-
useArtistSearch
|
|
21
|
+
useArtistSearch,
|
|
22
|
+
useSongSearch,
|
|
23
|
+
isSongSearchResultPlayable
|
|
18
24
|
} from "@behindthemusictree/app-kit/genre-tree";
|
|
19
25
|
import { useGenreTreeViewState, useArtist as useArtist2, useUpdateGenre as useUpdateGenre2, genreUpdateMutationKey } from "@behindthemusictree/app-kit/genre-tree";
|
|
20
26
|
import { usePopup as usePopup5 } from "@behindthemusictree/app-kit/popup";
|
|
21
27
|
|
|
28
|
+
// src/genre-tree/playlist-tree/size-metric.ts
|
|
29
|
+
var hasViewsCount = (genrePlaylists) => genrePlaylists.every((genrePlaylist) => genrePlaylist.viewsCount !== void 0);
|
|
30
|
+
var genrePlaylistSize = (genrePlaylist, metric) => {
|
|
31
|
+
if (metric === "songs") return genrePlaylist.tracksCount;
|
|
32
|
+
if (genrePlaylist.viewsCount === void 0) {
|
|
33
|
+
throw new Error(`Genre playlist ${genrePlaylist.uuid} has no viewsCount to size by views`);
|
|
34
|
+
}
|
|
35
|
+
return genrePlaylist.viewsCount;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// src/genre-tree/playlist-tree/toTreeNodes.ts
|
|
39
|
+
function toTreeNodes(genrePlaylists, sizeMetric) {
|
|
40
|
+
return genrePlaylists.map((genrePlaylist) => ({
|
|
41
|
+
id: genrePlaylist.uuid,
|
|
42
|
+
parentId: genrePlaylist.parent?.uuid ?? null,
|
|
43
|
+
name: genrePlaylist.name,
|
|
44
|
+
itemCount: genrePlaylistSize(genrePlaylist, sizeMetric),
|
|
45
|
+
actionable: Boolean(genrePlaylist.criteria),
|
|
46
|
+
detached: !genrePlaylist.criteria,
|
|
47
|
+
side: genrePlaylist.criteria?.side ?? void 0,
|
|
48
|
+
wikidataId: genrePlaylist.criteria?.wikidataId ?? null,
|
|
49
|
+
umbrella: genrePlaylist.isUmbrella,
|
|
50
|
+
scene: genrePlaylist.scene ?? void 0
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
|
|
22
54
|
// src/genre-tree/playlist-tree/TreePerRoot.tsx
|
|
23
55
|
import { useCallback as useCallback2, useMemo as useMemo2 } from "react";
|
|
24
56
|
import {
|
|
@@ -61,7 +93,6 @@ function useGenrePlaylistPlayback({
|
|
|
61
93
|
}
|
|
62
94
|
|
|
63
95
|
// src/genre-tree/playlist-tree/TreePerRoot.tsx
|
|
64
|
-
import { capitalize, useTrackNoun } from "@behindthemusictree/app-kit/track-noun";
|
|
65
96
|
import { jsx } from "react/jsx-runtime";
|
|
66
97
|
function GenrePlaylistTreePerRoot({
|
|
67
98
|
scope,
|
|
@@ -73,24 +104,19 @@ function GenrePlaylistTreePerRoot({
|
|
|
73
104
|
additionalActions,
|
|
74
105
|
onNodeClick,
|
|
75
106
|
onNodeHover,
|
|
76
|
-
renderInfoPanelBody,
|
|
77
107
|
selectedNodeId,
|
|
78
108
|
onSelectedNodeChange,
|
|
79
|
-
hideInfoPanelClose,
|
|
80
|
-
hideInfoPanelTitle,
|
|
81
109
|
readOnly = false,
|
|
82
|
-
showToolbar,
|
|
83
110
|
wheelZoom,
|
|
84
111
|
defaultViewport,
|
|
85
112
|
viewportRestore,
|
|
86
113
|
onViewportChange,
|
|
87
114
|
playback,
|
|
88
|
-
...
|
|
115
|
+
...hostPanel
|
|
89
116
|
}) {
|
|
90
117
|
const { isPlaying, setIsPlaying } = usePlayer();
|
|
91
118
|
const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists: genrePlaylistTreePerRoot });
|
|
92
119
|
const { playingNodeId, play } = playback ?? defaultPlayback;
|
|
93
|
-
const trackNoun = useTrackNoun();
|
|
94
120
|
const { showPopup } = usePopup();
|
|
95
121
|
const nodes = useMemo2(
|
|
96
122
|
() => genrePlaylistTreePerRoot.map((genrePlaylist) => ({
|
|
@@ -155,7 +181,6 @@ function GenrePlaylistTreePerRoot({
|
|
|
155
181
|
{
|
|
156
182
|
className: className2,
|
|
157
183
|
nodes,
|
|
158
|
-
itemCountLabel: `${capitalize(trackNoun.singular)} count`,
|
|
159
184
|
rootColor: getGenreTreeColor(rootUuid),
|
|
160
185
|
playingNodeId,
|
|
161
186
|
playState: isPlaying ? "playing" : "paused",
|
|
@@ -166,18 +191,13 @@ function GenrePlaylistTreePerRoot({
|
|
|
166
191
|
additionalActions,
|
|
167
192
|
onNodeClick,
|
|
168
193
|
onNodeHover,
|
|
169
|
-
renderInfoPanelBody,
|
|
170
194
|
selectedNodeId,
|
|
171
195
|
onSelectedNodeChange,
|
|
172
|
-
hideInfoPanelClose,
|
|
173
|
-
hideInfoPanelTitle,
|
|
174
|
-
showToolbar,
|
|
175
196
|
wheelZoom,
|
|
176
197
|
defaultViewport,
|
|
177
198
|
viewportRestore,
|
|
178
199
|
onViewportChange,
|
|
179
|
-
...
|
|
180
|
-
readOnly
|
|
200
|
+
...hostPanel
|
|
181
201
|
}
|
|
182
202
|
);
|
|
183
203
|
}
|
|
@@ -190,19 +210,6 @@ import {
|
|
|
190
210
|
import { usePlayer as usePlayer2 } from "@behindthemusictree/app-kit/player";
|
|
191
211
|
import { usePopup as usePopup2 } from "@behindthemusictree/app-kit/popup";
|
|
192
212
|
import { ErrorCode as ErrorCode2 } from "@behindthemusictree/app-kit/transport";
|
|
193
|
-
|
|
194
|
-
// src/genre-tree/playlist-tree/size-metric.ts
|
|
195
|
-
var hasViewsCount = (genrePlaylists) => genrePlaylists.every((genrePlaylist) => genrePlaylist.viewsCount !== void 0);
|
|
196
|
-
var genrePlaylistSize = (genrePlaylist, metric) => {
|
|
197
|
-
if (metric === "songs") return genrePlaylist.tracksCount;
|
|
198
|
-
if (genrePlaylist.viewsCount === void 0) {
|
|
199
|
-
throw new Error(`Genre playlist ${genrePlaylist.uuid} has no viewsCount to size by views`);
|
|
200
|
-
}
|
|
201
|
-
return genrePlaylist.viewsCount;
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
// src/genre-tree/playlist-tree/TreeWheel.tsx
|
|
205
|
-
import { capitalize as capitalize2, useTrackNoun as useTrackNoun2 } from "@behindthemusictree/app-kit/track-noun";
|
|
206
213
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
207
214
|
function GenrePlaylistTreeWheel({
|
|
208
215
|
scope,
|
|
@@ -213,14 +220,10 @@ function GenrePlaylistTreeWheel({
|
|
|
213
220
|
additionalActions,
|
|
214
221
|
onNodeClick,
|
|
215
222
|
onNodeHover,
|
|
216
|
-
renderInfoPanelBody,
|
|
217
223
|
selectedNodeId,
|
|
218
224
|
onSelectedNodeChange,
|
|
219
|
-
hideInfoPanelClose,
|
|
220
|
-
hideInfoPanelTitle,
|
|
221
225
|
readOnly = false,
|
|
222
226
|
allowWheelRotation,
|
|
223
|
-
showToolbar,
|
|
224
227
|
sizeMetric = "songs",
|
|
225
228
|
floatingControlsLeading,
|
|
226
229
|
busy,
|
|
@@ -228,12 +231,11 @@ function GenrePlaylistTreeWheel({
|
|
|
228
231
|
viewportRestore,
|
|
229
232
|
onViewportChange,
|
|
230
233
|
playback,
|
|
231
|
-
...
|
|
234
|
+
...hostPanel
|
|
232
235
|
}) {
|
|
233
236
|
const { isPlaying, setIsPlaying } = usePlayer2();
|
|
234
237
|
const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists });
|
|
235
238
|
const { playingNodeId, play } = playback ?? defaultPlayback;
|
|
236
|
-
const trackNoun = useTrackNoun2();
|
|
237
239
|
const { showPopup } = usePopup2();
|
|
238
240
|
const nodes = useMemo3(
|
|
239
241
|
() => genrePlaylists.map((genrePlaylist) => ({
|
|
@@ -297,7 +299,6 @@ function GenrePlaylistTreeWheel({
|
|
|
297
299
|
{
|
|
298
300
|
className: className2,
|
|
299
301
|
nodes,
|
|
300
|
-
itemCountLabel: sizeMetric === "views" ? "Views" : `${capitalize2(trackNoun.singular)} count`,
|
|
301
302
|
playingNodeId,
|
|
302
303
|
playState: isPlaying ? "playing" : "paused",
|
|
303
304
|
onPlayPause: handlePlayPause,
|
|
@@ -307,20 +308,15 @@ function GenrePlaylistTreeWheel({
|
|
|
307
308
|
additionalActions,
|
|
308
309
|
onNodeClick,
|
|
309
310
|
onNodeHover,
|
|
310
|
-
renderInfoPanelBody,
|
|
311
311
|
selectedNodeId,
|
|
312
312
|
onSelectedNodeChange,
|
|
313
|
-
hideInfoPanelClose,
|
|
314
|
-
hideInfoPanelTitle,
|
|
315
313
|
allowWheelRotation,
|
|
316
|
-
showToolbar,
|
|
317
314
|
floatingControlsLeading,
|
|
318
315
|
busy,
|
|
319
316
|
defaultViewport,
|
|
320
317
|
viewportRestore,
|
|
321
318
|
onViewportChange,
|
|
322
|
-
...
|
|
323
|
-
readOnly
|
|
319
|
+
...hostPanel
|
|
324
320
|
}
|
|
325
321
|
);
|
|
326
322
|
}
|
|
@@ -332,7 +328,6 @@ import {
|
|
|
332
328
|
GenreTreeWheelRadialPopCore
|
|
333
329
|
} from "@behindthemusictree/genre-tree-view";
|
|
334
330
|
import { usePlayer as usePlayer3 } from "@behindthemusictree/app-kit/player";
|
|
335
|
-
import { capitalize as capitalize3, useTrackNoun as useTrackNoun3 } from "@behindthemusictree/app-kit/track-noun";
|
|
336
331
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
337
332
|
function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
338
333
|
const {
|
|
@@ -344,14 +339,10 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
344
339
|
additionalActions,
|
|
345
340
|
onNodeClick,
|
|
346
341
|
onNodeHover,
|
|
347
|
-
renderInfoPanelBody,
|
|
348
342
|
selectedNodeId,
|
|
349
343
|
onSelectedNodeChange,
|
|
350
|
-
hideInfoPanelClose,
|
|
351
|
-
hideInfoPanelTitle,
|
|
352
344
|
readOnly = false,
|
|
353
345
|
allowWheelRotation,
|
|
354
|
-
showToolbar,
|
|
355
346
|
sizeMetric = "songs",
|
|
356
347
|
floatingControlsLeading,
|
|
357
348
|
busy,
|
|
@@ -362,33 +353,14 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
362
353
|
playback
|
|
363
354
|
} = props;
|
|
364
355
|
const focus = props;
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
onExpandedChange: props.onExpandedChange,
|
|
369
|
-
collapsed: props.collapsed,
|
|
370
|
-
hideExpandButton: props.hideExpandButton,
|
|
371
|
-
overlayPanel: props.overlayPanel
|
|
356
|
+
const hostPanel = {
|
|
357
|
+
obscuredInset: props.obscuredInset,
|
|
358
|
+
onNodeStyleResolverChange: props.onNodeStyleResolverChange
|
|
372
359
|
};
|
|
373
360
|
const { isPlaying, setIsPlaying } = usePlayer3();
|
|
374
361
|
const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists });
|
|
375
362
|
const { playingNodeId, play } = playback ?? defaultPlayback;
|
|
376
|
-
const
|
|
377
|
-
const nodes = useMemo4(
|
|
378
|
-
() => genrePlaylists.map((genrePlaylist) => ({
|
|
379
|
-
id: genrePlaylist.uuid,
|
|
380
|
-
parentId: genrePlaylist.parent?.uuid ?? null,
|
|
381
|
-
name: genrePlaylist.name,
|
|
382
|
-
itemCount: genrePlaylistSize(genrePlaylist, sizeMetric),
|
|
383
|
-
actionable: Boolean(genrePlaylist.criteria),
|
|
384
|
-
detached: !genrePlaylist.criteria,
|
|
385
|
-
side: genrePlaylist.criteria?.side ?? void 0,
|
|
386
|
-
wikidataId: genrePlaylist.criteria?.wikidataId ?? null,
|
|
387
|
-
umbrella: genrePlaylist.isUmbrella,
|
|
388
|
-
scene: genrePlaylist.scene ?? void 0
|
|
389
|
-
})),
|
|
390
|
-
[genrePlaylists, sizeMetric]
|
|
391
|
-
);
|
|
363
|
+
const nodes = useMemo4(() => toTreeNodes(genrePlaylists, sizeMetric), [genrePlaylists, sizeMetric]);
|
|
392
364
|
const handlePlayPause = useCallback4(
|
|
393
365
|
(nodeId) => {
|
|
394
366
|
const genrePlaylist = genrePlaylists.find((g) => g.uuid === nodeId);
|
|
@@ -429,7 +401,6 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
429
401
|
const treeProps = {
|
|
430
402
|
className: className2,
|
|
431
403
|
nodes,
|
|
432
|
-
itemCountLabel: sizeMetric === "views" ? "Views" : `${capitalize3(trackNoun.singular)} count`,
|
|
433
404
|
playingNodeId,
|
|
434
405
|
playState: isPlaying ? "playing" : "paused",
|
|
435
406
|
onPlayPause: handlePlayPause,
|
|
@@ -439,14 +410,9 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
439
410
|
additionalActions,
|
|
440
411
|
onNodeClick,
|
|
441
412
|
onNodeHover,
|
|
442
|
-
renderInfoPanelBody,
|
|
443
413
|
selectedNodeId,
|
|
444
414
|
onSelectedNodeChange,
|
|
445
|
-
|
|
446
|
-
hideInfoPanelTitle,
|
|
447
|
-
showToolbar,
|
|
448
|
-
...infoPanelCuration,
|
|
449
|
-
readOnly
|
|
415
|
+
...hostPanel
|
|
450
416
|
};
|
|
451
417
|
return outline ? /* @__PURE__ */ jsx3(GenreTreeOutline, { ...treeProps }) : /* @__PURE__ */ jsx3(
|
|
452
418
|
GenreTreeWheelRadialPopCore,
|
|
@@ -499,10 +465,11 @@ function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
|
|
|
499
465
|
}
|
|
500
466
|
|
|
501
467
|
// src/genre-tree/GenreSearch.tsx
|
|
468
|
+
import { useState as useState2 } from "react";
|
|
502
469
|
import { ArrowLeft, LoaderCircle, Search, X } from "lucide-react";
|
|
503
|
-
import {
|
|
470
|
+
import { Combobox } from "@behindthemusictree/ui";
|
|
504
471
|
import { useGenreSearch } from "@behindthemusictree/app-kit/genre-tree";
|
|
505
|
-
import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
472
|
+
import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
506
473
|
function GenreSearch({
|
|
507
474
|
genrePlaylists,
|
|
508
475
|
onSelect,
|
|
@@ -511,7 +478,7 @@ function GenreSearch({
|
|
|
511
478
|
onPrevious,
|
|
512
479
|
placeholder,
|
|
513
480
|
onQueryChange,
|
|
514
|
-
|
|
481
|
+
extraGroups = []
|
|
515
482
|
}) {
|
|
516
483
|
const { query, setQuery, results, canClear, clear, select } = useGenreSearch({
|
|
517
484
|
genrePlaylists,
|
|
@@ -519,11 +486,45 @@ function GenreSearch({
|
|
|
519
486
|
selectedName,
|
|
520
487
|
onClear
|
|
521
488
|
});
|
|
522
|
-
const
|
|
523
|
-
const
|
|
524
|
-
const
|
|
525
|
-
const
|
|
526
|
-
const
|
|
489
|
+
const [open, setOpen] = useState2(false);
|
|
490
|
+
const active = query !== "" && query !== selectedName;
|
|
491
|
+
const hasExtras = extraGroups.length > 0;
|
|
492
|
+
const label = hasExtras ? "Search" : "Search a genre";
|
|
493
|
+
const loading = active && extraGroups.some((group) => group.loading);
|
|
494
|
+
const status = active ? extraGroups.map((group) => group.status).filter(Boolean).join(". ") : "";
|
|
495
|
+
const groups = [
|
|
496
|
+
{
|
|
497
|
+
key: "genres",
|
|
498
|
+
label: hasExtras ? "Genres" : void 0,
|
|
499
|
+
options: results.map((genrePlaylist) => ({
|
|
500
|
+
key: genrePlaylist.uuid,
|
|
501
|
+
content: /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
502
|
+
genrePlaylist.name,
|
|
503
|
+
genrePlaylist.isUmbrella && /* @__PURE__ */ jsx5("span", { className: "ml-2 text-xs italic text-[var(--color-text-muted)]", children: "umbrella" })
|
|
504
|
+
] })
|
|
505
|
+
}))
|
|
506
|
+
},
|
|
507
|
+
...extraGroups.map((group) => ({
|
|
508
|
+
key: group.key,
|
|
509
|
+
label: group.label,
|
|
510
|
+
options: active ? group.items.map((item) => ({ key: item.key, content: item.content })) : []
|
|
511
|
+
}))
|
|
512
|
+
];
|
|
513
|
+
const handlePick = (groupKey, optionKey) => {
|
|
514
|
+
setOpen(false);
|
|
515
|
+
if (groupKey === "genres") {
|
|
516
|
+
const genrePlaylist = results.find((result) => result.uuid === optionKey);
|
|
517
|
+
if (!genrePlaylist) return;
|
|
518
|
+
select(genrePlaylist);
|
|
519
|
+
onQueryChange?.("");
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
const item = extraGroups.find((group) => group.key === groupKey)?.items.find((entry) => entry.key === optionKey);
|
|
523
|
+
if (!item) return;
|
|
524
|
+
setQuery("");
|
|
525
|
+
onQueryChange?.("");
|
|
526
|
+
item.onSelect();
|
|
527
|
+
};
|
|
527
528
|
return /* @__PURE__ */ jsxs2("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: [
|
|
528
529
|
onPrevious && /* @__PURE__ */ jsx5(
|
|
529
530
|
"button",
|
|
@@ -531,53 +532,51 @@ function GenreSearch({
|
|
|
531
532
|
type: "button",
|
|
532
533
|
"aria-label": "Previous",
|
|
533
534
|
title: "Previous",
|
|
534
|
-
className: "genre-search-previous absolute left-2 top-1/2 z-10
|
|
535
|
+
className: "genre-search-previous absolute left-2 top-1/2 z-10 flex 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)]",
|
|
535
536
|
onClick: onPrevious,
|
|
536
537
|
children: /* @__PURE__ */ jsx5(ArrowLeft, { "aria-hidden": "true", className: "w-4 h-4" })
|
|
537
538
|
}
|
|
538
539
|
),
|
|
539
540
|
/* @__PURE__ */ jsx5(
|
|
540
|
-
|
|
541
|
+
Combobox,
|
|
541
542
|
{
|
|
542
|
-
|
|
543
|
+
ariaLabel: label,
|
|
544
|
+
listLabel: `${label} results`,
|
|
543
545
|
value: query,
|
|
544
|
-
onChange: (event) => {
|
|
545
|
-
setQuery(event.target.value);
|
|
546
|
-
onQueryChange?.(event.target.value);
|
|
547
|
-
},
|
|
548
546
|
placeholder: placeholder ?? `${label}\u2026`,
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
547
|
+
groups,
|
|
548
|
+
open,
|
|
549
|
+
onOpenChange: setOpen,
|
|
550
|
+
onValueChange: (value) => {
|
|
551
|
+
setQuery(value);
|
|
552
|
+
onQueryChange?.(value);
|
|
553
|
+
},
|
|
554
|
+
onPick: handlePick,
|
|
555
|
+
status,
|
|
556
|
+
loading: loading ? "Searching\u2026" : void 0,
|
|
557
|
+
inputClassName: `h-[var(--layout-search-h,3rem)] ${onPrevious ? "pl-11" : "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`,
|
|
558
|
+
className: "genre-search-results z-40 rounded-xl shadow-md",
|
|
559
|
+
listClassName: "max-h-[min(20rem,50dvh)]",
|
|
560
|
+
optionClassName: "[@media(pointer:coarse)]:min-h-[var(--layout-touch-target)]"
|
|
555
561
|
}
|
|
556
562
|
),
|
|
557
563
|
/* @__PURE__ */ jsxs2("div", { className: "absolute right-3 top-1/2 flex items-center -translate-y-1/2", children: [
|
|
558
|
-
/* @__PURE__ */ jsxs2(
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
"aria-hidden": "true",
|
|
575
|
-
className: `absolute inset-2 w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? "opacity-100 delay-300 animate-spin motion-reduce:animate-none" : "opacity-0"}`
|
|
576
|
-
}
|
|
577
|
-
)
|
|
578
|
-
]
|
|
579
|
-
}
|
|
580
|
-
),
|
|
564
|
+
/* @__PURE__ */ jsxs2("span", { "data-loading": loading, className: "relative p-2 text-[var(--color-text-muted)] pointer-events-none", children: [
|
|
565
|
+
/* @__PURE__ */ jsx5(
|
|
566
|
+
Search,
|
|
567
|
+
{
|
|
568
|
+
"aria-hidden": "true",
|
|
569
|
+
className: `w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? "opacity-0" : ""}`
|
|
570
|
+
}
|
|
571
|
+
),
|
|
572
|
+
/* @__PURE__ */ jsx5(
|
|
573
|
+
LoaderCircle,
|
|
574
|
+
{
|
|
575
|
+
"aria-hidden": "true",
|
|
576
|
+
className: `absolute inset-2 w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? "opacity-100 delay-300 animate-spin motion-reduce:animate-none" : "opacity-0"}`
|
|
577
|
+
}
|
|
578
|
+
)
|
|
579
|
+
] }),
|
|
581
580
|
canClear && /* @__PURE__ */ jsx5(
|
|
582
581
|
"button",
|
|
583
582
|
{
|
|
@@ -591,39 +590,6 @@ function GenreSearch({
|
|
|
591
590
|
children: /* @__PURE__ */ jsx5(X, { "aria-hidden": "true", className: "w-6 h-6" })
|
|
592
591
|
}
|
|
593
592
|
)
|
|
594
|
-
] }),
|
|
595
|
-
extraResults?.status && /* @__PURE__ */ jsx5("p", { role: "status", "aria-live": "polite", className: "sr-only", children: extraResults.status }),
|
|
596
|
-
showList && /* @__PURE__ */ jsxs2("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: [
|
|
597
|
-
extraResults && results.length > 0 && /* @__PURE__ */ jsx5("li", { className: heading, children: "Genres" }),
|
|
598
|
-
results.map((genrePlaylist) => /* @__PURE__ */ jsx5("li", { children: /* @__PURE__ */ jsxs2(
|
|
599
|
-
"button",
|
|
600
|
-
{
|
|
601
|
-
type: "button",
|
|
602
|
-
className: "w-full px-3 py-1.5 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] text-left hover:bg-[var(--color-surface-muted)]",
|
|
603
|
-
onClick: () => {
|
|
604
|
-
select(genrePlaylist);
|
|
605
|
-
onQueryChange?.("");
|
|
606
|
-
},
|
|
607
|
-
children: [
|
|
608
|
-
genrePlaylist.name,
|
|
609
|
-
genrePlaylist.isUmbrella && /* @__PURE__ */ jsx5("span", { className: "ml-2 text-xs italic text-[var(--color-text-muted)]", children: "umbrella" })
|
|
610
|
-
]
|
|
611
|
-
}
|
|
612
|
-
) }, genrePlaylist.uuid)),
|
|
613
|
-
extraResults && extraItems.length > 0 && /* @__PURE__ */ jsx5("li", { className: heading, children: extraResults.label }),
|
|
614
|
-
extraItems.map((item) => /* @__PURE__ */ jsx5("li", { children: /* @__PURE__ */ jsx5(
|
|
615
|
-
"button",
|
|
616
|
-
{
|
|
617
|
-
type: "button",
|
|
618
|
-
className: "w-full px-3 py-1.5 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] text-left hover:bg-[var(--color-surface-muted)]",
|
|
619
|
-
onClick: () => {
|
|
620
|
-
setQuery("");
|
|
621
|
-
onQueryChange?.("");
|
|
622
|
-
item.onSelect();
|
|
623
|
-
},
|
|
624
|
-
children: item.content
|
|
625
|
-
}
|
|
626
|
-
) }, item.key))
|
|
627
593
|
] })
|
|
628
594
|
] });
|
|
629
595
|
}
|
|
@@ -639,16 +605,16 @@ import { Share2 as Share22 } from "lucide-react";
|
|
|
639
605
|
import { usePopup as usePopup3 } from "@behindthemusictree/app-kit/popup";
|
|
640
606
|
|
|
641
607
|
// src/genre-tree/SharePopup.tsx
|
|
642
|
-
import { useCallback as useCallback5, useEffect as useEffect2, useMemo as useMemo5, useState as
|
|
608
|
+
import { useCallback as useCallback5, useEffect as useEffect2, useMemo as useMemo5, useState as useState3 } from "react";
|
|
643
609
|
import { Link2, Share2 } from "lucide-react";
|
|
644
610
|
import { FaWhatsapp } from "react-icons/fa";
|
|
645
611
|
import { SiGmail } from "react-icons/si";
|
|
646
612
|
|
|
647
613
|
// src/genre-tree/ShareDestinationButton.tsx
|
|
648
|
-
import { Fragment, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
614
|
+
import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
649
615
|
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";
|
|
650
616
|
function Contents({ icon, label }) {
|
|
651
|
-
return /* @__PURE__ */ jsxs3(
|
|
617
|
+
return /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
652
618
|
/* @__PURE__ */ jsx6("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 }),
|
|
653
619
|
/* @__PURE__ */ jsx6("span", { className: "max-w-full truncate", children: label })
|
|
654
620
|
] });
|
|
@@ -668,11 +634,11 @@ function ShareDestinationButton({
|
|
|
668
634
|
}
|
|
669
635
|
|
|
670
636
|
// src/genre-tree/SharePreviewCard.tsx
|
|
671
|
-
import { Music2, UserRound } from "lucide-react";
|
|
637
|
+
import { Music, Music2, UserRound } from "lucide-react";
|
|
672
638
|
import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
673
639
|
function SharePreviewCard({ target, url }) {
|
|
674
|
-
const Icon =
|
|
675
|
-
const typeLabel =
|
|
640
|
+
const Icon = { genre: Music2, artist: UserRound, song: Music }[target.kind];
|
|
641
|
+
const typeLabel = { genre: "Genre", artist: "Artist", song: "Song" }[target.kind];
|
|
676
642
|
return /* @__PURE__ */ jsx7("div", { className: "rounded-3xl bg-[var(--color-surface-muted)] p-4", children: /* @__PURE__ */ jsxs4("div", { className: "flex min-h-32 overflow-hidden rounded-2xl bg-[var(--color-surface)] shadow-lg", children: [
|
|
677
643
|
/* @__PURE__ */ jsx7("div", { className: "flex w-28 shrink-0 items-center justify-center bg-[var(--color-primary)]/15 text-[var(--color-primary)]", children: /* @__PURE__ */ jsx7(Icon, { "aria-hidden": "true", className: "h-10 w-10" }) }),
|
|
678
644
|
/* @__PURE__ */ jsxs4("div", { className: "flex min-w-0 flex-1 flex-col justify-center gap-1 p-4", children: [
|
|
@@ -684,7 +650,7 @@ function SharePreviewCard({ target, url }) {
|
|
|
684
650
|
}
|
|
685
651
|
|
|
686
652
|
// src/genre-tree/SharePopup.tsx
|
|
687
|
-
import { Fragment as
|
|
653
|
+
import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
688
654
|
function resolveAbsoluteUrl(url) {
|
|
689
655
|
try {
|
|
690
656
|
const parsed = new URL(url, window.location.href);
|
|
@@ -717,11 +683,11 @@ async function copyText(text) {
|
|
|
717
683
|
}
|
|
718
684
|
}
|
|
719
685
|
function SharePopup({ target, url, onClose }) {
|
|
720
|
-
const [absoluteUrl, setAbsoluteUrl] =
|
|
721
|
-
const [copyStatus, setCopyStatus] =
|
|
722
|
-
const [canUseNativeShare, setCanUseNativeShare] =
|
|
723
|
-
const [shareError, setShareError] =
|
|
724
|
-
const [toastId, setToastId] =
|
|
686
|
+
const [absoluteUrl, setAbsoluteUrl] = useState3(url);
|
|
687
|
+
const [copyStatus, setCopyStatus] = useState3("idle");
|
|
688
|
+
const [canUseNativeShare, setCanUseNativeShare] = useState3(false);
|
|
689
|
+
const [shareError, setShareError] = useState3(null);
|
|
690
|
+
const [toastId, setToastId] = useState3(0);
|
|
725
691
|
useEffect2(() => {
|
|
726
692
|
setAbsoluteUrl(resolveAbsoluteUrl(url) ?? "");
|
|
727
693
|
setCopyStatus("idle");
|
|
@@ -759,7 +725,7 @@ function SharePopup({ target, url, onClose }) {
|
|
|
759
725
|
setShareError("Could not open the share options.");
|
|
760
726
|
}
|
|
761
727
|
};
|
|
762
|
-
return /* @__PURE__ */ jsxs5(
|
|
728
|
+
return /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
763
729
|
/* @__PURE__ */ jsx8(
|
|
764
730
|
BasePopup_default,
|
|
765
731
|
{
|
|
@@ -954,18 +920,18 @@ import { Skeleton as Skeleton2 } from "@behindthemusictree/ui";
|
|
|
954
920
|
import { useFetchGenreOverview } from "@behindthemusictree/app-kit/genre-tree";
|
|
955
921
|
|
|
956
922
|
// src/genre-tree/ClampedText.tsx
|
|
957
|
-
import { useLayoutEffect, useRef as useRef2, useState as
|
|
958
|
-
import { Fragment as
|
|
923
|
+
import { useLayoutEffect, useRef as useRef2, useState as useState4 } from "react";
|
|
924
|
+
import { Fragment as Fragment4, jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
959
925
|
function ClampedText({ children, className: className2 = "" }) {
|
|
960
926
|
const ref = useRef2(null);
|
|
961
|
-
const [expanded, setExpanded] =
|
|
962
|
-
const [overflows, setOverflows] =
|
|
927
|
+
const [expanded, setExpanded] = useState4(false);
|
|
928
|
+
const [overflows, setOverflows] = useState4(false);
|
|
963
929
|
useLayoutEffect(() => {
|
|
964
930
|
const element = ref.current;
|
|
965
931
|
if (!element || expanded) return;
|
|
966
932
|
setOverflows(element.scrollHeight > element.clientHeight);
|
|
967
933
|
}, [children, expanded]);
|
|
968
|
-
return /* @__PURE__ */ jsxs8(
|
|
934
|
+
return /* @__PURE__ */ jsxs8(Fragment4, { children: [
|
|
969
935
|
/* @__PURE__ */ jsx11("p", { ref, className: `${className2} ${expanded ? "" : "line-clamp-3"}`, children }),
|
|
970
936
|
(overflows || expanded) && /* @__PURE__ */ jsx11(
|
|
971
937
|
"button",
|
|
@@ -1001,14 +967,14 @@ import { useTrackList as useTrackList2 } from "@behindthemusictree/app-kit/genre
|
|
|
1001
967
|
|
|
1002
968
|
// src/genre-tree/MusicBrainzRecordingLink.tsx
|
|
1003
969
|
import { musicBrainzRecordingUrl } from "@behindthemusictree/app-kit/genre-tree";
|
|
1004
|
-
import { Fragment as
|
|
970
|
+
import { Fragment as Fragment5, jsx as jsx12 } from "react/jsx-runtime";
|
|
1005
971
|
function MusicBrainzRecordingLink({
|
|
1006
972
|
song,
|
|
1007
973
|
children,
|
|
1008
974
|
className: className2 = ""
|
|
1009
975
|
}) {
|
|
1010
976
|
const url = musicBrainzRecordingUrl(song);
|
|
1011
|
-
if (!url) return /* @__PURE__ */ jsx12(
|
|
977
|
+
if (!url) return /* @__PURE__ */ jsx12(Fragment5, { children });
|
|
1012
978
|
return /* @__PURE__ */ jsx12(
|
|
1013
979
|
"a",
|
|
1014
980
|
{
|
|
@@ -1025,7 +991,7 @@ function MusicBrainzRecordingLink({
|
|
|
1025
991
|
|
|
1026
992
|
// src/genre-tree/GenrePlaylistTracks.tsx
|
|
1027
993
|
import { ArtistNames } from "@behindthemusictree/app-kit/genre-tree";
|
|
1028
|
-
import { capitalize
|
|
994
|
+
import { capitalize, useTrackNoun } from "@behindthemusictree/app-kit/track-noun";
|
|
1029
995
|
import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1030
996
|
var CATALOGUE_PAGE_SIZE = 100;
|
|
1031
997
|
var SongPageSchema = PaginatedResponseSchema(SongDetailedSchema).extend({
|
|
@@ -1049,6 +1015,7 @@ function GenrePlaylistTracks({
|
|
|
1049
1015
|
scope,
|
|
1050
1016
|
getBackendBaseUrl,
|
|
1051
1017
|
renderArtistLink,
|
|
1018
|
+
renderSongLink,
|
|
1052
1019
|
renderSongLeading,
|
|
1053
1020
|
renderSongActions,
|
|
1054
1021
|
renderTracksToolbar,
|
|
@@ -1058,7 +1025,7 @@ function GenrePlaylistTracks({
|
|
|
1058
1025
|
const { session, sessionRestored } = useSession();
|
|
1059
1026
|
const { playerTrackObject, playState, handlePlayPauseAction } = usePlayer4();
|
|
1060
1027
|
const { showPopup } = usePopup4();
|
|
1061
|
-
const trackNoun =
|
|
1028
|
+
const trackNoun = useTrackNoun();
|
|
1062
1029
|
const { uuid } = genrePlaylist;
|
|
1063
1030
|
const { fetch } = useFetchWrapper(getBackendBaseUrl);
|
|
1064
1031
|
const { data, isPending, isError, refetch, hasNextPage, isFetchingNextPage, fetchNextPage } = useInfiniteQuery({
|
|
@@ -1103,7 +1070,7 @@ function GenrePlaylistTracks({
|
|
|
1103
1070
|
};
|
|
1104
1071
|
return /* @__PURE__ */ jsxs9("div", { className: "gtv-info-panel-children", children: [
|
|
1105
1072
|
/* @__PURE__ */ jsxs9("span", { className: "gtv-info-panel-children-title", children: [
|
|
1106
|
-
|
|
1073
|
+
capitalize(trackNoun.plural),
|
|
1107
1074
|
total !== void 0 && ` (${total})`
|
|
1108
1075
|
] }),
|
|
1109
1076
|
renderTracksToolbar?.(),
|
|
@@ -1119,7 +1086,7 @@ function GenrePlaylistTracks({
|
|
|
1119
1086
|
const isCurrent = playerTrackObject?.track.id === track.uuid;
|
|
1120
1087
|
const isPlaying = isCurrent && playState === PlayStates.PLAYING;
|
|
1121
1088
|
const text = /* @__PURE__ */ jsxs9("span", { className: TEXT_CELL, children: [
|
|
1122
|
-
/* @__PURE__ */ jsx13("span", { className: TITLE_CELL, children: isSong(track) ? /* @__PURE__ */ jsx13(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
|
|
1089
|
+
/* @__PURE__ */ jsx13("span", { className: TITLE_CELL, children: renderSongLink ? renderSongLink(track) : isSong(track) ? /* @__PURE__ */ jsx13(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
|
|
1123
1090
|
track.artists?.length ? /* @__PURE__ */ jsx13("span", { className: SEPARATOR, children: " \u2014 " }) : null,
|
|
1124
1091
|
/* @__PURE__ */ jsx13("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ jsx13(ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
|
|
1125
1092
|
track.album && /* @__PURE__ */ jsx13("span", { className: ALBUM_CELL, children: track.album.name })
|
|
@@ -1174,9 +1141,9 @@ function GenrePlaylistTracks({
|
|
|
1174
1141
|
}
|
|
1175
1142
|
|
|
1176
1143
|
// src/genre-tree/GenreDetailExtras.tsx
|
|
1177
|
-
import { useTrackNoun as
|
|
1144
|
+
import { useTrackNoun as useTrackNoun2 } from "@behindthemusictree/app-kit/track-noun";
|
|
1178
1145
|
import { SiWikidata, SiWikipedia } from "react-icons/si";
|
|
1179
|
-
import { Fragment as
|
|
1146
|
+
import { Fragment as Fragment6, jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1180
1147
|
function GenreDetailExtras({
|
|
1181
1148
|
genreUuid,
|
|
1182
1149
|
genrePlaylist,
|
|
@@ -1186,12 +1153,13 @@ function GenreDetailExtras({
|
|
|
1186
1153
|
criteriaOverviewSchema,
|
|
1187
1154
|
renderGenreDetailExtras,
|
|
1188
1155
|
renderArtistLink,
|
|
1156
|
+
renderSongLink,
|
|
1189
1157
|
renderSummary,
|
|
1190
1158
|
renderEssentialTracks,
|
|
1191
1159
|
family,
|
|
1192
1160
|
...trackSlots
|
|
1193
1161
|
}) {
|
|
1194
|
-
const trackNoun =
|
|
1162
|
+
const trackNoun = useTrackNoun2();
|
|
1195
1163
|
const { data: overview, isPending } = useFetchGenreOverview(
|
|
1196
1164
|
genreUuid,
|
|
1197
1165
|
scope,
|
|
@@ -1199,7 +1167,7 @@ function GenreDetailExtras({
|
|
|
1199
1167
|
criteriaOverviewSchema
|
|
1200
1168
|
);
|
|
1201
1169
|
if (isPending) {
|
|
1202
|
-
return /* @__PURE__ */ jsxs10(
|
|
1170
|
+
return /* @__PURE__ */ jsxs10(Fragment6, { children: [
|
|
1203
1171
|
/* @__PURE__ */ jsxs10("div", { className: "gtv-info-panel-children", "aria-busy": "true", "data-testid": "genre-detail-extras-skeleton", children: [
|
|
1204
1172
|
/* @__PURE__ */ jsx14("span", { className: "gtv-info-panel-children-title", children: "Summary" }),
|
|
1205
1173
|
/* @__PURE__ */ jsx14(Skeleton2, { className: "h-4 w-3/4" })
|
|
@@ -1207,10 +1175,10 @@ function GenreDetailExtras({
|
|
|
1207
1175
|
family
|
|
1208
1176
|
] });
|
|
1209
1177
|
}
|
|
1210
|
-
if (!overview) return /* @__PURE__ */ jsx14(
|
|
1178
|
+
if (!overview) return /* @__PURE__ */ jsx14(Fragment6, { children: family });
|
|
1211
1179
|
const { summary, summarySourceUrl, essentialSongs } = overview;
|
|
1212
1180
|
const wikidataUrl = wikidataId ? `https://www.wikidata.org/wiki/${encodeURIComponent(wikidataId)}` : null;
|
|
1213
|
-
return /* @__PURE__ */ jsxs10(
|
|
1181
|
+
return /* @__PURE__ */ jsxs10(Fragment6, { children: [
|
|
1214
1182
|
(summarySourceUrl || wikidataUrl) && /* @__PURE__ */ jsxs10("div", { className: "gtv-info-panel-children text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: [
|
|
1215
1183
|
/* @__PURE__ */ jsx14("span", { className: "gtv-info-panel-children-title", children: "Links" }),
|
|
1216
1184
|
/* @__PURE__ */ jsxs10("div", { className: "mt-1 flex flex-wrap gap-x-3 gap-y-1 text-xs", children: [
|
|
@@ -1263,6 +1231,7 @@ function GenreDetailExtras({
|
|
|
1263
1231
|
scope,
|
|
1264
1232
|
getBackendBaseUrl,
|
|
1265
1233
|
renderArtistLink,
|
|
1234
|
+
renderSongLink,
|
|
1266
1235
|
...trackSlots
|
|
1267
1236
|
}
|
|
1268
1237
|
)
|
|
@@ -1270,7 +1239,7 @@ function GenreDetailExtras({
|
|
|
1270
1239
|
}
|
|
1271
1240
|
|
|
1272
1241
|
// src/genre-tree/GenreInfoPanelBody.tsx
|
|
1273
|
-
import { Fragment as
|
|
1242
|
+
import { Fragment as Fragment7, jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1274
1243
|
import { createElement } from "react";
|
|
1275
1244
|
function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
|
|
1276
1245
|
const { isPlaying, setIsPlaying } = usePlayer5();
|
|
@@ -1305,7 +1274,7 @@ function GenreInfoPanelBody({
|
|
|
1305
1274
|
band,
|
|
1306
1275
|
extras
|
|
1307
1276
|
}) {
|
|
1308
|
-
return /* @__PURE__ */ jsxs11(
|
|
1277
|
+
return /* @__PURE__ */ jsxs11(Fragment7, { children: [
|
|
1309
1278
|
toolbar,
|
|
1310
1279
|
/* @__PURE__ */ jsx15(GenreNodeActionBand, { ...band, genrePlaylist, playback }),
|
|
1311
1280
|
/* @__PURE__ */ createElement(
|
|
@@ -1324,69 +1293,13 @@ function GenreInfoPanelBody({
|
|
|
1324
1293
|
] });
|
|
1325
1294
|
}
|
|
1326
1295
|
|
|
1327
|
-
// src/genre-tree/PanelEdgeTab.tsx
|
|
1328
|
-
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
1329
|
-
import { twMerge } from "tailwind-merge";
|
|
1330
|
-
import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1331
|
-
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)]";
|
|
1332
|
-
function PanelEdgeTab({ side, edge, center, controls, className: className2 }) {
|
|
1333
|
-
if (!edge && !center) return null;
|
|
1334
|
-
const isLeft = side === "left";
|
|
1335
|
-
const EdgeIcon = isLeft ? ChevronLeft : ChevronRight;
|
|
1336
|
-
const CenterIcon = isLeft ? ChevronRight : ChevronLeft;
|
|
1337
|
-
const renderButton = (action, icon) => /* @__PURE__ */ jsx16(
|
|
1338
|
-
"button",
|
|
1339
|
-
{
|
|
1340
|
-
type: "button",
|
|
1341
|
-
"aria-label": action.label,
|
|
1342
|
-
title: action.label,
|
|
1343
|
-
"aria-controls": controls,
|
|
1344
|
-
"aria-expanded": action.expanded,
|
|
1345
|
-
className: buttonClassName,
|
|
1346
|
-
onClick: action.onClick,
|
|
1347
|
-
children: icon
|
|
1348
|
-
}
|
|
1349
|
-
);
|
|
1350
|
-
const edgeButton = edge && renderButton(edge, /* @__PURE__ */ jsx16(EdgeIcon, { "aria-hidden": "true", className: "h-5 w-5" }));
|
|
1351
|
-
const centerButton = center && renderButton(center, /* @__PURE__ */ jsx16(CenterIcon, { "aria-hidden": "true", className: "h-5 w-5" }));
|
|
1352
|
-
const buttons = isLeft ? [edgeButton, centerButton] : [centerButton, edgeButton];
|
|
1353
|
-
return /* @__PURE__ */ jsxs12(
|
|
1354
|
-
"div",
|
|
1355
|
-
{
|
|
1356
|
-
className: twMerge(
|
|
1357
|
-
"panel-edge-tab flex w-[var(--layout-edge-tab-w)] flex-col overflow-hidden bg-[var(--color-surface)] shadow-[0_1px_2px_rgba(60,64,67,0.3),0_2px_6px_2px_rgba(60,64,67,0.15)] [clip-path:inset(-0.5rem_-0.5rem_-0.5rem_0)]",
|
|
1358
|
-
isLeft ? "rounded-r-lg" : "rounded-l-lg [clip-path:inset(-0.5rem_0_-0.5rem_-0.5rem)]",
|
|
1359
|
-
className2
|
|
1360
|
-
),
|
|
1361
|
-
children: [
|
|
1362
|
-
buttons[0],
|
|
1363
|
-
buttons[0] && buttons[1] && /* @__PURE__ */ jsx16("div", { role: "presentation", className: "h-px shrink-0 bg-[var(--color-border)]" }),
|
|
1364
|
-
buttons[1]
|
|
1365
|
-
]
|
|
1366
|
-
}
|
|
1367
|
-
);
|
|
1368
|
-
}
|
|
1369
|
-
|
|
1370
|
-
// src/genre-tree/useLeftDetailPanelLayout.ts
|
|
1371
|
-
import { useEffect as useEffect4 } from "react";
|
|
1372
|
-
function useLeftDetailPanelLayout(isPanelShown, enabled = true) {
|
|
1373
|
-
useEffect4(() => {
|
|
1374
|
-
if (!enabled || isPanelShown) return void 0;
|
|
1375
|
-
const root = document.documentElement;
|
|
1376
|
-
root.style.setProperty("--layout-left-w", "0px");
|
|
1377
|
-
return () => {
|
|
1378
|
-
root.style.removeProperty("--layout-left-w");
|
|
1379
|
-
};
|
|
1380
|
-
}, [enabled, isPanelShown]);
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
1296
|
// src/genre-tree/ChangeParentPopup.tsx
|
|
1384
|
-
import { useEffect as
|
|
1297
|
+
import { useEffect as useEffect4, useMemo as useMemo6 } from "react";
|
|
1385
1298
|
import {
|
|
1386
1299
|
getReparentCandidates,
|
|
1387
1300
|
useUpdateGenre
|
|
1388
1301
|
} from "@behindthemusictree/app-kit/genre-tree";
|
|
1389
|
-
import { jsx as
|
|
1302
|
+
import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1390
1303
|
function ChangeParentPopup({
|
|
1391
1304
|
genrePlaylist,
|
|
1392
1305
|
genrePlaylists,
|
|
@@ -1414,19 +1327,19 @@ function ChangeParentPopup({
|
|
|
1414
1327
|
}
|
|
1415
1328
|
);
|
|
1416
1329
|
};
|
|
1417
|
-
|
|
1330
|
+
useEffect4(() => {
|
|
1418
1331
|
const handleKeyDown = (event) => {
|
|
1419
1332
|
if (event.key === "Escape") onClose();
|
|
1420
1333
|
};
|
|
1421
1334
|
window.addEventListener("keydown", handleKeyDown);
|
|
1422
1335
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
1423
1336
|
}, [onClose]);
|
|
1424
|
-
return /* @__PURE__ */
|
|
1425
|
-
/* @__PURE__ */
|
|
1337
|
+
return /* @__PURE__ */ jsxs12(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
|
|
1338
|
+
/* @__PURE__ */ jsxs12("p", { className: "mb-3 text-sm text-[var(--color-text-muted)]", children: [
|
|
1426
1339
|
"Current parent: ",
|
|
1427
1340
|
genrePlaylist.parent?.name ?? "none"
|
|
1428
1341
|
] }),
|
|
1429
|
-
/* @__PURE__ */
|
|
1342
|
+
/* @__PURE__ */ jsx16("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ jsx16(
|
|
1430
1343
|
GenreSearch,
|
|
1431
1344
|
{
|
|
1432
1345
|
genrePlaylists: candidates,
|
|
@@ -1434,7 +1347,7 @@ function ChangeParentPopup({
|
|
|
1434
1347
|
placeholder: "Search a new parent\u2026"
|
|
1435
1348
|
}
|
|
1436
1349
|
) }),
|
|
1437
|
-
formErrors.length > 0 && /* @__PURE__ */
|
|
1350
|
+
formErrors.length > 0 && /* @__PURE__ */ jsx16("p", { role: "alert", className: "mt-3 text-sm text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
|
|
1438
1351
|
] });
|
|
1439
1352
|
}
|
|
1440
1353
|
|
|
@@ -1458,7 +1371,7 @@ import {
|
|
|
1458
1371
|
SiYoutube
|
|
1459
1372
|
} from "react-icons/si";
|
|
1460
1373
|
import { Chip } from "@behindthemusictree/ui";
|
|
1461
|
-
import { jsx as
|
|
1374
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
1462
1375
|
var LINK_KINDS = {
|
|
1463
1376
|
official: { label: "Official website", Icon: Globe },
|
|
1464
1377
|
wikipedia: { label: "Wikipedia", Icon: SiWikipedia2 },
|
|
@@ -1495,7 +1408,7 @@ function artistLinks(artist) {
|
|
|
1495
1408
|
return links;
|
|
1496
1409
|
}
|
|
1497
1410
|
function ArtistLinks({ links }) {
|
|
1498
|
-
return /* @__PURE__ */
|
|
1411
|
+
return /* @__PURE__ */ jsx17("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ jsx17("li", { children: /* @__PURE__ */ jsx17(Chip, { href: url, icon: /* @__PURE__ */ jsx17(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
|
|
1499
1412
|
}
|
|
1500
1413
|
|
|
1501
1414
|
// src/genre-tree/artist/formatYears.ts
|
|
@@ -1506,7 +1419,7 @@ function formatYears(beginYear, endYear, ended) {
|
|
|
1506
1419
|
}
|
|
1507
1420
|
|
|
1508
1421
|
// src/genre-tree/artist/ArtistHeader.tsx
|
|
1509
|
-
import { jsx as
|
|
1422
|
+
import { jsx as jsx18 } from "react/jsx-runtime";
|
|
1510
1423
|
var countryNames = new Intl.DisplayNames(["en"], { type: "region" });
|
|
1511
1424
|
function ArtistHeader({ artist }) {
|
|
1512
1425
|
const fields = [];
|
|
@@ -1516,35 +1429,35 @@ function ArtistHeader({ artist }) {
|
|
|
1516
1429
|
const years = formatYears(artist.beginYear, artist.endYear, artist.ended);
|
|
1517
1430
|
if (years) fields.push({ label: "Active", value: years });
|
|
1518
1431
|
const links = artistLinks(artist);
|
|
1519
|
-
if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */
|
|
1432
|
+
if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ jsx18(ArtistLinks, { links }) });
|
|
1520
1433
|
if (fields.length === 0) return null;
|
|
1521
|
-
return /* @__PURE__ */
|
|
1434
|
+
return /* @__PURE__ */ jsx18(FieldList, { items: fields });
|
|
1522
1435
|
}
|
|
1523
1436
|
|
|
1524
1437
|
// src/genre-tree/artist/ArtistPhoto.tsx
|
|
1525
|
-
import { jsx as
|
|
1438
|
+
import { jsx as jsx19, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1526
1439
|
function ExternalLink({ href, children }) {
|
|
1527
|
-
return /* @__PURE__ */
|
|
1440
|
+
return /* @__PURE__ */ jsx19("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
|
|
1528
1441
|
}
|
|
1529
1442
|
function ArtistPhoto({ image, name }) {
|
|
1530
|
-
return /* @__PURE__ */
|
|
1531
|
-
/* @__PURE__ */
|
|
1532
|
-
/* @__PURE__ */
|
|
1443
|
+
return /* @__PURE__ */ jsxs13("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
|
|
1444
|
+
/* @__PURE__ */ jsx19("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
|
|
1445
|
+
/* @__PURE__ */ jsxs13("figcaption", { className: "text-xs text-[var(--color-text-muted)]", children: [
|
|
1533
1446
|
"Photo: ",
|
|
1534
|
-
/* @__PURE__ */
|
|
1447
|
+
/* @__PURE__ */ jsx19(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
|
|
1535
1448
|
",",
|
|
1536
1449
|
" ",
|
|
1537
|
-
image.licenseUrl ? /* @__PURE__ */
|
|
1450
|
+
image.licenseUrl ? /* @__PURE__ */ jsx19(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
|
|
1538
1451
|
] })
|
|
1539
1452
|
] });
|
|
1540
1453
|
}
|
|
1541
1454
|
|
|
1542
1455
|
// src/genre-tree/artist/ArtistMembers.tsx
|
|
1543
1456
|
import { PanelGroup, PanelSection } from "@behindthemusictree/ui";
|
|
1544
|
-
import { jsx as
|
|
1457
|
+
import { jsx as jsx20, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1545
1458
|
function MembershipList({ title, memberships, renderArtistLink }) {
|
|
1546
1459
|
if (memberships.length === 0) return null;
|
|
1547
|
-
return /* @__PURE__ */
|
|
1460
|
+
return /* @__PURE__ */ jsx20(PanelGroup, { title, children: /* @__PURE__ */ jsx20("ul", { children: memberships.map((membership, index) => {
|
|
1548
1461
|
const details = [
|
|
1549
1462
|
formatYears(membership.beginYear, membership.endYear, membership.ended),
|
|
1550
1463
|
...membership.roles
|
|
@@ -1552,9 +1465,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
|
|
|
1552
1465
|
const detailsText = details.filter(Boolean).join(" \xB7 ");
|
|
1553
1466
|
return (
|
|
1554
1467
|
// A member can have several periods in the same group.
|
|
1555
|
-
/* @__PURE__ */
|
|
1556
|
-
/* @__PURE__ */
|
|
1557
|
-
detailsText && /* @__PURE__ */
|
|
1468
|
+
/* @__PURE__ */ jsxs14("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
|
|
1469
|
+
/* @__PURE__ */ jsx20("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
|
|
1470
|
+
detailsText && /* @__PURE__ */ jsxs14("span", { className: "text-[var(--color-text-muted)]", children: [
|
|
1558
1471
|
" \u2014 ",
|
|
1559
1472
|
detailsText
|
|
1560
1473
|
] })
|
|
@@ -1565,9 +1478,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
|
|
|
1565
1478
|
function ArtistMembers({ title, memberships, renderArtistLink }) {
|
|
1566
1479
|
if (memberships.length === 0) return null;
|
|
1567
1480
|
const isCurrent = (membership) => !membership.ended && membership.endYear === null;
|
|
1568
|
-
return /* @__PURE__ */
|
|
1569
|
-
/* @__PURE__ */
|
|
1570
|
-
/* @__PURE__ */
|
|
1481
|
+
return /* @__PURE__ */ jsxs14(PanelSection, { title, children: [
|
|
1482
|
+
/* @__PURE__ */ jsx20(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
|
|
1483
|
+
/* @__PURE__ */ jsx20(
|
|
1571
1484
|
MembershipList,
|
|
1572
1485
|
{
|
|
1573
1486
|
title: "Past",
|
|
@@ -1580,7 +1493,7 @@ function ArtistMembers({ title, memberships, renderArtistLink }) {
|
|
|
1580
1493
|
|
|
1581
1494
|
// src/genre-tree/artist/ArtistDiscography.tsx
|
|
1582
1495
|
import { PanelGroup as PanelGroup2, PanelSection as PanelSection2 } from "@behindthemusictree/ui";
|
|
1583
|
-
import { jsx as
|
|
1496
|
+
import { jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1584
1497
|
var GROUPS = ["Albums", "EPs", "Singles", "Live", "Compilations", "Other"];
|
|
1585
1498
|
var PRIMARY_GROUPS = { album: "Albums", ep: "EPs", single: "Singles" };
|
|
1586
1499
|
function discographyGroup({ primaryType, secondaryTypes }) {
|
|
@@ -1600,18 +1513,18 @@ function byYear(a, b) {
|
|
|
1600
1513
|
function ArtistDiscography({ albums }) {
|
|
1601
1514
|
if (albums.length === 0) return null;
|
|
1602
1515
|
const sorted = [...albums].sort(byYear);
|
|
1603
|
-
return /* @__PURE__ */
|
|
1516
|
+
return /* @__PURE__ */ jsx21(PanelSection2, { title: "Discography", children: GROUPS.map((group) => {
|
|
1604
1517
|
const groupAlbums = sorted.filter((album) => discographyGroup(album) === group);
|
|
1605
1518
|
if (groupAlbums.length === 0) return null;
|
|
1606
|
-
return /* @__PURE__ */
|
|
1607
|
-
/* @__PURE__ */
|
|
1608
|
-
/* @__PURE__ */
|
|
1519
|
+
return /* @__PURE__ */ jsx21(PanelGroup2, { title: group, children: /* @__PURE__ */ jsx21("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ jsxs15("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
|
|
1520
|
+
/* @__PURE__ */ jsx21("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
|
|
1521
|
+
/* @__PURE__ */ jsx21("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
|
|
1609
1522
|
] }, album.uuid)) }) }, group);
|
|
1610
1523
|
}) });
|
|
1611
1524
|
}
|
|
1612
1525
|
|
|
1613
1526
|
// src/genre-tree/artist/ArtistSongs.tsx
|
|
1614
|
-
import { useState as
|
|
1527
|
+
import { useState as useState5 } from "react";
|
|
1615
1528
|
import { keepPreviousData } from "@tanstack/react-query";
|
|
1616
1529
|
import { Play as Play2 } from "lucide-react";
|
|
1617
1530
|
import { Pagination, PanelSection as PanelSection3, Skeleton as Skeleton3 } from "@behindthemusictree/ui";
|
|
@@ -1632,7 +1545,7 @@ import { useTrackList as useTrackList3 } from "@behindthemusictree/app-kit/genre
|
|
|
1632
1545
|
import { FaPlay as FaPlay2, FaPause as FaPause2 } from "react-icons/fa";
|
|
1633
1546
|
import { usePlayer as usePlayer6 } from "@behindthemusictree/app-kit/player";
|
|
1634
1547
|
import { PlayStates as PlayStates2 } from "@behindthemusictree/app-kit/player";
|
|
1635
|
-
import { jsx as
|
|
1548
|
+
import { jsx as jsx22, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1636
1549
|
function TrackPositionPlayPause({
|
|
1637
1550
|
position,
|
|
1638
1551
|
uuid,
|
|
@@ -1646,14 +1559,14 @@ function TrackPositionPlayPause({
|
|
|
1646
1559
|
const isPlaying = isThisTrack && isConfirmedPlayable && playState === PlayStates2.PLAYING;
|
|
1647
1560
|
const isPaused = isThisTrack && isConfirmedPlayable && playState === PlayStates2.PAUSED;
|
|
1648
1561
|
const isCurrentTrackActive = isPlaying || isPaused;
|
|
1649
|
-
return /* @__PURE__ */
|
|
1562
|
+
return /* @__PURE__ */ jsxs16(
|
|
1650
1563
|
"div",
|
|
1651
1564
|
{
|
|
1652
1565
|
className: "track-position-play-pause relative flex items-center justify-center gap-2 text-lg w-16 cursor-pointer",
|
|
1653
1566
|
style: { minWidth: "4rem", maxWidth: "4rem" },
|
|
1654
1567
|
onClick: handlePlayPauseClick,
|
|
1655
1568
|
children: [
|
|
1656
|
-
/* @__PURE__ */
|
|
1569
|
+
/* @__PURE__ */ jsx22("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ jsx22("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ jsxs16(
|
|
1657
1570
|
"span",
|
|
1658
1571
|
{
|
|
1659
1572
|
className: "track-position-play-pause__bars",
|
|
@@ -1661,18 +1574,18 @@ function TrackPositionPlayPause({
|
|
|
1661
1574
|
role: "img",
|
|
1662
1575
|
"aria-label": isPlaying ? "Playing" : "Paused",
|
|
1663
1576
|
children: [
|
|
1664
|
-
/* @__PURE__ */
|
|
1665
|
-
/* @__PURE__ */
|
|
1666
|
-
/* @__PURE__ */
|
|
1577
|
+
/* @__PURE__ */ jsx22("span", { className: "track-position-play-pause__bar" }),
|
|
1578
|
+
/* @__PURE__ */ jsx22("span", { className: "track-position-play-pause__bar" }),
|
|
1579
|
+
/* @__PURE__ */ jsx22("span", { className: "track-position-play-pause__bar" })
|
|
1667
1580
|
]
|
|
1668
1581
|
}
|
|
1669
1582
|
) : position }),
|
|
1670
|
-
/* @__PURE__ */
|
|
1583
|
+
/* @__PURE__ */ jsx22(
|
|
1671
1584
|
"div",
|
|
1672
1585
|
{
|
|
1673
1586
|
className: `hidden group-hover:flex items-center justify-center ${isCurrentTrackActive ? "" : "absolute inset-0"}`,
|
|
1674
1587
|
"aria-hidden": "true",
|
|
1675
|
-
children: isAwaitingPlayback ? /* @__PURE__ */
|
|
1588
|
+
children: isAwaitingPlayback ? /* @__PURE__ */ jsx22("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ jsx22(FaPause2, {}) : /* @__PURE__ */ jsx22(FaPlay2, {})
|
|
1676
1589
|
}
|
|
1677
1590
|
)
|
|
1678
1591
|
]
|
|
@@ -1683,13 +1596,14 @@ function TrackPositionPlayPause({
|
|
|
1683
1596
|
// src/genre-tree/track-list-sidebar/TrackItem.tsx
|
|
1684
1597
|
import { isSong as isSong2, songUnplayableReason as songUnplayableReason2 } from "@behindthemusictree/app-kit/genre-tree";
|
|
1685
1598
|
import { ArtistNames as ArtistNames2 } from "@behindthemusictree/app-kit/genre-tree";
|
|
1686
|
-
import { jsx as
|
|
1599
|
+
import { jsx as jsx23, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
1687
1600
|
function TrackItem({
|
|
1688
1601
|
track,
|
|
1689
1602
|
position,
|
|
1690
1603
|
renderDuration,
|
|
1691
1604
|
renderActions,
|
|
1692
|
-
renderArtistLink
|
|
1605
|
+
renderArtistLink,
|
|
1606
|
+
renderSongLink
|
|
1693
1607
|
}) {
|
|
1694
1608
|
const { handlePlayPauseAction, playerTrackObject, loadTrackForPlayer } = usePlayer7();
|
|
1695
1609
|
const { trackList, selectedTrack, toTrackAtPosition, runtimeUnplayableTrackIds } = useTrackList3();
|
|
@@ -1706,12 +1620,12 @@ function TrackItem({
|
|
|
1706
1620
|
loadTrackForPlayer(track.uuid);
|
|
1707
1621
|
}
|
|
1708
1622
|
};
|
|
1709
|
-
return /* @__PURE__ */
|
|
1623
|
+
return /* @__PURE__ */ jsxs17(
|
|
1710
1624
|
"div",
|
|
1711
1625
|
{
|
|
1712
1626
|
className: `track-item flex h-14 text-[var(--color-text)] ${isUnplayable ? "opacity-50" : "hover:bg-[var(--color-surface-muted)] group"}`,
|
|
1713
1627
|
children: [
|
|
1714
|
-
isUnplayable ? /* @__PURE__ */
|
|
1628
|
+
isUnplayable ? /* @__PURE__ */ jsx23(
|
|
1715
1629
|
"div",
|
|
1716
1630
|
{
|
|
1717
1631
|
className: "flex items-center justify-center text-lg w-16",
|
|
@@ -1719,7 +1633,7 @@ function TrackItem({
|
|
|
1719
1633
|
"aria-disabled": "true",
|
|
1720
1634
|
children: position
|
|
1721
1635
|
}
|
|
1722
|
-
) : /* @__PURE__ */
|
|
1636
|
+
) : /* @__PURE__ */ jsx23(
|
|
1723
1637
|
TrackPositionPlayPause,
|
|
1724
1638
|
{
|
|
1725
1639
|
position,
|
|
@@ -1728,12 +1642,12 @@ function TrackItem({
|
|
|
1728
1642
|
isSelected: selectedTrack?.uuid === track.uuid
|
|
1729
1643
|
}
|
|
1730
1644
|
),
|
|
1731
|
-
/* @__PURE__ */
|
|
1732
|
-
/* @__PURE__ */
|
|
1733
|
-
track.artists && track.artists.length > 0 ? /* @__PURE__ */
|
|
1645
|
+
/* @__PURE__ */ jsxs17("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
|
|
1646
|
+
/* @__PURE__ */ jsx23("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ jsx23("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: renderSongLink ? renderSongLink(track) : isSong2(track) ? /* @__PURE__ */ jsx23(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
|
|
1647
|
+
track.artists && track.artists.length > 0 ? /* @__PURE__ */ jsx23("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ jsx23(ArtistNames2, { artists: track.artists, renderArtistLink }) }) : ""
|
|
1734
1648
|
] }),
|
|
1735
|
-
/* @__PURE__ */
|
|
1736
|
-
renderDuration && /* @__PURE__ */
|
|
1649
|
+
/* @__PURE__ */ jsx23("div", { className: "genre-name-container flex items-center justify-start w-[21.6%] min-w-0 shrink-0", children: track.genre ? /* @__PURE__ */ jsx23("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
|
|
1650
|
+
renderDuration && /* @__PURE__ */ jsx23(
|
|
1737
1651
|
"div",
|
|
1738
1652
|
{
|
|
1739
1653
|
className: "duration flex text-base w-16 items-center justify-center",
|
|
@@ -1741,22 +1655,22 @@ function TrackItem({
|
|
|
1741
1655
|
children: renderDuration(track)
|
|
1742
1656
|
}
|
|
1743
1657
|
),
|
|
1744
|
-
renderActions && /* @__PURE__ */
|
|
1658
|
+
renderActions && /* @__PURE__ */ jsx23("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
|
|
1745
1659
|
]
|
|
1746
1660
|
}
|
|
1747
1661
|
);
|
|
1748
1662
|
}
|
|
1749
1663
|
|
|
1750
1664
|
// src/genre-tree/artist/ArtistSongs.tsx
|
|
1751
|
-
import { Fragment as
|
|
1665
|
+
import { Fragment as Fragment8, jsx as jsx24, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
1752
1666
|
var SongPageSchema2 = PaginatedResponseSchema2(SongDetailedSchema2);
|
|
1753
1667
|
var SORTS = [
|
|
1754
1668
|
{ ordering: "title", label: "Title" },
|
|
1755
1669
|
{ ordering: "-albumYear", label: "Year" }
|
|
1756
1670
|
];
|
|
1757
|
-
function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
1758
|
-
const [page, setPage] =
|
|
1759
|
-
const [ordering, setOrdering] =
|
|
1671
|
+
function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLink }) {
|
|
1672
|
+
const [page, setPage] = useState5(1);
|
|
1673
|
+
const [ordering, setOrdering] = useState5("title");
|
|
1760
1674
|
const { fetch } = useFetchWrapper2(getBackendBaseUrl);
|
|
1761
1675
|
const { playNewTrackListFromArtist } = useTrackList4();
|
|
1762
1676
|
const { playerTrackObject } = usePlayer8();
|
|
@@ -1781,21 +1695,21 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
|
1781
1695
|
event.stopPropagation();
|
|
1782
1696
|
void play(song.uuid);
|
|
1783
1697
|
};
|
|
1784
|
-
return /* @__PURE__ */
|
|
1785
|
-
/* @__PURE__ */
|
|
1786
|
-
/* @__PURE__ */
|
|
1698
|
+
return /* @__PURE__ */ jsx24(PanelSection3, { title: "Songs", children: isPending ? /* @__PURE__ */ jsx24(Skeleton3, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ jsx24("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ jsx24("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ jsxs18(Fragment8, { children: [
|
|
1699
|
+
/* @__PURE__ */ jsxs18("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
|
|
1700
|
+
/* @__PURE__ */ jsxs18(
|
|
1787
1701
|
"button",
|
|
1788
1702
|
{
|
|
1789
1703
|
type: "button",
|
|
1790
1704
|
onClick: () => void play(),
|
|
1791
1705
|
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",
|
|
1792
1706
|
children: [
|
|
1793
|
-
/* @__PURE__ */
|
|
1707
|
+
/* @__PURE__ */ jsx24(Play2, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
|
|
1794
1708
|
"Play all"
|
|
1795
1709
|
]
|
|
1796
1710
|
}
|
|
1797
1711
|
),
|
|
1798
|
-
/* @__PURE__ */
|
|
1712
|
+
/* @__PURE__ */ jsx24("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ jsx24(
|
|
1799
1713
|
"button",
|
|
1800
1714
|
{
|
|
1801
1715
|
type: "button",
|
|
@@ -1810,26 +1724,28 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
|
1810
1724
|
sort.ordering
|
|
1811
1725
|
)) })
|
|
1812
1726
|
] }),
|
|
1813
|
-
/* @__PURE__ */
|
|
1727
|
+
/* @__PURE__ */ jsx24("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ jsx24("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ jsx24(
|
|
1814
1728
|
TrackItem,
|
|
1815
1729
|
{
|
|
1816
1730
|
track: song,
|
|
1817
1731
|
position: (page - 1) * data.pageSize + index + 1,
|
|
1818
|
-
renderArtistLink
|
|
1732
|
+
renderArtistLink,
|
|
1733
|
+
renderSongLink
|
|
1819
1734
|
}
|
|
1820
1735
|
) }, song.uuid)) }),
|
|
1821
|
-
/* @__PURE__ */
|
|
1736
|
+
/* @__PURE__ */ jsx24(Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
|
|
1822
1737
|
] }) });
|
|
1823
1738
|
}
|
|
1824
1739
|
|
|
1825
1740
|
// src/genre-tree/artist/useArtistPanel.tsx
|
|
1826
|
-
import { Fragment as
|
|
1741
|
+
import { Fragment as Fragment9, jsx as jsx25, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
1827
1742
|
function useArtistPanel({
|
|
1828
1743
|
uuid,
|
|
1829
1744
|
scope,
|
|
1830
1745
|
getBackendBaseUrl,
|
|
1831
1746
|
getShareUrl,
|
|
1832
1747
|
renderArtistLink,
|
|
1748
|
+
renderSongLink,
|
|
1833
1749
|
expanded,
|
|
1834
1750
|
onExpandedChange,
|
|
1835
1751
|
onClose
|
|
@@ -1843,26 +1759,26 @@ function useArtistPanel({
|
|
|
1843
1759
|
return {
|
|
1844
1760
|
...panel,
|
|
1845
1761
|
title: "Artist",
|
|
1846
|
-
content: /* @__PURE__ */
|
|
1847
|
-
/* @__PURE__ */
|
|
1848
|
-
/* @__PURE__ */
|
|
1849
|
-
/* @__PURE__ */
|
|
1850
|
-
/* @__PURE__ */
|
|
1851
|
-
/* @__PURE__ */
|
|
1852
|
-
/* @__PURE__ */
|
|
1762
|
+
content: /* @__PURE__ */ jsxs19("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
|
|
1763
|
+
/* @__PURE__ */ jsx25(Skeleton4, { className: "h-3 w-16" }),
|
|
1764
|
+
/* @__PURE__ */ jsx25(Skeleton4, { className: "h-4 w-32" }),
|
|
1765
|
+
/* @__PURE__ */ jsx25(Skeleton4, { className: "h-3 w-16" }),
|
|
1766
|
+
/* @__PURE__ */ jsx25(Skeleton4, { className: "h-4 w-24" }),
|
|
1767
|
+
/* @__PURE__ */ jsx25(Skeleton4, { className: "h-16 mt-2.5" }),
|
|
1768
|
+
/* @__PURE__ */ jsx25(Skeleton4, { className: "h-16" })
|
|
1853
1769
|
] })
|
|
1854
1770
|
};
|
|
1855
1771
|
case "not-found":
|
|
1856
1772
|
return {
|
|
1857
1773
|
...panel,
|
|
1858
1774
|
title: "Artist not found",
|
|
1859
|
-
content: /* @__PURE__ */
|
|
1775
|
+
content: /* @__PURE__ */ jsx25("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This artist does not exist." })
|
|
1860
1776
|
};
|
|
1861
1777
|
case "error":
|
|
1862
1778
|
return {
|
|
1863
1779
|
...panel,
|
|
1864
1780
|
title: "Artist",
|
|
1865
|
-
content: /* @__PURE__ */
|
|
1781
|
+
content: /* @__PURE__ */ jsx25("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
|
|
1866
1782
|
};
|
|
1867
1783
|
case "success": {
|
|
1868
1784
|
const { artist } = result;
|
|
@@ -1871,22 +1787,225 @@ function useArtistPanel({
|
|
|
1871
1787
|
return {
|
|
1872
1788
|
...panel,
|
|
1873
1789
|
title: artist.name,
|
|
1874
|
-
content: /* @__PURE__ */
|
|
1875
|
-
shareUrl && /* @__PURE__ */
|
|
1876
|
-
artist.image && /* @__PURE__ */
|
|
1877
|
-
/* @__PURE__ */
|
|
1878
|
-
/* @__PURE__ */
|
|
1879
|
-
/* @__PURE__ */
|
|
1880
|
-
artist.tracksCount > 0 && /* @__PURE__ */
|
|
1790
|
+
content: /* @__PURE__ */ jsxs19(Fragment9, { children: [
|
|
1791
|
+
shareUrl && /* @__PURE__ */ jsx25("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ jsx25("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: shareTarget && /* @__PURE__ */ jsx25(ShareActionButton, { target: shareTarget, url: shareUrl }) }) }),
|
|
1792
|
+
artist.image && /* @__PURE__ */ jsx25(ArtistPhoto, { image: artist.image, name: artist.name }),
|
|
1793
|
+
/* @__PURE__ */ jsx25(ArtistHeader, { artist }),
|
|
1794
|
+
/* @__PURE__ */ jsx25(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
|
|
1795
|
+
/* @__PURE__ */ jsx25(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
|
|
1796
|
+
artist.tracksCount > 0 && /* @__PURE__ */ jsx25(
|
|
1881
1797
|
ArtistSongs,
|
|
1882
1798
|
{
|
|
1883
1799
|
artist: { uuid: artist.uuid, name: artist.name },
|
|
1884
1800
|
getBackendBaseUrl,
|
|
1885
|
-
renderArtistLink
|
|
1801
|
+
renderArtistLink,
|
|
1802
|
+
renderSongLink
|
|
1886
1803
|
}
|
|
1887
1804
|
)
|
|
1888
1805
|
] }),
|
|
1889
|
-
extraContent: artist.albums.length > 0 ? /* @__PURE__ */
|
|
1806
|
+
extraContent: artist.albums.length > 0 ? /* @__PURE__ */ jsx25(ArtistDiscography, { albums: artist.albums }) : void 0
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
// src/genre-tree/song/useSongPanel.tsx
|
|
1813
|
+
import { Skeleton as Skeleton6 } from "@behindthemusictree/ui";
|
|
1814
|
+
import {
|
|
1815
|
+
useSong
|
|
1816
|
+
} from "@behindthemusictree/app-kit/genre-tree";
|
|
1817
|
+
|
|
1818
|
+
// src/genre-tree/song/SongPanelContent.tsx
|
|
1819
|
+
import { useState as useState6 } from "react";
|
|
1820
|
+
import { Pause as Pause2, Play as Play3 } from "lucide-react";
|
|
1821
|
+
import { FieldList as FieldList2, PanelSection as PanelSection4, Skeleton as Skeleton5 } from "@behindthemusictree/ui";
|
|
1822
|
+
import { PlayStates as PlayStates3, usePlayer as usePlayer9 } from "@behindthemusictree/app-kit/player";
|
|
1823
|
+
import {
|
|
1824
|
+
ArtistNames as ArtistNames3,
|
|
1825
|
+
musicBrainzRecordingUrl as musicBrainzRecordingUrl2,
|
|
1826
|
+
songUnplayableReason as songUnplayableReason4,
|
|
1827
|
+
useSongHistory,
|
|
1828
|
+
useTrackList as useTrackList5
|
|
1829
|
+
} from "@behindthemusictree/app-kit/genre-tree";
|
|
1830
|
+
import { Fragment as Fragment10, jsx as jsx26, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
1831
|
+
var numberFormat = new Intl.NumberFormat("en");
|
|
1832
|
+
var dateFormat = new Intl.DateTimeFormat("en", { dateStyle: "medium" });
|
|
1833
|
+
var externalLink = "text-[var(--color-primary)] hover:underline focus-visible:underline";
|
|
1834
|
+
function SongHistory({ uuid, getBackendBaseUrl }) {
|
|
1835
|
+
const [open, setOpen] = useState6(false);
|
|
1836
|
+
const history = useSongHistory(uuid, getBackendBaseUrl, open);
|
|
1837
|
+
let body = null;
|
|
1838
|
+
if (history.status === "loading") body = /* @__PURE__ */ jsx26(Skeleton5, { className: "h-10", "aria-busy": "true" });
|
|
1839
|
+
else if (history.status === "error") body = /* @__PURE__ */ jsx26("p", { className: "text-red-600", children: "Could not load the history." });
|
|
1840
|
+
else if (history.status === "success")
|
|
1841
|
+
body = history.entries.length === 0 ? /* @__PURE__ */ jsx26("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ jsx26("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ jsxs20("li", { className: "py-1.5 text-sm", children: [
|
|
1842
|
+
/* @__PURE__ */ jsx26("span", { className: "font-medium", children: entry.action }),
|
|
1843
|
+
entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ jsxs20("span", { className: "text-[var(--color-text-muted)]", children: [
|
|
1844
|
+
" ",
|
|
1845
|
+
"\u2014 ",
|
|
1846
|
+
entry.oldValue ?? "none",
|
|
1847
|
+
" \u2192 ",
|
|
1848
|
+
entry.newValue ?? "none"
|
|
1849
|
+
] }) : null,
|
|
1850
|
+
/* @__PURE__ */ jsxs20("span", { className: "block text-xs text-[var(--color-text-muted)]", children: [
|
|
1851
|
+
entry.actorPseudo ?? "Anonymous",
|
|
1852
|
+
" \xB7 ",
|
|
1853
|
+
dateFormat.format(new Date(entry.createdOn))
|
|
1854
|
+
] })
|
|
1855
|
+
] }, entry.uuid)) });
|
|
1856
|
+
return /* @__PURE__ */ jsxs20(
|
|
1857
|
+
"details",
|
|
1858
|
+
{
|
|
1859
|
+
onToggle: (event) => setOpen(event.currentTarget.open),
|
|
1860
|
+
className: "px-6 pb-2.5 mt-2.5 border-t border-[var(--color-border)]",
|
|
1861
|
+
children: [
|
|
1862
|
+
/* @__PURE__ */ jsx26("summary", { className: "mt-2.5 cursor-pointer text-[0.6875rem] font-semibold uppercase text-[var(--color-text-muted)]", children: "History" }),
|
|
1863
|
+
/* @__PURE__ */ jsx26("div", { className: "mt-1.5", children: body })
|
|
1864
|
+
]
|
|
1865
|
+
}
|
|
1866
|
+
);
|
|
1867
|
+
}
|
|
1868
|
+
function SongPanelContent({
|
|
1869
|
+
song,
|
|
1870
|
+
scope,
|
|
1871
|
+
getBackendBaseUrl,
|
|
1872
|
+
shareUrl,
|
|
1873
|
+
renderArtistLink,
|
|
1874
|
+
genrePath,
|
|
1875
|
+
onSelectGenre
|
|
1876
|
+
}) {
|
|
1877
|
+
const { playNewTrackListFromTrackUuid } = useTrackList5();
|
|
1878
|
+
const { playerTrackObject, playState, handlePlayPauseAction } = usePlayer9();
|
|
1879
|
+
const unplayableReason = songUnplayableReason4(song);
|
|
1880
|
+
const isCurrent = playerTrackObject?.track.id === song.uuid;
|
|
1881
|
+
const isPlaying = isCurrent && playState === PlayStates3.PLAYING;
|
|
1882
|
+
const shareTarget = { kind: "song", uuid: song.uuid, name: song.title, scope };
|
|
1883
|
+
const musicBrainzUrl = musicBrainzRecordingUrl2(song);
|
|
1884
|
+
const fields = [];
|
|
1885
|
+
if (song.genre) {
|
|
1886
|
+
const path = genrePath.length > 0 ? genrePath : null;
|
|
1887
|
+
fields.push({
|
|
1888
|
+
label: "Genre",
|
|
1889
|
+
value: path ? /* @__PURE__ */ jsx26("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ jsxs20("span", { children: [
|
|
1890
|
+
index > 0 && /* @__PURE__ */ jsx26("span", { "aria-hidden": "true", children: "\u203A " }),
|
|
1891
|
+
/* @__PURE__ */ jsx26("button", { type: "button", onClick: () => onSelectGenre(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
|
|
1892
|
+
] }, genrePlaylist.uuid)) }) : song.genre.name
|
|
1893
|
+
});
|
|
1894
|
+
} else {
|
|
1895
|
+
fields.push({ label: "Genre", value: /* @__PURE__ */ jsx26("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
|
|
1896
|
+
}
|
|
1897
|
+
if (song.firstReleaseYear) fields.push({ label: "First released", value: song.firstReleaseYear });
|
|
1898
|
+
const viewCount = song.youtubeVideo?.viewCount;
|
|
1899
|
+
if (viewCount !== null && viewCount !== void 0)
|
|
1900
|
+
fields.push({ label: "YouTube views", value: numberFormat.format(viewCount) });
|
|
1901
|
+
fields.push({ label: "Plays", value: numberFormat.format(song.playCount) });
|
|
1902
|
+
const links = [];
|
|
1903
|
+
if (musicBrainzUrl)
|
|
1904
|
+
links.push(
|
|
1905
|
+
/* @__PURE__ */ jsx26("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
|
|
1906
|
+
);
|
|
1907
|
+
if (song.youtubeVideo)
|
|
1908
|
+
links.push(
|
|
1909
|
+
/* @__PURE__ */ jsx26(
|
|
1910
|
+
"a",
|
|
1911
|
+
{
|
|
1912
|
+
href: `https://www.youtube.com/watch?v=${encodeURIComponent(song.youtubeVideo.id)}`,
|
|
1913
|
+
target: "_blank",
|
|
1914
|
+
rel: "noopener noreferrer",
|
|
1915
|
+
className: externalLink,
|
|
1916
|
+
children: "YouTube"
|
|
1917
|
+
},
|
|
1918
|
+
"yt"
|
|
1919
|
+
)
|
|
1920
|
+
);
|
|
1921
|
+
if (links.length > 0)
|
|
1922
|
+
fields.push({
|
|
1923
|
+
label: "Links",
|
|
1924
|
+
value: /* @__PURE__ */ jsx26("span", { className: "flex gap-3", children: links })
|
|
1925
|
+
});
|
|
1926
|
+
return /* @__PURE__ */ jsxs20(Fragment10, { children: [
|
|
1927
|
+
/* @__PURE__ */ jsx26("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ jsxs20("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
|
|
1928
|
+
unplayableReason ? /* @__PURE__ */ jsx26("p", { className: "flex items-center px-2.5 text-sm text-[var(--color-text-muted)]", children: unplayableReason }) : /* @__PURE__ */ jsxs20(
|
|
1929
|
+
"button",
|
|
1930
|
+
{
|
|
1931
|
+
type: "button",
|
|
1932
|
+
onClick: () => isCurrent ? handlePlayPauseAction() : playNewTrackListFromTrackUuid(song, scope),
|
|
1933
|
+
className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none",
|
|
1934
|
+
children: [
|
|
1935
|
+
/* @__PURE__ */ jsx26("span", { className: "flex items-center justify-center w-10 h-10 rounded-full group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)] bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]", children: isPlaying ? /* @__PURE__ */ jsx26(Pause2, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ jsx26(Play3, { "aria-hidden": "true", className: "w-5 h-5" }) }),
|
|
1936
|
+
isPlaying ? "Pause" : "Play"
|
|
1937
|
+
]
|
|
1938
|
+
}
|
|
1939
|
+
),
|
|
1940
|
+
shareUrl && /* @__PURE__ */ jsx26(ShareActionButton, { target: shareTarget, url: shareUrl })
|
|
1941
|
+
] }) }),
|
|
1942
|
+
song.artists && song.artists.length > 0 && /* @__PURE__ */ jsx26("p", { className: "px-6 pt-2.5 text-[var(--color-text-muted)]", children: /* @__PURE__ */ jsx26(ArtistNames3, { artists: song.artists, renderArtistLink }) }),
|
|
1943
|
+
/* @__PURE__ */ jsx26(FieldList2, { items: fields }),
|
|
1944
|
+
song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ jsx26(PanelSection4, { title: "Essential in", children: /* @__PURE__ */ jsx26("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ jsx26("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
|
|
1945
|
+
/* @__PURE__ */ jsx26(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
|
|
1946
|
+
] });
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
// src/genre-tree/song/useSongPanel.tsx
|
|
1950
|
+
import { jsx as jsx27, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
1951
|
+
function useSongPanel({
|
|
1952
|
+
uuid,
|
|
1953
|
+
scope,
|
|
1954
|
+
getBackendBaseUrl,
|
|
1955
|
+
getShareUrl,
|
|
1956
|
+
renderArtistLink,
|
|
1957
|
+
getGenrePath,
|
|
1958
|
+
onSelectGenre,
|
|
1959
|
+
expanded,
|
|
1960
|
+
onExpandedChange,
|
|
1961
|
+
onClose
|
|
1962
|
+
}) {
|
|
1963
|
+
const result = useSong(uuid, getBackendBaseUrl);
|
|
1964
|
+
const panel = { expanded, onExpandedChange, onClose };
|
|
1965
|
+
switch (result.status) {
|
|
1966
|
+
case "idle":
|
|
1967
|
+
return void 0;
|
|
1968
|
+
case "loading":
|
|
1969
|
+
return {
|
|
1970
|
+
...panel,
|
|
1971
|
+
title: "Song",
|
|
1972
|
+
content: /* @__PURE__ */ jsxs21("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
|
|
1973
|
+
/* @__PURE__ */ jsx27(Skeleton6, { className: "h-3 w-16" }),
|
|
1974
|
+
/* @__PURE__ */ jsx27(Skeleton6, { className: "h-4 w-32" }),
|
|
1975
|
+
/* @__PURE__ */ jsx27(Skeleton6, { className: "h-3 w-16" }),
|
|
1976
|
+
/* @__PURE__ */ jsx27(Skeleton6, { className: "h-4 w-24" })
|
|
1977
|
+
] })
|
|
1978
|
+
};
|
|
1979
|
+
case "not-found":
|
|
1980
|
+
return {
|
|
1981
|
+
...panel,
|
|
1982
|
+
title: "Song not found",
|
|
1983
|
+
content: /* @__PURE__ */ jsx27("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This song does not exist." })
|
|
1984
|
+
};
|
|
1985
|
+
case "error":
|
|
1986
|
+
return {
|
|
1987
|
+
...panel,
|
|
1988
|
+
title: "Song",
|
|
1989
|
+
content: /* @__PURE__ */ jsx27("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this song." })
|
|
1990
|
+
};
|
|
1991
|
+
case "success": {
|
|
1992
|
+
const { song } = result;
|
|
1993
|
+
const shareUrl = getShareUrl?.({ kind: "song", uuid: song.uuid, name: song.title, scope });
|
|
1994
|
+
return {
|
|
1995
|
+
...panel,
|
|
1996
|
+
title: song.title,
|
|
1997
|
+
content: /* @__PURE__ */ jsx27(
|
|
1998
|
+
SongPanelContent,
|
|
1999
|
+
{
|
|
2000
|
+
song,
|
|
2001
|
+
scope,
|
|
2002
|
+
getBackendBaseUrl,
|
|
2003
|
+
shareUrl: shareUrl ?? void 0,
|
|
2004
|
+
renderArtistLink,
|
|
2005
|
+
genrePath: song.genre ? getGenrePath(song.genre) : [],
|
|
2006
|
+
onSelectGenre
|
|
2007
|
+
}
|
|
2008
|
+
)
|
|
1890
2009
|
};
|
|
1891
2010
|
}
|
|
1892
2011
|
}
|
|
@@ -1895,12 +2014,12 @@ function useArtistPanel({
|
|
|
1895
2014
|
// src/genre-tree/SizeMetricToggle.tsx
|
|
1896
2015
|
import { MdMusicNote, MdVisibility } from "react-icons/md";
|
|
1897
2016
|
import { SegmentedControl } from "@behindthemusictree/ui";
|
|
1898
|
-
import { jsx as
|
|
2017
|
+
import { jsx as jsx28 } from "react/jsx-runtime";
|
|
1899
2018
|
function SizeMetricToggle({
|
|
1900
2019
|
value,
|
|
1901
2020
|
onChange
|
|
1902
2021
|
}) {
|
|
1903
|
-
return /* @__PURE__ */
|
|
2022
|
+
return /* @__PURE__ */ jsx28(
|
|
1904
2023
|
SegmentedControl,
|
|
1905
2024
|
{
|
|
1906
2025
|
variant: "floating",
|
|
@@ -1908,26 +2027,19 @@ function SizeMetricToggle({
|
|
|
1908
2027
|
value,
|
|
1909
2028
|
onChange,
|
|
1910
2029
|
options: [
|
|
1911
|
-
{ value: "songs", label: /* @__PURE__ */
|
|
1912
|
-
{ value: "views", label: /* @__PURE__ */
|
|
2030
|
+
{ value: "songs", label: /* @__PURE__ */ jsx28(MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
|
|
2031
|
+
{ value: "views", label: /* @__PURE__ */ jsx28(MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
|
|
1913
2032
|
]
|
|
1914
2033
|
}
|
|
1915
2034
|
);
|
|
1916
2035
|
}
|
|
1917
2036
|
|
|
1918
2037
|
// src/genre-tree/GenreTreeView.tsx
|
|
1919
|
-
import { Fragment as
|
|
1920
|
-
var PREVIOUS_BUTTON_CSS = `
|
|
1921
|
-
.genre-tree-view .search-overlay .gtv-info-panel-previous { display: none; }
|
|
1922
|
-
.genre-tree-view:has(.search-overlay .gtv-info-panel-previous) .genre-search input { padding-left: 2.75rem; }
|
|
1923
|
-
.genre-tree-view:has(.search-overlay .gtv-info-panel-previous) .genre-search-previous { display: flex; }
|
|
1924
|
-
@keyframes genre-tree-view-previous-in { from { opacity: 0; transform: translate(-0.5rem, -50%); } to { opacity: 1; transform: translate(0, -50%); } }
|
|
1925
|
-
@media (prefers-reduced-motion: no-preference) {
|
|
1926
|
-
.genre-search-previous { animation: genre-tree-view-previous-in 200ms ease-out; }
|
|
1927
|
-
}
|
|
1928
|
-
`;
|
|
2038
|
+
import { Fragment as Fragment11, jsx as jsx29, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
1929
2039
|
var NO_POP_ROOT_TITLE = "This genre tree has no 'Mainstream Pop' root yet";
|
|
1930
|
-
var REGIONS_CLASS = "[--gtv-center-left:var(--layout-center-left)] [--gtv-center-right:var(--layout-center-right)] [--gtv-center-top:var(--layout-band-top)]
|
|
2040
|
+
var REGIONS_CLASS = "[--gtv-center-left:var(--layout-center-left)] [--gtv-center-right:var(--layout-center-right)] [--gtv-center-top:var(--layout-band-top)]";
|
|
2041
|
+
var noop = () => {
|
|
2042
|
+
};
|
|
1931
2043
|
function GenreTreeView({
|
|
1932
2044
|
scope,
|
|
1933
2045
|
handleGenreCreationAction,
|
|
@@ -1960,6 +2072,13 @@ function GenreTreeView({
|
|
|
1960
2072
|
artistSearch = true,
|
|
1961
2073
|
onArtistSelect,
|
|
1962
2074
|
artistSearchBaseUrl,
|
|
2075
|
+
songUuid: controlledSongUuid,
|
|
2076
|
+
onSongUuidChange,
|
|
2077
|
+
songExpanded: controlledSongExpanded,
|
|
2078
|
+
onSongExpandedChange,
|
|
2079
|
+
songSearch = true,
|
|
2080
|
+
onSongSelect,
|
|
2081
|
+
renderSongLink,
|
|
1963
2082
|
renderSummary,
|
|
1964
2083
|
renderEssentialTracks,
|
|
1965
2084
|
renderSongLeading,
|
|
@@ -1976,10 +2095,17 @@ function GenreTreeView({
|
|
|
1976
2095
|
}) {
|
|
1977
2096
|
const getShareUrlRef = useRef4(getShareUrl);
|
|
1978
2097
|
getShareUrlRef.current = getShareUrl;
|
|
1979
|
-
const [internalArtistUuid, setInternalArtistUuid] =
|
|
2098
|
+
const [internalArtistUuid, setInternalArtistUuid] = useState7(null);
|
|
1980
2099
|
const isArtistControlled = controlledArtistUuid !== void 0;
|
|
1981
2100
|
const artistUuid = isArtistControlled ? controlledArtistUuid : internalArtistUuid;
|
|
1982
|
-
const [
|
|
2101
|
+
const [internalSongUuid, setInternalSongUuid] = useState7(null);
|
|
2102
|
+
const isSongControlled = controlledSongUuid !== void 0;
|
|
2103
|
+
const songUuid = isSongControlled ? controlledSongUuid : internalSongUuid;
|
|
2104
|
+
const changeSong = (uuid) => {
|
|
2105
|
+
if (!isSongControlled) setInternalSongUuid(uuid);
|
|
2106
|
+
onSongUuidChange?.(uuid);
|
|
2107
|
+
};
|
|
2108
|
+
const [internalSizeMetric, setInternalSizeMetric] = useState7("songs");
|
|
1983
2109
|
const sizeMetric = controlledSizeMetric ?? internalSizeMetric;
|
|
1984
2110
|
const deferredSizeMetric = useDeferredValue(sizeMetric);
|
|
1985
2111
|
const changeSizeMetric = (metric) => {
|
|
@@ -2018,7 +2144,7 @@ function GenreTreeView({
|
|
|
2018
2144
|
artistUuid
|
|
2019
2145
|
});
|
|
2020
2146
|
const restoreRevision = viewportRestore?.revision;
|
|
2021
|
-
const [stackedRestoreOwner, setStackedRestoreOwner] =
|
|
2147
|
+
const [stackedRestoreOwner, setStackedRestoreOwner] = useState7({ revision: restoreRevision, rootUuid: selectedGenrePlaylist?.root.uuid });
|
|
2022
2148
|
let restoreRootUuid = stackedRestoreOwner.rootUuid;
|
|
2023
2149
|
if (stackedRestoreOwner.revision !== restoreRevision) {
|
|
2024
2150
|
restoreRootUuid = selectedGenrePlaylist?.root.uuid;
|
|
@@ -2042,27 +2168,71 @@ function GenreTreeView({
|
|
|
2042
2168
|
const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: visibleGenrePlaylists, artist: filterArtist });
|
|
2043
2169
|
const openArtist = (uuid) => {
|
|
2044
2170
|
if (selectedNodeId !== null) clearSelection();
|
|
2171
|
+
if (songUuid !== null) changeSong(null);
|
|
2045
2172
|
changeArtist(uuid);
|
|
2046
2173
|
};
|
|
2047
|
-
const
|
|
2174
|
+
const openSong = (uuid) => {
|
|
2175
|
+
if (selectedNodeId !== null) clearSelection();
|
|
2176
|
+
changeSong(uuid);
|
|
2177
|
+
};
|
|
2178
|
+
const openSongRef = useRef4(openSong);
|
|
2179
|
+
openSongRef.current = openSong;
|
|
2180
|
+
const resolvedRenderSongLink = useCallback6(
|
|
2181
|
+
(song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ jsx29("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
|
|
2182
|
+
[renderSongLink]
|
|
2183
|
+
);
|
|
2184
|
+
const [expandedSongUuid, setExpandedSongUuid] = useState7(null);
|
|
2185
|
+
const [expandedArtistUuid, setExpandedArtistUuid] = useState7(null);
|
|
2048
2186
|
const artistBaseUrl = artistSearchBaseUrl ?? getBackendBaseUrl;
|
|
2187
|
+
const songBaseUrl = getBackendBaseUrl;
|
|
2049
2188
|
const artistPanel = useArtistPanel({
|
|
2050
2189
|
uuid: artistUuid,
|
|
2051
2190
|
scope,
|
|
2052
2191
|
getBackendBaseUrl: artistBaseUrl,
|
|
2053
2192
|
getShareUrl: getShareUrlRef.current,
|
|
2054
|
-
renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */
|
|
2193
|
+
renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ jsx29("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
|
|
2055
2194
|
expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
|
|
2056
2195
|
onExpandedChange: (on) => {
|
|
2057
2196
|
if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
|
|
2058
2197
|
onArtistExpandedChange?.(on);
|
|
2059
2198
|
},
|
|
2199
|
+
renderSongLink: resolvedRenderSongLink,
|
|
2060
2200
|
onClose: () => changeArtist(null)
|
|
2061
2201
|
});
|
|
2062
|
-
const
|
|
2063
|
-
const
|
|
2064
|
-
|
|
2065
|
-
|
|
2202
|
+
const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ jsx29("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
|
|
2203
|
+
const getGenrePath = (genre) => {
|
|
2204
|
+
const path = [];
|
|
2205
|
+
let current = visibleGenrePlaylists.find((playlist) => playlist.criteria?.uuid === genre.uuid);
|
|
2206
|
+
while (current) {
|
|
2207
|
+
path.unshift(current);
|
|
2208
|
+
const parentUuid = current.parent?.uuid;
|
|
2209
|
+
current = parentUuid ? visibleGenrePlaylists.find((playlist) => playlist.uuid === parentUuid) : void 0;
|
|
2210
|
+
}
|
|
2211
|
+
return path;
|
|
2212
|
+
};
|
|
2213
|
+
const songPanel = useSongPanel({
|
|
2214
|
+
uuid: songUuid,
|
|
2215
|
+
scope,
|
|
2216
|
+
getBackendBaseUrl: songBaseUrl,
|
|
2217
|
+
getShareUrl: getShareUrlRef.current,
|
|
2218
|
+
renderArtistLink: resolvedRenderArtistLink,
|
|
2219
|
+
getGenrePath,
|
|
2220
|
+
onSelectGenre: (genrePlaylist) => {
|
|
2221
|
+
changeSong(null);
|
|
2222
|
+
selectGenrePlaylist(genrePlaylist);
|
|
2223
|
+
},
|
|
2224
|
+
expanded: controlledSongExpanded ?? (songUuid !== null && expandedSongUuid === songUuid),
|
|
2225
|
+
onExpandedChange: (on) => {
|
|
2226
|
+
if (controlledSongExpanded === void 0) setExpandedSongUuid(on ? songUuid : null);
|
|
2227
|
+
onSongExpandedChange?.(on);
|
|
2228
|
+
},
|
|
2229
|
+
onClose: () => changeSong(null)
|
|
2230
|
+
});
|
|
2231
|
+
const builtInSongPanel = overlayPanel || hasSelectedGenreNode ? void 0 : songPanel;
|
|
2232
|
+
const builtInArtistPanel = overlayPanel || hasSelectedGenreNode || builtInSongPanel ? void 0 : artistPanel;
|
|
2233
|
+
const builtInPanel = builtInSongPanel ?? builtInArtistPanel;
|
|
2234
|
+
const [uncontrolledCollapsed, setUncontrolledCollapsed] = useState7(false);
|
|
2235
|
+
const [uncontrolledExpanded, setUncontrolledExpanded] = useState7(false);
|
|
2066
2236
|
const isCollapsed = collapsed ?? uncontrolledCollapsed;
|
|
2067
2237
|
const isExpanded = expanded ?? uncontrolledExpanded;
|
|
2068
2238
|
const changeCollapsed = (next) => {
|
|
@@ -2073,54 +2243,56 @@ function GenreTreeView({
|
|
|
2073
2243
|
setUncontrolledExpanded(next);
|
|
2074
2244
|
onExpandedChange?.(next);
|
|
2075
2245
|
};
|
|
2076
|
-
|
|
2246
|
+
useEffect5(() => {
|
|
2077
2247
|
setUncontrolledCollapsed(false);
|
|
2078
|
-
}, [selectedNodeId, artistUuid]);
|
|
2079
|
-
const
|
|
2080
|
-
useLeftDetailPanelLayout(!isDetailHidden, placement === "regions");
|
|
2081
|
-
const detailOverlay = placement === "regions" && activeOverlayPanel?.onExpandedChange ? activeOverlayPanel : void 0;
|
|
2082
|
-
const detailExpanded = detailOverlay ? !!detailOverlay.expanded : isExpanded;
|
|
2083
|
-
const onDetailExpandedChange = detailOverlay ? detailOverlay.onExpandedChange : changeExpanded;
|
|
2084
|
-
const infoPanelCuration = {
|
|
2085
|
-
renderDangerZone,
|
|
2086
|
-
expanded: placement === "regions" ? isExpanded : expanded,
|
|
2087
|
-
onExpandedChange: placement === "regions" ? changeExpanded : onExpandedChange,
|
|
2088
|
-
collapsed: placement === "regions" ? isCollapsed : void 0,
|
|
2089
|
-
hideExpandButton: placement === "regions" ? true : void 0,
|
|
2090
|
-
overlayPanel: detailOverlay ? { ...detailOverlay, collapsed: isCollapsed, hideExpandButton: true } : activeOverlayPanel
|
|
2091
|
-
};
|
|
2092
|
-
const [isWheelRevealed, setIsWheelRevealed] = useState5(false);
|
|
2248
|
+
}, [selectedNodeId, artistUuid, songUuid]);
|
|
2249
|
+
const [isWheelRevealed, setIsWheelRevealed] = useState7(false);
|
|
2093
2250
|
const usesHandoff = viewMode === "wheel" || viewMode === "pop-core";
|
|
2094
2251
|
const isTreeReady = !isLoading && (!usesHandoff || isWheelRevealed);
|
|
2095
|
-
const
|
|
2096
|
-
const
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2252
|
+
const [resolveStyle, setResolveStyle] = useState7(null);
|
|
2253
|
+
const handleNodeStyleResolverChange = useCallback6(
|
|
2254
|
+
(resolver) => setResolveStyle(() => resolver),
|
|
2255
|
+
[]
|
|
2256
|
+
);
|
|
2257
|
+
const panelRef = useRef4(null);
|
|
2258
|
+
const obscuredInset = useObscuredInset(panelRef);
|
|
2259
|
+
const hostPanel = { obscuredInset, onNodeStyleResolverChange: handleNodeStyleResolverChange };
|
|
2260
|
+
const nodeMetric = isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric;
|
|
2261
|
+
const treeNodes = useMemo7(() => toTreeNodes(visibleGenrePlaylists, nodeMetric), [visibleGenrePlaylists, nodeMetric]);
|
|
2262
|
+
const relations = useGenreNodeRelations(
|
|
2263
|
+
treeNodes,
|
|
2264
|
+
!overlayPanel && hasSelectedGenreNode ? selectedNodeId : null,
|
|
2265
|
+
resolveStyle
|
|
2266
|
+
);
|
|
2267
|
+
const [selectionHistory, setSelectionHistory] = useState7({
|
|
2268
|
+
current: selectedNodeId,
|
|
2269
|
+
previous: null
|
|
2270
|
+
});
|
|
2271
|
+
if (selectionHistory.current !== selectedNodeId) {
|
|
2272
|
+
setSelectionHistory({
|
|
2273
|
+
current: selectedNodeId,
|
|
2274
|
+
previous: selectionHistory.current ?? selectionHistory.previous
|
|
2275
|
+
});
|
|
2276
|
+
}
|
|
2277
|
+
const previousPlaylist = selectionHistory.previous !== null && selectionHistory.previous !== selectedNodeId ? genrePlaylistByNodeId.get(selectionHistory.previous) : void 0;
|
|
2278
|
+
const handlePrevious = previousPlaylist && visibleGenrePlaylists.includes(previousPlaylist) ? () => handleGenreSearchSelect(previousPlaylist) : void 0;
|
|
2108
2279
|
const { showPopup, hidePopup } = usePopup5();
|
|
2109
|
-
const [searchText, setSearchText] =
|
|
2280
|
+
const [searchText, setSearchText] = useState7("");
|
|
2110
2281
|
const artistSearchResult = useArtistSearch(artistBaseUrl, artistSearch ? searchText : "");
|
|
2111
|
-
const
|
|
2282
|
+
const artistGroup = artistSearch ? {
|
|
2283
|
+
key: "artists",
|
|
2112
2284
|
label: "Artists",
|
|
2113
2285
|
items: artistSearchResult.artists.map((artist) => ({
|
|
2114
2286
|
key: artist.uuid,
|
|
2115
|
-
content: /* @__PURE__ */
|
|
2116
|
-
/* @__PURE__ */
|
|
2287
|
+
content: /* @__PURE__ */ jsxs22(Fragment11, { children: [
|
|
2288
|
+
/* @__PURE__ */ jsxs22("span", { className: "block truncate font-medium", children: [
|
|
2117
2289
|
artist.name,
|
|
2118
|
-
artist.disambiguation && /* @__PURE__ */
|
|
2290
|
+
artist.disambiguation && /* @__PURE__ */ jsxs22("span", { className: "font-normal text-[var(--color-neutral-500)]", children: [
|
|
2119
2291
|
" \xB7 ",
|
|
2120
2292
|
artist.disambiguation
|
|
2121
2293
|
] })
|
|
2122
2294
|
] }),
|
|
2123
|
-
artist.matchedAlias && /* @__PURE__ */
|
|
2295
|
+
artist.matchedAlias && /* @__PURE__ */ jsxs22("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [
|
|
2124
2296
|
"also known as ",
|
|
2125
2297
|
artist.matchedAlias
|
|
2126
2298
|
] })
|
|
@@ -2133,11 +2305,32 @@ function GenreTreeView({
|
|
|
2133
2305
|
status: artistSearchResult.isError ? "Search failed" : artistSearchResult.settled && artistSearchResult.artists.length === 0 ? "No artists found" : "",
|
|
2134
2306
|
loading: artistSearchResult.pending
|
|
2135
2307
|
} : void 0;
|
|
2308
|
+
const songSearchResult = useSongSearch(songBaseUrl, songSearch ? searchText : "");
|
|
2309
|
+
const songGroup = songSearch ? {
|
|
2310
|
+
key: "songs",
|
|
2311
|
+
label: "Songs",
|
|
2312
|
+
items: songSearchResult.songs.map((song) => ({
|
|
2313
|
+
key: song.uuid,
|
|
2314
|
+
content: /* @__PURE__ */ jsxs22("span", { className: isSongSearchResultPlayable(song) ? void 0 : "opacity-60", children: [
|
|
2315
|
+
/* @__PURE__ */ jsx29("span", { className: "block truncate font-medium", children: song.title }),
|
|
2316
|
+
/* @__PURE__ */ jsx29("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [song.artists.map((artist) => artist.name).join(", "), song.genre?.name ?? "No genre"].filter(Boolean).join(" \xB7 ") })
|
|
2317
|
+
] }),
|
|
2318
|
+
onSelect: () => {
|
|
2319
|
+
openSong(song.uuid);
|
|
2320
|
+
onSongSelect?.(song);
|
|
2321
|
+
}
|
|
2322
|
+
})),
|
|
2323
|
+
status: songSearchResult.isError ? "Search failed" : songSearchResult.settled && songSearchResult.songs.length === 0 ? "No songs found" : "",
|
|
2324
|
+
loading: songSearchResult.pending
|
|
2325
|
+
} : void 0;
|
|
2326
|
+
const extraGroups = [artistGroup, songGroup].filter((group) => group !== void 0);
|
|
2136
2327
|
const handleSelectedNodeChange = (node) => {
|
|
2328
|
+
if (node !== null && songUuid !== null) changeSong(null);
|
|
2137
2329
|
selectNode(node);
|
|
2138
2330
|
};
|
|
2139
2331
|
const handleGenreSearchClear = clearSelection;
|
|
2140
2332
|
const handleGenreSearchSelect = (genrePlaylist) => {
|
|
2333
|
+
if (songUuid !== null) changeSong(null);
|
|
2141
2334
|
selectGenrePlaylist(genrePlaylist);
|
|
2142
2335
|
};
|
|
2143
2336
|
const selectedUuid = selectedGenrePlaylist?.uuid;
|
|
@@ -2148,34 +2341,47 @@ function GenreTreeView({
|
|
|
2148
2341
|
};
|
|
2149
2342
|
const zoomInRef = useRef4(void 0);
|
|
2150
2343
|
zoomInRef.current = canZoomIn ? handleGenreSearchZoomIn : void 0;
|
|
2151
|
-
const
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2344
|
+
const selectGenreFromPanel = (nodeId) => {
|
|
2345
|
+
const genrePlaylist = genrePlaylistByNodeId.get(nodeId);
|
|
2346
|
+
if (genrePlaylist) handleGenreSearchSelect(genrePlaylist);
|
|
2347
|
+
};
|
|
2348
|
+
const renderGenreBody = (relation) => {
|
|
2349
|
+
const { node } = relation;
|
|
2350
|
+
const family = /* @__PURE__ */ jsx29(
|
|
2351
|
+
GenreNodeFamily,
|
|
2352
|
+
{
|
|
2353
|
+
node,
|
|
2354
|
+
fill: relation.fill,
|
|
2355
|
+
textColor: relation.textColor,
|
|
2356
|
+
parentNode: relation.parentNode,
|
|
2357
|
+
childNodes: relation.childNodes,
|
|
2358
|
+
onSelectNode: selectGenreFromPanel
|
|
2359
|
+
}
|
|
2360
|
+
);
|
|
2361
|
+
const ancestors = /* @__PURE__ */ jsx29(GenreNodeAncestors, { ancestorNodes: relation.ancestorNodes, onSelectNode: selectGenreFromPanel });
|
|
2362
|
+
const genrePlaylist = genrePlaylistByNodeId.get(node.id);
|
|
2363
|
+
const criteria = genrePlaylist?.criteria;
|
|
2364
|
+
if (!genrePlaylist || !criteria) return { primary: family, aside: ancestors };
|
|
2365
|
+
const genreUuid = criteria.uuid;
|
|
2366
|
+
const parentPlaylist = visibleGenrePlaylists.find(({ uuid }) => uuid === genrePlaylist.parent?.uuid);
|
|
2367
|
+
const canToggleSide = !readOnly && parentPlaylist !== void 0 && parentPlaylist.parent === null;
|
|
2368
|
+
const side = criteria.side ?? "core";
|
|
2369
|
+
const shareTarget = {
|
|
2370
|
+
kind: "genre",
|
|
2371
|
+
genrePlaylistUuid: genrePlaylist.uuid,
|
|
2372
|
+
name: criteria.name,
|
|
2373
|
+
scope,
|
|
2374
|
+
artistUuid
|
|
2375
|
+
};
|
|
2376
|
+
const shareUrl = getShareUrlRef.current?.(shareTarget) ?? void 0;
|
|
2377
|
+
const popSibling = canToggleSide && side === "core" ? visibleGenrePlaylists.find(
|
|
2378
|
+
(sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist.uuid && sibling.criteria?.side === "pop"
|
|
2379
|
+
) : void 0;
|
|
2380
|
+
return {
|
|
2381
|
+
primary: /* @__PURE__ */ jsxs22(Fragment11, { children: [
|
|
2382
|
+
/* @__PURE__ */ jsx29(
|
|
2176
2383
|
GenreInfoPanelBody,
|
|
2177
2384
|
{
|
|
2178
|
-
toolbar,
|
|
2179
2385
|
family,
|
|
2180
2386
|
genreUuid,
|
|
2181
2387
|
genrePlaylist,
|
|
@@ -2191,7 +2397,7 @@ function GenreTreeView({
|
|
|
2191
2397
|
onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
|
|
2192
2398
|
onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
|
|
2193
2399
|
onChangeParent: readOnly ? void 0 : () => showPopup(
|
|
2194
|
-
/* @__PURE__ */
|
|
2400
|
+
/* @__PURE__ */ jsx29(
|
|
2195
2401
|
ChangeParentPopup,
|
|
2196
2402
|
{
|
|
2197
2403
|
genrePlaylist: { ...genrePlaylist, criteria },
|
|
@@ -2212,6 +2418,7 @@ function GenreTreeView({
|
|
|
2212
2418
|
criteriaOverviewSchema,
|
|
2213
2419
|
renderGenreDetailExtras,
|
|
2214
2420
|
renderArtistLink,
|
|
2421
|
+
renderSongLink: resolvedRenderSongLink,
|
|
2215
2422
|
artist: filterArtist,
|
|
2216
2423
|
renderSummary,
|
|
2217
2424
|
renderEssentialTracks,
|
|
@@ -2221,48 +2428,65 @@ function GenreTreeView({
|
|
|
2221
2428
|
}
|
|
2222
2429
|
}
|
|
2223
2430
|
),
|
|
2224
|
-
|
|
2225
|
-
}
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
additionalActions,
|
|
2230
|
-
readOnly,
|
|
2231
|
-
handleGenreCreationAction,
|
|
2232
|
-
handleGenreRenameAction,
|
|
2233
|
-
showPopup,
|
|
2234
|
-
hidePopup,
|
|
2235
|
-
onParentChanged,
|
|
2236
|
-
visibleGenrePlaylists,
|
|
2237
|
-
updateGenre,
|
|
2238
|
-
playback,
|
|
2239
|
-
filterArtist,
|
|
2240
|
-
scope,
|
|
2241
|
-
getBackendBaseUrl,
|
|
2242
|
-
criteriaOverviewSchema,
|
|
2243
|
-
renderGenreDetailExtras,
|
|
2244
|
-
renderArtistLink,
|
|
2245
|
-
renderSummary,
|
|
2246
|
-
renderEssentialTracks,
|
|
2247
|
-
renderSongLeading,
|
|
2248
|
-
renderSongActions,
|
|
2249
|
-
renderTracksToolbar
|
|
2250
|
-
]
|
|
2251
|
-
);
|
|
2431
|
+
renderDangerZone?.(node)
|
|
2432
|
+
] }),
|
|
2433
|
+
aside: ancestors
|
|
2434
|
+
};
|
|
2435
|
+
};
|
|
2252
2436
|
const isRotationControlled = controlledAllowWheelRotation !== void 0;
|
|
2253
2437
|
const allowWheelRotation = controlledAllowWheelRotation ?? internalAllowWheelRotation;
|
|
2254
|
-
|
|
2255
|
-
|
|
2438
|
+
let activePanel = null;
|
|
2439
|
+
if (overlayPanel) {
|
|
2440
|
+
activePanel = {
|
|
2441
|
+
title: overlayPanel.title,
|
|
2442
|
+
content: overlayPanel.content,
|
|
2443
|
+
aside: overlayPanel.extraContent,
|
|
2444
|
+
expanded: overlayPanel.expanded ?? false,
|
|
2445
|
+
onExpandedChange: overlayPanel.onExpandedChange,
|
|
2446
|
+
onClose: overlayPanel.onClose,
|
|
2447
|
+
compact: false
|
|
2448
|
+
};
|
|
2449
|
+
} else if (relations) {
|
|
2450
|
+
const { primary, aside } = renderGenreBody(relations);
|
|
2451
|
+
activePanel = {
|
|
2452
|
+
title: relations.node.name,
|
|
2453
|
+
content: primary,
|
|
2454
|
+
aside,
|
|
2455
|
+
expanded: isExpanded,
|
|
2456
|
+
onExpandedChange: changeExpanded,
|
|
2457
|
+
onClose: clearSelection,
|
|
2458
|
+
compact: true,
|
|
2459
|
+
headerAccent: { background: relations.fill, color: relations.textColor }
|
|
2460
|
+
};
|
|
2461
|
+
} else if (builtInPanel) {
|
|
2462
|
+
activePanel = {
|
|
2463
|
+
title: builtInPanel.title,
|
|
2464
|
+
content: builtInPanel.content,
|
|
2465
|
+
aside: builtInPanel.extraContent,
|
|
2466
|
+
expanded: builtInPanel.expanded,
|
|
2467
|
+
onExpandedChange: builtInPanel.onExpandedChange,
|
|
2468
|
+
onClose: builtInPanel.onClose,
|
|
2469
|
+
compact: true
|
|
2470
|
+
};
|
|
2471
|
+
}
|
|
2472
|
+
const isPanelShown = isTreeReady && activePanel !== null;
|
|
2473
|
+
const detailTab = placement === "regions" && activePanel?.onExpandedChange ? {
|
|
2474
|
+
edge: activePanel.expanded ? { label: "Shrink panel", onClick: () => activePanel.onExpandedChange?.(false) } : isCollapsed ? null : { label: "Hide panel", onClick: () => changeCollapsed(true) },
|
|
2475
|
+
center: activePanel.expanded ? null : isCollapsed ? { label: "Show panel", onClick: () => changeCollapsed(false) } : activePanel.onExpandedChange ? { label: "Extend panel", onClick: () => activePanel.onExpandedChange?.(true) } : null
|
|
2476
|
+
} : void 0;
|
|
2477
|
+
const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ jsx29(
|
|
2478
|
+
IconButton,
|
|
2256
2479
|
{
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2480
|
+
"aria-label": activePanel.expanded ? "Shrink panel" : "Extend panel",
|
|
2481
|
+
style: activePanel.headerAccent && { color: "inherit" },
|
|
2482
|
+
onClick: () => activePanel.onExpandedChange?.(!activePanel.expanded),
|
|
2483
|
+
children: activePanel.expanded ? /* @__PURE__ */ jsx29(Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ jsx29(Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
2260
2484
|
}
|
|
2261
2485
|
) : null;
|
|
2262
2486
|
const renderArtistFilter = () => {
|
|
2263
2487
|
if (isLoading || !filterArtist) return null;
|
|
2264
|
-
return /* @__PURE__ */
|
|
2265
|
-
/* @__PURE__ */
|
|
2488
|
+
return /* @__PURE__ */ jsxs22("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
|
|
2489
|
+
/* @__PURE__ */ jsx29(
|
|
2266
2490
|
"button",
|
|
2267
2491
|
{
|
|
2268
2492
|
type: "button",
|
|
@@ -2274,7 +2498,7 @@ function GenreTreeView({
|
|
|
2274
2498
|
children: filterArtist.name
|
|
2275
2499
|
}
|
|
2276
2500
|
),
|
|
2277
|
-
/* @__PURE__ */
|
|
2501
|
+
/* @__PURE__ */ jsx29(
|
|
2278
2502
|
"button",
|
|
2279
2503
|
{
|
|
2280
2504
|
type: "button",
|
|
@@ -2286,22 +2510,22 @@ function GenreTreeView({
|
|
|
2286
2510
|
)
|
|
2287
2511
|
] });
|
|
2288
2512
|
};
|
|
2289
|
-
const actions = /* @__PURE__ */
|
|
2290
|
-
!isLoading && /* @__PURE__ */
|
|
2513
|
+
const actions = /* @__PURE__ */ jsxs22(Fragment11, { children: [
|
|
2514
|
+
!isLoading && /* @__PURE__ */ jsx29(
|
|
2291
2515
|
GenreSearch,
|
|
2292
2516
|
{
|
|
2293
2517
|
genrePlaylists: visibleGenrePlaylists,
|
|
2294
2518
|
onSelect: handleGenreSearchSelect,
|
|
2295
|
-
selectedName: selectedGenrePlaylist?.name ??
|
|
2296
|
-
onClear: builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
|
|
2519
|
+
selectedName: selectedGenrePlaylist?.name ?? (typeof builtInPanel?.title === "string" ? builtInPanel.title : null),
|
|
2520
|
+
onClear: builtInSongPanel ? () => changeSong(null) : builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
|
|
2297
2521
|
onPrevious: handlePrevious,
|
|
2298
|
-
onQueryChange: artistSearch ? setSearchText : void 0,
|
|
2299
|
-
|
|
2522
|
+
onQueryChange: artistSearch || songSearch ? setSearchText : void 0,
|
|
2523
|
+
extraGroups
|
|
2300
2524
|
}
|
|
2301
2525
|
),
|
|
2302
|
-
!isLoading && filterArtist && /* @__PURE__ */
|
|
2303
|
-
/* @__PURE__ */
|
|
2304
|
-
!isLoading && !isControlled && /* @__PURE__ */
|
|
2526
|
+
!isLoading && filterArtist && /* @__PURE__ */ jsx29("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
|
|
2527
|
+
/* @__PURE__ */ jsxs22("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: [
|
|
2528
|
+
!isLoading && !isControlled && /* @__PURE__ */ jsx29(
|
|
2305
2529
|
SegmentedControl2,
|
|
2306
2530
|
{
|
|
2307
2531
|
"aria-label": "Tree view mode",
|
|
@@ -2315,13 +2539,13 @@ function GenreTreeView({
|
|
|
2315
2539
|
]
|
|
2316
2540
|
}
|
|
2317
2541
|
),
|
|
2318
|
-
!isLoading && showDisplayOptions && /* @__PURE__ */
|
|
2542
|
+
!isLoading && showDisplayOptions && /* @__PURE__ */ jsx29(
|
|
2319
2543
|
"div",
|
|
2320
2544
|
{
|
|
2321
2545
|
className: "flex items-center gap-1",
|
|
2322
2546
|
role: "group",
|
|
2323
2547
|
"aria-label": "Tree display options",
|
|
2324
|
-
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */
|
|
2548
|
+
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ jsx29(
|
|
2325
2549
|
Button,
|
|
2326
2550
|
{
|
|
2327
2551
|
variant: allowWheelRotation ? "default" : "outline",
|
|
@@ -2332,7 +2556,7 @@ function GenreTreeView({
|
|
|
2332
2556
|
)
|
|
2333
2557
|
}
|
|
2334
2558
|
),
|
|
2335
|
-
!isLoading && !readOnly && showAddRoot && /* @__PURE__ */
|
|
2559
|
+
!isLoading && !readOnly && showAddRoot && /* @__PURE__ */ jsx29(
|
|
2336
2560
|
IconTextButton,
|
|
2337
2561
|
{
|
|
2338
2562
|
icon: Plus2,
|
|
@@ -2342,42 +2566,34 @@ function GenreTreeView({
|
|
|
2342
2566
|
)
|
|
2343
2567
|
] })
|
|
2344
2568
|
] });
|
|
2345
|
-
return /* @__PURE__ */
|
|
2569
|
+
return /* @__PURE__ */ jsxs22(
|
|
2346
2570
|
"div",
|
|
2347
2571
|
{
|
|
2348
|
-
|
|
2572
|
+
"data-left-panel": isPanelShown && !isCollapsed ? void 0 : "hidden",
|
|
2349
2573
|
className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
|
|
2350
2574
|
children: [
|
|
2351
|
-
/* @__PURE__ */
|
|
2352
|
-
/* @__PURE__ */ jsx28(
|
|
2575
|
+
/* @__PURE__ */ jsx29(
|
|
2353
2576
|
"div",
|
|
2354
2577
|
{
|
|
2355
2578
|
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"}`,
|
|
2356
2579
|
children: actions
|
|
2357
2580
|
}
|
|
2358
2581
|
),
|
|
2359
|
-
!isLoading && filterArtist && /* @__PURE__ */
|
|
2582
|
+
!isLoading && filterArtist && /* @__PURE__ */ jsx29(
|
|
2360
2583
|
"div",
|
|
2361
2584
|
{
|
|
2362
2585
|
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"}`,
|
|
2363
2586
|
children: renderArtistFilter()
|
|
2364
2587
|
}
|
|
2365
2588
|
),
|
|
2366
|
-
|
|
2367
|
-
"div",
|
|
2368
|
-
{
|
|
2369
|
-
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))]"}`,
|
|
2370
|
-
children: detailTab
|
|
2371
|
-
}
|
|
2372
|
-
),
|
|
2373
|
-
/* @__PURE__ */ jsx28("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ jsxs21("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
|
|
2374
|
-
!isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ jsx28("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
|
|
2375
|
-
isLoading ? /* @__PURE__ */ jsx28(GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
|
|
2589
|
+
/* @__PURE__ */ jsx29("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ jsxs22("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
|
|
2590
|
+
!isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ jsx29("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
|
|
2591
|
+
isLoading ? /* @__PURE__ */ jsx29(GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
|
|
2376
2592
|
// The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
|
|
2377
2593
|
// and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
|
|
2378
2594
|
// Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
|
|
2379
2595
|
// The panel's own Zoom in button is hidden: the search bar carries it.
|
|
2380
|
-
/* @__PURE__ */
|
|
2596
|
+
/* @__PURE__ */ jsx29("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ jsx29(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ jsx29(GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ jsx29(
|
|
2381
2597
|
GenrePlaylistTreeWheel,
|
|
2382
2598
|
{
|
|
2383
2599
|
scope,
|
|
@@ -2386,29 +2602,26 @@ function GenreTreeView({
|
|
|
2386
2602
|
handleGenreRenameAction,
|
|
2387
2603
|
additionalActions,
|
|
2388
2604
|
onSelectedNodeChange: handleSelectedNodeChange,
|
|
2389
|
-
hideInfoPanelClose: true,
|
|
2390
|
-
hideInfoPanelTitle: true,
|
|
2391
2605
|
onNodeHover: handleNodeHover,
|
|
2392
|
-
|
|
2606
|
+
...hostPanel,
|
|
2393
2607
|
playback,
|
|
2394
2608
|
selectedNodeId,
|
|
2395
2609
|
readOnly,
|
|
2396
2610
|
allowWheelRotation,
|
|
2397
2611
|
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2398
2612
|
busy,
|
|
2399
|
-
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */
|
|
2613
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ jsxs22("div", { className: "flex items-center gap-2", children: [
|
|
2400
2614
|
floatingControlsLeading,
|
|
2401
|
-
canSizeByViews && /* @__PURE__ */
|
|
2615
|
+
canSizeByViews && /* @__PURE__ */ jsx29(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2402
2616
|
] }) : void 0,
|
|
2403
2617
|
defaultViewport,
|
|
2404
2618
|
viewportRestore,
|
|
2405
|
-
onViewportChange
|
|
2406
|
-
...infoPanelCuration
|
|
2619
|
+
onViewportChange
|
|
2407
2620
|
}
|
|
2408
2621
|
) }) })
|
|
2409
2622
|
) : viewMode === "pop-core" ? (
|
|
2410
2623
|
// Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
|
|
2411
|
-
/* @__PURE__ */
|
|
2624
|
+
/* @__PURE__ */ jsx29("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ jsx29(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ jsx29(GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ jsx29(
|
|
2412
2625
|
GenrePlaylistTreeWheelRadialPopCore,
|
|
2413
2626
|
{
|
|
2414
2627
|
scope,
|
|
@@ -2417,28 +2630,25 @@ function GenreTreeView({
|
|
|
2417
2630
|
handleGenreRenameAction,
|
|
2418
2631
|
additionalActions,
|
|
2419
2632
|
onSelectedNodeChange: handleSelectedNodeChange,
|
|
2420
|
-
hideInfoPanelClose: true,
|
|
2421
|
-
hideInfoPanelTitle: true,
|
|
2422
2633
|
onNodeHover: handleNodeHover,
|
|
2423
|
-
|
|
2634
|
+
...hostPanel,
|
|
2424
2635
|
playback,
|
|
2425
2636
|
selectedNodeId,
|
|
2426
2637
|
readOnly,
|
|
2427
2638
|
allowWheelRotation,
|
|
2428
2639
|
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2429
2640
|
busy,
|
|
2430
|
-
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */
|
|
2641
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ jsxs22("div", { className: "flex items-center gap-2", children: [
|
|
2431
2642
|
floatingControlsLeading,
|
|
2432
|
-
canSizeByViews && /* @__PURE__ */
|
|
2643
|
+
canSizeByViews && /* @__PURE__ */ jsx29(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2433
2644
|
] }) : void 0,
|
|
2434
2645
|
defaultViewport,
|
|
2435
2646
|
viewportRestore,
|
|
2436
2647
|
onViewportChange,
|
|
2437
|
-
...infoPanelCuration,
|
|
2438
2648
|
...focus
|
|
2439
2649
|
}
|
|
2440
2650
|
) }) })
|
|
2441
|
-
) : viewMode === "outline" ? /* @__PURE__ */
|
|
2651
|
+
) : viewMode === "outline" ? /* @__PURE__ */ jsx29("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ jsx29(
|
|
2442
2652
|
GenrePlaylistTreeWheelRadialPopCore,
|
|
2443
2653
|
{
|
|
2444
2654
|
outline: true,
|
|
@@ -2448,22 +2658,19 @@ function GenreTreeView({
|
|
|
2448
2658
|
handleGenreRenameAction,
|
|
2449
2659
|
additionalActions,
|
|
2450
2660
|
onSelectedNodeChange: handleSelectedNodeChange,
|
|
2451
|
-
hideInfoPanelClose: true,
|
|
2452
|
-
hideInfoPanelTitle: true,
|
|
2453
2661
|
onNodeHover: handleNodeHover,
|
|
2454
|
-
|
|
2662
|
+
...hostPanel,
|
|
2455
2663
|
playback,
|
|
2456
2664
|
selectedNodeId,
|
|
2457
|
-
readOnly
|
|
2458
|
-
...infoPanelCuration
|
|
2665
|
+
readOnly
|
|
2459
2666
|
}
|
|
2460
|
-
) }) : /* @__PURE__ */
|
|
2461
|
-
([uuid, genrePlaylistTreePerRoot]
|
|
2462
|
-
return /* @__PURE__ */
|
|
2667
|
+
) }) : /* @__PURE__ */ jsx29("div", { className: "tree-container relative flex flex-col gap-4 w-full pt-14 overflow-x-auto overflow-y-auto text-[var(--color-text)]", children: Object.entries(groupedGenrePlaylistsByRoot).map(
|
|
2668
|
+
([uuid, genrePlaylistTreePerRoot]) => {
|
|
2669
|
+
return /* @__PURE__ */ jsx29(
|
|
2463
2670
|
"div",
|
|
2464
2671
|
{
|
|
2465
2672
|
className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
|
|
2466
|
-
children: /* @__PURE__ */
|
|
2673
|
+
children: /* @__PURE__ */ jsx29("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ jsx29(
|
|
2467
2674
|
GenrePlaylistTreePerRoot,
|
|
2468
2675
|
{
|
|
2469
2676
|
scope,
|
|
@@ -2473,26 +2680,45 @@ function GenreTreeView({
|
|
|
2473
2680
|
handleGenreRenameAction,
|
|
2474
2681
|
additionalActions,
|
|
2475
2682
|
onSelectedNodeChange: handleSelectedNodeChange,
|
|
2476
|
-
hideInfoPanelClose: true,
|
|
2477
|
-
hideInfoPanelTitle: true,
|
|
2478
2683
|
onNodeHover: handleNodeHover,
|
|
2479
|
-
|
|
2684
|
+
...hostPanel,
|
|
2480
2685
|
playback,
|
|
2481
2686
|
selectedNodeId: uuid === selectedGenrePlaylist?.root.uuid ? selectedNodeId : null,
|
|
2482
2687
|
readOnly,
|
|
2483
2688
|
wheelZoom: "modifier",
|
|
2484
2689
|
defaultViewport: uuid === selectedGenrePlaylist?.root.uuid ? defaultViewport : void 0,
|
|
2485
2690
|
viewportRestore: uuid === selectedGenrePlaylist?.root.uuid && uuid === restoreRootUuid ? viewportRestore : void 0,
|
|
2486
|
-
onViewportChange: uuid === selectedGenrePlaylist?.root.uuid ? onViewportChange : void 0
|
|
2487
|
-
...infoPanelCuration,
|
|
2488
|
-
overlayPanel: rootIndex === 0 ? activeOverlayPanel : void 0
|
|
2691
|
+
onViewportChange: uuid === selectedGenrePlaylist?.root.uuid ? onViewportChange : void 0
|
|
2489
2692
|
}
|
|
2490
2693
|
) })
|
|
2491
2694
|
},
|
|
2492
2695
|
uuid
|
|
2493
2696
|
);
|
|
2494
2697
|
}
|
|
2495
|
-
) })
|
|
2698
|
+
) }),
|
|
2699
|
+
/* @__PURE__ */ jsx29(
|
|
2700
|
+
DetailPanel,
|
|
2701
|
+
{
|
|
2702
|
+
ref: panelRef,
|
|
2703
|
+
side: "left",
|
|
2704
|
+
title: activePanel?.title ?? "",
|
|
2705
|
+
hideTitle: activePanel?.compact,
|
|
2706
|
+
hideClose: activePanel?.compact,
|
|
2707
|
+
headerAccent: activePanel?.headerAccent,
|
|
2708
|
+
headerActions: expandButton,
|
|
2709
|
+
onClose: activePanel?.onClose ?? noop,
|
|
2710
|
+
collapsed: isCollapsed || !isTreeReady || !activePanel,
|
|
2711
|
+
expanded: activePanel?.expanded,
|
|
2712
|
+
onExpandedChange: activePanel?.onExpandedChange,
|
|
2713
|
+
aside: activePanel?.aside,
|
|
2714
|
+
edgeTab: detailTab,
|
|
2715
|
+
headerClassName: activePanel?.compact ? "pt-[calc(var(--layout-search-h)+var(--layout-panel-inset)+0.5rem)]" : void 0,
|
|
2716
|
+
children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ jsxs22(Fragment11, { children: [
|
|
2717
|
+
activePanel.content,
|
|
2718
|
+
activePanel.aside
|
|
2719
|
+
] }) : activePanel?.content
|
|
2720
|
+
}
|
|
2721
|
+
)
|
|
2496
2722
|
] }) })
|
|
2497
2723
|
]
|
|
2498
2724
|
}
|
|
@@ -2503,17 +2729,18 @@ function GenreTreeView({
|
|
|
2503
2729
|
import { useMemo as useMemo8 } from "react";
|
|
2504
2730
|
import {
|
|
2505
2731
|
ACCENT_TEXT_COLOR,
|
|
2732
|
+
GenreNodeAncestors as GenreNodeAncestors2,
|
|
2733
|
+
GenreNodeFamily as GenreNodeFamily2,
|
|
2506
2734
|
getGenreTreeColor as getGenreTreeColor2,
|
|
2507
|
-
InfoPanel,
|
|
2508
2735
|
TEXT_COLOR,
|
|
2509
2736
|
tintSurface
|
|
2510
2737
|
} from "@behindthemusictree/genre-tree-view";
|
|
2511
|
-
import { Skeleton as
|
|
2738
|
+
import { DetailPanel as DetailPanel2, IconButton as IconButton2, Skeleton as Skeleton7 } from "@behindthemusictree/ui";
|
|
2739
|
+
import { Maximize2 as Maximize22, Minimize2 as Minimize22 } from "lucide-react";
|
|
2512
2740
|
import {
|
|
2513
2741
|
useFetchGenreDetail
|
|
2514
2742
|
} from "@behindthemusictree/app-kit/genre-tree";
|
|
2515
|
-
import { jsx as
|
|
2516
|
-
var ITEM_COUNT_LABEL = "Songs";
|
|
2743
|
+
import { jsx as jsx30, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
2517
2744
|
var RELATIVE_ITEM_COUNT = 1;
|
|
2518
2745
|
function toNode(genre, parentId) {
|
|
2519
2746
|
return {
|
|
@@ -2537,6 +2764,7 @@ function GenreDetailPanel({
|
|
|
2537
2764
|
collapsed,
|
|
2538
2765
|
expanded,
|
|
2539
2766
|
onExpandedChange,
|
|
2767
|
+
edgeTab,
|
|
2540
2768
|
extras
|
|
2541
2769
|
}) {
|
|
2542
2770
|
const { data: detail, isPending, isError } = useFetchGenreDetail(genreUuid, scope, getBackendBaseUrl);
|
|
@@ -2549,7 +2777,7 @@ function GenreDetailPanel({
|
|
|
2549
2777
|
fill: tintSurface(rootColor),
|
|
2550
2778
|
textColor: TEXT_COLOR
|
|
2551
2779
|
});
|
|
2552
|
-
const
|
|
2780
|
+
const ancestors2 = [...detail.ascendants].sort((a, b) => b.degree - a.degree);
|
|
2553
2781
|
return {
|
|
2554
2782
|
node: {
|
|
2555
2783
|
id: detail.uuid,
|
|
@@ -2561,57 +2789,72 @@ function GenreDetailPanel({
|
|
|
2561
2789
|
fill: rootColor,
|
|
2562
2790
|
parentNode: detail.parent ? relative(detail.parent, null) : null,
|
|
2563
2791
|
childNodes: detail.children.map((child) => relative(child, detail.uuid)),
|
|
2564
|
-
ancestorNodes:
|
|
2565
|
-
({ ascendant }, index) => relative(ascendant, index === 0 ? null :
|
|
2792
|
+
ancestorNodes: ancestors2.map(
|
|
2793
|
+
({ ascendant }, index) => relative(ascendant, index === 0 ? null : ancestors2[index - 1].ascendant.uuid)
|
|
2566
2794
|
)
|
|
2567
2795
|
};
|
|
2568
2796
|
}, [detail]);
|
|
2569
2797
|
if (isError) {
|
|
2570
|
-
return /* @__PURE__ */
|
|
2798
|
+
return /* @__PURE__ */ jsx30("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
|
|
2571
2799
|
}
|
|
2572
2800
|
if (isPending || !detail || !view) {
|
|
2573
|
-
return /* @__PURE__ */
|
|
2574
|
-
/* @__PURE__ */
|
|
2575
|
-
/* @__PURE__ */
|
|
2576
|
-
/* @__PURE__ */
|
|
2801
|
+
return /* @__PURE__ */ jsxs23("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
|
|
2802
|
+
/* @__PURE__ */ jsx30(Skeleton7, { className: "h-6 w-2/3" }),
|
|
2803
|
+
/* @__PURE__ */ jsx30(Skeleton7, { className: "h-4 w-full" }),
|
|
2804
|
+
/* @__PURE__ */ jsx30(Skeleton7, { className: "h-4 w-3/4" })
|
|
2577
2805
|
] });
|
|
2578
2806
|
}
|
|
2579
|
-
const
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
{
|
|
2583
|
-
toolbar: sections.toolbar,
|
|
2584
|
-
family: sections.family,
|
|
2585
|
-
genreUuid: detail.uuid,
|
|
2586
|
-
genrePlaylist: { ...detail.criteriaPlaylist, tracksCount: detail.tracksCount },
|
|
2587
|
-
wikidataId: detail.wikidataId,
|
|
2588
|
-
scope,
|
|
2589
|
-
getBackendBaseUrl,
|
|
2590
|
-
playback,
|
|
2591
|
-
band: { node, onZoomIn, shareUrl, shareTarget: shareUrl ? shareTarget : void 0 },
|
|
2592
|
-
extras
|
|
2593
|
-
}
|
|
2594
|
-
),
|
|
2595
|
-
aside: null
|
|
2596
|
-
});
|
|
2597
|
-
return /* @__PURE__ */ jsx29(
|
|
2598
|
-
InfoPanel,
|
|
2807
|
+
const selectGenre = (nodeId) => onSelectGenre(nodeId);
|
|
2808
|
+
const family = /* @__PURE__ */ jsx30(
|
|
2809
|
+
GenreNodeFamily2,
|
|
2599
2810
|
{
|
|
2600
2811
|
node: view.node,
|
|
2601
2812
|
fill: view.fill,
|
|
2602
2813
|
textColor: ACCENT_TEXT_COLOR,
|
|
2603
2814
|
parentNode: view.parentNode,
|
|
2604
2815
|
childNodes: view.childNodes,
|
|
2605
|
-
|
|
2816
|
+
onSelectNode: selectGenre
|
|
2817
|
+
}
|
|
2818
|
+
);
|
|
2819
|
+
const ancestors = /* @__PURE__ */ jsx30(GenreNodeAncestors2, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
|
|
2820
|
+
return /* @__PURE__ */ jsxs23(
|
|
2821
|
+
DetailPanel2,
|
|
2822
|
+
{
|
|
2606
2823
|
side: "left",
|
|
2824
|
+
title: view.node.name,
|
|
2825
|
+
headerAccent: { background: view.fill, color: ACCENT_TEXT_COLOR },
|
|
2826
|
+
headerActions: onExpandedChange ? /* @__PURE__ */ jsx30(
|
|
2827
|
+
IconButton2,
|
|
2828
|
+
{
|
|
2829
|
+
"aria-label": expanded ? "Shrink panel" : "Extend panel",
|
|
2830
|
+
style: { color: "inherit" },
|
|
2831
|
+
onClick: () => onExpandedChange(!expanded),
|
|
2832
|
+
children: expanded ? /* @__PURE__ */ jsx30(Minimize22, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ jsx30(Maximize22, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
2833
|
+
}
|
|
2834
|
+
) : void 0,
|
|
2607
2835
|
onClose,
|
|
2608
|
-
|
|
2609
|
-
itemCountLabel: ITEM_COUNT_LABEL,
|
|
2610
|
-
renderInfoPanelBody: renderBody,
|
|
2836
|
+
collapsed,
|
|
2611
2837
|
expanded,
|
|
2612
2838
|
onExpandedChange,
|
|
2613
|
-
|
|
2614
|
-
|
|
2839
|
+
edgeTab,
|
|
2840
|
+
aside: ancestors,
|
|
2841
|
+
children: [
|
|
2842
|
+
/* @__PURE__ */ jsx30(
|
|
2843
|
+
GenreInfoPanelBody,
|
|
2844
|
+
{
|
|
2845
|
+
family,
|
|
2846
|
+
genreUuid: detail.uuid,
|
|
2847
|
+
genrePlaylist: { ...detail.criteriaPlaylist, tracksCount: detail.tracksCount },
|
|
2848
|
+
wikidataId: detail.wikidataId,
|
|
2849
|
+
scope,
|
|
2850
|
+
getBackendBaseUrl,
|
|
2851
|
+
playback,
|
|
2852
|
+
band: { node: view.node, onZoomIn, shareUrl, shareTarget: shareUrl ? shareTarget : void 0 },
|
|
2853
|
+
extras
|
|
2854
|
+
}
|
|
2855
|
+
),
|
|
2856
|
+
!expanded && ancestors
|
|
2857
|
+
]
|
|
2615
2858
|
}
|
|
2616
2859
|
);
|
|
2617
2860
|
}
|
|
@@ -2625,13 +2868,13 @@ import {
|
|
|
2625
2868
|
} from "@behindthemusictree/genre-tree-view";
|
|
2626
2869
|
|
|
2627
2870
|
// src/genre-tree/track-list-sidebar/TrackListSidebar.tsx
|
|
2628
|
-
import { useEffect as
|
|
2871
|
+
import { useEffect as useEffect6, useLayoutEffect as useLayoutEffect2, useRef as useRef5 } from "react";
|
|
2629
2872
|
import { ListMusic } from "lucide-react";
|
|
2630
|
-
import { useTrackList as
|
|
2873
|
+
import { useTrackList as useTrackList6 } from "@behindthemusictree/app-kit/genre-tree";
|
|
2631
2874
|
import { TrackListOriginType as TrackListOriginType2 } from "@behindthemusictree/app-kit/genre-tree";
|
|
2632
|
-
import { useTrackNoun as
|
|
2633
|
-
import { jsx as
|
|
2634
|
-
var useBrowserLayoutEffect = typeof window === "undefined" ?
|
|
2875
|
+
import { useTrackNoun as useTrackNoun3 } from "@behindthemusictree/app-kit/track-noun";
|
|
2876
|
+
import { jsx as jsx31, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
2877
|
+
var useBrowserLayoutEffect = typeof window === "undefined" ? useEffect6 : useLayoutEffect2;
|
|
2635
2878
|
function TrackListSidebar({
|
|
2636
2879
|
className: className2 = "",
|
|
2637
2880
|
renderDuration,
|
|
@@ -2640,10 +2883,11 @@ function TrackListSidebar({
|
|
|
2640
2883
|
isCollapsed = false,
|
|
2641
2884
|
onExpand,
|
|
2642
2885
|
onMinimize,
|
|
2643
|
-
renderArtistLink
|
|
2886
|
+
renderArtistLink,
|
|
2887
|
+
renderSongLink
|
|
2644
2888
|
}) {
|
|
2645
|
-
const { trackList, loadMore, autoAdvanceScrollRequest } =
|
|
2646
|
-
const trackNoun =
|
|
2889
|
+
const { trackList, loadMore, autoAdvanceScrollRequest } = useTrackList6();
|
|
2890
|
+
const trackNoun = useTrackNoun3();
|
|
2647
2891
|
const trackListRef = useRef5(null);
|
|
2648
2892
|
const previousOriginRef = useRef5(trackList?.origin ?? null);
|
|
2649
2893
|
const handledAutoAdvanceRequestIdRef = useRef5(null);
|
|
@@ -2653,7 +2897,7 @@ function TrackListSidebar({
|
|
|
2653
2897
|
const userScrollIntentTimerRef = useRef5(null);
|
|
2654
2898
|
const loadMoreSentinelRef = useRef5(null);
|
|
2655
2899
|
const hasMore = !!trackList && trackList.nextPage !== null;
|
|
2656
|
-
|
|
2900
|
+
useEffect6(() => {
|
|
2657
2901
|
const sentinel = loadMoreSentinelRef.current;
|
|
2658
2902
|
if (!hasMore || !sentinel) return;
|
|
2659
2903
|
const observer = new IntersectionObserver((entries) => {
|
|
@@ -2673,11 +2917,11 @@ function TrackListSidebar({
|
|
|
2673
2917
|
programmaticScrollTopRef.current = null;
|
|
2674
2918
|
if (trackListRef.current) trackListRef.current.scrollTop = 0;
|
|
2675
2919
|
}, [trackList?.origin]);
|
|
2676
|
-
|
|
2920
|
+
useEffect6(() => {
|
|
2677
2921
|
const list = trackListRef.current;
|
|
2678
2922
|
if (!isCollapsed && list?.scrollTop === 0) shouldFollowPlaybackRef.current = true;
|
|
2679
2923
|
}, [isCollapsed]);
|
|
2680
|
-
|
|
2924
|
+
useEffect6(
|
|
2681
2925
|
() => () => {
|
|
2682
2926
|
if (userScrollIntentTimerRef.current !== null) clearTimeout(userScrollIntentTimerRef.current);
|
|
2683
2927
|
},
|
|
@@ -2731,25 +2975,25 @@ function TrackListSidebar({
|
|
|
2731
2975
|
}
|
|
2732
2976
|
};
|
|
2733
2977
|
const positionClasses = layout === "inline" ? "relative w-full flex h-full flex-col" : "fixed right-0 w-[50rem] flex flex-col";
|
|
2734
|
-
return trackList ? /* @__PURE__ */
|
|
2978
|
+
return trackList ? /* @__PURE__ */ jsxs24(
|
|
2735
2979
|
"div",
|
|
2736
2980
|
{
|
|
2737
2981
|
"data-theme": "dark",
|
|
2738
2982
|
className: `track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${className2}`,
|
|
2739
2983
|
style: layout === "fixed" ? { top: "63.5px", maxHeight: "calc(100vh - 63.5px - 79px)" } : void 0,
|
|
2740
2984
|
children: [
|
|
2741
|
-
/* @__PURE__ */
|
|
2742
|
-
/* @__PURE__ */
|
|
2743
|
-
/* @__PURE__ */
|
|
2744
|
-
/* @__PURE__ */
|
|
2745
|
-
/* @__PURE__ */
|
|
2985
|
+
/* @__PURE__ */ jsxs24("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
|
|
2986
|
+
/* @__PURE__ */ jsxs24("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
|
|
2987
|
+
/* @__PURE__ */ jsxs24("div", { className: "origin flex text-xl", children: [
|
|
2988
|
+
/* @__PURE__ */ jsx31("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
|
|
2989
|
+
/* @__PURE__ */ jsx31("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__ */ jsx31("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
|
|
2746
2990
|
] }),
|
|
2747
|
-
/* @__PURE__ */
|
|
2991
|
+
/* @__PURE__ */ jsxs24("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
|
|
2748
2992
|
trackList.origin.type === TrackListOriginType2.GENRE_PLAYLIST ? "Genre \u2022 " : trackList.origin.type === TrackListOriginType2.ARTIST ? "Artist \u2022 " : `${trackNoun.singular} playlist \u2022 `,
|
|
2749
2993
|
`${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`
|
|
2750
2994
|
] })
|
|
2751
2995
|
] }),
|
|
2752
|
-
(isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */
|
|
2996
|
+
(isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ jsx31(
|
|
2753
2997
|
"button",
|
|
2754
2998
|
{
|
|
2755
2999
|
type: "button",
|
|
@@ -2758,11 +3002,11 @@ function TrackListSidebar({
|
|
|
2758
3002
|
"aria-expanded": !isCollapsed,
|
|
2759
3003
|
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"}`,
|
|
2760
3004
|
onClick: isCollapsed ? onExpand : onMinimize,
|
|
2761
|
-
children: /* @__PURE__ */
|
|
3005
|
+
children: /* @__PURE__ */ jsx31(ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
|
|
2762
3006
|
}
|
|
2763
3007
|
)
|
|
2764
3008
|
] }),
|
|
2765
|
-
!isCollapsed && /* @__PURE__ */
|
|
3009
|
+
!isCollapsed && /* @__PURE__ */ jsxs24(
|
|
2766
3010
|
"ul",
|
|
2767
3011
|
{
|
|
2768
3012
|
ref: trackListRef,
|
|
@@ -2772,17 +3016,18 @@ function TrackListSidebar({
|
|
|
2772
3016
|
onPointerDown: markUserScrollIntent,
|
|
2773
3017
|
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",
|
|
2774
3018
|
children: [
|
|
2775
|
-
trackList.tracks.map((track, index) => /* @__PURE__ */
|
|
3019
|
+
trackList.tracks.map((track, index) => /* @__PURE__ */ jsx31("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ jsx31(
|
|
2776
3020
|
TrackItem,
|
|
2777
3021
|
{
|
|
2778
3022
|
track,
|
|
2779
3023
|
position: index + 1,
|
|
2780
3024
|
renderDuration,
|
|
2781
3025
|
renderActions,
|
|
2782
|
-
renderArtistLink
|
|
3026
|
+
renderArtistLink,
|
|
3027
|
+
renderSongLink
|
|
2783
3028
|
}
|
|
2784
3029
|
) }, track.uuid)),
|
|
2785
|
-
hasMore && /* @__PURE__ */
|
|
3030
|
+
hasMore && /* @__PURE__ */ jsx31("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
|
|
2786
3031
|
]
|
|
2787
3032
|
}
|
|
2788
3033
|
)
|
|
@@ -2792,21 +3037,22 @@ function TrackListSidebar({
|
|
|
2792
3037
|
}
|
|
2793
3038
|
|
|
2794
3039
|
// src/genre-tree/PlayerPanel.tsx
|
|
2795
|
-
import { useEffect as
|
|
2796
|
-
import { twMerge
|
|
3040
|
+
import { useEffect as useEffect7, useState as useState8 } from "react";
|
|
3041
|
+
import { twMerge } from "tailwind-merge";
|
|
2797
3042
|
import {
|
|
2798
|
-
usePlayer as
|
|
3043
|
+
usePlayer as usePlayer11,
|
|
2799
3044
|
PlayerVideoSurface
|
|
2800
3045
|
} from "@behindthemusictree/app-kit/player";
|
|
2801
3046
|
import {
|
|
2802
3047
|
useTrackListSidebarVisibility
|
|
2803
3048
|
} from "@behindthemusictree/app-kit/genre-tree";
|
|
3049
|
+
import { PanelEdgeTab } from "@behindthemusictree/ui";
|
|
2804
3050
|
|
|
2805
3051
|
// src/genre-tree/PlayerControls.tsx
|
|
2806
|
-
import { Pause as
|
|
2807
|
-
import { formatTime, useTrackList as
|
|
2808
|
-
import { PlayStates as
|
|
2809
|
-
import { jsx as
|
|
3052
|
+
import { Pause as Pause3, Play as Play4, SkipBack, SkipForward, Volume2, VolumeX } from "lucide-react";
|
|
3053
|
+
import { formatTime, useTrackList as useTrackList7 } from "@behindthemusictree/app-kit/genre-tree";
|
|
3054
|
+
import { PlayStates as PlayStates4, useCurrentTime, usePlayer as usePlayer10 } from "@behindthemusictree/app-kit/player";
|
|
3055
|
+
import { jsx as jsx32, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
2810
3056
|
function PlayerControls() {
|
|
2811
3057
|
const {
|
|
2812
3058
|
duration,
|
|
@@ -2817,23 +3063,23 @@ function PlayerControls() {
|
|
|
2817
3063
|
seekTo,
|
|
2818
3064
|
setVolume,
|
|
2819
3065
|
volume
|
|
2820
|
-
} =
|
|
2821
|
-
const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } =
|
|
3066
|
+
} = usePlayer10();
|
|
3067
|
+
const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } = useTrackList7();
|
|
2822
3068
|
const currentTime = useCurrentTime();
|
|
2823
|
-
const isBusy = isLoading || playState ===
|
|
3069
|
+
const isBusy = isLoading || playState === PlayStates4.LOADING;
|
|
2824
3070
|
const isReady = !!playerTrackObject?.isReady && !isBusy && !playerTrackObject.loadError;
|
|
2825
3071
|
const safeDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;
|
|
2826
3072
|
const safeCurrentTime = Number.isFinite(currentTime) ? Math.max(0, Math.min(currentTime, safeDuration)) : 0;
|
|
2827
|
-
const isPlaying = playState ===
|
|
2828
|
-
return /* @__PURE__ */
|
|
3073
|
+
const isPlaying = playState === PlayStates4.PLAYING;
|
|
3074
|
+
return /* @__PURE__ */ jsxs25(
|
|
2829
3075
|
"section",
|
|
2830
3076
|
{
|
|
2831
3077
|
"aria-label": "Player controls",
|
|
2832
3078
|
className: "player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200",
|
|
2833
3079
|
children: [
|
|
2834
|
-
/* @__PURE__ */
|
|
2835
|
-
/* @__PURE__ */
|
|
2836
|
-
/* @__PURE__ */
|
|
3080
|
+
/* @__PURE__ */ jsxs25("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
|
|
3081
|
+
/* @__PURE__ */ jsx32("span", { "aria-label": "Elapsed time", children: formatTime(safeCurrentTime) }),
|
|
3082
|
+
/* @__PURE__ */ jsx32(
|
|
2837
3083
|
"input",
|
|
2838
3084
|
{
|
|
2839
3085
|
"aria-label": "Seek",
|
|
@@ -2847,11 +3093,11 @@ function PlayerControls() {
|
|
|
2847
3093
|
onChange: (event) => seekTo(Number(event.currentTarget.value))
|
|
2848
3094
|
}
|
|
2849
3095
|
),
|
|
2850
|
-
/* @__PURE__ */
|
|
3096
|
+
/* @__PURE__ */ jsx32("span", { "aria-label": "Duration", children: formatTime(safeDuration) })
|
|
2851
3097
|
] }),
|
|
2852
|
-
/* @__PURE__ */
|
|
2853
|
-
/* @__PURE__ */
|
|
2854
|
-
/* @__PURE__ */
|
|
3098
|
+
/* @__PURE__ */ jsxs25("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
|
|
3099
|
+
/* @__PURE__ */ jsxs25("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
|
|
3100
|
+
/* @__PURE__ */ jsx32(
|
|
2855
3101
|
"button",
|
|
2856
3102
|
{
|
|
2857
3103
|
type: "button",
|
|
@@ -2860,10 +3106,10 @@ function PlayerControls() {
|
|
|
2860
3106
|
disabled: !isReady || !hasPreviousTrack,
|
|
2861
3107
|
onClick: playPreviousTrack,
|
|
2862
3108
|
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",
|
|
2863
|
-
children: /* @__PURE__ */
|
|
3109
|
+
children: /* @__PURE__ */ jsx32(SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
2864
3110
|
}
|
|
2865
3111
|
),
|
|
2866
|
-
/* @__PURE__ */
|
|
3112
|
+
/* @__PURE__ */ jsx32(
|
|
2867
3113
|
"button",
|
|
2868
3114
|
{
|
|
2869
3115
|
type: "button",
|
|
@@ -2872,10 +3118,10 @@ function PlayerControls() {
|
|
|
2872
3118
|
disabled: !isReady,
|
|
2873
3119
|
onClick: handlePlayPauseAction,
|
|
2874
3120
|
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",
|
|
2875
|
-
children: isPlaying ? /* @__PURE__ */
|
|
3121
|
+
children: isPlaying ? /* @__PURE__ */ jsx32(Pause3, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ jsx32(Play4, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
|
|
2876
3122
|
}
|
|
2877
3123
|
),
|
|
2878
|
-
/* @__PURE__ */
|
|
3124
|
+
/* @__PURE__ */ jsx32(
|
|
2879
3125
|
"button",
|
|
2880
3126
|
{
|
|
2881
3127
|
type: "button",
|
|
@@ -2884,14 +3130,14 @@ function PlayerControls() {
|
|
|
2884
3130
|
disabled: !isReady || !hasNextTrack,
|
|
2885
3131
|
onClick: playNextTrack,
|
|
2886
3132
|
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",
|
|
2887
|
-
children: /* @__PURE__ */
|
|
3133
|
+
children: /* @__PURE__ */ jsx32(SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
2888
3134
|
}
|
|
2889
3135
|
)
|
|
2890
3136
|
] }),
|
|
2891
|
-
/* @__PURE__ */
|
|
2892
|
-
volume === 0 ? /* @__PURE__ */
|
|
2893
|
-
/* @__PURE__ */
|
|
2894
|
-
/* @__PURE__ */
|
|
3137
|
+
/* @__PURE__ */ jsxs25("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: [
|
|
3138
|
+
volume === 0 ? /* @__PURE__ */ jsx32(VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ jsx32(Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
|
|
3139
|
+
/* @__PURE__ */ jsx32("span", { className: "sr-only", children: "Volume" }),
|
|
3140
|
+
/* @__PURE__ */ jsx32(
|
|
2895
3141
|
"input",
|
|
2896
3142
|
{
|
|
2897
3143
|
"aria-label": "Volume",
|
|
@@ -2913,7 +3159,7 @@ function PlayerControls() {
|
|
|
2913
3159
|
}
|
|
2914
3160
|
|
|
2915
3161
|
// src/genre-tree/PlayerPanel.tsx
|
|
2916
|
-
import { jsx as
|
|
3162
|
+
import { jsx as jsx33, jsxs as jsxs26 } from "react/jsx-runtime";
|
|
2917
3163
|
var PANEL_ID = "player-panel";
|
|
2918
3164
|
var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
|
|
2919
3165
|
function PlayerPanel({
|
|
@@ -2922,16 +3168,17 @@ function PlayerPanel({
|
|
|
2922
3168
|
expanded,
|
|
2923
3169
|
onExpandedChange,
|
|
2924
3170
|
extendedWidth = DEFAULT_EXTENDED_WIDTH,
|
|
2925
|
-
renderArtistLink
|
|
3171
|
+
renderArtistLink,
|
|
3172
|
+
renderSongLink
|
|
2926
3173
|
}) {
|
|
2927
|
-
const { playerTrackObject } =
|
|
3174
|
+
const { playerTrackObject } = usePlayer11();
|
|
2928
3175
|
const { toggleTrackListSidebar, isTrackListSidebarVisible } = useTrackListSidebarVisibility();
|
|
2929
|
-
const [lastLoadedTrackTitle, setLastLoadedTrackTitle] =
|
|
3176
|
+
const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = useState8("");
|
|
2930
3177
|
const loadedTrackTitle = playerTrackObject?.track.title;
|
|
2931
|
-
|
|
3178
|
+
useEffect7(() => {
|
|
2932
3179
|
if (loadedTrackTitle) setLastLoadedTrackTitle(loadedTrackTitle);
|
|
2933
3180
|
}, [loadedTrackTitle]);
|
|
2934
|
-
const [uncontrolledExtended, setUncontrolledExtended] =
|
|
3181
|
+
const [uncontrolledExtended, setUncontrolledExtended] = useState8(false);
|
|
2935
3182
|
const isExtendRequested = expanded ?? uncontrolledExtended;
|
|
2936
3183
|
const setIsExtendRequested = (next) => {
|
|
2937
3184
|
setUncontrolledExtended(next);
|
|
@@ -2952,11 +3199,11 @@ function PlayerPanel({
|
|
|
2952
3199
|
onClick: () => setIsExtendRequested(true),
|
|
2953
3200
|
expanded: false
|
|
2954
3201
|
} : null;
|
|
2955
|
-
return /* @__PURE__ */
|
|
3202
|
+
return /* @__PURE__ */ jsxs26(
|
|
2956
3203
|
"div",
|
|
2957
3204
|
{
|
|
2958
3205
|
id: PANEL_ID,
|
|
2959
|
-
className:
|
|
3206
|
+
className: twMerge(
|
|
2960
3207
|
"fixed right-[var(--layout-gutter)] top-[var(--layout-band-top)] z-40 max-h-[calc(100vh-var(--layout-band-top)-3.5rem)]",
|
|
2961
3208
|
!isExtended && "w-[var(--layout-right-w)]",
|
|
2962
3209
|
isTrackListSidebarVisible && "h-[calc(100vh-var(--layout-band-top)-3.5rem)]",
|
|
@@ -2965,7 +3212,7 @@ function PlayerPanel({
|
|
|
2965
3212
|
),
|
|
2966
3213
|
style: isExtended ? { width: extendedWidth } : void 0,
|
|
2967
3214
|
children: [
|
|
2968
|
-
/* @__PURE__ */
|
|
3215
|
+
/* @__PURE__ */ jsx33(
|
|
2969
3216
|
PanelEdgeTab,
|
|
2970
3217
|
{
|
|
2971
3218
|
side: "right",
|
|
@@ -2975,34 +3222,35 @@ function PlayerPanel({
|
|
|
2975
3222
|
className: "absolute right-full top-1/2 -translate-y-1/2"
|
|
2976
3223
|
}
|
|
2977
3224
|
),
|
|
2978
|
-
/* @__PURE__ */
|
|
3225
|
+
/* @__PURE__ */ jsxs26(
|
|
2979
3226
|
"div",
|
|
2980
3227
|
{
|
|
2981
|
-
className:
|
|
3228
|
+
className: twMerge(
|
|
2982
3229
|
"flex h-full max-h-[inherit] overflow-hidden rounded-[var(--radius-panel,1rem)] bg-zinc-950 text-zinc-100 shadow-[0_1px_2px_rgba(0,0,0,0.5),0_2px_6px_2px_rgba(0,0,0,0.3)]",
|
|
2983
3230
|
isExtended ? "flex-row" : "flex-col"
|
|
2984
3231
|
),
|
|
2985
3232
|
children: [
|
|
2986
|
-
/* @__PURE__ */
|
|
2987
|
-
/* @__PURE__ */
|
|
2988
|
-
/* @__PURE__ */
|
|
2989
|
-
/* @__PURE__ */
|
|
2990
|
-
playerTrackObject?.loadError && /* @__PURE__ */
|
|
3233
|
+
/* @__PURE__ */ jsxs26("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
|
|
3234
|
+
/* @__PURE__ */ jsx33("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ jsx33("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
|
|
3235
|
+
/* @__PURE__ */ jsxs26("div", { className: "relative w-full shrink-0", children: [
|
|
3236
|
+
/* @__PURE__ */ jsx33(PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
|
|
3237
|
+
playerTrackObject?.loadError && /* @__PURE__ */ jsx33("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: playerTrackObject.loadError })
|
|
2991
3238
|
] }),
|
|
2992
|
-
/* @__PURE__ */
|
|
3239
|
+
/* @__PURE__ */ jsx33(PlayerControls, {})
|
|
2993
3240
|
] }),
|
|
2994
|
-
/* @__PURE__ */
|
|
3241
|
+
/* @__PURE__ */ jsx33(
|
|
2995
3242
|
TrackListSidebar,
|
|
2996
3243
|
{
|
|
2997
3244
|
layout: "inline",
|
|
2998
3245
|
isCollapsed: !isTrackListSidebarVisible,
|
|
2999
3246
|
onExpand: toggleTrackListSidebar,
|
|
3000
3247
|
onMinimize: hideSongList,
|
|
3001
|
-
className:
|
|
3248
|
+
className: twMerge(
|
|
3002
3249
|
"min-h-0 min-w-0 flex-1",
|
|
3003
3250
|
isExtended && "border-l border-zinc-800"
|
|
3004
3251
|
),
|
|
3005
|
-
renderArtistLink
|
|
3252
|
+
renderArtistLink,
|
|
3253
|
+
renderSongLink
|
|
3006
3254
|
}
|
|
3007
3255
|
)
|
|
3008
3256
|
]
|
|
@@ -3017,11 +3265,11 @@ function PlayerPanel({
|
|
|
3017
3265
|
import { v4 as uuidv4 } from "uuid";
|
|
3018
3266
|
import { AiFillStar, AiOutlineStar } from "react-icons/ai";
|
|
3019
3267
|
import { FORM_RATING_NULL_VALUE } from "@behindthemusictree/app-kit/genre-tree";
|
|
3020
|
-
import { jsx as
|
|
3268
|
+
import { jsx as jsx34, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
3021
3269
|
function Rating({ rating, handleChange }) {
|
|
3022
3270
|
const instanceId = uuidv4();
|
|
3023
|
-
return /* @__PURE__ */
|
|
3024
|
-
/* @__PURE__ */
|
|
3271
|
+
return /* @__PURE__ */ jsx34("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ jsxs27("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
|
|
3272
|
+
/* @__PURE__ */ jsx34(
|
|
3025
3273
|
"input",
|
|
3026
3274
|
{
|
|
3027
3275
|
id: `star-${instanceId}-${i}`,
|
|
@@ -3033,16 +3281,16 @@ function Rating({ rating, handleChange }) {
|
|
|
3033
3281
|
onChange: handleChange
|
|
3034
3282
|
}
|
|
3035
3283
|
),
|
|
3036
|
-
/* @__PURE__ */
|
|
3037
|
-
/* @__PURE__ */
|
|
3284
|
+
/* @__PURE__ */ jsx34("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ jsxs27("div", { className: "flex", children: [
|
|
3285
|
+
/* @__PURE__ */ jsx34(
|
|
3038
3286
|
AiOutlineStar,
|
|
3039
3287
|
{
|
|
3040
3288
|
className: `w-7 h-7 absolute top-0 left-0 ${rating === FORM_RATING_NULL_VALUE ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
|
|
3041
3289
|
}
|
|
3042
3290
|
),
|
|
3043
|
-
/* @__PURE__ */
|
|
3291
|
+
/* @__PURE__ */ jsx34("span", { className: "flex-grow w-8" }),
|
|
3044
3292
|
" "
|
|
3045
|
-
] }) : /* @__PURE__ */
|
|
3293
|
+
] }) : /* @__PURE__ */ jsx34(
|
|
3046
3294
|
AiFillStar,
|
|
3047
3295
|
{
|
|
3048
3296
|
className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
|
|
@@ -3057,8 +3305,6 @@ export {
|
|
|
3057
3305
|
GenreSearch,
|
|
3058
3306
|
GenreNodeActionBand,
|
|
3059
3307
|
GenreInfoPanelBody,
|
|
3060
|
-
PanelEdgeTab,
|
|
3061
|
-
useLeftDetailPanelLayout,
|
|
3062
3308
|
TrackPositionPlayPause,
|
|
3063
3309
|
TrackItem,
|
|
3064
3310
|
GenreTreeView,
|
|
@@ -3071,4 +3317,4 @@ export {
|
|
|
3071
3317
|
GenreTreeViewSkeleton2 as GenreTreeViewSkeleton,
|
|
3072
3318
|
GenreTreeOutlineSkeleton
|
|
3073
3319
|
};
|
|
3074
|
-
//# sourceMappingURL=chunk-
|
|
3320
|
+
//# sourceMappingURL=chunk-AQASG5KT.js.map
|