@behindthemusictree/app-kit-ui 29.0.1 → 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-TFLX6C5Q.js → chunk-2HE2HDFA.js} +552 -253
- package/dist/chunk-2HE2HDFA.js.map +1 -0
- package/dist/genre-tree/index.cjs +588 -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 +585 -296
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +6 -6
- package/dist/chunk-TFLX6C5Q.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,30 +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
|
{
|
|
2649
|
+
variant: "floating",
|
|
2448
2650
|
"aria-label": "Size nodes by",
|
|
2449
2651
|
value,
|
|
2450
2652
|
onChange,
|
|
2451
2653
|
options: [
|
|
2452
|
-
{ value: "songs", label: /* @__PURE__ */ (0,
|
|
2453
|
-
{ 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" }
|
|
2454
2656
|
]
|
|
2455
2657
|
}
|
|
2456
2658
|
);
|
|
2457
2659
|
}
|
|
2458
2660
|
|
|
2459
2661
|
// src/genre-tree/GenreTreeView.tsx
|
|
2460
|
-
var
|
|
2662
|
+
var import_jsx_runtime41 = require("react/jsx-runtime");
|
|
2461
2663
|
var PREVIOUS_BUTTON_CSS = `
|
|
2462
2664
|
.genre-tree-view .search-overlay .gtv-info-panel-previous { display: none; }
|
|
2463
2665
|
.genre-tree-view:has(.search-overlay .gtv-info-panel-previous) .genre-search input { padding-left: 2.75rem; }
|
|
@@ -2501,6 +2703,13 @@ function GenreTreeView({
|
|
|
2501
2703
|
artistSearch = true,
|
|
2502
2704
|
onArtistSelect,
|
|
2503
2705
|
artistSearchBaseUrl,
|
|
2706
|
+
songUuid: controlledSongUuid,
|
|
2707
|
+
onSongUuidChange,
|
|
2708
|
+
songExpanded: controlledSongExpanded,
|
|
2709
|
+
onSongExpandedChange,
|
|
2710
|
+
songSearch = true,
|
|
2711
|
+
onSongSelect,
|
|
2712
|
+
renderSongLink,
|
|
2504
2713
|
renderSummary,
|
|
2505
2714
|
renderEssentialTracks,
|
|
2506
2715
|
renderSongLeading,
|
|
@@ -2515,14 +2724,21 @@ function GenreTreeView({
|
|
|
2515
2724
|
placement = "default",
|
|
2516
2725
|
...focus
|
|
2517
2726
|
}) {
|
|
2518
|
-
const getShareUrlRef = (0,
|
|
2727
|
+
const getShareUrlRef = (0, import_react17.useRef)(getShareUrl);
|
|
2519
2728
|
getShareUrlRef.current = getShareUrl;
|
|
2520
|
-
const [internalArtistUuid, setInternalArtistUuid] = (0,
|
|
2729
|
+
const [internalArtistUuid, setInternalArtistUuid] = (0, import_react17.useState)(null);
|
|
2521
2730
|
const isArtistControlled = controlledArtistUuid !== void 0;
|
|
2522
2731
|
const artistUuid = isArtistControlled ? controlledArtistUuid : internalArtistUuid;
|
|
2523
|
-
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");
|
|
2524
2740
|
const sizeMetric = controlledSizeMetric ?? internalSizeMetric;
|
|
2525
|
-
const deferredSizeMetric = (0,
|
|
2741
|
+
const deferredSizeMetric = (0, import_react17.useDeferredValue)(sizeMetric);
|
|
2526
2742
|
const changeSizeMetric = (metric) => {
|
|
2527
2743
|
if (controlledSizeMetric === void 0) setInternalSizeMetric(metric);
|
|
2528
2744
|
onSizeMetricChange?.(metric);
|
|
@@ -2549,7 +2765,7 @@ function GenreTreeView({
|
|
|
2549
2765
|
allowWheelRotation: internalAllowWheelRotation,
|
|
2550
2766
|
toggleWheelRotation,
|
|
2551
2767
|
onNodeHover: handleNodeHover
|
|
2552
|
-
} = (0,
|
|
2768
|
+
} = (0, import_genre_tree19.useGenreTreeViewState)({
|
|
2553
2769
|
scope,
|
|
2554
2770
|
getBackendBaseUrl,
|
|
2555
2771
|
viewMode: controlledViewMode,
|
|
@@ -2559,7 +2775,7 @@ function GenreTreeView({
|
|
|
2559
2775
|
artistUuid
|
|
2560
2776
|
});
|
|
2561
2777
|
const restoreRevision = viewportRestore?.revision;
|
|
2562
|
-
const [stackedRestoreOwner, setStackedRestoreOwner] = (0,
|
|
2778
|
+
const [stackedRestoreOwner, setStackedRestoreOwner] = (0, import_react17.useState)({ revision: restoreRevision, rootUuid: selectedGenrePlaylist?.root.uuid });
|
|
2563
2779
|
let restoreRootUuid = stackedRestoreOwner.rootUuid;
|
|
2564
2780
|
if (stackedRestoreOwner.revision !== restoreRevision) {
|
|
2565
2781
|
restoreRootUuid = selectedGenrePlaylist?.root.uuid;
|
|
@@ -2568,14 +2784,14 @@ function GenreTreeView({
|
|
|
2568
2784
|
restoreRootUuid = selectedGenrePlaylist.root.uuid;
|
|
2569
2785
|
setStackedRestoreOwner({ revision: restoreRevision, rootUuid: restoreRootUuid });
|
|
2570
2786
|
}
|
|
2571
|
-
const isGenreUpdating = (0, import_react_query3.useIsMutating)({ mutationKey:
|
|
2572
|
-
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);
|
|
2573
2789
|
const busy = deferredSizeMetric !== sizeMetric || isTreeFetching || isGenreUpdating;
|
|
2574
2790
|
const isArtistFiltered = artistUuid !== null;
|
|
2575
2791
|
const canSizeByViews = !isArtistFiltered && hasViewsCount(visibleGenrePlaylists);
|
|
2576
|
-
const artistResult = (0,
|
|
2792
|
+
const artistResult = (0, import_genre_tree19.useArtist)(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
|
|
2577
2793
|
const filterArtistName = artistResult.status === "success" ? artistResult.artist.name : null;
|
|
2578
|
-
const filterArtist = (0,
|
|
2794
|
+
const filterArtist = (0, import_react17.useMemo)(
|
|
2579
2795
|
() => artistUuid !== null && filterArtistName !== null ? { uuid: artistUuid, name: filterArtistName } : null,
|
|
2580
2796
|
[artistUuid, filterArtistName]
|
|
2581
2797
|
);
|
|
@@ -2583,27 +2799,72 @@ function GenreTreeView({
|
|
|
2583
2799
|
const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: visibleGenrePlaylists, artist: filterArtist });
|
|
2584
2800
|
const openArtist = (uuid) => {
|
|
2585
2801
|
if (selectedNodeId !== null) clearSelection();
|
|
2802
|
+
if (songUuid !== null) changeSong(null);
|
|
2586
2803
|
changeArtist(uuid);
|
|
2587
2804
|
};
|
|
2588
|
-
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);
|
|
2589
2817
|
const artistBaseUrl = artistSearchBaseUrl ?? getBackendBaseUrl;
|
|
2818
|
+
const songBaseUrl = getBackendBaseUrl;
|
|
2590
2819
|
const artistPanel = useArtistPanel({
|
|
2591
2820
|
uuid: artistUuid,
|
|
2592
2821
|
scope,
|
|
2593
2822
|
getBackendBaseUrl: artistBaseUrl,
|
|
2594
2823
|
getShareUrl: getShareUrlRef.current,
|
|
2595
|
-
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 })),
|
|
2596
2825
|
expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
|
|
2597
2826
|
onExpandedChange: (on) => {
|
|
2598
2827
|
if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
|
|
2599
2828
|
onArtistExpandedChange?.(on);
|
|
2600
2829
|
},
|
|
2830
|
+
renderSongLink: resolvedRenderSongLink,
|
|
2601
2831
|
onClose: () => changeArtist(null)
|
|
2602
2832
|
});
|
|
2603
|
-
const
|
|
2604
|
-
const
|
|
2605
|
-
|
|
2606
|
-
|
|
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);
|
|
2607
2868
|
const isCollapsed = collapsed ?? uncontrolledCollapsed;
|
|
2608
2869
|
const isExpanded = expanded ?? uncontrolledExpanded;
|
|
2609
2870
|
const changeCollapsed = (next) => {
|
|
@@ -2614,9 +2875,9 @@ function GenreTreeView({
|
|
|
2614
2875
|
setUncontrolledExpanded(next);
|
|
2615
2876
|
onExpandedChange?.(next);
|
|
2616
2877
|
};
|
|
2617
|
-
(0,
|
|
2878
|
+
(0, import_react17.useEffect)(() => {
|
|
2618
2879
|
setUncontrolledCollapsed(false);
|
|
2619
|
-
}, [selectedNodeId, artistUuid]);
|
|
2880
|
+
}, [selectedNodeId, artistUuid, songUuid]);
|
|
2620
2881
|
const isDetailHidden = isCollapsed || !hasSelectedGenreNode && !activeOverlayPanel;
|
|
2621
2882
|
useLeftDetailPanelLayout(!isDetailHidden, placement === "regions");
|
|
2622
2883
|
const detailOverlay = placement === "regions" && activeOverlayPanel?.onExpandedChange ? activeOverlayPanel : void 0;
|
|
@@ -2630,14 +2891,14 @@ function GenreTreeView({
|
|
|
2630
2891
|
hideExpandButton: placement === "regions" ? true : void 0,
|
|
2631
2892
|
overlayPanel: detailOverlay ? { ...detailOverlay, collapsed: isCollapsed, hideExpandButton: true } : activeOverlayPanel
|
|
2632
2893
|
};
|
|
2633
|
-
const [isWheelRevealed, setIsWheelRevealed] = (0,
|
|
2894
|
+
const [isWheelRevealed, setIsWheelRevealed] = (0, import_react17.useState)(false);
|
|
2634
2895
|
const usesHandoff = viewMode === "wheel" || viewMode === "pop-core";
|
|
2635
2896
|
const isTreeReady = !isLoading && (!usesHandoff || isWheelRevealed);
|
|
2636
|
-
const rootRef = (0,
|
|
2897
|
+
const rootRef = (0, import_react17.useRef)(null);
|
|
2637
2898
|
const handlePrevious = () => {
|
|
2638
2899
|
rootRef.current?.querySelector(".search-overlay .gtv-info-panel-previous")?.click();
|
|
2639
2900
|
};
|
|
2640
|
-
(0,
|
|
2901
|
+
(0, import_react17.useEffect)(() => {
|
|
2641
2902
|
if (!isTreeReady || viewMode !== "pop-core" || !hasSelectedGenreNode || !selectedGenrePlaylist) return;
|
|
2642
2903
|
const root = rootRef.current;
|
|
2643
2904
|
const centerChip = root?.querySelector(".gtv-wheel-center-node .gtv-wheel-chip");
|
|
@@ -2647,21 +2908,22 @@ function GenreTreeView({
|
|
|
2647
2908
|
centerChip.click();
|
|
2648
2909
|
}, [hasSelectedGenreNode, isTreeReady, selectedGenrePlaylist, viewMode]);
|
|
2649
2910
|
const { showPopup, hidePopup } = (0, import_popup6.usePopup)();
|
|
2650
|
-
const [searchText, setSearchText] = (0,
|
|
2651
|
-
const artistSearchResult = (0,
|
|
2652
|
-
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",
|
|
2653
2915
|
label: "Artists",
|
|
2654
2916
|
items: artistSearchResult.artists.map((artist) => ({
|
|
2655
2917
|
key: artist.uuid,
|
|
2656
|
-
content: /* @__PURE__ */ (0,
|
|
2657
|
-
/* @__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: [
|
|
2658
2920
|
artist.name,
|
|
2659
|
-
artist.disambiguation && /* @__PURE__ */ (0,
|
|
2921
|
+
artist.disambiguation && /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { className: "font-normal text-[var(--color-neutral-500)]", children: [
|
|
2660
2922
|
" \xB7 ",
|
|
2661
2923
|
artist.disambiguation
|
|
2662
2924
|
] })
|
|
2663
2925
|
] }),
|
|
2664
|
-
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: [
|
|
2665
2927
|
"also known as ",
|
|
2666
2928
|
artist.matchedAlias
|
|
2667
2929
|
] })
|
|
@@ -2674,11 +2936,32 @@ function GenreTreeView({
|
|
|
2674
2936
|
status: artistSearchResult.isError ? "Search failed" : artistSearchResult.settled && artistSearchResult.artists.length === 0 ? "No artists found" : "",
|
|
2675
2937
|
loading: artistSearchResult.pending
|
|
2676
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);
|
|
2677
2958
|
const handleSelectedNodeChange = (node) => {
|
|
2959
|
+
if (node !== null && songUuid !== null) changeSong(null);
|
|
2678
2960
|
selectNode(node);
|
|
2679
2961
|
};
|
|
2680
2962
|
const handleGenreSearchClear = clearSelection;
|
|
2681
2963
|
const handleGenreSearchSelect = (genrePlaylist) => {
|
|
2964
|
+
if (songUuid !== null) changeSong(null);
|
|
2682
2965
|
selectGenrePlaylist(genrePlaylist);
|
|
2683
2966
|
};
|
|
2684
2967
|
const selectedUuid = selectedGenrePlaylist?.uuid;
|
|
@@ -2687,13 +2970,13 @@ function GenreTreeView({
|
|
|
2687
2970
|
const handleGenreSearchZoomIn = () => {
|
|
2688
2971
|
if (selectedUuid) focus.onFocusGenreChange?.(selectedUuid, "zoom-in");
|
|
2689
2972
|
};
|
|
2690
|
-
const zoomInRef = (0,
|
|
2973
|
+
const zoomInRef = (0, import_react17.useRef)(void 0);
|
|
2691
2974
|
zoomInRef.current = canZoomIn ? handleGenreSearchZoomIn : void 0;
|
|
2692
|
-
const renderInfoPanelBody = (0,
|
|
2975
|
+
const renderInfoPanelBody = (0, import_react17.useCallback)(
|
|
2693
2976
|
(node, { toolbar, ancestors, family }) => {
|
|
2694
2977
|
const genrePlaylist = genrePlaylistByNodeId.get(node.id);
|
|
2695
2978
|
const criteria = genrePlaylist?.criteria;
|
|
2696
|
-
if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ (0,
|
|
2979
|
+
if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(import_jsx_runtime41.Fragment, { children: [
|
|
2697
2980
|
toolbar,
|
|
2698
2981
|
family
|
|
2699
2982
|
] }), aside: ancestors };
|
|
@@ -2713,7 +2996,7 @@ function GenreTreeView({
|
|
|
2713
2996
|
(sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist.uuid && sibling.criteria?.side === "pop"
|
|
2714
2997
|
) : void 0;
|
|
2715
2998
|
return {
|
|
2716
|
-
primary: /* @__PURE__ */ (0,
|
|
2999
|
+
primary: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
2717
3000
|
GenreInfoPanelBody,
|
|
2718
3001
|
{
|
|
2719
3002
|
toolbar,
|
|
@@ -2732,7 +3015,7 @@ function GenreTreeView({
|
|
|
2732
3015
|
onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
|
|
2733
3016
|
onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
|
|
2734
3017
|
onChangeParent: readOnly ? void 0 : () => showPopup(
|
|
2735
|
-
/* @__PURE__ */ (0,
|
|
3018
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
2736
3019
|
ChangeParentPopup,
|
|
2737
3020
|
{
|
|
2738
3021
|
genrePlaylist: { ...genrePlaylist, criteria },
|
|
@@ -2753,6 +3036,7 @@ function GenreTreeView({
|
|
|
2753
3036
|
criteriaOverviewSchema,
|
|
2754
3037
|
renderGenreDetailExtras,
|
|
2755
3038
|
renderArtistLink,
|
|
3039
|
+
renderSongLink: resolvedRenderSongLink,
|
|
2756
3040
|
artist: filterArtist,
|
|
2757
3041
|
renderSummary,
|
|
2758
3042
|
renderEssentialTracks,
|
|
@@ -2783,6 +3067,7 @@ function GenreTreeView({
|
|
|
2783
3067
|
criteriaOverviewSchema,
|
|
2784
3068
|
renderGenreDetailExtras,
|
|
2785
3069
|
renderArtistLink,
|
|
3070
|
+
resolvedRenderSongLink,
|
|
2786
3071
|
renderSummary,
|
|
2787
3072
|
renderEssentialTracks,
|
|
2788
3073
|
renderSongLeading,
|
|
@@ -2792,7 +3077,7 @@ function GenreTreeView({
|
|
|
2792
3077
|
);
|
|
2793
3078
|
const isRotationControlled = controlledAllowWheelRotation !== void 0;
|
|
2794
3079
|
const allowWheelRotation = controlledAllowWheelRotation ?? internalAllowWheelRotation;
|
|
2795
|
-
const detailTab = placement === "regions" && (hasSelectedGenreNode || detailOverlay) ? /* @__PURE__ */ (0,
|
|
3080
|
+
const detailTab = placement === "regions" && (hasSelectedGenreNode || detailOverlay) ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
2796
3081
|
PanelEdgeTab,
|
|
2797
3082
|
{
|
|
2798
3083
|
side: "left",
|
|
@@ -2802,8 +3087,8 @@ function GenreTreeView({
|
|
|
2802
3087
|
) : null;
|
|
2803
3088
|
const renderArtistFilter = () => {
|
|
2804
3089
|
if (isLoading || !filterArtist) return null;
|
|
2805
|
-
return /* @__PURE__ */ (0,
|
|
2806
|
-
/* @__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)(
|
|
2807
3092
|
"button",
|
|
2808
3093
|
{
|
|
2809
3094
|
type: "button",
|
|
@@ -2815,7 +3100,7 @@ function GenreTreeView({
|
|
|
2815
3100
|
children: filterArtist.name
|
|
2816
3101
|
}
|
|
2817
3102
|
),
|
|
2818
|
-
/* @__PURE__ */ (0,
|
|
3103
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
2819
3104
|
"button",
|
|
2820
3105
|
{
|
|
2821
3106
|
type: "button",
|
|
@@ -2827,23 +3112,23 @@ function GenreTreeView({
|
|
|
2827
3112
|
)
|
|
2828
3113
|
] });
|
|
2829
3114
|
};
|
|
2830
|
-
const actions = /* @__PURE__ */ (0,
|
|
2831
|
-
!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)(
|
|
2832
3117
|
GenreSearch,
|
|
2833
3118
|
{
|
|
2834
3119
|
genrePlaylists: visibleGenrePlaylists,
|
|
2835
3120
|
onSelect: handleGenreSearchSelect,
|
|
2836
|
-
selectedName: selectedGenrePlaylist?.name ??
|
|
2837
|
-
onClear: builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
|
|
3121
|
+
selectedName: selectedGenrePlaylist?.name ?? builtInPanel?.title ?? null,
|
|
3122
|
+
onClear: builtInSongPanel ? () => changeSong(null) : builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
|
|
2838
3123
|
onPrevious: handlePrevious,
|
|
2839
|
-
onQueryChange: artistSearch ? setSearchText : void 0,
|
|
2840
|
-
|
|
3124
|
+
onQueryChange: artistSearch || songSearch ? setSearchText : void 0,
|
|
3125
|
+
extraGroups
|
|
2841
3126
|
}
|
|
2842
3127
|
),
|
|
2843
|
-
!isLoading && filterArtist && /* @__PURE__ */ (0,
|
|
2844
|
-
/* @__PURE__ */ (0,
|
|
2845
|
-
!isLoading && !isControlled && /* @__PURE__ */ (0,
|
|
2846
|
-
|
|
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,
|
|
2847
3132
|
{
|
|
2848
3133
|
"aria-label": "Tree view mode",
|
|
2849
3134
|
value: viewMode,
|
|
@@ -2856,14 +3141,14 @@ function GenreTreeView({
|
|
|
2856
3141
|
]
|
|
2857
3142
|
}
|
|
2858
3143
|
),
|
|
2859
|
-
!isLoading && showDisplayOptions && /* @__PURE__ */ (0,
|
|
3144
|
+
!isLoading && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
2860
3145
|
"div",
|
|
2861
3146
|
{
|
|
2862
3147
|
className: "flex items-center gap-1",
|
|
2863
3148
|
role: "group",
|
|
2864
3149
|
"aria-label": "Tree display options",
|
|
2865
|
-
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0,
|
|
2866
|
-
|
|
3150
|
+
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
3151
|
+
import_ui18.Button,
|
|
2867
3152
|
{
|
|
2868
3153
|
variant: allowWheelRotation ? "default" : "outline",
|
|
2869
3154
|
size: "sm",
|
|
@@ -2873,52 +3158,52 @@ function GenreTreeView({
|
|
|
2873
3158
|
)
|
|
2874
3159
|
}
|
|
2875
3160
|
),
|
|
2876
|
-
!isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0,
|
|
2877
|
-
|
|
3161
|
+
!isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
3162
|
+
import_ui18.IconTextButton,
|
|
2878
3163
|
{
|
|
2879
|
-
icon:
|
|
3164
|
+
icon: import_lucide_react16.Plus,
|
|
2880
3165
|
text: "Add root",
|
|
2881
3166
|
onClick: () => handleGenreCreationAction(null)
|
|
2882
3167
|
}
|
|
2883
3168
|
)
|
|
2884
3169
|
] })
|
|
2885
3170
|
] });
|
|
2886
|
-
return /* @__PURE__ */ (0,
|
|
3171
|
+
return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
|
|
2887
3172
|
"div",
|
|
2888
3173
|
{
|
|
2889
3174
|
ref: rootRef,
|
|
2890
3175
|
className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
|
|
2891
3176
|
children: [
|
|
2892
|
-
/* @__PURE__ */ (0,
|
|
2893
|
-
/* @__PURE__ */ (0,
|
|
3177
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("style", { children: PREVIOUS_BUTTON_CSS }),
|
|
3178
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
2894
3179
|
"div",
|
|
2895
3180
|
{
|
|
2896
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"}`,
|
|
2897
3182
|
children: actions
|
|
2898
3183
|
}
|
|
2899
3184
|
),
|
|
2900
|
-
!isLoading && filterArtist && /* @__PURE__ */ (0,
|
|
3185
|
+
!isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
2901
3186
|
"div",
|
|
2902
3187
|
{
|
|
2903
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"}`,
|
|
2904
3189
|
children: renderArtistFilter()
|
|
2905
3190
|
}
|
|
2906
3191
|
),
|
|
2907
|
-
detailTab && /* @__PURE__ */ (0,
|
|
3192
|
+
detailTab && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
2908
3193
|
"div",
|
|
2909
3194
|
{
|
|
2910
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))]"}`,
|
|
2911
3196
|
children: detailTab
|
|
2912
3197
|
}
|
|
2913
3198
|
),
|
|
2914
|
-
/* @__PURE__ */ (0,
|
|
2915
|
-
!isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0,
|
|
2916
|
-
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" ? (
|
|
2917
3202
|
// The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
|
|
2918
3203
|
// and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
|
|
2919
3204
|
// Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
|
|
2920
3205
|
// The panel's own Zoom in button is hidden: the search bar carries it.
|
|
2921
|
-
/* @__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)(
|
|
2922
3207
|
GenrePlaylistTreeWheel,
|
|
2923
3208
|
{
|
|
2924
3209
|
scope,
|
|
@@ -2937,9 +3222,9 @@ function GenreTreeView({
|
|
|
2937
3222
|
allowWheelRotation,
|
|
2938
3223
|
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2939
3224
|
busy,
|
|
2940
|
-
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0,
|
|
3225
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
2941
3226
|
floatingControlsLeading,
|
|
2942
|
-
canSizeByViews && /* @__PURE__ */ (0,
|
|
3227
|
+
canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2943
3228
|
] }) : void 0,
|
|
2944
3229
|
defaultViewport,
|
|
2945
3230
|
viewportRestore,
|
|
@@ -2949,7 +3234,7 @@ function GenreTreeView({
|
|
|
2949
3234
|
) }) })
|
|
2950
3235
|
) : viewMode === "pop-core" ? (
|
|
2951
3236
|
// Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
|
|
2952
|
-
/* @__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)(
|
|
2953
3238
|
GenrePlaylistTreeWheelRadialPopCore,
|
|
2954
3239
|
{
|
|
2955
3240
|
scope,
|
|
@@ -2968,9 +3253,9 @@ function GenreTreeView({
|
|
|
2968
3253
|
allowWheelRotation,
|
|
2969
3254
|
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
2970
3255
|
busy,
|
|
2971
|
-
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0,
|
|
3256
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
2972
3257
|
floatingControlsLeading,
|
|
2973
|
-
canSizeByViews && /* @__PURE__ */ (0,
|
|
3258
|
+
canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
2974
3259
|
] }) : void 0,
|
|
2975
3260
|
defaultViewport,
|
|
2976
3261
|
viewportRestore,
|
|
@@ -2979,7 +3264,7 @@ function GenreTreeView({
|
|
|
2979
3264
|
...focus
|
|
2980
3265
|
}
|
|
2981
3266
|
) }) })
|
|
2982
|
-
) : 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)(
|
|
2983
3268
|
GenrePlaylistTreeWheelRadialPopCore,
|
|
2984
3269
|
{
|
|
2985
3270
|
outline: true,
|
|
@@ -2998,13 +3283,13 @@ function GenreTreeView({
|
|
|
2998
3283
|
readOnly,
|
|
2999
3284
|
...infoPanelCuration
|
|
3000
3285
|
}
|
|
3001
|
-
) }) : /* @__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(
|
|
3002
3287
|
([uuid, genrePlaylistTreePerRoot], rootIndex) => {
|
|
3003
|
-
return /* @__PURE__ */ (0,
|
|
3288
|
+
return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
3004
3289
|
"div",
|
|
3005
3290
|
{
|
|
3006
3291
|
className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
|
|
3007
|
-
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)(
|
|
3008
3293
|
GenrePlaylistTreePerRoot,
|
|
3009
3294
|
{
|
|
3010
3295
|
scope,
|
|
@@ -3041,11 +3326,11 @@ function GenreTreeView({
|
|
|
3041
3326
|
}
|
|
3042
3327
|
|
|
3043
3328
|
// src/genre-tree/GenreDetailPanel.tsx
|
|
3044
|
-
var
|
|
3329
|
+
var import_react18 = require("react");
|
|
3045
3330
|
var import_genre_tree_view5 = require("@behindthemusictree/genre-tree-view");
|
|
3046
|
-
var
|
|
3047
|
-
var
|
|
3048
|
-
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");
|
|
3049
3334
|
var ITEM_COUNT_LABEL = "Songs";
|
|
3050
3335
|
var RELATIVE_ITEM_COUNT = 1;
|
|
3051
3336
|
function toNode(genre, parentId) {
|
|
@@ -3072,9 +3357,9 @@ function GenreDetailPanel({
|
|
|
3072
3357
|
onExpandedChange,
|
|
3073
3358
|
extras
|
|
3074
3359
|
}) {
|
|
3075
|
-
const { data: detail, isPending, isError } = (0,
|
|
3360
|
+
const { data: detail, isPending, isError } = (0, import_genre_tree20.useFetchGenreDetail)(genreUuid, scope, getBackendBaseUrl);
|
|
3076
3361
|
const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: [] });
|
|
3077
|
-
const view = (0,
|
|
3362
|
+
const view = (0, import_react18.useMemo)(() => {
|
|
3078
3363
|
if (!detail) return null;
|
|
3079
3364
|
const rootColor = (0, import_genre_tree_view5.getGenreTreeColor)(detail.root.wikidataId);
|
|
3080
3365
|
const relative = (genre, parentId) => ({
|
|
@@ -3100,17 +3385,17 @@ function GenreDetailPanel({
|
|
|
3100
3385
|
};
|
|
3101
3386
|
}, [detail]);
|
|
3102
3387
|
if (isError) {
|
|
3103
|
-
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." });
|
|
3104
3389
|
}
|
|
3105
3390
|
if (isPending || !detail || !view) {
|
|
3106
|
-
return /* @__PURE__ */ (0,
|
|
3107
|
-
/* @__PURE__ */ (0,
|
|
3108
|
-
/* @__PURE__ */ (0,
|
|
3109
|
-
/* @__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" })
|
|
3110
3395
|
] });
|
|
3111
3396
|
}
|
|
3112
3397
|
const renderBody = (node, sections) => ({
|
|
3113
|
-
primary: /* @__PURE__ */ (0,
|
|
3398
|
+
primary: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
3114
3399
|
GenreInfoPanelBody,
|
|
3115
3400
|
{
|
|
3116
3401
|
toolbar: sections.toolbar,
|
|
@@ -3127,7 +3412,7 @@ function GenreDetailPanel({
|
|
|
3127
3412
|
),
|
|
3128
3413
|
aside: null
|
|
3129
3414
|
});
|
|
3130
|
-
return /* @__PURE__ */ (0,
|
|
3415
|
+
return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
3131
3416
|
import_genre_tree_view5.InfoPanel,
|
|
3132
3417
|
{
|
|
3133
3418
|
node: view.node,
|
|
@@ -3153,13 +3438,13 @@ function GenreDetailPanel({
|
|
|
3153
3438
|
var import_genre_tree_view6 = require("@behindthemusictree/genre-tree-view");
|
|
3154
3439
|
|
|
3155
3440
|
// src/genre-tree/track-list-sidebar/TrackListSidebar.tsx
|
|
3156
|
-
var
|
|
3157
|
-
var
|
|
3158
|
-
var
|
|
3159
|
-
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");
|
|
3160
3445
|
var import_track_noun7 = require("@behindthemusictree/app-kit/track-noun");
|
|
3161
|
-
var
|
|
3162
|
-
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;
|
|
3163
3448
|
function TrackListSidebar({
|
|
3164
3449
|
className: className2 = "",
|
|
3165
3450
|
renderDuration,
|
|
@@ -3168,20 +3453,21 @@ function TrackListSidebar({
|
|
|
3168
3453
|
isCollapsed = false,
|
|
3169
3454
|
onExpand,
|
|
3170
3455
|
onMinimize,
|
|
3171
|
-
renderArtistLink
|
|
3456
|
+
renderArtistLink,
|
|
3457
|
+
renderSongLink
|
|
3172
3458
|
}) {
|
|
3173
|
-
const { trackList, loadMore, autoAdvanceScrollRequest } = (0,
|
|
3459
|
+
const { trackList, loadMore, autoAdvanceScrollRequest } = (0, import_genre_tree21.useTrackList)();
|
|
3174
3460
|
const trackNoun = (0, import_track_noun7.useTrackNoun)();
|
|
3175
|
-
const trackListRef = (0,
|
|
3176
|
-
const previousOriginRef = (0,
|
|
3177
|
-
const handledAutoAdvanceRequestIdRef = (0,
|
|
3178
|
-
const shouldFollowPlaybackRef = (0,
|
|
3179
|
-
const programmaticScrollTopRef = (0,
|
|
3180
|
-
const userScrollIntentRef = (0,
|
|
3181
|
-
const userScrollIntentTimerRef = (0,
|
|
3182
|
-
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);
|
|
3183
3469
|
const hasMore = !!trackList && trackList.nextPage !== null;
|
|
3184
|
-
(0,
|
|
3470
|
+
(0, import_react19.useEffect)(() => {
|
|
3185
3471
|
const sentinel = loadMoreSentinelRef.current;
|
|
3186
3472
|
if (!hasMore || !sentinel) return;
|
|
3187
3473
|
const observer = new IntersectionObserver((entries) => {
|
|
@@ -3201,11 +3487,11 @@ function TrackListSidebar({
|
|
|
3201
3487
|
programmaticScrollTopRef.current = null;
|
|
3202
3488
|
if (trackListRef.current) trackListRef.current.scrollTop = 0;
|
|
3203
3489
|
}, [trackList?.origin]);
|
|
3204
|
-
(0,
|
|
3490
|
+
(0, import_react19.useEffect)(() => {
|
|
3205
3491
|
const list = trackListRef.current;
|
|
3206
3492
|
if (!isCollapsed && list?.scrollTop === 0) shouldFollowPlaybackRef.current = true;
|
|
3207
3493
|
}, [isCollapsed]);
|
|
3208
|
-
(0,
|
|
3494
|
+
(0, import_react19.useEffect)(
|
|
3209
3495
|
() => () => {
|
|
3210
3496
|
if (userScrollIntentTimerRef.current !== null) clearTimeout(userScrollIntentTimerRef.current);
|
|
3211
3497
|
},
|
|
@@ -3259,25 +3545,25 @@ function TrackListSidebar({
|
|
|
3259
3545
|
}
|
|
3260
3546
|
};
|
|
3261
3547
|
const positionClasses = layout === "inline" ? "relative w-full flex h-full flex-col" : "fixed right-0 w-[50rem] flex flex-col";
|
|
3262
|
-
return trackList ? /* @__PURE__ */ (0,
|
|
3548
|
+
return trackList ? /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(
|
|
3263
3549
|
"div",
|
|
3264
3550
|
{
|
|
3265
3551
|
"data-theme": "dark",
|
|
3266
3552
|
className: `track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${className2}`,
|
|
3267
3553
|
style: layout === "fixed" ? { top: "63.5px", maxHeight: "calc(100vh - 63.5px - 79px)" } : void 0,
|
|
3268
3554
|
children: [
|
|
3269
|
-
/* @__PURE__ */ (0,
|
|
3270
|
-
/* @__PURE__ */ (0,
|
|
3271
|
-
/* @__PURE__ */ (0,
|
|
3272
|
-
/* @__PURE__ */ (0,
|
|
3273
|
-
/* @__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 }) })
|
|
3274
3560
|
] }),
|
|
3275
|
-
/* @__PURE__ */ (0,
|
|
3276
|
-
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 `,
|
|
3277
3563
|
`${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`
|
|
3278
3564
|
] })
|
|
3279
3565
|
] }),
|
|
3280
|
-
(isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0,
|
|
3566
|
+
(isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
3281
3567
|
"button",
|
|
3282
3568
|
{
|
|
3283
3569
|
type: "button",
|
|
@@ -3286,11 +3572,11 @@ function TrackListSidebar({
|
|
|
3286
3572
|
"aria-expanded": !isCollapsed,
|
|
3287
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"}`,
|
|
3288
3574
|
onClick: isCollapsed ? onExpand : onMinimize,
|
|
3289
|
-
children: /* @__PURE__ */ (0,
|
|
3575
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react17.ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
|
|
3290
3576
|
}
|
|
3291
3577
|
)
|
|
3292
3578
|
] }),
|
|
3293
|
-
!isCollapsed && /* @__PURE__ */ (0,
|
|
3579
|
+
!isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(
|
|
3294
3580
|
"ul",
|
|
3295
3581
|
{
|
|
3296
3582
|
ref: trackListRef,
|
|
@@ -3300,17 +3586,18 @@ function TrackListSidebar({
|
|
|
3300
3586
|
onPointerDown: markUserScrollIntent,
|
|
3301
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",
|
|
3302
3588
|
children: [
|
|
3303
|
-
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)(
|
|
3304
3590
|
TrackItem,
|
|
3305
3591
|
{
|
|
3306
3592
|
track,
|
|
3307
3593
|
position: index + 1,
|
|
3308
3594
|
renderDuration,
|
|
3309
3595
|
renderActions,
|
|
3310
|
-
renderArtistLink
|
|
3596
|
+
renderArtistLink,
|
|
3597
|
+
renderSongLink
|
|
3311
3598
|
}
|
|
3312
3599
|
) }, track.uuid)),
|
|
3313
|
-
hasMore && /* @__PURE__ */ (0,
|
|
3600
|
+
hasMore && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
|
|
3314
3601
|
]
|
|
3315
3602
|
}
|
|
3316
3603
|
)
|
|
@@ -3320,16 +3607,16 @@ function TrackListSidebar({
|
|
|
3320
3607
|
}
|
|
3321
3608
|
|
|
3322
3609
|
// src/genre-tree/PlayerPanel.tsx
|
|
3323
|
-
var
|
|
3610
|
+
var import_react20 = require("react");
|
|
3324
3611
|
var import_tailwind_merge2 = require("tailwind-merge");
|
|
3325
|
-
var
|
|
3326
|
-
var
|
|
3612
|
+
var import_player13 = require("@behindthemusictree/app-kit/player");
|
|
3613
|
+
var import_genre_tree24 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3327
3614
|
|
|
3328
3615
|
// src/genre-tree/PlayerControls.tsx
|
|
3329
|
-
var
|
|
3330
|
-
var
|
|
3331
|
-
var
|
|
3332
|
-
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");
|
|
3333
3620
|
function PlayerControls() {
|
|
3334
3621
|
const {
|
|
3335
3622
|
duration,
|
|
@@ -3340,23 +3627,23 @@ function PlayerControls() {
|
|
|
3340
3627
|
seekTo,
|
|
3341
3628
|
setVolume,
|
|
3342
3629
|
volume
|
|
3343
|
-
} = (0,
|
|
3344
|
-
const { hasNextTrack, hasPreviousTrack, playNextTrack, playPreviousTrack } = (0,
|
|
3345
|
-
const currentTime = (0,
|
|
3346
|
-
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;
|
|
3347
3634
|
const isReady = !!playerTrackObject?.isReady && !isBusy && !playerTrackObject.loadError;
|
|
3348
3635
|
const safeDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;
|
|
3349
3636
|
const safeCurrentTime = Number.isFinite(currentTime) ? Math.max(0, Math.min(currentTime, safeDuration)) : 0;
|
|
3350
|
-
const isPlaying = playState ===
|
|
3351
|
-
return /* @__PURE__ */ (0,
|
|
3637
|
+
const isPlaying = playState === import_player12.PlayStates.PLAYING;
|
|
3638
|
+
return /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(
|
|
3352
3639
|
"section",
|
|
3353
3640
|
{
|
|
3354
3641
|
"aria-label": "Player controls",
|
|
3355
3642
|
className: "player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200",
|
|
3356
3643
|
children: [
|
|
3357
|
-
/* @__PURE__ */ (0,
|
|
3358
|
-
/* @__PURE__ */ (0,
|
|
3359
|
-
/* @__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)(
|
|
3360
3647
|
"input",
|
|
3361
3648
|
{
|
|
3362
3649
|
"aria-label": "Seek",
|
|
@@ -3370,11 +3657,11 @@ function PlayerControls() {
|
|
|
3370
3657
|
onChange: (event) => seekTo(Number(event.currentTarget.value))
|
|
3371
3658
|
}
|
|
3372
3659
|
),
|
|
3373
|
-
/* @__PURE__ */ (0,
|
|
3660
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { "aria-label": "Duration", children: (0, import_genre_tree23.formatTime)(safeDuration) })
|
|
3374
3661
|
] }),
|
|
3375
|
-
/* @__PURE__ */ (0,
|
|
3376
|
-
/* @__PURE__ */ (0,
|
|
3377
|
-
/* @__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)(
|
|
3378
3665
|
"button",
|
|
3379
3666
|
{
|
|
3380
3667
|
type: "button",
|
|
@@ -3383,10 +3670,10 @@ function PlayerControls() {
|
|
|
3383
3670
|
disabled: !isReady || !hasPreviousTrack,
|
|
3384
3671
|
onClick: playPreviousTrack,
|
|
3385
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",
|
|
3386
|
-
children: /* @__PURE__ */ (0,
|
|
3673
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_lucide_react18.SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
3387
3674
|
}
|
|
3388
3675
|
),
|
|
3389
|
-
/* @__PURE__ */ (0,
|
|
3676
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
3390
3677
|
"button",
|
|
3391
3678
|
{
|
|
3392
3679
|
type: "button",
|
|
@@ -3395,10 +3682,10 @@ function PlayerControls() {
|
|
|
3395
3682
|
disabled: !isReady,
|
|
3396
3683
|
onClick: handlePlayPauseAction,
|
|
3397
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",
|
|
3398
|
-
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" })
|
|
3399
3686
|
}
|
|
3400
3687
|
),
|
|
3401
|
-
/* @__PURE__ */ (0,
|
|
3688
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
3402
3689
|
"button",
|
|
3403
3690
|
{
|
|
3404
3691
|
type: "button",
|
|
@@ -3407,14 +3694,14 @@ function PlayerControls() {
|
|
|
3407
3694
|
disabled: !isReady || !hasNextTrack,
|
|
3408
3695
|
onClick: playNextTrack,
|
|
3409
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",
|
|
3410
|
-
children: /* @__PURE__ */ (0,
|
|
3697
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_lucide_react18.SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
3411
3698
|
}
|
|
3412
3699
|
)
|
|
3413
3700
|
] }),
|
|
3414
|
-
/* @__PURE__ */ (0,
|
|
3415
|
-
volume === 0 ? /* @__PURE__ */ (0,
|
|
3416
|
-
/* @__PURE__ */ (0,
|
|
3417
|
-
/* @__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)(
|
|
3418
3705
|
"input",
|
|
3419
3706
|
{
|
|
3420
3707
|
"aria-label": "Volume",
|
|
@@ -3436,7 +3723,7 @@ function PlayerControls() {
|
|
|
3436
3723
|
}
|
|
3437
3724
|
|
|
3438
3725
|
// src/genre-tree/PlayerPanel.tsx
|
|
3439
|
-
var
|
|
3726
|
+
var import_jsx_runtime45 = require("react/jsx-runtime");
|
|
3440
3727
|
var PANEL_ID = "player-panel";
|
|
3441
3728
|
var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
|
|
3442
3729
|
function PlayerPanel({
|
|
@@ -3445,16 +3732,17 @@ function PlayerPanel({
|
|
|
3445
3732
|
expanded,
|
|
3446
3733
|
onExpandedChange,
|
|
3447
3734
|
extendedWidth = DEFAULT_EXTENDED_WIDTH,
|
|
3448
|
-
renderArtistLink
|
|
3735
|
+
renderArtistLink,
|
|
3736
|
+
renderSongLink
|
|
3449
3737
|
}) {
|
|
3450
|
-
const { playerTrackObject } = (0,
|
|
3451
|
-
const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0,
|
|
3452
|
-
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)("");
|
|
3453
3741
|
const loadedTrackTitle = playerTrackObject?.track.title;
|
|
3454
|
-
(0,
|
|
3742
|
+
(0, import_react20.useEffect)(() => {
|
|
3455
3743
|
if (loadedTrackTitle) setLastLoadedTrackTitle(loadedTrackTitle);
|
|
3456
3744
|
}, [loadedTrackTitle]);
|
|
3457
|
-
const [uncontrolledExtended, setUncontrolledExtended] = (0,
|
|
3745
|
+
const [uncontrolledExtended, setUncontrolledExtended] = (0, import_react20.useState)(false);
|
|
3458
3746
|
const isExtendRequested = expanded ?? uncontrolledExtended;
|
|
3459
3747
|
const setIsExtendRequested = (next) => {
|
|
3460
3748
|
setUncontrolledExtended(next);
|
|
@@ -3475,7 +3763,7 @@ function PlayerPanel({
|
|
|
3475
3763
|
onClick: () => setIsExtendRequested(true),
|
|
3476
3764
|
expanded: false
|
|
3477
3765
|
} : null;
|
|
3478
|
-
return /* @__PURE__ */ (0,
|
|
3766
|
+
return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(
|
|
3479
3767
|
"div",
|
|
3480
3768
|
{
|
|
3481
3769
|
id: PANEL_ID,
|
|
@@ -3488,7 +3776,7 @@ function PlayerPanel({
|
|
|
3488
3776
|
),
|
|
3489
3777
|
style: isExtended ? { width: extendedWidth } : void 0,
|
|
3490
3778
|
children: [
|
|
3491
|
-
/* @__PURE__ */ (0,
|
|
3779
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
|
|
3492
3780
|
PanelEdgeTab,
|
|
3493
3781
|
{
|
|
3494
3782
|
side: "right",
|
|
@@ -3498,7 +3786,7 @@ function PlayerPanel({
|
|
|
3498
3786
|
className: "absolute right-full top-1/2 -translate-y-1/2"
|
|
3499
3787
|
}
|
|
3500
3788
|
),
|
|
3501
|
-
/* @__PURE__ */ (0,
|
|
3789
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(
|
|
3502
3790
|
"div",
|
|
3503
3791
|
{
|
|
3504
3792
|
className: (0, import_tailwind_merge2.twMerge)(
|
|
@@ -3506,15 +3794,15 @@ function PlayerPanel({
|
|
|
3506
3794
|
isExtended ? "flex-row" : "flex-col"
|
|
3507
3795
|
),
|
|
3508
3796
|
children: [
|
|
3509
|
-
/* @__PURE__ */ (0,
|
|
3510
|
-
/* @__PURE__ */ (0,
|
|
3511
|
-
/* @__PURE__ */ (0,
|
|
3512
|
-
/* @__PURE__ */ (0,
|
|
3513
|
-
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 })
|
|
3514
3802
|
] }),
|
|
3515
|
-
/* @__PURE__ */ (0,
|
|
3803
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)(PlayerControls, {})
|
|
3516
3804
|
] }),
|
|
3517
|
-
/* @__PURE__ */ (0,
|
|
3805
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
|
|
3518
3806
|
TrackListSidebar,
|
|
3519
3807
|
{
|
|
3520
3808
|
layout: "inline",
|
|
@@ -3525,7 +3813,8 @@ function PlayerPanel({
|
|
|
3525
3813
|
"min-h-0 min-w-0 flex-1",
|
|
3526
3814
|
isExtended && "border-l border-zinc-800"
|
|
3527
3815
|
),
|
|
3528
|
-
renderArtistLink
|
|
3816
|
+
renderArtistLink,
|
|
3817
|
+
renderSongLink
|
|
3529
3818
|
}
|
|
3530
3819
|
)
|
|
3531
3820
|
]
|
|
@@ -3539,33 +3828,33 @@ function PlayerPanel({
|
|
|
3539
3828
|
// src/genre-tree/Rating.tsx
|
|
3540
3829
|
var import_uuid = require("uuid");
|
|
3541
3830
|
var import_ai = require("react-icons/ai");
|
|
3542
|
-
var
|
|
3543
|
-
var
|
|
3831
|
+
var import_genre_tree25 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3832
|
+
var import_jsx_runtime46 = require("react/jsx-runtime");
|
|
3544
3833
|
function Rating({ rating, handleChange }) {
|
|
3545
3834
|
const instanceId = (0, import_uuid.v4)();
|
|
3546
|
-
return /* @__PURE__ */ (0,
|
|
3547
|
-
/* @__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)(
|
|
3548
3837
|
"input",
|
|
3549
3838
|
{
|
|
3550
3839
|
id: `star-${instanceId}-${i}`,
|
|
3551
3840
|
className: "w-6 h-6 opacity-0",
|
|
3552
3841
|
type: "radio",
|
|
3553
3842
|
name: "rating",
|
|
3554
|
-
value: i === 0 ?
|
|
3843
|
+
value: i === 0 ? import_genre_tree25.FORM_RATING_NULL_VALUE : i,
|
|
3555
3844
|
checked: rating === (i === 0 ? null : i),
|
|
3556
3845
|
onChange: handleChange
|
|
3557
3846
|
}
|
|
3558
3847
|
),
|
|
3559
|
-
/* @__PURE__ */ (0,
|
|
3560
|
-
/* @__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)(
|
|
3561
3850
|
import_ai.AiOutlineStar,
|
|
3562
3851
|
{
|
|
3563
|
-
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)]"}`
|
|
3564
3853
|
}
|
|
3565
3854
|
),
|
|
3566
|
-
/* @__PURE__ */ (0,
|
|
3855
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { className: "flex-grow w-8" }),
|
|
3567
3856
|
" "
|
|
3568
|
-
] }) : /* @__PURE__ */ (0,
|
|
3857
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
3569
3858
|
import_ai.AiFillStar,
|
|
3570
3859
|
{
|
|
3571
3860
|
className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
|