@behindthemusictree/app-kit-ui 35.0.0 → 37.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.
@@ -483,6 +483,7 @@ function SearchBox({
483
483
  groups,
484
484
  onPicked,
485
485
  onClear,
486
+ clearLabel,
486
487
  leading,
487
488
  className: className2,
488
489
  onFocus
@@ -514,6 +515,7 @@ function SearchBox({
514
515
  status,
515
516
  loading,
516
517
  onClear,
518
+ clearLabel,
517
519
  leading,
518
520
  inputClassName: "focus-visible:ring-0"
519
521
  }
@@ -614,6 +616,8 @@ function GenreSearch({
614
616
  onSelect,
615
617
  selectedName = null,
616
618
  onClear,
619
+ showClear,
620
+ clearLabel,
617
621
  onPrevious,
618
622
  placeholder,
619
623
  onQueryChange,
@@ -661,7 +665,8 @@ function GenreSearch({
661
665
  if (groupKey !== "genres") setQuery("");
662
666
  onQueryChange?.("");
663
667
  },
664
- onClear: canClear ? () => {
668
+ clearLabel,
669
+ onClear: showClear ?? canClear ? () => {
665
670
  clear();
666
671
  onQueryChange?.("");
667
672
  } : void 0,
@@ -1096,7 +1101,7 @@ import { z } from "zod";
1096
1101
  import { FaPlay, FaPause } from "react-icons/fa";
1097
1102
  import { PanelSection, RingLoader, Skeleton } from "@behindthemusictree/ui";
1098
1103
  import { useSession } from "@behindthemusictree/app-kit/auth";
1099
- import { usePlaybackContext, useFetchWrapper } from "@behindthemusictree/app-kit/transport";
1104
+ import { usePlaybackContext, usePlaybackFetch } from "@behindthemusictree/app-kit/transport";
1100
1105
  import { PaginatedResponseSchema } from "@behindthemusictree/app-kit/transport";
1101
1106
  import { parseWithLog } from "@behindthemusictree/app-kit/transport";
1102
1107
  import { usePlayer as usePlayer4 } from "@behindthemusictree/app-kit/player";
@@ -1174,7 +1179,7 @@ function GenrePlaylistTracks({
1174
1179
  const trackNoun = useTrackNoun();
1175
1180
  const uuid = genrePlaylist?.uuid;
1176
1181
  const genreName = selectedGenreName ?? genrePlaylist?.name ?? "Genre";
1177
- const { fetch } = useFetchWrapper(getBackendBaseUrl);
1182
+ const { fetchBound } = usePlaybackFetch(getBackendBaseUrl);
1178
1183
  const playback = usePlaybackContext();
1179
1184
  const { data, isPending, isError, refetch, hasNextPage, isFetchingNextPage, fetchNextPage } = useInfiniteQuery({
1180
1185
  queryKey: [...scope === "me" ? uuid ? genrePlaylistQueryKeys.me.tracks(uuid) : ["missing-personal-genre-playlist", genreUuid] : artist ? [...libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid, selection), "artist", artist.uuid] : libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid, selection), ...playback.key ? [playback.key] : []],
@@ -1183,7 +1188,7 @@ function GenrePlaylistTracks({
1183
1188
  if (!uuid) throw new Error("Personal genre has no playlist");
1184
1189
  return fetchGenrePlaylistTracksPage(uuid, scope, Number(pageParam));
1185
1190
  }
1186
- const response = await fetch(
1191
+ const response = await fetchBound(
1187
1192
  libraryEndpoints.reference.songs.list(),
1188
1193
  true,
1189
1194
  false,
@@ -1195,7 +1200,9 @@ function GenrePlaylistTracks({
1195
1200
  },
1196
1201
  initialPageParam: scope === "me" ? 1 : "",
1197
1202
  getNextPageParam: (lastPage) => lastPage.nextPage ?? void 0,
1198
- enabled: playback.ready && (scope !== "me" || uuid !== void 0 && sessionRestored && !!session?.accessToken)
1203
+ enabled: playback.ready && (scope !== "me" || uuid !== void 0 && sessionRestored && !!session?.accessToken),
1204
+ retry: (failureCount, error) => failureCount < 1 && error instanceof DOMException && error.name === "AbortError",
1205
+ retryDelay: 0
1199
1206
  });
1200
1207
  const [sentinel, setSentinel] = useState6(null);
1201
1208
  useEffect3(() => {
@@ -1514,10 +1521,12 @@ function useGenreDetailContent({
1514
1521
  };
1515
1522
  }, [detail, scope]);
1516
1523
  if (isError) return {
1524
+ name: null,
1517
1525
  title: "Genre unavailable",
1518
1526
  content: /* @__PURE__ */ jsx20("p", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." })
1519
1527
  };
1520
1528
  if (isPending || !detail || !view) return {
1529
+ name: null,
1521
1530
  title: "Loading genre\u2026",
1522
1531
  content: /* @__PURE__ */ jsxs15("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
1523
1532
  /* @__PURE__ */ jsx20(Skeleton3, { className: "h-6 w-2/3" }),
@@ -1539,6 +1548,7 @@ function useGenreDetailContent({
1539
1548
  );
1540
1549
  const ancestors = /* @__PURE__ */ jsx20(GenreNodeAncestors, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
1541
1550
  return {
1551
+ name: detail.name,
1542
1552
  title: detail.name,
1543
1553
  headerAccent: { background: view.fill, color: ACCENT_TEXT_COLOR },
1544
1554
  aside: expanded ? ancestors : void 0,
@@ -1580,7 +1590,7 @@ function GenreDetailPanel({
1580
1590
  onExpandedChange,
1581
1591
  edgeTab,
1582
1592
  extras,
1583
- headerClassName
1593
+ titleInSearch
1584
1594
  }) {
1585
1595
  const panel = useGenreDetailContent({
1586
1596
  genreUuid,
@@ -1603,7 +1613,7 @@ function GenreDetailPanel({
1603
1613
  onExpandedChange,
1604
1614
  edgeTab,
1605
1615
  aside: panel.aside,
1606
- headerClassName,
1616
+ titleInSearch,
1607
1617
  headerActions: onExpandedChange ? /* @__PURE__ */ jsx20(IconButton, { "aria-label": expanded ? "Shrink panel" : "Extend panel", onClick: () => onExpandedChange(!expanded), children: expanded ? /* @__PURE__ */ jsx20(Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ jsx20(Maximize2, { "aria-hidden": "true", className: "h-4 w-4" }) }) : void 0,
1608
1618
  children: panel.content
1609
1619
  }
@@ -1852,7 +1862,7 @@ import {
1852
1862
  songUnplayableReason as songUnplayableReason3,
1853
1863
  useTrackList as useTrackList4
1854
1864
  } from "@behindthemusictree/app-kit/genre-tree";
1855
- import { PaginatedResponseSchema as PaginatedResponseSchema2, useFetchWrapper as useFetchWrapper2, useQueryWithParse } from "@behindthemusictree/app-kit/transport";
1865
+ import { PaginatedResponseSchema as PaginatedResponseSchema2, usePlaybackQuery } from "@behindthemusictree/app-kit/transport";
1856
1866
 
1857
1867
  // src/genre-tree/track-list-sidebar/TrackItem.tsx
1858
1868
  import { usePlayer as usePlayer7 } from "@behindthemusictree/app-kit/player";
@@ -1988,12 +1998,12 @@ var SORTS = [
1988
1998
  function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLink }) {
1989
1999
  const [page, setPage] = useState7(1);
1990
2000
  const [ordering, setOrdering] = useState7("title");
1991
- const { fetch } = useFetchWrapper2(getBackendBaseUrl);
1992
2001
  const { playNewTrackListFromArtist } = useTrackList4();
1993
2002
  const { playerTrackObject } = usePlayer8();
1994
- const { data, isPending, isError } = useQueryWithParse({
2003
+ const { data, isPending, isError } = usePlaybackQuery({
2004
+ getBackendBaseUrl,
1995
2005
  queryKey: ["referenceSongs", "artist", artist.uuid, ordering, page],
1996
- queryFn: () => fetch(libraryEndpoints2.reference.songs.list(), true, false, void 0, {
2006
+ queryFn: (fetchBound) => fetchBound(libraryEndpoints2.reference.songs.list(), true, false, void 0, {
1997
2007
  artist: artist.uuid,
1998
2008
  ordering,
1999
2009
  page
@@ -2439,6 +2449,8 @@ function GenreTreeView({
2439
2449
  };
2440
2450
  const {
2441
2451
  isLoading,
2452
+ isError,
2453
+ refetch,
2442
2454
  isFetching: isTreeFetching,
2443
2455
  genrePlaylistByNodeId,
2444
2456
  visibleGenrePlaylists,
@@ -2571,7 +2583,7 @@ function GenreTreeView({
2571
2583
  }, [selectedGenreUuid, artistUuid, songUuid]);
2572
2584
  const [isWheelRevealed, setIsWheelRevealed] = useState9(false);
2573
2585
  const usesHandoff = viewMode === "wheel" || viewMode === "pop-core";
2574
- const isTreeReady = !isLoading && (!usesHandoff || isWheelRevealed);
2586
+ const isTreeReady = !isLoading && !isError && (!usesHandoff || isWheelRevealed);
2575
2587
  const [resolveStyle, setResolveStyle] = useState9(null);
2576
2588
  const handleNodeStyleResolverChange = useCallback6(
2577
2589
  (resolver) => setResolveStyle(() => resolver),
@@ -2602,7 +2614,6 @@ function GenreTreeView({
2602
2614
  if (node !== null && songUuid !== null) changeSong(null);
2603
2615
  selectNode(node);
2604
2616
  };
2605
- const handleGenreSearchClear = clearSelection;
2606
2617
  const handleGenreSearchSelect = (genrePlaylist2) => {
2607
2618
  reopenPanel();
2608
2619
  if (songUuid !== null) changeSong(null);
@@ -2685,7 +2696,7 @@ function GenreTreeView({
2685
2696
  expanded: overlayPanel.expanded ?? false,
2686
2697
  onExpandedChange: overlayPanel.onExpandedChange,
2687
2698
  onClose: overlayPanel.onClose,
2688
- compact: false
2699
+ titleInSearch: false
2689
2700
  };
2690
2701
  } else if (selectedGenreUuid) {
2691
2702
  activePanel = {
@@ -2695,7 +2706,7 @@ function GenreTreeView({
2695
2706
  expanded: isExpanded,
2696
2707
  onExpandedChange: changeExpanded,
2697
2708
  onClose: clearSelection,
2698
- compact: false,
2709
+ titleInSearch: true,
2699
2710
  headerAccent: relations ? { background: relations.fill, color: relations.textColor } : genreContent.headerAccent
2700
2711
  };
2701
2712
  } else if (builtInPanel) {
@@ -2706,7 +2717,7 @@ function GenreTreeView({
2706
2717
  expanded: builtInPanel.expanded,
2707
2718
  onExpandedChange: builtInPanel.onExpandedChange,
2708
2719
  onClose: builtInPanel.onClose,
2709
- compact: true
2720
+ titleInSearch: true
2710
2721
  };
2711
2722
  }
2712
2723
  const isPanelShown = activePanel !== null && (selectedGenreUuid !== null || isTreeReady);
@@ -2751,7 +2762,7 @@ function GenreTreeView({
2751
2762
  ] });
2752
2763
  };
2753
2764
  const actions = /* @__PURE__ */ jsxs26(Fragment14, { children: [
2754
- !isLoading && /* @__PURE__ */ jsx34(
2765
+ !isLoading && !isError && /* @__PURE__ */ jsx34(
2755
2766
  GenreTreeSearch,
2756
2767
  {
2757
2768
  artistBaseUrl,
@@ -2774,17 +2785,19 @@ function GenreTreeView({
2774
2785
  if (songUuid) changeSong(null);
2775
2786
  selectGenre(uuid);
2776
2787
  },
2777
- selectedName: (selectedGenreUuid ? genreContent.title : null) ?? (typeof builtInPanel?.title === "string" ? builtInPanel.title : null),
2778
- onClear: builtInSongPanel ? () => changeSong(null) : builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
2788
+ selectedName: selectedGenreUuid ? genreContent.name : typeof builtInPanel?.title === "string" ? builtInPanel.title : null,
2789
+ onClear: activePanel?.onClose,
2790
+ showClear: activePanel?.titleInSearch ?? false,
2791
+ clearLabel: "Close panel",
2779
2792
  onPrevious: handlePrevious,
2780
2793
  onQueryChange: onSearchQueryChange,
2781
2794
  onFocus: onSearchFocus,
2782
2795
  extraGroups: extraSearchGroups
2783
2796
  }
2784
2797
  ),
2785
- !isLoading && filterArtist && /* @__PURE__ */ jsx34("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
2798
+ !isLoading && !isError && filterArtist && /* @__PURE__ */ jsx34("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
2786
2799
  /* @__PURE__ */ jsxs26("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: [
2787
- !isLoading && !isControlled && /* @__PURE__ */ jsx34(
2800
+ !isLoading && !isError && !isControlled && /* @__PURE__ */ jsx34(
2788
2801
  SegmentedControl2,
2789
2802
  {
2790
2803
  "aria-label": "Tree view mode",
@@ -2798,7 +2811,7 @@ function GenreTreeView({
2798
2811
  ]
2799
2812
  }
2800
2813
  ),
2801
- !isLoading && showDisplayOptions && /* @__PURE__ */ jsx34(
2814
+ !isLoading && !isError && showDisplayOptions && /* @__PURE__ */ jsx34(
2802
2815
  "div",
2803
2816
  {
2804
2817
  className: "flex items-center gap-1",
@@ -2815,7 +2828,7 @@ function GenreTreeView({
2815
2828
  )
2816
2829
  }
2817
2830
  ),
2818
- !isLoading && !readOnly && showAddRoot && /* @__PURE__ */ jsx34(
2831
+ !isLoading && !isError && !readOnly && showAddRoot && /* @__PURE__ */ jsx34(
2819
2832
  IconTextButton,
2820
2833
  {
2821
2834
  icon: Plus2,
@@ -2838,7 +2851,7 @@ function GenreTreeView({
2838
2851
  children: actions
2839
2852
  }
2840
2853
  ),
2841
- !isLoading && filterArtist && /* @__PURE__ */ jsx34(
2854
+ !isLoading && !isError && filterArtist && /* @__PURE__ */ jsx34(
2842
2855
  "div",
2843
2856
  {
2844
2857
  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"}`,
@@ -2846,8 +2859,12 @@ function GenreTreeView({
2846
2859
  }
2847
2860
  ),
2848
2861
  /* @__PURE__ */ jsx34("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ jsxs26("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
2849
- !isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ jsx34("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
2850
- isLoading ? /* @__PURE__ */ jsx34(GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
2862
+ !isLoading && !isError && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ jsx34("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
2863
+ isLoading ? /* @__PURE__ */ jsx34(GenreTreeViewSkeleton, { viewMode }) : isError ? /* @__PURE__ */ jsxs26("p", { role: "alert", className: "m-auto text-center", children: [
2864
+ "Couldn't load the genre tree.",
2865
+ " ",
2866
+ /* @__PURE__ */ jsx34("button", { type: "button", className: "underline", onClick: refetch, children: "Retry" })
2867
+ ] }) : viewMode === "wheel" ? (
2851
2868
  // The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
2852
2869
  // and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
2853
2870
  // Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
@@ -2961,8 +2978,7 @@ function GenreTreeView({
2961
2978
  ref: panelRef,
2962
2979
  side: "left",
2963
2980
  title: activePanel?.title ?? "",
2964
- hideTitle: activePanel?.compact,
2965
- hideClose: activePanel?.compact,
2981
+ titleInSearch: activePanel?.titleInSearch,
2966
2982
  headerAccent: activePanel?.headerAccent,
2967
2983
  headerActions: expandButton,
2968
2984
  onClose: activePanel?.onClose ?? noop,
@@ -2971,7 +2987,7 @@ function GenreTreeView({
2971
2987
  onExpandedChange: activePanel?.onExpandedChange,
2972
2988
  aside: activePanel?.aside,
2973
2989
  edgeTab: detailTab,
2974
- headerClassName: "pt-[calc(var(--layout-search-h)+var(--layout-panel-inset)+0.5rem)]",
2990
+ headerClassName: activePanel?.titleInSearch ? void 0 : "pt-[calc(var(--layout-search-h)+var(--layout-panel-inset)+0.5rem)]",
2975
2991
  children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ jsxs26(Fragment14, { children: [
2976
2992
  activePanel.content,
2977
2993
  activePanel.aside
@@ -3453,4 +3469,4 @@ export {
3453
3469
  GenreTreeViewSkeleton2 as GenreTreeViewSkeleton,
3454
3470
  GenreTreeOutlineSkeleton
3455
3471
  };
3456
- //# sourceMappingURL=chunk-YC3WBUVO.js.map
3472
+ //# sourceMappingURL=chunk-FTYMRCJS.js.map