@behindthemusictree/app-kit-ui 28.0.2 → 29.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-5PQWJRDZ.js → chunk-TFLX6C5Q.js} +1018 -850
- package/dist/chunk-TFLX6C5Q.js.map +1 -0
- package/dist/genre-tree/index.cjs +1106 -941
- package/dist/genre-tree/index.cjs.map +1 -1
- package/dist/genre-tree/index.d.ts +119 -12
- package/dist/genre-tree/index.js +13 -3
- package/dist/index.cjs +1187 -1022
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +13 -3
- package/package.json +6 -6
- package/dist/chunk-5PQWJRDZ.js.map +0 -1
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from "./chunk-DKQQLLMY.js";
|
|
6
6
|
|
|
7
7
|
// src/genre-tree/GenreTreeView.tsx
|
|
8
|
-
import { useCallback as useCallback6, useDeferredValue, useEffect as
|
|
8
|
+
import { useCallback as useCallback6, useDeferredValue, useEffect as useEffect6, useMemo as useMemo7, useRef as useRef4, useState as useState5 } from "react";
|
|
9
9
|
import { useIsMutating } from "@tanstack/react-query";
|
|
10
10
|
import { Plus as Plus2 } from "lucide-react";
|
|
11
11
|
import { IconTextButton, Button, SegmentedControl as SegmentedControl2 } from "@behindthemusictree/ui";
|
|
@@ -17,429 +17,108 @@ import {
|
|
|
17
17
|
useArtistSearch
|
|
18
18
|
} from "@behindthemusictree/app-kit/genre-tree";
|
|
19
19
|
import { useGenreTreeViewState, useArtist as useArtist2, useUpdateGenre as useUpdateGenre2, genreUpdateMutationKey } from "@behindthemusictree/app-kit/genre-tree";
|
|
20
|
-
import { usePlayer as usePlayer8 } from "@behindthemusictree/app-kit/player";
|
|
21
20
|
import { usePopup as usePopup5 } from "@behindthemusictree/app-kit/popup";
|
|
22
21
|
|
|
23
|
-
// src/genre-tree/
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
30
|
-
function ClampedText({ children, className: className2 = "" }) {
|
|
31
|
-
const ref = useRef(null);
|
|
32
|
-
const [expanded, setExpanded] = useState(false);
|
|
33
|
-
const [overflows, setOverflows] = useState(false);
|
|
34
|
-
useLayoutEffect(() => {
|
|
35
|
-
const element = ref.current;
|
|
36
|
-
if (!element || expanded) return;
|
|
37
|
-
setOverflows(element.scrollHeight > element.clientHeight);
|
|
38
|
-
}, [children, expanded]);
|
|
39
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
40
|
-
/* @__PURE__ */ jsx("p", { ref, className: `${className2} ${expanded ? "" : "line-clamp-3"}`, children }),
|
|
41
|
-
(overflows || expanded) && /* @__PURE__ */ jsx(
|
|
42
|
-
"button",
|
|
43
|
-
{
|
|
44
|
-
type: "button",
|
|
45
|
-
"aria-expanded": expanded,
|
|
46
|
-
onClick: () => setExpanded((value) => !value),
|
|
47
|
-
className: "mt-1 text-xs text-[var(--color-text-muted)] underline hover:text-[var(--color-text)]",
|
|
48
|
-
children: expanded ? "Show less" : "Show more"
|
|
49
|
-
}
|
|
50
|
-
)
|
|
51
|
-
] });
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// src/genre-tree/GenrePlaylistTracks.tsx
|
|
55
|
-
import { useEffect, useRef as useRef2 } from "react";
|
|
56
|
-
import { useInfiniteQuery } from "@tanstack/react-query";
|
|
57
|
-
import { z } from "zod";
|
|
58
|
-
import { FaPlay, FaPause } from "react-icons/fa";
|
|
59
|
-
import { RingLoader, Skeleton } from "@behindthemusictree/ui";
|
|
60
|
-
import { useSession } from "@behindthemusictree/app-kit/auth";
|
|
61
|
-
import { useFetchWrapper } from "@behindthemusictree/app-kit/transport";
|
|
62
|
-
import { PaginatedResponseSchema } from "@behindthemusictree/app-kit/transport";
|
|
63
|
-
import { parseWithLog } from "@behindthemusictree/app-kit/transport";
|
|
22
|
+
// src/genre-tree/playlist-tree/TreePerRoot.tsx
|
|
23
|
+
import { useCallback as useCallback2, useMemo as useMemo2 } from "react";
|
|
24
|
+
import {
|
|
25
|
+
GenreTree,
|
|
26
|
+
getGenreTreeColor
|
|
27
|
+
} from "@behindthemusictree/genre-tree-view";
|
|
64
28
|
import { usePlayer } from "@behindthemusictree/app-kit/player";
|
|
65
|
-
import { PlayStates } from "@behindthemusictree/app-kit/player";
|
|
66
29
|
import { usePopup } from "@behindthemusictree/app-kit/popup";
|
|
67
30
|
import { ErrorCode } from "@behindthemusictree/app-kit/transport";
|
|
68
|
-
import { genrePlaylistQueryKeys } from "@behindthemusictree/app-kit/genre-tree";
|
|
69
|
-
import { libraryEndpoints, libraryQueryKeys } from "@behindthemusictree/app-kit/genre-tree";
|
|
70
|
-
import { isSong, songUnplayableReason, SongDetailedSchema } from "@behindthemusictree/app-kit/genre-tree";
|
|
71
|
-
import { useTrackList } from "@behindthemusictree/app-kit/genre-tree";
|
|
72
31
|
|
|
73
|
-
// src/genre-tree/
|
|
74
|
-
import {
|
|
75
|
-
import {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
32
|
+
// src/genre-tree/playlist-tree/useGenrePlaylistPlayback.ts
|
|
33
|
+
import { useCallback, useMemo } from "react";
|
|
34
|
+
import {
|
|
35
|
+
TrackListOriginType,
|
|
36
|
+
useTrackList
|
|
37
|
+
} from "@behindthemusictree/app-kit/genre-tree";
|
|
38
|
+
function useGenrePlaylistPlayback({
|
|
39
|
+
scope,
|
|
40
|
+
genrePlaylists,
|
|
41
|
+
artist = null
|
|
80
42
|
}) {
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
43
|
+
const { trackList, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist } = useTrackList();
|
|
44
|
+
const origin = trackList?.origin;
|
|
45
|
+
const playingNodeId = useMemo(() => {
|
|
46
|
+
if (!origin) return null;
|
|
47
|
+
if (!artist) return origin.type === TrackListOriginType.GENRE_PLAYLIST ? origin.uuid : null;
|
|
48
|
+
if (origin.type !== TrackListOriginType.ARTIST || origin.uuid !== artist.uuid) return null;
|
|
49
|
+
const { genreUuid } = origin;
|
|
50
|
+
return genreUuid ? genrePlaylists.find((gp) => gp.criteria?.uuid === genreUuid)?.uuid ?? null : null;
|
|
51
|
+
}, [origin, artist, genrePlaylists]);
|
|
52
|
+
const play = useCallback(
|
|
53
|
+
async (genrePlaylist) => {
|
|
54
|
+
if (!artist) return playNewTrackListFromGenrePlaylist(genrePlaylist, scope);
|
|
55
|
+
if (!genrePlaylist.criteria) return;
|
|
56
|
+
return playNewTrackListFromArtist(artist, scope, { ordering: "title", genreUuid: genrePlaylist.criteria.uuid });
|
|
57
|
+
},
|
|
58
|
+
[artist, scope, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist]
|
|
94
59
|
);
|
|
60
|
+
return useMemo(() => ({ playingNodeId, play }), [playingNodeId, play]);
|
|
95
61
|
}
|
|
96
62
|
|
|
97
|
-
// src/genre-tree/
|
|
98
|
-
import { ArtistNames } from "@behindthemusictree/app-kit/genre-tree";
|
|
63
|
+
// src/genre-tree/playlist-tree/TreePerRoot.tsx
|
|
99
64
|
import { capitalize, useTrackNoun } from "@behindthemusictree/app-kit/track-noun";
|
|
100
|
-
import { jsx
|
|
101
|
-
|
|
102
|
-
var SongPageSchema = PaginatedResponseSchema(SongDetailedSchema).extend({
|
|
103
|
-
overallTotal: z.number().nullable(),
|
|
104
|
-
page: z.number().nullable(),
|
|
105
|
-
totalPages: z.number().nullable()
|
|
106
|
-
});
|
|
107
|
-
var TEXT_CELL = "min-w-0 flex-1 truncate [@container(min-width:40rem)]:flex [@container(min-width:40rem)]:gap-2";
|
|
108
|
-
var TITLE_CELL = "[@container(min-width:40rem)]:flex-[2] [@container(min-width:40rem)]:truncate";
|
|
109
|
-
var COLUMN_CELL = "[@container(min-width:40rem)]:flex-1 [@container(min-width:40rem)]:truncate";
|
|
110
|
-
var ALBUM_CELL = `hidden [@container(min-width:40rem)]:block ${COLUMN_CELL}`;
|
|
111
|
-
var SEPARATOR = "[@container(min-width:40rem)]:hidden";
|
|
112
|
-
var nextCursor = (next) => {
|
|
113
|
-
const cursor = new URL(next).searchParams.get("cursor");
|
|
114
|
-
if (!cursor) throw new Error(`Songs next link has no cursor: ${next}`);
|
|
115
|
-
return cursor;
|
|
116
|
-
};
|
|
117
|
-
function GenrePlaylistTracks({
|
|
118
|
-
genreUuid,
|
|
119
|
-
genrePlaylist,
|
|
65
|
+
import { jsx } from "react/jsx-runtime";
|
|
66
|
+
function GenrePlaylistTreePerRoot({
|
|
120
67
|
scope,
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
68
|
+
className: className2,
|
|
69
|
+
rootUuid,
|
|
70
|
+
genrePlaylistTreePerRoot,
|
|
71
|
+
handleGenreCreationAction,
|
|
72
|
+
handleGenreRenameAction,
|
|
73
|
+
additionalActions,
|
|
74
|
+
onNodeClick,
|
|
75
|
+
onNodeHover,
|
|
76
|
+
renderInfoPanelBody,
|
|
77
|
+
selectedNodeId,
|
|
78
|
+
onSelectedNodeChange,
|
|
79
|
+
hideInfoPanelClose,
|
|
80
|
+
hideInfoPanelTitle,
|
|
81
|
+
readOnly = false,
|
|
82
|
+
showToolbar,
|
|
83
|
+
wheelZoom,
|
|
84
|
+
defaultViewport,
|
|
85
|
+
viewportRestore,
|
|
86
|
+
onViewportChange,
|
|
87
|
+
playback,
|
|
88
|
+
...infoPanelCuration
|
|
127
89
|
}) {
|
|
128
|
-
const {
|
|
129
|
-
const {
|
|
130
|
-
const {
|
|
131
|
-
const { showPopup } = usePopup();
|
|
90
|
+
const { isPlaying, setIsPlaying } = usePlayer();
|
|
91
|
+
const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists: genrePlaylistTreePerRoot });
|
|
92
|
+
const { playingNodeId, play } = playback ?? defaultPlayback;
|
|
132
93
|
const trackNoun = useTrackNoun();
|
|
133
|
-
const {
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
useEffect(() => {
|
|
155
|
-
const sentinel = sentinelRef.current;
|
|
156
|
-
if (!hasNextPage || isFetchingNextPage || !sentinel) return;
|
|
157
|
-
const observer = new IntersectionObserver((entries) => {
|
|
158
|
-
if (entries.some((entry) => entry.isIntersecting)) {
|
|
159
|
-
void fetchNextPage();
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
|
-
observer.observe(sentinel);
|
|
163
|
-
return () => observer.disconnect();
|
|
164
|
-
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);
|
|
165
|
-
const pages = data?.pages ?? [];
|
|
166
|
-
const tracks = pages.flatMap((page) => page.tracks);
|
|
167
|
-
const total = pages[0]?.total ?? void 0;
|
|
168
|
-
const play = (startSongUuid) => {
|
|
169
|
-
const started = artist ? playNewTrackListFromArtist(artist, scope, { startSongUuid, ordering: "title", genreUuid }) : playNewTrackListFromGenrePlaylist(genrePlaylist, scope, { startSongUuid });
|
|
170
|
-
started.catch((error) => {
|
|
171
|
-
console.error("Failed to play genre playlist:", error);
|
|
172
|
-
showPopup(/* @__PURE__ */ jsx3(InternalErrorPopup, { errorCode: ErrorCode.CLIENT_UNKNOWN }));
|
|
173
|
-
});
|
|
174
|
-
};
|
|
175
|
-
return /* @__PURE__ */ jsxs2("div", { className: "gtv-info-panel-children", children: [
|
|
176
|
-
/* @__PURE__ */ jsxs2("span", { className: "gtv-info-panel-children-title", children: [
|
|
177
|
-
capitalize(trackNoun.plural),
|
|
178
|
-
total !== void 0 && ` (${total})`
|
|
179
|
-
] }),
|
|
180
|
-
renderTracksToolbar?.(),
|
|
181
|
-
isPending ? /* @__PURE__ */ jsx3(Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ jsxs2("p", { role: "alert", children: [
|
|
182
|
-
"Couldn't load ",
|
|
183
|
-
trackNoun.plural,
|
|
184
|
-
".",
|
|
185
|
-
" ",
|
|
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: [
|
|
188
|
-
tracks.map((track, index) => {
|
|
189
|
-
const label = track.artists?.length ? `${track.title} \u2014 ${track.artists.map((artist2) => artist2.name).join(", ")}` : track.title;
|
|
190
|
-
const isCurrent = playerTrackObject?.track.id === track.uuid;
|
|
191
|
-
const isPlaying = isCurrent && playState === PlayStates.PLAYING;
|
|
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 })
|
|
197
|
-
] });
|
|
198
|
-
const leading = renderSongLeading?.(track, index);
|
|
199
|
-
const actions = renderSongActions?.(track);
|
|
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: [
|
|
203
|
-
leading,
|
|
204
|
-
/* @__PURE__ */ jsx3(
|
|
205
|
-
"button",
|
|
206
|
-
{
|
|
207
|
-
type: "button",
|
|
208
|
-
disabled: true,
|
|
209
|
-
"aria-disabled": "true",
|
|
210
|
-
"aria-label": label,
|
|
211
|
-
className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full opacity-0 cursor-not-allowed",
|
|
212
|
-
children: /* @__PURE__ */ jsx3(FaPlay, { size: 10 })
|
|
213
|
-
}
|
|
214
|
-
),
|
|
215
|
-
text,
|
|
216
|
-
actions
|
|
217
|
-
] }, track.uuid);
|
|
218
|
-
}
|
|
219
|
-
return /* @__PURE__ */ jsxs2(
|
|
220
|
-
"li",
|
|
221
|
-
{
|
|
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)]" : ""}`,
|
|
223
|
-
children: [
|
|
224
|
-
leading,
|
|
225
|
-
/* @__PURE__ */ jsx3(
|
|
226
|
-
"button",
|
|
227
|
-
{
|
|
228
|
-
type: "button",
|
|
229
|
-
"aria-label": `${isPlaying ? "Pause" : "Play"} ${label}`,
|
|
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"}`,
|
|
231
|
-
onClick: () => isCurrent ? handlePlayPauseAction() : play(track.uuid),
|
|
232
|
-
children: isCurrent && playState === PlayStates.LOADING ? /* @__PURE__ */ jsx3(RingLoader, { size: 10 }) : isPlaying ? /* @__PURE__ */ jsx3(FaPause, { size: 10 }) : /* @__PURE__ */ jsx3(FaPlay, { size: 10 })
|
|
233
|
-
}
|
|
234
|
-
),
|
|
235
|
-
text,
|
|
236
|
-
actions
|
|
237
|
-
]
|
|
238
|
-
},
|
|
239
|
-
track.uuid
|
|
240
|
-
);
|
|
241
|
-
}),
|
|
242
|
-
hasNextPage && /* @__PURE__ */ jsx3("li", { ref: sentinelRef, "aria-hidden": "true", className: "h-4" })
|
|
243
|
-
] })
|
|
244
|
-
] });
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// src/genre-tree/GenreDetailExtras.tsx
|
|
248
|
-
import { useTrackNoun as useTrackNoun2 } from "@behindthemusictree/app-kit/track-noun";
|
|
249
|
-
import { SiWikidata, SiWikipedia } from "react-icons/si";
|
|
250
|
-
import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
251
|
-
function GenreDetailExtras({
|
|
252
|
-
genreUuid,
|
|
253
|
-
genrePlaylist,
|
|
254
|
-
wikidataId,
|
|
255
|
-
scope,
|
|
256
|
-
getBackendBaseUrl,
|
|
257
|
-
criteriaOverviewSchema,
|
|
258
|
-
renderGenreDetailExtras,
|
|
259
|
-
renderArtistLink,
|
|
260
|
-
renderSummary,
|
|
261
|
-
renderEssentialTracks,
|
|
262
|
-
family,
|
|
263
|
-
...trackSlots
|
|
264
|
-
}) {
|
|
265
|
-
const trackNoun = useTrackNoun2();
|
|
266
|
-
const { data: overview, isPending } = useFetchGenreOverview(
|
|
267
|
-
genreUuid,
|
|
268
|
-
scope,
|
|
269
|
-
getBackendBaseUrl,
|
|
270
|
-
criteriaOverviewSchema
|
|
271
|
-
);
|
|
272
|
-
if (isPending) {
|
|
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" })
|
|
277
|
-
] }),
|
|
278
|
-
family
|
|
279
|
-
] });
|
|
280
|
-
}
|
|
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)
|
|
319
|
-
] }),
|
|
320
|
-
family,
|
|
321
|
-
renderGenreDetailExtras?.(overview),
|
|
322
|
-
renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && /* @__PURE__ */ jsxs3("div", { className: "gtv-info-panel-children text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: [
|
|
323
|
-
/* @__PURE__ */ jsxs3("span", { className: "gtv-info-panel-children-title", children: [
|
|
324
|
-
"Essential ",
|
|
325
|
-
trackNoun.plural
|
|
326
|
-
] }),
|
|
327
|
-
essentialSongs.length > 0 ? /* @__PURE__ */ jsx4("ul", { className: "mt-1 divide-y divide-[var(--color-border)]", children: essentialSongs.map((track) => /* @__PURE__ */ jsx4("li", { className: "py-1", children: track.title }, track.uuid)) }) : /* @__PURE__ */ jsx4("p", { className: "mt-1", children: "\u2014" })
|
|
328
|
-
] }),
|
|
329
|
-
/* @__PURE__ */ jsx4(
|
|
330
|
-
GenrePlaylistTracks,
|
|
331
|
-
{
|
|
332
|
-
genreUuid,
|
|
333
|
-
genrePlaylist,
|
|
334
|
-
scope,
|
|
335
|
-
getBackendBaseUrl,
|
|
336
|
-
renderArtistLink,
|
|
337
|
-
...trackSlots
|
|
338
|
-
}
|
|
339
|
-
)
|
|
340
|
-
] });
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// src/genre-tree/playlist-tree/TreePerRoot.tsx
|
|
344
|
-
import { useCallback as useCallback2, useMemo as useMemo2 } from "react";
|
|
345
|
-
import {
|
|
346
|
-
GenreTree,
|
|
347
|
-
getGenreTreeColor
|
|
348
|
-
} from "@behindthemusictree/genre-tree-view";
|
|
349
|
-
import { usePlayer as usePlayer2 } from "@behindthemusictree/app-kit/player";
|
|
350
|
-
import { usePopup as usePopup2 } from "@behindthemusictree/app-kit/popup";
|
|
351
|
-
import { ErrorCode as ErrorCode2 } from "@behindthemusictree/app-kit/transport";
|
|
352
|
-
|
|
353
|
-
// src/genre-tree/playlist-tree/useGenrePlaylistPlayback.ts
|
|
354
|
-
import { useCallback, useMemo } from "react";
|
|
355
|
-
import {
|
|
356
|
-
TrackListOriginType,
|
|
357
|
-
useTrackList as useTrackList2
|
|
358
|
-
} from "@behindthemusictree/app-kit/genre-tree";
|
|
359
|
-
function useGenrePlaylistPlayback({
|
|
360
|
-
scope,
|
|
361
|
-
genrePlaylists,
|
|
362
|
-
artist = null
|
|
363
|
-
}) {
|
|
364
|
-
const { trackList, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist } = useTrackList2();
|
|
365
|
-
const origin = trackList?.origin;
|
|
366
|
-
const playingNodeId = useMemo(() => {
|
|
367
|
-
if (!origin) return null;
|
|
368
|
-
if (!artist) return origin.type === TrackListOriginType.GENRE_PLAYLIST ? origin.uuid : null;
|
|
369
|
-
if (origin.type !== TrackListOriginType.ARTIST || origin.uuid !== artist.uuid) return null;
|
|
370
|
-
const { genreUuid } = origin;
|
|
371
|
-
return genreUuid ? genrePlaylists.find((gp) => gp.criteria?.uuid === genreUuid)?.uuid ?? null : null;
|
|
372
|
-
}, [origin, artist, genrePlaylists]);
|
|
373
|
-
const play = useCallback(
|
|
374
|
-
async (genrePlaylist) => {
|
|
375
|
-
if (!artist) return playNewTrackListFromGenrePlaylist(genrePlaylist, scope);
|
|
376
|
-
if (!genrePlaylist.criteria) return;
|
|
377
|
-
return playNewTrackListFromArtist(artist, scope, { ordering: "title", genreUuid: genrePlaylist.criteria.uuid });
|
|
378
|
-
},
|
|
379
|
-
[artist, scope, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist]
|
|
380
|
-
);
|
|
381
|
-
return useMemo(() => ({ playingNodeId, play }), [playingNodeId, play]);
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
// src/genre-tree/playlist-tree/TreePerRoot.tsx
|
|
385
|
-
import { capitalize as capitalize2, useTrackNoun as useTrackNoun3 } from "@behindthemusictree/app-kit/track-noun";
|
|
386
|
-
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
387
|
-
function GenrePlaylistTreePerRoot({
|
|
388
|
-
scope,
|
|
389
|
-
className: className2,
|
|
390
|
-
rootUuid,
|
|
391
|
-
genrePlaylistTreePerRoot,
|
|
392
|
-
handleGenreCreationAction,
|
|
393
|
-
handleGenreRenameAction,
|
|
394
|
-
additionalActions,
|
|
395
|
-
onNodeClick,
|
|
396
|
-
onNodeHover,
|
|
397
|
-
renderInfoPanelBody,
|
|
398
|
-
selectedNodeId,
|
|
399
|
-
onSelectedNodeChange,
|
|
400
|
-
hideInfoPanelClose,
|
|
401
|
-
hideInfoPanelTitle,
|
|
402
|
-
readOnly = false,
|
|
403
|
-
showToolbar,
|
|
404
|
-
wheelZoom,
|
|
405
|
-
controlsOrientation,
|
|
406
|
-
defaultViewport,
|
|
407
|
-
onViewportChange,
|
|
408
|
-
playback,
|
|
409
|
-
...infoPanelCuration
|
|
410
|
-
}) {
|
|
411
|
-
const { isPlaying, setIsPlaying } = usePlayer2();
|
|
412
|
-
const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists: genrePlaylistTreePerRoot });
|
|
413
|
-
const { playingNodeId, play } = playback ?? defaultPlayback;
|
|
414
|
-
const trackNoun = useTrackNoun3();
|
|
415
|
-
const { showPopup } = usePopup2();
|
|
416
|
-
const nodes = useMemo2(
|
|
417
|
-
() => genrePlaylistTreePerRoot.map((genrePlaylist) => ({
|
|
418
|
-
id: genrePlaylist.uuid,
|
|
419
|
-
parentId: genrePlaylist.parent?.uuid ?? null,
|
|
420
|
-
name: genrePlaylist.name,
|
|
421
|
-
itemCount: genrePlaylist.tracksCount,
|
|
422
|
-
actionable: Boolean(genrePlaylist.criteria),
|
|
423
|
-
side: genrePlaylist.criteria?.side ?? void 0,
|
|
424
|
-
wikidataId: genrePlaylist.criteria?.wikidataId ?? null,
|
|
425
|
-
umbrella: genrePlaylist.isUmbrella
|
|
426
|
-
})),
|
|
427
|
-
[genrePlaylistTreePerRoot]
|
|
428
|
-
);
|
|
429
|
-
const handlePlayPause = useCallback2(
|
|
430
|
-
(nodeId) => {
|
|
431
|
-
const genrePlaylist = genrePlaylistTreePerRoot.find((g) => g.uuid === nodeId);
|
|
432
|
-
if (!genrePlaylist) return;
|
|
433
|
-
if (nodeId === playingNodeId) {
|
|
434
|
-
setIsPlaying(!isPlaying);
|
|
435
|
-
return;
|
|
94
|
+
const { showPopup } = usePopup();
|
|
95
|
+
const nodes = useMemo2(
|
|
96
|
+
() => genrePlaylistTreePerRoot.map((genrePlaylist) => ({
|
|
97
|
+
id: genrePlaylist.uuid,
|
|
98
|
+
parentId: genrePlaylist.parent?.uuid ?? null,
|
|
99
|
+
name: genrePlaylist.name,
|
|
100
|
+
itemCount: genrePlaylist.tracksCount,
|
|
101
|
+
actionable: Boolean(genrePlaylist.criteria),
|
|
102
|
+
side: genrePlaylist.criteria?.side ?? void 0,
|
|
103
|
+
wikidataId: genrePlaylist.criteria?.wikidataId ?? null,
|
|
104
|
+
umbrella: genrePlaylist.isUmbrella
|
|
105
|
+
})),
|
|
106
|
+
[genrePlaylistTreePerRoot]
|
|
107
|
+
);
|
|
108
|
+
const handlePlayPause = useCallback2(
|
|
109
|
+
(nodeId) => {
|
|
110
|
+
const genrePlaylist = genrePlaylistTreePerRoot.find((g) => g.uuid === nodeId);
|
|
111
|
+
if (!genrePlaylist) return;
|
|
112
|
+
if (nodeId === playingNodeId) {
|
|
113
|
+
setIsPlaying(!isPlaying);
|
|
114
|
+
return;
|
|
436
115
|
}
|
|
437
116
|
if (genrePlaylist.tracksCount === 0) {
|
|
438
117
|
return;
|
|
439
118
|
}
|
|
440
119
|
play(genrePlaylist).catch((error) => {
|
|
441
120
|
console.error("Failed to load genre playlist tracks:", error);
|
|
442
|
-
showPopup(/* @__PURE__ */
|
|
121
|
+
showPopup(/* @__PURE__ */ jsx(InternalErrorPopup, { errorCode: ErrorCode.CLIENT_UNKNOWN }));
|
|
443
122
|
});
|
|
444
123
|
},
|
|
445
124
|
[
|
|
@@ -471,12 +150,12 @@ function GenrePlaylistTreePerRoot({
|
|
|
471
150
|
if (confirm(`Are you sure you want to delete "${node.name}"?`)) {
|
|
472
151
|
}
|
|
473
152
|
}, []);
|
|
474
|
-
return /* @__PURE__ */
|
|
153
|
+
return /* @__PURE__ */ jsx(
|
|
475
154
|
GenreTree,
|
|
476
155
|
{
|
|
477
156
|
className: className2,
|
|
478
157
|
nodes,
|
|
479
|
-
itemCountLabel: `${
|
|
158
|
+
itemCountLabel: `${capitalize(trackNoun.singular)} count`,
|
|
480
159
|
rootColor: getGenreTreeColor(rootUuid),
|
|
481
160
|
playingNodeId,
|
|
482
161
|
playState: isPlaying ? "playing" : "paused",
|
|
@@ -494,8 +173,8 @@ function GenrePlaylistTreePerRoot({
|
|
|
494
173
|
hideInfoPanelTitle,
|
|
495
174
|
showToolbar,
|
|
496
175
|
wheelZoom,
|
|
497
|
-
controlsOrientation,
|
|
498
176
|
defaultViewport,
|
|
177
|
+
viewportRestore,
|
|
499
178
|
onViewportChange,
|
|
500
179
|
...infoPanelCuration,
|
|
501
180
|
readOnly
|
|
@@ -508,9 +187,9 @@ import { useCallback as useCallback3, useMemo as useMemo3 } from "react";
|
|
|
508
187
|
import {
|
|
509
188
|
GenreTreeWheel
|
|
510
189
|
} from "@behindthemusictree/genre-tree-view";
|
|
511
|
-
import { usePlayer as
|
|
512
|
-
import { usePopup as
|
|
513
|
-
import { ErrorCode as
|
|
190
|
+
import { usePlayer as usePlayer2 } from "@behindthemusictree/app-kit/player";
|
|
191
|
+
import { usePopup as usePopup2 } from "@behindthemusictree/app-kit/popup";
|
|
192
|
+
import { ErrorCode as ErrorCode2 } from "@behindthemusictree/app-kit/transport";
|
|
514
193
|
|
|
515
194
|
// src/genre-tree/playlist-tree/size-metric.ts
|
|
516
195
|
var hasViewsCount = (genrePlaylists) => genrePlaylists.every((genrePlaylist) => genrePlaylist.viewsCount !== void 0);
|
|
@@ -523,8 +202,8 @@ var genrePlaylistSize = (genrePlaylist, metric) => {
|
|
|
523
202
|
};
|
|
524
203
|
|
|
525
204
|
// src/genre-tree/playlist-tree/TreeWheel.tsx
|
|
526
|
-
import { capitalize as
|
|
527
|
-
import { jsx as
|
|
205
|
+
import { capitalize as capitalize2, useTrackNoun as useTrackNoun2 } from "@behindthemusictree/app-kit/track-noun";
|
|
206
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
528
207
|
function GenrePlaylistTreeWheel({
|
|
529
208
|
scope,
|
|
530
209
|
className: className2,
|
|
@@ -542,20 +221,20 @@ function GenrePlaylistTreeWheel({
|
|
|
542
221
|
readOnly = false,
|
|
543
222
|
allowWheelRotation,
|
|
544
223
|
showToolbar,
|
|
545
|
-
controlsOrientation,
|
|
546
224
|
sizeMetric = "songs",
|
|
547
225
|
floatingControlsLeading,
|
|
548
226
|
busy,
|
|
549
227
|
defaultViewport,
|
|
228
|
+
viewportRestore,
|
|
550
229
|
onViewportChange,
|
|
551
230
|
playback,
|
|
552
231
|
...infoPanelCuration
|
|
553
232
|
}) {
|
|
554
|
-
const { isPlaying, setIsPlaying } =
|
|
233
|
+
const { isPlaying, setIsPlaying } = usePlayer2();
|
|
555
234
|
const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists });
|
|
556
235
|
const { playingNodeId, play } = playback ?? defaultPlayback;
|
|
557
|
-
const trackNoun =
|
|
558
|
-
const { showPopup } =
|
|
236
|
+
const trackNoun = useTrackNoun2();
|
|
237
|
+
const { showPopup } = usePopup2();
|
|
559
238
|
const nodes = useMemo3(
|
|
560
239
|
() => genrePlaylists.map((genrePlaylist) => ({
|
|
561
240
|
id: genrePlaylist.uuid,
|
|
@@ -581,7 +260,7 @@ function GenrePlaylistTreeWheel({
|
|
|
581
260
|
}
|
|
582
261
|
play(genrePlaylist).catch((error) => {
|
|
583
262
|
console.error("Failed to load genre playlist tracks:", error);
|
|
584
|
-
showPopup(/* @__PURE__ */
|
|
263
|
+
showPopup(/* @__PURE__ */ jsx2(InternalErrorPopup, { errorCode: ErrorCode2.CLIENT_UNKNOWN }));
|
|
585
264
|
});
|
|
586
265
|
},
|
|
587
266
|
[
|
|
@@ -613,12 +292,12 @@ function GenrePlaylistTreeWheel({
|
|
|
613
292
|
if (confirm(`Are you sure you want to delete "${node.name}"?`)) {
|
|
614
293
|
}
|
|
615
294
|
}, []);
|
|
616
|
-
return /* @__PURE__ */
|
|
295
|
+
return /* @__PURE__ */ jsx2(
|
|
617
296
|
GenreTreeWheel,
|
|
618
297
|
{
|
|
619
298
|
className: className2,
|
|
620
299
|
nodes,
|
|
621
|
-
itemCountLabel: sizeMetric === "views" ? "Views" : `${
|
|
300
|
+
itemCountLabel: sizeMetric === "views" ? "Views" : `${capitalize2(trackNoun.singular)} count`,
|
|
622
301
|
playingNodeId,
|
|
623
302
|
playState: isPlaying ? "playing" : "paused",
|
|
624
303
|
onPlayPause: handlePlayPause,
|
|
@@ -635,10 +314,10 @@ function GenrePlaylistTreeWheel({
|
|
|
635
314
|
hideInfoPanelTitle,
|
|
636
315
|
allowWheelRotation,
|
|
637
316
|
showToolbar,
|
|
638
|
-
controlsOrientation,
|
|
639
317
|
floatingControlsLeading,
|
|
640
318
|
busy,
|
|
641
319
|
defaultViewport,
|
|
320
|
+
viewportRestore,
|
|
642
321
|
onViewportChange,
|
|
643
322
|
...infoPanelCuration,
|
|
644
323
|
readOnly
|
|
@@ -652,9 +331,9 @@ import {
|
|
|
652
331
|
GenreTreeOutline,
|
|
653
332
|
GenreTreeWheelRadialPopCore
|
|
654
333
|
} from "@behindthemusictree/genre-tree-view";
|
|
655
|
-
import { usePlayer as
|
|
656
|
-
import { capitalize as
|
|
657
|
-
import { jsx as
|
|
334
|
+
import { usePlayer as usePlayer3 } from "@behindthemusictree/app-kit/player";
|
|
335
|
+
import { capitalize as capitalize3, useTrackNoun as useTrackNoun3 } from "@behindthemusictree/app-kit/track-noun";
|
|
336
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
658
337
|
function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
659
338
|
const {
|
|
660
339
|
scope,
|
|
@@ -673,12 +352,12 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
673
352
|
readOnly = false,
|
|
674
353
|
allowWheelRotation,
|
|
675
354
|
showToolbar,
|
|
676
|
-
controlsOrientation,
|
|
677
355
|
sizeMetric = "songs",
|
|
678
356
|
floatingControlsLeading,
|
|
679
357
|
busy,
|
|
680
358
|
outline = false,
|
|
681
359
|
defaultViewport,
|
|
360
|
+
viewportRestore,
|
|
682
361
|
onViewportChange,
|
|
683
362
|
playback
|
|
684
363
|
} = props;
|
|
@@ -691,10 +370,10 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
691
370
|
hideExpandButton: props.hideExpandButton,
|
|
692
371
|
overlayPanel: props.overlayPanel
|
|
693
372
|
};
|
|
694
|
-
const { isPlaying, setIsPlaying } =
|
|
373
|
+
const { isPlaying, setIsPlaying } = usePlayer3();
|
|
695
374
|
const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists });
|
|
696
375
|
const { playingNodeId, play } = playback ?? defaultPlayback;
|
|
697
|
-
const trackNoun =
|
|
376
|
+
const trackNoun = useTrackNoun3();
|
|
698
377
|
const nodes = useMemo4(
|
|
699
378
|
() => genrePlaylists.map((genrePlaylist) => ({
|
|
700
379
|
id: genrePlaylist.uuid,
|
|
@@ -750,7 +429,7 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
750
429
|
const treeProps = {
|
|
751
430
|
className: className2,
|
|
752
431
|
nodes,
|
|
753
|
-
itemCountLabel: sizeMetric === "views" ? "Views" : `${
|
|
432
|
+
itemCountLabel: sizeMetric === "views" ? "Views" : `${capitalize3(trackNoun.singular)} count`,
|
|
754
433
|
playingNodeId,
|
|
755
434
|
playState: isPlaying ? "playing" : "paused",
|
|
756
435
|
onPlayPause: handlePlayPause,
|
|
@@ -769,15 +448,15 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
769
448
|
...infoPanelCuration,
|
|
770
449
|
readOnly
|
|
771
450
|
};
|
|
772
|
-
return outline ? /* @__PURE__ */
|
|
451
|
+
return outline ? /* @__PURE__ */ jsx3(GenreTreeOutline, { ...treeProps }) : /* @__PURE__ */ jsx3(
|
|
773
452
|
GenreTreeWheelRadialPopCore,
|
|
774
453
|
{
|
|
775
454
|
...treeProps,
|
|
776
455
|
allowWheelRotation,
|
|
777
|
-
controlsOrientation,
|
|
778
456
|
floatingControlsLeading,
|
|
779
457
|
busy,
|
|
780
458
|
defaultViewport,
|
|
459
|
+
viewportRestore,
|
|
781
460
|
onViewportChange,
|
|
782
461
|
...focus.onFocusGenreChange ? { focusNodeId: focus.focusGenreUuid, onFocusChange: focus.onFocusGenreChange } : { focusNodeId: void 0, onFocusChange: void 0 }
|
|
783
462
|
}
|
|
@@ -785,13 +464,13 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
|
|
|
785
464
|
}
|
|
786
465
|
|
|
787
466
|
// src/genre-tree/GenreTreeWheelHandoff.tsx
|
|
788
|
-
import { useEffect
|
|
789
|
-
import { jsx as
|
|
467
|
+
import { useEffect, useRef, useState } from "react";
|
|
468
|
+
import { jsx as jsx4, jsxs } from "react/jsx-runtime";
|
|
790
469
|
function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
|
|
791
|
-
const [revealed, setRevealed] =
|
|
792
|
-
const onRevealedChangeRef =
|
|
470
|
+
const [revealed, setRevealed] = useState(false);
|
|
471
|
+
const onRevealedChangeRef = useRef(onRevealedChange);
|
|
793
472
|
onRevealedChangeRef.current = onRevealedChange;
|
|
794
|
-
|
|
473
|
+
useEffect(() => {
|
|
795
474
|
let innerFrame = 0;
|
|
796
475
|
const outerFrame = requestAnimationFrame(() => {
|
|
797
476
|
innerFrame = requestAnimationFrame(() => {
|
|
@@ -805,8 +484,8 @@ function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
|
|
|
805
484
|
cancelAnimationFrame(innerFrame);
|
|
806
485
|
};
|
|
807
486
|
}, []);
|
|
808
|
-
return /* @__PURE__ */
|
|
809
|
-
/* @__PURE__ */
|
|
487
|
+
return /* @__PURE__ */ jsxs("div", { className: "relative w-full h-full", children: [
|
|
488
|
+
/* @__PURE__ */ jsx4(
|
|
810
489
|
"div",
|
|
811
490
|
{
|
|
812
491
|
className: "absolute inset-0 transition-opacity duration-150 ease-out",
|
|
@@ -815,7 +494,7 @@ function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
|
|
|
815
494
|
children
|
|
816
495
|
}
|
|
817
496
|
),
|
|
818
|
-
!revealed && /* @__PURE__ */
|
|
497
|
+
!revealed && /* @__PURE__ */ jsx4("div", { className: "absolute inset-0", "aria-hidden": "true", children: skeleton })
|
|
819
498
|
] });
|
|
820
499
|
}
|
|
821
500
|
|
|
@@ -823,7 +502,7 @@ function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
|
|
|
823
502
|
import { ArrowLeft, LoaderCircle, Search, X } from "lucide-react";
|
|
824
503
|
import { Input } from "@behindthemusictree/ui";
|
|
825
504
|
import { useGenreSearch } from "@behindthemusictree/app-kit/genre-tree";
|
|
826
|
-
import { jsx as
|
|
505
|
+
import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
827
506
|
function GenreSearch({
|
|
828
507
|
genrePlaylists,
|
|
829
508
|
onSelect,
|
|
@@ -845,8 +524,8 @@ function GenreSearch({
|
|
|
845
524
|
const loading = query !== "" && query !== selectedName && (extraResults?.loading ?? false);
|
|
846
525
|
const showList = results.length > 0 || extraItems.length > 0;
|
|
847
526
|
const heading = "px-3 pt-2 pb-1 text-xs font-medium uppercase text-[var(--color-text-muted)]";
|
|
848
|
-
return /* @__PURE__ */
|
|
849
|
-
onPrevious && /* @__PURE__ */
|
|
527
|
+
return /* @__PURE__ */ jsxs2("div", { className: "genre-search relative w-[calc(var(--layout-detail-panel-w,25.5rem)-2*var(--layout-panel-inset,1rem))] shrink-0 max-md:w-full", children: [
|
|
528
|
+
onPrevious && /* @__PURE__ */ jsx5(
|
|
850
529
|
"button",
|
|
851
530
|
{
|
|
852
531
|
type: "button",
|
|
@@ -854,10 +533,10 @@ function GenreSearch({
|
|
|
854
533
|
title: "Previous",
|
|
855
534
|
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)]",
|
|
856
535
|
onClick: onPrevious,
|
|
857
|
-
children: /* @__PURE__ */
|
|
536
|
+
children: /* @__PURE__ */ jsx5(ArrowLeft, { "aria-hidden": "true", className: "w-4 h-4" })
|
|
858
537
|
}
|
|
859
538
|
),
|
|
860
|
-
/* @__PURE__ */
|
|
539
|
+
/* @__PURE__ */ jsx5(
|
|
861
540
|
Input,
|
|
862
541
|
{
|
|
863
542
|
type: "text",
|
|
@@ -875,21 +554,21 @@ function GenreSearch({
|
|
|
875
554
|
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"
|
|
876
555
|
}
|
|
877
556
|
),
|
|
878
|
-
/* @__PURE__ */
|
|
879
|
-
/* @__PURE__ */
|
|
557
|
+
/* @__PURE__ */ jsxs2("div", { className: "absolute right-3 top-1/2 flex items-center -translate-y-1/2", children: [
|
|
558
|
+
/* @__PURE__ */ jsxs2(
|
|
880
559
|
"span",
|
|
881
560
|
{
|
|
882
561
|
"data-loading": loading,
|
|
883
562
|
className: "relative p-2 text-[var(--color-text-muted)] pointer-events-none",
|
|
884
563
|
children: [
|
|
885
|
-
/* @__PURE__ */
|
|
564
|
+
/* @__PURE__ */ jsx5(
|
|
886
565
|
Search,
|
|
887
566
|
{
|
|
888
567
|
"aria-hidden": "true",
|
|
889
568
|
className: `w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? "opacity-0" : ""}`
|
|
890
569
|
}
|
|
891
570
|
),
|
|
892
|
-
/* @__PURE__ */
|
|
571
|
+
/* @__PURE__ */ jsx5(
|
|
893
572
|
LoaderCircle,
|
|
894
573
|
{
|
|
895
574
|
"aria-hidden": "true",
|
|
@@ -899,7 +578,7 @@ function GenreSearch({
|
|
|
899
578
|
]
|
|
900
579
|
}
|
|
901
580
|
),
|
|
902
|
-
canClear && /* @__PURE__ */
|
|
581
|
+
canClear && /* @__PURE__ */ jsx5(
|
|
903
582
|
"button",
|
|
904
583
|
{
|
|
905
584
|
type: "button",
|
|
@@ -909,14 +588,14 @@ function GenreSearch({
|
|
|
909
588
|
clear();
|
|
910
589
|
onQueryChange?.("");
|
|
911
590
|
},
|
|
912
|
-
children: /* @__PURE__ */
|
|
591
|
+
children: /* @__PURE__ */ jsx5(X, { "aria-hidden": "true", className: "w-6 h-6" })
|
|
913
592
|
}
|
|
914
593
|
)
|
|
915
594
|
] }),
|
|
916
|
-
extraResults?.status && /* @__PURE__ */
|
|
917
|
-
showList && /* @__PURE__ */
|
|
918
|
-
extraResults && results.length > 0 && /* @__PURE__ */
|
|
919
|
-
results.map((genrePlaylist) => /* @__PURE__ */
|
|
595
|
+
extraResults?.status && /* @__PURE__ */ jsx5("p", { role: "status", "aria-live": "polite", className: "sr-only", children: extraResults.status }),
|
|
596
|
+
showList && /* @__PURE__ */ jsxs2("ul", { className: "genre-search-results absolute z-40 w-full max-h-[min(16rem,50dvh)] mt-1 overflow-y-auto bg-[var(--color-surface)] border border-[var(--color-border)] rounded-xl shadow-md", children: [
|
|
597
|
+
extraResults && results.length > 0 && /* @__PURE__ */ jsx5("li", { className: heading, children: "Genres" }),
|
|
598
|
+
results.map((genrePlaylist) => /* @__PURE__ */ jsx5("li", { children: /* @__PURE__ */ jsxs2(
|
|
920
599
|
"button",
|
|
921
600
|
{
|
|
922
601
|
type: "button",
|
|
@@ -927,12 +606,12 @@ function GenreSearch({
|
|
|
927
606
|
},
|
|
928
607
|
children: [
|
|
929
608
|
genrePlaylist.name,
|
|
930
|
-
genrePlaylist.isUmbrella && /* @__PURE__ */
|
|
609
|
+
genrePlaylist.isUmbrella && /* @__PURE__ */ jsx5("span", { className: "ml-2 text-xs italic text-[var(--color-text-muted)]", children: "umbrella" })
|
|
931
610
|
]
|
|
932
611
|
}
|
|
933
612
|
) }, genrePlaylist.uuid)),
|
|
934
|
-
extraResults && extraItems.length > 0 && /* @__PURE__ */
|
|
935
|
-
extraItems.map((item) => /* @__PURE__ */
|
|
613
|
+
extraResults && extraItems.length > 0 && /* @__PURE__ */ jsx5("li", { className: heading, children: extraResults.label }),
|
|
614
|
+
extraItems.map((item) => /* @__PURE__ */ jsx5("li", { children: /* @__PURE__ */ jsx5(
|
|
936
615
|
"button",
|
|
937
616
|
{
|
|
938
617
|
type: "button",
|
|
@@ -949,26 +628,29 @@ function GenreSearch({
|
|
|
949
628
|
] });
|
|
950
629
|
}
|
|
951
630
|
|
|
631
|
+
// src/genre-tree/GenreInfoPanelBody.tsx
|
|
632
|
+
import { usePlayer as usePlayer5 } from "@behindthemusictree/app-kit/player";
|
|
633
|
+
|
|
952
634
|
// src/genre-tree/GenreActionBand.tsx
|
|
953
635
|
import { ArrowLeftRight, MoveUpRight, Pause, Pencil, Play, Plus, ZoomIn } from "lucide-react";
|
|
954
636
|
|
|
955
637
|
// src/genre-tree/ShareActionButton.tsx
|
|
956
638
|
import { Share2 as Share22 } from "lucide-react";
|
|
957
|
-
import { usePopup as
|
|
639
|
+
import { usePopup as usePopup3 } from "@behindthemusictree/app-kit/popup";
|
|
958
640
|
|
|
959
641
|
// src/genre-tree/SharePopup.tsx
|
|
960
|
-
import { useCallback as useCallback5, useEffect as
|
|
642
|
+
import { useCallback as useCallback5, useEffect as useEffect2, useMemo as useMemo5, useState as useState2 } from "react";
|
|
961
643
|
import { Link2, Share2 } from "lucide-react";
|
|
962
644
|
import { FaWhatsapp } from "react-icons/fa";
|
|
963
645
|
import { SiGmail } from "react-icons/si";
|
|
964
646
|
|
|
965
647
|
// src/genre-tree/ShareDestinationButton.tsx
|
|
966
|
-
import { Fragment
|
|
648
|
+
import { Fragment, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
967
649
|
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
650
|
function Contents({ icon, label }) {
|
|
969
|
-
return /* @__PURE__ */
|
|
970
|
-
/* @__PURE__ */
|
|
971
|
-
/* @__PURE__ */
|
|
651
|
+
return /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
652
|
+
/* @__PURE__ */ jsx6("span", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]", children: icon }),
|
|
653
|
+
/* @__PURE__ */ jsx6("span", { className: "max-w-full truncate", children: label })
|
|
972
654
|
] });
|
|
973
655
|
}
|
|
974
656
|
function ShareDestinationButton({
|
|
@@ -980,29 +662,29 @@ function ShareDestinationButton({
|
|
|
980
662
|
ariaLabel
|
|
981
663
|
}) {
|
|
982
664
|
if (href) {
|
|
983
|
-
return /* @__PURE__ */
|
|
665
|
+
return /* @__PURE__ */ jsx6("a", { href, target: "_blank", rel: "noopener noreferrer", "aria-label": ariaLabel, className, children: /* @__PURE__ */ jsx6(Contents, { icon, label }) });
|
|
984
666
|
}
|
|
985
|
-
return /* @__PURE__ */
|
|
667
|
+
return /* @__PURE__ */ jsx6("button", { type: "button", onClick, disabled, "aria-label": ariaLabel, className, children: /* @__PURE__ */ jsx6(Contents, { icon, label }) });
|
|
986
668
|
}
|
|
987
669
|
|
|
988
670
|
// src/genre-tree/SharePreviewCard.tsx
|
|
989
671
|
import { Music2, UserRound } from "lucide-react";
|
|
990
|
-
import { jsx as
|
|
672
|
+
import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
991
673
|
function SharePreviewCard({ target, url }) {
|
|
992
674
|
const Icon = target.kind === "genre" ? Music2 : UserRound;
|
|
993
675
|
const typeLabel = target.kind === "genre" ? "Genre" : "Artist";
|
|
994
|
-
return /* @__PURE__ */
|
|
995
|
-
/* @__PURE__ */
|
|
996
|
-
/* @__PURE__ */
|
|
997
|
-
/* @__PURE__ */
|
|
998
|
-
/* @__PURE__ */
|
|
999
|
-
/* @__PURE__ */
|
|
676
|
+
return /* @__PURE__ */ jsx7("div", { className: "rounded-3xl bg-[var(--color-surface-muted)] p-4", children: /* @__PURE__ */ jsxs4("div", { className: "flex min-h-32 overflow-hidden rounded-2xl bg-[var(--color-surface)] shadow-lg", children: [
|
|
677
|
+
/* @__PURE__ */ jsx7("div", { className: "flex w-28 shrink-0 items-center justify-center bg-[var(--color-primary)]/15 text-[var(--color-primary)]", children: /* @__PURE__ */ jsx7(Icon, { "aria-hidden": "true", className: "h-10 w-10" }) }),
|
|
678
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex min-w-0 flex-1 flex-col justify-center gap-1 p-4", children: [
|
|
679
|
+
/* @__PURE__ */ jsx7("span", { className: "text-xs font-medium uppercase tracking-wide text-[var(--color-text-muted)]", children: typeLabel }),
|
|
680
|
+
/* @__PURE__ */ jsx7("span", { className: "truncate text-base font-semibold text-[var(--color-text)]", title: target.name, children: target.name }),
|
|
681
|
+
/* @__PURE__ */ jsx7("span", { className: "truncate text-sm text-[var(--color-primary)]", title: url, children: url })
|
|
1000
682
|
] })
|
|
1001
683
|
] }) });
|
|
1002
684
|
}
|
|
1003
685
|
|
|
1004
686
|
// src/genre-tree/SharePopup.tsx
|
|
1005
|
-
import { Fragment as
|
|
687
|
+
import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1006
688
|
function resolveAbsoluteUrl(url) {
|
|
1007
689
|
try {
|
|
1008
690
|
const parsed = new URL(url, window.location.href);
|
|
@@ -1035,17 +717,17 @@ async function copyText(text) {
|
|
|
1035
717
|
}
|
|
1036
718
|
}
|
|
1037
719
|
function SharePopup({ target, url, onClose }) {
|
|
1038
|
-
const [absoluteUrl, setAbsoluteUrl] =
|
|
1039
|
-
const [copyStatus, setCopyStatus] =
|
|
1040
|
-
const [canUseNativeShare, setCanUseNativeShare] =
|
|
1041
|
-
const [shareError, setShareError] =
|
|
1042
|
-
const [toastId, setToastId] =
|
|
1043
|
-
|
|
720
|
+
const [absoluteUrl, setAbsoluteUrl] = useState2(url);
|
|
721
|
+
const [copyStatus, setCopyStatus] = useState2("idle");
|
|
722
|
+
const [canUseNativeShare, setCanUseNativeShare] = useState2(false);
|
|
723
|
+
const [shareError, setShareError] = useState2(null);
|
|
724
|
+
const [toastId, setToastId] = useState2(0);
|
|
725
|
+
useEffect2(() => {
|
|
1044
726
|
setAbsoluteUrl(resolveAbsoluteUrl(url) ?? "");
|
|
1045
727
|
setCopyStatus("idle");
|
|
1046
728
|
setShareError(null);
|
|
1047
729
|
}, [url]);
|
|
1048
|
-
|
|
730
|
+
useEffect2(() => {
|
|
1049
731
|
setCanUseNativeShare(typeof navigator !== "undefined" && typeof navigator.share === "function");
|
|
1050
732
|
}, []);
|
|
1051
733
|
const destinationUrls = useMemo5(() => {
|
|
@@ -1077,8 +759,8 @@ function SharePopup({ target, url, onClose }) {
|
|
|
1077
759
|
setShareError("Could not open the share options.");
|
|
1078
760
|
}
|
|
1079
761
|
};
|
|
1080
|
-
return /* @__PURE__ */
|
|
1081
|
-
/* @__PURE__ */
|
|
762
|
+
return /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
763
|
+
/* @__PURE__ */ jsx8(
|
|
1082
764
|
BasePopup_default,
|
|
1083
765
|
{
|
|
1084
766
|
title: "Share",
|
|
@@ -1093,175 +775,550 @@ function SharePopup({ target, url, onClose }) {
|
|
|
1093
775
|
titleTextClassName: "!text-xl !font-semibold !text-[var(--color-text)]",
|
|
1094
776
|
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
777
|
contentClassName: "!rounded-none !bg-transparent !px-6 !pb-6 !pt-2",
|
|
1096
|
-
children: /* @__PURE__ */
|
|
1097
|
-
/* @__PURE__ */
|
|
1098
|
-
/* @__PURE__ */
|
|
1099
|
-
/* @__PURE__ */
|
|
778
|
+
children: /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-4", children: [
|
|
779
|
+
/* @__PURE__ */ jsx8(SharePreviewCard, { target, url: absoluteUrl || url }),
|
|
780
|
+
/* @__PURE__ */ jsxs5("div", { className: "grid grid-cols-3 gap-1 rounded-3xl bg-[var(--color-surface)] p-3 sm:grid-cols-4", children: [
|
|
781
|
+
/* @__PURE__ */ jsx8(
|
|
1100
782
|
ShareDestinationButton,
|
|
1101
783
|
{
|
|
1102
784
|
label: "Copy Link",
|
|
1103
|
-
icon: /* @__PURE__ */
|
|
785
|
+
icon: /* @__PURE__ */ jsx8(Link2, { "aria-hidden": "true", className: "h-6 w-6" }),
|
|
1104
786
|
onClick: () => void handleCopy(),
|
|
1105
787
|
ariaLabel: "Copy link",
|
|
1106
788
|
disabled: !absoluteUrl
|
|
1107
789
|
}
|
|
1108
790
|
),
|
|
1109
|
-
/* @__PURE__ */
|
|
791
|
+
/* @__PURE__ */ jsx8(
|
|
1110
792
|
ShareDestinationButton,
|
|
1111
793
|
{
|
|
1112
794
|
label: "WhatsApp",
|
|
1113
|
-
icon: /* @__PURE__ */
|
|
795
|
+
icon: /* @__PURE__ */ jsx8(FaWhatsapp, { "aria-hidden": "true", className: "h-9 w-9 text-[#25D366]" }),
|
|
1114
796
|
href: destinationUrls?.whatsapp,
|
|
1115
797
|
ariaLabel: `Share ${target.name} via WhatsApp`,
|
|
1116
798
|
disabled: !absoluteUrl
|
|
1117
799
|
}
|
|
1118
800
|
),
|
|
1119
|
-
/* @__PURE__ */
|
|
801
|
+
/* @__PURE__ */ jsx8(
|
|
1120
802
|
ShareDestinationButton,
|
|
1121
803
|
{
|
|
1122
804
|
label: "Gmail",
|
|
1123
|
-
icon: /* @__PURE__ */
|
|
805
|
+
icon: /* @__PURE__ */ jsx8(SiGmail, { "aria-hidden": "true", className: "h-8 w-8 text-[#EA4335]" }),
|
|
1124
806
|
href: destinationUrls?.gmail,
|
|
1125
807
|
ariaLabel: `Email ${target.name} with Gmail`,
|
|
1126
808
|
disabled: !absoluteUrl
|
|
1127
809
|
}
|
|
1128
810
|
),
|
|
1129
|
-
canUseNativeShare && /* @__PURE__ */
|
|
811
|
+
canUseNativeShare && /* @__PURE__ */ jsx8(
|
|
1130
812
|
ShareDestinationButton,
|
|
1131
813
|
{
|
|
1132
814
|
label: "More",
|
|
1133
|
-
icon: /* @__PURE__ */
|
|
815
|
+
icon: /* @__PURE__ */ jsx8(Share2, { "aria-hidden": "true", className: "h-6 w-6" }),
|
|
1134
816
|
onClick: () => void handleNativeShare(),
|
|
1135
817
|
ariaLabel: "More sharing options",
|
|
1136
818
|
disabled: !absoluteUrl
|
|
1137
819
|
}
|
|
1138
820
|
)
|
|
1139
821
|
] }),
|
|
1140
|
-
copyStatus === "failed" && /* @__PURE__ */
|
|
1141
|
-
shareError && /* @__PURE__ */
|
|
822
|
+
copyStatus === "failed" && /* @__PURE__ */ jsx8("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: "Could not copy the link." }),
|
|
823
|
+
shareError && /* @__PURE__ */ jsx8("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: shareError })
|
|
1142
824
|
] })
|
|
1143
825
|
}
|
|
1144
|
-
),
|
|
1145
|
-
toastId > 0 && /* @__PURE__ */
|
|
826
|
+
),
|
|
827
|
+
toastId > 0 && /* @__PURE__ */ jsx8(Toast, { message: "Link copied", onClose: dismissToast }, toastId)
|
|
828
|
+
] });
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// src/genre-tree/ShareActionButton.tsx
|
|
832
|
+
import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
833
|
+
function ShareActionButton({ target, url }) {
|
|
834
|
+
const { showPopup, hidePopup } = usePopup3();
|
|
835
|
+
const openSharePopup = () => {
|
|
836
|
+
showPopup(/* @__PURE__ */ jsx9(SharePopup, { target, url, onClose: hidePopup }));
|
|
837
|
+
};
|
|
838
|
+
return /* @__PURE__ */ jsxs6(
|
|
839
|
+
"button",
|
|
840
|
+
{
|
|
841
|
+
type: "button",
|
|
842
|
+
onClick: openSharePopup,
|
|
843
|
+
className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none",
|
|
844
|
+
"aria-label": `Share ${target.name}`,
|
|
845
|
+
title: `Share ${target.name}`,
|
|
846
|
+
children: [
|
|
847
|
+
/* @__PURE__ */ jsx9("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__ */ jsx9(Share22, { "aria-hidden": "true", className: "w-5 h-5" }) }),
|
|
848
|
+
"Share"
|
|
849
|
+
]
|
|
850
|
+
}
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// src/genre-tree/GenreActionBand.tsx
|
|
855
|
+
import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
856
|
+
function GenreActionBand({
|
|
857
|
+
node,
|
|
858
|
+
onPlayPause,
|
|
859
|
+
isPlaying = false,
|
|
860
|
+
onZoomIn,
|
|
861
|
+
onAddChild,
|
|
862
|
+
onRename,
|
|
863
|
+
onChangeParent,
|
|
864
|
+
side,
|
|
865
|
+
onToggleSide,
|
|
866
|
+
toggleSideDisabledReason,
|
|
867
|
+
additionalActions,
|
|
868
|
+
shareTarget,
|
|
869
|
+
shareUrl
|
|
870
|
+
}) {
|
|
871
|
+
const items = [];
|
|
872
|
+
const adminItems = [];
|
|
873
|
+
if (onPlayPause) {
|
|
874
|
+
items.push({
|
|
875
|
+
key: "play-pause",
|
|
876
|
+
label: isPlaying ? "Pause" : "Play",
|
|
877
|
+
icon: isPlaying ? /* @__PURE__ */ jsx10(Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ jsx10(Play, { "aria-hidden": "true", className: "w-5 h-5" }),
|
|
878
|
+
onClick: onPlayPause
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
if (onZoomIn) {
|
|
882
|
+
items.push({
|
|
883
|
+
key: "zoom-in",
|
|
884
|
+
label: "Focus",
|
|
885
|
+
icon: /* @__PURE__ */ jsx10(ZoomIn, { "aria-hidden": "true", className: "w-5 h-5" }),
|
|
886
|
+
onClick: onZoomIn
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
if (onAddChild) {
|
|
890
|
+
adminItems.push({ key: "add-child", label: "Add", icon: /* @__PURE__ */ jsx10(Plus, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onAddChild });
|
|
891
|
+
}
|
|
892
|
+
if (onRename) {
|
|
893
|
+
adminItems.push({ key: "rename", label: "Rename", icon: /* @__PURE__ */ jsx10(Pencil, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onRename });
|
|
894
|
+
}
|
|
895
|
+
if (onChangeParent) {
|
|
896
|
+
adminItems.push({ key: "change-parent", label: "Parent", icon: /* @__PURE__ */ jsx10(MoveUpRight, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onChangeParent });
|
|
897
|
+
}
|
|
898
|
+
if (onToggleSide) {
|
|
899
|
+
adminItems.push({
|
|
900
|
+
key: "toggle-side",
|
|
901
|
+
label: side === "pop" ? "Make core" : "Make pop",
|
|
902
|
+
icon: /* @__PURE__ */ jsx10(ArrowLeftRight, { "aria-hidden": "true", className: "w-5 h-5" }),
|
|
903
|
+
onClick: onToggleSide,
|
|
904
|
+
disabled: !!toggleSideDisabledReason,
|
|
905
|
+
title: toggleSideDisabledReason
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
for (const action of additionalActions?.(node) ?? []) {
|
|
909
|
+
items.push({
|
|
910
|
+
key: action.key,
|
|
911
|
+
label: action.label(node),
|
|
912
|
+
icon: action.icon(node),
|
|
913
|
+
onClick: (event) => action.onClick(event, node),
|
|
914
|
+
disabled: action.enabled ? !action.enabled(node) : false
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
if (items.length === 0 && (!shareTarget || !shareUrl) && adminItems.length === 0) return null;
|
|
918
|
+
const renderItems = (list) => list.map((item) => /* @__PURE__ */ jsxs7(
|
|
919
|
+
"button",
|
|
920
|
+
{
|
|
921
|
+
type: "button",
|
|
922
|
+
disabled: item.disabled,
|
|
923
|
+
title: item.title,
|
|
924
|
+
onClick: item.onClick,
|
|
925
|
+
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",
|
|
926
|
+
children: [
|
|
927
|
+
/* @__PURE__ */ jsx10("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 }),
|
|
928
|
+
item.label
|
|
929
|
+
]
|
|
930
|
+
},
|
|
931
|
+
item.key
|
|
932
|
+
));
|
|
933
|
+
return /* @__PURE__ */ jsxs7("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: [
|
|
934
|
+
(items.length > 0 || shareTarget && shareUrl) && /* @__PURE__ */ jsxs7("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
|
|
935
|
+
renderItems(items),
|
|
936
|
+
shareTarget && shareUrl && /* @__PURE__ */ jsx10(ShareActionButton, { target: shareTarget, url: shareUrl })
|
|
937
|
+
] }),
|
|
938
|
+
adminItems.length > 0 && /* @__PURE__ */ jsxs7(
|
|
939
|
+
"section",
|
|
940
|
+
{
|
|
941
|
+
"aria-label": "Admin actions",
|
|
942
|
+
className: `px-3.5 py-3 ${items.length > 0 ? "border-t border-[var(--color-border)]" : ""}`,
|
|
943
|
+
children: [
|
|
944
|
+
/* @__PURE__ */ jsx10("h3", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-[var(--color-text-muted)]", children: "Admin" }),
|
|
945
|
+
/* @__PURE__ */ jsx10("div", { className: "flex flex-wrap gap-1", children: renderItems(adminItems) })
|
|
946
|
+
]
|
|
947
|
+
}
|
|
948
|
+
)
|
|
949
|
+
] });
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
// src/genre-tree/GenreDetailExtras.tsx
|
|
953
|
+
import { Skeleton as Skeleton2 } from "@behindthemusictree/ui";
|
|
954
|
+
import { useFetchGenreOverview } from "@behindthemusictree/app-kit/genre-tree";
|
|
955
|
+
|
|
956
|
+
// src/genre-tree/ClampedText.tsx
|
|
957
|
+
import { useLayoutEffect, useRef as useRef2, useState as useState3 } from "react";
|
|
958
|
+
import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
959
|
+
function ClampedText({ children, className: className2 = "" }) {
|
|
960
|
+
const ref = useRef2(null);
|
|
961
|
+
const [expanded, setExpanded] = useState3(false);
|
|
962
|
+
const [overflows, setOverflows] = useState3(false);
|
|
963
|
+
useLayoutEffect(() => {
|
|
964
|
+
const element = ref.current;
|
|
965
|
+
if (!element || expanded) return;
|
|
966
|
+
setOverflows(element.scrollHeight > element.clientHeight);
|
|
967
|
+
}, [children, expanded]);
|
|
968
|
+
return /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
969
|
+
/* @__PURE__ */ jsx11("p", { ref, className: `${className2} ${expanded ? "" : "line-clamp-3"}`, children }),
|
|
970
|
+
(overflows || expanded) && /* @__PURE__ */ jsx11(
|
|
971
|
+
"button",
|
|
972
|
+
{
|
|
973
|
+
type: "button",
|
|
974
|
+
"aria-expanded": expanded,
|
|
975
|
+
onClick: () => setExpanded((value) => !value),
|
|
976
|
+
className: "mt-1 text-xs text-[var(--color-text-muted)] underline hover:text-[var(--color-text)]",
|
|
977
|
+
children: expanded ? "Show less" : "Show more"
|
|
978
|
+
}
|
|
979
|
+
)
|
|
980
|
+
] });
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// src/genre-tree/GenrePlaylistTracks.tsx
|
|
984
|
+
import { useEffect as useEffect3, useRef as useRef3 } from "react";
|
|
985
|
+
import { useInfiniteQuery } from "@tanstack/react-query";
|
|
986
|
+
import { z } from "zod";
|
|
987
|
+
import { FaPlay, FaPause } from "react-icons/fa";
|
|
988
|
+
import { RingLoader, Skeleton } from "@behindthemusictree/ui";
|
|
989
|
+
import { useSession } from "@behindthemusictree/app-kit/auth";
|
|
990
|
+
import { useFetchWrapper } from "@behindthemusictree/app-kit/transport";
|
|
991
|
+
import { PaginatedResponseSchema } from "@behindthemusictree/app-kit/transport";
|
|
992
|
+
import { parseWithLog } from "@behindthemusictree/app-kit/transport";
|
|
993
|
+
import { usePlayer as usePlayer4 } from "@behindthemusictree/app-kit/player";
|
|
994
|
+
import { PlayStates } from "@behindthemusictree/app-kit/player";
|
|
995
|
+
import { usePopup as usePopup4 } from "@behindthemusictree/app-kit/popup";
|
|
996
|
+
import { ErrorCode as ErrorCode3 } from "@behindthemusictree/app-kit/transport";
|
|
997
|
+
import { genrePlaylistQueryKeys } from "@behindthemusictree/app-kit/genre-tree";
|
|
998
|
+
import { libraryEndpoints, libraryQueryKeys } from "@behindthemusictree/app-kit/genre-tree";
|
|
999
|
+
import { isSong, songUnplayableReason, SongDetailedSchema } from "@behindthemusictree/app-kit/genre-tree";
|
|
1000
|
+
import { useTrackList as useTrackList2 } from "@behindthemusictree/app-kit/genre-tree";
|
|
1001
|
+
|
|
1002
|
+
// src/genre-tree/MusicBrainzRecordingLink.tsx
|
|
1003
|
+
import { musicBrainzRecordingUrl } from "@behindthemusictree/app-kit/genre-tree";
|
|
1004
|
+
import { Fragment as Fragment4, jsx as jsx12 } from "react/jsx-runtime";
|
|
1005
|
+
function MusicBrainzRecordingLink({
|
|
1006
|
+
song,
|
|
1007
|
+
children,
|
|
1008
|
+
className: className2 = ""
|
|
1009
|
+
}) {
|
|
1010
|
+
const url = musicBrainzRecordingUrl(song);
|
|
1011
|
+
if (!url) return /* @__PURE__ */ jsx12(Fragment4, { children });
|
|
1012
|
+
return /* @__PURE__ */ jsx12(
|
|
1013
|
+
"a",
|
|
1014
|
+
{
|
|
1015
|
+
href: url,
|
|
1016
|
+
target: "_blank",
|
|
1017
|
+
rel: "noopener noreferrer",
|
|
1018
|
+
"aria-label": `View ${song.title} on MusicBrainz`,
|
|
1019
|
+
className: className2,
|
|
1020
|
+
onClick: (event) => event.stopPropagation(),
|
|
1021
|
+
children
|
|
1022
|
+
}
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// src/genre-tree/GenrePlaylistTracks.tsx
|
|
1027
|
+
import { ArtistNames } from "@behindthemusictree/app-kit/genre-tree";
|
|
1028
|
+
import { capitalize as capitalize4, useTrackNoun as useTrackNoun4 } from "@behindthemusictree/app-kit/track-noun";
|
|
1029
|
+
import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1030
|
+
var CATALOGUE_PAGE_SIZE = 100;
|
|
1031
|
+
var SongPageSchema = PaginatedResponseSchema(SongDetailedSchema).extend({
|
|
1032
|
+
overallTotal: z.number().nullable(),
|
|
1033
|
+
page: z.number().nullable(),
|
|
1034
|
+
totalPages: z.number().nullable()
|
|
1035
|
+
});
|
|
1036
|
+
var TEXT_CELL = "min-w-0 flex-1 truncate [@container(min-width:40rem)]:flex [@container(min-width:40rem)]:gap-2";
|
|
1037
|
+
var TITLE_CELL = "[@container(min-width:40rem)]:flex-[2] [@container(min-width:40rem)]:truncate";
|
|
1038
|
+
var COLUMN_CELL = "[@container(min-width:40rem)]:flex-1 [@container(min-width:40rem)]:truncate";
|
|
1039
|
+
var ALBUM_CELL = `hidden [@container(min-width:40rem)]:block ${COLUMN_CELL}`;
|
|
1040
|
+
var SEPARATOR = "[@container(min-width:40rem)]:hidden";
|
|
1041
|
+
var nextCursor = (next) => {
|
|
1042
|
+
const cursor = new URL(next).searchParams.get("cursor");
|
|
1043
|
+
if (!cursor) throw new Error(`Songs next link has no cursor: ${next}`);
|
|
1044
|
+
return cursor;
|
|
1045
|
+
};
|
|
1046
|
+
function GenrePlaylistTracks({
|
|
1047
|
+
genreUuid,
|
|
1048
|
+
genrePlaylist,
|
|
1049
|
+
scope,
|
|
1050
|
+
getBackendBaseUrl,
|
|
1051
|
+
renderArtistLink,
|
|
1052
|
+
renderSongLeading,
|
|
1053
|
+
renderSongActions,
|
|
1054
|
+
renderTracksToolbar,
|
|
1055
|
+
artist = null
|
|
1056
|
+
}) {
|
|
1057
|
+
const { fetchGenrePlaylistTracksPage, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist } = useTrackList2();
|
|
1058
|
+
const { session, sessionRestored } = useSession();
|
|
1059
|
+
const { playerTrackObject, playState, handlePlayPauseAction } = usePlayer4();
|
|
1060
|
+
const { showPopup } = usePopup4();
|
|
1061
|
+
const trackNoun = useTrackNoun4();
|
|
1062
|
+
const { uuid } = genrePlaylist;
|
|
1063
|
+
const { fetch } = useFetchWrapper(getBackendBaseUrl);
|
|
1064
|
+
const { data, isPending, isError, refetch, hasNextPage, isFetchingNextPage, fetchNextPage } = useInfiniteQuery({
|
|
1065
|
+
queryKey: scope === "me" ? genrePlaylistQueryKeys.me.tracks(uuid) : artist ? [...libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid), "artist", artist.uuid] : libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid),
|
|
1066
|
+
queryFn: async ({ pageParam }) => {
|
|
1067
|
+
if (scope === "me") return fetchGenrePlaylistTracksPage(uuid, scope, Number(pageParam));
|
|
1068
|
+
const response = await fetch(
|
|
1069
|
+
libraryEndpoints.reference.songs.list(),
|
|
1070
|
+
true,
|
|
1071
|
+
false,
|
|
1072
|
+
{},
|
|
1073
|
+
{ genre: genreUuid, ...artist && { artist: artist.uuid }, pageSize: CATALOGUE_PAGE_SIZE, withTotal: false, ...pageParam && { cursor: pageParam } }
|
|
1074
|
+
);
|
|
1075
|
+
const parsed = parseWithLog(SongPageSchema, response, "GenrePlaylistTracks");
|
|
1076
|
+
return { tracks: parsed.results, total: parsed.overallTotal, nextPage: parsed.next ? nextCursor(parsed.next) : null };
|
|
1077
|
+
},
|
|
1078
|
+
initialPageParam: scope === "me" ? 1 : "",
|
|
1079
|
+
getNextPageParam: (lastPage) => lastPage.nextPage ?? void 0,
|
|
1080
|
+
enabled: scope !== "me" || sessionRestored && !!session?.accessToken
|
|
1081
|
+
});
|
|
1082
|
+
const sentinelRef = useRef3(null);
|
|
1083
|
+
useEffect3(() => {
|
|
1084
|
+
const sentinel = sentinelRef.current;
|
|
1085
|
+
if (!hasNextPage || isFetchingNextPage || !sentinel) return;
|
|
1086
|
+
const observer = new IntersectionObserver((entries) => {
|
|
1087
|
+
if (entries.some((entry) => entry.isIntersecting)) {
|
|
1088
|
+
void fetchNextPage();
|
|
1089
|
+
}
|
|
1090
|
+
});
|
|
1091
|
+
observer.observe(sentinel);
|
|
1092
|
+
return () => observer.disconnect();
|
|
1093
|
+
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);
|
|
1094
|
+
const pages = data?.pages ?? [];
|
|
1095
|
+
const tracks = pages.flatMap((page) => page.tracks);
|
|
1096
|
+
const total = pages[0]?.total ?? void 0;
|
|
1097
|
+
const play = (startSongUuid) => {
|
|
1098
|
+
const started = artist ? playNewTrackListFromArtist(artist, scope, { startSongUuid, ordering: "title", genreUuid }) : playNewTrackListFromGenrePlaylist(genrePlaylist, scope, { startSongUuid });
|
|
1099
|
+
started.catch((error) => {
|
|
1100
|
+
console.error("Failed to play genre playlist:", error);
|
|
1101
|
+
showPopup(/* @__PURE__ */ jsx13(InternalErrorPopup, { errorCode: ErrorCode3.CLIENT_UNKNOWN }));
|
|
1102
|
+
});
|
|
1103
|
+
};
|
|
1104
|
+
return /* @__PURE__ */ jsxs9("div", { className: "gtv-info-panel-children", children: [
|
|
1105
|
+
/* @__PURE__ */ jsxs9("span", { className: "gtv-info-panel-children-title", children: [
|
|
1106
|
+
capitalize4(trackNoun.plural),
|
|
1107
|
+
total !== void 0 && ` (${total})`
|
|
1108
|
+
] }),
|
|
1109
|
+
renderTracksToolbar?.(),
|
|
1110
|
+
isPending ? /* @__PURE__ */ jsx13(Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ jsxs9("p", { role: "alert", children: [
|
|
1111
|
+
"Couldn't load ",
|
|
1112
|
+
trackNoun.plural,
|
|
1113
|
+
".",
|
|
1114
|
+
" ",
|
|
1115
|
+
/* @__PURE__ */ jsx13("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
|
|
1116
|
+
] }) : tracks.length === 0 ? /* @__PURE__ */ jsx13("p", { children: "\u2014" }) : /* @__PURE__ */ jsxs9("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: [
|
|
1117
|
+
tracks.map((track, index) => {
|
|
1118
|
+
const label = track.artists?.length ? `${track.title} \u2014 ${track.artists.map((artist2) => artist2.name).join(", ")}` : track.title;
|
|
1119
|
+
const isCurrent = playerTrackObject?.track.id === track.uuid;
|
|
1120
|
+
const isPlaying = isCurrent && playState === PlayStates.PLAYING;
|
|
1121
|
+
const text = /* @__PURE__ */ jsxs9("span", { className: TEXT_CELL, children: [
|
|
1122
|
+
/* @__PURE__ */ jsx13("span", { className: TITLE_CELL, children: isSong(track) ? /* @__PURE__ */ jsx13(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
|
|
1123
|
+
track.artists?.length ? /* @__PURE__ */ jsx13("span", { className: SEPARATOR, children: " \u2014 " }) : null,
|
|
1124
|
+
/* @__PURE__ */ jsx13("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ jsx13(ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
|
|
1125
|
+
track.album && /* @__PURE__ */ jsx13("span", { className: ALBUM_CELL, children: track.album.name })
|
|
1126
|
+
] });
|
|
1127
|
+
const leading = renderSongLeading?.(track, index);
|
|
1128
|
+
const actions = renderSongActions?.(track);
|
|
1129
|
+
const isUnplayable = isSong(track) && songUnplayableReason(track) !== null;
|
|
1130
|
+
if (isUnplayable) {
|
|
1131
|
+
return /* @__PURE__ */ jsxs9("li", { className: "flex min-h-8 items-center gap-1 px-1 opacity-50", children: [
|
|
1132
|
+
leading,
|
|
1133
|
+
/* @__PURE__ */ jsx13(
|
|
1134
|
+
"button",
|
|
1135
|
+
{
|
|
1136
|
+
type: "button",
|
|
1137
|
+
disabled: true,
|
|
1138
|
+
"aria-disabled": "true",
|
|
1139
|
+
"aria-label": label,
|
|
1140
|
+
className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full opacity-0 cursor-not-allowed",
|
|
1141
|
+
children: /* @__PURE__ */ jsx13(FaPlay, { size: 10 })
|
|
1142
|
+
}
|
|
1143
|
+
),
|
|
1144
|
+
text,
|
|
1145
|
+
actions
|
|
1146
|
+
] }, track.uuid);
|
|
1147
|
+
}
|
|
1148
|
+
return /* @__PURE__ */ jsxs9(
|
|
1149
|
+
"li",
|
|
1150
|
+
{
|
|
1151
|
+
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)]" : ""}`,
|
|
1152
|
+
children: [
|
|
1153
|
+
leading,
|
|
1154
|
+
/* @__PURE__ */ jsx13(
|
|
1155
|
+
"button",
|
|
1156
|
+
{
|
|
1157
|
+
type: "button",
|
|
1158
|
+
"aria-label": `${isPlaying ? "Pause" : "Play"} ${label}`,
|
|
1159
|
+
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"}`,
|
|
1160
|
+
onClick: () => isCurrent ? handlePlayPauseAction() : play(track.uuid),
|
|
1161
|
+
children: isCurrent && playState === PlayStates.LOADING ? /* @__PURE__ */ jsx13(RingLoader, { size: 10 }) : isPlaying ? /* @__PURE__ */ jsx13(FaPause, { size: 10 }) : /* @__PURE__ */ jsx13(FaPlay, { size: 10 })
|
|
1162
|
+
}
|
|
1163
|
+
),
|
|
1164
|
+
text,
|
|
1165
|
+
actions
|
|
1166
|
+
]
|
|
1167
|
+
},
|
|
1168
|
+
track.uuid
|
|
1169
|
+
);
|
|
1170
|
+
}),
|
|
1171
|
+
hasNextPage && /* @__PURE__ */ jsx13("li", { ref: sentinelRef, "aria-hidden": "true", className: "h-4" })
|
|
1172
|
+
] })
|
|
1173
|
+
] });
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// src/genre-tree/GenreDetailExtras.tsx
|
|
1177
|
+
import { useTrackNoun as useTrackNoun5 } from "@behindthemusictree/app-kit/track-noun";
|
|
1178
|
+
import { SiWikidata, SiWikipedia } from "react-icons/si";
|
|
1179
|
+
import { Fragment as Fragment5, jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1180
|
+
function GenreDetailExtras({
|
|
1181
|
+
genreUuid,
|
|
1182
|
+
genrePlaylist,
|
|
1183
|
+
wikidataId,
|
|
1184
|
+
scope,
|
|
1185
|
+
getBackendBaseUrl,
|
|
1186
|
+
criteriaOverviewSchema,
|
|
1187
|
+
renderGenreDetailExtras,
|
|
1188
|
+
renderArtistLink,
|
|
1189
|
+
renderSummary,
|
|
1190
|
+
renderEssentialTracks,
|
|
1191
|
+
family,
|
|
1192
|
+
...trackSlots
|
|
1193
|
+
}) {
|
|
1194
|
+
const trackNoun = useTrackNoun5();
|
|
1195
|
+
const { data: overview, isPending } = useFetchGenreOverview(
|
|
1196
|
+
genreUuid,
|
|
1197
|
+
scope,
|
|
1198
|
+
getBackendBaseUrl,
|
|
1199
|
+
criteriaOverviewSchema
|
|
1200
|
+
);
|
|
1201
|
+
if (isPending) {
|
|
1202
|
+
return /* @__PURE__ */ jsxs10(Fragment5, { children: [
|
|
1203
|
+
/* @__PURE__ */ jsxs10("div", { className: "gtv-info-panel-children", "aria-busy": "true", "data-testid": "genre-detail-extras-skeleton", children: [
|
|
1204
|
+
/* @__PURE__ */ jsx14("span", { className: "gtv-info-panel-children-title", children: "Summary" }),
|
|
1205
|
+
/* @__PURE__ */ jsx14(Skeleton2, { className: "h-4 w-3/4" })
|
|
1206
|
+
] }),
|
|
1207
|
+
family
|
|
1208
|
+
] });
|
|
1209
|
+
}
|
|
1210
|
+
if (!overview) return /* @__PURE__ */ jsx14(Fragment5, { children: family });
|
|
1211
|
+
const { summary, summarySourceUrl, essentialSongs } = overview;
|
|
1212
|
+
const wikidataUrl = wikidataId ? `https://www.wikidata.org/wiki/${encodeURIComponent(wikidataId)}` : null;
|
|
1213
|
+
return /* @__PURE__ */ jsxs10(Fragment5, { children: [
|
|
1214
|
+
(summarySourceUrl || wikidataUrl) && /* @__PURE__ */ jsxs10("div", { className: "gtv-info-panel-children text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: [
|
|
1215
|
+
/* @__PURE__ */ jsx14("span", { className: "gtv-info-panel-children-title", children: "Links" }),
|
|
1216
|
+
/* @__PURE__ */ jsxs10("div", { className: "mt-1 flex flex-wrap gap-x-3 gap-y-1 text-xs", children: [
|
|
1217
|
+
summarySourceUrl && /* @__PURE__ */ jsxs10(
|
|
1218
|
+
"a",
|
|
1219
|
+
{
|
|
1220
|
+
href: summarySourceUrl,
|
|
1221
|
+
target: "_blank",
|
|
1222
|
+
rel: "noopener noreferrer",
|
|
1223
|
+
className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
|
|
1224
|
+
children: [
|
|
1225
|
+
/* @__PURE__ */ jsx14(SiWikipedia, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
|
|
1226
|
+
"Wikipedia"
|
|
1227
|
+
]
|
|
1228
|
+
}
|
|
1229
|
+
),
|
|
1230
|
+
wikidataUrl && /* @__PURE__ */ jsxs10(
|
|
1231
|
+
"a",
|
|
1232
|
+
{
|
|
1233
|
+
href: wikidataUrl,
|
|
1234
|
+
target: "_blank",
|
|
1235
|
+
rel: "noopener noreferrer",
|
|
1236
|
+
className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
|
|
1237
|
+
children: [
|
|
1238
|
+
/* @__PURE__ */ jsx14(SiWikidata, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
|
|
1239
|
+
"Wikidata"
|
|
1240
|
+
]
|
|
1241
|
+
}
|
|
1242
|
+
)
|
|
1243
|
+
] })
|
|
1244
|
+
] }),
|
|
1245
|
+
renderSummary ? renderSummary(overview) : /* @__PURE__ */ jsxs10("div", { className: "gtv-info-panel-children text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: [
|
|
1246
|
+
/* @__PURE__ */ jsx14("span", { className: "gtv-info-panel-children-title", children: "Summary" }),
|
|
1247
|
+
/* @__PURE__ */ jsx14(ClampedText, { className: "mt-1", children: summary ?? "\u2014" }, genreUuid)
|
|
1248
|
+
] }),
|
|
1249
|
+
family,
|
|
1250
|
+
renderGenreDetailExtras?.(overview),
|
|
1251
|
+
renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && /* @__PURE__ */ jsxs10("div", { className: "gtv-info-panel-children text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: [
|
|
1252
|
+
/* @__PURE__ */ jsxs10("span", { className: "gtv-info-panel-children-title", children: [
|
|
1253
|
+
"Essential ",
|
|
1254
|
+
trackNoun.plural
|
|
1255
|
+
] }),
|
|
1256
|
+
essentialSongs.length > 0 ? /* @__PURE__ */ jsx14("ul", { className: "mt-1 divide-y divide-[var(--color-border)]", children: essentialSongs.map((track) => /* @__PURE__ */ jsx14("li", { className: "py-1", children: track.title }, track.uuid)) }) : /* @__PURE__ */ jsx14("p", { className: "mt-1", children: "\u2014" })
|
|
1257
|
+
] }),
|
|
1258
|
+
/* @__PURE__ */ jsx14(
|
|
1259
|
+
GenrePlaylistTracks,
|
|
1260
|
+
{
|
|
1261
|
+
genreUuid,
|
|
1262
|
+
genrePlaylist,
|
|
1263
|
+
scope,
|
|
1264
|
+
getBackendBaseUrl,
|
|
1265
|
+
renderArtistLink,
|
|
1266
|
+
...trackSlots
|
|
1267
|
+
}
|
|
1268
|
+
)
|
|
1146
1269
|
] });
|
|
1147
1270
|
}
|
|
1148
1271
|
|
|
1149
|
-
// src/genre-tree/
|
|
1150
|
-
import { jsx as
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
const
|
|
1154
|
-
|
|
1272
|
+
// src/genre-tree/GenreInfoPanelBody.tsx
|
|
1273
|
+
import { Fragment as Fragment6, jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1274
|
+
import { createElement } from "react";
|
|
1275
|
+
function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
|
|
1276
|
+
const { isPlaying, setIsPlaying } = usePlayer5();
|
|
1277
|
+
const isCurrentTrackList = playback.playingNodeId === genrePlaylist.uuid;
|
|
1278
|
+
const handlePlayPause = () => {
|
|
1279
|
+
if (isCurrentTrackList) {
|
|
1280
|
+
setIsPlaying(!isPlaying);
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1283
|
+
playback.play(genrePlaylist).catch((error) => {
|
|
1284
|
+
console.error("Failed to load genre playlist tracks:", error);
|
|
1285
|
+
});
|
|
1155
1286
|
};
|
|
1156
|
-
return /* @__PURE__ */
|
|
1157
|
-
|
|
1287
|
+
return /* @__PURE__ */ jsx15(
|
|
1288
|
+
GenreActionBand,
|
|
1158
1289
|
{
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
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
|
-
]
|
|
1290
|
+
...bandProps,
|
|
1291
|
+
onPlayPause: genrePlaylist.tracksCount > 0 ? handlePlayPause : void 0,
|
|
1292
|
+
isPlaying: isCurrentTrackList && isPlaying
|
|
1168
1293
|
}
|
|
1169
1294
|
);
|
|
1170
1295
|
}
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
side,
|
|
1183
|
-
onToggleSide,
|
|
1184
|
-
toggleSideDisabledReason,
|
|
1185
|
-
additionalActions,
|
|
1186
|
-
shareTarget,
|
|
1187
|
-
shareUrl
|
|
1296
|
+
function GenreInfoPanelBody({
|
|
1297
|
+
toolbar,
|
|
1298
|
+
family,
|
|
1299
|
+
genreUuid,
|
|
1300
|
+
genrePlaylist,
|
|
1301
|
+
wikidataId,
|
|
1302
|
+
scope,
|
|
1303
|
+
getBackendBaseUrl,
|
|
1304
|
+
playback,
|
|
1305
|
+
band,
|
|
1306
|
+
extras
|
|
1188
1307
|
}) {
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
label: isPlaying ? "Pause" : "Play",
|
|
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" }),
|
|
1196
|
-
onClick: onPlayPause
|
|
1197
|
-
});
|
|
1198
|
-
}
|
|
1199
|
-
if (onZoomIn) {
|
|
1200
|
-
items.push({
|
|
1201
|
-
key: "zoom-in",
|
|
1202
|
-
label: "Focus",
|
|
1203
|
-
icon: /* @__PURE__ */ jsx14(ZoomIn, { "aria-hidden": "true", className: "w-5 h-5" }),
|
|
1204
|
-
onClick: onZoomIn
|
|
1205
|
-
});
|
|
1206
|
-
}
|
|
1207
|
-
if (onAddChild) {
|
|
1208
|
-
adminItems.push({ key: "add-child", label: "Add", icon: /* @__PURE__ */ jsx14(Plus, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onAddChild });
|
|
1209
|
-
}
|
|
1210
|
-
if (onRename) {
|
|
1211
|
-
adminItems.push({ key: "rename", label: "Rename", icon: /* @__PURE__ */ jsx14(Pencil, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onRename });
|
|
1212
|
-
}
|
|
1213
|
-
if (onChangeParent) {
|
|
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
|
-
});
|
|
1225
|
-
}
|
|
1226
|
-
for (const action of additionalActions?.(node) ?? []) {
|
|
1227
|
-
items.push({
|
|
1228
|
-
key: action.key,
|
|
1229
|
-
label: action.label(node),
|
|
1230
|
-
icon: action.icon(node),
|
|
1231
|
-
onClick: (event) => action.onClick(event, node),
|
|
1232
|
-
disabled: action.enabled ? !action.enabled(node) : false
|
|
1233
|
-
});
|
|
1234
|
-
}
|
|
1235
|
-
if (items.length === 0 && (!shareTarget || !shareUrl) && adminItems.length === 0) return null;
|
|
1236
|
-
const renderItems = (list) => list.map((item) => /* @__PURE__ */ jsxs10(
|
|
1237
|
-
"button",
|
|
1238
|
-
{
|
|
1239
|
-
type: "button",
|
|
1240
|
-
disabled: item.disabled,
|
|
1241
|
-
title: item.title,
|
|
1242
|
-
onClick: item.onClick,
|
|
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",
|
|
1244
|
-
children: [
|
|
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 }),
|
|
1246
|
-
item.label
|
|
1247
|
-
]
|
|
1248
|
-
},
|
|
1249
|
-
item.key
|
|
1250
|
-
));
|
|
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(
|
|
1257
|
-
"section",
|
|
1308
|
+
return /* @__PURE__ */ jsxs11(Fragment6, { children: [
|
|
1309
|
+
toolbar,
|
|
1310
|
+
/* @__PURE__ */ jsx15(GenreNodeActionBand, { ...band, genrePlaylist, playback }),
|
|
1311
|
+
/* @__PURE__ */ createElement(
|
|
1312
|
+
GenreDetailExtras,
|
|
1258
1313
|
{
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1314
|
+
...extras,
|
|
1315
|
+
key: genreUuid,
|
|
1316
|
+
family,
|
|
1317
|
+
genreUuid,
|
|
1318
|
+
genrePlaylist,
|
|
1319
|
+
wikidataId,
|
|
1320
|
+
scope,
|
|
1321
|
+
getBackendBaseUrl
|
|
1265
1322
|
}
|
|
1266
1323
|
)
|
|
1267
1324
|
] });
|
|
@@ -1270,14 +1327,14 @@ function GenreActionBand({
|
|
|
1270
1327
|
// src/genre-tree/PanelEdgeTab.tsx
|
|
1271
1328
|
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
1272
1329
|
import { twMerge } from "tailwind-merge";
|
|
1273
|
-
import { jsx as
|
|
1330
|
+
import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1274
1331
|
var buttonClassName = "flex h-12 w-full items-center justify-center p-0 text-[var(--color-text-muted)] hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-text)] focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--color-primary)]";
|
|
1275
1332
|
function PanelEdgeTab({ side, edge, center, controls, className: className2 }) {
|
|
1276
1333
|
if (!edge && !center) return null;
|
|
1277
1334
|
const isLeft = side === "left";
|
|
1278
1335
|
const EdgeIcon = isLeft ? ChevronLeft : ChevronRight;
|
|
1279
1336
|
const CenterIcon = isLeft ? ChevronRight : ChevronLeft;
|
|
1280
|
-
const renderButton = (action, icon) => /* @__PURE__ */
|
|
1337
|
+
const renderButton = (action, icon) => /* @__PURE__ */ jsx16(
|
|
1281
1338
|
"button",
|
|
1282
1339
|
{
|
|
1283
1340
|
type: "button",
|
|
@@ -1290,10 +1347,10 @@ function PanelEdgeTab({ side, edge, center, controls, className: className2 }) {
|
|
|
1290
1347
|
children: icon
|
|
1291
1348
|
}
|
|
1292
1349
|
);
|
|
1293
|
-
const edgeButton = edge && renderButton(edge, /* @__PURE__ */
|
|
1294
|
-
const centerButton = center && renderButton(center, /* @__PURE__ */
|
|
1350
|
+
const edgeButton = edge && renderButton(edge, /* @__PURE__ */ jsx16(EdgeIcon, { "aria-hidden": "true", className: "h-5 w-5" }));
|
|
1351
|
+
const centerButton = center && renderButton(center, /* @__PURE__ */ jsx16(CenterIcon, { "aria-hidden": "true", className: "h-5 w-5" }));
|
|
1295
1352
|
const buttons = isLeft ? [edgeButton, centerButton] : [centerButton, edgeButton];
|
|
1296
|
-
return /* @__PURE__ */
|
|
1353
|
+
return /* @__PURE__ */ jsxs12(
|
|
1297
1354
|
"div",
|
|
1298
1355
|
{
|
|
1299
1356
|
className: twMerge(
|
|
@@ -1303,20 +1360,33 @@ function PanelEdgeTab({ side, edge, center, controls, className: className2 }) {
|
|
|
1303
1360
|
),
|
|
1304
1361
|
children: [
|
|
1305
1362
|
buttons[0],
|
|
1306
|
-
buttons[0] && buttons[1] && /* @__PURE__ */
|
|
1363
|
+
buttons[0] && buttons[1] && /* @__PURE__ */ jsx16("div", { role: "presentation", className: "h-px shrink-0 bg-[var(--color-border)]" }),
|
|
1307
1364
|
buttons[1]
|
|
1308
1365
|
]
|
|
1309
1366
|
}
|
|
1310
1367
|
);
|
|
1311
1368
|
}
|
|
1312
1369
|
|
|
1370
|
+
// src/genre-tree/useLeftDetailPanelLayout.ts
|
|
1371
|
+
import { useEffect as useEffect4 } from "react";
|
|
1372
|
+
function useLeftDetailPanelLayout(isPanelShown, enabled = true) {
|
|
1373
|
+
useEffect4(() => {
|
|
1374
|
+
if (!enabled || isPanelShown) return void 0;
|
|
1375
|
+
const root = document.documentElement;
|
|
1376
|
+
root.style.setProperty("--layout-left-w", "0px");
|
|
1377
|
+
return () => {
|
|
1378
|
+
root.style.removeProperty("--layout-left-w");
|
|
1379
|
+
};
|
|
1380
|
+
}, [enabled, isPanelShown]);
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1313
1383
|
// src/genre-tree/ChangeParentPopup.tsx
|
|
1314
|
-
import { useEffect as
|
|
1384
|
+
import { useEffect as useEffect5, useMemo as useMemo6 } from "react";
|
|
1315
1385
|
import {
|
|
1316
1386
|
getReparentCandidates,
|
|
1317
1387
|
useUpdateGenre
|
|
1318
1388
|
} from "@behindthemusictree/app-kit/genre-tree";
|
|
1319
|
-
import { jsx as
|
|
1389
|
+
import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1320
1390
|
function ChangeParentPopup({
|
|
1321
1391
|
genrePlaylist,
|
|
1322
1392
|
genrePlaylists,
|
|
@@ -1344,19 +1414,19 @@ function ChangeParentPopup({
|
|
|
1344
1414
|
}
|
|
1345
1415
|
);
|
|
1346
1416
|
};
|
|
1347
|
-
|
|
1417
|
+
useEffect5(() => {
|
|
1348
1418
|
const handleKeyDown = (event) => {
|
|
1349
1419
|
if (event.key === "Escape") onClose();
|
|
1350
1420
|
};
|
|
1351
1421
|
window.addEventListener("keydown", handleKeyDown);
|
|
1352
1422
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
1353
1423
|
}, [onClose]);
|
|
1354
|
-
return /* @__PURE__ */
|
|
1355
|
-
/* @__PURE__ */
|
|
1424
|
+
return /* @__PURE__ */ jsxs13(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
|
|
1425
|
+
/* @__PURE__ */ jsxs13("p", { className: "mb-3 text-sm text-[var(--color-text-muted)]", children: [
|
|
1356
1426
|
"Current parent: ",
|
|
1357
1427
|
genrePlaylist.parent?.name ?? "none"
|
|
1358
1428
|
] }),
|
|
1359
|
-
/* @__PURE__ */
|
|
1429
|
+
/* @__PURE__ */ jsx17("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ jsx17(
|
|
1360
1430
|
GenreSearch,
|
|
1361
1431
|
{
|
|
1362
1432
|
genrePlaylists: candidates,
|
|
@@ -1364,7 +1434,7 @@ function ChangeParentPopup({
|
|
|
1364
1434
|
placeholder: "Search a new parent\u2026"
|
|
1365
1435
|
}
|
|
1366
1436
|
) }),
|
|
1367
|
-
formErrors.length > 0 && /* @__PURE__ */
|
|
1437
|
+
formErrors.length > 0 && /* @__PURE__ */ jsx17("p", { role: "alert", className: "mt-3 text-sm text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
|
|
1368
1438
|
] });
|
|
1369
1439
|
}
|
|
1370
1440
|
|
|
@@ -1388,7 +1458,7 @@ import {
|
|
|
1388
1458
|
SiYoutube
|
|
1389
1459
|
} from "react-icons/si";
|
|
1390
1460
|
import { Chip } from "@behindthemusictree/ui";
|
|
1391
|
-
import { jsx as
|
|
1461
|
+
import { jsx as jsx18 } from "react/jsx-runtime";
|
|
1392
1462
|
var LINK_KINDS = {
|
|
1393
1463
|
official: { label: "Official website", Icon: Globe },
|
|
1394
1464
|
wikipedia: { label: "Wikipedia", Icon: SiWikipedia2 },
|
|
@@ -1425,7 +1495,7 @@ function artistLinks(artist) {
|
|
|
1425
1495
|
return links;
|
|
1426
1496
|
}
|
|
1427
1497
|
function ArtistLinks({ links }) {
|
|
1428
|
-
return /* @__PURE__ */
|
|
1498
|
+
return /* @__PURE__ */ jsx18("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ jsx18("li", { children: /* @__PURE__ */ jsx18(Chip, { href: url, icon: /* @__PURE__ */ jsx18(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
|
|
1429
1499
|
}
|
|
1430
1500
|
|
|
1431
1501
|
// src/genre-tree/artist/formatYears.ts
|
|
@@ -1436,7 +1506,7 @@ function formatYears(beginYear, endYear, ended) {
|
|
|
1436
1506
|
}
|
|
1437
1507
|
|
|
1438
1508
|
// src/genre-tree/artist/ArtistHeader.tsx
|
|
1439
|
-
import { jsx as
|
|
1509
|
+
import { jsx as jsx19 } from "react/jsx-runtime";
|
|
1440
1510
|
var countryNames = new Intl.DisplayNames(["en"], { type: "region" });
|
|
1441
1511
|
function ArtistHeader({ artist }) {
|
|
1442
1512
|
const fields = [];
|
|
@@ -1446,35 +1516,35 @@ function ArtistHeader({ artist }) {
|
|
|
1446
1516
|
const years = formatYears(artist.beginYear, artist.endYear, artist.ended);
|
|
1447
1517
|
if (years) fields.push({ label: "Active", value: years });
|
|
1448
1518
|
const links = artistLinks(artist);
|
|
1449
|
-
if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */
|
|
1519
|
+
if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ jsx19(ArtistLinks, { links }) });
|
|
1450
1520
|
if (fields.length === 0) return null;
|
|
1451
|
-
return /* @__PURE__ */
|
|
1521
|
+
return /* @__PURE__ */ jsx19(FieldList, { items: fields });
|
|
1452
1522
|
}
|
|
1453
1523
|
|
|
1454
1524
|
// src/genre-tree/artist/ArtistPhoto.tsx
|
|
1455
|
-
import { jsx as
|
|
1525
|
+
import { jsx as jsx20, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1456
1526
|
function ExternalLink({ href, children }) {
|
|
1457
|
-
return /* @__PURE__ */
|
|
1527
|
+
return /* @__PURE__ */ jsx20("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
|
|
1458
1528
|
}
|
|
1459
1529
|
function ArtistPhoto({ image, name }) {
|
|
1460
|
-
return /* @__PURE__ */
|
|
1461
|
-
/* @__PURE__ */
|
|
1462
|
-
/* @__PURE__ */
|
|
1530
|
+
return /* @__PURE__ */ jsxs14("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
|
|
1531
|
+
/* @__PURE__ */ jsx20("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
|
|
1532
|
+
/* @__PURE__ */ jsxs14("figcaption", { className: "text-xs text-[var(--color-text-muted)]", children: [
|
|
1463
1533
|
"Photo: ",
|
|
1464
|
-
/* @__PURE__ */
|
|
1534
|
+
/* @__PURE__ */ jsx20(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
|
|
1465
1535
|
",",
|
|
1466
1536
|
" ",
|
|
1467
|
-
image.licenseUrl ? /* @__PURE__ */
|
|
1537
|
+
image.licenseUrl ? /* @__PURE__ */ jsx20(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
|
|
1468
1538
|
] })
|
|
1469
1539
|
] });
|
|
1470
1540
|
}
|
|
1471
1541
|
|
|
1472
1542
|
// src/genre-tree/artist/ArtistMembers.tsx
|
|
1473
1543
|
import { PanelGroup, PanelSection } from "@behindthemusictree/ui";
|
|
1474
|
-
import { jsx as
|
|
1544
|
+
import { jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1475
1545
|
function MembershipList({ title, memberships, renderArtistLink }) {
|
|
1476
1546
|
if (memberships.length === 0) return null;
|
|
1477
|
-
return /* @__PURE__ */
|
|
1547
|
+
return /* @__PURE__ */ jsx21(PanelGroup, { title, children: /* @__PURE__ */ jsx21("ul", { children: memberships.map((membership, index) => {
|
|
1478
1548
|
const details = [
|
|
1479
1549
|
formatYears(membership.beginYear, membership.endYear, membership.ended),
|
|
1480
1550
|
...membership.roles
|
|
@@ -1482,9 +1552,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
|
|
|
1482
1552
|
const detailsText = details.filter(Boolean).join(" \xB7 ");
|
|
1483
1553
|
return (
|
|
1484
1554
|
// A member can have several periods in the same group.
|
|
1485
|
-
/* @__PURE__ */
|
|
1486
|
-
/* @__PURE__ */
|
|
1487
|
-
detailsText && /* @__PURE__ */
|
|
1555
|
+
/* @__PURE__ */ jsxs15("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
|
|
1556
|
+
/* @__PURE__ */ jsx21("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
|
|
1557
|
+
detailsText && /* @__PURE__ */ jsxs15("span", { className: "text-[var(--color-text-muted)]", children: [
|
|
1488
1558
|
" \u2014 ",
|
|
1489
1559
|
detailsText
|
|
1490
1560
|
] })
|
|
@@ -1495,9 +1565,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
|
|
|
1495
1565
|
function ArtistMembers({ title, memberships, renderArtistLink }) {
|
|
1496
1566
|
if (memberships.length === 0) return null;
|
|
1497
1567
|
const isCurrent = (membership) => !membership.ended && membership.endYear === null;
|
|
1498
|
-
return /* @__PURE__ */
|
|
1499
|
-
/* @__PURE__ */
|
|
1500
|
-
/* @__PURE__ */
|
|
1568
|
+
return /* @__PURE__ */ jsxs15(PanelSection, { title, children: [
|
|
1569
|
+
/* @__PURE__ */ jsx21(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
|
|
1570
|
+
/* @__PURE__ */ jsx21(
|
|
1501
1571
|
MembershipList,
|
|
1502
1572
|
{
|
|
1503
1573
|
title: "Past",
|
|
@@ -1510,7 +1580,7 @@ function ArtistMembers({ title, memberships, renderArtistLink }) {
|
|
|
1510
1580
|
|
|
1511
1581
|
// src/genre-tree/artist/ArtistDiscography.tsx
|
|
1512
1582
|
import { PanelGroup as PanelGroup2, PanelSection as PanelSection2 } from "@behindthemusictree/ui";
|
|
1513
|
-
import { jsx as
|
|
1583
|
+
import { jsx as jsx22, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1514
1584
|
var GROUPS = ["Albums", "EPs", "Singles", "Live", "Compilations", "Other"];
|
|
1515
1585
|
var PRIMARY_GROUPS = { album: "Albums", ep: "EPs", single: "Singles" };
|
|
1516
1586
|
function discographyGroup({ primaryType, secondaryTypes }) {
|
|
@@ -1530,12 +1600,12 @@ function byYear(a, b) {
|
|
|
1530
1600
|
function ArtistDiscography({ albums }) {
|
|
1531
1601
|
if (albums.length === 0) return null;
|
|
1532
1602
|
const sorted = [...albums].sort(byYear);
|
|
1533
|
-
return /* @__PURE__ */
|
|
1603
|
+
return /* @__PURE__ */ jsx22(PanelSection2, { title: "Discography", children: GROUPS.map((group) => {
|
|
1534
1604
|
const groupAlbums = sorted.filter((album) => discographyGroup(album) === group);
|
|
1535
1605
|
if (groupAlbums.length === 0) return null;
|
|
1536
|
-
return /* @__PURE__ */
|
|
1537
|
-
/* @__PURE__ */
|
|
1538
|
-
/* @__PURE__ */
|
|
1606
|
+
return /* @__PURE__ */ jsx22(PanelGroup2, { title: group, children: /* @__PURE__ */ jsx22("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ jsxs16("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
|
|
1607
|
+
/* @__PURE__ */ jsx22("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
|
|
1608
|
+
/* @__PURE__ */ jsx22("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
|
|
1539
1609
|
] }, album.uuid)) }) }, group);
|
|
1540
1610
|
}) });
|
|
1541
1611
|
}
|
|
@@ -1545,7 +1615,7 @@ import { useState as useState4 } from "react";
|
|
|
1545
1615
|
import { keepPreviousData } from "@tanstack/react-query";
|
|
1546
1616
|
import { Play as Play2 } from "lucide-react";
|
|
1547
1617
|
import { Pagination, PanelSection as PanelSection3, Skeleton as Skeleton3 } from "@behindthemusictree/ui";
|
|
1548
|
-
import { usePlayer as
|
|
1618
|
+
import { usePlayer as usePlayer8 } from "@behindthemusictree/app-kit/player";
|
|
1549
1619
|
import {
|
|
1550
1620
|
SongDetailedSchema as SongDetailedSchema2,
|
|
1551
1621
|
libraryEndpoints as libraryEndpoints2,
|
|
@@ -1555,35 +1625,35 @@ import {
|
|
|
1555
1625
|
import { PaginatedResponseSchema as PaginatedResponseSchema2, useFetchWrapper as useFetchWrapper2, useQueryWithParse } from "@behindthemusictree/app-kit/transport";
|
|
1556
1626
|
|
|
1557
1627
|
// src/genre-tree/track-list-sidebar/TrackItem.tsx
|
|
1558
|
-
import { usePlayer as
|
|
1628
|
+
import { usePlayer as usePlayer7 } from "@behindthemusictree/app-kit/player";
|
|
1559
1629
|
import { useTrackList as useTrackList3 } from "@behindthemusictree/app-kit/genre-tree";
|
|
1560
1630
|
|
|
1561
1631
|
// src/genre-tree/TrackPositionPlayPause.tsx
|
|
1562
1632
|
import { FaPlay as FaPlay2, FaPause as FaPause2 } from "react-icons/fa";
|
|
1563
|
-
import { usePlayer as
|
|
1633
|
+
import { usePlayer as usePlayer6 } from "@behindthemusictree/app-kit/player";
|
|
1564
1634
|
import { PlayStates as PlayStates2 } from "@behindthemusictree/app-kit/player";
|
|
1565
|
-
import { jsx as
|
|
1635
|
+
import { jsx as jsx23, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
1566
1636
|
function TrackPositionPlayPause({
|
|
1567
1637
|
position,
|
|
1568
1638
|
uuid,
|
|
1569
1639
|
handlePlayPauseClick,
|
|
1570
1640
|
isSelected = false
|
|
1571
1641
|
}) {
|
|
1572
|
-
const { playerTrackObject, playbackConfirmedTrackId, playState, isLoading: isPlayerLoading } =
|
|
1642
|
+
const { playerTrackObject, playbackConfirmedTrackId, playState, isLoading: isPlayerLoading } = usePlayer6();
|
|
1573
1643
|
const isThisTrack = playerTrackObject?.track.id === uuid;
|
|
1574
1644
|
const isConfirmedPlayable = playbackConfirmedTrackId === uuid;
|
|
1575
1645
|
const isAwaitingPlayback = (isSelected || isThisTrack) && !isConfirmedPlayable && !(isThisTrack && playerTrackObject?.loadError) && (!isThisTrack || isPlayerLoading || playState === PlayStates2.LOADING || playState === PlayStates2.PLAYING);
|
|
1576
1646
|
const isPlaying = isThisTrack && isConfirmedPlayable && playState === PlayStates2.PLAYING;
|
|
1577
1647
|
const isPaused = isThisTrack && isConfirmedPlayable && playState === PlayStates2.PAUSED;
|
|
1578
1648
|
const isCurrentTrackActive = isPlaying || isPaused;
|
|
1579
|
-
return /* @__PURE__ */
|
|
1649
|
+
return /* @__PURE__ */ jsxs17(
|
|
1580
1650
|
"div",
|
|
1581
1651
|
{
|
|
1582
1652
|
className: "track-position-play-pause relative flex items-center justify-center gap-2 text-lg w-16 cursor-pointer",
|
|
1583
1653
|
style: { minWidth: "4rem", maxWidth: "4rem" },
|
|
1584
1654
|
onClick: handlePlayPauseClick,
|
|
1585
1655
|
children: [
|
|
1586
|
-
/* @__PURE__ */
|
|
1656
|
+
/* @__PURE__ */ jsx23("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ jsx23("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ jsxs17(
|
|
1587
1657
|
"span",
|
|
1588
1658
|
{
|
|
1589
1659
|
className: "track-position-play-pause__bars",
|
|
@@ -1591,18 +1661,18 @@ function TrackPositionPlayPause({
|
|
|
1591
1661
|
role: "img",
|
|
1592
1662
|
"aria-label": isPlaying ? "Playing" : "Paused",
|
|
1593
1663
|
children: [
|
|
1594
|
-
/* @__PURE__ */
|
|
1595
|
-
/* @__PURE__ */
|
|
1596
|
-
/* @__PURE__ */
|
|
1664
|
+
/* @__PURE__ */ jsx23("span", { className: "track-position-play-pause__bar" }),
|
|
1665
|
+
/* @__PURE__ */ jsx23("span", { className: "track-position-play-pause__bar" }),
|
|
1666
|
+
/* @__PURE__ */ jsx23("span", { className: "track-position-play-pause__bar" })
|
|
1597
1667
|
]
|
|
1598
1668
|
}
|
|
1599
1669
|
) : position }),
|
|
1600
|
-
/* @__PURE__ */
|
|
1670
|
+
/* @__PURE__ */ jsx23(
|
|
1601
1671
|
"div",
|
|
1602
1672
|
{
|
|
1603
1673
|
className: `hidden group-hover:flex items-center justify-center ${isCurrentTrackActive ? "" : "absolute inset-0"}`,
|
|
1604
1674
|
"aria-hidden": "true",
|
|
1605
|
-
children: isAwaitingPlayback ? /* @__PURE__ */
|
|
1675
|
+
children: isAwaitingPlayback ? /* @__PURE__ */ jsx23("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ jsx23(FaPause2, {}) : /* @__PURE__ */ jsx23(FaPlay2, {})
|
|
1606
1676
|
}
|
|
1607
1677
|
)
|
|
1608
1678
|
]
|
|
@@ -1613,7 +1683,7 @@ function TrackPositionPlayPause({
|
|
|
1613
1683
|
// src/genre-tree/track-list-sidebar/TrackItem.tsx
|
|
1614
1684
|
import { isSong as isSong2, songUnplayableReason as songUnplayableReason2 } from "@behindthemusictree/app-kit/genre-tree";
|
|
1615
1685
|
import { ArtistNames as ArtistNames2 } from "@behindthemusictree/app-kit/genre-tree";
|
|
1616
|
-
import { jsx as
|
|
1686
|
+
import { jsx as jsx24, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
1617
1687
|
function TrackItem({
|
|
1618
1688
|
track,
|
|
1619
1689
|
position,
|
|
@@ -1621,7 +1691,7 @@ function TrackItem({
|
|
|
1621
1691
|
renderActions,
|
|
1622
1692
|
renderArtistLink
|
|
1623
1693
|
}) {
|
|
1624
|
-
const { handlePlayPauseAction, playerTrackObject, loadTrackForPlayer } =
|
|
1694
|
+
const { handlePlayPauseAction, playerTrackObject, loadTrackForPlayer } = usePlayer7();
|
|
1625
1695
|
const { trackList, selectedTrack, toTrackAtPosition, runtimeUnplayableTrackIds } = useTrackList3();
|
|
1626
1696
|
const scope = trackList?.origin?.scope ?? null;
|
|
1627
1697
|
const isCurrentTrack = playerTrackObject?.track.id === track.uuid;
|
|
@@ -1636,12 +1706,12 @@ function TrackItem({
|
|
|
1636
1706
|
loadTrackForPlayer(track.uuid);
|
|
1637
1707
|
}
|
|
1638
1708
|
};
|
|
1639
|
-
return /* @__PURE__ */
|
|
1709
|
+
return /* @__PURE__ */ jsxs18(
|
|
1640
1710
|
"div",
|
|
1641
1711
|
{
|
|
1642
1712
|
className: `track-item flex h-14 text-[var(--color-text)] ${isUnplayable ? "opacity-50" : "hover:bg-[var(--color-surface-muted)] group"}`,
|
|
1643
1713
|
children: [
|
|
1644
|
-
isUnplayable ? /* @__PURE__ */
|
|
1714
|
+
isUnplayable ? /* @__PURE__ */ jsx24(
|
|
1645
1715
|
"div",
|
|
1646
1716
|
{
|
|
1647
1717
|
className: "flex items-center justify-center text-lg w-16",
|
|
@@ -1649,7 +1719,7 @@ function TrackItem({
|
|
|
1649
1719
|
"aria-disabled": "true",
|
|
1650
1720
|
children: position
|
|
1651
1721
|
}
|
|
1652
|
-
) : /* @__PURE__ */
|
|
1722
|
+
) : /* @__PURE__ */ jsx24(
|
|
1653
1723
|
TrackPositionPlayPause,
|
|
1654
1724
|
{
|
|
1655
1725
|
position,
|
|
@@ -1658,12 +1728,12 @@ function TrackItem({
|
|
|
1658
1728
|
isSelected: selectedTrack?.uuid === track.uuid
|
|
1659
1729
|
}
|
|
1660
1730
|
),
|
|
1661
|
-
/* @__PURE__ */
|
|
1662
|
-
/* @__PURE__ */
|
|
1663
|
-
track.artists && track.artists.length > 0 ? /* @__PURE__ */
|
|
1731
|
+
/* @__PURE__ */ jsxs18("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
|
|
1732
|
+
/* @__PURE__ */ jsx24("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ jsx24("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: isSong2(track) ? /* @__PURE__ */ jsx24(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
|
|
1733
|
+
track.artists && track.artists.length > 0 ? /* @__PURE__ */ jsx24("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ jsx24(ArtistNames2, { artists: track.artists, renderArtistLink }) }) : ""
|
|
1664
1734
|
] }),
|
|
1665
|
-
/* @__PURE__ */
|
|
1666
|
-
renderDuration && /* @__PURE__ */
|
|
1735
|
+
/* @__PURE__ */ jsx24("div", { className: "genre-name-container flex items-center justify-start w-[21.6%] min-w-0 shrink-0", children: track.genre ? /* @__PURE__ */ jsx24("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
|
|
1736
|
+
renderDuration && /* @__PURE__ */ jsx24(
|
|
1667
1737
|
"div",
|
|
1668
1738
|
{
|
|
1669
1739
|
className: "duration flex text-base w-16 items-center justify-center",
|
|
@@ -1671,14 +1741,14 @@ function TrackItem({
|
|
|
1671
1741
|
children: renderDuration(track)
|
|
1672
1742
|
}
|
|
1673
1743
|
),
|
|
1674
|
-
renderActions && /* @__PURE__ */
|
|
1744
|
+
renderActions && /* @__PURE__ */ jsx24("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
|
|
1675
1745
|
]
|
|
1676
1746
|
}
|
|
1677
1747
|
);
|
|
1678
1748
|
}
|
|
1679
1749
|
|
|
1680
1750
|
// src/genre-tree/artist/ArtistSongs.tsx
|
|
1681
|
-
import { Fragment as
|
|
1751
|
+
import { Fragment as Fragment7, jsx as jsx25, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
1682
1752
|
var SongPageSchema2 = PaginatedResponseSchema2(SongDetailedSchema2);
|
|
1683
1753
|
var SORTS = [
|
|
1684
1754
|
{ ordering: "title", label: "Title" },
|
|
@@ -1689,7 +1759,7 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
|
1689
1759
|
const [ordering, setOrdering] = useState4("title");
|
|
1690
1760
|
const { fetch } = useFetchWrapper2(getBackendBaseUrl);
|
|
1691
1761
|
const { playNewTrackListFromArtist } = useTrackList4();
|
|
1692
|
-
const { playerTrackObject } =
|
|
1762
|
+
const { playerTrackObject } = usePlayer8();
|
|
1693
1763
|
const { data, isPending, isError } = useQueryWithParse({
|
|
1694
1764
|
queryKey: ["referenceSongs", "artist", artist.uuid, ordering, page],
|
|
1695
1765
|
queryFn: () => fetch(libraryEndpoints2.reference.songs.list(), true, false, void 0, {
|
|
@@ -1711,21 +1781,21 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
|
1711
1781
|
event.stopPropagation();
|
|
1712
1782
|
void play(song.uuid);
|
|
1713
1783
|
};
|
|
1714
|
-
return /* @__PURE__ */
|
|
1715
|
-
/* @__PURE__ */
|
|
1716
|
-
/* @__PURE__ */
|
|
1784
|
+
return /* @__PURE__ */ jsx25(PanelSection3, { title: "Songs", children: isPending ? /* @__PURE__ */ jsx25(Skeleton3, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ jsx25("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ jsx25("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ jsxs19(Fragment7, { children: [
|
|
1785
|
+
/* @__PURE__ */ jsxs19("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
|
|
1786
|
+
/* @__PURE__ */ jsxs19(
|
|
1717
1787
|
"button",
|
|
1718
1788
|
{
|
|
1719
1789
|
type: "button",
|
|
1720
1790
|
onClick: () => void play(),
|
|
1721
1791
|
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",
|
|
1722
1792
|
children: [
|
|
1723
|
-
/* @__PURE__ */
|
|
1793
|
+
/* @__PURE__ */ jsx25(Play2, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
|
|
1724
1794
|
"Play all"
|
|
1725
1795
|
]
|
|
1726
1796
|
}
|
|
1727
1797
|
),
|
|
1728
|
-
/* @__PURE__ */
|
|
1798
|
+
/* @__PURE__ */ jsx25("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ jsx25(
|
|
1729
1799
|
"button",
|
|
1730
1800
|
{
|
|
1731
1801
|
type: "button",
|
|
@@ -1740,7 +1810,7 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
|
1740
1810
|
sort.ordering
|
|
1741
1811
|
)) })
|
|
1742
1812
|
] }),
|
|
1743
|
-
/* @__PURE__ */
|
|
1813
|
+
/* @__PURE__ */ jsx25("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ jsx25("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ jsx25(
|
|
1744
1814
|
TrackItem,
|
|
1745
1815
|
{
|
|
1746
1816
|
track: song,
|
|
@@ -1748,12 +1818,12 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
|
1748
1818
|
renderArtistLink
|
|
1749
1819
|
}
|
|
1750
1820
|
) }, song.uuid)) }),
|
|
1751
|
-
/* @__PURE__ */
|
|
1821
|
+
/* @__PURE__ */ jsx25(Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
|
|
1752
1822
|
] }) });
|
|
1753
1823
|
}
|
|
1754
1824
|
|
|
1755
1825
|
// src/genre-tree/artist/useArtistPanel.tsx
|
|
1756
|
-
import { Fragment as
|
|
1826
|
+
import { Fragment as Fragment8, jsx as jsx26, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
1757
1827
|
function useArtistPanel({
|
|
1758
1828
|
uuid,
|
|
1759
1829
|
scope,
|
|
@@ -1773,26 +1843,26 @@ function useArtistPanel({
|
|
|
1773
1843
|
return {
|
|
1774
1844
|
...panel,
|
|
1775
1845
|
title: "Artist",
|
|
1776
|
-
content: /* @__PURE__ */
|
|
1777
|
-
/* @__PURE__ */
|
|
1778
|
-
/* @__PURE__ */
|
|
1779
|
-
/* @__PURE__ */
|
|
1780
|
-
/* @__PURE__ */
|
|
1781
|
-
/* @__PURE__ */
|
|
1782
|
-
/* @__PURE__ */
|
|
1846
|
+
content: /* @__PURE__ */ jsxs20("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
|
|
1847
|
+
/* @__PURE__ */ jsx26(Skeleton4, { className: "h-3 w-16" }),
|
|
1848
|
+
/* @__PURE__ */ jsx26(Skeleton4, { className: "h-4 w-32" }),
|
|
1849
|
+
/* @__PURE__ */ jsx26(Skeleton4, { className: "h-3 w-16" }),
|
|
1850
|
+
/* @__PURE__ */ jsx26(Skeleton4, { className: "h-4 w-24" }),
|
|
1851
|
+
/* @__PURE__ */ jsx26(Skeleton4, { className: "h-16 mt-2.5" }),
|
|
1852
|
+
/* @__PURE__ */ jsx26(Skeleton4, { className: "h-16" })
|
|
1783
1853
|
] })
|
|
1784
1854
|
};
|
|
1785
1855
|
case "not-found":
|
|
1786
1856
|
return {
|
|
1787
1857
|
...panel,
|
|
1788
1858
|
title: "Artist not found",
|
|
1789
|
-
content: /* @__PURE__ */
|
|
1859
|
+
content: /* @__PURE__ */ jsx26("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This artist does not exist." })
|
|
1790
1860
|
};
|
|
1791
1861
|
case "error":
|
|
1792
1862
|
return {
|
|
1793
1863
|
...panel,
|
|
1794
1864
|
title: "Artist",
|
|
1795
|
-
content: /* @__PURE__ */
|
|
1865
|
+
content: /* @__PURE__ */ jsx26("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
|
|
1796
1866
|
};
|
|
1797
1867
|
case "success": {
|
|
1798
1868
|
const { artist } = result;
|
|
@@ -1801,13 +1871,13 @@ function useArtistPanel({
|
|
|
1801
1871
|
return {
|
|
1802
1872
|
...panel,
|
|
1803
1873
|
title: artist.name,
|
|
1804
|
-
content: /* @__PURE__ */
|
|
1805
|
-
shareUrl && /* @__PURE__ */
|
|
1806
|
-
artist.image && /* @__PURE__ */
|
|
1807
|
-
/* @__PURE__ */
|
|
1808
|
-
/* @__PURE__ */
|
|
1809
|
-
/* @__PURE__ */
|
|
1810
|
-
artist.tracksCount > 0 && /* @__PURE__ */
|
|
1874
|
+
content: /* @__PURE__ */ jsxs20(Fragment8, { children: [
|
|
1875
|
+
shareUrl && /* @__PURE__ */ jsx26("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ jsx26("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: shareTarget && /* @__PURE__ */ jsx26(ShareActionButton, { target: shareTarget, url: shareUrl }) }) }),
|
|
1876
|
+
artist.image && /* @__PURE__ */ jsx26(ArtistPhoto, { image: artist.image, name: artist.name }),
|
|
1877
|
+
/* @__PURE__ */ jsx26(ArtistHeader, { artist }),
|
|
1878
|
+
/* @__PURE__ */ jsx26(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
|
|
1879
|
+
/* @__PURE__ */ jsx26(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
|
|
1880
|
+
artist.tracksCount > 0 && /* @__PURE__ */ jsx26(
|
|
1811
1881
|
ArtistSongs,
|
|
1812
1882
|
{
|
|
1813
1883
|
artist: { uuid: artist.uuid, name: artist.name },
|
|
@@ -1816,7 +1886,7 @@ function useArtistPanel({
|
|
|
1816
1886
|
}
|
|
1817
1887
|
)
|
|
1818
1888
|
] }),
|
|
1819
|
-
extraContent: artist.albums.length > 0 ? /* @__PURE__ */
|
|
1889
|
+
extraContent: artist.albums.length > 0 ? /* @__PURE__ */ jsx26(ArtistDiscography, { albums: artist.albums }) : void 0
|
|
1820
1890
|
};
|
|
1821
1891
|
}
|
|
1822
1892
|
}
|
|
@@ -1825,27 +1895,27 @@ function useArtistPanel({
|
|
|
1825
1895
|
// src/genre-tree/SizeMetricToggle.tsx
|
|
1826
1896
|
import { MdMusicNote, MdVisibility } from "react-icons/md";
|
|
1827
1897
|
import { SegmentedControl } from "@behindthemusictree/ui";
|
|
1828
|
-
import { jsx as
|
|
1898
|
+
import { jsx as jsx27 } from "react/jsx-runtime";
|
|
1829
1899
|
function SizeMetricToggle({
|
|
1830
1900
|
value,
|
|
1831
1901
|
onChange
|
|
1832
1902
|
}) {
|
|
1833
|
-
return /* @__PURE__ */
|
|
1903
|
+
return /* @__PURE__ */ jsx27(
|
|
1834
1904
|
SegmentedControl,
|
|
1835
1905
|
{
|
|
1836
1906
|
"aria-label": "Size nodes by",
|
|
1837
1907
|
value,
|
|
1838
1908
|
onChange,
|
|
1839
1909
|
options: [
|
|
1840
|
-
{ value: "songs", label: /* @__PURE__ */
|
|
1841
|
-
{ value: "views", label: /* @__PURE__ */
|
|
1910
|
+
{ value: "songs", label: /* @__PURE__ */ jsx27(MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
|
|
1911
|
+
{ value: "views", label: /* @__PURE__ */ jsx27(MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
|
|
1842
1912
|
]
|
|
1843
1913
|
}
|
|
1844
1914
|
);
|
|
1845
1915
|
}
|
|
1846
1916
|
|
|
1847
1917
|
// src/genre-tree/GenreTreeView.tsx
|
|
1848
|
-
import { Fragment as
|
|
1918
|
+
import { Fragment as Fragment9, jsx as jsx28, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
1849
1919
|
var PREVIOUS_BUTTON_CSS = `
|
|
1850
1920
|
.genre-tree-view .search-overlay .gtv-info-panel-previous { display: none; }
|
|
1851
1921
|
.genre-tree-view:has(.search-overlay .gtv-info-panel-previous) .genre-search input { padding-left: 2.75rem; }
|
|
@@ -1855,27 +1925,6 @@ var PREVIOUS_BUTTON_CSS = `
|
|
|
1855
1925
|
.genre-search-previous { animation: genre-tree-view-previous-in 200ms ease-out; }
|
|
1856
1926
|
}
|
|
1857
1927
|
`;
|
|
1858
|
-
function GenreNodeActionBand({ genrePlaylist, scope, playback, ...bandProps }) {
|
|
1859
|
-
const { isPlaying, setIsPlaying } = usePlayer8();
|
|
1860
|
-
const isCurrentTrackList = playback.playingNodeId === genrePlaylist.uuid;
|
|
1861
|
-
const handlePlayPause = () => {
|
|
1862
|
-
if (isCurrentTrackList) {
|
|
1863
|
-
setIsPlaying(!isPlaying);
|
|
1864
|
-
return;
|
|
1865
|
-
}
|
|
1866
|
-
playback.play(genrePlaylist).catch((error) => {
|
|
1867
|
-
console.error("Failed to load genre playlist tracks:", error);
|
|
1868
|
-
});
|
|
1869
|
-
};
|
|
1870
|
-
return /* @__PURE__ */ jsx27(
|
|
1871
|
-
GenreActionBand,
|
|
1872
|
-
{
|
|
1873
|
-
...bandProps,
|
|
1874
|
-
onPlayPause: genrePlaylist.tracksCount > 0 ? handlePlayPause : void 0,
|
|
1875
|
-
isPlaying: isCurrentTrackList && isPlaying
|
|
1876
|
-
}
|
|
1877
|
-
);
|
|
1878
|
-
}
|
|
1879
1928
|
var NO_POP_ROOT_TITLE = "This genre tree has no 'Mainstream Pop' root yet";
|
|
1880
1929
|
var REGIONS_CLASS = "[--gtv-center-left:var(--layout-center-left)] [--gtv-center-right:var(--layout-center-right)] [--gtv-center-top:var(--layout-band-top)] [&_.gtv-info-panel--overlay]:[--gtv-info-panel-header-inset:0px]";
|
|
1881
1930
|
function GenreTreeView({
|
|
@@ -1889,6 +1938,7 @@ function GenreTreeView({
|
|
|
1889
1938
|
selectedNodeId: controlledSelectedNodeId,
|
|
1890
1939
|
onSelectedNodeIdChange,
|
|
1891
1940
|
defaultViewport,
|
|
1941
|
+
viewportRestore,
|
|
1892
1942
|
onViewportChange,
|
|
1893
1943
|
readOnly = false,
|
|
1894
1944
|
className: className2,
|
|
@@ -1897,7 +1947,6 @@ function GenreTreeView({
|
|
|
1897
1947
|
allowWheelRotation: controlledAllowWheelRotation,
|
|
1898
1948
|
criteriaOverviewSchema,
|
|
1899
1949
|
renderGenreDetailExtras,
|
|
1900
|
-
controlsOrientation = "horizontal",
|
|
1901
1950
|
floatingControlsLeading,
|
|
1902
1951
|
sizeMetric: controlledSizeMetric,
|
|
1903
1952
|
onSizeMetricChange,
|
|
@@ -1967,6 +2016,16 @@ function GenreTreeView({
|
|
|
1967
2016
|
criteriaOverviewSchema,
|
|
1968
2017
|
artistUuid
|
|
1969
2018
|
});
|
|
2019
|
+
const restoreRevision = viewportRestore?.revision;
|
|
2020
|
+
const [stackedRestoreOwner, setStackedRestoreOwner] = useState5({ revision: restoreRevision, rootUuid: selectedGenrePlaylist?.root.uuid });
|
|
2021
|
+
let restoreRootUuid = stackedRestoreOwner.rootUuid;
|
|
2022
|
+
if (stackedRestoreOwner.revision !== restoreRevision) {
|
|
2023
|
+
restoreRootUuid = selectedGenrePlaylist?.root.uuid;
|
|
2024
|
+
setStackedRestoreOwner({ revision: restoreRevision, rootUuid: restoreRootUuid });
|
|
2025
|
+
} else if (restoreRootUuid === void 0 && selectedGenrePlaylist) {
|
|
2026
|
+
restoreRootUuid = selectedGenrePlaylist.root.uuid;
|
|
2027
|
+
setStackedRestoreOwner({ revision: restoreRevision, rootUuid: restoreRootUuid });
|
|
2028
|
+
}
|
|
1970
2029
|
const isGenreUpdating = useIsMutating({ mutationKey: genreUpdateMutationKey }) > 0;
|
|
1971
2030
|
const { mutate: updateGenre } = useUpdateGenre2(scope, getBackendBaseUrl);
|
|
1972
2031
|
const busy = deferredSizeMetric !== sizeMetric || isTreeFetching || isGenreUpdating;
|
|
@@ -1991,7 +2050,7 @@ function GenreTreeView({
|
|
|
1991
2050
|
scope,
|
|
1992
2051
|
getBackendBaseUrl: artistBaseUrl,
|
|
1993
2052
|
getShareUrl: getShareUrlRef.current,
|
|
1994
|
-
renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */
|
|
2053
|
+
renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ jsx28("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
|
|
1995
2054
|
expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
|
|
1996
2055
|
onExpandedChange: (on) => {
|
|
1997
2056
|
if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
|
|
@@ -2013,18 +2072,11 @@ function GenreTreeView({
|
|
|
2013
2072
|
setUncontrolledExpanded(next);
|
|
2014
2073
|
onExpandedChange?.(next);
|
|
2015
2074
|
};
|
|
2016
|
-
|
|
2075
|
+
useEffect6(() => {
|
|
2017
2076
|
setUncontrolledCollapsed(false);
|
|
2018
2077
|
}, [selectedNodeId, artistUuid]);
|
|
2019
2078
|
const isDetailHidden = isCollapsed || !hasSelectedGenreNode && !activeOverlayPanel;
|
|
2020
|
-
|
|
2021
|
-
if (placement !== "regions" || !isDetailHidden) return void 0;
|
|
2022
|
-
const root = document.documentElement;
|
|
2023
|
-
root.style.setProperty("--layout-left-w", "0px");
|
|
2024
|
-
return () => {
|
|
2025
|
-
root.style.removeProperty("--layout-left-w");
|
|
2026
|
-
};
|
|
2027
|
-
}, [placement, isDetailHidden]);
|
|
2079
|
+
useLeftDetailPanelLayout(!isDetailHidden, placement === "regions");
|
|
2028
2080
|
const detailOverlay = placement === "regions" && activeOverlayPanel?.onExpandedChange ? activeOverlayPanel : void 0;
|
|
2029
2081
|
const detailExpanded = detailOverlay ? !!detailOverlay.expanded : isExpanded;
|
|
2030
2082
|
const onDetailExpandedChange = detailOverlay ? detailOverlay.onExpandedChange : changeExpanded;
|
|
@@ -2043,7 +2095,7 @@ function GenreTreeView({
|
|
|
2043
2095
|
const handlePrevious = () => {
|
|
2044
2096
|
rootRef.current?.querySelector(".search-overlay .gtv-info-panel-previous")?.click();
|
|
2045
2097
|
};
|
|
2046
|
-
|
|
2098
|
+
useEffect6(() => {
|
|
2047
2099
|
if (!isTreeReady || viewMode !== "pop-core" || !hasSelectedGenreNode || !selectedGenrePlaylist) return;
|
|
2048
2100
|
const root = rootRef.current;
|
|
2049
2101
|
const centerChip = root?.querySelector(".gtv-wheel-center-node .gtv-wheel-chip");
|
|
@@ -2059,15 +2111,15 @@ function GenreTreeView({
|
|
|
2059
2111
|
label: "Artists",
|
|
2060
2112
|
items: artistSearchResult.artists.map((artist) => ({
|
|
2061
2113
|
key: artist.uuid,
|
|
2062
|
-
content: /* @__PURE__ */
|
|
2063
|
-
/* @__PURE__ */
|
|
2114
|
+
content: /* @__PURE__ */ jsxs21(Fragment9, { children: [
|
|
2115
|
+
/* @__PURE__ */ jsxs21("span", { className: "block truncate font-medium", children: [
|
|
2064
2116
|
artist.name,
|
|
2065
|
-
artist.disambiguation && /* @__PURE__ */
|
|
2117
|
+
artist.disambiguation && /* @__PURE__ */ jsxs21("span", { className: "font-normal text-[var(--color-neutral-500)]", children: [
|
|
2066
2118
|
" \xB7 ",
|
|
2067
2119
|
artist.disambiguation
|
|
2068
2120
|
] })
|
|
2069
2121
|
] }),
|
|
2070
|
-
artist.matchedAlias && /* @__PURE__ */
|
|
2122
|
+
artist.matchedAlias && /* @__PURE__ */ jsxs21("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [
|
|
2071
2123
|
"also known as ",
|
|
2072
2124
|
artist.matchedAlias
|
|
2073
2125
|
] })
|
|
@@ -2099,7 +2151,7 @@ function GenreTreeView({
|
|
|
2099
2151
|
(node, { toolbar, ancestors, family }) => {
|
|
2100
2152
|
const genrePlaylist = genrePlaylistByNodeId.get(node.id);
|
|
2101
2153
|
const criteria = genrePlaylist?.criteria;
|
|
2102
|
-
if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */
|
|
2154
|
+
if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ jsxs21(Fragment9, { children: [
|
|
2103
2155
|
toolbar,
|
|
2104
2156
|
family
|
|
2105
2157
|
] }), aside: ancestors };
|
|
@@ -2119,21 +2171,26 @@ function GenreTreeView({
|
|
|
2119
2171
|
(sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist.uuid && sibling.criteria?.side === "pop"
|
|
2120
2172
|
) : void 0;
|
|
2121
2173
|
return {
|
|
2122
|
-
primary: /* @__PURE__ */
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2174
|
+
primary: /* @__PURE__ */ jsx28(
|
|
2175
|
+
GenreInfoPanelBody,
|
|
2176
|
+
{
|
|
2177
|
+
toolbar,
|
|
2178
|
+
family,
|
|
2179
|
+
genreUuid,
|
|
2180
|
+
genrePlaylist,
|
|
2181
|
+
wikidataId: criteria.wikidataId,
|
|
2182
|
+
scope,
|
|
2183
|
+
getBackendBaseUrl,
|
|
2184
|
+
playback,
|
|
2185
|
+
band: {
|
|
2127
2186
|
node,
|
|
2128
|
-
genrePlaylist,
|
|
2129
|
-
scope,
|
|
2130
2187
|
shareTarget: shareUrl ? shareTarget : void 0,
|
|
2131
2188
|
shareUrl,
|
|
2132
2189
|
onZoomIn: zoomInRef.current,
|
|
2133
2190
|
onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
|
|
2134
2191
|
onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
|
|
2135
2192
|
onChangeParent: readOnly ? void 0 : () => showPopup(
|
|
2136
|
-
/* @__PURE__ */
|
|
2193
|
+
/* @__PURE__ */ jsx28(
|
|
2137
2194
|
ChangeParentPopup,
|
|
2138
2195
|
{
|
|
2139
2196
|
genrePlaylist: { ...genrePlaylist, criteria },
|
|
@@ -2148,19 +2205,9 @@ function GenreTreeView({
|
|
|
2148
2205
|
side,
|
|
2149
2206
|
onToggleSide: canToggleSide ? () => updateGenre({ uuid: genreUuid, data: { side: side === "pop" ? "core" : "pop" } }) : void 0,
|
|
2150
2207
|
toggleSideDisabledReason: popSibling ? `${popSibling.name} is already the pop side of ${parentPlaylist?.name}` : void 0,
|
|
2151
|
-
additionalActions
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
),
|
|
2155
|
-
/* @__PURE__ */ jsx27(
|
|
2156
|
-
GenreDetailExtras,
|
|
2157
|
-
{
|
|
2158
|
-
family,
|
|
2159
|
-
genreUuid,
|
|
2160
|
-
genrePlaylist,
|
|
2161
|
-
wikidataId: criteria.wikidataId,
|
|
2162
|
-
scope,
|
|
2163
|
-
getBackendBaseUrl,
|
|
2208
|
+
additionalActions
|
|
2209
|
+
},
|
|
2210
|
+
extras: {
|
|
2164
2211
|
criteriaOverviewSchema,
|
|
2165
2212
|
renderGenreDetailExtras,
|
|
2166
2213
|
renderArtistLink,
|
|
@@ -2170,10 +2217,9 @@ function GenreTreeView({
|
|
|
2170
2217
|
renderSongLeading,
|
|
2171
2218
|
renderSongActions,
|
|
2172
2219
|
renderTracksToolbar
|
|
2173
|
-
}
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
] }),
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
),
|
|
2177
2223
|
aside: ancestors
|
|
2178
2224
|
};
|
|
2179
2225
|
},
|
|
@@ -2204,7 +2250,7 @@ function GenreTreeView({
|
|
|
2204
2250
|
);
|
|
2205
2251
|
const isRotationControlled = controlledAllowWheelRotation !== void 0;
|
|
2206
2252
|
const allowWheelRotation = controlledAllowWheelRotation ?? internalAllowWheelRotation;
|
|
2207
|
-
const detailTab = placement === "regions" && (hasSelectedGenreNode || detailOverlay) ? /* @__PURE__ */
|
|
2253
|
+
const detailTab = placement === "regions" && (hasSelectedGenreNode || detailOverlay) ? /* @__PURE__ */ jsx28(
|
|
2208
2254
|
PanelEdgeTab,
|
|
2209
2255
|
{
|
|
2210
2256
|
side: "left",
|
|
@@ -2214,8 +2260,8 @@ function GenreTreeView({
|
|
|
2214
2260
|
) : null;
|
|
2215
2261
|
const renderArtistFilter = () => {
|
|
2216
2262
|
if (isLoading || !filterArtist) return null;
|
|
2217
|
-
return /* @__PURE__ */
|
|
2218
|
-
/* @__PURE__ */
|
|
2263
|
+
return /* @__PURE__ */ jsxs21("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
|
|
2264
|
+
/* @__PURE__ */ jsx28(
|
|
2219
2265
|
"button",
|
|
2220
2266
|
{
|
|
2221
2267
|
type: "button",
|
|
@@ -2227,7 +2273,7 @@ function GenreTreeView({
|
|
|
2227
2273
|
children: filterArtist.name
|
|
2228
2274
|
}
|
|
2229
2275
|
),
|
|
2230
|
-
/* @__PURE__ */
|
|
2276
|
+
/* @__PURE__ */ jsx28(
|
|
2231
2277
|
"button",
|
|
2232
2278
|
{
|
|
2233
2279
|
type: "button",
|
|
@@ -2239,8 +2285,8 @@ function GenreTreeView({
|
|
|
2239
2285
|
)
|
|
2240
2286
|
] });
|
|
2241
2287
|
};
|
|
2242
|
-
const actions = /* @__PURE__ */
|
|
2243
|
-
!isLoading && /* @__PURE__ */
|
|
2288
|
+
const actions = /* @__PURE__ */ jsxs21(Fragment9, { children: [
|
|
2289
|
+
!isLoading && /* @__PURE__ */ jsx28(
|
|
2244
2290
|
GenreSearch,
|
|
2245
2291
|
{
|
|
2246
2292
|
genrePlaylists: visibleGenrePlaylists,
|
|
@@ -2252,9 +2298,9 @@ function GenreTreeView({
|
|
|
2252
2298
|
extraResults: artistExtraResults
|
|
2253
2299
|
}
|
|
2254
2300
|
),
|
|
2255
|
-
!isLoading && filterArtist && /* @__PURE__ */
|
|
2256
|
-
/* @__PURE__ */
|
|
2257
|
-
!isLoading && !isControlled && /* @__PURE__ */
|
|
2301
|
+
!isLoading && filterArtist && /* @__PURE__ */ jsx28("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
|
|
2302
|
+
/* @__PURE__ */ jsxs21("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: [
|
|
2303
|
+
!isLoading && !isControlled && /* @__PURE__ */ jsx28(
|
|
2258
2304
|
SegmentedControl2,
|
|
2259
2305
|
{
|
|
2260
2306
|
"aria-label": "Tree view mode",
|
|
@@ -2268,13 +2314,13 @@ function GenreTreeView({
|
|
|
2268
2314
|
]
|
|
2269
2315
|
}
|
|
2270
2316
|
),
|
|
2271
|
-
!isLoading && showDisplayOptions && /* @__PURE__ */
|
|
2317
|
+
!isLoading && showDisplayOptions && /* @__PURE__ */ jsx28(
|
|
2272
2318
|
"div",
|
|
2273
2319
|
{
|
|
2274
2320
|
className: "flex items-center gap-1",
|
|
2275
2321
|
role: "group",
|
|
2276
2322
|
"aria-label": "Tree display options",
|
|
2277
|
-
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */
|
|
2323
|
+
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ jsx28(
|
|
2278
2324
|
Button,
|
|
2279
2325
|
{
|
|
2280
2326
|
variant: allowWheelRotation ? "default" : "outline",
|
|
@@ -2285,7 +2331,7 @@ function GenreTreeView({
|
|
|
2285
2331
|
)
|
|
2286
2332
|
}
|
|
2287
2333
|
),
|
|
2288
|
-
!isLoading && !readOnly && showAddRoot && /* @__PURE__ */
|
|
2334
|
+
!isLoading && !readOnly && showAddRoot && /* @__PURE__ */ jsx28(
|
|
2289
2335
|
IconTextButton,
|
|
2290
2336
|
{
|
|
2291
2337
|
icon: Plus2,
|
|
@@ -2295,42 +2341,42 @@ function GenreTreeView({
|
|
|
2295
2341
|
)
|
|
2296
2342
|
] })
|
|
2297
2343
|
] });
|
|
2298
|
-
return /* @__PURE__ */
|
|
2344
|
+
return /* @__PURE__ */ jsxs21(
|
|
2299
2345
|
"div",
|
|
2300
2346
|
{
|
|
2301
2347
|
ref: rootRef,
|
|
2302
2348
|
className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
|
|
2303
2349
|
children: [
|
|
2304
|
-
/* @__PURE__ */
|
|
2305
|
-
/* @__PURE__ */
|
|
2350
|
+
/* @__PURE__ */ jsx28("style", { children: PREVIOUS_BUTTON_CSS }),
|
|
2351
|
+
/* @__PURE__ */ jsx28(
|
|
2306
2352
|
"div",
|
|
2307
2353
|
{
|
|
2308
2354
|
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"}`,
|
|
2309
2355
|
children: actions
|
|
2310
2356
|
}
|
|
2311
2357
|
),
|
|
2312
|
-
!isLoading && filterArtist && /* @__PURE__ */
|
|
2358
|
+
!isLoading && filterArtist && /* @__PURE__ */ jsx28(
|
|
2313
2359
|
"div",
|
|
2314
2360
|
{
|
|
2315
2361
|
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
2362
|
children: renderArtistFilter()
|
|
2317
2363
|
}
|
|
2318
2364
|
),
|
|
2319
|
-
detailTab && /* @__PURE__ */
|
|
2365
|
+
detailTab && /* @__PURE__ */ jsx28(
|
|
2320
2366
|
"div",
|
|
2321
2367
|
{
|
|
2322
2368
|
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))]"}`,
|
|
2323
2369
|
children: detailTab
|
|
2324
2370
|
}
|
|
2325
2371
|
),
|
|
2326
|
-
/* @__PURE__ */
|
|
2327
|
-
!isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */
|
|
2328
|
-
isLoading ? /* @__PURE__ */
|
|
2372
|
+
/* @__PURE__ */ jsx28("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ jsxs21("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
|
|
2373
|
+
!isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ jsx28("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
|
|
2374
|
+
isLoading ? /* @__PURE__ */ jsx28(GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
|
|
2329
2375
|
// The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
|
|
2330
2376
|
// and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
|
|
2331
2377
|
// Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
|
|
2332
2378
|
// The panel's own Zoom in button is hidden: the search bar carries it.
|
|
2333
|
-
/* @__PURE__ */
|
|
2379
|
+
/* @__PURE__ */ jsx28("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__ */ jsx28(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ jsx28(GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ jsx28(
|
|
2334
2380
|
GenrePlaylistTreeWheel,
|
|
2335
2381
|
{
|
|
2336
2382
|
scope,
|
|
@@ -2347,21 +2393,21 @@ function GenreTreeView({
|
|
|
2347
2393
|
selectedNodeId,
|
|
2348
2394
|
readOnly,
|
|
2349
2395
|
allowWheelRotation,
|
|
2350
|
-
controlsOrientation,
|
|
2351
2396
|
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2352
2397
|
busy,
|
|
2353
|
-
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */
|
|
2398
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ jsxs21("div", { className: "flex items-center gap-2", children: [
|
|
2354
2399
|
floatingControlsLeading,
|
|
2355
|
-
canSizeByViews && /* @__PURE__ */
|
|
2400
|
+
canSizeByViews && /* @__PURE__ */ jsx28(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2356
2401
|
] }) : void 0,
|
|
2357
2402
|
defaultViewport,
|
|
2403
|
+
viewportRestore,
|
|
2358
2404
|
onViewportChange,
|
|
2359
2405
|
...infoPanelCuration
|
|
2360
2406
|
}
|
|
2361
2407
|
) }) })
|
|
2362
2408
|
) : viewMode === "pop-core" ? (
|
|
2363
2409
|
// Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
|
|
2364
|
-
/* @__PURE__ */
|
|
2410
|
+
/* @__PURE__ */ jsx28("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__ */ jsx28(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ jsx28(GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ jsx28(
|
|
2365
2411
|
GenrePlaylistTreeWheelRadialPopCore,
|
|
2366
2412
|
{
|
|
2367
2413
|
scope,
|
|
@@ -2378,20 +2424,20 @@ function GenreTreeView({
|
|
|
2378
2424
|
selectedNodeId,
|
|
2379
2425
|
readOnly,
|
|
2380
2426
|
allowWheelRotation,
|
|
2381
|
-
controlsOrientation,
|
|
2382
2427
|
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2383
2428
|
busy,
|
|
2384
|
-
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */
|
|
2429
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ jsxs21("div", { className: "flex items-center gap-2", children: [
|
|
2385
2430
|
floatingControlsLeading,
|
|
2386
|
-
canSizeByViews && /* @__PURE__ */
|
|
2431
|
+
canSizeByViews && /* @__PURE__ */ jsx28(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2387
2432
|
] }) : void 0,
|
|
2388
2433
|
defaultViewport,
|
|
2434
|
+
viewportRestore,
|
|
2389
2435
|
onViewportChange,
|
|
2390
2436
|
...infoPanelCuration,
|
|
2391
2437
|
...focus
|
|
2392
2438
|
}
|
|
2393
2439
|
) }) })
|
|
2394
|
-
) : viewMode === "outline" ? /* @__PURE__ */
|
|
2440
|
+
) : viewMode === "outline" ? /* @__PURE__ */ jsx28("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__ */ jsx28(
|
|
2395
2441
|
GenrePlaylistTreeWheelRadialPopCore,
|
|
2396
2442
|
{
|
|
2397
2443
|
outline: true,
|
|
@@ -2410,13 +2456,13 @@ function GenreTreeView({
|
|
|
2410
2456
|
readOnly,
|
|
2411
2457
|
...infoPanelCuration
|
|
2412
2458
|
}
|
|
2413
|
-
) }) : /* @__PURE__ */
|
|
2459
|
+
) }) : /* @__PURE__ */ jsx28("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
2460
|
([uuid, genrePlaylistTreePerRoot], rootIndex) => {
|
|
2415
|
-
return /* @__PURE__ */
|
|
2461
|
+
return /* @__PURE__ */ jsx28(
|
|
2416
2462
|
"div",
|
|
2417
2463
|
{
|
|
2418
2464
|
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__ */
|
|
2465
|
+
children: /* @__PURE__ */ jsx28("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ jsx28(
|
|
2420
2466
|
GenrePlaylistTreePerRoot,
|
|
2421
2467
|
{
|
|
2422
2468
|
scope,
|
|
@@ -2434,8 +2480,8 @@ function GenreTreeView({
|
|
|
2434
2480
|
selectedNodeId: uuid === selectedGenrePlaylist?.root.uuid ? selectedNodeId : null,
|
|
2435
2481
|
readOnly,
|
|
2436
2482
|
wheelZoom: "modifier",
|
|
2437
|
-
controlsOrientation,
|
|
2438
2483
|
defaultViewport: uuid === selectedGenrePlaylist?.root.uuid ? defaultViewport : void 0,
|
|
2484
|
+
viewportRestore: uuid === selectedGenrePlaylist?.root.uuid && uuid === restoreRootUuid ? viewportRestore : void 0,
|
|
2439
2485
|
onViewportChange: uuid === selectedGenrePlaylist?.root.uuid ? onViewportChange : void 0,
|
|
2440
2486
|
...infoPanelCuration,
|
|
2441
2487
|
overlayPanel: rootIndex === 0 ? activeOverlayPanel : void 0
|
|
@@ -2452,6 +2498,123 @@ function GenreTreeView({
|
|
|
2452
2498
|
);
|
|
2453
2499
|
}
|
|
2454
2500
|
|
|
2501
|
+
// src/genre-tree/GenreDetailPanel.tsx
|
|
2502
|
+
import { useMemo as useMemo8 } from "react";
|
|
2503
|
+
import {
|
|
2504
|
+
ACCENT_TEXT_COLOR,
|
|
2505
|
+
getGenreTreeColor as getGenreTreeColor2,
|
|
2506
|
+
InfoPanel,
|
|
2507
|
+
TEXT_COLOR,
|
|
2508
|
+
tintSurface
|
|
2509
|
+
} from "@behindthemusictree/genre-tree-view";
|
|
2510
|
+
import { Skeleton as Skeleton5 } from "@behindthemusictree/ui";
|
|
2511
|
+
import {
|
|
2512
|
+
useFetchGenreDetail
|
|
2513
|
+
} from "@behindthemusictree/app-kit/genre-tree";
|
|
2514
|
+
import { jsx as jsx29, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
2515
|
+
var ITEM_COUNT_LABEL = "Songs";
|
|
2516
|
+
var RELATIVE_ITEM_COUNT = 1;
|
|
2517
|
+
function toNode(genre, parentId) {
|
|
2518
|
+
return {
|
|
2519
|
+
id: genre.uuid,
|
|
2520
|
+
parentId,
|
|
2521
|
+
name: genre.name,
|
|
2522
|
+
itemCount: RELATIVE_ITEM_COUNT,
|
|
2523
|
+
side: genre.side ?? void 0,
|
|
2524
|
+
wikidataId: genre.wikidataId
|
|
2525
|
+
};
|
|
2526
|
+
}
|
|
2527
|
+
function GenreDetailPanel({
|
|
2528
|
+
genreUuid,
|
|
2529
|
+
scope,
|
|
2530
|
+
getBackendBaseUrl,
|
|
2531
|
+
onClose,
|
|
2532
|
+
onSelectGenre,
|
|
2533
|
+
onZoomIn,
|
|
2534
|
+
shareUrl,
|
|
2535
|
+
shareTarget,
|
|
2536
|
+
collapsed,
|
|
2537
|
+
expanded,
|
|
2538
|
+
onExpandedChange,
|
|
2539
|
+
extras
|
|
2540
|
+
}) {
|
|
2541
|
+
const { data: detail, isPending, isError } = useFetchGenreDetail(genreUuid, scope, getBackendBaseUrl);
|
|
2542
|
+
const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: [] });
|
|
2543
|
+
const view = useMemo8(() => {
|
|
2544
|
+
if (!detail) return null;
|
|
2545
|
+
const rootColor = getGenreTreeColor2(detail.root.wikidataId);
|
|
2546
|
+
const relative = (genre, parentId) => ({
|
|
2547
|
+
node: toNode(genre, parentId),
|
|
2548
|
+
fill: tintSurface(rootColor),
|
|
2549
|
+
textColor: TEXT_COLOR
|
|
2550
|
+
});
|
|
2551
|
+
const ancestors = [...detail.ascendants].sort((a, b) => b.degree - a.degree);
|
|
2552
|
+
return {
|
|
2553
|
+
node: {
|
|
2554
|
+
id: detail.uuid,
|
|
2555
|
+
parentId: detail.parent?.uuid ?? null,
|
|
2556
|
+
name: detail.name,
|
|
2557
|
+
itemCount: detail.tracksCount,
|
|
2558
|
+
wikidataId: detail.wikidataId
|
|
2559
|
+
},
|
|
2560
|
+
fill: rootColor,
|
|
2561
|
+
parentNode: detail.parent ? relative(detail.parent, null) : null,
|
|
2562
|
+
childNodes: detail.children.map((child) => relative(child, detail.uuid)),
|
|
2563
|
+
ancestorNodes: ancestors.map(
|
|
2564
|
+
({ ascendant }, index) => relative(ascendant, index === 0 ? null : ancestors[index - 1].ascendant.uuid)
|
|
2565
|
+
)
|
|
2566
|
+
};
|
|
2567
|
+
}, [detail]);
|
|
2568
|
+
if (isError) {
|
|
2569
|
+
return /* @__PURE__ */ jsx29("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
|
|
2570
|
+
}
|
|
2571
|
+
if (isPending || !detail || !view) {
|
|
2572
|
+
return /* @__PURE__ */ jsxs22("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
|
|
2573
|
+
/* @__PURE__ */ jsx29(Skeleton5, { className: "h-6 w-2/3" }),
|
|
2574
|
+
/* @__PURE__ */ jsx29(Skeleton5, { className: "h-4 w-full" }),
|
|
2575
|
+
/* @__PURE__ */ jsx29(Skeleton5, { className: "h-4 w-3/4" })
|
|
2576
|
+
] });
|
|
2577
|
+
}
|
|
2578
|
+
const renderBody = (node, sections) => ({
|
|
2579
|
+
primary: /* @__PURE__ */ jsx29(
|
|
2580
|
+
GenreInfoPanelBody,
|
|
2581
|
+
{
|
|
2582
|
+
toolbar: sections.toolbar,
|
|
2583
|
+
family: sections.family,
|
|
2584
|
+
genreUuid: detail.uuid,
|
|
2585
|
+
genrePlaylist: { ...detail.criteriaPlaylist, tracksCount: detail.tracksCount },
|
|
2586
|
+
wikidataId: detail.wikidataId,
|
|
2587
|
+
scope,
|
|
2588
|
+
getBackendBaseUrl,
|
|
2589
|
+
playback,
|
|
2590
|
+
band: { node, onZoomIn, shareUrl, shareTarget: shareUrl ? shareTarget : void 0 },
|
|
2591
|
+
extras
|
|
2592
|
+
}
|
|
2593
|
+
),
|
|
2594
|
+
aside: null
|
|
2595
|
+
});
|
|
2596
|
+
return /* @__PURE__ */ jsx29(
|
|
2597
|
+
InfoPanel,
|
|
2598
|
+
{
|
|
2599
|
+
node: view.node,
|
|
2600
|
+
fill: view.fill,
|
|
2601
|
+
textColor: ACCENT_TEXT_COLOR,
|
|
2602
|
+
parentNode: view.parentNode,
|
|
2603
|
+
childNodes: view.childNodes,
|
|
2604
|
+
ancestorNodes: view.ancestorNodes,
|
|
2605
|
+
side: "left",
|
|
2606
|
+
onClose,
|
|
2607
|
+
onSelectNode: (nodeId) => onSelectGenre(nodeId),
|
|
2608
|
+
itemCountLabel: ITEM_COUNT_LABEL,
|
|
2609
|
+
renderInfoPanelBody: renderBody,
|
|
2610
|
+
expanded,
|
|
2611
|
+
onExpandedChange,
|
|
2612
|
+
collapsed,
|
|
2613
|
+
hideExpandButton: onExpandedChange === void 0
|
|
2614
|
+
}
|
|
2615
|
+
);
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2455
2618
|
// src/genre-tree/index.ts
|
|
2456
2619
|
import {
|
|
2457
2620
|
GenreTreeSkeleton,
|
|
@@ -2461,13 +2624,13 @@ import {
|
|
|
2461
2624
|
} from "@behindthemusictree/genre-tree-view";
|
|
2462
2625
|
|
|
2463
2626
|
// src/genre-tree/track-list-sidebar/TrackListSidebar.tsx
|
|
2464
|
-
import { useEffect as
|
|
2627
|
+
import { useEffect as useEffect7, useLayoutEffect as useLayoutEffect2, useRef as useRef5 } from "react";
|
|
2465
2628
|
import { ListMusic } from "lucide-react";
|
|
2466
2629
|
import { useTrackList as useTrackList5 } from "@behindthemusictree/app-kit/genre-tree";
|
|
2467
2630
|
import { TrackListOriginType as TrackListOriginType2 } from "@behindthemusictree/app-kit/genre-tree";
|
|
2468
2631
|
import { useTrackNoun as useTrackNoun6 } from "@behindthemusictree/app-kit/track-noun";
|
|
2469
|
-
import { jsx as
|
|
2470
|
-
var useBrowserLayoutEffect = typeof window === "undefined" ?
|
|
2632
|
+
import { jsx as jsx30, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
2633
|
+
var useBrowserLayoutEffect = typeof window === "undefined" ? useEffect7 : useLayoutEffect2;
|
|
2471
2634
|
function TrackListSidebar({
|
|
2472
2635
|
className: className2 = "",
|
|
2473
2636
|
renderDuration,
|
|
@@ -2489,7 +2652,7 @@ function TrackListSidebar({
|
|
|
2489
2652
|
const userScrollIntentTimerRef = useRef5(null);
|
|
2490
2653
|
const loadMoreSentinelRef = useRef5(null);
|
|
2491
2654
|
const hasMore = !!trackList && trackList.nextPage !== null;
|
|
2492
|
-
|
|
2655
|
+
useEffect7(() => {
|
|
2493
2656
|
const sentinel = loadMoreSentinelRef.current;
|
|
2494
2657
|
if (!hasMore || !sentinel) return;
|
|
2495
2658
|
const observer = new IntersectionObserver((entries) => {
|
|
@@ -2509,11 +2672,11 @@ function TrackListSidebar({
|
|
|
2509
2672
|
programmaticScrollTopRef.current = null;
|
|
2510
2673
|
if (trackListRef.current) trackListRef.current.scrollTop = 0;
|
|
2511
2674
|
}, [trackList?.origin]);
|
|
2512
|
-
|
|
2675
|
+
useEffect7(() => {
|
|
2513
2676
|
const list = trackListRef.current;
|
|
2514
2677
|
if (!isCollapsed && list?.scrollTop === 0) shouldFollowPlaybackRef.current = true;
|
|
2515
2678
|
}, [isCollapsed]);
|
|
2516
|
-
|
|
2679
|
+
useEffect7(
|
|
2517
2680
|
() => () => {
|
|
2518
2681
|
if (userScrollIntentTimerRef.current !== null) clearTimeout(userScrollIntentTimerRef.current);
|
|
2519
2682
|
},
|
|
@@ -2567,25 +2730,25 @@ function TrackListSidebar({
|
|
|
2567
2730
|
}
|
|
2568
2731
|
};
|
|
2569
2732
|
const positionClasses = layout === "inline" ? "relative w-full flex h-full flex-col" : "fixed right-0 w-[50rem] flex flex-col";
|
|
2570
|
-
return trackList ? /* @__PURE__ */
|
|
2733
|
+
return trackList ? /* @__PURE__ */ jsxs23(
|
|
2571
2734
|
"div",
|
|
2572
2735
|
{
|
|
2573
2736
|
"data-theme": "dark",
|
|
2574
2737
|
className: `track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${className2}`,
|
|
2575
2738
|
style: layout === "fixed" ? { top: "63.5px", maxHeight: "calc(100vh - 63.5px - 79px)" } : void 0,
|
|
2576
2739
|
children: [
|
|
2577
|
-
/* @__PURE__ */
|
|
2578
|
-
/* @__PURE__ */
|
|
2579
|
-
/* @__PURE__ */
|
|
2580
|
-
/* @__PURE__ */
|
|
2581
|
-
/* @__PURE__ */
|
|
2740
|
+
/* @__PURE__ */ jsxs23("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
|
|
2741
|
+
/* @__PURE__ */ jsxs23("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
|
|
2742
|
+
/* @__PURE__ */ jsxs23("div", { className: "origin flex text-xl", children: [
|
|
2743
|
+
/* @__PURE__ */ jsx30("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
|
|
2744
|
+
/* @__PURE__ */ jsx30("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__ */ jsx30("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
|
|
2582
2745
|
] }),
|
|
2583
|
-
/* @__PURE__ */
|
|
2746
|
+
/* @__PURE__ */ jsxs23("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
|
|
2584
2747
|
trackList.origin.type === TrackListOriginType2.GENRE_PLAYLIST ? "Genre \u2022 " : trackList.origin.type === TrackListOriginType2.ARTIST ? "Artist \u2022 " : `${trackNoun.singular} playlist \u2022 `,
|
|
2585
2748
|
`${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`
|
|
2586
2749
|
] })
|
|
2587
2750
|
] }),
|
|
2588
|
-
(isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */
|
|
2751
|
+
(isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ jsx30(
|
|
2589
2752
|
"button",
|
|
2590
2753
|
{
|
|
2591
2754
|
type: "button",
|
|
@@ -2594,11 +2757,11 @@ function TrackListSidebar({
|
|
|
2594
2757
|
"aria-expanded": !isCollapsed,
|
|
2595
2758
|
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
2759
|
onClick: isCollapsed ? onExpand : onMinimize,
|
|
2597
|
-
children: /* @__PURE__ */
|
|
2760
|
+
children: /* @__PURE__ */ jsx30(ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
|
|
2598
2761
|
}
|
|
2599
2762
|
)
|
|
2600
2763
|
] }),
|
|
2601
|
-
!isCollapsed && /* @__PURE__ */
|
|
2764
|
+
!isCollapsed && /* @__PURE__ */ jsxs23(
|
|
2602
2765
|
"ul",
|
|
2603
2766
|
{
|
|
2604
2767
|
ref: trackListRef,
|
|
@@ -2608,7 +2771,7 @@ function TrackListSidebar({
|
|
|
2608
2771
|
onPointerDown: markUserScrollIntent,
|
|
2609
2772
|
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",
|
|
2610
2773
|
children: [
|
|
2611
|
-
trackList.tracks.map((track, index) => /* @__PURE__ */
|
|
2774
|
+
trackList.tracks.map((track, index) => /* @__PURE__ */ jsx30("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ jsx30(
|
|
2612
2775
|
TrackItem,
|
|
2613
2776
|
{
|
|
2614
2777
|
track,
|
|
@@ -2618,7 +2781,7 @@ function TrackListSidebar({
|
|
|
2618
2781
|
renderArtistLink
|
|
2619
2782
|
}
|
|
2620
2783
|
) }, track.uuid)),
|
|
2621
|
-
hasMore && /* @__PURE__ */
|
|
2784
|
+
hasMore && /* @__PURE__ */ jsx30("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
|
|
2622
2785
|
]
|
|
2623
2786
|
}
|
|
2624
2787
|
)
|
|
@@ -2628,7 +2791,7 @@ function TrackListSidebar({
|
|
|
2628
2791
|
}
|
|
2629
2792
|
|
|
2630
2793
|
// src/genre-tree/PlayerPanel.tsx
|
|
2631
|
-
import { useEffect as
|
|
2794
|
+
import { useEffect as useEffect8, useState as useState6 } from "react";
|
|
2632
2795
|
import { twMerge as twMerge2 } from "tailwind-merge";
|
|
2633
2796
|
import {
|
|
2634
2797
|
usePlayer as usePlayer10,
|
|
@@ -2642,7 +2805,7 @@ import {
|
|
|
2642
2805
|
import { Pause as Pause2, Play as Play3, SkipBack, SkipForward, Volume2, VolumeX } from "lucide-react";
|
|
2643
2806
|
import { formatTime, useTrackList as useTrackList6 } from "@behindthemusictree/app-kit/genre-tree";
|
|
2644
2807
|
import { PlayStates as PlayStates3, useCurrentTime, usePlayer as usePlayer9 } from "@behindthemusictree/app-kit/player";
|
|
2645
|
-
import { jsx as
|
|
2808
|
+
import { jsx as jsx31, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
2646
2809
|
function PlayerControls() {
|
|
2647
2810
|
const {
|
|
2648
2811
|
duration,
|
|
@@ -2661,15 +2824,15 @@ function PlayerControls() {
|
|
|
2661
2824
|
const safeDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;
|
|
2662
2825
|
const safeCurrentTime = Number.isFinite(currentTime) ? Math.max(0, Math.min(currentTime, safeDuration)) : 0;
|
|
2663
2826
|
const isPlaying = playState === PlayStates3.PLAYING;
|
|
2664
|
-
return /* @__PURE__ */
|
|
2827
|
+
return /* @__PURE__ */ jsxs24(
|
|
2665
2828
|
"section",
|
|
2666
2829
|
{
|
|
2667
2830
|
"aria-label": "Player controls",
|
|
2668
2831
|
className: "player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200",
|
|
2669
2832
|
children: [
|
|
2670
|
-
/* @__PURE__ */
|
|
2671
|
-
/* @__PURE__ */
|
|
2672
|
-
/* @__PURE__ */
|
|
2833
|
+
/* @__PURE__ */ jsxs24("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
|
|
2834
|
+
/* @__PURE__ */ jsx31("span", { "aria-label": "Elapsed time", children: formatTime(safeCurrentTime) }),
|
|
2835
|
+
/* @__PURE__ */ jsx31(
|
|
2673
2836
|
"input",
|
|
2674
2837
|
{
|
|
2675
2838
|
"aria-label": "Seek",
|
|
@@ -2683,11 +2846,11 @@ function PlayerControls() {
|
|
|
2683
2846
|
onChange: (event) => seekTo(Number(event.currentTarget.value))
|
|
2684
2847
|
}
|
|
2685
2848
|
),
|
|
2686
|
-
/* @__PURE__ */
|
|
2849
|
+
/* @__PURE__ */ jsx31("span", { "aria-label": "Duration", children: formatTime(safeDuration) })
|
|
2687
2850
|
] }),
|
|
2688
|
-
/* @__PURE__ */
|
|
2689
|
-
/* @__PURE__ */
|
|
2690
|
-
/* @__PURE__ */
|
|
2851
|
+
/* @__PURE__ */ jsxs24("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
|
|
2852
|
+
/* @__PURE__ */ jsxs24("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
|
|
2853
|
+
/* @__PURE__ */ jsx31(
|
|
2691
2854
|
"button",
|
|
2692
2855
|
{
|
|
2693
2856
|
type: "button",
|
|
@@ -2696,10 +2859,10 @@ function PlayerControls() {
|
|
|
2696
2859
|
disabled: !isReady || !hasPreviousTrack,
|
|
2697
2860
|
onClick: playPreviousTrack,
|
|
2698
2861
|
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__ */
|
|
2862
|
+
children: /* @__PURE__ */ jsx31(SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
2700
2863
|
}
|
|
2701
2864
|
),
|
|
2702
|
-
/* @__PURE__ */
|
|
2865
|
+
/* @__PURE__ */ jsx31(
|
|
2703
2866
|
"button",
|
|
2704
2867
|
{
|
|
2705
2868
|
type: "button",
|
|
@@ -2708,10 +2871,10 @@ function PlayerControls() {
|
|
|
2708
2871
|
disabled: !isReady,
|
|
2709
2872
|
onClick: handlePlayPauseAction,
|
|
2710
2873
|
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__ */
|
|
2874
|
+
children: isPlaying ? /* @__PURE__ */ jsx31(Pause2, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ jsx31(Play3, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
|
|
2712
2875
|
}
|
|
2713
2876
|
),
|
|
2714
|
-
/* @__PURE__ */
|
|
2877
|
+
/* @__PURE__ */ jsx31(
|
|
2715
2878
|
"button",
|
|
2716
2879
|
{
|
|
2717
2880
|
type: "button",
|
|
@@ -2720,14 +2883,14 @@ function PlayerControls() {
|
|
|
2720
2883
|
disabled: !isReady || !hasNextTrack,
|
|
2721
2884
|
onClick: playNextTrack,
|
|
2722
2885
|
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__ */
|
|
2886
|
+
children: /* @__PURE__ */ jsx31(SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
2724
2887
|
}
|
|
2725
2888
|
)
|
|
2726
2889
|
] }),
|
|
2727
|
-
/* @__PURE__ */
|
|
2728
|
-
volume === 0 ? /* @__PURE__ */
|
|
2729
|
-
/* @__PURE__ */
|
|
2730
|
-
/* @__PURE__ */
|
|
2890
|
+
/* @__PURE__ */ jsxs24("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: [
|
|
2891
|
+
volume === 0 ? /* @__PURE__ */ jsx31(VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ jsx31(Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
|
|
2892
|
+
/* @__PURE__ */ jsx31("span", { className: "sr-only", children: "Volume" }),
|
|
2893
|
+
/* @__PURE__ */ jsx31(
|
|
2731
2894
|
"input",
|
|
2732
2895
|
{
|
|
2733
2896
|
"aria-label": "Volume",
|
|
@@ -2749,7 +2912,7 @@ function PlayerControls() {
|
|
|
2749
2912
|
}
|
|
2750
2913
|
|
|
2751
2914
|
// src/genre-tree/PlayerPanel.tsx
|
|
2752
|
-
import { jsx as
|
|
2915
|
+
import { jsx as jsx32, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
2753
2916
|
var PANEL_ID = "player-panel";
|
|
2754
2917
|
var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
|
|
2755
2918
|
function PlayerPanel({
|
|
@@ -2764,7 +2927,7 @@ function PlayerPanel({
|
|
|
2764
2927
|
const { toggleTrackListSidebar, isTrackListSidebarVisible } = useTrackListSidebarVisibility();
|
|
2765
2928
|
const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = useState6("");
|
|
2766
2929
|
const loadedTrackTitle = playerTrackObject?.track.title;
|
|
2767
|
-
|
|
2930
|
+
useEffect8(() => {
|
|
2768
2931
|
if (loadedTrackTitle) setLastLoadedTrackTitle(loadedTrackTitle);
|
|
2769
2932
|
}, [loadedTrackTitle]);
|
|
2770
2933
|
const [uncontrolledExtended, setUncontrolledExtended] = useState6(false);
|
|
@@ -2788,7 +2951,7 @@ function PlayerPanel({
|
|
|
2788
2951
|
onClick: () => setIsExtendRequested(true),
|
|
2789
2952
|
expanded: false
|
|
2790
2953
|
} : null;
|
|
2791
|
-
return /* @__PURE__ */
|
|
2954
|
+
return /* @__PURE__ */ jsxs25(
|
|
2792
2955
|
"div",
|
|
2793
2956
|
{
|
|
2794
2957
|
id: PANEL_ID,
|
|
@@ -2801,7 +2964,7 @@ function PlayerPanel({
|
|
|
2801
2964
|
),
|
|
2802
2965
|
style: isExtended ? { width: extendedWidth } : void 0,
|
|
2803
2966
|
children: [
|
|
2804
|
-
/* @__PURE__ */
|
|
2967
|
+
/* @__PURE__ */ jsx32(
|
|
2805
2968
|
PanelEdgeTab,
|
|
2806
2969
|
{
|
|
2807
2970
|
side: "right",
|
|
@@ -2811,7 +2974,7 @@ function PlayerPanel({
|
|
|
2811
2974
|
className: "absolute right-full top-1/2 -translate-y-1/2"
|
|
2812
2975
|
}
|
|
2813
2976
|
),
|
|
2814
|
-
/* @__PURE__ */
|
|
2977
|
+
/* @__PURE__ */ jsxs25(
|
|
2815
2978
|
"div",
|
|
2816
2979
|
{
|
|
2817
2980
|
className: twMerge2(
|
|
@@ -2819,15 +2982,15 @@ function PlayerPanel({
|
|
|
2819
2982
|
isExtended ? "flex-row" : "flex-col"
|
|
2820
2983
|
),
|
|
2821
2984
|
children: [
|
|
2822
|
-
/* @__PURE__ */
|
|
2823
|
-
/* @__PURE__ */
|
|
2824
|
-
/* @__PURE__ */
|
|
2825
|
-
/* @__PURE__ */
|
|
2826
|
-
playerTrackObject?.loadError && /* @__PURE__ */
|
|
2985
|
+
/* @__PURE__ */ jsxs25("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
|
|
2986
|
+
/* @__PURE__ */ jsx32("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ jsx32("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
|
|
2987
|
+
/* @__PURE__ */ jsxs25("div", { className: "relative w-full shrink-0", children: [
|
|
2988
|
+
/* @__PURE__ */ jsx32(PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
|
|
2989
|
+
playerTrackObject?.loadError && /* @__PURE__ */ jsx32("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: playerTrackObject.loadError })
|
|
2827
2990
|
] }),
|
|
2828
|
-
/* @__PURE__ */
|
|
2991
|
+
/* @__PURE__ */ jsx32(PlayerControls, {})
|
|
2829
2992
|
] }),
|
|
2830
|
-
/* @__PURE__ */
|
|
2993
|
+
/* @__PURE__ */ jsx32(
|
|
2831
2994
|
TrackListSidebar,
|
|
2832
2995
|
{
|
|
2833
2996
|
layout: "inline",
|
|
@@ -2853,11 +3016,11 @@ function PlayerPanel({
|
|
|
2853
3016
|
import { v4 as uuidv4 } from "uuid";
|
|
2854
3017
|
import { AiFillStar, AiOutlineStar } from "react-icons/ai";
|
|
2855
3018
|
import { FORM_RATING_NULL_VALUE } from "@behindthemusictree/app-kit/genre-tree";
|
|
2856
|
-
import { jsx as
|
|
3019
|
+
import { jsx as jsx33, jsxs as jsxs26 } from "react/jsx-runtime";
|
|
2857
3020
|
function Rating({ rating, handleChange }) {
|
|
2858
3021
|
const instanceId = uuidv4();
|
|
2859
|
-
return /* @__PURE__ */
|
|
2860
|
-
/* @__PURE__ */
|
|
3022
|
+
return /* @__PURE__ */ jsx33("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ jsxs26("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
|
|
3023
|
+
/* @__PURE__ */ jsx33(
|
|
2861
3024
|
"input",
|
|
2862
3025
|
{
|
|
2863
3026
|
id: `star-${instanceId}-${i}`,
|
|
@@ -2869,16 +3032,16 @@ function Rating({ rating, handleChange }) {
|
|
|
2869
3032
|
onChange: handleChange
|
|
2870
3033
|
}
|
|
2871
3034
|
),
|
|
2872
|
-
/* @__PURE__ */
|
|
2873
|
-
/* @__PURE__ */
|
|
3035
|
+
/* @__PURE__ */ jsx33("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ jsxs26("div", { className: "flex", children: [
|
|
3036
|
+
/* @__PURE__ */ jsx33(
|
|
2874
3037
|
AiOutlineStar,
|
|
2875
3038
|
{
|
|
2876
3039
|
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)]"}`
|
|
2877
3040
|
}
|
|
2878
3041
|
),
|
|
2879
|
-
/* @__PURE__ */
|
|
3042
|
+
/* @__PURE__ */ jsx33("span", { className: "flex-grow w-8" }),
|
|
2880
3043
|
" "
|
|
2881
|
-
] }) : /* @__PURE__ */
|
|
3044
|
+
] }) : /* @__PURE__ */ jsx33(
|
|
2882
3045
|
AiFillStar,
|
|
2883
3046
|
{
|
|
2884
3047
|
className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
|
|
@@ -2888,12 +3051,17 @@ function Rating({ rating, handleChange }) {
|
|
|
2888
3051
|
}
|
|
2889
3052
|
|
|
2890
3053
|
export {
|
|
3054
|
+
useGenrePlaylistPlayback,
|
|
2891
3055
|
GenrePlaylistTreePerRoot,
|
|
2892
3056
|
GenreSearch,
|
|
3057
|
+
GenreNodeActionBand,
|
|
3058
|
+
GenreInfoPanelBody,
|
|
2893
3059
|
PanelEdgeTab,
|
|
3060
|
+
useLeftDetailPanelLayout,
|
|
2894
3061
|
TrackPositionPlayPause,
|
|
2895
3062
|
TrackItem,
|
|
2896
3063
|
GenreTreeView,
|
|
3064
|
+
GenreDetailPanel,
|
|
2897
3065
|
TrackListSidebar,
|
|
2898
3066
|
PlayerPanel,
|
|
2899
3067
|
Rating,
|
|
@@ -2902,4 +3070,4 @@ export {
|
|
|
2902
3070
|
GenreTreeViewSkeleton2 as GenreTreeViewSkeleton,
|
|
2903
3071
|
GenreTreeOutlineSkeleton
|
|
2904
3072
|
};
|
|
2905
|
-
//# sourceMappingURL=chunk-
|
|
3073
|
+
//# sourceMappingURL=chunk-TFLX6C5Q.js.map
|