@behindthemusictree/app-kit-ui 33.3.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
@@ -2420,6 +2464,7 @@ function GenreTreeView({
2420
2464
  ] }),
2421
2465
  genreUuid,
2422
2466
  genrePlaylist,
2467
+ catalogue: genrePlaylist.catalogue,
2423
2468
  wikidataId: criteria.wikidataId,
2424
2469
  scope,
2425
2470
  getBackendBaseUrl,
@@ -2775,7 +2820,9 @@ import {
2775
2820
  import { DetailPanel as DetailPanel2, IconButton as IconButton2, Skeleton as Skeleton7 } from "@behindthemusictree/ui";
2776
2821
  import { Maximize2 as Maximize22, Minimize2 as Minimize22 } from "lucide-react";
2777
2822
  import {
2778
- useFetchGenreDetail
2823
+ useFetchGenreDetail,
2824
+ genreMusicCount as genreMusicCount2,
2825
+ GenrePlaylistSummarySchema
2779
2826
  } from "@behindthemusictree/app-kit/genre-tree";
2780
2827
  import { Fragment as Fragment14, jsx as jsx33, jsxs as jsxs26 } from "react/jsx-runtime";
2781
2828
  var RELATIVE_ITEM_COUNT = 1;
@@ -2820,7 +2867,7 @@ function GenreDetailPanel({
2820
2867
  id: detail.uuid,
2821
2868
  parentId: detail.parent?.uuid ?? null,
2822
2869
  name: detail.name,
2823
- itemCount: detail.tracksCount,
2870
+ itemCount: scope === "reference" ? genreMusicCount2(GenrePlaylistSummarySchema.parse(detail.criteriaPlaylist)) : detail.tracksCount,
2824
2871
  wikidataId: detail.wikidataId
2825
2872
  },
2826
2873
  fill: rootColor,
@@ -2830,7 +2877,7 @@ function GenreDetailPanel({
2830
2877
  ({ ascendant }, index) => relative(ascendant, index === 0 ? null : ancestors2[index - 1].ascendant.uuid)
2831
2878
  )
2832
2879
  };
2833
- }, [detail]);
2880
+ }, [detail, scope]);
2834
2881
  if (isError) {
2835
2882
  return /* @__PURE__ */ jsx33("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
2836
2883
  }
@@ -2883,7 +2930,8 @@ function GenreDetailPanel({
2883
2930
  ancestors
2884
2931
  ] }),
2885
2932
  genreUuid: detail.uuid,
2886
- genrePlaylist: { ...detail.criteriaPlaylist, tracksCount: detail.tracksCount },
2933
+ genrePlaylist: scope === "reference" ? GenrePlaylistSummarySchema.parse(detail.criteriaPlaylist) : { ...detail.criteriaPlaylist, tracksCount: detail.tracksCount },
2934
+ catalogue: detail.catalogue,
2887
2935
  wikidataId: detail.wikidataId,
2888
2936
  scope,
2889
2937
  getBackendBaseUrl,
@@ -2911,7 +2959,7 @@ import { useEffect as useEffect6, useLayoutEffect as useLayoutEffect2, useRef as
2911
2959
  import { ListMusic } from "lucide-react";
2912
2960
  import { useTrackList as useTrackList6 } from "@behindthemusictree/app-kit/genre-tree";
2913
2961
  import { TrackListOriginType as TrackListOriginType2 } from "@behindthemusictree/app-kit/genre-tree";
2914
- import { useTrackNoun as useTrackNoun3 } from "@behindthemusictree/app-kit/track-noun";
2962
+ import { useTrackNoun as useTrackNoun4 } from "@behindthemusictree/app-kit/track-noun";
2915
2963
  import { jsx as jsx34, jsxs as jsxs27 } from "react/jsx-runtime";
2916
2964
  var useBrowserLayoutEffect = typeof window === "undefined" ? useEffect6 : useLayoutEffect2;
2917
2965
  function TrackListSidebar({
@@ -2926,7 +2974,7 @@ function TrackListSidebar({
2926
2974
  renderSongLink
2927
2975
  }) {
2928
2976
  const { trackList, loadMore, autoAdvanceScrollRequest } = useTrackList6();
2929
- const trackNoun = useTrackNoun3();
2977
+ const trackNoun = useTrackNoun4();
2930
2978
  const trackListRef = useRef4(null);
2931
2979
  const previousOriginRef = useRef4(trackList?.origin ?? null);
2932
2980
  const handledAutoAdvanceRequestIdRef = useRef4(null);
@@ -3367,4 +3415,4 @@ export {
3367
3415
  GenreTreeViewSkeleton2 as GenreTreeViewSkeleton,
3368
3416
  GenreTreeOutlineSkeleton
3369
3417
  };
3370
- //# sourceMappingURL=chunk-QMELESFB.js.map
3418
+ //# sourceMappingURL=chunk-A7OWCEIK.js.map