@behindthemusictree/app-kit-ui 36.0.0 → 37.1.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-RTXLUHJY.js → chunk-57OI426Y.js} +154 -57
- package/dist/chunk-57OI426Y.js.map +1 -0
- package/dist/genre-tree/index.cjs +155 -56
- package/dist/genre-tree/index.cjs.map +1 -1
- package/dist/genre-tree/index.d.ts +62 -5
- package/dist/genre-tree/index.js +5 -1
- package/dist/index.cjs +155 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -1
- package/package.json +5 -5
- package/dist/chunk-RTXLUHJY.js.map +0 -1
|
@@ -130,6 +130,7 @@ type SearchBoxProps = {
|
|
|
130
130
|
onPicked?: (groupKey: string, itemKey: string) => void;
|
|
131
131
|
/** Shows the clear button when set. */
|
|
132
132
|
onClear?: () => void;
|
|
133
|
+
clearLabel?: string;
|
|
133
134
|
/** An action left of the text, e.g. a Previous button. */
|
|
134
135
|
leading?: ReactNode;
|
|
135
136
|
className?: string;
|
|
@@ -137,7 +138,7 @@ type SearchBoxProps = {
|
|
|
137
138
|
onFocus?: () => void;
|
|
138
139
|
};
|
|
139
140
|
/** A search pill over any number of result groups; picking an item calls that item's `onSelect`. */
|
|
140
|
-
declare function SearchBox({ label, placeholder, value, onValueChange, groups, onPicked, onClear, leading, className, onFocus, }: SearchBoxProps): react.JSX.Element;
|
|
141
|
+
declare function SearchBox({ label, placeholder, value, onValueChange, groups, onPicked, onClear, clearLabel, leading, className, onFocus, }: SearchBoxProps): react.JSX.Element;
|
|
141
142
|
|
|
142
143
|
declare function useArtistSearchGroup(getBaseUrl: () => string, text: string, onSelect: (artist: ArtistSearchResult) => void, enabled?: boolean): SearchGroup | undefined;
|
|
143
144
|
|
|
@@ -165,6 +166,9 @@ type GenreSearchProps<T extends CriteriaPlaylistSimple = CriteriaPlaylistSimple>
|
|
|
165
166
|
onPrevious?: () => void;
|
|
166
167
|
/** Called by the clear button, after it empties the input. */
|
|
167
168
|
onClear?: () => void;
|
|
169
|
+
/** Forces the clear button on or off; by default it shows while the input has something to clear. */
|
|
170
|
+
showClear?: boolean;
|
|
171
|
+
clearLabel?: string;
|
|
168
172
|
placeholder?: string;
|
|
169
173
|
/** Called with what the user types, and with "" on clear or pick; never when `selectedName` refills the input. */
|
|
170
174
|
onQueryChange?: (query: string) => void;
|
|
@@ -173,7 +177,7 @@ type GenreSearchProps<T extends CriteriaPlaylistSimple = CriteriaPlaylistSimple>
|
|
|
173
177
|
/** Called when the input gains focus. */
|
|
174
178
|
onFocus?: () => void;
|
|
175
179
|
};
|
|
176
|
-
declare function GenreSearch<T extends CriteriaPlaylistSimple = CriteriaPlaylistSimple>({ genrePlaylists, onSelect, selectedName, onClear, onPrevious, placeholder, onQueryChange, extraGroups, onFocus, }: GenreSearchProps<T>): react.JSX.Element;
|
|
180
|
+
declare function GenreSearch<T extends CriteriaPlaylistSimple = CriteriaPlaylistSimple>({ genrePlaylists, onSelect, selectedName, onClear, showClear, clearLabel, onPrevious, placeholder, onQueryChange, extraGroups, onFocus, }: GenreSearchProps<T>): react.JSX.Element;
|
|
177
181
|
|
|
178
182
|
interface GenreSearchGroupsProps {
|
|
179
183
|
query: string;
|
|
@@ -393,9 +397,10 @@ type GenreDetailPanelProps<O extends CriteriaOverview> = {
|
|
|
393
397
|
/** Tab on the panel's inner edge, for hosts that hide and show the panel. */
|
|
394
398
|
edgeTab?: ComponentProps<typeof DetailPanel>["edgeTab"];
|
|
395
399
|
extras?: GenreInfoPanelBodyProps<O>["extras"];
|
|
396
|
-
|
|
400
|
+
/** The host's search field shows this genre's name and closes the panel. */
|
|
401
|
+
titleInSearch?: boolean;
|
|
397
402
|
};
|
|
398
|
-
declare function GenreDetailPanel<O extends CriteriaOverview>({ genreUuid, scope, getBackendBaseUrl, onClose, onSelectGenre, onZoomIn, shareUrl, shareTarget, collapsed, expanded, onExpandedChange, edgeTab, extras,
|
|
403
|
+
declare function GenreDetailPanel<O extends CriteriaOverview>({ genreUuid, scope, getBackendBaseUrl, onClose, onSelectGenre, onZoomIn, shareUrl, shareTarget, collapsed, expanded, onExpandedChange, edgeTab, extras, titleInSearch, }: GenreDetailPanelProps<O>): react.JSX.Element;
|
|
399
404
|
|
|
400
405
|
type GenrePlaylistTreePerRootProps<T extends TrackBase> = {
|
|
401
406
|
scope: Scope;
|
|
@@ -483,4 +488,56 @@ interface RatingProps {
|
|
|
483
488
|
}
|
|
484
489
|
declare function Rating({ rating, handleChange }: RatingProps): react.JSX.Element;
|
|
485
490
|
|
|
486
|
-
|
|
491
|
+
/** How the song's genre is opened: through the loaded tree's playlists, or by the genre alone for a host without a tree. */
|
|
492
|
+
type SongGenreNavigation = {
|
|
493
|
+
/** Root-to-leaf playlists of a genre in the loaded tree; empty when the tree doesn't hold it. */
|
|
494
|
+
getGenrePath: (genre: CriteriaMinimum) => CriteriaPlaylistSimple[];
|
|
495
|
+
onSelectGenrePlaylist: (genrePlaylist: CriteriaPlaylistSimple) => void;
|
|
496
|
+
onSelectGenre?: never;
|
|
497
|
+
} | {
|
|
498
|
+
onSelectGenre: (genre: CriteriaMinimum) => void;
|
|
499
|
+
getGenrePath?: never;
|
|
500
|
+
onSelectGenrePlaylist?: never;
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
interface EntityPanelState {
|
|
504
|
+
uuid: string | null;
|
|
505
|
+
expanded: boolean;
|
|
506
|
+
onExpandedChange: (expanded: boolean) => void;
|
|
507
|
+
onClose: () => void;
|
|
508
|
+
}
|
|
509
|
+
type EntityPanelOptions = {
|
|
510
|
+
scope: Scope;
|
|
511
|
+
getBackendBaseUrl: () => string;
|
|
512
|
+
/** Artist requests go here when the host serves them from elsewhere; defaults to `getBackendBaseUrl`. */
|
|
513
|
+
getArtistBackendBaseUrl?: () => string;
|
|
514
|
+
getShareUrl?: GetGenreTreeShareUrl;
|
|
515
|
+
renderArtistLink: RenderArtistLink;
|
|
516
|
+
renderSongLink: RenderSongLink;
|
|
517
|
+
song: EntityPanelState;
|
|
518
|
+
artist: EntityPanelState;
|
|
519
|
+
} & SongGenreNavigation;
|
|
520
|
+
/** The song or artist detail overlay panel; a song takes precedence over an artist, undefined when neither is set. */
|
|
521
|
+
declare function useEntityPanel({ scope, getBackendBaseUrl, getArtistBackendBaseUrl, getShareUrl, renderArtistLink, renderSongLink, song, artist, ...navigation }: EntityPanelOptions): OverlayPanel | undefined;
|
|
522
|
+
|
|
523
|
+
type EntityDetailPanelProps = {
|
|
524
|
+
scope: Scope;
|
|
525
|
+
getBackendBaseUrl: () => string;
|
|
526
|
+
getArtistBackendBaseUrl?: () => string;
|
|
527
|
+
getShareUrl?: GetGenreTreeShareUrl;
|
|
528
|
+
songUuid: string | null;
|
|
529
|
+
onSongUuidChange: (uuid: string | null) => void;
|
|
530
|
+
artistUuid: string | null;
|
|
531
|
+
onArtistUuidChange: (uuid: string | null) => void;
|
|
532
|
+
/** Names render as buttons that open the artist by default. */
|
|
533
|
+
renderArtistLink?: RenderArtistLink;
|
|
534
|
+
/** Titles render as buttons that open the song by default. */
|
|
535
|
+
renderSongLink?: RenderSongLink;
|
|
536
|
+
collapsed?: boolean;
|
|
537
|
+
edgeTab?: ComponentProps<typeof DetailPanel>["edgeTab"];
|
|
538
|
+
} & SongGenreNavigation;
|
|
539
|
+
/** The song or artist detail in the shared left DetailPanel, for a host that has no genre tree. Renders nothing
|
|
540
|
+
* while neither is set; a song takes precedence over an artist. */
|
|
541
|
+
declare function EntityDetailPanel({ scope, getBackendBaseUrl, getArtistBackendBaseUrl, getShareUrl, songUuid, onSongUuidChange, artistUuid, onArtistUuidChange, renderArtistLink, renderSongLink, collapsed, edgeTab, ...navigation }: EntityDetailPanelProps): react.JSX.Element | null;
|
|
542
|
+
|
|
543
|
+
export { type ArtistSearchProps, EntityDetailPanel, type EntityDetailPanelProps, type EntityPanelOptions, type EntityPanelState, GenreDetailPanel, type GenreDetailPanelProps, type GenreDetailSlots, type GenreFocusProps, GenreInfoPanelBody, type GenreInfoPanelBodyProps, GenreNodeActionBand, type GenrePlaylistPlayback, type GenrePlaylistTracksSlots, GenrePlaylistTreePerRoot, type GenrePlaylistTreePerRootProps, GenreSearch, type GenreSearchExtraGroup, type GenreSearchGroupsProps, type GenreSearchProps, type GenreTreeShareTarget, GenreTreeView, type GenreTreeViewProps, type GetGenreTreeShareUrl, type ParentChange, type PlayableGenrePlaylist, PlayerPanel, type PlayerPanelProps, Rating, type RatingProps, SEARCH_FAILED_STATUS, SearchBox, type SearchBoxProps, type SearchGroup, type SearchGroupItem, type SongGenreNavigation, TrackItem, type TrackItemProps, TrackListSidebar, type TrackListSidebarProps, TrackPositionPlayPause, type TrackPositionPlayPauseProps, type UseGenrePlaylistPlaybackOptions, type UseGenreRemoteSearchGroupOptions, searchStatus, useArtistSearchGroup, useEntityPanel, useGenrePlaylistPlayback, useGenreRemoteSearchGroup, useSongSearchGroup };
|
package/dist/genre-tree/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
EntityDetailPanel,
|
|
2
3
|
GenreDetailPanel,
|
|
3
4
|
GenreInfoPanelBody,
|
|
4
5
|
GenreNodeActionBand,
|
|
@@ -18,12 +19,14 @@ import {
|
|
|
18
19
|
TrackPositionPlayPause,
|
|
19
20
|
searchStatus,
|
|
20
21
|
useArtistSearchGroup,
|
|
22
|
+
useEntityPanel,
|
|
21
23
|
useGenrePlaylistPlayback,
|
|
22
24
|
useGenreRemoteSearchGroup,
|
|
23
25
|
useSongSearchGroup
|
|
24
|
-
} from "../chunk-
|
|
26
|
+
} from "../chunk-57OI426Y.js";
|
|
25
27
|
import "../chunk-DKQQLLMY.js";
|
|
26
28
|
export {
|
|
29
|
+
EntityDetailPanel,
|
|
27
30
|
GenreDetailPanel,
|
|
28
31
|
GenreInfoPanelBody,
|
|
29
32
|
GenreNodeActionBand,
|
|
@@ -43,6 +46,7 @@ export {
|
|
|
43
46
|
TrackPositionPlayPause,
|
|
44
47
|
searchStatus,
|
|
45
48
|
useArtistSearchGroup,
|
|
49
|
+
useEntityPanel,
|
|
46
50
|
useGenrePlaylistPlayback,
|
|
47
51
|
useGenreRemoteSearchGroup,
|
|
48
52
|
useSongSearchGroup
|
package/dist/index.cjs
CHANGED
|
@@ -24,6 +24,7 @@ __export(src_exports, {
|
|
|
24
24
|
AuthErrorPopup: () => AuthErrorPopup,
|
|
25
25
|
AuthPopup: () => AuthPopup,
|
|
26
26
|
BasePopup: () => BasePopup,
|
|
27
|
+
EntityDetailPanel: () => EntityDetailPanel,
|
|
27
28
|
GenreDetailPanel: () => GenreDetailPanel,
|
|
28
29
|
GenreInfoPanelBody: () => GenreInfoPanelBody,
|
|
29
30
|
GenreNodeActionBand: () => GenreNodeActionBand,
|
|
@@ -50,6 +51,7 @@ __export(src_exports, {
|
|
|
50
51
|
TrackUploadPopup: () => TrackUploadPopup,
|
|
51
52
|
searchStatus: () => searchStatus,
|
|
52
53
|
useArtistSearchGroup: () => useArtistSearchGroup,
|
|
54
|
+
useEntityPanel: () => useEntityPanel,
|
|
53
55
|
useGenrePlaylistPlayback: () => useGenrePlaylistPlayback,
|
|
54
56
|
useGenreRemoteSearchGroup: () => useGenreRemoteSearchGroup,
|
|
55
57
|
useSongSearchGroup: () => useSongSearchGroup
|
|
@@ -1047,6 +1049,7 @@ function SearchBox({
|
|
|
1047
1049
|
groups,
|
|
1048
1050
|
onPicked,
|
|
1049
1051
|
onClear,
|
|
1052
|
+
clearLabel,
|
|
1050
1053
|
leading,
|
|
1051
1054
|
className: className2,
|
|
1052
1055
|
onFocus
|
|
@@ -1078,6 +1081,7 @@ function SearchBox({
|
|
|
1078
1081
|
status,
|
|
1079
1082
|
loading,
|
|
1080
1083
|
onClear,
|
|
1084
|
+
clearLabel,
|
|
1081
1085
|
leading,
|
|
1082
1086
|
inputClassName: "focus-visible:ring-0"
|
|
1083
1087
|
}
|
|
@@ -1173,6 +1177,8 @@ function GenreSearch({
|
|
|
1173
1177
|
onSelect,
|
|
1174
1178
|
selectedName = null,
|
|
1175
1179
|
onClear,
|
|
1180
|
+
showClear,
|
|
1181
|
+
clearLabel,
|
|
1176
1182
|
onPrevious,
|
|
1177
1183
|
placeholder,
|
|
1178
1184
|
onQueryChange,
|
|
@@ -1220,7 +1226,8 @@ function GenreSearch({
|
|
|
1220
1226
|
if (groupKey !== "genres") setQuery("");
|
|
1221
1227
|
onQueryChange?.("");
|
|
1222
1228
|
},
|
|
1223
|
-
|
|
1229
|
+
clearLabel,
|
|
1230
|
+
onClear: showClear ?? canClear ? () => {
|
|
1224
1231
|
clear();
|
|
1225
1232
|
onQueryChange?.("");
|
|
1226
1233
|
} : void 0,
|
|
@@ -2057,10 +2064,12 @@ function useGenreDetailContent({
|
|
|
2057
2064
|
};
|
|
2058
2065
|
}, [detail, scope]);
|
|
2059
2066
|
if (isError) return {
|
|
2067
|
+
name: null,
|
|
2060
2068
|
title: "Genre unavailable",
|
|
2061
2069
|
content: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." })
|
|
2062
2070
|
};
|
|
2063
2071
|
if (isPending || !detail || !view) return {
|
|
2072
|
+
name: null,
|
|
2064
2073
|
title: "Loading genre\u2026",
|
|
2065
2074
|
content: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
|
|
2066
2075
|
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui10.Skeleton, { className: "h-6 w-2/3" }),
|
|
@@ -2082,6 +2091,7 @@ function useGenreDetailContent({
|
|
|
2082
2091
|
);
|
|
2083
2092
|
const ancestors = /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_genre_tree_view4.GenreNodeAncestors, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
|
|
2084
2093
|
return {
|
|
2094
|
+
name: detail.name,
|
|
2085
2095
|
title: detail.name,
|
|
2086
2096
|
headerAccent: { background: view.fill, color: import_genre_tree_view4.ACCENT_TEXT_COLOR },
|
|
2087
2097
|
aside: expanded ? ancestors : void 0,
|
|
@@ -2123,7 +2133,7 @@ function GenreDetailPanel({
|
|
|
2123
2133
|
onExpandedChange,
|
|
2124
2134
|
edgeTab,
|
|
2125
2135
|
extras,
|
|
2126
|
-
|
|
2136
|
+
titleInSearch
|
|
2127
2137
|
}) {
|
|
2128
2138
|
const panel = useGenreDetailContent({
|
|
2129
2139
|
genreUuid,
|
|
@@ -2146,7 +2156,7 @@ function GenreDetailPanel({
|
|
|
2146
2156
|
onExpandedChange,
|
|
2147
2157
|
edgeTab,
|
|
2148
2158
|
aside: panel.aside,
|
|
2149
|
-
|
|
2159
|
+
titleInSearch,
|
|
2150
2160
|
headerActions: onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui10.IconButton, { "aria-label": expanded ? "Shrink panel" : "Extend panel", onClick: () => onExpandedChange(!expanded), children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react12.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react12.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" }) }) : void 0,
|
|
2151
2161
|
children: panel.content
|
|
2152
2162
|
}
|
|
@@ -2700,15 +2710,8 @@ function SongHistory({ uuid, getBackendBaseUrl }) {
|
|
|
2700
2710
|
}
|
|
2701
2711
|
);
|
|
2702
2712
|
}
|
|
2703
|
-
function SongPanelContent({
|
|
2704
|
-
song,
|
|
2705
|
-
scope,
|
|
2706
|
-
getBackendBaseUrl,
|
|
2707
|
-
shareUrl,
|
|
2708
|
-
renderArtistLink,
|
|
2709
|
-
genrePath,
|
|
2710
|
-
onSelectGenre
|
|
2711
|
-
}) {
|
|
2713
|
+
function SongPanelContent(props) {
|
|
2714
|
+
const { song, scope, getBackendBaseUrl, shareUrl, renderArtistLink } = props;
|
|
2712
2715
|
const { playNewTrackListFromTrackUuid } = (0, import_genre_tree26.useTrackList)();
|
|
2713
2716
|
const { playerTrackObject, playState, handlePlayPauseAction } = (0, import_player11.usePlayer)();
|
|
2714
2717
|
const unplayableReason = (0, import_genre_tree26.songUnplayableReason)(song);
|
|
@@ -2718,12 +2721,14 @@ function SongPanelContent({
|
|
|
2718
2721
|
const musicBrainzUrl = (0, import_genre_tree26.musicBrainzRecordingUrl)(song);
|
|
2719
2722
|
const fields = [];
|
|
2720
2723
|
if (song.genre) {
|
|
2721
|
-
const path = genrePath.length > 0 ? genrePath : null;
|
|
2724
|
+
const path = props.genrePath && props.genrePath.length > 0 ? props.genrePath : null;
|
|
2725
|
+
const { onSelectGenre } = props;
|
|
2726
|
+
const genre = song.genre;
|
|
2722
2727
|
fields.push({
|
|
2723
2728
|
label: "Genre",
|
|
2724
|
-
value: path ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("span", { children: [
|
|
2729
|
+
value: onSelectGenre ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("button", { type: "button", onClick: () => onSelectGenre(genre), className: "hover:underline", children: genre.name }) : path ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("span", { children: [
|
|
2725
2730
|
index > 0 && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { "aria-hidden": "true", children: "\u203A " }),
|
|
2726
|
-
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)("button", { type: "button", onClick: () =>
|
|
2731
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)("button", { type: "button", onClick: () => props.onSelectGenrePlaylist?.(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
|
|
2727
2732
|
] }, genrePlaylist.uuid)) }) : song.genre.name
|
|
2728
2733
|
});
|
|
2729
2734
|
} else {
|
|
@@ -2789,11 +2794,10 @@ function useSongPanel({
|
|
|
2789
2794
|
getBackendBaseUrl,
|
|
2790
2795
|
getShareUrl,
|
|
2791
2796
|
renderArtistLink,
|
|
2792
|
-
getGenrePath,
|
|
2793
|
-
onSelectGenre,
|
|
2794
2797
|
expanded,
|
|
2795
2798
|
onExpandedChange,
|
|
2796
|
-
onClose
|
|
2799
|
+
onClose,
|
|
2800
|
+
...navigation
|
|
2797
2801
|
}) {
|
|
2798
2802
|
const result = (0, import_genre_tree27.useSong)(uuid, getBackendBaseUrl);
|
|
2799
2803
|
const panel = { expanded, onExpandedChange, onClose };
|
|
@@ -2837,8 +2841,10 @@ function useSongPanel({
|
|
|
2837
2841
|
getBackendBaseUrl,
|
|
2838
2842
|
shareUrl: shareUrl ?? void 0,
|
|
2839
2843
|
renderArtistLink,
|
|
2840
|
-
|
|
2841
|
-
|
|
2844
|
+
...navigation.getGenrePath ? {
|
|
2845
|
+
genrePath: song.genre ? navigation.getGenrePath(song.genre) : [],
|
|
2846
|
+
onSelectGenrePlaylist: navigation.onSelectGenrePlaylist
|
|
2847
|
+
} : { onSelectGenre: navigation.onSelectGenre }
|
|
2842
2848
|
}
|
|
2843
2849
|
)
|
|
2844
2850
|
};
|
|
@@ -2846,6 +2852,37 @@ function useSongPanel({
|
|
|
2846
2852
|
}
|
|
2847
2853
|
}
|
|
2848
2854
|
|
|
2855
|
+
// src/genre-tree/useEntityPanel.tsx
|
|
2856
|
+
function useEntityPanel({
|
|
2857
|
+
scope,
|
|
2858
|
+
getBackendBaseUrl,
|
|
2859
|
+
getArtistBackendBaseUrl,
|
|
2860
|
+
getShareUrl,
|
|
2861
|
+
renderArtistLink,
|
|
2862
|
+
renderSongLink,
|
|
2863
|
+
song,
|
|
2864
|
+
artist,
|
|
2865
|
+
...navigation
|
|
2866
|
+
}) {
|
|
2867
|
+
const songPanel = useSongPanel({
|
|
2868
|
+
...song,
|
|
2869
|
+
scope,
|
|
2870
|
+
getBackendBaseUrl,
|
|
2871
|
+
getShareUrl,
|
|
2872
|
+
renderArtistLink,
|
|
2873
|
+
...navigation
|
|
2874
|
+
});
|
|
2875
|
+
const artistPanel = useArtistPanel({
|
|
2876
|
+
...artist,
|
|
2877
|
+
scope,
|
|
2878
|
+
getBackendBaseUrl: getArtistBackendBaseUrl ?? getBackendBaseUrl,
|
|
2879
|
+
getShareUrl,
|
|
2880
|
+
renderArtistLink,
|
|
2881
|
+
renderSongLink
|
|
2882
|
+
});
|
|
2883
|
+
return songPanel ?? artistPanel;
|
|
2884
|
+
}
|
|
2885
|
+
|
|
2849
2886
|
// src/genre-tree/SizeMetricToggle.tsx
|
|
2850
2887
|
var import_md2 = require("react-icons/md");
|
|
2851
2888
|
var import_ui19 = require("@behindthemusictree/ui");
|
|
@@ -3028,20 +3065,6 @@ function GenreTreeView({
|
|
|
3028
3065
|
const [expandedArtistUuid, setExpandedArtistUuid] = (0, import_react18.useState)(null);
|
|
3029
3066
|
const artistBaseUrl = artistSearchBaseUrl ?? getBackendBaseUrl;
|
|
3030
3067
|
const songBaseUrl = getBackendBaseUrl;
|
|
3031
|
-
const artistPanel = useArtistPanel({
|
|
3032
|
-
uuid: artistUuid,
|
|
3033
|
-
scope,
|
|
3034
|
-
getBackendBaseUrl: artistBaseUrl,
|
|
3035
|
-
getShareUrl: getShareUrlRef.current,
|
|
3036
|
-
renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
|
|
3037
|
-
expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
|
|
3038
|
-
onExpandedChange: (on) => {
|
|
3039
|
-
if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
|
|
3040
|
-
onArtistExpandedChange?.(on);
|
|
3041
|
-
},
|
|
3042
|
-
renderSongLink: resolvedRenderSongLink,
|
|
3043
|
-
onClose: () => changeArtist(null)
|
|
3044
|
-
});
|
|
3045
3068
|
const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
|
|
3046
3069
|
const getGenrePath = (genre) => {
|
|
3047
3070
|
const path = [];
|
|
@@ -3053,27 +3076,38 @@ function GenreTreeView({
|
|
|
3053
3076
|
}
|
|
3054
3077
|
return path;
|
|
3055
3078
|
};
|
|
3056
|
-
const
|
|
3057
|
-
uuid: songUuid,
|
|
3079
|
+
const entityPanel = useEntityPanel({
|
|
3058
3080
|
scope,
|
|
3059
3081
|
getBackendBaseUrl: songBaseUrl,
|
|
3082
|
+
getArtistBackendBaseUrl: artistBaseUrl,
|
|
3060
3083
|
getShareUrl: getShareUrlRef.current,
|
|
3061
3084
|
renderArtistLink: resolvedRenderArtistLink,
|
|
3085
|
+
renderSongLink: resolvedRenderSongLink,
|
|
3086
|
+
song: {
|
|
3087
|
+
uuid: songUuid,
|
|
3088
|
+
expanded: controlledSongExpanded ?? (songUuid !== null && expandedSongUuid === songUuid),
|
|
3089
|
+
onExpandedChange: (on) => {
|
|
3090
|
+
if (controlledSongExpanded === void 0) setExpandedSongUuid(on ? songUuid : null);
|
|
3091
|
+
onSongExpandedChange?.(on);
|
|
3092
|
+
},
|
|
3093
|
+
onClose: () => changeSong(null)
|
|
3094
|
+
},
|
|
3095
|
+
artist: {
|
|
3096
|
+
uuid: artistUuid,
|
|
3097
|
+
expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
|
|
3098
|
+
onExpandedChange: (on) => {
|
|
3099
|
+
if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
|
|
3100
|
+
onArtistExpandedChange?.(on);
|
|
3101
|
+
},
|
|
3102
|
+
onClose: () => changeArtist(null)
|
|
3103
|
+
},
|
|
3062
3104
|
getGenrePath,
|
|
3063
|
-
|
|
3105
|
+
onSelectGenrePlaylist: (genrePlaylist2) => {
|
|
3064
3106
|
changeSong(null);
|
|
3065
3107
|
selectGenrePlaylist(genrePlaylist2);
|
|
3066
|
-
}
|
|
3067
|
-
expanded: controlledSongExpanded ?? (songUuid !== null && expandedSongUuid === songUuid),
|
|
3068
|
-
onExpandedChange: (on) => {
|
|
3069
|
-
if (controlledSongExpanded === void 0) setExpandedSongUuid(on ? songUuid : null);
|
|
3070
|
-
onSongExpandedChange?.(on);
|
|
3071
|
-
},
|
|
3072
|
-
onClose: () => changeSong(null)
|
|
3108
|
+
}
|
|
3073
3109
|
});
|
|
3074
|
-
const
|
|
3075
|
-
const builtInArtistPanel = overlayPanel || selectedGenreUuid !== null || builtInSongPanel ? void 0 : artistPanel;
|
|
3076
|
-
const builtInPanel = builtInSongPanel ?? builtInArtistPanel;
|
|
3110
|
+
const builtInPanel = overlayPanel || selectedGenreUuid !== null ? void 0 : entityPanel;
|
|
3077
3111
|
const [uncontrolledCollapsed, setUncontrolledCollapsed] = (0, import_react18.useState)(false);
|
|
3078
3112
|
const [uncontrolledExpanded, setUncontrolledExpanded] = (0, import_react18.useState)(false);
|
|
3079
3113
|
const isCollapsed = collapsed ?? uncontrolledCollapsed;
|
|
@@ -3122,7 +3156,6 @@ function GenreTreeView({
|
|
|
3122
3156
|
if (node !== null && songUuid !== null) changeSong(null);
|
|
3123
3157
|
selectNode(node);
|
|
3124
3158
|
};
|
|
3125
|
-
const handleGenreSearchClear = clearSelection;
|
|
3126
3159
|
const handleGenreSearchSelect = (genrePlaylist2) => {
|
|
3127
3160
|
reopenPanel();
|
|
3128
3161
|
if (songUuid !== null) changeSong(null);
|
|
@@ -3205,7 +3238,7 @@ function GenreTreeView({
|
|
|
3205
3238
|
expanded: overlayPanel.expanded ?? false,
|
|
3206
3239
|
onExpandedChange: overlayPanel.onExpandedChange,
|
|
3207
3240
|
onClose: overlayPanel.onClose,
|
|
3208
|
-
|
|
3241
|
+
titleInSearch: false
|
|
3209
3242
|
};
|
|
3210
3243
|
} else if (selectedGenreUuid) {
|
|
3211
3244
|
activePanel = {
|
|
@@ -3215,7 +3248,7 @@ function GenreTreeView({
|
|
|
3215
3248
|
expanded: isExpanded,
|
|
3216
3249
|
onExpandedChange: changeExpanded,
|
|
3217
3250
|
onClose: clearSelection,
|
|
3218
|
-
|
|
3251
|
+
titleInSearch: true,
|
|
3219
3252
|
headerAccent: relations ? { background: relations.fill, color: relations.textColor } : genreContent.headerAccent
|
|
3220
3253
|
};
|
|
3221
3254
|
} else if (builtInPanel) {
|
|
@@ -3226,7 +3259,7 @@ function GenreTreeView({
|
|
|
3226
3259
|
expanded: builtInPanel.expanded,
|
|
3227
3260
|
onExpandedChange: builtInPanel.onExpandedChange,
|
|
3228
3261
|
onClose: builtInPanel.onClose,
|
|
3229
|
-
|
|
3262
|
+
titleInSearch: true
|
|
3230
3263
|
};
|
|
3231
3264
|
}
|
|
3232
3265
|
const isPanelShown = activePanel !== null && (selectedGenreUuid !== null || isTreeReady);
|
|
@@ -3294,8 +3327,10 @@ function GenreTreeView({
|
|
|
3294
3327
|
if (songUuid) changeSong(null);
|
|
3295
3328
|
selectGenre(uuid);
|
|
3296
3329
|
},
|
|
3297
|
-
selectedName:
|
|
3298
|
-
onClear:
|
|
3330
|
+
selectedName: selectedGenreUuid ? genreContent.name : typeof builtInPanel?.title === "string" ? builtInPanel.title : null,
|
|
3331
|
+
onClear: activePanel?.onClose,
|
|
3332
|
+
showClear: activePanel?.titleInSearch ?? false,
|
|
3333
|
+
clearLabel: "Close panel",
|
|
3299
3334
|
onPrevious: handlePrevious,
|
|
3300
3335
|
onQueryChange: onSearchQueryChange,
|
|
3301
3336
|
onFocus: onSearchFocus,
|
|
@@ -3485,8 +3520,7 @@ function GenreTreeView({
|
|
|
3485
3520
|
ref: panelRef,
|
|
3486
3521
|
side: "left",
|
|
3487
3522
|
title: activePanel?.title ?? "",
|
|
3488
|
-
|
|
3489
|
-
hideClose: activePanel?.compact,
|
|
3523
|
+
titleInSearch: activePanel?.titleInSearch,
|
|
3490
3524
|
headerAccent: activePanel?.headerAccent,
|
|
3491
3525
|
headerActions: expandButton,
|
|
3492
3526
|
onClose: activePanel?.onClose ?? noop,
|
|
@@ -3495,7 +3529,7 @@ function GenreTreeView({
|
|
|
3495
3529
|
onExpandedChange: activePanel?.onExpandedChange,
|
|
3496
3530
|
aside: activePanel?.aside,
|
|
3497
3531
|
edgeTab: detailTab,
|
|
3498
|
-
headerClassName: "pt-[calc(var(--layout-search-h)+var(--layout-panel-inset)+0.5rem)]",
|
|
3532
|
+
headerClassName: activePanel?.titleInSearch ? void 0 : "pt-[calc(var(--layout-search-h)+var(--layout-panel-inset)+0.5rem)]",
|
|
3499
3533
|
children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(import_jsx_runtime46.Fragment, { children: [
|
|
3500
3534
|
activePanel.content,
|
|
3501
3535
|
activePanel.aside
|
|
@@ -3942,12 +3976,76 @@ function Rating({ rating, handleChange }) {
|
|
|
3942
3976
|
) })
|
|
3943
3977
|
] }, i)) });
|
|
3944
3978
|
}
|
|
3979
|
+
|
|
3980
|
+
// src/genre-tree/EntityDetailPanel.tsx
|
|
3981
|
+
var import_react21 = require("react");
|
|
3982
|
+
var import_ui22 = require("@behindthemusictree/ui");
|
|
3983
|
+
var import_jsx_runtime51 = require("react/jsx-runtime");
|
|
3984
|
+
function EntityDetailPanel({
|
|
3985
|
+
scope,
|
|
3986
|
+
getBackendBaseUrl,
|
|
3987
|
+
getArtistBackendBaseUrl,
|
|
3988
|
+
getShareUrl,
|
|
3989
|
+
songUuid,
|
|
3990
|
+
onSongUuidChange,
|
|
3991
|
+
artistUuid,
|
|
3992
|
+
onArtistUuidChange,
|
|
3993
|
+
renderArtistLink,
|
|
3994
|
+
renderSongLink,
|
|
3995
|
+
collapsed,
|
|
3996
|
+
edgeTab,
|
|
3997
|
+
...navigation
|
|
3998
|
+
}) {
|
|
3999
|
+
const [expandedSongUuid, setExpandedSongUuid] = (0, import_react21.useState)(null);
|
|
4000
|
+
const [expandedArtistUuid, setExpandedArtistUuid] = (0, import_react21.useState)(null);
|
|
4001
|
+
const panel = useEntityPanel({
|
|
4002
|
+
scope,
|
|
4003
|
+
getBackendBaseUrl,
|
|
4004
|
+
getArtistBackendBaseUrl,
|
|
4005
|
+
getShareUrl,
|
|
4006
|
+
renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("button", { type: "button", onClick: () => onArtistUuidChange(artist.uuid), className: "hover:underline", children: artist.name })),
|
|
4007
|
+
renderSongLink: renderSongLink ?? ((song) => /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("button", { type: "button", onClick: () => onSongUuidChange(song.uuid), className: "hover:underline", children: song.title })),
|
|
4008
|
+
song: {
|
|
4009
|
+
uuid: songUuid,
|
|
4010
|
+
expanded: songUuid !== null && expandedSongUuid === songUuid,
|
|
4011
|
+
onExpandedChange: (on) => setExpandedSongUuid(on ? songUuid : null),
|
|
4012
|
+
onClose: () => onSongUuidChange(null)
|
|
4013
|
+
},
|
|
4014
|
+
artist: {
|
|
4015
|
+
uuid: artistUuid,
|
|
4016
|
+
expanded: artistUuid !== null && expandedArtistUuid === artistUuid,
|
|
4017
|
+
onExpandedChange: (on) => setExpandedArtistUuid(on ? artistUuid : null),
|
|
4018
|
+
onClose: () => onArtistUuidChange(null)
|
|
4019
|
+
},
|
|
4020
|
+
...navigation
|
|
4021
|
+
});
|
|
4022
|
+
if (!panel) return null;
|
|
4023
|
+
return /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
|
|
4024
|
+
import_ui22.DetailPanel,
|
|
4025
|
+
{
|
|
4026
|
+
side: "left",
|
|
4027
|
+
title: panel.title,
|
|
4028
|
+
titleInSearch: true,
|
|
4029
|
+
onClose: panel.onClose,
|
|
4030
|
+
collapsed,
|
|
4031
|
+
expanded: panel.expanded,
|
|
4032
|
+
onExpandedChange: panel.onExpandedChange,
|
|
4033
|
+
aside: panel.extraContent,
|
|
4034
|
+
edgeTab,
|
|
4035
|
+
children: panel.extraContent && !panel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(import_jsx_runtime51.Fragment, { children: [
|
|
4036
|
+
panel.content,
|
|
4037
|
+
panel.extraContent
|
|
4038
|
+
] }) : panel.content
|
|
4039
|
+
}
|
|
4040
|
+
);
|
|
4041
|
+
}
|
|
3945
4042
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3946
4043
|
0 && (module.exports = {
|
|
3947
4044
|
AuthCallbackHandler,
|
|
3948
4045
|
AuthErrorPopup,
|
|
3949
4046
|
AuthPopup,
|
|
3950
4047
|
BasePopup,
|
|
4048
|
+
EntityDetailPanel,
|
|
3951
4049
|
GenreDetailPanel,
|
|
3952
4050
|
GenreInfoPanelBody,
|
|
3953
4051
|
GenreNodeActionBand,
|
|
@@ -3974,6 +4072,7 @@ function Rating({ rating, handleChange }) {
|
|
|
3974
4072
|
TrackUploadPopup,
|
|
3975
4073
|
searchStatus,
|
|
3976
4074
|
useArtistSearchGroup,
|
|
4075
|
+
useEntityPanel,
|
|
3977
4076
|
useGenrePlaylistPlayback,
|
|
3978
4077
|
useGenreRemoteSearchGroup,
|
|
3979
4078
|
useSongSearchGroup
|