@signalsandsorcery/plugin-sdk 2.26.1 → 2.28.1
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 +215 -2
- package/dist/index.d.ts +215 -2
- package/dist/index.js +668 -173
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +661 -173
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -2570,9 +2570,448 @@ function buildCrossfadeInpaintPrompt(input) {
|
|
|
2570
2570
|
return lines.join("\n");
|
|
2571
2571
|
}
|
|
2572
2572
|
|
|
2573
|
+
// src/fade-meta.ts
|
|
2574
|
+
function asFadeMeta(val) {
|
|
2575
|
+
if (!val || typeof val !== "object") return null;
|
|
2576
|
+
const m = val;
|
|
2577
|
+
if (m.direction !== "in" && m.direction !== "out") return null;
|
|
2578
|
+
if (m.gesture !== "volume" && m.gesture !== "build") return null;
|
|
2579
|
+
return {
|
|
2580
|
+
direction: m.direction,
|
|
2581
|
+
gesture: m.gesture,
|
|
2582
|
+
sourceTrackDbId: typeof m.sourceTrackDbId === "string" ? m.sourceTrackDbId : "",
|
|
2583
|
+
sourceSceneId: typeof m.sourceSceneId === "string" ? m.sourceSceneId : "",
|
|
2584
|
+
sourceName: typeof m.sourceName === "string" ? m.sourceName : "",
|
|
2585
|
+
soundLabel: typeof m.soundLabel === "string" ? m.soundLabel : "",
|
|
2586
|
+
sliderPos: typeof m.sliderPos === "number" ? m.sliderPos : 0.5
|
|
2587
|
+
};
|
|
2588
|
+
}
|
|
2589
|
+
function parseFades(sceneData) {
|
|
2590
|
+
const out = [];
|
|
2591
|
+
for (const [key, val] of Object.entries(sceneData)) {
|
|
2592
|
+
const match = /^track:(.+):fade$/.exec(key);
|
|
2593
|
+
if (!match) continue;
|
|
2594
|
+
const meta = asFadeMeta(val);
|
|
2595
|
+
if (!meta) continue;
|
|
2596
|
+
out.push({ dbId: match[1], meta });
|
|
2597
|
+
}
|
|
2598
|
+
return out;
|
|
2599
|
+
}
|
|
2600
|
+
function buildFadeVolumeCurve(bars, bpm, direction, sliderPos, gesture, steps = 32) {
|
|
2601
|
+
const durationSeconds = bars * 4 * 60 / Math.max(1, bpm);
|
|
2602
|
+
if (gesture === "build") {
|
|
2603
|
+
return [
|
|
2604
|
+
{ time: 0, db: 0 },
|
|
2605
|
+
{ time: Math.round(durationSeconds * 1e3) / 1e3, db: 0 }
|
|
2606
|
+
];
|
|
2607
|
+
}
|
|
2608
|
+
const s = Math.min(0.98, Math.max(0.02, sliderPos));
|
|
2609
|
+
const round = (n) => Math.round(n * 1e3) / 1e3;
|
|
2610
|
+
const points = [];
|
|
2611
|
+
for (let i = 0; i <= steps; i++) {
|
|
2612
|
+
const x = i / steps;
|
|
2613
|
+
const time = round(x * durationSeconds);
|
|
2614
|
+
const theta = x <= s ? x / s * (Math.PI / 4) : Math.PI / 4 + (x - s) / (1 - s) * (Math.PI / 4);
|
|
2615
|
+
const gain = direction === "out" ? Math.cos(theta) : Math.sin(theta);
|
|
2616
|
+
points.push({ time, db: Math.round(gainToDb(gain) * 100) / 100 });
|
|
2617
|
+
}
|
|
2618
|
+
return points;
|
|
2619
|
+
}
|
|
2620
|
+
var TEXTURAL_ROLES = /* @__PURE__ */ new Set([
|
|
2621
|
+
"pads",
|
|
2622
|
+
"pad",
|
|
2623
|
+
"strings",
|
|
2624
|
+
"atmospheres",
|
|
2625
|
+
"atmosphere",
|
|
2626
|
+
"atmos",
|
|
2627
|
+
"drones",
|
|
2628
|
+
"drone",
|
|
2629
|
+
"soundscapes",
|
|
2630
|
+
"soundscape"
|
|
2631
|
+
]);
|
|
2632
|
+
function defaultFadeGesture(role) {
|
|
2633
|
+
if (!role) return "build";
|
|
2634
|
+
const norm = role.toLowerCase().replace(/[\s_-]+/g, " ").trim();
|
|
2635
|
+
if (TEXTURAL_ROLES.has(norm)) return "volume";
|
|
2636
|
+
for (const token of norm.split(" ")) {
|
|
2637
|
+
if (TEXTURAL_ROLES.has(token)) return "volume";
|
|
2638
|
+
}
|
|
2639
|
+
return "build";
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2642
|
+
// src/components/FadeTrackRow.tsx
|
|
2643
|
+
import React10 from "react";
|
|
2644
|
+
import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2645
|
+
function FadeCaption({
|
|
2646
|
+
layer,
|
|
2647
|
+
direction,
|
|
2648
|
+
gesture
|
|
2649
|
+
}) {
|
|
2650
|
+
const tag = direction === "in" ? "Fade in" : "Fade out";
|
|
2651
|
+
return /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-1.5 min-w-0 px-2 py-0.5", children: [
|
|
2652
|
+
/* @__PURE__ */ jsx13("span", { className: "text-[9px] font-bold uppercase tracking-wide text-sas-accent flex-shrink-0", children: tag }),
|
|
2653
|
+
/* @__PURE__ */ jsx13("span", { className: "text-[11px] text-sas-text truncate", title: layer.sourceName ?? layer.name, children: layer.sourceName ?? layer.name }),
|
|
2654
|
+
layer.soundLabel && /* @__PURE__ */ jsxs9("span", { className: "text-[9px] text-sas-muted/60 truncate flex-shrink-0", title: layer.soundLabel, children: [
|
|
2655
|
+
"\xB7 ",
|
|
2656
|
+
layer.soundLabel
|
|
2657
|
+
] }),
|
|
2658
|
+
/* @__PURE__ */ jsxs9("span", { className: "text-[9px] text-sas-muted/50 flex-shrink-0", title: `Fade gesture: ${gesture}`, children: [
|
|
2659
|
+
"\xB7 ",
|
|
2660
|
+
gesture
|
|
2661
|
+
] })
|
|
2662
|
+
] });
|
|
2663
|
+
}
|
|
2664
|
+
function FadeTrackRow({
|
|
2665
|
+
layer,
|
|
2666
|
+
direction,
|
|
2667
|
+
gesture,
|
|
2668
|
+
sliderPos = 0.5,
|
|
2669
|
+
onMuteToggle,
|
|
2670
|
+
onSoloToggle,
|
|
2671
|
+
onVolumeChange,
|
|
2672
|
+
onPanChange,
|
|
2673
|
+
onDelete,
|
|
2674
|
+
onSliderChange,
|
|
2675
|
+
levels,
|
|
2676
|
+
accentColor = "#9333EA"
|
|
2677
|
+
}) {
|
|
2678
|
+
const [confirmDelete, setConfirmDelete] = React10.useState(false);
|
|
2679
|
+
const leftLabel = direction === "in" ? "(silent)" : layer.sourceName ?? layer.name;
|
|
2680
|
+
const rightLabel = direction === "in" ? layer.sourceName ?? layer.name : "(silent)";
|
|
2681
|
+
const badge = direction === "in" ? "\u2197 Fade in" : "\u2198 Fade out";
|
|
2682
|
+
return /* @__PURE__ */ jsxs9(
|
|
2683
|
+
"div",
|
|
2684
|
+
{
|
|
2685
|
+
"data-testid": "fade-track-row",
|
|
2686
|
+
className: "w-full rounded-sm border border-sas-border bg-sas-panel/40 overflow-hidden",
|
|
2687
|
+
style: { borderLeftColor: accentColor, borderLeftWidth: "3px" },
|
|
2688
|
+
children: [
|
|
2689
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between px-2 py-1 bg-sas-panel-alt/60", children: [
|
|
2690
|
+
/* @__PURE__ */ jsx13(
|
|
2691
|
+
"span",
|
|
2692
|
+
{
|
|
2693
|
+
"data-testid": "fade-direction-badge",
|
|
2694
|
+
className: "text-[10px] font-bold uppercase tracking-wide",
|
|
2695
|
+
style: { color: accentColor },
|
|
2696
|
+
children: badge
|
|
2697
|
+
}
|
|
2698
|
+
),
|
|
2699
|
+
/* @__PURE__ */ jsx13(
|
|
2700
|
+
"button",
|
|
2701
|
+
{
|
|
2702
|
+
"data-testid": "fade-delete-button",
|
|
2703
|
+
onClick: () => setConfirmDelete(true),
|
|
2704
|
+
className: "text-sas-danger/70 hover:text-sas-danger px-1 transition-colors text-sm",
|
|
2705
|
+
title: "Delete fade",
|
|
2706
|
+
"aria-label": "Delete fade",
|
|
2707
|
+
children: "x"
|
|
2708
|
+
}
|
|
2709
|
+
)
|
|
2710
|
+
] }),
|
|
2711
|
+
/* @__PURE__ */ jsx13(
|
|
2712
|
+
TrackRow,
|
|
2713
|
+
{
|
|
2714
|
+
track: { id: layer.trackId, name: "", role: layer.role },
|
|
2715
|
+
runtimeState: layer.runtimeState,
|
|
2716
|
+
fxDetailState: EMPTY_FX_DETAIL_STATE,
|
|
2717
|
+
drawerOpen: false,
|
|
2718
|
+
drawerTab: "fx",
|
|
2719
|
+
levels,
|
|
2720
|
+
accentColor,
|
|
2721
|
+
contentSlot: /* @__PURE__ */ jsx13(FadeCaption, { layer, direction, gesture }),
|
|
2722
|
+
onMuteToggle,
|
|
2723
|
+
onSoloToggle,
|
|
2724
|
+
onVolumeChange,
|
|
2725
|
+
onPanChange
|
|
2726
|
+
}
|
|
2727
|
+
),
|
|
2728
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2 px-3 py-1.5", "data-testid": "fade-slider-row", children: [
|
|
2729
|
+
/* @__PURE__ */ jsx13(
|
|
2730
|
+
"span",
|
|
2731
|
+
{
|
|
2732
|
+
className: "text-[9px] text-sas-muted/60 truncate max-w-[70px] text-right flex-shrink-0",
|
|
2733
|
+
title: leftLabel,
|
|
2734
|
+
children: leftLabel
|
|
2735
|
+
}
|
|
2736
|
+
),
|
|
2737
|
+
/* @__PURE__ */ jsx13(
|
|
2738
|
+
"input",
|
|
2739
|
+
{
|
|
2740
|
+
type: "range",
|
|
2741
|
+
"data-testid": "fade-slider",
|
|
2742
|
+
min: 0,
|
|
2743
|
+
max: 1,
|
|
2744
|
+
step: 0.01,
|
|
2745
|
+
value: sliderPos,
|
|
2746
|
+
disabled: !onSliderChange,
|
|
2747
|
+
onChange: onSliderChange ? (e) => onSliderChange(Number(e.target.value)) : void 0,
|
|
2748
|
+
style: { accentColor },
|
|
2749
|
+
className: "flex-1 disabled:opacity-60 disabled:cursor-not-allowed",
|
|
2750
|
+
"aria-label": "Fade position"
|
|
2751
|
+
}
|
|
2752
|
+
),
|
|
2753
|
+
/* @__PURE__ */ jsx13(
|
|
2754
|
+
"span",
|
|
2755
|
+
{
|
|
2756
|
+
className: "text-[9px] text-sas-muted/60 truncate max-w-[70px] flex-shrink-0",
|
|
2757
|
+
title: rightLabel,
|
|
2758
|
+
children: rightLabel
|
|
2759
|
+
}
|
|
2760
|
+
)
|
|
2761
|
+
] }),
|
|
2762
|
+
/* @__PURE__ */ jsx13(
|
|
2763
|
+
ConfirmDialog,
|
|
2764
|
+
{
|
|
2765
|
+
open: confirmDelete,
|
|
2766
|
+
title: "Delete fade?",
|
|
2767
|
+
message: /* @__PURE__ */ jsx13(Fragment3, { children: "This fade track will be permanently removed from this scene. This cannot be undone." }),
|
|
2768
|
+
confirmLabel: "Delete",
|
|
2769
|
+
onConfirm: () => {
|
|
2770
|
+
setConfirmDelete(false);
|
|
2771
|
+
onDelete();
|
|
2772
|
+
},
|
|
2773
|
+
onCancel: () => setConfirmDelete(false),
|
|
2774
|
+
testIdPrefix: "fade-delete-confirm"
|
|
2775
|
+
}
|
|
2776
|
+
)
|
|
2777
|
+
]
|
|
2778
|
+
}
|
|
2779
|
+
);
|
|
2780
|
+
}
|
|
2781
|
+
|
|
2782
|
+
// src/components/FadeModal.tsx
|
|
2783
|
+
import { useCallback as useCallback4, useEffect as useEffect6, useMemo as useMemo3, useRef as useRef7, useState as useState7 } from "react";
|
|
2784
|
+
import { Fragment as Fragment4, jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2785
|
+
function shortId(dbId) {
|
|
2786
|
+
return dbId.length > 8 ? dbId.slice(0, 8) : dbId;
|
|
2787
|
+
}
|
|
2788
|
+
var normRole = (r) => (r ?? "").toLowerCase().trim();
|
|
2789
|
+
function computeOrphans(from, to, excludeSet) {
|
|
2790
|
+
const bucket = (list) => {
|
|
2791
|
+
const m = /* @__PURE__ */ new Map();
|
|
2792
|
+
for (const t of list) {
|
|
2793
|
+
const k = normRole(t.role);
|
|
2794
|
+
const arr = m.get(k);
|
|
2795
|
+
if (arr) arr.push(t);
|
|
2796
|
+
else m.set(k, [t]);
|
|
2797
|
+
}
|
|
2798
|
+
return m;
|
|
2799
|
+
};
|
|
2800
|
+
const fromByRole = bucket(from);
|
|
2801
|
+
const toByRole = bucket(to);
|
|
2802
|
+
const roles = /* @__PURE__ */ new Set([...fromByRole.keys(), ...toByRole.keys()]);
|
|
2803
|
+
const fadeOut = [];
|
|
2804
|
+
const fadeIn = [];
|
|
2805
|
+
for (const role of roles) {
|
|
2806
|
+
const f = fromByRole.get(role) ?? [];
|
|
2807
|
+
const t = toByRole.get(role) ?? [];
|
|
2808
|
+
const shared = Math.min(f.length, t.length);
|
|
2809
|
+
fadeOut.push(...f.slice(shared));
|
|
2810
|
+
fadeIn.push(...t.slice(shared));
|
|
2811
|
+
}
|
|
2812
|
+
return {
|
|
2813
|
+
fadeOut: fadeOut.filter((x) => !excludeSet.has(x.dbId)),
|
|
2814
|
+
fadeIn: fadeIn.filter((x) => !excludeSet.has(x.dbId))
|
|
2815
|
+
};
|
|
2816
|
+
}
|
|
2817
|
+
function OrphanRow({
|
|
2818
|
+
track,
|
|
2819
|
+
gesture,
|
|
2820
|
+
selected,
|
|
2821
|
+
disabled,
|
|
2822
|
+
onSelect,
|
|
2823
|
+
testId
|
|
2824
|
+
}) {
|
|
2825
|
+
const primary = track.prompt?.trim() || track.name;
|
|
2826
|
+
const meta = [track.role, shortId(track.dbId), gesture].filter(Boolean).join(" \xB7 ");
|
|
2827
|
+
return /* @__PURE__ */ jsxs10(
|
|
2828
|
+
"button",
|
|
2829
|
+
{
|
|
2830
|
+
type: "button",
|
|
2831
|
+
role: "radio",
|
|
2832
|
+
"aria-checked": selected,
|
|
2833
|
+
"data-testid": testId,
|
|
2834
|
+
"data-value": track.dbId,
|
|
2835
|
+
onClick: onSelect,
|
|
2836
|
+
disabled,
|
|
2837
|
+
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"}`,
|
|
2838
|
+
children: [
|
|
2839
|
+
/* @__PURE__ */ jsx14("div", { className: "text-xs text-sas-text truncate", title: primary, children: primary }),
|
|
2840
|
+
meta && /* @__PURE__ */ jsx14("div", { className: "text-[10px] text-sas-muted truncate mt-0.5", title: track.dbId, children: meta })
|
|
2841
|
+
]
|
|
2842
|
+
}
|
|
2843
|
+
);
|
|
2844
|
+
}
|
|
2845
|
+
function FadeModal({
|
|
2846
|
+
host,
|
|
2847
|
+
open,
|
|
2848
|
+
fromSceneId,
|
|
2849
|
+
toSceneId,
|
|
2850
|
+
fromSceneName,
|
|
2851
|
+
toSceneName,
|
|
2852
|
+
excludeSourceDbIds,
|
|
2853
|
+
onClose,
|
|
2854
|
+
onCreate,
|
|
2855
|
+
testIdPrefix = "fade-modal"
|
|
2856
|
+
}) {
|
|
2857
|
+
const [load, setLoad] = useState7({ status: "loading" });
|
|
2858
|
+
const [selectedDbId, setSelectedDbId] = useState7("");
|
|
2859
|
+
const [isCreating, setIsCreating] = useState7(false);
|
|
2860
|
+
const [error, setError] = useState7(null);
|
|
2861
|
+
const [fromName, setFromName] = useState7(null);
|
|
2862
|
+
const [toName, setToName] = useState7(null);
|
|
2863
|
+
const cancelRef = useRef7(null);
|
|
2864
|
+
const refresh = useCallback4(async () => {
|
|
2865
|
+
if (!host.listSceneFamilyTracks) {
|
|
2866
|
+
setLoad({ status: "error", message: "This host does not support fades." });
|
|
2867
|
+
return;
|
|
2868
|
+
}
|
|
2869
|
+
setLoad({ status: "loading" });
|
|
2870
|
+
try {
|
|
2871
|
+
const [from, to, fName, tName] = await Promise.all([
|
|
2872
|
+
host.listSceneFamilyTracks(fromSceneId),
|
|
2873
|
+
host.listSceneFamilyTracks(toSceneId),
|
|
2874
|
+
host.getSceneName ? host.getSceneName(fromSceneId) : Promise.resolve(null),
|
|
2875
|
+
host.getSceneName ? host.getSceneName(toSceneId) : Promise.resolve(null)
|
|
2876
|
+
]);
|
|
2877
|
+
setFromName(fName);
|
|
2878
|
+
setToName(tName);
|
|
2879
|
+
setLoad({ status: "ready", from, to });
|
|
2880
|
+
} catch (err) {
|
|
2881
|
+
setLoad({ status: "error", message: err instanceof Error ? err.message : "Failed to load tracks." });
|
|
2882
|
+
}
|
|
2883
|
+
}, [host, fromSceneId, toSceneId]);
|
|
2884
|
+
useEffect6(() => {
|
|
2885
|
+
if (open) {
|
|
2886
|
+
setError(null);
|
|
2887
|
+
setIsCreating(false);
|
|
2888
|
+
setSelectedDbId("");
|
|
2889
|
+
void refresh();
|
|
2890
|
+
}
|
|
2891
|
+
}, [open, refresh]);
|
|
2892
|
+
const excludeSet = useMemo3(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);
|
|
2893
|
+
const { fadeOut, fadeIn } = useMemo3(
|
|
2894
|
+
() => load.status === "ready" ? computeOrphans(load.from, load.to, excludeSet) : { fadeOut: [], fadeIn: [] },
|
|
2895
|
+
[load, excludeSet]
|
|
2896
|
+
);
|
|
2897
|
+
const allOrphans = useMemo3(
|
|
2898
|
+
() => [
|
|
2899
|
+
...fadeOut.map((t) => ({ track: t, direction: "out" })),
|
|
2900
|
+
...fadeIn.map((t) => ({ track: t, direction: "in" }))
|
|
2901
|
+
],
|
|
2902
|
+
[fadeOut, fadeIn]
|
|
2903
|
+
);
|
|
2904
|
+
useEffect6(() => {
|
|
2905
|
+
if (!allOrphans.some((o) => o.track.dbId === selectedDbId)) {
|
|
2906
|
+
setSelectedDbId(allOrphans[0]?.track.dbId ?? "");
|
|
2907
|
+
}
|
|
2908
|
+
}, [allOrphans, selectedDbId]);
|
|
2909
|
+
const selected = allOrphans.find((o) => o.track.dbId === selectedDbId) ?? null;
|
|
2910
|
+
const canCreate = !isCreating && !!selected;
|
|
2911
|
+
const handleClose = useCallback4(() => {
|
|
2912
|
+
if (!isCreating) onClose();
|
|
2913
|
+
}, [isCreating, onClose]);
|
|
2914
|
+
const handleCreate = useCallback4(async () => {
|
|
2915
|
+
if (!selected) return;
|
|
2916
|
+
setIsCreating(true);
|
|
2917
|
+
setError(null);
|
|
2918
|
+
try {
|
|
2919
|
+
await onCreate(
|
|
2920
|
+
{ dbId: selected.track.dbId, name: selected.track.name, role: selected.track.role },
|
|
2921
|
+
selected.direction,
|
|
2922
|
+
defaultFadeGesture(selected.track.role)
|
|
2923
|
+
);
|
|
2924
|
+
onClose();
|
|
2925
|
+
} catch (err) {
|
|
2926
|
+
setError(err instanceof Error ? err.message : "Failed to create fade.");
|
|
2927
|
+
setIsCreating(false);
|
|
2928
|
+
}
|
|
2929
|
+
}, [selected, onCreate, onClose]);
|
|
2930
|
+
const fromLabel = fromName ?? fromSceneName ?? null;
|
|
2931
|
+
const toLabel = toName ?? toSceneName ?? null;
|
|
2932
|
+
if (!open) return null;
|
|
2933
|
+
const renderSection = (heading, list, section) => {
|
|
2934
|
+
if (list.length === 0) return null;
|
|
2935
|
+
return /* @__PURE__ */ jsxs10("div", { className: "block", children: [
|
|
2936
|
+
/* @__PURE__ */ jsx14("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: heading }),
|
|
2937
|
+
/* @__PURE__ */ jsx14(
|
|
2938
|
+
"div",
|
|
2939
|
+
{
|
|
2940
|
+
role: "radiogroup",
|
|
2941
|
+
"aria-label": heading,
|
|
2942
|
+
"data-testid": `${testIdPrefix}-${section === "out" ? "fade-out" : "fade-in"}-list`,
|
|
2943
|
+
className: "mt-1 space-y-1 max-h-40 overflow-y-auto pr-0.5",
|
|
2944
|
+
children: list.map((t) => /* @__PURE__ */ jsx14(
|
|
2945
|
+
OrphanRow,
|
|
2946
|
+
{
|
|
2947
|
+
track: t,
|
|
2948
|
+
gesture: defaultFadeGesture(t.role),
|
|
2949
|
+
selected: t.dbId === selectedDbId,
|
|
2950
|
+
disabled: isCreating,
|
|
2951
|
+
onSelect: () => setSelectedDbId(t.dbId),
|
|
2952
|
+
testId: `${testIdPrefix}-option-${t.dbId}`
|
|
2953
|
+
},
|
|
2954
|
+
t.dbId
|
|
2955
|
+
))
|
|
2956
|
+
}
|
|
2957
|
+
)
|
|
2958
|
+
] });
|
|
2959
|
+
};
|
|
2960
|
+
return /* @__PURE__ */ jsx14(Modal, { open, onClose: handleClose, testIdPrefix, initialFocusRef: cancelRef, children: /* @__PURE__ */ jsxs10(
|
|
2961
|
+
"div",
|
|
2962
|
+
{
|
|
2963
|
+
className: "bg-sas-panel border border-sas-border rounded-md shadow-xl w-[420px] max-w-[92vw] p-4 space-y-3",
|
|
2964
|
+
onClick: (e) => e.stopPropagation(),
|
|
2965
|
+
"data-testid": `${testIdPrefix}-box`,
|
|
2966
|
+
children: [
|
|
2967
|
+
/* @__PURE__ */ jsx14("h3", { className: "text-sm font-bold text-sas-text", children: "Add fade" }),
|
|
2968
|
+
/* @__PURE__ */ jsxs10("p", { className: "text-[11px] text-sas-muted leading-relaxed", children: [
|
|
2969
|
+
"Tracks with no counterpart between",
|
|
2970
|
+
" ",
|
|
2971
|
+
/* @__PURE__ */ jsx14("span", { className: "text-sas-text", children: fromLabel ?? "the origin scene" }),
|
|
2972
|
+
" and",
|
|
2973
|
+
" ",
|
|
2974
|
+
/* @__PURE__ */ jsx14("span", { className: "text-sas-text", children: toLabel ?? "the target scene" }),
|
|
2975
|
+
" can gracefully fade out (leaving) or fade in (entering) across this transition."
|
|
2976
|
+
] }),
|
|
2977
|
+
load.status === "loading" && /* @__PURE__ */ jsx14("div", { className: "text-xs text-sas-muted py-4 text-center", children: "Loading tracks\u2026" }),
|
|
2978
|
+
load.status === "error" && /* @__PURE__ */ jsx14("div", { className: "text-xs text-sas-danger py-4 text-center", children: load.message }),
|
|
2979
|
+
load.status === "ready" && (allOrphans.length === 0 ? /* @__PURE__ */ jsx14("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__ */ jsxs10(Fragment4, { children: [
|
|
2980
|
+
renderSection(`Fade out${fromLabel ? ` (from ${fromLabel})` : ""}`, fadeOut, "out"),
|
|
2981
|
+
renderSection(`Fade in${toLabel ? ` (to ${toLabel})` : ""}`, fadeIn, "in")
|
|
2982
|
+
] })),
|
|
2983
|
+
error && /* @__PURE__ */ jsx14("div", { className: "text-xs text-sas-danger", "data-testid": `${testIdPrefix}-error`, children: error }),
|
|
2984
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex justify-end gap-2 pt-1", children: [
|
|
2985
|
+
/* @__PURE__ */ jsx14(
|
|
2986
|
+
"button",
|
|
2987
|
+
{
|
|
2988
|
+
ref: cancelRef,
|
|
2989
|
+
"data-testid": `${testIdPrefix}-cancel`,
|
|
2990
|
+
onClick: onClose,
|
|
2991
|
+
disabled: isCreating,
|
|
2992
|
+
className: "px-3 py-1 text-xs rounded-sm border border-sas-border text-sas-muted hover:text-sas-text disabled:opacity-50",
|
|
2993
|
+
children: "Cancel"
|
|
2994
|
+
}
|
|
2995
|
+
),
|
|
2996
|
+
/* @__PURE__ */ jsx14(
|
|
2997
|
+
"button",
|
|
2998
|
+
{
|
|
2999
|
+
"data-testid": `${testIdPrefix}-confirm`,
|
|
3000
|
+
onClick: handleCreate,
|
|
3001
|
+
disabled: !canCreate,
|
|
3002
|
+
className: `px-3 py-1 text-xs rounded-sm border transition-colors ${canCreate ? "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"}`,
|
|
3003
|
+
children: isCreating ? "Generating fade\u2026" : "Create fade"
|
|
3004
|
+
}
|
|
3005
|
+
)
|
|
3006
|
+
] })
|
|
3007
|
+
]
|
|
3008
|
+
}
|
|
3009
|
+
) });
|
|
3010
|
+
}
|
|
3011
|
+
|
|
2573
3012
|
// src/components/ImportTrackModal.tsx
|
|
2574
|
-
import { useCallback as
|
|
2575
|
-
import { jsx as
|
|
3013
|
+
import { useCallback as useCallback5, useEffect as useEffect7, useState as useState8 } from "react";
|
|
3014
|
+
import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2576
3015
|
function ImportTrackModal({
|
|
2577
3016
|
host,
|
|
2578
3017
|
open,
|
|
@@ -2584,10 +3023,10 @@ function ImportTrackModal({
|
|
|
2584
3023
|
onPick,
|
|
2585
3024
|
onPortTrack
|
|
2586
3025
|
}) {
|
|
2587
|
-
const [load, setLoad] =
|
|
2588
|
-
const [selectedSceneId, setSelectedSceneId] =
|
|
2589
|
-
const [importingTrackId, setImportingTrackId] =
|
|
2590
|
-
const refresh =
|
|
3026
|
+
const [load, setLoad] = useState8({ status: "loading" });
|
|
3027
|
+
const [selectedSceneId, setSelectedSceneId] = useState8(null);
|
|
3028
|
+
const [importingTrackId, setImportingTrackId] = useState8(null);
|
|
3029
|
+
const refresh = useCallback5(async () => {
|
|
2591
3030
|
if (!host.listImportableTracks) {
|
|
2592
3031
|
setLoad({ status: "error", message: "This host does not support importing tracks." });
|
|
2593
3032
|
return;
|
|
@@ -2603,14 +3042,14 @@ function ImportTrackModal({
|
|
|
2603
3042
|
setLoad({ status: "error", message: err instanceof Error ? err.message : "Failed to load scenes." });
|
|
2604
3043
|
}
|
|
2605
3044
|
}, [host, mode, onPortTrack]);
|
|
2606
|
-
|
|
3045
|
+
useEffect7(() => {
|
|
2607
3046
|
if (open) {
|
|
2608
3047
|
setSelectedSceneId(null);
|
|
2609
3048
|
setImportingTrackId(null);
|
|
2610
3049
|
void refresh();
|
|
2611
3050
|
}
|
|
2612
3051
|
}, [open, refresh]);
|
|
2613
|
-
const handleImport =
|
|
3052
|
+
const handleImport = useCallback5(
|
|
2614
3053
|
async (track, sourceSceneId, sceneName, isSameScene) => {
|
|
2615
3054
|
if (isSameScene && onPortTrack) {
|
|
2616
3055
|
if (!track.importable) return;
|
|
@@ -2651,16 +3090,16 @@ function ImportTrackModal({
|
|
|
2651
3090
|
if (!open) return null;
|
|
2652
3091
|
const scenes = load.status === "ready" ? load.scenes : [];
|
|
2653
3092
|
const selectedScene = scenes.find((s) => s.sceneId === selectedSceneId) ?? null;
|
|
2654
|
-
return /* @__PURE__ */
|
|
3093
|
+
return /* @__PURE__ */ jsx15(Modal, { open, onClose, testIdPrefix, children: /* @__PURE__ */ jsxs11(
|
|
2655
3094
|
"div",
|
|
2656
3095
|
{
|
|
2657
3096
|
className: "w-[420px] max-h-[70vh] overflow-hidden flex flex-col rounded-md border border-sas-border bg-sas-panel shadow-xl",
|
|
2658
3097
|
onClick: (e) => e.stopPropagation(),
|
|
2659
3098
|
"data-testid": `${testIdPrefix}-modal`,
|
|
2660
3099
|
children: [
|
|
2661
|
-
/* @__PURE__ */
|
|
2662
|
-
/* @__PURE__ */
|
|
2663
|
-
selectedScene && /* @__PURE__ */
|
|
3100
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex items-center justify-between px-3 py-2 border-b border-sas-border", children: [
|
|
3101
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-2", children: [
|
|
3102
|
+
selectedScene && /* @__PURE__ */ jsx15(
|
|
2664
3103
|
"button",
|
|
2665
3104
|
{
|
|
2666
3105
|
className: "text-sas-muted hover:text-sas-accent text-xs",
|
|
@@ -2669,9 +3108,9 @@ function ImportTrackModal({
|
|
|
2669
3108
|
children: "\u2190"
|
|
2670
3109
|
}
|
|
2671
3110
|
),
|
|
2672
|
-
/* @__PURE__ */
|
|
3111
|
+
/* @__PURE__ */ jsx15("span", { className: "text-sm font-medium text-sas-text", children: selectedScene ? selectedScene.sceneName : title })
|
|
2673
3112
|
] }),
|
|
2674
|
-
/* @__PURE__ */
|
|
3113
|
+
/* @__PURE__ */ jsx15(
|
|
2675
3114
|
"button",
|
|
2676
3115
|
{
|
|
2677
3116
|
className: "text-sas-muted hover:text-sas-accent text-sm",
|
|
@@ -2681,30 +3120,30 @@ function ImportTrackModal({
|
|
|
2681
3120
|
}
|
|
2682
3121
|
)
|
|
2683
3122
|
] }),
|
|
2684
|
-
/* @__PURE__ */
|
|
2685
|
-
load.status === "loading" && /* @__PURE__ */
|
|
2686
|
-
load.status === "error" && /* @__PURE__ */
|
|
2687
|
-
load.status === "ready" && scenes.length === 0 && /* @__PURE__ */
|
|
2688
|
-
load.status === "ready" && scenes.length > 0 && !selectedScene && /* @__PURE__ */
|
|
3123
|
+
/* @__PURE__ */ jsxs11("div", { className: "overflow-y-auto p-2 flex-1", children: [
|
|
3124
|
+
load.status === "loading" && /* @__PURE__ */ jsx15("div", { className: "py-8 text-center text-xs text-sas-muted", "data-testid": `${testIdPrefix}-loading`, children: "Loading scenes\u2026" }),
|
|
3125
|
+
load.status === "error" && /* @__PURE__ */ jsx15("div", { className: "py-8 text-center text-xs text-red-400", "data-testid": `${testIdPrefix}-error`, children: load.message }),
|
|
3126
|
+
load.status === "ready" && scenes.length === 0 && /* @__PURE__ */ jsx15("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." }),
|
|
3127
|
+
load.status === "ready" && scenes.length > 0 && !selectedScene && /* @__PURE__ */ jsx15("ul", { className: "flex flex-col gap-1", "data-testid": `${testIdPrefix}-scene-list`, children: scenes.map((scene) => /* @__PURE__ */ jsx15("li", { children: /* @__PURE__ */ jsxs11(
|
|
2689
3128
|
"button",
|
|
2690
3129
|
{
|
|
2691
3130
|
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",
|
|
2692
3131
|
onClick: () => setSelectedSceneId(scene.sceneId),
|
|
2693
3132
|
"data-testid": `${testIdPrefix}-scene`,
|
|
2694
3133
|
children: [
|
|
2695
|
-
/* @__PURE__ */
|
|
2696
|
-
/* @__PURE__ */
|
|
3134
|
+
/* @__PURE__ */ jsx15("span", { className: "truncate", children: scene.sceneName }),
|
|
3135
|
+
/* @__PURE__ */ jsxs11("span", { className: "text-sas-muted", children: [
|
|
2697
3136
|
scene.tracks.length,
|
|
2698
3137
|
" \u2192"
|
|
2699
3138
|
] })
|
|
2700
3139
|
]
|
|
2701
3140
|
}
|
|
2702
3141
|
) }, scene.sceneId)) }),
|
|
2703
|
-
selectedScene && /* @__PURE__ */
|
|
3142
|
+
selectedScene && /* @__PURE__ */ jsx15("ul", { className: "flex flex-col gap-1", "data-testid": `${testIdPrefix}-track-list`, children: selectedScene.tracks.map((track) => {
|
|
2704
3143
|
const busy = importingTrackId === track.trackId;
|
|
2705
3144
|
const gated = mode === "track" && !track.importable;
|
|
2706
3145
|
const disabled = gated || busy;
|
|
2707
|
-
return /* @__PURE__ */
|
|
3146
|
+
return /* @__PURE__ */ jsx15("li", { children: /* @__PURE__ */ jsxs11(
|
|
2708
3147
|
"button",
|
|
2709
3148
|
{
|
|
2710
3149
|
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"}`,
|
|
@@ -2714,14 +3153,14 @@ function ImportTrackModal({
|
|
|
2714
3153
|
"data-testid": `${testIdPrefix}-track`,
|
|
2715
3154
|
"data-importable": mode === "sound" || track.importable ? "true" : "false",
|
|
2716
3155
|
children: [
|
|
2717
|
-
/* @__PURE__ */
|
|
3156
|
+
/* @__PURE__ */ jsxs11("span", { className: "truncate", children: [
|
|
2718
3157
|
track.name,
|
|
2719
|
-
track.role ? /* @__PURE__ */
|
|
3158
|
+
track.role ? /* @__PURE__ */ jsxs11("span", { className: "text-sas-muted", children: [
|
|
2720
3159
|
" \xB7 ",
|
|
2721
3160
|
track.role
|
|
2722
3161
|
] }) : null
|
|
2723
3162
|
] }),
|
|
2724
|
-
busy ? /* @__PURE__ */
|
|
3163
|
+
busy ? /* @__PURE__ */ jsx15("span", { className: "text-sas-muted", children: "\u2026" }) : gated ? /* @__PURE__ */ jsx15("span", { className: "text-sas-muted", children: "\u2298" }) : null
|
|
2725
3164
|
]
|
|
2726
3165
|
}
|
|
2727
3166
|
) }, track.dbId);
|
|
@@ -2733,8 +3172,38 @@ function ImportTrackModal({
|
|
|
2733
3172
|
}
|
|
2734
3173
|
|
|
2735
3174
|
// src/components/CrossfadeModal.tsx
|
|
2736
|
-
import { useCallback as
|
|
2737
|
-
import { Fragment as
|
|
3175
|
+
import { useCallback as useCallback6, useEffect as useEffect8, useMemo as useMemo4, useRef as useRef8, useState as useState9 } from "react";
|
|
3176
|
+
import { Fragment as Fragment5, jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3177
|
+
function shortId2(dbId) {
|
|
3178
|
+
return dbId.length > 8 ? dbId.slice(0, 8) : dbId;
|
|
3179
|
+
}
|
|
3180
|
+
function CandidateRow({
|
|
3181
|
+
track,
|
|
3182
|
+
selected,
|
|
3183
|
+
disabled,
|
|
3184
|
+
onSelect,
|
|
3185
|
+
testId
|
|
3186
|
+
}) {
|
|
3187
|
+
const primary = track.prompt?.trim() || track.name;
|
|
3188
|
+
const meta = [track.role, shortId2(track.dbId)].filter(Boolean).join(" \xB7 ");
|
|
3189
|
+
return /* @__PURE__ */ jsxs12(
|
|
3190
|
+
"button",
|
|
3191
|
+
{
|
|
3192
|
+
type: "button",
|
|
3193
|
+
role: "radio",
|
|
3194
|
+
"aria-checked": selected,
|
|
3195
|
+
"data-testid": testId,
|
|
3196
|
+
"data-value": track.dbId,
|
|
3197
|
+
onClick: onSelect,
|
|
3198
|
+
disabled,
|
|
3199
|
+
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"}`,
|
|
3200
|
+
children: [
|
|
3201
|
+
/* @__PURE__ */ jsx16("div", { className: "text-xs text-sas-text truncate", title: primary, children: primary }),
|
|
3202
|
+
meta && /* @__PURE__ */ jsx16("div", { className: "text-[10px] text-sas-muted truncate mt-0.5", title: track.dbId, children: meta })
|
|
3203
|
+
]
|
|
3204
|
+
}
|
|
3205
|
+
);
|
|
3206
|
+
}
|
|
2738
3207
|
function CrossfadeModal({
|
|
2739
3208
|
host,
|
|
2740
3209
|
open,
|
|
@@ -2747,15 +3216,15 @@ function CrossfadeModal({
|
|
|
2747
3216
|
onCreate,
|
|
2748
3217
|
testIdPrefix = "crossfade-modal"
|
|
2749
3218
|
}) {
|
|
2750
|
-
const [load, setLoad] =
|
|
2751
|
-
const [originDbId, setOriginDbId] =
|
|
2752
|
-
const [targetDbId, setTargetDbId] =
|
|
2753
|
-
const [isCreating, setIsCreating] =
|
|
2754
|
-
const [error, setError] =
|
|
2755
|
-
const [fromName, setFromName] =
|
|
2756
|
-
const [toName, setToName] =
|
|
2757
|
-
const cancelRef =
|
|
2758
|
-
const refresh =
|
|
3219
|
+
const [load, setLoad] = useState9({ status: "loading" });
|
|
3220
|
+
const [originDbId, setOriginDbId] = useState9("");
|
|
3221
|
+
const [targetDbId, setTargetDbId] = useState9("");
|
|
3222
|
+
const [isCreating, setIsCreating] = useState9(false);
|
|
3223
|
+
const [error, setError] = useState9(null);
|
|
3224
|
+
const [fromName, setFromName] = useState9(null);
|
|
3225
|
+
const [toName, setToName] = useState9(null);
|
|
3226
|
+
const cancelRef = useRef8(null);
|
|
3227
|
+
const refresh = useCallback6(async () => {
|
|
2759
3228
|
if (!host.listSceneFamilyTracks) {
|
|
2760
3229
|
setLoad({ status: "error", message: "This host does not support crossfade tracks." });
|
|
2761
3230
|
return;
|
|
@@ -2775,7 +3244,7 @@ function CrossfadeModal({
|
|
|
2775
3244
|
setLoad({ status: "error", message: err instanceof Error ? err.message : "Failed to load tracks." });
|
|
2776
3245
|
}
|
|
2777
3246
|
}, [host, fromSceneId, toSceneId]);
|
|
2778
|
-
|
|
3247
|
+
useEffect8(() => {
|
|
2779
3248
|
if (open) {
|
|
2780
3249
|
setError(null);
|
|
2781
3250
|
setIsCreating(false);
|
|
@@ -2784,21 +3253,21 @@ function CrossfadeModal({
|
|
|
2784
3253
|
void refresh();
|
|
2785
3254
|
}
|
|
2786
3255
|
}, [open, refresh]);
|
|
2787
|
-
const excludeSet =
|
|
2788
|
-
const originCandidates =
|
|
3256
|
+
const excludeSet = useMemo4(() => new Set(excludeSourceDbIds ?? []), [excludeSourceDbIds]);
|
|
3257
|
+
const originCandidates = useMemo4(
|
|
2789
3258
|
() => load.status === "ready" ? load.origin.filter((t) => !excludeSet.has(t.dbId)) : [],
|
|
2790
3259
|
[load, excludeSet]
|
|
2791
3260
|
);
|
|
2792
|
-
const targetCandidates =
|
|
3261
|
+
const targetCandidates = useMemo4(
|
|
2793
3262
|
() => load.status === "ready" ? load.target.filter((t) => !excludeSet.has(t.dbId)) : [],
|
|
2794
3263
|
[load, excludeSet]
|
|
2795
3264
|
);
|
|
2796
|
-
|
|
3265
|
+
useEffect8(() => {
|
|
2797
3266
|
if (!originCandidates.some((t) => t.dbId === originDbId)) {
|
|
2798
3267
|
setOriginDbId(originCandidates[0]?.dbId ?? "");
|
|
2799
3268
|
}
|
|
2800
3269
|
}, [originCandidates, originDbId]);
|
|
2801
|
-
|
|
3270
|
+
useEffect8(() => {
|
|
2802
3271
|
if (!targetCandidates.some((t) => t.dbId === targetDbId)) {
|
|
2803
3272
|
setTargetDbId(targetCandidates[0]?.dbId ?? "");
|
|
2804
3273
|
}
|
|
@@ -2806,10 +3275,10 @@ function CrossfadeModal({
|
|
|
2806
3275
|
const originTrack = originCandidates.find((t) => t.dbId === originDbId) ?? null;
|
|
2807
3276
|
const targetTrack = targetCandidates.find((t) => t.dbId === targetDbId) ?? null;
|
|
2808
3277
|
const canCreate = !isCreating && !!originTrack && !!targetTrack;
|
|
2809
|
-
const handleClose =
|
|
3278
|
+
const handleClose = useCallback6(() => {
|
|
2810
3279
|
if (!isCreating) onClose();
|
|
2811
3280
|
}, [isCreating, onClose]);
|
|
2812
|
-
const handleCreate =
|
|
3281
|
+
const handleCreate = useCallback6(async () => {
|
|
2813
3282
|
if (!originTrack || !targetTrack) return;
|
|
2814
3283
|
setIsCreating(true);
|
|
2815
3284
|
setError(null);
|
|
@@ -2827,26 +3296,26 @@ function CrossfadeModal({
|
|
|
2827
3296
|
const fromLabel = fromName ?? fromSceneName ?? null;
|
|
2828
3297
|
const toLabel = toName ?? toSceneName ?? null;
|
|
2829
3298
|
if (!open) return null;
|
|
2830
|
-
return /* @__PURE__ */
|
|
3299
|
+
return /* @__PURE__ */ jsx16(Modal, { open, onClose: handleClose, testIdPrefix, initialFocusRef: cancelRef, children: /* @__PURE__ */ jsxs12(
|
|
2831
3300
|
"div",
|
|
2832
3301
|
{
|
|
2833
3302
|
className: "bg-sas-panel border border-sas-border rounded-md shadow-xl w-[420px] max-w-[92vw] p-4 space-y-3",
|
|
2834
3303
|
onClick: (e) => e.stopPropagation(),
|
|
2835
3304
|
"data-testid": `${testIdPrefix}-box`,
|
|
2836
3305
|
children: [
|
|
2837
|
-
/* @__PURE__ */
|
|
2838
|
-
/* @__PURE__ */
|
|
3306
|
+
/* @__PURE__ */ jsx16("h3", { className: "text-sm font-bold text-sas-text", children: "Add crossfade" }),
|
|
3307
|
+
/* @__PURE__ */ jsxs12("p", { className: "text-[11px] text-sas-muted leading-relaxed", children: [
|
|
2839
3308
|
"Bridge a track from",
|
|
2840
3309
|
" ",
|
|
2841
|
-
/* @__PURE__ */
|
|
3310
|
+
/* @__PURE__ */ jsx16("span", { className: "text-sas-text", children: fromLabel ?? "the origin scene" }),
|
|
2842
3311
|
" into one from",
|
|
2843
3312
|
" ",
|
|
2844
|
-
/* @__PURE__ */
|
|
3313
|
+
/* @__PURE__ */ jsx16("span", { className: "text-sas-text", children: toLabel ?? "the target scene" }),
|
|
2845
3314
|
". Both layers share one generated part; each keeps its own preset."
|
|
2846
3315
|
] }),
|
|
2847
|
-
load.status === "loading" && /* @__PURE__ */
|
|
2848
|
-
load.status === "error" && /* @__PURE__ */
|
|
2849
|
-
load.status === "ready" && (originCandidates.length === 0 ? /* @__PURE__ */
|
|
3316
|
+
load.status === "loading" && /* @__PURE__ */ jsx16("div", { className: "text-xs text-sas-muted py-4 text-center", children: "Loading tracks\u2026" }),
|
|
3317
|
+
load.status === "error" && /* @__PURE__ */ jsx16("div", { className: "text-xs text-sas-danger py-4 text-center", children: load.message }),
|
|
3318
|
+
load.status === "ready" && (originCandidates.length === 0 ? /* @__PURE__ */ jsxs12(
|
|
2850
3319
|
"div",
|
|
2851
3320
|
{
|
|
2852
3321
|
className: "text-xs text-sas-muted py-4 text-center",
|
|
@@ -2857,55 +3326,67 @@ function CrossfadeModal({
|
|
|
2857
3326
|
". Add one (or free one from another crossfade) first."
|
|
2858
3327
|
]
|
|
2859
3328
|
}
|
|
2860
|
-
) : /* @__PURE__ */
|
|
2861
|
-
/* @__PURE__ */
|
|
2862
|
-
/* @__PURE__ */
|
|
3329
|
+
) : /* @__PURE__ */ jsxs12(Fragment5, { children: [
|
|
3330
|
+
/* @__PURE__ */ jsxs12("div", { className: "block", children: [
|
|
3331
|
+
/* @__PURE__ */ jsxs12("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: [
|
|
2863
3332
|
"Origin ",
|
|
2864
3333
|
fromLabel ? `(${fromLabel})` : "(top)"
|
|
2865
3334
|
] }),
|
|
2866
|
-
/* @__PURE__ */
|
|
2867
|
-
"
|
|
3335
|
+
/* @__PURE__ */ jsx16(
|
|
3336
|
+
"div",
|
|
2868
3337
|
{
|
|
2869
|
-
"
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
3338
|
+
role: "radiogroup",
|
|
3339
|
+
"aria-label": "Origin track",
|
|
3340
|
+
"data-testid": `${testIdPrefix}-origin-list`,
|
|
3341
|
+
className: "mt-1 space-y-1 max-h-40 overflow-y-auto pr-0.5",
|
|
3342
|
+
children: originCandidates.map((t) => /* @__PURE__ */ jsx16(
|
|
3343
|
+
CandidateRow,
|
|
3344
|
+
{
|
|
3345
|
+
track: t,
|
|
3346
|
+
selected: t.dbId === originDbId,
|
|
3347
|
+
disabled: isCreating,
|
|
3348
|
+
onSelect: () => setOriginDbId(t.dbId),
|
|
3349
|
+
testId: `${testIdPrefix}-origin-option-${t.dbId}`
|
|
3350
|
+
},
|
|
3351
|
+
t.dbId
|
|
3352
|
+
))
|
|
2878
3353
|
}
|
|
2879
3354
|
)
|
|
2880
3355
|
] }),
|
|
2881
|
-
/* @__PURE__ */
|
|
2882
|
-
/* @__PURE__ */
|
|
3356
|
+
/* @__PURE__ */ jsxs12("div", { className: "block", children: [
|
|
3357
|
+
/* @__PURE__ */ jsxs12("span", { className: "text-[10px] uppercase tracking-wide text-sas-muted", children: [
|
|
2883
3358
|
"Target ",
|
|
2884
3359
|
toLabel ? `(${toLabel})` : "(bottom)"
|
|
2885
3360
|
] }),
|
|
2886
|
-
targetCandidates.length === 0 ? /* @__PURE__ */
|
|
3361
|
+
targetCandidates.length === 0 ? /* @__PURE__ */ jsxs12("div", { className: "text-xs text-sas-danger mt-0.5", "data-testid": `${testIdPrefix}-empty-target`, children: [
|
|
2887
3362
|
"No available tracks in ",
|
|
2888
3363
|
toLabel ?? "the target scene",
|
|
2889
3364
|
" to crossfade into."
|
|
2890
|
-
] }) : /* @__PURE__ */
|
|
2891
|
-
"
|
|
3365
|
+
] }) : /* @__PURE__ */ jsx16(
|
|
3366
|
+
"div",
|
|
2892
3367
|
{
|
|
2893
|
-
"
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
3368
|
+
role: "radiogroup",
|
|
3369
|
+
"aria-label": "Target track",
|
|
3370
|
+
"data-testid": `${testIdPrefix}-target-list`,
|
|
3371
|
+
className: "mt-1 space-y-1 max-h-40 overflow-y-auto pr-0.5",
|
|
3372
|
+
children: targetCandidates.map((t) => /* @__PURE__ */ jsx16(
|
|
3373
|
+
CandidateRow,
|
|
3374
|
+
{
|
|
3375
|
+
track: t,
|
|
3376
|
+
selected: t.dbId === targetDbId,
|
|
3377
|
+
disabled: isCreating,
|
|
3378
|
+
onSelect: () => setTargetDbId(t.dbId),
|
|
3379
|
+
testId: `${testIdPrefix}-target-option-${t.dbId}`
|
|
3380
|
+
},
|
|
3381
|
+
t.dbId
|
|
3382
|
+
))
|
|
2902
3383
|
}
|
|
2903
3384
|
)
|
|
2904
3385
|
] })
|
|
2905
3386
|
] })),
|
|
2906
|
-
error && /* @__PURE__ */
|
|
2907
|
-
/* @__PURE__ */
|
|
2908
|
-
/* @__PURE__ */
|
|
3387
|
+
error && /* @__PURE__ */ jsx16("div", { className: "text-xs text-sas-danger", "data-testid": `${testIdPrefix}-error`, children: error }),
|
|
3388
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex justify-end gap-2 pt-1", children: [
|
|
3389
|
+
/* @__PURE__ */ jsx16(
|
|
2909
3390
|
"button",
|
|
2910
3391
|
{
|
|
2911
3392
|
ref: cancelRef,
|
|
@@ -2916,7 +3397,7 @@ function CrossfadeModal({
|
|
|
2916
3397
|
children: "Cancel"
|
|
2917
3398
|
}
|
|
2918
3399
|
),
|
|
2919
|
-
/* @__PURE__ */
|
|
3400
|
+
/* @__PURE__ */ jsx16(
|
|
2920
3401
|
"button",
|
|
2921
3402
|
{
|
|
2922
3403
|
"data-testid": `${testIdPrefix}-confirm`,
|
|
@@ -2933,8 +3414,8 @@ function CrossfadeModal({
|
|
|
2933
3414
|
}
|
|
2934
3415
|
|
|
2935
3416
|
// src/components/DownloadPackButton.tsx
|
|
2936
|
-
import { useCallback as
|
|
2937
|
-
import { jsx as
|
|
3417
|
+
import { useCallback as useCallback7, useEffect as useEffect9, useState as useState10 } from "react";
|
|
3418
|
+
import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
2938
3419
|
function formatSize(bytes) {
|
|
2939
3420
|
if (!bytes || bytes <= 0) return "";
|
|
2940
3421
|
const gb = bytes / 1024 ** 3;
|
|
@@ -2950,10 +3431,10 @@ var DownloadPackButton = ({
|
|
|
2950
3431
|
variant = "compact",
|
|
2951
3432
|
onDownloadComplete
|
|
2952
3433
|
}) => {
|
|
2953
|
-
const [status, setStatus] =
|
|
2954
|
-
const [progress, setProgress] =
|
|
2955
|
-
const [errorMessage, setErrorMessage] =
|
|
2956
|
-
|
|
3434
|
+
const [status, setStatus] = useState10("idle");
|
|
3435
|
+
const [progress, setProgress] = useState10(0);
|
|
3436
|
+
const [errorMessage, setErrorMessage] = useState10(null);
|
|
3437
|
+
useEffect9(() => {
|
|
2957
3438
|
const unsub = host.onSamplePackProgress(packId, (p) => {
|
|
2958
3439
|
setStatus(p.status);
|
|
2959
3440
|
setProgress(p.progress);
|
|
@@ -2968,7 +3449,7 @@ var DownloadPackButton = ({
|
|
|
2968
3449
|
});
|
|
2969
3450
|
return unsub;
|
|
2970
3451
|
}, [host, packId, onDownloadComplete]);
|
|
2971
|
-
const handleClick =
|
|
3452
|
+
const handleClick = useCallback7(async () => {
|
|
2972
3453
|
if (status !== "idle" && status !== "error") return;
|
|
2973
3454
|
try {
|
|
2974
3455
|
setStatus("downloading");
|
|
@@ -3022,8 +3503,8 @@ var DownloadPackButton = ({
|
|
|
3022
3503
|
} else {
|
|
3023
3504
|
className = `${baseClasses} text-sas-muted hover:text-sas-accent border-sas-border hover:border-sas-accent`;
|
|
3024
3505
|
}
|
|
3025
|
-
return /* @__PURE__ */
|
|
3026
|
-
/* @__PURE__ */
|
|
3506
|
+
return /* @__PURE__ */ jsxs13("div", { children: [
|
|
3507
|
+
/* @__PURE__ */ jsx17(
|
|
3027
3508
|
"button",
|
|
3028
3509
|
{
|
|
3029
3510
|
"data-testid": `download-pack-button-${packId}`,
|
|
@@ -3034,12 +3515,12 @@ var DownloadPackButton = ({
|
|
|
3034
3515
|
children: buttonLabel
|
|
3035
3516
|
}
|
|
3036
3517
|
),
|
|
3037
|
-
variant === "large" && status === "error" && errorMessage && /* @__PURE__ */
|
|
3518
|
+
variant === "large" && status === "error" && errorMessage && /* @__PURE__ */ jsx17("div", { className: "text-xs text-sas-danger mt-2", "data-testid": `download-pack-error-${packId}`, children: errorMessage })
|
|
3038
3519
|
] });
|
|
3039
3520
|
};
|
|
3040
3521
|
|
|
3041
3522
|
// src/components/SamplePackCTACard.tsx
|
|
3042
|
-
import { jsx as
|
|
3523
|
+
import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
3043
3524
|
var SamplePackCTACard = ({
|
|
3044
3525
|
host,
|
|
3045
3526
|
pack,
|
|
@@ -3047,7 +3528,7 @@ var SamplePackCTACard = ({
|
|
|
3047
3528
|
onDownloadComplete
|
|
3048
3529
|
}) => {
|
|
3049
3530
|
if (status === "checking") {
|
|
3050
|
-
return /* @__PURE__ */
|
|
3531
|
+
return /* @__PURE__ */ jsx18(
|
|
3051
3532
|
"div",
|
|
3052
3533
|
{
|
|
3053
3534
|
"data-testid": `sample-pack-cta-checking-${pack.packId}`,
|
|
@@ -3058,16 +3539,16 @@ var SamplePackCTACard = ({
|
|
|
3058
3539
|
}
|
|
3059
3540
|
const headline = status === "stale" ? `${pack.displayName} update available` : `${pack.displayName} not installed`;
|
|
3060
3541
|
const sublabel = status === "stale" ? `A newer version is available for download.` : pack.description;
|
|
3061
|
-
return /* @__PURE__ */
|
|
3542
|
+
return /* @__PURE__ */ jsxs14(
|
|
3062
3543
|
"div",
|
|
3063
3544
|
{
|
|
3064
3545
|
"data-testid": `sample-pack-cta-${pack.packId}`,
|
|
3065
3546
|
className: "flex flex-col items-center justify-center py-12 px-6 text-center",
|
|
3066
3547
|
children: [
|
|
3067
|
-
/* @__PURE__ */
|
|
3068
|
-
/* @__PURE__ */
|
|
3069
|
-
/* @__PURE__ */
|
|
3070
|
-
/* @__PURE__ */
|
|
3548
|
+
/* @__PURE__ */ jsx18("div", { className: "text-sm uppercase tracking-wide text-sas-muted mb-2", children: status === "stale" ? "Update available" : "Sample library not installed" }),
|
|
3549
|
+
/* @__PURE__ */ jsx18("div", { className: "text-base text-sas-text mb-1", children: headline }),
|
|
3550
|
+
/* @__PURE__ */ jsx18("div", { className: "text-xs text-sas-muted mb-6 max-w-md", children: sublabel }),
|
|
3551
|
+
/* @__PURE__ */ jsx18(
|
|
3071
3552
|
DownloadPackButton,
|
|
3072
3553
|
{
|
|
3073
3554
|
host,
|
|
@@ -3084,7 +3565,7 @@ var SamplePackCTACard = ({
|
|
|
3084
3565
|
};
|
|
3085
3566
|
|
|
3086
3567
|
// src/components/WaveformView.tsx
|
|
3087
|
-
import { useEffect as
|
|
3568
|
+
import { useEffect as useEffect10, useRef as useRef9, useState as useState11 } from "react";
|
|
3088
3569
|
|
|
3089
3570
|
// src/components/waveform.ts
|
|
3090
3571
|
function computePeaks(audioBuffer, bins, targetSamples) {
|
|
@@ -3147,7 +3628,7 @@ function drawWaveform(canvas, peaks, options = {}) {
|
|
|
3147
3628
|
}
|
|
3148
3629
|
|
|
3149
3630
|
// src/components/WaveformView.tsx
|
|
3150
|
-
import { jsx as
|
|
3631
|
+
import { jsx as jsx19 } from "react/jsx-runtime";
|
|
3151
3632
|
var WaveformView = ({
|
|
3152
3633
|
host,
|
|
3153
3634
|
filePath,
|
|
@@ -3156,9 +3637,9 @@ var WaveformView = ({
|
|
|
3156
3637
|
fillStyle,
|
|
3157
3638
|
targetSamples
|
|
3158
3639
|
}) => {
|
|
3159
|
-
const canvasRef =
|
|
3160
|
-
const [peaks, setPeaks] =
|
|
3161
|
-
|
|
3640
|
+
const canvasRef = useRef9(null);
|
|
3641
|
+
const [peaks, setPeaks] = useState11(null);
|
|
3642
|
+
useEffect10(() => {
|
|
3162
3643
|
let cancelled = false;
|
|
3163
3644
|
let audioContext = null;
|
|
3164
3645
|
(async () => {
|
|
@@ -3184,7 +3665,7 @@ var WaveformView = ({
|
|
|
3184
3665
|
cancelled = true;
|
|
3185
3666
|
};
|
|
3186
3667
|
}, [host, filePath, bins, targetSamples]);
|
|
3187
|
-
|
|
3668
|
+
useEffect10(() => {
|
|
3188
3669
|
if (!peaks) return;
|
|
3189
3670
|
const canvas = canvasRef.current;
|
|
3190
3671
|
if (!canvas) return;
|
|
@@ -3195,7 +3676,7 @@ var WaveformView = ({
|
|
|
3195
3676
|
observer.observe(canvas);
|
|
3196
3677
|
return () => observer.disconnect();
|
|
3197
3678
|
}, [peaks, fillStyle]);
|
|
3198
|
-
return /* @__PURE__ */
|
|
3679
|
+
return /* @__PURE__ */ jsx19(
|
|
3199
3680
|
"canvas",
|
|
3200
3681
|
{
|
|
3201
3682
|
ref: canvasRef,
|
|
@@ -3206,8 +3687,8 @@ var WaveformView = ({
|
|
|
3206
3687
|
};
|
|
3207
3688
|
|
|
3208
3689
|
// src/components/ScrollingWaveform.tsx
|
|
3209
|
-
import { useEffect as
|
|
3210
|
-
import { jsx as
|
|
3690
|
+
import { useEffect as useEffect11, useRef as useRef10 } from "react";
|
|
3691
|
+
import { jsx as jsx20 } from "react/jsx-runtime";
|
|
3211
3692
|
var ScrollingWaveform = ({
|
|
3212
3693
|
getPeakDb,
|
|
3213
3694
|
active,
|
|
@@ -3215,11 +3696,11 @@ var ScrollingWaveform = ({
|
|
|
3215
3696
|
className,
|
|
3216
3697
|
fillStyle
|
|
3217
3698
|
}) => {
|
|
3218
|
-
const canvasRef =
|
|
3219
|
-
const ringRef =
|
|
3220
|
-
const writeIdxRef =
|
|
3221
|
-
const rafRef =
|
|
3222
|
-
|
|
3699
|
+
const canvasRef = useRef10(null);
|
|
3700
|
+
const ringRef = useRef10(new Float32Array(columns));
|
|
3701
|
+
const writeIdxRef = useRef10(0);
|
|
3702
|
+
const rafRef = useRef10(null);
|
|
3703
|
+
useEffect11(() => {
|
|
3223
3704
|
if (ringRef.current.length !== columns) {
|
|
3224
3705
|
const next = new Float32Array(columns);
|
|
3225
3706
|
const prev = ringRef.current;
|
|
@@ -3231,7 +3712,7 @@ var ScrollingWaveform = ({
|
|
|
3231
3712
|
writeIdxRef.current = writeIdxRef.current % columns;
|
|
3232
3713
|
}
|
|
3233
3714
|
}, [columns]);
|
|
3234
|
-
|
|
3715
|
+
useEffect11(() => {
|
|
3235
3716
|
if (!active) {
|
|
3236
3717
|
if (rafRef.current !== null) {
|
|
3237
3718
|
cancelAnimationFrame(rafRef.current);
|
|
@@ -3283,7 +3764,7 @@ var ScrollingWaveform = ({
|
|
|
3283
3764
|
}
|
|
3284
3765
|
};
|
|
3285
3766
|
}, [active, getPeakDb, fillStyle]);
|
|
3286
|
-
return /* @__PURE__ */
|
|
3767
|
+
return /* @__PURE__ */ jsx20(
|
|
3287
3768
|
"canvas",
|
|
3288
3769
|
{
|
|
3289
3770
|
ref: canvasRef,
|
|
@@ -3294,8 +3775,8 @@ var ScrollingWaveform = ({
|
|
|
3294
3775
|
};
|
|
3295
3776
|
|
|
3296
3777
|
// src/components/OffsetScrubber.tsx
|
|
3297
|
-
import { useCallback as
|
|
3298
|
-
import { jsx as
|
|
3778
|
+
import { useCallback as useCallback8, useEffect as useEffect12, useMemo as useMemo5, useRef as useRef11, useState as useState12 } from "react";
|
|
3779
|
+
import { jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
3299
3780
|
var SLIDER_HEIGHT_PX = 28;
|
|
3300
3781
|
var TICK_HEIGHT_PX = 14;
|
|
3301
3782
|
var DOWNBEAT_TICK_HEIGHT_PX = 22;
|
|
@@ -3308,40 +3789,40 @@ function OffsetScrubber({
|
|
|
3308
3789
|
onChange,
|
|
3309
3790
|
disabled = false
|
|
3310
3791
|
}) {
|
|
3311
|
-
const trackRef =
|
|
3312
|
-
const [draftOffset, setDraftOffset] =
|
|
3313
|
-
const [isDragging, setIsDragging] =
|
|
3314
|
-
|
|
3792
|
+
const trackRef = useRef11(null);
|
|
3793
|
+
const [draftOffset, setDraftOffset] = useState12(offsetSamples);
|
|
3794
|
+
const [isDragging, setIsDragging] = useState12(false);
|
|
3795
|
+
useEffect12(() => {
|
|
3315
3796
|
if (!isDragging) setDraftOffset(offsetSamples);
|
|
3316
3797
|
}, [offsetSamples, isDragging]);
|
|
3317
3798
|
const sampleRate = cuePoints?.sample_rate ?? 44100;
|
|
3318
3799
|
const detectedBpm = cuePoints?.detected_bpm ?? projectBpm;
|
|
3319
|
-
const beatsForRange =
|
|
3800
|
+
const beatsForRange = useMemo5(() => {
|
|
3320
3801
|
return Math.round(60 / projectBpm * sampleRate);
|
|
3321
3802
|
}, [projectBpm, sampleRate]);
|
|
3322
3803
|
const rangeSamples = beatsForRange * meter;
|
|
3323
|
-
const sampleToFraction =
|
|
3804
|
+
const sampleToFraction = useCallback8(
|
|
3324
3805
|
(sample) => {
|
|
3325
3806
|
const clamped = Math.max(-rangeSamples, Math.min(rangeSamples, sample));
|
|
3326
3807
|
return (clamped + rangeSamples) / (2 * rangeSamples);
|
|
3327
3808
|
},
|
|
3328
3809
|
[rangeSamples]
|
|
3329
3810
|
);
|
|
3330
|
-
const fractionToSample =
|
|
3811
|
+
const fractionToSample = useCallback8(
|
|
3331
3812
|
(fraction) => {
|
|
3332
3813
|
const clamped = Math.max(0, Math.min(1, fraction));
|
|
3333
3814
|
return Math.round(clamped * 2 * rangeSamples - rangeSamples);
|
|
3334
3815
|
},
|
|
3335
3816
|
[rangeSamples]
|
|
3336
3817
|
);
|
|
3337
|
-
const snapTargets =
|
|
3818
|
+
const snapTargets = useMemo5(() => {
|
|
3338
3819
|
if (!cuePoints || cuePoints.beats.length === 0) return [];
|
|
3339
3820
|
const downbeat = cuePoints.beats[0];
|
|
3340
3821
|
const positives = cuePoints.beats.map((b) => b - downbeat);
|
|
3341
3822
|
const negatives = positives.slice(1).map((p) => -p);
|
|
3342
3823
|
return [...negatives, ...positives].sort((a, b) => a - b);
|
|
3343
3824
|
}, [cuePoints]);
|
|
3344
|
-
const snapToBeat =
|
|
3825
|
+
const snapToBeat = useCallback8(
|
|
3345
3826
|
(sample) => {
|
|
3346
3827
|
if (snapTargets.length === 0) return sample;
|
|
3347
3828
|
let best = snapTargets[0];
|
|
@@ -3357,7 +3838,7 @@ function OffsetScrubber({
|
|
|
3357
3838
|
},
|
|
3358
3839
|
[snapTargets]
|
|
3359
3840
|
);
|
|
3360
|
-
const handlePointerDown =
|
|
3841
|
+
const handlePointerDown = useCallback8(
|
|
3361
3842
|
(e) => {
|
|
3362
3843
|
if (disabled || !cuePoints) return;
|
|
3363
3844
|
e.preventDefault();
|
|
@@ -3391,7 +3872,7 @@ function OffsetScrubber({
|
|
|
3391
3872
|
},
|
|
3392
3873
|
[disabled, cuePoints, fractionToSample, onChange, snapToBeat]
|
|
3393
3874
|
);
|
|
3394
|
-
const handleResetToZero =
|
|
3875
|
+
const handleResetToZero = useCallback8(() => {
|
|
3395
3876
|
if (disabled) return;
|
|
3396
3877
|
setDraftOffset(0);
|
|
3397
3878
|
onChange(0);
|
|
@@ -3399,7 +3880,7 @@ function OffsetScrubber({
|
|
|
3399
3880
|
const thumbFraction = sampleToFraction(draftOffset);
|
|
3400
3881
|
const thumbLeftPct = `${(thumbFraction * 100).toFixed(2)}%`;
|
|
3401
3882
|
const bpmMismatch = cuePoints?.detected_bpm != null && Math.abs(cuePoints.detected_bpm - projectBpm) > 1;
|
|
3402
|
-
const ticks =
|
|
3883
|
+
const ticks = useMemo5(() => {
|
|
3403
3884
|
if (!cuePoints) return [];
|
|
3404
3885
|
const downbeat = cuePoints.beats[0] ?? 0;
|
|
3405
3886
|
return cuePoints.beats.map((b, i) => {
|
|
@@ -3410,9 +3891,9 @@ function OffsetScrubber({
|
|
|
3410
3891
|
});
|
|
3411
3892
|
}, [cuePoints, sampleToFraction]);
|
|
3412
3893
|
const isDisabled = disabled || !cuePoints || cuePoints.beats.length === 0;
|
|
3413
|
-
return /* @__PURE__ */
|
|
3414
|
-
/* @__PURE__ */
|
|
3415
|
-
/* @__PURE__ */
|
|
3894
|
+
return /* @__PURE__ */ jsxs15("div", { "data-testid": "offset-scrubber", className: "flex items-center gap-2 w-full", children: [
|
|
3895
|
+
/* @__PURE__ */ jsx21("span", { className: "text-[9px] text-sas-muted/60 uppercase tracking-wide flex-shrink-0", children: "Align" }),
|
|
3896
|
+
/* @__PURE__ */ jsxs15(
|
|
3416
3897
|
"div",
|
|
3417
3898
|
{
|
|
3418
3899
|
ref: trackRef,
|
|
@@ -3428,7 +3909,7 @@ function OffsetScrubber({
|
|
|
3428
3909
|
"aria-valuenow": draftOffset,
|
|
3429
3910
|
"aria-disabled": isDisabled,
|
|
3430
3911
|
children: [
|
|
3431
|
-
/* @__PURE__ */
|
|
3912
|
+
/* @__PURE__ */ jsx21(
|
|
3432
3913
|
"div",
|
|
3433
3914
|
{
|
|
3434
3915
|
"aria-hidden": "true",
|
|
@@ -3436,7 +3917,7 @@ function OffsetScrubber({
|
|
|
3436
3917
|
style: { left: "50%" }
|
|
3437
3918
|
}
|
|
3438
3919
|
),
|
|
3439
|
-
ticks.map((t) => /* @__PURE__ */
|
|
3920
|
+
ticks.map((t) => /* @__PURE__ */ jsx21(
|
|
3440
3921
|
"div",
|
|
3441
3922
|
{
|
|
3442
3923
|
"data-testid": t.isDownbeat ? "offset-tick-downbeat" : "offset-tick",
|
|
@@ -3451,7 +3932,7 @@ function OffsetScrubber({
|
|
|
3451
3932
|
},
|
|
3452
3933
|
t.i
|
|
3453
3934
|
)),
|
|
3454
|
-
/* @__PURE__ */
|
|
3935
|
+
/* @__PURE__ */ jsx21(
|
|
3455
3936
|
"div",
|
|
3456
3937
|
{
|
|
3457
3938
|
"data-testid": "offset-scrubber-thumb",
|
|
@@ -3468,7 +3949,7 @@ function OffsetScrubber({
|
|
|
3468
3949
|
]
|
|
3469
3950
|
}
|
|
3470
3951
|
),
|
|
3471
|
-
/* @__PURE__ */
|
|
3952
|
+
/* @__PURE__ */ jsx21(
|
|
3472
3953
|
"span",
|
|
3473
3954
|
{
|
|
3474
3955
|
"data-testid": "offset-scrubber-readout",
|
|
@@ -3476,7 +3957,7 @@ function OffsetScrubber({
|
|
|
3476
3957
|
children: formatOffset(draftOffset, sampleRate)
|
|
3477
3958
|
}
|
|
3478
3959
|
),
|
|
3479
|
-
/* @__PURE__ */
|
|
3960
|
+
/* @__PURE__ */ jsx21(
|
|
3480
3961
|
"button",
|
|
3481
3962
|
{
|
|
3482
3963
|
type: "button",
|
|
@@ -3488,7 +3969,7 @@ function OffsetScrubber({
|
|
|
3488
3969
|
children: "\u2316"
|
|
3489
3970
|
}
|
|
3490
3971
|
),
|
|
3491
|
-
bpmMismatch && /* @__PURE__ */
|
|
3972
|
+
bpmMismatch && /* @__PURE__ */ jsx21(
|
|
3492
3973
|
"span",
|
|
3493
3974
|
{
|
|
3494
3975
|
"data-testid": "offset-bpm-mismatch",
|
|
@@ -3560,13 +4041,13 @@ function synthesizeCuePoints({
|
|
|
3560
4041
|
}
|
|
3561
4042
|
|
|
3562
4043
|
// src/hooks/useSceneState.ts
|
|
3563
|
-
import { useState as
|
|
4044
|
+
import { useState as useState13, useCallback as useCallback9, useRef as useRef12 } from "react";
|
|
3564
4045
|
function useSceneState(activeSceneId, initialValue) {
|
|
3565
|
-
const [stateMap, setStateMap] =
|
|
3566
|
-
const activeSceneIdRef =
|
|
4046
|
+
const [stateMap, setStateMap] = useState13(() => /* @__PURE__ */ new Map());
|
|
4047
|
+
const activeSceneIdRef = useRef12(activeSceneId);
|
|
3567
4048
|
activeSceneIdRef.current = activeSceneId;
|
|
3568
4049
|
const currentValue = activeSceneId !== null && stateMap.has(activeSceneId) ? stateMap.get(activeSceneId) : initialValue;
|
|
3569
|
-
const setForCurrentScene =
|
|
4050
|
+
const setForCurrentScene = useCallback9((value) => {
|
|
3570
4051
|
const sid = activeSceneIdRef.current;
|
|
3571
4052
|
if (sid === null) return;
|
|
3572
4053
|
setStateMap((prev) => {
|
|
@@ -3577,7 +4058,7 @@ function useSceneState(activeSceneId, initialValue) {
|
|
|
3577
4058
|
return newMap;
|
|
3578
4059
|
});
|
|
3579
4060
|
}, [initialValue]);
|
|
3580
|
-
const setForScene =
|
|
4061
|
+
const setForScene = useCallback9((sceneId, value) => {
|
|
3581
4062
|
setStateMap((prev) => {
|
|
3582
4063
|
const current = prev.has(sceneId) ? prev.get(sceneId) : initialValue;
|
|
3583
4064
|
const next = typeof value === "function" ? value(current) : value;
|
|
@@ -3590,10 +4071,10 @@ function useSceneState(activeSceneId, initialValue) {
|
|
|
3590
4071
|
}
|
|
3591
4072
|
|
|
3592
4073
|
// src/hooks/useAnySolo.ts
|
|
3593
|
-
import { useEffect as
|
|
4074
|
+
import { useEffect as useEffect13, useState as useState14 } from "react";
|
|
3594
4075
|
function useAnySolo(host) {
|
|
3595
|
-
const [anySolo, setAnySolo] =
|
|
3596
|
-
|
|
4076
|
+
const [anySolo, setAnySolo] = useState14(false);
|
|
4077
|
+
useEffect13(() => {
|
|
3597
4078
|
let active = true;
|
|
3598
4079
|
const refresh = () => {
|
|
3599
4080
|
host.isAnySoloActive().then((v) => {
|
|
@@ -3612,7 +4093,7 @@ function useAnySolo(host) {
|
|
|
3612
4093
|
}
|
|
3613
4094
|
|
|
3614
4095
|
// src/hooks/useSoundHistory.ts
|
|
3615
|
-
import { useCallback as
|
|
4096
|
+
import { useCallback as useCallback10, useMemo as useMemo6, useRef as useRef13, useState as useState15 } from "react";
|
|
3616
4097
|
var EMPTY = { entries: [], cursor: -1 };
|
|
3617
4098
|
function sameDescriptor(a, b) {
|
|
3618
4099
|
if (a === b) return true;
|
|
@@ -3624,14 +4105,14 @@ function sameDescriptor(a, b) {
|
|
|
3624
4105
|
}
|
|
3625
4106
|
function useSoundHistory(applySound, opts = {}) {
|
|
3626
4107
|
const max = Math.max(2, opts.max ?? 24);
|
|
3627
|
-
const applyRef =
|
|
4108
|
+
const applyRef = useRef13(applySound);
|
|
3628
4109
|
applyRef.current = applySound;
|
|
3629
|
-
const onChangeRef =
|
|
4110
|
+
const onChangeRef = useRef13(opts.onChange);
|
|
3630
4111
|
onChangeRef.current = opts.onChange;
|
|
3631
|
-
const dataRef =
|
|
3632
|
-
const [, setVersion] =
|
|
3633
|
-
const bump =
|
|
3634
|
-
const commit =
|
|
4112
|
+
const dataRef = useRef13({});
|
|
4113
|
+
const [, setVersion] = useState15(0);
|
|
4114
|
+
const bump = useCallback10(() => setVersion((v) => v + 1), []);
|
|
4115
|
+
const commit = useCallback10(
|
|
3635
4116
|
(trackId, next, notify) => {
|
|
3636
4117
|
dataRef.current = { ...dataRef.current, [trackId]: next };
|
|
3637
4118
|
bump();
|
|
@@ -3639,7 +4120,7 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
3639
4120
|
},
|
|
3640
4121
|
[bump]
|
|
3641
4122
|
);
|
|
3642
|
-
const record =
|
|
4123
|
+
const record = useCallback10(
|
|
3643
4124
|
(trackId, descriptor, label) => {
|
|
3644
4125
|
const h = dataRef.current[trackId];
|
|
3645
4126
|
const current = h && h.cursor >= 0 ? h.entries[h.cursor] : void 0;
|
|
@@ -3654,7 +4135,7 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
3654
4135
|
},
|
|
3655
4136
|
[max, commit]
|
|
3656
4137
|
);
|
|
3657
|
-
const restoreTo =
|
|
4138
|
+
const restoreTo = useCallback10(
|
|
3658
4139
|
async (trackId, index) => {
|
|
3659
4140
|
const h = dataRef.current[trackId];
|
|
3660
4141
|
if (!h || index < 0 || index >= h.entries.length || index === h.cursor) return false;
|
|
@@ -3664,7 +4145,7 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
3664
4145
|
},
|
|
3665
4146
|
[commit]
|
|
3666
4147
|
);
|
|
3667
|
-
const undo =
|
|
4148
|
+
const undo = useCallback10(
|
|
3668
4149
|
(trackId) => {
|
|
3669
4150
|
const h = dataRef.current[trackId];
|
|
3670
4151
|
if (!h || h.cursor <= 0) return Promise.resolve(false);
|
|
@@ -3672,7 +4153,7 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
3672
4153
|
},
|
|
3673
4154
|
[restoreTo]
|
|
3674
4155
|
);
|
|
3675
|
-
const toggleFavorite =
|
|
4156
|
+
const toggleFavorite = useCallback10(
|
|
3676
4157
|
(trackId, index) => {
|
|
3677
4158
|
const h = dataRef.current[trackId];
|
|
3678
4159
|
if (!h || index < 0 || index >= h.entries.length) return;
|
|
@@ -3681,7 +4162,7 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
3681
4162
|
},
|
|
3682
4163
|
[commit]
|
|
3683
4164
|
);
|
|
3684
|
-
const restore =
|
|
4165
|
+
const restore = useCallback10(
|
|
3685
4166
|
(trackId, state) => {
|
|
3686
4167
|
const entries = Array.isArray(state?.entries) ? [...state.entries] : [];
|
|
3687
4168
|
const raw = typeof state?.cursor === "number" ? state.cursor : entries.length - 1;
|
|
@@ -3690,15 +4171,15 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
3690
4171
|
},
|
|
3691
4172
|
[commit]
|
|
3692
4173
|
);
|
|
3693
|
-
const list =
|
|
4174
|
+
const list = useCallback10(
|
|
3694
4175
|
(trackId) => dataRef.current[trackId] ?? EMPTY,
|
|
3695
4176
|
[]
|
|
3696
4177
|
);
|
|
3697
|
-
const canUndo =
|
|
4178
|
+
const canUndo = useCallback10((trackId) => {
|
|
3698
4179
|
const h = dataRef.current[trackId];
|
|
3699
4180
|
return !!h && h.cursor > 0;
|
|
3700
4181
|
}, []);
|
|
3701
|
-
const clear =
|
|
4182
|
+
const clear = useCallback10(
|
|
3702
4183
|
(trackId) => {
|
|
3703
4184
|
if (dataRef.current[trackId]) {
|
|
3704
4185
|
const next = { ...dataRef.current };
|
|
@@ -3710,18 +4191,18 @@ function useSoundHistory(applySound, opts = {}) {
|
|
|
3710
4191
|
},
|
|
3711
4192
|
[bump]
|
|
3712
4193
|
);
|
|
3713
|
-
const reset =
|
|
4194
|
+
const reset = useCallback10(() => {
|
|
3714
4195
|
dataRef.current = {};
|
|
3715
4196
|
bump();
|
|
3716
4197
|
}, [bump]);
|
|
3717
|
-
return
|
|
4198
|
+
return useMemo6(
|
|
3718
4199
|
() => ({ record, undo, restoreTo, list, canUndo, clear, reset, restore, toggleFavorite }),
|
|
3719
4200
|
[record, undo, restoreTo, list, canUndo, clear, reset, restore, toggleFavorite]
|
|
3720
4201
|
);
|
|
3721
4202
|
}
|
|
3722
4203
|
|
|
3723
4204
|
// src/hooks/useTrackReorder.ts
|
|
3724
|
-
import { useCallback as
|
|
4205
|
+
import { useCallback as useCallback11, useRef as useRef14, useState as useState16 } from "react";
|
|
3725
4206
|
function moveItem(arr, from, to) {
|
|
3726
4207
|
const next = arr.slice();
|
|
3727
4208
|
if (from === to || from < 0 || to < 0 || from >= next.length || to >= next.length) {
|
|
@@ -3738,12 +4219,12 @@ function useTrackReorder({
|
|
|
3738
4219
|
getId,
|
|
3739
4220
|
onError
|
|
3740
4221
|
}) {
|
|
3741
|
-
const [draggingIndex, setDraggingIndex] =
|
|
3742
|
-
const [dragOverIndex, setDragOverIndex] =
|
|
3743
|
-
const fromRef =
|
|
3744
|
-
const itemsRef =
|
|
4222
|
+
const [draggingIndex, setDraggingIndex] = useState16(null);
|
|
4223
|
+
const [dragOverIndex, setDragOverIndex] = useState16(null);
|
|
4224
|
+
const fromRef = useRef14(null);
|
|
4225
|
+
const itemsRef = useRef14(items);
|
|
3745
4226
|
itemsRef.current = items;
|
|
3746
|
-
const dragPropsFor =
|
|
4227
|
+
const dragPropsFor = useCallback11(
|
|
3747
4228
|
(index) => ({
|
|
3748
4229
|
handleProps: {
|
|
3749
4230
|
draggable: true,
|
|
@@ -3805,7 +4286,7 @@ function useTrackReorder({
|
|
|
3805
4286
|
}
|
|
3806
4287
|
|
|
3807
4288
|
// src/constants/sdk-version.ts
|
|
3808
|
-
var PLUGIN_SDK_VERSION = "2.
|
|
4289
|
+
var PLUGIN_SDK_VERSION = "2.28.0";
|
|
3809
4290
|
|
|
3810
4291
|
// src/utils/format-concurrent-tracks.ts
|
|
3811
4292
|
function formatConcurrentTracks(ctx) {
|
|
@@ -3965,6 +4446,8 @@ export {
|
|
|
3965
4446
|
FX_DISPLAY_LABELS,
|
|
3966
4447
|
FX_ENGINE_PLUGIN_NAMES,
|
|
3967
4448
|
FX_PRESET_CONFIGS,
|
|
4449
|
+
FadeModal,
|
|
4450
|
+
FadeTrackRow,
|
|
3968
4451
|
FxToggleBar,
|
|
3969
4452
|
GUTTER_W,
|
|
3970
4453
|
ImportTrackModal,
|
|
@@ -3983,6 +4466,7 @@ export {
|
|
|
3983
4466
|
SamplePackCTACard,
|
|
3984
4467
|
ScrollingWaveform,
|
|
3985
4468
|
SorceryProgressBar,
|
|
4469
|
+
TEXTURAL_ROLES,
|
|
3986
4470
|
TrackDrawer,
|
|
3987
4471
|
TrackMeterStrip,
|
|
3988
4472
|
TrackRow,
|
|
@@ -3990,17 +4474,21 @@ export {
|
|
|
3990
4474
|
WaveformView,
|
|
3991
4475
|
analyzeWavPeak,
|
|
3992
4476
|
asCrossfadeMeta,
|
|
4477
|
+
asFadeMeta,
|
|
3993
4478
|
buildCrossfadeInpaintPrompt,
|
|
3994
4479
|
buildCrossfadeVolumeCurves,
|
|
4480
|
+
buildFadeVolumeCurve,
|
|
3995
4481
|
calculateTimeBasedTarget,
|
|
3996
4482
|
cellToPx,
|
|
3997
4483
|
centerScrollTop,
|
|
3998
4484
|
computePeaks,
|
|
3999
4485
|
dbToSlider,
|
|
4486
|
+
defaultFadeGesture,
|
|
4000
4487
|
drawWaveform,
|
|
4001
4488
|
formatConcurrentTracks,
|
|
4002
4489
|
moveItem,
|
|
4003
4490
|
parseCrossfadePairs,
|
|
4491
|
+
parseFades,
|
|
4004
4492
|
pickTopKWeighted,
|
|
4005
4493
|
pitchToName,
|
|
4006
4494
|
pxToCell,
|