@behindthemusictree/app-kit-ui 33.2.0 → 34.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.
@@ -21,9 +21,10 @@ import { useGenreTreeViewState, useArtist as useArtist2, useUpdateGenre as useUp
21
21
  import { usePopup as usePopup5 } from "@behindthemusictree/app-kit/popup";
22
22
 
23
23
  // src/genre-tree/playlist-tree/size-metric.ts
24
+ import { genrePlaylistTrackCount } from "@behindthemusictree/app-kit/genre-tree";
24
25
  var hasViewsCount = (genrePlaylists) => genrePlaylists.every((genrePlaylist) => genrePlaylist.viewsCount !== void 0);
25
26
  var genrePlaylistSize = (genrePlaylist, metric) => {
26
- if (metric === "songs") return genrePlaylist.tracksCount;
27
+ if (metric === "songs") return genrePlaylistTrackCount(genrePlaylist);
27
28
  if (genrePlaylist.viewsCount === void 0) {
28
29
  throw new Error(`Genre playlist ${genrePlaylist.uuid} has no viewsCount to size by views`);
29
30
  }
@@ -47,6 +48,7 @@ function toTreeNodes(genrePlaylists, sizeMetric) {
47
48
  }
48
49
 
49
50
  // src/genre-tree/playlist-tree/TreePerRoot.tsx
51
+ import { genrePlaylistTrackCount as genrePlaylistTrackCount2 } from "@behindthemusictree/app-kit/genre-tree";
50
52
  import { useCallback as useCallback2, useMemo as useMemo2 } from "react";
51
53
  import {
52
54
  GenreTree,
@@ -60,30 +62,32 @@ import { ErrorCode } from "@behindthemusictree/app-kit/transport";
60
62
  import { useCallback, useMemo } from "react";
61
63
  import {
62
64
  TrackListOriginType,
63
- useTrackList
65
+ useTrackList,
66
+ GenreCatalogueCountsSchema,
67
+ defaultGenreMusicSelection,
68
+ genreMusicSelectionKey
64
69
  } from "@behindthemusictree/app-kit/genre-tree";
65
70
  function useGenrePlaylistPlayback({
66
71
  scope,
67
72
  genrePlaylists,
68
73
  artist = null
69
74
  }) {
70
- const { trackList, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist } = useTrackList();
75
+ const { trackList, playNewTrackListFromGenrePlaylist } = useTrackList();
71
76
  const origin = trackList?.origin;
72
77
  const playingNodeId = useMemo(() => {
73
- if (!origin) return null;
74
- if (!artist) return origin.type === TrackListOriginType.GENRE_PLAYLIST ? origin.uuid : null;
75
- if (origin.type !== TrackListOriginType.ARTIST || origin.uuid !== artist.uuid) return null;
76
- const { genreUuid } = origin;
77
- return genreUuid ? genrePlaylists.find((gp) => gp.criteria?.uuid === genreUuid)?.uuid ?? null : null;
78
- }, [origin, artist, genrePlaylists]);
79
- const play = useCallback(
80
- async (genrePlaylist) => {
81
- if (!artist) return playNewTrackListFromGenrePlaylist(genrePlaylist, scope);
82
- if (!genrePlaylist.criteria) return;
83
- return playNewTrackListFromArtist(artist, scope, { ordering: "title", genreUuid: genrePlaylist.criteria.uuid });
84
- },
85
- [artist, scope, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist]
86
- );
78
+ if (!origin || origin.type !== TrackListOriginType.GENRE_PLAYLIST || origin.scope !== scope) return null;
79
+ const playlistOrigin = origin;
80
+ if (playlistOrigin.artistUuid !== artist?.uuid) return null;
81
+ const playlist = genrePlaylists.find((item) => item.uuid === origin.uuid) ?? playlistOrigin.criteriaPlaylist;
82
+ const expected = scope === "reference" && playlist && !("criteria" in playlist && playlist.criteria === null) ? defaultGenreMusicSelection(GenreCatalogueCountsSchema.parse(playlist)) : void 0;
83
+ if (playlistOrigin.selection && expected && genreMusicSelectionKey(playlistOrigin.selection).join() !== genreMusicSelectionKey(expected).join()) return null;
84
+ return origin.uuid;
85
+ }, [origin, artist, genrePlaylists, scope]);
86
+ const play = useCallback(async (playlist) => {
87
+ if (artist && !playlist.criteria) return;
88
+ const selection = scope === "reference" && playlist.criteria !== null ? defaultGenreMusicSelection(GenreCatalogueCountsSchema.parse(playlist)) : void 0;
89
+ return selection || artist ? playNewTrackListFromGenrePlaylist(playlist, scope, { selection, ...artist && { artistUuid: artist.uuid } }) : playNewTrackListFromGenrePlaylist(playlist, scope);
90
+ }, [artist, scope, playNewTrackListFromGenrePlaylist]);
87
91
  return useMemo(() => ({ playingNodeId, play }), [playingNodeId, play]);
88
92
  }
89
93
 
@@ -118,7 +122,7 @@ function GenrePlaylistTreePerRoot({
118
122
  id: genrePlaylist.uuid,
119
123
  parentId: genrePlaylist.parent?.uuid ?? null,
120
124
  name: genrePlaylist.name,
121
- itemCount: genrePlaylist.tracksCount,
125
+ itemCount: genrePlaylistTrackCount2(genrePlaylist),
122
126
  actionable: Boolean(genrePlaylist.criteria),
123
127
  side: genrePlaylist.criteria?.side ?? void 0,
124
128
  wikidataId: genrePlaylist.criteria?.wikidataId ?? null,
@@ -134,7 +138,7 @@ function GenrePlaylistTreePerRoot({
134
138
  setIsPlaying(!isPlaying);
135
139
  return;
136
140
  }
137
- if (genrePlaylist.tracksCount === 0) {
141
+ if (genrePlaylistTrackCount2(genrePlaylist) === 0) {
138
142
  return;
139
143
  }
140
144
  play(genrePlaylist).catch((error) => {
@@ -198,6 +202,7 @@ function GenrePlaylistTreePerRoot({
198
202
  }
199
203
 
200
204
  // src/genre-tree/playlist-tree/TreeWheel.tsx
205
+ import { genrePlaylistTrackCount as genrePlaylistTrackCount3 } from "@behindthemusictree/app-kit/genre-tree";
201
206
  import { useCallback as useCallback3, useMemo as useMemo3 } from "react";
202
207
  import {
203
208
  GenreTreeWheel
@@ -252,7 +257,7 @@ function GenrePlaylistTreeWheel({
252
257
  setIsPlaying(!isPlaying);
253
258
  return;
254
259
  }
255
- if (genrePlaylist.tracksCount === 0) {
260
+ if (genrePlaylistTrackCount3(genrePlaylist) === 0) {
256
261
  return;
257
262
  }
258
263
  play(genrePlaylist).catch((error) => {
@@ -317,6 +322,7 @@ function GenrePlaylistTreeWheel({
317
322
  }
318
323
 
319
324
  // src/genre-tree/playlist-tree/TreeWheelRadialPopCore.tsx
325
+ import { genrePlaylistTrackCount as genrePlaylistTrackCount4 } from "@behindthemusictree/app-kit/genre-tree";
320
326
  import { useCallback as useCallback4, useMemo as useMemo4 } from "react";
321
327
  import {
322
328
  GenreTreeOutline,
@@ -364,7 +370,7 @@ function GenrePlaylistTreeWheelRadialPopCore(props) {
364
370
  setIsPlaying(!isPlaying);
365
371
  return;
366
372
  }
367
- if (genrePlaylist.tracksCount === 0) {
373
+ if (genrePlaylistTrackCount4(genrePlaylist) === 0) {
368
374
  return;
369
375
  }
370
376
  play(genrePlaylist).catch((error) => {
@@ -665,6 +671,12 @@ function GenreSearch({
665
671
  }
666
672
 
667
673
  // src/genre-tree/GenreInfoPanelBody.tsx
674
+ import {
675
+ genreMusicCount,
676
+ defaultGenreMusicSelection as defaultGenreMusicSelection2,
677
+ GenreCatalogueCountsSchema as GenreCatalogueCountsSchema2
678
+ } from "@behindthemusictree/app-kit/genre-tree";
679
+ import { useTrackNoun as useTrackNoun3 } from "@behindthemusictree/app-kit/track-noun";
668
680
  import { usePlayer as usePlayer5 } from "@behindthemusictree/app-kit/player";
669
681
 
670
682
  // src/genre-tree/GenreActionBand.tsx
@@ -1030,6 +1042,7 @@ import { usePlayer as usePlayer4 } from "@behindthemusictree/app-kit/player";
1030
1042
  import { PlayStates } from "@behindthemusictree/app-kit/player";
1031
1043
  import { usePopup as usePopup4 } from "@behindthemusictree/app-kit/popup";
1032
1044
  import { ErrorCode as ErrorCode3 } from "@behindthemusictree/app-kit/transport";
1045
+ import { genreMusicQueryParams } from "@behindthemusictree/app-kit/genre-tree";
1033
1046
  import { genrePlaylistQueryKeys } from "@behindthemusictree/app-kit/genre-tree";
1034
1047
  import { libraryEndpoints, libraryQueryKeys } from "@behindthemusictree/app-kit/genre-tree";
1035
1048
  import { isSong, songUnplayableReason, SongDetailedSchema } from "@behindthemusictree/app-kit/genre-tree";
@@ -1082,6 +1095,7 @@ var nextCursor = (next) => {
1082
1095
  function GenrePlaylistTracks({
1083
1096
  genreUuid,
1084
1097
  genrePlaylist,
1098
+ selection,
1085
1099
  scope,
1086
1100
  getBackendBaseUrl,
1087
1101
  renderArtistLink,
@@ -1091,7 +1105,7 @@ function GenrePlaylistTracks({
1091
1105
  renderTracksToolbar,
1092
1106
  artist = null
1093
1107
  }) {
1094
- const { fetchGenrePlaylistTracksPage, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist } = useTrackList2();
1108
+ const { fetchGenrePlaylistTracksPage, playNewTrackListFromGenrePlaylist, playNewTrackListFromArtist, playNewTrackListFromGenreCatalogue } = useTrackList2();
1095
1109
  const { session, sessionRestored } = useSession();
1096
1110
  const { playerTrackObject, playState, handlePlayPauseAction } = usePlayer4();
1097
1111
  const { showPopup } = usePopup4();
@@ -1100,7 +1114,7 @@ function GenrePlaylistTracks({
1100
1114
  const { fetch } = useFetchWrapper(getBackendBaseUrl);
1101
1115
  const playback = usePlaybackContext();
1102
1116
  const { data, isPending, isError, refetch, hasNextPage, isFetchingNextPage, fetchNextPage } = useInfiniteQuery({
1103
- queryKey: [...scope === "me" ? genrePlaylistQueryKeys.me.tracks(uuid) : artist ? [...libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid), "artist", artist.uuid] : libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid), ...playback.key ? [playback.key] : []],
1117
+ queryKey: [...scope === "me" ? genrePlaylistQueryKeys.me.tracks(uuid) : artist ? [...libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid, selection), "artist", artist.uuid] : libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid, selection), ...playback.key ? [playback.key] : []],
1104
1118
  queryFn: async ({ pageParam }) => {
1105
1119
  if (scope === "me") return fetchGenrePlaylistTracksPage(uuid, scope, Number(pageParam));
1106
1120
  const response = await fetch(
@@ -1108,7 +1122,7 @@ function GenrePlaylistTracks({
1108
1122
  true,
1109
1123
  false,
1110
1124
  {},
1111
- { genre: genreUuid, ...artist && { artist: artist.uuid }, pageSize: CATALOGUE_PAGE_SIZE, withTotal: false, ...pageParam && { cursor: pageParam } }
1125
+ { genre: genreUuid, ...genreMusicQueryParams(selection), ...artist && { artist: artist.uuid }, pageSize: CATALOGUE_PAGE_SIZE, withTotal: false, ...pageParam && { cursor: pageParam } }
1112
1126
  );
1113
1127
  const parsed = parseWithLog(SongPageSchema, response, "GenrePlaylistTracks");
1114
1128
  return { tracks: parsed.results, total: parsed.overallTotal, nextPage: parsed.next ? nextCursor(parsed.next) : null };
@@ -1132,7 +1146,12 @@ function GenrePlaylistTracks({
1132
1146
  const tracks = pages.flatMap((page) => page.tracks);
1133
1147
  const total = pages[0]?.total ?? void 0;
1134
1148
  const play = (startSongUuid) => {
1135
- const started = artist ? playNewTrackListFromArtist(artist, scope, { startSongUuid, ordering: "title", genreUuid }) : playNewTrackListFromGenrePlaylist(genrePlaylist, scope, { startSongUuid });
1149
+ const started = artist ? playNewTrackListFromArtist(artist, scope, {
1150
+ startSongUuid,
1151
+ ordering: "title",
1152
+ genreUuid,
1153
+ ...selection && { selection }
1154
+ }) : scope === "reference" ? playNewTrackListFromGenreCatalogue({ uuid: genreUuid, name: genrePlaylist.name }, scope, { startSongUuid, ...selection && { selection } }) : playNewTrackListFromGenrePlaylist(genrePlaylist, scope, { startSongUuid });
1136
1155
  started.catch((error) => {
1137
1156
  console.error("Failed to play genre playlist:", error);
1138
1157
  showPopup(/* @__PURE__ */ jsx16(InternalErrorPopup, { errorCode: ErrorCode3.CLIENT_UNKNOWN }));
@@ -1213,6 +1232,7 @@ import { Fragment as Fragment8, jsx as jsx17, jsxs as jsxs13 } from "react/jsx-r
1213
1232
  function GenreDetailExtras({
1214
1233
  genreUuid,
1215
1234
  genrePlaylist,
1235
+ selection,
1216
1236
  wikidataId,
1217
1237
  scope,
1218
1238
  getBackendBaseUrl,
@@ -1279,6 +1299,7 @@ function GenreDetailExtras({
1279
1299
  {
1280
1300
  genreUuid,
1281
1301
  genrePlaylist,
1302
+ selection,
1282
1303
  scope,
1283
1304
  getBackendBaseUrl,
1284
1305
  renderArtistLink,
@@ -1308,7 +1329,7 @@ function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
1308
1329
  GenreActionBand,
1309
1330
  {
1310
1331
  ...bandProps,
1311
- onPlayPause: genrePlaylist.tracksCount > 0 ? handlePlayPause : void 0,
1332
+ onPlayPause: ("genreOnlyTracksCount" in genrePlaylist ? genreMusicCount(genrePlaylist) : genrePlaylist.tracksCount) > 0 ? handlePlayPause : void 0,
1312
1333
  isPlaying: isCurrentTrackList && isPlaying
1313
1334
  }
1314
1335
  );
@@ -1318,6 +1339,7 @@ function GenreInfoPanelBody({
1318
1339
  family,
1319
1340
  genreUuid,
1320
1341
  genrePlaylist,
1342
+ catalogue,
1321
1343
  wikidataId,
1322
1344
  scope,
1323
1345
  getBackendBaseUrl,
@@ -1325,9 +1347,30 @@ function GenreInfoPanelBody({
1325
1347
  band,
1326
1348
  extras
1327
1349
  }) {
1350
+ const noun = useTrackNoun3();
1351
+ const summary = scope === "reference" ? GenreCatalogueCountsSchema2.parse(genrePlaylist) : void 0;
1352
+ const selection = summary ? defaultGenreMusicSelection2(summary) : void 0;
1353
+ const describeCounts = (counts) => {
1354
+ const scopes = counts.genreAndSubgenresTracksCount;
1355
+ return "canonical" in scopes ? `${counts.genreOnlyTracksCount} this genre only \xB7 ${scopes.canonical} with canonical subgenres \xB7 ${scopes.regional} with regional subgenres \xB7 ${scopes.all} with all subgenres` : `${counts.genreOnlyTracksCount} this genre only \xB7 ${scopes.all} with subgenres`;
1356
+ };
1328
1357
  return /* @__PURE__ */ jsxs14(Fragment9, { children: [
1329
1358
  toolbar,
1330
1359
  /* @__PURE__ */ jsx18(GenreNodeActionBand, { ...band, genrePlaylist, playback }),
1360
+ summary && catalogue && /* @__PURE__ */ jsxs14("div", { className: "px-3.5 py-3 text-sm text-[var(--color-text)]", children: [
1361
+ /* @__PURE__ */ jsxs14("p", { children: [
1362
+ "Catalogue ",
1363
+ noun.plural,
1364
+ ": ",
1365
+ describeCounts(catalogue)
1366
+ ] }),
1367
+ /* @__PURE__ */ jsxs14("p", { children: [
1368
+ "Playlist ",
1369
+ noun.plural,
1370
+ ": ",
1371
+ describeCounts(summary)
1372
+ ] })
1373
+ ] }),
1331
1374
  /* @__PURE__ */ createElement(
1332
1375
  GenreDetailExtras,
1333
1376
  {
@@ -1336,6 +1379,7 @@ function GenreInfoPanelBody({
1336
1379
  family,
1337
1380
  genreUuid,
1338
1381
  genrePlaylist,
1382
+ selection,
1339
1383
  wikidataId,
1340
1384
  scope,
1341
1385
  getBackendBaseUrl
@@ -2121,6 +2165,8 @@ function GenreTreeView({
2121
2165
  onArtistExpandedChange,
2122
2166
  getShareUrl,
2123
2167
  extraSearchGroups = [],
2168
+ onSearchQueryChange,
2169
+ onSearchFocus,
2124
2170
  artistSearch = true,
2125
2171
  onArtistSelect,
2126
2172
  artistSearchBaseUrl,
@@ -2418,6 +2464,7 @@ function GenreTreeView({
2418
2464
  ] }),
2419
2465
  genreUuid,
2420
2466
  genrePlaylist,
2467
+ catalogue: genrePlaylist.catalogue,
2421
2468
  wikidataId: criteria.wikidataId,
2422
2469
  scope,
2423
2470
  getBackendBaseUrl,
@@ -2550,7 +2597,11 @@ function GenreTreeView({
2550
2597
  selectedName: selectedGenrePlaylist?.name ?? (typeof builtInPanel?.title === "string" ? builtInPanel.title : null),
2551
2598
  onClear: builtInSongPanel ? () => changeSong(null) : builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
2552
2599
  onPrevious: handlePrevious,
2553
- onQueryChange: artistSearch || songSearch || extraSearchGroups.length > 0 ? setSearchText : void 0,
2600
+ onQueryChange: (text) => {
2601
+ setSearchText(text);
2602
+ onSearchQueryChange?.(text);
2603
+ },
2604
+ onFocus: onSearchFocus,
2554
2605
  extraGroups
2555
2606
  }
2556
2607
  ),
@@ -2769,7 +2820,9 @@ import {
2769
2820
  import { DetailPanel as DetailPanel2, IconButton as IconButton2, Skeleton as Skeleton7 } from "@behindthemusictree/ui";
2770
2821
  import { Maximize2 as Maximize22, Minimize2 as Minimize22 } from "lucide-react";
2771
2822
  import {
2772
- useFetchGenreDetail
2823
+ useFetchGenreDetail,
2824
+ genreMusicCount as genreMusicCount2,
2825
+ GenrePlaylistSummarySchema
2773
2826
  } from "@behindthemusictree/app-kit/genre-tree";
2774
2827
  import { Fragment as Fragment14, jsx as jsx33, jsxs as jsxs26 } from "react/jsx-runtime";
2775
2828
  var RELATIVE_ITEM_COUNT = 1;
@@ -2814,7 +2867,7 @@ function GenreDetailPanel({
2814
2867
  id: detail.uuid,
2815
2868
  parentId: detail.parent?.uuid ?? null,
2816
2869
  name: detail.name,
2817
- itemCount: detail.tracksCount,
2870
+ itemCount: scope === "reference" ? genreMusicCount2(GenrePlaylistSummarySchema.parse(detail.criteriaPlaylist)) : detail.tracksCount,
2818
2871
  wikidataId: detail.wikidataId
2819
2872
  },
2820
2873
  fill: rootColor,
@@ -2824,7 +2877,7 @@ function GenreDetailPanel({
2824
2877
  ({ ascendant }, index) => relative(ascendant, index === 0 ? null : ancestors2[index - 1].ascendant.uuid)
2825
2878
  )
2826
2879
  };
2827
- }, [detail]);
2880
+ }, [detail, scope]);
2828
2881
  if (isError) {
2829
2882
  return /* @__PURE__ */ jsx33("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
2830
2883
  }
@@ -2877,7 +2930,8 @@ function GenreDetailPanel({
2877
2930
  ancestors
2878
2931
  ] }),
2879
2932
  genreUuid: detail.uuid,
2880
- genrePlaylist: { ...detail.criteriaPlaylist, tracksCount: detail.tracksCount },
2933
+ genrePlaylist: scope === "reference" ? GenrePlaylistSummarySchema.parse(detail.criteriaPlaylist) : { ...detail.criteriaPlaylist, tracksCount: detail.tracksCount },
2934
+ catalogue: detail.catalogue,
2881
2935
  wikidataId: detail.wikidataId,
2882
2936
  scope,
2883
2937
  getBackendBaseUrl,
@@ -2905,7 +2959,7 @@ import { useEffect as useEffect6, useLayoutEffect as useLayoutEffect2, useRef as
2905
2959
  import { ListMusic } from "lucide-react";
2906
2960
  import { useTrackList as useTrackList6 } from "@behindthemusictree/app-kit/genre-tree";
2907
2961
  import { TrackListOriginType as TrackListOriginType2 } from "@behindthemusictree/app-kit/genre-tree";
2908
- import { useTrackNoun as useTrackNoun3 } from "@behindthemusictree/app-kit/track-noun";
2962
+ import { useTrackNoun as useTrackNoun4 } from "@behindthemusictree/app-kit/track-noun";
2909
2963
  import { jsx as jsx34, jsxs as jsxs27 } from "react/jsx-runtime";
2910
2964
  var useBrowserLayoutEffect = typeof window === "undefined" ? useEffect6 : useLayoutEffect2;
2911
2965
  function TrackListSidebar({
@@ -2920,7 +2974,7 @@ function TrackListSidebar({
2920
2974
  renderSongLink
2921
2975
  }) {
2922
2976
  const { trackList, loadMore, autoAdvanceScrollRequest } = useTrackList6();
2923
- const trackNoun = useTrackNoun3();
2977
+ const trackNoun = useTrackNoun4();
2924
2978
  const trackListRef = useRef4(null);
2925
2979
  const previousOriginRef = useRef4(trackList?.origin ?? null);
2926
2980
  const handledAutoAdvanceRequestIdRef = useRef4(null);
@@ -3361,4 +3415,4 @@ export {
3361
3415
  GenreTreeViewSkeleton2 as GenreTreeViewSkeleton,
3362
3416
  GenreTreeOutlineSkeleton
3363
3417
  };
3364
- //# sourceMappingURL=chunk-FHGQGTCN.js.map
3418
+ //# sourceMappingURL=chunk-A7OWCEIK.js.map