@behindthemusictree/app-kit-ui 24.0.1 → 28.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-BPCFNBFO.js → chunk-5PQWJRDZ.js} +1160 -552
- package/dist/chunk-5PQWJRDZ.js.map +1 -0
- package/dist/chunk-DKQQLLMY.js +246 -0
- package/dist/chunk-DKQQLLMY.js.map +1 -0
- package/dist/{chunk-ZLCRD7OK.js → chunk-XUC4UJDQ.js} +2 -2
- package/dist/genre-tree/index.cjs +1352 -639
- package/dist/genre-tree/index.cjs.map +1 -1
- package/dist/genre-tree/index.d.ts +53 -10
- package/dist/genre-tree/index.js +2 -2
- package/dist/genre-tree/styles.css +80 -0
- package/dist/index.cjs +1381 -668
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +6 -4
- package/dist/popup/index.cjs +179 -73
- package/dist/popup/index.cjs.map +1 -1
- package/dist/popup/index.d.ts +22 -2
- package/dist/popup/index.js +5 -3
- package/package.json +7 -7
- package/dist/chunk-BPCFNBFO.js.map +0 -1
- package/dist/chunk-VDXUW7PP.js +0 -141
- package/dist/chunk-VDXUW7PP.js.map +0 -1
- /package/dist/{chunk-ZLCRD7OK.js.map → chunk-XUC4UJDQ.js.map} +0 -0
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BasePopup_default,
|
|
3
|
-
InternalErrorPopup
|
|
4
|
-
|
|
3
|
+
InternalErrorPopup,
|
|
4
|
+
Toast
|
|
5
|
+
} from "./chunk-DKQQLLMY.js";
|
|
5
6
|
|
|
6
7
|
// src/genre-tree/GenreTreeView.tsx
|
|
7
|
-
import { useCallback as
|
|
8
|
+
import { useCallback as useCallback6, useDeferredValue, useEffect as useEffect5, useMemo as useMemo7, useRef as useRef4, useState as useState5 } from "react";
|
|
9
|
+
import { useIsMutating } from "@tanstack/react-query";
|
|
8
10
|
import { Plus as Plus2 } from "lucide-react";
|
|
9
|
-
import { IconTextButton, Button, SegmentedControl } from "@behindthemusictree/ui";
|
|
11
|
+
import { IconTextButton, Button, SegmentedControl as SegmentedControl2 } from "@behindthemusictree/ui";
|
|
10
12
|
import {
|
|
11
13
|
GenreTreeWheelSkeleton,
|
|
12
14
|
GenreTreeViewSkeleton
|
|
@@ -14,9 +16,9 @@ import {
|
|
|
14
16
|
import {
|
|
15
17
|
useArtistSearch
|
|
16
18
|
} from "@behindthemusictree/app-kit/genre-tree";
|
|
17
|
-
import { useGenreTreeViewState, useArtist as useArtist2 } from "@behindthemusictree/app-kit/genre-tree";
|
|
19
|
+
import { useGenreTreeViewState, useArtist as useArtist2, useUpdateGenre as useUpdateGenre2, genreUpdateMutationKey } from "@behindthemusictree/app-kit/genre-tree";
|
|
18
20
|
import { usePlayer as usePlayer8 } from "@behindthemusictree/app-kit/player";
|
|
19
|
-
import { usePopup as
|
|
21
|
+
import { usePopup as usePopup5 } from "@behindthemusictree/app-kit/popup";
|
|
20
22
|
|
|
21
23
|
// src/genre-tree/GenreDetailExtras.tsx
|
|
22
24
|
import { Skeleton as Skeleton2 } from "@behindthemusictree/ui";
|
|
@@ -25,7 +27,7 @@ import { useFetchGenreOverview } from "@behindthemusictree/app-kit/genre-tree";
|
|
|
25
27
|
// src/genre-tree/ClampedText.tsx
|
|
26
28
|
import { useLayoutEffect, useRef, useState } from "react";
|
|
27
29
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
28
|
-
function ClampedText({ children, className = "" }) {
|
|
30
|
+
function ClampedText({ children, className: className2 = "" }) {
|
|
29
31
|
const ref = useRef(null);
|
|
30
32
|
const [expanded, setExpanded] = useState(false);
|
|
31
33
|
const [overflows, setOverflows] = useState(false);
|
|
@@ -35,7 +37,7 @@ function ClampedText({ children, className = "" }) {
|
|
|
35
37
|
setOverflows(element.scrollHeight > element.clientHeight);
|
|
36
38
|
}, [children, expanded]);
|
|
37
39
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
38
|
-
/* @__PURE__ */ jsx("p", { ref, className: `${
|
|
40
|
+
/* @__PURE__ */ jsx("p", { ref, className: `${className2} ${expanded ? "" : "line-clamp-3"}`, children }),
|
|
39
41
|
(overflows || expanded) && /* @__PURE__ */ jsx(
|
|
40
42
|
"button",
|
|
41
43
|
{
|
|
@@ -62,20 +64,6 @@ import { parseWithLog } from "@behindthemusictree/app-kit/transport";
|
|
|
62
64
|
import { usePlayer } from "@behindthemusictree/app-kit/player";
|
|
63
65
|
import { PlayStates } from "@behindthemusictree/app-kit/player";
|
|
64
66
|
import { usePopup } from "@behindthemusictree/app-kit/popup";
|
|
65
|
-
|
|
66
|
-
// src/genre-tree/UnplayableReason.tsx
|
|
67
|
-
import { FaYoutube } from "react-icons/fa";
|
|
68
|
-
import { NO_VIDEO_LABEL } from "@behindthemusictree/app-kit/genre-tree";
|
|
69
|
-
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
70
|
-
function UnplayableReason({ reason }) {
|
|
71
|
-
if (reason !== NO_VIDEO_LABEL) return /* @__PURE__ */ jsx2(Fragment2, { children: reason });
|
|
72
|
-
return /* @__PURE__ */ jsxs2("span", { role: "img", "aria-label": reason, className: "relative inline-flex shrink-0 items-center", children: [
|
|
73
|
-
/* @__PURE__ */ jsx2(FaYoutube, { size: 16, "aria-hidden": "true" }),
|
|
74
|
-
/* @__PURE__ */ jsx2("span", { "aria-hidden": "true", className: "absolute left-1/2 top-1/2 h-0.5 w-[130%] -translate-x-1/2 -translate-y-1/2 -rotate-45 rounded bg-current" })
|
|
75
|
-
] });
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// src/genre-tree/GenrePlaylistTracks.tsx
|
|
79
67
|
import { ErrorCode } from "@behindthemusictree/app-kit/transport";
|
|
80
68
|
import { genrePlaylistQueryKeys } from "@behindthemusictree/app-kit/genre-tree";
|
|
81
69
|
import { libraryEndpoints, libraryQueryKeys } from "@behindthemusictree/app-kit/genre-tree";
|
|
@@ -83,23 +71,25 @@ import { isSong, songUnplayableReason, SongDetailedSchema } from "@behindthemusi
|
|
|
83
71
|
import { useTrackList } from "@behindthemusictree/app-kit/genre-tree";
|
|
84
72
|
|
|
85
73
|
// src/genre-tree/MusicBrainzRecordingLink.tsx
|
|
86
|
-
import { FaExternalLinkAlt } from "react-icons/fa";
|
|
87
74
|
import { musicBrainzRecordingUrl } from "@behindthemusictree/app-kit/genre-tree";
|
|
88
|
-
import { jsx as
|
|
89
|
-
function MusicBrainzRecordingLink({
|
|
75
|
+
import { Fragment as Fragment2, jsx as jsx2 } from "react/jsx-runtime";
|
|
76
|
+
function MusicBrainzRecordingLink({
|
|
77
|
+
song,
|
|
78
|
+
children,
|
|
79
|
+
className: className2 = ""
|
|
80
|
+
}) {
|
|
90
81
|
const url = musicBrainzRecordingUrl(song);
|
|
91
|
-
if (!url) return
|
|
92
|
-
return /* @__PURE__ */
|
|
82
|
+
if (!url) return /* @__PURE__ */ jsx2(Fragment2, { children });
|
|
83
|
+
return /* @__PURE__ */ jsx2(
|
|
93
84
|
"a",
|
|
94
85
|
{
|
|
95
86
|
href: url,
|
|
96
87
|
target: "_blank",
|
|
97
88
|
rel: "noopener noreferrer",
|
|
98
89
|
"aria-label": `View ${song.title} on MusicBrainz`,
|
|
99
|
-
|
|
100
|
-
className: "inline-flex shrink-0 items-center opacity-50 hover:opacity-100 focus-visible:opacity-100",
|
|
90
|
+
className: className2,
|
|
101
91
|
onClick: (event) => event.stopPropagation(),
|
|
102
|
-
children
|
|
92
|
+
children
|
|
103
93
|
}
|
|
104
94
|
);
|
|
105
95
|
}
|
|
@@ -107,7 +97,7 @@ function MusicBrainzRecordingLink({ song }) {
|
|
|
107
97
|
// src/genre-tree/GenrePlaylistTracks.tsx
|
|
108
98
|
import { ArtistNames } from "@behindthemusictree/app-kit/genre-tree";
|
|
109
99
|
import { capitalize, useTrackNoun } from "@behindthemusictree/app-kit/track-noun";
|
|
110
|
-
import { jsx as
|
|
100
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
111
101
|
var CATALOGUE_PAGE_SIZE = 100;
|
|
112
102
|
var SongPageSchema = PaginatedResponseSchema(SongDetailedSchema).extend({
|
|
113
103
|
overallTotal: z.number().nullable(),
|
|
@@ -179,90 +169,89 @@ function GenrePlaylistTracks({
|
|
|
179
169
|
const started = artist ? playNewTrackListFromArtist(artist, scope, { startSongUuid, ordering: "title", genreUuid }) : playNewTrackListFromGenrePlaylist(genrePlaylist, scope, { startSongUuid });
|
|
180
170
|
started.catch((error) => {
|
|
181
171
|
console.error("Failed to play genre playlist:", error);
|
|
182
|
-
showPopup(/* @__PURE__ */
|
|
172
|
+
showPopup(/* @__PURE__ */ jsx3(InternalErrorPopup, { errorCode: ErrorCode.CLIENT_UNKNOWN }));
|
|
183
173
|
});
|
|
184
174
|
};
|
|
185
|
-
return /* @__PURE__ */
|
|
186
|
-
/* @__PURE__ */
|
|
175
|
+
return /* @__PURE__ */ jsxs2("div", { className: "gtv-info-panel-children", children: [
|
|
176
|
+
/* @__PURE__ */ jsxs2("span", { className: "gtv-info-panel-children-title", children: [
|
|
187
177
|
capitalize(trackNoun.plural),
|
|
188
178
|
total !== void 0 && ` (${total})`
|
|
189
179
|
] }),
|
|
190
180
|
renderTracksToolbar?.(),
|
|
191
|
-
isPending ? /* @__PURE__ */
|
|
181
|
+
isPending ? /* @__PURE__ */ jsx3(Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ jsxs2("p", { role: "alert", children: [
|
|
192
182
|
"Couldn't load ",
|
|
193
183
|
trackNoun.plural,
|
|
194
184
|
".",
|
|
195
185
|
" ",
|
|
196
|
-
/* @__PURE__ */
|
|
197
|
-
] }) : tracks.length === 0 ? /* @__PURE__ */
|
|
186
|
+
/* @__PURE__ */ jsx3("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
|
|
187
|
+
] }) : tracks.length === 0 ? /* @__PURE__ */ jsx3("p", { children: "\u2014" }) : /* @__PURE__ */ jsxs2("ul", { className: "mt-1 max-h-64 overflow-y-auto text-[0.8125rem] leading-[1.45] [container-type:inline-size]", "aria-label": `${genrePlaylist.name} ${trackNoun.plural}`, children: [
|
|
198
188
|
tracks.map((track, index) => {
|
|
199
189
|
const label = track.artists?.length ? `${track.title} \u2014 ${track.artists.map((artist2) => artist2.name).join(", ")}` : track.title;
|
|
200
190
|
const isCurrent = playerTrackObject?.track.id === track.uuid;
|
|
201
191
|
const isPlaying = isCurrent && playState === PlayStates.PLAYING;
|
|
202
|
-
const text = /* @__PURE__ */
|
|
203
|
-
/* @__PURE__ */
|
|
204
|
-
track.artists?.length ? /* @__PURE__ */
|
|
205
|
-
/* @__PURE__ */
|
|
206
|
-
track.album && /* @__PURE__ */
|
|
192
|
+
const text = /* @__PURE__ */ jsxs2("span", { className: TEXT_CELL, children: [
|
|
193
|
+
/* @__PURE__ */ jsx3("span", { className: TITLE_CELL, children: isSong(track) ? /* @__PURE__ */ jsx3(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
|
|
194
|
+
track.artists?.length ? /* @__PURE__ */ jsx3("span", { className: SEPARATOR, children: " \u2014 " }) : null,
|
|
195
|
+
/* @__PURE__ */ jsx3("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ jsx3(ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
|
|
196
|
+
track.album && /* @__PURE__ */ jsx3("span", { className: ALBUM_CELL, children: track.album.name })
|
|
207
197
|
] });
|
|
208
198
|
const leading = renderSongLeading?.(track, index);
|
|
209
199
|
const actions = renderSongActions?.(track);
|
|
210
|
-
const
|
|
211
|
-
if (
|
|
212
|
-
return /* @__PURE__ */
|
|
200
|
+
const isUnplayable = isSong(track) && songUnplayableReason(track) !== null;
|
|
201
|
+
if (isUnplayable) {
|
|
202
|
+
return /* @__PURE__ */ jsxs2("li", { className: "flex min-h-8 items-center gap-1 px-1 opacity-50", children: [
|
|
213
203
|
leading,
|
|
214
|
-
/* @__PURE__ */
|
|
204
|
+
/* @__PURE__ */ jsx3(
|
|
215
205
|
"button",
|
|
216
206
|
{
|
|
217
207
|
type: "button",
|
|
218
208
|
disabled: true,
|
|
219
209
|
"aria-disabled": "true",
|
|
220
|
-
"aria-label":
|
|
210
|
+
"aria-label": label,
|
|
221
211
|
className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full opacity-0 cursor-not-allowed",
|
|
222
|
-
children: /* @__PURE__ */
|
|
212
|
+
children: /* @__PURE__ */ jsx3(FaPlay, { size: 10 })
|
|
223
213
|
}
|
|
224
214
|
),
|
|
225
215
|
text,
|
|
226
|
-
isSong(track) && /* @__PURE__ */ jsx4(MusicBrainzRecordingLink, { song: track }),
|
|
227
|
-
/* @__PURE__ */ jsx4("span", { className: "shrink-0 text-xs", children: /* @__PURE__ */ jsx4(UnplayableReason, { reason: unplayableReason }) }),
|
|
228
216
|
actions
|
|
229
217
|
] }, track.uuid);
|
|
230
218
|
}
|
|
231
|
-
return /* @__PURE__ */
|
|
219
|
+
return /* @__PURE__ */ jsxs2(
|
|
232
220
|
"li",
|
|
233
221
|
{
|
|
234
222
|
className: `group flex min-h-8 items-center gap-1 rounded px-1 hover:bg-[var(--color-neutral-100)] ${isCurrent ? "font-medium text-[var(--color-primary)]" : ""}`,
|
|
235
223
|
children: [
|
|
236
224
|
leading,
|
|
237
|
-
/* @__PURE__ */
|
|
225
|
+
/* @__PURE__ */ jsx3(
|
|
238
226
|
"button",
|
|
239
227
|
{
|
|
240
228
|
type: "button",
|
|
241
229
|
"aria-label": `${isPlaying ? "Pause" : "Play"} ${label}`,
|
|
242
230
|
className: `flex h-6 w-6 shrink-0 items-center justify-center rounded-full cursor-pointer group-hover:opacity-100 focus-visible:opacity-100 ${isCurrent && playState !== PlayStates.STOPPED ? "opacity-100" : "opacity-0 [@media(pointer:coarse)]:opacity-100"}`,
|
|
243
231
|
onClick: () => isCurrent ? handlePlayPauseAction() : play(track.uuid),
|
|
244
|
-
children: isCurrent && playState === PlayStates.LOADING ? /* @__PURE__ */
|
|
232
|
+
children: isCurrent && playState === PlayStates.LOADING ? /* @__PURE__ */ jsx3(RingLoader, { size: 10 }) : isPlaying ? /* @__PURE__ */ jsx3(FaPause, { size: 10 }) : /* @__PURE__ */ jsx3(FaPlay, { size: 10 })
|
|
245
233
|
}
|
|
246
234
|
),
|
|
247
235
|
text,
|
|
248
|
-
isSong(track) && /* @__PURE__ */ jsx4(MusicBrainzRecordingLink, { song: track }),
|
|
249
236
|
actions
|
|
250
237
|
]
|
|
251
238
|
},
|
|
252
239
|
track.uuid
|
|
253
240
|
);
|
|
254
241
|
}),
|
|
255
|
-
hasNextPage && /* @__PURE__ */
|
|
242
|
+
hasNextPage && /* @__PURE__ */ jsx3("li", { ref: sentinelRef, "aria-hidden": "true", className: "h-4" })
|
|
256
243
|
] })
|
|
257
244
|
] });
|
|
258
245
|
}
|
|
259
246
|
|
|
260
247
|
// src/genre-tree/GenreDetailExtras.tsx
|
|
261
248
|
import { useTrackNoun as useTrackNoun2 } from "@behindthemusictree/app-kit/track-noun";
|
|
262
|
-
import {
|
|
249
|
+
import { SiWikidata, SiWikipedia } from "react-icons/si";
|
|
250
|
+
import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
263
251
|
function GenreDetailExtras({
|
|
264
252
|
genreUuid,
|
|
265
253
|
genrePlaylist,
|
|
254
|
+
wikidataId,
|
|
266
255
|
scope,
|
|
267
256
|
getBackendBaseUrl,
|
|
268
257
|
criteriaOverviewSchema,
|
|
@@ -281,52 +270,63 @@ function GenreDetailExtras({
|
|
|
281
270
|
criteriaOverviewSchema
|
|
282
271
|
);
|
|
283
272
|
if (isPending) {
|
|
284
|
-
return /* @__PURE__ */
|
|
285
|
-
/* @__PURE__ */
|
|
286
|
-
/* @__PURE__ */
|
|
287
|
-
/* @__PURE__ */
|
|
273
|
+
return /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
274
|
+
/* @__PURE__ */ jsxs3("div", { className: "gtv-info-panel-children", "aria-busy": "true", "data-testid": "genre-detail-extras-skeleton", children: [
|
|
275
|
+
/* @__PURE__ */ jsx4("span", { className: "gtv-info-panel-children-title", children: "Summary" }),
|
|
276
|
+
/* @__PURE__ */ jsx4(Skeleton2, { className: "h-4 w-3/4" })
|
|
288
277
|
] }),
|
|
289
278
|
family
|
|
290
279
|
] });
|
|
291
280
|
}
|
|
292
|
-
if (!overview) return /* @__PURE__ */
|
|
293
|
-
const { summary,
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
/* @__PURE__ */
|
|
298
|
-
/* @__PURE__ */
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
281
|
+
if (!overview) return /* @__PURE__ */ jsx4(Fragment3, { children: family });
|
|
282
|
+
const { summary, summarySourceUrl, essentialSongs } = overview;
|
|
283
|
+
const wikidataUrl = wikidataId ? `https://www.wikidata.org/wiki/${encodeURIComponent(wikidataId)}` : null;
|
|
284
|
+
return /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
285
|
+
(summarySourceUrl || wikidataUrl) && /* @__PURE__ */ jsxs3("div", { className: "gtv-info-panel-children text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: [
|
|
286
|
+
/* @__PURE__ */ jsx4("span", { className: "gtv-info-panel-children-title", children: "Links" }),
|
|
287
|
+
/* @__PURE__ */ jsxs3("div", { className: "mt-1 flex flex-wrap gap-x-3 gap-y-1 text-xs", children: [
|
|
288
|
+
summarySourceUrl && /* @__PURE__ */ jsxs3(
|
|
289
|
+
"a",
|
|
290
|
+
{
|
|
291
|
+
href: summarySourceUrl,
|
|
292
|
+
target: "_blank",
|
|
293
|
+
rel: "noopener noreferrer",
|
|
294
|
+
className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
|
|
295
|
+
children: [
|
|
296
|
+
/* @__PURE__ */ jsx4(SiWikipedia, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
|
|
297
|
+
"Wikipedia"
|
|
298
|
+
]
|
|
299
|
+
}
|
|
300
|
+
),
|
|
301
|
+
wikidataUrl && /* @__PURE__ */ jsxs3(
|
|
302
|
+
"a",
|
|
303
|
+
{
|
|
304
|
+
href: wikidataUrl,
|
|
305
|
+
target: "_blank",
|
|
306
|
+
rel: "noopener noreferrer",
|
|
307
|
+
className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
|
|
308
|
+
children: [
|
|
309
|
+
/* @__PURE__ */ jsx4(SiWikidata, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
|
|
310
|
+
"Wikidata"
|
|
311
|
+
]
|
|
312
|
+
}
|
|
313
|
+
)
|
|
314
|
+
] })
|
|
315
|
+
] }),
|
|
316
|
+
renderSummary ? renderSummary(overview) : /* @__PURE__ */ jsxs3("div", { className: "gtv-info-panel-children text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: [
|
|
317
|
+
/* @__PURE__ */ jsx4("span", { className: "gtv-info-panel-children-title", children: "Summary" }),
|
|
318
|
+
/* @__PURE__ */ jsx4(ClampedText, { className: "mt-1", children: summary ?? "\u2014" }, genreUuid)
|
|
309
319
|
] }),
|
|
310
|
-
renderSummary && summarySourceUrl && /* @__PURE__ */ jsx5(
|
|
311
|
-
"a",
|
|
312
|
-
{
|
|
313
|
-
href: summarySourceUrl,
|
|
314
|
-
target: "_blank",
|
|
315
|
-
rel: "noopener noreferrer",
|
|
316
|
-
className: "mx-3.5 mb-2.5 inline-block text-xs text-[var(--color-text-muted)] underline hover:text-[var(--color-text)]",
|
|
317
|
-
children: "Source: Wikipedia"
|
|
318
|
-
}
|
|
319
|
-
),
|
|
320
320
|
family,
|
|
321
321
|
renderGenreDetailExtras?.(overview),
|
|
322
|
-
renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && /* @__PURE__ */
|
|
323
|
-
/* @__PURE__ */
|
|
322
|
+
renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && /* @__PURE__ */ jsxs3("div", { className: "gtv-info-panel-children text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: [
|
|
323
|
+
/* @__PURE__ */ jsxs3("span", { className: "gtv-info-panel-children-title", children: [
|
|
324
324
|
"Essential ",
|
|
325
325
|
trackNoun.plural
|
|
326
326
|
] }),
|
|
327
|
-
essentialSongs.length > 0 ? /* @__PURE__ */
|
|
327
|
+
essentialSongs.length > 0 ? /* @__PURE__ */ jsx4("ul", { className: "mt-1 divide-y divide-[var(--color-border)]", children: essentialSongs.map((track) => /* @__PURE__ */ jsx4("li", { className: "py-1", children: track.title }, track.uuid)) }) : /* @__PURE__ */ jsx4("p", { className: "mt-1", children: "\u2014" })
|
|
328
328
|
] }),
|
|
329
|
-
/* @__PURE__ */
|
|
329
|
+
/* @__PURE__ */ jsx4(
|
|
330
330
|
GenrePlaylistTracks,
|
|
331
331
|
{
|
|
332
332
|
genreUuid,
|
|
@@ -383,10 +383,10 @@ function useGenrePlaylistPlayback({
|
|
|
383
383
|
|
|
384
384
|
// src/genre-tree/playlist-tree/TreePerRoot.tsx
|
|
385
385
|
import { capitalize as capitalize2, useTrackNoun as useTrackNoun3 } from "@behindthemusictree/app-kit/track-noun";
|
|
386
|
-
import { jsx as
|
|
386
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
387
387
|
function GenrePlaylistTreePerRoot({
|
|
388
388
|
scope,
|
|
389
|
-
className,
|
|
389
|
+
className: className2,
|
|
390
390
|
rootUuid,
|
|
391
391
|
genrePlaylistTreePerRoot,
|
|
392
392
|
handleGenreCreationAction,
|
|
@@ -439,7 +439,7 @@ function GenrePlaylistTreePerRoot({
|
|
|
439
439
|
}
|
|
440
440
|
play(genrePlaylist).catch((error) => {
|
|
441
441
|
console.error("Failed to load genre playlist tracks:", error);
|
|
442
|
-
showPopup(/* @__PURE__ */
|
|
442
|
+
showPopup(/* @__PURE__ */ jsx5(InternalErrorPopup, { errorCode: ErrorCode2.CLIENT_UNKNOWN }));
|
|
443
443
|
});
|
|
444
444
|
},
|
|
445
445
|
[
|
|
@@ -471,10 +471,10 @@ function GenrePlaylistTreePerRoot({
|
|
|
471
471
|
if (confirm(`Are you sure you want to delete "${node.name}"?`)) {
|
|
472
472
|
}
|
|
473
473
|
}, []);
|
|
474
|
-
return /* @__PURE__ */
|
|
474
|
+
return /* @__PURE__ */ jsx5(
|
|
475
475
|
GenreTree,
|
|
476
476
|
{
|
|
477
|
-
className,
|
|
477
|
+
className: className2,
|
|
478
478
|
nodes,
|
|
479
479
|
itemCountLabel: `${capitalize2(trackNoun.singular)} count`,
|
|
480
480
|
rootColor: getGenreTreeColor(rootUuid),
|
|
@@ -511,11 +511,23 @@ import {
|
|
|
511
511
|
import { usePlayer as usePlayer3 } from "@behindthemusictree/app-kit/player";
|
|
512
512
|
import { usePopup as usePopup3 } from "@behindthemusictree/app-kit/popup";
|
|
513
513
|
import { ErrorCode as ErrorCode3 } from "@behindthemusictree/app-kit/transport";
|
|
514
|
+
|
|
515
|
+
// src/genre-tree/playlist-tree/size-metric.ts
|
|
516
|
+
var hasViewsCount = (genrePlaylists) => genrePlaylists.every((genrePlaylist) => genrePlaylist.viewsCount !== void 0);
|
|
517
|
+
var genrePlaylistSize = (genrePlaylist, metric) => {
|
|
518
|
+
if (metric === "songs") return genrePlaylist.tracksCount;
|
|
519
|
+
if (genrePlaylist.viewsCount === void 0) {
|
|
520
|
+
throw new Error(`Genre playlist ${genrePlaylist.uuid} has no viewsCount to size by views`);
|
|
521
|
+
}
|
|
522
|
+
return genrePlaylist.viewsCount;
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
// src/genre-tree/playlist-tree/TreeWheel.tsx
|
|
514
526
|
import { capitalize as capitalize3, useTrackNoun as useTrackNoun4 } from "@behindthemusictree/app-kit/track-noun";
|
|
515
|
-
import { jsx as
|
|
527
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
516
528
|
function GenrePlaylistTreeWheel({
|
|
517
529
|
scope,
|
|
518
|
-
className,
|
|
530
|
+
className: className2,
|
|
519
531
|
genrePlaylists,
|
|
520
532
|
handleGenreCreationAction,
|
|
521
533
|
handleGenreRenameAction,
|
|
@@ -531,6 +543,9 @@ function GenrePlaylistTreeWheel({
|
|
|
531
543
|
allowWheelRotation,
|
|
532
544
|
showToolbar,
|
|
533
545
|
controlsOrientation,
|
|
546
|
+
sizeMetric = "songs",
|
|
547
|
+
floatingControlsLeading,
|
|
548
|
+
busy,
|
|
534
549
|
defaultViewport,
|
|
535
550
|
onViewportChange,
|
|
536
551
|
playback,
|
|
@@ -546,12 +561,12 @@ function GenrePlaylistTreeWheel({
|
|
|
546
561
|
id: genrePlaylist.uuid,
|
|
547
562
|
parentId: genrePlaylist.parent?.uuid ?? null,
|
|
548
563
|
name: genrePlaylist.name,
|
|
549
|
-
itemCount: genrePlaylist
|
|
564
|
+
itemCount: genrePlaylistSize(genrePlaylist, sizeMetric),
|
|
550
565
|
actionable: Boolean(genrePlaylist.criteria),
|
|
551
566
|
side: genrePlaylist.criteria?.side ?? void 0,
|
|
552
567
|
wikidataId: genrePlaylist.criteria?.wikidataId ?? null
|
|
553
568
|
})),
|
|
554
|
-
[genrePlaylists]
|
|
569
|
+
[genrePlaylists, sizeMetric]
|
|
555
570
|
);
|
|
556
571
|
const handlePlayPause = useCallback3(
|
|
557
572
|
(nodeId) => {
|
|
@@ -566,7 +581,7 @@ function GenrePlaylistTreeWheel({
|
|
|
566
581
|
}
|
|
567
582
|
play(genrePlaylist).catch((error) => {
|
|
568
583
|
console.error("Failed to load genre playlist tracks:", error);
|
|
569
|
-
showPopup(/* @__PURE__ */
|
|
584
|
+
showPopup(/* @__PURE__ */ jsx6(InternalErrorPopup, { errorCode: ErrorCode3.CLIENT_UNKNOWN }));
|
|
570
585
|
});
|
|
571
586
|
},
|
|
572
587
|
[
|
|
@@ -598,12 +613,12 @@ function GenrePlaylistTreeWheel({
|
|
|
598
613
|
if (confirm(`Are you sure you want to delete "${node.name}"?`)) {
|
|
599
614
|
}
|
|
600
615
|
}, []);
|
|
601
|
-
return /* @__PURE__ */
|
|
616
|
+
return /* @__PURE__ */ jsx6(
|
|
602
617
|
GenreTreeWheel,
|
|
603
618
|
{
|
|
604
|
-
className,
|
|
619
|
+
className: className2,
|
|
605
620
|
nodes,
|
|
606
|
-
itemCountLabel: `${capitalize3(trackNoun.singular)} count`,
|
|
621
|
+
itemCountLabel: sizeMetric === "views" ? "Views" : `${capitalize3(trackNoun.singular)} count`,
|
|
607
622
|
playingNodeId,
|
|
608
623
|
playState: isPlaying ? "playing" : "paused",
|
|
609
624
|
onPlayPause: handlePlayPause,
|
|
@@ -621,6 +636,8 @@ function GenrePlaylistTreeWheel({
|
|
|
621
636
|
allowWheelRotation,
|
|
622
637
|
showToolbar,
|
|
623
638
|
controlsOrientation,
|
|
639
|
+
floatingControlsLeading,
|
|
640
|
+
busy,
|
|
624
641
|
defaultViewport,
|
|
625
642
|
onViewportChange,
|
|
626
643
|
...infoPanelCuration,
|
|
@@ -637,11 +654,11 @@ import {
|
|
|
637
654
|
} from "@behindthemusictree/genre-tree-view";
|
|
638
655
|
import { usePlayer as usePlayer4 } from "@behindthemusictree/app-kit/player";
|
|
639
656
|
import { capitalize as capitalize4, useTrackNoun as useTrackNoun5 } from "@behindthemusictree/app-kit/track-noun";
|
|
640
|
-
import { jsx as
|
|
657
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
641
658
|
function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
642
659
|
const {
|
|
643
660
|
scope,
|
|
644
|
-
className,
|
|
661
|
+
className: className2,
|
|
645
662
|
genrePlaylists,
|
|
646
663
|
handleGenreCreationAction,
|
|
647
664
|
handleGenreRenameAction,
|
|
@@ -657,6 +674,9 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
657
674
|
allowWheelRotation,
|
|
658
675
|
showToolbar,
|
|
659
676
|
controlsOrientation,
|
|
677
|
+
sizeMetric = "songs",
|
|
678
|
+
floatingControlsLeading,
|
|
679
|
+
busy,
|
|
660
680
|
outline = false,
|
|
661
681
|
defaultViewport,
|
|
662
682
|
onViewportChange,
|
|
@@ -664,10 +684,6 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
664
684
|
} = props;
|
|
665
685
|
const focus = props;
|
|
666
686
|
const infoPanelCuration = {
|
|
667
|
-
editMode: props.editMode,
|
|
668
|
-
onEditModeChange: props.onEditModeChange,
|
|
669
|
-
renderTitleEditor: props.renderTitleEditor,
|
|
670
|
-
renderParentsEditor: props.renderParentsEditor,
|
|
671
687
|
renderDangerZone: props.renderDangerZone,
|
|
672
688
|
expanded: props.expanded,
|
|
673
689
|
onExpandedChange: props.onExpandedChange,
|
|
@@ -684,14 +700,15 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
684
700
|
id: genrePlaylist.uuid,
|
|
685
701
|
parentId: genrePlaylist.parent?.uuid ?? null,
|
|
686
702
|
name: genrePlaylist.name,
|
|
687
|
-
itemCount: genrePlaylist
|
|
703
|
+
itemCount: genrePlaylistSize(genrePlaylist, sizeMetric),
|
|
688
704
|
actionable: Boolean(genrePlaylist.criteria),
|
|
689
705
|
detached: !genrePlaylist.criteria,
|
|
690
706
|
side: genrePlaylist.criteria?.side ?? void 0,
|
|
691
707
|
wikidataId: genrePlaylist.criteria?.wikidataId ?? null,
|
|
692
|
-
umbrella: genrePlaylist.isUmbrella
|
|
708
|
+
umbrella: genrePlaylist.isUmbrella,
|
|
709
|
+
scene: genrePlaylist.scene ?? void 0
|
|
693
710
|
})),
|
|
694
|
-
[genrePlaylists]
|
|
711
|
+
[genrePlaylists, sizeMetric]
|
|
695
712
|
);
|
|
696
713
|
const handlePlayPause = useCallback4(
|
|
697
714
|
(nodeId) => {
|
|
@@ -731,9 +748,9 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
731
748
|
}
|
|
732
749
|
}, []);
|
|
733
750
|
const treeProps = {
|
|
734
|
-
className,
|
|
751
|
+
className: className2,
|
|
735
752
|
nodes,
|
|
736
|
-
itemCountLabel: `${capitalize4(trackNoun.singular)} count`,
|
|
753
|
+
itemCountLabel: sizeMetric === "views" ? "Views" : `${capitalize4(trackNoun.singular)} count`,
|
|
737
754
|
playingNodeId,
|
|
738
755
|
playState: isPlaying ? "playing" : "paused",
|
|
739
756
|
onPlayPause: handlePlayPause,
|
|
@@ -752,12 +769,14 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
752
769
|
...infoPanelCuration,
|
|
753
770
|
readOnly
|
|
754
771
|
};
|
|
755
|
-
return outline ? /* @__PURE__ */
|
|
772
|
+
return outline ? /* @__PURE__ */ jsx7(GenreTreeOutline, { ...treeProps }) : /* @__PURE__ */ jsx7(
|
|
756
773
|
GenreTreeWheelRadialPopCore,
|
|
757
774
|
{
|
|
758
775
|
...treeProps,
|
|
759
776
|
allowWheelRotation,
|
|
760
777
|
controlsOrientation,
|
|
778
|
+
floatingControlsLeading,
|
|
779
|
+
busy,
|
|
761
780
|
defaultViewport,
|
|
762
781
|
onViewportChange,
|
|
763
782
|
...focus.onFocusGenreChange ? { focusNodeId: focus.focusGenreUuid, onFocusChange: focus.onFocusGenreChange } : { focusNodeId: void 0, onFocusChange: void 0 }
|
|
@@ -767,7 +786,7 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
767
786
|
|
|
768
787
|
// src/genre-tree/GenreTreeWheelHandoff.tsx
|
|
769
788
|
import { useEffect as useEffect2, useRef as useRef3, useState as useState2 } from "react";
|
|
770
|
-
import { jsx as
|
|
789
|
+
import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
771
790
|
function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
|
|
772
791
|
const [revealed, setRevealed] = useState2(false);
|
|
773
792
|
const onRevealedChangeRef = useRef3(onRevealedChange);
|
|
@@ -786,8 +805,8 @@ function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
|
|
|
786
805
|
cancelAnimationFrame(innerFrame);
|
|
787
806
|
};
|
|
788
807
|
}, []);
|
|
789
|
-
return /* @__PURE__ */
|
|
790
|
-
/* @__PURE__ */
|
|
808
|
+
return /* @__PURE__ */ jsxs4("div", { className: "relative w-full h-full", children: [
|
|
809
|
+
/* @__PURE__ */ jsx8(
|
|
791
810
|
"div",
|
|
792
811
|
{
|
|
793
812
|
className: "absolute inset-0 transition-opacity duration-150 ease-out",
|
|
@@ -796,15 +815,15 @@ function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
|
|
|
796
815
|
children
|
|
797
816
|
}
|
|
798
817
|
),
|
|
799
|
-
!revealed && /* @__PURE__ */
|
|
818
|
+
!revealed && /* @__PURE__ */ jsx8("div", { className: "absolute inset-0", "aria-hidden": "true", children: skeleton })
|
|
800
819
|
] });
|
|
801
820
|
}
|
|
802
821
|
|
|
803
822
|
// src/genre-tree/GenreSearch.tsx
|
|
804
|
-
import { ArrowLeft, Search, X } from "lucide-react";
|
|
823
|
+
import { ArrowLeft, LoaderCircle, Search, X } from "lucide-react";
|
|
805
824
|
import { Input } from "@behindthemusictree/ui";
|
|
806
825
|
import { useGenreSearch } from "@behindthemusictree/app-kit/genre-tree";
|
|
807
|
-
import { jsx as
|
|
826
|
+
import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
808
827
|
function GenreSearch({
|
|
809
828
|
genrePlaylists,
|
|
810
829
|
onSelect,
|
|
@@ -823,21 +842,22 @@ function GenreSearch({
|
|
|
823
842
|
});
|
|
824
843
|
const extraItems = query !== "" && query !== selectedName ? extraResults?.items ?? [] : [];
|
|
825
844
|
const label = extraResults ? "Search" : "Search a genre";
|
|
845
|
+
const loading = query !== "" && query !== selectedName && (extraResults?.loading ?? false);
|
|
826
846
|
const showList = results.length > 0 || extraItems.length > 0;
|
|
827
847
|
const heading = "px-3 pt-2 pb-1 text-xs font-medium uppercase text-[var(--color-text-muted)]";
|
|
828
|
-
return /* @__PURE__ */
|
|
829
|
-
onPrevious && /* @__PURE__ */
|
|
848
|
+
return /* @__PURE__ */ jsxs5("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: [
|
|
849
|
+
onPrevious && /* @__PURE__ */ jsx9(
|
|
830
850
|
"button",
|
|
831
851
|
{
|
|
832
852
|
type: "button",
|
|
833
853
|
"aria-label": "Previous",
|
|
834
854
|
title: "Previous",
|
|
835
|
-
className: "genre-search-previous absolute left-2 top-1/2 z-10 hidden p-1.5 text-[var(--color-text-muted)] rounded-full -translate-y-1/2 hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-text)]",
|
|
855
|
+
className: "genre-search-previous absolute left-2 top-1/2 z-10 hidden items-center justify-center p-1.5 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] [@media(pointer:coarse)]:min-w-[var(--layout-touch-target)] text-[var(--color-text-muted)] rounded-full -translate-y-1/2 hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-text)]",
|
|
836
856
|
onClick: onPrevious,
|
|
837
|
-
children: /* @__PURE__ */
|
|
857
|
+
children: /* @__PURE__ */ jsx9(ArrowLeft, { "aria-hidden": "true", className: "w-4 h-4" })
|
|
838
858
|
}
|
|
839
859
|
),
|
|
840
|
-
/* @__PURE__ */
|
|
860
|
+
/* @__PURE__ */ jsx9(
|
|
841
861
|
Input,
|
|
842
862
|
{
|
|
843
863
|
type: "text",
|
|
@@ -848,49 +868,75 @@ function GenreSearch({
|
|
|
848
868
|
},
|
|
849
869
|
placeholder: placeholder ?? `${label}\u2026`,
|
|
850
870
|
"aria-label": label,
|
|
871
|
+
"aria-busy": loading,
|
|
872
|
+
inputMode: "search",
|
|
873
|
+
enterKeyHint: "search",
|
|
874
|
+
autoComplete: "off",
|
|
851
875
|
className: "h-[var(--layout-search-h,3rem)] pl-6 pr-20 text-base transition-[padding] duration-200 ease-out motion-reduce:transition-none bg-[var(--color-surface)] border-[var(--color-border)] rounded-full shadow-md"
|
|
852
876
|
}
|
|
853
877
|
),
|
|
854
|
-
/* @__PURE__ */
|
|
855
|
-
/* @__PURE__ */
|
|
856
|
-
|
|
878
|
+
/* @__PURE__ */ jsxs5("div", { className: "absolute right-3 top-1/2 flex items-center -translate-y-1/2", children: [
|
|
879
|
+
/* @__PURE__ */ jsxs5(
|
|
880
|
+
"span",
|
|
881
|
+
{
|
|
882
|
+
"data-loading": loading,
|
|
883
|
+
className: "relative p-2 text-[var(--color-text-muted)] pointer-events-none",
|
|
884
|
+
children: [
|
|
885
|
+
/* @__PURE__ */ jsx9(
|
|
886
|
+
Search,
|
|
887
|
+
{
|
|
888
|
+
"aria-hidden": "true",
|
|
889
|
+
className: `w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? "opacity-0" : ""}`
|
|
890
|
+
}
|
|
891
|
+
),
|
|
892
|
+
/* @__PURE__ */ jsx9(
|
|
893
|
+
LoaderCircle,
|
|
894
|
+
{
|
|
895
|
+
"aria-hidden": "true",
|
|
896
|
+
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"}`
|
|
897
|
+
}
|
|
898
|
+
)
|
|
899
|
+
]
|
|
900
|
+
}
|
|
901
|
+
),
|
|
902
|
+
canClear && /* @__PURE__ */ jsx9(
|
|
857
903
|
"button",
|
|
858
904
|
{
|
|
859
905
|
type: "button",
|
|
860
906
|
"aria-label": "Clear search",
|
|
861
|
-
className: "p-2 text-[var(--color-text-muted)] rounded-full hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-text)]",
|
|
907
|
+
className: "flex items-center justify-center p-2 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] [@media(pointer:coarse)]:min-w-[var(--layout-touch-target)] text-[var(--color-text-muted)] rounded-full hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-text)]",
|
|
862
908
|
onClick: () => {
|
|
863
909
|
clear();
|
|
864
910
|
onQueryChange?.("");
|
|
865
911
|
},
|
|
866
|
-
children: /* @__PURE__ */
|
|
912
|
+
children: /* @__PURE__ */ jsx9(X, { "aria-hidden": "true", className: "w-6 h-6" })
|
|
867
913
|
}
|
|
868
914
|
)
|
|
869
915
|
] }),
|
|
870
|
-
extraResults?.status && /* @__PURE__ */
|
|
871
|
-
showList && /* @__PURE__ */
|
|
872
|
-
extraResults && results.length > 0 && /* @__PURE__ */
|
|
873
|
-
results.map((genrePlaylist) => /* @__PURE__ */
|
|
916
|
+
extraResults?.status && /* @__PURE__ */ jsx9("p", { role: "status", "aria-live": "polite", className: "sr-only", children: extraResults.status }),
|
|
917
|
+
showList && /* @__PURE__ */ jsxs5("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: [
|
|
918
|
+
extraResults && results.length > 0 && /* @__PURE__ */ jsx9("li", { className: heading, children: "Genres" }),
|
|
919
|
+
results.map((genrePlaylist) => /* @__PURE__ */ jsx9("li", { children: /* @__PURE__ */ jsxs5(
|
|
874
920
|
"button",
|
|
875
921
|
{
|
|
876
922
|
type: "button",
|
|
877
|
-
className: "w-full px-3 py-1.5 text-left hover:bg-[var(--color-surface-muted)]",
|
|
923
|
+
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)]",
|
|
878
924
|
onClick: () => {
|
|
879
925
|
select(genrePlaylist);
|
|
880
926
|
onQueryChange?.("");
|
|
881
927
|
},
|
|
882
928
|
children: [
|
|
883
929
|
genrePlaylist.name,
|
|
884
|
-
genrePlaylist.isUmbrella && /* @__PURE__ */
|
|
930
|
+
genrePlaylist.isUmbrella && /* @__PURE__ */ jsx9("span", { className: "ml-2 text-xs italic text-[var(--color-text-muted)]", children: "umbrella" })
|
|
885
931
|
]
|
|
886
932
|
}
|
|
887
933
|
) }, genrePlaylist.uuid)),
|
|
888
|
-
extraResults && extraItems.length > 0 && /* @__PURE__ */
|
|
889
|
-
extraItems.map((item) => /* @__PURE__ */
|
|
934
|
+
extraResults && extraItems.length > 0 && /* @__PURE__ */ jsx9("li", { className: heading, children: extraResults.label }),
|
|
935
|
+
extraItems.map((item) => /* @__PURE__ */ jsx9("li", { children: /* @__PURE__ */ jsx9(
|
|
890
936
|
"button",
|
|
891
937
|
{
|
|
892
938
|
type: "button",
|
|
893
|
-
className: "w-full px-3 py-1.5 text-left hover:bg-[var(--color-surface-muted)]",
|
|
939
|
+
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)]",
|
|
894
940
|
onClick: () => {
|
|
895
941
|
setQuery("");
|
|
896
942
|
onQueryChange?.("");
|
|
@@ -904,8 +950,227 @@ function GenreSearch({
|
|
|
904
950
|
}
|
|
905
951
|
|
|
906
952
|
// src/genre-tree/GenreActionBand.tsx
|
|
907
|
-
import { MoveUpRight, Pause, Pencil, Play, Plus, ZoomIn } from "lucide-react";
|
|
953
|
+
import { ArrowLeftRight, MoveUpRight, Pause, Pencil, Play, Plus, ZoomIn } from "lucide-react";
|
|
954
|
+
|
|
955
|
+
// src/genre-tree/ShareActionButton.tsx
|
|
956
|
+
import { Share2 as Share22 } from "lucide-react";
|
|
957
|
+
import { usePopup as usePopup4 } from "@behindthemusictree/app-kit/popup";
|
|
958
|
+
|
|
959
|
+
// src/genre-tree/SharePopup.tsx
|
|
960
|
+
import { useCallback as useCallback5, useEffect as useEffect3, useMemo as useMemo5, useState as useState3 } from "react";
|
|
961
|
+
import { Link2, Share2 } from "lucide-react";
|
|
962
|
+
import { FaWhatsapp } from "react-icons/fa";
|
|
963
|
+
import { SiGmail } from "react-icons/si";
|
|
964
|
+
|
|
965
|
+
// src/genre-tree/ShareDestinationButton.tsx
|
|
966
|
+
import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
967
|
+
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";
|
|
968
|
+
function Contents({ icon, label }) {
|
|
969
|
+
return /* @__PURE__ */ jsxs6(Fragment4, { children: [
|
|
970
|
+
/* @__PURE__ */ jsx10("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 }),
|
|
971
|
+
/* @__PURE__ */ jsx10("span", { className: "max-w-full truncate", children: label })
|
|
972
|
+
] });
|
|
973
|
+
}
|
|
974
|
+
function ShareDestinationButton({
|
|
975
|
+
label,
|
|
976
|
+
icon,
|
|
977
|
+
href,
|
|
978
|
+
onClick,
|
|
979
|
+
disabled = false,
|
|
980
|
+
ariaLabel
|
|
981
|
+
}) {
|
|
982
|
+
if (href) {
|
|
983
|
+
return /* @__PURE__ */ jsx10("a", { href, target: "_blank", rel: "noopener noreferrer", "aria-label": ariaLabel, className, children: /* @__PURE__ */ jsx10(Contents, { icon, label }) });
|
|
984
|
+
}
|
|
985
|
+
return /* @__PURE__ */ jsx10("button", { type: "button", onClick, disabled, "aria-label": ariaLabel, className, children: /* @__PURE__ */ jsx10(Contents, { icon, label }) });
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// src/genre-tree/SharePreviewCard.tsx
|
|
989
|
+
import { Music2, UserRound } from "lucide-react";
|
|
908
990
|
import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
991
|
+
function SharePreviewCard({ target, url }) {
|
|
992
|
+
const Icon = target.kind === "genre" ? Music2 : UserRound;
|
|
993
|
+
const typeLabel = target.kind === "genre" ? "Genre" : "Artist";
|
|
994
|
+
return /* @__PURE__ */ jsx11("div", { className: "rounded-3xl bg-[var(--color-surface-muted)] p-4", children: /* @__PURE__ */ jsxs7("div", { className: "flex min-h-32 overflow-hidden rounded-2xl bg-[var(--color-surface)] shadow-lg", children: [
|
|
995
|
+
/* @__PURE__ */ jsx11("div", { className: "flex w-28 shrink-0 items-center justify-center bg-[var(--color-primary)]/15 text-[var(--color-primary)]", children: /* @__PURE__ */ jsx11(Icon, { "aria-hidden": "true", className: "h-10 w-10" }) }),
|
|
996
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex min-w-0 flex-1 flex-col justify-center gap-1 p-4", children: [
|
|
997
|
+
/* @__PURE__ */ jsx11("span", { className: "text-xs font-medium uppercase tracking-wide text-[var(--color-text-muted)]", children: typeLabel }),
|
|
998
|
+
/* @__PURE__ */ jsx11("span", { className: "truncate text-base font-semibold text-[var(--color-text)]", title: target.name, children: target.name }),
|
|
999
|
+
/* @__PURE__ */ jsx11("span", { className: "truncate text-sm text-[var(--color-primary)]", title: url, children: url })
|
|
1000
|
+
] })
|
|
1001
|
+
] }) });
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// src/genre-tree/SharePopup.tsx
|
|
1005
|
+
import { Fragment as Fragment5, jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1006
|
+
function resolveAbsoluteUrl(url) {
|
|
1007
|
+
try {
|
|
1008
|
+
const parsed = new URL(url, window.location.href);
|
|
1009
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:" ? parsed.href : void 0;
|
|
1010
|
+
} catch {
|
|
1011
|
+
return void 0;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
async function copyText(text) {
|
|
1015
|
+
try {
|
|
1016
|
+
if (navigator.clipboard?.writeText) {
|
|
1017
|
+
await navigator.clipboard.writeText(text);
|
|
1018
|
+
return true;
|
|
1019
|
+
}
|
|
1020
|
+
} catch {
|
|
1021
|
+
}
|
|
1022
|
+
const input = document.createElement("textarea");
|
|
1023
|
+
input.value = text;
|
|
1024
|
+
input.setAttribute("readonly", "");
|
|
1025
|
+
input.style.position = "fixed";
|
|
1026
|
+
input.style.opacity = "0";
|
|
1027
|
+
document.body.append(input);
|
|
1028
|
+
input.select();
|
|
1029
|
+
try {
|
|
1030
|
+
return document.execCommand("copy");
|
|
1031
|
+
} catch {
|
|
1032
|
+
return false;
|
|
1033
|
+
} finally {
|
|
1034
|
+
input.remove();
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
function SharePopup({ target, url, onClose }) {
|
|
1038
|
+
const [absoluteUrl, setAbsoluteUrl] = useState3(url);
|
|
1039
|
+
const [copyStatus, setCopyStatus] = useState3("idle");
|
|
1040
|
+
const [canUseNativeShare, setCanUseNativeShare] = useState3(false);
|
|
1041
|
+
const [shareError, setShareError] = useState3(null);
|
|
1042
|
+
const [toastId, setToastId] = useState3(0);
|
|
1043
|
+
useEffect3(() => {
|
|
1044
|
+
setAbsoluteUrl(resolveAbsoluteUrl(url) ?? "");
|
|
1045
|
+
setCopyStatus("idle");
|
|
1046
|
+
setShareError(null);
|
|
1047
|
+
}, [url]);
|
|
1048
|
+
useEffect3(() => {
|
|
1049
|
+
setCanUseNativeShare(typeof navigator !== "undefined" && typeof navigator.share === "function");
|
|
1050
|
+
}, []);
|
|
1051
|
+
const destinationUrls = useMemo5(() => {
|
|
1052
|
+
if (!absoluteUrl) return null;
|
|
1053
|
+
const message = `${target.name} \u2014 ${absoluteUrl}`;
|
|
1054
|
+
return {
|
|
1055
|
+
whatsapp: `https://wa.me/?text=${encodeURIComponent(message)}`,
|
|
1056
|
+
gmail: `https://mail.google.com/mail/?view=cm&fs=1&su=${encodeURIComponent(`Share: ${target.name}`)}&body=${encodeURIComponent(message)}`
|
|
1057
|
+
};
|
|
1058
|
+
}, [absoluteUrl, target.name]);
|
|
1059
|
+
const handleCopy = async () => {
|
|
1060
|
+
if (!absoluteUrl) {
|
|
1061
|
+
setCopyStatus("failed");
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
setShareError(null);
|
|
1065
|
+
const copied = await copyText(absoluteUrl);
|
|
1066
|
+
setCopyStatus(copied ? "idle" : "failed");
|
|
1067
|
+
if (copied) setToastId((id) => id + 1);
|
|
1068
|
+
};
|
|
1069
|
+
const dismissToast = useCallback5(() => setToastId(0), []);
|
|
1070
|
+
const handleNativeShare = async () => {
|
|
1071
|
+
if (!absoluteUrl || !navigator.share) return;
|
|
1072
|
+
setShareError(null);
|
|
1073
|
+
try {
|
|
1074
|
+
await navigator.share({ title: target.name, url: absoluteUrl });
|
|
1075
|
+
} catch (error) {
|
|
1076
|
+
if (error instanceof Error && error.name === "AbortError") return;
|
|
1077
|
+
setShareError("Could not open the share options.");
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
1080
|
+
return /* @__PURE__ */ jsxs8(Fragment5, { children: [
|
|
1081
|
+
/* @__PURE__ */ jsx12(
|
|
1082
|
+
BasePopup_default,
|
|
1083
|
+
{
|
|
1084
|
+
title: "Share",
|
|
1085
|
+
onClose,
|
|
1086
|
+
isDismissable: true,
|
|
1087
|
+
trapFocus: true,
|
|
1088
|
+
closeOnEscape: true,
|
|
1089
|
+
closeOnBackdrop: true,
|
|
1090
|
+
className: "!w-full !max-w-[28rem] overflow-hidden !rounded-[2rem] !bg-[var(--color-surface)] shadow-2xl",
|
|
1091
|
+
overlayClassName: "!bg-black/50 p-4 backdrop-blur-[2px]",
|
|
1092
|
+
titleClassName: "!rounded-none !bg-transparent !px-6 !pb-2 !pt-6",
|
|
1093
|
+
titleTextClassName: "!text-xl !font-semibold !text-[var(--color-text)]",
|
|
1094
|
+
closeButtonClassName: "!h-10 !w-10 !rounded-none !border-0 !bg-transparent !p-0 !text-[var(--color-text)] hover:!bg-transparent hover:!text-[var(--color-text)]",
|
|
1095
|
+
contentClassName: "!rounded-none !bg-transparent !px-6 !pb-6 !pt-2",
|
|
1096
|
+
children: /* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-4", children: [
|
|
1097
|
+
/* @__PURE__ */ jsx12(SharePreviewCard, { target, url: absoluteUrl || url }),
|
|
1098
|
+
/* @__PURE__ */ jsxs8("div", { className: "grid grid-cols-3 gap-1 rounded-3xl bg-[var(--color-surface)] p-3 sm:grid-cols-4", children: [
|
|
1099
|
+
/* @__PURE__ */ jsx12(
|
|
1100
|
+
ShareDestinationButton,
|
|
1101
|
+
{
|
|
1102
|
+
label: "Copy Link",
|
|
1103
|
+
icon: /* @__PURE__ */ jsx12(Link2, { "aria-hidden": "true", className: "h-6 w-6" }),
|
|
1104
|
+
onClick: () => void handleCopy(),
|
|
1105
|
+
ariaLabel: "Copy link",
|
|
1106
|
+
disabled: !absoluteUrl
|
|
1107
|
+
}
|
|
1108
|
+
),
|
|
1109
|
+
/* @__PURE__ */ jsx12(
|
|
1110
|
+
ShareDestinationButton,
|
|
1111
|
+
{
|
|
1112
|
+
label: "WhatsApp",
|
|
1113
|
+
icon: /* @__PURE__ */ jsx12(FaWhatsapp, { "aria-hidden": "true", className: "h-9 w-9 text-[#25D366]" }),
|
|
1114
|
+
href: destinationUrls?.whatsapp,
|
|
1115
|
+
ariaLabel: `Share ${target.name} via WhatsApp`,
|
|
1116
|
+
disabled: !absoluteUrl
|
|
1117
|
+
}
|
|
1118
|
+
),
|
|
1119
|
+
/* @__PURE__ */ jsx12(
|
|
1120
|
+
ShareDestinationButton,
|
|
1121
|
+
{
|
|
1122
|
+
label: "Gmail",
|
|
1123
|
+
icon: /* @__PURE__ */ jsx12(SiGmail, { "aria-hidden": "true", className: "h-8 w-8 text-[#EA4335]" }),
|
|
1124
|
+
href: destinationUrls?.gmail,
|
|
1125
|
+
ariaLabel: `Email ${target.name} with Gmail`,
|
|
1126
|
+
disabled: !absoluteUrl
|
|
1127
|
+
}
|
|
1128
|
+
),
|
|
1129
|
+
canUseNativeShare && /* @__PURE__ */ jsx12(
|
|
1130
|
+
ShareDestinationButton,
|
|
1131
|
+
{
|
|
1132
|
+
label: "More",
|
|
1133
|
+
icon: /* @__PURE__ */ jsx12(Share2, { "aria-hidden": "true", className: "h-6 w-6" }),
|
|
1134
|
+
onClick: () => void handleNativeShare(),
|
|
1135
|
+
ariaLabel: "More sharing options",
|
|
1136
|
+
disabled: !absoluteUrl
|
|
1137
|
+
}
|
|
1138
|
+
)
|
|
1139
|
+
] }),
|
|
1140
|
+
copyStatus === "failed" && /* @__PURE__ */ jsx12("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: "Could not copy the link." }),
|
|
1141
|
+
shareError && /* @__PURE__ */ jsx12("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: shareError })
|
|
1142
|
+
] })
|
|
1143
|
+
}
|
|
1144
|
+
),
|
|
1145
|
+
toastId > 0 && /* @__PURE__ */ jsx12(Toast, { message: "Link copied", onClose: dismissToast }, toastId)
|
|
1146
|
+
] });
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
// src/genre-tree/ShareActionButton.tsx
|
|
1150
|
+
import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1151
|
+
function ShareActionButton({ target, url }) {
|
|
1152
|
+
const { showPopup, hidePopup } = usePopup4();
|
|
1153
|
+
const openSharePopup = () => {
|
|
1154
|
+
showPopup(/* @__PURE__ */ jsx13(SharePopup, { target, url, onClose: hidePopup }));
|
|
1155
|
+
};
|
|
1156
|
+
return /* @__PURE__ */ jsxs9(
|
|
1157
|
+
"button",
|
|
1158
|
+
{
|
|
1159
|
+
type: "button",
|
|
1160
|
+
onClick: openSharePopup,
|
|
1161
|
+
className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none",
|
|
1162
|
+
"aria-label": `Share ${target.name}`,
|
|
1163
|
+
title: `Share ${target.name}`,
|
|
1164
|
+
children: [
|
|
1165
|
+
/* @__PURE__ */ jsx13("span", { className: "flex items-center justify-center w-10 h-10 rounded-full group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)] bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]", children: /* @__PURE__ */ jsx13(Share22, { "aria-hidden": "true", className: "w-5 h-5" }) }),
|
|
1166
|
+
"Share"
|
|
1167
|
+
]
|
|
1168
|
+
}
|
|
1169
|
+
);
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
// src/genre-tree/GenreActionBand.tsx
|
|
1173
|
+
import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
909
1174
|
function GenreActionBand({
|
|
910
1175
|
node,
|
|
911
1176
|
onPlayPause,
|
|
@@ -914,7 +1179,12 @@ function GenreActionBand({
|
|
|
914
1179
|
onAddChild,
|
|
915
1180
|
onRename,
|
|
916
1181
|
onChangeParent,
|
|
917
|
-
|
|
1182
|
+
side,
|
|
1183
|
+
onToggleSide,
|
|
1184
|
+
toggleSideDisabledReason,
|
|
1185
|
+
additionalActions,
|
|
1186
|
+
shareTarget,
|
|
1187
|
+
shareUrl
|
|
918
1188
|
}) {
|
|
919
1189
|
const items = [];
|
|
920
1190
|
const adminItems = [];
|
|
@@ -922,7 +1192,7 @@ function GenreActionBand({
|
|
|
922
1192
|
items.push({
|
|
923
1193
|
key: "play-pause",
|
|
924
1194
|
label: isPlaying ? "Pause" : "Play",
|
|
925
|
-
icon: isPlaying ? /* @__PURE__ */
|
|
1195
|
+
icon: isPlaying ? /* @__PURE__ */ jsx14(Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ jsx14(Play, { "aria-hidden": "true", className: "w-5 h-5" }),
|
|
926
1196
|
onClick: onPlayPause
|
|
927
1197
|
});
|
|
928
1198
|
}
|
|
@@ -930,18 +1200,28 @@ function GenreActionBand({
|
|
|
930
1200
|
items.push({
|
|
931
1201
|
key: "zoom-in",
|
|
932
1202
|
label: "Focus",
|
|
933
|
-
icon: /* @__PURE__ */
|
|
1203
|
+
icon: /* @__PURE__ */ jsx14(ZoomIn, { "aria-hidden": "true", className: "w-5 h-5" }),
|
|
934
1204
|
onClick: onZoomIn
|
|
935
1205
|
});
|
|
936
1206
|
}
|
|
937
1207
|
if (onAddChild) {
|
|
938
|
-
adminItems.push({ key: "add-child", label: "Add", icon: /* @__PURE__ */
|
|
1208
|
+
adminItems.push({ key: "add-child", label: "Add", icon: /* @__PURE__ */ jsx14(Plus, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onAddChild });
|
|
939
1209
|
}
|
|
940
1210
|
if (onRename) {
|
|
941
|
-
adminItems.push({ key: "rename", label: "Rename", icon: /* @__PURE__ */
|
|
1211
|
+
adminItems.push({ key: "rename", label: "Rename", icon: /* @__PURE__ */ jsx14(Pencil, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onRename });
|
|
942
1212
|
}
|
|
943
1213
|
if (onChangeParent) {
|
|
944
|
-
adminItems.push({ key: "change-parent", label: "Parent", icon: /* @__PURE__ */
|
|
1214
|
+
adminItems.push({ key: "change-parent", label: "Parent", icon: /* @__PURE__ */ jsx14(MoveUpRight, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onChangeParent });
|
|
1215
|
+
}
|
|
1216
|
+
if (onToggleSide) {
|
|
1217
|
+
adminItems.push({
|
|
1218
|
+
key: "toggle-side",
|
|
1219
|
+
label: side === "pop" ? "Make core" : "Make pop",
|
|
1220
|
+
icon: /* @__PURE__ */ jsx14(ArrowLeftRight, { "aria-hidden": "true", className: "w-5 h-5" }),
|
|
1221
|
+
onClick: onToggleSide,
|
|
1222
|
+
disabled: !!toggleSideDisabledReason,
|
|
1223
|
+
title: toggleSideDisabledReason
|
|
1224
|
+
});
|
|
945
1225
|
}
|
|
946
1226
|
for (const action of additionalActions?.(node) ?? []) {
|
|
947
1227
|
items.push({
|
|
@@ -952,31 +1232,35 @@ function GenreActionBand({
|
|
|
952
1232
|
disabled: action.enabled ? !action.enabled(node) : false
|
|
953
1233
|
});
|
|
954
1234
|
}
|
|
955
|
-
if (items.length === 0 && adminItems.length === 0) return null;
|
|
956
|
-
const renderItems = (list) => list.map((item) => /* @__PURE__ */
|
|
1235
|
+
if (items.length === 0 && (!shareTarget || !shareUrl) && adminItems.length === 0) return null;
|
|
1236
|
+
const renderItems = (list) => list.map((item) => /* @__PURE__ */ jsxs10(
|
|
957
1237
|
"button",
|
|
958
1238
|
{
|
|
959
1239
|
type: "button",
|
|
960
1240
|
disabled: item.disabled,
|
|
1241
|
+
title: item.title,
|
|
961
1242
|
onClick: item.onClick,
|
|
962
1243
|
className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none disabled:opacity-50",
|
|
963
1244
|
children: [
|
|
964
|
-
/* @__PURE__ */
|
|
1245
|
+
/* @__PURE__ */ jsx14("span", { className: "flex items-center justify-center w-10 h-10 rounded-full group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)] bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)] group-disabled:group-hover:bg-[var(--color-surface-muted)]", children: item.icon }),
|
|
965
1246
|
item.label
|
|
966
1247
|
]
|
|
967
1248
|
},
|
|
968
1249
|
item.key
|
|
969
1250
|
));
|
|
970
|
-
return /* @__PURE__ */
|
|
971
|
-
items.length > 0 && /* @__PURE__ */
|
|
972
|
-
|
|
1251
|
+
return /* @__PURE__ */ jsxs10("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: [
|
|
1252
|
+
(items.length > 0 || shareTarget && shareUrl) && /* @__PURE__ */ jsxs10("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
|
|
1253
|
+
renderItems(items),
|
|
1254
|
+
shareTarget && shareUrl && /* @__PURE__ */ jsx14(ShareActionButton, { target: shareTarget, url: shareUrl })
|
|
1255
|
+
] }),
|
|
1256
|
+
adminItems.length > 0 && /* @__PURE__ */ jsxs10(
|
|
973
1257
|
"section",
|
|
974
1258
|
{
|
|
975
1259
|
"aria-label": "Admin actions",
|
|
976
1260
|
className: `px-3.5 py-3 ${items.length > 0 ? "border-t border-[var(--color-border)]" : ""}`,
|
|
977
1261
|
children: [
|
|
978
|
-
/* @__PURE__ */
|
|
979
|
-
/* @__PURE__ */
|
|
1262
|
+
/* @__PURE__ */ jsx14("h3", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-[var(--color-text-muted)]", children: "Admin" }),
|
|
1263
|
+
/* @__PURE__ */ jsx14("div", { className: "flex flex-wrap gap-1", children: renderItems(adminItems) })
|
|
980
1264
|
]
|
|
981
1265
|
}
|
|
982
1266
|
)
|
|
@@ -986,14 +1270,14 @@ function GenreActionBand({
|
|
|
986
1270
|
// src/genre-tree/PanelEdgeTab.tsx
|
|
987
1271
|
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
988
1272
|
import { twMerge } from "tailwind-merge";
|
|
989
|
-
import { jsx as
|
|
1273
|
+
import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
990
1274
|
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)]";
|
|
991
|
-
function PanelEdgeTab({ side, edge, center, controls, className }) {
|
|
1275
|
+
function PanelEdgeTab({ side, edge, center, controls, className: className2 }) {
|
|
992
1276
|
if (!edge && !center) return null;
|
|
993
1277
|
const isLeft = side === "left";
|
|
994
1278
|
const EdgeIcon = isLeft ? ChevronLeft : ChevronRight;
|
|
995
1279
|
const CenterIcon = isLeft ? ChevronRight : ChevronLeft;
|
|
996
|
-
const renderButton = (action, icon) => /* @__PURE__ */
|
|
1280
|
+
const renderButton = (action, icon) => /* @__PURE__ */ jsx15(
|
|
997
1281
|
"button",
|
|
998
1282
|
{
|
|
999
1283
|
type: "button",
|
|
@@ -1006,20 +1290,20 @@ function PanelEdgeTab({ side, edge, center, controls, className }) {
|
|
|
1006
1290
|
children: icon
|
|
1007
1291
|
}
|
|
1008
1292
|
);
|
|
1009
|
-
const edgeButton = edge && renderButton(edge, /* @__PURE__ */
|
|
1010
|
-
const centerButton = center && renderButton(center, /* @__PURE__ */
|
|
1293
|
+
const edgeButton = edge && renderButton(edge, /* @__PURE__ */ jsx15(EdgeIcon, { "aria-hidden": "true", className: "h-5 w-5" }));
|
|
1294
|
+
const centerButton = center && renderButton(center, /* @__PURE__ */ jsx15(CenterIcon, { "aria-hidden": "true", className: "h-5 w-5" }));
|
|
1011
1295
|
const buttons = isLeft ? [edgeButton, centerButton] : [centerButton, edgeButton];
|
|
1012
|
-
return /* @__PURE__ */
|
|
1296
|
+
return /* @__PURE__ */ jsxs11(
|
|
1013
1297
|
"div",
|
|
1014
1298
|
{
|
|
1015
1299
|
className: twMerge(
|
|
1016
1300
|
"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)]",
|
|
1017
1301
|
isLeft ? "rounded-r-lg" : "rounded-l-lg [clip-path:inset(-0.5rem_0_-0.5rem_-0.5rem)]",
|
|
1018
|
-
|
|
1302
|
+
className2
|
|
1019
1303
|
),
|
|
1020
1304
|
children: [
|
|
1021
1305
|
buttons[0],
|
|
1022
|
-
buttons[0] && buttons[1] && /* @__PURE__ */
|
|
1306
|
+
buttons[0] && buttons[1] && /* @__PURE__ */ jsx15("div", { role: "presentation", className: "h-px shrink-0 bg-[var(--color-border)]" }),
|
|
1023
1307
|
buttons[1]
|
|
1024
1308
|
]
|
|
1025
1309
|
}
|
|
@@ -1027,45 +1311,60 @@ function PanelEdgeTab({ side, edge, center, controls, className }) {
|
|
|
1027
1311
|
}
|
|
1028
1312
|
|
|
1029
1313
|
// src/genre-tree/ChangeParentPopup.tsx
|
|
1030
|
-
import { useEffect as
|
|
1314
|
+
import { useEffect as useEffect4, useMemo as useMemo6 } from "react";
|
|
1031
1315
|
import {
|
|
1032
1316
|
getReparentCandidates,
|
|
1033
1317
|
useUpdateGenre
|
|
1034
1318
|
} from "@behindthemusictree/app-kit/genre-tree";
|
|
1035
|
-
import { jsx as
|
|
1319
|
+
import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1036
1320
|
function ChangeParentPopup({
|
|
1037
1321
|
genrePlaylist,
|
|
1038
1322
|
genrePlaylists,
|
|
1039
1323
|
scope,
|
|
1040
1324
|
getBackendBaseUrl,
|
|
1041
|
-
onClose
|
|
1325
|
+
onClose,
|
|
1326
|
+
onParentChanged
|
|
1042
1327
|
}) {
|
|
1043
1328
|
const { mutate, isPending, formErrors } = useUpdateGenre(scope, getBackendBaseUrl);
|
|
1044
|
-
const candidates =
|
|
1329
|
+
const candidates = useMemo6(
|
|
1045
1330
|
() => getReparentCandidates(genrePlaylists, genrePlaylist.uuid),
|
|
1046
1331
|
[genrePlaylists, genrePlaylist.uuid]
|
|
1047
1332
|
);
|
|
1048
|
-
|
|
1333
|
+
const handleSelect = (newParent) => {
|
|
1334
|
+
const genre = genrePlaylist.criteria.uuid;
|
|
1335
|
+
const parentPlaylistUuid = genrePlaylist.parent?.uuid;
|
|
1336
|
+
const from = parentPlaylistUuid ? genrePlaylists.find(({ uuid }) => uuid === parentPlaylistUuid)?.criteria?.uuid ?? null : null;
|
|
1337
|
+
mutate(
|
|
1338
|
+
{ uuid: genre, data: { parent: newParent.criteria.uuid } },
|
|
1339
|
+
{
|
|
1340
|
+
onSuccess: () => {
|
|
1341
|
+
onClose();
|
|
1342
|
+
onParentChanged?.({ genre, from, to: newParent.criteria.uuid });
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
);
|
|
1346
|
+
};
|
|
1347
|
+
useEffect4(() => {
|
|
1049
1348
|
const handleKeyDown = (event) => {
|
|
1050
1349
|
if (event.key === "Escape") onClose();
|
|
1051
1350
|
};
|
|
1052
1351
|
window.addEventListener("keydown", handleKeyDown);
|
|
1053
1352
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
1054
1353
|
}, [onClose]);
|
|
1055
|
-
return /* @__PURE__ */
|
|
1056
|
-
/* @__PURE__ */
|
|
1354
|
+
return /* @__PURE__ */ jsxs12(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
|
|
1355
|
+
/* @__PURE__ */ jsxs12("p", { className: "mb-3 text-sm text-[var(--color-text-muted)]", children: [
|
|
1057
1356
|
"Current parent: ",
|
|
1058
1357
|
genrePlaylist.parent?.name ?? "none"
|
|
1059
1358
|
] }),
|
|
1060
|
-
/* @__PURE__ */
|
|
1359
|
+
/* @__PURE__ */ jsx16("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ jsx16(
|
|
1061
1360
|
GenreSearch,
|
|
1062
1361
|
{
|
|
1063
1362
|
genrePlaylists: candidates,
|
|
1064
|
-
onSelect:
|
|
1363
|
+
onSelect: handleSelect,
|
|
1065
1364
|
placeholder: "Search a new parent\u2026"
|
|
1066
1365
|
}
|
|
1067
1366
|
) }),
|
|
1068
|
-
formErrors.length > 0 && /* @__PURE__ */
|
|
1367
|
+
formErrors.length > 0 && /* @__PURE__ */ jsx16("p", { role: "alert", className: "mt-3 text-sm text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
|
|
1069
1368
|
] });
|
|
1070
1369
|
}
|
|
1071
1370
|
|
|
@@ -1077,29 +1376,29 @@ import { useArtist } from "@behindthemusictree/app-kit/genre-tree";
|
|
|
1077
1376
|
import { FieldList } from "@behindthemusictree/ui";
|
|
1078
1377
|
|
|
1079
1378
|
// src/genre-tree/artist/ArtistLinks.tsx
|
|
1080
|
-
import { Globe, Share2 } from "lucide-react";
|
|
1379
|
+
import { Globe, Share2 as Share23 } from "lucide-react";
|
|
1081
1380
|
import {
|
|
1082
1381
|
SiBandcamp,
|
|
1083
1382
|
SiDiscogs,
|
|
1084
1383
|
SiMusicbrainz,
|
|
1085
1384
|
SiSoundcloud,
|
|
1086
1385
|
SiSpotify,
|
|
1087
|
-
SiWikidata,
|
|
1088
|
-
SiWikipedia,
|
|
1386
|
+
SiWikidata as SiWikidata2,
|
|
1387
|
+
SiWikipedia as SiWikipedia2,
|
|
1089
1388
|
SiYoutube
|
|
1090
1389
|
} from "react-icons/si";
|
|
1091
1390
|
import { Chip } from "@behindthemusictree/ui";
|
|
1092
|
-
import { jsx as
|
|
1391
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
1093
1392
|
var LINK_KINDS = {
|
|
1094
1393
|
official: { label: "Official website", Icon: Globe },
|
|
1095
|
-
wikipedia: { label: "Wikipedia", Icon:
|
|
1096
|
-
wikidata: { label: "Wikidata", Icon:
|
|
1394
|
+
wikipedia: { label: "Wikipedia", Icon: SiWikipedia2 },
|
|
1395
|
+
wikidata: { label: "Wikidata", Icon: SiWikidata2 },
|
|
1097
1396
|
spotify: { label: "Spotify", Icon: SiSpotify },
|
|
1098
1397
|
youtube: { label: "YouTube", Icon: SiYoutube },
|
|
1099
1398
|
bandcamp: { label: "Bandcamp", Icon: SiBandcamp },
|
|
1100
1399
|
soundcloud: { label: "SoundCloud", Icon: SiSoundcloud },
|
|
1101
1400
|
discogs: { label: "Discogs", Icon: SiDiscogs },
|
|
1102
|
-
social: { label: "Social", Icon:
|
|
1401
|
+
social: { label: "Social", Icon: Share23 }
|
|
1103
1402
|
};
|
|
1104
1403
|
function socialLabel(url) {
|
|
1105
1404
|
try {
|
|
@@ -1126,7 +1425,7 @@ function artistLinks(artist) {
|
|
|
1126
1425
|
return links;
|
|
1127
1426
|
}
|
|
1128
1427
|
function ArtistLinks({ links }) {
|
|
1129
|
-
return /* @__PURE__ */
|
|
1428
|
+
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)) });
|
|
1130
1429
|
}
|
|
1131
1430
|
|
|
1132
1431
|
// src/genre-tree/artist/formatYears.ts
|
|
@@ -1137,7 +1436,7 @@ function formatYears(beginYear, endYear, ended) {
|
|
|
1137
1436
|
}
|
|
1138
1437
|
|
|
1139
1438
|
// src/genre-tree/artist/ArtistHeader.tsx
|
|
1140
|
-
import { jsx as
|
|
1439
|
+
import { jsx as jsx18 } from "react/jsx-runtime";
|
|
1141
1440
|
var countryNames = new Intl.DisplayNames(["en"], { type: "region" });
|
|
1142
1441
|
function ArtistHeader({ artist }) {
|
|
1143
1442
|
const fields = [];
|
|
@@ -1147,35 +1446,35 @@ function ArtistHeader({ artist }) {
|
|
|
1147
1446
|
const years = formatYears(artist.beginYear, artist.endYear, artist.ended);
|
|
1148
1447
|
if (years) fields.push({ label: "Active", value: years });
|
|
1149
1448
|
const links = artistLinks(artist);
|
|
1150
|
-
if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */
|
|
1449
|
+
if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ jsx18(ArtistLinks, { links }) });
|
|
1151
1450
|
if (fields.length === 0) return null;
|
|
1152
|
-
return /* @__PURE__ */
|
|
1451
|
+
return /* @__PURE__ */ jsx18(FieldList, { items: fields });
|
|
1153
1452
|
}
|
|
1154
1453
|
|
|
1155
1454
|
// src/genre-tree/artist/ArtistPhoto.tsx
|
|
1156
|
-
import { jsx as
|
|
1455
|
+
import { jsx as jsx19, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1157
1456
|
function ExternalLink({ href, children }) {
|
|
1158
|
-
return /* @__PURE__ */
|
|
1457
|
+
return /* @__PURE__ */ jsx19("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
|
|
1159
1458
|
}
|
|
1160
1459
|
function ArtistPhoto({ image, name }) {
|
|
1161
|
-
return /* @__PURE__ */
|
|
1162
|
-
/* @__PURE__ */
|
|
1163
|
-
/* @__PURE__ */
|
|
1460
|
+
return /* @__PURE__ */ jsxs13("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
|
|
1461
|
+
/* @__PURE__ */ jsx19("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
|
|
1462
|
+
/* @__PURE__ */ jsxs13("figcaption", { className: "text-xs text-[var(--color-text-muted)]", children: [
|
|
1164
1463
|
"Photo: ",
|
|
1165
|
-
/* @__PURE__ */
|
|
1464
|
+
/* @__PURE__ */ jsx19(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
|
|
1166
1465
|
",",
|
|
1167
1466
|
" ",
|
|
1168
|
-
image.licenseUrl ? /* @__PURE__ */
|
|
1467
|
+
image.licenseUrl ? /* @__PURE__ */ jsx19(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
|
|
1169
1468
|
] })
|
|
1170
1469
|
] });
|
|
1171
1470
|
}
|
|
1172
1471
|
|
|
1173
1472
|
// src/genre-tree/artist/ArtistMembers.tsx
|
|
1174
1473
|
import { PanelGroup, PanelSection } from "@behindthemusictree/ui";
|
|
1175
|
-
import { jsx as
|
|
1474
|
+
import { jsx as jsx20, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1176
1475
|
function MembershipList({ title, memberships, renderArtistLink }) {
|
|
1177
1476
|
if (memberships.length === 0) return null;
|
|
1178
|
-
return /* @__PURE__ */
|
|
1477
|
+
return /* @__PURE__ */ jsx20(PanelGroup, { title, children: /* @__PURE__ */ jsx20("ul", { children: memberships.map((membership, index) => {
|
|
1179
1478
|
const details = [
|
|
1180
1479
|
formatYears(membership.beginYear, membership.endYear, membership.ended),
|
|
1181
1480
|
...membership.roles
|
|
@@ -1183,9 +1482,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
|
|
|
1183
1482
|
const detailsText = details.filter(Boolean).join(" \xB7 ");
|
|
1184
1483
|
return (
|
|
1185
1484
|
// A member can have several periods in the same group.
|
|
1186
|
-
/* @__PURE__ */
|
|
1187
|
-
/* @__PURE__ */
|
|
1188
|
-
detailsText && /* @__PURE__ */
|
|
1485
|
+
/* @__PURE__ */ jsxs14("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
|
|
1486
|
+
/* @__PURE__ */ jsx20("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
|
|
1487
|
+
detailsText && /* @__PURE__ */ jsxs14("span", { className: "text-[var(--color-text-muted)]", children: [
|
|
1189
1488
|
" \u2014 ",
|
|
1190
1489
|
detailsText
|
|
1191
1490
|
] })
|
|
@@ -1196,9 +1495,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
|
|
|
1196
1495
|
function ArtistMembers({ title, memberships, renderArtistLink }) {
|
|
1197
1496
|
if (memberships.length === 0) return null;
|
|
1198
1497
|
const isCurrent = (membership) => !membership.ended && membership.endYear === null;
|
|
1199
|
-
return /* @__PURE__ */
|
|
1200
|
-
/* @__PURE__ */
|
|
1201
|
-
/* @__PURE__ */
|
|
1498
|
+
return /* @__PURE__ */ jsxs14(PanelSection, { title, children: [
|
|
1499
|
+
/* @__PURE__ */ jsx20(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
|
|
1500
|
+
/* @__PURE__ */ jsx20(
|
|
1202
1501
|
MembershipList,
|
|
1203
1502
|
{
|
|
1204
1503
|
title: "Past",
|
|
@@ -1211,7 +1510,7 @@ function ArtistMembers({ title, memberships, renderArtistLink }) {
|
|
|
1211
1510
|
|
|
1212
1511
|
// src/genre-tree/artist/ArtistDiscography.tsx
|
|
1213
1512
|
import { PanelGroup as PanelGroup2, PanelSection as PanelSection2 } from "@behindthemusictree/ui";
|
|
1214
|
-
import { jsx as
|
|
1513
|
+
import { jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1215
1514
|
var GROUPS = ["Albums", "EPs", "Singles", "Live", "Compilations", "Other"];
|
|
1216
1515
|
var PRIMARY_GROUPS = { album: "Albums", ep: "EPs", single: "Singles" };
|
|
1217
1516
|
function discographyGroup({ primaryType, secondaryTypes }) {
|
|
@@ -1231,18 +1530,18 @@ function byYear(a, b) {
|
|
|
1231
1530
|
function ArtistDiscography({ albums }) {
|
|
1232
1531
|
if (albums.length === 0) return null;
|
|
1233
1532
|
const sorted = [...albums].sort(byYear);
|
|
1234
|
-
return /* @__PURE__ */
|
|
1533
|
+
return /* @__PURE__ */ jsx21(PanelSection2, { title: "Discography", children: GROUPS.map((group) => {
|
|
1235
1534
|
const groupAlbums = sorted.filter((album) => discographyGroup(album) === group);
|
|
1236
1535
|
if (groupAlbums.length === 0) return null;
|
|
1237
|
-
return /* @__PURE__ */
|
|
1238
|
-
/* @__PURE__ */
|
|
1239
|
-
/* @__PURE__ */
|
|
1536
|
+
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: [
|
|
1537
|
+
/* @__PURE__ */ jsx21("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
|
|
1538
|
+
/* @__PURE__ */ jsx21("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
|
|
1240
1539
|
] }, album.uuid)) }) }, group);
|
|
1241
1540
|
}) });
|
|
1242
1541
|
}
|
|
1243
1542
|
|
|
1244
1543
|
// src/genre-tree/artist/ArtistSongs.tsx
|
|
1245
|
-
import { useState as
|
|
1544
|
+
import { useState as useState4 } from "react";
|
|
1246
1545
|
import { keepPreviousData } from "@tanstack/react-query";
|
|
1247
1546
|
import { Play as Play2 } from "lucide-react";
|
|
1248
1547
|
import { Pagination, PanelSection as PanelSection3, Skeleton as Skeleton3 } from "@behindthemusictree/ui";
|
|
@@ -1263,41 +1562,49 @@ import { useTrackList as useTrackList3 } from "@behindthemusictree/app-kit/genre
|
|
|
1263
1562
|
import { FaPlay as FaPlay2, FaPause as FaPause2 } from "react-icons/fa";
|
|
1264
1563
|
import { usePlayer as usePlayer5 } from "@behindthemusictree/app-kit/player";
|
|
1265
1564
|
import { PlayStates as PlayStates2 } from "@behindthemusictree/app-kit/player";
|
|
1266
|
-
import {
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1565
|
+
import { jsx as jsx22, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1566
|
+
function TrackPositionPlayPause({
|
|
1567
|
+
position,
|
|
1568
|
+
uuid,
|
|
1569
|
+
handlePlayPauseClick,
|
|
1570
|
+
isSelected = false
|
|
1571
|
+
}) {
|
|
1572
|
+
const { playerTrackObject, playbackConfirmedTrackId, playState, isLoading: isPlayerLoading } = usePlayer5();
|
|
1270
1573
|
const isThisTrack = playerTrackObject?.track.id === uuid;
|
|
1271
|
-
|
|
1574
|
+
const isConfirmedPlayable = playbackConfirmedTrackId === uuid;
|
|
1575
|
+
const isAwaitingPlayback = (isSelected || isThisTrack) && !isConfirmedPlayable && !(isThisTrack && playerTrackObject?.loadError) && (!isThisTrack || isPlayerLoading || playState === PlayStates2.LOADING || playState === PlayStates2.PLAYING);
|
|
1576
|
+
const isPlaying = isThisTrack && isConfirmedPlayable && playState === PlayStates2.PLAYING;
|
|
1577
|
+
const isPaused = isThisTrack && isConfirmedPlayable && playState === PlayStates2.PAUSED;
|
|
1578
|
+
const isCurrentTrackActive = isPlaying || isPaused;
|
|
1579
|
+
return /* @__PURE__ */ jsxs16(
|
|
1272
1580
|
"div",
|
|
1273
1581
|
{
|
|
1274
|
-
className: "track-position-play-pause flex items-center justify-center text-lg w-16 cursor-pointer",
|
|
1582
|
+
className: "track-position-play-pause relative flex items-center justify-center gap-2 text-lg w-16 cursor-pointer",
|
|
1275
1583
|
style: { minWidth: "4rem", maxWidth: "4rem" },
|
|
1276
1584
|
onClick: handlePlayPauseClick,
|
|
1277
1585
|
children: [
|
|
1278
|
-
/* @__PURE__ */
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
/* @__PURE__ */ jsx19("div", { className: "hidden group-hover:flex items-center justify-center", children: isThisTrack && playState === PlayStates2.LOADING ? /* @__PURE__ */ jsx19(RingLoader2, { size: 16 }) : isThisTrack && playState === PlayStates2.PLAYING ? /* @__PURE__ */ jsx19(FaPause2, {}) : /* @__PURE__ */ jsx19(FaPlay2, {}) })
|
|
1586
|
+
/* @__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(
|
|
1587
|
+
"span",
|
|
1588
|
+
{
|
|
1589
|
+
className: "track-position-play-pause__bars",
|
|
1590
|
+
"data-playing": isPlaying,
|
|
1591
|
+
role: "img",
|
|
1592
|
+
"aria-label": isPlaying ? "Playing" : "Paused",
|
|
1593
|
+
children: [
|
|
1594
|
+
/* @__PURE__ */ jsx22("span", { className: "track-position-play-pause__bar" }),
|
|
1595
|
+
/* @__PURE__ */ jsx22("span", { className: "track-position-play-pause__bar" }),
|
|
1596
|
+
/* @__PURE__ */ jsx22("span", { className: "track-position-play-pause__bar" })
|
|
1597
|
+
]
|
|
1598
|
+
}
|
|
1599
|
+
) : position }),
|
|
1600
|
+
/* @__PURE__ */ jsx22(
|
|
1601
|
+
"div",
|
|
1602
|
+
{
|
|
1603
|
+
className: `hidden group-hover:flex items-center justify-center ${isCurrentTrackActive ? "" : "absolute inset-0"}`,
|
|
1604
|
+
"aria-hidden": "true",
|
|
1605
|
+
children: isAwaitingPlayback ? /* @__PURE__ */ jsx22("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ jsx22(FaPause2, {}) : /* @__PURE__ */ jsx22(FaPlay2, {})
|
|
1606
|
+
}
|
|
1607
|
+
)
|
|
1301
1608
|
]
|
|
1302
1609
|
}
|
|
1303
1610
|
);
|
|
@@ -1306,7 +1613,7 @@ function TrackPositionPlayPause({ position, uuid, handlePlayPauseClick }) {
|
|
|
1306
1613
|
// src/genre-tree/track-list-sidebar/TrackItem.tsx
|
|
1307
1614
|
import { isSong as isSong2, songUnplayableReason as songUnplayableReason2 } from "@behindthemusictree/app-kit/genre-tree";
|
|
1308
1615
|
import { ArtistNames as ArtistNames2 } from "@behindthemusictree/app-kit/genre-tree";
|
|
1309
|
-
import { jsx as
|
|
1616
|
+
import { jsx as jsx23, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
1310
1617
|
function TrackItem({
|
|
1311
1618
|
track,
|
|
1312
1619
|
position,
|
|
@@ -1315,25 +1622,26 @@ function TrackItem({
|
|
|
1315
1622
|
renderArtistLink
|
|
1316
1623
|
}) {
|
|
1317
1624
|
const { handlePlayPauseAction, playerTrackObject, loadTrackForPlayer } = usePlayer6();
|
|
1318
|
-
const { trackList, toTrackAtPosition } = useTrackList3();
|
|
1625
|
+
const { trackList, selectedTrack, toTrackAtPosition, runtimeUnplayableTrackIds } = useTrackList3();
|
|
1319
1626
|
const scope = trackList?.origin?.scope ?? null;
|
|
1320
|
-
const
|
|
1627
|
+
const isCurrentTrack = playerTrackObject?.track.id === track.uuid;
|
|
1628
|
+
const hasPlaybackError = isCurrentTrack && !!playerTrackObject?.loadError;
|
|
1629
|
+
const isUnplayable = hasPlaybackError || runtimeUnplayableTrackIds?.has(track.uuid) === true || isSong2(track) && songUnplayableReason2(track) !== null;
|
|
1321
1630
|
const handlePlayPauseClick = (event) => {
|
|
1322
1631
|
event.stopPropagation();
|
|
1323
|
-
if (
|
|
1632
|
+
if (isCurrentTrack) {
|
|
1324
1633
|
handlePlayPauseAction();
|
|
1325
1634
|
} else if (scope != null) {
|
|
1326
1635
|
toTrackAtPosition(position);
|
|
1327
1636
|
loadTrackForPlayer(track.uuid);
|
|
1328
1637
|
}
|
|
1329
1638
|
};
|
|
1330
|
-
return /* @__PURE__ */
|
|
1639
|
+
return /* @__PURE__ */ jsxs17(
|
|
1331
1640
|
"div",
|
|
1332
1641
|
{
|
|
1333
|
-
className: `track-item flex h-14 text-[var(--color-text)] ${
|
|
1334
|
-
title: unplayableReason ?? void 0,
|
|
1642
|
+
className: `track-item flex h-14 text-[var(--color-text)] ${isUnplayable ? "opacity-50" : "hover:bg-[var(--color-surface-muted)] group"}`,
|
|
1335
1643
|
children: [
|
|
1336
|
-
|
|
1644
|
+
isUnplayable ? /* @__PURE__ */ jsx23(
|
|
1337
1645
|
"div",
|
|
1338
1646
|
{
|
|
1339
1647
|
className: "flex items-center justify-center text-lg w-16",
|
|
@@ -1341,18 +1649,21 @@ function TrackItem({
|
|
|
1341
1649
|
"aria-disabled": "true",
|
|
1342
1650
|
children: position
|
|
1343
1651
|
}
|
|
1344
|
-
) : /* @__PURE__ */
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1652
|
+
) : /* @__PURE__ */ jsx23(
|
|
1653
|
+
TrackPositionPlayPause,
|
|
1654
|
+
{
|
|
1655
|
+
position,
|
|
1656
|
+
uuid: track.uuid,
|
|
1657
|
+
handlePlayPauseClick,
|
|
1658
|
+
isSelected: selectedTrack?.uuid === track.uuid
|
|
1659
|
+
}
|
|
1660
|
+
),
|
|
1661
|
+
/* @__PURE__ */ jsxs17("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
|
|
1662
|
+
/* @__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: isSong2(track) ? /* @__PURE__ */ jsx23(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
|
|
1663
|
+
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 }) }) : ""
|
|
1352
1664
|
] }),
|
|
1353
|
-
/* @__PURE__ */
|
|
1354
|
-
|
|
1355
|
-
renderDuration && /* @__PURE__ */ jsx20(
|
|
1665
|
+
/* @__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 }) : "" }),
|
|
1666
|
+
renderDuration && /* @__PURE__ */ jsx23(
|
|
1356
1667
|
"div",
|
|
1357
1668
|
{
|
|
1358
1669
|
className: "duration flex text-base w-16 items-center justify-center",
|
|
@@ -1360,22 +1671,22 @@ function TrackItem({
|
|
|
1360
1671
|
children: renderDuration(track)
|
|
1361
1672
|
}
|
|
1362
1673
|
),
|
|
1363
|
-
renderActions && /* @__PURE__ */
|
|
1674
|
+
renderActions && /* @__PURE__ */ jsx23("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
|
|
1364
1675
|
]
|
|
1365
1676
|
}
|
|
1366
1677
|
);
|
|
1367
1678
|
}
|
|
1368
1679
|
|
|
1369
1680
|
// src/genre-tree/artist/ArtistSongs.tsx
|
|
1370
|
-
import { Fragment as
|
|
1681
|
+
import { Fragment as Fragment6, jsx as jsx24, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
1371
1682
|
var SongPageSchema2 = PaginatedResponseSchema2(SongDetailedSchema2);
|
|
1372
1683
|
var SORTS = [
|
|
1373
1684
|
{ ordering: "title", label: "Title" },
|
|
1374
1685
|
{ ordering: "-albumYear", label: "Year" }
|
|
1375
1686
|
];
|
|
1376
1687
|
function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
1377
|
-
const [page, setPage] =
|
|
1378
|
-
const [ordering, setOrdering] =
|
|
1688
|
+
const [page, setPage] = useState4(1);
|
|
1689
|
+
const [ordering, setOrdering] = useState4("title");
|
|
1379
1690
|
const { fetch } = useFetchWrapper2(getBackendBaseUrl);
|
|
1380
1691
|
const { playNewTrackListFromArtist } = useTrackList4();
|
|
1381
1692
|
const { playerTrackObject } = usePlayer7();
|
|
@@ -1400,21 +1711,21 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
|
1400
1711
|
event.stopPropagation();
|
|
1401
1712
|
void play(song.uuid);
|
|
1402
1713
|
};
|
|
1403
|
-
return /* @__PURE__ */
|
|
1404
|
-
/* @__PURE__ */
|
|
1405
|
-
/* @__PURE__ */
|
|
1714
|
+
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(Fragment6, { children: [
|
|
1715
|
+
/* @__PURE__ */ jsxs18("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
|
|
1716
|
+
/* @__PURE__ */ jsxs18(
|
|
1406
1717
|
"button",
|
|
1407
1718
|
{
|
|
1408
1719
|
type: "button",
|
|
1409
1720
|
onClick: () => void play(),
|
|
1410
1721
|
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",
|
|
1411
1722
|
children: [
|
|
1412
|
-
/* @__PURE__ */
|
|
1723
|
+
/* @__PURE__ */ jsx24(Play2, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
|
|
1413
1724
|
"Play all"
|
|
1414
1725
|
]
|
|
1415
1726
|
}
|
|
1416
1727
|
),
|
|
1417
|
-
/* @__PURE__ */
|
|
1728
|
+
/* @__PURE__ */ jsx24("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ jsx24(
|
|
1418
1729
|
"button",
|
|
1419
1730
|
{
|
|
1420
1731
|
type: "button",
|
|
@@ -1429,7 +1740,7 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
|
1429
1740
|
sort.ordering
|
|
1430
1741
|
)) })
|
|
1431
1742
|
] }),
|
|
1432
|
-
/* @__PURE__ */
|
|
1743
|
+
/* @__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(
|
|
1433
1744
|
TrackItem,
|
|
1434
1745
|
{
|
|
1435
1746
|
track: song,
|
|
@@ -1437,15 +1748,17 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
|
1437
1748
|
renderArtistLink
|
|
1438
1749
|
}
|
|
1439
1750
|
) }, song.uuid)) }),
|
|
1440
|
-
/* @__PURE__ */
|
|
1751
|
+
/* @__PURE__ */ jsx24(Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
|
|
1441
1752
|
] }) });
|
|
1442
1753
|
}
|
|
1443
1754
|
|
|
1444
1755
|
// src/genre-tree/artist/useArtistPanel.tsx
|
|
1445
|
-
import { Fragment as
|
|
1756
|
+
import { Fragment as Fragment7, jsx as jsx25, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
1446
1757
|
function useArtistPanel({
|
|
1447
1758
|
uuid,
|
|
1759
|
+
scope,
|
|
1448
1760
|
getBackendBaseUrl,
|
|
1761
|
+
getShareUrl,
|
|
1449
1762
|
renderArtistLink,
|
|
1450
1763
|
expanded,
|
|
1451
1764
|
onExpandedChange,
|
|
@@ -1460,38 +1773,41 @@ function useArtistPanel({
|
|
|
1460
1773
|
return {
|
|
1461
1774
|
...panel,
|
|
1462
1775
|
title: "Artist",
|
|
1463
|
-
content: /* @__PURE__ */
|
|
1464
|
-
/* @__PURE__ */
|
|
1465
|
-
/* @__PURE__ */
|
|
1466
|
-
/* @__PURE__ */
|
|
1467
|
-
/* @__PURE__ */
|
|
1468
|
-
/* @__PURE__ */
|
|
1469
|
-
/* @__PURE__ */
|
|
1776
|
+
content: /* @__PURE__ */ jsxs19("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
|
|
1777
|
+
/* @__PURE__ */ jsx25(Skeleton4, { className: "h-3 w-16" }),
|
|
1778
|
+
/* @__PURE__ */ jsx25(Skeleton4, { className: "h-4 w-32" }),
|
|
1779
|
+
/* @__PURE__ */ jsx25(Skeleton4, { className: "h-3 w-16" }),
|
|
1780
|
+
/* @__PURE__ */ jsx25(Skeleton4, { className: "h-4 w-24" }),
|
|
1781
|
+
/* @__PURE__ */ jsx25(Skeleton4, { className: "h-16 mt-2.5" }),
|
|
1782
|
+
/* @__PURE__ */ jsx25(Skeleton4, { className: "h-16" })
|
|
1470
1783
|
] })
|
|
1471
1784
|
};
|
|
1472
1785
|
case "not-found":
|
|
1473
1786
|
return {
|
|
1474
1787
|
...panel,
|
|
1475
1788
|
title: "Artist not found",
|
|
1476
|
-
content: /* @__PURE__ */
|
|
1789
|
+
content: /* @__PURE__ */ jsx25("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This artist does not exist." })
|
|
1477
1790
|
};
|
|
1478
1791
|
case "error":
|
|
1479
1792
|
return {
|
|
1480
1793
|
...panel,
|
|
1481
1794
|
title: "Artist",
|
|
1482
|
-
content: /* @__PURE__ */
|
|
1795
|
+
content: /* @__PURE__ */ jsx25("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
|
|
1483
1796
|
};
|
|
1484
1797
|
case "success": {
|
|
1485
1798
|
const { artist } = result;
|
|
1799
|
+
const shareTarget = scope ? { kind: "artist", uuid: artist.uuid, name: artist.name, scope } : void 0;
|
|
1800
|
+
const shareUrl = shareTarget ? getShareUrl?.(shareTarget) : void 0;
|
|
1486
1801
|
return {
|
|
1487
1802
|
...panel,
|
|
1488
1803
|
title: artist.name,
|
|
1489
|
-
content: /* @__PURE__ */
|
|
1490
|
-
|
|
1491
|
-
/* @__PURE__ */
|
|
1492
|
-
/* @__PURE__ */
|
|
1493
|
-
/* @__PURE__ */
|
|
1494
|
-
|
|
1804
|
+
content: /* @__PURE__ */ jsxs19(Fragment7, { children: [
|
|
1805
|
+
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 }) }) }),
|
|
1806
|
+
artist.image && /* @__PURE__ */ jsx25(ArtistPhoto, { image: artist.image, name: artist.name }),
|
|
1807
|
+
/* @__PURE__ */ jsx25(ArtistHeader, { artist }),
|
|
1808
|
+
/* @__PURE__ */ jsx25(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
|
|
1809
|
+
/* @__PURE__ */ jsx25(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
|
|
1810
|
+
artist.tracksCount > 0 && /* @__PURE__ */ jsx25(
|
|
1495
1811
|
ArtistSongs,
|
|
1496
1812
|
{
|
|
1497
1813
|
artist: { uuid: artist.uuid, name: artist.name },
|
|
@@ -1500,14 +1816,36 @@ function useArtistPanel({
|
|
|
1500
1816
|
}
|
|
1501
1817
|
)
|
|
1502
1818
|
] }),
|
|
1503
|
-
extraContent: artist.albums.length > 0 ? /* @__PURE__ */
|
|
1819
|
+
extraContent: artist.albums.length > 0 ? /* @__PURE__ */ jsx25(ArtistDiscography, { albums: artist.albums }) : void 0
|
|
1504
1820
|
};
|
|
1505
1821
|
}
|
|
1506
1822
|
}
|
|
1507
1823
|
}
|
|
1508
1824
|
|
|
1825
|
+
// src/genre-tree/SizeMetricToggle.tsx
|
|
1826
|
+
import { MdMusicNote, MdVisibility } from "react-icons/md";
|
|
1827
|
+
import { SegmentedControl } from "@behindthemusictree/ui";
|
|
1828
|
+
import { jsx as jsx26 } from "react/jsx-runtime";
|
|
1829
|
+
function SizeMetricToggle({
|
|
1830
|
+
value,
|
|
1831
|
+
onChange
|
|
1832
|
+
}) {
|
|
1833
|
+
return /* @__PURE__ */ jsx26(
|
|
1834
|
+
SegmentedControl,
|
|
1835
|
+
{
|
|
1836
|
+
"aria-label": "Size nodes by",
|
|
1837
|
+
value,
|
|
1838
|
+
onChange,
|
|
1839
|
+
options: [
|
|
1840
|
+
{ value: "songs", label: /* @__PURE__ */ jsx26(MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
|
|
1841
|
+
{ value: "views", label: /* @__PURE__ */ jsx26(MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
|
|
1842
|
+
]
|
|
1843
|
+
}
|
|
1844
|
+
);
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1509
1847
|
// src/genre-tree/GenreTreeView.tsx
|
|
1510
|
-
import { Fragment as
|
|
1848
|
+
import { Fragment as Fragment8, jsx as jsx27, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
1511
1849
|
var PREVIOUS_BUTTON_CSS = `
|
|
1512
1850
|
.genre-tree-view .search-overlay .gtv-info-panel-previous { display: none; }
|
|
1513
1851
|
.genre-tree-view:has(.search-overlay .gtv-info-panel-previous) .genre-search input { padding-left: 2.75rem; }
|
|
@@ -1529,7 +1867,7 @@ function GenreNodeActionBand({ genrePlaylist, scope, playback, ...bandProps }) {
|
|
|
1529
1867
|
console.error("Failed to load genre playlist tracks:", error);
|
|
1530
1868
|
});
|
|
1531
1869
|
};
|
|
1532
|
-
return /* @__PURE__ */
|
|
1870
|
+
return /* @__PURE__ */ jsx27(
|
|
1533
1871
|
GenreActionBand,
|
|
1534
1872
|
{
|
|
1535
1873
|
...bandProps,
|
|
@@ -1544,6 +1882,7 @@ function GenreTreeView({
|
|
|
1544
1882
|
scope,
|
|
1545
1883
|
handleGenreCreationAction,
|
|
1546
1884
|
handleGenreRenameAction,
|
|
1885
|
+
onParentChanged,
|
|
1547
1886
|
getBackendBaseUrl,
|
|
1548
1887
|
additionalActions,
|
|
1549
1888
|
viewMode: controlledViewMode,
|
|
@@ -1552,18 +1891,22 @@ function GenreTreeView({
|
|
|
1552
1891
|
defaultViewport,
|
|
1553
1892
|
onViewportChange,
|
|
1554
1893
|
readOnly = false,
|
|
1555
|
-
className,
|
|
1894
|
+
className: className2,
|
|
1556
1895
|
showDisplayOptions = true,
|
|
1557
1896
|
showAddRoot = true,
|
|
1558
1897
|
allowWheelRotation: controlledAllowWheelRotation,
|
|
1559
1898
|
criteriaOverviewSchema,
|
|
1560
1899
|
renderGenreDetailExtras,
|
|
1561
1900
|
controlsOrientation = "horizontal",
|
|
1901
|
+
floatingControlsLeading,
|
|
1902
|
+
sizeMetric: controlledSizeMetric,
|
|
1903
|
+
onSizeMetricChange,
|
|
1562
1904
|
renderArtistLink,
|
|
1563
1905
|
artistUuid: controlledArtistUuid,
|
|
1564
1906
|
onArtistUuidChange,
|
|
1565
1907
|
artistExpanded: controlledArtistExpanded,
|
|
1566
1908
|
onArtistExpandedChange,
|
|
1909
|
+
getShareUrl,
|
|
1567
1910
|
artistSearch = true,
|
|
1568
1911
|
onArtistSelect,
|
|
1569
1912
|
artistSearchBaseUrl,
|
|
@@ -1572,10 +1915,6 @@ function GenreTreeView({
|
|
|
1572
1915
|
renderSongLeading,
|
|
1573
1916
|
renderSongActions,
|
|
1574
1917
|
renderTracksToolbar,
|
|
1575
|
-
editMode,
|
|
1576
|
-
onEditModeChange,
|
|
1577
|
-
renderTitleEditor,
|
|
1578
|
-
renderParentsEditor,
|
|
1579
1918
|
renderDangerZone,
|
|
1580
1919
|
expanded,
|
|
1581
1920
|
onExpandedChange,
|
|
@@ -1585,15 +1924,25 @@ function GenreTreeView({
|
|
|
1585
1924
|
placement = "default",
|
|
1586
1925
|
...focus
|
|
1587
1926
|
}) {
|
|
1588
|
-
const
|
|
1927
|
+
const getShareUrlRef = useRef4(getShareUrl);
|
|
1928
|
+
getShareUrlRef.current = getShareUrl;
|
|
1929
|
+
const [internalArtistUuid, setInternalArtistUuid] = useState5(null);
|
|
1589
1930
|
const isArtistControlled = controlledArtistUuid !== void 0;
|
|
1590
1931
|
const artistUuid = isArtistControlled ? controlledArtistUuid : internalArtistUuid;
|
|
1932
|
+
const [internalSizeMetric, setInternalSizeMetric] = useState5("songs");
|
|
1933
|
+
const sizeMetric = controlledSizeMetric ?? internalSizeMetric;
|
|
1934
|
+
const deferredSizeMetric = useDeferredValue(sizeMetric);
|
|
1935
|
+
const changeSizeMetric = (metric) => {
|
|
1936
|
+
if (controlledSizeMetric === void 0) setInternalSizeMetric(metric);
|
|
1937
|
+
onSizeMetricChange?.(metric);
|
|
1938
|
+
};
|
|
1591
1939
|
const changeArtist = (uuid) => {
|
|
1592
1940
|
if (!isArtistControlled) setInternalArtistUuid(uuid);
|
|
1593
1941
|
onArtistUuidChange?.(uuid);
|
|
1594
1942
|
};
|
|
1595
1943
|
const {
|
|
1596
1944
|
isLoading,
|
|
1945
|
+
isFetching: isTreeFetching,
|
|
1597
1946
|
genrePlaylistByNodeId,
|
|
1598
1947
|
visibleGenrePlaylists,
|
|
1599
1948
|
groupedGenrePlaylistsByRoot,
|
|
@@ -1618,23 +1967,31 @@ function GenreTreeView({
|
|
|
1618
1967
|
criteriaOverviewSchema,
|
|
1619
1968
|
artistUuid
|
|
1620
1969
|
});
|
|
1970
|
+
const isGenreUpdating = useIsMutating({ mutationKey: genreUpdateMutationKey }) > 0;
|
|
1971
|
+
const { mutate: updateGenre } = useUpdateGenre2(scope, getBackendBaseUrl);
|
|
1972
|
+
const busy = deferredSizeMetric !== sizeMetric || isTreeFetching || isGenreUpdating;
|
|
1973
|
+
const isArtistFiltered = artistUuid !== null;
|
|
1974
|
+
const canSizeByViews = !isArtistFiltered && hasViewsCount(visibleGenrePlaylists);
|
|
1621
1975
|
const artistResult = useArtist2(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
|
|
1622
1976
|
const filterArtistName = artistResult.status === "success" ? artistResult.artist.name : null;
|
|
1623
|
-
const filterArtist =
|
|
1977
|
+
const filterArtist = useMemo7(
|
|
1624
1978
|
() => artistUuid !== null && filterArtistName !== null ? { uuid: artistUuid, name: filterArtistName } : null,
|
|
1625
1979
|
[artistUuid, filterArtistName]
|
|
1626
1980
|
);
|
|
1981
|
+
const hasSelectedGenreNode = selectedGenrePlaylist !== void 0 && visibleGenrePlaylists.some(({ uuid }) => uuid === selectedGenrePlaylist.uuid);
|
|
1627
1982
|
const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: visibleGenrePlaylists, artist: filterArtist });
|
|
1628
1983
|
const openArtist = (uuid) => {
|
|
1629
1984
|
if (selectedNodeId !== null) clearSelection();
|
|
1630
1985
|
changeArtist(uuid);
|
|
1631
1986
|
};
|
|
1632
|
-
const [expandedArtistUuid, setExpandedArtistUuid] =
|
|
1987
|
+
const [expandedArtistUuid, setExpandedArtistUuid] = useState5(null);
|
|
1633
1988
|
const artistBaseUrl = artistSearchBaseUrl ?? getBackendBaseUrl;
|
|
1634
1989
|
const artistPanel = useArtistPanel({
|
|
1635
1990
|
uuid: artistUuid,
|
|
1991
|
+
scope,
|
|
1636
1992
|
getBackendBaseUrl: artistBaseUrl,
|
|
1637
|
-
|
|
1993
|
+
getShareUrl: getShareUrlRef.current,
|
|
1994
|
+
renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ jsx27("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
|
|
1638
1995
|
expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
|
|
1639
1996
|
onExpandedChange: (on) => {
|
|
1640
1997
|
if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
|
|
@@ -1642,10 +1999,10 @@ function GenreTreeView({
|
|
|
1642
1999
|
},
|
|
1643
2000
|
onClose: () => changeArtist(null)
|
|
1644
2001
|
});
|
|
1645
|
-
const builtInArtistPanel = overlayPanel ||
|
|
2002
|
+
const builtInArtistPanel = overlayPanel || hasSelectedGenreNode ? void 0 : artistPanel;
|
|
1646
2003
|
const activeOverlayPanel = overlayPanel ?? (builtInArtistPanel && { ...builtInArtistPanel, hideTitle: true, hideClose: true });
|
|
1647
|
-
const [uncontrolledCollapsed, setUncontrolledCollapsed] =
|
|
1648
|
-
const [uncontrolledExpanded, setUncontrolledExpanded] =
|
|
2004
|
+
const [uncontrolledCollapsed, setUncontrolledCollapsed] = useState5(false);
|
|
2005
|
+
const [uncontrolledExpanded, setUncontrolledExpanded] = useState5(false);
|
|
1649
2006
|
const isCollapsed = collapsed ?? uncontrolledCollapsed;
|
|
1650
2007
|
const isExpanded = expanded ?? uncontrolledExpanded;
|
|
1651
2008
|
const changeCollapsed = (next) => {
|
|
@@ -1656,11 +2013,11 @@ function GenreTreeView({
|
|
|
1656
2013
|
setUncontrolledExpanded(next);
|
|
1657
2014
|
onExpandedChange?.(next);
|
|
1658
2015
|
};
|
|
1659
|
-
|
|
2016
|
+
useEffect5(() => {
|
|
1660
2017
|
setUncontrolledCollapsed(false);
|
|
1661
2018
|
}, [selectedNodeId, artistUuid]);
|
|
1662
|
-
const isDetailHidden = isCollapsed || !
|
|
1663
|
-
|
|
2019
|
+
const isDetailHidden = isCollapsed || !hasSelectedGenreNode && !activeOverlayPanel;
|
|
2020
|
+
useEffect5(() => {
|
|
1664
2021
|
if (placement !== "regions" || !isDetailHidden) return void 0;
|
|
1665
2022
|
const root = document.documentElement;
|
|
1666
2023
|
root.style.setProperty("--layout-left-w", "0px");
|
|
@@ -1672,10 +2029,6 @@ function GenreTreeView({
|
|
|
1672
2029
|
const detailExpanded = detailOverlay ? !!detailOverlay.expanded : isExpanded;
|
|
1673
2030
|
const onDetailExpandedChange = detailOverlay ? detailOverlay.onExpandedChange : changeExpanded;
|
|
1674
2031
|
const infoPanelCuration = {
|
|
1675
|
-
editMode,
|
|
1676
|
-
onEditModeChange,
|
|
1677
|
-
renderTitleEditor,
|
|
1678
|
-
renderParentsEditor,
|
|
1679
2032
|
renderDangerZone,
|
|
1680
2033
|
expanded: placement === "regions" ? isExpanded : expanded,
|
|
1681
2034
|
onExpandedChange: placement === "regions" ? changeExpanded : onExpandedChange,
|
|
@@ -1683,29 +2036,38 @@ function GenreTreeView({
|
|
|
1683
2036
|
hideExpandButton: placement === "regions" ? true : void 0,
|
|
1684
2037
|
overlayPanel: detailOverlay ? { ...detailOverlay, collapsed: isCollapsed, hideExpandButton: true } : activeOverlayPanel
|
|
1685
2038
|
};
|
|
1686
|
-
const [isWheelRevealed, setIsWheelRevealed] =
|
|
2039
|
+
const [isWheelRevealed, setIsWheelRevealed] = useState5(false);
|
|
1687
2040
|
const usesHandoff = viewMode === "wheel" || viewMode === "pop-core";
|
|
1688
2041
|
const isTreeReady = !isLoading && (!usesHandoff || isWheelRevealed);
|
|
1689
2042
|
const rootRef = useRef4(null);
|
|
1690
2043
|
const handlePrevious = () => {
|
|
1691
2044
|
rootRef.current?.querySelector(".search-overlay .gtv-info-panel-previous")?.click();
|
|
1692
2045
|
};
|
|
1693
|
-
|
|
1694
|
-
|
|
2046
|
+
useEffect5(() => {
|
|
2047
|
+
if (!isTreeReady || viewMode !== "pop-core" || !hasSelectedGenreNode || !selectedGenrePlaylist) return;
|
|
2048
|
+
const root = rootRef.current;
|
|
2049
|
+
const centerChip = root?.querySelector(".gtv-wheel-center-node .gtv-wheel-chip");
|
|
2050
|
+
if (!centerChip || centerChip.getAttribute("aria-label") !== selectedGenrePlaylist.name) return;
|
|
2051
|
+
const panelTitle = root?.querySelector(".gtv-info-panel-title");
|
|
2052
|
+
if (panelTitle?.textContent?.trim().startsWith(selectedGenrePlaylist.name)) return;
|
|
2053
|
+
centerChip.click();
|
|
2054
|
+
}, [hasSelectedGenreNode, isTreeReady, selectedGenrePlaylist, viewMode]);
|
|
2055
|
+
const { showPopup, hidePopup } = usePopup5();
|
|
2056
|
+
const [searchText, setSearchText] = useState5("");
|
|
1695
2057
|
const artistSearchResult = useArtistSearch(artistBaseUrl, artistSearch ? searchText : "");
|
|
1696
2058
|
const artistExtraResults = artistSearch ? {
|
|
1697
2059
|
label: "Artists",
|
|
1698
2060
|
items: artistSearchResult.artists.map((artist) => ({
|
|
1699
2061
|
key: artist.uuid,
|
|
1700
|
-
content: /* @__PURE__ */
|
|
1701
|
-
/* @__PURE__ */
|
|
2062
|
+
content: /* @__PURE__ */ jsxs20(Fragment8, { children: [
|
|
2063
|
+
/* @__PURE__ */ jsxs20("span", { className: "block truncate font-medium", children: [
|
|
1702
2064
|
artist.name,
|
|
1703
|
-
artist.disambiguation && /* @__PURE__ */
|
|
2065
|
+
artist.disambiguation && /* @__PURE__ */ jsxs20("span", { className: "font-normal text-[var(--color-neutral-500)]", children: [
|
|
1704
2066
|
" \xB7 ",
|
|
1705
2067
|
artist.disambiguation
|
|
1706
2068
|
] })
|
|
1707
2069
|
] }),
|
|
1708
|
-
artist.matchedAlias && /* @__PURE__ */
|
|
2070
|
+
artist.matchedAlias && /* @__PURE__ */ jsxs20("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [
|
|
1709
2071
|
"also known as ",
|
|
1710
2072
|
artist.matchedAlias
|
|
1711
2073
|
] })
|
|
@@ -1715,7 +2077,8 @@ function GenreTreeView({
|
|
|
1715
2077
|
onArtistSelect?.(artist);
|
|
1716
2078
|
}
|
|
1717
2079
|
})),
|
|
1718
|
-
status: artistSearchResult.isError ? "Search failed" : artistSearchResult.settled && artistSearchResult.artists.length === 0 ? "No artists found" : ""
|
|
2080
|
+
status: artistSearchResult.isError ? "Search failed" : artistSearchResult.settled && artistSearchResult.artists.length === 0 ? "No artists found" : "",
|
|
2081
|
+
loading: artistSearchResult.pending
|
|
1719
2082
|
} : void 0;
|
|
1720
2083
|
const handleSelectedNodeChange = (node) => {
|
|
1721
2084
|
selectNode(node);
|
|
@@ -1732,49 +2095,70 @@ function GenreTreeView({
|
|
|
1732
2095
|
};
|
|
1733
2096
|
const zoomInRef = useRef4(void 0);
|
|
1734
2097
|
zoomInRef.current = canZoomIn ? handleGenreSearchZoomIn : void 0;
|
|
1735
|
-
const renderInfoPanelBody =
|
|
2098
|
+
const renderInfoPanelBody = useCallback6(
|
|
1736
2099
|
(node, { toolbar, ancestors, family }) => {
|
|
1737
2100
|
const genrePlaylist = genrePlaylistByNodeId.get(node.id);
|
|
1738
2101
|
const criteria = genrePlaylist?.criteria;
|
|
1739
|
-
if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */
|
|
2102
|
+
if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ jsxs20(Fragment8, { children: [
|
|
1740
2103
|
toolbar,
|
|
1741
2104
|
family
|
|
1742
2105
|
] }), aside: ancestors };
|
|
1743
2106
|
const genreUuid = criteria.uuid;
|
|
2107
|
+
const parentPlaylist = visibleGenrePlaylists.find(({ uuid }) => uuid === genrePlaylist.parent?.uuid);
|
|
2108
|
+
const canToggleSide = !readOnly && parentPlaylist !== void 0 && parentPlaylist.parent === null;
|
|
2109
|
+
const side = criteria.side ?? "core";
|
|
2110
|
+
const shareTarget = {
|
|
2111
|
+
kind: "genre",
|
|
2112
|
+
genrePlaylistUuid: genrePlaylist.uuid,
|
|
2113
|
+
name: criteria.name,
|
|
2114
|
+
scope,
|
|
2115
|
+
artistUuid
|
|
2116
|
+
};
|
|
2117
|
+
const shareUrl = getShareUrlRef.current?.(shareTarget) ?? void 0;
|
|
2118
|
+
const popSibling = canToggleSide && side === "core" ? visibleGenrePlaylists.find(
|
|
2119
|
+
(sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist.uuid && sibling.criteria?.side === "pop"
|
|
2120
|
+
) : void 0;
|
|
1744
2121
|
return {
|
|
1745
|
-
primary: /* @__PURE__ */
|
|
2122
|
+
primary: /* @__PURE__ */ jsxs20(Fragment8, { children: [
|
|
1746
2123
|
toolbar,
|
|
1747
|
-
/* @__PURE__ */
|
|
2124
|
+
/* @__PURE__ */ jsx27(
|
|
1748
2125
|
GenreNodeActionBand,
|
|
1749
2126
|
{
|
|
1750
2127
|
node,
|
|
1751
2128
|
genrePlaylist,
|
|
1752
2129
|
scope,
|
|
2130
|
+
shareTarget: shareUrl ? shareTarget : void 0,
|
|
2131
|
+
shareUrl,
|
|
1753
2132
|
onZoomIn: zoomInRef.current,
|
|
1754
2133
|
onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
|
|
1755
2134
|
onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
|
|
1756
2135
|
onChangeParent: readOnly ? void 0 : () => showPopup(
|
|
1757
|
-
/* @__PURE__ */
|
|
2136
|
+
/* @__PURE__ */ jsx27(
|
|
1758
2137
|
ChangeParentPopup,
|
|
1759
2138
|
{
|
|
1760
2139
|
genrePlaylist: { ...genrePlaylist, criteria },
|
|
1761
2140
|
genrePlaylists: visibleGenrePlaylists,
|
|
1762
2141
|
scope,
|
|
1763
2142
|
getBackendBaseUrl,
|
|
1764
|
-
onClose: hidePopup
|
|
2143
|
+
onClose: hidePopup,
|
|
2144
|
+
onParentChanged
|
|
1765
2145
|
}
|
|
1766
2146
|
)
|
|
1767
2147
|
),
|
|
2148
|
+
side,
|
|
2149
|
+
onToggleSide: canToggleSide ? () => updateGenre({ uuid: genreUuid, data: { side: side === "pop" ? "core" : "pop" } }) : void 0,
|
|
2150
|
+
toggleSideDisabledReason: popSibling ? `${popSibling.name} is already the pop side of ${parentPlaylist?.name}` : void 0,
|
|
1768
2151
|
additionalActions,
|
|
1769
2152
|
playback
|
|
1770
2153
|
}
|
|
1771
2154
|
),
|
|
1772
|
-
/* @__PURE__ */
|
|
2155
|
+
/* @__PURE__ */ jsx27(
|
|
1773
2156
|
GenreDetailExtras,
|
|
1774
2157
|
{
|
|
1775
2158
|
family,
|
|
1776
2159
|
genreUuid,
|
|
1777
2160
|
genrePlaylist,
|
|
2161
|
+
wikidataId: criteria.wikidataId,
|
|
1778
2162
|
scope,
|
|
1779
2163
|
getBackendBaseUrl,
|
|
1780
2164
|
criteriaOverviewSchema,
|
|
@@ -1801,7 +2185,9 @@ function GenreTreeView({
|
|
|
1801
2185
|
handleGenreRenameAction,
|
|
1802
2186
|
showPopup,
|
|
1803
2187
|
hidePopup,
|
|
2188
|
+
onParentChanged,
|
|
1804
2189
|
visibleGenrePlaylists,
|
|
2190
|
+
updateGenre,
|
|
1805
2191
|
playback,
|
|
1806
2192
|
filterArtist,
|
|
1807
2193
|
scope,
|
|
@@ -1818,7 +2204,7 @@ function GenreTreeView({
|
|
|
1818
2204
|
);
|
|
1819
2205
|
const isRotationControlled = controlledAllowWheelRotation !== void 0;
|
|
1820
2206
|
const allowWheelRotation = controlledAllowWheelRotation ?? internalAllowWheelRotation;
|
|
1821
|
-
const detailTab = placement === "regions" && (
|
|
2207
|
+
const detailTab = placement === "regions" && (hasSelectedGenreNode || detailOverlay) ? /* @__PURE__ */ jsx27(
|
|
1822
2208
|
PanelEdgeTab,
|
|
1823
2209
|
{
|
|
1824
2210
|
side: "left",
|
|
@@ -1826,25 +2212,14 @@ function GenreTreeView({
|
|
|
1826
2212
|
center: detailExpanded ? null : isCollapsed ? { label: "Show panel", onClick: () => changeCollapsed(false) } : onDetailExpandedChange ? { label: "Extend panel", onClick: () => onDetailExpandedChange(true) } : null
|
|
1827
2213
|
}
|
|
1828
2214
|
) : null;
|
|
1829
|
-
const
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
genrePlaylists: visibleGenrePlaylists,
|
|
1834
|
-
onSelect: handleGenreSearchSelect,
|
|
1835
|
-
selectedName: selectedGenrePlaylist?.name ?? builtInArtistPanel?.title ?? null,
|
|
1836
|
-
onClear: builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
|
|
1837
|
-
onPrevious: handlePrevious,
|
|
1838
|
-
onQueryChange: artistSearch ? setSearchText : void 0,
|
|
1839
|
-
extraResults: artistExtraResults
|
|
1840
|
-
}
|
|
1841
|
-
),
|
|
1842
|
-
!isLoading && filterArtist && /* @__PURE__ */ jsxs17("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
|
|
1843
|
-
/* @__PURE__ */ jsx23(
|
|
2215
|
+
const renderArtistFilter = () => {
|
|
2216
|
+
if (isLoading || !filterArtist) return null;
|
|
2217
|
+
return /* @__PURE__ */ jsxs20("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
|
|
2218
|
+
/* @__PURE__ */ jsx27(
|
|
1844
2219
|
"button",
|
|
1845
2220
|
{
|
|
1846
2221
|
type: "button",
|
|
1847
|
-
className: "py-1 pl-3 pr-2 hover:underline",
|
|
2222
|
+
className: "py-1 pl-3 pr-2 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] hover:underline",
|
|
1848
2223
|
"aria-label": `Show artist ${filterArtist.name}`,
|
|
1849
2224
|
onClick: () => {
|
|
1850
2225
|
if (selectedNodeId !== null) clearSelection();
|
|
@@ -1852,86 +2227,174 @@ function GenreTreeView({
|
|
|
1852
2227
|
children: filterArtist.name
|
|
1853
2228
|
}
|
|
1854
2229
|
),
|
|
1855
|
-
/* @__PURE__ */
|
|
2230
|
+
/* @__PURE__ */ jsx27(
|
|
1856
2231
|
"button",
|
|
1857
2232
|
{
|
|
1858
2233
|
type: "button",
|
|
1859
|
-
className: "py-1 pl-1 pr-3 hover:text-[var(--color-text)]",
|
|
2234
|
+
className: "py-1 pl-1 pr-3 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] hover:text-[var(--color-text)]",
|
|
1860
2235
|
"aria-label": `Clear artist filter ${filterArtist.name}`,
|
|
1861
2236
|
onClick: () => changeArtist(null),
|
|
1862
2237
|
children: "\xD7"
|
|
1863
2238
|
}
|
|
1864
2239
|
)
|
|
1865
|
-
] })
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
value: viewMode,
|
|
1871
|
-
onChange: setViewMode,
|
|
1872
|
-
options: [
|
|
1873
|
-
{ value: "pop-core", label: "Pop/Core", disabled: !canShowPopCore, title: canShowPopCore ? void 0 : NO_POP_ROOT_TITLE },
|
|
1874
|
-
{ value: "wheel", label: "Wheel" },
|
|
1875
|
-
{ value: "stacked", label: "Stacked" },
|
|
1876
|
-
{ value: "outline", label: "Outline", disabled: !canShowPopCore, title: canShowPopCore ? void 0 : NO_POP_ROOT_TITLE }
|
|
1877
|
-
]
|
|
1878
|
-
}
|
|
1879
|
-
),
|
|
1880
|
-
!isLoading && showDisplayOptions && /* @__PURE__ */ jsx23(
|
|
1881
|
-
"div",
|
|
2240
|
+
] });
|
|
2241
|
+
};
|
|
2242
|
+
const actions = /* @__PURE__ */ jsxs20(Fragment8, { children: [
|
|
2243
|
+
!isLoading && /* @__PURE__ */ jsx27(
|
|
2244
|
+
GenreSearch,
|
|
1882
2245
|
{
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
size: "sm",
|
|
1891
|
-
onClick: toggleWheelRotation,
|
|
1892
|
-
children: "Rotation"
|
|
1893
|
-
}
|
|
1894
|
-
)
|
|
2246
|
+
genrePlaylists: visibleGenrePlaylists,
|
|
2247
|
+
onSelect: handleGenreSearchSelect,
|
|
2248
|
+
selectedName: selectedGenrePlaylist?.name ?? builtInArtistPanel?.title ?? null,
|
|
2249
|
+
onClear: builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
|
|
2250
|
+
onPrevious: handlePrevious,
|
|
2251
|
+
onQueryChange: artistSearch ? setSearchText : void 0,
|
|
2252
|
+
extraResults: artistExtraResults
|
|
1895
2253
|
}
|
|
1896
2254
|
),
|
|
1897
|
-
!isLoading &&
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
2255
|
+
!isLoading && filterArtist && /* @__PURE__ */ jsx27("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
|
|
2256
|
+
/* @__PURE__ */ jsxs20("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: [
|
|
2257
|
+
!isLoading && !isControlled && /* @__PURE__ */ jsx27(
|
|
2258
|
+
SegmentedControl2,
|
|
2259
|
+
{
|
|
2260
|
+
"aria-label": "Tree view mode",
|
|
2261
|
+
value: viewMode,
|
|
2262
|
+
onChange: setViewMode,
|
|
2263
|
+
options: [
|
|
2264
|
+
{ value: "pop-core", label: "Pop/Core", disabled: !canShowPopCore, title: canShowPopCore ? void 0 : NO_POP_ROOT_TITLE },
|
|
2265
|
+
{ value: "wheel", label: "Wheel" },
|
|
2266
|
+
{ value: "stacked", label: "Stacked" },
|
|
2267
|
+
{ value: "outline", label: "Outline", disabled: !canShowPopCore, title: canShowPopCore ? void 0 : NO_POP_ROOT_TITLE }
|
|
2268
|
+
]
|
|
2269
|
+
}
|
|
2270
|
+
),
|
|
2271
|
+
!isLoading && showDisplayOptions && /* @__PURE__ */ jsx27(
|
|
2272
|
+
"div",
|
|
2273
|
+
{
|
|
2274
|
+
className: "flex items-center gap-1",
|
|
2275
|
+
role: "group",
|
|
2276
|
+
"aria-label": "Tree display options",
|
|
2277
|
+
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ jsx27(
|
|
2278
|
+
Button,
|
|
2279
|
+
{
|
|
2280
|
+
variant: allowWheelRotation ? "default" : "outline",
|
|
2281
|
+
size: "sm",
|
|
2282
|
+
onClick: toggleWheelRotation,
|
|
2283
|
+
children: "Rotation"
|
|
2284
|
+
}
|
|
2285
|
+
)
|
|
2286
|
+
}
|
|
2287
|
+
),
|
|
2288
|
+
!isLoading && !readOnly && showAddRoot && /* @__PURE__ */ jsx27(
|
|
2289
|
+
IconTextButton,
|
|
2290
|
+
{
|
|
2291
|
+
icon: Plus2,
|
|
2292
|
+
text: "Add root",
|
|
2293
|
+
onClick: () => handleGenreCreationAction(null)
|
|
2294
|
+
}
|
|
2295
|
+
)
|
|
2296
|
+
] })
|
|
1905
2297
|
] });
|
|
1906
|
-
return /* @__PURE__ */
|
|
2298
|
+
return /* @__PURE__ */ jsxs20(
|
|
1907
2299
|
"div",
|
|
1908
2300
|
{
|
|
1909
2301
|
ref: rootRef,
|
|
1910
|
-
className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS,
|
|
2302
|
+
className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
|
|
1911
2303
|
children: [
|
|
1912
|
-
/* @__PURE__ */
|
|
1913
|
-
/* @__PURE__ */
|
|
2304
|
+
/* @__PURE__ */ jsx27("style", { children: PREVIOUS_BUTTON_CSS }),
|
|
2305
|
+
/* @__PURE__ */ jsx27(
|
|
1914
2306
|
"div",
|
|
1915
2307
|
{
|
|
1916
|
-
className: `actions-container absolute z-30 flex items-center gap-2 ${placement === "regions" ? "left-[calc(var(--layout-gutter)+var(--layout-panel-inset))] top-[calc(var(--layout-gutter)+var(--layout-panel-inset))]" : "left-5 top-5"}`,
|
|
2308
|
+
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"}`,
|
|
1917
2309
|
children: actions
|
|
1918
2310
|
}
|
|
1919
2311
|
),
|
|
1920
|
-
|
|
2312
|
+
!isLoading && filterArtist && /* @__PURE__ */ jsx27(
|
|
1921
2313
|
"div",
|
|
1922
2314
|
{
|
|
1923
|
-
className: `absolute
|
|
2315
|
+
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"}`,
|
|
2316
|
+
children: renderArtistFilter()
|
|
2317
|
+
}
|
|
2318
|
+
),
|
|
2319
|
+
detailTab && /* @__PURE__ */ jsx27(
|
|
2320
|
+
"div",
|
|
2321
|
+
{
|
|
2322
|
+
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))]"}`,
|
|
1924
2323
|
children: detailTab
|
|
1925
2324
|
}
|
|
1926
2325
|
),
|
|
1927
|
-
/* @__PURE__ */
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
2326
|
+
/* @__PURE__ */ jsx27("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ jsxs20("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
|
|
2327
|
+
!isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ jsx27("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
|
|
2328
|
+
isLoading ? /* @__PURE__ */ jsx27(GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
|
|
2329
|
+
// The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
|
|
2330
|
+
// and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
|
|
2331
|
+
// Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
|
|
2332
|
+
// The panel's own Zoom in button is hidden: the search bar carries it.
|
|
2333
|
+
/* @__PURE__ */ jsx27("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 [--gtv-info-panel-header-inset:calc(var(--layout-search-h,2.5rem)+var(--layout-panel-inset,0.5rem))] [&_.gtv-info-panel-header]:pb-2 [&_.gtv-info-panel-title--hidden]:h-0 [&_.gtv-toolbar]:hidden", children: /* @__PURE__ */ jsx27(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ jsx27(GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ jsx27(
|
|
2334
|
+
GenrePlaylistTreeWheel,
|
|
2335
|
+
{
|
|
2336
|
+
scope,
|
|
2337
|
+
genrePlaylists: visibleGenrePlaylists,
|
|
2338
|
+
handleGenreCreationAction,
|
|
2339
|
+
handleGenreRenameAction,
|
|
2340
|
+
additionalActions,
|
|
2341
|
+
onSelectedNodeChange: handleSelectedNodeChange,
|
|
2342
|
+
hideInfoPanelClose: true,
|
|
2343
|
+
hideInfoPanelTitle: true,
|
|
2344
|
+
onNodeHover: handleNodeHover,
|
|
2345
|
+
renderInfoPanelBody,
|
|
2346
|
+
playback,
|
|
2347
|
+
selectedNodeId,
|
|
2348
|
+
readOnly,
|
|
2349
|
+
allowWheelRotation,
|
|
2350
|
+
controlsOrientation,
|
|
2351
|
+
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2352
|
+
busy,
|
|
2353
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ jsxs20("div", { className: "flex items-center gap-2", children: [
|
|
2354
|
+
floatingControlsLeading,
|
|
2355
|
+
canSizeByViews && /* @__PURE__ */ jsx27(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2356
|
+
] }) : void 0,
|
|
2357
|
+
defaultViewport,
|
|
2358
|
+
onViewportChange,
|
|
2359
|
+
...infoPanelCuration
|
|
2360
|
+
}
|
|
2361
|
+
) }) })
|
|
2362
|
+
) : viewMode === "pop-core" ? (
|
|
2363
|
+
// Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
|
|
2364
|
+
/* @__PURE__ */ jsx27("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 [--gtv-info-panel-header-inset:calc(var(--layout-search-h,2.5rem)+var(--layout-panel-inset,0.5rem))] [&_.gtv-info-panel-header]:pb-2 [&_.gtv-info-panel-title--hidden]:h-0 [&_.gtv-toolbar]:hidden [&_.gtv-info-panel-zoom-in]:hidden", children: /* @__PURE__ */ jsx27(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ jsx27(GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ jsx27(
|
|
2365
|
+
GenrePlaylistTreeWheelRadialPopCore,
|
|
2366
|
+
{
|
|
2367
|
+
scope,
|
|
2368
|
+
genrePlaylists: visibleGenrePlaylists,
|
|
2369
|
+
handleGenreCreationAction,
|
|
2370
|
+
handleGenreRenameAction,
|
|
2371
|
+
additionalActions,
|
|
2372
|
+
onSelectedNodeChange: handleSelectedNodeChange,
|
|
2373
|
+
hideInfoPanelClose: true,
|
|
2374
|
+
hideInfoPanelTitle: true,
|
|
2375
|
+
onNodeHover: handleNodeHover,
|
|
2376
|
+
renderInfoPanelBody,
|
|
2377
|
+
playback,
|
|
2378
|
+
selectedNodeId,
|
|
2379
|
+
readOnly,
|
|
2380
|
+
allowWheelRotation,
|
|
2381
|
+
controlsOrientation,
|
|
2382
|
+
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2383
|
+
busy,
|
|
2384
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ jsxs20("div", { className: "flex items-center gap-2", children: [
|
|
2385
|
+
floatingControlsLeading,
|
|
2386
|
+
canSizeByViews && /* @__PURE__ */ jsx27(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2387
|
+
] }) : void 0,
|
|
2388
|
+
defaultViewport,
|
|
2389
|
+
onViewportChange,
|
|
2390
|
+
...infoPanelCuration,
|
|
2391
|
+
...focus
|
|
2392
|
+
}
|
|
2393
|
+
) }) })
|
|
2394
|
+
) : viewMode === "outline" ? /* @__PURE__ */ jsx27("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative [--gtv-info-panel-header-inset:calc(var(--layout-search-h,2.5rem)+var(--layout-panel-inset,0.5rem)+0.75rem)] [&_.gtv-toolbar]:hidden", children: /* @__PURE__ */ jsx27(
|
|
2395
|
+
GenrePlaylistTreeWheelRadialPopCore,
|
|
1934
2396
|
{
|
|
2397
|
+
outline: true,
|
|
1935
2398
|
scope,
|
|
1936
2399
|
genrePlaylists: visibleGenrePlaylists,
|
|
1937
2400
|
handleGenreCreationAction,
|
|
@@ -1945,94 +2408,45 @@ function GenreTreeView({
|
|
|
1945
2408
|
playback,
|
|
1946
2409
|
selectedNodeId,
|
|
1947
2410
|
readOnly,
|
|
1948
|
-
allowWheelRotation,
|
|
1949
|
-
controlsOrientation,
|
|
1950
|
-
defaultViewport,
|
|
1951
|
-
onViewportChange,
|
|
1952
2411
|
...infoPanelCuration
|
|
1953
2412
|
}
|
|
1954
|
-
) })
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
2413
|
+
) }) : /* @__PURE__ */ jsx27("div", { className: "tree-container relative flex flex-col gap-4 w-full pt-14 overflow-x-auto overflow-y-auto text-[var(--color-text)] [&_.gtv-toolbar]:hidden", children: Object.entries(groupedGenrePlaylistsByRoot).map(
|
|
2414
|
+
([uuid, genrePlaylistTreePerRoot], rootIndex) => {
|
|
2415
|
+
return /* @__PURE__ */ jsx27(
|
|
2416
|
+
"div",
|
|
2417
|
+
{
|
|
2418
|
+
className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
|
|
2419
|
+
children: /* @__PURE__ */ jsx27("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ jsx27(
|
|
2420
|
+
GenrePlaylistTreePerRoot,
|
|
2421
|
+
{
|
|
2422
|
+
scope,
|
|
2423
|
+
rootUuid: uuid,
|
|
2424
|
+
genrePlaylistTreePerRoot,
|
|
2425
|
+
handleGenreCreationAction,
|
|
2426
|
+
handleGenreRenameAction,
|
|
2427
|
+
additionalActions,
|
|
2428
|
+
onSelectedNodeChange: handleSelectedNodeChange,
|
|
2429
|
+
hideInfoPanelClose: true,
|
|
2430
|
+
hideInfoPanelTitle: true,
|
|
2431
|
+
onNodeHover: handleNodeHover,
|
|
2432
|
+
renderInfoPanelBody,
|
|
2433
|
+
playback,
|
|
2434
|
+
selectedNodeId: uuid === selectedGenrePlaylist?.root.uuid ? selectedNodeId : null,
|
|
2435
|
+
readOnly,
|
|
2436
|
+
wheelZoom: "modifier",
|
|
2437
|
+
controlsOrientation,
|
|
2438
|
+
defaultViewport: uuid === selectedGenrePlaylist?.root.uuid ? defaultViewport : void 0,
|
|
2439
|
+
onViewportChange: uuid === selectedGenrePlaylist?.root.uuid ? onViewportChange : void 0,
|
|
2440
|
+
...infoPanelCuration,
|
|
2441
|
+
overlayPanel: rootIndex === 0 ? activeOverlayPanel : void 0
|
|
2442
|
+
}
|
|
2443
|
+
) })
|
|
2444
|
+
},
|
|
2445
|
+
uuid
|
|
2446
|
+
);
|
|
1979
2447
|
}
|
|
1980
|
-
) })
|
|
1981
|
-
)
|
|
1982
|
-
GenrePlaylistTreeWheelRadialPopCore,
|
|
1983
|
-
{
|
|
1984
|
-
outline: true,
|
|
1985
|
-
scope,
|
|
1986
|
-
genrePlaylists: visibleGenrePlaylists,
|
|
1987
|
-
handleGenreCreationAction,
|
|
1988
|
-
handleGenreRenameAction,
|
|
1989
|
-
additionalActions,
|
|
1990
|
-
onSelectedNodeChange: handleSelectedNodeChange,
|
|
1991
|
-
hideInfoPanelClose: true,
|
|
1992
|
-
hideInfoPanelTitle: true,
|
|
1993
|
-
onNodeHover: handleNodeHover,
|
|
1994
|
-
renderInfoPanelBody,
|
|
1995
|
-
playback,
|
|
1996
|
-
selectedNodeId,
|
|
1997
|
-
readOnly,
|
|
1998
|
-
...infoPanelCuration
|
|
1999
|
-
}
|
|
2000
|
-
) }) : /* @__PURE__ */ jsx23("div", { className: "tree-container relative flex flex-col gap-4 w-full pt-14 overflow-x-auto overflow-y-auto text-[var(--color-text)] [&_.gtv-toolbar]:hidden", children: Object.entries(groupedGenrePlaylistsByRoot).map(
|
|
2001
|
-
([uuid, genrePlaylistTreePerRoot], rootIndex) => {
|
|
2002
|
-
return /* @__PURE__ */ jsx23(
|
|
2003
|
-
"div",
|
|
2004
|
-
{
|
|
2005
|
-
className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
|
|
2006
|
-
children: /* @__PURE__ */ jsx23("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ jsx23(
|
|
2007
|
-
GenrePlaylistTreePerRoot,
|
|
2008
|
-
{
|
|
2009
|
-
scope,
|
|
2010
|
-
rootUuid: uuid,
|
|
2011
|
-
genrePlaylistTreePerRoot,
|
|
2012
|
-
handleGenreCreationAction,
|
|
2013
|
-
handleGenreRenameAction,
|
|
2014
|
-
additionalActions,
|
|
2015
|
-
onSelectedNodeChange: handleSelectedNodeChange,
|
|
2016
|
-
hideInfoPanelClose: true,
|
|
2017
|
-
hideInfoPanelTitle: true,
|
|
2018
|
-
onNodeHover: handleNodeHover,
|
|
2019
|
-
renderInfoPanelBody,
|
|
2020
|
-
playback,
|
|
2021
|
-
selectedNodeId: uuid === selectedGenrePlaylist?.root.uuid ? selectedNodeId : null,
|
|
2022
|
-
readOnly,
|
|
2023
|
-
wheelZoom: "modifier",
|
|
2024
|
-
controlsOrientation,
|
|
2025
|
-
defaultViewport: uuid === selectedGenrePlaylist?.root.uuid ? defaultViewport : void 0,
|
|
2026
|
-
onViewportChange: uuid === selectedGenrePlaylist?.root.uuid ? onViewportChange : void 0,
|
|
2027
|
-
...infoPanelCuration,
|
|
2028
|
-
overlayPanel: rootIndex === 0 ? activeOverlayPanel : void 0
|
|
2029
|
-
}
|
|
2030
|
-
) })
|
|
2031
|
-
},
|
|
2032
|
-
uuid
|
|
2033
|
-
);
|
|
2034
|
-
}
|
|
2035
|
-
) }) }) })
|
|
2448
|
+
) })
|
|
2449
|
+
] }) })
|
|
2036
2450
|
]
|
|
2037
2451
|
}
|
|
2038
2452
|
);
|
|
@@ -2047,24 +2461,35 @@ import {
|
|
|
2047
2461
|
} from "@behindthemusictree/genre-tree-view";
|
|
2048
2462
|
|
|
2049
2463
|
// src/genre-tree/track-list-sidebar/TrackListSidebar.tsx
|
|
2050
|
-
import { useEffect as
|
|
2464
|
+
import { useEffect as useEffect6, useLayoutEffect as useLayoutEffect2, useRef as useRef5 } from "react";
|
|
2465
|
+
import { ListMusic } from "lucide-react";
|
|
2051
2466
|
import { useTrackList as useTrackList5 } from "@behindthemusictree/app-kit/genre-tree";
|
|
2052
|
-
import { useTrackListSidebarVisibility } from "@behindthemusictree/app-kit/genre-tree";
|
|
2053
2467
|
import { TrackListOriginType as TrackListOriginType2 } from "@behindthemusictree/app-kit/genre-tree";
|
|
2054
2468
|
import { useTrackNoun as useTrackNoun6 } from "@behindthemusictree/app-kit/track-noun";
|
|
2055
|
-
import { jsx as
|
|
2469
|
+
import { jsx as jsx28, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
2470
|
+
var useBrowserLayoutEffect = typeof window === "undefined" ? useEffect6 : useLayoutEffect2;
|
|
2056
2471
|
function TrackListSidebar({
|
|
2057
|
-
className = "",
|
|
2472
|
+
className: className2 = "",
|
|
2058
2473
|
renderDuration,
|
|
2059
2474
|
renderActions,
|
|
2060
2475
|
layout = "fixed",
|
|
2476
|
+
isCollapsed = false,
|
|
2477
|
+
onExpand,
|
|
2478
|
+
onMinimize,
|
|
2061
2479
|
renderArtistLink
|
|
2062
2480
|
}) {
|
|
2063
|
-
const { trackList, loadMore } = useTrackList5();
|
|
2481
|
+
const { trackList, loadMore, autoAdvanceScrollRequest } = useTrackList5();
|
|
2064
2482
|
const trackNoun = useTrackNoun6();
|
|
2483
|
+
const trackListRef = useRef5(null);
|
|
2484
|
+
const previousOriginRef = useRef5(trackList?.origin ?? null);
|
|
2485
|
+
const handledAutoAdvanceRequestIdRef = useRef5(null);
|
|
2486
|
+
const shouldFollowPlaybackRef = useRef5(true);
|
|
2487
|
+
const programmaticScrollTopRef = useRef5(null);
|
|
2488
|
+
const userScrollIntentRef = useRef5(false);
|
|
2489
|
+
const userScrollIntentTimerRef = useRef5(null);
|
|
2065
2490
|
const loadMoreSentinelRef = useRef5(null);
|
|
2066
2491
|
const hasMore = !!trackList && trackList.nextPage !== null;
|
|
2067
|
-
|
|
2492
|
+
useEffect6(() => {
|
|
2068
2493
|
const sentinel = loadMoreSentinelRef.current;
|
|
2069
2494
|
if (!hasMore || !sentinel) return;
|
|
2070
2495
|
const observer = new IntersectionObserver((entries) => {
|
|
@@ -2075,44 +2500,115 @@ function TrackListSidebar({
|
|
|
2075
2500
|
observer.observe(sentinel);
|
|
2076
2501
|
return () => observer.disconnect();
|
|
2077
2502
|
}, [hasMore, loadMore]);
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2503
|
+
useBrowserLayoutEffect(() => {
|
|
2504
|
+
const origin = trackList?.origin ?? null;
|
|
2505
|
+
if (previousOriginRef.current === origin) return;
|
|
2506
|
+
previousOriginRef.current = origin;
|
|
2507
|
+
handledAutoAdvanceRequestIdRef.current = null;
|
|
2508
|
+
shouldFollowPlaybackRef.current = true;
|
|
2509
|
+
programmaticScrollTopRef.current = null;
|
|
2510
|
+
if (trackListRef.current) trackListRef.current.scrollTop = 0;
|
|
2511
|
+
}, [trackList?.origin]);
|
|
2512
|
+
useEffect6(() => {
|
|
2513
|
+
const list = trackListRef.current;
|
|
2514
|
+
if (!isCollapsed && list?.scrollTop === 0) shouldFollowPlaybackRef.current = true;
|
|
2515
|
+
}, [isCollapsed]);
|
|
2516
|
+
useEffect6(
|
|
2517
|
+
() => () => {
|
|
2518
|
+
if (userScrollIntentTimerRef.current !== null) clearTimeout(userScrollIntentTimerRef.current);
|
|
2519
|
+
},
|
|
2520
|
+
[]
|
|
2521
|
+
);
|
|
2522
|
+
useBrowserLayoutEffect(() => {
|
|
2523
|
+
if (!autoAdvanceScrollRequest || handledAutoAdvanceRequestIdRef.current === autoAdvanceScrollRequest.id) {
|
|
2524
|
+
return;
|
|
2525
|
+
}
|
|
2526
|
+
if (isCollapsed) return;
|
|
2527
|
+
const list = trackListRef.current;
|
|
2528
|
+
if (!list) return;
|
|
2529
|
+
if (list.scrollTop <= 1) shouldFollowPlaybackRef.current = true;
|
|
2530
|
+
if (!shouldFollowPlaybackRef.current) {
|
|
2531
|
+
handledAutoAdvanceRequestIdRef.current = autoAdvanceScrollRequest.id;
|
|
2532
|
+
return;
|
|
2533
|
+
}
|
|
2534
|
+
const row = Array.from(list.querySelectorAll("[data-track-uuid]")).find(
|
|
2535
|
+
(element) => element.dataset.trackUuid === autoAdvanceScrollRequest.trackUuid
|
|
2536
|
+
);
|
|
2537
|
+
if (!row) return;
|
|
2538
|
+
handledAutoAdvanceRequestIdRef.current = autoAdvanceScrollRequest.id;
|
|
2539
|
+
const rowTop = row.getBoundingClientRect().top - list.getBoundingClientRect().top;
|
|
2540
|
+
const targetScrollTop = list.scrollTop + rowTop;
|
|
2541
|
+
programmaticScrollTopRef.current = targetScrollTop;
|
|
2542
|
+
list.scrollTop = targetScrollTop;
|
|
2543
|
+
programmaticScrollTopRef.current = list.scrollTop;
|
|
2544
|
+
}, [autoAdvanceScrollRequest, isCollapsed, trackList]);
|
|
2545
|
+
const markUserScrollIntent = () => {
|
|
2546
|
+
userScrollIntentRef.current = true;
|
|
2547
|
+
if (userScrollIntentTimerRef.current !== null) clearTimeout(userScrollIntentTimerRef.current);
|
|
2548
|
+
userScrollIntentTimerRef.current = setTimeout(() => {
|
|
2549
|
+
userScrollIntentRef.current = false;
|
|
2550
|
+
userScrollIntentTimerRef.current = null;
|
|
2551
|
+
}, 250);
|
|
2552
|
+
};
|
|
2553
|
+
const handleTrackListScroll = (event) => {
|
|
2554
|
+
const list = event.currentTarget;
|
|
2555
|
+
if (userScrollIntentRef.current) {
|
|
2556
|
+
userScrollIntentRef.current = false;
|
|
2557
|
+
if (userScrollIntentTimerRef.current !== null) clearTimeout(userScrollIntentTimerRef.current);
|
|
2558
|
+
userScrollIntentTimerRef.current = null;
|
|
2559
|
+
programmaticScrollTopRef.current = null;
|
|
2560
|
+
shouldFollowPlaybackRef.current = list.scrollTop <= 1;
|
|
2561
|
+
return;
|
|
2562
|
+
}
|
|
2563
|
+
const programmaticScrollTop = programmaticScrollTopRef.current;
|
|
2564
|
+
if (programmaticScrollTop !== null) {
|
|
2565
|
+
if (Math.abs(list.scrollTop - programmaticScrollTop) <= 1) programmaticScrollTopRef.current = null;
|
|
2566
|
+
return;
|
|
2567
|
+
}
|
|
2568
|
+
};
|
|
2569
|
+
const positionClasses = layout === "inline" ? "relative w-full flex h-full flex-col" : "fixed right-0 w-[50rem] flex flex-col";
|
|
2570
|
+
return trackList ? /* @__PURE__ */ jsxs21(
|
|
2081
2571
|
"div",
|
|
2082
2572
|
{
|
|
2083
2573
|
"data-theme": "dark",
|
|
2084
|
-
className: `track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${
|
|
2085
|
-
style: layout === "fixed" ? {
|
|
2086
|
-
bottom: "79px"
|
|
2087
|
-
/* Match player height exactly */
|
|
2088
|
-
} : void 0,
|
|
2574
|
+
className: `track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${className2}`,
|
|
2575
|
+
style: layout === "fixed" ? { top: "63.5px", maxHeight: "calc(100vh - 63.5px - 79px)" } : void 0,
|
|
2089
2576
|
children: [
|
|
2090
|
-
/* @__PURE__ */
|
|
2091
|
-
/* @__PURE__ */
|
|
2092
|
-
/* @__PURE__ */
|
|
2093
|
-
|
|
2577
|
+
/* @__PURE__ */ jsxs21("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
|
|
2578
|
+
/* @__PURE__ */ jsxs21("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
|
|
2579
|
+
/* @__PURE__ */ jsxs21("div", { className: "origin flex text-xl", children: [
|
|
2580
|
+
/* @__PURE__ */ jsx28("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
|
|
2581
|
+
/* @__PURE__ */ jsx28("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__ */ jsx28("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
|
|
2582
|
+
] }),
|
|
2583
|
+
/* @__PURE__ */ jsxs21("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
|
|
2584
|
+
trackList.origin.type === TrackListOriginType2.GENRE_PLAYLIST ? "Genre \u2022 " : trackList.origin.type === TrackListOriginType2.ARTIST ? "Artist \u2022 " : `${trackNoun.singular} playlist \u2022 `,
|
|
2585
|
+
`${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`
|
|
2586
|
+
] })
|
|
2094
2587
|
] }),
|
|
2095
|
-
|
|
2096
|
-
trackList.origin.type === TrackListOriginType2.GENRE_PLAYLIST ? "\u2022 Genre playlist \u2022 " : trackList.origin.type === TrackListOriginType2.ARTIST ? "\u2022 Artist \u2022 " : `\u2022 ${trackNoun.singular} playlist \u2022 `,
|
|
2097
|
-
`${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular} \u2022`
|
|
2098
|
-
] }),
|
|
2099
|
-
/* @__PURE__ */ jsx24(
|
|
2588
|
+
(isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ jsx28(
|
|
2100
2589
|
"button",
|
|
2101
2590
|
{
|
|
2102
2591
|
type: "button",
|
|
2103
|
-
"aria-label": "
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2592
|
+
"aria-label": isCollapsed ? "Show tracks" : "Hide tracks",
|
|
2593
|
+
title: isCollapsed ? "Show tracks" : "Hide tracks",
|
|
2594
|
+
"aria-expanded": !isCollapsed,
|
|
2595
|
+
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"}`,
|
|
2596
|
+
onClick: isCollapsed ? onExpand : onMinimize,
|
|
2597
|
+
children: /* @__PURE__ */ jsx28(ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
|
|
2107
2598
|
}
|
|
2108
2599
|
)
|
|
2109
2600
|
] }),
|
|
2110
|
-
/* @__PURE__ */
|
|
2601
|
+
!isCollapsed && /* @__PURE__ */ jsxs21(
|
|
2111
2602
|
"ul",
|
|
2112
2603
|
{
|
|
2113
|
-
|
|
2604
|
+
ref: trackListRef,
|
|
2605
|
+
onScroll: handleTrackListScroll,
|
|
2606
|
+
onWheel: markUserScrollIntent,
|
|
2607
|
+
onTouchMove: markUserScrollIntent,
|
|
2608
|
+
onPointerDown: markUserScrollIntent,
|
|
2609
|
+
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",
|
|
2114
2610
|
children: [
|
|
2115
|
-
trackList.tracks.map((track, index) => /* @__PURE__ */
|
|
2611
|
+
trackList.tracks.map((track, index) => /* @__PURE__ */ jsx28("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ jsx28(
|
|
2116
2612
|
TrackItem,
|
|
2117
2613
|
{
|
|
2118
2614
|
track,
|
|
@@ -2122,7 +2618,7 @@ function TrackListSidebar({
|
|
|
2122
2618
|
renderArtistLink
|
|
2123
2619
|
}
|
|
2124
2620
|
) }, track.uuid)),
|
|
2125
|
-
hasMore && /* @__PURE__ */
|
|
2621
|
+
hasMore && /* @__PURE__ */ jsx28("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
|
|
2126
2622
|
]
|
|
2127
2623
|
}
|
|
2128
2624
|
)
|
|
@@ -2132,47 +2628,167 @@ function TrackListSidebar({
|
|
|
2132
2628
|
}
|
|
2133
2629
|
|
|
2134
2630
|
// src/genre-tree/PlayerPanel.tsx
|
|
2135
|
-
import { useState as
|
|
2136
|
-
import { X as X2 } from "lucide-react";
|
|
2631
|
+
import { useEffect as useEffect7, useState as useState6 } from "react";
|
|
2137
2632
|
import { twMerge as twMerge2 } from "tailwind-merge";
|
|
2138
2633
|
import {
|
|
2139
|
-
usePlayer as
|
|
2634
|
+
usePlayer as usePlayer10,
|
|
2140
2635
|
PlayerVideoSurface
|
|
2141
2636
|
} from "@behindthemusictree/app-kit/player";
|
|
2142
2637
|
import {
|
|
2143
|
-
useTrackListSidebarVisibility
|
|
2638
|
+
useTrackListSidebarVisibility
|
|
2144
2639
|
} from "@behindthemusictree/app-kit/genre-tree";
|
|
2145
|
-
|
|
2640
|
+
|
|
2641
|
+
// src/genre-tree/PlayerControls.tsx
|
|
2642
|
+
import { Pause as Pause2, Play as Play3, SkipBack, SkipForward, Volume2, VolumeX } from "lucide-react";
|
|
2643
|
+
import { formatTime, useTrackList as useTrackList6 } from "@behindthemusictree/app-kit/genre-tree";
|
|
2644
|
+
import { PlayStates as PlayStates3, useCurrentTime, usePlayer as usePlayer9 } from "@behindthemusictree/app-kit/player";
|
|
2645
|
+
import { jsx as jsx29, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
2646
|
+
function PlayerControls() {
|
|
2647
|
+
const {
|
|
2648
|
+
duration,
|
|
2649
|
+
handlePlayPauseAction,
|
|
2650
|
+
isLoading,
|
|
2651
|
+
playState,
|
|
2652
|
+
playerTrackObject,
|
|
2653
|
+
seekTo,
|
|
2654
|
+
setVolume,
|
|
2655
|
+
volume
|
|
2656
|
+
} = usePlayer9();
|
|
2657
|
+
const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } = useTrackList6();
|
|
2658
|
+
const currentTime = useCurrentTime();
|
|
2659
|
+
const isBusy = isLoading || playState === PlayStates3.LOADING;
|
|
2660
|
+
const isReady = !!playerTrackObject?.isReady && !isBusy && !playerTrackObject.loadError;
|
|
2661
|
+
const safeDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;
|
|
2662
|
+
const safeCurrentTime = Number.isFinite(currentTime) ? Math.max(0, Math.min(currentTime, safeDuration)) : 0;
|
|
2663
|
+
const isPlaying = playState === PlayStates3.PLAYING;
|
|
2664
|
+
return /* @__PURE__ */ jsxs22(
|
|
2665
|
+
"section",
|
|
2666
|
+
{
|
|
2667
|
+
"aria-label": "Player controls",
|
|
2668
|
+
className: "player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200",
|
|
2669
|
+
children: [
|
|
2670
|
+
/* @__PURE__ */ jsxs22("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
|
|
2671
|
+
/* @__PURE__ */ jsx29("span", { "aria-label": "Elapsed time", children: formatTime(safeCurrentTime) }),
|
|
2672
|
+
/* @__PURE__ */ jsx29(
|
|
2673
|
+
"input",
|
|
2674
|
+
{
|
|
2675
|
+
"aria-label": "Seek",
|
|
2676
|
+
className: "h-1 min-w-0 flex-1 cursor-pointer accent-white disabled:cursor-not-allowed disabled:opacity-40",
|
|
2677
|
+
type: "range",
|
|
2678
|
+
min: 0,
|
|
2679
|
+
max: safeDuration,
|
|
2680
|
+
step: 0.1,
|
|
2681
|
+
value: safeCurrentTime,
|
|
2682
|
+
disabled: !isReady || safeDuration <= 0,
|
|
2683
|
+
onChange: (event) => seekTo(Number(event.currentTarget.value))
|
|
2684
|
+
}
|
|
2685
|
+
),
|
|
2686
|
+
/* @__PURE__ */ jsx29("span", { "aria-label": "Duration", children: formatTime(safeDuration) })
|
|
2687
|
+
] }),
|
|
2688
|
+
/* @__PURE__ */ jsxs22("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
|
|
2689
|
+
/* @__PURE__ */ jsxs22("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
|
|
2690
|
+
/* @__PURE__ */ jsx29(
|
|
2691
|
+
"button",
|
|
2692
|
+
{
|
|
2693
|
+
type: "button",
|
|
2694
|
+
"aria-label": "Previous track",
|
|
2695
|
+
title: "Previous track",
|
|
2696
|
+
disabled: !isReady || !hasPreviousTrack,
|
|
2697
|
+
onClick: playPreviousTrack,
|
|
2698
|
+
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",
|
|
2699
|
+
children: /* @__PURE__ */ jsx29(SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
2700
|
+
}
|
|
2701
|
+
),
|
|
2702
|
+
/* @__PURE__ */ jsx29(
|
|
2703
|
+
"button",
|
|
2704
|
+
{
|
|
2705
|
+
type: "button",
|
|
2706
|
+
"aria-label": isPlaying ? "Pause" : "Play",
|
|
2707
|
+
title: isPlaying ? "Pause" : "Play",
|
|
2708
|
+
disabled: !isReady,
|
|
2709
|
+
onClick: handlePlayPauseAction,
|
|
2710
|
+
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",
|
|
2711
|
+
children: isPlaying ? /* @__PURE__ */ jsx29(Pause2, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ jsx29(Play3, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
|
|
2712
|
+
}
|
|
2713
|
+
),
|
|
2714
|
+
/* @__PURE__ */ jsx29(
|
|
2715
|
+
"button",
|
|
2716
|
+
{
|
|
2717
|
+
type: "button",
|
|
2718
|
+
"aria-label": "Next track",
|
|
2719
|
+
title: "Next track",
|
|
2720
|
+
disabled: !isReady || !hasNextTrack,
|
|
2721
|
+
onClick: playNextTrack,
|
|
2722
|
+
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",
|
|
2723
|
+
children: /* @__PURE__ */ jsx29(SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
2724
|
+
}
|
|
2725
|
+
)
|
|
2726
|
+
] }),
|
|
2727
|
+
/* @__PURE__ */ jsxs22("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: [
|
|
2728
|
+
volume === 0 ? /* @__PURE__ */ jsx29(VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ jsx29(Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
|
|
2729
|
+
/* @__PURE__ */ jsx29("span", { className: "sr-only", children: "Volume" }),
|
|
2730
|
+
/* @__PURE__ */ jsx29(
|
|
2731
|
+
"input",
|
|
2732
|
+
{
|
|
2733
|
+
"aria-label": "Volume",
|
|
2734
|
+
className: "h-[3px] min-w-0 flex-1 cursor-pointer accent-white disabled:cursor-not-allowed disabled:opacity-40",
|
|
2735
|
+
type: "range",
|
|
2736
|
+
min: 0,
|
|
2737
|
+
max: 100,
|
|
2738
|
+
step: 1,
|
|
2739
|
+
value: volume,
|
|
2740
|
+
disabled: !isReady,
|
|
2741
|
+
onChange: (event) => setVolume(Number(event.currentTarget.value))
|
|
2742
|
+
}
|
|
2743
|
+
)
|
|
2744
|
+
] })
|
|
2745
|
+
] })
|
|
2746
|
+
]
|
|
2747
|
+
}
|
|
2748
|
+
);
|
|
2749
|
+
}
|
|
2750
|
+
|
|
2751
|
+
// src/genre-tree/PlayerPanel.tsx
|
|
2752
|
+
import { jsx as jsx30, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
2146
2753
|
var PANEL_ID = "player-panel";
|
|
2147
|
-
var iconButtonClassName = "flex h-6 w-6 shrink-0 items-center justify-center p-0 rounded-md hover:bg-white/10";
|
|
2148
2754
|
var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
|
|
2149
2755
|
function PlayerPanel({
|
|
2150
|
-
className,
|
|
2756
|
+
className: className2,
|
|
2151
2757
|
isVisible = true,
|
|
2152
|
-
onClose,
|
|
2153
2758
|
expanded,
|
|
2154
2759
|
onExpandedChange,
|
|
2155
2760
|
extendedWidth = DEFAULT_EXTENDED_WIDTH,
|
|
2156
2761
|
renderArtistLink
|
|
2157
2762
|
}) {
|
|
2158
|
-
const { playerTrackObject } =
|
|
2159
|
-
const { toggleTrackListSidebar, isTrackListSidebarVisible } =
|
|
2160
|
-
const [
|
|
2763
|
+
const { playerTrackObject } = usePlayer10();
|
|
2764
|
+
const { toggleTrackListSidebar, isTrackListSidebarVisible } = useTrackListSidebarVisibility();
|
|
2765
|
+
const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = useState6("");
|
|
2766
|
+
const loadedTrackTitle = playerTrackObject?.track.title;
|
|
2767
|
+
useEffect7(() => {
|
|
2768
|
+
if (loadedTrackTitle) setLastLoadedTrackTitle(loadedTrackTitle);
|
|
2769
|
+
}, [loadedTrackTitle]);
|
|
2770
|
+
const [uncontrolledExtended, setUncontrolledExtended] = useState6(false);
|
|
2161
2771
|
const isExtendRequested = expanded ?? uncontrolledExtended;
|
|
2162
2772
|
const setIsExtendRequested = (next) => {
|
|
2163
2773
|
setUncontrolledExtended(next);
|
|
2164
2774
|
onExpandedChange?.(next);
|
|
2165
2775
|
};
|
|
2166
2776
|
const isExtended = isExtendRequested && isTrackListSidebarVisible;
|
|
2167
|
-
const
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
} : { label: "Hide song list", onClick: toggleTrackListSidebar };
|
|
2171
|
-
const tabCenter = !isTrackListSidebarVisible ? { label: "Show song list", onClick: toggleTrackListSidebar } : isExtended ? null : {
|
|
2172
|
-
label: "Extend song list panel",
|
|
2173
|
-
onClick: () => setIsExtendRequested(true)
|
|
2777
|
+
const hideSongList = () => {
|
|
2778
|
+
setIsExtendRequested(false);
|
|
2779
|
+
if (isTrackListSidebarVisible) toggleTrackListSidebar();
|
|
2174
2780
|
};
|
|
2175
|
-
|
|
2781
|
+
const tabEdge = isTrackListSidebarVisible && isExtended ? {
|
|
2782
|
+
label: "Shrink player panel",
|
|
2783
|
+
onClick: () => setIsExtendRequested(false),
|
|
2784
|
+
expanded: true
|
|
2785
|
+
} : null;
|
|
2786
|
+
const tabCenter = isTrackListSidebarVisible && !isExtended ? {
|
|
2787
|
+
label: "Extend player panel",
|
|
2788
|
+
onClick: () => setIsExtendRequested(true),
|
|
2789
|
+
expanded: false
|
|
2790
|
+
} : null;
|
|
2791
|
+
return /* @__PURE__ */ jsxs23(
|
|
2176
2792
|
"div",
|
|
2177
2793
|
{
|
|
2178
2794
|
id: PANEL_ID,
|
|
@@ -2181,11 +2797,11 @@ function PlayerPanel({
|
|
|
2181
2797
|
!isExtended && "w-[var(--layout-right-w)]",
|
|
2182
2798
|
isTrackListSidebarVisible && "h-[calc(100vh-var(--layout-band-top)-3.5rem)]",
|
|
2183
2799
|
!isVisible && "hidden",
|
|
2184
|
-
|
|
2800
|
+
className2
|
|
2185
2801
|
),
|
|
2186
2802
|
style: isExtended ? { width: extendedWidth } : void 0,
|
|
2187
2803
|
children: [
|
|
2188
|
-
/* @__PURE__ */
|
|
2804
|
+
/* @__PURE__ */ jsx30(
|
|
2189
2805
|
PanelEdgeTab,
|
|
2190
2806
|
{
|
|
2191
2807
|
side: "right",
|
|
@@ -2195,7 +2811,7 @@ function PlayerPanel({
|
|
|
2195
2811
|
className: "absolute right-full top-1/2 -translate-y-1/2"
|
|
2196
2812
|
}
|
|
2197
2813
|
),
|
|
2198
|
-
/* @__PURE__ */
|
|
2814
|
+
/* @__PURE__ */ jsxs23(
|
|
2199
2815
|
"div",
|
|
2200
2816
|
{
|
|
2201
2817
|
className: twMerge2(
|
|
@@ -2203,29 +2819,21 @@ function PlayerPanel({
|
|
|
2203
2819
|
isExtended ? "flex-row" : "flex-col"
|
|
2204
2820
|
),
|
|
2205
2821
|
children: [
|
|
2206
|
-
/* @__PURE__ */
|
|
2207
|
-
/* @__PURE__ */
|
|
2208
|
-
|
|
2209
|
-
/* @__PURE__ */
|
|
2210
|
-
|
|
2211
|
-
{
|
|
2212
|
-
type: "button",
|
|
2213
|
-
onClick: onClose,
|
|
2214
|
-
className: iconButtonClassName,
|
|
2215
|
-
"aria-label": "Close player",
|
|
2216
|
-
children: /* @__PURE__ */ jsx25(X2, { size: 16 })
|
|
2217
|
-
}
|
|
2218
|
-
) })
|
|
2822
|
+
/* @__PURE__ */ jsxs23("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
|
|
2823
|
+
/* @__PURE__ */ jsx30("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ jsx30("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
|
|
2824
|
+
/* @__PURE__ */ jsxs23("div", { className: "relative w-full shrink-0", children: [
|
|
2825
|
+
/* @__PURE__ */ jsx30(PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
|
|
2826
|
+
playerTrackObject?.loadError && /* @__PURE__ */ jsx30("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: playerTrackObject.loadError })
|
|
2219
2827
|
] }),
|
|
2220
|
-
/* @__PURE__ */
|
|
2221
|
-
/* @__PURE__ */ jsx25(PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
|
|
2222
|
-
playerTrackObject?.loadError && /* @__PURE__ */ jsx25("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: playerTrackObject.loadError })
|
|
2223
|
-
] })
|
|
2828
|
+
/* @__PURE__ */ jsx30(PlayerControls, {})
|
|
2224
2829
|
] }),
|
|
2225
|
-
|
|
2830
|
+
/* @__PURE__ */ jsx30(
|
|
2226
2831
|
TrackListSidebar,
|
|
2227
2832
|
{
|
|
2228
2833
|
layout: "inline",
|
|
2834
|
+
isCollapsed: !isTrackListSidebarVisible,
|
|
2835
|
+
onExpand: toggleTrackListSidebar,
|
|
2836
|
+
onMinimize: hideSongList,
|
|
2229
2837
|
className: twMerge2(
|
|
2230
2838
|
"min-h-0 min-w-0 flex-1",
|
|
2231
2839
|
isExtended && "border-l border-zinc-800"
|
|
@@ -2245,11 +2853,11 @@ function PlayerPanel({
|
|
|
2245
2853
|
import { v4 as uuidv4 } from "uuid";
|
|
2246
2854
|
import { AiFillStar, AiOutlineStar } from "react-icons/ai";
|
|
2247
2855
|
import { FORM_RATING_NULL_VALUE } from "@behindthemusictree/app-kit/genre-tree";
|
|
2248
|
-
import { jsx as
|
|
2856
|
+
import { jsx as jsx31, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
2249
2857
|
function Rating({ rating, handleChange }) {
|
|
2250
2858
|
const instanceId = uuidv4();
|
|
2251
|
-
return /* @__PURE__ */
|
|
2252
|
-
/* @__PURE__ */
|
|
2859
|
+
return /* @__PURE__ */ jsx31("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ jsxs24("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
|
|
2860
|
+
/* @__PURE__ */ jsx31(
|
|
2253
2861
|
"input",
|
|
2254
2862
|
{
|
|
2255
2863
|
id: `star-${instanceId}-${i}`,
|
|
@@ -2261,16 +2869,16 @@ function Rating({ rating, handleChange }) {
|
|
|
2261
2869
|
onChange: handleChange
|
|
2262
2870
|
}
|
|
2263
2871
|
),
|
|
2264
|
-
/* @__PURE__ */
|
|
2265
|
-
/* @__PURE__ */
|
|
2872
|
+
/* @__PURE__ */ jsx31("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ jsxs24("div", { className: "flex", children: [
|
|
2873
|
+
/* @__PURE__ */ jsx31(
|
|
2266
2874
|
AiOutlineStar,
|
|
2267
2875
|
{
|
|
2268
2876
|
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)]"}`
|
|
2269
2877
|
}
|
|
2270
2878
|
),
|
|
2271
|
-
/* @__PURE__ */
|
|
2879
|
+
/* @__PURE__ */ jsx31("span", { className: "flex-grow w-8" }),
|
|
2272
2880
|
" "
|
|
2273
|
-
] }) : /* @__PURE__ */
|
|
2881
|
+
] }) : /* @__PURE__ */ jsx31(
|
|
2274
2882
|
AiFillStar,
|
|
2275
2883
|
{
|
|
2276
2884
|
className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
|
|
@@ -2294,4 +2902,4 @@ export {
|
|
|
2294
2902
|
GenreTreeViewSkeleton2 as GenreTreeViewSkeleton,
|
|
2295
2903
|
GenreTreeOutlineSkeleton
|
|
2296
2904
|
};
|
|
2297
|
-
//# sourceMappingURL=chunk-
|
|
2905
|
+
//# sourceMappingURL=chunk-5PQWJRDZ.js.map
|