@behindthemusictree/app-kit-ui 34.0.0 → 35.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-A7OWCEIK.js → chunk-YC3WBUVO.js} +643 -605
- package/dist/chunk-YC3WBUVO.js.map +1 -0
- package/dist/genre-tree/index.cjs +744 -707
- package/dist/genre-tree/index.cjs.map +1 -1
- package/dist/genre-tree/index.d.ts +51 -39
- package/dist/genre-tree/index.js +1 -1
- package/dist/index.cjs +739 -702
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +3 -3
- package/dist/chunk-A7OWCEIK.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -587,13 +587,13 @@ function AuthCallbackHandler({ renderStatus, ...options }) {
|
|
|
587
587
|
}
|
|
588
588
|
|
|
589
589
|
// src/genre-tree/GenreTreeView.tsx
|
|
590
|
-
var
|
|
590
|
+
var import_react18 = require("react");
|
|
591
591
|
var import_react_query3 = require("@tanstack/react-query");
|
|
592
|
-
var import_lucide_react15 = require("lucide-react");
|
|
593
592
|
var import_lucide_react16 = require("lucide-react");
|
|
594
|
-
var
|
|
595
|
-
var
|
|
596
|
-
var
|
|
593
|
+
var import_lucide_react17 = require("lucide-react");
|
|
594
|
+
var import_ui20 = require("@behindthemusictree/ui");
|
|
595
|
+
var import_genre_tree_view5 = require("@behindthemusictree/genre-tree-view");
|
|
596
|
+
var import_genre_tree28 = require("@behindthemusictree/app-kit/genre-tree");
|
|
597
597
|
var import_popup6 = require("@behindthemusictree/app-kit/popup");
|
|
598
598
|
|
|
599
599
|
// src/genre-tree/playlist-tree/size-metric.ts
|
|
@@ -1027,6 +1027,9 @@ function GenreTreeWheelHandoff({ skeleton, children, onRevealedChange }) {
|
|
|
1027
1027
|
] });
|
|
1028
1028
|
}
|
|
1029
1029
|
|
|
1030
|
+
// src/genre-tree/GenreTreeSearch.tsx
|
|
1031
|
+
var import_react9 = require("react");
|
|
1032
|
+
|
|
1030
1033
|
// src/genre-tree/GenreSearch.tsx
|
|
1031
1034
|
var import_lucide_react7 = require("lucide-react");
|
|
1032
1035
|
var import_ui7 = require("@behindthemusictree/ui");
|
|
@@ -1227,6 +1230,52 @@ function GenreSearch({
|
|
|
1227
1230
|
) });
|
|
1228
1231
|
}
|
|
1229
1232
|
|
|
1233
|
+
// src/genre-tree/GenreTreeSearch.tsx
|
|
1234
|
+
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
1235
|
+
function GenreTreeSearch({
|
|
1236
|
+
artistBaseUrl,
|
|
1237
|
+
songBaseUrl,
|
|
1238
|
+
onArtistSelect,
|
|
1239
|
+
onSongSelect,
|
|
1240
|
+
artistSearch,
|
|
1241
|
+
songSearch,
|
|
1242
|
+
SearchGroups,
|
|
1243
|
+
onSelectGenre,
|
|
1244
|
+
extraGroups = [],
|
|
1245
|
+
onQueryChange,
|
|
1246
|
+
onFocus,
|
|
1247
|
+
...props
|
|
1248
|
+
}) {
|
|
1249
|
+
const [query, setQuery] = (0, import_react9.useState)("");
|
|
1250
|
+
const [focused, setFocused] = (0, import_react9.useState)(false);
|
|
1251
|
+
const deferredQuery = (0, import_react9.useDeferredValue)(query);
|
|
1252
|
+
const artistGroup = useArtistSearchGroup(artistBaseUrl, deferredQuery, onArtistSelect, artistSearch);
|
|
1253
|
+
const songGroup = useSongSearchGroup(songBaseUrl, deferredQuery, onSongSelect, songSearch);
|
|
1254
|
+
const render = (hostGroups) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
1255
|
+
GenreSearch,
|
|
1256
|
+
{
|
|
1257
|
+
...props,
|
|
1258
|
+
onQueryChange: (text) => {
|
|
1259
|
+
setQuery(text);
|
|
1260
|
+
onQueryChange?.(text);
|
|
1261
|
+
},
|
|
1262
|
+
onFocus: () => {
|
|
1263
|
+
setFocused(true);
|
|
1264
|
+
onFocus?.();
|
|
1265
|
+
},
|
|
1266
|
+
extraGroups: [...extraGroups, ...hostGroups, artistGroup, songGroup].filter((group) => group !== void 0)
|
|
1267
|
+
}
|
|
1268
|
+
);
|
|
1269
|
+
return SearchGroups ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(SearchGroups, { query: deferredQuery, focused, onSelectGenre, children: render }) : render([]);
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
// src/genre-tree/GenreDetailPanel.tsx
|
|
1273
|
+
var import_react14 = require("react");
|
|
1274
|
+
var import_genre_tree_view4 = require("@behindthemusictree/genre-tree-view");
|
|
1275
|
+
var import_ui10 = require("@behindthemusictree/ui");
|
|
1276
|
+
var import_lucide_react12 = require("lucide-react");
|
|
1277
|
+
var import_genre_tree19 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1278
|
+
|
|
1230
1279
|
// src/genre-tree/GenreInfoPanelBody.tsx
|
|
1231
1280
|
var import_genre_tree18 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1232
1281
|
var import_track_noun4 = require("@behindthemusictree/app-kit/track-noun");
|
|
@@ -1240,18 +1289,18 @@ var import_lucide_react10 = require("lucide-react");
|
|
|
1240
1289
|
var import_popup4 = require("@behindthemusictree/app-kit/popup");
|
|
1241
1290
|
|
|
1242
1291
|
// src/genre-tree/SharePopup.tsx
|
|
1243
|
-
var
|
|
1292
|
+
var import_react10 = require("react");
|
|
1244
1293
|
var import_lucide_react9 = require("lucide-react");
|
|
1245
1294
|
var import_fa3 = require("react-icons/fa");
|
|
1246
1295
|
var import_si = require("react-icons/si");
|
|
1247
1296
|
|
|
1248
1297
|
// src/genre-tree/ShareDestinationButton.tsx
|
|
1249
|
-
var
|
|
1298
|
+
var import_jsx_runtime22 = require("react/jsx-runtime");
|
|
1250
1299
|
var className = "group flex min-w-0 flex-1 flex-col items-center justify-center gap-1.5 rounded-xl px-2 py-2 text-xs text-[var(--color-text)] transition-colors hover:bg-[var(--color-surface-muted)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] disabled:cursor-not-allowed disabled:opacity-50";
|
|
1251
1300
|
function Contents({ icon, label }) {
|
|
1252
|
-
return /* @__PURE__ */ (0,
|
|
1253
|
-
/* @__PURE__ */ (0,
|
|
1254
|
-
/* @__PURE__ */ (0,
|
|
1301
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
|
|
1302
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]", children: icon }),
|
|
1303
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "max-w-full truncate", children: label })
|
|
1255
1304
|
] });
|
|
1256
1305
|
}
|
|
1257
1306
|
function ShareDestinationButton({
|
|
@@ -1263,29 +1312,29 @@ function ShareDestinationButton({
|
|
|
1263
1312
|
ariaLabel
|
|
1264
1313
|
}) {
|
|
1265
1314
|
if (href) {
|
|
1266
|
-
return /* @__PURE__ */ (0,
|
|
1315
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", "aria-label": ariaLabel, className, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Contents, { icon, label }) });
|
|
1267
1316
|
}
|
|
1268
|
-
return /* @__PURE__ */ (0,
|
|
1317
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("button", { type: "button", onClick, disabled, "aria-label": ariaLabel, className, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Contents, { icon, label }) });
|
|
1269
1318
|
}
|
|
1270
1319
|
|
|
1271
1320
|
// src/genre-tree/SharePreviewCard.tsx
|
|
1272
1321
|
var import_lucide_react8 = require("lucide-react");
|
|
1273
|
-
var
|
|
1322
|
+
var import_jsx_runtime23 = require("react/jsx-runtime");
|
|
1274
1323
|
function SharePreviewCard({ target, url }) {
|
|
1275
1324
|
const Icon = { genre: import_lucide_react8.Music2, artist: import_lucide_react8.UserRound, song: import_lucide_react8.Music }[target.kind];
|
|
1276
1325
|
const typeLabel = { genre: "Genre", artist: "Artist", song: "Song" }[target.kind];
|
|
1277
|
-
return /* @__PURE__ */ (0,
|
|
1278
|
-
/* @__PURE__ */ (0,
|
|
1279
|
-
/* @__PURE__ */ (0,
|
|
1280
|
-
/* @__PURE__ */ (0,
|
|
1281
|
-
/* @__PURE__ */ (0,
|
|
1282
|
-
/* @__PURE__ */ (0,
|
|
1326
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "rounded-3xl bg-[var(--color-surface-muted)] p-4", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex min-h-32 overflow-hidden rounded-2xl bg-[var(--color-surface)] shadow-lg", children: [
|
|
1327
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.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_runtime23.jsx)(Icon, { "aria-hidden": "true", className: "h-10 w-10" }) }),
|
|
1328
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col justify-center gap-1 p-4", children: [
|
|
1329
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "text-xs font-medium uppercase tracking-wide text-[var(--color-text-muted)]", children: typeLabel }),
|
|
1330
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "truncate text-base font-semibold text-[var(--color-text)]", title: target.name, children: target.name }),
|
|
1331
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "truncate text-sm text-[var(--color-primary)]", title: url, children: url })
|
|
1283
1332
|
] })
|
|
1284
1333
|
] }) });
|
|
1285
1334
|
}
|
|
1286
1335
|
|
|
1287
1336
|
// src/genre-tree/SharePopup.tsx
|
|
1288
|
-
var
|
|
1337
|
+
var import_jsx_runtime24 = require("react/jsx-runtime");
|
|
1289
1338
|
function resolveAbsoluteUrl(url) {
|
|
1290
1339
|
try {
|
|
1291
1340
|
const parsed = new URL(url, window.location.href);
|
|
@@ -1318,20 +1367,20 @@ async function copyText(text) {
|
|
|
1318
1367
|
}
|
|
1319
1368
|
}
|
|
1320
1369
|
function SharePopup({ target, url, onClose }) {
|
|
1321
|
-
const [absoluteUrl, setAbsoluteUrl] = (0,
|
|
1322
|
-
const [copyStatus, setCopyStatus] = (0,
|
|
1323
|
-
const [canUseNativeShare, setCanUseNativeShare] = (0,
|
|
1324
|
-
const [shareError, setShareError] = (0,
|
|
1325
|
-
const [toastId, setToastId] = (0,
|
|
1326
|
-
(0,
|
|
1370
|
+
const [absoluteUrl, setAbsoluteUrl] = (0, import_react10.useState)(url);
|
|
1371
|
+
const [copyStatus, setCopyStatus] = (0, import_react10.useState)("idle");
|
|
1372
|
+
const [canUseNativeShare, setCanUseNativeShare] = (0, import_react10.useState)(false);
|
|
1373
|
+
const [shareError, setShareError] = (0, import_react10.useState)(null);
|
|
1374
|
+
const [toastId, setToastId] = (0, import_react10.useState)(0);
|
|
1375
|
+
(0, import_react10.useEffect)(() => {
|
|
1327
1376
|
setAbsoluteUrl(resolveAbsoluteUrl(url) ?? "");
|
|
1328
1377
|
setCopyStatus("idle");
|
|
1329
1378
|
setShareError(null);
|
|
1330
1379
|
}, [url]);
|
|
1331
|
-
(0,
|
|
1380
|
+
(0, import_react10.useEffect)(() => {
|
|
1332
1381
|
setCanUseNativeShare(typeof navigator !== "undefined" && typeof navigator.share === "function");
|
|
1333
1382
|
}, []);
|
|
1334
|
-
const destinationUrls = (0,
|
|
1383
|
+
const destinationUrls = (0, import_react10.useMemo)(() => {
|
|
1335
1384
|
if (!absoluteUrl) return null;
|
|
1336
1385
|
const message = `${target.name} \u2014 ${absoluteUrl}`;
|
|
1337
1386
|
return {
|
|
@@ -1349,7 +1398,7 @@ function SharePopup({ target, url, onClose }) {
|
|
|
1349
1398
|
setCopyStatus(copied ? "idle" : "failed");
|
|
1350
1399
|
if (copied) setToastId((id) => id + 1);
|
|
1351
1400
|
};
|
|
1352
|
-
const dismissToast = (0,
|
|
1401
|
+
const dismissToast = (0, import_react10.useCallback)(() => setToastId(0), []);
|
|
1353
1402
|
const handleNativeShare = async () => {
|
|
1354
1403
|
if (!absoluteUrl || !navigator.share) return;
|
|
1355
1404
|
setShareError(null);
|
|
@@ -1360,8 +1409,8 @@ function SharePopup({ target, url, onClose }) {
|
|
|
1360
1409
|
setShareError("Could not open the share options.");
|
|
1361
1410
|
}
|
|
1362
1411
|
};
|
|
1363
|
-
return /* @__PURE__ */ (0,
|
|
1364
|
-
/* @__PURE__ */ (0,
|
|
1412
|
+
return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
|
|
1413
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
1365
1414
|
BasePopup_default,
|
|
1366
1415
|
{
|
|
1367
1416
|
title: "Share",
|
|
@@ -1376,67 +1425,67 @@ function SharePopup({ target, url, onClose }) {
|
|
|
1376
1425
|
titleTextClassName: "!text-xl !font-semibold !text-[var(--color-text)]",
|
|
1377
1426
|
closeButtonClassName: "!h-10 !w-10 !rounded-none !border-0 !bg-transparent !p-0 !text-[var(--color-text)] hover:!bg-transparent hover:!text-[var(--color-text)]",
|
|
1378
1427
|
contentClassName: "!rounded-none !bg-transparent !px-6 !pb-6 !pt-2",
|
|
1379
|
-
children: /* @__PURE__ */ (0,
|
|
1380
|
-
/* @__PURE__ */ (0,
|
|
1381
|
-
/* @__PURE__ */ (0,
|
|
1382
|
-
/* @__PURE__ */ (0,
|
|
1428
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex flex-col gap-4", children: [
|
|
1429
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(SharePreviewCard, { target, url: absoluteUrl || url }),
|
|
1430
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "grid grid-cols-3 gap-1 rounded-3xl bg-[var(--color-surface)] p-3 sm:grid-cols-4", children: [
|
|
1431
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
1383
1432
|
ShareDestinationButton,
|
|
1384
1433
|
{
|
|
1385
1434
|
label: "Copy Link",
|
|
1386
|
-
icon: /* @__PURE__ */ (0,
|
|
1435
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react9.Link2, { "aria-hidden": "true", className: "h-6 w-6" }),
|
|
1387
1436
|
onClick: () => void handleCopy(),
|
|
1388
1437
|
ariaLabel: "Copy link",
|
|
1389
1438
|
disabled: !absoluteUrl
|
|
1390
1439
|
}
|
|
1391
1440
|
),
|
|
1392
|
-
/* @__PURE__ */ (0,
|
|
1441
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
1393
1442
|
ShareDestinationButton,
|
|
1394
1443
|
{
|
|
1395
1444
|
label: "WhatsApp",
|
|
1396
|
-
icon: /* @__PURE__ */ (0,
|
|
1445
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_fa3.FaWhatsapp, { "aria-hidden": "true", className: "h-9 w-9 text-[#25D366]" }),
|
|
1397
1446
|
href: destinationUrls?.whatsapp,
|
|
1398
1447
|
ariaLabel: `Share ${target.name} via WhatsApp`,
|
|
1399
1448
|
disabled: !absoluteUrl
|
|
1400
1449
|
}
|
|
1401
1450
|
),
|
|
1402
|
-
/* @__PURE__ */ (0,
|
|
1451
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
1403
1452
|
ShareDestinationButton,
|
|
1404
1453
|
{
|
|
1405
1454
|
label: "Gmail",
|
|
1406
|
-
icon: /* @__PURE__ */ (0,
|
|
1455
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_si.SiGmail, { "aria-hidden": "true", className: "h-8 w-8 text-[#EA4335]" }),
|
|
1407
1456
|
href: destinationUrls?.gmail,
|
|
1408
1457
|
ariaLabel: `Email ${target.name} with Gmail`,
|
|
1409
1458
|
disabled: !absoluteUrl
|
|
1410
1459
|
}
|
|
1411
1460
|
),
|
|
1412
|
-
canUseNativeShare && /* @__PURE__ */ (0,
|
|
1461
|
+
canUseNativeShare && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
1413
1462
|
ShareDestinationButton,
|
|
1414
1463
|
{
|
|
1415
1464
|
label: "More",
|
|
1416
|
-
icon: /* @__PURE__ */ (0,
|
|
1465
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react9.Share2, { "aria-hidden": "true", className: "h-6 w-6" }),
|
|
1417
1466
|
onClick: () => void handleNativeShare(),
|
|
1418
1467
|
ariaLabel: "More sharing options",
|
|
1419
1468
|
disabled: !absoluteUrl
|
|
1420
1469
|
}
|
|
1421
1470
|
)
|
|
1422
1471
|
] }),
|
|
1423
|
-
copyStatus === "failed" && /* @__PURE__ */ (0,
|
|
1424
|
-
shareError && /* @__PURE__ */ (0,
|
|
1472
|
+
copyStatus === "failed" && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: "Could not copy the link." }),
|
|
1473
|
+
shareError && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: shareError })
|
|
1425
1474
|
] })
|
|
1426
1475
|
}
|
|
1427
1476
|
),
|
|
1428
|
-
toastId > 0 && /* @__PURE__ */ (0,
|
|
1477
|
+
toastId > 0 && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(Toast, { message: "Link copied", onClose: dismissToast }, toastId)
|
|
1429
1478
|
] });
|
|
1430
1479
|
}
|
|
1431
1480
|
|
|
1432
1481
|
// src/genre-tree/ShareActionButton.tsx
|
|
1433
|
-
var
|
|
1482
|
+
var import_jsx_runtime25 = require("react/jsx-runtime");
|
|
1434
1483
|
function ShareActionButton({ target, url }) {
|
|
1435
1484
|
const { showPopup, hidePopup } = (0, import_popup4.usePopup)();
|
|
1436
1485
|
const openSharePopup = () => {
|
|
1437
|
-
showPopup(/* @__PURE__ */ (0,
|
|
1486
|
+
showPopup(/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(SharePopup, { target, url, onClose: hidePopup }));
|
|
1438
1487
|
};
|
|
1439
|
-
return /* @__PURE__ */ (0,
|
|
1488
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
|
|
1440
1489
|
"button",
|
|
1441
1490
|
{
|
|
1442
1491
|
type: "button",
|
|
@@ -1445,7 +1494,7 @@ function ShareActionButton({ target, url }) {
|
|
|
1445
1494
|
"aria-label": `Share ${target.name}`,
|
|
1446
1495
|
title: `Share ${target.name}`,
|
|
1447
1496
|
children: [
|
|
1448
|
-
/* @__PURE__ */ (0,
|
|
1497
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.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: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react10.Share2, { "aria-hidden": "true", className: "w-5 h-5" }) }),
|
|
1449
1498
|
"Share"
|
|
1450
1499
|
]
|
|
1451
1500
|
}
|
|
@@ -1453,7 +1502,7 @@ function ShareActionButton({ target, url }) {
|
|
|
1453
1502
|
}
|
|
1454
1503
|
|
|
1455
1504
|
// src/genre-tree/GenreActionBand.tsx
|
|
1456
|
-
var
|
|
1505
|
+
var import_jsx_runtime26 = require("react/jsx-runtime");
|
|
1457
1506
|
function GenreActionBand({
|
|
1458
1507
|
node,
|
|
1459
1508
|
onPlayPause,
|
|
@@ -1475,7 +1524,7 @@ function GenreActionBand({
|
|
|
1475
1524
|
items.push({
|
|
1476
1525
|
key: "play-pause",
|
|
1477
1526
|
label: isPlaying ? "Pause" : "Play",
|
|
1478
|
-
icon: isPlaying ? /* @__PURE__ */ (0,
|
|
1527
|
+
icon: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react11.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react11.Play, { "aria-hidden": "true", className: "w-5 h-5" }),
|
|
1479
1528
|
onClick: onPlayPause
|
|
1480
1529
|
});
|
|
1481
1530
|
}
|
|
@@ -1483,24 +1532,24 @@ function GenreActionBand({
|
|
|
1483
1532
|
items.push({
|
|
1484
1533
|
key: "zoom-in",
|
|
1485
1534
|
label: "Focus",
|
|
1486
|
-
icon: /* @__PURE__ */ (0,
|
|
1535
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react11.ZoomIn, { "aria-hidden": "true", className: "w-5 h-5" }),
|
|
1487
1536
|
onClick: onZoomIn
|
|
1488
1537
|
});
|
|
1489
1538
|
}
|
|
1490
1539
|
if (onAddChild) {
|
|
1491
|
-
adminItems.push({ key: "add-child", label: "Add", icon: /* @__PURE__ */ (0,
|
|
1540
|
+
adminItems.push({ key: "add-child", label: "Add", icon: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react11.Plus, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onAddChild });
|
|
1492
1541
|
}
|
|
1493
1542
|
if (onRename) {
|
|
1494
|
-
adminItems.push({ key: "rename", label: "Rename", icon: /* @__PURE__ */ (0,
|
|
1543
|
+
adminItems.push({ key: "rename", label: "Rename", icon: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react11.Pencil, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onRename });
|
|
1495
1544
|
}
|
|
1496
1545
|
if (onChangeParent) {
|
|
1497
|
-
adminItems.push({ key: "change-parent", label: "Parent", icon: /* @__PURE__ */ (0,
|
|
1546
|
+
adminItems.push({ key: "change-parent", label: "Parent", icon: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react11.MoveUpRight, { "aria-hidden": "true", className: "w-5 h-5" }), onClick: onChangeParent });
|
|
1498
1547
|
}
|
|
1499
1548
|
if (onToggleSide) {
|
|
1500
1549
|
adminItems.push({
|
|
1501
1550
|
key: "toggle-side",
|
|
1502
1551
|
label: side === "pop" ? "Make core" : "Make pop",
|
|
1503
|
-
icon: /* @__PURE__ */ (0,
|
|
1552
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react11.ArrowLeftRight, { "aria-hidden": "true", className: "w-5 h-5" }),
|
|
1504
1553
|
onClick: onToggleSide,
|
|
1505
1554
|
disabled: !!toggleSideDisabledReason,
|
|
1506
1555
|
title: toggleSideDisabledReason
|
|
@@ -1516,7 +1565,7 @@ function GenreActionBand({
|
|
|
1516
1565
|
});
|
|
1517
1566
|
}
|
|
1518
1567
|
if (items.length === 0 && (!shareTarget || !shareUrl) && adminItems.length === 0) return null;
|
|
1519
|
-
const renderItems = (list) => list.map((item) => /* @__PURE__ */ (0,
|
|
1568
|
+
const renderItems = (list) => list.map((item) => /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
|
|
1520
1569
|
"button",
|
|
1521
1570
|
{
|
|
1522
1571
|
type: "button",
|
|
@@ -1525,25 +1574,25 @@ function GenreActionBand({
|
|
|
1525
1574
|
onClick: item.onClick,
|
|
1526
1575
|
className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none disabled:opacity-50",
|
|
1527
1576
|
children: [
|
|
1528
|
-
/* @__PURE__ */ (0,
|
|
1577
|
+
/* @__PURE__ */ (0, import_jsx_runtime26.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)] group-disabled:group-hover:bg-[var(--color-surface-muted)]", children: item.icon }),
|
|
1529
1578
|
item.label
|
|
1530
1579
|
]
|
|
1531
1580
|
},
|
|
1532
1581
|
item.key
|
|
1533
1582
|
));
|
|
1534
|
-
return /* @__PURE__ */ (0,
|
|
1535
|
-
(items.length > 0 || shareTarget && shareUrl) && /* @__PURE__ */ (0,
|
|
1583
|
+
return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: [
|
|
1584
|
+
(items.length > 0 || shareTarget && shareUrl) && /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
|
|
1536
1585
|
renderItems(items),
|
|
1537
|
-
shareTarget && shareUrl && /* @__PURE__ */ (0,
|
|
1586
|
+
shareTarget && shareUrl && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
|
|
1538
1587
|
] }),
|
|
1539
|
-
adminItems.length > 0 && /* @__PURE__ */ (0,
|
|
1588
|
+
adminItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
|
|
1540
1589
|
"section",
|
|
1541
1590
|
{
|
|
1542
1591
|
"aria-label": "Admin actions",
|
|
1543
1592
|
className: `px-3.5 py-3 ${items.length > 0 ? "border-t border-[var(--color-border)]" : ""}`,
|
|
1544
1593
|
children: [
|
|
1545
|
-
/* @__PURE__ */ (0,
|
|
1546
|
-
/* @__PURE__ */ (0,
|
|
1594
|
+
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)("h3", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-[var(--color-text-muted)]", children: "Admin" }),
|
|
1595
|
+
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "flex flex-wrap gap-1", children: renderItems(adminItems) })
|
|
1547
1596
|
]
|
|
1548
1597
|
}
|
|
1549
1598
|
)
|
|
@@ -1555,20 +1604,20 @@ var import_ui9 = require("@behindthemusictree/ui");
|
|
|
1555
1604
|
var import_genre_tree17 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1556
1605
|
|
|
1557
1606
|
// src/genre-tree/ClampedText.tsx
|
|
1558
|
-
var
|
|
1559
|
-
var
|
|
1607
|
+
var import_react11 = require("react");
|
|
1608
|
+
var import_jsx_runtime27 = require("react/jsx-runtime");
|
|
1560
1609
|
function ClampedText({ children, className: className2 = "" }) {
|
|
1561
|
-
const ref = (0,
|
|
1562
|
-
const [expanded, setExpanded] = (0,
|
|
1563
|
-
const [overflows, setOverflows] = (0,
|
|
1564
|
-
(0,
|
|
1610
|
+
const ref = (0, import_react11.useRef)(null);
|
|
1611
|
+
const [expanded, setExpanded] = (0, import_react11.useState)(false);
|
|
1612
|
+
const [overflows, setOverflows] = (0, import_react11.useState)(false);
|
|
1613
|
+
(0, import_react11.useLayoutEffect)(() => {
|
|
1565
1614
|
const element = ref.current;
|
|
1566
1615
|
if (!element || expanded) return;
|
|
1567
1616
|
setOverflows(element.scrollHeight > element.clientHeight);
|
|
1568
1617
|
}, [children, expanded]);
|
|
1569
|
-
return /* @__PURE__ */ (0,
|
|
1570
|
-
/* @__PURE__ */ (0,
|
|
1571
|
-
(overflows || expanded) && /* @__PURE__ */ (0,
|
|
1618
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
|
|
1619
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { ref, className: `${className2} ${expanded ? "" : "line-clamp-3"}`, children }),
|
|
1620
|
+
(overflows || expanded) && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
1572
1621
|
"button",
|
|
1573
1622
|
{
|
|
1574
1623
|
type: "button",
|
|
@@ -1582,7 +1631,7 @@ function ClampedText({ children, className: className2 = "" }) {
|
|
|
1582
1631
|
}
|
|
1583
1632
|
|
|
1584
1633
|
// src/genre-tree/GenrePlaylistTracks.tsx
|
|
1585
|
-
var
|
|
1634
|
+
var import_react12 = require("react");
|
|
1586
1635
|
var import_react_query = require("@tanstack/react-query");
|
|
1587
1636
|
var import_zod = require("zod");
|
|
1588
1637
|
var import_fa4 = require("react-icons/fa");
|
|
@@ -1603,15 +1652,15 @@ var import_genre_tree15 = require("@behindthemusictree/app-kit/genre-tree");
|
|
|
1603
1652
|
|
|
1604
1653
|
// src/genre-tree/MusicBrainzRecordingLink.tsx
|
|
1605
1654
|
var import_genre_tree10 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1606
|
-
var
|
|
1655
|
+
var import_jsx_runtime28 = require("react/jsx-runtime");
|
|
1607
1656
|
function MusicBrainzRecordingLink({
|
|
1608
1657
|
song,
|
|
1609
1658
|
children,
|
|
1610
1659
|
className: className2 = ""
|
|
1611
1660
|
}) {
|
|
1612
1661
|
const url = (0, import_genre_tree10.musicBrainzRecordingUrl)(song);
|
|
1613
|
-
if (!url) return /* @__PURE__ */ (0,
|
|
1614
|
-
return /* @__PURE__ */ (0,
|
|
1662
|
+
if (!url) return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_jsx_runtime28.Fragment, { children });
|
|
1663
|
+
return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
|
|
1615
1664
|
"a",
|
|
1616
1665
|
{
|
|
1617
1666
|
href: url,
|
|
@@ -1628,7 +1677,7 @@ function MusicBrainzRecordingLink({
|
|
|
1628
1677
|
// src/genre-tree/GenrePlaylistTracks.tsx
|
|
1629
1678
|
var import_genre_tree16 = require("@behindthemusictree/app-kit/genre-tree");
|
|
1630
1679
|
var import_track_noun2 = require("@behindthemusictree/app-kit/track-noun");
|
|
1631
|
-
var
|
|
1680
|
+
var import_jsx_runtime29 = require("react/jsx-runtime");
|
|
1632
1681
|
var CATALOGUE_PAGE_SIZE = 100;
|
|
1633
1682
|
var SongPageSchema = (0, import_transport6.PaginatedResponseSchema)(import_genre_tree14.SongDetailedSchema).extend({
|
|
1634
1683
|
overallTotal: import_zod.z.number().nullable(),
|
|
@@ -1648,6 +1697,7 @@ var nextCursor = (next) => {
|
|
|
1648
1697
|
function GenrePlaylistTracks({
|
|
1649
1698
|
genreUuid,
|
|
1650
1699
|
genrePlaylist,
|
|
1700
|
+
genreName: selectedGenreName,
|
|
1651
1701
|
selection,
|
|
1652
1702
|
scope,
|
|
1653
1703
|
getBackendBaseUrl,
|
|
@@ -1663,13 +1713,17 @@ function GenrePlaylistTracks({
|
|
|
1663
1713
|
const { playerTrackObject, playState, handlePlayPauseAction } = (0, import_player4.usePlayer)();
|
|
1664
1714
|
const { showPopup } = (0, import_popup5.usePopup)();
|
|
1665
1715
|
const trackNoun = (0, import_track_noun2.useTrackNoun)();
|
|
1666
|
-
const
|
|
1716
|
+
const uuid = genrePlaylist?.uuid;
|
|
1717
|
+
const genreName = selectedGenreName ?? genrePlaylist?.name ?? "Genre";
|
|
1667
1718
|
const { fetch } = (0, import_transport5.useFetchWrapper)(getBackendBaseUrl);
|
|
1668
1719
|
const playback = (0, import_transport5.usePlaybackContext)();
|
|
1669
1720
|
const { data, isPending, isError, refetch, hasNextPage, isFetchingNextPage, fetchNextPage } = (0, import_react_query.useInfiniteQuery)({
|
|
1670
|
-
queryKey: [...scope === "me" ? import_genre_tree12.genrePlaylistQueryKeys.me.tracks(uuid) : artist ? [...import_genre_tree13.libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid, selection), "artist", artist.uuid] : import_genre_tree13.libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid, selection), ...playback.key ? [playback.key] : []],
|
|
1721
|
+
queryKey: [...scope === "me" ? uuid ? import_genre_tree12.genrePlaylistQueryKeys.me.tracks(uuid) : ["missing-personal-genre-playlist", genreUuid] : artist ? [...import_genre_tree13.libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid, selection), "artist", artist.uuid] : import_genre_tree13.libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid, selection), ...playback.key ? [playback.key] : []],
|
|
1671
1722
|
queryFn: async ({ pageParam }) => {
|
|
1672
|
-
if (scope === "me")
|
|
1723
|
+
if (scope === "me") {
|
|
1724
|
+
if (!uuid) throw new Error("Personal genre has no playlist");
|
|
1725
|
+
return fetchGenrePlaylistTracksPage(uuid, scope, Number(pageParam));
|
|
1726
|
+
}
|
|
1673
1727
|
const response = await fetch(
|
|
1674
1728
|
import_genre_tree13.libraryEndpoints.reference.songs.list(),
|
|
1675
1729
|
true,
|
|
@@ -1682,10 +1736,10 @@ function GenrePlaylistTracks({
|
|
|
1682
1736
|
},
|
|
1683
1737
|
initialPageParam: scope === "me" ? 1 : "",
|
|
1684
1738
|
getNextPageParam: (lastPage) => lastPage.nextPage ?? void 0,
|
|
1685
|
-
enabled: playback.ready && (scope !== "me" || sessionRestored && !!session?.accessToken)
|
|
1739
|
+
enabled: playback.ready && (scope !== "me" || uuid !== void 0 && sessionRestored && !!session?.accessToken)
|
|
1686
1740
|
});
|
|
1687
|
-
const [sentinel, setSentinel] = (0,
|
|
1688
|
-
(0,
|
|
1741
|
+
const [sentinel, setSentinel] = (0, import_react12.useState)(null);
|
|
1742
|
+
(0, import_react12.useEffect)(() => {
|
|
1689
1743
|
if (!hasNextPage || isFetchingNextPage || !sentinel) return;
|
|
1690
1744
|
const observer = new IntersectionObserver((entries) => {
|
|
1691
1745
|
if (entries.some((entry) => entry.isIntersecting)) {
|
|
@@ -1704,38 +1758,38 @@ function GenrePlaylistTracks({
|
|
|
1704
1758
|
ordering: "title",
|
|
1705
1759
|
genreUuid,
|
|
1706
1760
|
...selection && { selection }
|
|
1707
|
-
}) : scope === "reference" ? playNewTrackListFromGenreCatalogue({ uuid: genreUuid, name:
|
|
1761
|
+
}) : scope === "reference" ? playNewTrackListFromGenreCatalogue({ uuid: genreUuid, name: genreName }, scope, { startSongUuid, ...selection && { selection } }) : genrePlaylist ? playNewTrackListFromGenrePlaylist(genrePlaylist, scope, { startSongUuid }) : Promise.reject(new Error("Personal genre has no playlist"));
|
|
1708
1762
|
started.catch((error) => {
|
|
1709
1763
|
console.error("Failed to play genre playlist:", error);
|
|
1710
|
-
showPopup(/* @__PURE__ */ (0,
|
|
1764
|
+
showPopup(/* @__PURE__ */ (0, import_jsx_runtime29.jsx)(InternalErrorPopup, { errorCode: import_transport8.ErrorCode.CLIENT_UNKNOWN }));
|
|
1711
1765
|
});
|
|
1712
1766
|
};
|
|
1713
|
-
return /* @__PURE__ */ (0,
|
|
1767
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui8.PanelSection, { title: `${(0, import_track_noun2.capitalize)(trackNoun.plural)}${total !== void 0 ? ` (${total})` : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { children: [
|
|
1714
1768
|
renderTracksToolbar?.(),
|
|
1715
|
-
isPending ? /* @__PURE__ */ (0,
|
|
1769
|
+
scope === "me" && !genrePlaylist ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { children: "\u2014" }) : isPending ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui8.Skeleton, { className: "h-4 w-3/4", "aria-busy": "true" }) : isError && tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("p", { role: "alert", children: [
|
|
1716
1770
|
"Couldn't load ",
|
|
1717
1771
|
trackNoun.plural,
|
|
1718
1772
|
".",
|
|
1719
1773
|
" ",
|
|
1720
|
-
/* @__PURE__ */ (0,
|
|
1721
|
-
] }) : tracks.length === 0 ? /* @__PURE__ */ (0,
|
|
1774
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
|
|
1775
|
+
] }) : tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { children: "\u2014" }) : /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("ul", { className: "mt-1 max-h-64 overflow-y-auto text-[0.8125rem] leading-[1.45] [container-type:inline-size]", "aria-label": `${genreName} ${trackNoun.plural}`, children: [
|
|
1722
1776
|
tracks.map((track, index) => {
|
|
1723
1777
|
const label = track.artists?.length ? `${track.title} \u2014 ${track.artists.map((artist2) => artist2.name).join(", ")}` : track.title;
|
|
1724
1778
|
const isCurrent = playerTrackObject?.track.id === track.uuid;
|
|
1725
1779
|
const isPlaying = isCurrent && playState === import_player5.PlayStates.PLAYING;
|
|
1726
|
-
const text = /* @__PURE__ */ (0,
|
|
1727
|
-
/* @__PURE__ */ (0,
|
|
1728
|
-
track.artists?.length ? /* @__PURE__ */ (0,
|
|
1729
|
-
/* @__PURE__ */ (0,
|
|
1730
|
-
track.album && /* @__PURE__ */ (0,
|
|
1780
|
+
const text = /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("span", { className: TEXT_CELL, children: [
|
|
1781
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: TITLE_CELL, children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree14.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }),
|
|
1782
|
+
track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: SEPARATOR, children: " \u2014 " }) : null,
|
|
1783
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: COLUMN_CELL, children: track.artists?.length ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_genre_tree16.ArtistNames, { artists: track.artists, renderArtistLink }) : null }),
|
|
1784
|
+
track.album && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: ALBUM_CELL, children: track.album.name })
|
|
1731
1785
|
] });
|
|
1732
1786
|
const leading = renderSongLeading?.(track, index);
|
|
1733
1787
|
const actions = renderSongActions?.(track);
|
|
1734
1788
|
const isUnplayable = (0, import_genre_tree14.isSong)(track) && (0, import_genre_tree14.songUnplayableReason)(track) !== null;
|
|
1735
1789
|
if (isUnplayable) {
|
|
1736
|
-
return /* @__PURE__ */ (0,
|
|
1790
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("li", { className: "flex min-h-8 items-center gap-1 px-1 opacity-50", children: [
|
|
1737
1791
|
leading,
|
|
1738
|
-
/* @__PURE__ */ (0,
|
|
1792
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
1739
1793
|
"button",
|
|
1740
1794
|
{
|
|
1741
1795
|
type: "button",
|
|
@@ -1743,27 +1797,27 @@ function GenrePlaylistTracks({
|
|
|
1743
1797
|
"aria-disabled": "true",
|
|
1744
1798
|
"aria-label": label,
|
|
1745
1799
|
className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full opacity-0 cursor-not-allowed",
|
|
1746
|
-
children: /* @__PURE__ */ (0,
|
|
1800
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_fa4.FaPlay, { size: 10 })
|
|
1747
1801
|
}
|
|
1748
1802
|
),
|
|
1749
1803
|
text,
|
|
1750
1804
|
actions
|
|
1751
1805
|
] }, track.uuid);
|
|
1752
1806
|
}
|
|
1753
|
-
return /* @__PURE__ */ (0,
|
|
1807
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
|
|
1754
1808
|
"li",
|
|
1755
1809
|
{
|
|
1756
1810
|
className: `group flex min-h-8 items-center gap-1 rounded px-1 hover:bg-[var(--color-neutral-100)] ${isCurrent ? "font-medium text-[var(--color-primary)]" : ""}`,
|
|
1757
1811
|
children: [
|
|
1758
1812
|
leading,
|
|
1759
|
-
/* @__PURE__ */ (0,
|
|
1813
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
1760
1814
|
"button",
|
|
1761
1815
|
{
|
|
1762
1816
|
type: "button",
|
|
1763
1817
|
"aria-label": `${isPlaying ? "Pause" : "Play"} ${label}`,
|
|
1764
1818
|
className: `flex h-6 w-6 shrink-0 items-center justify-center rounded-full cursor-pointer group-hover:opacity-100 focus-visible:opacity-100 ${isCurrent && playState !== import_player5.PlayStates.STOPPED ? "opacity-100" : "opacity-0 [@media(pointer:coarse)]:opacity-100"}`,
|
|
1765
1819
|
onClick: () => isCurrent ? handlePlayPauseAction() : play(track.uuid),
|
|
1766
|
-
children: isCurrent && playState === import_player5.PlayStates.LOADING ? /* @__PURE__ */ (0,
|
|
1820
|
+
children: isCurrent && playState === import_player5.PlayStates.LOADING ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_ui8.RingLoader, { size: 10 }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_fa4.FaPause, { size: 10 }) : /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_fa4.FaPlay, { size: 10 })
|
|
1767
1821
|
}
|
|
1768
1822
|
),
|
|
1769
1823
|
text,
|
|
@@ -1773,7 +1827,7 @@ function GenrePlaylistTracks({
|
|
|
1773
1827
|
track.uuid
|
|
1774
1828
|
);
|
|
1775
1829
|
}),
|
|
1776
|
-
hasNextPage && /* @__PURE__ */ (0,
|
|
1830
|
+
hasNextPage && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("li", { ref: setSentinel, "aria-hidden": "true", className: "h-4" })
|
|
1777
1831
|
] })
|
|
1778
1832
|
] }) });
|
|
1779
1833
|
}
|
|
@@ -1781,10 +1835,11 @@ function GenrePlaylistTracks({
|
|
|
1781
1835
|
// src/genre-tree/GenreDetailExtras.tsx
|
|
1782
1836
|
var import_track_noun3 = require("@behindthemusictree/app-kit/track-noun");
|
|
1783
1837
|
var import_si2 = require("react-icons/si");
|
|
1784
|
-
var
|
|
1838
|
+
var import_jsx_runtime30 = require("react/jsx-runtime");
|
|
1785
1839
|
function GenreDetailExtras({
|
|
1786
1840
|
genreUuid,
|
|
1787
1841
|
genrePlaylist,
|
|
1842
|
+
genreName,
|
|
1788
1843
|
selection,
|
|
1789
1844
|
wikidataId,
|
|
1790
1845
|
scope,
|
|
@@ -1806,17 +1861,17 @@ function GenreDetailExtras({
|
|
|
1806
1861
|
criteriaOverviewSchema
|
|
1807
1862
|
);
|
|
1808
1863
|
if (isPending) {
|
|
1809
|
-
return /* @__PURE__ */ (0,
|
|
1810
|
-
/* @__PURE__ */ (0,
|
|
1864
|
+
return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_jsx_runtime30.Fragment, { children: [
|
|
1865
|
+
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui9.PanelSection, { title: "Summary", children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { "aria-busy": "true", "data-testid": "genre-detail-extras-skeleton", children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui9.Skeleton, { className: "h-4 w-3/4" }) }) }),
|
|
1811
1866
|
family
|
|
1812
1867
|
] });
|
|
1813
1868
|
}
|
|
1814
|
-
if (!overview) return /* @__PURE__ */ (0,
|
|
1869
|
+
if (!overview) return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_jsx_runtime30.Fragment, { children: family });
|
|
1815
1870
|
const { summary, summarySourceUrl, essentialSongs } = overview;
|
|
1816
1871
|
const wikidataUrl = wikidataId ? `https://www.wikidata.org/wiki/${encodeURIComponent(wikidataId)}` : null;
|
|
1817
|
-
return /* @__PURE__ */ (0,
|
|
1818
|
-
(summarySourceUrl || wikidataUrl) && /* @__PURE__ */ (0,
|
|
1819
|
-
summarySourceUrl && /* @__PURE__ */ (0,
|
|
1872
|
+
return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_jsx_runtime30.Fragment, { children: [
|
|
1873
|
+
(summarySourceUrl || wikidataUrl) && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui9.PanelSection, { title: "Links", children: /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "flex flex-wrap gap-x-3 gap-y-1 text-xs text-[var(--color-text)]", children: [
|
|
1874
|
+
summarySourceUrl && /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
|
|
1820
1875
|
"a",
|
|
1821
1876
|
{
|
|
1822
1877
|
href: summarySourceUrl,
|
|
@@ -1824,12 +1879,12 @@ function GenreDetailExtras({
|
|
|
1824
1879
|
rel: "noopener noreferrer",
|
|
1825
1880
|
className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
|
|
1826
1881
|
children: [
|
|
1827
|
-
/* @__PURE__ */ (0,
|
|
1882
|
+
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_si2.SiWikipedia, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
|
|
1828
1883
|
"Wikipedia"
|
|
1829
1884
|
]
|
|
1830
1885
|
}
|
|
1831
1886
|
),
|
|
1832
|
-
wikidataUrl && /* @__PURE__ */ (0,
|
|
1887
|
+
wikidataUrl && /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
|
|
1833
1888
|
"a",
|
|
1834
1889
|
{
|
|
1835
1890
|
href: wikidataUrl,
|
|
@@ -1837,21 +1892,22 @@ function GenreDetailExtras({
|
|
|
1837
1892
|
rel: "noopener noreferrer",
|
|
1838
1893
|
className: "inline-flex items-center gap-1.5 text-[var(--color-text-muted)] hover:text-[var(--color-text)]",
|
|
1839
1894
|
children: [
|
|
1840
|
-
/* @__PURE__ */ (0,
|
|
1895
|
+
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_si2.SiWikidata, { "aria-hidden": "true", className: "h-3.5 w-3.5" }),
|
|
1841
1896
|
"Wikidata"
|
|
1842
1897
|
]
|
|
1843
1898
|
}
|
|
1844
1899
|
)
|
|
1845
1900
|
] }) }),
|
|
1846
|
-
/* @__PURE__ */ (0,
|
|
1901
|
+
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui9.PanelSection, { title: "Summary", children: renderSummary ? renderSummary(overview) : /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ClampedText, { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: summary ?? "\u2014" }, genreUuid) }),
|
|
1847
1902
|
family,
|
|
1848
1903
|
renderGenreDetailExtras?.(overview),
|
|
1849
|
-
(renderEssentialTracks || essentialSongs) && /* @__PURE__ */ (0,
|
|
1850
|
-
/* @__PURE__ */ (0,
|
|
1904
|
+
(renderEssentialTracks || essentialSongs) && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_ui9.PanelSection, { title: `Essential ${trackNoun.plural}`, children: renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && essentialSongs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("ul", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: essentialSongs.map((track) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("li", { className: "py-1", children: track.title }, track.uuid)) }) : /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("p", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: "\u2014" }) }),
|
|
1905
|
+
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
|
|
1851
1906
|
GenrePlaylistTracks,
|
|
1852
1907
|
{
|
|
1853
1908
|
genreUuid,
|
|
1854
1909
|
genrePlaylist,
|
|
1910
|
+
genreName,
|
|
1855
1911
|
selection,
|
|
1856
1912
|
scope,
|
|
1857
1913
|
getBackendBaseUrl,
|
|
@@ -1864,8 +1920,8 @@ function GenreDetailExtras({
|
|
|
1864
1920
|
}
|
|
1865
1921
|
|
|
1866
1922
|
// src/genre-tree/GenreInfoPanelBody.tsx
|
|
1867
|
-
var
|
|
1868
|
-
var
|
|
1923
|
+
var import_jsx_runtime31 = require("react/jsx-runtime");
|
|
1924
|
+
var import_react13 = require("react");
|
|
1869
1925
|
function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
|
|
1870
1926
|
const { isPlaying, setIsPlaying } = (0, import_player6.usePlayer)();
|
|
1871
1927
|
const isCurrentTrackList = playback.playingNodeId === genrePlaylist.uuid;
|
|
@@ -1878,7 +1934,7 @@ function GenreNodeActionBand({ genrePlaylist, playback, ...bandProps }) {
|
|
|
1878
1934
|
console.error("Failed to load genre playlist tracks:", error);
|
|
1879
1935
|
});
|
|
1880
1936
|
};
|
|
1881
|
-
return /* @__PURE__ */ (0,
|
|
1937
|
+
return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
|
|
1882
1938
|
GenreActionBand,
|
|
1883
1939
|
{
|
|
1884
1940
|
...bandProps,
|
|
@@ -1893,6 +1949,7 @@ function GenreInfoPanelBody({
|
|
|
1893
1949
|
genreUuid,
|
|
1894
1950
|
genrePlaylist,
|
|
1895
1951
|
catalogue,
|
|
1952
|
+
countsContext,
|
|
1896
1953
|
wikidataId,
|
|
1897
1954
|
scope,
|
|
1898
1955
|
getBackendBaseUrl,
|
|
@@ -1901,30 +1958,32 @@ function GenreInfoPanelBody({
|
|
|
1901
1958
|
extras
|
|
1902
1959
|
}) {
|
|
1903
1960
|
const noun = (0, import_track_noun4.useTrackNoun)();
|
|
1904
|
-
const summary = scope === "reference" ? import_genre_tree18.GenreCatalogueCountsSchema.parse(genrePlaylist) : void 0;
|
|
1961
|
+
const summary = scope === "reference" ? import_genre_tree18.GenreCatalogueCountsSchema.parse(genrePlaylist ?? catalogue) : void 0;
|
|
1905
1962
|
const selection = summary ? (0, import_genre_tree18.defaultGenreMusicSelection)(summary) : void 0;
|
|
1906
1963
|
const describeCounts = (counts) => {
|
|
1907
1964
|
const scopes = counts.genreAndSubgenresTracksCount;
|
|
1908
1965
|
return "canonical" in scopes ? `${counts.genreOnlyTracksCount} this genre only \xB7 ${scopes.canonical} with canonical subgenres \xB7 ${scopes.regional} with regional subgenres \xB7 ${scopes.all} with all subgenres` : `${counts.genreOnlyTracksCount} this genre only \xB7 ${scopes.all} with subgenres`;
|
|
1909
1966
|
};
|
|
1910
|
-
return /* @__PURE__ */ (0,
|
|
1967
|
+
return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_jsx_runtime31.Fragment, { children: [
|
|
1911
1968
|
toolbar,
|
|
1912
|
-
/* @__PURE__ */ (0,
|
|
1913
|
-
summary && catalogue && /* @__PURE__ */ (0,
|
|
1914
|
-
/* @__PURE__ */ (0,
|
|
1969
|
+
genrePlaylist ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(GenreNodeActionBand, { ...band, genrePlaylist, playback }) : /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(GenreActionBand, { ...band }),
|
|
1970
|
+
summary && catalogue && /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "px-3.5 py-3 text-sm text-[var(--color-text)]", children: [
|
|
1971
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("p", { children: [
|
|
1915
1972
|
"Catalogue ",
|
|
1916
1973
|
noun.plural,
|
|
1974
|
+
countsContext ? ` (${countsContext})` : "",
|
|
1917
1975
|
": ",
|
|
1918
1976
|
describeCounts(catalogue)
|
|
1919
1977
|
] }),
|
|
1920
|
-
/* @__PURE__ */ (0,
|
|
1978
|
+
genrePlaylist && /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("p", { children: [
|
|
1921
1979
|
"Playlist ",
|
|
1922
1980
|
noun.plural,
|
|
1981
|
+
countsContext ? ` (${countsContext})` : "",
|
|
1923
1982
|
": ",
|
|
1924
1983
|
describeCounts(summary)
|
|
1925
1984
|
] })
|
|
1926
1985
|
] }),
|
|
1927
|
-
/* @__PURE__ */ (0,
|
|
1986
|
+
/* @__PURE__ */ (0, import_react13.createElement)(
|
|
1928
1987
|
GenreDetailExtras,
|
|
1929
1988
|
{
|
|
1930
1989
|
...extras,
|
|
@@ -1932,6 +1991,7 @@ function GenreInfoPanelBody({
|
|
|
1932
1991
|
family,
|
|
1933
1992
|
genreUuid,
|
|
1934
1993
|
genrePlaylist,
|
|
1994
|
+
genreName: band.node.name,
|
|
1935
1995
|
selection,
|
|
1936
1996
|
wikidataId,
|
|
1937
1997
|
scope,
|
|
@@ -1941,10 +2001,160 @@ function GenreInfoPanelBody({
|
|
|
1941
2001
|
] });
|
|
1942
2002
|
}
|
|
1943
2003
|
|
|
2004
|
+
// src/genre-tree/GenreDetailPanel.tsx
|
|
2005
|
+
var import_jsx_runtime32 = require("react/jsx-runtime");
|
|
2006
|
+
var RELATIVE_ITEM_COUNT = 1;
|
|
2007
|
+
function toNode(genre, parentId) {
|
|
2008
|
+
return {
|
|
2009
|
+
id: genre.uuid,
|
|
2010
|
+
parentId,
|
|
2011
|
+
name: genre.name,
|
|
2012
|
+
itemCount: RELATIVE_ITEM_COUNT,
|
|
2013
|
+
side: genre.side ?? void 0,
|
|
2014
|
+
wikidataId: genre.wikidataId
|
|
2015
|
+
};
|
|
2016
|
+
}
|
|
2017
|
+
function useGenreDetailContent({
|
|
2018
|
+
genreUuid,
|
|
2019
|
+
scope,
|
|
2020
|
+
getBackendBaseUrl,
|
|
2021
|
+
onSelectGenre,
|
|
2022
|
+
extras,
|
|
2023
|
+
band,
|
|
2024
|
+
playback: hostPlayback,
|
|
2025
|
+
trailing,
|
|
2026
|
+
genrePlaylist: hostPlaylist,
|
|
2027
|
+
expanded
|
|
2028
|
+
}) {
|
|
2029
|
+
const { data: detail, isPending, isError } = (0, import_genre_tree19.useFetchGenreDetail)(genreUuid, scope, getBackendBaseUrl);
|
|
2030
|
+
const defaultPlayback = useGenrePlaylistPlayback({ scope, genrePlaylists: [], artist: extras?.artist });
|
|
2031
|
+
const playback = hostPlayback ?? defaultPlayback;
|
|
2032
|
+
const view = (0, import_react14.useMemo)(() => {
|
|
2033
|
+
if (!detail) return null;
|
|
2034
|
+
const rootColor = (0, import_genre_tree_view4.getGenreTreeColor)(detail.root.wikidataId);
|
|
2035
|
+
const relative = (genre, parentId) => ({
|
|
2036
|
+
node: toNode(genre, parentId),
|
|
2037
|
+
fill: (0, import_genre_tree_view4.tintSurface)(rootColor),
|
|
2038
|
+
textColor: import_genre_tree_view4.TEXT_COLOR
|
|
2039
|
+
});
|
|
2040
|
+
const ancestors2 = [...detail.ascendants].sort((a, b) => b.degree - a.degree);
|
|
2041
|
+
return {
|
|
2042
|
+
node: {
|
|
2043
|
+
id: detail.uuid,
|
|
2044
|
+
parentId: detail.parent?.uuid ?? null,
|
|
2045
|
+
name: detail.name,
|
|
2046
|
+
itemCount: scope === "reference" ? (0, import_genre_tree19.genreMusicCount)(detail.criteriaPlaylist ? import_genre_tree19.GenrePlaylistSummarySchema.parse(detail.criteriaPlaylist) : import_genre_tree19.GenreCatalogueCountsSchema.parse(detail.catalogue)) : detail.tracksCount,
|
|
2047
|
+
wikidataId: detail.wikidataId
|
|
2048
|
+
},
|
|
2049
|
+
fill: rootColor,
|
|
2050
|
+
parentNode: detail.parent ? relative(detail.parent, null) : null,
|
|
2051
|
+
childNodes: detail.children.map((child) => relative(child, detail.uuid)),
|
|
2052
|
+
ancestorNodes: ancestors2.map(
|
|
2053
|
+
({ ascendant }, index) => relative(ascendant, index === 0 ? null : ancestors2[index - 1].ascendant.uuid)
|
|
2054
|
+
)
|
|
2055
|
+
};
|
|
2056
|
+
}, [detail, scope]);
|
|
2057
|
+
if (isError) return {
|
|
2058
|
+
title: "Genre unavailable",
|
|
2059
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." })
|
|
2060
|
+
};
|
|
2061
|
+
if (isPending || !detail || !view) return {
|
|
2062
|
+
title: "Loading genre\u2026",
|
|
2063
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
|
|
2064
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui10.Skeleton, { className: "h-6 w-2/3" }),
|
|
2065
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui10.Skeleton, { className: "h-4 w-full" }),
|
|
2066
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui10.Skeleton, { className: "h-4 w-3/4" })
|
|
2067
|
+
] })
|
|
2068
|
+
};
|
|
2069
|
+
const selectGenre = (nodeId) => onSelectGenre(import_genre_tree19.GenreUuidSchema.parse(nodeId));
|
|
2070
|
+
const family = /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
|
|
2071
|
+
import_genre_tree_view4.GenreNodeFamily,
|
|
2072
|
+
{
|
|
2073
|
+
node: view.node,
|
|
2074
|
+
fill: view.fill,
|
|
2075
|
+
textColor: import_genre_tree_view4.ACCENT_TEXT_COLOR,
|
|
2076
|
+
parentNode: view.parentNode,
|
|
2077
|
+
childNodes: view.childNodes,
|
|
2078
|
+
onSelectNode: selectGenre
|
|
2079
|
+
}
|
|
2080
|
+
);
|
|
2081
|
+
const ancestors = /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_genre_tree_view4.GenreNodeAncestors, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
|
|
2082
|
+
return {
|
|
2083
|
+
title: detail.name,
|
|
2084
|
+
headerAccent: { background: view.fill, color: import_genre_tree_view4.ACCENT_TEXT_COLOR },
|
|
2085
|
+
aside: expanded ? ancestors : void 0,
|
|
2086
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_jsx_runtime32.Fragment, { children: [
|
|
2087
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
|
|
2088
|
+
GenreInfoPanelBody,
|
|
2089
|
+
{
|
|
2090
|
+
family: /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(import_jsx_runtime32.Fragment, { children: [
|
|
2091
|
+
family,
|
|
2092
|
+
!expanded && ancestors
|
|
2093
|
+
] }),
|
|
2094
|
+
genreUuid: detail.uuid,
|
|
2095
|
+
genrePlaylist: hostPlaylist ?? (detail.criteriaPlaylist === null ? null : scope === "reference" ? { ...import_genre_tree19.GenrePlaylistSummarySchema.parse(detail.criteriaPlaylist), criteria: import_genre_tree19.CriteriaMinimumSchema.parse(detail) } : { ...detail.criteriaPlaylist, criteria: import_genre_tree19.CriteriaMinimumSchema.parse(detail), tracksCount: detail.tracksCount }),
|
|
2096
|
+
catalogue: detail.catalogue,
|
|
2097
|
+
countsContext: !hostPlaylist && extras?.artist ? "all artists" : void 0,
|
|
2098
|
+
wikidataId: detail.wikidataId,
|
|
2099
|
+
scope,
|
|
2100
|
+
getBackendBaseUrl,
|
|
2101
|
+
playback,
|
|
2102
|
+
band: { node: view.node, ...band, shareTarget: band?.shareTarget?.kind === "genre" ? { ...band.shareTarget, name: detail.name } : band?.shareTarget },
|
|
2103
|
+
extras
|
|
2104
|
+
}
|
|
2105
|
+
),
|
|
2106
|
+
trailing
|
|
2107
|
+
] })
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
function GenreDetailPanel({
|
|
2111
|
+
genreUuid,
|
|
2112
|
+
scope,
|
|
2113
|
+
getBackendBaseUrl,
|
|
2114
|
+
onClose,
|
|
2115
|
+
onSelectGenre,
|
|
2116
|
+
onZoomIn,
|
|
2117
|
+
shareUrl,
|
|
2118
|
+
shareTarget,
|
|
2119
|
+
collapsed,
|
|
2120
|
+
expanded,
|
|
2121
|
+
onExpandedChange,
|
|
2122
|
+
edgeTab,
|
|
2123
|
+
extras,
|
|
2124
|
+
headerClassName
|
|
2125
|
+
}) {
|
|
2126
|
+
const panel = useGenreDetailContent({
|
|
2127
|
+
genreUuid,
|
|
2128
|
+
scope,
|
|
2129
|
+
getBackendBaseUrl,
|
|
2130
|
+
onSelectGenre,
|
|
2131
|
+
extras,
|
|
2132
|
+
expanded,
|
|
2133
|
+
band: { onZoomIn, shareUrl, shareTarget: shareUrl ? shareTarget : void 0 }
|
|
2134
|
+
});
|
|
2135
|
+
return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
|
|
2136
|
+
import_ui10.DetailPanel,
|
|
2137
|
+
{
|
|
2138
|
+
side: "left",
|
|
2139
|
+
title: panel.title,
|
|
2140
|
+
headerAccent: panel.headerAccent,
|
|
2141
|
+
onClose,
|
|
2142
|
+
collapsed,
|
|
2143
|
+
expanded,
|
|
2144
|
+
onExpandedChange,
|
|
2145
|
+
edgeTab,
|
|
2146
|
+
aside: panel.aside,
|
|
2147
|
+
headerClassName,
|
|
2148
|
+
headerActions: onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_ui10.IconButton, { "aria-label": expanded ? "Shrink panel" : "Extend panel", onClick: () => onExpandedChange(!expanded), children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react12.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_lucide_react12.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" }) }) : void 0,
|
|
2149
|
+
children: panel.content
|
|
2150
|
+
}
|
|
2151
|
+
);
|
|
2152
|
+
}
|
|
2153
|
+
|
|
1944
2154
|
// src/genre-tree/ChangeParentPopup.tsx
|
|
1945
|
-
var
|
|
1946
|
-
var
|
|
1947
|
-
var
|
|
2155
|
+
var import_react15 = require("react");
|
|
2156
|
+
var import_genre_tree20 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2157
|
+
var import_jsx_runtime33 = require("react/jsx-runtime");
|
|
1948
2158
|
function ChangeParentPopup({
|
|
1949
2159
|
genrePlaylist,
|
|
1950
2160
|
genrePlaylists,
|
|
@@ -1953,9 +2163,9 @@ function ChangeParentPopup({
|
|
|
1953
2163
|
onClose,
|
|
1954
2164
|
onParentChanged
|
|
1955
2165
|
}) {
|
|
1956
|
-
const { mutate, isPending, formErrors } = (0,
|
|
1957
|
-
const candidates = (0,
|
|
1958
|
-
() => (0,
|
|
2166
|
+
const { mutate, isPending, formErrors } = (0, import_genre_tree20.useUpdateGenre)(scope, getBackendBaseUrl);
|
|
2167
|
+
const candidates = (0, import_react15.useMemo)(
|
|
2168
|
+
() => (0, import_genre_tree20.getReparentCandidates)(genrePlaylists, genrePlaylist.uuid),
|
|
1959
2169
|
[genrePlaylists, genrePlaylist.uuid]
|
|
1960
2170
|
);
|
|
1961
2171
|
const handleSelect = (newParent) => {
|
|
@@ -1972,19 +2182,19 @@ function ChangeParentPopup({
|
|
|
1972
2182
|
}
|
|
1973
2183
|
);
|
|
1974
2184
|
};
|
|
1975
|
-
(0,
|
|
2185
|
+
(0, import_react15.useEffect)(() => {
|
|
1976
2186
|
const handleKeyDown = (event) => {
|
|
1977
2187
|
if (event.key === "Escape") onClose();
|
|
1978
2188
|
};
|
|
1979
2189
|
window.addEventListener("keydown", handleKeyDown);
|
|
1980
2190
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
1981
2191
|
}, [onClose]);
|
|
1982
|
-
return /* @__PURE__ */ (0,
|
|
1983
|
-
/* @__PURE__ */ (0,
|
|
2192
|
+
return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
|
|
2193
|
+
/* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("p", { className: "mb-3 text-sm text-[var(--color-text-muted)]", children: [
|
|
1984
2194
|
"Current parent: ",
|
|
1985
2195
|
genrePlaylist.parent?.name ?? "none"
|
|
1986
2196
|
] }),
|
|
1987
|
-
/* @__PURE__ */ (0,
|
|
2197
|
+
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { "aria-busy": isPending, className: isPending ? "pointer-events-none opacity-60" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
|
|
1988
2198
|
GenreSearch,
|
|
1989
2199
|
{
|
|
1990
2200
|
genrePlaylists: candidates,
|
|
@@ -1992,24 +2202,24 @@ function ChangeParentPopup({
|
|
|
1992
2202
|
placeholder: "Search a new parent\u2026"
|
|
1993
2203
|
}
|
|
1994
2204
|
) }),
|
|
1995
|
-
formErrors.length > 0 && /* @__PURE__ */ (0,
|
|
2205
|
+
formErrors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { role: "alert", className: "mt-3 text-sm text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
|
|
1996
2206
|
] });
|
|
1997
2207
|
}
|
|
1998
2208
|
|
|
1999
2209
|
// src/genre-tree/artist/useArtistPanel.tsx
|
|
2000
|
-
var
|
|
2001
|
-
var
|
|
2210
|
+
var import_ui16 = require("@behindthemusictree/ui");
|
|
2211
|
+
var import_genre_tree25 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2002
2212
|
|
|
2003
2213
|
// src/genre-tree/artist/ArtistHeader.tsx
|
|
2004
|
-
var
|
|
2214
|
+
var import_ui12 = require("@behindthemusictree/ui");
|
|
2005
2215
|
|
|
2006
2216
|
// src/genre-tree/artist/ArtistLinks.tsx
|
|
2007
|
-
var
|
|
2217
|
+
var import_lucide_react13 = require("lucide-react");
|
|
2008
2218
|
var import_si3 = require("react-icons/si");
|
|
2009
|
-
var
|
|
2010
|
-
var
|
|
2219
|
+
var import_ui11 = require("@behindthemusictree/ui");
|
|
2220
|
+
var import_jsx_runtime34 = require("react/jsx-runtime");
|
|
2011
2221
|
var LINK_KINDS = {
|
|
2012
|
-
official: { label: "Official website", Icon:
|
|
2222
|
+
official: { label: "Official website", Icon: import_lucide_react13.Globe },
|
|
2013
2223
|
wikipedia: { label: "Wikipedia", Icon: import_si3.SiWikipedia },
|
|
2014
2224
|
wikidata: { label: "Wikidata", Icon: import_si3.SiWikidata },
|
|
2015
2225
|
spotify: { label: "Spotify", Icon: import_si3.SiSpotify },
|
|
@@ -2017,7 +2227,7 @@ var LINK_KINDS = {
|
|
|
2017
2227
|
bandcamp: { label: "Bandcamp", Icon: import_si3.SiBandcamp },
|
|
2018
2228
|
soundcloud: { label: "SoundCloud", Icon: import_si3.SiSoundcloud },
|
|
2019
2229
|
discogs: { label: "Discogs", Icon: import_si3.SiDiscogs },
|
|
2020
|
-
social: { label: "Social", Icon:
|
|
2230
|
+
social: { label: "Social", Icon: import_lucide_react13.Share2 }
|
|
2021
2231
|
};
|
|
2022
2232
|
function socialLabel(url) {
|
|
2023
2233
|
try {
|
|
@@ -2044,7 +2254,7 @@ function artistLinks(artist) {
|
|
|
2044
2254
|
return links;
|
|
2045
2255
|
}
|
|
2046
2256
|
function ArtistLinks({ links }) {
|
|
2047
|
-
return /* @__PURE__ */ (0,
|
|
2257
|
+
return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("ul", { className: "flex flex-wrap gap-1.5", children: links.map(({ key, url, Icon, label }) => /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_ui11.Chip, { href: url, icon: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(Icon, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }), children: label }) }, key)) });
|
|
2048
2258
|
}
|
|
2049
2259
|
|
|
2050
2260
|
// src/genre-tree/artist/formatYears.ts
|
|
@@ -2055,7 +2265,7 @@ function formatYears(beginYear, endYear, ended) {
|
|
|
2055
2265
|
}
|
|
2056
2266
|
|
|
2057
2267
|
// src/genre-tree/artist/ArtistHeader.tsx
|
|
2058
|
-
var
|
|
2268
|
+
var import_jsx_runtime35 = require("react/jsx-runtime");
|
|
2059
2269
|
var countryNames = new Intl.DisplayNames(["en"], { type: "region" });
|
|
2060
2270
|
function ArtistHeader({ artist }) {
|
|
2061
2271
|
const fields = [];
|
|
@@ -2065,35 +2275,35 @@ function ArtistHeader({ artist }) {
|
|
|
2065
2275
|
const years = formatYears(artist.beginYear, artist.endYear, artist.ended);
|
|
2066
2276
|
if (years) fields.push({ label: "Active", value: years });
|
|
2067
2277
|
const links = artistLinks(artist);
|
|
2068
|
-
if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0,
|
|
2278
|
+
if (links.length > 0) fields.push({ label: "Links", value: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(ArtistLinks, { links }) });
|
|
2069
2279
|
if (fields.length === 0) return null;
|
|
2070
|
-
return /* @__PURE__ */ (0,
|
|
2280
|
+
return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_ui12.FieldList, { items: fields });
|
|
2071
2281
|
}
|
|
2072
2282
|
|
|
2073
2283
|
// src/genre-tree/artist/ArtistPhoto.tsx
|
|
2074
|
-
var
|
|
2284
|
+
var import_jsx_runtime36 = require("react/jsx-runtime");
|
|
2075
2285
|
function ExternalLink({ href, children }) {
|
|
2076
|
-
return /* @__PURE__ */ (0,
|
|
2286
|
+
return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("a", { href, target: "_blank", rel: "noopener noreferrer", className: "underline-offset-2 hover:underline", children });
|
|
2077
2287
|
}
|
|
2078
2288
|
function ArtistPhoto({ image, name }) {
|
|
2079
|
-
return /* @__PURE__ */ (0,
|
|
2080
|
-
/* @__PURE__ */ (0,
|
|
2081
|
-
/* @__PURE__ */ (0,
|
|
2289
|
+
return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
|
|
2290
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
|
|
2291
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("figcaption", { className: "text-xs text-[var(--color-text-muted)]", children: [
|
|
2082
2292
|
"Photo: ",
|
|
2083
|
-
/* @__PURE__ */ (0,
|
|
2293
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
|
|
2084
2294
|
",",
|
|
2085
2295
|
" ",
|
|
2086
|
-
image.licenseUrl ? /* @__PURE__ */ (0,
|
|
2296
|
+
image.licenseUrl ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ExternalLink, { href: image.licenseUrl, children: image.license }) : image.license
|
|
2087
2297
|
] })
|
|
2088
2298
|
] });
|
|
2089
2299
|
}
|
|
2090
2300
|
|
|
2091
2301
|
// src/genre-tree/artist/ArtistMembers.tsx
|
|
2092
|
-
var
|
|
2093
|
-
var
|
|
2302
|
+
var import_ui13 = require("@behindthemusictree/ui");
|
|
2303
|
+
var import_jsx_runtime37 = require("react/jsx-runtime");
|
|
2094
2304
|
function MembershipList({ title, memberships, renderArtistLink }) {
|
|
2095
2305
|
if (memberships.length === 0) return null;
|
|
2096
|
-
return /* @__PURE__ */ (0,
|
|
2306
|
+
return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui13.PanelGroup, { title, children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("ul", { children: memberships.map((membership, index) => {
|
|
2097
2307
|
const details = [
|
|
2098
2308
|
formatYears(membership.beginYear, membership.endYear, membership.ended),
|
|
2099
2309
|
...membership.roles
|
|
@@ -2101,9 +2311,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
|
|
|
2101
2311
|
const detailsText = details.filter(Boolean).join(" \xB7 ");
|
|
2102
2312
|
return (
|
|
2103
2313
|
// A member can have several periods in the same group.
|
|
2104
|
-
/* @__PURE__ */ (0,
|
|
2105
|
-
/* @__PURE__ */ (0,
|
|
2106
|
-
detailsText && /* @__PURE__ */ (0,
|
|
2314
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("li", { className: "truncate py-0.5", title: detailsText || void 0, children: [
|
|
2315
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "font-medium text-[var(--color-text)]", children: renderArtistLink(membership.artist) }),
|
|
2316
|
+
detailsText && /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
|
|
2107
2317
|
" \u2014 ",
|
|
2108
2318
|
detailsText
|
|
2109
2319
|
] })
|
|
@@ -2114,9 +2324,9 @@ function MembershipList({ title, memberships, renderArtistLink }) {
|
|
|
2114
2324
|
function ArtistMembers({ title, memberships, renderArtistLink }) {
|
|
2115
2325
|
if (memberships.length === 0) return null;
|
|
2116
2326
|
const isCurrent = (membership) => !membership.ended && membership.endYear === null;
|
|
2117
|
-
return /* @__PURE__ */ (0,
|
|
2118
|
-
/* @__PURE__ */ (0,
|
|
2119
|
-
/* @__PURE__ */ (0,
|
|
2327
|
+
return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(import_ui13.PanelSection, { title, children: [
|
|
2328
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(MembershipList, { title: "Current", memberships: memberships.filter(isCurrent), renderArtistLink }),
|
|
2329
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
|
|
2120
2330
|
MembershipList,
|
|
2121
2331
|
{
|
|
2122
2332
|
title: "Past",
|
|
@@ -2128,8 +2338,8 @@ function ArtistMembers({ title, memberships, renderArtistLink }) {
|
|
|
2128
2338
|
}
|
|
2129
2339
|
|
|
2130
2340
|
// src/genre-tree/artist/ArtistDiscography.tsx
|
|
2131
|
-
var
|
|
2132
|
-
var
|
|
2341
|
+
var import_ui14 = require("@behindthemusictree/ui");
|
|
2342
|
+
var import_jsx_runtime38 = require("react/jsx-runtime");
|
|
2133
2343
|
var GROUPS = ["Albums", "EPs", "Singles", "Live", "Compilations", "Other"];
|
|
2134
2344
|
var PRIMARY_GROUPS = { album: "Albums", ep: "EPs", single: "Singles" };
|
|
2135
2345
|
function discographyGroup({ primaryType, secondaryTypes }) {
|
|
@@ -2149,34 +2359,34 @@ function byYear(a, b) {
|
|
|
2149
2359
|
function ArtistDiscography({ albums }) {
|
|
2150
2360
|
if (albums.length === 0) return null;
|
|
2151
2361
|
const sorted = [...albums].sort(byYear);
|
|
2152
|
-
return /* @__PURE__ */ (0,
|
|
2362
|
+
return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_ui14.PanelSection, { title: "Discography", children: GROUPS.map((group) => {
|
|
2153
2363
|
const groupAlbums = sorted.filter((album) => discographyGroup(album) === group);
|
|
2154
2364
|
if (groupAlbums.length === 0) return null;
|
|
2155
|
-
return /* @__PURE__ */ (0,
|
|
2156
|
-
/* @__PURE__ */ (0,
|
|
2157
|
-
/* @__PURE__ */ (0,
|
|
2365
|
+
return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_ui14.PanelGroup, { title: group, children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("ul", { children: groupAlbums.map((album) => /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("li", { className: "grid grid-cols-[2.5rem_minmax(0,1fr)] gap-2 py-0.5", children: [
|
|
2366
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "tabular-nums text-[var(--color-text-muted)]", children: album.year ?? "\u2014" }),
|
|
2367
|
+
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "truncate text-[var(--color-text)]", title: album.name, children: album.name })
|
|
2158
2368
|
] }, album.uuid)) }) }, group);
|
|
2159
2369
|
}) });
|
|
2160
2370
|
}
|
|
2161
2371
|
|
|
2162
2372
|
// src/genre-tree/artist/ArtistSongs.tsx
|
|
2163
|
-
var
|
|
2373
|
+
var import_react16 = require("react");
|
|
2164
2374
|
var import_react_query2 = require("@tanstack/react-query");
|
|
2165
|
-
var
|
|
2166
|
-
var
|
|
2375
|
+
var import_lucide_react14 = require("lucide-react");
|
|
2376
|
+
var import_ui15 = require("@behindthemusictree/ui");
|
|
2167
2377
|
var import_player10 = require("@behindthemusictree/app-kit/player");
|
|
2168
|
-
var
|
|
2378
|
+
var import_genre_tree24 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2169
2379
|
var import_transport9 = require("@behindthemusictree/app-kit/transport");
|
|
2170
2380
|
|
|
2171
2381
|
// src/genre-tree/track-list-sidebar/TrackItem.tsx
|
|
2172
2382
|
var import_player9 = require("@behindthemusictree/app-kit/player");
|
|
2173
|
-
var
|
|
2383
|
+
var import_genre_tree21 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2174
2384
|
|
|
2175
2385
|
// src/genre-tree/TrackPositionPlayPause.tsx
|
|
2176
2386
|
var import_fa5 = require("react-icons/fa");
|
|
2177
2387
|
var import_player7 = require("@behindthemusictree/app-kit/player");
|
|
2178
2388
|
var import_player8 = require("@behindthemusictree/app-kit/player");
|
|
2179
|
-
var
|
|
2389
|
+
var import_jsx_runtime39 = require("react/jsx-runtime");
|
|
2180
2390
|
function TrackPositionPlayPause({
|
|
2181
2391
|
position,
|
|
2182
2392
|
uuid,
|
|
@@ -2190,14 +2400,14 @@ function TrackPositionPlayPause({
|
|
|
2190
2400
|
const isPlaying = isThisTrack && isConfirmedPlayable && playState === import_player8.PlayStates.PLAYING;
|
|
2191
2401
|
const isPaused = isThisTrack && isConfirmedPlayable && playState === import_player8.PlayStates.PAUSED;
|
|
2192
2402
|
const isCurrentTrackActive = isPlaying || isPaused;
|
|
2193
|
-
return /* @__PURE__ */ (0,
|
|
2403
|
+
return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
|
|
2194
2404
|
"div",
|
|
2195
2405
|
{
|
|
2196
2406
|
className: "track-position-play-pause relative flex items-center justify-center gap-2 text-lg w-16 cursor-pointer",
|
|
2197
2407
|
style: { minWidth: "4rem", maxWidth: "4rem" },
|
|
2198
2408
|
onClick: handlePlayPauseClick,
|
|
2199
2409
|
children: [
|
|
2200
|
-
/* @__PURE__ */ (0,
|
|
2410
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "track-position-play-pause__status group-hover:hidden flex items-center justify-center", children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "track-position-play-pause__spinner", role: "status", "aria-label": "Loading track" }) : isCurrentTrackActive ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
|
|
2201
2411
|
"span",
|
|
2202
2412
|
{
|
|
2203
2413
|
className: "track-position-play-pause__bars",
|
|
@@ -2205,18 +2415,18 @@ function TrackPositionPlayPause({
|
|
|
2205
2415
|
role: "img",
|
|
2206
2416
|
"aria-label": isPlaying ? "Playing" : "Paused",
|
|
2207
2417
|
children: [
|
|
2208
|
-
/* @__PURE__ */ (0,
|
|
2209
|
-
/* @__PURE__ */ (0,
|
|
2210
|
-
/* @__PURE__ */ (0,
|
|
2418
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "track-position-play-pause__bar" }),
|
|
2419
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "track-position-play-pause__bar" }),
|
|
2420
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "track-position-play-pause__bar" })
|
|
2211
2421
|
]
|
|
2212
2422
|
}
|
|
2213
2423
|
) : position }),
|
|
2214
|
-
/* @__PURE__ */ (0,
|
|
2424
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2215
2425
|
"div",
|
|
2216
2426
|
{
|
|
2217
2427
|
className: `hidden group-hover:flex items-center justify-center ${isCurrentTrackActive ? "" : "absolute inset-0"}`,
|
|
2218
2428
|
"aria-hidden": "true",
|
|
2219
|
-
children: isAwaitingPlayback ? /* @__PURE__ */ (0,
|
|
2429
|
+
children: isAwaitingPlayback ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "track-position-play-pause__spinner" }) : isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_fa5.FaPause, {}) : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_fa5.FaPlay, {})
|
|
2220
2430
|
}
|
|
2221
2431
|
)
|
|
2222
2432
|
]
|
|
@@ -2225,9 +2435,9 @@ function TrackPositionPlayPause({
|
|
|
2225
2435
|
}
|
|
2226
2436
|
|
|
2227
2437
|
// src/genre-tree/track-list-sidebar/TrackItem.tsx
|
|
2228
|
-
var import_genre_tree21 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2229
2438
|
var import_genre_tree22 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2230
|
-
var
|
|
2439
|
+
var import_genre_tree23 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2440
|
+
var import_jsx_runtime40 = require("react/jsx-runtime");
|
|
2231
2441
|
function TrackItem({
|
|
2232
2442
|
track,
|
|
2233
2443
|
position,
|
|
@@ -2237,11 +2447,11 @@ function TrackItem({
|
|
|
2237
2447
|
renderSongLink
|
|
2238
2448
|
}) {
|
|
2239
2449
|
const { handlePlayPauseAction, playerTrackObject, loadTrackForPlayer } = (0, import_player9.usePlayer)();
|
|
2240
|
-
const { trackList, selectedTrack, toTrackAtPosition, runtimeUnplayableTrackIds } = (0,
|
|
2450
|
+
const { trackList, selectedTrack, toTrackAtPosition, runtimeUnplayableTrackIds } = (0, import_genre_tree21.useTrackList)();
|
|
2241
2451
|
const scope = trackList?.origin?.scope ?? null;
|
|
2242
2452
|
const isCurrentTrack = playerTrackObject?.track.id === track.uuid;
|
|
2243
2453
|
const hasPlaybackError = isCurrentTrack && !!playerTrackObject?.loadError;
|
|
2244
|
-
const isUnplayable = hasPlaybackError || runtimeUnplayableTrackIds?.has(track.uuid) === true || (0,
|
|
2454
|
+
const isUnplayable = hasPlaybackError || runtimeUnplayableTrackIds?.has(track.uuid) === true || (0, import_genre_tree22.isSong)(track) && (0, import_genre_tree22.songUnplayableReason)(track) !== null;
|
|
2245
2455
|
const handlePlayPauseClick = (event) => {
|
|
2246
2456
|
event.stopPropagation();
|
|
2247
2457
|
if (isCurrentTrack) {
|
|
@@ -2251,12 +2461,12 @@ function TrackItem({
|
|
|
2251
2461
|
loadTrackForPlayer(track.uuid);
|
|
2252
2462
|
}
|
|
2253
2463
|
};
|
|
2254
|
-
return /* @__PURE__ */ (0,
|
|
2464
|
+
return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
|
|
2255
2465
|
"div",
|
|
2256
2466
|
{
|
|
2257
2467
|
className: `track-item flex h-14 text-[var(--color-text)] ${isUnplayable ? "opacity-50" : "hover:bg-[var(--color-surface-muted)] group"}`,
|
|
2258
2468
|
children: [
|
|
2259
|
-
isUnplayable ? /* @__PURE__ */ (0,
|
|
2469
|
+
isUnplayable ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2260
2470
|
"div",
|
|
2261
2471
|
{
|
|
2262
2472
|
className: "flex items-center justify-center text-lg w-16",
|
|
@@ -2264,7 +2474,7 @@ function TrackItem({
|
|
|
2264
2474
|
"aria-disabled": "true",
|
|
2265
2475
|
children: position
|
|
2266
2476
|
}
|
|
2267
|
-
) : /* @__PURE__ */ (0,
|
|
2477
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2268
2478
|
TrackPositionPlayPause,
|
|
2269
2479
|
{
|
|
2270
2480
|
position,
|
|
@@ -2273,12 +2483,12 @@ function TrackItem({
|
|
|
2273
2483
|
isSelected: selectedTrack?.uuid === track.uuid
|
|
2274
2484
|
}
|
|
2275
2485
|
),
|
|
2276
|
-
/* @__PURE__ */ (0,
|
|
2277
|
-
/* @__PURE__ */ (0,
|
|
2278
|
-
track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0,
|
|
2486
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "title-artist-container flex flex-col items-start justify-center min-w-0 flex-1", children: [
|
|
2487
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree22.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
|
|
2488
|
+
track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_genre_tree23.ArtistNames, { artists: track.artists, renderArtistLink }) }) : ""
|
|
2279
2489
|
] }),
|
|
2280
|
-
/* @__PURE__ */ (0,
|
|
2281
|
-
renderDuration && /* @__PURE__ */ (0,
|
|
2490
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.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_runtime40.jsx)("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
|
|
2491
|
+
renderDuration && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
2282
2492
|
"div",
|
|
2283
2493
|
{
|
|
2284
2494
|
className: "duration flex text-base w-16 items-center justify-center",
|
|
@@ -2286,28 +2496,28 @@ function TrackItem({
|
|
|
2286
2496
|
children: renderDuration(track)
|
|
2287
2497
|
}
|
|
2288
2498
|
),
|
|
2289
|
-
renderActions && /* @__PURE__ */ (0,
|
|
2499
|
+
renderActions && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "edit flex text-base w-6 items-center justify-center mr-2 cursor-pointer", children: renderActions(track) })
|
|
2290
2500
|
]
|
|
2291
2501
|
}
|
|
2292
2502
|
);
|
|
2293
2503
|
}
|
|
2294
2504
|
|
|
2295
2505
|
// src/genre-tree/artist/ArtistSongs.tsx
|
|
2296
|
-
var
|
|
2297
|
-
var SongPageSchema2 = (0, import_transport9.PaginatedResponseSchema)(
|
|
2506
|
+
var import_jsx_runtime41 = require("react/jsx-runtime");
|
|
2507
|
+
var SongPageSchema2 = (0, import_transport9.PaginatedResponseSchema)(import_genre_tree24.SongDetailedSchema);
|
|
2298
2508
|
var SORTS = [
|
|
2299
2509
|
{ ordering: "title", label: "Title" },
|
|
2300
2510
|
{ ordering: "-albumYear", label: "Year" }
|
|
2301
2511
|
];
|
|
2302
2512
|
function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLink }) {
|
|
2303
|
-
const [page, setPage] = (0,
|
|
2304
|
-
const [ordering, setOrdering] = (0,
|
|
2513
|
+
const [page, setPage] = (0, import_react16.useState)(1);
|
|
2514
|
+
const [ordering, setOrdering] = (0, import_react16.useState)("title");
|
|
2305
2515
|
const { fetch } = (0, import_transport9.useFetchWrapper)(getBackendBaseUrl);
|
|
2306
|
-
const { playNewTrackListFromArtist } = (0,
|
|
2516
|
+
const { playNewTrackListFromArtist } = (0, import_genre_tree24.useTrackList)();
|
|
2307
2517
|
const { playerTrackObject } = (0, import_player10.usePlayer)();
|
|
2308
2518
|
const { data, isPending, isError } = (0, import_transport9.useQueryWithParse)({
|
|
2309
2519
|
queryKey: ["referenceSongs", "artist", artist.uuid, ordering, page],
|
|
2310
|
-
queryFn: () => fetch(
|
|
2520
|
+
queryFn: () => fetch(import_genre_tree24.libraryEndpoints.reference.songs.list(), true, false, void 0, {
|
|
2311
2521
|
artist: artist.uuid,
|
|
2312
2522
|
ordering,
|
|
2313
2523
|
page
|
|
@@ -2322,25 +2532,25 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
|
|
|
2322
2532
|
});
|
|
2323
2533
|
const handleRowClickCapture = (event, song) => {
|
|
2324
2534
|
if (!(event.target instanceof Element) || !event.target.closest(".track-position-play-pause")) return;
|
|
2325
|
-
if (playerTrackObject?.track.id === song.uuid || (0,
|
|
2535
|
+
if (playerTrackObject?.track.id === song.uuid || (0, import_genre_tree24.songUnplayableReason)(song) !== null) return;
|
|
2326
2536
|
event.stopPropagation();
|
|
2327
2537
|
void play(song.uuid);
|
|
2328
2538
|
};
|
|
2329
|
-
return /* @__PURE__ */ (0,
|
|
2330
|
-
/* @__PURE__ */ (0,
|
|
2331
|
-
/* @__PURE__ */ (0,
|
|
2539
|
+
return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui15.PanelSection, { title: "Songs", children: isPending ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui15.Skeleton, { className: "h-14", "aria-busy": "true" }) : isError ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "text-red-600", children: "Could not load songs." }) : data.results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No songs yet." }) : /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(import_jsx_runtime41.Fragment, { children: [
|
|
2540
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-2 mb-2", children: [
|
|
2541
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
|
|
2332
2542
|
"button",
|
|
2333
2543
|
{
|
|
2334
2544
|
type: "button",
|
|
2335
2545
|
onClick: () => void play(),
|
|
2336
2546
|
className: "flex w-fit items-center gap-1.5 px-3 py-1 text-xs font-medium bg-gray-900 text-white rounded-full hover:bg-gray-700",
|
|
2337
2547
|
children: [
|
|
2338
|
-
/* @__PURE__ */ (0,
|
|
2548
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_lucide_react14.Play, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
|
|
2339
2549
|
"Play all"
|
|
2340
2550
|
]
|
|
2341
2551
|
}
|
|
2342
2552
|
),
|
|
2343
|
-
/* @__PURE__ */ (0,
|
|
2553
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
2344
2554
|
"button",
|
|
2345
2555
|
{
|
|
2346
2556
|
type: "button",
|
|
@@ -2355,7 +2565,7 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
|
|
|
2355
2565
|
sort.ordering
|
|
2356
2566
|
)) })
|
|
2357
2567
|
] }),
|
|
2358
|
-
/* @__PURE__ */ (0,
|
|
2568
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("ul", { className: "overflow-hidden mb-2 bg-zinc-950 rounded-lg", children: data.results.map((song, index) => /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("li", { onClickCapture: (event) => handleRowClickCapture(event, song), children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
2359
2569
|
TrackItem,
|
|
2360
2570
|
{
|
|
2361
2571
|
track: song,
|
|
@@ -2364,12 +2574,12 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
|
|
|
2364
2574
|
renderSongLink
|
|
2365
2575
|
}
|
|
2366
2576
|
) }, song.uuid)) }),
|
|
2367
|
-
/* @__PURE__ */ (0,
|
|
2577
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_ui15.Pagination, { currentPage: page, totalPages: data.totalPages, onPageChange: setPage })
|
|
2368
2578
|
] }) });
|
|
2369
2579
|
}
|
|
2370
2580
|
|
|
2371
2581
|
// src/genre-tree/artist/useArtistPanel.tsx
|
|
2372
|
-
var
|
|
2582
|
+
var import_jsx_runtime42 = require("react/jsx-runtime");
|
|
2373
2583
|
function useArtistPanel({
|
|
2374
2584
|
uuid,
|
|
2375
2585
|
scope,
|
|
@@ -2381,7 +2591,7 @@ function useArtistPanel({
|
|
|
2381
2591
|
onExpandedChange,
|
|
2382
2592
|
onClose
|
|
2383
2593
|
}) {
|
|
2384
|
-
const result = (0,
|
|
2594
|
+
const result = (0, import_genre_tree25.useArtist)(uuid, getBackendBaseUrl);
|
|
2385
2595
|
const panel = { expanded, onExpandedChange, onClose };
|
|
2386
2596
|
switch (result.status) {
|
|
2387
2597
|
case "idle":
|
|
@@ -2390,26 +2600,26 @@ function useArtistPanel({
|
|
|
2390
2600
|
return {
|
|
2391
2601
|
...panel,
|
|
2392
2602
|
title: "Artist",
|
|
2393
|
-
content: /* @__PURE__ */ (0,
|
|
2394
|
-
/* @__PURE__ */ (0,
|
|
2395
|
-
/* @__PURE__ */ (0,
|
|
2396
|
-
/* @__PURE__ */ (0,
|
|
2397
|
-
/* @__PURE__ */ (0,
|
|
2398
|
-
/* @__PURE__ */ (0,
|
|
2399
|
-
/* @__PURE__ */ (0,
|
|
2603
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
|
|
2604
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui16.Skeleton, { className: "h-3 w-16" }),
|
|
2605
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui16.Skeleton, { className: "h-4 w-32" }),
|
|
2606
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui16.Skeleton, { className: "h-3 w-16" }),
|
|
2607
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui16.Skeleton, { className: "h-4 w-24" }),
|
|
2608
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui16.Skeleton, { className: "h-16 mt-2.5" }),
|
|
2609
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_ui16.Skeleton, { className: "h-16" })
|
|
2400
2610
|
] })
|
|
2401
2611
|
};
|
|
2402
2612
|
case "not-found":
|
|
2403
2613
|
return {
|
|
2404
2614
|
...panel,
|
|
2405
2615
|
title: "Artist not found",
|
|
2406
|
-
content: /* @__PURE__ */ (0,
|
|
2616
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This artist does not exist." })
|
|
2407
2617
|
};
|
|
2408
2618
|
case "error":
|
|
2409
2619
|
return {
|
|
2410
2620
|
...panel,
|
|
2411
2621
|
title: "Artist",
|
|
2412
|
-
content: /* @__PURE__ */ (0,
|
|
2622
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this artist." })
|
|
2413
2623
|
};
|
|
2414
2624
|
case "success": {
|
|
2415
2625
|
const { artist } = result;
|
|
@@ -2418,13 +2628,13 @@ function useArtistPanel({
|
|
|
2418
2628
|
return {
|
|
2419
2629
|
...panel,
|
|
2420
2630
|
title: artist.name,
|
|
2421
|
-
content: /* @__PURE__ */ (0,
|
|
2422
|
-
shareUrl && /* @__PURE__ */ (0,
|
|
2423
|
-
artist.image && /* @__PURE__ */ (0,
|
|
2424
|
-
/* @__PURE__ */ (0,
|
|
2425
|
-
/* @__PURE__ */ (0,
|
|
2426
|
-
/* @__PURE__ */ (0,
|
|
2427
|
-
artist.tracksCount > 0 && /* @__PURE__ */ (0,
|
|
2631
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_jsx_runtime42.Fragment, { children: [
|
|
2632
|
+
shareUrl && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: shareTarget && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl }) }) }),
|
|
2633
|
+
artist.image && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ArtistPhoto, { image: artist.image, name: artist.name }),
|
|
2634
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ArtistHeader, { artist }),
|
|
2635
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ArtistMembers, { title: "Members", memberships: artist.members, renderArtistLink }),
|
|
2636
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ArtistMembers, { title: "Member of", memberships: artist.memberOf, renderArtistLink }),
|
|
2637
|
+
artist.tracksCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
2428
2638
|
ArtistSongs,
|
|
2429
2639
|
{
|
|
2430
2640
|
artist: { uuid: artist.uuid, name: artist.name },
|
|
@@ -2434,56 +2644,56 @@ function useArtistPanel({
|
|
|
2434
2644
|
}
|
|
2435
2645
|
)
|
|
2436
2646
|
] }),
|
|
2437
|
-
extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0,
|
|
2647
|
+
extraContent: artist.albums.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(ArtistDiscography, { albums: artist.albums }) : void 0
|
|
2438
2648
|
};
|
|
2439
2649
|
}
|
|
2440
2650
|
}
|
|
2441
2651
|
}
|
|
2442
2652
|
|
|
2443
2653
|
// src/genre-tree/song/useSongPanel.tsx
|
|
2444
|
-
var
|
|
2445
|
-
var
|
|
2654
|
+
var import_ui18 = require("@behindthemusictree/ui");
|
|
2655
|
+
var import_genre_tree27 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2446
2656
|
|
|
2447
2657
|
// src/genre-tree/song/SongPanelContent.tsx
|
|
2448
|
-
var
|
|
2449
|
-
var
|
|
2450
|
-
var
|
|
2658
|
+
var import_react17 = require("react");
|
|
2659
|
+
var import_lucide_react15 = require("lucide-react");
|
|
2660
|
+
var import_ui17 = require("@behindthemusictree/ui");
|
|
2451
2661
|
var import_player11 = require("@behindthemusictree/app-kit/player");
|
|
2452
|
-
var
|
|
2453
|
-
var
|
|
2662
|
+
var import_genre_tree26 = require("@behindthemusictree/app-kit/genre-tree");
|
|
2663
|
+
var import_jsx_runtime43 = require("react/jsx-runtime");
|
|
2454
2664
|
var numberFormat = new Intl.NumberFormat("en");
|
|
2455
2665
|
var dateFormat = new Intl.DateTimeFormat("en", { dateStyle: "medium" });
|
|
2456
2666
|
var externalLink = "text-[var(--color-primary)] hover:underline focus-visible:underline";
|
|
2457
2667
|
function SongHistory({ uuid, getBackendBaseUrl }) {
|
|
2458
|
-
const [open, setOpen] = (0,
|
|
2459
|
-
const history = (0,
|
|
2668
|
+
const [open, setOpen] = (0, import_react17.useState)(false);
|
|
2669
|
+
const history = (0, import_genre_tree26.useSongHistory)(uuid, getBackendBaseUrl, open);
|
|
2460
2670
|
let body = null;
|
|
2461
|
-
if (history.status === "loading") body = /* @__PURE__ */ (0,
|
|
2462
|
-
else if (history.status === "error") body = /* @__PURE__ */ (0,
|
|
2671
|
+
if (history.status === "loading") body = /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_ui17.Skeleton, { className: "h-10", "aria-busy": "true" });
|
|
2672
|
+
else if (history.status === "error") body = /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("p", { className: "text-red-600", children: "Could not load the history." });
|
|
2463
2673
|
else if (history.status === "success")
|
|
2464
|
-
body = history.entries.length === 0 ? /* @__PURE__ */ (0,
|
|
2465
|
-
/* @__PURE__ */ (0,
|
|
2466
|
-
entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ (0,
|
|
2674
|
+
body = history.entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("li", { className: "py-1.5 text-sm", children: [
|
|
2675
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "font-medium", children: entry.action }),
|
|
2676
|
+
entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
|
|
2467
2677
|
" ",
|
|
2468
2678
|
"\u2014 ",
|
|
2469
2679
|
entry.oldValue ?? "none",
|
|
2470
2680
|
" \u2192 ",
|
|
2471
2681
|
entry.newValue ?? "none"
|
|
2472
2682
|
] }) : null,
|
|
2473
|
-
/* @__PURE__ */ (0,
|
|
2683
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("span", { className: "block text-xs text-[var(--color-text-muted)]", children: [
|
|
2474
2684
|
entry.actorPseudo ?? "Anonymous",
|
|
2475
2685
|
" \xB7 ",
|
|
2476
2686
|
dateFormat.format(new Date(entry.createdOn))
|
|
2477
2687
|
] })
|
|
2478
2688
|
] }, entry.uuid)) });
|
|
2479
|
-
return /* @__PURE__ */ (0,
|
|
2689
|
+
return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(
|
|
2480
2690
|
"details",
|
|
2481
2691
|
{
|
|
2482
2692
|
onToggle: (event) => setOpen(event.currentTarget.open),
|
|
2483
2693
|
className: "px-6 pb-2.5 mt-2.5 border-t border-[var(--color-border)]",
|
|
2484
2694
|
children: [
|
|
2485
|
-
/* @__PURE__ */ (0,
|
|
2486
|
-
/* @__PURE__ */ (0,
|
|
2695
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)("summary", { className: "mt-2.5 cursor-pointer text-[0.6875rem] font-semibold uppercase text-[var(--color-text-muted)]", children: "History" }),
|
|
2696
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className: "mt-1.5", children: body })
|
|
2487
2697
|
]
|
|
2488
2698
|
}
|
|
2489
2699
|
);
|
|
@@ -2497,25 +2707,25 @@ function SongPanelContent({
|
|
|
2497
2707
|
genrePath,
|
|
2498
2708
|
onSelectGenre
|
|
2499
2709
|
}) {
|
|
2500
|
-
const { playNewTrackListFromTrackUuid } = (0,
|
|
2710
|
+
const { playNewTrackListFromTrackUuid } = (0, import_genre_tree26.useTrackList)();
|
|
2501
2711
|
const { playerTrackObject, playState, handlePlayPauseAction } = (0, import_player11.usePlayer)();
|
|
2502
|
-
const unplayableReason = (0,
|
|
2712
|
+
const unplayableReason = (0, import_genre_tree26.songUnplayableReason)(song);
|
|
2503
2713
|
const isCurrent = playerTrackObject?.track.id === song.uuid;
|
|
2504
2714
|
const isPlaying = isCurrent && playState === import_player11.PlayStates.PLAYING;
|
|
2505
2715
|
const shareTarget = { kind: "song", uuid: song.uuid, name: song.title, scope };
|
|
2506
|
-
const musicBrainzUrl = (0,
|
|
2716
|
+
const musicBrainzUrl = (0, import_genre_tree26.musicBrainzRecordingUrl)(song);
|
|
2507
2717
|
const fields = [];
|
|
2508
2718
|
if (song.genre) {
|
|
2509
2719
|
const path = genrePath.length > 0 ? genrePath : null;
|
|
2510
2720
|
fields.push({
|
|
2511
2721
|
label: "Genre",
|
|
2512
|
-
value: path ? /* @__PURE__ */ (0,
|
|
2513
|
-
index > 0 && /* @__PURE__ */ (0,
|
|
2514
|
-
/* @__PURE__ */ (0,
|
|
2722
|
+
value: path ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("span", { children: [
|
|
2723
|
+
index > 0 && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { "aria-hidden": "true", children: "\u203A " }),
|
|
2724
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)("button", { type: "button", onClick: () => onSelectGenre(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
|
|
2515
2725
|
] }, genrePlaylist.uuid)) }) : song.genre.name
|
|
2516
2726
|
});
|
|
2517
2727
|
} else {
|
|
2518
|
-
fields.push({ label: "Genre", value: /* @__PURE__ */ (0,
|
|
2728
|
+
fields.push({ label: "Genre", value: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "text-[var(--color-text-muted)]", children: "No genre" }) });
|
|
2519
2729
|
}
|
|
2520
2730
|
if (song.firstReleaseYear) fields.push({ label: "First released", value: song.firstReleaseYear });
|
|
2521
2731
|
const viewCount = song.youtubeVideo?.viewCount;
|
|
@@ -2525,11 +2735,11 @@ function SongPanelContent({
|
|
|
2525
2735
|
const links = [];
|
|
2526
2736
|
if (musicBrainzUrl)
|
|
2527
2737
|
links.push(
|
|
2528
|
-
/* @__PURE__ */ (0,
|
|
2738
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)("a", { href: musicBrainzUrl, target: "_blank", rel: "noopener noreferrer", className: externalLink, children: "MusicBrainz" }, "mb")
|
|
2529
2739
|
);
|
|
2530
2740
|
if (song.youtubeVideo)
|
|
2531
2741
|
links.push(
|
|
2532
|
-
/* @__PURE__ */ (0,
|
|
2742
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
2533
2743
|
"a",
|
|
2534
2744
|
{
|
|
2535
2745
|
href: `https://www.youtube.com/watch?v=${encodeURIComponent(song.youtubeVideo.id)}`,
|
|
@@ -2544,33 +2754,33 @@ function SongPanelContent({
|
|
|
2544
2754
|
if (links.length > 0)
|
|
2545
2755
|
fields.push({
|
|
2546
2756
|
label: "Links",
|
|
2547
|
-
value: /* @__PURE__ */ (0,
|
|
2757
|
+
value: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "flex gap-3", children: links })
|
|
2548
2758
|
});
|
|
2549
|
-
return /* @__PURE__ */ (0,
|
|
2550
|
-
/* @__PURE__ */ (0,
|
|
2551
|
-
unplayableReason ? /* @__PURE__ */ (0,
|
|
2759
|
+
return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(import_jsx_runtime43.Fragment, { children: [
|
|
2760
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
|
|
2761
|
+
unplayableReason ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("p", { className: "flex items-center px-2.5 text-sm text-[var(--color-text-muted)]", children: unplayableReason }) : /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(
|
|
2552
2762
|
"button",
|
|
2553
2763
|
{
|
|
2554
2764
|
type: "button",
|
|
2555
2765
|
onClick: () => isCurrent ? handlePlayPauseAction() : playNewTrackListFromTrackUuid(song, scope),
|
|
2556
2766
|
className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none",
|
|
2557
2767
|
children: [
|
|
2558
|
-
/* @__PURE__ */ (0,
|
|
2768
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.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_runtime43.jsx)(import_lucide_react15.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_lucide_react15.Play, { "aria-hidden": "true", className: "w-5 h-5" }) }),
|
|
2559
2769
|
isPlaying ? "Pause" : "Play"
|
|
2560
2770
|
]
|
|
2561
2771
|
}
|
|
2562
2772
|
),
|
|
2563
|
-
shareUrl && /* @__PURE__ */ (0,
|
|
2773
|
+
shareUrl && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(ShareActionButton, { target: shareTarget, url: shareUrl })
|
|
2564
2774
|
] }) }),
|
|
2565
|
-
song.artists && song.artists.length > 0 && /* @__PURE__ */ (0,
|
|
2566
|
-
/* @__PURE__ */ (0,
|
|
2567
|
-
song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ (0,
|
|
2568
|
-
/* @__PURE__ */ (0,
|
|
2775
|
+
song.artists && song.artists.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("p", { className: "px-6 pt-2.5 text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_genre_tree26.ArtistNames, { artists: song.artists, renderArtistLink }) }),
|
|
2776
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_ui17.FieldList, { items: fields }),
|
|
2777
|
+
song.essentialGenres && song.essentialGenres.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(import_ui17.PanelSection, { title: "Essential in", children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("ul", { children: song.essentialGenres.map((genre) => /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("li", { className: "py-0.5", children: genre.name }, genre.uuid)) }) }),
|
|
2778
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)(SongHistory, { uuid: song.uuid, getBackendBaseUrl })
|
|
2569
2779
|
] });
|
|
2570
2780
|
}
|
|
2571
2781
|
|
|
2572
2782
|
// src/genre-tree/song/useSongPanel.tsx
|
|
2573
|
-
var
|
|
2783
|
+
var import_jsx_runtime44 = require("react/jsx-runtime");
|
|
2574
2784
|
function useSongPanel({
|
|
2575
2785
|
uuid,
|
|
2576
2786
|
scope,
|
|
@@ -2583,7 +2793,7 @@ function useSongPanel({
|
|
|
2583
2793
|
onExpandedChange,
|
|
2584
2794
|
onClose
|
|
2585
2795
|
}) {
|
|
2586
|
-
const result = (0,
|
|
2796
|
+
const result = (0, import_genre_tree27.useSong)(uuid, getBackendBaseUrl);
|
|
2587
2797
|
const panel = { expanded, onExpandedChange, onClose };
|
|
2588
2798
|
switch (result.status) {
|
|
2589
2799
|
case "idle":
|
|
@@ -2592,24 +2802,24 @@ function useSongPanel({
|
|
|
2592
2802
|
return {
|
|
2593
2803
|
...panel,
|
|
2594
2804
|
title: "Song",
|
|
2595
|
-
content: /* @__PURE__ */ (0,
|
|
2596
|
-
/* @__PURE__ */ (0,
|
|
2597
|
-
/* @__PURE__ */ (0,
|
|
2598
|
-
/* @__PURE__ */ (0,
|
|
2599
|
-
/* @__PURE__ */ (0,
|
|
2805
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { "aria-busy": "true", className: "flex flex-col gap-2.5 px-6 py-2.5", children: [
|
|
2806
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_ui18.Skeleton, { className: "h-3 w-16" }),
|
|
2807
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_ui18.Skeleton, { className: "h-4 w-32" }),
|
|
2808
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_ui18.Skeleton, { className: "h-3 w-16" }),
|
|
2809
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_ui18.Skeleton, { className: "h-4 w-24" })
|
|
2600
2810
|
] })
|
|
2601
2811
|
};
|
|
2602
2812
|
case "not-found":
|
|
2603
2813
|
return {
|
|
2604
2814
|
...panel,
|
|
2605
2815
|
title: "Song not found",
|
|
2606
|
-
content: /* @__PURE__ */ (0,
|
|
2816
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("p", { className: "px-6 py-2.5 text-[var(--color-text-muted)]", children: "This song does not exist." })
|
|
2607
2817
|
};
|
|
2608
2818
|
case "error":
|
|
2609
2819
|
return {
|
|
2610
2820
|
...panel,
|
|
2611
2821
|
title: "Song",
|
|
2612
|
-
content: /* @__PURE__ */ (0,
|
|
2822
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("p", { className: "px-6 py-2.5 text-red-600", children: "Could not load this song." })
|
|
2613
2823
|
};
|
|
2614
2824
|
case "success": {
|
|
2615
2825
|
const { song } = result;
|
|
@@ -2617,7 +2827,7 @@ function useSongPanel({
|
|
|
2617
2827
|
return {
|
|
2618
2828
|
...panel,
|
|
2619
2829
|
title: song.title,
|
|
2620
|
-
content: /* @__PURE__ */ (0,
|
|
2830
|
+
content: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
2621
2831
|
SongPanelContent,
|
|
2622
2832
|
{
|
|
2623
2833
|
song,
|
|
@@ -2636,29 +2846,29 @@ function useSongPanel({
|
|
|
2636
2846
|
|
|
2637
2847
|
// src/genre-tree/SizeMetricToggle.tsx
|
|
2638
2848
|
var import_md2 = require("react-icons/md");
|
|
2639
|
-
var
|
|
2640
|
-
var
|
|
2849
|
+
var import_ui19 = require("@behindthemusictree/ui");
|
|
2850
|
+
var import_jsx_runtime45 = require("react/jsx-runtime");
|
|
2641
2851
|
function SizeMetricToggle({
|
|
2642
2852
|
value,
|
|
2643
2853
|
onChange
|
|
2644
2854
|
}) {
|
|
2645
|
-
return /* @__PURE__ */ (0,
|
|
2646
|
-
|
|
2855
|
+
return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
|
|
2856
|
+
import_ui19.SegmentedControl,
|
|
2647
2857
|
{
|
|
2648
2858
|
variant: "floating",
|
|
2649
2859
|
"aria-label": "Size nodes by",
|
|
2650
2860
|
value,
|
|
2651
2861
|
onChange,
|
|
2652
2862
|
options: [
|
|
2653
|
-
{ value: "songs", label: /* @__PURE__ */ (0,
|
|
2654
|
-
{ value: "views", label: /* @__PURE__ */ (0,
|
|
2863
|
+
{ value: "songs", label: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_md2.MdMusicNote, { size: 18, "aria-hidden": true }), "aria-label": "Size by songs", title: "Node size by song count" },
|
|
2864
|
+
{ value: "views", label: /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_md2.MdVisibility, { size: 18, "aria-hidden": true }), "aria-label": "Size by views", title: "Node size by song views" }
|
|
2655
2865
|
]
|
|
2656
2866
|
}
|
|
2657
2867
|
);
|
|
2658
2868
|
}
|
|
2659
2869
|
|
|
2660
2870
|
// src/genre-tree/GenreTreeView.tsx
|
|
2661
|
-
var
|
|
2871
|
+
var import_jsx_runtime46 = require("react/jsx-runtime");
|
|
2662
2872
|
var NO_POP_ROOT_TITLE = "This genre tree has no 'Mainstream Pop' root yet";
|
|
2663
2873
|
var REGIONS_CLASS = "[--gtv-center-left:var(--layout-center-left)] [--gtv-center-right:var(--layout-center-right)] [--gtv-center-top:var(--layout-band-top)]";
|
|
2664
2874
|
var noop = () => {
|
|
@@ -2671,8 +2881,8 @@ function GenreTreeView({
|
|
|
2671
2881
|
getBackendBaseUrl,
|
|
2672
2882
|
additionalActions,
|
|
2673
2883
|
viewMode: controlledViewMode,
|
|
2674
|
-
|
|
2675
|
-
|
|
2884
|
+
selectedGenreUuid: controlledSelectedGenreUuid,
|
|
2885
|
+
onSelectedGenreUuidChange,
|
|
2676
2886
|
defaultViewport,
|
|
2677
2887
|
viewportRestore,
|
|
2678
2888
|
onViewportChange,
|
|
@@ -2693,6 +2903,7 @@ function GenreTreeView({
|
|
|
2693
2903
|
onArtistExpandedChange,
|
|
2694
2904
|
getShareUrl,
|
|
2695
2905
|
extraSearchGroups = [],
|
|
2906
|
+
SearchGroups,
|
|
2696
2907
|
onSearchQueryChange,
|
|
2697
2908
|
onSearchFocus,
|
|
2698
2909
|
artistSearch = true,
|
|
@@ -2719,21 +2930,21 @@ function GenreTreeView({
|
|
|
2719
2930
|
placement = "default",
|
|
2720
2931
|
...focus
|
|
2721
2932
|
}) {
|
|
2722
|
-
const getShareUrlRef = (0,
|
|
2933
|
+
const getShareUrlRef = (0, import_react18.useRef)(getShareUrl);
|
|
2723
2934
|
getShareUrlRef.current = getShareUrl;
|
|
2724
|
-
const [internalArtistUuid, setInternalArtistUuid] = (0,
|
|
2935
|
+
const [internalArtistUuid, setInternalArtistUuid] = (0, import_react18.useState)(null);
|
|
2725
2936
|
const isArtistControlled = controlledArtistUuid !== void 0;
|
|
2726
2937
|
const artistUuid = isArtistControlled ? controlledArtistUuid : internalArtistUuid;
|
|
2727
|
-
const [internalSongUuid, setInternalSongUuid] = (0,
|
|
2938
|
+
const [internalSongUuid, setInternalSongUuid] = (0, import_react18.useState)(null);
|
|
2728
2939
|
const isSongControlled = controlledSongUuid !== void 0;
|
|
2729
2940
|
const songUuid = isSongControlled ? controlledSongUuid : internalSongUuid;
|
|
2730
2941
|
const changeSong = (uuid) => {
|
|
2731
2942
|
if (!isSongControlled) setInternalSongUuid(uuid);
|
|
2732
2943
|
onSongUuidChange?.(uuid);
|
|
2733
2944
|
};
|
|
2734
|
-
const [internalSizeMetric, setInternalSizeMetric] = (0,
|
|
2945
|
+
const [internalSizeMetric, setInternalSizeMetric] = (0, import_react18.useState)("songs");
|
|
2735
2946
|
const sizeMetric = controlledSizeMetric ?? internalSizeMetric;
|
|
2736
|
-
const deferredSizeMetric = (0,
|
|
2947
|
+
const deferredSizeMetric = (0, import_react18.useDeferredValue)(sizeMetric);
|
|
2737
2948
|
const changeSizeMetric = (metric) => {
|
|
2738
2949
|
if (controlledSizeMetric === void 0) setInternalSizeMetric(metric);
|
|
2739
2950
|
onSizeMetricChange?.(metric);
|
|
@@ -2748,6 +2959,8 @@ function GenreTreeView({
|
|
|
2748
2959
|
genrePlaylistByNodeId,
|
|
2749
2960
|
visibleGenrePlaylists,
|
|
2750
2961
|
groupedGenrePlaylistsByRoot,
|
|
2962
|
+
selectedGenreUuid,
|
|
2963
|
+
selectGenre,
|
|
2751
2964
|
selectedNodeId,
|
|
2752
2965
|
selectedGenrePlaylist,
|
|
2753
2966
|
selectNode,
|
|
@@ -2760,17 +2973,17 @@ function GenreTreeView({
|
|
|
2760
2973
|
allowWheelRotation: internalAllowWheelRotation,
|
|
2761
2974
|
toggleWheelRotation,
|
|
2762
2975
|
onNodeHover: handleNodeHover
|
|
2763
|
-
} = (0,
|
|
2976
|
+
} = (0, import_genre_tree28.useGenreTreeViewState)({
|
|
2764
2977
|
scope,
|
|
2765
2978
|
getBackendBaseUrl,
|
|
2766
2979
|
viewMode: controlledViewMode,
|
|
2767
|
-
|
|
2768
|
-
|
|
2980
|
+
selectedGenreUuid: controlledSelectedGenreUuid,
|
|
2981
|
+
onSelectedGenreUuidChange,
|
|
2769
2982
|
criteriaOverviewSchema,
|
|
2770
2983
|
artistUuid
|
|
2771
2984
|
});
|
|
2772
2985
|
const restoreRevision = viewportRestore?.revision;
|
|
2773
|
-
const [stackedRestoreOwner, setStackedRestoreOwner] = (0,
|
|
2986
|
+
const [stackedRestoreOwner, setStackedRestoreOwner] = (0, import_react18.useState)({ revision: restoreRevision, rootUuid: selectedGenrePlaylist?.root.uuid });
|
|
2774
2987
|
let restoreRootUuid = stackedRestoreOwner.rootUuid;
|
|
2775
2988
|
if (stackedRestoreOwner.revision !== restoreRevision) {
|
|
2776
2989
|
restoreRootUuid = selectedGenrePlaylist?.root.uuid;
|
|
@@ -2779,36 +2992,36 @@ function GenreTreeView({
|
|
|
2779
2992
|
restoreRootUuid = selectedGenrePlaylist.root.uuid;
|
|
2780
2993
|
setStackedRestoreOwner({ revision: restoreRevision, rootUuid: restoreRootUuid });
|
|
2781
2994
|
}
|
|
2782
|
-
const isGenreUpdating = (0, import_react_query3.useIsMutating)({ mutationKey:
|
|
2783
|
-
const { mutate: updateGenre } = (0,
|
|
2995
|
+
const isGenreUpdating = (0, import_react_query3.useIsMutating)({ mutationKey: import_genre_tree28.genreUpdateMutationKey }) > 0;
|
|
2996
|
+
const { mutate: updateGenre } = (0, import_genre_tree28.useUpdateGenre)(scope, getBackendBaseUrl);
|
|
2784
2997
|
const busy = deferredSizeMetric !== sizeMetric || isTreeFetching || isGenreUpdating;
|
|
2785
2998
|
const isArtistFiltered = artistUuid !== null;
|
|
2786
2999
|
const canSizeByViews = !isArtistFiltered && hasViewsCount(visibleGenrePlaylists);
|
|
2787
|
-
const artistResult = (0,
|
|
3000
|
+
const artistResult = (0, import_genre_tree28.useArtist)(artistUuid, artistSearchBaseUrl ?? getBackendBaseUrl);
|
|
2788
3001
|
const filterArtistName = artistResult.status === "success" ? artistResult.artist.name : null;
|
|
2789
|
-
const filterArtist = (0,
|
|
3002
|
+
const filterArtist = (0, import_react18.useMemo)(
|
|
2790
3003
|
() => artistUuid !== null && filterArtistName !== null ? { uuid: artistUuid, name: filterArtistName } : null,
|
|
2791
3004
|
[artistUuid, filterArtistName]
|
|
2792
3005
|
);
|
|
2793
3006
|
const hasSelectedGenreNode = selectedGenrePlaylist !== void 0 && visibleGenrePlaylists.some(({ uuid }) => uuid === selectedGenrePlaylist.uuid);
|
|
2794
3007
|
const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: visibleGenrePlaylists, artist: filterArtist });
|
|
2795
3008
|
const openArtist = (uuid) => {
|
|
2796
|
-
if (
|
|
3009
|
+
if (selectedGenreUuid !== null) clearSelection();
|
|
2797
3010
|
if (songUuid !== null) changeSong(null);
|
|
2798
3011
|
changeArtist(uuid);
|
|
2799
3012
|
};
|
|
2800
3013
|
const openSong = (uuid) => {
|
|
2801
|
-
if (
|
|
3014
|
+
if (selectedGenreUuid !== null) clearSelection();
|
|
2802
3015
|
changeSong(uuid);
|
|
2803
3016
|
};
|
|
2804
|
-
const openSongRef = (0,
|
|
3017
|
+
const openSongRef = (0, import_react18.useRef)(openSong);
|
|
2805
3018
|
openSongRef.current = openSong;
|
|
2806
|
-
const resolvedRenderSongLink = (0,
|
|
2807
|
-
(song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ (0,
|
|
3019
|
+
const resolvedRenderSongLink = (0, import_react18.useCallback)(
|
|
3020
|
+
(song) => renderSongLink ? renderSongLink(song) : /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("button", { type: "button", onClick: () => openSongRef.current(song.uuid), className: "hover:underline", children: song.title }),
|
|
2808
3021
|
[renderSongLink]
|
|
2809
3022
|
);
|
|
2810
|
-
const [expandedSongUuid, setExpandedSongUuid] = (0,
|
|
2811
|
-
const [expandedArtistUuid, setExpandedArtistUuid] = (0,
|
|
3023
|
+
const [expandedSongUuid, setExpandedSongUuid] = (0, import_react18.useState)(null);
|
|
3024
|
+
const [expandedArtistUuid, setExpandedArtistUuid] = (0, import_react18.useState)(null);
|
|
2812
3025
|
const artistBaseUrl = artistSearchBaseUrl ?? getBackendBaseUrl;
|
|
2813
3026
|
const songBaseUrl = getBackendBaseUrl;
|
|
2814
3027
|
const artistPanel = useArtistPanel({
|
|
@@ -2816,7 +3029,7 @@ function GenreTreeView({
|
|
|
2816
3029
|
scope,
|
|
2817
3030
|
getBackendBaseUrl: artistBaseUrl,
|
|
2818
3031
|
getShareUrl: getShareUrlRef.current,
|
|
2819
|
-
renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0,
|
|
3032
|
+
renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
|
|
2820
3033
|
expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
|
|
2821
3034
|
onExpandedChange: (on) => {
|
|
2822
3035
|
if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
|
|
@@ -2825,7 +3038,7 @@ function GenreTreeView({
|
|
|
2825
3038
|
renderSongLink: resolvedRenderSongLink,
|
|
2826
3039
|
onClose: () => changeArtist(null)
|
|
2827
3040
|
});
|
|
2828
|
-
const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0,
|
|
3041
|
+
const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
|
|
2829
3042
|
const getGenrePath = (genre) => {
|
|
2830
3043
|
const path = [];
|
|
2831
3044
|
let current = visibleGenrePlaylists.find((playlist) => playlist.criteria?.uuid === genre.uuid);
|
|
@@ -2843,9 +3056,9 @@ function GenreTreeView({
|
|
|
2843
3056
|
getShareUrl: getShareUrlRef.current,
|
|
2844
3057
|
renderArtistLink: resolvedRenderArtistLink,
|
|
2845
3058
|
getGenrePath,
|
|
2846
|
-
onSelectGenre: (
|
|
3059
|
+
onSelectGenre: (genrePlaylist2) => {
|
|
2847
3060
|
changeSong(null);
|
|
2848
|
-
selectGenrePlaylist(
|
|
3061
|
+
selectGenrePlaylist(genrePlaylist2);
|
|
2849
3062
|
},
|
|
2850
3063
|
expanded: controlledSongExpanded ?? (songUuid !== null && expandedSongUuid === songUuid),
|
|
2851
3064
|
onExpandedChange: (on) => {
|
|
@@ -2854,11 +3067,11 @@ function GenreTreeView({
|
|
|
2854
3067
|
},
|
|
2855
3068
|
onClose: () => changeSong(null)
|
|
2856
3069
|
});
|
|
2857
|
-
const builtInSongPanel = overlayPanel ||
|
|
2858
|
-
const builtInArtistPanel = overlayPanel ||
|
|
3070
|
+
const builtInSongPanel = overlayPanel || selectedGenreUuid !== null ? void 0 : songPanel;
|
|
3071
|
+
const builtInArtistPanel = overlayPanel || selectedGenreUuid !== null || builtInSongPanel ? void 0 : artistPanel;
|
|
2859
3072
|
const builtInPanel = builtInSongPanel ?? builtInArtistPanel;
|
|
2860
|
-
const [uncontrolledCollapsed, setUncontrolledCollapsed] = (0,
|
|
2861
|
-
const [uncontrolledExpanded, setUncontrolledExpanded] = (0,
|
|
3073
|
+
const [uncontrolledCollapsed, setUncontrolledCollapsed] = (0, import_react18.useState)(false);
|
|
3074
|
+
const [uncontrolledExpanded, setUncontrolledExpanded] = (0, import_react18.useState)(false);
|
|
2862
3075
|
const isCollapsed = collapsed ?? uncontrolledCollapsed;
|
|
2863
3076
|
const isExpanded = expanded ?? uncontrolledExpanded;
|
|
2864
3077
|
const changeCollapsed = (next) => {
|
|
@@ -2869,177 +3082,114 @@ function GenreTreeView({
|
|
|
2869
3082
|
setUncontrolledExpanded(next);
|
|
2870
3083
|
onExpandedChange?.(next);
|
|
2871
3084
|
};
|
|
2872
|
-
(0,
|
|
3085
|
+
(0, import_react18.useEffect)(() => {
|
|
2873
3086
|
setUncontrolledCollapsed(false);
|
|
2874
|
-
}, [
|
|
2875
|
-
const [isWheelRevealed, setIsWheelRevealed] = (0,
|
|
3087
|
+
}, [selectedGenreUuid, artistUuid, songUuid]);
|
|
3088
|
+
const [isWheelRevealed, setIsWheelRevealed] = (0, import_react18.useState)(false);
|
|
2876
3089
|
const usesHandoff = viewMode === "wheel" || viewMode === "pop-core";
|
|
2877
3090
|
const isTreeReady = !isLoading && (!usesHandoff || isWheelRevealed);
|
|
2878
|
-
const [resolveStyle, setResolveStyle] = (0,
|
|
2879
|
-
const handleNodeStyleResolverChange = (0,
|
|
3091
|
+
const [resolveStyle, setResolveStyle] = (0, import_react18.useState)(null);
|
|
3092
|
+
const handleNodeStyleResolverChange = (0, import_react18.useCallback)(
|
|
2880
3093
|
(resolver) => setResolveStyle(() => resolver),
|
|
2881
3094
|
[]
|
|
2882
3095
|
);
|
|
2883
|
-
const panelRef = (0,
|
|
2884
|
-
const obscuredInset = (0,
|
|
3096
|
+
const panelRef = (0, import_react18.useRef)(null);
|
|
3097
|
+
const obscuredInset = (0, import_ui20.useObscuredInset)(panelRef);
|
|
2885
3098
|
const hostPanel = { obscuredInset, onNodeStyleResolverChange: handleNodeStyleResolverChange };
|
|
2886
3099
|
const nodeMetric = isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric;
|
|
2887
|
-
const treeNodes = (0,
|
|
2888
|
-
const relations = (0,
|
|
3100
|
+
const treeNodes = (0, import_react18.useMemo)(() => toTreeNodes(visibleGenrePlaylists, nodeMetric), [visibleGenrePlaylists, nodeMetric]);
|
|
3101
|
+
const relations = (0, import_genre_tree_view5.useGenreNodeRelations)(
|
|
2889
3102
|
treeNodes,
|
|
2890
3103
|
!overlayPanel && hasSelectedGenreNode ? selectedNodeId : null,
|
|
2891
3104
|
resolveStyle
|
|
2892
3105
|
);
|
|
2893
|
-
const [selectionHistory, setSelectionHistory] = (0,
|
|
2894
|
-
|
|
2895
|
-
previous:
|
|
2896
|
-
});
|
|
2897
|
-
if (selectionHistory.current !== selectedNodeId) {
|
|
2898
|
-
setSelectionHistory({
|
|
2899
|
-
current: selectedNodeId,
|
|
2900
|
-
previous: selectionHistory.current ?? selectionHistory.previous
|
|
2901
|
-
});
|
|
3106
|
+
const [selectionHistory, setSelectionHistory] = (0, import_react18.useState)({ current: selectedGenreUuid, previous: null });
|
|
3107
|
+
if (selectionHistory.current !== selectedGenreUuid) {
|
|
3108
|
+
setSelectionHistory({ current: selectedGenreUuid, previous: selectionHistory.current ?? selectionHistory.previous });
|
|
2902
3109
|
}
|
|
2903
|
-
const
|
|
2904
|
-
const handlePrevious = previousPlaylist && visibleGenrePlaylists.includes(previousPlaylist) ? () => handleGenreSearchSelect(previousPlaylist) : void 0;
|
|
3110
|
+
const handlePrevious = selectionHistory.previous ? () => selectGenre(selectionHistory.previous) : void 0;
|
|
2905
3111
|
const { showPopup, hidePopup } = (0, import_popup6.usePopup)();
|
|
2906
|
-
const
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
(artist) => {
|
|
2911
|
-
openArtist(artist.uuid);
|
|
2912
|
-
onArtistSelect?.(artist);
|
|
2913
|
-
},
|
|
2914
|
-
Boolean(artistSearch)
|
|
2915
|
-
);
|
|
2916
|
-
const songGroup = useSongSearchGroup(
|
|
2917
|
-
songBaseUrl,
|
|
2918
|
-
searchText,
|
|
2919
|
-
(song) => {
|
|
2920
|
-
openSong(song.uuid);
|
|
2921
|
-
onSongSelect?.(song);
|
|
2922
|
-
},
|
|
2923
|
-
Boolean(songSearch)
|
|
2924
|
-
);
|
|
2925
|
-
const extraGroups = [...extraSearchGroups, artistGroup, songGroup].filter(
|
|
2926
|
-
(group) => group !== void 0
|
|
2927
|
-
);
|
|
3112
|
+
const reopenPanel = () => {
|
|
3113
|
+
setUncontrolledCollapsed(false);
|
|
3114
|
+
onCollapsedChange?.(false);
|
|
3115
|
+
};
|
|
2928
3116
|
const handleSelectedNodeChange = (node) => {
|
|
3117
|
+
reopenPanel();
|
|
2929
3118
|
if (node !== null && songUuid !== null) changeSong(null);
|
|
2930
3119
|
selectNode(node);
|
|
2931
3120
|
};
|
|
2932
3121
|
const handleGenreSearchClear = clearSelection;
|
|
2933
|
-
const handleGenreSearchSelect = (
|
|
3122
|
+
const handleGenreSearchSelect = (genrePlaylist2) => {
|
|
3123
|
+
reopenPanel();
|
|
2934
3124
|
if (songUuid !== null) changeSong(null);
|
|
2935
|
-
selectGenrePlaylist(
|
|
3125
|
+
selectGenrePlaylist(genrePlaylist2);
|
|
2936
3126
|
};
|
|
2937
3127
|
const selectedUuid = selectedGenrePlaylist?.uuid;
|
|
2938
|
-
const selectedChildren = selectedUuid ? visibleGenrePlaylists.filter((
|
|
3128
|
+
const selectedChildren = selectedUuid ? visibleGenrePlaylists.filter((genrePlaylist2) => genrePlaylist2.parent?.uuid === selectedUuid) : [];
|
|
2939
3129
|
const canZoomIn = viewMode === "pop-core" && focus.onFocusGenreChange !== void 0 && selectedUuid !== void 0 && focus.focusGenreUuid !== selectedUuid && selectedChildren.length > 0 && selectedChildren.filter((child) => child.criteria?.side === "pop").length <= 1;
|
|
2940
3130
|
const handleGenreSearchZoomIn = () => {
|
|
2941
3131
|
if (selectedUuid) focus.onFocusGenreChange?.(selectedUuid, "zoom-in");
|
|
2942
3132
|
};
|
|
2943
|
-
const zoomInRef = (0,
|
|
3133
|
+
const zoomInRef = (0, import_react18.useRef)(void 0);
|
|
2944
3134
|
zoomInRef.current = canZoomIn ? handleGenreSearchZoomIn : void 0;
|
|
2945
|
-
const
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
const
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
kind: "genre",
|
|
2975
|
-
genrePlaylistUuid: genrePlaylist.uuid,
|
|
2976
|
-
name: criteria.name,
|
|
2977
|
-
scope,
|
|
2978
|
-
artistUuid
|
|
2979
|
-
};
|
|
2980
|
-
const shareUrl = getShareUrlRef.current?.(shareTarget) ?? void 0;
|
|
2981
|
-
const popSibling = canToggleSide && side === "core" ? visibleGenrePlaylists.find(
|
|
2982
|
-
(sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist.uuid && sibling.criteria?.side === "pop"
|
|
2983
|
-
) : void 0;
|
|
2984
|
-
return {
|
|
2985
|
-
primary: /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(import_jsx_runtime44.Fragment, { children: [
|
|
2986
|
-
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
2987
|
-
GenreInfoPanelBody,
|
|
3135
|
+
const genrePlaylist = selectedGenrePlaylist;
|
|
3136
|
+
const criteria = genrePlaylist?.criteria;
|
|
3137
|
+
const parentPlaylist = visibleGenrePlaylists.find(({ uuid }) => uuid === genrePlaylist?.parent?.uuid);
|
|
3138
|
+
const canToggleSide = !readOnly && criteria !== void 0 && criteria !== null && parentPlaylist !== void 0 && parentPlaylist.parent === null;
|
|
3139
|
+
const side = criteria?.side ?? "core";
|
|
3140
|
+
const popSibling = canToggleSide && side === "core" ? visibleGenrePlaylists.find((sibling) => sibling.parent?.uuid === parentPlaylist.uuid && sibling.uuid !== genrePlaylist?.uuid && sibling.criteria?.side === "pop") : void 0;
|
|
3141
|
+
const shareTarget = selectedGenreUuid ? { kind: "genre", genreUuid: selectedGenreUuid, name: criteria?.name ?? "Genre", scope, artistUuid } : void 0;
|
|
3142
|
+
const shareUrl = shareTarget ? getShareUrlRef.current?.(shareTarget) ?? void 0 : void 0;
|
|
3143
|
+
const genreContent = useGenreDetailContent({
|
|
3144
|
+
genreUuid: selectedGenreUuid,
|
|
3145
|
+
scope,
|
|
3146
|
+
getBackendBaseUrl,
|
|
3147
|
+
onSelectGenre: (uuid) => {
|
|
3148
|
+
reopenPanel();
|
|
3149
|
+
selectGenre(uuid);
|
|
3150
|
+
},
|
|
3151
|
+
expanded: isExpanded,
|
|
3152
|
+
playback,
|
|
3153
|
+
genrePlaylist: selectedGenrePlaylist,
|
|
3154
|
+
band: {
|
|
3155
|
+
...relations ? { node: relations.node } : {},
|
|
3156
|
+
shareTarget: shareUrl ? shareTarget : void 0,
|
|
3157
|
+
shareUrl,
|
|
3158
|
+
onZoomIn: zoomInRef.current,
|
|
3159
|
+
onAddChild: readOnly || !criteria ? void 0 : () => handleGenreCreationAction(criteria),
|
|
3160
|
+
onRename: readOnly || !criteria ? void 0 : () => handleGenreRenameAction(criteria),
|
|
3161
|
+
onChangeParent: readOnly || !genrePlaylist || !criteria ? void 0 : () => showPopup(
|
|
3162
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
3163
|
+
ChangeParentPopup,
|
|
2988
3164
|
{
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
ancestors
|
|
2992
|
-
] }),
|
|
2993
|
-
genreUuid,
|
|
2994
|
-
genrePlaylist,
|
|
2995
|
-
catalogue: genrePlaylist.catalogue,
|
|
2996
|
-
wikidataId: criteria.wikidataId,
|
|
3165
|
+
genrePlaylist: { ...genrePlaylist, criteria },
|
|
3166
|
+
genrePlaylists: visibleGenrePlaylists,
|
|
2997
3167
|
scope,
|
|
2998
3168
|
getBackendBaseUrl,
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
node,
|
|
3002
|
-
shareTarget: shareUrl ? shareTarget : void 0,
|
|
3003
|
-
shareUrl,
|
|
3004
|
-
onZoomIn: zoomInRef.current,
|
|
3005
|
-
onAddChild: readOnly ? void 0 : () => handleGenreCreationAction(criteria),
|
|
3006
|
-
onRename: readOnly ? void 0 : () => handleGenreRenameAction(criteria),
|
|
3007
|
-
onChangeParent: readOnly ? void 0 : () => showPopup(
|
|
3008
|
-
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
3009
|
-
ChangeParentPopup,
|
|
3010
|
-
{
|
|
3011
|
-
genrePlaylist: { ...genrePlaylist, criteria },
|
|
3012
|
-
genrePlaylists: visibleGenrePlaylists,
|
|
3013
|
-
scope,
|
|
3014
|
-
getBackendBaseUrl,
|
|
3015
|
-
onClose: hidePopup,
|
|
3016
|
-
onParentChanged
|
|
3017
|
-
}
|
|
3018
|
-
)
|
|
3019
|
-
),
|
|
3020
|
-
side,
|
|
3021
|
-
onToggleSide: canToggleSide ? () => updateGenre({ uuid: genreUuid, data: { side: side === "pop" ? "core" : "pop" } }) : void 0,
|
|
3022
|
-
toggleSideDisabledReason: popSibling ? `${popSibling.name} is already the pop side of ${parentPlaylist?.name}` : void 0,
|
|
3023
|
-
additionalActions
|
|
3024
|
-
},
|
|
3025
|
-
extras: {
|
|
3026
|
-
criteriaOverviewSchema,
|
|
3027
|
-
renderGenreDetailExtras,
|
|
3028
|
-
renderArtistLink,
|
|
3029
|
-
renderSongLink: resolvedRenderSongLink,
|
|
3030
|
-
artist: filterArtist,
|
|
3031
|
-
renderSummary,
|
|
3032
|
-
renderEssentialTracks,
|
|
3033
|
-
renderSongLeading,
|
|
3034
|
-
renderSongActions,
|
|
3035
|
-
renderTracksToolbar
|
|
3036
|
-
}
|
|
3169
|
+
onClose: hidePopup,
|
|
3170
|
+
onParentChanged
|
|
3037
3171
|
}
|
|
3038
|
-
)
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3172
|
+
)
|
|
3173
|
+
),
|
|
3174
|
+
side,
|
|
3175
|
+
onToggleSide: canToggleSide && criteria ? () => updateGenre({ uuid: criteria.uuid, data: { side: side === "pop" ? "core" : "pop" } }) : void 0,
|
|
3176
|
+
toggleSideDisabledReason: popSibling ? `${popSibling.name} is already the pop side of ${parentPlaylist?.name}` : void 0,
|
|
3177
|
+
additionalActions
|
|
3178
|
+
},
|
|
3179
|
+
extras: {
|
|
3180
|
+
criteriaOverviewSchema,
|
|
3181
|
+
renderGenreDetailExtras,
|
|
3182
|
+
renderArtistLink,
|
|
3183
|
+
renderSongLink: resolvedRenderSongLink,
|
|
3184
|
+
artist: filterArtist,
|
|
3185
|
+
renderSummary,
|
|
3186
|
+
renderEssentialTracks,
|
|
3187
|
+
renderSongLeading,
|
|
3188
|
+
renderSongActions,
|
|
3189
|
+
renderTracksToolbar
|
|
3190
|
+
},
|
|
3191
|
+
trailing: relations ? renderDangerZone?.(relations.node) : void 0
|
|
3192
|
+
});
|
|
3043
3193
|
const isRotationControlled = controlledAllowWheelRotation !== void 0;
|
|
3044
3194
|
const allowWheelRotation = controlledAllowWheelRotation ?? internalAllowWheelRotation;
|
|
3045
3195
|
let activePanel = null;
|
|
@@ -3053,16 +3203,16 @@ function GenreTreeView({
|
|
|
3053
3203
|
onClose: overlayPanel.onClose,
|
|
3054
3204
|
compact: false
|
|
3055
3205
|
};
|
|
3056
|
-
} else if (
|
|
3057
|
-
const { primary } = renderGenreBody(relations);
|
|
3206
|
+
} else if (selectedGenreUuid) {
|
|
3058
3207
|
activePanel = {
|
|
3059
|
-
title:
|
|
3060
|
-
content:
|
|
3208
|
+
title: genreContent.title,
|
|
3209
|
+
content: genreContent.content,
|
|
3210
|
+
aside: genreContent.aside,
|
|
3061
3211
|
expanded: isExpanded,
|
|
3062
3212
|
onExpandedChange: changeExpanded,
|
|
3063
3213
|
onClose: clearSelection,
|
|
3064
|
-
compact:
|
|
3065
|
-
headerAccent: { background: relations.fill, color: relations.textColor }
|
|
3214
|
+
compact: false,
|
|
3215
|
+
headerAccent: relations ? { background: relations.fill, color: relations.textColor } : genreContent.headerAccent
|
|
3066
3216
|
};
|
|
3067
3217
|
} else if (builtInPanel) {
|
|
3068
3218
|
activePanel = {
|
|
@@ -3075,36 +3225,36 @@ function GenreTreeView({
|
|
|
3075
3225
|
compact: true
|
|
3076
3226
|
};
|
|
3077
3227
|
}
|
|
3078
|
-
const isPanelShown =
|
|
3228
|
+
const isPanelShown = activePanel !== null && (selectedGenreUuid !== null || isTreeReady);
|
|
3079
3229
|
const detailTab = placement === "regions" && activePanel?.onExpandedChange ? {
|
|
3080
3230
|
edge: activePanel.expanded ? { label: "Shrink panel", onClick: () => activePanel.onExpandedChange?.(false) } : isCollapsed ? null : { label: "Hide panel", onClick: () => changeCollapsed(true) },
|
|
3081
3231
|
center: activePanel.expanded ? null : isCollapsed ? { label: "Show panel", onClick: () => changeCollapsed(false) } : activePanel.onExpandedChange ? { label: "Extend panel", onClick: () => activePanel.onExpandedChange?.(true) } : null
|
|
3082
3232
|
} : void 0;
|
|
3083
|
-
const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ (0,
|
|
3084
|
-
|
|
3233
|
+
const expandButton = placement === "default" && activePanel?.onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
3234
|
+
import_ui20.IconButton,
|
|
3085
3235
|
{
|
|
3086
3236
|
"aria-label": activePanel.expanded ? "Shrink panel" : "Extend panel",
|
|
3087
3237
|
style: activePanel.headerAccent && { color: "inherit" },
|
|
3088
3238
|
onClick: () => activePanel.onExpandedChange?.(!activePanel.expanded),
|
|
3089
|
-
children: activePanel.expanded ? /* @__PURE__ */ (0,
|
|
3239
|
+
children: activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_lucide_react17.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_lucide_react17.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
3090
3240
|
}
|
|
3091
3241
|
) : null;
|
|
3092
3242
|
const renderArtistFilter = () => {
|
|
3093
3243
|
if (isLoading || !filterArtist) return null;
|
|
3094
|
-
return /* @__PURE__ */ (0,
|
|
3095
|
-
/* @__PURE__ */ (0,
|
|
3244
|
+
return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
|
|
3245
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
3096
3246
|
"button",
|
|
3097
3247
|
{
|
|
3098
3248
|
type: "button",
|
|
3099
3249
|
className: "py-1 pl-3 pr-2 [@media(pointer:coarse)]:min-h-[var(--layout-touch-target)] hover:underline",
|
|
3100
3250
|
"aria-label": `Show artist ${filterArtist.name}`,
|
|
3101
3251
|
onClick: () => {
|
|
3102
|
-
if (
|
|
3252
|
+
if (selectedGenreUuid !== null) clearSelection();
|
|
3103
3253
|
},
|
|
3104
3254
|
children: filterArtist.name
|
|
3105
3255
|
}
|
|
3106
3256
|
),
|
|
3107
|
-
/* @__PURE__ */ (0,
|
|
3257
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
3108
3258
|
"button",
|
|
3109
3259
|
{
|
|
3110
3260
|
type: "button",
|
|
@@ -3116,27 +3266,42 @@ function GenreTreeView({
|
|
|
3116
3266
|
)
|
|
3117
3267
|
] });
|
|
3118
3268
|
};
|
|
3119
|
-
const actions = /* @__PURE__ */ (0,
|
|
3120
|
-
!isLoading && /* @__PURE__ */ (0,
|
|
3121
|
-
|
|
3269
|
+
const actions = /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(import_jsx_runtime46.Fragment, { children: [
|
|
3270
|
+
!isLoading && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
3271
|
+
GenreTreeSearch,
|
|
3122
3272
|
{
|
|
3273
|
+
artistBaseUrl,
|
|
3274
|
+
songBaseUrl,
|
|
3275
|
+
artistSearch: Boolean(artistSearch),
|
|
3276
|
+
songSearch: Boolean(songSearch),
|
|
3277
|
+
onArtistSelect: (artist) => {
|
|
3278
|
+
openArtist(artist.uuid);
|
|
3279
|
+
onArtistSelect?.(artist);
|
|
3280
|
+
},
|
|
3281
|
+
onSongSelect: (song) => {
|
|
3282
|
+
openSong(song.uuid);
|
|
3283
|
+
onSongSelect?.(song);
|
|
3284
|
+
},
|
|
3285
|
+
SearchGroups,
|
|
3123
3286
|
genrePlaylists: visibleGenrePlaylists,
|
|
3124
3287
|
onSelect: handleGenreSearchSelect,
|
|
3125
|
-
|
|
3288
|
+
onSelectGenre: (uuid) => {
|
|
3289
|
+
reopenPanel();
|
|
3290
|
+
if (songUuid) changeSong(null);
|
|
3291
|
+
selectGenre(uuid);
|
|
3292
|
+
},
|
|
3293
|
+
selectedName: (selectedGenreUuid ? genreContent.title : null) ?? (typeof builtInPanel?.title === "string" ? builtInPanel.title : null),
|
|
3126
3294
|
onClear: builtInSongPanel ? () => changeSong(null) : builtInArtistPanel ? () => changeArtist(null) : handleGenreSearchClear,
|
|
3127
3295
|
onPrevious: handlePrevious,
|
|
3128
|
-
onQueryChange:
|
|
3129
|
-
setSearchText(text);
|
|
3130
|
-
onSearchQueryChange?.(text);
|
|
3131
|
-
},
|
|
3296
|
+
onQueryChange: onSearchQueryChange,
|
|
3132
3297
|
onFocus: onSearchFocus,
|
|
3133
|
-
extraGroups
|
|
3298
|
+
extraGroups: extraSearchGroups
|
|
3134
3299
|
}
|
|
3135
3300
|
),
|
|
3136
|
-
!isLoading && filterArtist && /* @__PURE__ */ (0,
|
|
3137
|
-
/* @__PURE__ */ (0,
|
|
3138
|
-
!isLoading && !isControlled && /* @__PURE__ */ (0,
|
|
3139
|
-
|
|
3301
|
+
!isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "hidden w-full justify-center max-md:flex", children: renderArtistFilter() }),
|
|
3302
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.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: [
|
|
3303
|
+
!isLoading && !isControlled && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
3304
|
+
import_ui20.SegmentedControl,
|
|
3140
3305
|
{
|
|
3141
3306
|
"aria-label": "Tree view mode",
|
|
3142
3307
|
value: viewMode,
|
|
@@ -3149,14 +3314,14 @@ function GenreTreeView({
|
|
|
3149
3314
|
]
|
|
3150
3315
|
}
|
|
3151
3316
|
),
|
|
3152
|
-
!isLoading && showDisplayOptions && /* @__PURE__ */ (0,
|
|
3317
|
+
!isLoading && showDisplayOptions && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
3153
3318
|
"div",
|
|
3154
3319
|
{
|
|
3155
3320
|
className: "flex items-center gap-1",
|
|
3156
3321
|
role: "group",
|
|
3157
3322
|
"aria-label": "Tree display options",
|
|
3158
|
-
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0,
|
|
3159
|
-
|
|
3323
|
+
children: !isRotationControlled && viewMode !== "stacked" && viewMode !== "outline" && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
3324
|
+
import_ui20.Button,
|
|
3160
3325
|
{
|
|
3161
3326
|
variant: allowWheelRotation ? "default" : "outline",
|
|
3162
3327
|
size: "sm",
|
|
@@ -3166,44 +3331,44 @@ function GenreTreeView({
|
|
|
3166
3331
|
)
|
|
3167
3332
|
}
|
|
3168
3333
|
),
|
|
3169
|
-
!isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0,
|
|
3170
|
-
|
|
3334
|
+
!isLoading && !readOnly && showAddRoot && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
3335
|
+
import_ui20.IconTextButton,
|
|
3171
3336
|
{
|
|
3172
|
-
icon:
|
|
3337
|
+
icon: import_lucide_react16.Plus,
|
|
3173
3338
|
text: "Add root",
|
|
3174
3339
|
onClick: () => handleGenreCreationAction(null)
|
|
3175
3340
|
}
|
|
3176
3341
|
)
|
|
3177
3342
|
] })
|
|
3178
3343
|
] });
|
|
3179
|
-
return /* @__PURE__ */ (0,
|
|
3344
|
+
return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
|
|
3180
3345
|
"div",
|
|
3181
3346
|
{
|
|
3182
3347
|
"data-left-panel": isPanelShown && !isCollapsed ? void 0 : "hidden",
|
|
3183
3348
|
className: ["genre-tree-view relative flex flex-col h-full", placement === "regions" && REGIONS_CLASS, className2].filter(Boolean).join(" "),
|
|
3184
3349
|
children: [
|
|
3185
|
-
/* @__PURE__ */ (0,
|
|
3350
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
3186
3351
|
"div",
|
|
3187
3352
|
{
|
|
3188
3353
|
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"}`,
|
|
3189
3354
|
children: actions
|
|
3190
3355
|
}
|
|
3191
3356
|
),
|
|
3192
|
-
!isLoading && filterArtist && /* @__PURE__ */ (0,
|
|
3357
|
+
!isLoading && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
3193
3358
|
"div",
|
|
3194
3359
|
{
|
|
3195
3360
|
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"}`,
|
|
3196
3361
|
children: renderArtistFilter()
|
|
3197
3362
|
}
|
|
3198
3363
|
),
|
|
3199
|
-
/* @__PURE__ */ (0,
|
|
3200
|
-
!isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0,
|
|
3201
|
-
isLoading ? /* @__PURE__ */ (0,
|
|
3364
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "content-container flex-1 min-h-0 flex flex-row gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "tree-view-container relative flex-1 min-w-0 flex flex-col h-full", children: [
|
|
3365
|
+
!isLoading && viewMode === "outline" && floatingControlsLeading && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "absolute bottom-3 right-3 z-10", children: floatingControlsLeading }),
|
|
3366
|
+
isLoading ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_genre_tree_view5.GenreTreeViewSkeleton, { viewMode }) : viewMode === "wheel" ? (
|
|
3202
3367
|
// The info panel's top edge aligns with the tree's; the search bar floats `--layout-panel-inset` below it,
|
|
3203
3368
|
// and the header inset (the bar's height plus that inset) extends the header's fill behind the bar,
|
|
3204
3369
|
// Google Maps style; the 8px bottom padding mirrors the inset above the bar, and the hidden title's height is zeroed.
|
|
3205
3370
|
// The panel's own Zoom in button is hidden: the search bar carries it.
|
|
3206
|
-
/* @__PURE__ */ (0,
|
|
3371
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_genre_tree_view5.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
3207
3372
|
GenrePlaylistTreeWheel,
|
|
3208
3373
|
{
|
|
3209
3374
|
scope,
|
|
@@ -3220,9 +3385,9 @@ function GenreTreeView({
|
|
|
3220
3385
|
allowWheelRotation,
|
|
3221
3386
|
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
3222
3387
|
busy,
|
|
3223
|
-
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0,
|
|
3388
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
3224
3389
|
floatingControlsLeading,
|
|
3225
|
-
canSizeByViews && /* @__PURE__ */ (0,
|
|
3390
|
+
canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
3226
3391
|
] }) : void 0,
|
|
3227
3392
|
defaultViewport,
|
|
3228
3393
|
viewportRestore,
|
|
@@ -3231,7 +3396,7 @@ function GenreTreeView({
|
|
|
3231
3396
|
) }) })
|
|
3232
3397
|
) : viewMode === "pop-core" ? (
|
|
3233
3398
|
// Same as the wheel: the header inset extends its fill behind the floating search bar, Google Maps style.
|
|
3234
|
-
/* @__PURE__ */ (0,
|
|
3399
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "tree-container search-overlay relative flex-1 w-full min-h-0 ", children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(GenreTreeWheelHandoff, { skeleton: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_genre_tree_view5.GenreTreeWheelSkeleton, {}), onRevealedChange: setIsWheelRevealed, children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
3235
3400
|
GenrePlaylistTreeWheelRadialPopCore,
|
|
3236
3401
|
{
|
|
3237
3402
|
scope,
|
|
@@ -3248,9 +3413,9 @@ function GenreTreeView({
|
|
|
3248
3413
|
allowWheelRotation,
|
|
3249
3414
|
sizeMetric: isArtistFiltered || !canSizeByViews ? "songs" : deferredSizeMetric,
|
|
3250
3415
|
busy,
|
|
3251
|
-
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0,
|
|
3416
|
+
floatingControlsLeading: floatingControlsLeading || canSizeByViews ? /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
3252
3417
|
floatingControlsLeading,
|
|
3253
|
-
canSizeByViews && /* @__PURE__ */ (0,
|
|
3418
|
+
canSizeByViews && /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(SizeMetricToggle, { value: sizeMetric, onChange: changeSizeMetric })
|
|
3254
3419
|
] }) : void 0,
|
|
3255
3420
|
defaultViewport,
|
|
3256
3421
|
viewportRestore,
|
|
@@ -3258,7 +3423,7 @@ function GenreTreeView({
|
|
|
3258
3423
|
...focus
|
|
3259
3424
|
}
|
|
3260
3425
|
) }) })
|
|
3261
|
-
) : viewMode === "outline" ? /* @__PURE__ */ (0,
|
|
3426
|
+
) : viewMode === "outline" ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "tree-container flex-1 min-h-0 w-full overflow-y-auto relative", children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
3262
3427
|
GenrePlaylistTreeWheelRadialPopCore,
|
|
3263
3428
|
{
|
|
3264
3429
|
outline: true,
|
|
@@ -3274,13 +3439,13 @@ function GenreTreeView({
|
|
|
3274
3439
|
selectedNodeId,
|
|
3275
3440
|
readOnly
|
|
3276
3441
|
}
|
|
3277
|
-
) }) : /* @__PURE__ */ (0,
|
|
3442
|
+
) }) : /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "tree-container relative flex flex-col gap-4 w-full pt-14 overflow-x-auto overflow-y-auto text-[var(--color-text)]", children: Object.entries(groupedGenrePlaylistsByRoot).map(
|
|
3278
3443
|
([uuid, genrePlaylistTreePerRoot]) => {
|
|
3279
|
-
return /* @__PURE__ */ (0,
|
|
3444
|
+
return /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
3280
3445
|
"div",
|
|
3281
3446
|
{
|
|
3282
3447
|
className: "tree-per-root-container relative shrink-0 h-[28rem] mt-2 mr-16 p-2 bg-[var(--color-neutral-100)] rounded-lg",
|
|
3283
|
-
children: /* @__PURE__ */ (0,
|
|
3448
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: "graph-container relative z-10 h-full", children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
3284
3449
|
GenrePlaylistTreePerRoot,
|
|
3285
3450
|
{
|
|
3286
3451
|
scope,
|
|
@@ -3306,8 +3471,8 @@ function GenreTreeView({
|
|
|
3306
3471
|
);
|
|
3307
3472
|
}
|
|
3308
3473
|
) }),
|
|
3309
|
-
/* @__PURE__ */ (0,
|
|
3310
|
-
|
|
3474
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
3475
|
+
import_ui20.DetailPanel,
|
|
3311
3476
|
{
|
|
3312
3477
|
ref: panelRef,
|
|
3313
3478
|
side: "left",
|
|
@@ -3317,13 +3482,13 @@ function GenreTreeView({
|
|
|
3317
3482
|
headerAccent: activePanel?.headerAccent,
|
|
3318
3483
|
headerActions: expandButton,
|
|
3319
3484
|
onClose: activePanel?.onClose ?? noop,
|
|
3320
|
-
collapsed: isCollapsed || !
|
|
3485
|
+
collapsed: isCollapsed || !isPanelShown,
|
|
3321
3486
|
expanded: activePanel?.expanded,
|
|
3322
3487
|
onExpandedChange: activePanel?.onExpandedChange,
|
|
3323
3488
|
aside: activePanel?.aside,
|
|
3324
3489
|
edgeTab: detailTab,
|
|
3325
|
-
headerClassName:
|
|
3326
|
-
children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ (0,
|
|
3490
|
+
headerClassName: "pt-[calc(var(--layout-search-h)+var(--layout-panel-inset)+0.5rem)]",
|
|
3491
|
+
children: activePanel?.aside && !activePanel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(import_jsx_runtime46.Fragment, { children: [
|
|
3327
3492
|
activePanel.content,
|
|
3328
3493
|
activePanel.aside
|
|
3329
3494
|
] }) : activePanel?.content
|
|
@@ -3335,145 +3500,17 @@ function GenreTreeView({
|
|
|
3335
3500
|
);
|
|
3336
3501
|
}
|
|
3337
3502
|
|
|
3338
|
-
// src/genre-tree/GenreDetailPanel.tsx
|
|
3339
|
-
var import_react17 = require("react");
|
|
3340
|
-
var import_genre_tree_view5 = require("@behindthemusictree/genre-tree-view");
|
|
3341
|
-
var import_ui20 = require("@behindthemusictree/ui");
|
|
3342
|
-
var import_lucide_react17 = require("lucide-react");
|
|
3343
|
-
var import_genre_tree28 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3344
|
-
var import_jsx_runtime45 = require("react/jsx-runtime");
|
|
3345
|
-
var RELATIVE_ITEM_COUNT = 1;
|
|
3346
|
-
function toNode(genre, parentId) {
|
|
3347
|
-
return {
|
|
3348
|
-
id: genre.uuid,
|
|
3349
|
-
parentId,
|
|
3350
|
-
name: genre.name,
|
|
3351
|
-
itemCount: RELATIVE_ITEM_COUNT,
|
|
3352
|
-
side: genre.side ?? void 0,
|
|
3353
|
-
wikidataId: genre.wikidataId
|
|
3354
|
-
};
|
|
3355
|
-
}
|
|
3356
|
-
function GenreDetailPanel({
|
|
3357
|
-
genreUuid,
|
|
3358
|
-
scope,
|
|
3359
|
-
getBackendBaseUrl,
|
|
3360
|
-
onClose,
|
|
3361
|
-
onSelectGenre,
|
|
3362
|
-
onZoomIn,
|
|
3363
|
-
shareUrl,
|
|
3364
|
-
shareTarget,
|
|
3365
|
-
collapsed,
|
|
3366
|
-
expanded,
|
|
3367
|
-
onExpandedChange,
|
|
3368
|
-
edgeTab,
|
|
3369
|
-
extras
|
|
3370
|
-
}) {
|
|
3371
|
-
const { data: detail, isPending, isError } = (0, import_genre_tree28.useFetchGenreDetail)(genreUuid, scope, getBackendBaseUrl);
|
|
3372
|
-
const playback = useGenrePlaylistPlayback({ scope, genrePlaylists: [] });
|
|
3373
|
-
const view = (0, import_react17.useMemo)(() => {
|
|
3374
|
-
if (!detail) return null;
|
|
3375
|
-
const rootColor = (0, import_genre_tree_view5.getGenreTreeColor)(detail.root.wikidataId);
|
|
3376
|
-
const relative = (genre, parentId) => ({
|
|
3377
|
-
node: toNode(genre, parentId),
|
|
3378
|
-
fill: (0, import_genre_tree_view5.tintSurface)(rootColor),
|
|
3379
|
-
textColor: import_genre_tree_view5.TEXT_COLOR
|
|
3380
|
-
});
|
|
3381
|
-
const ancestors2 = [...detail.ascendants].sort((a, b) => b.degree - a.degree);
|
|
3382
|
-
return {
|
|
3383
|
-
node: {
|
|
3384
|
-
id: detail.uuid,
|
|
3385
|
-
parentId: detail.parent?.uuid ?? null,
|
|
3386
|
-
name: detail.name,
|
|
3387
|
-
itemCount: scope === "reference" ? (0, import_genre_tree28.genreMusicCount)(import_genre_tree28.GenrePlaylistSummarySchema.parse(detail.criteriaPlaylist)) : detail.tracksCount,
|
|
3388
|
-
wikidataId: detail.wikidataId
|
|
3389
|
-
},
|
|
3390
|
-
fill: rootColor,
|
|
3391
|
-
parentNode: detail.parent ? relative(detail.parent, null) : null,
|
|
3392
|
-
childNodes: detail.children.map((child) => relative(child, detail.uuid)),
|
|
3393
|
-
ancestorNodes: ancestors2.map(
|
|
3394
|
-
({ ascendant }, index) => relative(ascendant, index === 0 ? null : ancestors2[index - 1].ascendant.uuid)
|
|
3395
|
-
)
|
|
3396
|
-
};
|
|
3397
|
-
}, [detail, scope]);
|
|
3398
|
-
if (isError) {
|
|
3399
|
-
return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." });
|
|
3400
|
-
}
|
|
3401
|
-
if (isPending || !detail || !view) {
|
|
3402
|
-
return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("div", { className: "flex flex-col gap-3 p-4", "aria-busy": "true", "data-testid": "genre-detail-panel-skeleton", children: [
|
|
3403
|
-
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_ui20.Skeleton, { className: "h-6 w-2/3" }),
|
|
3404
|
-
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_ui20.Skeleton, { className: "h-4 w-full" }),
|
|
3405
|
-
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_ui20.Skeleton, { className: "h-4 w-3/4" })
|
|
3406
|
-
] });
|
|
3407
|
-
}
|
|
3408
|
-
const selectGenre = (nodeId) => onSelectGenre(nodeId);
|
|
3409
|
-
const family = /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
|
|
3410
|
-
import_genre_tree_view5.GenreNodeFamily,
|
|
3411
|
-
{
|
|
3412
|
-
node: view.node,
|
|
3413
|
-
fill: view.fill,
|
|
3414
|
-
textColor: import_genre_tree_view5.ACCENT_TEXT_COLOR,
|
|
3415
|
-
parentNode: view.parentNode,
|
|
3416
|
-
childNodes: view.childNodes,
|
|
3417
|
-
onSelectNode: selectGenre
|
|
3418
|
-
}
|
|
3419
|
-
);
|
|
3420
|
-
const ancestors = /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_genre_tree_view5.GenreNodeAncestors, { ancestorNodes: view.ancestorNodes, onSelectNode: selectGenre });
|
|
3421
|
-
return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(
|
|
3422
|
-
import_ui20.DetailPanel,
|
|
3423
|
-
{
|
|
3424
|
-
side: "left",
|
|
3425
|
-
title: view.node.name,
|
|
3426
|
-
headerAccent: { background: view.fill, color: import_genre_tree_view5.ACCENT_TEXT_COLOR },
|
|
3427
|
-
headerActions: onExpandedChange ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
|
|
3428
|
-
import_ui20.IconButton,
|
|
3429
|
-
{
|
|
3430
|
-
"aria-label": expanded ? "Shrink panel" : "Extend panel",
|
|
3431
|
-
style: { color: "inherit" },
|
|
3432
|
-
onClick: () => onExpandedChange(!expanded),
|
|
3433
|
-
children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_lucide_react17.Minimize2, { "aria-hidden": "true", className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(import_lucide_react17.Maximize2, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
3434
|
-
}
|
|
3435
|
-
) : void 0,
|
|
3436
|
-
onClose,
|
|
3437
|
-
collapsed,
|
|
3438
|
-
expanded,
|
|
3439
|
-
onExpandedChange,
|
|
3440
|
-
edgeTab,
|
|
3441
|
-
children: [
|
|
3442
|
-
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
|
|
3443
|
-
GenreInfoPanelBody,
|
|
3444
|
-
{
|
|
3445
|
-
family: /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(import_jsx_runtime45.Fragment, { children: [
|
|
3446
|
-
family,
|
|
3447
|
-
ancestors
|
|
3448
|
-
] }),
|
|
3449
|
-
genreUuid: detail.uuid,
|
|
3450
|
-
genrePlaylist: scope === "reference" ? import_genre_tree28.GenrePlaylistSummarySchema.parse(detail.criteriaPlaylist) : { ...detail.criteriaPlaylist, tracksCount: detail.tracksCount },
|
|
3451
|
-
catalogue: detail.catalogue,
|
|
3452
|
-
wikidataId: detail.wikidataId,
|
|
3453
|
-
scope,
|
|
3454
|
-
getBackendBaseUrl,
|
|
3455
|
-
playback,
|
|
3456
|
-
band: { node: view.node, onZoomIn, shareUrl, shareTarget: shareUrl ? shareTarget : void 0 },
|
|
3457
|
-
extras
|
|
3458
|
-
}
|
|
3459
|
-
),
|
|
3460
|
-
!expanded && ancestors
|
|
3461
|
-
]
|
|
3462
|
-
}
|
|
3463
|
-
);
|
|
3464
|
-
}
|
|
3465
|
-
|
|
3466
3503
|
// src/genre-tree/index.ts
|
|
3467
3504
|
var import_genre_tree_view6 = require("@behindthemusictree/genre-tree-view");
|
|
3468
3505
|
|
|
3469
3506
|
// src/genre-tree/track-list-sidebar/TrackListSidebar.tsx
|
|
3470
|
-
var
|
|
3507
|
+
var import_react19 = require("react");
|
|
3471
3508
|
var import_lucide_react18 = require("lucide-react");
|
|
3472
3509
|
var import_genre_tree29 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3473
3510
|
var import_genre_tree30 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3474
3511
|
var import_track_noun5 = require("@behindthemusictree/app-kit/track-noun");
|
|
3475
|
-
var
|
|
3476
|
-
var useBrowserLayoutEffect = typeof window === "undefined" ?
|
|
3512
|
+
var import_jsx_runtime47 = require("react/jsx-runtime");
|
|
3513
|
+
var useBrowserLayoutEffect = typeof window === "undefined" ? import_react19.useEffect : import_react19.useLayoutEffect;
|
|
3477
3514
|
function TrackListSidebar({
|
|
3478
3515
|
className: className2 = "",
|
|
3479
3516
|
renderDuration,
|
|
@@ -3487,16 +3524,16 @@ function TrackListSidebar({
|
|
|
3487
3524
|
}) {
|
|
3488
3525
|
const { trackList, loadMore, autoAdvanceScrollRequest } = (0, import_genre_tree29.useTrackList)();
|
|
3489
3526
|
const trackNoun = (0, import_track_noun5.useTrackNoun)();
|
|
3490
|
-
const trackListRef = (0,
|
|
3491
|
-
const previousOriginRef = (0,
|
|
3492
|
-
const handledAutoAdvanceRequestIdRef = (0,
|
|
3493
|
-
const shouldFollowPlaybackRef = (0,
|
|
3494
|
-
const programmaticScrollTopRef = (0,
|
|
3495
|
-
const userScrollIntentRef = (0,
|
|
3496
|
-
const userScrollIntentTimerRef = (0,
|
|
3497
|
-
const loadMoreSentinelRef = (0,
|
|
3527
|
+
const trackListRef = (0, import_react19.useRef)(null);
|
|
3528
|
+
const previousOriginRef = (0, import_react19.useRef)(trackList?.origin ?? null);
|
|
3529
|
+
const handledAutoAdvanceRequestIdRef = (0, import_react19.useRef)(null);
|
|
3530
|
+
const shouldFollowPlaybackRef = (0, import_react19.useRef)(true);
|
|
3531
|
+
const programmaticScrollTopRef = (0, import_react19.useRef)(null);
|
|
3532
|
+
const userScrollIntentRef = (0, import_react19.useRef)(false);
|
|
3533
|
+
const userScrollIntentTimerRef = (0, import_react19.useRef)(null);
|
|
3534
|
+
const loadMoreSentinelRef = (0, import_react19.useRef)(null);
|
|
3498
3535
|
const hasMore = !!trackList && trackList.nextPage !== null;
|
|
3499
|
-
(0,
|
|
3536
|
+
(0, import_react19.useEffect)(() => {
|
|
3500
3537
|
const sentinel = loadMoreSentinelRef.current;
|
|
3501
3538
|
if (!hasMore || !sentinel) return;
|
|
3502
3539
|
const observer = new IntersectionObserver((entries) => {
|
|
@@ -3516,11 +3553,11 @@ function TrackListSidebar({
|
|
|
3516
3553
|
programmaticScrollTopRef.current = null;
|
|
3517
3554
|
if (trackListRef.current) trackListRef.current.scrollTop = 0;
|
|
3518
3555
|
}, [trackList?.origin]);
|
|
3519
|
-
(0,
|
|
3556
|
+
(0, import_react19.useEffect)(() => {
|
|
3520
3557
|
const list = trackListRef.current;
|
|
3521
3558
|
if (!isCollapsed && list?.scrollTop === 0) shouldFollowPlaybackRef.current = true;
|
|
3522
3559
|
}, [isCollapsed]);
|
|
3523
|
-
(0,
|
|
3560
|
+
(0, import_react19.useEffect)(
|
|
3524
3561
|
() => () => {
|
|
3525
3562
|
if (userScrollIntentTimerRef.current !== null) clearTimeout(userScrollIntentTimerRef.current);
|
|
3526
3563
|
},
|
|
@@ -3574,25 +3611,25 @@ function TrackListSidebar({
|
|
|
3574
3611
|
}
|
|
3575
3612
|
};
|
|
3576
3613
|
const positionClasses = layout === "inline" ? "relative w-full flex h-full flex-col" : "fixed right-0 w-[50rem] flex flex-col";
|
|
3577
|
-
return trackList ? /* @__PURE__ */ (0,
|
|
3614
|
+
return trackList ? /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(
|
|
3578
3615
|
"div",
|
|
3579
3616
|
{
|
|
3580
3617
|
"data-theme": "dark",
|
|
3581
3618
|
className: `track-list-sidebar ${positionClasses} rounded-2xl bg-[var(--color-surface)] pb-1 ${className2}`,
|
|
3582
3619
|
style: layout === "fixed" ? { top: "63.5px", maxHeight: "calc(100vh - 63.5px - 79px)" } : void 0,
|
|
3583
3620
|
children: [
|
|
3584
|
-
/* @__PURE__ */ (0,
|
|
3585
|
-
/* @__PURE__ */ (0,
|
|
3586
|
-
/* @__PURE__ */ (0,
|
|
3587
|
-
/* @__PURE__ */ (0,
|
|
3588
|
-
/* @__PURE__ */ (0,
|
|
3621
|
+
/* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { className: "header flex h-16 items-center px-4 py-2 text-[var(--color-text)]", children: [
|
|
3622
|
+
/* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { className: "min-w-0 flex flex-1 flex-col justify-center", children: [
|
|
3623
|
+
/* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { className: "origin flex text-xl", children: [
|
|
3624
|
+
/* @__PURE__ */ (0, import_jsx_runtime47.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
|
|
3625
|
+
/* @__PURE__ */ (0, import_jsx_runtime47.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_runtime47.jsx)("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
|
|
3589
3626
|
] }),
|
|
3590
|
-
/* @__PURE__ */ (0,
|
|
3627
|
+
/* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
|
|
3591
3628
|
trackList.origin.type === import_genre_tree30.TrackListOriginType.GENRE_PLAYLIST ? "Genre \u2022 " : trackList.origin.type === import_genre_tree30.TrackListOriginType.ARTIST ? "Artist \u2022 " : `${trackNoun.singular} playlist \u2022 `,
|
|
3592
3629
|
`${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`
|
|
3593
3630
|
] })
|
|
3594
3631
|
] }),
|
|
3595
|
-
(isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0,
|
|
3632
|
+
(isCollapsed && onExpand || !isCollapsed && onMinimize) && /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
|
|
3596
3633
|
"button",
|
|
3597
3634
|
{
|
|
3598
3635
|
type: "button",
|
|
@@ -3601,11 +3638,11 @@ function TrackListSidebar({
|
|
|
3601
3638
|
"aria-expanded": !isCollapsed,
|
|
3602
3639
|
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"}`,
|
|
3603
3640
|
onClick: isCollapsed ? onExpand : onMinimize,
|
|
3604
|
-
children: /* @__PURE__ */ (0,
|
|
3641
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_lucide_react18.ListMusic, { "aria-hidden": "true", className: "h-5 w-5" })
|
|
3605
3642
|
}
|
|
3606
3643
|
)
|
|
3607
3644
|
] }),
|
|
3608
|
-
!isCollapsed && /* @__PURE__ */ (0,
|
|
3645
|
+
!isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(
|
|
3609
3646
|
"ul",
|
|
3610
3647
|
{
|
|
3611
3648
|
ref: trackListRef,
|
|
@@ -3615,7 +3652,7 @@ function TrackListSidebar({
|
|
|
3615
3652
|
onPointerDown: markUserScrollIntent,
|
|
3616
3653
|
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",
|
|
3617
3654
|
children: [
|
|
3618
|
-
trackList.tracks.map((track, index) => /* @__PURE__ */ (0,
|
|
3655
|
+
trackList.tracks.map((track, index) => /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("li", { "data-track-uuid": track.uuid, children: /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
|
|
3619
3656
|
TrackItem,
|
|
3620
3657
|
{
|
|
3621
3658
|
track,
|
|
@@ -3626,7 +3663,7 @@ function TrackListSidebar({
|
|
|
3626
3663
|
renderSongLink
|
|
3627
3664
|
}
|
|
3628
3665
|
) }, track.uuid)),
|
|
3629
|
-
hasMore && /* @__PURE__ */ (0,
|
|
3666
|
+
hasMore && /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("li", { ref: loadMoreSentinelRef, "aria-hidden": "true", className: "h-px" })
|
|
3630
3667
|
]
|
|
3631
3668
|
}
|
|
3632
3669
|
)
|
|
@@ -3636,7 +3673,7 @@ function TrackListSidebar({
|
|
|
3636
3673
|
}
|
|
3637
3674
|
|
|
3638
3675
|
// src/genre-tree/PlayerPanel.tsx
|
|
3639
|
-
var
|
|
3676
|
+
var import_react20 = require("react");
|
|
3640
3677
|
var import_tailwind_merge = require("tailwind-merge");
|
|
3641
3678
|
var import_player13 = require("@behindthemusictree/app-kit/player");
|
|
3642
3679
|
var import_genre_tree32 = require("@behindthemusictree/app-kit/genre-tree");
|
|
@@ -3646,7 +3683,7 @@ var import_ui21 = require("@behindthemusictree/ui");
|
|
|
3646
3683
|
var import_lucide_react19 = require("lucide-react");
|
|
3647
3684
|
var import_genre_tree31 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3648
3685
|
var import_player12 = require("@behindthemusictree/app-kit/player");
|
|
3649
|
-
var
|
|
3686
|
+
var import_jsx_runtime48 = require("react/jsx-runtime");
|
|
3650
3687
|
function PlayerControls() {
|
|
3651
3688
|
const {
|
|
3652
3689
|
duration,
|
|
@@ -3665,15 +3702,15 @@ function PlayerControls() {
|
|
|
3665
3702
|
const safeDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;
|
|
3666
3703
|
const safeCurrentTime = Number.isFinite(currentTime) ? Math.max(0, Math.min(currentTime, safeDuration)) : 0;
|
|
3667
3704
|
const isPlaying = playState === import_player12.PlayStates.PLAYING;
|
|
3668
|
-
return /* @__PURE__ */ (0,
|
|
3705
|
+
return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(
|
|
3669
3706
|
"section",
|
|
3670
3707
|
{
|
|
3671
3708
|
"aria-label": "Player controls",
|
|
3672
3709
|
className: "player-controls flex shrink-0 flex-col gap-2 border-b border-zinc-800 px-3 py-2 text-zinc-200",
|
|
3673
3710
|
children: [
|
|
3674
|
-
/* @__PURE__ */ (0,
|
|
3675
|
-
/* @__PURE__ */ (0,
|
|
3676
|
-
/* @__PURE__ */ (0,
|
|
3711
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex min-w-0 items-center gap-2 text-xs tabular-nums text-zinc-400", children: [
|
|
3712
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { "aria-label": "Elapsed time", children: (0, import_genre_tree31.formatTime)(safeCurrentTime) }),
|
|
3713
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
|
|
3677
3714
|
"input",
|
|
3678
3715
|
{
|
|
3679
3716
|
"aria-label": "Seek",
|
|
@@ -3687,11 +3724,11 @@ function PlayerControls() {
|
|
|
3687
3724
|
onChange: (event) => seekTo(Number(event.currentTarget.value))
|
|
3688
3725
|
}
|
|
3689
3726
|
),
|
|
3690
|
-
/* @__PURE__ */ (0,
|
|
3727
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { "aria-label": "Duration", children: (0, import_genre_tree31.formatTime)(safeDuration) })
|
|
3691
3728
|
] }),
|
|
3692
|
-
/* @__PURE__ */ (0,
|
|
3693
|
-
/* @__PURE__ */ (0,
|
|
3694
|
-
/* @__PURE__ */ (0,
|
|
3729
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "grid min-h-9 grid-cols-[1fr_auto_1fr] items-center gap-3", children: [
|
|
3730
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "col-start-2 flex items-center justify-center gap-2", children: [
|
|
3731
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
|
|
3695
3732
|
"button",
|
|
3696
3733
|
{
|
|
3697
3734
|
type: "button",
|
|
@@ -3700,10 +3737,10 @@ function PlayerControls() {
|
|
|
3700
3737
|
disabled: !isReady || !hasPreviousTrack,
|
|
3701
3738
|
onClick: playPreviousTrack,
|
|
3702
3739
|
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",
|
|
3703
|
-
children: /* @__PURE__ */ (0,
|
|
3740
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_lucide_react19.SkipBack, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
3704
3741
|
}
|
|
3705
3742
|
),
|
|
3706
|
-
/* @__PURE__ */ (0,
|
|
3743
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
|
|
3707
3744
|
"button",
|
|
3708
3745
|
{
|
|
3709
3746
|
type: "button",
|
|
@@ -3712,10 +3749,10 @@ function PlayerControls() {
|
|
|
3712
3749
|
disabled: !isReady,
|
|
3713
3750
|
onClick: handlePlayPauseAction,
|
|
3714
3751
|
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",
|
|
3715
|
-
children: isPlaying ? /* @__PURE__ */ (0,
|
|
3752
|
+
children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_lucide_react19.Pause, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" }) : /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_lucide_react19.Play, { "aria-hidden": "true", className: "h-4 w-4", fill: "currentColor" })
|
|
3716
3753
|
}
|
|
3717
3754
|
),
|
|
3718
|
-
/* @__PURE__ */ (0,
|
|
3755
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
|
|
3719
3756
|
"button",
|
|
3720
3757
|
{
|
|
3721
3758
|
type: "button",
|
|
@@ -3724,14 +3761,14 @@ function PlayerControls() {
|
|
|
3724
3761
|
disabled: !isReady || !hasNextTrack,
|
|
3725
3762
|
onClick: playNextTrack,
|
|
3726
3763
|
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",
|
|
3727
|
-
children: /* @__PURE__ */ (0,
|
|
3764
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_lucide_react19.SkipForward, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
3728
3765
|
}
|
|
3729
3766
|
)
|
|
3730
3767
|
] }),
|
|
3731
|
-
/* @__PURE__ */ (0,
|
|
3732
|
-
volume === 0 ? /* @__PURE__ */ (0,
|
|
3733
|
-
/* @__PURE__ */ (0,
|
|
3734
|
-
/* @__PURE__ */ (0,
|
|
3768
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.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: [
|
|
3769
|
+
volume === 0 ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_lucide_react19.VolumeX, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }) : /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_lucide_react19.Volume2, { "aria-hidden": "true", className: "h-[0.7rem] w-[0.7rem] shrink-0" }),
|
|
3770
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "sr-only", children: "Volume" }),
|
|
3771
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
|
|
3735
3772
|
"input",
|
|
3736
3773
|
{
|
|
3737
3774
|
"aria-label": "Volume",
|
|
@@ -3753,7 +3790,7 @@ function PlayerControls() {
|
|
|
3753
3790
|
}
|
|
3754
3791
|
|
|
3755
3792
|
// src/genre-tree/PlayerPanel.tsx
|
|
3756
|
-
var
|
|
3793
|
+
var import_jsx_runtime49 = require("react/jsx-runtime");
|
|
3757
3794
|
var PANEL_ID = "player-panel";
|
|
3758
3795
|
var DEFAULT_EXTENDED_WIDTH = "calc(100vw - var(--layout-center-left) - var(--layout-gutter))";
|
|
3759
3796
|
function PlayerPanel({
|
|
@@ -3767,12 +3804,12 @@ function PlayerPanel({
|
|
|
3767
3804
|
}) {
|
|
3768
3805
|
const { playerTrackObject, retryPlayback } = (0, import_player13.usePlayer)();
|
|
3769
3806
|
const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0, import_genre_tree32.useTrackListSidebarVisibility)();
|
|
3770
|
-
const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = (0,
|
|
3807
|
+
const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = (0, import_react20.useState)("");
|
|
3771
3808
|
const loadedTrackTitle = playerTrackObject?.track.title;
|
|
3772
|
-
(0,
|
|
3809
|
+
(0, import_react20.useEffect)(() => {
|
|
3773
3810
|
if (loadedTrackTitle) setLastLoadedTrackTitle(loadedTrackTitle);
|
|
3774
3811
|
}, [loadedTrackTitle]);
|
|
3775
|
-
const [uncontrolledExtended, setUncontrolledExtended] = (0,
|
|
3812
|
+
const [uncontrolledExtended, setUncontrolledExtended] = (0, import_react20.useState)(false);
|
|
3776
3813
|
const isExtendRequested = expanded ?? uncontrolledExtended;
|
|
3777
3814
|
const setIsExtendRequested = (next) => {
|
|
3778
3815
|
setUncontrolledExtended(next);
|
|
@@ -3793,7 +3830,7 @@ function PlayerPanel({
|
|
|
3793
3830
|
onClick: () => setIsExtendRequested(true),
|
|
3794
3831
|
expanded: false
|
|
3795
3832
|
} : null;
|
|
3796
|
-
return /* @__PURE__ */ (0,
|
|
3833
|
+
return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(
|
|
3797
3834
|
"div",
|
|
3798
3835
|
{
|
|
3799
3836
|
id: PANEL_ID,
|
|
@@ -3806,7 +3843,7 @@ function PlayerPanel({
|
|
|
3806
3843
|
),
|
|
3807
3844
|
style: isExtended ? { width: extendedWidth } : void 0,
|
|
3808
3845
|
children: [
|
|
3809
|
-
/* @__PURE__ */ (0,
|
|
3846
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
|
|
3810
3847
|
import_ui21.PanelEdgeTab,
|
|
3811
3848
|
{
|
|
3812
3849
|
side: "right",
|
|
@@ -3816,7 +3853,7 @@ function PlayerPanel({
|
|
|
3816
3853
|
className: "absolute right-full top-1/2 -translate-y-1/2"
|
|
3817
3854
|
}
|
|
3818
3855
|
),
|
|
3819
|
-
/* @__PURE__ */ (0,
|
|
3856
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(
|
|
3820
3857
|
"div",
|
|
3821
3858
|
{
|
|
3822
3859
|
className: (0, import_tailwind_merge.twMerge)(
|
|
@@ -3824,20 +3861,20 @@ function PlayerPanel({
|
|
|
3824
3861
|
isExtended ? "flex-row" : "flex-col"
|
|
3825
3862
|
),
|
|
3826
3863
|
children: [
|
|
3827
|
-
/* @__PURE__ */ (0,
|
|
3828
|
-
/* @__PURE__ */ (0,
|
|
3829
|
-
/* @__PURE__ */ (0,
|
|
3830
|
-
/* @__PURE__ */ (0,
|
|
3831
|
-
playerTrackObject?.loadError && /* @__PURE__ */ (0,
|
|
3864
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: "flex w-[var(--layout-right-w)] shrink-0 flex-col", children: [
|
|
3865
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
|
|
3866
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: "relative w-full shrink-0", children: [
|
|
3867
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_player13.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
|
|
3868
|
+
playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: [
|
|
3832
3869
|
playerTrackObject.loadError,
|
|
3833
|
-
/* @__PURE__ */ (0,
|
|
3870
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)("button", { type: "button", className: "ml-2 underline", onClick: () => {
|
|
3834
3871
|
void retryPlayback();
|
|
3835
3872
|
}, children: "Retry" })
|
|
3836
3873
|
] })
|
|
3837
3874
|
] }),
|
|
3838
|
-
/* @__PURE__ */ (0,
|
|
3875
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)(PlayerControls, {})
|
|
3839
3876
|
] }),
|
|
3840
|
-
/* @__PURE__ */ (0,
|
|
3877
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
|
|
3841
3878
|
TrackListSidebar,
|
|
3842
3879
|
{
|
|
3843
3880
|
layout: "inline",
|
|
@@ -3864,11 +3901,11 @@ function PlayerPanel({
|
|
|
3864
3901
|
var import_uuid = require("uuid");
|
|
3865
3902
|
var import_ai = require("react-icons/ai");
|
|
3866
3903
|
var import_genre_tree33 = require("@behindthemusictree/app-kit/genre-tree");
|
|
3867
|
-
var
|
|
3904
|
+
var import_jsx_runtime50 = require("react/jsx-runtime");
|
|
3868
3905
|
function Rating({ rating, handleChange }) {
|
|
3869
3906
|
const instanceId = (0, import_uuid.v4)();
|
|
3870
|
-
return /* @__PURE__ */ (0,
|
|
3871
|
-
/* @__PURE__ */ (0,
|
|
3907
|
+
return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("div", { className: "flex items-center", children: [...Array(6)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("div", { className: `relative ${i === 0 ? "mr-4" : "mr-1"}`, children: [
|
|
3908
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
|
|
3872
3909
|
"input",
|
|
3873
3910
|
{
|
|
3874
3911
|
id: `star-${instanceId}-${i}`,
|
|
@@ -3880,16 +3917,16 @@ function Rating({ rating, handleChange }) {
|
|
|
3880
3917
|
onChange: handleChange
|
|
3881
3918
|
}
|
|
3882
3919
|
),
|
|
3883
|
-
/* @__PURE__ */ (0,
|
|
3884
|
-
/* @__PURE__ */ (0,
|
|
3920
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)("label", { htmlFor: `star-${instanceId}-${i}`, className: "cursor-pointer", children: i === 0 ? /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("div", { className: "flex", children: [
|
|
3921
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
|
|
3885
3922
|
import_ai.AiOutlineStar,
|
|
3886
3923
|
{
|
|
3887
3924
|
className: `w-7 h-7 absolute top-0 left-0 ${rating === import_genre_tree33.FORM_RATING_NULL_VALUE ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
|
|
3888
3925
|
}
|
|
3889
3926
|
),
|
|
3890
|
-
/* @__PURE__ */ (0,
|
|
3927
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)("span", { className: "flex-grow w-8" }),
|
|
3891
3928
|
" "
|
|
3892
|
-
] }) : /* @__PURE__ */ (0,
|
|
3929
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
|
|
3893
3930
|
import_ai.AiFillStar,
|
|
3894
3931
|
{
|
|
3895
3932
|
className: `w-7 h-7 absolute top-0 left-0 ${(rating ?? -1) >= i ? "text-[var(--color-accent-rating)]" : "text-[var(--color-text-muted)]"}`
|