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