@behindthemusictree/app-kit-ui 40.1.0 → 41.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/index.cjs CHANGED
@@ -596,6 +596,7 @@ var import_lucide_react18 = require("lucide-react");
596
596
  var import_ui24 = require("@behindthemusictree/ui");
597
597
  var import_genre_tree_view5 = require("@behindthemusictree/genre-tree-view");
598
598
  var import_genre_tree30 = require("@behindthemusictree/app-kit/genre-tree");
599
+ var import_genre_tree31 = require("@behindthemusictree/app-kit/genre-tree");
599
600
  var import_popup5 = require("@behindthemusictree/app-kit/popup");
600
601
 
601
602
  // src/genre-tree/playlist-tree/size-metric.ts
@@ -1700,6 +1701,29 @@ function ClampedText({ children, className: className2 = "" }) {
1700
1701
  ] });
1701
1702
  }
1702
1703
 
1704
+ // src/genre-tree/CommonsPhoto.tsx
1705
+ var import_jsx_runtime32 = require("react/jsx-runtime");
1706
+ function ExternalLink({ href, children }) {
1707
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
1708
+ }
1709
+ function CommonsPhoto({
1710
+ image,
1711
+ alt,
1712
+ creditLabel = "Photo"
1713
+ }) {
1714
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("figure", { className: "flex flex-col gap-1.5", children: [
1715
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("img", { src: image.url, alt, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
1716
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("figcaption", { className: "text-caption text-[var(--color-text-muted)]", children: [
1717
+ creditLabel,
1718
+ ": ",
1719
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
1720
+ ",",
1721
+ " ",
1722
+ image.licenseUrl ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
1723
+ ] })
1724
+ ] });
1725
+ }
1726
+
1703
1727
  // src/genre-tree/GenrePlaylistTracks.tsx
1704
1728
  var import_react13 = require("react");
1705
1729
  var import_react_query = require("@tanstack/react-query");
@@ -1722,15 +1746,15 @@ var import_genre_tree16 = require("@behindthemusictree/app-kit/genre-tree");
1722
1746
 
1723
1747
  // src/genre-tree/MusicBrainzRecordingLink.tsx
1724
1748
  var import_genre_tree11 = require("@behindthemusictree/app-kit/genre-tree");
1725
- var import_jsx_runtime32 = require("react/jsx-runtime");
1749
+ var import_jsx_runtime33 = require("react/jsx-runtime");
1726
1750
  function MusicBrainzRecordingLink({
1727
1751
  song,
1728
1752
  children,
1729
1753
  className: className2 = ""
1730
1754
  }) {
1731
1755
  const url = (0, import_genre_tree11.musicBrainzRecordingUrl)(song);
1732
- if (!url) return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_jsx_runtime32.Fragment, { children });
1733
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1756
+ if (!url) return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_jsx_runtime33.Fragment, { children });
1757
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
1734
1758
  "a",
1735
1759
  {
1736
1760
  href: url,
@@ -1747,7 +1771,7 @@ function MusicBrainzRecordingLink({
1747
1771
  // src/genre-tree/GenrePlaylistTracks.tsx
1748
1772
  var import_genre_tree17 = require("@behindthemusictree/app-kit/genre-tree");
1749
1773
  var import_track_noun2 = require("@behindthemusictree/app-kit/track-noun");
1750
- var import_jsx_runtime33 = require("react/jsx-runtime");
1774
+ var import_jsx_runtime34 = require("react/jsx-runtime");
1751
1775
  var CATALOGUE_PAGE_SIZE = 100;
1752
1776
  var SongPageSchema = (0, import_transport5.PaginatedResponseSchema)(import_genre_tree15.SongDetailedSchema).extend({
1753
1777
  overallTotal: import_zod.z.number().nullable(),
@@ -1834,35 +1858,35 @@ function GenrePlaylistTracks({
1834
1858
  started.catch((error) => {
1835
1859
  console.error("Failed to play genre playlist:", error);
1836
1860
  if (error instanceof import_genre_tree16.TracksFailureAlreadyReportedError) return;
1837
- showPopup(/* @__PURE__ */ (0, import_jsx_runtime33.jsx)(InternalErrorPopup, { errorCode: error instanceof import_transport7.AppError ? error.code : import_transport7.ErrorCode.CLIENT_UNKNOWN }));
1861
+ showPopup(/* @__PURE__ */ (0, import_jsx_runtime34.jsx)(InternalErrorPopup, { errorCode: error instanceof import_transport7.AppError ? error.code : import_transport7.ErrorCode.CLIENT_UNKNOWN }));
1838
1862
  });
1839
1863
  };
1840
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ui11.PanelSection, { title: `${(0, import_track_noun2.capitalize)(trackNoun.plural)}${total !== void 0 ? ` (${total})` : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { children: [
1864
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui11.PanelSection, { title: `${(0, import_track_noun2.capitalize)(trackNoun.plural)}${total !== void 0 ? ` (${total})` : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { children: [
1841
1865
  renderTracksToolbar?.(),
1842
- scope === "me" && !genrePlaylist ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { children: "\u2014" }) : isPending ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ui11.Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("p", { role: "alert", children: [
1866
+ scope === "me" && !genrePlaylist ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("p", { children: "\u2014" }) : isPending ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui11.Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("p", { role: "alert", children: [
1843
1867
  "Couldn't load ",
1844
1868
  trackNoun.plural,
1845
1869
  ".",
1846
1870
  " ",
1847
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
1848
- ] }) : tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { children: "\u2014" }) : /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("ul", { className: "mt-1 max-h-64 overflow-y-auto text-body [container-type:inline-size]", "aria-label": `${genreName} ${trackNoun.plural}`, children: [
1871
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
1872
+ ] }) : tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("p", { children: "\u2014" }) : /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("ul", { className: "mt-1 max-h-64 overflow-y-auto text-body [container-type:inline-size]", "aria-label": `${genreName} ${trackNoun.plural}`, children: [
1849
1873
  tracks.map((track, index) => {
1850
1874
  const label = track.artists?.length ? `${track.title} \u2014 ${track.artists.map((artist2) => artist2.name).join(", ")}` : track.title;
1851
1875
  const isCurrent = playerTrackObject?.track.id === track.uuid;
1852
1876
  const isPlaying = isCurrent && playState === import_player6.PlayStates.PLAYING;
1853
- const text = /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("span", { className: TEXT_CELL, children: [
1854
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: TITLE_CELL, children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree15.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
1855
- track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: SEPARATOR, children: " \u2014 " }) : null,
1856
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_genre_tree17.ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
1857
- track.album && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: ALBUM_CELL, children: track.album.name })
1877
+ const text = /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: TEXT_CELL, children: [
1878
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: TITLE_CELL, children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree15.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
1879
+ track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: SEPARATOR, children: " \u2014 " }) : null,
1880
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_genre_tree17.ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
1881
+ track.album && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: ALBUM_CELL, children: track.album.name })
1858
1882
  ] });
1859
1883
  const leading = renderSongLeading?.(track, index);
1860
1884
  const actions = renderSongActions?.(track);
1861
1885
  const isUnplayable = (0, import_genre_tree15.isSong)(track) && (0, import_genre_tree15.songUnplayableReason)(track) !== null;
1862
1886
  if (isUnplayable) {
1863
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("li", { className: "flex min-h-8 items-center gap-1 px-1 opacity-50", children: [
1887
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("li", { className: "flex min-h-8 items-center gap-1 px-1 opacity-50", children: [
1864
1888
  leading,
1865
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
1889
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
1866
1890
  "button",
1867
1891
  {
1868
1892
  type: "button",
@@ -1870,27 +1894,27 @@ function GenrePlaylistTracks({
1870
1894
  "aria-disabled": "true",
1871
1895
  "aria-label": label,
1872
1896
  className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full opacity-0 cursor-not-allowed",
1873
- children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_fa4.FaPlay, { size: 10 })
1897
+ children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_fa4.FaPlay, { size: 10 })
1874
1898
  }
1875
1899
  ),
1876
1900
  text,
1877
1901
  actions
1878
1902
  ] }, track.uuid);
1879
1903
  }
1880
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
1904
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
1881
1905
  "li",
1882
1906
  {
1883
1907
  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)]" : ""}`,
1884
1908
  children: [
1885
1909
  leading,
1886
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
1910
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
1887
1911
  "button",
1888
1912
  {
1889
1913
  type: "button",
1890
1914
  "aria-label": `${isPlaying ? "Pause" : "Play"} ${label}`,
1891
1915
  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 !== import_player6.PlayStates.STOPPED ? "opacity-100" : "opacity-0 [@media(pointer:coarse)]:opacity-100"}`,
1892
1916
  onClick: () => isCurrent ? handlePlayPauseAction() : play(track.uuid),
1893
- children: isCurrent && playState === import_player6.PlayStates.LOADING ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_ui11.RingLoader, { size: 10 }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_fa4.FaPause, { size: 10 }) : /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_fa4.FaPlay, { size: 10 })
1917
+ children: isCurrent && playState === import_player6.PlayStates.LOADING ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui11.RingLoader, { size: 10 }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_fa4.FaPause, { size: 10 }) : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_fa4.FaPlay, { size: 10 })
1894
1918
  }
1895
1919
  ),
1896
1920
  text,
@@ -1900,7 +1924,7 @@ function GenrePlaylistTracks({
1900
1924
  track.uuid
1901
1925
  );
1902
1926
  }),
1903
- hasNextPage && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("li", { ref: setSentinel, "aria-hidden": "true", className: "h-4" })
1927
+ hasNextPage && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("li", { ref: setSentinel, "aria-hidden": "true", className: "h-4" })
1904
1928
  ] })
1905
1929
  ] }) });
1906
1930
  }
@@ -1908,7 +1932,7 @@ function GenrePlaylistTracks({
1908
1932
  // src/genre-tree/GenreDetailExtras.tsx
1909
1933
  var import_track_noun3 = require("@behindthemusictree/app-kit/track-noun");
1910
1934
  var import_si2 = require("react-icons/si");
1911
- var import_jsx_runtime34 = require("react/jsx-runtime");
1935
+ var import_jsx_runtime35 = require("react/jsx-runtime");
1912
1936
  function GenreDetailExtras({
1913
1937
  genreUuid,
1914
1938
  genrePlaylist,
@@ -1934,17 +1958,18 @@ function GenreDetailExtras({
1934
1958
  criteriaOverviewSchema
1935
1959
  );
1936
1960
  if (isPending) {
1937
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
1938
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui12.PanelSection, { title: "Summary", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { "aria-busy": "true", "data-testid": "genre-detail-extras-skeleton", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui12.Skeleton, { className: "h-4 w-3/4" }) }) }),
1961
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
1962
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui12.PanelSection, { title: "Summary", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { "aria-busy": "true", "data-testid": "genre-detail-extras-skeleton", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui12.Skeleton, { className: "h-4 w-3/4" }) }) }),
1939
1963
  family
1940
1964
  ] });
1941
1965
  }
1942
- if (!overview) return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_jsx_runtime34.Fragment, { children: family });
1943
- const { summary, summarySourceUrl, essentialSongs } = overview;
1966
+ if (!overview) return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_jsx_runtime35.Fragment, { children: family });
1967
+ const { summary, summarySourceUrl, essentialSongs, image } = overview;
1944
1968
  const wikidataUrl = wikidataId ? `https://www.wikidata.org/wiki/${encodeURIComponent(wikidataId)}` : null;
1945
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
1946
- (summarySourceUrl || wikidataUrl) && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui12.PanelSection, { title: "Links", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex flex-wrap gap-x-3 gap-y-1 text-caption text-[var(--color-text)]", children: [
1947
- summarySourceUrl && /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
1969
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
1970
+ image && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(CommonsPhoto, { image, alt: overview.name, creditLabel: "Image" }),
1971
+ (summarySourceUrl || wikidataUrl) && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui12.PanelSection, { title: "Links", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "flex flex-wrap gap-x-3 gap-y-1 text-caption text-[var(--color-text)]", children: [
1972
+ summarySourceUrl && /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
1948
1973
  "a",
1949
1974
  {
1950
1975
  href: summarySourceUrl,
@@ -1952,12 +1977,12 @@ function GenreDetailExtras({
1952
1977
  rel: "noopener noreferrer",
1953
1978
  className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
1954
1979
  children: [
1955
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_si2.SiWikipedia, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1980
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_si2.SiWikipedia, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1956
1981
  "Wikipedia"
1957
1982
  ]
1958
1983
  }
1959
1984
  ),
1960
- wikidataUrl && /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
1985
+ wikidataUrl && /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
1961
1986
  "a",
1962
1987
  {
1963
1988
  href: wikidataUrl,
@@ -1965,17 +1990,17 @@ function GenreDetailExtras({
1965
1990
  rel: "noopener noreferrer",
1966
1991
  className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
1967
1992
  children: [
1968
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_si2.SiWikidata, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1993
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_si2.SiWikidata, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
1969
1994
  "Wikidata"
1970
1995
  ]
1971
1996
  }
1972
1997
  )
1973
1998
  ] }) }),
1974
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui12.PanelSection, { title: "Summary", children: renderSummary ? renderSummary(overview) : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(ClampedText, { className: "text-body text-[var(--color-text)]", children: summary ?? "\u2014" }, genreUuid) }),
1999
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui12.PanelSection, { title: "Summary", children: renderSummary ? renderSummary(overview) : /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ClampedText, { className: "text-body text-[var(--color-text)]", children: summary ?? "\u2014" }, genreUuid) }),
1975
2000
  family,
1976
2001
  renderGenreDetailExtras?.(overview),
1977
- (renderEssentialTracks || essentialSongs) && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui12.PanelSection, { title: `Essential ${trackNoun.plural}`, children: renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && essentialSongs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("ul", { className: "text-body text-[var(--color-text)]", children: essentialSongs.map((track) => /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("li", { className: "py-1", children: track.title }, track.uuid)) }) : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("p", { className: "text-body text-[var(--color-text)]", children: "\u2014" }) }),
1978
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2002
+ (renderEssentialTracks || essentialSongs) && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui12.PanelSection, { title: `Essential ${trackNoun.plural}`, children: renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && essentialSongs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("ul", { className: "text-body text-[var(--color-text)]", children: essentialSongs.map((track) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("li", { className: "py-1", children: track.title }, track.uuid)) }) : /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "text-body text-[var(--color-text)]", children: "\u2014" }) }),
2003
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
1979
2004
  GenrePlaylistTracks,
1980
2005
  {
1981
2006
  genreUuid,
@@ -1993,10 +2018,10 @@ function GenreDetailExtras({
1993
2018
  }
1994
2019
 
1995
2020
  // src/genre-tree/GenreInfoPanelBody.tsx
1996
- var import_jsx_runtime35 = require("react/jsx-runtime");
2021
+ var import_jsx_runtime36 = require("react/jsx-runtime");
1997
2022
  var import_react14 = require("react");
1998
2023
  function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
1999
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(GenreActionBand, { ...bandProps, playActions: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(GenrePlayActions, { genrePlaylist, playback }) });
2024
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(GenreActionBand, { ...bandProps, playActions: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(GenrePlayActions, { genrePlaylist, playback }) });
2000
2025
  }
2001
2026
  function GenreInfoPanelBody({
2002
2027
  toolbar,
@@ -2013,9 +2038,9 @@ function GenreInfoPanelBody({
2013
2038
  }) {
2014
2039
  const summary = scope === "reference" ? import_genre_tree19.GenreCatalogueCountsSchema.parse(genrePlaylist ?? catalogue) : void 0;
2015
2040
  const selection = summary ? (0, import_genre_tree19.defaultGenreMusicSelection)(summary) : void 0;
2016
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
2041
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_jsx_runtime36.Fragment, { children: [
2017
2042
  toolbar,
2018
- genrePlaylist ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(GenreNodeActionBand, { ...band, genrePlaylist, playback }) : /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(GenreActionBand, { ...band }),
2043
+ genrePlaylist ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(GenreNodeActionBand, { ...band, genrePlaylist, playback }) : /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(GenreActionBand, { ...band }),
2019
2044
  /* @__PURE__ */ (0, import_react14.createElement)(
2020
2045
  GenreDetailExtras,
2021
2046
  {
@@ -2035,7 +2060,7 @@ function GenreInfoPanelBody({
2035
2060
  }
2036
2061
 
2037
2062
  // src/genre-tree/GenreDetailPanel.tsx
2038
- var import_jsx_runtime36 = require("react/jsx-runtime");
2063
+ var import_jsx_runtime37 = require("react/jsx-runtime");
2039
2064
  var RELATIVE_ITEM_COUNT = 1;
2040
2065
  function toNode(genre, parentId) {
2041
2066
  return {
@@ -2060,7 +2085,8 @@ function useGenreDetailContent({
2060
2085
  expanded,
2061
2086
  treeStyles
2062
2087
  }) {
2063
- const { data: detail, isPending, isError } = (0, import_genre_tree20.useFetchGenreDetail)(genreUuid, scope, getBackendBaseUrl);
2088
+ const result = (0, import_genre_tree20.useFetchGenreDetail)(genreUuid, scope, getBackendBaseUrl);
2089
+ const detail = result.status === "success" ? result.genre : void 0;
2064
2090
  const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists: [], artist: extras?.artist });
2065
2091
  const playback = hostPlayback ?? defaultPlayback;
2066
2092
  const view = (0, import_react15.useMemo)(() => {
@@ -2089,22 +2115,27 @@ function useGenreDetailContent({
2089
2115
  )
2090
2116
  };
2091
2117
  }, [detail, scope, treeStyles]);
2092
- if (isError) return {
2118
+ if (result.status === "not-found") return {
2119
+ name: null,
2120
+ title: "Genre not found",
2121
+ content: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "This genre does not exist." })
2122
+ };
2123
+ if (result.status === "error") return {
2093
2124
  name: null,
2094
2125
  title: "Genre unavailable",
2095
- content: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { role: "alert", className: "text-body text-[var(--color-text)]", children: "This genre could not be loaded." })
2126
+ content: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { role: "alert", className: "text-body text-[var(--color-text)]", children: "This genre could not be loaded." })
2096
2127
  };
2097
- if (isPending || !detail || !view) return {
2128
+ if (!detail || !view) return {
2098
2129
  name: null,
2099
2130
  title: "Loading genre\u2026",
2100
- content: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex flex-col gap-3", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
2101
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.Skeleton, { className: "h-6 w-2/3" }),
2102
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.Skeleton, { className: "h-4 w-full" }),
2103
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.Skeleton, { className: "h-4 w-3/4" })
2131
+ content: /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "flex flex-col gap-3", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
2132
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui13.Skeleton, { className: "h-6 w-2/3" }),
2133
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui13.Skeleton, { className: "h-4 w-full" }),
2134
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui13.Skeleton, { className: "h-4 w-3/4" })
2104
2135
  ] })
2105
2136
  };
2106
2137
  const selectGenre = (nodeId) => onSelectGenre(import_genre_tree20.GenreUuidSchema.parse(nodeId));
2107
- const family = /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2138
+ const family = /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
2108
2139
  import_genre_tree_view4.GenreNodeFamily,
2109
2140
  {
2110
2141
  node: view.node,
@@ -2115,17 +2146,17 @@ function useGenreDetailContent({
2115
2146
  onSelectNode: selectGenre
2116
2147
  }
2117
2148
  );
2118
- const ancestors = /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_genre_tree_view4.GenreNodeAncestors, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
2149
+ const ancestors = /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_genre_tree_view4.GenreNodeAncestors, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
2119
2150
  return {
2120
2151
  name: detail.name,
2121
2152
  title: detail.name,
2122
2153
  headerAccent: { background: view.fill, color: view.textColor },
2123
2154
  aside: expanded ? ancestors : void 0,
2124
- content: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_jsx_runtime36.Fragment, { children: [
2125
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2155
+ content: /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(import_jsx_runtime37.Fragment, { children: [
2156
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
2126
2157
  GenreInfoPanelBody,
2127
2158
  {
2128
- family: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_jsx_runtime36.Fragment, { children: [
2159
+ family: /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(import_jsx_runtime37.Fragment, { children: [
2129
2160
  family,
2130
2161
  !expanded && ancestors
2131
2162
  ] }),
@@ -2169,7 +2200,7 @@ function GenreDetailPanel({
2169
2200
  expanded,
2170
2201
  band: { onZoomIn, shareUrl, shareTarget: shareUrl ? shareTarget : void 0 }
2171
2202
  });
2172
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2203
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
2173
2204
  import_ui13.DetailPanel,
2174
2205
  {
2175
2206
  side: "left",
@@ -2182,7 +2213,7 @@ function GenreDetailPanel({
2182
2213
  edgeTab,
2183
2214
  aside: panel.aside,
2184
2215
  titleInSearch,
2185
- headerActions: onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.IconButton, { "aria-label": expanded ? "Shrink panel" : "Extend panel", onClick: () => onExpandedChange(!expanded), children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_lucide_react13.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_lucide_react13.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" }) }) : void 0,
2216
+ headerActions: onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui13.IconButton, { "aria-label": expanded ? "Shrink panel" : "Extend panel", onClick: () => onExpandedChange(!expanded), children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react13.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react13.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" }) }) : void 0,
2186
2217
  children: panel.content
2187
2218
  }
2188
2219
  );
@@ -2191,7 +2222,7 @@ function GenreDetailPanel({
2191
2222
  // src/genre-tree/ChangeParentPopup.tsx
2192
2223
  var import_react16 = require("react");
2193
2224
  var import_genre_tree21 = require("@behindthemusictree/app-kit/genre-tree");
2194
- var import_jsx_runtime37 = require("react/jsx-runtime");
2225
+ var import_jsx_runtime38 = require("react/jsx-runtime");
2195
2226
  function ChangeParentPopup({
2196
2227
  genrePlaylist,
2197
2228
  genrePlaylists,
@@ -2226,12 +2257,12 @@ function ChangeParentPopup({
2226
2257
  window.addEventListener("keydown", handleKeyDown);
2227
2258
  return () => window.removeEventListener("keydown", handleKeyDown);
2228
2259
  }, [onClose]);
2229
- return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
2230
- /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("p", { className: "mb-3 text-body text-[var(--color-text-muted)]", children: [
2260
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
2261
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("p", { className: "mb-3 text-body text-[var(--color-text-muted)]", children: [
2231
2262
  "Current parent: ",
2232
2263
  genrePlaylist.parent?.name ?? "none"
2233
2264
  ] }),
2234
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
2265
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2235
2266
  GenreSearch,
2236
2267
  {
2237
2268
  genrePlaylists: candidates,
@@ -2239,7 +2270,7 @@ function ChangeParentPopup({
2239
2270
  placeholder: "Search a new parent\u2026"
2240
2271
  }
2241
2272
  ) }),
2242
- formErrors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { role: "alert", className: "mt-3 text-body text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
2273
+ formErrors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("p", { role: "alert", className: "mt-3 text-body text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
2243
2274
  ] });
2244
2275
  }
2245
2276
 
@@ -2252,7 +2283,7 @@ var import_lucide_react14 = require("lucide-react");
2252
2283
  var import_ui14 = require("@behindthemusictree/ui");
2253
2284
  var import_genre_tree22 = require("@behindthemusictree/app-kit/genre-tree");
2254
2285
  var import_player7 = require("@behindthemusictree/app-kit/player");
2255
- var import_jsx_runtime38 = require("react/jsx-runtime");
2286
+ var import_jsx_runtime39 = require("react/jsx-runtime");
2256
2287
  function ArtistActionBand({ artist, share }) {
2257
2288
  const { trackList, playNewTrackListFromArtist } = (0, import_genre_tree22.useTrackList)();
2258
2289
  const { isPlaying, setIsPlaying } = (0, import_player7.usePlayer)();
@@ -2268,11 +2299,11 @@ function ArtistActionBand({ artist, share }) {
2268
2299
  console.error("Failed to load artist tracks:", error);
2269
2300
  });
2270
2301
  };
2271
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_ui14.PanelBleed, { children: /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_ui14.ActionBand, { children: [
2272
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2302
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_ui14.PanelBleed, { children: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(import_ui14.ActionBand, { children: [
2303
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2273
2304
  import_ui14.ActionButton,
2274
2305
  {
2275
- icon: playing ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_lucide_react14.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_lucide_react14.Play, { "aria-hidden": "true", className: "w-5 h-5" }),
2306
+ icon: playing ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_lucide_react14.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_lucide_react14.Play, { "aria-hidden": "true", className: "w-5 h-5" }),
2276
2307
  label: playing ? "Pause" : "Play",
2277
2308
  caption: String(artist.playableTracksCount),
2278
2309
  disabled: artist.playableTracksCount === 0,
@@ -2291,7 +2322,7 @@ var import_ui16 = require("@behindthemusictree/ui");
2291
2322
  var import_lucide_react15 = require("lucide-react");
2292
2323
  var import_si3 = require("react-icons/si");
2293
2324
  var import_ui15 = require("@behindthemusictree/ui");
2294
- var import_jsx_runtime39 = require("react/jsx-runtime");
2325
+ var import_jsx_runtime40 = require("react/jsx-runtime");
2295
2326
  var LINK_KINDS = {
2296
2327
  official: { label: "Official website", Icon: import_lucide_react15.Globe },
2297
2328
  wikipedia: { label: "Wikipedia", Icon: import_si3.SiWikipedia },
@@ -2328,7 +2359,7 @@ function artistLinks(artist) {
2328
2359
  return links;
2329
2360
  }
2330
2361
  function ArtistLinks({ links }) {
2331
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_ui15.Chip, { href: url, icon: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
2362
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui15.Chip, { href: url, icon: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
2332
2363
  }
2333
2364
 
2334
2365
  // src/genre-tree/artist/formatYears.ts
@@ -2339,7 +2370,7 @@ function formatYears(beginYear, endYear, ended) {
2339
2370
  }
2340
2371
 
2341
2372
  // src/genre-tree/artist/ArtistHeader.tsx
2342
- var import_jsx_runtime40 = require("react/jsx-runtime");
2373
+ var import_jsx_runtime41 = require("react/jsx-runtime");
2343
2374
  var countryNames = new Intl.DisplayNames(["en"], { type: "region" });
2344
2375
  function ArtistHeader({ artist }) {
2345
2376
  const fields = [];
@@ -2349,35 +2380,23 @@ function ArtistHeader({ artist }) {
2349
2380
  const years = formatYears(artist.beginYear, artist.endYear, artist.ended);
2350
2381
  if (years) fields.push({ label: "Active", value: years });
2351
2382
  const links = artistLinks(artist);
2352
- if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(ArtistLinks, { links }) });
2383
+ if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(ArtistLinks, { links }) });
2353
2384
  if (fields.length === 0) return null;
2354
- return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_ui16.FieldList, { items: fields });
2385
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui16.FieldList, { items: fields });
2355
2386
  }
2356
2387
 
2357
2388
  // src/genre-tree/artist/ArtistPhoto.tsx
2358
- var import_jsx_runtime41 = require("react/jsx-runtime");
2359
- function ExternalLink({ href, children }) {
2360
- return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
2361
- }
2389
+ var import_jsx_runtime42 = require("react/jsx-runtime");
2362
2390
  function ArtistPhoto({ image, name }) {
2363
- return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("figure", { className: "flex flex-col gap-1.5", children: [
2364
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
2365
- /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("figcaption", { className: "text-caption text-[var(--color-text-muted)]", children: [
2366
- "Photo: ",
2367
- /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
2368
- ",",
2369
- " ",
2370
- image.licenseUrl ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
2371
- ] })
2372
- ] });
2391
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(CommonsPhoto, { image, alt: name });
2373
2392
  }
2374
2393
 
2375
2394
  // src/genre-tree/artist/ArtistMembers.tsx
2376
2395
  var import_ui17 = require("@behindthemusictree/ui");
2377
- var import_jsx_runtime42 = require("react/jsx-runtime");
2396
+ var import_jsx_runtime43 = require("react/jsx-runtime");
2378
2397
  function MembershipList({ title, memberships, renderArtistLink }) {
2379
2398
  if (memberships.length === 0) return null;
2380
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui17.PanelGroup, { title, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("ul", { children: memberships.map((membership, index) => {
2399
+ return /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_ui17.PanelGroup, { title, children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("ul", { children: memberships.map((membership, index) => {
2381
2400
  const details = [
2382
2401
  formatYears(membership.beginYear, membership.endYear, membership.ended),
2383
2402
  ...membership.roles
@@ -2385,9 +2404,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
2385
2404
  const detailsText = details.filter(Boolean).join(" \xB7 ");
2386
2405
  return (
2387
2406
  // A member can have several periods in the same group.
2388
- /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
2389
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
2390
- detailsText && /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
2407
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
2408
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
2409
+ detailsText && /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
2391
2410
  " \u2014 ",
2392
2411
  detailsText
2393
2412
  ] })
@@ -2398,9 +2417,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
2398
2417
  function ArtistMembers({ title, memberships, renderArtistLink }) {
2399
2418
  if (memberships.length === 0) return null;
2400
2419
  const isCurrent = (membership) => !membership.ended && membership.endYear === null;
2401
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_ui17.PanelSection, { title, children: [
2402
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
2403
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
2420
+ return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(import_ui17.PanelSection, { title, children: [
2421
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
2422
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
2404
2423
  MembershipList,
2405
2424
  {
2406
2425
  title: "Past",
@@ -2413,7 +2432,7 @@ function ArtistMembers({ title, memberships, renderArtistLink }) {
2413
2432
 
2414
2433
  // src/genre-tree/artist/ArtistDiscography.tsx
2415
2434
  var import_ui18 = require("@behindthemusictree/ui");
2416
- var import_jsx_runtime43 = require("react/jsx-runtime");
2435
+ var import_jsx_runtime44 = require("react/jsx-runtime");
2417
2436
  var GROUPS = ["Albums", "EPs", "Singles", "Live", "Compilations", "Other"];
2418
2437
  var PRIMARY_GROUPS = { album: "Albums", ep: "EPs", single: "Singles" };
2419
2438
  function discographyGroup({ primaryType, secondaryTypes }) {
@@ -2433,12 +2452,12 @@ function byYear(a, b) {
2433
2452
  function ArtistDiscography({ albums }) {
2434
2453
  if (albums.length === 0) return null;
2435
2454
  const sorted = [...albums].sort(byYear);
2436
- return /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_ui18.PanelSection, { title: "Discography", children: GROUPS.map((group) => {
2455
+ return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_ui18.PanelSection, { title: "Discography", children: GROUPS.map((group) => {
2437
2456
  const groupAlbums = sorted.filter((album) => discographyGroup(album) === group);
2438
2457
  if (groupAlbums.length === 0) return null;
2439
- return /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_ui18.PanelGroup, { title: group, children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
2440
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
2441
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
2458
+ return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_ui18.PanelGroup, { title: group, children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
2459
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
2460
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
2442
2461
  ] }, album.uuid)) }) }, group);
2443
2462
  }) });
2444
2463
  }
@@ -2459,7 +2478,7 @@ var import_genre_tree23 = require("@behindthemusictree/app-kit/genre-tree");
2459
2478
  var import_fa5 = require("react-icons/fa");
2460
2479
  var import_player8 = require("@behindthemusictree/app-kit/player");
2461
2480
  var import_player9 = require("@behindthemusictree/app-kit/player");
2462
- var import_jsx_runtime44 = require("react/jsx-runtime");
2481
+ var import_jsx_runtime45 = require("react/jsx-runtime");
2463
2482
  function TrackPositionPlayPause({
2464
2483
  position,
2465
2484
  uuid,
@@ -2473,14 +2492,14 @@ function TrackPositionPlayPause({
2473
2492
  const isPlaying = isThisTrack && isConfirmedPlayable && playState === import_player9.PlayStates.PLAYING;
2474
2493
  const isPaused = isThisTrack && isConfirmedPlayable && playState === import_player9.PlayStates.PAUSED;
2475
2494
  const isCurrentTrackActive = isPlaying || isPaused;
2476
- return /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(
2495
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(
2477
2496
  "div",
2478
2497
  {
2479
2498
  className: "track-position-play-pause relative flex items-center justify-center gap-2 text-lg w-16 cursor-pointer",
2480
2499
  style: { minWidth: "4rem", maxWidth: "4rem" },
2481
2500
  onClick: handlePlayPauseClick,
2482
2501
  children: [
2483
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(
2502
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(
2484
2503
  "span",
2485
2504
  {
2486
2505
  className: "track-position-play-pause__bars",
@@ -2488,18 +2507,18 @@ function TrackPositionPlayPause({
2488
2507
  role: "img",
2489
2508
  "aria-label": isPlaying ? "Playing" : "Paused",
2490
2509
  children: [
2491
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { className: "track-position-play-pause__bar" }),
2492
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { className: "track-position-play-pause__bar" }),
2493
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { className: "track-position-play-pause__bar" })
2510
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "track-position-play-pause__bar" }),
2511
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "track-position-play-pause__bar" }),
2512
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "track-position-play-pause__bar" })
2494
2513
  ]
2495
2514
  }
2496
2515
  ) : position }),
2497
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
2516
+ /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
2498
2517
  "div",
2499
2518
  {
2500
2519
  className: `hidden group-hover:flex items-center justify-center ${isCurrentTrackActive ? "" : "absolute inset-0"}`,
2501
2520
  "aria-hidden": "true",
2502
- children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_fa5.FaPause, {}) : /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_fa5.FaPlay, {})
2521
+ children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_fa5.FaPause, {}) : /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_fa5.FaPlay, {})
2503
2522
  }
2504
2523
  )
2505
2524
  ]
@@ -2510,7 +2529,7 @@ function TrackPositionPlayPause({
2510
2529
  // src/genre-tree/track-list-sidebar/TrackItem.tsx
2511
2530
  var import_genre_tree24 = require("@behindthemusictree/app-kit/genre-tree");
2512
2531
  var import_genre_tree25 = require("@behindthemusictree/app-kit/genre-tree");
2513
- var import_jsx_runtime45 = require("react/jsx-runtime");
2532
+ var import_jsx_runtime46 = require("react/jsx-runtime");
2514
2533
  function TrackItem({
2515
2534
  track,
2516
2535
  position,
@@ -2534,12 +2553,12 @@ function TrackItem({
2534
2553
  loadTrackForPlayer(track.uuid);
2535
2554
  }
2536
2555
  };
2537
- return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(
2556
+ return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
2538
2557
  "div",
2539
2558
  {
2540
2559
  className: `track-item flex h-14 text-[var(--color-text)] ${isUnplayable ? "opacity-50" : "hover:bg-[var(--color-surface-muted)] group"}`,
2541
2560
  children: [
2542
- isUnplayable ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
2561
+ isUnplayable ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
2543
2562
  "div",
2544
2563
  {
2545
2564
  className: "flex items-center justify-center text-lg w-16",
@@ -2547,7 +2566,7 @@ function TrackItem({
2547
2566
  "aria-disabled": "true",
2548
2567
  children: position
2549
2568
  }
2550
- ) : /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
2569
+ ) : /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
2551
2570
  TrackPositionPlayPause,
2552
2571
  {
2553
2572
  position,
@@ -2556,12 +2575,12 @@ function TrackItem({
2556
2575
  isSelected: selectedTrack?.uuid === track.uuid
2557
2576
  }
2558
2577
  ),
2559
- /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
2560
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree24.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
2561
- track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_genre_tree25.ArtistNames, { artists: track.artists, renderArtistLink }) }) : ""
2578
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
2579
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree24.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
2580
+ track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_genre_tree25.ArtistNames, { artists: track.artists, renderArtistLink }) }) : ""
2562
2581
  ] }),
2563
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: "genre-name-container flex items-center justify-start w-[21.6%] min-w-0 shrink-0", children: track.genre ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-caption font-bold", children: track.genre.name }) : "" }),
2564
- renderDuration && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
2582
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "genre-name-container flex items-center justify-start w-[21.6%] min-w-0 shrink-0", children: track.genre ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-caption font-bold", children: track.genre.name }) : "" }),
2583
+ renderDuration && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
2565
2584
  "div",
2566
2585
  {
2567
2586
  className: "duration flex text-base w-16 items-center justify-center",
@@ -2569,14 +2588,14 @@ function TrackItem({
2569
2588
  children: renderDuration(track)
2570
2589
  }
2571
2590
  ),
2572
- renderActions && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
2591
+ renderActions && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
2573
2592
  ]
2574
2593
  }
2575
2594
  );
2576
2595
  }
2577
2596
 
2578
2597
  // src/genre-tree/artist/ArtistSongs.tsx
2579
- var import_jsx_runtime46 = require("react/jsx-runtime");
2598
+ var import_jsx_runtime47 = require("react/jsx-runtime");
2580
2599
  var SongPageSchema2 = (0, import_transport8.PaginatedResponseSchema)(import_genre_tree26.SongDetailedSchema);
2581
2600
  var SORTS = [
2582
2601
  { ordering: "title", label: "Title" },
@@ -2609,8 +2628,8 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
2609
2628
  event.stopPropagation();
2610
2629
  void play(song.uuid);
2611
2630
  };
2612
- return /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_ui19.PanelSection, { title: "Songs", children: isPending ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_ui19.Skeleton, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(import_jsx_runtime46.Fragment, { children: [
2613
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "flex flex-wrap items-center justify-end gap-2 mb-2", children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-caption", children: SORTS.map((sort) => /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
2631
+ return /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_ui19.PanelSection, { title: "Songs", children: isPending ? /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_ui19.Skeleton, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(import_jsx_runtime47.Fragment, { children: [
2632
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("div", { className: "flex flex-wrap items-center justify-end gap-2 mb-2", children: /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-caption", children: SORTS.map((sort) => /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
2614
2633
  "button",
2615
2634
  {
2616
2635
  type: "button",
@@ -2624,7 +2643,7 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
2624
2643
  },
2625
2644
  sort.ordering
2626
2645
  )) }) }),
2627
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
2646
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
2628
2647
  TrackItem,
2629
2648
  {
2630
2649
  track: song,
@@ -2633,12 +2652,12 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
2633
2652
  renderSongLink
2634
2653
  }
2635
2654
  ) }, song.uuid)) }),
2636
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_ui19.Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
2655
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_ui19.Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
2637
2656
  ] }) });
2638
2657
  }
2639
2658
 
2640
2659
  // src/genre-tree/artist/useArtistPanel.tsx
2641
- var import_jsx_runtime47 = require("react/jsx-runtime");
2660
+ var import_jsx_runtime48 = require("react/jsx-runtime");
2642
2661
  function useArtistPanel({
2643
2662
  uuid,
2644
2663
  scope,
@@ -2659,26 +2678,26 @@ function useArtistPanel({
2659
2678
  return {
2660
2679
  ...panel,
2661
2680
  title: "Artist",
2662
- content: /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5", children: [
2663
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_ui20.Skeleton, { className: "h-3 w-16" }),
2664
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_ui20.Skeleton, { className: "h-4 w-32" }),
2665
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_ui20.Skeleton, { className: "h-3 w-16" }),
2666
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_ui20.Skeleton, { className: "h-4 w-24" }),
2667
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_ui20.Skeleton, { className: "h-16 mt-2.5" }),
2668
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_ui20.Skeleton, { className: "h-16" })
2681
+ content: /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5", children: [
2682
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_ui20.Skeleton, { className: "h-3 w-16" }),
2683
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_ui20.Skeleton, { className: "h-4 w-32" }),
2684
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_ui20.Skeleton, { className: "h-3 w-16" }),
2685
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_ui20.Skeleton, { className: "h-4 w-24" }),
2686
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_ui20.Skeleton, { className: "h-16 mt-2.5" }),
2687
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_ui20.Skeleton, { className: "h-16" })
2669
2688
  ] })
2670
2689
  };
2671
2690
  case "not-found":
2672
2691
  return {
2673
2692
  ...panel,
2674
2693
  title: "Artist not found",
2675
- content: /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "This artist does not exist." })
2694
+ content: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "This artist does not exist." })
2676
2695
  };
2677
2696
  case "error":
2678
2697
  return {
2679
2698
  ...panel,
2680
2699
  title: "Artist",
2681
- content: /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("p", { className: "text-red-600", children: "Could not load this artist." })
2700
+ content: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("p", { className: "text-red-600", children: "Could not load this artist." })
2682
2701
  };
2683
2702
  case "success": {
2684
2703
  const { artist } = result;
@@ -2687,19 +2706,19 @@ function useArtistPanel({
2687
2706
  return {
2688
2707
  ...panel,
2689
2708
  title: artist.name,
2690
- content: /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(import_jsx_runtime47.Fragment, { children: [
2691
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
2709
+ content: /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(import_jsx_runtime48.Fragment, { children: [
2710
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
2692
2711
  ArtistActionBand,
2693
2712
  {
2694
2713
  artist,
2695
- share: shareTarget && shareUrl ? /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl }) : void 0
2714
+ share: shareTarget && shareUrl ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl }) : void 0
2696
2715
  }
2697
2716
  ),
2698
- artist.image && /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(ArtistPhoto, { image: artist.image, name: artist.name }),
2699
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(ArtistHeader, { artist }),
2700
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
2701
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
2702
- artist.tracksCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
2717
+ artist.image && /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(ArtistPhoto, { image: artist.image, name: artist.name }),
2718
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(ArtistHeader, { artist }),
2719
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
2720
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
2721
+ artist.tracksCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
2703
2722
  ArtistSongs,
2704
2723
  {
2705
2724
  artist: { uuid: artist.uuid, name: artist.name },
@@ -2709,7 +2728,7 @@ function useArtistPanel({
2709
2728
  }
2710
2729
  )
2711
2730
  ] }),
2712
- extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(ArtistDiscography, { albums: artist.albums }) : void 0
2731
+ extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(ArtistDiscography, { albums: artist.albums }) : void 0
2713
2732
  };
2714
2733
  }
2715
2734
  }
@@ -2725,7 +2744,7 @@ var import_lucide_react16 = require("lucide-react");
2725
2744
  var import_ui21 = require("@behindthemusictree/ui");
2726
2745
  var import_player12 = require("@behindthemusictree/app-kit/player");
2727
2746
  var import_genre_tree28 = require("@behindthemusictree/app-kit/genre-tree");
2728
- var import_jsx_runtime48 = require("react/jsx-runtime");
2747
+ var import_jsx_runtime49 = require("react/jsx-runtime");
2729
2748
  var numberFormat = new Intl.NumberFormat("en");
2730
2749
  var dateFormat = new Intl.DateTimeFormat("en", { dateStyle: "medium" });
2731
2750
  var externalLink = "text-[var(--color-primary)] hover:underline focus-visible:underline";
@@ -2733,27 +2752,27 @@ function SongHistory({ uuid, getBackendBaseUrl }) {
2733
2752
  const [open, setOpen] = (0, import_react18.useState)(false);
2734
2753
  const history = (0, import_genre_tree28.useSongHistory)(uuid, getBackendBaseUrl, open);
2735
2754
  let body = null;
2736
- if (history.status === "loading") body = /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_ui21.Skeleton, { className: "h-10", "aria-busy": "true" });
2737
- else if (history.status === "error") body = /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("p", { className: "text-red-600", children: "Could not load the history." });
2755
+ if (history.status === "loading") body = /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_ui21.Skeleton, { className: "h-10", "aria-busy": "true" });
2756
+ else if (history.status === "error") body = /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("p", { className: "text-red-600", children: "Could not load the history." });
2738
2757
  else if (history.status === "success")
2739
- body = history.entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("li", { className: "py-1.5 text-body", children: [
2740
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "font-medium", children: entry.action }),
2741
- entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
2758
+ body = history.entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("li", { className: "py-1.5 text-body", children: [
2759
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: "font-medium", children: entry.action }),
2760
+ entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
2742
2761
  " ",
2743
2762
  "\u2014 ",
2744
2763
  entry.oldValue ?? "none",
2745
2764
  " \u2192 ",
2746
2765
  entry.newValue ?? "none"
2747
2766
  ] }) : null,
2748
- /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("span", { className: "block text-caption text-[var(--color-text-muted)]", children: [
2767
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("span", { className: "block text-caption text-[var(--color-text-muted)]", children: [
2749
2768
  entry.actorPseudo ?? "Anonymous",
2750
2769
  " \xB7 ",
2751
2770
  dateFormat.format(new Date(entry.createdOn))
2752
2771
  ] })
2753
2772
  ] }, entry.uuid)) });
2754
- return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_ui21.PanelBleed, { padded: true, className: "border-t border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("details", { onToggle: (event) => setOpen(event.currentTarget.open), className: "pb-2.5", children: [
2755
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("summary", { className: `mt-2.5 cursor-pointer ${import_ui21.PANEL_LABEL_CLASS}`, children: "History" }),
2756
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "mt-1.5", children: body })
2773
+ return /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_ui21.PanelBleed, { padded: true, className: "border-t border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("details", { onToggle: (event) => setOpen(event.currentTarget.open), className: "pb-2.5", children: [
2774
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("summary", { className: `mt-2.5 cursor-pointer ${import_ui21.PANEL_LABEL_CLASS}`, children: "History" }),
2775
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("div", { className: "mt-1.5", children: body })
2757
2776
  ] }) });
