@behindthemusictree/app-kit-ui 29.1.0 → 30.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/{chunk-36YVZ6JR.js → chunk-2HE2HDFA.js} +551 -253
- package/dist/chunk-2HE2HDFA.js.map +1 -0
- package/dist/genre-tree/index.cjs +587 -299
- package/dist/genre-tree/index.cjs.map +1 -1
- package/dist/genre-tree/index.d.ts +40 -12
- package/dist/genre-tree/index.js +1 -1
- package/dist/index.cjs +584 -296
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +5 -5
- package/dist/chunk-36YVZ6JR.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -580,13 +580,13 @@ function AuthCallbackHandler({ renderStatus, ...options }) {
|
|
|
580
580
|
}
|
|
581
581
|
|
|
582
582
|
// src/genre-tree/GenreTreeView.tsx
|
|
583
|
-
var
|
|
583
|
+
var import_react17 = require("react");
|
|
584
584
|
var import_react_query3 = require("@tanstack/react-query");
|
|
585
|
-
var
|
|
586
|
-
var
|
|
585
|
+
var import_lucide_react16 = require("lucide-react");
|
|
586
|
+
var import_ui18 = require("@behindthemusictree/ui");
|
|
587
587
|
var import_genre_tree_view4 = require("@behindthemusictree/genre-tree-view");
|
|
588
|
-
var
|
|
589
|
-
var
|
|
588
|
+
var import_genre_tree18 = require("@behindthemusictree/app-kit/genre-tree");
|
|
589
|
+
var import_genre_tree19 = require("@behindthemusictree/app-kit/genre-tree");
|
|
590
590
|
var import_popup6 = require("@behindthemusictree/app-kit/popup");
|
|
591
591
|
|
|
592
592
|
// src/genre-tree/playlist-tree/TreePerRoot.tsx
|
|
@@ -1058,6 +1058,7 @@ function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
|
|
|
1058
1058
|
}
|
|
1059
1059
|
|
|
1060
1060
|
// src/genre-tree/GenreSearch.tsx
|
|
1061
|
+
var import_react8 = require("react");
|
|
1061
1062
|
var import_lucide_react7 = require("lucide-react");
|
|
1062
1063
|
var import_ui6 = require("@behindthemusictree/ui");
|
|
1063
1064
|
var import_genre_tree2 = require("@behindthemusictree/app-kit/genre-tree");
|
|
@@ -1070,7 +1071,7 @@ function GenreSearch({
|
|
|
1070
1071
|
onPrevious,
|
|
1071
1072
|
placeholder,
|
|
1072
1073
|
onQueryChange,
|
|
1073
|
-
|
|
1074
|
+
extraGroups = []
|
|
1074
1075
|
}) {
|
|
1075
1076
|
const { query, setQuery, results, canClear, clear, select } = (0, import_genre_tree2.useGenreSearch)({
|
|
1076
1077
|
genrePlaylists,
|
|
@@ -1078,11 +1079,45 @@ function GenreSearch({
|
|
|
1078
1079
|
selectedName,
|
|
1079
1080
|
onClear
|
|
1080
1081
|
});
|
|
1081
|
-
const
|
|
1082
|
-
const
|
|
1083
|
-
const
|
|
1084
|
-
const
|
|
1085
|
-
const
|
|
1082
|
+
const [open, setOpen] = (0, import_react8.useState)(false);
|
|
1083
|
+
const active = query !== "" && query !== selectedName;
|
|
1084
|
+
const hasExtras = extraGroups.length > 0;
|
|
1085
|
+
const label = hasExtras ? "Search" : "Search a genre";
|
|
1086
|
+
const loading = active && extraGroups.some((group) => group.loading);
|
|
1087
|
+
const status = active ? extraGroups.map((group) => group.status).filter(Boolean).join(". ") : "";
|
|
1088
|
+
const groups = [
|
|
1089
|
+
{
|
|
1090
|
+
key: "genres",
|
|
1091
|
+
label: hasExtras ? "Genres" : void 0,
|
|
1092
|
+
options: results.map((genrePlaylist) => ({
|
|
1093
|
+
key: genrePlaylist.uuid,
|
|
1094
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
|
|
1095
|
+
genrePlaylist.name,
|
|
1096
|
+
genrePlaylist.isUmbrella && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "ml-2 text-xs italic text-[var(--color-text-muted)]", children: "umbrella" })
|
|
1097
|
+
] })
|
|
1098
|
+
}))
|
|
1099
|
+
},
|
|
1100
|
+
...extraGroups.map((group) => ({
|
|
1101
|
+
key: group.key,
|
|
1102
|
+
label: group.label,
|
|
1103
|
+
options: active ? group.items.map((item) => ({ key: item.key, content: item.content })) : []
|
|
1104
|
+
}))
|
|
1105
|
+
];
|
|
1106
|
+
const handlePick = (groupKey, optionKey) => {
|
|
1107
|
+
setOpen(false);
|
|
1108
|
+
if (groupKey === "genres") {
|
|
1109
|
+
const genrePlaylist = results.find((result) => result.uuid === optionKey);
|
|
1110
|
+
if (!genrePlaylist) return;
|
|
1111
|
+
select(genrePlaylist);
|
|
1112
|
+
onQueryChange?.("");
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
const item = extraGroups.find((group) => group.key === groupKey)?.items.find((entry) => entry.key === optionKey);
|
|
1116
|
+
if (!item) return;
|
|
1117
|
+
setQuery("");
|
|
1118
|
+
onQueryChange?.("");
|
|
1119
|
+
item.onSelect();
|
|
1120
|
+
};
|
|
1086
1121
|
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "genre-search relative w-[calc(var(--layout-detail-panel-w,25.5rem)-2*var(--layout-panel-inset,1rem))] shrink-0 max-md:w-full", children: [
|
|
1087
1122
|
onPrevious && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1088
1123
|
"button",
|
|
@@ -1096,47 +1131,45 @@ function GenreSearch({
|
|
|
1096
1131
|
}
|
|
1097
1132
|
),
|
|
1098
1133
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1099
|
-
import_ui6.
|
|
1134
|
+
import_ui6.Combobox,
|
|
1100
1135
|
{
|
|
1101
|
-
|
|
1136
|
+
ariaLabel: label,
|
|
1137
|
+
listLabel: `${label} results`,
|
|
1102
1138
|
value: query,
|
|
1103
|
-
onChange: (event) => {
|
|
1104
|
-
setQuery(event.target.value);
|
|
1105
|
-
onQueryChange?.(event.target.value);
|
|
1106
|
-
},
|
|
1107
1139
|
placeholder: placeholder ?? `${label}\u2026`,
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1140
|
+
groups,
|
|
1141
|
+
open,
|
|
1142
|
+
onOpenChange: setOpen,
|
|
1143
|
+
onValueChange: (value) => {
|
|
1144
|
+
setQuery(value);
|
|
1145
|
+
onQueryChange?.(value);
|
|
1146
|
+
},
|
|
1147
|
+
onPick: handlePick,
|
|
1148
|
+
status,
|
|
1149
|
+
loading: loading ? "Searching\u2026" : void 0,
|
|
1150
|
+
inputClassName: "h-[var(--layout-search-h,3rem)] pl-6 pr-20 text-base transition-[padding] duration-200 ease-out motion-reduce:transition-none bg-[var(--color-surface)] border-[var(--color-border)] rounded-full shadow-md",
|
|
1151
|
+
className: "genre-search-results z-40 rounded-xl shadow-md",
|
|
1152
|
+
listClassName: "max-h-[min(20rem,50dvh)]",
|
|
1153
|
+
optionClassName: "[@media(pointer:coarse)]:min-h-[var(--layout-touch-target)]"
|
|
1114
1154
|
}
|
|
1115
1155
|
),
|
|
1116
1156
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "absolute right-3 top-1/2 flex items-center -translate-y-1/2", children: [
|
|
1117
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
"aria-hidden": "true",
|
|
1134
|
-
className: `absolute inset-2 w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? "opacity-100 delay-300 animate-spin motion-reduce:animate-none" : "opacity-0"}`
|
|
1135
|
-
}
|
|
1136
|
-
)
|
|
1137
|
-
]
|
|
1138
|
-
}
|
|
1139
|
-
),
|
|
1157
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { "data-loading": loading, className: "relative p-2 text-[var(--color-text-muted)] pointer-events-none", children: [
|
|
1158
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1159
|
+
import_lucide_react7.Search,
|
|
1160
|
+
{
|
|
1161
|
+
"aria-hidden": "true",
|
|
1162
|
+
className: `w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? "opacity-0" : ""}`
|
|
1163
|
+
}
|
|
1164
|
+
),
|
|
1165
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1166
|
+
import_lucide_react7.LoaderCircle,
|
|
1167
|
+
{
|
|
1168
|
+
"aria-hidden": "true",
|
|
1169
|
+
className: `absolute inset-2 w-6 h-6 transition-opacity duration-200 motion-reduce:transition-none ${loading ? "opacity-100 delay-300 animate-spin motion-reduce:animate-none" : "opacity-0"}`
|
|
1170
|
+
}
|
|
1171
|
+
)
|
|
1172
|
+
] }),
|
|
1140
1173
|
canClear && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1141
1174
|
"button",
|
|
1142
1175
|
{
|
|
@@ -1150,39 +1183,6 @@ function GenreSearch({
|
|
|
1150
1183
|
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react7.X, { "aria-hidden": "true", className: "w-6 h-6" })
|
|
1151
1184
|
}
|
|
1152
1185
|
)
|
|
1153
|
-
] }),
|
|
1154
|
-
extraResults?.status && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { role: "status", "aria-live": "polite", className: "sr-only", children: extraResults.status }),
|
|
1155
|
-
showList && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("ul", { className: "genre-search-results absolute z-40 w-full max-h-[min(16rem,50dvh)] mt-1 overflow-y-auto bg-[var(--color-surface)] border border-[var(--color-border)] rounded-xl shadow-md", children: [
|
|
1156
|
-
extraResults && results.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("li", { className: heading, children: "Genres" }),
|
|
1157
|
-
results.map((genrePlaylist) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
1158
|
-
"button",
|
|
1159
|
-
{
|
|
1160
|
-
type: "button",
|
|
1161
|
-
className: "w-full px-3 py-1.5 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] text-left hover:bg-[var(--color-surface-muted)]",
|
|
1162
|
-
onClick: () => {
|
|
1163
|
-
select(genrePlaylist);
|
|
1164
|
-
onQueryChange?.("");
|
|
1165
|
-
},
|
|
1166
|
-
children: [
|
|
1167
|
-
genrePlaylist.name,
|
|
1168
|
-
genrePlaylist.isUmbrella && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "ml-2 text-xs italic text-[var(--color-text-muted)]", children: "umbrella" })
|
|
1169
|
-
]
|
|
1170
|
-
}
|
|
1171
|
-
) }, genrePlaylist.uuid)),
|
|
1172
|
-
extraResults && extraItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("li", { className: heading, children: extraResults.label }),
|
|
1173
|
-
extraItems.map((item) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1174
|
-
"button",
|
|
1175
|
-
{
|
|
1176
|
-
type: "button",
|
|
1177
|
-
className: "w-full px-3 py-1.5 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] text-left hover:bg-[var(--color-surface-muted)]",
|
|
1178
|
-
onClick: () => {
|
|
1179
|
-
setQuery("");
|
|
1180
|
-
onQueryChange?.("");
|
|
1181
|
-
item.onSelect();
|
|
1182
|
-
},
|
|
1183
|
-
children: item.content
|
|
1184
|
-
}
|
|
1185
|
-
) }, item.key))
|
|
1186
1186
|
] })
|
|
1187
1187
|
] });
|
|
1188
1188
|
}
|
|
@@ -1198,7 +1198,7 @@ var import_lucide_react10 = require("lucide-react");
|
|
|
1198
1198
|
var import_popup4 = require("@behindthemusictree/app-kit/popup");
|
|
1199
1199
|
|
|
1200
1200
|
// src/genre-tree/SharePopup.tsx
|
|
1201
|
-
var
|
|
1201
|
+
var import_react9 = require("react");
|
|
1202
1202
|
var import_lucide_react9 = require("lucide-react");
|
|
1203
1203
|
var import_fa3 = require("react-icons/fa");
|
|
1204
1204
|
var import_si = require("react-icons/si");
|
|
@@ -1230,8 +1230,8 @@ function ShareDestinationButton({
|
|
|
1230
1230
|
var import_lucide_react8 = require("lucide-react");
|
|
1231
1231
|
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
1232
1232
|
function SharePreviewCard({ target, url }) {
|
|
1233
|
-
const Icon =
|
|
1234
|
-
const typeLabel =
|
|
1233
|
+
const Icon = { genre: import_lucide_react8.Music2, artist: import_lucide_react8.UserRound, song: import_lucide_react8.Music }[target.kind];
|
|
1234
|
+
const typeLabel = { genre: "Genre", artist: "Artist", song: "Song" }[target.kind];
|
|
1235
1235
|
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "rounded-3xl bg-[var(--color-surface-muted)] p-4", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex min-h-32 overflow-hidden rounded-2xl bg-[var(--color-surface)] shadow-lg", children: [
|
|
1236
1236
|
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "flex w-28 shrink-0 items-center justify-center bg-[var(--color-primary)]/15 text-[var(--color-primary)]", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Icon, { "aria-hidden": "true", className: "h-10 w-10" }) }),
|
|
1237
1237
|
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col justify-center gap-1 p-4", children: [
|
|
@@ -1276,20 +1276,20 @@ async function copyText(text) {
|
|
|
1276
1276
|
}
|
|
1277
1277
|
}
|
|
1278
1278
|
function SharePopup({ target, url, onClose }) {
|
|
1279
|
-
const [absoluteUrl, setAbsoluteUrl] = (0,
|
|
1280
|
-
const [copyStatus, setCopyStatus] = (0,
|
|
1281
|
-
const [canUseNativeShare, setCanUseNativeShare] = (0,
|
|
1282
|
-
const [shareError, setShareError] = (0,
|
|
1283
|
-
const [toastId, setToastId] = (0,
|
|
1284
|
-
(0,
|
|
1279
|
+
const [absoluteUrl, setAbsoluteUrl] = (0, import_react9.useState)(url);
|
|
1280
|
+
const [copyStatus, setCopyStatus] = (0, import_react9.useState)("idle");
|
|
1281
|
+
const [canUseNativeShare, setCanUseNativeShare] = (0, import_react9.useState)(false);
|
|
1282
|
+
const [shareError, setShareError] = (0, import_react9.useState)(null);
|
|
1283
|
+
const [toastId, setToastId] = (0, import_react9.useState)(0);
|
|
1284
|
+
(0, import_react9.useEffect)(() => {
|
|
1285
1285
|
setAbsoluteUrl(resolveAbsoluteUrl(url) ?? "");
|
|
1286
1286
|
setCopyStatus("idle");
|
|
1287
1287
|
setShareError(null);
|
|
1288
1288
|
}, [url]);
|
|
1289
|
-
(0,
|
|
1289
|
+
(0, import_react9.useEffect)(() => {
|
|
1290
1290
|
setCanUseNativeShare(typeof navigator !== "undefined" && typeof navigator.share === "function");
|
|
1291
1291
|
}, []);
|
|
1292
|
-
const destinationUrls = (0,
|
|
1292
|
+
const destinationUrls = (0, import_react9.useMemo)(() => {
|
|
1293
1293
|
if (!absoluteUrl) return null;
|
|
1294
1294
|
const message = `${target.name} \u2014 ${absoluteUrl}`;
|
|
1295
1295
|
return {
|
|
@@ -1307,7 +1307,7 @@ function SharePopup({ target, url, onClose }) {
|
|
|
1307
1307
|
setCopyStatus(copied ? "idle" : "failed");
|
|
1308
1308
|
if (copied) setToastId((id) => id + 1);
|
|
1309
1309
|
};
|
|
1310
|
-
const dismissToast = (0,
|
|
1310
|
+
const dismissToast = (0, import_react9.useCallback)(() => setToastId(0), []);
|
|
1311
1311
|
const handleNativeShare = async () => {
|
|
1312
1312
|
if (!absoluteUrl || !navigator.share) return;
|
|
1313
1313
|
setShareError(null);
|
|
@@ -1513,13 +1513,13 @@ var import_ui8 = require("@behindthemusictree/ui");
|
|
|
1513
1513
|
var import_genre_tree9 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1514
1514
|
|
|
1515
1515
|
// src/genre-tree/ClampedText.tsx
|
|
1516
|
-
var
|
|
1516
|
+
var import_react10 = require("react");
|
|
1517
1517
|
var import_jsx_runtime22 = require("react/jsx-runtime");
|
|
1518
1518
|
function ClampedText({ children, className: className2 = "" }) {
|
|
1519
|
-
const ref = (0,
|
|
1520
|
-
const [expanded, setExpanded] = (0,
|
|
1521
|
-
const [overflows, setOverflows] = (0,
|
|
1522
|
-
(0,
|
|
1519
|
+
const ref = (0, import_react10.useRef)(null);
|
|
1520
|
+
const [expanded, setExpanded] = (0, import_react10.useState)(false);
|
|
1521
|
+
const [overflows, setOverflows] = (0, import_react10.useState)(false);
|
|
1522
|
+
(0, import_react10.useLayoutEffect)(() => {
|
|
1523
1523
|
const element = ref.current;
|
|
1524
1524
|
if (!element || expanded) return;
|
|
1525
1525
|
setOverflows(element.scrollHeight > element.clientHeight);
|
|
@@ -1540,7 +1540,7 @@ function ClampedText({ children, className: className2 = "" }) {
|
|
|
1540
1540
|
}
|
|
1541
1541
|
|
|
1542
1542
|
// src/genre-tree/GenrePlaylistTracks.tsx
|
|
1543
|
-
var
|
|
1543
|
+
var import_react11 = require("react");
|
|
1544
1544
|
var import_react_query = require("@tanstack/react-query");
|
|
1545
1545
|
var import_zod = require("zod");
|
|
1546
1546
|
var import_fa4 = require("react-icons/fa");
|
|
@@ -1608,6 +1608,7 @@ function GenrePlaylistTracks({
|
|
|
1608
1608
|
scope,
|
|
1609
1609
|
getBackendBaseUrl,
|
|
1610
1610
|
renderArtistLink,
|
|
1611
|
+
renderSongLink,
|
|
1611
1612
|
renderSongLeading,
|
|
1612
1613
|
renderSongActions,
|
|
1613
1614
|
renderTracksToolbar,
|
|
@@ -1638,8 +1639,8 @@ function GenrePlaylistTracks({
|
|
|
1638
1639
|
getNextPageParam: (lastPage) => lastPage.nextPage ?? void 0,
|
|
1639
1640
|
enabled: scope !== "me" || sessionRestored && !!session?.accessToken
|
|
1640
1641
|
});
|
|
1641
|
-
const sentinelRef = (0,
|
|
1642
|
-
(0,
|
|
1642
|
+
const sentinelRef = (0, import_react11.useRef)(null);
|
|
1643
|
+
(0, import_react11.useEffect)(() => {
|
|
1643
1644
|
const sentinel = sentinelRef.current;
|
|
1644
1645
|
if (!hasNextPage || isFetchingNextPage || !sentinel) return;
|
|
1645
1646
|
const observer = new IntersectionObserver((entries) => {
|
|
@@ -1678,7 +1679,7 @@ function GenrePlaylistTracks({
|
|
|
1678
1679
|
const isCurrent = playerTrackObject?.track.id === track.uuid;
|
|
1679
1680
|
const isPlaying = isCurrent && playState === import_player5.PlayStates.PLAYING;
|
|
1680
1681
|
const text = /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { className: TEXT_CELL, children: [
|
|
1681
|
-
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: TITLE_CELL, children: (0, import_genre_tree6.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
|
|
1682
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: TITLE_CELL, children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree6.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
|
|
1682
1683
|
track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: SEPARATOR, children: " \u2014 " }) : null,
|
|
1683
1684
|
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_genre_tree8.ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
|
|
1684
1685
|
track.album && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: ALBUM_CELL, children: track.album.name })
|
|
@@ -1745,6 +1746,7 @@ function GenreDetailExtras({
|
|
|
1745
1746
|
criteriaOverviewSchema,
|
|
1746
1747
|
renderGenreDetailExtras,
|
|
1747
1748
|
renderArtistLink,
|
|
1749
|
+
renderSongLink,
|
|
1748
1750
|
renderSummary,
|
|
1749
1751
|
renderEssentialTracks,
|
|
1750
1752
|
family,
|
|
@@ -1822,6 +1824,7 @@ function GenreDetailExtras({
|
|
|
1822
1824
|
scope,
|
|
1823
1825
|
getBackendBaseUrl,
|
|
1824
1826
|
renderArtistLink,
|
|
1827
|
+
renderSongLink,
|
|
1825
1828
|
...trackSlots
|
|
1826
1829
|
}
|
|
1827
1830
|
)
|
|
@@ -1830,7 +1833,7 @@ function GenreDetailExtras({
|
|
|
1830
1833
|
|
|
1831
1834
|
// src/genre-tree/GenreInfoPanelBody.tsx
|
|
1832
1835
|
var import_jsx_runtime26 = require("react/jsx-runtime");
|
|
1833
|
-
var
|
|
1836
|
+
var import_react12 = require("react");
|
|
1834
1837
|
function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
|
|
1835
1838
|
const { isPlaying, setIsPlaying } = (0, import_player6.usePlayer)();
|
|
1836
1839
|
const isCurrentTrackList = playback.playingNodeId === genrePlaylist.uuid;
|
|
@@ -1867,7 +1870,7 @@ function GenreInfoPanelBody({
|
|
|
1867
1870
|
return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_jsx_runtime26.Fragment, { children: [
|
|
1868
1871
|
toolbar,
|
|
1869
1872
|
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)(GenreNodeActionBand, { ...band, genrePlaylist, playback }),
|
|
1870
|
-
/* @__PURE__ */ (0,
|
|
1873
|
+
/* @__PURE__ */ (0, import_react12.createElement)(
|
|
1871
1874
|
GenreDetailExtras,
|
|
1872
1875
|
{
|
|
1873
1876
|
...extras,
|
|
@@ -1927,9 +1930,9 @@ function PanelEdgeTab({ side, edge, center, controls, className: className2 }) {
|
|
|
1927
1930
|
}
|
|
1928
1931
|
|
|
1929
1932
|
// src/genre-tree/useLeftDetailPanelLayout.ts
|
|
1930
|
-
var
|
|
1933
|
+
var import_react13 = require("react");
|
|
1931
1934
|
function useLeftDetailPanelLayout(isPanelShown, enabled = true) {
|
|
1932
|
-
(0,
|
|
1935
|
+
(0, import_react13.useEffect)(() => {
|
|
1933
1936
|
if (!enabled || isPanelShown) return void 0;
|
|
1934
1937
|
const root = document.documentElement;
|
|
1935
1938
|
root.style.setProperty("--layout-left-w", "0px");
|
|
@@ -1940,7 +1943,7 @@ function useLeftDetailPanelLayout(isPanelShown, enabled = true) {
|
|
|
1940
1943
|
}
|
|
1941
1944
|
|
|
1942
1945
|
// src/genre-tree/ChangeParentPopup.tsx
|
|
1943
|
-
var
|
|
1946
|
+
var import_react14 = require("react");
|
|
1944
1947
|
var import_genre_tree10 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1945
1948
|
var import_jsx_runtime28 = require("react/jsx-runtime");
|
|
1946
1949
|
function ChangeParentPopup({
|
|
@@ -1952,7 +1955,7 @@ function ChangeParentPopup({
|
|
|
1952
1955
|
onParentChanged
|
|
1953
1956
|
}) {
|
|
1954
1957
|
const { mutate, isPending, formErrors } = (0, import_genre_tree10.useUpdateGenre)(scope, getBackendBaseUrl);
|
|
1955
|
-
const candidates = (0,
|
|
1958
|
+
const candidates = (0, import_react14.useMemo)(
|
|
1956
1959
|
() => (0, import_genre_tree10.getReparentCandidates)(genrePlaylists, genrePlaylist.uuid),
|
|
1957
1960
|
[genrePlaylists, genrePlaylist.uuid]
|
|
1958
1961
|
);
|
|
@@ -1970,7 +1973,7 @@ function ChangeParentPopup({
|
|
|
1970
1973
|
}
|
|
1971
1974
|
);
|
|
1972
1975
|
};
|
|
1973
|
-
(0,
|
|
1976
|
+
(0, import_react14.useEffect)(() => {
|
|
1974
1977
|
const handleKeyDown = (event) => {
|
|
1975
1978
|
if (event.key === "Escape") onClose();
|
|
1976
1979
|
};
|
|
@@ -2158,7 +2161,7 @@ function ArtistDiscography({ albums }) {
|
|
|
2158
2161
|
}
|
|
2159
2162
|
|
|
2160
2163
|
// src/genre-tree/artist/ArtistSongs.tsx
|
|
2161
|
-
var
|
|
2164
|
+
var import_react15 = require("react");
|
|
2162
2165
|
var import_react_query2 = require("@tanstack/react-query");
|
|
2163
2166
|
var import_lucide_react14 = require("lucide-react");
|
|
2164
2167
|
var import_ui13 = require("@behindthemusictree/ui");
|
|
@@ -2231,7 +2234,8 @@ function TrackItem({
|
|
|
2231
2234
|
position,
|
|
2232
2235
|
renderDuration,
|
|
2233
2236
|
renderActions,
|
|
2234
|
-
renderArtistLink
|
|
2237
|
+
renderArtistLink,
|
|
2238
|
+
renderSongLink
|
|
2235
2239
|
}) {
|
|
2236
2240
|
const { handlePlayPauseAction, playerTrackObject, loadTrackForPlayer } = (0, import_player9.usePlayer)();
|
|
2237
2241
|
const { trackList, selectedTrack, toTrackAtPosition, runtimeUnplayableTrackIds } = (0, import_genre_tree11.useTrackList)();
|
|
@@ -2271,7 +2275,7 @@ function TrackItem({
|
|
|
2271
2275
|
}
|
|
2272
2276
|
),
|
|
2273
2277
|
/* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
|
|
2274
|
-
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: (0, import_genre_tree12.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
|
|
2278
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree12.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
|
|
2275
2279
|
track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_genre_tree13.ArtistNames, { artists: track.artists, renderArtistLink }) }) : ""
|
|
2276
2280
|
] }),
|
|
2277
2281
|
/* @__PURE__ */ (0, import_jsx_runtime35.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_runtime35.jsx)("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
|
|
@@ -2296,9 +2300,9 @@ var SORTS = [
|
|
|
2296
2300
|
{ ordering: "title", label: "Title" },
|
|
2297
2301
|
{ ordering: "-albumYear", label: "Year" }
|
|
2298
2302
|
];
|
|
2299
|
-
function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
2300
|
-
const [page, setPage] = (0,
|
|
2301
|
-
const [ordering, setOrdering] = (0,
|
|
2303
|
+
function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLink }) {
|
|
2304
|
+
const [page, setPage] = (0, import_react15.useState)(1);
|
|
2305
|
+
const [ordering, setOrdering] = (0, import_react15.useState)("title");
|
|
2302
2306
|
const { fetch } = (0, import_transport8.useFetchWrapper)(getBackendBaseUrl);
|
|
2303
2307
|
const { playNewTrackListFromArtist } = (0, import_genre_tree14.useTrackList)();
|
|
2304
2308
|
const { playerTrackObject } = (0, import_player10.usePlayer)();
|
|
@@ -2357,7 +2361,8 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink }) {
|
|
|
2357
2361
|
{
|
|
2358
2362
|
track: song,
|
|
2359
2363
|
position: (page - 1) * data.pageSize + index + 1,
|
|
2360
|
-
renderArtistLink
|
|
2364
|
+
renderArtistLink,
|
|
2365
|
+
renderSongLink
|
|
2361
2366
|
}
|
|
2362
2367
|
) }, song.uuid)) }),
|
|
2363
2368
|
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_ui13.Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
|
|
@@ -2372,6 +2377,7 @@ function useArtistPanel({
|
|
|
2372
2377
|
getBackendBaseUrl,
|
|
2373
2378
|
getShareUrl,
|
|
2374
2379
|
renderArtistLink,
|
|
2380
|
+
renderSongLink,
|
|
2375
2381
|
expanded,
|
|
2376
2382
|
onExpandedChange,
|
|
2377
2383
|
onClose
|
|
@@ -2424,7 +2430,8 @@ function useArtistPanel({
|
|
|
2424
2430
|
{
|
|
2425
2431
|
artist: { uuid: artist.uuid, name: artist.name },
|
|
2426
2432
|
getBackendBaseUrl,
|
|
2427
|
-
renderArtistLink
|
|
2433
|
+
renderArtistLink,
|
|
2434
|
+
renderSongLink
|
|
2428
2435
|
}
|
|
2429
2436
|
)
|
|
2430
2437
|
] }),
|
|
@@ -2434,31 +2441,225 @@ function useArtistPanel({
|
|
|
2434
2441
|
}
|
|
2435
2442
|
}
|
|
2436
2443
|
|
|
2437
|
-
// src/genre-tree/
|
|
2438
|
-
var
|
|
2444
|
+
// src/genre-tree/song/useSongPanel.tsx
|
|
2445
|
+
var import_ui16 = require("@behindthemusictree/ui");
|
|
2446
|
+
var import_genre_tree17 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2447
|
+
|
|
2448
|
+
// src/genre-tree/song/SongPanelContent.tsx
|
|
2449
|
+
var import_react16 = require("react");
|
|
2450
|
+
var import_lucide_react15 = require("lucide-react");
|
|
2439
2451
|
var import_ui15 = require("@behindthemusictree/ui");
|
|
2452
|
+
var import_player11 = require("@behindthemusictree/app-kit/player");
|
|
2453
|
+
var import_genre_tree16 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2440
2454
|
var import_jsx_runtime38 = require("react/jsx-runtime");
|
|
2455
|
+
var numberFormat = new Intl.NumberFormat("en");
|
|
2456
|
+
var dateFormat = new Intl.DateTimeFormat("en", { dateStyle: "medium" });
|
|
2457
|
+
var externalLink = "text-[var(--color-primary)] hover:underline focus-visible:underline";
|
|
2458
|
+
function SongHistory({ uuid, getBackendBaseUrl }) {
|
|
2459
|
+
const [open, setOpen] = (0, import_react16.useState)(false);
|
|
2460
|
+
const history = (0, import_genre_tree16.useSongHistory)(uuid, getBackendBaseUrl, open);
|
|
2461
|
+
let body = null;
|
|
2462
|
+
if (history.status === "loading") body = /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_ui15.Skeleton, { className: "h-10", "aria-busy": "true" });
|
|
2463
|
+
else if (history.status === "error") body = /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("p", { className: "text-red-600", children: "Could not load the history." });
|
|
2464
|
+
else if (history.status === "success")
|
|
2465
|
+
body = history.entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("li", { className: "py-1.5 text-sm", children: [
|
|
2466
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "font-medium", children: entry.action }),
|
|
2467
|
+
entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
|
|
2468
|
+
" ",
|
|
2469
|
+
"\u2014 ",
|
|
2470
|
+
entry.oldValue ?? "none",
|
|
2471
|
+
" \u2192 ",
|
|
2472
|
+
entry.newValue ?? "none"
|
|
2473
|
+
] }) : null,
|
|
2474
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("span", { className: "block text-xs text-[var(--color-text-muted)]", children: [
|
|
2475
|
+
entry.actorPseudo ?? "Anonymous",
|
|
2476
|
+
" \xB7 ",
|
|
2477
|
+
dateFormat.format(new Date(entry.createdOn))
|
|
2478
|
+
] })
|
|
2479
|
+
] }, entry.uuid)) });
|
|
2480
|
+
return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
|
|
2481
|
+
"details",
|
|
2482
|
+
{
|
|
2483
|
+
onToggle: (event) => setOpen(event.currentTarget.open),
|
|
2484
|
+
className: "px-6 pb-2.5 mt-2.5 border-t border-[var(--color-border)]",
|
|
2485
|
+
children: [
|
|
2486
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)("summary", { className: "mt-2.5 cursor-pointer text-[0.6875rem] font-semibold uppercase text-[var(--color-text-muted)]", children: "History" }),
|
|
2487
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "mt-1.5", children: body })
|
|
2488
|
+
]
|
|
2489
|
+
}
|
|
2490
|
+
);
|
|
2491
|
+
}
|
|
2492
|
+
function SongPanelContent({
|
|
2493
|
+
song,
|
|
2494
|
+
scope,
|
|
2495
|
+
getBackendBaseUrl,
|
|
2496
|
+
shareUrl,
|
|
2497
|
+
renderArtistLink,
|
|
2498
|
+
genrePath,
|
|
2499
|
+
onSelectGenre
|
|
2500
|
+
}) {
|
|
2501
|
+
const { playNewTrackListFromTrackUuid } = (0, import_genre_tree16.useTrackList)();
|
|
2502
|
+
const { playerTrackObject, playState, handlePlayPauseAction } = (0, import_player11.usePlayer)();
|
|
2503
|
+
const unplayableReason = (0, import_genre_tree16.songUnplayableReason)(song);
|
|
2504
|
+
const isCurrent = playerTrackObject?.track.id === song.uuid;
|
|
2505
|
+
const isPlaying = isCurrent && playState === import_player11.PlayStates.PLAYING;
|
|
2506
|
+
const shareTarget = { kind: "song", uuid: song.uuid, name: song.title, scope };
|
|
2507
|
+
const musicBrainzUrl = (0, import_genre_tree16.musicBrainzRecordingUrl)(song);
|
|
2508
|
+
const fields = [];
|
|
2509
|
+
if (song.genre) {
|
|
2510
|
+
const path = genrePath.length > 0 ? genrePath : null;
|
|
2511
|
+
fields.push({
|
|
2512
|
+
label: "Genre",
|
|
2513
|
+
value: path ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("span", { children: [
|
|
2514
|
+
index > 0 && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { "aria-hidden": "true", children: "\u203A " }),
|
|
2515
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)("button", { type: "button", onClick: () => onSelectGenre(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
|
|
2516
|
+
] }, genrePlaylist.uuid)) }) : song.genre.name
|
|
2517
|
+
});
|
|
2518
|
+
} else {
|
|
2519
|
+
fields.push({ label: "Genre", value: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
|
|
2520
|
+
}
|
|
2521
|
+
if (song.firstReleaseYear) fields.push({ label: "First released", value: song.firstReleaseYear });
|
|
2522
|
+
const viewCount = song.youtubeVideo?.viewCount;
|
|
2523
|
+
if (viewCount !== null && viewCount !== void 0)
|
|
2524
|
+
fields.push({ label: "YouTube views", value: numberFormat.format(viewCount) });
|
|
2525
|
+
fields.push({ label: "Plays", value: numberFormat.format(song.playCount) });
|
|
2526
|
+
const links = [];
|
|
2527
|
+
if (musicBrainzUrl)
|
|
2528
|
+
links.push(
|
|
2529
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
|
|
2530
|
+
);
|
|
2531
|
+
if (song.youtubeVideo)
|
|
2532
|
+
links.push(
|
|
2533
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
|
|
2534
|
+
"a",
|
|
2535
|
+
{
|
|
2536
|
+
href: `https://www.youtube.com/watch?v=${encodeURIComponent(song.youtubeVideo.id)}`,
|
|
2537
|
+
target: "_blank",
|
|
2538
|
+
rel: "noopener noreferrer",
|
|
2539
|
+
className: externalLink,
|
|
2540
|
+
children: "YouTube"
|
|
2541
|
+
},
|
|
2542
|
+
"yt"
|
|
2543
|
+
)
|
|
2544
|
+
);
|
|
2545
|
+
if (links.length > 0)
|
|
2546
|
+
fields.push({
|
|
2547
|
+
label: "Links",
|
|
2548
|
+
value: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "flex gap-3", children: links })
|
|
2549
|
+
});
|
|
2550
|
+
return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_jsx_runtime38.Fragment, { children: [
|
|
2551
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
|
|
2552
|
+
unplayableReason ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("p", { className: "flex items-center px-2.5 text-sm text-[var(--color-text-muted)]", children: unplayableReason }) : /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
|
|
2553
|
+
"button",
|
|
2554
|
+
{
|
|
2555
|
+
type: "button",
|
|
2556
|
+
onClick: () => isCurrent ? handlePlayPauseAction() : playNewTrackListFromTrackUuid(song, scope),
|
|
2557
|
+
className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none",
|
|
2558
|
+
children: [
|
|
2559
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "flex items-center justify-center w-10 h-10 rounded-full group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)] bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]", children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_lucide_react15.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_lucide_react15.Play, { "aria-hidden": "true", className: "w-5 h-5" }) }),
|
|
2560
|
+
isPlaying ? "Pause" : "Play"
|
|
2561
|
+
]
|
|
2562
|
+
}
|
|
2563
|
+
),
|
|
2564
|
+
shareUrl && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
|
|
2565
|
+
] }) }),
|
|
2566
|
+
song.artists && song.artists.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("p", { className: "px-6 pt-2.5 text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_genre_tree16.ArtistNames, { artists: song.artists, renderArtistLink }) }),
|
|
2567
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_ui15.FieldList, { items: fields }),
|
|
2568
|
+
song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_ui15.PanelSection, { title: "Essential in", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
|
|
2569
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
|
|
2570
|
+
] });
|
|
2571
|
+
}
|
|
2572
|
+
|
|
2573
|
+
// src/genre-tree/song/useSongPanel.tsx
|
|
2574
|
+
var import_jsx_runtime39 = require("react/jsx-runtime");
|
|
2575
|
+
function useSongPanel({
|
|
2576
|
+
uuid,
|
|
2577
|
+
scope,
|
|
2578
|
+
getBackendBaseUrl,
|
|
2579
|
+
getShareUrl,
|
|
2580
|
+
renderArtistLink,
|
|
2581
|
+
getGenrePath,
|
|
2582
|
+
onSelectGenre,
|
|
2583
|
+
expanded,
|
|
2584
|
+
onExpandedChange,
|
|
2585
|
+
onClose
|
|
2586
|
+
}) {
|
|
2587
|
+
const result = (0, import_genre_tree17.useSong)(uuid, getBackendBaseUrl);
|
|
2588
|
+
const panel = { expanded, onExpandedChange, onClose };
|
|
2589
|
+
switch (result.status) {
|
|
2590
|
+
case "idle":
|
|
2591
|
+
return void 0;
|
|
2592
|
+
case "loading":
|
|
2593
|
+
return {
|
|
2594
|
+
...panel,
|
|
2595
|
+
title: "Song",
|
|
2596
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
|
|
2597
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_ui16.Skeleton, { className: "h-3 w-16" }),
|
|
2598
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_ui16.Skeleton, { className: "h-4 w-32" }),
|
|
2599
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_ui16.Skeleton, { className: "h-3 w-16" }),
|
|
2600
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_ui16.Skeleton, { className: "h-4 w-24" })
|
|
2601
|
+
] })
|
|
2602
|
+
};
|
|
2603
|
+
case "not-found":
|
|
2604
|
+
return {
|
|
2605
|
+
...panel,
|
|
2606
|
+
title: "Song not found",
|
|
2607
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This song does not exist." })
|
|
2608
|
+
};
|
|
2609
|
+
case "error":
|
|
2610
|
+
return {
|
|
2611
|
+
...panel,
|
|
2612
|
+
title: "Song",
|
|
2613
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this song." })
|
|
2614
|
+
};
|
|
2615
|
+
case "success": {
|
|
2616
|
+
const { song } = result;
|
|
2617
|
+
const shareUrl = getShareUrl?.({ kind: "song", uuid: song.uuid, name: song.title, scope });
|
|
2618
|
+
return {
|
|
2619
|
+
...panel,
|
|
2620
|
+
title: song.title,
|
|
2621
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2622
|
+
SongPanelContent,
|
|
2623
|
+
{
|
|
2624
|
+
song,
|
|
2625
|
+
scope,
|
|
2626
|
+
getBackendBaseUrl,
|
|
2627
|
+
shareUrl: shareUrl ?? void 0,
|
|
2628
|
+
renderArtistLink,
|
|
2629
|
+
genrePath: song.genre ? getGenrePath(song.genre) : [],
|
|
2630
|
+
onSelectGenre
|
|
2631
|
+
}
|
|
2632
|
+
)
|
|
2633
|
+
};
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2638
|
+
// src/genre-tree/SizeMetricToggle.tsx
|
|
2639
|
+
var import_md2 = require("react-icons/md");
|
|
2640
|
+
var import_ui17 = require("@behindthemusictree/ui");
|
|
2641
|
+
var import_jsx_runtime40 = require("react/jsx-runtime");
|
|
2441
2642
|
function SizeMetricToggle({
|
|
2442
2643
|
value,
|
|
2443
2644
|
onChange
|
|
2444
2645
|
}) {
|
|
2445
|
-
return /* @__PURE__ */ (0,
|
|
2446
|
-
|
|
2646
|
+
return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2647
|
+
import_ui17.SegmentedControl,
|
|
2447
2648
|
{
|
|
2448
2649
|
variant: "floating",
|
|
2449
2650
|
"aria-label": "Size nodes by",
|
|
2450
2651
|
value,
|
|
2451
2652
|
onChange,
|
|
2452
2653
|
options: [
|
|
2453
|
-
{ value: "songs", label: /* @__PURE__ */ (0,
|
|
2454
|
-
{ value: "views", label: /* @__PURE__ */ (0,
|
|
2654
|
+
{ value: "songs", label: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_md2.MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
|
|
2655
|
+
{ value: "views", label: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_md2.MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
|
|
2455
2656
|
]
|
|
2456
2657
|
}
|
|
2457
2658
|
);
|
|
2458
2659
|
}
|
|
2459
2660
|
|
|
2460
2661
|
// src/genre-tree/GenreTreeView.tsx
|
|
2461
|
-
var
|
|
2662
|
+
var import_jsx_runtime41 = require("react/jsx-runtime");
|
|
2462
2663
|
var PREVIOUS_BUTTON_CSS = `
|
|
2463
2664
|
.genre-tree-view .search-overlay .gtv-info-panel-previous { display: none; }
|
|
2464
2665
|
.genre-tree-view:has(.search-overlay .gtv-info-panel-previous) .genre-search input { padding-left: 2.75rem; }
|
|
@@ -2502,6 +2703,13 @@ function GenreTreeView({
|
|
|
2502
2703
|
artistSearch = true,
|
|
2503
2704
|
onArtistSelect,
|
|
2504
2705
|
artistSearchBaseUrl,
|
|
2706
|
+
songUuid: controlledSongUuid,
|
|
2707
|
+
onSongUuidChange,
|
|
2708
|
+
songExpanded: controlledSongExpanded,
|
|
2709
|
+
onSongExpandedChange,
|
|
2710
|
+
songSearch = true,
|
|
2711
|
+
onSongSelect,
|
|
2712
|
+
renderSongLink,
|
|
2505
2713
|
renderSummary,
|
|
2506
2714
|
renderEssentialTracks,
|
|
2507
2715
|
renderSongLeading,
|
|
@@ -2516,14 +2724,21 @@ function GenreTreeView({
|
|
|
2516
2724
|
placement = "default",
|
|
2517
2725
|
...focus
|
|
2518
2726
|
}) {
|
|
2519
|
-
const getShareUrlRef = (0,
|
|
2727
|
+
const getShareUrlRef = (0, import_react17.useRef)(getShareUrl);
|
|
2520
2728
|
getShareUrlRef.current = getShareUrl;
|
|
2521
|
-
const [internalArtistUuid, setInternalArtistUuid] = (0,
|
|
2729
|
+
const [internalArtistUuid, setInternalArtistUuid] = (0, import_react17.useState)(null);
|
|
2522
2730
|
const isArtistControlled = controlledArtistUuid !== void 0;
|
|
2523
2731
|
const artistUuid = isArtistControlled ? controlledArtistUuid : internalArtistUuid;
|
|
2524
|
-
const [
|
|
2732
|
+
const [internalSongUuid, setInternalSongUuid] = (0, import_react17.useState)(null);
|
|
2733
|
+
const isSongControlled = controlledSongUuid !== void 0;
|
|
2734
|
+
const songUuid = isSongControlled ? controlledSongUuid : internalSongUuid;
|
|
2735
|
+
const changeSong = (uuid) => {
|
|
2736
|
+
if (!isSongControlled) setInternalSongUuid(uuid);
|
|
2737
|
+
onSongUuidChange?.(uuid);
|
|
2738
|
+
};
|
|
2739
|
+
const [internalSizeMetric, setInternalSizeMetric] = (0, import_react17.useState)("songs");
|
|
2525
2740
|
const sizeMetric = controlledSizeMetric ?? internalSizeMetric;
|
|
2526
|
-
const deferredSizeMetric = (0,
|
|
2741
|
+
const deferredSizeMetric = (0, import_react17.useDeferredValue)(sizeMetric);
|
|
2527
2742
|
const changeSizeMetric = (metric) => {
|
|
2528
2743
|
if (controlledSizeMetric === void 0) setInternalSizeMetric(metric);
|
|
2529
2744
|
onSizeMetricChange?.(metric);
|
|
@@ -2550,7 +2765,7 @@ function GenreTreeView({
|
|
|
2550
2765
|
allowWheelRotation: internalAllowWheelRotation,
|
|
2551
2766
|
toggleWheelRotation,
|
|
2552
2767
|
onNodeHover: handleNodeHover
|
|
2553
|
-
} = (0,
|
|
2768
|
+
} = (0, import_genre_tree19.useGenreTreeViewState)({
|
|
2554
2769
|
scope,
|
|
2555
2770
|
getBackendBaseUrl,
|
|
2556
2771
|
viewMode: controlledViewMode,
|
|
@@ -2560,7 +2775,7 @@ function GenreTreeView({
|
|
|
2560
2775
|
artistUuid
|
|
2561
2776
|
});
|
|
2562
2777
|
const restoreRevision = viewportRestore?.revision;
|
|
2563
|
-
const [stackedRestoreOwner, setStackedRestoreOwner] = (0,
|
|
2778
|
+
const [stackedRestoreOwner, setStackedRestoreOwner] = (0, import_react17.useState)({ revision: restoreRevision, rootUuid: selectedGenrePlaylist?.root.uuid });
|
|
2564
2779
|
let restoreRootUuid = stackedRestoreOwner.rootUuid;
|
|
2565
2780
|
if (stackedRestoreOwner.revision !== restoreRevision) {
|
|
2566
2781
|
restoreRootUuid = selectedGenrePlaylist?.root.uuid;
|
|
@@ -2569,14 +2784,14 @@ function GenreTreeView({
|
|
|
2569
2784
|
restoreRootUuid = selectedGenrePlaylist.root.uuid;
|
|
2570
2785
|
setStackedRestoreOwner({ revision: restoreRevision, rootUuid: restoreRootUuid });
|
|
2571
2786
|
}
|
|
2572
|
-
const isGenreUpdating = (0, import_react_query3.useIsMutating)({ mutationKey:
|
|
2573
|
-
const { mutate: updateGenre } = (0,
|
|
2787
|
+
const isGenreUpdating = (0, import_react_query3.useIsMutating)({ mutationKey: import_genre_tree19.genreUpdateMutationKey }) > 0;
|
|
2788
|
+
const { mutate: updateGenre } = (0, import_genre_tree19.useUpdateGenre)(scope, getBackendBaseUrl);
|
|
2574
2789
|
const busy = deferredSizeMetric !== sizeMetric || isTreeFetching || isGenreUpdating;
|
|
2575
2790
|
const isArtistFiltered = artistUuid !== null;
|
|
2576
2791
|
const canSizeByViews = !isArtistFiltered && hasViewsCount(visibleGenrePlaylists);
|
|
2577
|
-
const artistResult = (0,
|
|
2792
|
+
const artistResult = (0, import_genre_tree19.useArtist)(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
|
|
2578
2793
|
const filterArtistName = artistResult.status === "success" ? artistResult.artist.name : null;
|
|
2579
|
-
const filterArtist = (0,
|
|
2794
|
+
const filterArtist = (0, import_react17.useMemo)(
|
|
2580
2795
|
() => artistUuid !== null && filterArtistName !== null ? { uuid: artistUuid, name: filterArtistName } : null,
|
|
2581
2796
|
[artistUuid, filterArtistName]
|
|
2582
2797
|
);
|
|
@@ -2584,27 +2799,72 @@ function GenreTreeView({
|
|
|
2584
2799
|
const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: visibleGenrePlaylists, artist: filterArtist });
|
|
2585
2800
|
const openArtist = (uuid) => {
|
|
2586
2801
|
if (selectedNodeId !== null) clearSelection();
|
|
2802
|
+
if (songUuid !== null) changeSong(null);
|
|
2587
2803
|
changeArtist(uuid);
|
|
2588
2804
|
};
|
|
2589
|
-
const
|
|
2805
|
+
const openSong = (uuid) => {
|
|
2806
|
+
if (selectedNodeId !== null) clearSelection();
|
|
2807
|
+
changeSong(uuid);
|
|
2808
|
+
};
|
|
2809
|
+
const openSongRef = (0, import_react17.useRef)(openSong);
|
|
2810
|
+
openSongRef.current = openSong;
|
|
2811
|
+
const resolvedRenderSongLink = (0, import_react17.useCallback)(
|
|
2812
|
+
(song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
|
|
2813
|
+
[renderSongLink]
|
|
2814
|
+
);
|
|
2815
|
+
const [expandedSongUuid, setExpandedSongUuid] = (0, import_react17.useState)(null);
|
|
2816
|
+
const [expandedArtistUuid, setExpandedArtistUuid] = (0, import_react17.useState)(null);
|
|
2590
2817
|
const artistBaseUrl = artistSearchBaseUrl ?? getBackendBaseUrl;
|
|
2818
|
+
const songBaseUrl = getBackendBaseUrl;
|
|
2591
2819
|
const artistPanel = useArtistPanel({
|
|
2592
2820
|
uuid: artistUuid,
|
|
2593
2821
|
scope,
|
|
2594
2822
|
getBackendBaseUrl: artistBaseUrl,
|
|
2595
2823
|
getShareUrl: getShareUrlRef.current,
|
|
2596
|
-
renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0,
|
|
2824
|
+
renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
|
|
2597
2825
|
expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
|
|
2598
2826
|
onExpandedChange: (on) => {
|
|
2599
2827
|
if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
|
|
2600
2828
|
onArtistExpandedChange?.(on);
|
|
2601
2829
|
},
|
|
2830
|
+
renderSongLink: resolvedRenderSongLink,
|
|
2602
2831
|
onClose: () => changeArtist(null)
|
|
2603
2832
|
});
|
|
2604
|
-
const
|
|
2605
|
-
const
|
|
2606
|
-
|
|
2607
|
-
|
|
2833
|
+
const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
|
|
2834
|
+
const getGenrePath = (genre) => {
|
|
2835
|
+
const path = [];
|
|
2836
|
+
let current = visibleGenrePlaylists.find((playlist) => playlist.criteria?.uuid === genre.uuid);
|
|
2837
|
+
while (current) {
|
|
2838
|
+
path.unshift(current);
|
|
2839
|
+
const parentUuid = current.parent?.uuid;
|
|
2840
|
+
current = parentUuid ? visibleGenrePlaylists.find((playlist) => playlist.uuid === parentUuid) : void 0;
|
|
2841
|
+
}
|
|
2842
|
+
return path;
|
|
2843
|
+
};
|
|
2844
|
+
const songPanel = useSongPanel({
|
|
2845
|
+
uuid: songUuid,
|
|
2846
|
+
scope,
|
|
2847
|
+
getBackendBaseUrl: songBaseUrl,
|
|
2848
|
+
getShareUrl: getShareUrlRef.current,
|
|
2849
|
+
renderArtistLink: resolvedRenderArtistLink,
|
|
2850
|
+
getGenrePath,
|
|
2851
|
+
onSelectGenre: (genrePlaylist) => {
|
|
2852
|
+
changeSong(null);
|
|
2853
|
+
selectGenrePlaylist(genrePlaylist);
|
|
2854
|
+
},
|
|
2855
|
+
expanded: controlledSongExpanded ?? (songUuid !== null && expandedSongUuid === songUuid),
|
|
2856
|
+
onExpandedChange: (on) => {
|
|
2857
|
+
if (controlledSongExpanded === void 0) setExpandedSongUuid(on ? songUuid : null);
|
|
2858
|
+
onSongExpandedChange?.(on);
|
|
2859
|
+
},
|
|
2860
|
+
onClose: () => changeSong(null)
|
|
2861
|
+
});
|
|
2862
|
+
const builtInSongPanel = overlayPanel || hasSelectedGenreNode ? void 0 : songPanel;
|
|
2863
|
+
const builtInArtistPanel = overlayPanel || hasSelectedGenreNode || builtInSongPanel ? void 0 : artistPanel;
|
|
2864
|
+
const builtInPanel = builtInSongPanel ?? builtInArtistPanel;
|
|
2865
|
+
const activeOverlayPanel = overlayPanel ?? (builtInPanel && { ...builtInPanel, hideTitle: true, hideClose: true });
|
|
2866
|
+
const [uncontrolledCollapsed, setUncontrolledCollapsed] = (0, import_react17.useState)(false);
|
|
2867
|
+
const [uncontrolledExpanded, setUncontrolledExpanded] = (0, import_react17.useState)(false);
|
|
2608
2868
|
const isCollapsed = collapsed ?? uncontrolledCollapsed;
|
|
2609
2869
|
const isExpanded = expanded ?? uncontrolledExpanded;
|
|
2610
2870
|
const changeCollapsed = (next) => {
|
|
@@ -2615,9 +2875,9 @@ function GenreTreeView({
|
|
|
2615
2875
|
setUncontrolledExpanded(next);
|
|
2616
2876
|
onExpandedChange?.(next);
|
|
2617
2877
|
};
|
|
2618
|
-
(0,
|
|
2878
|
+
(0, import_react17.useEffect)(() => {
|
|
2619
2879
|
setUncontrolledCollapsed(false);
|
|
2620
|
-
}, [selectedNodeId, artistUuid]);
|
|
2880
|
+
}, [selectedNodeId, artistUuid, songUuid]);
|
|
2621
2881
|
const isDetailHidden = isCollapsed || !hasSelectedGenreNode && !activeOverlayPanel;
|
|
2622
2882
|
useLeftDetailPanelLayout(!isDetailHidden, placement === "regions");
|
|
2623
2883
|
const detailOverlay = placement === "regions" && activeOverlayPanel?.onExpandedChange ? activeOverlayPanel : void 0;
|
|
@@ -2631,14 +2891,14 @@ function GenreTreeView({
|
|
|
2631
2891
|
hideExpandButton: placement === "regions" ? true : void 0,
|
|
2632
2892
|
overlayPanel: detailOverlay ? { ...detailOverlay, collapsed: isCollapsed, hideExpandButton: true } : activeOverlayPanel
|
|
2633
2893
|
};
|
|
2634
|
-
const [isWheelRevealed, setIsWheelRevealed] = (0,
|
|
2894
|
+
const [isWheelRevealed, setIsWheelRevealed] = (0, import_react17.useState)(false);
|
|
2635
2895
|
const usesHandoff = viewMode === "wheel" || viewMode === "pop-core";
|
|
2636
2896
|
const isTreeReady = !isLoading && (!usesHandoff || isWheelRevealed);
|
|
2637
|
-
const rootRef = (0,
|
|
2897
|
+
const rootRef = (0, import_react17.useRef)(null);
|
|
2638
2898
|
const handlePrevious = () => {
|
|
2639
2899
|
rootRef.current?.querySelector(".search-overlay .gtv-info-panel-previous")?.click();
|
|
2640
2900
|
};
|
|
2641
|
-
(0,
|
|
2901
|
+
(0, import_react17.useEffect)(() => {
|
|
2642
2902
|
if (!isTreeReady || viewMode !== "pop-core" || !hasSelectedGenreNode || !selectedGenrePlaylist) return;
|
|
2643
2903
|
const root = rootRef.current;
|
|
2644
2904
|
const centerChip = root?.querySelector(".gtv-wheel-center-node .gtv-wheel-chip");
|
|
@@ -2648,21 +2908,22 @@ function GenreTreeView({
|
|
|
2648
2908
|
centerChip.click();
|
|
2649
2909
|
}, [hasSelectedGenreNode, isTreeReady, selectedGenrePlaylist, viewMode]);
|
|
2650
2910
|
const { showPopup, hidePopup } = (0, import_popup6.usePopup)();
|
|
2651
|
-
const [searchText, setSearchText] = (0,
|
|
2652
|
-
const artistSearchResult = (0,
|
|
2653
|
-
const
|
|
2911
|
+
const [searchText, setSearchText] = (0, import_react17.useState)("");
|
|
2912
|
+
const artistSearchResult = (0, import_genre_tree18.useArtistSearch)(artistBaseUrl, artistSearch ? searchText : "");
|
|
2913
|
+
const artistGroup = artistSearch ? {
|
|
2914
|
+
key: "artists",
|
|
2654
2915
|
label: "Artists",
|
|
2655
2916
|
items: artistSearchResult.artists.map((artist) => ({
|
|
2656
2917
|
key: artist.uuid,
|
|
2657
|
-
content: /* @__PURE__ */ (0,
|
|
2658
|
-
/* @__PURE__ */ (0,
|
|
2918
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(import_jsx_runtime41.Fragment, { children: [
|
|
2919
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { className: "block truncate font-medium", children: [
|
|
2659
2920
|
artist.name,
|
|
2660
|
-
artist.disambiguation && /* @__PURE__ */ (0,
|
|
2921
|
+
artist.disambiguation && /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { className: "font-normal text-[var(--color-neutral-500)]", children: [
|
|
2661
2922
|
" \xB7 ",
|
|
2662
2923
|
artist.disambiguation
|
|
2663
2924
|
] })
|
|
2664
2925
|
] }),
|
|
2665
|
-
artist.matchedAlias && /* @__PURE__ */ (0,
|
|
2926
|
+
artist.matchedAlias && /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [
|
|
2666
2927
|
"also known as ",
|
|
2667
2928
|
artist.matchedAlias
|
|
2668
2929
|
] })
|
|
@@ -2675,11 +2936,32 @@ function GenreTreeView({
|
|
|
2675
2936
|
status: artistSearchResult.isError ? "Search failed" : artistSearchResult.settled && artistSearchResult.artists.length === 0 ? "No artists found" : "",
|
|
2676
2937
|
loading: artistSearchResult.pending
|
|
2677
2938
|
} : void 0;
|
|
2939
|
+
const songSearchResult = (0, import_genre_tree18.useSongSearch)(songBaseUrl, songSearch ? searchText : "");
|
|
2940
|
+
const songGroup = songSearch ? {
|
|
2941
|
+
key: "songs",
|
|
2942
|
+
label: "Songs",
|
|
2943
|
+
items: songSearchResult.songs.map((song) => ({
|
|
2944
|
+
key: song.uuid,
|
|
2945
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { className: (0, import_genre_tree18.isSongSearchResultPlayable)(song) ? void 0 : "opacity-60", children: [
|
|
2946
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "block truncate font-medium", children: song.title }),
|
|
2947
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [song.artists.map((artist) => artist.name).join(", "), song.genre?.name ?? "No genre"].filter(Boolean).join(" \xB7 ") })
|
|
2948
|
+
] }),
|
|
2949
|
+
onSelect: () => {
|
|
2950
|
+
openSong(song.uuid);
|
|
2951
|
+
onSongSelect?.(song);
|
|
2952
|
+
}
|
|
2953
|
+
})),
|
|
2954
|
+
status: songSearchResult.isError ? "Search failed" : songSearchResult.settled && songSearchResult.songs.length === 0 ? "No songs found" : "",
|
|
2955
|
+
loading: songSearchResult.pending
|
|
2956
|
+
} : void 0;
|
|
2957
|
+
const extraGroups = [artistGroup, songGroup].filter((group) => group !== void 0);
|
|
2678
2958
|
const handleSelectedNodeChange = (node) => {
|
|
2959
|
+
if (node !== null && songUuid !== null) changeSong(null);
|
|
2679
2960
|
selectNode(node);
|
|
2680
2961
|
};
|
|
2681
2962
|
const handleGenreSearchClear = clearSelection;
|
|
2682
2963
|
const handleGenreSearchSelect = (genrePlaylist) => {
|
|
2964
|
+
if (songUuid !== null) changeSong(null);
|
|
2683
2965
|
selectGenrePlaylist(genrePlaylist);
|
|
2684
2966
|
};
|
|
2685
2967
|
const selectedUuid = selectedGenrePlaylist?.uuid;
|
|
@@ -2688,13 +2970,13 @@ function GenreTreeView({
|
|
|
2688
2970
|
const handleGenreSearchZoomIn = () => {
|
|
2689
2971
|
if (selectedUuid) focus.onFocusGenreChange?.(selectedUuid, "zoom-in");
|
|
2690
2972
|
};
|
|
2691
|
-
const zoomInRef = (0,
|
|
2973
|
+
const zoomInRef = (0, import_react17.useRef)(void 0);
|
|
2692
2974
|
zoomInRef.current = canZoomIn ? handleGenreSearchZoomIn : void 0;
|
|
2693
|
-
const renderInfoPanelBody = (0,
|
|
2975
|
+
const renderInfoPanelBody = (0, import_react17.useCallback)(
|
|
2694
2976
|
(node, { toolbar, ancestors, family }) => {
|
|
2695
2977
|
const genrePlaylist = genrePlaylistByNodeId.get(node.id);
|
|
2696
2978
|
const criteria = genrePlaylist?.criteria;
|
|
2697
|
-
if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ (0,
|
|
2979
|
+
if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(import_jsx_runtime41.Fragment, { children: [
|
|
2698
2980
|
toolbar,
|
|
2699
2981
|
family
|
|
2700
2982
|
] }), aside: ancestors };
|
|
@@ -2714,7 +2996,7 @@ function GenreTreeView({
|
|
|
2714
2996
|
(sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist.uuid && sibling.criteria?.side === "pop"
|
|
2715
2997
|
) : void 0;
|
|
2716
2998
|
return {
|
|
2717
|
-
primary: /* @__PURE__ */ (0,
|
|
2999
|
+
primary: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
2718
3000
|
GenreInfoPanelBody,
|
|
2719
3001
|
{
|
|
2720
3002
|
toolbar,
|
|
@@ -2733,7 +3015,7 @@ function GenreTreeView({
|
|
|
2733
3015
|
onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
|
|
2734
3016
|
onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
|
|
2735
3017
|
onChangeParent: readOnly ? void 0 : () => showPopup(
|
|
2736
|
-
/* @__PURE__ */ (0,
|
|
3018
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
2737
3019
|
ChangeParentPopup,
|
|
2738
3020
|
{
|
|
2739
3021
|
genrePlaylist: { ...genrePlaylist, criteria },
|
|
@@ -2754,6 +3036,7 @@ function GenreTreeView({
|
|
|
2754
3036
|
criteriaOverviewSchema,
|
|
2755
3037
|
renderGenreDetailExtras,
|
|
2756
3038
|
renderArtistLink,
|
|
3039
|
+
renderSongLink: resolvedRenderSongLink,
|
|
2757
3040
|
artist: filterArtist,
|
|
2758
3041
|
renderSummary,
|
|
2759
3042
|
renderEssentialTracks,
|
|
@@ -2784,6 +3067,7 @@ function GenreTreeView({
|
|
|
2784
3067
|
criteriaOverviewSchema,
|
|
2785
3068
|
renderGenreDetailExtras,
|
|
2786
3069
|
renderArtistLink,
|
|
3070
|
+
resolvedRenderSongLink,
|
|
2787
3071
|
renderSummary,
|
|
2788
3072
|
renderEssentialTracks,
|
|
2789
3073
|
renderSongLeading,
|
|
@@ -2793,7 +3077,7 @@ function GenreTreeView({
|
|
|
2793
3077
|
);
|
|
2794
3078
|
const isRotationControlled = controlledAllowWheelRotation !== void 0;
|
|
2795
3079
|
const allowWheelRotation = controlledAllowWheelRotation ?? internalAllowWheelRotation;
|
|
2796
|
-
const detailTab = placement === "regions" && (hasSelectedGenreNode || detailOverlay) ? /* @__PURE__ */ (0,
|
|
3080
|
+
const detailTab = placement === "regions" && (hasSelectedGenreNode || detailOverlay) ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
2797
3081
|
PanelEdgeTab,
|
|
2798
3082
|
{
|
|
2799
3083
|
side: "left",
|
|
@@ -2803,8 +3087,8 @@ function GenreTreeView({
|
|
|
2803
3087
|
) : null;
|
|
2804
3088
|
const renderArtistFilter = () => {
|
|
2805
3089
|
if (isLoading || !filterArtist) return null;
|
|
2806
|
-
return /* @__PURE__ */ (0,
|
|
2807
|
-
/* @__PURE__ */ (0,
|
|
3090
|
+
return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
|
|
3091
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
2808
3092
|
"button",
|
|
2809
3093
|
{
|
|
2810
3094
|
type: "button",
|
|
@@ -2816,7 +3100,7 @@ function GenreTreeView({
|
|
|
2816
3100
|
children: filterArtist.name
|
|
2817
3101
|
}
|
|
2818
3102
|
),
|
|
2819
|
-
/* @__PURE__ */ (0,
|
|
3103
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
2820
3104
|
"button",
|
|
2821
3105
|
{
|
|
2822
3106
|
type: "button",
|
|
@@ -2828,23 +3112,23 @@ function GenreTreeView({
|
|
|
2828
3112
|
)
|
|
2829
3113
|
] });
|
|
2830
3114
|
};
|
|
2831
|
-
const actions = /* @__PURE__ */ (0,
|
|
2832
|
-
!isLoading && /* @__PURE__ */ (0,
|
|
3115
|
+
const actions = /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(import_jsx_runtime41.Fragment, { children: [
|
|
3116
|
+
!isLoading && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
2833
3117
|
GenreSearch,
|
|
2834
3118
|
{
|
|
2835
3119
|
genrePlaylists: visibleGenrePlaylists,
|
|
2836
3120
|
onSelect: handleGenreSearchSelect,
|
|
2837
|
-
selectedName: selectedGenrePlaylist?.name ??
|
|
2838
|
-
onClear: builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
|
|
3121
|
+
selectedName: selectedGenrePlaylist?.name ?? builtInPanel?.title ?? null,
|
|
3122
|
+
onClear: builtInSongPanel ? () => changeSong(null) : builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
|
|
2839
3123
|
onPrevious: handlePrevious,
|
|
2840
|
-
onQueryChange: artistSearch ? setSearchText : void 0,
|
|
2841
|
-
|
|
3124
|
+
onQueryChange: artistSearch || songSearch ? setSearchText : void 0,
|
|
3125
|
+
extraGroups
|
|
2842
3126
|
}
|
|
2843
3127
|
),
|
|
2844
|
-
!isLoading && filterArtist && /* @__PURE__ */ (0,
|
|
2845
|
-
/* @__PURE__ */ (0,
|
|
2846
|
-
!isLoading && !isControlled && /* @__PURE__ */ (0,
|
|
2847
|
-
|
|
3128
|
+
!isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
|
|
3129
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.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: [
|
|
3130
|
+
!isLoading && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
3131
|
+
import_ui18.SegmentedControl,
|
|
2848
3132
|
{
|
|
2849
3133
|
"aria-label": "Tree view mode",
|
|
2850
3134
|
value: viewMode,
|
|
@@ -2857,14 +3141,14 @@ function GenreTreeView({
|
|
|
2857
3141
|
]
|
|
2858
3142
|
}
|
|
2859
3143
|
),
|
|
2860
|
-
!isLoading && showDisplayOptions && /* @__PURE__ */ (0,
|
|
3144
|
+
!isLoading && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
2861
3145
|
"div",
|
|
2862
3146
|
{
|
|
2863
3147
|
className: "flex items-center gap-1",
|
|
2864
3148
|
role: "group",
|
|
2865
3149
|
"aria-label": "Tree display options",
|
|
2866
|
-
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0,
|
|
2867
|
-
|
|
3150
|
+
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
3151
|
+
import_ui18.Button,
|
|
2868
3152
|
{
|
|
2869
3153
|
variant: allowWheelRotation ? "default" : "outline",
|
|
2870
3154
|
size: "sm",
|
|
@@ -2874,52 +3158,52 @@ function GenreTreeView({
|
|
|
2874
3158
|
)
|
|
2875
3159
|
}
|
|
2876
3160
|
),
|
|
2877
|
-
!isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0,
|
|
2878
|
-
|
|
3161
|
+
!isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
3162
|
+
import_ui18.IconTextButton,
|
|
2879
3163
|
{
|
|
2880
|
-
icon:
|
|
3164
|
+
icon: import_lucide_react16.Plus,
|
|
2881
3165
|
text: "Add root",
|
|
2882
3166
|
onClick: () => handleGenreCreationAction(null)
|
|
2883
3167
|
}
|
|
2884
3168
|
)
|
|
2885
3169
|
] })
|
|
2886
3170
|
] });
|
|
2887
|
-
return /* @__PURE__ */ (0,
|
|
3171
|
+
return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
|
|
2888
3172
|
"div",
|
|
2889
3173
|
{
|
|
2890
3174
|
ref: rootRef,
|
|
2891
3175
|
className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
|
|
2892
3176
|
children: [
|
|
2893
|
-
/* @__PURE__ */ (0,
|
|
2894
|
-
/* @__PURE__ */ (0,
|
|
3177
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("style", { children: PREVIOUS_BUTTON_CSS }),
|
|
3178
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
2895
3179
|
"div",
|
|
2896
3180
|
{
|
|
2897
3181
|
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))] 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"}`,
|
|
2898
3182
|
children: actions
|
|
2899
3183
|
}
|
|
2900
3184
|
),
|
|
2901
|
-
!isLoading && filterArtist && /* @__PURE__ */ (0,
|
|
3185
|
+
!isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
2902
3186
|
"div",
|
|
2903
3187
|
{
|
|
2904
3188
|
className: `absolute left-1/2 z-30 hidden -translate-x-1/2 md:block ${placement === "regions" ? "top-[calc(var(--layout-gutter)+var(--layout-panel-inset))]" : "top-5"}`,
|
|
2905
3189
|
children: renderArtistFilter()
|
|
2906
3190
|
}
|
|
2907
3191
|
),
|
|
2908
|
-
detailTab && /* @__PURE__ */ (0,
|
|
3192
|
+
detailTab && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
2909
3193
|
"div",
|
|
2910
3194
|
{
|
|
2911
3195
|
className: `absolute top-1/2 z-30 -translate-y-1/2 max-md:hidden transition-[opacity,visibility] duration-150 ease-out ${isTreeReady ? "opacity-100" : "pointer-events-none invisible opacity-0"} ${detailExpanded ? "right-[calc(var(--layout-center-right)-var(--layout-edge-tab-w))]" : isCollapsed ? "left-0" : "left-[calc(var(--layout-gutter)+var(--layout-left-w))]"}`,
|
|
2912
3196
|
children: detailTab
|
|
2913
3197
|
}
|
|
2914
3198
|
),
|
|
2915
|
-
/* @__PURE__ */ (0,
|
|
2916
|
-
!isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0,
|
|
2917
|
-
isLoading ? /* @__PURE__ */ (0,
|
|
3199
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
|
|
3200
|
+
!isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
|
|
3201
|
+
isLoading ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_genre_tree_view4.GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
|
|
2918
3202
|
// The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
|
|
2919
3203
|
// and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
|
|
2920
3204
|
// Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
|
|
2921
3205
|
// The panel's own Zoom in button is hidden: the search bar carries it.
|
|
2922
|
-
/* @__PURE__ */ (0,
|
|
3206
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 [--gtv-info-panel-header-inset:calc(var(--layout-search-h,2.5rem)+var(--layout-panel-inset,0.5rem))] [&_.gtv-info-panel-header]:pb-2 [&_.gtv-info-panel-title--hidden]:h-0 [&_.gtv-toolbar]:hidden", children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
2923
3207
|
GenrePlaylistTreeWheel,
|
|
2924
3208
|
{
|
|
2925
3209
|
scope,
|
|
@@ -2938,9 +3222,9 @@ function GenreTreeView({
|
|
|
2938
3222
|
allowWheelRotation,
|
|
2939
3223
|
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2940
3224
|
busy,
|
|
2941
|
-
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0,
|
|
3225
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
2942
3226
|
floatingControlsLeading,
|
|
2943
|
-
canSizeByViews && /* @__PURE__ */ (0,
|
|
3227
|
+
canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2944
3228
|
] }) : void 0,
|
|
2945
3229
|
defaultViewport,
|
|
2946
3230
|
viewportRestore,
|
|
@@ -2950,7 +3234,7 @@ function GenreTreeView({
|
|
|
2950
3234
|
) }) })
|
|
2951
3235
|
) : viewMode === "pop-core" ? (
|
|
2952
3236
|
// Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
|
|
2953
|
-
/* @__PURE__ */ (0,
|
|
3237
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 [--gtv-info-panel-header-inset:calc(var(--layout-search-h,2.5rem)+var(--layout-panel-inset,0.5rem))] [&_.gtv-info-panel-header]:pb-2 [&_.gtv-info-panel-title--hidden]:h-0 [&_.gtv-toolbar]:hidden [&_.gtv-info-panel-zoom-in]:hidden", children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_genre_tree_view4.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
2954
3238
|
GenrePlaylistTreeWheelRadialPopCore,
|
|
2955
3239
|
{
|
|
2956
3240
|
scope,
|
|
@@ -2969,9 +3253,9 @@ function GenreTreeView({
|
|
|
2969
3253
|
allowWheelRotation,
|
|
2970
3254
|
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2971
3255
|
busy,
|
|
2972
|
-
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0,
|
|
3256
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
2973
3257
|
floatingControlsLeading,
|
|
2974
|
-
canSizeByViews && /* @__PURE__ */ (0,
|
|
3258
|
+
canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2975
3259
|
] }) : void 0,
|
|
2976
3260
|
defaultViewport,
|
|
2977
3261
|
viewportRestore,
|
|
@@ -2980,7 +3264,7 @@ function GenreTreeView({
|
|
|
2980
3264
|
...focus
|
|
2981
3265
|
}
|
|
2982
3266
|
) }) })
|
|
2983
|
-
) : viewMode === "outline" ? /* @__PURE__ */ (0,
|
|
3267
|
+
) : viewMode === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative [--gtv-info-panel-header-inset:calc(var(--layout-search-h,2.5rem)+var(--layout-panel-inset,0.5rem)+0.75rem)] [&_.gtv-toolbar]:hidden", children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
2984
3268
|
GenrePlaylistTreeWheelRadialPopCore,
|
|
2985
3269
|
{
|
|
2986
3270
|
outline: true,
|
|
@@ -2999,13 +3283,13 @@ function GenreTreeView({
|
|
|
2999
3283
|
readOnly,
|
|
3000
3284
|
...infoPanelCuration
|
|
3001
3285
|
}
|
|
3002
|
-
) }) : /* @__PURE__ */ (0,
|
|
3286
|
+
) }) : /* @__PURE__ */ (0, import_jsx_runtime41.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)] [&_.gtv-toolbar]:hidden", children: Object.entries(groupedGenrePlaylistsByRoot).map(
|
|
3003
3287
|
([uuid, genrePlaylistTreePerRoot], rootIndex) => {
|
|
3004
|
-
return /* @__PURE__ */ (0,
|
|
3288
|
+
return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
3005
3289
|
"div",
|
|
3006
3290
|
{
|
|
3007
3291
|
className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
|
|
3008
|
-
children: /* @__PURE__ */ (0,
|
|
3292
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
3009
3293
|
GenrePlaylistTreePerRoot,
|
|
3010
3294
|
{
|
|
3011
3295
|
scope,
|
|
@@ -3042,11 +3326,11 @@ function GenreTreeView({
|
|
|
3042
3326
|
}
|
|
3043
3327
|
|
|
3044
3328
|
// src/genre-tree/GenreDetailPanel.tsx
|
|
3045
|
-
var
|
|
3329
|
+
var import_react18 = require("react");
|
|
3046
3330
|
var import_genre_tree_view5 = require("@behindthemusictree/genre-tree-view");
|
|
3047
|
-
var
|
|
3048
|
-
var
|
|
3049
|
-
var
|
|
3331
|
+
var import_ui19 = require("@behindthemusictree/ui");
|
|
3332
|
+
var import_genre_tree20 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3333
|
+
var import_jsx_runtime42 = require("react/jsx-runtime");
|
|
3050
3334
|
var ITEM_COUNT_LABEL = "Songs";
|
|
3051
3335
|
var RELATIVE_ITEM_COUNT = 1;
|
|
3052
3336
|
function toNode(genre, parentId) {
|
|
@@ -3073,9 +3357,9 @@ function GenreDetailPanel({
|
|
|
3073
3357
|
onExpandedChange,
|
|
3074
3358
|
extras
|
|
3075
3359
|
}) {
|
|
3076
|
-
const { data: detail, isPending, isError } = (0,
|
|
3360
|
+
const { data: detail, isPending, isError } = (0, import_genre_tree20.useFetchGenreDetail)(genreUuid, scope, getBackendBaseUrl);
|
|
3077
3361
|
const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: [] });
|
|
3078
|
-
const view = (0,
|
|
3362
|
+
const view = (0, import_react18.useMemo)(() => {
|
|
3079
3363
|
if (!detail) return null;
|
|
3080
3364
|
const rootColor = (0, import_genre_tree_view5.getGenreTreeColor)(detail.root.wikidataId);
|
|
3081
3365
|
const relative = (genre, parentId) => ({
|
|
@@ -3101,17 +3385,17 @@ function GenreDetailPanel({
|
|
|
3101
3385
|
};
|
|
3102
3386
|
}, [detail]);
|
|
3103
3387
|
if (isError) {
|
|
3104
|
-
return /* @__PURE__ */ (0,
|
|
3388
|
+
return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
|
|
3105
3389
|
}
|
|
3106
3390
|
if (isPending || !detail || !view) {
|
|
3107
|
-
return /* @__PURE__ */ (0,
|
|
3108
|
-
/* @__PURE__ */ (0,
|
|
3109
|
-
/* @__PURE__ */ (0,
|
|
3110
|
-
/* @__PURE__ */ (0,
|
|
3391
|
+
return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
|
|
3392
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui19.Skeleton, { className: "h-6 w-2/3" }),
|
|
3393
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui19.Skeleton, { className: "h-4 w-full" }),
|
|
3394
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui19.Skeleton, { className: "h-4 w-3/4" })
|
|
3111
3395
|
] });
|
|
3112
3396
|
}
|
|
3113
3397
|
const renderBody = (node, sections) => ({
|
|
3114
|
-
primary: /* @__PURE__ */ (0,
|
|
3398
|
+
primary: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
3115
3399
|
GenreInfoPanelBody,
|
|
3116
3400
|
{
|
|
3117
3401
|
toolbar: sections.toolbar,
|
|
@@ -3128,7 +3412,7 @@ function GenreDetailPanel({
|
|
|
3128
3412
|
),
|
|
3129
3413
|
aside: null
|
|
3130
3414
|
});
|
|
3131
|
-
return /* @__PURE__ */ (0,
|
|
3415
|
+
return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
3132
3416
|
import_genre_tree_view5.InfoPanel,
|
|
3133
3417
|
{
|
|
3134
3418
|
node: view.node,
|
|
@@ -3154,13 +3438,13 @@ function GenreDetailPanel({
|
|
|
3154
3438
|
var import_genre_tree_view6 = require("@behindthemusictree/genre-tree-view");
|
|
3155
3439
|
|
|
3156
3440
|
// src/genre-tree/track-list-sidebar/TrackListSidebar.tsx
|
|
3157
|
-
var
|
|
3158
|
-
var
|
|
3159
|
-
var
|
|
3160
|
-
var
|
|
3441
|
+
var import_react19 = require("react");
|
|
3442
|
+
var import_lucide_react17 = require("lucide-react");
|
|
3443
|
+
var import_genre_tree21 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3444
|
+
var import_genre_tree22 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3161
3445
|
var import_track_noun7 = require("@behindthemusictree/app-kit/track-noun");
|
|
3162
|
-
var
|
|
3163
|
-
var useBrowserLayoutEffect = typeof window === "undefined" ?
|
|
3446
|
+
var import_jsx_runtime43 = require("react/jsx-runtime");
|
|
3447
|
+
var useBrowserLayoutEffect = typeof window === "undefined" ? import_react19.useEffect : import_react19.useLayoutEffect;
|
|
3164
3448
|
function TrackListSidebar({
|
|
3165
3449
|
className: className2 = "",
|
|
3166
3450
|
renderDuration,
|
|
@@ -3169,20 +3453,21 @@ function TrackListSidebar({
|
|
|
3169
3453
|
isCollapsed = false,
|
|
3170
3454
|
onExpand,
|
|
3171
3455
|
onMinimize,
|
|
3172
|
-
renderArtistLink
|
|
3456
|
+
renderArtistLink,
|
|
3457
|
+
renderSongLink
|
|
3173
3458
|
}) {
|
|
3174
|
-
const { trackList, loadMore, autoAdvanceScrollRequest } = (0,
|
|
3459
|
+
const { trackList, loadMore, autoAdvanceScrollRequest } = (0, import_genre_tree21.useTrackList)();
|
|
3175
3460
|
const trackNoun = (0, import_track_noun7.useTrackNoun)();
|
|
3176
|
-
const trackListRef = (0,
|
|
3177
|
-
const previousOriginRef = (0,
|
|
3178
|
-
const handledAutoAdvanceRequestIdRef = (0,
|
|
3179
|
-
const shouldFollowPlaybackRef = (0,
|
|
3180
|
-
const programmaticScrollTopRef = (0,
|
|
3181
|
-
const userScrollIntentRef = (0,
|
|
3182
|
-
const userScrollIntentTimerRef = (0,
|
|
3183
|
-
const loadMoreSentinelRef = (0,
|
|
3461
|
+
const trackListRef = (0, import_react19.useRef)(null);
|
|
3462
|
+
const previousOriginRef = (0, import_react19.useRef)(trackList?.origin ?? null);
|
|
3463
|
+
const handledAutoAdvanceRequestIdRef = (0, import_react19.useRef)(null);
|
|
3464
|
+
const shouldFollowPlaybackRef = (0, import_react19.useRef)(true);
|
|
3465
|
+
const programmaticScrollTopRef = (0, import_react19.useRef)(null);
|
|
3466
|
+
const userScrollIntentRef = (0, import_react19.useRef)(false);
|
|
3467
|
+
const userScrollIntentTimerRef = (0, import_react19.useRef)(null);
|
|
3468
|
+
const loadMoreSentinelRef = (0, import_react19.useRef)(null);
|
|
3184
3469
|
const hasMore = !!trackList && trackList.nextPage !== null;
|
|
3185
|
-
(0,
|
|
3470
|
+
(0, import_react19.useEffect)(() => {
|
|
3186
3471
|
const sentinel = loadMoreSentinelRef.current;
|
|
3187
3472
|
if (!hasMore || !sentinel) return;
|
|
3188
3473
|
const observer = new IntersectionObserver((entries) => {
|
|
@@ -3202,11 +3487,11 @@ function TrackListSidebar({
|
|
|
3202
3487
|
programmaticScrollTopRef.current = null;
|
|
3203
3488
|
if (trackListRef.current) trackListRef.current.scrollTop = 0;
|
|
3204
3489
|
}, [trackList?.origin]);
|
|
3205
|
-
(0,
|
|
3490
|
+
(0, import_react19.useEffect)(() => {
|
|
3206
3491
|
const list = trackListRef.current;
|
|
3207
3492
|
if (!isCollapsed && list?.scrollTop === 0) shouldFollowPlaybackRef.current = true;
|
|
3208
3493
|
}, [isCollapsed]);
|
|
3209
|
-
(0,
|
|
3494
|
+
(0, import_react19.useEffect)(
|
|
3210
3495
|
() => () => {
|
|
3211
3496
|
if (userScrollIntentTimerRef.current !== null) clearTimeout(userScrollIntentTimerRef.current);
|
|
3212
3497
|
},
|
|
@@ -3260,25 +3545,25 @@ function TrackListSidebar({
|
|
|
3260
3545
|
}
|
|
3261
3546
|
};
|
|
3262
3547
|
const positionClasses = layout === "inline" ? "relative w-full flex h-full flex-col" : "fixed right-0 w-[50rem] flex flex-col";
|
|
3263
|
-
return trackList ? /* @__PURE__ */ (0,
|
|
3548
|
+
return trackList ? /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(
|
|
3264
3549
|
"div",
|
|
3265
3550
|
{
|
|
3266
3551
|
"data-theme": "dark",
|
|
3267
3552
|
className: `track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${className2}`,
|
|
3268
3553
|
style: layout === "fixed" ? { top: "63.5px", maxHeight: "calc(100vh - 63.5px - 79px)" } : void 0,
|
|
3269
3554
|
children: [
|
|
3270
|
-
/* @__PURE__ */ (0,
|
|
3271
|
-
/* @__PURE__ */ (0,
|
|
3272
|
-
/* @__PURE__ */ (0,
|
|
3273
|
-
/* @__PURE__ */ (0,
|
|
3274
|
-
/* @__PURE__ */ (0,
|
|
3555
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
|
|
3556
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
|
|
3557
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "origin flex text-xl", children: [
|
|
3558
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
|
|
3559
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.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_runtime43.jsx)("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
|
|
3275
3560
|
] }),
|
|
3276
|
-
/* @__PURE__ */ (0,
|
|
3277
|
-
trackList.origin.type ===
|
|
3561
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
|
|
3562
|
+
trackList.origin.type === import_genre_tree22.TrackListOriginType.GENRE_PLAYLIST ? "Genre \u2022 " : trackList.origin.type === import_genre_tree22.TrackListOriginType.ARTIST ? "Artist \u2022 " : `${trackNoun.singular} playlist \u2022 `,
|
|
3278
3563
|
`${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`
|
|
3279
3564
|
] })
|
|
3280
3565
|
] }),
|
|
3281
|
-
(isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0,
|
|
3566
|
+
(isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
3282
3567
|
"button",
|
|
3283
3568
|
{
|
|
3284
3569
|
type: "button",
|
|
@@ -3287,11 +3572,11 @@ function TrackListSidebar({
|
|
|
3287
3572
|
"aria-expanded": !isCollapsed,
|
|
3288
3573
|
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"}`,
|
|
3289
3574
|
onClick: isCollapsed ? onExpand : onMinimize,
|
|
3290
|
-
children: /* @__PURE__ */ (0,
|
|
3575
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react17.ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
|
|
3291
3576
|
}
|
|
3292
3577
|
)
|
|
3293
3578
|
] }),
|
|
3294
|
-
!isCollapsed && /* @__PURE__ */ (0,
|
|
3579
|
+
!isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(
|
|
3295
3580
|
"ul",
|
|
3296
3581
|
{
|
|
3297
3582
|
ref: trackListRef,
|
|
@@ -3301,17 +3586,18 @@ function TrackListSidebar({
|
|
|
3301
3586
|
onPointerDown: markUserScrollIntent,
|
|
3302
3587
|
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",
|
|
3303
3588
|
children: [
|
|
3304
|
-
trackList.tracks.map((track, index) => /* @__PURE__ */ (0,
|
|
3589
|
+
trackList.tracks.map((track, index) => /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
3305
3590
|
TrackItem,
|
|
3306
3591
|
{
|
|
3307
3592
|
track,
|
|
3308
3593
|
position: index + 1,
|
|
3309
3594
|
renderDuration,
|
|
3310
3595
|
renderActions,
|
|
3311
|
-
renderArtistLink
|
|
3596
|
+
renderArtistLink,
|
|
3597
|
+
renderSongLink
|
|
3312
3598
|
}
|
|
3313
3599
|
) }, track.uuid)),
|
|
3314
|
-
hasMore && /* @__PURE__ */ (0,
|
|
3600
|
+
hasMore && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
|
|
3315
3601
|
]
|
|
3316
3602
|
}
|
|
3317
3603
|
)
|
|
@@ -3321,16 +3607,16 @@ function TrackListSidebar({
|
|
|
3321
3607
|
}
|
|
3322
3608
|
|
|
3323
3609
|
// src/genre-tree/PlayerPanel.tsx
|
|
3324
|
-
var
|
|
3610
|
+
var import_react20 = require("react");
|
|
3325
3611
|
var import_tailwind_merge2 = require("tailwind-merge");
|
|
3326
|
-
var
|
|
3327
|
-
var
|
|
3612
|
+
var import_player13 = require("@behindthemusictree/app-kit/player");
|
|
3613
|
+
var import_genre_tree24 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3328
3614
|
|
|
3329
3615
|
// src/genre-tree/PlayerControls.tsx
|
|
3330
|
-
var
|
|
3331
|
-
var
|
|
3332
|
-
var
|
|
3333
|
-
var
|
|
3616
|
+
var import_lucide_react18 = require("lucide-react");
|
|
3617
|
+
var import_genre_tree23 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3618
|
+
var import_player12 = require("@behindthemusictree/app-kit/player");
|
|
3619
|
+
var import_jsx_runtime44 = require("react/jsx-runtime");
|
|
3334
3620
|
function PlayerControls() {
|
|
3335
3621
|
const {
|
|
3336
3622
|
duration,
|
|
@@ -3341,23 +3627,23 @@ function PlayerControls() {
|
|
|
3341
3627
|
seekTo,
|
|
3342
3628
|
setVolume,
|
|
3343
3629
|
volume
|
|
3344
|
-
} = (0,
|
|
3345
|
-
const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } = (0,
|
|
3346
|
-
const currentTime = (0,
|
|
3347
|
-
const isBusy = isLoading || playState ===
|
|
3630
|
+
} = (0, import_player12.usePlayer)();
|
|
3631
|
+
const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } = (0, import_genre_tree23.useTrackList)();
|
|
3632
|
+
const currentTime = (0, import_player12.useCurrentTime)();
|
|
3633
|
+
const isBusy = isLoading || playState === import_player12.PlayStates.LOADING;
|
|
3348
3634
|
const isReady = !!playerTrackObject?.isReady && !isBusy && !playerTrackObject.loadError;
|
|
3349
3635
|
const safeDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;
|
|
3350
3636
|
const safeCurrentTime = Number.isFinite(currentTime) ? Math.max(0, Math.min(currentTime, safeDuration)) : 0;
|
|
3351
|
-
const isPlaying = playState ===
|
|
3352
|
-
return /* @__PURE__ */ (0,
|
|
3637
|
+
const isPlaying = playState === import_player12.PlayStates.PLAYING;
|
|
3638
|
+
return /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(
|
|
3353
3639
|
"section",
|
|
3354
3640
|
{
|
|
3355
3641
|
"aria-label": "Player controls",
|
|
3356
3642
|
className: "player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200",
|
|
3357
3643
|
children: [
|
|
3358
|
-
/* @__PURE__ */ (0,
|
|
3359
|
-
/* @__PURE__ */ (0,
|
|
3360
|
-
/* @__PURE__ */ (0,
|
|
3644
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
|
|
3645
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { "aria-label": "Elapsed time", children: (0, import_genre_tree23.formatTime)(safeCurrentTime) }),
|
|
3646
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
3361
3647
|
"input",
|
|
3362
3648
|
{
|
|
3363
3649
|
"aria-label": "Seek",
|
|
@@ -3371,11 +3657,11 @@ function PlayerControls() {
|
|
|
3371
3657
|
onChange: (event) => seekTo(Number(event.currentTarget.value))
|
|
3372
3658
|
}
|
|
3373
3659
|
),
|
|
3374
|
-
/* @__PURE__ */ (0,
|
|
3660
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { "aria-label": "Duration", children: (0, import_genre_tree23.formatTime)(safeDuration) })
|
|
3375
3661
|
] }),
|
|
3376
|
-
/* @__PURE__ */ (0,
|
|
3377
|
-
/* @__PURE__ */ (0,
|
|
3378
|
-
/* @__PURE__ */ (0,
|
|
3662
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
|
|
3663
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
|
|
3664
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
3379
3665
|
"button",
|
|
3380
3666
|
{
|
|
3381
3667
|
type: "button",
|
|
@@ -3384,10 +3670,10 @@ function PlayerControls() {
|
|
|
3384
3670
|
disabled: !isReady || !hasPreviousTrack,
|
|
3385
3671
|
onClick: playPreviousTrack,
|
|
3386
3672
|
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",
|
|
3387
|
-
children: /* @__PURE__ */ (0,
|
|
3673
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_lucide_react18.SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
3388
3674
|
}
|
|
3389
3675
|
),
|
|
3390
|
-
/* @__PURE__ */ (0,
|
|
3676
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
3391
3677
|
"button",
|
|
3392
3678
|
{
|
|
3393
3679
|
type: "button",
|
|
@@ -3396,10 +3682,10 @@ function PlayerControls() {
|
|
|
3396
3682
|
disabled: !isReady,
|
|
3397
3683
|
onClick: handlePlayPauseAction,
|
|
3398
3684
|
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",
|
|
3399
|
-
children: isPlaying ? /* @__PURE__ */ (0,
|
|
3685
|
+
children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_lucide_react18.Pause, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_lucide_react18.Play, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
|
|
3400
3686
|
}
|
|
3401
3687
|
),
|
|
3402
|
-
/* @__PURE__ */ (0,
|
|
3688
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
3403
3689
|
"button",
|
|
3404
3690
|
{
|
|
3405
3691
|
type: "button",
|
|
@@ -3408,14 +3694,14 @@ function PlayerControls() {
|
|
|
3408
3694
|
disabled: !isReady || !hasNextTrack,
|
|
3409
3695
|
onClick: playNextTrack,
|
|
3410
3696
|
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",
|
|
3411
|
-
children: /* @__PURE__ */ (0,
|
|
3697
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_lucide_react18.SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
3412
3698
|
}
|
|
3413
3699
|
)
|
|
3414
3700
|
] }),
|
|
3415
|
-
/* @__PURE__ */ (0,
|
|
3416
|
-
volume === 0 ? /* @__PURE__ */ (0,
|
|
3417
|
-
/* @__PURE__ */ (0,
|
|
3418
|
-
/* @__PURE__ */ (0,
|
|
3701
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.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: [
|
|
3702
|
+
volume === 0 ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_lucide_react18.VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_lucide_react18.Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
|
|
3703
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { className: "sr-only", children: "Volume" }),
|
|
3704
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
3419
3705
|
"input",
|
|
3420
3706
|
{
|
|
3421
3707
|
"aria-label": "Volume",
|
|
@@ -3437,7 +3723,7 @@ function PlayerControls() {
|
|
|
3437
3723
|
}
|
|
3438
3724
|
|
|
3439
3725
|
// src/genre-tree/PlayerPanel.tsx
|
|
3440
|
-
var
|
|
3726
|
+
var import_jsx_runtime45 = require("react/jsx-runtime");
|
|
3441
3727
|
var PANEL_ID = "player-panel";
|
|
3442
3728
|
var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
|
|
3443
3729
|
function PlayerPanel({
|
|
@@ -3446,16 +3732,17 @@ function PlayerPanel({
|
|
|
3446
3732
|
expanded,
|
|
3447
3733
|
onExpandedChange,
|
|
3448
3734
|
extendedWidth = DEFAULT_EXTENDED_WIDTH,
|
|
3449
|
-
renderArtistLink
|
|
3735
|
+
renderArtistLink,
|
|
3736
|
+
renderSongLink
|
|
3450
3737
|
}) {
|
|
3451
|
-
const { playerTrackObject } = (0,
|
|
3452
|
-
const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0,
|
|
3453
|
-
const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = (0,
|
|
3738
|
+
const { playerTrackObject } = (0, import_player13.usePlayer)();
|
|
3739
|
+
const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0, import_genre_tree24.useTrackListSidebarVisibility)();
|
|
3740
|
+
const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = (0, import_react20.useState)("");
|
|
3454
3741
|
const loadedTrackTitle = playerTrackObject?.track.title;
|
|
3455
|
-
(0,
|
|
3742
|
+
(0, import_react20.useEffect)(() => {
|
|
3456
3743
|
if (loadedTrackTitle) setLastLoadedTrackTitle(loadedTrackTitle);
|
|
3457
3744
|
}, [loadedTrackTitle]);
|
|
3458
|
-
const [uncontrolledExtended, setUncontrolledExtended] = (0,
|
|
3745
|
+
const [uncontrolledExtended, setUncontrolledExtended] = (0, import_react20.useState)(false);
|
|
3459
3746
|
const isExtendRequested = expanded ?? uncontrolledExtended;
|
|
3460
3747
|
const setIsExtendRequested = (next) => {
|
|
3461
3748
|
setUncontrolledExtended(next);
|
|
@@ -3476,7 +3763,7 @@ function PlayerPanel({
|
|
|
3476
3763
|
onClick: () => setIsExtendRequested(true),
|
|
3477
3764
|
expanded: false
|
|
3478
3765
|
} : null;
|
|
3479
|
-
return /* @__PURE__ */ (0,
|
|
3766
|
+
return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(
|
|
3480
3767
|
"div",
|
|
3481
3768
|
{
|
|
3482
3769
|
id: PANEL_ID,
|
|
@@ -3489,7 +3776,7 @@ function PlayerPanel({
|
|
|
3489
3776
|
),
|
|
3490
3777
|
style: isExtended ? { width: extendedWidth } : void 0,
|
|
3491
3778
|
children: [
|
|
3492
|
-
/* @__PURE__ */ (0,
|
|
3779
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
|
|
3493
3780
|
PanelEdgeTab,
|
|
3494
3781
|
{
|
|
3495
3782
|
side: "right",
|
|
@@ -3499,7 +3786,7 @@ function PlayerPanel({
|
|
|
3499
3786
|
className: "absolute right-full top-1/2 -translate-y-1/2"
|
|
3500
3787
|
}
|
|
3501
3788
|
),
|
|
3502
|
-
/* @__PURE__ */ (0,
|
|
3789
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(
|
|
3503
3790
|
"div",
|
|
3504
3791
|
{
|
|
3505
3792
|
className: (0, import_tailwind_merge2.twMerge)(
|
|
@@ -3507,15 +3794,15 @@ function PlayerPanel({
|
|
|
3507
3794
|
isExtended ? "flex-row" : "flex-col"
|
|
3508
3795
|
),
|
|
3509
3796
|
children: [
|
|
3510
|
-
/* @__PURE__ */ (0,
|
|
3511
|
-
/* @__PURE__ */ (0,
|
|
3512
|
-
/* @__PURE__ */ (0,
|
|
3513
|
-
/* @__PURE__ */ (0,
|
|
3514
|
-
playerTrackObject?.loadError && /* @__PURE__ */ (0,
|
|
3797
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
|
|
3798
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
|
|
3799
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("div", { className: "relative w-full shrink-0", children: [
|
|
3800
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_player13.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
|
|
3801
|
+
playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: playerTrackObject.loadError })
|
|
3515
3802
|
] }),
|
|
3516
|
-
/* @__PURE__ */ (0,
|
|
3803
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)(PlayerControls, {})
|
|
3517
3804
|
] }),
|
|
3518
|
-
/* @__PURE__ */ (0,
|
|
3805
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
|
|
3519
3806
|
TrackListSidebar,
|
|
3520
3807
|
{
|
|
3521
3808
|
layout: "inline",
|
|
@@ -3526,7 +3813,8 @@ function PlayerPanel({
|
|
|
3526
3813
|
"min-h-0 min-w-0 flex-1",
|
|
3527
3814
|
isExtended && "border-l border-zinc-800"
|
|
3528
3815
|
),
|
|
3529
|
-
renderArtistLink
|
|
3816
|
+
renderArtistLink,
|
|
3817
|
+
renderSongLink
|
|
3530
3818
|
}
|
|
3531
3819
|
)
|
|
3532
3820
|
]
|
|
@@ -3540,33 +3828,33 @@ function PlayerPanel({
|
|
|
3540
3828
|
// src/genre-tree/Rating.tsx
|
|
3541
3829
|
var import_uuid = require("uuid");
|
|
3542
3830
|
var import_ai = require("react-icons/ai");
|
|
3543
|
-
var
|
|
3544
|
-
var
|
|
3831
|
+
var import_genre_tree25 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3832
|
+
var import_jsx_runtime46 = require("react/jsx-runtime");
|
|
3545
3833
|
function Rating({ rating, handleChange }) {
|
|
3546
3834
|
const instanceId = (0, import_uuid.v4)();
|
|
3547
|
-
return /* @__PURE__ */ (0,
|
|
3548
|
-
/* @__PURE__ */ (0,
|
|
3835
|
+
return /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
|
|
3836
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
3549
3837
|
"input",
|
|
3550
3838
|
{
|
|
3551
3839
|
id: `star-${instanceId}-${i}`,
|
|
3552
3840
|
className: "w-6 h-6 opacity-0",
|
|
3553
3841
|
type: "radio",
|
|
3554
3842
|
name: "rating",
|
|
3555
|
-
value: i === 0 ?
|
|
3843
|
+
value: i === 0 ? import_genre_tree25.FORM_RATING_NULL_VALUE : i,
|
|
3556
3844
|
checked: rating === (i === 0 ? null : i),
|
|
3557
3845
|
onChange: handleChange
|
|
3558
3846
|
}
|
|
3559
3847
|
),
|
|
3560
|
-
/* @__PURE__ */ (0,
|
|
3561
|
-
/* @__PURE__ */ (0,
|
|
3848
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "flex", children: [
|
|
3849
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
3562
3850
|
import_ai.AiOutlineStar,
|
|
3563
3851
|
{
|
|
3564
|
-
className: `w-7 h-7 absolute top-0 left-0 ${rating ===
|
|
3852
|
+
className: `w-7 h-7 absolute top-0 left-0 ${rating === import_genre_tree25.FORM_RATING_NULL_VALUE ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
|
|
3565
3853
|
}
|
|
3566
3854
|
),
|
|
3567
|
-
/* @__PURE__ */ (0,
|
|
3855
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { className: "flex-grow w-8" }),
|
|
3568
3856
|
" "
|
|
3569
|
-
] }) : /* @__PURE__ */ (0,
|
|
3857
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
3570
3858
|
import_ai.AiFillStar,
|
|
3571
3859
|
{
|
|
3572
3860
|
className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
|