@signalsandsorcery/plugin-sdk 2.35.4 → 2.35.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +218 -3
- package/dist/index.d.ts +218 -3
- package/dist/index.js +1088 -453
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +829 -196
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -54,6 +54,7 @@ __export(index_exports, {
|
|
|
54
54
|
FxToggleBar: () => FxToggleBar,
|
|
55
55
|
GUTTER_W: () => GUTTER_W,
|
|
56
56
|
GeneratorPanelShell: () => GeneratorPanelShell,
|
|
57
|
+
GroupFadeTrackRow: () => GroupFadeTrackRow,
|
|
57
58
|
ImportTrackModal: () => ImportTrackModal,
|
|
58
59
|
InstrumentDrawer: () => TrackDrawer,
|
|
59
60
|
LevelMeter: () => LevelMeter,
|
|
@@ -122,6 +123,7 @@ __export(index_exports, {
|
|
|
122
123
|
sliderToDb: () => sliderToDb,
|
|
123
124
|
slotsEqual: () => slotsEqual,
|
|
124
125
|
soundIdentity: () => soundIdentity,
|
|
126
|
+
splitFadeEntries: () => splitFadeEntries,
|
|
125
127
|
synthesizeCuePoints: () => synthesizeCuePoints,
|
|
126
128
|
tokenizePrompt: () => tokenizePrompt,
|
|
127
129
|
trackDataKey: () => trackDataKey,
|
|
@@ -2991,7 +2993,10 @@ function asFadeMeta(val) {
|
|
|
2991
2993
|
sourceSceneId: typeof m.sourceSceneId === "string" ? m.sourceSceneId : "",
|
|
2992
2994
|
sourceName: typeof m.sourceName === "string" ? m.sourceName : "",
|
|
2993
2995
|
soundLabel: typeof m.soundLabel === "string" ? m.soundLabel : "",
|
|
2994
|
-
sliderPos: typeof m.sliderPos === "number" ? m.sliderPos : 0.5
|
|
2996
|
+
sliderPos: typeof m.sliderPos === "number" ? m.sliderPos : 0.5,
|
|
2997
|
+
groupId: typeof m.groupId === "string" && m.groupId ? m.groupId : void 0,
|
|
2998
|
+
memberIndex: typeof m.memberIndex === "number" ? m.memberIndex : void 0,
|
|
2999
|
+
memberLabel: typeof m.memberLabel === "string" && m.memberLabel ? m.memberLabel : void 0
|
|
2995
3000
|
};
|
|
2996
3001
|
}
|
|
2997
3002
|
function parseFades(sceneData) {
|
|
@@ -3005,6 +3010,33 @@ function parseFades(sceneData) {
|
|
|
3005
3010
|
}
|
|
3006
3011
|
return out;
|
|
3007
3012
|
}
|
|
3013
|
+
function splitFadeEntries(entries) {
|
|
3014
|
+
const singles = [];
|
|
3015
|
+
const byGroup = /* @__PURE__ */ new Map();
|
|
3016
|
+
for (const entry of entries) {
|
|
3017
|
+
const gid = entry.meta.groupId;
|
|
3018
|
+
if (!gid) {
|
|
3019
|
+
singles.push(entry);
|
|
3020
|
+
continue;
|
|
3021
|
+
}
|
|
3022
|
+
const list = byGroup.get(gid) ?? [];
|
|
3023
|
+
list.push(entry);
|
|
3024
|
+
byGroup.set(gid, list);
|
|
3025
|
+
}
|
|
3026
|
+
const groups = [];
|
|
3027
|
+
for (const [groupId, members] of byGroup) {
|
|
3028
|
+
members.sort((a, b) => (a.meta.memberIndex ?? 0) - (b.meta.memberIndex ?? 0));
|
|
3029
|
+
const head = members[0].meta;
|
|
3030
|
+
groups.push({
|
|
3031
|
+
groupId,
|
|
3032
|
+
direction: head.direction,
|
|
3033
|
+
gesture: head.gesture,
|
|
3034
|
+
sliderPos: head.sliderPos,
|
|
3035
|
+
members
|
|
3036
|
+
});
|
|
3037
|
+
}
|
|
3038
|
+
return { singles, groups };
|
|
3039
|
+
}
|
|
3008
3040
|
function buildFadeVolumeCurve(bars, bpm, direction, sliderPos, gesture, steps = 32) {
|
|
3009
3041
|
const durationSeconds = bars * 4 * 60 / Math.max(1, bpm);
|
|
3010
3042
|
if (gesture === "build") {
|
|
@@ -3189,9 +3221,191 @@ function FadeTrackRow({
|
|
|
3189
3221
|
);
|
|
3190
3222
|
}
|
|
3191
3223
|
|
|
3192
|
-
// src/components/
|
|
3193
|
-
var import_react14 = require("react");
|
|
3224
|
+
// src/components/GroupFadeTrackRow.tsx
|
|
3225
|
+
var import_react14 = __toESM(require("react"));
|
|
3194
3226
|
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
3227
|
+
function MemberCaption({
|
|
3228
|
+
member,
|
|
3229
|
+
direction
|
|
3230
|
+
}) {
|
|
3231
|
+
const tag = direction === "in" ? "Fade in" : "Fade out";
|
|
3232
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center gap-1.5 min-w-0 px-2 py-0.5", children: [
|
|
3233
|
+
member.memberLabel && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "text-[9px] font-bold uppercase tracking-wide text-sas-accent flex-shrink-0", children: member.memberLabel }),
|
|
3234
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "text-[11px] text-sas-text truncate", title: member.sourceName ?? member.name, children: member.sourceName ?? member.name }),
|
|
3235
|
+
member.soundLabel && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "text-[9px] text-sas-muted/60 truncate flex-shrink-0", title: member.soundLabel, children: [
|
|
3236
|
+
"\xB7 ",
|
|
3237
|
+
member.soundLabel
|
|
3238
|
+
] }),
|
|
3239
|
+
!member.memberLabel && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "text-[9px] text-sas-muted/50 flex-shrink-0", children: [
|
|
3240
|
+
"\xB7 ",
|
|
3241
|
+
tag
|
|
3242
|
+
] })
|
|
3243
|
+
] });
|
|
3244
|
+
}
|
|
3245
|
+
function GroupFadeTrackRow({
|
|
3246
|
+
groupLabel,
|
|
3247
|
+
direction,
|
|
3248
|
+
gesture,
|
|
3249
|
+
sliderPos = 0.5,
|
|
3250
|
+
members,
|
|
3251
|
+
onMemberMuteToggle,
|
|
3252
|
+
onMemberSoloToggle,
|
|
3253
|
+
onMemberVolumeChange,
|
|
3254
|
+
onMemberPanChange,
|
|
3255
|
+
onMuteAll,
|
|
3256
|
+
onSoloAll,
|
|
3257
|
+
onDelete,
|
|
3258
|
+
onSliderChange,
|
|
3259
|
+
levels,
|
|
3260
|
+
accentColor = "#9333EA"
|
|
3261
|
+
}) {
|
|
3262
|
+
const [confirmDelete, setConfirmDelete] = import_react14.default.useState(false);
|
|
3263
|
+
const allMuted = members.length > 0 && members.every((m) => m.runtimeState.muted);
|
|
3264
|
+
const allSolo = members.length > 0 && members.every((m) => m.runtimeState.solo);
|
|
3265
|
+
const badge = direction === "in" ? "\u2197 Fade in" : "\u2198 Fade out";
|
|
3266
|
+
const leftLabel = direction === "in" ? "(silent)" : groupLabel;
|
|
3267
|
+
const rightLabel = direction === "in" ? groupLabel : "(silent)";
|
|
3268
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
3269
|
+
"div",
|
|
3270
|
+
{
|
|
3271
|
+
"data-testid": "group-fade-track-row",
|
|
3272
|
+
className: "w-full rounded-sm border border-sas-border bg-sas-panel/40 overflow-hidden",
|
|
3273
|
+
style: { borderLeftColor: accentColor, borderLeftWidth: "3px" },
|
|
3274
|
+
children: [
|
|
3275
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center justify-between px-2 py-1 bg-sas-panel-alt/60 gap-2", children: [
|
|
3276
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center gap-2 min-w-0", children: [
|
|
3277
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3278
|
+
"span",
|
|
3279
|
+
{
|
|
3280
|
+
"data-testid": "group-fade-direction-badge",
|
|
3281
|
+
className: "text-[10px] font-bold uppercase tracking-wide flex-shrink-0",
|
|
3282
|
+
style: { color: accentColor },
|
|
3283
|
+
children: badge
|
|
3284
|
+
}
|
|
3285
|
+
),
|
|
3286
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3287
|
+
"span",
|
|
3288
|
+
{
|
|
3289
|
+
"data-testid": "group-fade-label",
|
|
3290
|
+
className: "text-[11px] text-sas-text truncate",
|
|
3291
|
+
title: `${groupLabel} \xB7 ${gesture}`,
|
|
3292
|
+
children: groupLabel
|
|
3293
|
+
}
|
|
3294
|
+
)
|
|
3295
|
+
] }),
|
|
3296
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center gap-1 flex-shrink-0", children: [
|
|
3297
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3298
|
+
"button",
|
|
3299
|
+
{
|
|
3300
|
+
"data-testid": "group-fade-mute-button",
|
|
3301
|
+
onClick: onMuteAll,
|
|
3302
|
+
className: `px-1.5 py-0.5 text-[9px] font-bold rounded-sm border transition-colors ${allMuted ? "bg-sas-danger/20 border-sas-danger text-sas-danger" : "border-sas-border text-sas-muted hover:text-sas-text"}`,
|
|
3303
|
+
title: allMuted ? "Unmute all members" : "Mute all members",
|
|
3304
|
+
children: "M"
|
|
3305
|
+
}
|
|
3306
|
+
),
|
|
3307
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3308
|
+
"button",
|
|
3309
|
+
{
|
|
3310
|
+
"data-testid": "group-fade-solo-button",
|
|
3311
|
+
onClick: onSoloAll,
|
|
3312
|
+
className: `px-1.5 py-0.5 text-[9px] font-bold rounded-sm border transition-colors ${allSolo ? "bg-sas-accent/20 border-sas-accent text-sas-accent" : "border-sas-border text-sas-muted hover:text-sas-text"}`,
|
|
3313
|
+
title: allSolo ? "Unsolo all members" : "Solo all members",
|
|
3314
|
+
children: "S"
|
|
3315
|
+
}
|
|
3316
|
+
),
|
|
3317
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3318
|
+
"button",
|
|
3319
|
+
{
|
|
3320
|
+
"data-testid": "group-fade-delete-button",
|
|
3321
|
+
onClick: () => setConfirmDelete(true),
|
|
3322
|
+
className: "text-sas-danger/70 hover:text-sas-danger px-1 transition-colors text-sm",
|
|
3323
|
+
title: "Delete group fade",
|
|
3324
|
+
"aria-label": "Delete group fade",
|
|
3325
|
+
children: "x"
|
|
3326
|
+
}
|
|
3327
|
+
)
|
|
3328
|
+
] })
|
|
3329
|
+
] }),
|
|
3330
|
+
members.map((member) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3331
|
+
TrackRow,
|
|
3332
|
+
{
|
|
3333
|
+
track: { id: member.trackId, name: "", role: member.role },
|
|
3334
|
+
runtimeState: member.runtimeState,
|
|
3335
|
+
fxDetailState: EMPTY_FX_DETAIL_STATE,
|
|
3336
|
+
drawerOpen: false,
|
|
3337
|
+
drawerTab: "fx",
|
|
3338
|
+
levels,
|
|
3339
|
+
accentColor,
|
|
3340
|
+
contentSlot: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(MemberCaption, { member, direction }),
|
|
3341
|
+
onMuteToggle: () => onMemberMuteToggle(member.trackId),
|
|
3342
|
+
onSoloToggle: () => onMemberSoloToggle(member.trackId),
|
|
3343
|
+
onVolumeChange: (vol) => onMemberVolumeChange(member.trackId, vol),
|
|
3344
|
+
onPanChange: (pan) => onMemberPanChange(member.trackId, pan)
|
|
3345
|
+
},
|
|
3346
|
+
member.trackId
|
|
3347
|
+
)),
|
|
3348
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center gap-2 px-3 py-1.5", "data-testid": "group-fade-slider-row", children: [
|
|
3349
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3350
|
+
"span",
|
|
3351
|
+
{
|
|
3352
|
+
className: "text-[9px] text-sas-muted/60 truncate max-w-[70px] text-right flex-shrink-0",
|
|
3353
|
+
title: leftLabel,
|
|
3354
|
+
children: leftLabel
|
|
3355
|
+
}
|
|
3356
|
+
),
|
|
3357
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3358
|
+
"input",
|
|
3359
|
+
{
|
|
3360
|
+
type: "range",
|
|
3361
|
+
"data-testid": "group-fade-slider",
|
|
3362
|
+
min: 0,
|
|
3363
|
+
max: 1,
|
|
3364
|
+
step: 0.01,
|
|
3365
|
+
value: sliderPos,
|
|
3366
|
+
disabled: !onSliderChange,
|
|
3367
|
+
onChange: onSliderChange ? (e) => onSliderChange(Number(e.target.value)) : void 0,
|
|
3368
|
+
style: { accentColor },
|
|
3369
|
+
className: "flex-1 disabled:opacity-60 disabled:cursor-not-allowed",
|
|
3370
|
+
"aria-label": "Group fade position"
|
|
3371
|
+
}
|
|
3372
|
+
),
|
|
3373
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3374
|
+
"span",
|
|
3375
|
+
{
|
|
3376
|
+
className: "text-[9px] text-sas-muted/60 truncate max-w-[70px] flex-shrink-0",
|
|
3377
|
+
title: rightLabel,
|
|
3378
|
+
children: rightLabel
|
|
3379
|
+
}
|
|
3380
|
+
)
|
|
3381
|
+
] }),
|
|
3382
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
3383
|
+
ConfirmDialog,
|
|
3384
|
+
{
|
|
3385
|
+
open: confirmDelete,
|
|
3386
|
+
title: "Delete group fade?",
|
|
3387
|
+
message: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
|
|
3388
|
+
"All ",
|
|
3389
|
+
members.length,
|
|
3390
|
+
" member tracks of this fade will be permanently removed from this scene. This cannot be undone."
|
|
3391
|
+
] }),
|
|
3392
|
+
confirmLabel: "Delete",
|
|
3393
|
+
onConfirm: () => {
|
|
3394
|
+
setConfirmDelete(false);
|
|
3395
|
+
onDelete();
|
|
3396
|
+
},
|
|
3397
|
+
onCancel: () => setConfirmDelete(false),
|
|
3398
|
+
testIdPrefix: "group-fade-delete-confirm"
|
|
3399
|
+
}
|
|
3400
|
+
)
|
|
3401
|
+
]
|
|
3402
|
+
}
|
|
3403
|
+
);
|
|
3404
|
+
}
|
|
3405
|
+
|
|
3406
|
+
// src/components/FadeModal.tsx
|
|
3407
|
+
var import_react15 = require("react");
|
|
3408
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
3195
3409
|
function shortId(dbId) {
|
|
3196
3410
|
return dbId.length > 8 ? dbId.slice(0, 8) : dbId;
|
|
3197
3411
|
}
|
|
@@ -3234,7 +3448,7 @@ function OrphanRow({
|
|
|
3234
3448
|
}) {
|
|
3235
3449
|
const primary = track.prompt?.trim() || track.name;
|
|
3236
3450
|
const meta = [track.role, shortId(track.dbId), gesture].filter(Boolean).join(" \xB7 ");
|
|
3237
|
-
return /* @__PURE__ */ (0,
|
|
3451
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
3238
3452
|
"button",
|
|
3239
3453
|
{
|
|
3240
3454
|
type: "button",
|
|
@@ -3246,8 +3460,8 @@ function OrphanRow({
|
|
|
3246
3460
|
disabled,
|
|
3247
3461
|
className: `w-full text-left px-2 py-1.5 rounded-sm border transition-colors disabled:opacity-50 ${selected ? "bg-sas-accent/15 border-sas-accent" : "bg-sas-panel border-sas-border hover:border-sas-accent/50"}`,
|
|
3248
3462
|
children: [
|
|
3249
|
-
/* @__PURE__ */ (0,
|
|
3250
|
-
meta && /* @__PURE__ */ (0,
|
|
3463
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "text-xs text-sas-text truncate", title: primary, children: primary }),
|
|
3464
|
+
meta && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "text-[10px] text-sas-muted truncate mt-0.5", title: track.dbId, children: meta })
|
|
3251
3465
|
]
|
|
3252
3466
|
}
|
|
3253
3467
|
);
|
|
@@ -3264,14 +3478,14 @@ function FadeModal({
|
|
|
3264
3478
|
onCreate,
|
|
3265
3479
|
testIdPrefix = "fade-modal"
|
|
3266
3480
|
}) {
|
|
3267
|
-
const [load, setLoad] = (0,
|
|
3268
|
-
const [selectedDbId, setSelectedDbId] = (0,
|
|
3269
|
-
const [isCreating, setIsCreating] = (0,
|
|
3270
|
-
const [error, setError] = (0,
|
|
3271
|
-
const [fromName, setFromName] = (0,
|
|
3272
|
-
const [toName, setToName] = (0,
|
|
3273
|
-
const cancelRef = (0,
|
|
3274
|
-
const refresh = (0,
|
|
3481
|
+
const [load, setLoad] = (0, import_react15.useState)({ status: "loading" });
|
|
3482
|
+
const [selectedDbId, setSelectedDbId] = (0, import_react15.useState)("");
|
|
3483
|
+
const [isCreating, setIsCreating] = (0, import_react15.useState)(false);
|
|
3484
|
+
const [error, setError] = (0, import_react15.useState)(null);
|
|
3485
|
+
const [fromName, setFromName] = (0, import_react15.useState)(null);
|
|
3486
|
+
const [toName, setToName] = (0, import_react15.useState)(null);
|
|
3487
|
+
const cancelRef = (0, import_react15.useRef)(null);
|
|
3488
|
+
const refresh = (0, import_react15.useCallback)(async () => {
|
|
3275
3489
|
if (!host.listSceneFamilyTracks) {
|
|
3276
3490
|
setLoad({ status: "error", message: "This host does not support fades." });
|
|
3277
3491
|
return;
|
|
@@ -3291,7 +3505,7 @@ function FadeModal({
|
|
|
3291
3505
|
setLoad({ status: "error", message: err instanceof Error ? err.message : "Failed to load tracks." });
|
|
3292
3506
|
}
|
|
3293
3507
|
}, [host, fromSceneId, toSceneId]);
|
|
3294
|
-
(0,
|
|
3508
|
+
(0, import_react15.useEffect)(() => {
|
|
3295
3509
|
if (open) {
|
|
3296
3510
|
setError(null);
|
|
3297
3511
|
setIsCreating(false);
|
|
@@ -3299,29 +3513,29 @@ function FadeModal({
|
|
|
3299
3513
|
void refresh();
|
|
3300
3514
|
}
|
|
3301
3515
|
}, [open, refresh]);
|
|
3302
|
-
const excludeSet = (0,
|
|
3303
|
-
const { fadeOut, fadeIn } = (0,
|
|
3516
|
+
const excludeSet = (0, import_react15.useMemo)(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);
|
|
3517
|
+
const { fadeOut, fadeIn } = (0, import_react15.useMemo)(
|
|
3304
3518
|
() => load.status === "ready" ? computeOrphans(load.from, load.to, excludeSet) : { fadeOut: [], fadeIn: [] },
|
|
3305
3519
|
[load, excludeSet]
|
|
3306
3520
|
);
|
|
3307
|
-
const allOrphans = (0,
|
|
3521
|
+
const allOrphans = (0, import_react15.useMemo)(
|
|
3308
3522
|
() => [
|
|
3309
3523
|
...fadeOut.map((t) => ({ track: t, direction: "out" })),
|
|
3310
3524
|
...fadeIn.map((t) => ({ track: t, direction: "in" }))
|
|
3311
3525
|
],
|
|
3312
3526
|
[fadeOut, fadeIn]
|
|
3313
3527
|
);
|
|
3314
|
-
(0,
|
|
3528
|
+
(0, import_react15.useEffect)(() => {
|
|
3315
3529
|
if (!allOrphans.some((o) => o.track.dbId === selectedDbId)) {
|
|
3316
3530
|
setSelectedDbId(allOrphans[0]?.track.dbId ?? "");
|
|
3317
3531
|
}
|
|
3318
3532
|
}, [allOrphans, selectedDbId]);
|
|
3319
3533
|
const selected = allOrphans.find((o) => o.track.dbId === selectedDbId) ?? null;
|
|
3320
3534
|
const canCreate = !isCreating && !!selected;
|
|
3321
|
-
const handleClose = (0,
|
|
3535
|
+
const handleClose = (0, import_react15.useCallback)(() => {
|
|
3322
3536
|
if (!isCreating) onClose();
|
|
3323
3537
|
}, [isCreating, onClose]);
|
|
3324
|
-
const handleCreate = (0,
|
|
3538
|
+
const handleCreate = (0, import_react15.useCallback)(async () => {
|
|
3325
3539
|
if (!selected) return;
|
|
3326
3540
|
setIsCreating(true);
|
|
3327
3541
|
setError(null);
|
|
@@ -3342,16 +3556,16 @@ function FadeModal({
|
|
|
3342
3556
|
if (!open) return null;
|
|
3343
3557
|
const renderSection = (heading, list, section) => {
|
|
3344
3558
|
if (list.length === 0) return null;
|
|
3345
|
-
return /* @__PURE__ */ (0,
|
|
3346
|
-
/* @__PURE__ */ (0,
|
|
3347
|
-
/* @__PURE__ */ (0,
|
|
3559
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "block", children: [
|
|
3560
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: heading }),
|
|
3561
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3348
3562
|
"div",
|
|
3349
3563
|
{
|
|
3350
3564
|
role: "radiogroup",
|
|
3351
3565
|
"aria-label": heading,
|
|
3352
3566
|
"data-testid": `${testIdPrefix}-${section === "out" ? "fade-out" : "fade-in"}-list`,
|
|
3353
3567
|
className: "mt-1 space-y-1 max-h-40 overflow-y-auto pr-0.5",
|
|
3354
|
-
children: list.map((t) => /* @__PURE__ */ (0,
|
|
3568
|
+
children: list.map((t) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3355
3569
|
OrphanRow,
|
|
3356
3570
|
{
|
|
3357
3571
|
track: t,
|
|
@@ -3367,32 +3581,32 @@ function FadeModal({
|
|
|
3367
3581
|
)
|
|
3368
3582
|
] });
|
|
3369
3583
|
};
|
|
3370
|
-
return /* @__PURE__ */ (0,
|
|
3584
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Modal, { open, onClose: handleClose, testIdPrefix, initialFocusRef: cancelRef, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
3371
3585
|
"div",
|
|
3372
3586
|
{
|
|
3373
3587
|
className: "bg-sas-panel border border-sas-border rounded-md shadow-xl w-[420px] max-w-[92vw] p-4 space-y-3",
|
|
3374
3588
|
onClick: (e) => e.stopPropagation(),
|
|
3375
3589
|
"data-testid": `${testIdPrefix}-box`,
|
|
3376
3590
|
children: [
|
|
3377
|
-
/* @__PURE__ */ (0,
|
|
3378
|
-
/* @__PURE__ */ (0,
|
|
3591
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("h3", { className: "text-sm font-bold text-sas-text", children: "Add fade" }),
|
|
3592
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("p", { className: "text-[11px] text-sas-muted leading-relaxed", children: [
|
|
3379
3593
|
"Tracks with no counterpart between",
|
|
3380
3594
|
" ",
|
|
3381
|
-
/* @__PURE__ */ (0,
|
|
3595
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-sas-text", children: fromLabel ?? "the origin scene" }),
|
|
3382
3596
|
" and",
|
|
3383
3597
|
" ",
|
|
3384
|
-
/* @__PURE__ */ (0,
|
|
3598
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-sas-text", children: toLabel ?? "the target scene" }),
|
|
3385
3599
|
" can gracefully fade out (leaving) or fade in (entering) across this transition."
|
|
3386
3600
|
] }),
|
|
3387
|
-
load.status === "loading" && /* @__PURE__ */ (0,
|
|
3388
|
-
load.status === "error" && /* @__PURE__ */ (0,
|
|
3389
|
-
load.status === "ready" && (allOrphans.length === 0 ? /* @__PURE__ */ (0,
|
|
3601
|
+
load.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "text-xs text-sas-muted py-4 text-center", children: "Loading tracks\u2026" }),
|
|
3602
|
+
load.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "text-xs text-sas-danger py-4 text-center", children: load.message }),
|
|
3603
|
+
load.status === "ready" && (allOrphans.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "text-xs text-sas-muted py-4 text-center", "data-testid": `${testIdPrefix}-empty`, children: "Every track has a counterpart in the other scene \u2014 nothing to fade. Use \u201C+ Crossfade\u201D to bridge matching tracks." }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
|
|
3390
3604
|
renderSection(`Fade out${fromLabel ? ` (from ${fromLabel})` : ""}`, fadeOut, "out"),
|
|
3391
3605
|
renderSection(`Fade in${toLabel ? ` (to ${toLabel})` : ""}`, fadeIn, "in")
|
|
3392
3606
|
] })),
|
|
3393
|
-
error && /* @__PURE__ */ (0,
|
|
3394
|
-
/* @__PURE__ */ (0,
|
|
3395
|
-
/* @__PURE__ */ (0,
|
|
3607
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "text-xs text-sas-danger", "data-testid": `${testIdPrefix}-error`, children: error }),
|
|
3608
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex justify-end gap-2 pt-1", children: [
|
|
3609
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3396
3610
|
"button",
|
|
3397
3611
|
{
|
|
3398
3612
|
ref: cancelRef,
|
|
@@ -3403,7 +3617,7 @@ function FadeModal({
|
|
|
3403
3617
|
children: "Cancel"
|
|
3404
3618
|
}
|
|
3405
3619
|
),
|
|
3406
|
-
/* @__PURE__ */ (0,
|
|
3620
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3407
3621
|
"button",
|
|
3408
3622
|
{
|
|
3409
3623
|
"data-testid": `${testIdPrefix}-confirm`,
|
|
@@ -3420,8 +3634,8 @@ function FadeModal({
|
|
|
3420
3634
|
}
|
|
3421
3635
|
|
|
3422
3636
|
// src/components/ImportTrackModal.tsx
|
|
3423
|
-
var
|
|
3424
|
-
var
|
|
3637
|
+
var import_react16 = require("react");
|
|
3638
|
+
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
3425
3639
|
function ImportTrackModal({
|
|
3426
3640
|
host,
|
|
3427
3641
|
open,
|
|
@@ -3433,10 +3647,10 @@ function ImportTrackModal({
|
|
|
3433
3647
|
onPick,
|
|
3434
3648
|
onPortTrack
|
|
3435
3649
|
}) {
|
|
3436
|
-
const [load, setLoad] = (0,
|
|
3437
|
-
const [selectedSceneId, setSelectedSceneId] = (0,
|
|
3438
|
-
const [importingTrackId, setImportingTrackId] = (0,
|
|
3439
|
-
const refresh = (0,
|
|
3650
|
+
const [load, setLoad] = (0, import_react16.useState)({ status: "loading" });
|
|
3651
|
+
const [selectedSceneId, setSelectedSceneId] = (0, import_react16.useState)(null);
|
|
3652
|
+
const [importingTrackId, setImportingTrackId] = (0, import_react16.useState)(null);
|
|
3653
|
+
const refresh = (0, import_react16.useCallback)(async () => {
|
|
3440
3654
|
if (!host.listImportableTracks) {
|
|
3441
3655
|
setLoad({ status: "error", message: "This host does not support importing tracks." });
|
|
3442
3656
|
return;
|
|
@@ -3452,14 +3666,14 @@ function ImportTrackModal({
|
|
|
3452
3666
|
setLoad({ status: "error", message: err instanceof Error ? err.message : "Failed to load scenes." });
|
|
3453
3667
|
}
|
|
3454
3668
|
}, [host, mode, onPortTrack]);
|
|
3455
|
-
(0,
|
|
3669
|
+
(0, import_react16.useEffect)(() => {
|
|
3456
3670
|
if (open) {
|
|
3457
3671
|
setSelectedSceneId(null);
|
|
3458
3672
|
setImportingTrackId(null);
|
|
3459
3673
|
void refresh();
|
|
3460
3674
|
}
|
|
3461
3675
|
}, [open, refresh]);
|
|
3462
|
-
const handleImport = (0,
|
|
3676
|
+
const handleImport = (0, import_react16.useCallback)(
|
|
3463
3677
|
async (track, sourceSceneId, sceneName, isSameScene) => {
|
|
3464
3678
|
if (isSameScene && onPortTrack) {
|
|
3465
3679
|
if (!track.importable) return;
|
|
@@ -3500,16 +3714,16 @@ function ImportTrackModal({
|
|
|
3500
3714
|
if (!open) return null;
|
|
3501
3715
|
const scenes = load.status === "ready" ? load.scenes : [];
|
|
3502
3716
|
const selectedScene = scenes.find((s) => s.sceneId === selectedSceneId) ?? null;
|
|
3503
|
-
return /* @__PURE__ */ (0,
|
|
3717
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Modal, { open, onClose, testIdPrefix, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
|
|
3504
3718
|
"div",
|
|
3505
3719
|
{
|
|
3506
3720
|
className: "w-[420px] max-h-[70vh] overflow-hidden flex flex-col rounded-md border border-sas-border bg-sas-panel shadow-xl",
|
|
3507
3721
|
onClick: (e) => e.stopPropagation(),
|
|
3508
3722
|
"data-testid": `${testIdPrefix}-modal`,
|
|
3509
3723
|
children: [
|
|
3510
|
-
/* @__PURE__ */ (0,
|
|
3511
|
-
/* @__PURE__ */ (0,
|
|
3512
|
-
selectedScene && /* @__PURE__ */ (0,
|
|
3724
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center justify-between px-3 py-2 border-b border-sas-border", children: [
|
|
3725
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
3726
|
+
selectedScene && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
3513
3727
|
"button",
|
|
3514
3728
|
{
|
|
3515
3729
|
className: "text-sas-muted hover:text-sas-accent text-xs",
|
|
@@ -3518,9 +3732,9 @@ function ImportTrackModal({
|
|
|
3518
3732
|
children: "\u2190"
|
|
3519
3733
|
}
|
|
3520
3734
|
),
|
|
3521
|
-
/* @__PURE__ */ (0,
|
|
3735
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-sm font-medium text-sas-text", children: selectedScene ? selectedScene.sceneName : title })
|
|
3522
3736
|
] }),
|
|
3523
|
-
/* @__PURE__ */ (0,
|
|
3737
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
3524
3738
|
"button",
|
|
3525
3739
|
{
|
|
3526
3740
|
className: "text-sas-muted hover:text-sas-accent text-sm",
|
|
@@ -3530,30 +3744,30 @@ function ImportTrackModal({
|
|
|
3530
3744
|
}
|
|
3531
3745
|
)
|
|
3532
3746
|
] }),
|
|
3533
|
-
/* @__PURE__ */ (0,
|
|
3534
|
-
load.status === "loading" && /* @__PURE__ */ (0,
|
|
3535
|
-
load.status === "error" && /* @__PURE__ */ (0,
|
|
3536
|
-
load.status === "ready" && scenes.length === 0 && /* @__PURE__ */ (0,
|
|
3537
|
-
load.status === "ready" && scenes.length > 0 && !selectedScene && /* @__PURE__ */ (0,
|
|
3747
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "overflow-y-auto p-2 flex-1", children: [
|
|
3748
|
+
load.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "py-8 text-center text-xs text-sas-muted", "data-testid": `${testIdPrefix}-loading`, children: "Loading scenes\u2026" }),
|
|
3749
|
+
load.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "py-8 text-center text-xs text-red-400", "data-testid": `${testIdPrefix}-error`, children: load.message }),
|
|
3750
|
+
load.status === "ready" && scenes.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "py-8 text-center text-xs text-sas-muted", "data-testid": `${testIdPrefix}-empty`, children: mode === "sound" ? "No other scenes have a sound to import." : "No other scenes have a compatible track to import." }),
|
|
3751
|
+
load.status === "ready" && scenes.length > 0 && !selectedScene && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("ul", { className: "flex flex-col gap-1", "data-testid": `${testIdPrefix}-scene-list`, children: scenes.map((scene) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
|
|
3538
3752
|
"button",
|
|
3539
3753
|
{
|
|
3540
3754
|
className: "w-full flex items-center justify-between px-2 py-1.5 rounded-sm border border-sas-border bg-sas-panel-alt text-left text-xs text-sas-text hover:border-sas-accent hover:text-sas-accent transition-colors",
|
|
3541
3755
|
onClick: () => setSelectedSceneId(scene.sceneId),
|
|
3542
3756
|
"data-testid": `${testIdPrefix}-scene`,
|
|
3543
3757
|
children: [
|
|
3544
|
-
/* @__PURE__ */ (0,
|
|
3545
|
-
/* @__PURE__ */ (0,
|
|
3758
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "truncate", children: scene.sceneName }),
|
|
3759
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "text-sas-muted", children: [
|
|
3546
3760
|
scene.tracks.length,
|
|
3547
3761
|
" \u2192"
|
|
3548
3762
|
] })
|
|
3549
3763
|
]
|
|
3550
3764
|
}
|
|
3551
3765
|
) }, scene.sceneId)) }),
|
|
3552
|
-
selectedScene && /* @__PURE__ */ (0,
|
|
3766
|
+
selectedScene && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("ul", { className: "flex flex-col gap-1", "data-testid": `${testIdPrefix}-track-list`, children: selectedScene.tracks.map((track) => {
|
|
3553
3767
|
const busy = importingTrackId === track.trackId;
|
|
3554
3768
|
const gated = mode === "track" && !track.importable;
|
|
3555
3769
|
const disabled = gated || busy;
|
|
3556
|
-
return /* @__PURE__ */ (0,
|
|
3770
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
|
|
3557
3771
|
"button",
|
|
3558
3772
|
{
|
|
3559
3773
|
className: `w-full flex items-center justify-between px-2 py-1.5 rounded-sm border text-left text-xs transition-colors ${disabled ? "bg-sas-panel border-sas-border text-sas-muted/50 cursor-not-allowed" : "bg-sas-panel-alt border-sas-border text-sas-text hover:border-sas-accent hover:text-sas-accent"}`,
|
|
@@ -3563,14 +3777,14 @@ function ImportTrackModal({
|
|
|
3563
3777
|
"data-testid": `${testIdPrefix}-track`,
|
|
3564
3778
|
"data-importable": mode === "sound" || track.importable ? "true" : "false",
|
|
3565
3779
|
children: [
|
|
3566
|
-
/* @__PURE__ */ (0,
|
|
3780
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "truncate", children: [
|
|
3567
3781
|
track.name,
|
|
3568
|
-
track.role ? /* @__PURE__ */ (0,
|
|
3782
|
+
track.role ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "text-sas-muted", children: [
|
|
3569
3783
|
" \xB7 ",
|
|
3570
3784
|
track.role
|
|
3571
3785
|
] }) : null
|
|
3572
3786
|
] }),
|
|
3573
|
-
busy ? /* @__PURE__ */ (0,
|
|
3787
|
+
busy ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-sas-muted", children: "\u2026" }) : gated ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-sas-muted", children: "\u2298" }) : null
|
|
3574
3788
|
]
|
|
3575
3789
|
}
|
|
3576
3790
|
) }, track.dbId);
|
|
@@ -3582,8 +3796,8 @@ function ImportTrackModal({
|
|
|
3582
3796
|
}
|
|
3583
3797
|
|
|
3584
3798
|
// src/components/CrossfadeModal.tsx
|
|
3585
|
-
var
|
|
3586
|
-
var
|
|
3799
|
+
var import_react17 = require("react");
|
|
3800
|
+
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
3587
3801
|
function shortId2(dbId) {
|
|
3588
3802
|
return dbId.length > 8 ? dbId.slice(0, 8) : dbId;
|
|
3589
3803
|
}
|
|
@@ -3596,7 +3810,7 @@ function CandidateRow({
|
|
|
3596
3810
|
}) {
|
|
3597
3811
|
const primary = track.prompt?.trim() || track.name;
|
|
3598
3812
|
const meta = [track.role, shortId2(track.dbId)].filter(Boolean).join(" \xB7 ");
|
|
3599
|
-
return /* @__PURE__ */ (0,
|
|
3813
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
|
|
3600
3814
|
"button",
|
|
3601
3815
|
{
|
|
3602
3816
|
type: "button",
|
|
@@ -3608,8 +3822,8 @@ function CandidateRow({
|
|
|
3608
3822
|
disabled,
|
|
3609
3823
|
className: `w-full text-left px-2 py-1.5 rounded-sm border transition-colors disabled:opacity-50 ${selected ? "bg-sas-accent/15 border-sas-accent" : "bg-sas-panel border-sas-border hover:border-sas-accent/50"}`,
|
|
3610
3824
|
children: [
|
|
3611
|
-
/* @__PURE__ */ (0,
|
|
3612
|
-
meta && /* @__PURE__ */ (0,
|
|
3825
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "text-xs text-sas-text truncate", title: primary, children: primary }),
|
|
3826
|
+
meta && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "text-[10px] text-sas-muted truncate mt-0.5", title: track.dbId, children: meta })
|
|
3613
3827
|
]
|
|
3614
3828
|
}
|
|
3615
3829
|
);
|
|
@@ -3626,15 +3840,15 @@ function CrossfadeModal({
|
|
|
3626
3840
|
onCreate,
|
|
3627
3841
|
testIdPrefix = "crossfade-modal"
|
|
3628
3842
|
}) {
|
|
3629
|
-
const [load, setLoad] = (0,
|
|
3630
|
-
const [originDbId, setOriginDbId] = (0,
|
|
3631
|
-
const [targetDbId, setTargetDbId] = (0,
|
|
3632
|
-
const [isCreating, setIsCreating] = (0,
|
|
3633
|
-
const [error, setError] = (0,
|
|
3634
|
-
const [fromName, setFromName] = (0,
|
|
3635
|
-
const [toName, setToName] = (0,
|
|
3636
|
-
const cancelRef = (0,
|
|
3637
|
-
const refresh = (0,
|
|
3843
|
+
const [load, setLoad] = (0, import_react17.useState)({ status: "loading" });
|
|
3844
|
+
const [originDbId, setOriginDbId] = (0, import_react17.useState)("");
|
|
3845
|
+
const [targetDbId, setTargetDbId] = (0, import_react17.useState)("");
|
|
3846
|
+
const [isCreating, setIsCreating] = (0, import_react17.useState)(false);
|
|
3847
|
+
const [error, setError] = (0, import_react17.useState)(null);
|
|
3848
|
+
const [fromName, setFromName] = (0, import_react17.useState)(null);
|
|
3849
|
+
const [toName, setToName] = (0, import_react17.useState)(null);
|
|
3850
|
+
const cancelRef = (0, import_react17.useRef)(null);
|
|
3851
|
+
const refresh = (0, import_react17.useCallback)(async () => {
|
|
3638
3852
|
if (!host.listSceneFamilyTracks) {
|
|
3639
3853
|
setLoad({ status: "error", message: "This host does not support crossfade tracks." });
|
|
3640
3854
|
return;
|
|
@@ -3654,7 +3868,7 @@ function CrossfadeModal({
|
|
|
3654
3868
|
setLoad({ status: "error", message: err instanceof Error ? err.message : "Failed to load tracks." });
|
|
3655
3869
|
}
|
|
3656
3870
|
}, [host, fromSceneId, toSceneId]);
|
|
3657
|
-
(0,
|
|
3871
|
+
(0, import_react17.useEffect)(() => {
|
|
3658
3872
|
if (open) {
|
|
3659
3873
|
setError(null);
|
|
3660
3874
|
setIsCreating(false);
|
|
@@ -3663,21 +3877,21 @@ function CrossfadeModal({
|
|
|
3663
3877
|
void refresh();
|
|
3664
3878
|
}
|
|
3665
3879
|
}, [open, refresh]);
|
|
3666
|
-
const excludeSet = (0,
|
|
3667
|
-
const originCandidates = (0,
|
|
3880
|
+
const excludeSet = (0, import_react17.useMemo)(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);
|
|
3881
|
+
const originCandidates = (0, import_react17.useMemo)(
|
|
3668
3882
|
() => load.status === "ready" ? load.origin.filter((t) => !excludeSet.has(t.dbId)) : [],
|
|
3669
3883
|
[load, excludeSet]
|
|
3670
3884
|
);
|
|
3671
|
-
const targetCandidates = (0,
|
|
3885
|
+
const targetCandidates = (0, import_react17.useMemo)(
|
|
3672
3886
|
() => load.status === "ready" ? load.target.filter((t) => !excludeSet.has(t.dbId)) : [],
|
|
3673
3887
|
[load, excludeSet]
|
|
3674
3888
|
);
|
|
3675
|
-
(0,
|
|
3889
|
+
(0, import_react17.useEffect)(() => {
|
|
3676
3890
|
if (!originCandidates.some((t) => t.dbId === originDbId)) {
|
|
3677
3891
|
setOriginDbId(originCandidates[0]?.dbId ?? "");
|
|
3678
3892
|
}
|
|
3679
3893
|
}, [originCandidates, originDbId]);
|
|
3680
|
-
(0,
|
|
3894
|
+
(0, import_react17.useEffect)(() => {
|
|
3681
3895
|
if (!targetCandidates.some((t) => t.dbId === targetDbId)) {
|
|
3682
3896
|
setTargetDbId(targetCandidates[0]?.dbId ?? "");
|
|
3683
3897
|
}
|
|
@@ -3685,10 +3899,10 @@ function CrossfadeModal({
|
|
|
3685
3899
|
const originTrack = originCandidates.find((t) => t.dbId === originDbId) ?? null;
|
|
3686
3900
|
const targetTrack = targetCandidates.find((t) => t.dbId === targetDbId) ?? null;
|
|
3687
3901
|
const canCreate = !isCreating && !!originTrack && !!targetTrack;
|
|
3688
|
-
const handleClose = (0,
|
|
3902
|
+
const handleClose = (0, import_react17.useCallback)(() => {
|
|
3689
3903
|
if (!isCreating) onClose();
|
|
3690
3904
|
}, [isCreating, onClose]);
|
|
3691
|
-
const handleCreate = (0,
|
|
3905
|
+
const handleCreate = (0, import_react17.useCallback)(async () => {
|
|
3692
3906
|
if (!originTrack || !targetTrack) return;
|
|
3693
3907
|
setIsCreating(true);
|
|
3694
3908
|
setError(null);
|
|
@@ -3706,26 +3920,26 @@ function CrossfadeModal({
|
|
|
3706
3920
|
const fromLabel = fromName ?? fromSceneName ?? null;
|
|
3707
3921
|
const toLabel = toName ?? toSceneName ?? null;
|
|
3708
3922
|
if (!open) return null;
|
|
3709
|
-
return /* @__PURE__ */ (0,
|
|
3923
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Modal, { open, onClose: handleClose, testIdPrefix, initialFocusRef: cancelRef, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
|
|
3710
3924
|
"div",
|
|
3711
3925
|
{
|
|
3712
3926
|
className: "bg-sas-panel border border-sas-border rounded-md shadow-xl w-[420px] max-w-[92vw] p-4 space-y-3",
|
|
3713
3927
|
onClick: (e) => e.stopPropagation(),
|
|
3714
3928
|
"data-testid": `${testIdPrefix}-box`,
|
|
3715
3929
|
children: [
|
|
3716
|
-
/* @__PURE__ */ (0,
|
|
3717
|
-
/* @__PURE__ */ (0,
|
|
3930
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h3", { className: "text-sm font-bold text-sas-text", children: "Add crossfade" }),
|
|
3931
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("p", { className: "text-[11px] text-sas-muted leading-relaxed", children: [
|
|
3718
3932
|
"Bridge a track from",
|
|
3719
3933
|
" ",
|
|
3720
|
-
/* @__PURE__ */ (0,
|
|
3934
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "text-sas-text", children: fromLabel ?? "the origin scene" }),
|
|
3721
3935
|
" into one from",
|
|
3722
3936
|
" ",
|
|
3723
|
-
/* @__PURE__ */ (0,
|
|
3937
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "text-sas-text", children: toLabel ?? "the target scene" }),
|
|
3724
3938
|
". Both layers share one generated part; each keeps its own preset."
|
|
3725
3939
|
] }),
|
|
3726
|
-
load.status === "loading" && /* @__PURE__ */ (0,
|
|
3727
|
-
load.status === "error" && /* @__PURE__ */ (0,
|
|
3728
|
-
load.status === "ready" && (originCandidates.length === 0 ? /* @__PURE__ */ (0,
|
|
3940
|
+
load.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "text-xs text-sas-muted py-4 text-center", children: "Loading tracks\u2026" }),
|
|
3941
|
+
load.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "text-xs text-sas-danger py-4 text-center", children: load.message }),
|
|
3942
|
+
load.status === "ready" && (originCandidates.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
|
|
3729
3943
|
"div",
|
|
3730
3944
|
{
|
|
3731
3945
|
className: "text-xs text-sas-muted py-4 text-center",
|
|
@@ -3736,20 +3950,20 @@ function CrossfadeModal({
|
|
|
3736
3950
|
". Add one (or free one from another crossfade) first."
|
|
3737
3951
|
]
|
|
3738
3952
|
}
|
|
3739
|
-
) : /* @__PURE__ */ (0,
|
|
3740
|
-
/* @__PURE__ */ (0,
|
|
3741
|
-
/* @__PURE__ */ (0,
|
|
3953
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
|
|
3954
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "block", children: [
|
|
3955
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: [
|
|
3742
3956
|
"Origin ",
|
|
3743
3957
|
fromLabel ? `(${fromLabel})` : "(top)"
|
|
3744
3958
|
] }),
|
|
3745
|
-
/* @__PURE__ */ (0,
|
|
3959
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
3746
3960
|
"div",
|
|
3747
3961
|
{
|
|
3748
3962
|
role: "radiogroup",
|
|
3749
3963
|
"aria-label": "Origin track",
|
|
3750
3964
|
"data-testid": `${testIdPrefix}-origin-list`,
|
|
3751
3965
|
className: "mt-1 space-y-1 max-h-40 overflow-y-auto pr-0.5",
|
|
3752
|
-
children: originCandidates.map((t) => /* @__PURE__ */ (0,
|
|
3966
|
+
children: originCandidates.map((t) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
3753
3967
|
CandidateRow,
|
|
3754
3968
|
{
|
|
3755
3969
|
track: t,
|
|
@@ -3763,23 +3977,23 @@ function CrossfadeModal({
|
|
|
3763
3977
|
}
|
|
3764
3978
|
)
|
|
3765
3979
|
] }),
|
|
3766
|
-
/* @__PURE__ */ (0,
|
|
3767
|
-
/* @__PURE__ */ (0,
|
|
3980
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "block", children: [
|
|
3981
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: [
|
|
3768
3982
|
"Target ",
|
|
3769
3983
|
toLabel ? `(${toLabel})` : "(bottom)"
|
|
3770
3984
|
] }),
|
|
3771
|
-
targetCandidates.length === 0 ? /* @__PURE__ */ (0,
|
|
3985
|
+
targetCandidates.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "text-xs text-sas-danger mt-0.5", "data-testid": `${testIdPrefix}-empty-target`, children: [
|
|
3772
3986
|
"No available tracks in ",
|
|
3773
3987
|
toLabel ?? "the target scene",
|
|
3774
3988
|
" to crossfade into."
|
|
3775
|
-
] }) : /* @__PURE__ */ (0,
|
|
3989
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
3776
3990
|
"div",
|
|
3777
3991
|
{
|
|
3778
3992
|
role: "radiogroup",
|
|
3779
3993
|
"aria-label": "Target track",
|
|
3780
3994
|
"data-testid": `${testIdPrefix}-target-list`,
|
|
3781
3995
|
className: "mt-1 space-y-1 max-h-40 overflow-y-auto pr-0.5",
|
|
3782
|
-
children: targetCandidates.map((t) => /* @__PURE__ */ (0,
|
|
3996
|
+
children: targetCandidates.map((t) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
3783
3997
|
CandidateRow,
|
|
3784
3998
|
{
|
|
3785
3999
|
track: t,
|
|
@@ -3794,9 +4008,9 @@ function CrossfadeModal({
|
|
|
3794
4008
|
)
|
|
3795
4009
|
] })
|
|
3796
4010
|
] })),
|
|
3797
|
-
error && /* @__PURE__ */ (0,
|
|
3798
|
-
/* @__PURE__ */ (0,
|
|
3799
|
-
/* @__PURE__ */ (0,
|
|
4011
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "text-xs text-sas-danger", "data-testid": `${testIdPrefix}-error`, children: error }),
|
|
4012
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex justify-end gap-2 pt-1", children: [
|
|
4013
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
3800
4014
|
"button",
|
|
3801
4015
|
{
|
|
3802
4016
|
ref: cancelRef,
|
|
@@ -3807,7 +4021,7 @@ function CrossfadeModal({
|
|
|
3807
4021
|
children: "Cancel"
|
|
3808
4022
|
}
|
|
3809
4023
|
),
|
|
3810
|
-
/* @__PURE__ */ (0,
|
|
4024
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
3811
4025
|
"button",
|
|
3812
4026
|
{
|
|
3813
4027
|
"data-testid": `${testIdPrefix}-confirm`,
|
|
@@ -3824,10 +4038,10 @@ function CrossfadeModal({
|
|
|
3824
4038
|
}
|
|
3825
4039
|
|
|
3826
4040
|
// src/components/TransitionDesigner.tsx
|
|
3827
|
-
var
|
|
4041
|
+
var import_react19 = require("react");
|
|
3828
4042
|
|
|
3829
4043
|
// src/hooks/useTrackReorder.ts
|
|
3830
|
-
var
|
|
4044
|
+
var import_react18 = require("react");
|
|
3831
4045
|
function moveItem(arr, from, to) {
|
|
3832
4046
|
const next = arr.slice();
|
|
3833
4047
|
if (from === to || from < 0 || to < 0 || from >= next.length || to >= next.length) {
|
|
@@ -3844,12 +4058,12 @@ function useTrackReorder({
|
|
|
3844
4058
|
getId,
|
|
3845
4059
|
onError
|
|
3846
4060
|
}) {
|
|
3847
|
-
const [draggingIndex, setDraggingIndex] = (0,
|
|
3848
|
-
const [dragOverIndex, setDragOverIndex] = (0,
|
|
3849
|
-
const fromRef = (0,
|
|
3850
|
-
const itemsRef = (0,
|
|
4061
|
+
const [draggingIndex, setDraggingIndex] = (0, import_react18.useState)(null);
|
|
4062
|
+
const [dragOverIndex, setDragOverIndex] = (0, import_react18.useState)(null);
|
|
4063
|
+
const fromRef = (0, import_react18.useRef)(null);
|
|
4064
|
+
const itemsRef = (0, import_react18.useRef)(items);
|
|
3851
4065
|
itemsRef.current = items;
|
|
3852
|
-
const dragPropsFor = (0,
|
|
4066
|
+
const dragPropsFor = (0, import_react18.useCallback)(
|
|
3853
4067
|
(index) => ({
|
|
3854
4068
|
handleProps: {
|
|
3855
4069
|
draggable: true,
|
|
@@ -4031,7 +4245,7 @@ function dbIdsFromKeys(keys) {
|
|
|
4031
4245
|
}
|
|
4032
4246
|
|
|
4033
4247
|
// src/components/TransitionDesigner.tsx
|
|
4034
|
-
var
|
|
4248
|
+
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
4035
4249
|
var CROSSFADE_ESTIMATE_MS = 15e3;
|
|
4036
4250
|
var FADE_ESTIMATE_MS = 11e3;
|
|
4037
4251
|
var CREATE_ALL_CONCURRENCY = 5;
|
|
@@ -4052,56 +4266,67 @@ function TransitionDesigner({
|
|
|
4052
4266
|
onCreateCrossfade,
|
|
4053
4267
|
onCreateFade,
|
|
4054
4268
|
onCreateAudioTransition,
|
|
4269
|
+
mapColumnSubjects,
|
|
4270
|
+
fadeEstimateMs,
|
|
4055
4271
|
familyLabel,
|
|
4056
4272
|
testIdPrefix = "transition-designer"
|
|
4057
4273
|
}) {
|
|
4058
|
-
const
|
|
4059
|
-
const [
|
|
4060
|
-
const [
|
|
4061
|
-
const [
|
|
4062
|
-
const [
|
|
4063
|
-
const [
|
|
4064
|
-
const [
|
|
4065
|
-
const [
|
|
4066
|
-
const
|
|
4274
|
+
const fadeOnly = !onCreateCrossfade;
|
|
4275
|
+
const [load, setLoad] = (0, import_react19.useState)({ status: "loading" });
|
|
4276
|
+
const [fromName, setFromName] = (0, import_react19.useState)(null);
|
|
4277
|
+
const [toName, setToName] = (0, import_react19.useState)(null);
|
|
4278
|
+
const [originSlots, setOriginSlots] = (0, import_react19.useState)([]);
|
|
4279
|
+
const [targetSlots, setTargetSlots] = (0, import_react19.useState)([]);
|
|
4280
|
+
const [creatingKeys, setCreatingKeys] = (0, import_react19.useState)(() => /* @__PURE__ */ new Set());
|
|
4281
|
+
const [rowErrors, setRowErrors] = (0, import_react19.useState)({});
|
|
4282
|
+
const [rowEffects, setRowEffects] = (0, import_react19.useState)({});
|
|
4283
|
+
const rowEffectsRef = (0, import_react19.useRef)(rowEffects);
|
|
4067
4284
|
rowEffectsRef.current = rowEffects;
|
|
4068
4285
|
const audioEffectsEnabled = !!onCreateAudioTransition;
|
|
4069
|
-
const excludeRef = (0,
|
|
4286
|
+
const excludeRef = (0, import_react19.useRef)(excludeSourceDbIds);
|
|
4070
4287
|
excludeRef.current = excludeSourceDbIds;
|
|
4071
|
-
const originSlotsRef = (0,
|
|
4288
|
+
const originSlotsRef = (0, import_react19.useRef)(originSlots);
|
|
4072
4289
|
originSlotsRef.current = originSlots;
|
|
4073
|
-
const targetSlotsRef = (0,
|
|
4290
|
+
const targetSlotsRef = (0, import_react19.useRef)(targetSlots);
|
|
4074
4291
|
targetSlotsRef.current = targetSlots;
|
|
4075
|
-
const creatingKeysRef = (0,
|
|
4292
|
+
const creatingKeysRef = (0, import_react19.useRef)(creatingKeys);
|
|
4076
4293
|
creatingKeysRef.current = creatingKeys;
|
|
4077
|
-
const dragRef = (0,
|
|
4078
|
-
const [dragging, setDragging] = (0,
|
|
4079
|
-
const [dragOver, setDragOver] = (0,
|
|
4080
|
-
const excludeSet = (0,
|
|
4081
|
-
const originPool = (0,
|
|
4294
|
+
const dragRef = (0, import_react19.useRef)(null);
|
|
4295
|
+
const [dragging, setDragging] = (0, import_react19.useState)(null);
|
|
4296
|
+
const [dragOver, setDragOver] = (0, import_react19.useState)(null);
|
|
4297
|
+
const excludeSet = (0, import_react19.useMemo)(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);
|
|
4298
|
+
const originPool = (0, import_react19.useMemo)(
|
|
4082
4299
|
() => load.status === "ready" ? load.origin.filter((t) => !excludeSet.has(t.dbId)) : [],
|
|
4083
4300
|
[load, excludeSet]
|
|
4084
4301
|
);
|
|
4085
|
-
const targetPool = (0,
|
|
4302
|
+
const targetPool = (0, import_react19.useMemo)(
|
|
4086
4303
|
() => load.status === "ready" ? load.target.filter((t) => !excludeSet.has(t.dbId)) : [],
|
|
4087
4304
|
[load, excludeSet]
|
|
4088
4305
|
);
|
|
4089
|
-
const originById = (0,
|
|
4090
|
-
const targetById = (0,
|
|
4091
|
-
const originByIdRef = (0,
|
|
4306
|
+
const originById = (0, import_react19.useMemo)(() => new Map(originPool.map((t) => [t.dbId, t])), [originPool]);
|
|
4307
|
+
const targetById = (0, import_react19.useMemo)(() => new Map(targetPool.map((t) => [t.dbId, t])), [targetPool]);
|
|
4308
|
+
const originByIdRef = (0, import_react19.useRef)(originById);
|
|
4092
4309
|
originByIdRef.current = originById;
|
|
4093
|
-
const targetByIdRef = (0,
|
|
4310
|
+
const targetByIdRef = (0, import_react19.useRef)(targetById);
|
|
4094
4311
|
targetByIdRef.current = targetById;
|
|
4095
|
-
const refresh = (0,
|
|
4312
|
+
const refresh = (0, import_react19.useCallback)(async () => {
|
|
4096
4313
|
if (!host.listSceneFamilyTracks) {
|
|
4097
4314
|
setLoad({ status: "error", message: "This host does not support transition tracks." });
|
|
4098
4315
|
return;
|
|
4099
4316
|
}
|
|
4100
4317
|
setLoad({ status: "loading" });
|
|
4101
4318
|
try {
|
|
4102
|
-
|
|
4319
|
+
let [origin, target] = await Promise.all([
|
|
4103
4320
|
host.listSceneFamilyTracks(fromSceneId),
|
|
4104
|
-
host.listSceneFamilyTracks(toSceneId)
|
|
4321
|
+
host.listSceneFamilyTracks(toSceneId)
|
|
4322
|
+
]);
|
|
4323
|
+
if (mapColumnSubjects) {
|
|
4324
|
+
[origin, target] = await Promise.all([
|
|
4325
|
+
mapColumnSubjects(fromSceneId, origin),
|
|
4326
|
+
mapColumnSubjects(toSceneId, target)
|
|
4327
|
+
]);
|
|
4328
|
+
}
|
|
4329
|
+
const [fName, tName, draftRaw] = await Promise.all([
|
|
4105
4330
|
host.getSceneName ? host.getSceneName(fromSceneId) : Promise.resolve(null),
|
|
4106
4331
|
host.getSceneName ? host.getSceneName(toSceneId) : Promise.resolve(null),
|
|
4107
4332
|
host.getSceneData ? host.getSceneData(transitionSceneId, TRANSITION_DESIGNER_DRAFT_KEY) : Promise.resolve(null)
|
|
@@ -4126,11 +4351,11 @@ function TransitionDesigner({
|
|
|
4126
4351
|
message: err instanceof Error ? err.message : "Failed to load tracks."
|
|
4127
4352
|
});
|
|
4128
4353
|
}
|
|
4129
|
-
}, [host, fromSceneId, toSceneId, transitionSceneId]);
|
|
4130
|
-
(0,
|
|
4354
|
+
}, [host, fromSceneId, toSceneId, transitionSceneId, mapColumnSubjects]);
|
|
4355
|
+
(0, import_react19.useEffect)(() => {
|
|
4131
4356
|
void refresh();
|
|
4132
4357
|
}, [refresh]);
|
|
4133
|
-
(0,
|
|
4358
|
+
(0, import_react19.useEffect)(() => {
|
|
4134
4359
|
if (load.status !== "ready") return;
|
|
4135
4360
|
const [po, pt] = padPair(
|
|
4136
4361
|
reconcileSlots(originSlotsRef.current, originPool.map((t) => t.dbId)),
|
|
@@ -4139,7 +4364,7 @@ function TransitionDesigner({
|
|
|
4139
4364
|
if (!slotsEqual(po, originSlotsRef.current)) setOriginSlots(po);
|
|
4140
4365
|
if (!slotsEqual(pt, targetSlotsRef.current)) setTargetSlots(pt);
|
|
4141
4366
|
}, [originPool, targetPool, load.status]);
|
|
4142
|
-
const mutate = (0,
|
|
4367
|
+
const mutate = (0, import_react19.useCallback)(
|
|
4143
4368
|
(nextOrigin, nextTarget) => {
|
|
4144
4369
|
const norm = normalizeSlots(nextOrigin, nextTarget);
|
|
4145
4370
|
const [po, pt] = padPair(norm.originOrder, norm.targetOrder);
|
|
@@ -4152,7 +4377,7 @@ function TransitionDesigner({
|
|
|
4152
4377
|
},
|
|
4153
4378
|
[host, transitionSceneId]
|
|
4154
4379
|
);
|
|
4155
|
-
const setRowEffect = (0,
|
|
4380
|
+
const setRowEffect = (0, import_react19.useCallback)(
|
|
4156
4381
|
(sourceDbId, effect) => {
|
|
4157
4382
|
setRowEffects((prev) => {
|
|
4158
4383
|
const next = { ...prev, [sourceDbId]: effect };
|
|
@@ -4166,7 +4391,7 @@ function TransitionDesigner({
|
|
|
4166
4391
|
},
|
|
4167
4392
|
[host, transitionSceneId]
|
|
4168
4393
|
);
|
|
4169
|
-
const insertGapAbove = (0,
|
|
4394
|
+
const insertGapAbove = (0, import_react19.useCallback)(
|
|
4170
4395
|
(col, index) => {
|
|
4171
4396
|
const slots = col === "origin" ? originSlots : targetSlots;
|
|
4172
4397
|
const next = [...slots.slice(0, index), null, ...slots.slice(index)];
|
|
@@ -4175,7 +4400,7 @@ function TransitionDesigner({
|
|
|
4175
4400
|
},
|
|
4176
4401
|
[originSlots, targetSlots, mutate]
|
|
4177
4402
|
);
|
|
4178
|
-
const removeGap = (0,
|
|
4403
|
+
const removeGap = (0, import_react19.useCallback)(
|
|
4179
4404
|
(col, index) => {
|
|
4180
4405
|
const slots = col === "origin" ? originSlots : targetSlots;
|
|
4181
4406
|
const next = slots.filter((_, i) => i !== index);
|
|
@@ -4184,7 +4409,7 @@ function TransitionDesigner({
|
|
|
4184
4409
|
},
|
|
4185
4410
|
[originSlots, targetSlots, mutate]
|
|
4186
4411
|
);
|
|
4187
|
-
const handleDrop = (0,
|
|
4412
|
+
const handleDrop = (0, import_react19.useCallback)(
|
|
4188
4413
|
(col, to) => {
|
|
4189
4414
|
const from = dragRef.current;
|
|
4190
4415
|
dragRef.current = null;
|
|
@@ -4196,16 +4421,34 @@ function TransitionDesigner({
|
|
|
4196
4421
|
},
|
|
4197
4422
|
[originSlots, targetSlots, mutate]
|
|
4198
4423
|
);
|
|
4199
|
-
const rows = (0,
|
|
4200
|
-
const
|
|
4201
|
-
|
|
4202
|
-
|
|
4424
|
+
const rows = (0, import_react19.useMemo)(() => buildRowSlots(originSlots, targetSlots), [originSlots, targetSlots]);
|
|
4425
|
+
const fadeOnlyRows = (0, import_react19.useMemo)(
|
|
4426
|
+
() => fadeOnly ? [
|
|
4427
|
+
...originPool.map((t) => ({
|
|
4428
|
+
originId: t.dbId,
|
|
4429
|
+
targetId: null,
|
|
4430
|
+
type: "fade-out"
|
|
4431
|
+
})),
|
|
4432
|
+
...targetPool.map((t) => ({
|
|
4433
|
+
originId: null,
|
|
4434
|
+
targetId: t.dbId,
|
|
4435
|
+
type: "fade-in"
|
|
4436
|
+
}))
|
|
4437
|
+
] : [],
|
|
4438
|
+
[fadeOnly, originPool, targetPool]
|
|
4439
|
+
);
|
|
4440
|
+
const fadeOnlyRowsRef = (0, import_react19.useRef)(fadeOnlyRows);
|
|
4441
|
+
fadeOnlyRowsRef.current = fadeOnlyRows;
|
|
4442
|
+
const effectiveRows = fadeOnly ? fadeOnlyRows : rows;
|
|
4443
|
+
const creatingDbIds = (0, import_react19.useMemo)(() => dbIdsFromKeys(creatingKeys), [creatingKeys]);
|
|
4444
|
+
const eligibleCount = (0, import_react19.useMemo)(
|
|
4445
|
+
() => effectiveRows.filter((r) => {
|
|
4203
4446
|
const k = rowKey(r);
|
|
4204
4447
|
return k !== null && !creatingKeys.has(k);
|
|
4205
4448
|
}).length,
|
|
4206
|
-
[
|
|
4449
|
+
[effectiveRows, creatingKeys]
|
|
4207
4450
|
);
|
|
4208
|
-
const createRow = (0,
|
|
4451
|
+
const createRow = (0, import_react19.useCallback)(
|
|
4209
4452
|
async (row) => {
|
|
4210
4453
|
const key = rowKey(row);
|
|
4211
4454
|
if (!key || !row.type || creatingKeysRef.current.has(key)) return;
|
|
@@ -4221,6 +4464,7 @@ function TransitionDesigner({
|
|
|
4221
4464
|
const o = row.originId ? originByIdRef.current.get(row.originId) : void 0;
|
|
4222
4465
|
const t = row.targetId ? targetByIdRef.current.get(row.targetId) : void 0;
|
|
4223
4466
|
if (!o || !t) throw new Error("Track is no longer available \u2014 refresh and retry.");
|
|
4467
|
+
if (!onCreateCrossfade) throw new Error("This panel does not support crossfades.");
|
|
4224
4468
|
await onCreateCrossfade(
|
|
4225
4469
|
{ dbId: o.dbId, name: o.name, role: o.role },
|
|
4226
4470
|
{ dbId: t.dbId, name: t.name, role: t.role }
|
|
@@ -4259,8 +4503,9 @@ function TransitionDesigner({
|
|
|
4259
4503
|
},
|
|
4260
4504
|
[onCreateCrossfade, onCreateFade, onCreateAudioTransition]
|
|
4261
4505
|
);
|
|
4262
|
-
const createAll = (0,
|
|
4263
|
-
const
|
|
4506
|
+
const createAll = (0, import_react19.useCallback)(async () => {
|
|
4507
|
+
const source = fadeOnly ? fadeOnlyRowsRef.current : buildRowSlots(originSlotsRef.current, targetSlotsRef.current);
|
|
4508
|
+
const eligible = source.filter((r) => {
|
|
4264
4509
|
const k = rowKey(r);
|
|
4265
4510
|
return k !== null && !creatingKeysRef.current.has(k);
|
|
4266
4511
|
});
|
|
@@ -4276,7 +4521,7 @@ function TransitionDesigner({
|
|
|
4276
4521
|
await Promise.all(
|
|
4277
4522
|
Array.from({ length: Math.min(CREATE_ALL_CONCURRENCY, eligible.length) }, () => worker())
|
|
4278
4523
|
);
|
|
4279
|
-
}, [createRow]);
|
|
4524
|
+
}, [createRow, fadeOnly]);
|
|
4280
4525
|
const fromLabel = fromName ?? "origin";
|
|
4281
4526
|
const toLabel = toName ?? "target";
|
|
4282
4527
|
const cellDragProps = (col, index, locked) => ({
|
|
@@ -4327,15 +4572,15 @@ function TransitionDesigner({
|
|
|
4327
4572
|
const base = "group relative rounded-sm border px-2 py-1.5 text-left transition-colors select-none";
|
|
4328
4573
|
const tone = isDragTarget ? "border-sas-accent bg-sas-accent/10" : "border-sas-border bg-sas-panel";
|
|
4329
4574
|
if (slotId === null) {
|
|
4330
|
-
return /* @__PURE__ */ (0,
|
|
4575
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
4331
4576
|
"div",
|
|
4332
4577
|
{
|
|
4333
4578
|
...cellDragProps(col, index, false),
|
|
4334
4579
|
"data-testid": `${testIdPrefix}-${col}-gap-${index}`,
|
|
4335
4580
|
className: `${base} ${tone} border-dashed flex items-center justify-between ${isDragging ? "opacity-40" : "opacity-70"}`,
|
|
4336
4581
|
children: [
|
|
4337
|
-
/* @__PURE__ */ (0,
|
|
4338
|
-
/* @__PURE__ */ (0,
|
|
4582
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: "\u2014 gap \u2014" }),
|
|
4583
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
4339
4584
|
"button",
|
|
4340
4585
|
{
|
|
4341
4586
|
type: "button",
|
|
@@ -4352,7 +4597,7 @@ function TransitionDesigner({
|
|
|
4352
4597
|
}
|
|
4353
4598
|
const primary = track ? track.prompt?.trim() || track.name : slotId;
|
|
4354
4599
|
const meta = track ? [track.role, shortId3(track.dbId)].filter(Boolean).join(" \xB7 ") : "missing";
|
|
4355
|
-
return /* @__PURE__ */ (0,
|
|
4600
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
4356
4601
|
"div",
|
|
4357
4602
|
{
|
|
4358
4603
|
...cellDragProps(col, index, locked),
|
|
@@ -4360,13 +4605,13 @@ function TransitionDesigner({
|
|
|
4360
4605
|
"data-value": slotId,
|
|
4361
4606
|
className: `${base} ${tone} ${isDragging ? "opacity-40" : ""} ${locked ? "opacity-60" : "cursor-grab active:cursor-grabbing"}`,
|
|
4362
4607
|
title: track ? track.dbId : "Track no longer available",
|
|
4363
|
-
children: /* @__PURE__ */ (0,
|
|
4364
|
-
/* @__PURE__ */ (0,
|
|
4365
|
-
/* @__PURE__ */ (0,
|
|
4366
|
-
/* @__PURE__ */ (0,
|
|
4367
|
-
meta && /* @__PURE__ */ (0,
|
|
4608
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-start gap-1", children: [
|
|
4609
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "text-sas-muted/60 text-xs leading-tight pt-0.5", "aria-hidden": true, children: "\u283F" }),
|
|
4610
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "min-w-0 flex-1", children: [
|
|
4611
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "text-xs text-sas-text truncate", children: primary }),
|
|
4612
|
+
meta && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "text-[10px] text-sas-muted truncate mt-0.5", children: meta })
|
|
4368
4613
|
] }),
|
|
4369
|
-
/* @__PURE__ */ (0,
|
|
4614
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
4370
4615
|
"button",
|
|
4371
4616
|
{
|
|
4372
4617
|
type: "button",
|
|
@@ -4382,22 +4627,159 @@ function TransitionDesigner({
|
|
|
4382
4627
|
}
|
|
4383
4628
|
);
|
|
4384
4629
|
};
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4630
|
+
const renderFadeOnlyRow = (row, i) => {
|
|
4631
|
+
const slotId = row.originId ?? row.targetId;
|
|
4632
|
+
const byId = row.type === "fade-out" ? originById : targetById;
|
|
4633
|
+
const track = byId.get(slotId);
|
|
4634
|
+
const key = rowKey(row);
|
|
4635
|
+
const isCreatingThis = key !== null && creatingKeys.has(key);
|
|
4636
|
+
const errMsg = key !== null ? rowErrors[key] : void 0;
|
|
4637
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
4638
|
+
"div",
|
|
4639
|
+
{
|
|
4640
|
+
"data-testid": `${testIdPrefix}-row-${i}`,
|
|
4641
|
+
className: "grid grid-cols-[1fr_auto] gap-2 items-center",
|
|
4642
|
+
children: [
|
|
4643
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
4644
|
+
"div",
|
|
4645
|
+
{
|
|
4646
|
+
"data-testid": `${testIdPrefix}-subject-${slotId}`,
|
|
4647
|
+
className: `rounded-sm border border-sas-border bg-sas-panel px-2 py-1.5 ${isCreatingThis ? "opacity-60" : ""}`,
|
|
4648
|
+
title: track ? track.dbId : "Track no longer available",
|
|
4649
|
+
children: [
|
|
4650
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "text-xs text-sas-text truncate", children: track ? track.prompt?.trim() || track.name : slotId }),
|
|
4651
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "text-[10px] text-sas-muted truncate mt-0.5", children: track ? [track.role, shortId3(track.dbId)].filter(Boolean).join(" \xB7 ") : "missing" })
|
|
4652
|
+
]
|
|
4653
|
+
}
|
|
4654
|
+
),
|
|
4655
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "w-[160px] flex flex-col items-center gap-1", children: [
|
|
4656
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
4657
|
+
"span",
|
|
4658
|
+
{
|
|
4659
|
+
"data-testid": `${testIdPrefix}-type-${i}`,
|
|
4660
|
+
className: "text-[10px] font-medium px-1.5 py-0.5 rounded-sm border border-sas-border text-sas-muted",
|
|
4661
|
+
children: row.type ? TYPE_LABEL[row.type] : "\u2014"
|
|
4662
|
+
}
|
|
4663
|
+
),
|
|
4664
|
+
isCreatingThis ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "w-full", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
4665
|
+
SorceryProgressBar,
|
|
4666
|
+
{
|
|
4667
|
+
isLoading: true,
|
|
4668
|
+
heightClass: "h-5",
|
|
4669
|
+
statusText: "CREATING",
|
|
4670
|
+
estimatedDurationMs: fadeEstimateMs ?? FADE_ESTIMATE_MS
|
|
4671
|
+
}
|
|
4672
|
+
) }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
4673
|
+
"button",
|
|
4674
|
+
{
|
|
4675
|
+
type: "button",
|
|
4676
|
+
"data-testid": `${testIdPrefix}-create-${i}`,
|
|
4677
|
+
onClick: () => createRow(row),
|
|
4678
|
+
className: "w-full px-2 py-0.5 text-[10px] font-medium rounded-sm border transition-colors bg-sas-accent/20 border-sas-accent text-sas-accent hover:bg-sas-accent hover:text-sas-bg",
|
|
4679
|
+
children: "Create"
|
|
4680
|
+
}
|
|
4681
|
+
),
|
|
4682
|
+
errMsg && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
4683
|
+
"span",
|
|
4684
|
+
{
|
|
4685
|
+
"data-testid": `${testIdPrefix}-row-error-${i}`,
|
|
4686
|
+
className: "text-[10px] text-sas-danger text-center leading-tight",
|
|
4687
|
+
children: errMsg
|
|
4688
|
+
}
|
|
4689
|
+
)
|
|
4690
|
+
] })
|
|
4691
|
+
]
|
|
4692
|
+
},
|
|
4693
|
+
slotId
|
|
4694
|
+
);
|
|
4695
|
+
};
|
|
4696
|
+
if (fadeOnly) {
|
|
4697
|
+
const outRows = fadeOnlyRows.filter((r) => r.type === "fade-out");
|
|
4698
|
+
const inRows = fadeOnlyRows.filter((r) => r.type === "fade-in");
|
|
4699
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "space-y-2", "data-testid": `${testIdPrefix}-box`, children: [
|
|
4700
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-center justify-between gap-3 pb-1 border-b border-sas-border", children: [
|
|
4701
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("p", { className: "text-[11px] text-sas-muted leading-snug min-w-0", children: [
|
|
4702
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "text-sas-text", children: fromLabel }),
|
|
4703
|
+
" \u2192",
|
|
4704
|
+
" ",
|
|
4705
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "text-sas-text", children: toLabel }),
|
|
4706
|
+
familyLabel ? ` \xB7 ${familyLabel}` : "",
|
|
4707
|
+
" \xB7 each entry fades on its own \u2014 origin fades out, target fades in."
|
|
4708
|
+
] }),
|
|
4709
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-center gap-2 shrink-0", children: [
|
|
4710
|
+
creatingKeys.size > 0 && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
4711
|
+
"span",
|
|
4712
|
+
{
|
|
4713
|
+
className: "text-[10px] text-sas-accent whitespace-nowrap",
|
|
4714
|
+
"data-testid": `${testIdPrefix}-creating-count`,
|
|
4715
|
+
children: [
|
|
4716
|
+
creatingKeys.size,
|
|
4717
|
+
" creating\u2026"
|
|
4718
|
+
]
|
|
4719
|
+
}
|
|
4720
|
+
),
|
|
4721
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
4722
|
+
"button",
|
|
4723
|
+
{
|
|
4724
|
+
type: "button",
|
|
4725
|
+
"data-testid": `${testIdPrefix}-create-all`,
|
|
4726
|
+
onClick: createAll,
|
|
4727
|
+
disabled: eligibleCount === 0,
|
|
4728
|
+
title: "Create every fade at once (runs several concurrently)",
|
|
4729
|
+
className: `px-2 py-0.5 text-[10px] font-medium rounded-sm border transition-colors whitespace-nowrap ${eligibleCount > 0 ? "bg-sas-accent/20 border-sas-accent text-sas-accent hover:bg-sas-accent hover:text-sas-bg" : "bg-sas-panel border-sas-border text-sas-muted/50 cursor-not-allowed"}`,
|
|
4730
|
+
children: [
|
|
4731
|
+
"Create all",
|
|
4732
|
+
eligibleCount > 0 ? ` (${eligibleCount})` : ""
|
|
4733
|
+
]
|
|
4734
|
+
}
|
|
4735
|
+
)
|
|
4736
|
+
] })
|
|
4737
|
+
] }),
|
|
4738
|
+
load.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "text-xs text-sas-muted py-6 text-center", children: "Loading tracks\u2026" }),
|
|
4739
|
+
load.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "text-xs text-sas-danger py-6 text-center", "data-testid": `${testIdPrefix}-error`, children: load.message }),
|
|
4740
|
+
load.status === "ready" && (fadeOnlyRows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "text-xs text-sas-muted py-6 text-center", "data-testid": `${testIdPrefix}-empty`, children: [
|
|
4741
|
+
"Nothing to fade in this panel for either scene. Add tracks to ",
|
|
4742
|
+
fromLabel,
|
|
4743
|
+
" or ",
|
|
4744
|
+
toLabel,
|
|
4745
|
+
" ",
|
|
4746
|
+
"first (or free one by deleting an existing fade)."
|
|
4747
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "space-y-3", children: [
|
|
4748
|
+
outRows.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "space-y-2", "data-testid": `${testIdPrefix}-fade-out-section`, children: [
|
|
4749
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: [
|
|
4750
|
+
"Origin (",
|
|
4751
|
+
fromLabel,
|
|
4752
|
+
") \u2014 fades out"
|
|
4753
|
+
] }),
|
|
4754
|
+
outRows.map((row, i) => renderFadeOnlyRow(row, i))
|
|
4755
|
+
] }),
|
|
4756
|
+
inRows.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "space-y-2", "data-testid": `${testIdPrefix}-fade-in-section`, children: [
|
|
4757
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: [
|
|
4758
|
+
"Target (",
|
|
4759
|
+
toLabel,
|
|
4760
|
+
") \u2014 fades in"
|
|
4761
|
+
] }),
|
|
4762
|
+
inRows.map((row, j) => renderFadeOnlyRow(row, outRows.length + j))
|
|
4763
|
+
] })
|
|
4764
|
+
] }))
|
|
4765
|
+
] });
|
|
4766
|
+
}
|
|
4767
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "space-y-2", "data-testid": `${testIdPrefix}-box`, children: [
|
|
4768
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-center justify-between gap-3 pb-1 border-b border-sas-border", children: [
|
|
4769
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("p", { className: "text-[11px] text-sas-muted leading-snug min-w-0", children: [
|
|
4770
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "text-sas-text", children: fromLabel }),
|
|
4389
4771
|
" \u2192",
|
|
4390
4772
|
" ",
|
|
4391
|
-
/* @__PURE__ */ (0,
|
|
4773
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "text-sas-text", children: toLabel }),
|
|
4392
4774
|
familyLabel ? ` \xB7 ${familyLabel}` : "",
|
|
4393
4775
|
" \xB7 line up a track on each side to crossfade; leave one blank (or insert a gap) to fade."
|
|
4394
4776
|
] }),
|
|
4395
|
-
/* @__PURE__ */ (0,
|
|
4396
|
-
creatingKeys.size > 0 && /* @__PURE__ */ (0,
|
|
4777
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-center gap-2 shrink-0", children: [
|
|
4778
|
+
creatingKeys.size > 0 && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("span", { className: "text-[10px] text-sas-accent whitespace-nowrap", "data-testid": `${testIdPrefix}-creating-count`, children: [
|
|
4397
4779
|
creatingKeys.size,
|
|
4398
4780
|
" creating\u2026"
|
|
4399
4781
|
] }),
|
|
4400
|
-
/* @__PURE__ */ (0,
|
|
4782
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
4401
4783
|
"button",
|
|
4402
4784
|
{
|
|
4403
4785
|
type: "button",
|
|
@@ -4414,49 +4796,49 @@ function TransitionDesigner({
|
|
|
4414
4796
|
)
|
|
4415
4797
|
] })
|
|
4416
4798
|
] }),
|
|
4417
|
-
/* @__PURE__ */ (0,
|
|
4418
|
-
/* @__PURE__ */ (0,
|
|
4799
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "grid grid-cols-[1fr_auto_1fr] gap-2", children: [
|
|
4800
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted truncate", children: [
|
|
4419
4801
|
"Origin (",
|
|
4420
4802
|
fromLabel,
|
|
4421
4803
|
")"
|
|
4422
4804
|
] }),
|
|
4423
|
-
/* @__PURE__ */ (0,
|
|
4424
|
-
/* @__PURE__ */ (0,
|
|
4805
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted text-center px-2", children: "Transition" }),
|
|
4806
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted truncate text-right", children: [
|
|
4425
4807
|
"Target (",
|
|
4426
4808
|
toLabel,
|
|
4427
4809
|
")"
|
|
4428
4810
|
] })
|
|
4429
4811
|
] }),
|
|
4430
|
-
load.status === "loading" && /* @__PURE__ */ (0,
|
|
4431
|
-
load.status === "error" && /* @__PURE__ */ (0,
|
|
4432
|
-
load.status === "ready" && (rows.length === 0 ? /* @__PURE__ */ (0,
|
|
4812
|
+
load.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "text-xs text-sas-muted py-6 text-center", children: "Loading tracks\u2026" }),
|
|
4813
|
+
load.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "text-xs text-sas-danger py-6 text-center", "data-testid": `${testIdPrefix}-error`, children: load.message }),
|
|
4814
|
+
load.status === "ready" && (rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "text-xs text-sas-muted py-6 text-center", "data-testid": `${testIdPrefix}-empty`, children: [
|
|
4433
4815
|
"No tracks to arrange in this panel for either scene. Add tracks to ",
|
|
4434
4816
|
fromLabel,
|
|
4435
4817
|
" or ",
|
|
4436
4818
|
toLabel,
|
|
4437
4819
|
" ",
|
|
4438
4820
|
"first (or free one by deleting an existing crossfade/fade)."
|
|
4439
|
-
] }) : /* @__PURE__ */ (0,
|
|
4821
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "space-y-2", children: rows.map((row, i) => {
|
|
4440
4822
|
const key = rowKey(row);
|
|
4441
4823
|
const isCreatingThis = key !== null && creatingKeys.has(key);
|
|
4442
4824
|
const errMsg = key !== null ? rowErrors[key] : void 0;
|
|
4443
|
-
return /* @__PURE__ */ (0,
|
|
4825
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
4444
4826
|
"div",
|
|
4445
4827
|
{
|
|
4446
4828
|
"data-testid": `${testIdPrefix}-row-${i}`,
|
|
4447
4829
|
className: "grid grid-cols-[1fr_auto_1fr] gap-2 items-center",
|
|
4448
4830
|
children: [
|
|
4449
4831
|
renderCell("origin", i, row.originId),
|
|
4450
|
-
/* @__PURE__ */ (0,
|
|
4451
|
-
!row.type ? /* @__PURE__ */ (0,
|
|
4832
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "w-[160px] flex flex-col items-center gap-1", children: [
|
|
4833
|
+
!row.type ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "text-[10px] text-sas-muted/50", children: "\u2014" }) : row.type === "crossfade" ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
4452
4834
|
"span",
|
|
4453
4835
|
{
|
|
4454
4836
|
"data-testid": `${testIdPrefix}-type-${i}`,
|
|
4455
4837
|
className: "text-[10px] font-medium px-1.5 py-0.5 rounded-sm border border-sas-accent/50 text-sas-accent",
|
|
4456
4838
|
children: TYPE_LABEL[row.type]
|
|
4457
4839
|
}
|
|
4458
|
-
) : audioEffectsEnabled ? /* @__PURE__ */ (0,
|
|
4459
|
-
/* @__PURE__ */ (0,
|
|
4840
|
+
) : audioEffectsEnabled ? /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-center gap-1", "data-testid": `${testIdPrefix}-type-${i}`, children: [
|
|
4841
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
4460
4842
|
"select",
|
|
4461
4843
|
{
|
|
4462
4844
|
"data-testid": `${testIdPrefix}-effect-${i}`,
|
|
@@ -4466,11 +4848,11 @@ function TransitionDesigner({
|
|
|
4466
4848
|
if (id) setRowEffect(id, e.target.value);
|
|
4467
4849
|
},
|
|
4468
4850
|
className: "text-[10px] bg-sas-panel border border-sas-border rounded-sm px-1 py-0.5 text-sas-text",
|
|
4469
|
-
children: AUDIO_EFFECTS.map((eff) => /* @__PURE__ */ (0,
|
|
4851
|
+
children: AUDIO_EFFECTS.map((eff) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("option", { value: eff, children: AUDIO_EFFECT_LABEL[eff] }, eff))
|
|
4470
4852
|
}
|
|
4471
4853
|
),
|
|
4472
|
-
/* @__PURE__ */ (0,
|
|
4473
|
-
] }) : /* @__PURE__ */ (0,
|
|
4854
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "text-[9px] text-sas-muted", children: row.type === "fade-out" ? "out" : "in" })
|
|
4855
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
4474
4856
|
"span",
|
|
4475
4857
|
{
|
|
4476
4858
|
"data-testid": `${testIdPrefix}-type-${i}`,
|
|
@@ -4478,7 +4860,7 @@ function TransitionDesigner({
|
|
|
4478
4860
|
children: TYPE_LABEL[row.type]
|
|
4479
4861
|
}
|
|
4480
4862
|
),
|
|
4481
|
-
isCreatingThis ? /* @__PURE__ */ (0,
|
|
4863
|
+
isCreatingThis ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "w-full", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
4482
4864
|
SorceryProgressBar,
|
|
4483
4865
|
{
|
|
4484
4866
|
isLoading: true,
|
|
@@ -4486,7 +4868,7 @@ function TransitionDesigner({
|
|
|
4486
4868
|
statusText: "CREATING",
|
|
4487
4869
|
estimatedDurationMs: row.type === "crossfade" ? CROSSFADE_ESTIMATE_MS : FADE_ESTIMATE_MS
|
|
4488
4870
|
}
|
|
4489
|
-
) }) : /* @__PURE__ */ (0,
|
|
4871
|
+
) }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
4490
4872
|
"button",
|
|
4491
4873
|
{
|
|
4492
4874
|
type: "button",
|
|
@@ -4497,7 +4879,7 @@ function TransitionDesigner({
|
|
|
4497
4879
|
children: "Create"
|
|
4498
4880
|
}
|
|
4499
4881
|
),
|
|
4500
|
-
errMsg && /* @__PURE__ */ (0,
|
|
4882
|
+
errMsg && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
4501
4883
|
"span",
|
|
4502
4884
|
{
|
|
4503
4885
|
"data-testid": `${testIdPrefix}-row-error-${i}`,
|
|
@@ -4516,8 +4898,8 @@ function TransitionDesigner({
|
|
|
4516
4898
|
}
|
|
4517
4899
|
|
|
4518
4900
|
// src/components/PanelMasterStrip.tsx
|
|
4519
|
-
var
|
|
4520
|
-
var
|
|
4901
|
+
var import_react20 = require("react");
|
|
4902
|
+
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
4521
4903
|
function PanelMasterStrip({
|
|
4522
4904
|
bus,
|
|
4523
4905
|
levels = null,
|
|
@@ -4536,22 +4918,22 @@ function PanelMasterStrip({
|
|
|
4536
4918
|
onToggleFxEnabled,
|
|
4537
4919
|
onShowFxEditor
|
|
4538
4920
|
}) {
|
|
4539
|
-
const [search, setSearch] = (0,
|
|
4540
|
-
const filtered = (0,
|
|
4921
|
+
const [search, setSearch] = (0, import_react20.useState)("");
|
|
4922
|
+
const filtered = (0, import_react20.useMemo)(() => {
|
|
4541
4923
|
const q = search.trim().toLowerCase();
|
|
4542
4924
|
if (!q) return availableFx;
|
|
4543
4925
|
return availableFx.filter(
|
|
4544
4926
|
(fx) => fx.name.toLowerCase().includes(q) || fx.manufacturer.toLowerCase().includes(q)
|
|
4545
4927
|
);
|
|
4546
4928
|
}, [availableFx, search]);
|
|
4547
|
-
return /* @__PURE__ */ (0,
|
|
4929
|
+
return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
|
|
4548
4930
|
"div",
|
|
4549
4931
|
{
|
|
4550
4932
|
"data-testid": "panel-master-strip",
|
|
4551
4933
|
className: `flex flex-col gap-1 px-2 py-1.5 rounded-sm border border-sas-border border-l-2 border-l-sas-accent/50 bg-sas-accent/5 transition-opacity ${soloedOut ? "opacity-40" : ""}`,
|
|
4552
4934
|
children: [
|
|
4553
|
-
/* @__PURE__ */ (0,
|
|
4554
|
-
/* @__PURE__ */ (0,
|
|
4935
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
4936
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
4555
4937
|
"span",
|
|
4556
4938
|
{
|
|
4557
4939
|
className: "text-[9px] font-bold tracking-widest text-sas-muted/70 select-none",
|
|
@@ -4559,8 +4941,8 @@ function PanelMasterStrip({
|
|
|
4559
4941
|
children: "BUS"
|
|
4560
4942
|
}
|
|
4561
4943
|
),
|
|
4562
|
-
/* @__PURE__ */ (0,
|
|
4563
|
-
/* @__PURE__ */ (0,
|
|
4944
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex-1 min-w-[8rem] flex flex-col gap-0.5", children: [
|
|
4945
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
4564
4946
|
VolumeSlider,
|
|
4565
4947
|
{
|
|
4566
4948
|
value: dbToSlider(bus.volume),
|
|
@@ -4568,8 +4950,8 @@ function PanelMasterStrip({
|
|
|
4568
4950
|
disabled
|
|
4569
4951
|
}
|
|
4570
4952
|
),
|
|
4571
|
-
/* @__PURE__ */ (0,
|
|
4572
|
-
/* @__PURE__ */ (0,
|
|
4953
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex flex-col gap-px", "data-testid": "bus-meter", children: [
|
|
4954
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
4573
4955
|
LevelMeter,
|
|
4574
4956
|
{
|
|
4575
4957
|
peakDb: levels?.leftDb ?? -120,
|
|
@@ -4579,7 +4961,7 @@ function PanelMasterStrip({
|
|
|
4579
4961
|
"data-testid": "bus-meter-left"
|
|
4580
4962
|
}
|
|
4581
4963
|
),
|
|
4582
|
-
/* @__PURE__ */ (0,
|
|
4964
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
4583
4965
|
LevelMeter,
|
|
4584
4966
|
{
|
|
4585
4967
|
peakDb: levels?.rightDb ?? -120,
|
|
@@ -4590,7 +4972,7 @@ function PanelMasterStrip({
|
|
|
4590
4972
|
)
|
|
4591
4973
|
] })
|
|
4592
4974
|
] }),
|
|
4593
|
-
/* @__PURE__ */ (0,
|
|
4975
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
4594
4976
|
"button",
|
|
4595
4977
|
{
|
|
4596
4978
|
"data-testid": "bus-mute-button",
|
|
@@ -4601,7 +4983,7 @@ function PanelMasterStrip({
|
|
|
4601
4983
|
children: "M"
|
|
4602
4984
|
}
|
|
4603
4985
|
),
|
|
4604
|
-
/* @__PURE__ */ (0,
|
|
4986
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
4605
4987
|
"button",
|
|
4606
4988
|
{
|
|
4607
4989
|
"data-testid": "bus-solo-button",
|
|
@@ -4612,14 +4994,14 @@ function PanelMasterStrip({
|
|
|
4612
4994
|
children: "S"
|
|
4613
4995
|
}
|
|
4614
4996
|
),
|
|
4615
|
-
/* @__PURE__ */ (0,
|
|
4997
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "flex items-center gap-1 max-w-[45%] min-w-0 overflow-x-auto", children: bus.fx.map((fx) => /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
|
|
4616
4998
|
"span",
|
|
4617
4999
|
{
|
|
4618
5000
|
"data-testid": `bus-fx-chip-${fx.index}`,
|
|
4619
5001
|
className: `flex items-center gap-1 px-1.5 py-0.5 rounded-sm border text-[10px] whitespace-nowrap ${fx.enabled ? "border-sas-accent/60 text-sas-accent bg-sas-accent/10" : "border-sas-border text-sas-muted/50 bg-sas-panel"}`,
|
|
4620
5002
|
title: `${fx.name}${fx.enabled ? "" : " (bypassed)"}`,
|
|
4621
5003
|
children: [
|
|
4622
|
-
/* @__PURE__ */ (0,
|
|
5004
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
4623
5005
|
"button",
|
|
4624
5006
|
{
|
|
4625
5007
|
"data-testid": `bus-fx-toggle-${fx.index}`,
|
|
@@ -4630,7 +5012,7 @@ function PanelMasterStrip({
|
|
|
4630
5012
|
children: fx.enabled ? "\u25CF" : "\u25CB"
|
|
4631
5013
|
}
|
|
4632
5014
|
),
|
|
4633
|
-
onShowFxEditor ? /* @__PURE__ */ (0,
|
|
5015
|
+
onShowFxEditor ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
4634
5016
|
"button",
|
|
4635
5017
|
{
|
|
4636
5018
|
"data-testid": `bus-fx-edit-${fx.index}`,
|
|
@@ -4640,8 +5022,8 @@ function PanelMasterStrip({
|
|
|
4640
5022
|
title: `Open ${fx.name} editor`,
|
|
4641
5023
|
children: fx.name
|
|
4642
5024
|
}
|
|
4643
|
-
) : /* @__PURE__ */ (0,
|
|
4644
|
-
/* @__PURE__ */ (0,
|
|
5025
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "max-w-[80px] truncate", children: fx.name }),
|
|
5026
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
4645
5027
|
"button",
|
|
4646
5028
|
{
|
|
4647
5029
|
"data-testid": `bus-fx-remove-${fx.index}`,
|
|
@@ -4656,7 +5038,7 @@ function PanelMasterStrip({
|
|
|
4656
5038
|
},
|
|
4657
5039
|
`${fx.index}:${fx.pluginId}`
|
|
4658
5040
|
)) }),
|
|
4659
|
-
/* @__PURE__ */ (0,
|
|
5041
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
4660
5042
|
"button",
|
|
4661
5043
|
{
|
|
4662
5044
|
"data-testid": "bus-fx-add-button",
|
|
@@ -4668,9 +5050,9 @@ function PanelMasterStrip({
|
|
|
4668
5050
|
}
|
|
4669
5051
|
)
|
|
4670
5052
|
] }),
|
|
4671
|
-
fxPickerOpen && /* @__PURE__ */ (0,
|
|
4672
|
-
/* @__PURE__ */ (0,
|
|
4673
|
-
/* @__PURE__ */ (0,
|
|
5053
|
+
fxPickerOpen && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { "data-testid": "bus-fx-picker", className: "flex flex-col gap-2 pt-1", children: [
|
|
5054
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
5055
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
4674
5056
|
"input",
|
|
4675
5057
|
{
|
|
4676
5058
|
type: "text",
|
|
@@ -4680,7 +5062,7 @@ function PanelMasterStrip({
|
|
|
4680
5062
|
className: "sas-input flex-1 px-2 py-1 text-xs"
|
|
4681
5063
|
}
|
|
4682
5064
|
),
|
|
4683
|
-
onRefreshFx && /* @__PURE__ */ (0,
|
|
5065
|
+
onRefreshFx && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
4684
5066
|
"button",
|
|
4685
5067
|
{
|
|
4686
5068
|
onClick: () => onRefreshFx(),
|
|
@@ -4691,8 +5073,8 @@ function PanelMasterStrip({
|
|
|
4691
5073
|
}
|
|
4692
5074
|
)
|
|
4693
5075
|
] }),
|
|
4694
|
-
fxLoading && availableFx.length === 0 ? /* @__PURE__ */ (0,
|
|
4695
|
-
filtered.map((fx) => /* @__PURE__ */ (0,
|
|
5076
|
+
fxLoading && availableFx.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "text-xs text-sas-muted/60 text-center py-3", children: "Scanning plugins..." }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "grid grid-cols-3 gap-1 max-h-[140px] overflow-y-auto", children: [
|
|
5077
|
+
filtered.map((fx) => /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
|
|
4696
5078
|
"button",
|
|
4697
5079
|
{
|
|
4698
5080
|
"data-testid": `bus-fx-pick-${fx.pluginId}`,
|
|
@@ -4700,13 +5082,13 @@ function PanelMasterStrip({
|
|
|
4700
5082
|
className: "flex flex-col items-start px-2 py-1.5 rounded-sm border text-left transition-colors border-sas-border bg-sas-panel-alt text-sas-muted hover:border-sas-accent hover:text-sas-accent",
|
|
4701
5083
|
title: `${fx.name} by ${fx.manufacturer} (${fx.type.toUpperCase()})`,
|
|
4702
5084
|
children: [
|
|
4703
|
-
/* @__PURE__ */ (0,
|
|
4704
|
-
/* @__PURE__ */ (0,
|
|
5085
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "text-xs font-medium truncate w-full", children: fx.name }),
|
|
5086
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "text-[9px] text-sas-muted/50 truncate w-full", children: fx.manufacturer || fx.type.toUpperCase() })
|
|
4705
5087
|
]
|
|
4706
5088
|
},
|
|
4707
5089
|
fx.pluginId
|
|
4708
5090
|
)),
|
|
4709
|
-
filtered.length === 0 && /* @__PURE__ */ (0,
|
|
5091
|
+
filtered.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "col-span-3 text-xs text-sas-muted/60 text-center py-2", children: search.trim() ? "No matches" : "No FX plugins found" })
|
|
4710
5092
|
] })
|
|
4711
5093
|
] })
|
|
4712
5094
|
]
|
|
@@ -4715,18 +5097,18 @@ function PanelMasterStrip({
|
|
|
4715
5097
|
}
|
|
4716
5098
|
|
|
4717
5099
|
// src/hooks/usePanelBus.ts
|
|
4718
|
-
var
|
|
5100
|
+
var import_react21 = require("react");
|
|
4719
5101
|
var LEVELS_POLL_MS = 66;
|
|
4720
5102
|
function usePanelBus(host, activeSceneId) {
|
|
4721
5103
|
const supported = typeof host.getPanelBusState === "function";
|
|
4722
|
-
const [bus, setBus] = (0,
|
|
4723
|
-
const [levels, setLevels] = (0,
|
|
4724
|
-
const [availableFx, setAvailableFx] = (0,
|
|
4725
|
-
const [fxLoading, setFxLoading] = (0,
|
|
4726
|
-
const [fxPickerOpen, setFxPickerOpen] = (0,
|
|
4727
|
-
const fxLoadedRef = (0,
|
|
4728
|
-
const loadSeqRef = (0,
|
|
4729
|
-
const reload = (0,
|
|
5104
|
+
const [bus, setBus] = (0, import_react21.useState)(null);
|
|
5105
|
+
const [levels, setLevels] = (0, import_react21.useState)(null);
|
|
5106
|
+
const [availableFx, setAvailableFx] = (0, import_react21.useState)([]);
|
|
5107
|
+
const [fxLoading, setFxLoading] = (0, import_react21.useState)(false);
|
|
5108
|
+
const [fxPickerOpen, setFxPickerOpen] = (0, import_react21.useState)(false);
|
|
5109
|
+
const fxLoadedRef = (0, import_react21.useRef)(false);
|
|
5110
|
+
const loadSeqRef = (0, import_react21.useRef)(0);
|
|
5111
|
+
const reload = (0, import_react21.useCallback)(async () => {
|
|
4730
5112
|
if (!supported || !activeSceneId || !host.getPanelBusState) {
|
|
4731
5113
|
setBus(null);
|
|
4732
5114
|
return;
|
|
@@ -4738,12 +5120,12 @@ function usePanelBus(host, activeSceneId) {
|
|
|
4738
5120
|
} catch {
|
|
4739
5121
|
}
|
|
4740
5122
|
}, [host, activeSceneId, supported]);
|
|
4741
|
-
(0,
|
|
5123
|
+
(0, import_react21.useEffect)(() => {
|
|
4742
5124
|
setBus(null);
|
|
4743
5125
|
setFxPickerOpen(false);
|
|
4744
5126
|
void reload();
|
|
4745
5127
|
}, [reload]);
|
|
4746
|
-
(0,
|
|
5128
|
+
(0, import_react21.useEffect)(() => {
|
|
4747
5129
|
if (!supported || !activeSceneId || !bus?.engaged || !host.getPanelBusLevels) {
|
|
4748
5130
|
setLevels(null);
|
|
4749
5131
|
return;
|
|
@@ -4765,7 +5147,7 @@ function usePanelBus(host, activeSceneId) {
|
|
|
4765
5147
|
clearInterval(id);
|
|
4766
5148
|
};
|
|
4767
5149
|
}, [supported, activeSceneId, bus?.engaged, host]);
|
|
4768
|
-
const loadFxList = (0,
|
|
5150
|
+
const loadFxList = (0, import_react21.useCallback)(
|
|
4769
5151
|
async (opts) => {
|
|
4770
5152
|
if (!supported || !host.getAvailableFx) return;
|
|
4771
5153
|
if (fxLoadedRef.current && !opts.force && !opts.rescan) return;
|
|
@@ -4781,14 +5163,14 @@ function usePanelBus(host, activeSceneId) {
|
|
|
4781
5163
|
},
|
|
4782
5164
|
[host, supported]
|
|
4783
5165
|
);
|
|
4784
|
-
const openPicker = (0,
|
|
5166
|
+
const openPicker = (0, import_react21.useCallback)(
|
|
4785
5167
|
(open) => {
|
|
4786
5168
|
setFxPickerOpen(open);
|
|
4787
5169
|
if (open) void loadFxList({});
|
|
4788
5170
|
},
|
|
4789
5171
|
[loadFxList]
|
|
4790
5172
|
);
|
|
4791
|
-
const mutate = (0,
|
|
5173
|
+
const mutate = (0, import_react21.useCallback)(
|
|
4792
5174
|
(fn) => {
|
|
4793
5175
|
if (!fn || !activeSceneId) return;
|
|
4794
5176
|
void (async () => {
|
|
@@ -4832,8 +5214,8 @@ function usePanelBus(host, activeSceneId) {
|
|
|
4832
5214
|
}
|
|
4833
5215
|
|
|
4834
5216
|
// src/components/DownloadPackButton.tsx
|
|
4835
|
-
var
|
|
4836
|
-
var
|
|
5217
|
+
var import_react22 = require("react");
|
|
5218
|
+
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
4837
5219
|
function formatSize(bytes) {
|
|
4838
5220
|
if (!bytes || bytes <= 0) return "";
|
|
4839
5221
|
const gb = bytes / 1024 ** 3;
|
|
@@ -4849,10 +5231,10 @@ var DownloadPackButton = ({
|
|
|
4849
5231
|
variant = "compact",
|
|
4850
5232
|
onDownloadComplete
|
|
4851
5233
|
}) => {
|
|
4852
|
-
const [status, setStatus] = (0,
|
|
4853
|
-
const [progress, setProgress] = (0,
|
|
4854
|
-
const [errorMessage, setErrorMessage] = (0,
|
|
4855
|
-
(0,
|
|
5234
|
+
const [status, setStatus] = (0, import_react22.useState)("idle");
|
|
5235
|
+
const [progress, setProgress] = (0, import_react22.useState)(0);
|
|
5236
|
+
const [errorMessage, setErrorMessage] = (0, import_react22.useState)(null);
|
|
5237
|
+
(0, import_react22.useEffect)(() => {
|
|
4856
5238
|
const unsub = host.onSamplePackProgress(packId, (p) => {
|
|
4857
5239
|
setStatus(p.status);
|
|
4858
5240
|
setProgress(p.progress);
|
|
@@ -4867,7 +5249,7 @@ var DownloadPackButton = ({
|
|
|
4867
5249
|
});
|
|
4868
5250
|
return unsub;
|
|
4869
5251
|
}, [host, packId, onDownloadComplete]);
|
|
4870
|
-
const handleClick = (0,
|
|
5252
|
+
const handleClick = (0, import_react22.useCallback)(async () => {
|
|
4871
5253
|
if (status !== "idle" && status !== "error") return;
|
|
4872
5254
|
try {
|
|
4873
5255
|
setStatus("downloading");
|
|
@@ -4921,8 +5303,8 @@ var DownloadPackButton = ({
|
|
|
4921
5303
|
} else {
|
|
4922
5304
|
className = `${baseClasses} text-sas-muted hover:text-sas-accent border-sas-border hover:border-sas-accent`;
|
|
4923
5305
|
}
|
|
4924
|
-
return /* @__PURE__ */ (0,
|
|
4925
|
-
/* @__PURE__ */ (0,
|
|
5306
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { children: [
|
|
5307
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
4926
5308
|
"button",
|
|
4927
5309
|
{
|
|
4928
5310
|
"data-testid": `download-pack-button-${packId}`,
|
|
@@ -4933,12 +5315,12 @@ var DownloadPackButton = ({
|
|
|
4933
5315
|
children: buttonLabel
|
|
4934
5316
|
}
|
|
4935
5317
|
),
|
|
4936
|
-
variant === "large" && status === "error" && errorMessage && /* @__PURE__ */ (0,
|
|
5318
|
+
variant === "large" && status === "error" && errorMessage && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "text-xs text-sas-danger mt-2", "data-testid": `download-pack-error-${packId}`, children: errorMessage })
|
|
4937
5319
|
] });
|
|
4938
5320
|
};
|
|
4939
5321
|
|
|
4940
5322
|
// src/components/SamplePackCTACard.tsx
|
|
4941
|
-
var
|
|
5323
|
+
var import_jsx_runtime22 = require("react/jsx-runtime");
|
|
4942
5324
|
var SamplePackCTACard = ({
|
|
4943
5325
|
host,
|
|
4944
5326
|
pack,
|
|
@@ -4946,7 +5328,7 @@ var SamplePackCTACard = ({
|
|
|
4946
5328
|
onDownloadComplete
|
|
4947
5329
|
}) => {
|
|
4948
5330
|
if (status === "checking") {
|
|
4949
|
-
return /* @__PURE__ */ (0,
|
|
5331
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
4950
5332
|
"div",
|
|
4951
5333
|
{
|
|
4952
5334
|
"data-testid": `sample-pack-cta-checking-${pack.packId}`,
|
|
@@ -4957,16 +5339,16 @@ var SamplePackCTACard = ({
|
|
|
4957
5339
|
}
|
|
4958
5340
|
const headline = status === "stale" ? `${pack.displayName} update available` : `${pack.displayName} not installed`;
|
|
4959
5341
|
const sublabel = status === "stale" ? `A newer version is available for download.` : pack.description;
|
|
4960
|
-
return /* @__PURE__ */ (0,
|
|
5342
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
|
|
4961
5343
|
"div",
|
|
4962
5344
|
{
|
|
4963
5345
|
"data-testid": `sample-pack-cta-${pack.packId}`,
|
|
4964
5346
|
className: "flex flex-col items-center justify-center py-12 px-6 text-center",
|
|
4965
5347
|
children: [
|
|
4966
|
-
/* @__PURE__ */ (0,
|
|
4967
|
-
/* @__PURE__ */ (0,
|
|
4968
|
-
/* @__PURE__ */ (0,
|
|
4969
|
-
/* @__PURE__ */ (0,
|
|
5348
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "text-sm uppercase tracking-wide text-sas-muted mb-2", children: status === "stale" ? "Update available" : "Sample library not installed" }),
|
|
5349
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "text-base text-sas-text mb-1", children: headline }),
|
|
5350
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "text-xs text-sas-muted mb-6 max-w-md", children: sublabel }),
|
|
5351
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
4970
5352
|
DownloadPackButton,
|
|
4971
5353
|
{
|
|
4972
5354
|
host,
|
|
@@ -4983,7 +5365,7 @@ var SamplePackCTACard = ({
|
|
|
4983
5365
|
};
|
|
4984
5366
|
|
|
4985
5367
|
// src/components/WaveformView.tsx
|
|
4986
|
-
var
|
|
5368
|
+
var import_react23 = require("react");
|
|
4987
5369
|
|
|
4988
5370
|
// src/components/waveform.ts
|
|
4989
5371
|
function computePeaks(audioBuffer, bins, targetSamples) {
|
|
@@ -5046,7 +5428,7 @@ function drawWaveform(canvas, peaks, options = {}) {
|
|
|
5046
5428
|
}
|
|
5047
5429
|
|
|
5048
5430
|
// src/components/WaveformView.tsx
|
|
5049
|
-
var
|
|
5431
|
+
var import_jsx_runtime23 = require("react/jsx-runtime");
|
|
5050
5432
|
var WaveformView = ({
|
|
5051
5433
|
host,
|
|
5052
5434
|
filePath,
|
|
@@ -5055,9 +5437,9 @@ var WaveformView = ({
|
|
|
5055
5437
|
fillStyle,
|
|
5056
5438
|
targetSamples
|
|
5057
5439
|
}) => {
|
|
5058
|
-
const canvasRef = (0,
|
|
5059
|
-
const [peaks, setPeaks] = (0,
|
|
5060
|
-
(0,
|
|
5440
|
+
const canvasRef = (0, import_react23.useRef)(null);
|
|
5441
|
+
const [peaks, setPeaks] = (0, import_react23.useState)(null);
|
|
5442
|
+
(0, import_react23.useEffect)(() => {
|
|
5061
5443
|
let cancelled = false;
|
|
5062
5444
|
let audioContext = null;
|
|
5063
5445
|
(async () => {
|
|
@@ -5083,7 +5465,7 @@ var WaveformView = ({
|
|
|
5083
5465
|
cancelled = true;
|
|
5084
5466
|
};
|
|
5085
5467
|
}, [host, filePath, bins, targetSamples]);
|
|
5086
|
-
(0,
|
|
5468
|
+
(0, import_react23.useEffect)(() => {
|
|
5087
5469
|
if (!peaks) return;
|
|
5088
5470
|
const canvas = canvasRef.current;
|
|
5089
5471
|
if (!canvas) return;
|
|
@@ -5094,7 +5476,7 @@ var WaveformView = ({
|
|
|
5094
5476
|
observer.observe(canvas);
|
|
5095
5477
|
return () => observer.disconnect();
|
|
5096
5478
|
}, [peaks, fillStyle]);
|
|
5097
|
-
return /* @__PURE__ */ (0,
|
|
5479
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
5098
5480
|
"canvas",
|
|
5099
5481
|
{
|
|
5100
5482
|
ref: canvasRef,
|
|
@@ -5105,8 +5487,8 @@ var WaveformView = ({
|
|
|
5105
5487
|
};
|
|
5106
5488
|
|
|
5107
5489
|
// src/components/ScrollingWaveform.tsx
|
|
5108
|
-
var
|
|
5109
|
-
var
|
|
5490
|
+
var import_react24 = require("react");
|
|
5491
|
+
var import_jsx_runtime24 = require("react/jsx-runtime");
|
|
5110
5492
|
var ScrollingWaveform = ({
|
|
5111
5493
|
getPeakDb,
|
|
5112
5494
|
active,
|
|
@@ -5114,11 +5496,11 @@ var ScrollingWaveform = ({
|
|
|
5114
5496
|
className,
|
|
5115
5497
|
fillStyle
|
|
5116
5498
|
}) => {
|
|
5117
|
-
const canvasRef = (0,
|
|
5118
|
-
const ringRef = (0,
|
|
5119
|
-
const writeIdxRef = (0,
|
|
5120
|
-
const rafRef = (0,
|
|
5121
|
-
(0,
|
|
5499
|
+
const canvasRef = (0, import_react24.useRef)(null);
|
|
5500
|
+
const ringRef = (0, import_react24.useRef)(new Float32Array(columns));
|
|
5501
|
+
const writeIdxRef = (0, import_react24.useRef)(0);
|
|
5502
|
+
const rafRef = (0, import_react24.useRef)(null);
|
|
5503
|
+
(0, import_react24.useEffect)(() => {
|
|
5122
5504
|
if (ringRef.current.length !== columns) {
|
|
5123
5505
|
const next = new Float32Array(columns);
|
|
5124
5506
|
const prev = ringRef.current;
|
|
@@ -5130,7 +5512,7 @@ var ScrollingWaveform = ({
|
|
|
5130
5512
|
writeIdxRef.current = writeIdxRef.current % columns;
|
|
5131
5513
|
}
|
|
5132
5514
|
}, [columns]);
|
|
5133
|
-
(0,
|
|
5515
|
+
(0, import_react24.useEffect)(() => {
|
|
5134
5516
|
if (!active) {
|
|
5135
5517
|
if (rafRef.current !== null) {
|
|
5136
5518
|
cancelAnimationFrame(rafRef.current);
|
|
@@ -5182,7 +5564,7 @@ var ScrollingWaveform = ({
|
|
|
5182
5564
|
}
|
|
5183
5565
|
};
|
|
5184
5566
|
}, [active, getPeakDb, fillStyle]);
|
|
5185
|
-
return /* @__PURE__ */ (0,
|
|
5567
|
+
return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
5186
5568
|
"canvas",
|
|
5187
5569
|
{
|
|
5188
5570
|
ref: canvasRef,
|
|
@@ -5193,8 +5575,8 @@ var ScrollingWaveform = ({
|
|
|
5193
5575
|
};
|
|
5194
5576
|
|
|
5195
5577
|
// src/components/OffsetScrubber.tsx
|
|
5196
|
-
var
|
|
5197
|
-
var
|
|
5578
|
+
var import_react25 = require("react");
|
|
5579
|
+
var import_jsx_runtime25 = require("react/jsx-runtime");
|
|
5198
5580
|
var SLIDER_HEIGHT_PX = 28;
|
|
5199
5581
|
var TICK_HEIGHT_PX = 14;
|
|
5200
5582
|
var DOWNBEAT_TICK_HEIGHT_PX = 22;
|
|
@@ -5207,40 +5589,40 @@ function OffsetScrubber({
|
|
|
5207
5589
|
onChange,
|
|
5208
5590
|
disabled = false
|
|
5209
5591
|
}) {
|
|
5210
|
-
const trackRef = (0,
|
|
5211
|
-
const [draftOffset, setDraftOffset] = (0,
|
|
5212
|
-
const [isDragging, setIsDragging] = (0,
|
|
5213
|
-
(0,
|
|
5592
|
+
const trackRef = (0, import_react25.useRef)(null);
|
|
5593
|
+
const [draftOffset, setDraftOffset] = (0, import_react25.useState)(offsetSamples);
|
|
5594
|
+
const [isDragging, setIsDragging] = (0, import_react25.useState)(false);
|
|
5595
|
+
(0, import_react25.useEffect)(() => {
|
|
5214
5596
|
if (!isDragging) setDraftOffset(offsetSamples);
|
|
5215
5597
|
}, [offsetSamples, isDragging]);
|
|
5216
5598
|
const sampleRate = cuePoints?.sample_rate ?? 44100;
|
|
5217
5599
|
const detectedBpm = cuePoints?.detected_bpm ?? projectBpm;
|
|
5218
|
-
const beatsForRange = (0,
|
|
5600
|
+
const beatsForRange = (0, import_react25.useMemo)(() => {
|
|
5219
5601
|
return Math.round(60 / projectBpm * sampleRate);
|
|
5220
5602
|
}, [projectBpm, sampleRate]);
|
|
5221
5603
|
const rangeSamples = beatsForRange * meter;
|
|
5222
|
-
const sampleToFraction = (0,
|
|
5604
|
+
const sampleToFraction = (0, import_react25.useCallback)(
|
|
5223
5605
|
(sample) => {
|
|
5224
5606
|
const clamped = Math.max(-rangeSamples, Math.min(rangeSamples, sample));
|
|
5225
5607
|
return (clamped + rangeSamples) / (2 * rangeSamples);
|
|
5226
5608
|
},
|
|
5227
5609
|
[rangeSamples]
|
|
5228
5610
|
);
|
|
5229
|
-
const fractionToSample = (0,
|
|
5611
|
+
const fractionToSample = (0, import_react25.useCallback)(
|
|
5230
5612
|
(fraction) => {
|
|
5231
5613
|
const clamped = Math.max(0, Math.min(1, fraction));
|
|
5232
5614
|
return Math.round(clamped * 2 * rangeSamples - rangeSamples);
|
|
5233
5615
|
},
|
|
5234
5616
|
[rangeSamples]
|
|
5235
5617
|
);
|
|
5236
|
-
const snapTargets = (0,
|
|
5618
|
+
const snapTargets = (0, import_react25.useMemo)(() => {
|
|
5237
5619
|
if (!cuePoints || cuePoints.beats.length === 0) return [];
|
|
5238
5620
|
const downbeat = cuePoints.beats[0];
|
|
5239
5621
|
const positives = cuePoints.beats.map((b) => b - downbeat);
|
|
5240
5622
|
const negatives = positives.slice(1).map((p) => -p);
|
|
5241
5623
|
return [...negatives, ...positives].sort((a, b) => a - b);
|
|
5242
5624
|
}, [cuePoints]);
|
|
5243
|
-
const snapToBeat = (0,
|
|
5625
|
+
const snapToBeat = (0, import_react25.useCallback)(
|
|
5244
5626
|
(sample) => {
|
|
5245
5627
|
if (snapTargets.length === 0) return sample;
|
|
5246
5628
|
let best = snapTargets[0];
|
|
@@ -5256,7 +5638,7 @@ function OffsetScrubber({
|
|
|
5256
5638
|
},
|
|
5257
5639
|
[snapTargets]
|
|
5258
5640
|
);
|
|
5259
|
-
const handlePointerDown = (0,
|
|
5641
|
+
const handlePointerDown = (0, import_react25.useCallback)(
|
|
5260
5642
|
(e) => {
|
|
5261
5643
|
if (disabled || !cuePoints) return;
|
|
5262
5644
|
e.preventDefault();
|
|
@@ -5290,7 +5672,7 @@ function OffsetScrubber({
|
|
|
5290
5672
|
},
|
|
5291
5673
|
[disabled, cuePoints, fractionToSample, onChange, snapToBeat]
|
|
5292
5674
|
);
|
|
5293
|
-
const handleResetToZero = (0,
|
|
5675
|
+
const handleResetToZero = (0, import_react25.useCallback)(() => {
|
|
5294
5676
|
if (disabled) return;
|
|
5295
5677
|
setDraftOffset(0);
|
|
5296
5678
|
onChange(0);
|
|
@@ -5298,7 +5680,7 @@ function OffsetScrubber({
|
|
|
5298
5680
|
const thumbFraction = sampleToFraction(draftOffset);
|
|
5299
5681
|
const thumbLeftPct = `${(thumbFraction * 100).toFixed(2)}%`;
|
|
5300
5682
|
const bpmMismatch = cuePoints?.detected_bpm != null && Math.abs(cuePoints.detected_bpm - projectBpm) > 1;
|
|
5301
|
-
const ticks = (0,
|
|
5683
|
+
const ticks = (0, import_react25.useMemo)(() => {
|
|
5302
5684
|
if (!cuePoints) return [];
|
|
5303
5685
|
const downbeat = cuePoints.beats[0] ?? 0;
|
|
5304
5686
|
return cuePoints.beats.map((b, i) => {
|
|
@@ -5309,9 +5691,9 @@ function OffsetScrubber({
|
|
|
5309
5691
|
});
|
|
5310
5692
|
}, [cuePoints, sampleToFraction]);
|
|
5311
5693
|
const isDisabled = disabled || !cuePoints || cuePoints.beats.length === 0;
|
|
5312
|
-
return /* @__PURE__ */ (0,
|
|
5313
|
-
/* @__PURE__ */ (0,
|
|
5314
|
-
/* @__PURE__ */ (0,
|
|
5694
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { "data-testid": "offset-scrubber", className: "flex items-center gap-2 w-full", children: [
|
|
5695
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "text-[9px] text-sas-muted/60 uppercase tracking-wide flex-shrink-0", children: "Align" }),
|
|
5696
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
|
|
5315
5697
|
"div",
|
|
5316
5698
|
{
|
|
5317
5699
|
ref: trackRef,
|
|
@@ -5327,7 +5709,7 @@ function OffsetScrubber({
|
|
|
5327
5709
|
"aria-valuenow": draftOffset,
|
|
5328
5710
|
"aria-disabled": isDisabled,
|
|
5329
5711
|
children: [
|
|
5330
|
-
/* @__PURE__ */ (0,
|
|
5712
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
5331
5713
|
"div",
|
|
5332
5714
|
{
|
|
5333
5715
|
"aria-hidden": "true",
|
|
@@ -5335,7 +5717,7 @@ function OffsetScrubber({
|
|
|
5335
5717
|
style: { left: "50%" }
|
|
5336
5718
|
}
|
|
5337
5719
|
),
|
|
5338
|
-
ticks.map((t) => /* @__PURE__ */ (0,
|
|
5720
|
+
ticks.map((t) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
5339
5721
|
"div",
|
|
5340
5722
|
{
|
|
5341
5723
|
"data-testid": t.isDownbeat ? "offset-tick-downbeat" : "offset-tick",
|
|
@@ -5350,7 +5732,7 @@ function OffsetScrubber({
|
|
|
5350
5732
|
},
|
|
5351
5733
|
t.i
|
|
5352
5734
|
)),
|
|
5353
|
-
/* @__PURE__ */ (0,
|
|
5735
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
5354
5736
|
"div",
|
|
5355
5737
|
{
|
|
5356
5738
|
"data-testid": "offset-scrubber-thumb",
|
|
@@ -5367,7 +5749,7 @@ function OffsetScrubber({
|
|
|
5367
5749
|
]
|
|
5368
5750
|
}
|
|
5369
5751
|
),
|
|
5370
|
-
/* @__PURE__ */ (0,
|
|
5752
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
5371
5753
|
"span",
|
|
5372
5754
|
{
|
|
5373
5755
|
"data-testid": "offset-scrubber-readout",
|
|
@@ -5375,7 +5757,7 @@ function OffsetScrubber({
|
|
|
5375
5757
|
children: formatOffset(draftOffset, sampleRate)
|
|
5376
5758
|
}
|
|
5377
5759
|
),
|
|
5378
|
-
/* @__PURE__ */ (0,
|
|
5760
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
5379
5761
|
"button",
|
|
5380
5762
|
{
|
|
5381
5763
|
type: "button",
|
|
@@ -5387,7 +5769,7 @@ function OffsetScrubber({
|
|
|
5387
5769
|
children: "\u2316"
|
|
5388
5770
|
}
|
|
5389
5771
|
),
|
|
5390
|
-
bpmMismatch && /* @__PURE__ */ (0,
|
|
5772
|
+
bpmMismatch && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
5391
5773
|
"span",
|
|
5392
5774
|
{
|
|
5393
5775
|
"data-testid": "offset-bpm-mismatch",
|
|
@@ -5459,16 +5841,16 @@ function synthesizeCuePoints({
|
|
|
5459
5841
|
}
|
|
5460
5842
|
|
|
5461
5843
|
// src/panel-core/useGeneratorPanelCore.tsx
|
|
5462
|
-
var
|
|
5844
|
+
var import_react30 = require("react");
|
|
5463
5845
|
|
|
5464
5846
|
// src/hooks/useSceneState.ts
|
|
5465
|
-
var
|
|
5847
|
+
var import_react26 = require("react");
|
|
5466
5848
|
function useSceneState(activeSceneId, initialValue) {
|
|
5467
|
-
const [stateMap, setStateMap] = (0,
|
|
5468
|
-
const activeSceneIdRef = (0,
|
|
5849
|
+
const [stateMap, setStateMap] = (0, import_react26.useState)(() => /* @__PURE__ */ new Map());
|
|
5850
|
+
const activeSceneIdRef = (0, import_react26.useRef)(activeSceneId);
|
|
5469
5851
|
activeSceneIdRef.current = activeSceneId;
|
|
5470
5852
|
const currentValue = activeSceneId !== null && stateMap.has(activeSceneId) ? stateMap.get(activeSceneId) : initialValue;
|
|
5471
|
-
const setForCurrentScene = (0,
|
|
5853
|
+
const setForCurrentScene = (0, import_react26.useCallback)((value) => {
|
|
5472
5854
|
const sid = activeSceneIdRef.current;
|
|
5473
5855
|
if (sid === null) return;
|
|
5474
5856
|
setStateMap((prev) => {
|
|
@@ -5479,7 +5861,7 @@ function useSceneState(activeSceneId, initialValue) {
|
|
|
5479
5861
|
return newMap;
|
|
5480
5862
|
});
|
|
5481
5863
|
}, [initialValue]);
|
|
5482
|
-
const setForScene = (0,
|
|
5864
|
+
const setForScene = (0, import_react26.useCallback)((sceneId, value) => {
|
|
5483
5865
|
setStateMap((prev) => {
|
|
5484
5866
|
const current = prev.has(sceneId) ? prev.get(sceneId) : initialValue;
|
|
5485
5867
|
const next = typeof value === "function" ? value(current) : value;
|
|
@@ -5492,10 +5874,10 @@ function useSceneState(activeSceneId, initialValue) {
|
|
|
5492
5874
|
}
|
|
5493
5875
|
|
|
5494
5876
|
// src/hooks/useAnySolo.ts
|
|
5495
|
-
var
|
|
5877
|
+
var import_react27 = require("react");
|
|
5496
5878
|
function useAnySolo(host) {
|
|
5497
|
-
const [anySolo, setAnySolo] = (0,
|
|
5498
|
-
(0,
|
|
5879
|
+
const [anySolo, setAnySolo] = (0, import_react27.useState)(false);
|
|
5880
|
+
(0, import_react27.useEffect)(() => {
|
|
5499
5881
|
let active = true;
|
|
5500
5882
|
const refresh = () => {
|
|
5501
5883
|
host.isAnySoloActive().then((v) => {
|
|
@@ -5514,7 +5896,7 @@ function useAnySolo(host) {
|
|
|
5514
5896
|
}
|
|
5515
5897
|
|
|
5516
5898
|
// src/hooks/useSoundHistory.ts
|
|
5517
|
-
var
|
|
5899
|
+
var import_react28 = require("react");
|
|
5518
5900
|
var EMPTY = { entries: [], cursor: -1 };
|
|
5519
5901
|
function sameDescriptor(a, b) {
|
|
5520
5902
|
if (a === b) return true;
|
|
@@ -5526,14 +5908,14 @@ function sameDescriptor(a, b) {
|
|
|
5526
5908
|
}
|
|
5527
5909
|
function useSoundHistory(applySound, opts = {}) {
|
|
5528
5910
|
const max = Math.max(2, opts.max ?? 24);
|
|
5529
|
-
const applyRef = (0,
|
|
5911
|
+
const applyRef = (0, import_react28.useRef)(applySound);
|
|
5530
5912
|
applyRef.current = applySound;
|
|
5531
|
-
const onChangeRef = (0,
|
|
5913
|
+
const onChangeRef = (0, import_react28.useRef)(opts.onChange);
|
|
5532
5914
|
onChangeRef.current = opts.onChange;
|
|
5533
|
-
const dataRef = (0,
|
|
5534
|
-
const [, setVersion] = (0,
|
|
5535
|
-
const bump = (0,
|
|
5536
|
-
const commit = (0,
|
|
5915
|
+
const dataRef = (0, import_react28.useRef)({});
|
|
5916
|
+
const [, setVersion] = (0, import_react28.useState)(0);
|
|
5917
|
+
const bump = (0, import_react28.useCallback)(() => setVersion((v) => v + 1), []);
|
|
5918
|
+
const commit = (0, import_react28.useCallback)(
|
|
5537
5919
|
(trackId, next, notify) => {
|
|
5538
5920
|
dataRef.current = { ...dataRef.current, [trackId]: next };
|
|
5539
5921
|
bump();
|
|
@@ -5541,7 +5923,7 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
5541
5923
|
},
|
|
5542
5924
|
[bump]
|
|
5543
5925
|
);
|
|
5544
|
-
const record = (0,
|
|
5926
|
+
const record = (0, import_react28.useCallback)(
|
|
5545
5927
|
(trackId, descriptor, label) => {
|
|
5546
5928
|
const h = dataRef.current[trackId];
|
|
5547
5929
|
const current = h && h.cursor >= 0 ? h.entries[h.cursor] : void 0;
|
|
@@ -5556,7 +5938,7 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
5556
5938
|
},
|
|
5557
5939
|
[max, commit]
|
|
5558
5940
|
);
|
|
5559
|
-
const restoreTo = (0,
|
|
5941
|
+
const restoreTo = (0, import_react28.useCallback)(
|
|
5560
5942
|
async (trackId, index) => {
|
|
5561
5943
|
const h = dataRef.current[trackId];
|
|
5562
5944
|
if (!h || index < 0 || index >= h.entries.length || index === h.cursor) return false;
|
|
@@ -5566,7 +5948,7 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
5566
5948
|
},
|
|
5567
5949
|
[commit]
|
|
5568
5950
|
);
|
|
5569
|
-
const undo = (0,
|
|
5951
|
+
const undo = (0, import_react28.useCallback)(
|
|
5570
5952
|
(trackId) => {
|
|
5571
5953
|
const h = dataRef.current[trackId];
|
|
5572
5954
|
if (!h || h.cursor <= 0) return Promise.resolve(false);
|
|
@@ -5574,7 +5956,7 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
5574
5956
|
},
|
|
5575
5957
|
[restoreTo]
|
|
5576
5958
|
);
|
|
5577
|
-
const toggleFavorite = (0,
|
|
5959
|
+
const toggleFavorite = (0, import_react28.useCallback)(
|
|
5578
5960
|
(trackId, index) => {
|
|
5579
5961
|
const h = dataRef.current[trackId];
|
|
5580
5962
|
if (!h || index < 0 || index >= h.entries.length) return;
|
|
@@ -5583,7 +5965,7 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
5583
5965
|
},
|
|
5584
5966
|
[commit]
|
|
5585
5967
|
);
|
|
5586
|
-
const restore = (0,
|
|
5968
|
+
const restore = (0, import_react28.useCallback)(
|
|
5587
5969
|
(trackId, state) => {
|
|
5588
5970
|
const entries = Array.isArray(state?.entries) ? [...state.entries] : [];
|
|
5589
5971
|
const raw = typeof state?.cursor === "number" ? state.cursor : entries.length - 1;
|
|
@@ -5592,15 +5974,15 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
5592
5974
|
},
|
|
5593
5975
|
[commit]
|
|
5594
5976
|
);
|
|
5595
|
-
const list = (0,
|
|
5977
|
+
const list = (0, import_react28.useCallback)(
|
|
5596
5978
|
(trackId) => dataRef.current[trackId] ?? EMPTY,
|
|
5597
5979
|
[]
|
|
5598
5980
|
);
|
|
5599
|
-
const canUndo = (0,
|
|
5981
|
+
const canUndo = (0, import_react28.useCallback)((trackId) => {
|
|
5600
5982
|
const h = dataRef.current[trackId];
|
|
5601
5983
|
return !!h && h.cursor > 0;
|
|
5602
5984
|
}, []);
|
|
5603
|
-
const clear = (0,
|
|
5985
|
+
const clear = (0, import_react28.useCallback)(
|
|
5604
5986
|
(trackId) => {
|
|
5605
5987
|
if (dataRef.current[trackId]) {
|
|
5606
5988
|
const next = { ...dataRef.current };
|
|
@@ -5612,11 +5994,11 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
5612
5994
|
},
|
|
5613
5995
|
[bump]
|
|
5614
5996
|
);
|
|
5615
|
-
const reset = (0,
|
|
5997
|
+
const reset = (0, import_react28.useCallback)(() => {
|
|
5616
5998
|
dataRef.current = {};
|
|
5617
5999
|
bump();
|
|
5618
6000
|
}, [bump]);
|
|
5619
|
-
return (0,
|
|
6001
|
+
return (0, import_react28.useMemo)(
|
|
5620
6002
|
() => ({ record, undo, restoreTo, list, canUndo, clear, reset, restore, toggleFavorite }),
|
|
5621
6003
|
[record, undo, restoreTo, list, canUndo, clear, reset, restore, toggleFavorite]
|
|
5622
6004
|
);
|
|
@@ -5749,7 +6131,7 @@ function resolveTrackGroups(parsedGroups, tracks, getDbId, opts = {}) {
|
|
|
5749
6131
|
}
|
|
5750
6132
|
|
|
5751
6133
|
// src/panel-core/useTransitionOps.ts
|
|
5752
|
-
var
|
|
6134
|
+
var import_react29 = require("react");
|
|
5753
6135
|
function useTransitionOps({
|
|
5754
6136
|
host,
|
|
5755
6137
|
adapter,
|
|
@@ -5766,8 +6148,8 @@ function useTransitionOps({
|
|
|
5766
6148
|
resolvedFades
|
|
5767
6149
|
}) {
|
|
5768
6150
|
const { identity } = adapter;
|
|
5769
|
-
const appliedFadeAutomationRef = (0,
|
|
5770
|
-
const applyCrossfadeAutomation = (0,
|
|
6151
|
+
const appliedFadeAutomationRef = (0, import_react29.useRef)(/* @__PURE__ */ new Set());
|
|
6152
|
+
const applyCrossfadeAutomation = (0, import_react29.useCallback)(
|
|
5771
6153
|
async (originTrackId, targetTrackId, bars, bpm, sliderPos) => {
|
|
5772
6154
|
if (host.setTrackVolumeAutomation) {
|
|
5773
6155
|
const curves = buildCrossfadeVolumeCurves(bars, bpm, sliderPos);
|
|
@@ -5784,7 +6166,7 @@ function useTransitionOps({
|
|
|
5784
6166
|
},
|
|
5785
6167
|
[host]
|
|
5786
6168
|
);
|
|
5787
|
-
const applyFadeAutomation = (0,
|
|
6169
|
+
const applyFadeAutomation = (0, import_react29.useCallback)(
|
|
5788
6170
|
async (trackId, direction, bars, bpm, sliderPos, gesture) => {
|
|
5789
6171
|
if (!host.setTrackVolumeAutomation) return;
|
|
5790
6172
|
const points = buildFadeVolumeCurve(bars, bpm, direction, sliderPos, gesture);
|
|
@@ -5793,8 +6175,25 @@ function useTransitionOps({
|
|
|
5793
6175
|
},
|
|
5794
6176
|
[host]
|
|
5795
6177
|
);
|
|
5796
|
-
const
|
|
5797
|
-
|
|
6178
|
+
const copyTrackFx = (0, import_react29.useCallback)(
|
|
6179
|
+
async (newTrackId, sourceDbId) => {
|
|
6180
|
+
if (typeof host.copyTrackFxFrom !== "function") return;
|
|
6181
|
+
try {
|
|
6182
|
+
const res = await host.copyTrackFxFrom(newTrackId, sourceDbId);
|
|
6183
|
+
if (res.externalMissing.length > 0) {
|
|
6184
|
+
host.showToast(
|
|
6185
|
+
"warning",
|
|
6186
|
+
"Some FX not copied",
|
|
6187
|
+
`Missing plugin(s): ${res.externalMissing.join(", ")}`
|
|
6188
|
+
);
|
|
6189
|
+
}
|
|
6190
|
+
} catch {
|
|
6191
|
+
}
|
|
6192
|
+
},
|
|
6193
|
+
[host]
|
|
6194
|
+
);
|
|
6195
|
+
const [isCreatingCrossfade, setIsCreatingCrossfade] = (0, import_react29.useState)(false);
|
|
6196
|
+
const handleCreateCrossfade = (0, import_react29.useCallback)(
|
|
5798
6197
|
async (origin, target) => {
|
|
5799
6198
|
const scene = activeSceneId;
|
|
5800
6199
|
const fromSceneId = sceneContext?.transitionFromSceneId ?? "";
|
|
@@ -5871,6 +6270,8 @@ function useTransitionOps({
|
|
|
5871
6270
|
};
|
|
5872
6271
|
const originLabel = await copySound(top.id, origin.dbId);
|
|
5873
6272
|
const targetLabel = await copySound(bottom.id, target.dbId);
|
|
6273
|
+
await copyTrackFx(top.id, origin.dbId);
|
|
6274
|
+
await copyTrackFx(bottom.id, target.dbId);
|
|
5874
6275
|
await applyCrossfadeAutomation(top.id, bottom.id, mc.bars, mc.bpm, 0.5);
|
|
5875
6276
|
const groupId = top.dbId;
|
|
5876
6277
|
const originMeta = {
|
|
@@ -5922,8 +6323,9 @@ function useTransitionOps({
|
|
|
5922
6323
|
loadTracks
|
|
5923
6324
|
]
|
|
5924
6325
|
);
|
|
5925
|
-
const [
|
|
5926
|
-
const
|
|
6326
|
+
const [isCreatingGroupFade, setIsCreatingGroupFade] = (0, import_react29.useState)(false);
|
|
6327
|
+
const [isCreatingFade, setIsCreatingFade] = (0, import_react29.useState)(false);
|
|
6328
|
+
const handleCreateFade = (0, import_react29.useCallback)(
|
|
5927
6329
|
async (selection, direction, gesture) => {
|
|
5928
6330
|
const scene = activeSceneId;
|
|
5929
6331
|
const fromSceneId = sceneContext?.transitionFromSceneId ?? "";
|
|
@@ -5990,6 +6392,7 @@ function useTransitionOps({
|
|
|
5990
6392
|
soundLabel = await adapter.sound.copySnapshot(track.id, snap);
|
|
5991
6393
|
}
|
|
5992
6394
|
}
|
|
6395
|
+
await copyTrackFx(track.id, selection.dbId);
|
|
5993
6396
|
await applyFadeAutomation(track.id, direction, mc.bars, mc.bpm, 0.5, gesture);
|
|
5994
6397
|
appliedFadeAutomationRef.current.add(track.id);
|
|
5995
6398
|
const meta = {
|
|
@@ -6033,7 +6436,7 @@ function useTransitionOps({
|
|
|
6033
6436
|
loadTracks
|
|
6034
6437
|
]
|
|
6035
6438
|
);
|
|
6036
|
-
const handleCrossfadeMute = (0,
|
|
6439
|
+
const handleCrossfadeMute = (0, import_react29.useCallback)(
|
|
6037
6440
|
(pair) => {
|
|
6038
6441
|
const newMuted = !pair.origin.runtimeState.muted;
|
|
6039
6442
|
for (const id of [pair.origin.handle.id, pair.target.handle.id]) {
|
|
@@ -6048,7 +6451,7 @@ function useTransitionOps({
|
|
|
6048
6451
|
},
|
|
6049
6452
|
[host, setTracks]
|
|
6050
6453
|
);
|
|
6051
|
-
const handleCrossfadeSolo = (0,
|
|
6454
|
+
const handleCrossfadeSolo = (0, import_react29.useCallback)(
|
|
6052
6455
|
(pair) => {
|
|
6053
6456
|
const newSolo = !pair.origin.runtimeState.solo;
|
|
6054
6457
|
for (const id of [pair.origin.handle.id, pair.target.handle.id]) {
|
|
@@ -6063,7 +6466,7 @@ function useTransitionOps({
|
|
|
6063
6466
|
},
|
|
6064
6467
|
[host, setTracks]
|
|
6065
6468
|
);
|
|
6066
|
-
const handleCrossfadeDelete = (0,
|
|
6469
|
+
const handleCrossfadeDelete = (0, import_react29.useCallback)(
|
|
6067
6470
|
async (pair) => {
|
|
6068
6471
|
try {
|
|
6069
6472
|
for (const member of [pair.origin, pair.target]) {
|
|
@@ -6089,8 +6492,8 @@ function useTransitionOps({
|
|
|
6089
6492
|
},
|
|
6090
6493
|
[host, activeSceneId, setCrossfadePairsMeta, setTracks]
|
|
6091
6494
|
);
|
|
6092
|
-
const crossfadeSliderTimers = (0,
|
|
6093
|
-
const handleCrossfadeSlider = (0,
|
|
6495
|
+
const crossfadeSliderTimers = (0, import_react29.useRef)({});
|
|
6496
|
+
const handleCrossfadeSlider = (0, import_react29.useCallback)(
|
|
6094
6497
|
(pair, pos) => {
|
|
6095
6498
|
setCrossfadePairsMeta(
|
|
6096
6499
|
(prev) => prev.map((p) => p.groupId === pair.groupId ? { ...p, sliderPos: pos } : p)
|
|
@@ -6123,7 +6526,7 @@ function useTransitionOps({
|
|
|
6123
6526
|
},
|
|
6124
6527
|
[host, activeSceneId, applyCrossfadeAutomation, setCrossfadePairsMeta]
|
|
6125
6528
|
);
|
|
6126
|
-
const handleFadeDelete = (0,
|
|
6529
|
+
const handleFadeDelete = (0, import_react29.useCallback)(
|
|
6127
6530
|
async (fade) => {
|
|
6128
6531
|
try {
|
|
6129
6532
|
await host.deleteTrack(fade.track.handle.id);
|
|
@@ -6143,8 +6546,8 @@ function useTransitionOps({
|
|
|
6143
6546
|
},
|
|
6144
6547
|
[host, activeSceneId, setFadesMeta, setTracks]
|
|
6145
6548
|
);
|
|
6146
|
-
const fadeSliderTimers = (0,
|
|
6147
|
-
const handleFadeSlider = (0,
|
|
6549
|
+
const fadeSliderTimers = (0, import_react29.useRef)({});
|
|
6550
|
+
const handleFadeSlider = (0, import_react29.useCallback)(
|
|
6148
6551
|
(fade, pos) => {
|
|
6149
6552
|
setFadesMeta(
|
|
6150
6553
|
(prev) => prev.map((f) => f.dbId === fade.dbId ? { ...f, meta: { ...f.meta, sliderPos: pos } } : f)
|
|
@@ -6174,8 +6577,190 @@ function useTransitionOps({
|
|
|
6174
6577
|
},
|
|
6175
6578
|
[host, activeSceneId, applyFadeAutomation, setFadesMeta]
|
|
6176
6579
|
);
|
|
6177
|
-
const
|
|
6178
|
-
|
|
6580
|
+
const handleCreateVerbatimGroupFade = (0, import_react29.useCallback)(
|
|
6581
|
+
async (subject, direction) => {
|
|
6582
|
+
const groupAdapter = adapter.transitionGroup;
|
|
6583
|
+
if (!groupAdapter) throw new Error("This panel does not support group fades.");
|
|
6584
|
+
const scene = activeSceneId;
|
|
6585
|
+
const fromSceneId = sceneContext?.transitionFromSceneId ?? "";
|
|
6586
|
+
const toSceneId = sceneContext?.transitionToSceneId ?? "";
|
|
6587
|
+
if (!scene) throw new Error("No active scene.");
|
|
6588
|
+
if (!isConnected) throw new Error("Systems not connected.");
|
|
6589
|
+
const sourceSceneId = direction === "out" ? fromSceneId : toSceneId;
|
|
6590
|
+
const members = await groupAdapter.expandSubject(sourceSceneId, subject.dbId);
|
|
6591
|
+
if (members.length === 0) {
|
|
6592
|
+
throw new Error("Nothing to fade \u2014 the source group has no members.");
|
|
6593
|
+
}
|
|
6594
|
+
if (tracks.length + members.length > identity.maxTracks) {
|
|
6595
|
+
throw new Error(`Not enough track slots for this fade (needs ${members.length}).`);
|
|
6596
|
+
}
|
|
6597
|
+
setIsCreatingGroupFade(true);
|
|
6598
|
+
const created = [];
|
|
6599
|
+
try {
|
|
6600
|
+
const mc = await host.getMusicalContext();
|
|
6601
|
+
const sliderPos = groupAdapter.defaultSliderPos?.(direction) ?? 0.5;
|
|
6602
|
+
const gesture = "volume";
|
|
6603
|
+
const clipEnd = mc.bars * 4 * 60 / mc.bpm;
|
|
6604
|
+
const maxBeats = mc.bars * 4;
|
|
6605
|
+
for (const member of members) {
|
|
6606
|
+
const handle = await host.createTrack({
|
|
6607
|
+
name: `${identity.trackNamePrefix}-${Date.now()}-fade-${direction}-v${member.memberIndex}`,
|
|
6608
|
+
...adapter.createTrackOptions()
|
|
6609
|
+
});
|
|
6610
|
+
created.push({ handle, member, soundLabel: "default" });
|
|
6611
|
+
const role = member.role ?? subject.role ?? "";
|
|
6612
|
+
if (role) await host.setTrackRole(handle.id, role).catch(() => {
|
|
6613
|
+
});
|
|
6614
|
+
const srcMidi = host.readImportableTrackMidi ? await host.readImportableTrackMidi(member.dbId) : { clips: [] };
|
|
6615
|
+
const srcNotes = srcMidi.clips[0]?.notes ?? [];
|
|
6616
|
+
const notes = srcNotes.filter((n) => n.startBeat < maxBeats).map((n) => ({
|
|
6617
|
+
...n,
|
|
6618
|
+
durationBeats: Math.min(n.durationBeats, maxBeats - n.startBeat)
|
|
6619
|
+
}));
|
|
6620
|
+
if (notes.length > 0) {
|
|
6621
|
+
const clip = { startTime: 0, endTime: clipEnd, tempo: mc.bpm, notes };
|
|
6622
|
+
await host.writeMidiClip(handle.id, clip);
|
|
6623
|
+
}
|
|
6624
|
+
if (host.getTrackSound) {
|
|
6625
|
+
const snap = await host.getTrackSound(member.dbId);
|
|
6626
|
+
if (snap && snap.kind === adapter.sound.acceptedSnapshotKind) {
|
|
6627
|
+
created[created.length - 1].soundLabel = await adapter.sound.copySnapshot(
|
|
6628
|
+
handle.id,
|
|
6629
|
+
snap
|
|
6630
|
+
);
|
|
6631
|
+
}
|
|
6632
|
+
}
|
|
6633
|
+
await copyTrackFx(handle.id, member.dbId);
|
|
6634
|
+
await applyFadeAutomation(handle.id, direction, mc.bars, mc.bpm, sliderPos, gesture);
|
|
6635
|
+
appliedFadeAutomationRef.current.add(handle.id);
|
|
6636
|
+
}
|
|
6637
|
+
const groupId = created[0].handle.dbId;
|
|
6638
|
+
for (const { handle, member, soundLabel } of created) {
|
|
6639
|
+
const meta = {
|
|
6640
|
+
direction,
|
|
6641
|
+
gesture,
|
|
6642
|
+
sourceTrackDbId: member.dbId,
|
|
6643
|
+
sourceSceneId,
|
|
6644
|
+
sourceName: member.name,
|
|
6645
|
+
soundLabel,
|
|
6646
|
+
sliderPos,
|
|
6647
|
+
groupId,
|
|
6648
|
+
memberIndex: member.memberIndex,
|
|
6649
|
+
memberLabel: member.memberLabel
|
|
6650
|
+
};
|
|
6651
|
+
await host.setSceneData(scene, `track:${handle.dbId}:fade`, meta);
|
|
6652
|
+
}
|
|
6653
|
+
await groupAdapter.writeGroupMetas(
|
|
6654
|
+
scene,
|
|
6655
|
+
created.map(({ handle, member }) => ({ newDbId: handle.dbId, member })),
|
|
6656
|
+
groupId
|
|
6657
|
+
);
|
|
6658
|
+
await loadTracks(true);
|
|
6659
|
+
host.showToast(
|
|
6660
|
+
"success",
|
|
6661
|
+
direction === "in" ? "Fade in created" : "Fade out created",
|
|
6662
|
+
subject.name
|
|
6663
|
+
);
|
|
6664
|
+
} catch (err) {
|
|
6665
|
+
for (const { handle } of [...created].reverse()) {
|
|
6666
|
+
try {
|
|
6667
|
+
await host.deleteTrack(handle.id);
|
|
6668
|
+
} catch {
|
|
6669
|
+
}
|
|
6670
|
+
await host.deleteSceneData(scene, `track:${handle.dbId}:fade`).catch(() => {
|
|
6671
|
+
});
|
|
6672
|
+
for (const suffix of groupAdapter.cleanupKeySuffixes) {
|
|
6673
|
+
await host.deleteSceneData(scene, `track:${handle.dbId}:${suffix}`).catch(() => {
|
|
6674
|
+
});
|
|
6675
|
+
}
|
|
6676
|
+
}
|
|
6677
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
6678
|
+
} finally {
|
|
6679
|
+
setIsCreatingGroupFade(false);
|
|
6680
|
+
}
|
|
6681
|
+
},
|
|
6682
|
+
[
|
|
6683
|
+
host,
|
|
6684
|
+
adapter,
|
|
6685
|
+
identity,
|
|
6686
|
+
activeSceneId,
|
|
6687
|
+
isConnected,
|
|
6688
|
+
tracks.length,
|
|
6689
|
+
sceneContext,
|
|
6690
|
+
applyFadeAutomation,
|
|
6691
|
+
copyTrackFx,
|
|
6692
|
+
loadTracks
|
|
6693
|
+
]
|
|
6694
|
+
);
|
|
6695
|
+
const groupFadeSliderTimers = (0, import_react29.useRef)({});
|
|
6696
|
+
const handleGroupFadeSlider = (0, import_react29.useCallback)(
|
|
6697
|
+
(group, pos) => {
|
|
6698
|
+
setFadesMeta(
|
|
6699
|
+
(prev) => prev.map(
|
|
6700
|
+
(f) => f.meta.groupId === group.groupId ? { ...f, meta: { ...f.meta, sliderPos: pos } } : f
|
|
6701
|
+
)
|
|
6702
|
+
);
|
|
6703
|
+
if (groupFadeSliderTimers.current[group.groupId]) {
|
|
6704
|
+
clearTimeout(groupFadeSliderTimers.current[group.groupId]);
|
|
6705
|
+
}
|
|
6706
|
+
groupFadeSliderTimers.current[group.groupId] = setTimeout(() => {
|
|
6707
|
+
void (async () => {
|
|
6708
|
+
const mc = await host.getMusicalContext();
|
|
6709
|
+
const sceneData = activeSceneId ? await host.getAllSceneData(activeSceneId) : null;
|
|
6710
|
+
for (const member of group.members) {
|
|
6711
|
+
await applyFadeAutomation(
|
|
6712
|
+
member.track.handle.id,
|
|
6713
|
+
group.direction,
|
|
6714
|
+
mc.bars,
|
|
6715
|
+
mc.bpm,
|
|
6716
|
+
pos,
|
|
6717
|
+
group.gesture
|
|
6718
|
+
);
|
|
6719
|
+
if (activeSceneId && sceneData) {
|
|
6720
|
+
const meta = asFadeMeta(sceneData[`track:${member.dbId}:fade`]);
|
|
6721
|
+
if (meta) {
|
|
6722
|
+
host.setSceneData(activeSceneId, `track:${member.dbId}:fade`, {
|
|
6723
|
+
...meta,
|
|
6724
|
+
sliderPos: pos
|
|
6725
|
+
}).catch(() => {
|
|
6726
|
+
});
|
|
6727
|
+
}
|
|
6728
|
+
}
|
|
6729
|
+
}
|
|
6730
|
+
})();
|
|
6731
|
+
}, 200);
|
|
6732
|
+
},
|
|
6733
|
+
[host, activeSceneId, applyFadeAutomation, setFadesMeta]
|
|
6734
|
+
);
|
|
6735
|
+
const handleGroupFadeDelete = (0, import_react29.useCallback)(
|
|
6736
|
+
async (group) => {
|
|
6737
|
+
const suffixes = ["fade", ...adapter.transitionGroup?.cleanupKeySuffixes ?? []];
|
|
6738
|
+
try {
|
|
6739
|
+
for (const member of group.members) {
|
|
6740
|
+
await host.deleteTrack(member.track.handle.id);
|
|
6741
|
+
if (activeSceneId) {
|
|
6742
|
+
for (const suffix of suffixes) {
|
|
6743
|
+
await host.deleteSceneData(activeSceneId, `track:${member.dbId}:${suffix}`).catch(() => {
|
|
6744
|
+
});
|
|
6745
|
+
}
|
|
6746
|
+
}
|
|
6747
|
+
}
|
|
6748
|
+
setFadesMeta((prev) => prev.filter((f) => f.meta.groupId !== group.groupId));
|
|
6749
|
+
const gone = new Set(group.members.map((m) => m.track.handle.id));
|
|
6750
|
+
setTracks((prev) => prev.filter((t) => !gone.has(t.handle.id)));
|
|
6751
|
+
host.showToast("success", "Fade removed");
|
|
6752
|
+
} catch (err) {
|
|
6753
|
+
host.showToast(
|
|
6754
|
+
"error",
|
|
6755
|
+
"Failed to delete fade",
|
|
6756
|
+
err instanceof Error ? err.message : String(err)
|
|
6757
|
+
);
|
|
6758
|
+
}
|
|
6759
|
+
},
|
|
6760
|
+
[host, adapter, activeSceneId, setFadesMeta, setTracks]
|
|
6761
|
+
);
|
|
6762
|
+
const lastResyncKeyRef = (0, import_react29.useRef)("");
|
|
6763
|
+
(0, import_react29.useEffect)(() => {
|
|
6179
6764
|
if (!host.getTrackSound || resolvedCrossfadePairs.length === 0 && resolvedFades.length === 0) {
|
|
6180
6765
|
return;
|
|
6181
6766
|
}
|
|
@@ -6216,7 +6801,7 @@ function useTransitionOps({
|
|
|
6216
6801
|
cancelled = true;
|
|
6217
6802
|
};
|
|
6218
6803
|
}, [resolvedCrossfadePairs, resolvedFades, host, adapter]);
|
|
6219
|
-
(0,
|
|
6804
|
+
(0, import_react29.useEffect)(() => {
|
|
6220
6805
|
if (!host.setTrackVolumeAutomation || resolvedFades.length === 0) return;
|
|
6221
6806
|
void (async () => {
|
|
6222
6807
|
const mc = await host.getMusicalContext();
|
|
@@ -6245,12 +6830,16 @@ function useTransitionOps({
|
|
|
6245
6830
|
handleCrossfadeDelete,
|
|
6246
6831
|
handleCrossfadeSlider,
|
|
6247
6832
|
handleFadeDelete,
|
|
6248
|
-
handleFadeSlider
|
|
6833
|
+
handleFadeSlider,
|
|
6834
|
+
isCreatingGroupFade,
|
|
6835
|
+
handleCreateVerbatimGroupFade,
|
|
6836
|
+
handleGroupFadeSlider,
|
|
6837
|
+
handleGroupFadeDelete
|
|
6249
6838
|
};
|
|
6250
6839
|
}
|
|
6251
6840
|
|
|
6252
6841
|
// src/panel-core/useGeneratorPanelCore.tsx
|
|
6253
|
-
var
|
|
6842
|
+
var import_jsx_runtime26 = require("react/jsx-runtime");
|
|
6254
6843
|
var EMPTY_PLACEHOLDERS = [];
|
|
6255
6844
|
function useGeneratorPanelCore({
|
|
6256
6845
|
ui,
|
|
@@ -6270,8 +6859,8 @@ function useGeneratorPanelCore({
|
|
|
6270
6859
|
} = ui;
|
|
6271
6860
|
const { identity, features } = adapter;
|
|
6272
6861
|
const logTag = identity.logTag;
|
|
6273
|
-
const adapterRef = (0,
|
|
6274
|
-
(0,
|
|
6862
|
+
const adapterRef = (0, import_react30.useRef)(adapter);
|
|
6863
|
+
(0, import_react30.useEffect)(() => {
|
|
6275
6864
|
if (adapterRef.current !== adapter) {
|
|
6276
6865
|
adapterRef.current = adapter;
|
|
6277
6866
|
console.warn(
|
|
@@ -6281,27 +6870,27 @@ function useGeneratorPanelCore({
|
|
|
6281
6870
|
}, [adapter, logTag]);
|
|
6282
6871
|
const supportsMeters = typeof host.getTrackLevels === "function";
|
|
6283
6872
|
const trackLevels = useTrackLevels(host, isExpanded);
|
|
6284
|
-
const [tracks, setTracks] = (0,
|
|
6285
|
-
const [isLoadingTracks, setIsLoadingTracks] = (0,
|
|
6286
|
-
const [importOpen, setImportOpen] = (0,
|
|
6287
|
-
const [soundImportTarget, setSoundImportTarget] = (0,
|
|
6288
|
-
const [designerView, setDesignerView] = (0,
|
|
6289
|
-
const [transitionSourceTotal, setTransitionSourceTotal] = (0,
|
|
6290
|
-
const [crossfadePairsMeta, setCrossfadePairsMeta] = (0,
|
|
6291
|
-
const [fadesMeta, setFadesMeta] = (0,
|
|
6292
|
-
const [genericGroupMetas, setGenericGroupMetas] = (0,
|
|
6873
|
+
const [tracks, setTracks] = (0, import_react30.useState)([]);
|
|
6874
|
+
const [isLoadingTracks, setIsLoadingTracks] = (0, import_react30.useState)(false);
|
|
6875
|
+
const [importOpen, setImportOpen] = (0, import_react30.useState)(false);
|
|
6876
|
+
const [soundImportTarget, setSoundImportTarget] = (0, import_react30.useState)(null);
|
|
6877
|
+
const [designerView, setDesignerView] = (0, import_react30.useState)(false);
|
|
6878
|
+
const [transitionSourceTotal, setTransitionSourceTotal] = (0, import_react30.useState)(0);
|
|
6879
|
+
const [crossfadePairsMeta, setCrossfadePairsMeta] = (0, import_react30.useState)([]);
|
|
6880
|
+
const [fadesMeta, setFadesMeta] = (0, import_react30.useState)([]);
|
|
6881
|
+
const [genericGroupMetas, setGenericGroupMetas] = (0, import_react30.useState)({});
|
|
6293
6882
|
const [isComposing, , setIsComposingForScene] = useSceneState(activeSceneId, false);
|
|
6294
6883
|
const [placeholders, , setPlaceholdersForScene] = useSceneState(
|
|
6295
6884
|
activeSceneId,
|
|
6296
6885
|
EMPTY_PLACEHOLDERS
|
|
6297
6886
|
);
|
|
6298
|
-
const saveTimeoutRefs = (0,
|
|
6299
|
-
const editLoadStartedRef = (0,
|
|
6300
|
-
const [availableInstruments, setAvailableInstruments] = (0,
|
|
6301
|
-
const [instrumentsLoading, setInstrumentsLoading] = (0,
|
|
6302
|
-
const engineToDbIdRef = (0,
|
|
6303
|
-
const tracksLoadedForSceneRef = (0,
|
|
6304
|
-
const persistSoundHistory = (0,
|
|
6887
|
+
const saveTimeoutRefs = (0, import_react30.useRef)({});
|
|
6888
|
+
const editLoadStartedRef = (0, import_react30.useRef)(/* @__PURE__ */ new Set());
|
|
6889
|
+
const [availableInstruments, setAvailableInstruments] = (0, import_react30.useState)([]);
|
|
6890
|
+
const [instrumentsLoading, setInstrumentsLoading] = (0, import_react30.useState)(false);
|
|
6891
|
+
const engineToDbIdRef = (0, import_react30.useRef)(/* @__PURE__ */ new Map());
|
|
6892
|
+
const tracksLoadedForSceneRef = (0, import_react30.useRef)(null);
|
|
6893
|
+
const persistSoundHistory = (0, import_react30.useCallback)(
|
|
6305
6894
|
(trackId, state) => {
|
|
6306
6895
|
if (!activeSceneId) return;
|
|
6307
6896
|
const dbId = engineToDbIdRef.current.get(trackId) ?? trackId;
|
|
@@ -6321,7 +6910,7 @@ function useGeneratorPanelCore({
|
|
|
6321
6910
|
setItems: setTracks,
|
|
6322
6911
|
getId: (t) => t.handle.dbId
|
|
6323
6912
|
});
|
|
6324
|
-
const loadTracks = (0,
|
|
6913
|
+
const loadTracks = (0, import_react30.useCallback)(
|
|
6325
6914
|
async (incremental = false) => {
|
|
6326
6915
|
const sceneAtStart = activeSceneId;
|
|
6327
6916
|
if (!sceneAtStart) {
|
|
@@ -6446,18 +7035,18 @@ function useGeneratorPanelCore({
|
|
|
6446
7035
|
},
|
|
6447
7036
|
[host, activeSceneId, soundHistory, adapter, logTag]
|
|
6448
7037
|
);
|
|
6449
|
-
(0,
|
|
7038
|
+
(0, import_react30.useEffect)(() => {
|
|
6450
7039
|
loadTracks();
|
|
6451
7040
|
}, [loadTracks]);
|
|
6452
|
-
(0,
|
|
7041
|
+
(0, import_react30.useEffect)(() => {
|
|
6453
7042
|
const map = /* @__PURE__ */ new Map();
|
|
6454
7043
|
for (const t of tracks) {
|
|
6455
7044
|
map.set(t.handle.id, t.handle.dbId);
|
|
6456
7045
|
}
|
|
6457
7046
|
engineToDbIdRef.current = map;
|
|
6458
7047
|
}, [tracks]);
|
|
6459
|
-
const loadedCompletedIdsRef = (0,
|
|
6460
|
-
(0,
|
|
7048
|
+
const loadedCompletedIdsRef = (0, import_react30.useRef)(/* @__PURE__ */ new Set());
|
|
7049
|
+
(0, import_react30.useEffect)(() => {
|
|
6461
7050
|
if (placeholders.length === 0) {
|
|
6462
7051
|
loadedCompletedIdsRef.current.clear();
|
|
6463
7052
|
return;
|
|
@@ -6476,16 +7065,16 @@ function useGeneratorPanelCore({
|
|
|
6476
7065
|
loadTracks(true);
|
|
6477
7066
|
}
|
|
6478
7067
|
}, [placeholders, loadTracks, logTag]);
|
|
6479
|
-
const adoptAndLoad = (0,
|
|
7068
|
+
const adoptAndLoad = (0, import_react30.useCallback)(() => {
|
|
6480
7069
|
loadTracks(true);
|
|
6481
7070
|
}, [loadTracks]);
|
|
6482
|
-
(0,
|
|
7071
|
+
(0, import_react30.useEffect)(() => {
|
|
6483
7072
|
const unsub = host.onEngineReady(() => {
|
|
6484
7073
|
adoptAndLoad();
|
|
6485
7074
|
});
|
|
6486
7075
|
return unsub;
|
|
6487
7076
|
}, [host, adoptAndLoad]);
|
|
6488
|
-
(0,
|
|
7077
|
+
(0, import_react30.useEffect)(() => {
|
|
6489
7078
|
if (typeof host.onAfterAgentMutation !== "function") return;
|
|
6490
7079
|
let timer = null;
|
|
6491
7080
|
const unsub = host.onAfterAgentMutation(() => {
|
|
@@ -6500,13 +7089,13 @@ function useGeneratorPanelCore({
|
|
|
6500
7089
|
if (timer) clearTimeout(timer);
|
|
6501
7090
|
};
|
|
6502
7091
|
}, [host, loadTracks]);
|
|
6503
|
-
(0,
|
|
7092
|
+
(0, import_react30.useEffect)(() => {
|
|
6504
7093
|
const unsub = host.onTrackStateChange((trackId, state) => {
|
|
6505
7094
|
setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, runtimeState: state } : t));
|
|
6506
7095
|
});
|
|
6507
7096
|
return unsub;
|
|
6508
7097
|
}, [host]);
|
|
6509
|
-
(0,
|
|
7098
|
+
(0, import_react30.useEffect)(() => {
|
|
6510
7099
|
if (!features.bulkComposePlaceholders) return;
|
|
6511
7100
|
console.log(`[${logTag}] Subscribing to composeProgress`);
|
|
6512
7101
|
const unsub = host.onComposeProgress((event) => {
|
|
@@ -6540,7 +7129,7 @@ function useGeneratorPanelCore({
|
|
|
6540
7129
|
});
|
|
6541
7130
|
return unsub;
|
|
6542
7131
|
}, [host, setIsComposingForScene, setPlaceholdersForScene, features.bulkComposePlaceholders, logTag]);
|
|
6543
|
-
(0,
|
|
7132
|
+
(0, import_react30.useEffect)(() => {
|
|
6544
7133
|
const refs = saveTimeoutRefs;
|
|
6545
7134
|
return () => {
|
|
6546
7135
|
for (const timeout of Object.values(refs.current)) {
|
|
@@ -6548,9 +7137,9 @@ function useGeneratorPanelCore({
|
|
|
6548
7137
|
}
|
|
6549
7138
|
};
|
|
6550
7139
|
}, []);
|
|
6551
|
-
const isAddingTrackRef = (0,
|
|
6552
|
-
const [isAddingTrack, setIsAddingTrack] = (0,
|
|
6553
|
-
const handleAddTrack = (0,
|
|
7140
|
+
const isAddingTrackRef = (0, import_react30.useRef)(false);
|
|
7141
|
+
const [isAddingTrack, setIsAddingTrack] = (0, import_react30.useState)(false);
|
|
7142
|
+
const handleAddTrack = (0, import_react30.useCallback)(async () => {
|
|
6554
7143
|
if (isAddingTrackRef.current) return;
|
|
6555
7144
|
if (!activeSceneId) {
|
|
6556
7145
|
host.showToast("warning", "Select SCENE");
|
|
@@ -6590,7 +7179,7 @@ function useGeneratorPanelCore({
|
|
|
6590
7179
|
setIsAddingTrack(false);
|
|
6591
7180
|
}
|
|
6592
7181
|
}, [host, adapter, identity, activeSceneId, isConnected, isAuthenticated, tracks.length, onExpandSelf]);
|
|
6593
|
-
const handlePortTrack = (0,
|
|
7182
|
+
const handlePortTrack = (0, import_react30.useCallback)(
|
|
6594
7183
|
async (sel) => {
|
|
6595
7184
|
if (!activeSceneId) {
|
|
6596
7185
|
host.showToast("warning", "Select SCENE");
|
|
@@ -6647,7 +7236,7 @@ function useGeneratorPanelCore({
|
|
|
6647
7236
|
},
|
|
6648
7237
|
[host, adapter, identity, activeSceneId, isConnected, tracks.length, loadTracks]
|
|
6649
7238
|
);
|
|
6650
|
-
const handleSoundImportPick = (0,
|
|
7239
|
+
const handleSoundImportPick = (0, import_react30.useCallback)(
|
|
6651
7240
|
async (sel) => {
|
|
6652
7241
|
const target = soundImportTarget;
|
|
6653
7242
|
if (!target || !host.getTrackSound) {
|
|
@@ -6678,8 +7267,8 @@ function useGeneratorPanelCore({
|
|
|
6678
7267
|
},
|
|
6679
7268
|
[soundImportTarget, host, adapter, identity.familyKey, soundHistory]
|
|
6680
7269
|
);
|
|
6681
|
-
const [isExportingMidi, setIsExportingMidi] = (0,
|
|
6682
|
-
const handleExportMidi = (0,
|
|
7270
|
+
const [isExportingMidi, setIsExportingMidi] = (0, import_react30.useState)(false);
|
|
7271
|
+
const handleExportMidi = (0, import_react30.useCallback)(async () => {
|
|
6683
7272
|
if (isExportingMidi) return;
|
|
6684
7273
|
setIsExportingMidi(true);
|
|
6685
7274
|
try {
|
|
@@ -6710,10 +7299,10 @@ function useGeneratorPanelCore({
|
|
|
6710
7299
|
const xfFromId = sceneContext?.transitionFromSceneId ?? null;
|
|
6711
7300
|
const xfToId = sceneContext?.transitionToSceneId ?? null;
|
|
6712
7301
|
const canCrossfade = features.transitionDesigner && sceneContext?.sceneType === "transition" && !!xfFromId && !!xfToId && !!host.listSceneFamilyTracks;
|
|
6713
|
-
(0,
|
|
7302
|
+
(0, import_react30.useEffect)(() => {
|
|
6714
7303
|
if (!canCrossfade) setDesignerView(false);
|
|
6715
7304
|
}, [canCrossfade]);
|
|
6716
|
-
(0,
|
|
7305
|
+
(0, import_react30.useEffect)(() => {
|
|
6717
7306
|
if (!canCrossfade || !xfFromId || !xfToId || !host.listSceneFamilyTracks) {
|
|
6718
7307
|
setTransitionSourceTotal(0);
|
|
6719
7308
|
return;
|
|
@@ -6729,12 +7318,12 @@ function useGeneratorPanelCore({
|
|
|
6729
7318
|
};
|
|
6730
7319
|
}, [canCrossfade, xfFromId, xfToId, host]);
|
|
6731
7320
|
const transitionDone = crossfadePairsMeta.length * 2 + fadesMeta.length;
|
|
6732
|
-
(0,
|
|
7321
|
+
(0, import_react30.useEffect)(() => {
|
|
6733
7322
|
if (!onHeaderContent) return;
|
|
6734
7323
|
const addDisabled = needsContract || !isConnected || !activeSceneId || tracks.length >= identity.maxTracks || isAddingTrack;
|
|
6735
7324
|
onHeaderContent(
|
|
6736
|
-
/* @__PURE__ */ (0,
|
|
6737
|
-
features.importTracks && (!canCrossfade || !designerView) && host.listImportableTracks && /* @__PURE__ */ (0,
|
|
7325
|
+
/* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex gap-1 items-center", children: [
|
|
7326
|
+
features.importTracks && (!canCrossfade || !designerView) && host.listImportableTracks && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
|
|
6738
7327
|
"button",
|
|
6739
7328
|
{
|
|
6740
7329
|
"data-testid": `import-from-scene-${identity.familyKey}-button`,
|
|
@@ -6748,7 +7337,7 @@ function useGeneratorPanelCore({
|
|
|
6748
7337
|
children: identity.importTrackLabel ?? "Import Track"
|
|
6749
7338
|
}
|
|
6750
7339
|
),
|
|
6751
|
-
(!canCrossfade || !designerView) && /* @__PURE__ */ (0,
|
|
7340
|
+
(!canCrossfade || !designerView) && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
|
|
6752
7341
|
"button",
|
|
6753
7342
|
{
|
|
6754
7343
|
"data-testid": `add-${identity.familyKey}-track-button`,
|
|
@@ -6764,7 +7353,7 @@ function useGeneratorPanelCore({
|
|
|
6764
7353
|
children: identity.addTrackLabel ?? "Add Track"
|
|
6765
7354
|
}
|
|
6766
7355
|
),
|
|
6767
|
-
canCrossfade && /* @__PURE__ */ (0,
|
|
7356
|
+
canCrossfade && /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
|
|
6768
7357
|
"button",
|
|
6769
7358
|
{
|
|
6770
7359
|
"data-testid": `${identity.familyKey}-view-toggle`,
|
|
@@ -6783,7 +7372,7 @@ function useGeneratorPanelCore({
|
|
|
6783
7372
|
title: designerView ? "Back to the track list" : "Open the transition designer",
|
|
6784
7373
|
className: "relative overflow-hidden px-2 py-0.5 text-[10px] font-medium rounded-sm border border-sas-accent/40 text-sas-accent transition-colors hover:border-sas-accent disabled:opacity-50",
|
|
6785
7374
|
children: [
|
|
6786
|
-
transitionSourceTotal > 0 && /* @__PURE__ */ (0,
|
|
7375
|
+
transitionSourceTotal > 0 && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
|
|
6787
7376
|
"span",
|
|
6788
7377
|
{
|
|
6789
7378
|
className: "absolute inset-y-0 left-0 bg-sas-accent/25",
|
|
@@ -6791,7 +7380,7 @@ function useGeneratorPanelCore({
|
|
|
6791
7380
|
"aria-hidden": true
|
|
6792
7381
|
}
|
|
6793
7382
|
),
|
|
6794
|
-
/* @__PURE__ */ (0,
|
|
7383
|
+
/* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("span", { className: "relative", children: [
|
|
6795
7384
|
"\u21C4 ",
|
|
6796
7385
|
designerView ? "Transition" : "Tracks",
|
|
6797
7386
|
transitionSourceTotal > 0 ? ` ${transitionDone}/${transitionSourceTotal}` : ""
|
|
@@ -6822,7 +7411,7 @@ function useGeneratorPanelCore({
|
|
|
6822
7411
|
identity,
|
|
6823
7412
|
features.importTracks
|
|
6824
7413
|
]);
|
|
6825
|
-
(0,
|
|
7414
|
+
(0, import_react30.useEffect)(() => {
|
|
6826
7415
|
if (!onLoading) return;
|
|
6827
7416
|
const anyGenerating = tracks.some((t) => t.isGenerating);
|
|
6828
7417
|
onLoading(isLoadingTracks || anyGenerating || isBulkActive);
|
|
@@ -6830,7 +7419,7 @@ function useGeneratorPanelCore({
|
|
|
6830
7419
|
onLoading(false);
|
|
6831
7420
|
};
|
|
6832
7421
|
}, [onLoading, isLoadingTracks, tracks, isBulkActive]);
|
|
6833
|
-
const handleDeleteTrack = (0,
|
|
7422
|
+
const handleDeleteTrack = (0, import_react30.useCallback)(
|
|
6834
7423
|
async (trackId) => {
|
|
6835
7424
|
try {
|
|
6836
7425
|
await host.deleteTrack(trackId);
|
|
@@ -6846,7 +7435,7 @@ function useGeneratorPanelCore({
|
|
|
6846
7435
|
},
|
|
6847
7436
|
[host, activeSceneId]
|
|
6848
7437
|
);
|
|
6849
|
-
const handlePromptChange = (0,
|
|
7438
|
+
const handlePromptChange = (0, import_react30.useCallback)(
|
|
6850
7439
|
(trackId, prompt) => {
|
|
6851
7440
|
setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, prompt } : t));
|
|
6852
7441
|
const dbId = engineToDbIdRef.current.get(trackId) ?? trackId;
|
|
@@ -6862,7 +7451,7 @@ function useGeneratorPanelCore({
|
|
|
6862
7451
|
},
|
|
6863
7452
|
[host, activeSceneId]
|
|
6864
7453
|
);
|
|
6865
|
-
const resolvedGenericGroups = (0,
|
|
7454
|
+
const resolvedGenericGroups = (0, import_react30.useMemo)(() => {
|
|
6866
7455
|
const out = {};
|
|
6867
7456
|
for (const ext of adapter.groupExtensions ?? []) {
|
|
6868
7457
|
out[ext.metaKey] = resolveTrackGroups(
|
|
@@ -6876,18 +7465,18 @@ function useGeneratorPanelCore({
|
|
|
6876
7465
|
}
|
|
6877
7466
|
return out;
|
|
6878
7467
|
}, [adapter, genericGroupMetas, tracks]);
|
|
6879
|
-
const genericGroupMemberDbIds = (0,
|
|
7468
|
+
const genericGroupMemberDbIds = (0, import_react30.useMemo)(() => {
|
|
6880
7469
|
const s = /* @__PURE__ */ new Set();
|
|
6881
7470
|
for (const r of Object.values(resolvedGenericGroups)) {
|
|
6882
7471
|
for (const dbId of r.memberDbIds) s.add(dbId);
|
|
6883
7472
|
}
|
|
6884
7473
|
return s;
|
|
6885
7474
|
}, [resolvedGenericGroups]);
|
|
6886
|
-
const engineToDbId = (0,
|
|
7475
|
+
const engineToDbId = (0, import_react30.useCallback)(
|
|
6887
7476
|
(trackId) => engineToDbIdRef.current.get(trackId) ?? trackId,
|
|
6888
7477
|
[]
|
|
6889
7478
|
);
|
|
6890
|
-
const updateTrack = (0,
|
|
7479
|
+
const updateTrack = (0, import_react30.useCallback)(
|
|
6891
7480
|
(trackId, patch) => {
|
|
6892
7481
|
setTracks(
|
|
6893
7482
|
(prev) => prev.map(
|
|
@@ -6897,18 +7486,18 @@ function useGeneratorPanelCore({
|
|
|
6897
7486
|
},
|
|
6898
7487
|
[]
|
|
6899
7488
|
);
|
|
6900
|
-
const markEditLoaded = (0,
|
|
7489
|
+
const markEditLoaded = (0, import_react30.useCallback)((trackId) => {
|
|
6901
7490
|
editLoadStartedRef.current.add(trackId);
|
|
6902
7491
|
}, []);
|
|
6903
|
-
const tracksRef = (0,
|
|
6904
|
-
(0,
|
|
7492
|
+
const tracksRef = (0, import_react30.useRef)(tracks);
|
|
7493
|
+
(0, import_react30.useEffect)(() => {
|
|
6905
7494
|
tracksRef.current = tracks;
|
|
6906
7495
|
}, [tracks]);
|
|
6907
|
-
const resolvedGenericGroupsRef = (0,
|
|
6908
|
-
(0,
|
|
7496
|
+
const resolvedGenericGroupsRef = (0, import_react30.useRef)(resolvedGenericGroups);
|
|
7497
|
+
(0, import_react30.useEffect)(() => {
|
|
6909
7498
|
resolvedGenericGroupsRef.current = resolvedGenericGroups;
|
|
6910
7499
|
}, [resolvedGenericGroups]);
|
|
6911
|
-
const makeServices = (0,
|
|
7500
|
+
const makeServices = (0, import_react30.useCallback)(() => {
|
|
6912
7501
|
return {
|
|
6913
7502
|
host,
|
|
6914
7503
|
activeSceneId,
|
|
@@ -6927,7 +7516,7 @@ function useGeneratorPanelCore({
|
|
|
6927
7516
|
resolvedGroups: (metaKey) => resolvedGenericGroupsRef.current[metaKey]?.resolved ?? []
|
|
6928
7517
|
};
|
|
6929
7518
|
}, [host, activeSceneId, updateTrack, loadTracks, soundHistory, engineToDbId, markEditLoaded, identity, adapter]);
|
|
6930
|
-
const handleGenerate = (0,
|
|
7519
|
+
const handleGenerate = (0, import_react30.useCallback)(
|
|
6931
7520
|
async (trackId) => {
|
|
6932
7521
|
const track = tracks.find((t) => t.handle.id === trackId);
|
|
6933
7522
|
if (!track || !track.prompt.trim()) return;
|
|
@@ -6954,7 +7543,7 @@ function useGeneratorPanelCore({
|
|
|
6954
7543
|
},
|
|
6955
7544
|
[host, adapter, tracks, isAuthenticated, makeServices]
|
|
6956
7545
|
);
|
|
6957
|
-
const handleMuteToggle = (0,
|
|
7546
|
+
const handleMuteToggle = (0, import_react30.useCallback)(
|
|
6958
7547
|
(trackId) => {
|
|
6959
7548
|
const track = tracks.find((t) => t.handle.id === trackId);
|
|
6960
7549
|
if (!track) return;
|
|
@@ -6974,7 +7563,7 @@ function useGeneratorPanelCore({
|
|
|
6974
7563
|
},
|
|
6975
7564
|
[host, tracks]
|
|
6976
7565
|
);
|
|
6977
|
-
const handleSoloToggle = (0,
|
|
7566
|
+
const handleSoloToggle = (0, import_react30.useCallback)(
|
|
6978
7567
|
(trackId) => {
|
|
6979
7568
|
const track = tracks.find((t) => t.handle.id === trackId);
|
|
6980
7569
|
if (!track) return;
|
|
@@ -6994,7 +7583,7 @@ function useGeneratorPanelCore({
|
|
|
6994
7583
|
},
|
|
6995
7584
|
[host, tracks]
|
|
6996
7585
|
);
|
|
6997
|
-
const handleVolumeChange = (0,
|
|
7586
|
+
const handleVolumeChange = (0, import_react30.useCallback)(
|
|
6998
7587
|
(trackId, volume) => {
|
|
6999
7588
|
setTracks(
|
|
7000
7589
|
(prev) => prev.map((t) => t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, volume } } : t)
|
|
@@ -7004,7 +7593,7 @@ function useGeneratorPanelCore({
|
|
|
7004
7593
|
},
|
|
7005
7594
|
[host]
|
|
7006
7595
|
);
|
|
7007
|
-
const handlePanChange = (0,
|
|
7596
|
+
const handlePanChange = (0, import_react30.useCallback)(
|
|
7008
7597
|
(trackId, pan) => {
|
|
7009
7598
|
setTracks(
|
|
7010
7599
|
(prev) => prev.map((t) => t.handle.id === trackId ? { ...t, runtimeState: { ...t.runtimeState, pan } } : t)
|
|
@@ -7014,7 +7603,7 @@ function useGeneratorPanelCore({
|
|
|
7014
7603
|
},
|
|
7015
7604
|
[host]
|
|
7016
7605
|
);
|
|
7017
|
-
const handleShuffle = (0,
|
|
7606
|
+
const handleShuffle = (0, import_react30.useCallback)(
|
|
7018
7607
|
async (trackId) => {
|
|
7019
7608
|
const track = tracks.find((t) => t.handle.id === trackId);
|
|
7020
7609
|
if (!track) return;
|
|
@@ -7056,7 +7645,7 @@ function useGeneratorPanelCore({
|
|
|
7056
7645
|
},
|
|
7057
7646
|
[host, adapter, tracks, soundHistory, logTag]
|
|
7058
7647
|
);
|
|
7059
|
-
const handleCopy = (0,
|
|
7648
|
+
const handleCopy = (0, import_react30.useCallback)(
|
|
7060
7649
|
async (trackId) => {
|
|
7061
7650
|
try {
|
|
7062
7651
|
const newHandle = await host.duplicateTrack(trackId);
|
|
@@ -7069,7 +7658,7 @@ function useGeneratorPanelCore({
|
|
|
7069
7658
|
},
|
|
7070
7659
|
[host, loadTracks]
|
|
7071
7660
|
);
|
|
7072
|
-
const handleFxToggle = (0,
|
|
7661
|
+
const handleFxToggle = (0, import_react30.useCallback)(
|
|
7073
7662
|
(trackId, category, enabled) => {
|
|
7074
7663
|
setTracks(
|
|
7075
7664
|
(prev) => prev.map(
|
|
@@ -7092,7 +7681,7 @@ function useGeneratorPanelCore({
|
|
|
7092
7681
|
},
|
|
7093
7682
|
[host]
|
|
7094
7683
|
);
|
|
7095
|
-
const handleFxPresetChange = (0,
|
|
7684
|
+
const handleFxPresetChange = (0, import_react30.useCallback)(
|
|
7096
7685
|
(trackId, category, presetIndex) => {
|
|
7097
7686
|
setTracks(
|
|
7098
7687
|
(prev) => prev.map(
|
|
@@ -7118,7 +7707,7 @@ function useGeneratorPanelCore({
|
|
|
7118
7707
|
},
|
|
7119
7708
|
[host]
|
|
7120
7709
|
);
|
|
7121
|
-
const handleFxDryWetChange = (0,
|
|
7710
|
+
const handleFxDryWetChange = (0, import_react30.useCallback)(
|
|
7122
7711
|
(trackId, category, value) => {
|
|
7123
7712
|
setTracks(
|
|
7124
7713
|
(prev) => prev.map(
|
|
@@ -7130,7 +7719,7 @@ function useGeneratorPanelCore({
|
|
|
7130
7719
|
},
|
|
7131
7720
|
[host]
|
|
7132
7721
|
);
|
|
7133
|
-
const toggleFxDrawer = (0,
|
|
7722
|
+
const toggleFxDrawer = (0, import_react30.useCallback)(
|
|
7134
7723
|
(trackId) => {
|
|
7135
7724
|
setTracks(
|
|
7136
7725
|
(prev) => prev.map((t) => {
|
|
@@ -7152,7 +7741,7 @@ function useGeneratorPanelCore({
|
|
|
7152
7741
|
},
|
|
7153
7742
|
[host, tracks]
|
|
7154
7743
|
);
|
|
7155
|
-
const loadEditNotes = (0,
|
|
7744
|
+
const loadEditNotes = (0, import_react30.useCallback)(
|
|
7156
7745
|
async (trackId) => {
|
|
7157
7746
|
try {
|
|
7158
7747
|
const mc = await host.getMusicalContext();
|
|
@@ -7170,7 +7759,7 @@ function useGeneratorPanelCore({
|
|
|
7170
7759
|
},
|
|
7171
7760
|
[host, logTag]
|
|
7172
7761
|
);
|
|
7173
|
-
const handleNotesChange = (0,
|
|
7762
|
+
const handleNotesChange = (0, import_react30.useCallback)(
|
|
7174
7763
|
(trackId, notes) => {
|
|
7175
7764
|
setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, editNotes: notes } : t));
|
|
7176
7765
|
const key = `edit:${trackId}`;
|
|
@@ -7200,7 +7789,7 @@ function useGeneratorPanelCore({
|
|
|
7200
7789
|
},
|
|
7201
7790
|
[host]
|
|
7202
7791
|
);
|
|
7203
|
-
const handleTabChange = (0,
|
|
7792
|
+
const handleTabChange = (0, import_react30.useCallback)(
|
|
7204
7793
|
(trackId, tab) => {
|
|
7205
7794
|
setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, drawerOpen: true, drawerTab: tab } : t));
|
|
7206
7795
|
if (tab === "fx") {
|
|
@@ -7225,10 +7814,10 @@ function useGeneratorPanelCore({
|
|
|
7225
7814
|
},
|
|
7226
7815
|
[host, availableInstruments.length, instrumentsLoading, loadEditNotes]
|
|
7227
7816
|
);
|
|
7228
|
-
const handleProgressChange = (0,
|
|
7817
|
+
const handleProgressChange = (0, import_react30.useCallback)((trackId, pct) => {
|
|
7229
7818
|
setTracks((prev) => prev.map((t) => t.handle.id === trackId ? { ...t, generationProgress: pct } : t));
|
|
7230
7819
|
}, []);
|
|
7231
|
-
const handleToggleDrawer = (0,
|
|
7820
|
+
const handleToggleDrawer = (0, import_react30.useCallback)((trackId) => {
|
|
7232
7821
|
setTracks(
|
|
7233
7822
|
(prev) => prev.map((t) => {
|
|
7234
7823
|
if (t.handle.id !== trackId) return t;
|
|
@@ -7237,7 +7826,7 @@ function useGeneratorPanelCore({
|
|
|
7237
7826
|
})
|
|
7238
7827
|
);
|
|
7239
7828
|
}, []);
|
|
7240
|
-
const handleInstrumentSelect = (0,
|
|
7829
|
+
const handleInstrumentSelect = (0, import_react30.useCallback)(
|
|
7241
7830
|
async (trackId, pluginId) => {
|
|
7242
7831
|
const isDefaultInstrument = pluginId === (identity.defaultInstrumentPluginId ?? "Surge XT");
|
|
7243
7832
|
if (isDefaultInstrument) {
|
|
@@ -7290,7 +7879,7 @@ function useGeneratorPanelCore({
|
|
|
7290
7879
|
},
|
|
7291
7880
|
[host, identity.defaultInstrumentPluginId, logTag]
|
|
7292
7881
|
);
|
|
7293
|
-
const handleShowEditor = (0,
|
|
7882
|
+
const handleShowEditor = (0, import_react30.useCallback)(
|
|
7294
7883
|
async (trackId) => {
|
|
7295
7884
|
try {
|
|
7296
7885
|
await host.showInstrumentEditor(trackId);
|
|
@@ -7301,12 +7890,12 @@ function useGeneratorPanelCore({
|
|
|
7301
7890
|
},
|
|
7302
7891
|
[host]
|
|
7303
7892
|
);
|
|
7304
|
-
const handleBackToInstruments = (0,
|
|
7893
|
+
const handleBackToInstruments = (0, import_react30.useCallback)((trackId) => {
|
|
7305
7894
|
setTracks(
|
|
7306
7895
|
(prev) => prev.map((t) => t.handle.id === trackId ? { ...t, editorStage: false } : t)
|
|
7307
7896
|
);
|
|
7308
7897
|
}, []);
|
|
7309
|
-
const handleRefreshInstruments = (0,
|
|
7898
|
+
const handleRefreshInstruments = (0, import_react30.useCallback)(() => {
|
|
7310
7899
|
setInstrumentsLoading(true);
|
|
7311
7900
|
host.getAvailableInstruments().then((instruments) => {
|
|
7312
7901
|
setAvailableInstruments(instruments);
|
|
@@ -7315,13 +7904,13 @@ function useGeneratorPanelCore({
|
|
|
7315
7904
|
setInstrumentsLoading(false);
|
|
7316
7905
|
});
|
|
7317
7906
|
}, [host]);
|
|
7318
|
-
const onAuditionNote = (0,
|
|
7907
|
+
const onAuditionNote = (0, import_react30.useCallback)(
|
|
7319
7908
|
(trackId, pitch, velocity, ms) => {
|
|
7320
7909
|
void host.auditionNote(trackId, pitch, velocity, ms);
|
|
7321
7910
|
},
|
|
7322
7911
|
[host]
|
|
7323
7912
|
);
|
|
7324
|
-
const { resolvedCrossfadePairs, crossfadeMemberDbIds } = (0,
|
|
7913
|
+
const { resolvedCrossfadePairs, crossfadeMemberDbIds } = (0, import_react30.useMemo)(() => {
|
|
7325
7914
|
const byDbId = new Map(tracks.map((t) => [t.handle.dbId, t]));
|
|
7326
7915
|
const pairs = [];
|
|
7327
7916
|
const members = /* @__PURE__ */ new Set();
|
|
@@ -7336,7 +7925,7 @@ function useGeneratorPanelCore({
|
|
|
7336
7925
|
}
|
|
7337
7926
|
return { resolvedCrossfadePairs: pairs, crossfadeMemberDbIds: members };
|
|
7338
7927
|
}, [tracks, crossfadePairsMeta]);
|
|
7339
|
-
const { resolvedFades, fadeMemberDbIds } = (0,
|
|
7928
|
+
const { resolvedFades, fadeMemberDbIds } = (0, import_react30.useMemo)(() => {
|
|
7340
7929
|
const byDbId = new Map(tracks.map((t) => [t.handle.dbId, t]));
|
|
7341
7930
|
const list = [];
|
|
7342
7931
|
const members = /* @__PURE__ */ new Set();
|
|
@@ -7349,6 +7938,10 @@ function useGeneratorPanelCore({
|
|
|
7349
7938
|
}
|
|
7350
7939
|
return { resolvedFades: list, fadeMemberDbIds: members };
|
|
7351
7940
|
}, [tracks, fadesMeta]);
|
|
7941
|
+
const { singles: resolvedSingleFades, groups: resolvedGroupFades } = (0, import_react30.useMemo)(
|
|
7942
|
+
() => splitFadeEntries(resolvedFades),
|
|
7943
|
+
[resolvedFades]
|
|
7944
|
+
);
|
|
7352
7945
|
const transition = useTransitionOps({
|
|
7353
7946
|
host,
|
|
7354
7947
|
adapter,
|
|
@@ -7364,7 +7957,7 @@ function useGeneratorPanelCore({
|
|
|
7364
7957
|
resolvedCrossfadePairs,
|
|
7365
7958
|
resolvedFades
|
|
7366
7959
|
});
|
|
7367
|
-
const setGroupMute = (0,
|
|
7960
|
+
const setGroupMute = (0, import_react30.useCallback)(
|
|
7368
7961
|
(trackIds, muted) => {
|
|
7369
7962
|
for (const id of trackIds) {
|
|
7370
7963
|
setTracks(
|
|
@@ -7376,7 +7969,7 @@ function useGeneratorPanelCore({
|
|
|
7376
7969
|
},
|
|
7377
7970
|
[host]
|
|
7378
7971
|
);
|
|
7379
|
-
const setGroupSolo = (0,
|
|
7972
|
+
const setGroupSolo = (0, import_react30.useCallback)(
|
|
7380
7973
|
(trackIds, solo) => {
|
|
7381
7974
|
for (const id of trackIds) {
|
|
7382
7975
|
setTracks(
|
|
@@ -7388,7 +7981,7 @@ function useGeneratorPanelCore({
|
|
|
7388
7981
|
},
|
|
7389
7982
|
[host]
|
|
7390
7983
|
);
|
|
7391
|
-
const deleteGroup = (0,
|
|
7984
|
+
const deleteGroup = (0, import_react30.useCallback)(
|
|
7392
7985
|
async (members, cleanupKeySuffixes) => {
|
|
7393
7986
|
for (const member of members) {
|
|
7394
7987
|
try {
|
|
@@ -7408,7 +8001,7 @@ function useGeneratorPanelCore({
|
|
|
7408
8001
|
},
|
|
7409
8002
|
[host, activeSceneId, loadTracks]
|
|
7410
8003
|
);
|
|
7411
|
-
const handlers = (0,
|
|
8004
|
+
const handlers = (0, import_react30.useMemo)(
|
|
7412
8005
|
() => ({
|
|
7413
8006
|
promptChange: handlePromptChange,
|
|
7414
8007
|
generate: (trackId) => {
|
|
@@ -7485,6 +8078,8 @@ function useGeneratorPanelCore({
|
|
|
7485
8078
|
crossfadeMemberDbIds,
|
|
7486
8079
|
resolvedFades,
|
|
7487
8080
|
fadeMemberDbIds,
|
|
8081
|
+
resolvedSingleFades,
|
|
8082
|
+
resolvedGroupFades,
|
|
7488
8083
|
resolvedGenericGroups,
|
|
7489
8084
|
genericGroupMemberDbIds,
|
|
7490
8085
|
availableInstruments,
|
|
@@ -7522,8 +8117,8 @@ function useGeneratorPanelCore({
|
|
|
7522
8117
|
}
|
|
7523
8118
|
|
|
7524
8119
|
// src/panel-core/GeneratorPanelShell.tsx
|
|
7525
|
-
var
|
|
7526
|
-
var
|
|
8120
|
+
var import_react31 = __toESM(require("react"));
|
|
8121
|
+
var import_jsx_runtime27 = require("react/jsx-runtime");
|
|
7527
8122
|
function GeneratorPanelShell({ core, slots }) {
|
|
7528
8123
|
const {
|
|
7529
8124
|
ui,
|
|
@@ -7552,7 +8147,8 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
7552
8147
|
fadesMeta,
|
|
7553
8148
|
resolvedCrossfadePairs,
|
|
7554
8149
|
crossfadeMemberDbIds,
|
|
7555
|
-
|
|
8150
|
+
resolvedSingleFades,
|
|
8151
|
+
resolvedGroupFades,
|
|
7556
8152
|
fadeMemberDbIds,
|
|
7557
8153
|
resolvedGenericGroups,
|
|
7558
8154
|
genericGroupMemberDbIds,
|
|
@@ -7578,7 +8174,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
7578
8174
|
const { host, activeSceneId, isAuthenticated, sceneContext, onSelectScene, onOpenContract } = ui;
|
|
7579
8175
|
const panelBus = usePanelBus(host, activeSceneId);
|
|
7580
8176
|
const { identity, features } = adapter;
|
|
7581
|
-
const buildRowProps = (0,
|
|
8177
|
+
const buildRowProps = (0, import_react31.useCallback)(
|
|
7582
8178
|
(track, drag) => {
|
|
7583
8179
|
const id = track.handle.id;
|
|
7584
8180
|
const pickerProps = features.instrumentPicker ? {
|
|
@@ -7678,12 +8274,12 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
7678
8274
|
]
|
|
7679
8275
|
);
|
|
7680
8276
|
if (!activeSceneId) {
|
|
7681
|
-
return /* @__PURE__ */ (0,
|
|
8277
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
7682
8278
|
"div",
|
|
7683
8279
|
{
|
|
7684
8280
|
"data-testid": `no-scene-placeholder-${identity.familyKey}`,
|
|
7685
8281
|
className: "flex items-center justify-center py-8",
|
|
7686
|
-
children: /* @__PURE__ */ (0,
|
|
8282
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
7687
8283
|
"button",
|
|
7688
8284
|
{
|
|
7689
8285
|
onClick: () => onSelectScene?.(),
|
|
@@ -7695,12 +8291,12 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
7695
8291
|
);
|
|
7696
8292
|
}
|
|
7697
8293
|
if (!sceneContext?.hasContract) {
|
|
7698
|
-
return /* @__PURE__ */ (0,
|
|
8294
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
7699
8295
|
"div",
|
|
7700
8296
|
{
|
|
7701
8297
|
"data-testid": `no-contract-placeholder-${identity.familyKey}`,
|
|
7702
8298
|
className: "flex items-center justify-center py-8",
|
|
7703
|
-
children: /* @__PURE__ */ (0,
|
|
8299
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
7704
8300
|
"button",
|
|
7705
8301
|
{
|
|
7706
8302
|
onClick: () => onOpenContract?.(),
|
|
@@ -7712,7 +8308,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
7712
8308
|
);
|
|
7713
8309
|
}
|
|
7714
8310
|
if (features.bulkComposePlaceholders && isComposing) {
|
|
7715
|
-
return /* @__PURE__ */ (0,
|
|
8311
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(SorceryProgressBar, { isLoading: true, statusText: "COMPOSING...", heightClass: "h-10" }) });
|
|
7716
8312
|
}
|
|
7717
8313
|
const activePlaceholders = features.bulkComposePlaceholders ? placeholders : [];
|
|
7718
8314
|
if (activePlaceholders.length > 0) {
|
|
@@ -7723,18 +8319,18 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
7723
8319
|
tracksByDbId.set(t.handle.id, t);
|
|
7724
8320
|
}
|
|
7725
8321
|
}
|
|
7726
|
-
return /* @__PURE__ */ (0,
|
|
8322
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: activePlaceholders.map((ph) => {
|
|
7727
8323
|
const loadedTrack = ph.status === "completed" ? tracksByDbId.get(ph.id) : void 0;
|
|
7728
8324
|
if (loadedTrack) {
|
|
7729
|
-
return /* @__PURE__ */ (0,
|
|
8325
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(TrackRow, { ...buildRowProps(loadedTrack) }, ph.id);
|
|
7730
8326
|
}
|
|
7731
|
-
return /* @__PURE__ */ (0,
|
|
8327
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
7732
8328
|
"div",
|
|
7733
8329
|
{
|
|
7734
8330
|
"data-testid": "bulk-placeholder-track",
|
|
7735
8331
|
className: "relative rounded-sm border w-full overflow-hidden border-sas-border bg-sas-panel-alt",
|
|
7736
8332
|
style: { borderLeftColor: identity.placeholderAccentColor, borderLeftWidth: "3px" },
|
|
7737
|
-
children: /* @__PURE__ */ (0,
|
|
8333
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(SorceryProgressBar, { isLoading: true, statusText: "CONJURING MIDI...", heightClass: "h-10" })
|
|
7738
8334
|
},
|
|
7739
8335
|
ph.id
|
|
7740
8336
|
);
|
|
@@ -7746,13 +8342,13 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
7746
8342
|
supportsMeters,
|
|
7747
8343
|
levels: supportsMeters ? trackLevels : void 0,
|
|
7748
8344
|
handlers,
|
|
7749
|
-
renderDefaultTrackRow: (track, overrides, drag) => /* @__PURE__ */ (0,
|
|
8345
|
+
renderDefaultTrackRow: (track, overrides, drag) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(TrackRow, { ...{ ...buildRowProps(track, drag), ...overrides ?? {} } }, track.handle.id),
|
|
7750
8346
|
setGroupMute,
|
|
7751
8347
|
setGroupSolo,
|
|
7752
8348
|
deleteGroup
|
|
7753
8349
|
};
|
|
7754
|
-
return /* @__PURE__ */ (0,
|
|
7755
|
-
features.importTracks && host.listImportableTracks && /* @__PURE__ */ (0,
|
|
8350
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { "data-testid": `${identity.familyKey}-section`, className: "p-2 space-y-2", children: [
|
|
8351
|
+
features.importTracks && host.listImportableTracks && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
7756
8352
|
ImportTrackModal,
|
|
7757
8353
|
{
|
|
7758
8354
|
host,
|
|
@@ -7765,7 +8361,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
7765
8361
|
testIdPrefix: `${identity.familyKey}-import`
|
|
7766
8362
|
}
|
|
7767
8363
|
),
|
|
7768
|
-
features.importTracks && host.listImportableTracks && host.getTrackSound && /* @__PURE__ */ (0,
|
|
8364
|
+
features.importTracks && host.listImportableTracks && host.getTrackSound && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
7769
8365
|
ImportTrackModal,
|
|
7770
8366
|
{
|
|
7771
8367
|
host,
|
|
@@ -7780,7 +8376,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
7780
8376
|
}
|
|
7781
8377
|
),
|
|
7782
8378
|
slots?.modals,
|
|
7783
|
-
canCrossfade && xfFromId && xfToId && /* @__PURE__ */ (0,
|
|
8379
|
+
canCrossfade && xfFromId && xfToId && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: designerView ? "contents" : "hidden", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
7784
8380
|
TransitionDesigner,
|
|
7785
8381
|
{
|
|
7786
8382
|
host,
|
|
@@ -7791,14 +8387,16 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
7791
8387
|
...crossfadePairsMeta.flatMap((p) => [p.originSourceDbId, p.targetSourceDbId]),
|
|
7792
8388
|
...fadesMeta.map((f) => f.meta.sourceTrackDbId)
|
|
7793
8389
|
],
|
|
7794
|
-
onCreateCrossfade: transition.handleCreateCrossfade,
|
|
7795
|
-
onCreateFade: transition.handleCreateFade,
|
|
8390
|
+
onCreateCrossfade: adapter.transitionGroup?.fadeOnly ? void 0 : transition.handleCreateCrossfade,
|
|
8391
|
+
onCreateFade: adapter.transitionGroup ? (sel, dir) => transition.handleCreateVerbatimGroupFade(sel, dir) : transition.handleCreateFade,
|
|
8392
|
+
mapColumnSubjects: adapter.transitionGroup ? (sceneId, tracks2) => adapter.transitionGroup.mapColumnSubjects(sceneId, tracks2) : void 0,
|
|
8393
|
+
fadeEstimateMs: adapter.transitionGroup?.fadeEstimateMs,
|
|
7796
8394
|
familyLabel: identity.familyLabel,
|
|
7797
8395
|
testIdPrefix: `${identity.familyKey}-transition-designer`
|
|
7798
8396
|
}
|
|
7799
8397
|
) }),
|
|
7800
|
-
!(designerView && canCrossfade) && (isLoadingTracks ? /* @__PURE__ */ (0,
|
|
7801
|
-
panelBus.supported && panelBus.bus && /* @__PURE__ */ (0,
|
|
8398
|
+
!(designerView && canCrossfade) && (isLoadingTracks ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "text-sas-muted text-xs text-center py-4", children: "Loading tracks..." }) : /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
|
|
8399
|
+
panelBus.supported && panelBus.bus && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
7802
8400
|
PanelMasterStrip,
|
|
7803
8401
|
{
|
|
7804
8402
|
bus: panelBus.bus,
|
|
@@ -7819,7 +8417,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
7819
8417
|
}
|
|
7820
8418
|
),
|
|
7821
8419
|
slots?.beforeRows,
|
|
7822
|
-
resolvedCrossfadePairs.map((pair) => /* @__PURE__ */ (0,
|
|
8420
|
+
resolvedCrossfadePairs.map((pair) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
7823
8421
|
CrossfadeTrackRow,
|
|
7824
8422
|
{
|
|
7825
8423
|
accentColor: identity.transitionAccentColor,
|
|
@@ -7856,7 +8454,7 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
7856
8454
|
},
|
|
7857
8455
|
pair.groupId
|
|
7858
8456
|
)),
|
|
7859
|
-
|
|
8457
|
+
resolvedSingleFades.map((fade) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
7860
8458
|
FadeTrackRow,
|
|
7861
8459
|
{
|
|
7862
8460
|
accentColor: identity.transitionAccentColor,
|
|
@@ -7881,21 +8479,56 @@ function GeneratorPanelShell({ core, slots }) {
|
|
|
7881
8479
|
},
|
|
7882
8480
|
fade.dbId
|
|
7883
8481
|
)),
|
|
8482
|
+
resolvedGroupFades.map((group) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
8483
|
+
GroupFadeTrackRow,
|
|
8484
|
+
{
|
|
8485
|
+
accentColor: identity.transitionAccentColor,
|
|
8486
|
+
levels: supportsMeters ? trackLevels : void 0,
|
|
8487
|
+
direction: group.direction,
|
|
8488
|
+
gesture: group.gesture,
|
|
8489
|
+
sliderPos: group.sliderPos,
|
|
8490
|
+
groupLabel: adapter.transitionGroup?.groupRowLabel?.(group.members.length) ?? `Group (${group.members.length} tracks)`,
|
|
8491
|
+
members: group.members.map((m) => ({
|
|
8492
|
+
trackId: m.track.handle.id,
|
|
8493
|
+
name: m.track.handle.name,
|
|
8494
|
+
role: m.track.role,
|
|
8495
|
+
sourceName: m.meta.sourceName,
|
|
8496
|
+
soundLabel: m.meta.soundLabel,
|
|
8497
|
+
memberLabel: m.meta.memberLabel,
|
|
8498
|
+
runtimeState: m.track.runtimeState
|
|
8499
|
+
})),
|
|
8500
|
+
onMemberMuteToggle: (trackId) => handlers.muteToggle(trackId),
|
|
8501
|
+
onMemberSoloToggle: (trackId) => handlers.soloToggle(trackId),
|
|
8502
|
+
onMemberVolumeChange: (trackId, vol) => handlers.volumeChange(trackId, vol),
|
|
8503
|
+
onMemberPanChange: (trackId, pan) => handlers.panChange(trackId, pan),
|
|
8504
|
+
onMuteAll: () => setGroupMute(
|
|
8505
|
+
group.members.map((m) => m.track.handle.id),
|
|
8506
|
+
!group.members.every((m) => m.track.runtimeState.muted)
|
|
8507
|
+
),
|
|
8508
|
+
onSoloAll: () => setGroupSolo(
|
|
8509
|
+
group.members.map((m) => m.track.handle.id),
|
|
8510
|
+
!group.members.every((m) => m.track.runtimeState.solo)
|
|
8511
|
+
),
|
|
8512
|
+
onSliderChange: (pos) => transition.handleGroupFadeSlider(group, pos),
|
|
8513
|
+
onDelete: () => transition.handleGroupFadeDelete(group)
|
|
8514
|
+
},
|
|
8515
|
+
group.groupId
|
|
8516
|
+
)),
|
|
7884
8517
|
(adapter.groupExtensions ?? []).flatMap(
|
|
7885
|
-
(ext) => (resolvedGenericGroups[ext.metaKey]?.resolved ?? []).map((group) => /* @__PURE__ */ (0,
|
|
8518
|
+
(ext) => (resolvedGenericGroups[ext.metaKey]?.resolved ?? []).filter((group) => !group.members.every((m) => fadeMemberDbIds.has(m.dbId))).map((group) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_react31.default.Fragment, { children: ext.renderGroup(group, groupCtx) }, `${ext.metaKey}:${group.groupId}`))
|
|
7886
8519
|
),
|
|
7887
8520
|
tracks.map((track, index) => {
|
|
7888
8521
|
if (crossfadeMemberDbIds.has(track.handle.dbId) || fadeMemberDbIds.has(track.handle.dbId) || genericGroupMemberDbIds.has(track.handle.dbId)) {
|
|
7889
8522
|
return null;
|
|
7890
8523
|
}
|
|
7891
|
-
return /* @__PURE__ */ (0,
|
|
8524
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(TrackRow, { ...buildRowProps(track, reorder.dragPropsFor(index)) }, track.handle.id);
|
|
7892
8525
|
}),
|
|
7893
8526
|
slots?.afterRows
|
|
7894
8527
|
] })),
|
|
7895
8528
|
features.exportMidi && !designerView && !isLoadingTracks && tracks.length > 0 && (() => {
|
|
7896
8529
|
const hasAnyMidi = tracks.some((t) => t.hasMidi);
|
|
7897
8530
|
const exportDisabled = isExportingMidi || !hasAnyMidi;
|
|
7898
|
-
return /* @__PURE__ */ (0,
|
|
8531
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "pt-2", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
7899
8532
|
"button",
|
|
7900
8533
|
{
|
|
7901
8534
|
"data-testid": "export-midi-tracks-button",
|
|
@@ -8131,6 +8764,7 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
8131
8764
|
FxToggleBar,
|
|
8132
8765
|
GUTTER_W,
|
|
8133
8766
|
GeneratorPanelShell,
|
|
8767
|
+
GroupFadeTrackRow,
|
|
8134
8768
|
ImportTrackModal,
|
|
8135
8769
|
InstrumentDrawer,
|
|
8136
8770
|
LevelMeter,
|
|
@@ -8199,6 +8833,7 @@ function pickTopKWeighted(scored, options = {}) {
|
|
|
8199
8833
|
sliderToDb,
|
|
8200
8834
|
slotsEqual,
|
|
8201
8835
|
soundIdentity,
|
|
8836
|
+
splitFadeEntries,
|
|
8202
8837
|
synthesizeCuePoints,
|
|
8203
8838
|
tokenizePrompt,
|
|
8204
8839
|
trackDataKey,
|