2758
2777
  }
2759
2778
  function SongPanelContent(props) {
@@ -2772,13 +2791,13 @@ function SongPanelContent(props) {
2772
2791
  const genre = song.genre;
2773
2792
  fields.push({
2774
2793
  label: "Genre",
2775
- value: onSelectGenre ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("button", { type: "button", onClick: () => onSelectGenre(genre), className: "hover:underline", children: genre.name }) : path ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("span", { children: [
2776
- index > 0 && /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { "aria-hidden": "true", children: "\u203A " }),
2777
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("button", { type: "button", onClick: () => props.onSelectGenrePlaylist?.(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
2794
+ value: onSelectGenre ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("button", { type: "button", onClick: () => onSelectGenre(genre), className: "hover:underline", children: genre.name }) : path ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("span", { children: [
2795
+ index > 0 && /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { "aria-hidden": "true", children: "\u203A " }),
2796
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("button", { type: "button", onClick: () => props.onSelectGenrePlaylist?.(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
2778
2797
  ] }, genrePlaylist.uuid)) }) : song.genre.name
2779
2798
  });
2780
2799
  } else {
2781
- fields.push({ label: "Genre", value: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
2800
+ fields.push({ label: "Genre", value: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
2782
2801
  }
2783
2802
  if (song.firstReleaseYear) fields.push({ label: "First released", value: song.firstReleaseYear });
2784
2803
  const viewCount = song.youtubeVideo?.viewCount;
@@ -2788,11 +2807,11 @@ function SongPanelContent(props) {
2788
2807
  const links = [];
2789
2808
  if (musicBrainzUrl)
2790
2809
  links.push(
2791
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
2810
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
2792
2811
  );
2793
2812
  if (song.youtubeVideo)
2794
2813
  links.push(
2795
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
2814
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
2796
2815
  "a",
2797
2816
  {
2798
2817
  href: `https://www.youtube.com/watch?v=${encodeURIComponent(song.youtubeVideo.id)}`,
@@ -2807,31 +2826,31 @@ function SongPanelContent(props) {
2807
2826
  if (links.length > 0)
2808
2827
  fields.push({
2809
2828
  label: "Links",
2810
- value: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "flex gap-3", children: links })
2829
+ value: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: "flex gap-3", children: links })
2811
2830
  });
2812
- return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(import_jsx_runtime48.Fragment, { children: [
2813
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_ui21.PanelBleed, { children: /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(import_ui21.ActionBand, { children: [
2814
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
2831
+ return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(import_jsx_runtime49.Fragment, { children: [
2832
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_ui21.PanelBleed, { children: /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(import_ui21.ActionBand, { children: [
2833
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
2815
2834
  import_ui21.ActionButton,
2816
2835
  {
2817
- icon: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_lucide_react16.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_lucide_react16.Play, { "aria-hidden": "true", className: "w-5 h-5" }),
2836
+ icon: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_lucide_react16.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_lucide_react16.Play, { "aria-hidden": "true", className: "w-5 h-5" }),
2818
2837
  label: isPlaying ? "Pause" : "Play",
2819
2838
  disabled: !!unplayableReason,
2820
2839
  title: unplayableReason ?? void 0,
2821
2840
  onClick: () => isCurrent ? handlePlayPauseAction() : playNewTrackListFromTrackUuid(song, scope)
2822
2841
  }
2823
2842
  ),
2824
- shareUrl && /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
2843
+ shareUrl && /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
2825
2844
  ] }) }),
2826
- song.artists && song.artists.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("p", { className: "text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_genre_tree28.ArtistNames, { artists: song.artists, renderArtistLink }) }),
2827
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_ui21.FieldList, { items: fields }),
2828
- song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_ui21.PanelSection, { title: "Essential in", children: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
2829
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
2845
+ song.artists && song.artists.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("p", { className: "text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_genre_tree28.ArtistNames, { artists: song.artists, renderArtistLink }) }),
2846
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_ui21.FieldList, { items: fields }),
2847
+ song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_ui21.PanelSection, { title: "Essential in", children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
2848
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
2830
2849
  ] });
2831
2850
  }
2832
2851
 
2833
2852
  // src/genre-tree/song/useSongPanel.tsx
2834
- var import_jsx_runtime49 = require("react/jsx-runtime");
2853
+ var import_jsx_runtime50 = require("react/jsx-runtime");
2835
2854
  function useSongPanel({
2836
2855
  uuid,
2837
2856
  scope,
@@ -2852,24 +2871,24 @@ function useSongPanel({
2852
2871
  return {
2853
2872
  ...panel,
2854
2873
  title: "Song",
2855
- content: /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5", children: [
2856
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_ui22.Skeleton, { className: "h-3 w-16" }),
2857
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_ui22.Skeleton, { className: "h-4 w-32" }),
2858
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_ui22.Skeleton, { className: "h-3 w-16" }),
2859
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_ui22.Skeleton, { className: "h-4 w-24" })
2874
+ content: /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5", children: [
2875
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_ui22.Skeleton, { className: "h-3 w-16" }),
2876
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_ui22.Skeleton, { className: "h-4 w-32" }),
2877
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_ui22.Skeleton, { className: "h-3 w-16" }),
2878
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_ui22.Skeleton, { className: "h-4 w-24" })
2860
2879
  ] })
2861
2880
  };
2862
2881
  case "not-found":
2863
2882
  return {
2864
2883
  ...panel,
2865
2884
  title: "Song not found",
2866
- content: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "This song does not exist." })
2885
+ content: /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "This song does not exist." })
2867
2886
  };
2868
2887
  case "error":
2869
2888
  return {
2870
2889
  ...panel,
2871
2890
  title: "Song",
2872
- content: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("p", { className: "text-red-600", children: "Could not load this song." })
2891
+ content: /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("p", { className: "text-red-600", children: "Could not load this song." })
2873
2892
  };
2874
2893
  case "success": {
2875
2894
  const { song } = result;
@@ -2877,7 +2896,7 @@ function useSongPanel({
2877
2896
  return {
2878
2897
  ...panel,
2879
2898
  title: song.title,
2880
- content: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
2899
+ content: /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
2881
2900
  SongPanelContent,
2882
2901
  {
2883
2902
  song,
@@ -2930,12 +2949,12 @@ function useEntityPanel({
2930
2949
  // src/genre-tree/SizeMetricToggle.tsx
2931
2950
  var import_md2 = require("react-icons/md");
2932
2951
  var import_ui23 = require("@behindthemusictree/ui");
2933
- var import_jsx_runtime50 = require("react/jsx-runtime");
2952
+ var import_jsx_runtime51 = require("react/jsx-runtime");
2934
2953
  function SizeMetricToggle({
2935
2954
  value,
2936
2955
  onChange
2937
2956
  }) {
2938
- return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
2957
+ return /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
2939
2958
  import_ui23.SegmentedControl,
2940
2959
  {
2941
2960
  variant: "floating",
@@ -2943,15 +2962,15 @@ function SizeMetricToggle({
2943
2962
  value,
2944
2963
  onChange,
2945
2964
  options: [
2946
- { value: "songs", label: /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_md2.MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
2947
- { value: "views", label: /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_md2.MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
2965
+ { value: "songs", label: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(import_md2.MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
2966
+ { value: "views", label: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(import_md2.MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
2948
2967
  ]
2949
2968
  }
2950
2969
  );
2951
2970
  }
2952
2971
 
2953
2972
  // src/genre-tree/GenreTreeView.tsx
2954
- var import_jsx_runtime51 = require("react/jsx-runtime");
2973
+ var import_jsx_runtime52 = require("react/jsx-runtime");
2955
2974
  var NO_POP_ROOT_TITLE = "This genre tree has no 'Mainstream Pop' root yet";
2956
2975
  var REGIONS_CLASS = "[--gtv-center-left:var(--layout-center-left)] [--gtv-center-right:var(--layout-center-right)] [--gtv-center-top:var(--layout-band-top)]";
2957
2976
  var noop = () => {
@@ -3059,7 +3078,7 @@ function GenreTreeView({
3059
3078
  allowWheelRotation: internalAllowWheelRotation,
3060
3079
  toggleWheelRotation,
3061
3080
  onNodeHover: handleNodeHover
3062
- } = (0, import_genre_tree30.useGenreTreeViewState)({
3081
+ } = (0, import_genre_tree31.useGenreTreeViewState)({
3063
3082
  scope,
3064
3083
  getBackendBaseUrl,
3065
3084
  viewMode: controlledViewMode,
@@ -3078,12 +3097,12 @@ function GenreTreeView({
3078
3097
  restoreRootUuid = selectedGenrePlaylist.root.uuid;
3079
3098
  setStackedRestoreOwner({ revision: restoreRevision, rootUuid: restoreRootUuid });
3080
3099
  }
3081
- const isGenreUpdating = (0, import_react_query3.useIsMutating)({ mutationKey: import_genre_tree30.genreUpdateMutationKey }) > 0;
3082
- const { mutate: updateGenre } = (0, import_genre_tree30.useUpdateGenre)(scope, getBackendBaseUrl);
3100
+ const isGenreUpdating = (0, import_react_query3.useIsMutating)({ mutationKey: import_genre_tree31.genreUpdateMutationKey }) > 0;
3101
+ const { mutate: updateGenre } = (0, import_genre_tree31.useUpdateGenre)(scope, getBackendBaseUrl);
3083
3102
  const busy = deferredSizeMetric !== sizeMetric || isTreeFetching || isGenreUpdating;
3084
3103
  const isArtistFiltered = artistUuid !== null;
3085
3104
  const canSizeByViews = !isArtistFiltered && hasViewsCount(visibleGenrePlaylists);
3086
- const artistResult = (0, import_genre_tree30.useArtist)(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
3105
+ const artistResult = (0, import_genre_tree31.useArtist)(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
3087
3106
  const filterArtistName = artistResult.status === "success" ? artistResult.artist.name : null;
3088
3107
  const filterArtist = (0, import_react19.useMemo)(
3089
3108
  () => artistUuid !== null && filterArtistName !== null ? { uuid: artistUuid, name: filterArtistName } : null,
@@ -3103,14 +3122,14 @@ function GenreTreeView({
3103
3122
  const openSongRef = (0, import_react19.useRef)(openSong);
3104
3123
  openSongRef.current = openSong;
3105
3124
  const resolvedRenderSongLink = (0, import_react19.useCallback)(
3106
- (song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
3125
+ (song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
3107
3126
  [renderSongLink]
3108
3127
  );
3109
3128
  const [expandedSongUuid, setExpandedSongUuid] = (0, import_react19.useState)(null);
3110
3129
  const [expandedArtistUuid, setExpandedArtistUuid] = (0, import_react19.useState)(null);
3111
3130
  const artistBaseUrl = artistSearchBaseUrl ?? getBackendBaseUrl;
3112
3131
  const songBaseUrl = getBackendBaseUrl;
3113
- const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
3132
+ const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
3114
3133
  const getGenrePath = (genre) => {
3115
3134
  const path = [];
3116
3135
  let current = visibleGenrePlaylists.find((playlist) => playlist.criteria?.uuid === genre.uuid);
@@ -3187,9 +3206,10 @@ function GenreTreeView({
3187
3206
  resolveStyle
3188
3207
  );
3189
3208
  const treeStyles = (0, import_react19.useMemo)(() => toGenreNodeStyles(relations, visibleGenrePlaylists), [relations, visibleGenrePlaylists]);
3190
- const [selectionHistory, setSelectionHistory] = (0, import_react19.useState)({ current: selectedGenreUuid, previous: null });
3191
- if (selectionHistory.current !== selectedGenreUuid) {
3192
- setSelectionHistory({ current: selectedGenreUuid, previous: selectionHistory.current ?? selectionHistory.previous });
3209
+ const validSelectedGenreUuid = (0, import_genre_tree30.toGenreUuid)(selectedGenreUuid);
3210
+ const [selectionHistory, setSelectionHistory] = (0, import_react19.useState)({ current: validSelectedGenreUuid, previous: null });
3211
+ if (selectionHistory.current !== validSelectedGenreUuid) {
3212
+ setSelectionHistory({ current: validSelectedGenreUuid, previous: selectionHistory.current ?? selectionHistory.previous });
3193
3213
  }
3194
3214
  const handlePrevious = selectionHistory.previous ? () => selectGenre(selectionHistory.previous) : void 0;
3195
3215
  const { showPopup, hidePopup } = (0, import_popup5.usePopup)();
@@ -3221,7 +3241,7 @@ function GenreTreeView({
3221
3241
  const canToggleSide = !readOnly && criteria !== void 0 && criteria !== null && parentPlaylist !== void 0 && parentPlaylist.parent === null;
3222
3242
  const side = criteria?.side ?? "core";
3223
3243
  const popSibling = canToggleSide && side === "core" ? visibleGenrePlaylists.find((sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist?.uuid && sibling.criteria?.side === "pop") : void 0;
3224
- const shareTarget = selectedGenreUuid ? { kind: "genre", genreUuid: selectedGenreUuid, name: criteria?.name ?? "Genre", scope, artistUuid } : void 0;
3244
+ const shareTarget = validSelectedGenreUuid ? { kind: "genre", genreUuid: validSelectedGenreUuid, name: criteria?.name ?? "Genre", scope, artistUuid } : void 0;
3225
3245
  const shareUrl = shareTarget ? getShareUrlRef.current?.(shareTarget) ?? void 0 : void 0;
3226
3246
  const genreContent = useGenreDetailContent({
3227
3247
  genreUuid: selectedGenreUuid,
@@ -3243,7 +3263,7 @@ function GenreTreeView({
3243
3263
  onAddChild: readOnly || !criteria ? void 0 : () => handleGenreCreationAction(criteria),
3244
3264
  onRename: readOnly || !criteria ? void 0 : () => handleGenreRenameAction(criteria),
3245
3265
  onChangeParent: readOnly || !genrePlaylist || !criteria ? void 0 : () => showPopup(
3246
- /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3266
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
3247
3267
  ChangeParentPopup,
3248
3268
  {
3249
3269
  genrePlaylist: { ...genrePlaylist, criteria },
@@ -3287,7 +3307,7 @@ function GenreTreeView({
3287
3307
  onClose: otherPanel.onClose,
3288
3308
  searchName: otherPanel.title
3289
3309
  };
3290
- } else if (selectedGenreUuid) {
3310
+ } else if (selectedGenreUuid !== null) {
3291
3311
  activePanel = {
3292
3312
  title: genreContent.title,
3293
3313
  content: genreContent.content,
@@ -3304,19 +3324,19 @@ function GenreTreeView({
3304
3324
  edge: activePanel.expanded ? { label: "Shrink panel", onClick: () => activePanel.onExpandedChange?.(false) } : isCollapsed ? null : { label: "Hide panel", onClick: () => changeCollapsed(true) },
3305
3325
  center: activePanel.expanded ? null : isCollapsed ? { label: "Show panel", onClick: () => changeCollapsed(false) } : activePanel.onExpandedChange ? { label: "Extend panel", onClick: () => activePanel.onExpandedChange?.(true) } : null
3306
3326
  } : void 0;
3307
- const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3327
+ const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
3308
3328
  import_ui24.IconButton,
3309
3329
  {
3310
3330
  "aria-label": activePanel.expanded ? "Shrink panel" : "Extend panel",
3311
3331
  style: activePanel.headerAccent && { color: "inherit" },
3312
3332
  onClick: () => activePanel.onExpandedChange?.(!activePanel.expanded),
3313
- children: activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(import_lucide_react18.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(import_lucide_react18.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
3333
+ children: activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(import_lucide_react18.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(import_lucide_react18.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
3314
3334
  }
3315
3335
  ) : null;
3316
3336
  const renderArtistFilter = () => {
3317
3337
  if (isLoading || !filterArtist) return null;
3318
- return /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-body", children: [
3319
- /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3338
+ return /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-body", children: [
3339
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
3320
3340
  "button",
3321
3341
  {
3322
3342
  type: "button",
@@ -3328,7 +3348,7 @@ function GenreTreeView({
3328
3348
  children: filterArtist.name
3329
3349
  }
3330
3350
  ),
3331
- /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3351
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
3332
3352
  "button",
3333
3353
  {
3334
3354
  type: "button",
@@ -3340,8 +3360,8 @@ function GenreTreeView({
3340
3360
  )
3341
3361
  ] });
3342
3362
  };
3343
- const actions = /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(import_jsx_runtime51.Fragment, { children: [
3344
- !isLoading && !isError && /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3363
+ const actions = /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(import_jsx_runtime52.Fragment, { children: [
3364
+ !isLoading && !isError && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
3345
3365
  GenreTreeSearch,
3346
3366
  {
3347
3367
  artistBaseUrl,
@@ -3374,9 +3394,9 @@ function GenreTreeView({
3374
3394
  extraGroups: extraSearchGroups
3375
3395
  }
3376
3396
  ),
3377
- !isLoading && !isError && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
3378
- /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("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: [
3379
- !isLoading && !isError && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3397
+ !isLoading && !isError && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
3398
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("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: [
3399
+ !isLoading && !isError && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
3380
3400
  import_ui24.SegmentedControl,
3381
3401
  {
3382
3402
  "aria-label": "Tree view mode",
@@ -3390,13 +3410,13 @@ function GenreTreeView({
3390
3410
  ]
3391
3411
  }
3392
3412
  ),
3393
- !isLoading && !isError && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3413
+ !isLoading && !isError && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
3394
3414
  "div",
3395
3415
  {
3396
3416
  className: "flex items-center gap-1",
3397
3417
  role: "group",
3398
3418
  "aria-label": "Tree display options",
3399
- children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3419
+ children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
3400
3420
  import_ui24.Button,
3401
3421
  {
3402
3422
  variant: allowWheelRotation ? "default" : "outline",
@@ -3407,7 +3427,7 @@ function GenreTreeView({
3407
3427
  )
3408
3428
  }
3409
3429
  ),
3410
- !isLoading && !isError && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3430
+ !isLoading && !isError && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
3411
3431
  import_ui24.IconTextButton,
3412
3432
  {
3413
3433
  icon: import_lucide_react17.Plus,
@@ -3417,38 +3437,38 @@ function GenreTreeView({
3417
3437
  )
3418
3438
  ] })
3419
3439
  ] });
3420
- return /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(
3440
+ return /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(
3421
3441
  "div",
3422
3442
  {
3423
3443
  "data-left-panel": isPanelShown && !isCollapsed ? void 0 : "hidden",
3424
3444
  className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
3425
3445
  children: [
3426
- /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3446
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
3427
3447
  "div",
3428
3448
  {
3429
3449
  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))] md:left-[var(--layout-panel-inset)] md:top-[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"}`,
3430
3450
  children: actions
3431
3451
  }
3432
3452
  ),
3433
- !isLoading && !isError && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3453
+ !isLoading && !isError && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
3434
3454
  "div",
3435
3455
  {
3436
3456
  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))] md:top-[var(--layout-panel-inset)]" : "top-5"}`,
3437
3457
  children: renderArtistFilter()
3438
3458
  }
3439
3459
  ),
3440
- /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
3441
- !isLoading && !isError && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
3442
- isLoading ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(import_genre_tree_view5.GenreTreeViewSkeleton, { viewMode }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("p", { role: "alert", className: "m-auto text-center", children: [
3460
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
3461
+ !isLoading && !isError && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
3462
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(import_genre_tree_view5.GenreTreeViewSkeleton, { viewMode }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("p", { role: "alert", className: "m-auto text-center", children: [
3443
3463
  "Couldn't load the genre tree.",
3444
3464
  " ",
3445
- /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("button", { type: "button", className: "underline", onClick: refetch, children: "Retry" })
3465
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("button", { type: "button", className: "underline", onClick: refetch, children: "Retry" })
3446
3466
  ] }) : viewMode === "wheel" ? (
3447
3467
  // The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
3448
3468
  // and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
3449
3469
  // Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
3450
3470
  // The panel's own Zoom in button is hidden: the search bar carries it.
3451
- /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(import_genre_tree_view5.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3471
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(import_genre_tree_view5.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
3452
3472
  GenrePlaylistTreeWheel,
3453
3473
  {
3454
3474
  scope,
@@ -3466,9 +3486,9 @@ function GenreTreeView({
3466
3486
  allowWheelRotation,
3467
3487
  sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
3468
3488
  busy,
3469
- floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("div", { className: "flex items-center gap-2", children: [
3489
+ floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("div", { className: "flex items-center gap-2", children: [
3470
3490
  floatingControlsLeading,
3471
- canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
3491
+ canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
3472
3492
  ] }) : void 0,
3473
3493
  defaultViewport,
3474
3494
  viewportRestore,
@@ -3477,7 +3497,7 @@ function GenreTreeView({
3477
3497
  ) }) })
3478
3498
  ) : viewMode === "pop-core" ? (
3479
3499
  // Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
3480
- /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(import_genre_tree_view5.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3500
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(import_genre_tree_view5.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
3481
3501
  GenrePlaylistTreeWheelRadialPopCore,
3482
3502
  {
3483
3503
  scope,
@@ -3495,9 +3515,9 @@ function GenreTreeView({
3495
3515
  allowWheelRotation,
3496
3516
  sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
3497
3517
  busy,
3498
- floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("div", { className: "flex items-center gap-2", children: [
3518
+ floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("div", { className: "flex items-center gap-2", children: [
3499
3519
  floatingControlsLeading,
3500
- canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
3520
+ canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
3501
3521
  ] }) : void 0,
3502
3522
  defaultViewport,
3503
3523
  viewportRestore,
@@ -3505,7 +3525,7 @@ function GenreTreeView({
3505
3525
  ...focus
3506
3526
  }
3507
3527
  ) }) })
3508
- ) : viewMode === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3528
+ ) : viewMode === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
3509
3529
  GenrePlaylistTreeWheelRadialPopCore,
3510
3530
  {
3511
3531
  outline: true,
@@ -3522,13 +3542,13 @@ function GenreTreeView({
3522
3542
  selectedNodeId,
3523
3543
  readOnly
3524
3544
  }
3525
- ) }) : /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("div", { className: "tree-container relative flex flex-col gap-4 w-full pt-14 overflow-x-auto overflow-y-auto text-[var(--color-text)]", children: Object.entries(groupedGenrePlaylistsByRoot).map(
3545
+ ) }) : /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("div", { className: "tree-container relative flex flex-col gap-4 w-full pt-14 overflow-x-auto overflow-y-auto text-[var(--color-text)]", children: Object.entries(groupedGenrePlaylistsByRoot).map(
3526
3546
  ([uuid, genrePlaylistTreePerRoot]) => {
3527
- return /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3547
+ return /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
3528
3548
  "div",
3529
3549
  {
3530
3550
  className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
3531
- children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3551
+ children: /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
3532
3552
  GenrePlaylistTreePerRoot,
3533
3553
  {
3534
3554
  scope,
@@ -3555,7 +3575,7 @@ function GenreTreeView({
3555
3575
  );
3556
3576
  }
3557
3577
  ) }),
3558
- /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
3578
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
3559
3579
  import_ui24.DetailPanel,
3560
3580
  {
3561
3581
  ref: panelRef,
@@ -3570,7 +3590,7 @@ function GenreTreeView({
3570
3590
  onExpandedChange: activePanel?.onExpandedChange,
3571
3591
  aside: activePanel?.aside,
3572
3592
  edgeTab: detailTab,
3573
- children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(import_jsx_runtime51.Fragment, { children: [
3593
+ children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(import_jsx_runtime52.Fragment, { children: [
3574
3594
  activePanel.content,
3575
3595
  activePanel.aside
3576
3596
  ] }) : activePanel?.content
@@ -3588,10 +3608,10 @@ var import_genre_tree_view6 = require("@behindthemusictree/genre-tree-view");
3588
3608
  // src/genre-tree/track-list-sidebar/TrackListSidebar.tsx
3589
3609
  var import_react20 = require("react");
3590
3610
  var import_lucide_react19 = require("lucide-react");
3591
- var import_genre_tree31 = require("@behindthemusictree/app-kit/genre-tree");
3592
3611
  var import_genre_tree32 = require("@behindthemusictree/app-kit/genre-tree");
3612
+ var import_genre_tree33 = require("@behindthemusictree/app-kit/genre-tree");
3593
3613
  var import_track_noun4 = require("@behindthemusictree/app-kit/track-noun");
3594
- var import_jsx_runtime52 = require("react/jsx-runtime");
3614
+ var import_jsx_runtime53 = require("react/jsx-runtime");
3595
3615
  var useBrowserLayoutEffect = typeof window === "undefined" ? import_react20.useEffect : import_react20.useLayoutEffect;
3596
3616
  function TrackListSidebar({
3597
3617
  className: className2 = "",
@@ -3604,7 +3624,7 @@ function TrackListSidebar({
3604
3624
  renderArtistLink,
3605
3625
  renderSongLink
3606
3626
  }) {
3607
- const { trackList, loadMore, autoAdvanceScrollRequest } = (0, import_genre_tree31.useTrackList)();
3627
+ const { trackList, loadMore, autoAdvanceScrollRequest } = (0, import_genre_tree32.useTrackList)();
3608
3628
  const trackNoun = (0, import_track_noun4.useTrackNoun)();
3609
3629
  const trackListRef = (0, import_react20.useRef)(null);
3610
3630
  const previousOriginRef = (0, import_react20.useRef)(trackList?.origin ?? null);
@@ -3693,25 +3713,25 @@ function TrackListSidebar({
3693
3713
  }
3694
3714
  };
3695
3715
  const positionClasses = layout === "inline" ? "relative w-full flex h-full flex-col" : "fixed right-0 w-[50rem] flex flex-col";
3696
- return trackList ? /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(
3716
+ return trackList ? /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(
3697
3717
  "div",
3698
3718
  {
3699
3719
  "data-theme": "dark",
3700
3720
  className: `track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${className2}`,
3701
3721
  style: layout === "fixed" ? { top: "63.5px", maxHeight: "calc(100vh - 63.5px - 79px)" } : void 0,
3702
3722
  children: [
3703
- /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
3704
- /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
3705
- /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("div", { className: "origin flex text-xl", children: [
3706
- /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
3707
- /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("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__ */ (0, import_jsx_runtime52.jsx)("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
3723
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
3724
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
3725
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("div", { className: "origin flex text-xl", children: [
3726
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
3727
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("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__ */ (0, import_jsx_runtime53.jsx)("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
3708
3728
  ] }),
3709
- /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("div", { className: "info flex items-center text-body text-[var(--color-text-muted)]", children: [
3710
- trackList.origin.type === import_genre_tree32.TrackListOriginType.GENRE_PLAYLIST ? "Genre \u2022 " : trackList.origin.type === import_genre_tree32.TrackListOriginType.ARTIST ? "Artist \u2022 " : `${trackNoun.singular} playlist \u2022 `,
3729
+ /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("div", { className: "info flex items-center text-body text-[var(--color-text-muted)]", children: [
3730
+ trackList.origin.type === import_genre_tree33.TrackListOriginType.GENRE_PLAYLIST ? "Genre \u2022 " : trackList.origin.type === import_genre_tree33.TrackListOriginType.ARTIST ? "Artist \u2022 " : `${trackNoun.singular} playlist \u2022 `,
3711
3731
  `${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`
3712
3732
  ] })
3713
3733
  ] }),
3714
- (isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
3734
+ (isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
3715
3735
  "button",
3716
3736
  {
3717
3737
  type: "button",
@@ -3720,11 +3740,11 @@ function TrackListSidebar({
3720
3740
  "aria-expanded": !isCollapsed,
3721
3741
  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"}`,
3722
3742
  onClick: isCollapsed ? onExpand : onMinimize,
3723
- children: /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(import_lucide_react19.ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
3743
+ children: /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(import_lucide_react19.ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
3724
3744
  }
3725
3745
  )
3726
3746
  ] }),
3727
- !isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(
3747
+ !isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(
3728
3748
  "ul",
3729
3749
  {
3730
3750
  ref: trackListRef,
@@ -3734,7 +3754,7 @@ function TrackListSidebar({
3734
3754
  onPointerDown: markUserScrollIntent,
3735
3755
  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",
3736
3756
  children: [
3737
- trackList.tracks.map((track, index) => /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
3757
+ trackList.tracks.map((track, index) => /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
3738
3758
  TrackItem,
3739
3759
  {
3740
3760
  track,
@@ -3745,7 +3765,7 @@ function TrackListSidebar({
3745
3765
  renderSongLink
3746
3766
  }
3747
3767
  ) }, track.uuid)),
3748
- hasMore && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
3768
+ hasMore && /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
3749
3769
  ]
3750
3770
  }
3751
3771
  )
@@ -3758,14 +3778,14 @@ function TrackListSidebar({
3758
3778
  var import_react21 = require("react");
3759
3779
  var import_tailwind_merge = require("tailwind-merge");
3760
3780
  var import_player14 = require("@behindthemusictree/app-kit/player");
3761
- var import_genre_tree34 = require("@behindthemusictree/app-kit/genre-tree");
3781
+ var import_genre_tree35 = require("@behindthemusictree/app-kit/genre-tree");
3762
3782
  var import_ui25 = require("@behindthemusictree/ui");
3763
3783
 
3764
3784
  // src/genre-tree/PlayerControls.tsx
3765
3785
  var import_lucide_react20 = require("lucide-react");
3766
- var import_genre_tree33 = require("@behindthemusictree/app-kit/genre-tree");
3786
+ var import_genre_tree34 = require("@behindthemusictree/app-kit/genre-tree");
3767
3787
  var import_player13 = require("@behindthemusictree/app-kit/player");
3768
- var import_jsx_runtime53 = require("react/jsx-runtime");
3788
+ var import_jsx_runtime54 = require("react/jsx-runtime");
3769
3789
  function PlayerControls() {
3770
3790
  const {
3771
3791
  duration,
@@ -3777,22 +3797,22 @@ function PlayerControls() {
3777
3797
  setVolume,
3778
3798
  volume
3779
3799
  } = (0, import_player13.usePlayer)();
3780
- const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } = (0, import_genre_tree33.useTrackList)();
3800
+ const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } = (0, import_genre_tree34.useTrackList)();
3781
3801
  const currentTime = (0, import_player13.useCurrentTime)();
3782
3802
  const isBusy = isLoading || playState === import_player13.PlayStates.LOADING;
3783
3803
  const isReady = !!playerTrackObject?.isReady && !isBusy && !playerTrackObject.loadError;
3784
3804
  const safeDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;
3785
3805
  const safeCurrentTime = Number.isFinite(currentTime) ? Math.max(0, Math.min(currentTime, safeDuration)) : 0;
3786
3806
  const isPlaying = playState === import_player13.PlayStates.PLAYING;
3787
- return /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(
3807
+ return /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(
3788
3808
  "section",
3789
3809
  {
3790
3810
  "aria-label": "Player controls",
3791
3811
  className: "player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200",
3792
3812
  children: [
3793
- /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
3794
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("span", { "aria-label": "Elapsed time", children: (0, import_genre_tree33.formatTime)(safeCurrentTime) }),
3795
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
3813
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
3814
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("span", { "aria-label": "Elapsed time", children: (0, import_genre_tree34.formatTime)(safeCurrentTime) }),
3815
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
3796
3816
  "input",
3797
3817
  {
3798
3818
  "aria-label": "Seek",
@@ -3806,11 +3826,11 @@ function PlayerControls() {
3806
3826
  onChange: (event) => seekTo(Number(event.currentTarget.value))
3807
3827
  }
3808
3828
  ),
3809
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("span", { "aria-label": "Duration", children: (0, import_genre_tree33.formatTime)(safeDuration) })
3829
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("span", { "aria-label": "Duration", children: (0, import_genre_tree34.formatTime)(safeDuration) })
3810
3830
  ] }),
3811
- /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
3812
- /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
3813
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
3831
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
3832
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
3833
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
3814
3834
  "button",
3815
3835
  {
3816
3836
  type: "button",
@@ -3819,10 +3839,10 @@ function PlayerControls() {
3819
3839
  disabled: !isReady || !hasPreviousTrack,
3820
3840
  onClick: playPreviousTrack,
3821
3841
  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",
3822
- children: /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(import_lucide_react20.SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
3842
+ children: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_lucide_react20.SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
3823
3843
  }
3824
3844
  ),
3825
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
3845
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
3826
3846
  "button",
3827
3847
  {
3828
3848
  type: "button",
@@ -3831,10 +3851,10 @@ function PlayerControls() {
3831
3851
  disabled: !isReady,
3832
3852
  onClick: handlePlayPauseAction,
3833
3853
  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",
3834
- children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(import_lucide_react20.Pause, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(import_lucide_react20.Play, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
3854
+ children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_lucide_react20.Pause, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_lucide_react20.Play, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
3835
3855
  }
3836
3856
  ),
3837
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
3857
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
3838
3858
  "button",
3839
3859
  {
3840
3860
  type: "button",
@@ -3843,14 +3863,14 @@ function PlayerControls() {
3843
3863
  disabled: !isReady || !hasNextTrack,
3844
3864
  onClick: playNextTrack,
3845
3865
  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",
3846
- children: /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(import_lucide_react20.SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
3866
+ children: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_lucide_react20.SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
3847
3867
  }
3848
3868
  )
3849
3869
  ] }),
3850
- /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("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: [
3851
- volume === 0 ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(import_lucide_react20.VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(import_lucide_react20.Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
3852
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("span", { className: "sr-only", children: "Volume" }),
3853
- /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
3870
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("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: [
3871
+ volume === 0 ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_lucide_react20.VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_lucide_react20.Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
3872
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("span", { className: "sr-only", children: "Volume" }),
3873
+ /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
3854
3874
  "input",
3855
3875
  {
3856
3876
  "aria-label": "Volume",
@@ -3872,7 +3892,7 @@ function PlayerControls() {
3872
3892
  }
3873
3893
 
3874
3894
  // src/genre-tree/PlayerPanel.tsx
3875
- var import_jsx_runtime54 = require("react/jsx-runtime");
3895
+ var import_jsx_runtime55 = require("react/jsx-runtime");
3876
3896
  var PANEL_ID = "player-panel";
3877
3897
  var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
3878
3898
  function PlayerPanel({
@@ -3885,7 +3905,7 @@ function PlayerPanel({
3885
3905
  renderSongLink
3886
3906
  }) {
3887
3907
  const { playerTrackObject, retryPlayback } = (0, import_player14.usePlayer)();
3888
- const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0, import_genre_tree34.useTrackListSidebarVisibility)();
3908
+ const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0, import_genre_tree35.useTrackListSidebarVisibility)();
3889
3909
  const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = (0, import_react21.useState)("");
3890
3910
  const loadedTrackTitle = playerTrackObject?.track.title;
3891
3911
  (0, import_react21.useEffect)(() => {
@@ -3912,7 +3932,7 @@ function PlayerPanel({
3912
3932
  onClick: () => setIsExtendRequested(true),
3913
3933
  expanded: false
3914
3934
  } : null;
3915
- return /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(
3935
+ return /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(
3916
3936
  "div",
3917
3937
  {
3918
3938
  id: PANEL_ID,
@@ -3925,7 +3945,7 @@ function PlayerPanel({
3925
3945
  ),
3926
3946
  style: isExtended ? { width: extendedWidth } : void 0,
3927
3947
  children: [
3928
- /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
3948
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
3929
3949
  import_ui25.PanelEdgeTab,
3930
3950
  {
3931
3951
  side: "right",
@@ -3935,7 +3955,7 @@ function PlayerPanel({
3935
3955
  className: "absolute right-full top-1/2 -translate-y-1/2"
3936
3956
  }
3937
3957
  ),
3938
- /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(
3958
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(
3939
3959
  "div",
3940
3960
  {
3941
3961
  className: (0, import_tailwind_merge.twMerge)(
@@ -3943,20 +3963,20 @@ function PlayerPanel({
3943
3963
  isExtended ? "flex-row" : "flex-col"
3944
3964
  ),
3945
3965
  children: [
3946
- /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
3947
- /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
3948
- /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("div", { className: "relative w-full shrink-0", children: [
3949
- /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_player14.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
3950
- playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: [
3966
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
3967
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
3968
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "relative w-full shrink-0", children: [
3969
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(import_player14.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
3970
+ playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: [
3951
3971
  playerTrackObject.loadError,
3952
- /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("button", { type: "button", className: "ml-2 underline", onClick: () => {
3972
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("button", { type: "button", className: "ml-2 underline", onClick: () => {
3953
3973
  void retryPlayback();
3954
3974
  }, children: "Retry" })
3955
3975
  ] })
3956
3976
  ] }),
3957
- /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(PlayerControls, {})
3977
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(PlayerControls, {})
3958
3978
  ] }),
3959
- /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
3979
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
3960
3980
  TrackListSidebar,
3961
3981
  {
3962
3982
  layout: "inline",
@@ -3982,33 +4002,33 @@ function PlayerPanel({
3982
4002
  // src/genre-tree/Rating.tsx
3983
4003
  var import_uuid = require("uuid");
3984
4004
  var import_ai = require("react-icons/ai");
3985
- var import_genre_tree35 = require("@behindthemusictree/app-kit/genre-tree");
3986
- var import_jsx_runtime55 = require("react/jsx-runtime");
4005
+ var import_genre_tree36 = require("@behindthemusictree/app-kit/genre-tree");
4006
+ var import_jsx_runtime56 = require("react/jsx-runtime");
3987
4007
  function Rating({ rating, handleChange }) {
3988
4008
  const instanceId = (0, import_uuid.v4)();
3989
- return /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
3990
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
4009
+ return /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
4010
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
3991
4011
  "input",
3992
4012
  {
3993
4013
  id: `star-${instanceId}-${i}`,
3994
4014
  className: "w-6 h-6 opacity-0",
3995
4015
  type: "radio",
3996
4016
  name: "rating",
3997
- value: i === 0 ? import_genre_tree35.FORM_RATING_NULL_VALUE : i,
4017
+ value: i === 0 ? import_genre_tree36.FORM_RATING_NULL_VALUE : i,
3998
4018
  checked: rating === (i === 0 ? null : i),
3999
4019
  onChange: handleChange
4000
4020
  }
4001
4021
  ),
4002
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "flex", children: [
4003
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
4022
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: "flex", children: [
4023
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
4004
4024
  import_ai.AiOutlineStar,
4005
4025
  {
4006
- className: `w-7 h-7 absolute top-0 left-0 ${rating === import_genre_tree35.FORM_RATING_NULL_VALUE ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
4026
+ className: `w-7 h-7 absolute top-0 left-0 ${rating === import_genre_tree36.FORM_RATING_NULL_VALUE ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
4007
4027
  }
4008
4028
  ),
4009
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("span", { className: "flex-grow w-8" }),
4029
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("span", { className: "flex-grow w-8" }),
4010
4030
  " "
4011
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
4031
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
4012
4032
  import_ai.AiFillStar,
4013
4033
  {
4014
4034
  className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
@@ -4020,7 +4040,7 @@ function Rating({ rating, handleChange }) {
4020
4040
  // src/genre-tree/EntityDetailPanel.tsx
4021
4041
  var import_react22 = require("react");
4022
4042
  var import_ui26 = require("@behindthemusictree/ui");
4023
- var import_jsx_runtime56 = require("react/jsx-runtime");
4043
+ var import_jsx_runtime57 = require("react/jsx-runtime");
4024
4044
  function EntityDetailPanel({
4025
4045
  scope,
4026
4046
  getBackendBaseUrl,
@@ -4043,8 +4063,8 @@ function EntityDetailPanel({
4043
4063
  getBackendBaseUrl,
4044
4064
  getArtistBackendBaseUrl,
4045
4065
  getShareUrl,
4046
- renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("button", { type: "button", onClick: () => onArtistUuidChange(artist.uuid), className: "hover:underline", children: artist.name })),
4047
- renderSongLink: renderSongLink ?? ((song) => /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("button", { type: "button", onClick: () => onSongUuidChange(song.uuid), className: "hover:underline", children: song.title })),
4066
+ renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("button", { type: "button", onClick: () => onArtistUuidChange(artist.uuid), className: "hover:underline", children: artist.name })),
4067
+ renderSongLink: renderSongLink ?? ((song) => /* @__PURE__ */ (0, import_jsx_runtime57.jsx)("button", { type: "button", onClick: () => onSongUuidChange(song.uuid), className: "hover:underline", children: song.title })),
4048
4068
  song: {
4049
4069
  uuid: songUuid,
4050
4070
  expanded: songUuid !== null && expandedSongUuid === songUuid,
@@ -4060,7 +4080,7 @@ function EntityDetailPanel({
4060
4080
  ...navigation
4061
4081
  });
4062
4082
  if (!panel) return null;
4063
- return /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
4083
+ return /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
4064
4084
  import_ui26.DetailPanel,
4065
4085
  {
4066
4086
  side: "left",
@@ -4072,7 +4092,7 @@ function EntityDetailPanel({
4072
4092
  onExpandedChange: panel.onExpandedChange,
4073
4093
  aside: panel.extraContent,
4074
4094
  edgeTab,
4075
- children: panel.extraContent && !panel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)(import_jsx_runtime56.Fragment, { children: [
4095
+ children: panel.extraContent && !panel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime57.jsxs)(import_jsx_runtime57.Fragment, { children: [
4076
4096
  panel.content,
4077
4097
  panel.extraContent
4078
4098
  ] }) : panel.content