@behindthemusictree/app-kit-ui 26.1.0 → 28.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-KPWV4LEJ.js → chunk-5PQWJRDZ.js} +1061 -541
- package/dist/chunk-5PQWJRDZ.js.map +1 -0
- package/dist/chunk-DKQQLLMY.js +246 -0
- package/dist/chunk-DKQQLLMY.js.map +1 -0
- package/dist/{chunk-ZLCRD7OK.js → chunk-XUC4UJDQ.js} +2 -2
- package/dist/genre-tree/index.cjs +1260 -635
- package/dist/genre-tree/index.cjs.map +1 -1
- package/dist/genre-tree/index.d.ts +35 -8
- package/dist/genre-tree/index.js +2 -2
- package/dist/genre-tree/styles.css +80 -0
- package/dist/index.cjs +1286 -661
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +6 -4
- package/dist/popup/index.cjs +179 -73
- package/dist/popup/index.cjs.map +1 -1
- package/dist/popup/index.d.ts +22 -2
- package/dist/popup/index.js +5 -3
- package/package.json +7 -7
- package/dist/chunk-KPWV4LEJ.js.map +0 -1
- package/dist/chunk-VDXUW7PP.js +0 -141
- package/dist/chunk-VDXUW7PP.js.map +0 -1
- /package/dist/{chunk-ZLCRD7OK.js.map → chunk-XUC4UJDQ.js.map} +0 -0
|
@@ -1,10 +1,11 @@
|
|
|
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";
|
|
8
9
|
import { useIsMutating } from "@tanstack/react-query";
|
|
9
10
|
import { Plus as Plus2 } from "lucide-react";
|
|
10
11
|
import { IconTextButton, Button, SegmentedControl as SegmentedControl2 } from "@behindthemusictree/ui";
|
|
@@ -15,9 +16,9 @@ import {
|
|
|
15
16
|
import {
|
|
16
17
|
useArtistSearch
|
|
17
18
|
} from "@behindthemusictree/app-kit/genre-tree";
|
|
18
|
-
import { useGenreTreeViewState, useArtist as useArtist2, genreUpdateMutationKey } from "@behindthemusictree/app-kit/genre-tree";
|
|
19
|
+
import { useGenreTreeViewState, useArtist as useArtist2, useUpdateGenre as useUpdateGenre2, genreUpdateMutationKey } from "@behindthemusictree/app-kit/genre-tree";
|
|
19
20
|
import { usePlayer as usePlayer8 } from "@behindthemusictree/app-kit/player";
|
|
20
|
-
import { usePopup as
|
|
21
|
+
import { usePopup as usePopup5 } from "@behindthemusictree/app-kit/popup";
|
|
21
22
|
|
|
22
23
|
// src/genre-tree/GenreDetailExtras.tsx
|
|
23
24
|
import { Skeleton as Skeleton2 } from "@behindthemusictree/ui";
|
|
@@ -26,7 +27,7 @@ import { useFetchGenreOverview } from "@behindthemusictree/app-kit/genre-tree";
|
|
|
26
27
|
// src/genre-tree/ClampedText.tsx
|
|
27
28
|
import { useLayoutEffect, useRef, useState } from "react";
|
|
28
29
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
29
|
-
function ClampedText({ children, className = "" }) {
|
|
30
|
+
function ClampedText({ children, className: className2 = "" }) {
|
|
30
31
|
const ref = useRef(null);
|
|
31
32
|
const [expanded, setExpanded] = useState(false);
|
|
32
33
|
const [overflows, setOverflows] = useState(false);
|
|
@@ -36,7 +37,7 @@ function ClampedText({ children, className = "" }) {
|
|
|
36
37
|
setOverflows(element.scrollHeight > element.clientHeight);
|
|
37
38
|
}, [children, expanded]);
|
|
38
39
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
39
|
-
/* @__PURE__ */ jsx("p", { ref, className: `${
|
|
40
|
+
/* @__PURE__ */ jsx("p", { ref, className: `${className2} ${expanded ? "" : "line-clamp-3"}`, children }),
|
|
40
41
|
(overflows || expanded) && /* @__PURE__ */ jsx(
|
|
41
42
|
"button",
|
|
42
43
|
{
|
|
@@ -63,20 +64,6 @@ import { parseWithLog } from "@behindthemusictree/app-kit/transport";
|
|
|
63
64
|
import { usePlayer } from "@behindthemusictree/app-kit/player";
|
|
64
65
|
import { PlayStates } from "@behindthemusictree/app-kit/player";
|
|
65
66
|
import { usePopup } from "@behindthemusictree/app-kit/popup";
|
|
66
|
-
|
|
67
|
-
// src/genre-tree/UnplayableReason.tsx
|
|
68
|
-
import { FaYoutube } from "react-icons/fa";
|
|
69
|
-
import { NO_VIDEO_LABEL } from "@behindthemusictree/app-kit/genre-tree";
|
|
70
|
-
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
71
|
-
function UnplayableReason({ reason }) {
|
|
72
|
-
if (reason !== NO_VIDEO_LABEL) return /* @__PURE__ */ jsx2(Fragment2, { children: reason });
|
|
73
|
-
return /* @__PURE__ */ jsxs2("span", { role: "img", "aria-label": reason, className: "relative inline-flex shrink-0 items-center", children: [
|
|
74
|
-
/* @__PURE__ */ jsx2(FaYoutube, { size: 16, "aria-hidden": "true" }),
|
|
75
|
-
/* @__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" })
|
|
76
|
-
] });
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// src/genre-tree/GenrePlaylistTracks.tsx
|
|
80
67
|
import { ErrorCode } from "@behindthemusictree/app-kit/transport";
|
|
81
68
|
import { genrePlaylistQueryKeys } from "@behindthemusictree/app-kit/genre-tree";
|
|
82
69
|
import { libraryEndpoints, libraryQueryKeys } from "@behindthemusictree/app-kit/genre-tree";
|
|
@@ -84,23 +71,25 @@ import { isSong, songUnplayableReason, SongDetailedSchema } from "@behindthemusi
|
|
|
84
71
|
import { useTrackList } from "@behindthemusictree/app-kit/genre-tree";
|
|
85
72
|
|
|
86
73
|
// src/genre-tree/MusicBrainzRecordingLink.tsx
|
|
87
|
-
import { FaExternalLinkAlt } from "react-icons/fa";
|
|
88
74
|
import { musicBrainzRecordingUrl } from "@behindthemusictree/app-kit/genre-tree";
|
|
89
|
-
import { jsx as
|
|
90
|
-
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
|
+
}) {
|
|
91
81
|
const url = musicBrainzRecordingUrl(song);
|
|
92
|
-
if (!url) return
|
|
93
|
-
return /* @__PURE__ */
|
|
82
|
+
if (!url) return /* @__PURE__ */ jsx2(Fragment2, { children });
|
|
83
|
+
return /* @__PURE__ */ jsx2(
|
|
94
84
|
"a",
|
|
95
85
|
{
|
|
96
86
|
href: url,
|
|
97
87
|
target: "_blank",
|
|
98
88
|
rel: "noopener noreferrer",
|
|
99
89
|
"aria-label": `View ${song.title} on MusicBrainz`,
|
|
100
|
-
|
|
101
|
-
className: "inline-flex shrink-0 items-center opacity-50 hover:opacity-100 focus-visible:opacity-100",
|
|
90
|
+
className: className2,
|
|
102
91
|
onClick: (event) => event.stopPropagation(),
|
|
103
|
-
children
|
|
92
|
+
children
|
|
104
93
|
}
|
|
105
94
|
);
|
|
106
95
|
}
|
|
@@ -108,7 +97,7 @@ function MusicBrainzRecordingLink({ song }) {
|
|
|
108
97
|
// src/genre-tree/GenrePlaylistTracks.tsx
|
|
109
98
|
import { ArtistNames } from "@behindthemusictree/app-kit/genre-tree";
|
|
110
99
|
import { capitalize, useTrackNoun } from "@behindthemusictree/app-kit/track-noun";
|
|
111
|
-
import { jsx as
|
|
100
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
112
101
|
var CATALOGUE_PAGE_SIZE = 100;
|
|
113
102
|
var SongPageSchema = PaginatedResponseSchema(SongDetailedSchema).extend({
|
|
114
103
|
overallTotal: z.number().nullable(),
|
|
@@ -180,90 +169,89 @@ function GenrePlaylistTracks({
|
|
|
180
169
|
const started = artist ? playNewTrackListFromArtist(artist, scope, { startSongUuid, ordering: "title", genreUuid }) : playNewTrackListFromGenrePlaylist(genrePlaylist, scope, { startSongUuid });
|
|
181
170
|
started.catch((error) => {
|
|
182
171
|
console.error("Failed to play genre playlist:", error);
|
|
183
|
-
showPopup(/* @__PURE__ */
|
|
172
|
+
showPopup(/* @__PURE__ */ jsx3(InternalErrorPopup, { errorCode: ErrorCode.CLIENT_UNKNOWN }));
|
|
184
173
|
});
|
|
185
174
|
};
|
|
186
|
-
return /* @__PURE__ */
|
|
187
|
-
/* @__PURE__ */
|
|
175
|
+
return /* @__PURE__ */ jsxs2("div", { className: "gtv-info-panel-children", children: [
|
|
176
|
+
/* @__PURE__ */ jsxs2("span", { className: "gtv-info-panel-children-title", children: [
|
|
188
177
|
capitalize(trackNoun.plural),
|
|
189
178
|
total !== void 0 && ` (${total})`
|
|
190
179
|
] }),
|
|
191
180
|
renderTracksToolbar?.(),
|
|
192
|
-
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: [
|
|
193
182
|
"Couldn't load ",
|
|
194
183
|
trackNoun.plural,
|
|
195
184
|
".",
|
|
196
185
|
" ",
|
|
197
|
-
/* @__PURE__ */
|
|
198
|
-
] }) : 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: [
|
|
199
188
|
tracks.map((track, index) => {
|
|
200
189
|
const label = track.artists?.length ? `${track.title} \u2014 ${track.artists.map((artist2) => artist2.name).join(", ")}` : track.title;
|
|
201
190
|
const isCurrent = playerTrackObject?.track.id === track.uuid;
|
|
202
191
|
const isPlaying = isCurrent && playState === PlayStates.PLAYING;
|
|
203
|
-
const text = /* @__PURE__ */
|
|
204
|
-
/* @__PURE__ */
|
|
205
|
-
track.artists?.length ? /* @__PURE__ */
|
|
206
|
-
/* @__PURE__ */
|
|
207
|
-
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 })
|
|
208
197
|
] });
|
|
209
198
|
const leading = renderSongLeading?.(track, index);
|
|
210
199
|
const actions = renderSongActions?.(track);
|
|
211
|
-
const
|
|
212
|
-
if (
|
|
213
|
-
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: [
|
|
214
203
|
leading,
|
|
215
|
-
/* @__PURE__ */
|
|
204
|
+
/* @__PURE__ */ jsx3(
|
|
216
205
|
"button",
|
|
217
206
|
{
|
|
218
207
|
type: "button",
|
|
219
208
|
disabled: true,
|
|
220
209
|
"aria-disabled": "true",
|
|
221
|
-
"aria-label":
|
|
210
|
+
"aria-label": label,
|
|
222
211
|
className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full opacity-0 cursor-not-allowed",
|
|
223
|
-
children: /* @__PURE__ */
|
|
212
|
+
children: /* @__PURE__ */ jsx3(FaPlay, { size: 10 })
|
|
224
213
|
}
|
|
225
214
|
),
|
|
226
215
|
text,
|
|
227
|
-
isSong(track) && /* @__PURE__ */ jsx4(MusicBrainzRecordingLink, { song: track }),
|
|
228
|
-
/* @__PURE__ */ jsx4("span", { className: "shrink-0 text-xs", children: /* @__PURE__ */ jsx4(UnplayableReason, { reason: unplayableReason }) }),
|
|
229
216
|
actions
|
|
230
217
|
] }, track.uuid);
|
|
231
218
|
}
|
|
232
|
-
return /* @__PURE__ */
|
|
219
|
+
return /* @__PURE__ */ jsxs2(
|
|
233
220
|
"li",
|
|
234
221
|
{
|
|
235
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)]" : ""}`,
|
|
236
223
|
children: [
|
|
237
224
|
leading,
|
|
238
|
-
/* @__PURE__ */
|
|
225
|
+
/* @__PURE__ */ jsx3(
|
|
239
226
|
"button",
|
|
240
227
|
{
|
|
241
228
|
type: "button",
|
|
242
229
|
"aria-label": `${isPlaying ? "Pause" : "Play"} ${label}`,
|
|
243
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"}`,
|
|
244
231
|
onClick: () => isCurrent ? handlePlayPauseAction() : play(track.uuid),
|
|
245
|
-
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 })
|
|
246
233
|
}
|
|
247
234
|
),
|
|
248
235
|
text,
|
|
249
|
-
isSong(track) && /* @__PURE__ */ jsx4(MusicBrainzRecordingLink, { song: track }),
|
|
250
236
|
actions
|
|
251
237
|
]
|
|
252
238
|
},
|
|
253
239
|
track.uuid
|
|
254
240
|
);
|
|
255
241
|
}),
|
|
256
|
-
hasNextPage && /* @__PURE__ */
|
|
242
|
+
hasNextPage && /* @__PURE__ */ jsx3("li", { ref: sentinelRef, "aria-hidden": "true", className: "h-4" })
|
|
257
243
|
] })
|
|
258
244
|
] });
|
|
259
245
|
}
|
|
260
246
|
|
|
261
247
|
// src/genre-tree/GenreDetailExtras.tsx
|
|
262
248
|
import { useTrackNoun as useTrackNoun2 } from "@behindthemusictree/app-kit/track-noun";
|
|
263
|
-
import {
|
|
249
|
+
import { SiWikidata, SiWikipedia } from "react-icons/si";
|
|
250
|
+
import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
264
251
|
function GenreDetailExtras({
|
|
265
252
|
genreUuid,
|
|
266
253
|
genrePlaylist,
|
|
254
|
+
wikidataId,
|
|
267
255
|
scope,
|
|
268
256
|
getBackendBaseUrl,
|
|
269
257
|
criteriaOverviewSchema,
|
|
@@ -282,52 +270,63 @@ function GenreDetailExtras({
|
|
|
282
270
|
criteriaOverviewSchema
|
|
283
271
|
);
|
|
284
272
|
if (isPending) {
|
|
285
|
-
return /* @__PURE__ */
|
|
286
|
-
/* @__PURE__ */
|
|
287
|
-
/* @__PURE__ */
|
|
288
|
-
/* @__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" })
|
|
289
277
|
] }),
|
|
290
278
|
family
|
|
291
279
|
] });
|
|
292
280
|
}
|
|
293
|
-
if (!overview) return /* @__PURE__ */
|
|
294
|
-
const { summary,
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
/* @__PURE__ */
|
|
299
|
-
/* @__PURE__ */
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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)
|
|
310
319
|
] }),
|
|
311
|
-
renderSummary && summarySourceUrl && /* @__PURE__ */ jsx5(
|
|
312
|
-
"a",
|
|
313
|
-
{
|
|
314
|
-
href: summarySourceUrl,
|
|
315
|
-
target: "_blank",
|
|
316
|
-
rel: "noopener noreferrer",
|
|
317
|
-
className: "mx-3.5 mb-2.5 inline-block text-xs text-[var(--color-text-muted)] underline hover:text-[var(--color-text)]",
|
|
318
|
-
children: "Source: Wikipedia"
|
|
319
|
-
}
|
|
320
|
-
),
|
|
321
320
|
family,
|
|
322
321
|
renderGenreDetailExtras?.(overview),
|
|
323
|
-
renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && /* @__PURE__ */
|
|
324
|
-
/* @__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: [
|
|
325
324
|
"Essential ",
|
|
326
325
|
trackNoun.plural
|
|
327
326
|
] }),
|
|
328
|
-
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" })
|
|
329
328
|
] }),
|
|
330
|
-
/* @__PURE__ */
|
|
329
|
+
/* @__PURE__ */ jsx4(
|
|
331
330
|
GenrePlaylistTracks,
|
|
332
331
|
{
|
|
333
332
|
genreUuid,
|
|
@@ -384,10 +383,10 @@ function useGenrePlaylistPlayback({
|
|
|
384
383
|
|
|
385
384
|
// src/genre-tree/playlist-tree/TreePerRoot.tsx
|
|
386
385
|
import { capitalize as capitalize2, useTrackNoun as useTrackNoun3 } from "@behindthemusictree/app-kit/track-noun";
|
|
387
|
-
import { jsx as
|
|
386
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
388
387
|
function GenrePlaylistTreePerRoot({
|
|
389
388
|
scope,
|
|
390
|
-
className,
|
|
389
|
+
className: className2,
|
|
391
390
|
rootUuid,
|
|
392
391
|
genrePlaylistTreePerRoot,
|
|
393
392
|
handleGenreCreationAction,
|
|
@@ -440,7 +439,7 @@ function GenrePlaylistTreePerRoot({
|
|
|
440
439
|
}
|
|
441
440
|
play(genrePlaylist).catch((error) => {
|
|
442
441
|
console.error("Failed to load genre playlist tracks:", error);
|
|
443
|
-
showPopup(/* @__PURE__ */
|
|
442
|
+
showPopup(/* @__PURE__ */ jsx5(InternalErrorPopup, { errorCode: ErrorCode2.CLIENT_UNKNOWN }));
|
|
444
443
|
});
|
|
445
444
|
},
|
|
446
445
|
[
|
|
@@ -472,10 +471,10 @@ function GenrePlaylistTreePerRoot({
|
|
|
472
471
|
if (confirm(`Are you sure you want to delete "${node.name}"?`)) {
|
|
473
472
|
}
|
|
474
473
|
}, []);
|
|
475
|
-
return /* @__PURE__ */
|
|
474
|
+
return /* @__PURE__ */ jsx5(
|
|
476
475
|
GenreTree,
|
|
477
476
|
{
|
|
478
|
-
className,
|
|
477
|
+
className: className2,
|
|
479
478
|
nodes,
|
|
480
479
|
itemCountLabel: `${capitalize2(trackNoun.singular)} count`,
|
|
481
480
|
rootColor: getGenreTreeColor(rootUuid),
|
|
@@ -514,14 +513,21 @@ import { usePopup as usePopup3 } from "@behindthemusictree/app-kit/popup";
|
|
|
514
513
|
import { ErrorCode as ErrorCode3 } from "@behindthemusictree/app-kit/transport";
|
|
515
514
|
|
|
516
515
|
// src/genre-tree/playlist-tree/size-metric.ts
|
|
517
|
-
var
|
|
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
|
+
};
|
|
518
524
|
|
|
519
525
|
// src/genre-tree/playlist-tree/TreeWheel.tsx
|
|
520
526
|
import { capitalize as capitalize3, useTrackNoun as useTrackNoun4 } from "@behindthemusictree/app-kit/track-noun";
|
|
521
|
-
import { jsx as
|
|
527
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
522
528
|
function GenrePlaylistTreeWheel({
|
|
523
529
|
scope,
|
|
524
|
-
className,
|
|
530
|
+
className: className2,
|
|
525
531
|
genrePlaylists,
|
|
526
532
|
handleGenreCreationAction,
|
|
527
533
|
handleGenreRenameAction,
|
|
@@ -575,7 +581,7 @@ function GenrePlaylistTreeWheel({
|
|
|
575
581
|
}
|
|
576
582
|
play(genrePlaylist).catch((error) => {
|
|
577
583
|
console.error("Failed to load genre playlist tracks:", error);
|
|
578
|
-
showPopup(/* @__PURE__ */
|
|
584
|
+
showPopup(/* @__PURE__ */ jsx6(InternalErrorPopup, { errorCode: ErrorCode3.CLIENT_UNKNOWN }));
|
|
579
585
|
});
|
|
580
586
|
},
|
|
581
587
|
[
|
|
@@ -607,10 +613,10 @@ function GenrePlaylistTreeWheel({
|
|
|
607
613
|
if (confirm(`Are you sure you want to delete "${node.name}"?`)) {
|
|
608
614
|
}
|
|
609
615
|
}, []);
|
|
610
|
-
return /* @__PURE__ */
|
|
616
|
+
return /* @__PURE__ */ jsx6(
|
|
611
617
|
GenreTreeWheel,
|
|
612
618
|
{
|
|
613
|
-
className,
|
|
619
|
+
className: className2,
|
|
614
620
|
nodes,
|
|
615
621
|
itemCountLabel: sizeMetric === "views" ? "Views" : `${capitalize3(trackNoun.singular)} count`,
|
|
616
622
|
playingNodeId,
|
|
@@ -648,11 +654,11 @@ import {
|
|
|
648
654
|
} from "@behindthemusictree/genre-tree-view";
|
|
649
655
|
import { usePlayer as usePlayer4 } from "@behindthemusictree/app-kit/player";
|
|
650
656
|
import { capitalize as capitalize4, useTrackNoun as useTrackNoun5 } from "@behindthemusictree/app-kit/track-noun";
|
|
651
|
-
import { jsx as
|
|
657
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
652
658
|
function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
653
659
|
const {
|
|
654
660
|
scope,
|
|
655
|
-
className,
|
|
661
|
+
className: className2,
|
|
656
662
|
genrePlaylists,
|
|
657
663
|
handleGenreCreationAction,
|
|
658
664
|
handleGenreRenameAction,
|
|
@@ -742,7 +748,7 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
742
748
|
}
|
|
743
749
|
}, []);
|
|
744
750
|
const treeProps = {
|
|
745
|
-
className,
|
|
751
|
+
className: className2,
|
|
746
752
|
nodes,
|
|
747
753
|
itemCountLabel: sizeMetric === "views" ? "Views" : `${capitalize4(trackNoun.singular)} count`,
|
|
748
754
|
playingNodeId,
|
|
@@ -763,7 +769,7 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
763
769
|
...infoPanelCuration,
|
|
764
770
|
readOnly
|
|
765
771
|
};
|
|
766
|
-
return outline ? /* @__PURE__ */
|
|
772
|
+
return outline ? /* @__PURE__ */ jsx7(GenreTreeOutline, { ...treeProps }) : /* @__PURE__ */ jsx7(
|
|
767
773
|
GenreTreeWheelRadialPopCore,
|
|
768
774
|
{
|
|
769
775
|
...treeProps,
|
|
@@ -780,7 +786,7 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
780
786
|
|
|
781
787
|
// src/genre-tree/GenreTreeWheelHandoff.tsx
|
|
782
788
|
import { useEffect as useEffect2, useRef as useRef3, useState as useState2 } from "react";
|
|
783
|
-
import { jsx as
|
|
789
|
+
import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
784
790
|
function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
|
|
785
791
|
const [revealed, setRevealed] = useState2(false);
|
|
786
792
|
const onRevealedChangeRef = useRef3(onRevealedChange);
|
|
@@ -799,8 +805,8 @@ function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
|
|
|
799
805
|
cancelAnimationFrame(innerFrame);
|
|
800
806
|
};
|
|
801
807
|
}, []);
|
|
802
|
-
return /* @__PURE__ */
|
|
803
|
-
/* @__PURE__ */
|
|
808
|
+
return /* @__PURE__ */ jsxs4("div", { className: "relative w-full h-full", children: [
|
|
809
|
+
/* @__PURE__ */ jsx8(
|
|
804
810
|
"div",
|
|
805
811
|
{
|
|
806
812
|
className: "absolute inset-0 transition-opacity duration-150 ease-out",
|
|
@@ -809,7 +815,7 @@ function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
|
|
|
809
815
|
children
|
|
810
816
|
}
|
|
811
817
|
),
|
|
812
|
-
!revealed && /* @__PURE__ */
|
|
818
|
+
!revealed && /* @__PURE__ */ jsx8("div", { className: "absolute inset-0", "aria-hidden": "true", children: skeleton })
|
|
813
819
|
] });
|
|
814
820
|
}
|
|
815
821
|
|
|
@@ -817,7 +823,7 @@ function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
|
|
|
817
823
|
import { ArrowLeft, LoaderCircle, Search, X } from "lucide-react";
|
|
818
824
|
import { Input } from "@behindthemusictree/ui";
|
|
819
825
|
import { useGenreSearch } from "@behindthemusictree/app-kit/genre-tree";
|
|
820
|
-
import { jsx as
|
|
826
|
+
import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
821
827
|
function GenreSearch({
|
|
822
828
|
genrePlaylists,
|
|
823
829
|
onSelect,
|
|
@@ -839,19 +845,19 @@ function GenreSearch({
|
|
|
839
845
|
const loading = query !== "" && query !== selectedName && (extraResults?.loading ?? false);
|
|
840
846
|
const showList = results.length > 0 || extraItems.length > 0;
|
|
841
847
|
const heading = "px-3 pt-2 pb-1 text-xs font-medium uppercase text-[var(--color-text-muted)]";
|
|
842
|
-
return /* @__PURE__ */
|
|
843
|
-
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(
|
|
844
850
|
"button",
|
|
845
851
|
{
|
|
846
852
|
type: "button",
|
|
847
853
|
"aria-label": "Previous",
|
|
848
854
|
title: "Previous",
|
|
849
|
-
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)]",
|
|
850
856
|
onClick: onPrevious,
|
|
851
|
-
children: /* @__PURE__ */
|
|
857
|
+
children: /* @__PURE__ */ jsx9(ArrowLeft, { "aria-hidden": "true", className: "w-4 h-4" })
|
|
852
858
|
}
|
|
853
859
|
),
|
|
854
|
-
/* @__PURE__ */
|
|
860
|
+
/* @__PURE__ */ jsx9(
|
|
855
861
|
Input,
|
|
856
862
|
{
|
|
857
863
|
type: "text",
|
|
@@ -863,24 +869,27 @@ function GenreSearch({
|
|
|
863
869
|
placeholder: placeholder ?? `${label}\u2026`,
|
|
864
870
|
"aria-label": label,
|
|
865
871
|
"aria-busy": loading,
|
|
872
|
+
inputMode: "search",
|
|
873
|
+
enterKeyHint: "search",
|
|
874
|
+
autoComplete: "off",
|
|
866
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"
|
|
867
876
|
}
|
|
868
877
|
),
|
|
869
|
-
/* @__PURE__ */
|
|
870
|
-
/* @__PURE__ */
|
|
878
|
+
/* @__PURE__ */ jsxs5("div", { className: "absolute right-3 top-1/2 flex items-center -translate-y-1/2", children: [
|
|
879
|
+
/* @__PURE__ */ jsxs5(
|
|
871
880
|
"span",
|
|
872
881
|
{
|
|
873
882
|
"data-loading": loading,
|
|
874
883
|
className: "relative p-2 text-[var(--color-text-muted)] pointer-events-none",
|
|
875
884
|
children: [
|
|
876
|
-
/* @__PURE__ */
|
|
885
|
+
/* @__PURE__ */ jsx9(
|
|
877
886
|
Search,
|
|
878
887
|
{
|
|
879
888
|
"aria-hidden": "true",
|
|
880
889
|
className: `w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? "opacity-0" : ""}`
|
|
881
890
|
}
|
|
882
891
|
),
|
|
883
|
-
/* @__PURE__ */
|
|
892
|
+
/* @__PURE__ */ jsx9(
|
|
884
893
|
LoaderCircle,
|
|
885
894
|
{
|
|
886
895
|
"aria-hidden": "true",
|
|
@@ -890,44 +899,44 @@ function GenreSearch({
|
|
|
890
899
|
]
|
|
891
900
|
}
|
|
892
901
|
),
|
|
893
|
-
canClear && /* @__PURE__ */
|
|
902
|
+
canClear && /* @__PURE__ */ jsx9(
|
|
894
903
|
"button",
|
|
895
904
|
{
|
|
896
905
|
type: "button",
|
|
897
906
|
"aria-label": "Clear search",
|
|
898
|
-
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)]",
|
|
899
908
|
onClick: () => {
|
|
900
909
|
clear();
|
|
901
910
|
onQueryChange?.("");
|
|
902
911
|
},
|
|
903
|
-
children: /* @__PURE__ */
|
|
912
|
+
children: /* @__PURE__ */ jsx9(X, { "aria-hidden": "true", className: "w-6 h-6" })
|
|
904
913
|
}
|
|
905
914
|
)
|
|
906
915
|
] }),
|
|
907
|
-
extraResults?.status && /* @__PURE__ */
|
|
908
|
-
showList && /* @__PURE__ */
|
|
909
|
-
extraResults && results.length > 0 && /* @__PURE__ */
|
|
910
|
-
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(
|
|
911
920
|
"button",
|
|
912
921
|
{
|
|
913
922
|
type: "button",
|
|
914
|
-
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)]",
|
|
915
924
|
onClick: () => {
|
|
916
925
|
select(genrePlaylist);
|
|
917
926
|
onQueryChange?.("");
|
|
918
927
|
},
|
|
919
928
|
children: [
|
|
920
929
|
genrePlaylist.name,
|
|
921
|
-
genrePlaylist.isUmbrella && /* @__PURE__ */
|
|
930
|
+
genrePlaylist.isUmbrella && /* @__PURE__ */ jsx9("span", { className: "ml-2 text-xs italic text-[var(--color-text-muted)]", children: "umbrella" })
|
|
922
931
|
]
|
|
923
932
|
}
|
|
924
933
|
) }, genrePlaylist.uuid)),
|
|
925
|
-
extraResults && extraItems.length > 0 && /* @__PURE__ */
|
|
926
|
-
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(
|
|
927
936
|
"button",
|
|
928
937
|
{
|
|
929
938
|
type: "button",
|
|
930
|
-
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)]",
|
|
931
940
|
onClick: () => {
|
|
932
941
|
setQuery("");
|
|
933
942
|
onQueryChange?.("");
|
|
@@ -941,8 +950,227 @@ function GenreSearch({
|
|
|
941
950
|
}
|
|
942
951
|
|
|
943
952
|
// src/genre-tree/GenreActionBand.tsx
|
|
944
|
-
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";
|
|
945
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";
|
|
946
1174
|
function GenreActionBand({
|
|
947
1175
|
node,
|
|
948
1176
|
onPlayPause,
|
|
@@ -951,7 +1179,12 @@ function GenreActionBand({
|
|
|
951
1179
|
onAddChild,
|
|
952
1180
|
onRename,
|
|
953
1181
|
onChangeParent,
|
|
954
|
-
|
|
1182
|
+
side,
|
|
1183
|
+
onToggleSide,
|
|
1184
|
+
toggleSideDisabledReason,
|
|
1185
|
+
additionalActions,
|
|
1186
|
+
shareTarget,
|
|
1187
|
+
shareUrl
|
|
955
1188
|
}) {
|
|
956
1189
|
const items = [];
|
|
957
1190
|
const adminItems = [];
|
|
@@ -959,7 +1192,7 @@ function GenreActionBand({
|
|
|
959
1192
|
items.push({
|
|
960
1193
|
key: "play-pause",
|
|
961
1194
|
label: isPlaying ? "Pause" : "Play",
|
|
962
|
-
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" }),
|
|
963
1196
|
onClick: onPlayPause
|
|
964
1197
|
});
|
|
965
1198
|
}
|
|
@@ -967,18 +1200,28 @@ function GenreActionBand({
|
|
|
967
1200
|
items.push({
|
|
968
1201
|
key: "zoom-in",
|
|
969
1202
|
label: "Focus",
|
|
970
|
-
icon: /* @__PURE__ */
|
|
1203
|
+
icon: /* @__PURE__ */ jsx14(ZoomIn, { "aria-hidden": "true", className: "w-5 h-5" }),
|
|
971
1204
|
onClick: onZoomIn
|
|
972
1205
|
});
|
|
973
1206
|
}
|
|
974
1207
|
if (onAddChild) {
|
|
975
|
-
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 });
|
|
976
1209
|
}
|
|
977
1210
|
if (onRename) {
|
|
978
|
-
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 });
|
|
979
1212
|
}
|
|
980
1213
|
if (onChangeParent) {
|
|
981
|
-
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
|
+
});
|
|
982
1225
|
}
|
|
983
1226
|
for (const action of additionalActions?.(node) ?? []) {
|
|
984
1227
|
items.push({
|
|
@@ -989,31 +1232,35 @@ function GenreActionBand({
|
|
|
989
1232
|
disabled: action.enabled ? !action.enabled(node) : false
|
|
990
1233
|
});
|
|
991
1234
|
}
|
|
992
|
-
if (items.length === 0 && adminItems.length === 0) return null;
|
|
993
|
-
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(
|
|
994
1237
|
"button",
|
|
995
1238
|
{
|
|
996
1239
|
type: "button",
|
|
997
1240
|
disabled: item.disabled,
|
|
1241
|
+
title: item.title,
|
|
998
1242
|
onClick: item.onClick,
|
|
999
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",
|
|
1000
1244
|
children: [
|
|
1001
|
-
/* @__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 }),
|
|
1002
1246
|
item.label
|
|
1003
1247
|
]
|
|
1004
1248
|
},
|
|
1005
1249
|
item.key
|
|
1006
1250
|
));
|
|
1007
|
-
return /* @__PURE__ */
|
|
1008
|
-
items.length > 0 && /* @__PURE__ */
|
|
1009
|
-
|
|
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(
|
|
1010
1257
|
"section",
|
|
1011
1258
|
{
|
|
1012
1259
|
"aria-label": "Admin actions",
|
|
1013
1260
|
className: `px-3.5 py-3 ${items.length > 0 ? "border-t border-[var(--color-border)]" : ""}`,
|
|
1014
1261
|
children: [
|
|
1015
|
-
/* @__PURE__ */
|
|
1016
|
-
/* @__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) })
|
|
1017
1264
|
]
|
|
1018
1265
|
}
|
|
1019
1266
|
)
|
|
@@ -1023,14 +1270,14 @@ function GenreActionBand({
|
|
|
1023
1270
|
// src/genre-tree/PanelEdgeTab.tsx
|
|
1024
1271
|
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
1025
1272
|
import { twMerge } from "tailwind-merge";
|
|
1026
|
-
import { jsx as
|
|
1273
|
+
import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1027
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)]";
|
|
1028
|
-
function PanelEdgeTab({ side, edge, center, controls, className }) {
|
|
1275
|
+
function PanelEdgeTab({ side, edge, center, controls, className: className2 }) {
|
|
1029
1276
|
if (!edge && !center) return null;
|
|
1030
1277
|
const isLeft = side === "left";
|
|
1031
1278
|
const EdgeIcon = isLeft ? ChevronLeft : ChevronRight;
|
|
1032
1279
|
const CenterIcon = isLeft ? ChevronRight : ChevronLeft;
|
|
1033
|
-
const renderButton = (action, icon) => /* @__PURE__ */
|
|
1280
|
+
const renderButton = (action, icon) => /* @__PURE__ */ jsx15(
|
|
1034
1281
|
"button",
|
|
1035
1282
|
{
|
|
1036
1283
|
type: "button",
|
|
@@ -1043,20 +1290,20 @@ function PanelEdgeTab({ side, edge, center, controls, className }) {
|
|
|
1043
1290
|
children: icon
|
|
1044
1291
|
}
|
|
1045
1292
|
);
|
|
1046
|
-
const edgeButton = edge && renderButton(edge, /* @__PURE__ */
|
|
1047
|
-
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" }));
|
|
1048
1295
|
const buttons = isLeft ? [edgeButton, centerButton] : [centerButton, edgeButton];
|
|
1049
|
-
return /* @__PURE__ */
|
|
1296
|
+
return /* @__PURE__ */ jsxs11(
|
|
1050
1297
|
"div",
|
|
1051
1298
|
{
|
|
1052
1299
|
className: twMerge(
|
|
1053
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)]",
|
|
1054
1301
|
isLeft ? "rounded-r-lg" : "rounded-l-lg [clip-path:inset(-0.5rem_0_-0.5rem_-0.5rem)]",
|
|
1055
|
-
|
|
1302
|
+
className2
|
|
1056
1303
|
),
|
|
1057
1304
|
children: [
|
|
1058
1305
|
buttons[0],
|
|
1059
|
-
buttons[0] && buttons[1] && /* @__PURE__ */
|
|
1306
|
+
buttons[0] && buttons[1] && /* @__PURE__ */ jsx15("div", { role: "presentation", className: "h-px shrink-0 bg-[var(--color-border)]" }),
|
|
1060
1307
|
buttons[1]
|
|
1061
1308
|
]
|
|
1062
1309
|
}
|
|
@@ -1064,12 +1311,12 @@ function PanelEdgeTab({ side, edge, center, controls, className }) {
|
|
|
1064
1311
|
}
|
|
1065
1312
|
|
|
1066
1313
|
// src/genre-tree/ChangeParentPopup.tsx
|
|
1067
|
-
import { useEffect as
|
|
1314
|
+
import { useEffect as useEffect4, useMemo as useMemo6 } from "react";
|
|
1068
1315
|
import {
|
|
1069
1316
|
getReparentCandidates,
|
|
1070
1317
|
useUpdateGenre
|
|
1071
1318
|
} from "@behindthemusictree/app-kit/genre-tree";
|
|
1072
|
-
import { jsx as
|
|
1319
|
+
import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1073
1320
|
function ChangeParentPopup({
|
|
1074
1321
|
genrePlaylist,
|
|
1075
1322
|
genrePlaylists,
|
|
@@ -1079,7 +1326,7 @@ function ChangeParentPopup({
|
|
|
1079
1326
|
onParentChanged
|
|
1080
1327
|
}) {
|
|
1081
1328
|
const { mutate, isPending, formErrors } = useUpdateGenre(scope, getBackendBaseUrl);
|
|
1082
|
-
const candidates =
|
|
1329
|
+
const candidates = useMemo6(
|
|
1083
1330
|
() => getReparentCandidates(genrePlaylists, genrePlaylist.uuid),
|
|
1084
1331
|
[genrePlaylists, genrePlaylist.uuid]
|
|
1085
1332
|
);
|
|
@@ -1097,19 +1344,19 @@ function ChangeParentPopup({
|
|
|
1097
1344
|
}
|
|
1098
1345
|
);
|
|
1099
1346
|
};
|
|
1100
|
-
|
|
1347
|
+
useEffect4(() => {
|
|
1101
1348
|
const handleKeyDown = (event) => {
|
|
1102
1349
|
if (event.key === "Escape") onClose();
|
|
1103
1350
|
};
|
|
1104
1351
|
window.addEventListener("keydown", handleKeyDown);
|
|
1105
1352
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
1106
1353
|
}, [onClose]);
|
|
1107
|
-
return /* @__PURE__ */
|
|
1108
|
-
/* @__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: [
|
|
1109
1356
|
"Current parent: ",
|
|
1110
1357
|
genrePlaylist.parent?.name ?? "none"
|
|
1111
1358
|
] }),
|
|
1112
|
-
/* @__PURE__ */
|
|
1359
|
+
/* @__PURE__ */ jsx16("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ jsx16(
|
|
1113
1360
|
GenreSearch,
|
|
1114
1361
|
{
|
|
1115
1362
|
genrePlaylists: candidates,
|
|
@@ -1117,7 +1364,7 @@ function ChangeParentPopup({
|
|
|
1117
1364
|
placeholder: "Search a new parent\u2026"
|
|
1118
1365
|
}
|
|
1119
1366
|
) }),
|
|
1120
|
-
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(" ") })
|
|
1121
1368
|
] });
|
|
1122
1369
|
}
|
|
1123
1370
|
|
|
@@ -1129,29 +1376,29 @@ import { useArtist } from "@behindthemusictree/app-kit/genre-tree";
|
|
|
1129
1376
|
import { FieldList } from "@behindthemusictree/ui";
|
|
1130
1377
|
|
|
1131
1378
|
// src/genre-tree/artist/ArtistLinks.tsx
|
|
1132
|
-
import { Globe, Share2 } from "lucide-react";
|
|
1379
|
+
import { Globe, Share2 as Share23 } from "lucide-react";
|
|
1133
1380
|
import {
|
|
1134
1381
|
SiBandcamp,
|
|
1135
1382
|
SiDiscogs,
|
|
1136
1383
|
SiMusicbrainz,
|
|
1137
1384
|
SiSoundcloud,
|
|
1138
1385
|
SiSpotify,
|
|
1139
|
-
SiWikidata,
|
|
1140
|
-
SiWikipedia,
|
|
1386
|
+
SiWikidata as SiWikidata2,
|
|
1387
|
+
SiWikipedia as SiWikipedia2,
|
|
1141
1388
|
SiYoutube
|
|
1142
1389
|
} from "react-icons/si";
|
|
1143
1390
|
import { Chip } from "@behindthemusictree/ui";
|
|
1144
|
-
import { jsx as
|
|
1391
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
1145
1392
|
var LINK_KINDS = {
|
|
1146
1393
|
official: { label: "Official website", Icon: Globe },
|
|
1147
|
-
wikipedia: { label: "Wikipedia", Icon:
|
|
1148
|
-
wikidata: { label: "Wikidata", Icon:
|
|
1394
|
+
wikipedia: { label: "Wikipedia", Icon: SiWikipedia2 },
|
|
1395
|
+
wikidata: { label: "Wikidata", Icon: SiWikidata2 },
|
|
1149
1396
|
spotify: { label: "Spotify", Icon: SiSpotify },
|
|
1150
1397
|
youtube: { label: "YouTube", Icon: SiYoutube },
|
|
1151
1398
|
bandcamp: { label: "Bandcamp", Icon: SiBandcamp },
|
|
1152
1399
|
soundcloud: { label: "SoundCloud", Icon: SiSoundcloud },
|
|
1153
1400
|
discogs: { label: "Discogs", Icon: SiDiscogs },
|
|
1154
|
-
social: { label: "Social", Icon:
|
|
1401
|
+
social: { label: "Social", Icon: Share23 }
|
|
1155
1402
|
};
|
|
1156
1403
|
function socialLabel(url) {
|
|
1157
1404
|
try {
|
|
@@ -1178,7 +1425,7 @@ function artistLinks(artist) {
|
|
|
1178
1425
|
return links;
|
|
1179
1426
|
}
|
|
1180
1427
|
function ArtistLinks({ links }) {
|
|
1181
|
-
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)) });
|
|
1182
1429
|
}
|
|
1183
1430
|
|
|
1184
1431
|
// src/genre-tree/artist/formatYears.ts
|
|
@@ -1189,7 +1436,7 @@ function formatYears(beginYear, endYear, ended) {
|
|
|
1189
1436
|
}
|
|
1190
1437
|
|
|
1191
1438
|
// src/genre-tree/artist/ArtistHeader.tsx
|
|
1192
|
-
import { jsx as
|
|
1439
|
+
import { jsx as jsx18 } from "react/jsx-runtime";
|
|
1193
1440
|
var countryNames = new Intl.DisplayNames(["en"], { type: "region" });
|
|
1194
1441
|
function ArtistHeader({ artist }) {
|
|
1195
1442
|
const fields = [];
|
|
@@ -1199,35 +1446,35 @@ function ArtistHeader({ artist }) {
|
|
|
1199
1446
|
const years = formatYears(artist.beginYear, artist.endYear, artist.ended);
|
|
1200
1447
|
if (years) fields.push({ label: "Active", value: years });
|
|
1201
1448
|
const links = artistLinks(artist);
|
|
1202
|
-
if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */
|
|
1449
|
+
if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ jsx18(ArtistLinks, { links }) });
|
|
1203
1450
|
if (fields.length === 0) return null;
|
|
1204
|
-
return /* @__PURE__ */
|
|
1451
|
+
return /* @__PURE__ */ jsx18(FieldList, { items: fields });
|
|
1205
1452
|
}
|
|
1206
1453
|
|
|
1207
1454
|
// src/genre-tree/artist/ArtistPhoto.tsx
|
|
1208
|
-
import { jsx as
|
|
1455
|
+
import { jsx as jsx19, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1209
1456
|
function ExternalLink({ href, children }) {
|
|
1210
|
-
return /* @__PURE__ */
|
|
1457
|
+
return /* @__PURE__ */ jsx19("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
|
|
1211
1458
|
}
|
|
1212
1459
|
function ArtistPhoto({ image, name }) {
|
|
1213
|
-
return /* @__PURE__ */
|
|
1214
|
-
/* @__PURE__ */
|
|
1215
|
-
/* @__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: [
|
|
1216
1463
|
"Photo: ",
|
|
1217
|
-
/* @__PURE__ */
|
|
1464
|
+
/* @__PURE__ */ jsx19(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
|
|
1218
1465
|
",",
|
|
1219
1466
|
" ",
|
|
1220
|
-
image.licenseUrl ? /* @__PURE__ */
|
|
1467
|
+
image.licenseUrl ? /* @__PURE__ */ jsx19(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
|
|
1221
1468
|
] })
|
|
1222
1469
|
] });
|
|
1223
1470
|
}
|
|
1224
1471
|
|
|
1225
1472
|
// src/genre-tree/artist/ArtistMembers.tsx
|
|
1226
1473
|
import { PanelGroup, PanelSection } from "@behindthemusictree/ui";
|
|
1227
|
-
import { jsx as
|
|
1474
|
+
import { jsx as jsx20, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1228
1475
|
function MembershipList({ title, memberships, renderArtistLink }) {
|
|
1229
1476
|
if (memberships.length === 0) return null;
|
|
1230
|
-
return /* @__PURE__ */
|
|
1477
|
+
return /* @__PURE__ */ jsx20(PanelGroup, { title, children: /* @__PURE__ */ jsx20("ul", { children: memberships.map((membership, index) => {
|
|
1231
1478
|
const details = [
|
|
1232
1479
|
formatYears(membership.beginYear, membership.endYear, membership.ended),
|
|
1233
1480
|
...membership.roles
|
|
@@ -1235,9 +1482,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
|
|
|
1235
1482
|
const detailsText = details.filter(Boolean).join(" \xB7 ");
|
|
1236
1483
|
return (
|
|
1237
1484
|
// A member can have several periods in the same group.
|
|
1238
|
-
/* @__PURE__ */
|
|
1239
|
-
/* @__PURE__ */
|
|
1240
|
-
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: [
|
|
1241
1488
|
" \u2014 ",
|
|
1242
1489
|
detailsText
|
|
1243
1490
|
] })
|
|
@@ -1248,9 +1495,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
|
|
|
1248
1495
|
function ArtistMembers({ title, memberships, renderArtistLink }) {
|
|
1249
1496
|
if (memberships.length === 0) return null;
|
|
1250
1497
|
const isCurrent = (membership) => !membership.ended && membership.endYear === null;
|
|
1251
|
-
return /* @__PURE__ */
|
|
1252
|
-
/* @__PURE__ */
|
|
1253
|
-
/* @__PURE__ */
|
|
1498
|
+
return /* @__PURE__ */ jsxs14(PanelSection, { title, children: [
|
|
1499
|
+
/* @__PURE__ */ jsx20(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
|
|
1500
|
+
/* @__PURE__ */ jsx20(
|
|
1254
1501
|
MembershipList,
|
|
1255
1502
|
{
|
|
1256
1503
|
title: "Past",
|
|
@@ -1263,7 +1510,7 @@ function ArtistMembers({ title, memberships, renderArtistLink }) {
|
|
|
1263
1510
|
|
|
1264
1511
|
// src/genre-tree/artist/ArtistDiscography.tsx
|
|
1265
1512
|
import { PanelGroup as PanelGroup2, PanelSection as PanelSection2 } from "@behindthemusictree/ui";
|
|
1266
|
-
import { jsx as
|
|
1513
|
+
import { jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1267
1514
|
var GROUPS = ["Albums", "EPs", "Singles", "Live", "Compilations", "Other"];
|
|
1268
1515
|
var PRIMARY_GROUPS = { album: "Albums", ep: "EPs", single: "Singles" };
|
|
1269
1516
|
function discographyGroup({ primaryType, secondaryTypes }) {
|
|
@@ -1283,18 +1530,18 @@ function byYear(a, b) {
|
|
|
1283
1530
|
function ArtistDiscography({ albums }) {
|
|
1284
1531
|
if (albums.length === 0) return null;
|
|
1285
1532
|
const sorted = [...albums].sort(byYear);
|
|
1286
|
-
return /* @__PURE__ */
|
|
1533
|
+
return /* @__PURE__ */ jsx21(PanelSection2, { title: "Discography", children: GROUPS.map((group) => {
|
|
1287
1534
|
const groupAlbums = sorted.filter((album) => discographyGroup(album) === group);
|
|
1288
1535
|
if (groupAlbums.length === 0) return null;
|
|
1289
|
-
return /* @__PURE__ */
|
|
1290
|
-
/* @__PURE__ */
|
|
1291
|
-
/* @__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 })
|
|
1292
1539
|
] }, album.uuid)) }) }, group);
|
|
1293
1540
|
}) });
|
|
1294
1541
|
}
|
|
1295
1542
|
|
|
1296
1543
|
// src/genre-tree/artist/ArtistSongs.tsx
|
|
1297
|
-
import { useState as
|
|
1544
|
+
import { useState as useState4 } from "react";
|
|
1298
1545
|
import { keepPreviousData } from "@tanstack/react-query";
|
|
1299
1546
|
import { Play as Play2 } from "lucide-react";
|
|
1300
1547
|
import { Pagination, PanelSection as PanelSection3, Skeleton as Skeleton3 } from "@behindthemusictree/ui";
|
|
@@ -1315,41 +1562,49 @@ import { useTrackList as useTrackList3 } from "@behindthemusictree/app-kit/genre
|
|
|
1315
1562
|
import { FaPlay as FaPlay2, FaPause as FaPause2 } from "react-icons/fa";
|
|
1316
1563
|
import { usePlayer as usePlayer5 } from "@behindthemusictree/app-kit/player";
|
|
1317
1564
|
import { PlayStates as PlayStates2 } from "@behindthemusictree/app-kit/player";
|
|
1318
|
-
import {
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
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();
|
|
1322
1573
|
const isThisTrack = playerTrackObject?.track.id === uuid;
|
|
1323
|
-
|
|
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(
|
|
1324
1580
|
"div",
|
|
1325
1581
|
{
|
|
1326
|
-
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",
|
|
1327
1583
|
style: { minWidth: "4rem", maxWidth: "4rem" },
|
|
1328
1584
|
onClick: handlePlayPauseClick,
|
|
1329
1585
|
children: [
|
|
1330
|
-
/* @__PURE__ */
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
/* @__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
|
+
)
|
|
1353
1608
|
]
|
|
1354
1609
|
}
|
|
1355
1610
|
);
|
|
@@ -1358,7 +1613,7 @@ function TrackPositionPlayPause({ position, uuid, handlePlayPauseClick }) {
|
|
|
1358
1613
|
// src/genre-tree/track-list-sidebar/TrackItem.tsx
|
|
1359
1614
|
import { isSong as isSong2, songUnplayableReason as songUnplayableReason2 } from "@behindthemusictree/app-kit/genre-tree";
|
|
1360
1615
|
import { ArtistNames as ArtistNames2 } from "@behindthemusictree/app-kit/genre-tree";
|
|
1361
|
-
import { jsx as
|
|
1616
|
+
import { jsx as jsx23, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
1362
1617
|
function TrackItem({
|
|
1363
1618
|
track,
|
|
1364
1619
|
position,
|
|
@@ -1367,25 +1622,26 @@ function TrackItem({
|
|
|
1367
1622
|
renderArtistLink
|
|
1368
1623
|
}) {
|
|
1369
1624
|
const { handlePlayPauseAction, playerTrackObject, loadTrackForPlayer } = usePlayer6();
|
|
1370
|
-
const { trackList, toTrackAtPosition } = useTrackList3();
|
|
1625
|
+
const { trackList, selectedTrack, toTrackAtPosition, runtimeUnplayableTrackIds } = useTrackList3();
|
|
1371
1626
|
const scope = trackList?.origin?.scope ?? null;
|
|
1372
|
-
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;
|
|
1373
1630
|
const handlePlayPauseClick = (event) => {
|
|
1374
1631
|
event.stopPropagation();
|
|
1375
|
-
if (
|
|
1632
|
+
if (isCurrentTrack) {
|
|
1376
1633
|
handlePlayPauseAction();
|
|
1377
1634
|
} else if (scope != null) {
|
|
1378
1635
|
toTrackAtPosition(position);
|
|
1379
1636
|
loadTrackForPlayer(track.uuid);
|
|
1380
1637
|
}
|
|
1381
1638
|
};
|
|
1382
|
-
return /* @__PURE__ */
|
|
1639
|
+
return /* @__PURE__ */ jsxs17(
|
|
1383
1640
|
"div",
|
|
1384
1641
|
{
|
|
1385
|
-
className: `track-item flex h-14 text-[var(--color-text)] ${
|
|
1386
|
-
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"}`,
|
|
1387
1643
|
children: [
|
|
1388
|
-
|
|
1644
|
+
isUnplayable ? /* @__PURE__ */ jsx23(
|
|
1389
1645
|
"div",
|
|
1390
1646
|
{
|
|
1391
1647
|
className: "flex items-center justify-center text-lg w-16",
|
|
@@ -1393,18 +1649,21 @@ function TrackItem({
|
|
|
1393
1649
|
"aria-disabled": "true",
|
|
1394
1650
|
children: position
|
|
1395
1651
|
}
|
|
1396
|
-
) : /* @__PURE__ */
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
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 }) }) : ""
|
|
1404
1664
|
] }),
|
|
1405
|
-
/* @__PURE__ */
|
|
1406
|
-
|
|
1407
|
-
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(
|
|
1408
1667
|
"div",
|
|
1409
1668
|
{
|
|
1410
1669
|
className: "duration flex text-base w-16 items-center justify-center",
|
|
@@ -1412,22 +1671,22 @@ function TrackItem({
|
|
|
1412
1671
|
children: renderDuration(track)
|
|
1413
1672
|
}
|
|
1414
1673
|
),
|
|
1415
|
-
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) })
|
|
1416
1675
|
]
|
|
1417
1676
|
}
|
|
1418
1677
|
);
|
|
1419
1678
|
}
|
|
1420
1679
|
|
|
1421
1680
|
// src/genre-tree/artist/ArtistSongs.tsx
|
|
1422
|
-
import { Fragment as
|
|
1681
|
+
import { Fragment as Fragment6, jsx as jsx24, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
1423
1682
|
var SongPageSchema2 = PaginatedResponseSchema2(SongDetailedSchema2);
|
|
1424
1683
|
var SORTS = [
|
|
1425
1684
|
{ ordering: "title", label: "Title" },
|
|
1426
1685
|
{ ordering: "-albumYear", label: "Year" }
|
|
1427
1686
|
];
|
|
1428
1687
|
function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
1429
|
-
const [page, setPage] =
|
|
1430
|
-
const [ordering, setOrdering] =
|
|
1688
|
+
const [page, setPage] = useState4(1);
|
|
1689
|
+
const [ordering, setOrdering] = useState4("title");
|
|
1431
1690
|
const { fetch } = useFetchWrapper2(getBackendBaseUrl);
|
|
1432
1691
|
const { playNewTrackListFromArtist } = useTrackList4();
|
|
1433
1692
|
const { playerTrackObject } = usePlayer7();
|
|
@@ -1452,21 +1711,21 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
|
1452
1711
|
event.stopPropagation();
|
|
1453
1712
|
void play(song.uuid);
|
|
1454
1713
|
};
|
|
1455
|
-
return /* @__PURE__ */
|
|
1456
|
-
/* @__PURE__ */
|
|
1457
|
-
/* @__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(
|
|
1458
1717
|
"button",
|
|
1459
1718
|
{
|
|
1460
1719
|
type: "button",
|
|
1461
1720
|
onClick: () => void play(),
|
|
1462
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",
|
|
1463
1722
|
children: [
|
|
1464
|
-
/* @__PURE__ */
|
|
1723
|
+
/* @__PURE__ */ jsx24(Play2, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
|
|
1465
1724
|
"Play all"
|
|
1466
1725
|
]
|
|
1467
1726
|
}
|
|
1468
1727
|
),
|
|
1469
|
-
/* @__PURE__ */
|
|
1728
|
+
/* @__PURE__ */ jsx24("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ jsx24(
|
|
1470
1729
|
"button",
|
|
1471
1730
|
{
|
|
1472
1731
|
type: "button",
|
|
@@ -1481,7 +1740,7 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
|
1481
1740
|
sort.ordering
|
|
1482
1741
|
)) })
|
|
1483
1742
|
] }),
|
|
1484
|
-
/* @__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(
|
|
1485
1744
|
TrackItem,
|
|
1486
1745
|
{
|
|
1487
1746
|
track: song,
|
|
@@ -1489,15 +1748,17 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
|
1489
1748
|
renderArtistLink
|
|
1490
1749
|
}
|
|
1491
1750
|
) }, song.uuid)) }),
|
|
1492
|
-
/* @__PURE__ */
|
|
1751
|
+
/* @__PURE__ */ jsx24(Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
|
|
1493
1752
|
] }) });
|
|
1494
1753
|
}
|
|
1495
1754
|
|
|
1496
1755
|
// src/genre-tree/artist/useArtistPanel.tsx
|
|
1497
|
-
import { Fragment as
|
|
1756
|
+
import { Fragment as Fragment7, jsx as jsx25, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
1498
1757
|
function useArtistPanel({
|
|
1499
1758
|
uuid,
|
|
1759
|
+
scope,
|
|
1500
1760
|
getBackendBaseUrl,
|
|
1761
|
+
getShareUrl,
|
|
1501
1762
|
renderArtistLink,
|
|
1502
1763
|
expanded,
|
|
1503
1764
|
onExpandedChange,
|
|
@@ -1512,38 +1773,41 @@ function useArtistPanel({
|
|
|
1512
1773
|
return {
|
|
1513
1774
|
...panel,
|
|
1514
1775
|
title: "Artist",
|
|
1515
|
-
content: /* @__PURE__ */
|
|
1516
|
-
/* @__PURE__ */
|
|
1517
|
-
/* @__PURE__ */
|
|
1518
|
-
/* @__PURE__ */
|
|
1519
|
-
/* @__PURE__ */
|
|
1520
|
-
/* @__PURE__ */
|
|
1521
|
-
/* @__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" })
|
|
1522
1783
|
] })
|
|
1523
1784
|
};
|
|
1524
1785
|
case "not-found":
|
|
1525
1786
|
return {
|
|
1526
1787
|
...panel,
|
|
1527
1788
|
title: "Artist not found",
|
|
1528
|
-
content: /* @__PURE__ */
|
|
1789
|
+
content: /* @__PURE__ */ jsx25("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This artist does not exist." })
|
|
1529
1790
|
};
|
|
1530
1791
|
case "error":
|
|
1531
1792
|
return {
|
|
1532
1793
|
...panel,
|
|
1533
1794
|
title: "Artist",
|
|
1534
|
-
content: /* @__PURE__ */
|
|
1795
|
+
content: /* @__PURE__ */ jsx25("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
|
|
1535
1796
|
};
|
|
1536
1797
|
case "success": {
|
|
1537
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;
|
|
1538
1801
|
return {
|
|
1539
1802
|
...panel,
|
|
1540
1803
|
title: artist.name,
|
|
1541
|
-
content: /* @__PURE__ */
|
|
1542
|
-
|
|
1543
|
-
/* @__PURE__ */
|
|
1544
|
-
/* @__PURE__ */
|
|
1545
|
-
/* @__PURE__ */
|
|
1546
|
-
|
|
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(
|
|
1547
1811
|
ArtistSongs,
|
|
1548
1812
|
{
|
|
1549
1813
|
artist: { uuid: artist.uuid, name: artist.name },
|
|
@@ -1552,7 +1816,7 @@ function useArtistPanel({
|
|
|
1552
1816
|
}
|
|
1553
1817
|
)
|
|
1554
1818
|
] }),
|
|
1555
|
-
extraContent: artist.albums.length > 0 ? /* @__PURE__ */
|
|
1819
|
+
extraContent: artist.albums.length > 0 ? /* @__PURE__ */ jsx25(ArtistDiscography, { albums: artist.albums }) : void 0
|
|
1556
1820
|
};
|
|
1557
1821
|
}
|
|
1558
1822
|
}
|
|
@@ -1561,27 +1825,27 @@ function useArtistPanel({
|
|
|
1561
1825
|
// src/genre-tree/SizeMetricToggle.tsx
|
|
1562
1826
|
import { MdMusicNote, MdVisibility } from "react-icons/md";
|
|
1563
1827
|
import { SegmentedControl } from "@behindthemusictree/ui";
|
|
1564
|
-
import { jsx as
|
|
1828
|
+
import { jsx as jsx26 } from "react/jsx-runtime";
|
|
1565
1829
|
function SizeMetricToggle({
|
|
1566
1830
|
value,
|
|
1567
1831
|
onChange
|
|
1568
1832
|
}) {
|
|
1569
|
-
return /* @__PURE__ */
|
|
1833
|
+
return /* @__PURE__ */ jsx26(
|
|
1570
1834
|
SegmentedControl,
|
|
1571
1835
|
{
|
|
1572
1836
|
"aria-label": "Size nodes by",
|
|
1573
1837
|
value,
|
|
1574
1838
|
onChange,
|
|
1575
1839
|
options: [
|
|
1576
|
-
{ value: "songs", label: /* @__PURE__ */
|
|
1577
|
-
{ value: "views", label: /* @__PURE__ */
|
|
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" }
|
|
1578
1842
|
]
|
|
1579
1843
|
}
|
|
1580
1844
|
);
|
|
1581
1845
|
}
|
|
1582
1846
|
|
|
1583
1847
|
// src/genre-tree/GenreTreeView.tsx
|
|
1584
|
-
import { Fragment as
|
|
1848
|
+
import { Fragment as Fragment8, jsx as jsx27, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
1585
1849
|
var PREVIOUS_BUTTON_CSS = `
|
|
1586
1850
|
.genre-tree-view .search-overlay .gtv-info-panel-previous { display: none; }
|
|
1587
1851
|
.genre-tree-view:has(.search-overlay .gtv-info-panel-previous) .genre-search input { padding-left: 2.75rem; }
|
|
@@ -1603,7 +1867,7 @@ function GenreNodeActionBand({ genrePlaylist, scope, playback, ...bandProps }) {
|
|
|
1603
1867
|
console.error("Failed to load genre playlist tracks:", error);
|
|
1604
1868
|
});
|
|
1605
1869
|
};
|
|
1606
|
-
return /* @__PURE__ */
|
|
1870
|
+
return /* @__PURE__ */ jsx27(
|
|
1607
1871
|
GenreActionBand,
|
|
1608
1872
|
{
|
|
1609
1873
|
...bandProps,
|
|
@@ -1627,13 +1891,14 @@ function GenreTreeView({
|
|
|
1627
1891
|
defaultViewport,
|
|
1628
1892
|
onViewportChange,
|
|
1629
1893
|
readOnly = false,
|
|
1630
|
-
className,
|
|
1894
|
+
className: className2,
|
|
1631
1895
|
showDisplayOptions = true,
|
|
1632
1896
|
showAddRoot = true,
|
|
1633
1897
|
allowWheelRotation: controlledAllowWheelRotation,
|
|
1634
1898
|
criteriaOverviewSchema,
|
|
1635
1899
|
renderGenreDetailExtras,
|
|
1636
1900
|
controlsOrientation = "horizontal",
|
|
1901
|
+
floatingControlsLeading,
|
|
1637
1902
|
sizeMetric: controlledSizeMetric,
|
|
1638
1903
|
onSizeMetricChange,
|
|
1639
1904
|
renderArtistLink,
|
|
@@ -1641,6 +1906,7 @@ function GenreTreeView({
|
|
|
1641
1906
|
onArtistUuidChange,
|
|
1642
1907
|
artistExpanded: controlledArtistExpanded,
|
|
1643
1908
|
onArtistExpandedChange,
|
|
1909
|
+
getShareUrl,
|
|
1644
1910
|
artistSearch = true,
|
|
1645
1911
|
onArtistSelect,
|
|
1646
1912
|
artistSearchBaseUrl,
|
|
@@ -1658,10 +1924,12 @@ function GenreTreeView({
|
|
|
1658
1924
|
placement = "default",
|
|
1659
1925
|
...focus
|
|
1660
1926
|
}) {
|
|
1661
|
-
const
|
|
1927
|
+
const getShareUrlRef = useRef4(getShareUrl);
|
|
1928
|
+
getShareUrlRef.current = getShareUrl;
|
|
1929
|
+
const [internalArtistUuid, setInternalArtistUuid] = useState5(null);
|
|
1662
1930
|
const isArtistControlled = controlledArtistUuid !== void 0;
|
|
1663
1931
|
const artistUuid = isArtistControlled ? controlledArtistUuid : internalArtistUuid;
|
|
1664
|
-
const [internalSizeMetric, setInternalSizeMetric] =
|
|
1932
|
+
const [internalSizeMetric, setInternalSizeMetric] = useState5("songs");
|
|
1665
1933
|
const sizeMetric = controlledSizeMetric ?? internalSizeMetric;
|
|
1666
1934
|
const deferredSizeMetric = useDeferredValue(sizeMetric);
|
|
1667
1935
|
const changeSizeMetric = (metric) => {
|
|
@@ -1700,24 +1968,30 @@ function GenreTreeView({
|
|
|
1700
1968
|
artistUuid
|
|
1701
1969
|
});
|
|
1702
1970
|
const isGenreUpdating = useIsMutating({ mutationKey: genreUpdateMutationKey }) > 0;
|
|
1971
|
+
const { mutate: updateGenre } = useUpdateGenre2(scope, getBackendBaseUrl);
|
|
1703
1972
|
const busy = deferredSizeMetric !== sizeMetric || isTreeFetching || isGenreUpdating;
|
|
1973
|
+
const isArtistFiltered = artistUuid !== null;
|
|
1974
|
+
const canSizeByViews = !isArtistFiltered && hasViewsCount(visibleGenrePlaylists);
|
|
1704
1975
|
const artistResult = useArtist2(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
|
|
1705
1976
|
const filterArtistName = artistResult.status === "success" ? artistResult.artist.name : null;
|
|
1706
|
-
const filterArtist =
|
|
1977
|
+
const filterArtist = useMemo7(
|
|
1707
1978
|
() => artistUuid !== null && filterArtistName !== null ? { uuid: artistUuid, name: filterArtistName } : null,
|
|
1708
1979
|
[artistUuid, filterArtistName]
|
|
1709
1980
|
);
|
|
1981
|
+
const hasSelectedGenreNode = selectedGenrePlaylist !== void 0 && visibleGenrePlaylists.some(({ uuid }) => uuid === selectedGenrePlaylist.uuid);
|
|
1710
1982
|
const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: visibleGenrePlaylists, artist: filterArtist });
|
|
1711
1983
|
const openArtist = (uuid) => {
|
|
1712
1984
|
if (selectedNodeId !== null) clearSelection();
|
|
1713
1985
|
changeArtist(uuid);
|
|
1714
1986
|
};
|
|
1715
|
-
const [expandedArtistUuid, setExpandedArtistUuid] =
|
|
1987
|
+
const [expandedArtistUuid, setExpandedArtistUuid] = useState5(null);
|
|
1716
1988
|
const artistBaseUrl = artistSearchBaseUrl ?? getBackendBaseUrl;
|
|
1717
1989
|
const artistPanel = useArtistPanel({
|
|
1718
1990
|
uuid: artistUuid,
|
|
1991
|
+
scope,
|
|
1719
1992
|
getBackendBaseUrl: artistBaseUrl,
|
|
1720
|
-
|
|
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 })),
|
|
1721
1995
|
expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
|
|
1722
1996
|
onExpandedChange: (on) => {
|
|
1723
1997
|
if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
|
|
@@ -1725,10 +1999,10 @@ function GenreTreeView({
|
|
|
1725
1999
|
},
|
|
1726
2000
|
onClose: () => changeArtist(null)
|
|
1727
2001
|
});
|
|
1728
|
-
const builtInArtistPanel = overlayPanel ||
|
|
2002
|
+
const builtInArtistPanel = overlayPanel || hasSelectedGenreNode ? void 0 : artistPanel;
|
|
1729
2003
|
const activeOverlayPanel = overlayPanel ?? (builtInArtistPanel && { ...builtInArtistPanel, hideTitle: true, hideClose: true });
|
|
1730
|
-
const [uncontrolledCollapsed, setUncontrolledCollapsed] =
|
|
1731
|
-
const [uncontrolledExpanded, setUncontrolledExpanded] =
|
|
2004
|
+
const [uncontrolledCollapsed, setUncontrolledCollapsed] = useState5(false);
|
|
2005
|
+
const [uncontrolledExpanded, setUncontrolledExpanded] = useState5(false);
|
|
1732
2006
|
const isCollapsed = collapsed ?? uncontrolledCollapsed;
|
|
1733
2007
|
const isExpanded = expanded ?? uncontrolledExpanded;
|
|
1734
2008
|
const changeCollapsed = (next) => {
|
|
@@ -1739,11 +2013,11 @@ function GenreTreeView({
|
|
|
1739
2013
|
setUncontrolledExpanded(next);
|
|
1740
2014
|
onExpandedChange?.(next);
|
|
1741
2015
|
};
|
|
1742
|
-
|
|
2016
|
+
useEffect5(() => {
|
|
1743
2017
|
setUncontrolledCollapsed(false);
|
|
1744
2018
|
}, [selectedNodeId, artistUuid]);
|
|
1745
|
-
const isDetailHidden = isCollapsed || !
|
|
1746
|
-
|
|
2019
|
+
const isDetailHidden = isCollapsed || !hasSelectedGenreNode && !activeOverlayPanel;
|
|
2020
|
+
useEffect5(() => {
|
|
1747
2021
|
if (placement !== "regions" || !isDetailHidden) return void 0;
|
|
1748
2022
|
const root = document.documentElement;
|
|
1749
2023
|
root.style.setProperty("--layout-left-w", "0px");
|
|
@@ -1762,29 +2036,38 @@ function GenreTreeView({
|
|
|
1762
2036
|
hideExpandButton: placement === "regions" ? true : void 0,
|
|
1763
2037
|
overlayPanel: detailOverlay ? { ...detailOverlay, collapsed: isCollapsed, hideExpandButton: true } : activeOverlayPanel
|
|
1764
2038
|
};
|
|
1765
|
-
const [isWheelRevealed, setIsWheelRevealed] =
|
|
2039
|
+
const [isWheelRevealed, setIsWheelRevealed] = useState5(false);
|
|
1766
2040
|
const usesHandoff = viewMode === "wheel" || viewMode === "pop-core";
|
|
1767
2041
|
const isTreeReady = !isLoading && (!usesHandoff || isWheelRevealed);
|
|
1768
2042
|
const rootRef = useRef4(null);
|
|
1769
2043
|
const handlePrevious = () => {
|
|
1770
2044
|
rootRef.current?.querySelector(".search-overlay .gtv-info-panel-previous")?.click();
|
|
1771
2045
|
};
|
|
1772
|
-
|
|
1773
|
-
|
|
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("");
|
|
1774
2057
|
const artistSearchResult = useArtistSearch(artistBaseUrl, artistSearch ? searchText : "");
|
|
1775
2058
|
const artistExtraResults = artistSearch ? {
|
|
1776
2059
|
label: "Artists",
|
|
1777
2060
|
items: artistSearchResult.artists.map((artist) => ({
|
|
1778
2061
|
key: artist.uuid,
|
|
1779
|
-
content: /* @__PURE__ */
|
|
1780
|
-
/* @__PURE__ */
|
|
2062
|
+
content: /* @__PURE__ */ jsxs20(Fragment8, { children: [
|
|
2063
|
+
/* @__PURE__ */ jsxs20("span", { className: "block truncate font-medium", children: [
|
|
1781
2064
|
artist.name,
|
|
1782
|
-
artist.disambiguation && /* @__PURE__ */
|
|
2065
|
+
artist.disambiguation && /* @__PURE__ */ jsxs20("span", { className: "font-normal text-[var(--color-neutral-500)]", children: [
|
|
1783
2066
|
" \xB7 ",
|
|
1784
2067
|
artist.disambiguation
|
|
1785
2068
|
] })
|
|
1786
2069
|
] }),
|
|
1787
|
-
artist.matchedAlias && /* @__PURE__ */
|
|
2070
|
+
artist.matchedAlias && /* @__PURE__ */ jsxs20("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [
|
|
1788
2071
|
"also known as ",
|
|
1789
2072
|
artist.matchedAlias
|
|
1790
2073
|
] })
|
|
@@ -1812,29 +2095,45 @@ function GenreTreeView({
|
|
|
1812
2095
|
};
|
|
1813
2096
|
const zoomInRef = useRef4(void 0);
|
|
1814
2097
|
zoomInRef.current = canZoomIn ? handleGenreSearchZoomIn : void 0;
|
|
1815
|
-
const renderInfoPanelBody =
|
|
2098
|
+
const renderInfoPanelBody = useCallback6(
|
|
1816
2099
|
(node, { toolbar, ancestors, family }) => {
|
|
1817
2100
|
const genrePlaylist = genrePlaylistByNodeId.get(node.id);
|
|
1818
2101
|
const criteria = genrePlaylist?.criteria;
|
|
1819
|
-
if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */
|
|
2102
|
+
if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ jsxs20(Fragment8, { children: [
|
|
1820
2103
|
toolbar,
|
|
1821
2104
|
family
|
|
1822
2105
|
] }), aside: ancestors };
|
|
1823
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;
|
|
1824
2121
|
return {
|
|
1825
|
-
primary: /* @__PURE__ */
|
|
2122
|
+
primary: /* @__PURE__ */ jsxs20(Fragment8, { children: [
|
|
1826
2123
|
toolbar,
|
|
1827
|
-
/* @__PURE__ */
|
|
2124
|
+
/* @__PURE__ */ jsx27(
|
|
1828
2125
|
GenreNodeActionBand,
|
|
1829
2126
|
{
|
|
1830
2127
|
node,
|
|
1831
2128
|
genrePlaylist,
|
|
1832
2129
|
scope,
|
|
2130
|
+
shareTarget: shareUrl ? shareTarget : void 0,
|
|
2131
|
+
shareUrl,
|
|
1833
2132
|
onZoomIn: zoomInRef.current,
|
|
1834
2133
|
onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
|
|
1835
2134
|
onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
|
|
1836
2135
|
onChangeParent: readOnly ? void 0 : () => showPopup(
|
|
1837
|
-
/* @__PURE__ */
|
|
2136
|
+
/* @__PURE__ */ jsx27(
|
|
1838
2137
|
ChangeParentPopup,
|
|
1839
2138
|
{
|
|
1840
2139
|
genrePlaylist: { ...genrePlaylist, criteria },
|
|
@@ -1846,16 +2145,20 @@ function GenreTreeView({
|
|
|
1846
2145
|
}
|
|
1847
2146
|
)
|
|
1848
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,
|
|
1849
2151
|
additionalActions,
|
|
1850
2152
|
playback
|
|
1851
2153
|
}
|
|
1852
2154
|
),
|
|
1853
|
-
/* @__PURE__ */
|
|
2155
|
+
/* @__PURE__ */ jsx27(
|
|
1854
2156
|
GenreDetailExtras,
|
|
1855
2157
|
{
|
|
1856
2158
|
family,
|
|
1857
2159
|
genreUuid,
|
|
1858
2160
|
genrePlaylist,
|
|
2161
|
+
wikidataId: criteria.wikidataId,
|
|
1859
2162
|
scope,
|
|
1860
2163
|
getBackendBaseUrl,
|
|
1861
2164
|
criteriaOverviewSchema,
|
|
@@ -1884,6 +2187,7 @@ function GenreTreeView({
|
|
|
1884
2187
|
hidePopup,
|
|
1885
2188
|
onParentChanged,
|
|
1886
2189
|
visibleGenrePlaylists,
|
|
2190
|
+
updateGenre,
|
|
1887
2191
|
playback,
|
|
1888
2192
|
filterArtist,
|
|
1889
2193
|
scope,
|
|
@@ -1900,7 +2204,7 @@ function GenreTreeView({
|
|
|
1900
2204
|
);
|
|
1901
2205
|
const isRotationControlled = controlledAllowWheelRotation !== void 0;
|
|
1902
2206
|
const allowWheelRotation = controlledAllowWheelRotation ?? internalAllowWheelRotation;
|
|
1903
|
-
const detailTab = placement === "regions" && (
|
|
2207
|
+
const detailTab = placement === "regions" && (hasSelectedGenreNode || detailOverlay) ? /* @__PURE__ */ jsx27(
|
|
1904
2208
|
PanelEdgeTab,
|
|
1905
2209
|
{
|
|
1906
2210
|
side: "left",
|
|
@@ -1908,25 +2212,14 @@ function GenreTreeView({
|
|
|
1908
2212
|
center: detailExpanded ? null : isCollapsed ? { label: "Show panel", onClick: () => changeCollapsed(false) } : onDetailExpandedChange ? { label: "Extend panel", onClick: () => onDetailExpandedChange(true) } : null
|
|
1909
2213
|
}
|
|
1910
2214
|
) : null;
|
|
1911
|
-
const
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
genrePlaylists: visibleGenrePlaylists,
|
|
1916
|
-
onSelect: handleGenreSearchSelect,
|
|
1917
|
-
selectedName: selectedGenrePlaylist?.name ?? builtInArtistPanel?.title ?? null,
|
|
1918
|
-
onClear: builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
|
|
1919
|
-
onPrevious: handlePrevious,
|
|
1920
|
-
onQueryChange: artistSearch ? setSearchText : void 0,
|
|
1921
|
-
extraResults: artistExtraResults
|
|
1922
|
-
}
|
|
1923
|
-
),
|
|
1924
|
-
!isLoading && filterArtist && /* @__PURE__ */ jsxs17("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
|
|
1925
|
-
/* @__PURE__ */ jsx24(
|
|
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(
|
|
1926
2219
|
"button",
|
|
1927
2220
|
{
|
|
1928
2221
|
type: "button",
|
|
1929
|
-
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",
|
|
1930
2223
|
"aria-label": `Show artist ${filterArtist.name}`,
|
|
1931
2224
|
onClick: () => {
|
|
1932
2225
|
if (selectedNodeId !== null) clearSelection();
|
|
@@ -1934,86 +2227,174 @@ function GenreTreeView({
|
|
|
1934
2227
|
children: filterArtist.name
|
|
1935
2228
|
}
|
|
1936
2229
|
),
|
|
1937
|
-
/* @__PURE__ */
|
|
2230
|
+
/* @__PURE__ */ jsx27(
|
|
1938
2231
|
"button",
|
|
1939
2232
|
{
|
|
1940
2233
|
type: "button",
|
|
1941
|
-
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)]",
|
|
1942
2235
|
"aria-label": `Clear artist filter ${filterArtist.name}`,
|
|
1943
2236
|
onClick: () => changeArtist(null),
|
|
1944
2237
|
children: "\xD7"
|
|
1945
2238
|
}
|
|
1946
2239
|
)
|
|
1947
|
-
] })
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
value: viewMode,
|
|
1953
|
-
onChange: setViewMode,
|
|
1954
|
-
options: [
|
|
1955
|
-
{ value: "pop-core", label: "Pop/Core", disabled: !canShowPopCore, title: canShowPopCore ? void 0 : NO_POP_ROOT_TITLE },
|
|
1956
|
-
{ value: "wheel", label: "Wheel" },
|
|
1957
|
-
{ value: "stacked", label: "Stacked" },
|
|
1958
|
-
{ value: "outline", label: "Outline", disabled: !canShowPopCore, title: canShowPopCore ? void 0 : NO_POP_ROOT_TITLE }
|
|
1959
|
-
]
|
|
1960
|
-
}
|
|
1961
|
-
),
|
|
1962
|
-
!isLoading && showDisplayOptions && /* @__PURE__ */ jsx24(
|
|
1963
|
-
"div",
|
|
2240
|
+
] });
|
|
2241
|
+
};
|
|
2242
|
+
const actions = /* @__PURE__ */ jsxs20(Fragment8, { children: [
|
|
2243
|
+
!isLoading && /* @__PURE__ */ jsx27(
|
|
2244
|
+
GenreSearch,
|
|
1964
2245
|
{
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
size: "sm",
|
|
1973
|
-
onClick: toggleWheelRotation,
|
|
1974
|
-
children: "Rotation"
|
|
1975
|
-
}
|
|
1976
|
-
)
|
|
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
|
|
1977
2253
|
}
|
|
1978
2254
|
),
|
|
1979
|
-
!isLoading &&
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
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
|
+
] })
|
|
1987
2297
|
] });
|
|
1988
|
-
return /* @__PURE__ */
|
|
2298
|
+
return /* @__PURE__ */ jsxs20(
|
|
1989
2299
|
"div",
|
|
1990
2300
|
{
|
|
1991
2301
|
ref: rootRef,
|
|
1992
|
-
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(" "),
|
|
1993
2303
|
children: [
|
|
1994
|
-
/* @__PURE__ */
|
|
1995
|
-
/* @__PURE__ */
|
|
2304
|
+
/* @__PURE__ */ jsx27("style", { children: PREVIOUS_BUTTON_CSS }),
|
|
2305
|
+
/* @__PURE__ */ jsx27(
|
|
1996
2306
|
"div",
|
|
1997
2307
|
{
|
|
1998
|
-
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"}`,
|
|
1999
2309
|
children: actions
|
|
2000
2310
|
}
|
|
2001
2311
|
),
|
|
2002
|
-
|
|
2312
|
+
!isLoading && filterArtist && /* @__PURE__ */ jsx27(
|
|
2003
2313
|
"div",
|
|
2004
2314
|
{
|
|
2005
|
-
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))]"}`,
|
|
2006
2323
|
children: detailTab
|
|
2007
2324
|
}
|
|
2008
2325
|
),
|
|
2009
|
-
/* @__PURE__ */
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
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,
|
|
2016
2396
|
{
|
|
2397
|
+
outline: true,
|
|
2017
2398
|
scope,
|
|
2018
2399
|
genrePlaylists: visibleGenrePlaylists,
|
|
2019
2400
|
handleGenreCreationAction,
|
|
@@ -2027,100 +2408,45 @@ function GenreTreeView({
|
|
|
2027
2408
|
playback,
|
|
2028
2409
|
selectedNodeId,
|
|
2029
2410
|
readOnly,
|
|
2030
|
-
allowWheelRotation,
|
|
2031
|
-
controlsOrientation,
|
|
2032
|
-
sizeMetric: filterArtist ? "songs" : deferredSizeMetric,
|
|
2033
|
-
busy,
|
|
2034
|
-
floatingControlsLeading: filterArtist ? void 0 : /* @__PURE__ */ jsx24(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric }),
|
|
2035
|
-
defaultViewport,
|
|
2036
|
-
onViewportChange,
|
|
2037
2411
|
...infoPanelCuration
|
|
2038
2412
|
}
|
|
2039
|
-
) })
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
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
|
+
);
|
|
2067
2447
|
}
|
|
2068
|
-
) })
|
|
2069
|
-
)
|
|
2070
|
-
GenrePlaylistTreeWheelRadialPopCore,
|
|
2071
|
-
{
|
|
2072
|
-
outline: true,
|
|
2073
|
-
scope,
|
|
2074
|
-
genrePlaylists: visibleGenrePlaylists,
|
|
2075
|
-
handleGenreCreationAction,
|
|
2076
|
-
handleGenreRenameAction,
|
|
2077
|
-
additionalActions,
|
|
2078
|
-
onSelectedNodeChange: handleSelectedNodeChange,
|
|
2079
|
-
hideInfoPanelClose: true,
|
|
2080
|
-
hideInfoPanelTitle: true,
|
|
2081
|
-
onNodeHover: handleNodeHover,
|
|
2082
|
-
renderInfoPanelBody,
|
|
2083
|
-
playback,
|
|
2084
|
-
selectedNodeId,
|
|
2085
|
-
readOnly,
|
|
2086
|
-
...infoPanelCuration
|
|
2087
|
-
}
|
|
2088
|
-
) }) : /* @__PURE__ */ jsx24("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(
|
|
2089
|
-
([uuid, genrePlaylistTreePerRoot], rootIndex) => {
|
|
2090
|
-
return /* @__PURE__ */ jsx24(
|
|
2091
|
-
"div",
|
|
2092
|
-
{
|
|
2093
|
-
className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
|
|
2094
|
-
children: /* @__PURE__ */ jsx24("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ jsx24(
|
|
2095
|
-
GenrePlaylistTreePerRoot,
|
|
2096
|
-
{
|
|
2097
|
-
scope,
|
|
2098
|
-
rootUuid: uuid,
|
|
2099
|
-
genrePlaylistTreePerRoot,
|
|
2100
|
-
handleGenreCreationAction,
|
|
2101
|
-
handleGenreRenameAction,
|
|
2102
|
-
additionalActions,
|
|
2103
|
-
onSelectedNodeChange: handleSelectedNodeChange,
|
|
2104
|
-
hideInfoPanelClose: true,
|
|
2105
|
-
hideInfoPanelTitle: true,
|
|
2106
|
-
onNodeHover: handleNodeHover,
|
|
2107
|
-
renderInfoPanelBody,
|
|
2108
|
-
playback,
|
|
2109
|
-
selectedNodeId: uuid === selectedGenrePlaylist?.root.uuid ? selectedNodeId : null,
|
|
2110
|
-
readOnly,
|
|
2111
|
-
wheelZoom: "modifier",
|
|
2112
|
-
controlsOrientation,
|
|
2113
|
-
defaultViewport: uuid === selectedGenrePlaylist?.root.uuid ? defaultViewport : void 0,
|
|
2114
|
-
onViewportChange: uuid === selectedGenrePlaylist?.root.uuid ? onViewportChange : void 0,
|
|
2115
|
-
...infoPanelCuration,
|
|
2116
|
-
overlayPanel: rootIndex === 0 ? activeOverlayPanel : void 0
|
|
2117
|
-
}
|
|
2118
|
-
) })
|
|
2119
|
-
},
|
|
2120
|
-
uuid
|
|
2121
|
-
);
|
|
2122
|
-
}
|
|
2123
|
-
) }) }) })
|
|
2448
|
+
) })
|
|
2449
|
+
] }) })
|
|
2124
2450
|
]
|
|
2125
2451
|
}
|
|
2126
2452
|
);
|
|
@@ -2135,24 +2461,35 @@ import {
|
|
|
2135
2461
|
} from "@behindthemusictree/genre-tree-view";
|
|
2136
2462
|
|
|
2137
2463
|
// src/genre-tree/track-list-sidebar/TrackListSidebar.tsx
|
|
2138
|
-
import { useEffect as
|
|
2464
|
+
import { useEffect as useEffect6, useLayoutEffect as useLayoutEffect2, useRef as useRef5 } from "react";
|
|
2465
|
+
import { ListMusic } from "lucide-react";
|
|
2139
2466
|
import { useTrackList as useTrackList5 } from "@behindthemusictree/app-kit/genre-tree";
|
|
2140
|
-
import { useTrackListSidebarVisibility } from "@behindthemusictree/app-kit/genre-tree";
|
|
2141
2467
|
import { TrackListOriginType as TrackListOriginType2 } from "@behindthemusictree/app-kit/genre-tree";
|
|
2142
2468
|
import { useTrackNoun as useTrackNoun6 } from "@behindthemusictree/app-kit/track-noun";
|
|
2143
|
-
import { jsx as
|
|
2469
|
+
import { jsx as jsx28, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
2470
|
+
var useBrowserLayoutEffect = typeof window === "undefined" ? useEffect6 : useLayoutEffect2;
|
|
2144
2471
|
function TrackListSidebar({
|
|
2145
|
-
className = "",
|
|
2472
|
+
className: className2 = "",
|
|
2146
2473
|
renderDuration,
|
|
2147
2474
|
renderActions,
|
|
2148
2475
|
layout = "fixed",
|
|
2476
|
+
isCollapsed = false,
|
|
2477
|
+
onExpand,
|
|
2478
|
+
onMinimize,
|
|
2149
2479
|
renderArtistLink
|
|
2150
2480
|
}) {
|
|
2151
|
-
const { trackList, loadMore } = useTrackList5();
|
|
2481
|
+
const { trackList, loadMore, autoAdvanceScrollRequest } = useTrackList5();
|
|
2152
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);
|
|
2153
2490
|
const loadMoreSentinelRef = useRef5(null);
|
|
2154
2491
|
const hasMore = !!trackList && trackList.nextPage !== null;
|
|
2155
|
-
|
|
2492
|
+
useEffect6(() => {
|
|
2156
2493
|
const sentinel = loadMoreSentinelRef.current;
|
|
2157
2494
|
if (!hasMore || !sentinel) return;
|
|
2158
2495
|
const observer = new IntersectionObserver((entries) => {
|
|
@@ -2163,44 +2500,115 @@ function TrackListSidebar({
|
|
|
2163
2500
|
observer.observe(sentinel);
|
|
2164
2501
|
return () => observer.disconnect();
|
|
2165
2502
|
}, [hasMore, loadMore]);
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
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(
|
|
2169
2571
|
"div",
|
|
2170
2572
|
{
|
|
2171
2573
|
"data-theme": "dark",
|
|
2172
|
-
className: `track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${
|
|
2173
|
-
style: layout === "fixed" ? {
|
|
2174
|
-
bottom: "79px"
|
|
2175
|
-
/* Match player height exactly */
|
|
2176
|
-
} : 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,
|
|
2177
2576
|
children: [
|
|
2178
|
-
/* @__PURE__ */
|
|
2179
|
-
/* @__PURE__ */
|
|
2180
|
-
/* @__PURE__ */
|
|
2181
|
-
|
|
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
|
+
] })
|
|
2182
2587
|
] }),
|
|
2183
|
-
|
|
2184
|
-
trackList.origin.type === TrackListOriginType2.GENRE_PLAYLIST ? "\u2022 Genre playlist \u2022 " : trackList.origin.type === TrackListOriginType2.ARTIST ? "\u2022 Artist \u2022 " : `\u2022 ${trackNoun.singular} playlist \u2022 `,
|
|
2185
|
-
`${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular} \u2022`
|
|
2186
|
-
] }),
|
|
2187
|
-
/* @__PURE__ */ jsx25(
|
|
2588
|
+
(isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ jsx28(
|
|
2188
2589
|
"button",
|
|
2189
2590
|
{
|
|
2190
2591
|
type: "button",
|
|
2191
|
-
"aria-label": "
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
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" })
|
|
2195
2598
|
}
|
|
2196
2599
|
)
|
|
2197
2600
|
] }),
|
|
2198
|
-
/* @__PURE__ */
|
|
2601
|
+
!isCollapsed && /* @__PURE__ */ jsxs21(
|
|
2199
2602
|
"ul",
|
|
2200
2603
|
{
|
|
2201
|
-
|
|
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",
|
|
2202
2610
|
children: [
|
|
2203
|
-
trackList.tracks.map((track, index) => /* @__PURE__ */
|
|
2611
|
+
trackList.tracks.map((track, index) => /* @__PURE__ */ jsx28("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ jsx28(
|
|
2204
2612
|
TrackItem,
|
|
2205
2613
|
{
|
|
2206
2614
|
track,
|
|
@@ -2210,7 +2618,7 @@ function TrackListSidebar({
|
|
|
2210
2618
|
renderArtistLink
|
|
2211
2619
|
}
|
|
2212
2620
|
) }, track.uuid)),
|
|
2213
|
-
hasMore && /* @__PURE__ */
|
|
2621
|
+
hasMore && /* @__PURE__ */ jsx28("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
|
|
2214
2622
|
]
|
|
2215
2623
|
}
|
|
2216
2624
|
)
|
|
@@ -2220,47 +2628,167 @@ function TrackListSidebar({
|
|
|
2220
2628
|
}
|
|
2221
2629
|
|
|
2222
2630
|
// src/genre-tree/PlayerPanel.tsx
|
|
2223
|
-
import { useState as
|
|
2224
|
-
import { X as X2 } from "lucide-react";
|
|
2631
|
+
import { useEffect as useEffect7, useState as useState6 } from "react";
|
|
2225
2632
|
import { twMerge as twMerge2 } from "tailwind-merge";
|
|
2226
2633
|
import {
|
|
2227
|
-
usePlayer as
|
|
2634
|
+
usePlayer as usePlayer10,
|
|
2228
2635
|
PlayerVideoSurface
|
|
2229
2636
|
} from "@behindthemusictree/app-kit/player";
|
|
2230
2637
|
import {
|
|
2231
|
-
useTrackListSidebarVisibility
|
|
2638
|
+
useTrackListSidebarVisibility
|
|
2232
2639
|
} from "@behindthemusictree/app-kit/genre-tree";
|
|
2233
|
-
|
|
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";
|
|
2234
2753
|
var PANEL_ID = "player-panel";
|
|
2235
|
-
var iconButtonClassName = "flex h-6 w-6 shrink-0 items-center justify-center p-0 rounded-md hover:bg-white/10";
|
|
2236
2754
|
var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
|
|
2237
2755
|
function PlayerPanel({
|
|
2238
|
-
className,
|
|
2756
|
+
className: className2,
|
|
2239
2757
|
isVisible = true,
|
|
2240
|
-
onClose,
|
|
2241
2758
|
expanded,
|
|
2242
2759
|
onExpandedChange,
|
|
2243
2760
|
extendedWidth = DEFAULT_EXTENDED_WIDTH,
|
|
2244
2761
|
renderArtistLink
|
|
2245
2762
|
}) {
|
|
2246
|
-
const { playerTrackObject } =
|
|
2247
|
-
const { toggleTrackListSidebar, isTrackListSidebarVisible } =
|
|
2248
|
-
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);
|
|
2249
2771
|
const isExtendRequested = expanded ?? uncontrolledExtended;
|
|
2250
2772
|
const setIsExtendRequested = (next) => {
|
|
2251
2773
|
setUncontrolledExtended(next);
|
|
2252
2774
|
onExpandedChange?.(next);
|
|
2253
2775
|
};
|
|
2254
2776
|
const isExtended = isExtendRequested && isTrackListSidebarVisible;
|
|
2255
|
-
const
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
} : { label: "Hide song list", onClick: toggleTrackListSidebar };
|
|
2259
|
-
const tabCenter = !isTrackListSidebarVisible ? { label: "Show song list", onClick: toggleTrackListSidebar } : isExtended ? null : {
|
|
2260
|
-
label: "Extend song list panel",
|
|
2261
|
-
onClick: () => setIsExtendRequested(true)
|
|
2777
|
+
const hideSongList = () => {
|
|
2778
|
+
setIsExtendRequested(false);
|
|
2779
|
+
if (isTrackListSidebarVisible) toggleTrackListSidebar();
|
|
2262
2780
|
};
|
|
2263
|
-
|
|
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(
|
|
2264
2792
|
"div",
|
|
2265
2793
|
{
|
|
2266
2794
|
id: PANEL_ID,
|
|
@@ -2269,11 +2797,11 @@ function PlayerPanel({
|
|
|
2269
2797
|
!isExtended && "w-[var(--layout-right-w)]",
|
|
2270
2798
|
isTrackListSidebarVisible && "h-[calc(100vh-var(--layout-band-top)-3.5rem)]",
|
|
2271
2799
|
!isVisible && "hidden",
|
|
2272
|
-
|
|
2800
|
+
className2
|
|
2273
2801
|
),
|
|
2274
2802
|
style: isExtended ? { width: extendedWidth } : void 0,
|
|
2275
2803
|
children: [
|
|
2276
|
-
/* @__PURE__ */
|
|
2804
|
+
/* @__PURE__ */ jsx30(
|
|
2277
2805
|
PanelEdgeTab,
|
|
2278
2806
|
{
|
|
2279
2807
|
side: "right",
|
|
@@ -2283,7 +2811,7 @@ function PlayerPanel({
|
|
|
2283
2811
|
className: "absolute right-full top-1/2 -translate-y-1/2"
|
|
2284
2812
|
}
|
|
2285
2813
|
),
|
|
2286
|
-
/* @__PURE__ */
|
|
2814
|
+
/* @__PURE__ */ jsxs23(
|
|
2287
2815
|
"div",
|
|
2288
2816
|
{
|
|
2289
2817
|
className: twMerge2(
|
|
@@ -2291,29 +2819,21 @@ function PlayerPanel({
|
|
|
2291
2819
|
isExtended ? "flex-row" : "flex-col"
|
|
2292
2820
|
),
|
|
2293
2821
|
children: [
|
|
2294
|
-
/* @__PURE__ */
|
|
2295
|
-
/* @__PURE__ */
|
|
2296
|
-
|
|
2297
|
-
/* @__PURE__ */
|
|
2298
|
-
|
|
2299
|
-
{
|
|
2300
|
-
type: "button",
|
|
2301
|
-
onClick: onClose,
|
|
2302
|
-
className: iconButtonClassName,
|
|
2303
|
-
"aria-label": "Close player",
|
|
2304
|
-
children: /* @__PURE__ */ jsx26(X2, { size: 16 })
|
|
2305
|
-
}
|
|
2306
|
-
) })
|
|
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 })
|
|
2307
2827
|
] }),
|
|
2308
|
-
/* @__PURE__ */
|
|
2309
|
-
/* @__PURE__ */ jsx26(PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
|
|
2310
|
-
playerTrackObject?.loadError && /* @__PURE__ */ jsx26("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: playerTrackObject.loadError })
|
|
2311
|
-
] })
|
|
2828
|
+
/* @__PURE__ */ jsx30(PlayerControls, {})
|
|
2312
2829
|
] }),
|
|
2313
|
-
|
|
2830
|
+
/* @__PURE__ */ jsx30(
|
|
2314
2831
|
TrackListSidebar,
|
|
2315
2832
|
{
|
|
2316
2833
|
layout: "inline",
|
|
2834
|
+
isCollapsed: !isTrackListSidebarVisible,
|
|
2835
|
+
onExpand: toggleTrackListSidebar,
|
|
2836
|
+
onMinimize: hideSongList,
|
|
2317
2837
|
className: twMerge2(
|
|
2318
2838
|
"min-h-0 min-w-0 flex-1",
|
|
2319
2839
|
isExtended && "border-l border-zinc-800"
|
|
@@ -2333,11 +2853,11 @@ function PlayerPanel({
|
|
|
2333
2853
|
import { v4 as uuidv4 } from "uuid";
|
|
2334
2854
|
import { AiFillStar, AiOutlineStar } from "react-icons/ai";
|
|
2335
2855
|
import { FORM_RATING_NULL_VALUE } from "@behindthemusictree/app-kit/genre-tree";
|
|
2336
|
-
import { jsx as
|
|
2856
|
+
import { jsx as jsx31, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
2337
2857
|
function Rating({ rating, handleChange }) {
|
|
2338
2858
|
const instanceId = uuidv4();
|
|
2339
|
-
return /* @__PURE__ */
|
|
2340
|
-
/* @__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(
|
|
2341
2861
|
"input",
|
|
2342
2862
|
{
|
|
2343
2863
|
id: `star-${instanceId}-${i}`,
|
|
@@ -2349,16 +2869,16 @@ function Rating({ rating, handleChange }) {
|
|
|
2349
2869
|
onChange: handleChange
|
|
2350
2870
|
}
|
|
2351
2871
|
),
|
|
2352
|
-
/* @__PURE__ */
|
|
2353
|
-
/* @__PURE__ */
|
|
2872
|
+
/* @__PURE__ */ jsx31("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ jsxs24("div", { className: "flex", children: [
|
|
2873
|
+
/* @__PURE__ */ jsx31(
|
|
2354
2874
|
AiOutlineStar,
|
|
2355
2875
|
{
|
|
2356
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)]"}`
|
|
2357
2877
|
}
|
|
2358
2878
|
),
|
|
2359
|
-
/* @__PURE__ */
|
|
2879
|
+
/* @__PURE__ */ jsx31("span", { className: "flex-grow w-8" }),
|
|
2360
2880
|
" "
|
|
2361
|
-
] }) : /* @__PURE__ */
|
|
2881
|
+
] }) : /* @__PURE__ */ jsx31(
|
|
2362
2882
|
AiFillStar,
|
|
2363
2883
|
{
|
|
2364
2884
|
className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
|
|
@@ -2382,4 +2902,4 @@ export {
|
|
|
2382
2902
|
GenreTreeViewSkeleton2 as GenreTreeViewSkeleton,
|
|
2383
2903
|
GenreTreeOutlineSkeleton
|
|
2384
2904
|
};
|
|
2385
|
-
//# sourceMappingURL=chunk-
|
|
2905
|
+
//# sourceMappingURL=chunk-5PQWJRDZ.js.map
|