@imperosoft/cris-webui-components 1.1.4 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +296 -3
- package/dist/index.d.ts +296 -3
- package/dist/index.js +877 -43
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +867 -42
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -55,6 +55,7 @@ function isTouchActive() {
|
|
|
55
55
|
|
|
56
56
|
// src/components/CrisButton.tsx
|
|
57
57
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
58
|
+
var MIN_PRESS_VISUAL_MS = 120;
|
|
58
59
|
function CrisButton({
|
|
59
60
|
join,
|
|
60
61
|
joinFeedback,
|
|
@@ -87,6 +88,12 @@ function CrisButton({
|
|
|
87
88
|
children,
|
|
88
89
|
onPress,
|
|
89
90
|
onRelease,
|
|
91
|
+
onTap,
|
|
92
|
+
onLongPress,
|
|
93
|
+
holdMs = 2e3,
|
|
94
|
+
onHoldRepeat,
|
|
95
|
+
repeatMs = 150,
|
|
96
|
+
onHoldProgress,
|
|
90
97
|
debug = false
|
|
91
98
|
}) {
|
|
92
99
|
const debugRef = useRef(debug);
|
|
@@ -104,6 +111,12 @@ function CrisButton({
|
|
|
104
111
|
const touchMovedRef = useRef(false);
|
|
105
112
|
const touchPressTimerRef = useRef(null);
|
|
106
113
|
const touchPressedRef = useRef(false);
|
|
114
|
+
const holdTimerRef = useRef(null);
|
|
115
|
+
const repeatIntervalRef = useRef(null);
|
|
116
|
+
const progressRafRef = useRef(null);
|
|
117
|
+
const pressStartRef = useRef(0);
|
|
118
|
+
const gestureFiredRef = useRef(false);
|
|
119
|
+
const pressVisualTimerRef = useRef(null);
|
|
107
120
|
const feedbackJoin = joinFeedback ?? join;
|
|
108
121
|
const feedback = useDigital(feedbackJoin ?? 0);
|
|
109
122
|
const enabledJoin = useDigital(joinEnable ?? 0);
|
|
@@ -119,6 +132,12 @@ function CrisButton({
|
|
|
119
132
|
setPressed(false);
|
|
120
133
|
touchingRef.current = false;
|
|
121
134
|
touchStartedHereRef.current = false;
|
|
135
|
+
if (pressVisualTimerRef.current) {
|
|
136
|
+
clearTimeout(pressVisualTimerRef.current);
|
|
137
|
+
pressVisualTimerRef.current = null;
|
|
138
|
+
}
|
|
139
|
+
clearGestureTimers();
|
|
140
|
+
onHoldProgress?.(0);
|
|
122
141
|
if (join != null && smartId == null) {
|
|
123
142
|
log("sending release via dSet(false)");
|
|
124
143
|
dSet(join, false);
|
|
@@ -131,6 +150,22 @@ function CrisButton({
|
|
|
131
150
|
clearTimeout(touchPressTimerRef.current);
|
|
132
151
|
touchPressTimerRef.current = null;
|
|
133
152
|
}
|
|
153
|
+
if (holdTimerRef.current) {
|
|
154
|
+
clearTimeout(holdTimerRef.current);
|
|
155
|
+
holdTimerRef.current = null;
|
|
156
|
+
}
|
|
157
|
+
if (repeatIntervalRef.current) {
|
|
158
|
+
clearInterval(repeatIntervalRef.current);
|
|
159
|
+
repeatIntervalRef.current = null;
|
|
160
|
+
}
|
|
161
|
+
if (progressRafRef.current != null) {
|
|
162
|
+
cancelAnimationFrame(progressRafRef.current);
|
|
163
|
+
progressRafRef.current = null;
|
|
164
|
+
}
|
|
165
|
+
if (pressVisualTimerRef.current) {
|
|
166
|
+
clearTimeout(pressVisualTimerRef.current);
|
|
167
|
+
pressVisualTimerRef.current = null;
|
|
168
|
+
}
|
|
134
169
|
if (pressedRef.current && join != null && smartId == null) {
|
|
135
170
|
log("UNMOUNT RELEASE - component unmounting while pressed");
|
|
136
171
|
dSet(join, false);
|
|
@@ -146,6 +181,44 @@ function CrisButton({
|
|
|
146
181
|
} else if (hasControlFeedback && textSelected != null) {
|
|
147
182
|
currentText = textSelected;
|
|
148
183
|
}
|
|
184
|
+
const clearGestureTimers = () => {
|
|
185
|
+
if (holdTimerRef.current) {
|
|
186
|
+
clearTimeout(holdTimerRef.current);
|
|
187
|
+
holdTimerRef.current = null;
|
|
188
|
+
}
|
|
189
|
+
if (repeatIntervalRef.current) {
|
|
190
|
+
clearInterval(repeatIntervalRef.current);
|
|
191
|
+
repeatIntervalRef.current = null;
|
|
192
|
+
}
|
|
193
|
+
if (progressRafRef.current != null) {
|
|
194
|
+
cancelAnimationFrame(progressRafRef.current);
|
|
195
|
+
progressRafRef.current = null;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
const armGestures = () => {
|
|
199
|
+
gestureFiredRef.current = false;
|
|
200
|
+
pressStartRef.current = Date.now();
|
|
201
|
+
if (onLongPress) {
|
|
202
|
+
holdTimerRef.current = setTimeout(() => {
|
|
203
|
+
holdTimerRef.current = null;
|
|
204
|
+
gestureFiredRef.current = true;
|
|
205
|
+
onLongPress();
|
|
206
|
+
}, holdMs);
|
|
207
|
+
}
|
|
208
|
+
if (onHoldProgress) {
|
|
209
|
+
const tick = () => {
|
|
210
|
+
const p = Math.min(1, (Date.now() - pressStartRef.current) / holdMs);
|
|
211
|
+
onHoldProgress(p);
|
|
212
|
+
progressRafRef.current = p < 1 ? requestAnimationFrame(tick) : null;
|
|
213
|
+
};
|
|
214
|
+
progressRafRef.current = requestAnimationFrame(tick);
|
|
215
|
+
}
|
|
216
|
+
if (onHoldRepeat) {
|
|
217
|
+
repeatIntervalRef.current = setInterval(() => {
|
|
218
|
+
onHoldRepeat();
|
|
219
|
+
}, repeatMs);
|
|
220
|
+
}
|
|
221
|
+
};
|
|
149
222
|
const handlePress = () => {
|
|
150
223
|
log("handlePress called", { suppressKeyClicks, pressedRef: pressedRef.current, isEnabled });
|
|
151
224
|
if (suppressKeyClicks) {
|
|
@@ -158,18 +231,24 @@ function CrisButton({
|
|
|
158
231
|
}
|
|
159
232
|
pressedRef.current = true;
|
|
160
233
|
setPressed(true);
|
|
234
|
+
pressStartRef.current = Date.now();
|
|
235
|
+
if (pressVisualTimerRef.current) {
|
|
236
|
+
clearTimeout(pressVisualTimerRef.current);
|
|
237
|
+
pressVisualTimerRef.current = null;
|
|
238
|
+
}
|
|
161
239
|
if (!isEnabled) {
|
|
162
240
|
log("SKIPPED dSet: not enabled");
|
|
163
241
|
return;
|
|
164
242
|
}
|
|
165
243
|
onPress?.();
|
|
244
|
+
armGestures();
|
|
166
245
|
if (join != null && smartId == null) {
|
|
167
246
|
log("SENDING PRESS via dSet(true)");
|
|
168
247
|
dSet(join, true);
|
|
169
248
|
}
|
|
170
249
|
};
|
|
171
|
-
const handleRelease = () => {
|
|
172
|
-
log("handleRelease called", { suppressKeyClicks, pressedRef: pressedRef.current, isEnabled });
|
|
250
|
+
const handleRelease = (clean = true) => {
|
|
251
|
+
log("handleRelease called", { clean, suppressKeyClicks, pressedRef: pressedRef.current, isEnabled });
|
|
173
252
|
if (suppressKeyClicks) {
|
|
174
253
|
log("BLOCKED: suppressKeyClicks");
|
|
175
254
|
return;
|
|
@@ -179,7 +258,17 @@ function CrisButton({
|
|
|
179
258
|
return;
|
|
180
259
|
}
|
|
181
260
|
pressedRef.current = false;
|
|
182
|
-
|
|
261
|
+
const heldMs = Date.now() - pressStartRef.current;
|
|
262
|
+
if (heldMs >= MIN_PRESS_VISUAL_MS) {
|
|
263
|
+
setPressed(false);
|
|
264
|
+
} else {
|
|
265
|
+
pressVisualTimerRef.current = setTimeout(() => {
|
|
266
|
+
pressVisualTimerRef.current = null;
|
|
267
|
+
setPressed(false);
|
|
268
|
+
}, MIN_PRESS_VISUAL_MS - heldMs);
|
|
269
|
+
}
|
|
270
|
+
clearGestureTimers();
|
|
271
|
+
onHoldProgress?.(0);
|
|
183
272
|
if (!isEnabled) {
|
|
184
273
|
log("SKIPPED dSet: not enabled");
|
|
185
274
|
return;
|
|
@@ -189,6 +278,9 @@ function CrisButton({
|
|
|
189
278
|
log("SENDING RELEASE via dSet(false)");
|
|
190
279
|
dSet(join, false);
|
|
191
280
|
}
|
|
281
|
+
if (clean && onTap && !gestureFiredRef.current) {
|
|
282
|
+
onTap();
|
|
283
|
+
}
|
|
192
284
|
};
|
|
193
285
|
const SCROLL_THRESHOLD = 8;
|
|
194
286
|
const PRESS_DELAY = 80;
|
|
@@ -224,7 +316,7 @@ function CrisButton({
|
|
|
224
316
|
cancelPressTimer();
|
|
225
317
|
if (touchPressedRef.current) {
|
|
226
318
|
touchPressedRef.current = false;
|
|
227
|
-
handleRelease();
|
|
319
|
+
handleRelease(false);
|
|
228
320
|
log("touchMove: scroll detected after press, releasing");
|
|
229
321
|
} else {
|
|
230
322
|
log("touchMove: scroll detected, press cancelled");
|
|
@@ -245,11 +337,11 @@ function CrisButton({
|
|
|
245
337
|
touchStartedHereRef.current = false;
|
|
246
338
|
if (touchPressedRef.current) {
|
|
247
339
|
touchPressedRef.current = false;
|
|
248
|
-
handleRelease();
|
|
340
|
+
handleRelease(true);
|
|
249
341
|
} else {
|
|
250
342
|
touchPressedRef.current = false;
|
|
251
343
|
handlePress();
|
|
252
|
-
handleRelease();
|
|
344
|
+
handleRelease(true);
|
|
253
345
|
}
|
|
254
346
|
};
|
|
255
347
|
const handleTouchCancel = () => {
|
|
@@ -260,7 +352,7 @@ function CrisButton({
|
|
|
260
352
|
touchStartedHereRef.current = false;
|
|
261
353
|
if (touchPressedRef.current) {
|
|
262
354
|
touchPressedRef.current = false;
|
|
263
|
-
handleRelease();
|
|
355
|
+
handleRelease(false);
|
|
264
356
|
}
|
|
265
357
|
touchMovedRef.current = false;
|
|
266
358
|
};
|
|
@@ -270,11 +362,11 @@ function CrisButton({
|
|
|
270
362
|
};
|
|
271
363
|
const handleMouseUp = () => {
|
|
272
364
|
if (isTouchActive() || touchingRef.current) return;
|
|
273
|
-
handleRelease();
|
|
365
|
+
handleRelease(true);
|
|
274
366
|
};
|
|
275
367
|
const handleMouseLeave = () => {
|
|
276
368
|
if (isTouchActive() || touchingRef.current) return;
|
|
277
|
-
handleRelease();
|
|
369
|
+
handleRelease(false);
|
|
278
370
|
};
|
|
279
371
|
if (!isVisible) return null;
|
|
280
372
|
const classes = [
|
|
@@ -450,6 +542,10 @@ function CrisSlider({
|
|
|
450
542
|
trackSizePercent = 20,
|
|
451
543
|
thumbSizePercent = 4,
|
|
452
544
|
delayMsAfterDragUpdateFeedback = 1e3,
|
|
545
|
+
value,
|
|
546
|
+
onChange,
|
|
547
|
+
onCommit,
|
|
548
|
+
changeThrottleMs = 100,
|
|
453
549
|
className = "",
|
|
454
550
|
style,
|
|
455
551
|
barClassName = "",
|
|
@@ -469,15 +565,27 @@ function CrisSlider({
|
|
|
469
565
|
const aSet = useJoinsStore2((state) => state.aSet);
|
|
470
566
|
const [ratioCurrent, setRatioCurrent] = useState2(0);
|
|
471
567
|
const [isDragging, setIsDragging] = useState2(false);
|
|
568
|
+
const draggingRef = useRef2(false);
|
|
472
569
|
const isDraggingOrJustAfterRef = useRef2(false);
|
|
473
570
|
const ratioBeforeDragRef = useRef2(0);
|
|
474
571
|
const afterDragTimeoutRef = useRef2(null);
|
|
475
572
|
const touchingRef = useRef2(false);
|
|
573
|
+
const changeTimerRef = useRef2(null);
|
|
574
|
+
const lastChangeAtRef = useRef2(0);
|
|
575
|
+
const latestValueRef = useRef2(0);
|
|
576
|
+
const touchStartXRef = useRef2(0);
|
|
577
|
+
const touchStartYRef = useRef2(0);
|
|
578
|
+
const touchDecidedRef = useRef2(false);
|
|
579
|
+
const valueRef = useRef2(value);
|
|
580
|
+
valueRef.current = value;
|
|
581
|
+
const analogValueRef = useRef2(analogValue);
|
|
582
|
+
analogValueRef.current = analogValue;
|
|
476
583
|
const isEnabled = joinEnable == null ? true : enabled;
|
|
477
584
|
const isVisible = joinVisible == null ? true : visible;
|
|
478
585
|
useEffect2(() => {
|
|
479
586
|
if (!isVisible && isDraggingOrJustAfterRef.current) {
|
|
480
587
|
setIsDragging(false);
|
|
588
|
+
draggingRef.current = false;
|
|
481
589
|
isDraggingOrJustAfterRef.current = false;
|
|
482
590
|
touchingRef.current = false;
|
|
483
591
|
if (effectiveDigitalJoin != null) {
|
|
@@ -487,16 +595,24 @@ function CrisSlider({
|
|
|
487
595
|
}, [isVisible, effectiveDigitalJoin, dSet]);
|
|
488
596
|
useEffect2(() => {
|
|
489
597
|
return () => {
|
|
598
|
+
if (changeTimerRef.current != null) {
|
|
599
|
+
clearTimeout(changeTimerRef.current);
|
|
600
|
+
changeTimerRef.current = null;
|
|
601
|
+
}
|
|
602
|
+
if (afterDragTimeoutRef.current !== null) {
|
|
603
|
+
window.clearTimeout(afterDragTimeoutRef.current);
|
|
604
|
+
afterDragTimeoutRef.current = null;
|
|
605
|
+
}
|
|
490
606
|
if (isDraggingOrJustAfterRef.current && effectiveDigitalJoin != null) {
|
|
491
607
|
dSet(effectiveDigitalJoin, false);
|
|
492
608
|
}
|
|
493
609
|
};
|
|
494
610
|
}, [effectiveDigitalJoin, dSet]);
|
|
495
611
|
const analogToRatio = useCallback2(
|
|
496
|
-
(
|
|
612
|
+
(value2) => {
|
|
497
613
|
const range = maxValue - minValue;
|
|
498
614
|
if (range <= 0) return 0;
|
|
499
|
-
return Math.max(0, Math.min(1, (
|
|
615
|
+
return Math.max(0, Math.min(1, (value2 - minValue) / range));
|
|
500
616
|
},
|
|
501
617
|
[minValue, maxValue]
|
|
502
618
|
);
|
|
@@ -508,16 +624,17 @@ function CrisSlider({
|
|
|
508
624
|
[minValue, maxValue]
|
|
509
625
|
);
|
|
510
626
|
const updateFromFeedback = useCallback2(() => {
|
|
511
|
-
if (
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
}, [
|
|
627
|
+
if (isDraggingOrJustAfterRef.current) return;
|
|
628
|
+
const v = valueRef.current !== void 0 ? valueRef.current : effectiveAnalogJoin != null ? analogValueRef.current : void 0;
|
|
629
|
+
if (v !== void 0) setRatioCurrent(analogToRatio(v));
|
|
630
|
+
}, [analogToRatio, effectiveAnalogJoin]);
|
|
515
631
|
useEffect2(() => {
|
|
516
632
|
updateFromFeedback();
|
|
517
|
-
}, [updateFromFeedback]);
|
|
633
|
+
}, [value, analogValue, updateFromFeedback]);
|
|
518
634
|
const handleDragStart = useCallback2(() => {
|
|
519
635
|
if (!isEnabled) return;
|
|
520
636
|
setIsDragging(true);
|
|
637
|
+
draggingRef.current = true;
|
|
521
638
|
if (effectiveDigitalJoin != null) {
|
|
522
639
|
dSet(effectiveDigitalJoin, true);
|
|
523
640
|
}
|
|
@@ -531,11 +648,17 @@ function CrisSlider({
|
|
|
531
648
|
}
|
|
532
649
|
}, [isEnabled, effectiveDigitalJoin, dSet, ratioCurrent]);
|
|
533
650
|
const handleDragEnd = useCallback2(() => {
|
|
534
|
-
if (!
|
|
651
|
+
if (!draggingRef.current) return;
|
|
652
|
+
draggingRef.current = false;
|
|
535
653
|
setIsDragging(false);
|
|
536
654
|
if (effectiveDigitalJoin != null) {
|
|
537
655
|
dSet(effectiveDigitalJoin, false);
|
|
538
656
|
}
|
|
657
|
+
if (changeTimerRef.current != null) {
|
|
658
|
+
clearTimeout(changeTimerRef.current);
|
|
659
|
+
changeTimerRef.current = null;
|
|
660
|
+
}
|
|
661
|
+
onCommit?.(latestValueRef.current);
|
|
539
662
|
if (delayMsAfterDragUpdateFeedback > 0) {
|
|
540
663
|
afterDragTimeoutRef.current = window.setTimeout(() => {
|
|
541
664
|
isDraggingOrJustAfterRef.current = false;
|
|
@@ -546,10 +669,10 @@ function CrisSlider({
|
|
|
546
669
|
isDraggingOrJustAfterRef.current = false;
|
|
547
670
|
updateFromFeedback();
|
|
548
671
|
}
|
|
549
|
-
}, [
|
|
672
|
+
}, [effectiveDigitalJoin, dSet, delayMsAfterDragUpdateFeedback, updateFromFeedback, onCommit]);
|
|
550
673
|
const handleMove = useCallback2(
|
|
551
674
|
(clientX, clientY, bounds) => {
|
|
552
|
-
if (!
|
|
675
|
+
if (!draggingRef.current) return;
|
|
553
676
|
let newRatio;
|
|
554
677
|
if (horizontal) {
|
|
555
678
|
newRatio = (clientX - bounds.left) / bounds.width;
|
|
@@ -558,11 +681,31 @@ function CrisSlider({
|
|
|
558
681
|
}
|
|
559
682
|
newRatio = Math.max(0, Math.min(1, newRatio));
|
|
560
683
|
setRatioCurrent(newRatio);
|
|
684
|
+
const outValue = ratioToAnalog(newRatio);
|
|
685
|
+
latestValueRef.current = outValue;
|
|
561
686
|
if (effectiveAnalogJoin != null) {
|
|
562
|
-
aSet(effectiveAnalogJoin,
|
|
687
|
+
aSet(effectiveAnalogJoin, outValue);
|
|
688
|
+
}
|
|
689
|
+
if (onChange) {
|
|
690
|
+
const now = Date.now();
|
|
691
|
+
const wait = changeThrottleMs - (now - lastChangeAtRef.current);
|
|
692
|
+
if (wait <= 0) {
|
|
693
|
+
if (changeTimerRef.current != null) {
|
|
694
|
+
clearTimeout(changeTimerRef.current);
|
|
695
|
+
changeTimerRef.current = null;
|
|
696
|
+
}
|
|
697
|
+
lastChangeAtRef.current = now;
|
|
698
|
+
onChange(outValue);
|
|
699
|
+
} else if (changeTimerRef.current == null) {
|
|
700
|
+
changeTimerRef.current = window.setTimeout(() => {
|
|
701
|
+
changeTimerRef.current = null;
|
|
702
|
+
lastChangeAtRef.current = Date.now();
|
|
703
|
+
onChange(latestValueRef.current);
|
|
704
|
+
}, wait);
|
|
705
|
+
}
|
|
563
706
|
}
|
|
564
707
|
},
|
|
565
|
-
[
|
|
708
|
+
[horizontal, effectiveAnalogJoin, aSet, ratioToAnalog, onChange, changeThrottleMs]
|
|
566
709
|
);
|
|
567
710
|
const handleMouseDown = (event) => {
|
|
568
711
|
if (isTouchActive() || touchingRef.current) return;
|
|
@@ -572,7 +715,7 @@ function CrisSlider({
|
|
|
572
715
|
handleMove(event.clientX, event.clientY, bounds);
|
|
573
716
|
};
|
|
574
717
|
const handleMouseMove = (event) => {
|
|
575
|
-
if (!
|
|
718
|
+
if (!draggingRef.current) return;
|
|
576
719
|
const bounds = event.currentTarget.getBoundingClientRect();
|
|
577
720
|
handleMove(event.clientX, event.clientY, bounds);
|
|
578
721
|
};
|
|
@@ -580,38 +723,58 @@ function CrisSlider({
|
|
|
580
723
|
handleDragEnd();
|
|
581
724
|
};
|
|
582
725
|
const handleMouseLeave = (event) => {
|
|
583
|
-
if (
|
|
726
|
+
if (draggingRef.current) {
|
|
584
727
|
const bounds = event.currentTarget.getBoundingClientRect();
|
|
585
728
|
handleMove(event.clientX, event.clientY, bounds);
|
|
586
729
|
}
|
|
587
730
|
handleDragEnd();
|
|
588
731
|
};
|
|
732
|
+
const TOUCH_SLOP = 8;
|
|
589
733
|
const handleTouchStart = (event) => {
|
|
590
734
|
touchStart();
|
|
591
735
|
touchingRef.current = true;
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
736
|
+
const t = event.touches[0];
|
|
737
|
+
touchStartXRef.current = t.clientX;
|
|
738
|
+
touchStartYRef.current = t.clientY;
|
|
739
|
+
touchDecidedRef.current = false;
|
|
596
740
|
};
|
|
597
741
|
const handleTouchMove = (event) => {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
742
|
+
const t = event.touches[0];
|
|
743
|
+
if (!t) return;
|
|
744
|
+
if (draggingRef.current) {
|
|
745
|
+
handleMove(t.clientX, t.clientY, event.currentTarget.getBoundingClientRect());
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
if (touchDecidedRef.current) return;
|
|
749
|
+
const dx = Math.abs(t.clientX - touchStartXRef.current);
|
|
750
|
+
const dy = Math.abs(t.clientY - touchStartYRef.current);
|
|
751
|
+
if (dx < TOUCH_SLOP && dy < TOUCH_SLOP) return;
|
|
752
|
+
touchDecidedRef.current = true;
|
|
753
|
+
const along = horizontal ? dx : dy;
|
|
754
|
+
const cross = horizontal ? dy : dx;
|
|
755
|
+
if (along >= cross) {
|
|
756
|
+
handleDragStart();
|
|
757
|
+
handleMove(t.clientX, t.clientY, event.currentTarget.getBoundingClientRect());
|
|
758
|
+
}
|
|
602
759
|
};
|
|
603
|
-
const handleTouchEnd = () => {
|
|
760
|
+
const handleTouchEnd = (event) => {
|
|
604
761
|
touchEnd();
|
|
605
|
-
|
|
762
|
+
if (draggingRef.current) {
|
|
763
|
+
handleDragEnd();
|
|
764
|
+
} else if (!touchDecidedRef.current) {
|
|
765
|
+
const t = event.changedTouches[0];
|
|
766
|
+
if (t) {
|
|
767
|
+
handleDragStart();
|
|
768
|
+
handleMove(t.clientX, t.clientY, event.currentTarget.getBoundingClientRect());
|
|
769
|
+
handleDragEnd();
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
touchDecidedRef.current = false;
|
|
606
773
|
};
|
|
607
|
-
const handleTouchCancel = (
|
|
774
|
+
const handleTouchCancel = () => {
|
|
608
775
|
touchEnd();
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
const touch = event.touches[0];
|
|
612
|
-
handleMove(touch.clientX, touch.clientY, bounds);
|
|
613
|
-
}
|
|
614
|
-
handleDragEnd();
|
|
776
|
+
touchDecidedRef.current = false;
|
|
777
|
+
if (draggingRef.current) handleDragEnd();
|
|
615
778
|
};
|
|
616
779
|
if (!isVisible) return null;
|
|
617
780
|
const containerClasses = [
|
|
@@ -639,8 +802,10 @@ function CrisSlider({
|
|
|
639
802
|
computedBarStyle.height = `${height}%`;
|
|
640
803
|
computedBarStyle.top = `${top}%`;
|
|
641
804
|
}
|
|
805
|
+
const dragLayerStyle = isDragging ? { willChange: "transform" } : {};
|
|
642
806
|
const computedFillStyle = {
|
|
643
807
|
...fillStyle,
|
|
808
|
+
...dragLayerStyle,
|
|
644
809
|
position: "absolute"
|
|
645
810
|
};
|
|
646
811
|
if (horizontal) {
|
|
@@ -662,6 +827,7 @@ function CrisSlider({
|
|
|
662
827
|
}
|
|
663
828
|
const computedThumbStyle = {
|
|
664
829
|
...thumbStyle,
|
|
830
|
+
...dragLayerStyle,
|
|
665
831
|
position: "absolute"
|
|
666
832
|
};
|
|
667
833
|
if (horizontal) {
|
|
@@ -685,7 +851,9 @@ function CrisSlider({
|
|
|
685
851
|
...style,
|
|
686
852
|
position: "relative",
|
|
687
853
|
cursor: isEnabled ? "pointer" : "default",
|
|
688
|
-
|
|
854
|
+
// Let the browser scroll the cross-axis (so a horizontal swipe over a vertical
|
|
855
|
+
// fader scrolls the row); the slider handles drags along its own axis.
|
|
856
|
+
touchAction: horizontal ? "pan-y" : "pan-x",
|
|
689
857
|
userSelect: "none"
|
|
690
858
|
},
|
|
691
859
|
onMouseDown: handleMouseDown,
|
|
@@ -2120,6 +2288,654 @@ function CrisCoMatrixListsTie({
|
|
|
2120
2288
|
}
|
|
2121
2289
|
);
|
|
2122
2290
|
}
|
|
2291
|
+
|
|
2292
|
+
// src/components/CrisViewComm.tsx
|
|
2293
|
+
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2294
|
+
var ETH = /* @__PURE__ */ new Set(["tcp", "udp", "ssh", "ws"]);
|
|
2295
|
+
function commIndicators(c) {
|
|
2296
|
+
const isEth = !!c?.kd && ETH.has(c.kd);
|
|
2297
|
+
return {
|
|
2298
|
+
eth: { visible: isEth || c?.kd === "native", on: !!c?.co },
|
|
2299
|
+
serial: { visible: c?.kd === "serial" || isEth && !!c?.so, on: !!c?.al }
|
|
2300
|
+
};
|
|
2301
|
+
}
|
|
2302
|
+
var COMM_DEFAULTS = {
|
|
2303
|
+
root: "flex items-center gap-[0.4em]",
|
|
2304
|
+
dot: "w-[2.2em] h-[2.2em] rounded-full flex items-center justify-center",
|
|
2305
|
+
dotOn: "bg-[#22c55e]",
|
|
2306
|
+
dotOff: "bg-[#dc2626]",
|
|
2307
|
+
icon: ""
|
|
2308
|
+
};
|
|
2309
|
+
function isNode(v) {
|
|
2310
|
+
return v !== void 0 && typeof v !== "string";
|
|
2311
|
+
}
|
|
2312
|
+
function IndicatorDot({ icon, on, iconScale, cls }) {
|
|
2313
|
+
const iconStyle = {
|
|
2314
|
+
width: iconScale,
|
|
2315
|
+
height: iconScale,
|
|
2316
|
+
filter: "brightness(0) invert(1)",
|
|
2317
|
+
opacity: on ? 1 : 0.5
|
|
2318
|
+
};
|
|
2319
|
+
return /* @__PURE__ */ jsx11("div", { className: `${cls.dot} ${on ? cls.dotOn : cls.dotOff}`, children: isNode(icon) ? icon : /* @__PURE__ */ jsx11("img", { className: cls.icon, src: getIconUrl(icon), alt: "", draggable: false, style: iconStyle }) });
|
|
2320
|
+
}
|
|
2321
|
+
function CrisViewComm({ comm, classes, icons, className }) {
|
|
2322
|
+
const cls = {
|
|
2323
|
+
root: classes?.root ?? COMM_DEFAULTS.root,
|
|
2324
|
+
dot: classes?.dot ?? COMM_DEFAULTS.dot,
|
|
2325
|
+
dotOn: classes?.dotOn ?? COMM_DEFAULTS.dotOn,
|
|
2326
|
+
dotOff: classes?.dotOff ?? COMM_DEFAULTS.dotOff,
|
|
2327
|
+
icon: classes?.icon ?? COMM_DEFAULTS.icon
|
|
2328
|
+
};
|
|
2329
|
+
const ethIcon = icons?.ethernet ?? "ind-ethernet";
|
|
2330
|
+
const serialIcon = icons?.serial ?? "rs232";
|
|
2331
|
+
const { eth, serial } = commIndicators(comm);
|
|
2332
|
+
if (!eth.visible && !serial.visible) return null;
|
|
2333
|
+
return /* @__PURE__ */ jsxs9("div", { className: `${cls.root} ${className ?? ""}`, children: [
|
|
2334
|
+
eth.visible && /* @__PURE__ */ jsx11(IndicatorDot, { icon: ethIcon, on: eth.on, iconScale: "85%", cls }),
|
|
2335
|
+
serial.visible && /* @__PURE__ */ jsx11(IndicatorDot, { icon: serialIcon, on: serial.on, iconScale: "95%", cls })
|
|
2336
|
+
] });
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
// src/components/dsp/CrisViewDspFull.tsx
|
|
2340
|
+
import { useState as useState6 } from "react";
|
|
2341
|
+
import { useCustomObject as useCustomObject4, useCustomObjectSend as useCustomObjectSend4 } from "@imperosoft/cris-webui-ch5-core";
|
|
2342
|
+
|
|
2343
|
+
// src/components/dsp/DspIoPage.tsx
|
|
2344
|
+
import { useRef as useRef4 } from "react";
|
|
2345
|
+
|
|
2346
|
+
// src/components/dsp/dspModel.ts
|
|
2347
|
+
function levelToPercent(lv) {
|
|
2348
|
+
return Math.round(clampLevel(lv) / 65535 * 100);
|
|
2349
|
+
}
|
|
2350
|
+
function clampLevel(lv) {
|
|
2351
|
+
if (!Number.isFinite(lv)) return 0;
|
|
2352
|
+
return Math.max(0, Math.min(65535, lv));
|
|
2353
|
+
}
|
|
2354
|
+
function normalizeLinked(raw) {
|
|
2355
|
+
return {
|
|
2356
|
+
label: raw.lb ?? "",
|
|
2357
|
+
channels: raw.ch ?? []
|
|
2358
|
+
};
|
|
2359
|
+
}
|
|
2360
|
+
function collapseStrips(channels, links) {
|
|
2361
|
+
const byId = new Map(channels.map((c) => [c.id, c]));
|
|
2362
|
+
const grouped = /* @__PURE__ */ new Set();
|
|
2363
|
+
const groups = [];
|
|
2364
|
+
for (const raw of links ?? []) {
|
|
2365
|
+
const { label, channels: members } = normalizeLinked(raw);
|
|
2366
|
+
if (members.length === 0) continue;
|
|
2367
|
+
members.forEach((id) => grouped.add(id));
|
|
2368
|
+
const primary = members.find((id) => byId.has(id));
|
|
2369
|
+
const ref = primary !== void 0 ? byId.get(primary) : void 0;
|
|
2370
|
+
groups.push({
|
|
2371
|
+
kind: "group",
|
|
2372
|
+
key: `g:${members.join("-")}`,
|
|
2373
|
+
label: label || ref?.lb || `Grupo ${members[0]}`,
|
|
2374
|
+
channels: members,
|
|
2375
|
+
lv: clampLevel(ref?.lv),
|
|
2376
|
+
mt: ref?.mt ?? false
|
|
2377
|
+
});
|
|
2378
|
+
}
|
|
2379
|
+
const singles = channels.filter((c) => !grouped.has(c.id)).map((c) => ({
|
|
2380
|
+
kind: "single",
|
|
2381
|
+
key: `c:${c.id}`,
|
|
2382
|
+
label: c.lb ?? `Canal ${c.id}`,
|
|
2383
|
+
channels: [c.id],
|
|
2384
|
+
lv: clampLevel(c.lv),
|
|
2385
|
+
mt: c.mt
|
|
2386
|
+
}));
|
|
2387
|
+
return [...singles, ...groups];
|
|
2388
|
+
}
|
|
2389
|
+
function linksFor(ln, io) {
|
|
2390
|
+
return io === "in" ? ln?.ip : ln?.op;
|
|
2391
|
+
}
|
|
2392
|
+
function buildMixerAxis(channels, links) {
|
|
2393
|
+
const items = channels.map((c) => ({
|
|
2394
|
+
id: c.id,
|
|
2395
|
+
channelLabel: c.lb ?? `${c.id}`
|
|
2396
|
+
}));
|
|
2397
|
+
const indexById = new Map(channels.map((c, idx) => [c.id, idx]));
|
|
2398
|
+
for (const raw of links ?? []) {
|
|
2399
|
+
const { label, channels: members } = normalizeLinked(raw);
|
|
2400
|
+
if (members.length < 2) continue;
|
|
2401
|
+
const sorted = [...members].sort((a, b) => a - b);
|
|
2402
|
+
const consecutiveNumbers = sorted.every((n, k) => k === 0 || n === sorted[k - 1] + 1);
|
|
2403
|
+
if (!consecutiveNumbers) continue;
|
|
2404
|
+
const idxs = sorted.map((n) => indexById.get(n));
|
|
2405
|
+
if (idxs.some((x) => x === void 0)) continue;
|
|
2406
|
+
const indices = idxs;
|
|
2407
|
+
const consecutiveIdx = indices.every((x, k) => k === 0 || x === indices[k - 1] + 1);
|
|
2408
|
+
if (!consecutiveIdx) continue;
|
|
2409
|
+
const startIdx = indices[0];
|
|
2410
|
+
items[startIdx].groupStart = true;
|
|
2411
|
+
items[startIdx].groupSpan = indices.length;
|
|
2412
|
+
for (const idx of indices) items[idx].groupCommon = label;
|
|
2413
|
+
}
|
|
2414
|
+
return items;
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
// src/components/dsp/DspChannelStrip.tsx
|
|
2418
|
+
import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2419
|
+
function DspChannelStrip({ strip, io, oid, send, cls, icons }) {
|
|
2420
|
+
const primary = strip.channels[0];
|
|
2421
|
+
const sendLevel = (channel, value, queued) => send(oid, { action: "level.set", io, channel, value, queued });
|
|
2422
|
+
const streamLevel = (value) => {
|
|
2423
|
+
if (primary !== void 0) sendLevel(primary, value, false);
|
|
2424
|
+
};
|
|
2425
|
+
const commitLevel = (value) => {
|
|
2426
|
+
strip.channels.forEach((ch) => sendLevel(ch, value, true));
|
|
2427
|
+
};
|
|
2428
|
+
const toggleMute = () => {
|
|
2429
|
+
const value = !strip.mt;
|
|
2430
|
+
strip.channels.forEach(
|
|
2431
|
+
(channel) => send(oid, { action: "mute.set", io, channel, value, queued: false })
|
|
2432
|
+
);
|
|
2433
|
+
};
|
|
2434
|
+
return /* @__PURE__ */ jsxs10("div", { className: cls.strip, style: { minWidth: 0 }, children: [
|
|
2435
|
+
/* @__PURE__ */ jsx12("div", { className: "w-full h-[4.1em] flex items-center justify-center", children: /* @__PURE__ */ jsx12("span", { className: cls.stripLabel, title: strip.label, children: strip.label }) }),
|
|
2436
|
+
/* @__PURE__ */ jsxs10("span", { className: cls.levelReadout, children: [
|
|
2437
|
+
levelToPercent(strip.lv),
|
|
2438
|
+
"%"
|
|
2439
|
+
] }),
|
|
2440
|
+
/* @__PURE__ */ jsx12(
|
|
2441
|
+
CrisSlider,
|
|
2442
|
+
{
|
|
2443
|
+
value: strip.lv,
|
|
2444
|
+
onChange: streamLevel,
|
|
2445
|
+
onCommit: commitLevel,
|
|
2446
|
+
minValue: 0,
|
|
2447
|
+
maxValue: 65535,
|
|
2448
|
+
thumbSizePercent: 7,
|
|
2449
|
+
className: "flex-1 w-full",
|
|
2450
|
+
barClassName: cls.faderBar,
|
|
2451
|
+
fillClassName: cls.faderFill,
|
|
2452
|
+
thumbClassName: cls.faderThumb
|
|
2453
|
+
}
|
|
2454
|
+
),
|
|
2455
|
+
/* @__PURE__ */ jsx12("div", { className: "w-full h-[3.8em] shrink-0 mt-[0.6em]", children: /* @__PURE__ */ jsx12(
|
|
2456
|
+
CrisButton,
|
|
2457
|
+
{
|
|
2458
|
+
selected: strip.mt,
|
|
2459
|
+
onPress: toggleMute,
|
|
2460
|
+
iconName: icons.muteOff,
|
|
2461
|
+
iconNameActive: icons.muteOn,
|
|
2462
|
+
iconSize: "3.6em",
|
|
2463
|
+
iconStyle: { filter: icons.muteOffFilter },
|
|
2464
|
+
iconStyleActive: { filter: icons.muteOnFilter },
|
|
2465
|
+
className: cls.mute,
|
|
2466
|
+
classActive: cls.muteActive
|
|
2467
|
+
}
|
|
2468
|
+
) })
|
|
2469
|
+
] });
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
// src/components/dsp/useHorizontalWheel.ts
|
|
2473
|
+
import { useEffect as useEffect5 } from "react";
|
|
2474
|
+
function useHorizontalWheel(ref) {
|
|
2475
|
+
useEffect5(() => {
|
|
2476
|
+
const el = ref.current;
|
|
2477
|
+
if (!el) return;
|
|
2478
|
+
const onWheel = (e) => {
|
|
2479
|
+
if (e.deltaY === 0) return;
|
|
2480
|
+
if (el.scrollWidth <= el.clientWidth) return;
|
|
2481
|
+
e.preventDefault();
|
|
2482
|
+
el.scrollLeft += e.deltaY;
|
|
2483
|
+
};
|
|
2484
|
+
el.addEventListener("wheel", onWheel, { passive: false });
|
|
2485
|
+
return () => el.removeEventListener("wheel", onWheel);
|
|
2486
|
+
}, [ref]);
|
|
2487
|
+
}
|
|
2488
|
+
|
|
2489
|
+
// src/components/dsp/DspIoPage.tsx
|
|
2490
|
+
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
2491
|
+
function DspIoPage({ status, io, oid, send, skip, cls, icons, emptyLabel }) {
|
|
2492
|
+
const scrollRef = useRef4(null);
|
|
2493
|
+
useHorizontalWheel(scrollRef);
|
|
2494
|
+
const skipSet = new Set(skip ?? []);
|
|
2495
|
+
const raw = (io === "in" ? status?.ip : status?.op) ?? [];
|
|
2496
|
+
const channels = skipSet.size ? raw.filter((c) => !skipSet.has(c.id)) : raw;
|
|
2497
|
+
const allLinks = linksFor(status?.ln, io);
|
|
2498
|
+
const links = skipSet.size ? allLinks?.filter((g) => (g.ch ?? []).some((id) => !skipSet.has(id))) : allLinks;
|
|
2499
|
+
const strips = collapseStrips(channels, links);
|
|
2500
|
+
if (strips.length === 0) {
|
|
2501
|
+
return /* @__PURE__ */ jsx13("div", { className: cls.message, children: emptyLabel });
|
|
2502
|
+
}
|
|
2503
|
+
return /* @__PURE__ */ jsx13("div", { ref: scrollRef, className: "w-full h-full overflow-x-auto no-scrollbar", children: /* @__PURE__ */ jsx13(
|
|
2504
|
+
"div",
|
|
2505
|
+
{
|
|
2506
|
+
className: "flex h-full items-stretch gap-[0.3em] px-[0.5em] py-[0.5em]",
|
|
2507
|
+
style: { minWidth: "min-content" },
|
|
2508
|
+
children: strips.map((strip) => /* @__PURE__ */ jsx13(
|
|
2509
|
+
DspChannelStrip,
|
|
2510
|
+
{
|
|
2511
|
+
strip,
|
|
2512
|
+
io,
|
|
2513
|
+
oid,
|
|
2514
|
+
send,
|
|
2515
|
+
cls,
|
|
2516
|
+
icons
|
|
2517
|
+
},
|
|
2518
|
+
strip.key
|
|
2519
|
+
))
|
|
2520
|
+
}
|
|
2521
|
+
) });
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
// src/components/dsp/DspMixer.tsx
|
|
2525
|
+
import { useRef as useRef5 } from "react";
|
|
2526
|
+
import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2527
|
+
var COMMON_W = "9em";
|
|
2528
|
+
var CHAN_W = "3em";
|
|
2529
|
+
var COMMON_H = "2.4em";
|
|
2530
|
+
var CHAN_H = "2.1em";
|
|
2531
|
+
var CELL_W = "7.5em";
|
|
2532
|
+
var ROW_H = "4.5em";
|
|
2533
|
+
var DRAG_FACTOR = 0.6;
|
|
2534
|
+
function CrosspointOnIcon({ icon }) {
|
|
2535
|
+
if (typeof icon !== "string") return /* @__PURE__ */ jsx14(Fragment3, { children: icon });
|
|
2536
|
+
return /* @__PURE__ */ jsx14(
|
|
2537
|
+
"img",
|
|
2538
|
+
{
|
|
2539
|
+
src: getIconUrl(icon),
|
|
2540
|
+
alt: "",
|
|
2541
|
+
draggable: false,
|
|
2542
|
+
className: "pointer-events-none",
|
|
2543
|
+
style: { width: "4.4em", height: "4.4em", filter: "brightness(0) invert(1)" }
|
|
2544
|
+
}
|
|
2545
|
+
);
|
|
2546
|
+
}
|
|
2547
|
+
function DspMixer({ status, oid, send, cls, icons }) {
|
|
2548
|
+
const scrollRef = useRef5(null);
|
|
2549
|
+
const startRef = useRef5(null);
|
|
2550
|
+
const movedRef = useRef5(false);
|
|
2551
|
+
const thresholdRef = useRef5(8);
|
|
2552
|
+
const pendingRef = useRef5(null);
|
|
2553
|
+
const inputs = status?.ip ?? [];
|
|
2554
|
+
const outputs = status?.op ?? [];
|
|
2555
|
+
const isOn = (output, input) => outputs.find((o) => o.id === output)?.xp?.[input] === true;
|
|
2556
|
+
const onPointerDown = (e) => {
|
|
2557
|
+
const el = scrollRef.current;
|
|
2558
|
+
if (!el) return;
|
|
2559
|
+
movedRef.current = false;
|
|
2560
|
+
thresholdRef.current = parseFloat(getComputedStyle(el).fontSize || "16") * DRAG_FACTOR;
|
|
2561
|
+
startRef.current = { x: e.clientX, y: e.clientY, sl: el.scrollLeft, st: el.scrollTop };
|
|
2562
|
+
const cell = e.target.closest("[data-cell]");
|
|
2563
|
+
pendingRef.current = cell ? { input: Number(cell.dataset.in), output: Number(cell.dataset.out) } : null;
|
|
2564
|
+
el.setPointerCapture(e.pointerId);
|
|
2565
|
+
};
|
|
2566
|
+
const onPointerMove = (e) => {
|
|
2567
|
+
const el = scrollRef.current;
|
|
2568
|
+
const s = startRef.current;
|
|
2569
|
+
if (!el || !s) return;
|
|
2570
|
+
const dx = e.clientX - s.x;
|
|
2571
|
+
const dy = e.clientY - s.y;
|
|
2572
|
+
if (!movedRef.current && Math.hypot(dx, dy) > thresholdRef.current) movedRef.current = true;
|
|
2573
|
+
if (movedRef.current) {
|
|
2574
|
+
el.scrollLeft = s.sl - dx;
|
|
2575
|
+
el.scrollTop = s.st - dy;
|
|
2576
|
+
}
|
|
2577
|
+
};
|
|
2578
|
+
const onPointerUp = (e) => {
|
|
2579
|
+
const el = scrollRef.current;
|
|
2580
|
+
el?.releasePointerCapture?.(e.pointerId);
|
|
2581
|
+
if (!movedRef.current && pendingRef.current) {
|
|
2582
|
+
const { input, output } = pendingRef.current;
|
|
2583
|
+
send(oid, {
|
|
2584
|
+
action: "crosspoint.set",
|
|
2585
|
+
input,
|
|
2586
|
+
output,
|
|
2587
|
+
value: !isOn(output, input),
|
|
2588
|
+
queued: true
|
|
2589
|
+
});
|
|
2590
|
+
}
|
|
2591
|
+
startRef.current = null;
|
|
2592
|
+
pendingRef.current = null;
|
|
2593
|
+
};
|
|
2594
|
+
if (inputs.length === 0 || outputs.length === 0) {
|
|
2595
|
+
return /* @__PURE__ */ jsx14("div", { className: cls.message, children: "Sin crosspoints" });
|
|
2596
|
+
}
|
|
2597
|
+
const inAxis = buildMixerAxis(inputs, linksFor(status?.ln, "in"));
|
|
2598
|
+
const outAxis = buildMixerAxis(outputs, linksFor(status?.ln, "out"));
|
|
2599
|
+
return /* @__PURE__ */ jsx14(
|
|
2600
|
+
"div",
|
|
2601
|
+
{
|
|
2602
|
+
ref: scrollRef,
|
|
2603
|
+
onPointerDown,
|
|
2604
|
+
onPointerMove,
|
|
2605
|
+
onPointerUp,
|
|
2606
|
+
onPointerCancel: onPointerUp,
|
|
2607
|
+
className: "w-full h-full overflow-auto no-scrollbar relative touch-none select-none cursor-grab",
|
|
2608
|
+
children: /* @__PURE__ */ jsxs11(
|
|
2609
|
+
"div",
|
|
2610
|
+
{
|
|
2611
|
+
className: "grid",
|
|
2612
|
+
style: {
|
|
2613
|
+
// cols: [output common][output channel][inputs…] rows: [in common][in channel][outputs…]
|
|
2614
|
+
gridTemplateColumns: `${COMMON_W} ${CHAN_W} repeat(${inputs.length}, ${CELL_W})`,
|
|
2615
|
+
gridTemplateRows: `${COMMON_H} ${CHAN_H} repeat(${outputs.length}, ${ROW_H})`,
|
|
2616
|
+
minWidth: "min-content"
|
|
2617
|
+
},
|
|
2618
|
+
children: [
|
|
2619
|
+
/* @__PURE__ */ jsx14(
|
|
2620
|
+
"div",
|
|
2621
|
+
{
|
|
2622
|
+
className: cls.mixerCorner,
|
|
2623
|
+
style: { top: 0, left: 0, gridColumn: "1 / span 2", gridRow: "1 / span 2" },
|
|
2624
|
+
children: "OUT \\ IN"
|
|
2625
|
+
}
|
|
2626
|
+
),
|
|
2627
|
+
inAxis.flatMap((it, ii) => {
|
|
2628
|
+
const col = 3 + ii;
|
|
2629
|
+
if (it.groupCommon !== void 0) {
|
|
2630
|
+
const nodes = [
|
|
2631
|
+
/* @__PURE__ */ jsx14(
|
|
2632
|
+
"div",
|
|
2633
|
+
{
|
|
2634
|
+
className: `${cls.mixerChrome} z-20 px-[0.2em]`,
|
|
2635
|
+
style: { top: 0, gridRow: "1 / span 2", gridColumn: col, alignItems: "flex-end", paddingBottom: "0.3em" },
|
|
2636
|
+
children: /* @__PURE__ */ jsx14("span", { className: "line-clamp-1", children: it.channelLabel })
|
|
2637
|
+
},
|
|
2638
|
+
`ich:${it.id}`
|
|
2639
|
+
)
|
|
2640
|
+
];
|
|
2641
|
+
if (it.groupStart) {
|
|
2642
|
+
nodes.unshift(
|
|
2643
|
+
/* @__PURE__ */ jsx14(
|
|
2644
|
+
"div",
|
|
2645
|
+
{
|
|
2646
|
+
className: `${cls.mixerChrome} z-25 px-[0.2em] font-semibold`,
|
|
2647
|
+
style: { top: 0, gridRow: 1, gridColumn: `${col} / span ${it.groupSpan}` },
|
|
2648
|
+
title: it.groupCommon,
|
|
2649
|
+
children: /* @__PURE__ */ jsx14("span", { className: "line-clamp-2", children: it.groupCommon })
|
|
2650
|
+
},
|
|
2651
|
+
`icc:${it.id}`
|
|
2652
|
+
)
|
|
2653
|
+
);
|
|
2654
|
+
}
|
|
2655
|
+
return nodes;
|
|
2656
|
+
}
|
|
2657
|
+
return [
|
|
2658
|
+
/* @__PURE__ */ jsx14(
|
|
2659
|
+
"div",
|
|
2660
|
+
{
|
|
2661
|
+
className: `${cls.mixerChrome} z-20 px-[0.2em]`,
|
|
2662
|
+
style: { top: 0, gridRow: "1 / span 2", gridColumn: col, alignItems: "center" },
|
|
2663
|
+
title: it.channelLabel,
|
|
2664
|
+
children: /* @__PURE__ */ jsx14("span", { className: "line-clamp-3", children: it.channelLabel })
|
|
2665
|
+
},
|
|
2666
|
+
`is:${it.id}`
|
|
2667
|
+
)
|
|
2668
|
+
];
|
|
2669
|
+
}),
|
|
2670
|
+
outAxis.flatMap((it, oo) => {
|
|
2671
|
+
const row = 3 + oo;
|
|
2672
|
+
if (it.groupCommon !== void 0) {
|
|
2673
|
+
const nodes = [
|
|
2674
|
+
/* @__PURE__ */ jsx14(
|
|
2675
|
+
"div",
|
|
2676
|
+
{
|
|
2677
|
+
className: `${cls.mixerChrome} z-20`,
|
|
2678
|
+
style: { left: 0, gridColumn: "1 / span 2", gridRow: row, justifyContent: "flex-end", paddingRight: "0.6em" },
|
|
2679
|
+
children: /* @__PURE__ */ jsx14("span", { className: "line-clamp-1", children: it.channelLabel })
|
|
2680
|
+
},
|
|
2681
|
+
`och:${it.id}`
|
|
2682
|
+
)
|
|
2683
|
+
];
|
|
2684
|
+
if (it.groupStart) {
|
|
2685
|
+
nodes.unshift(
|
|
2686
|
+
/* @__PURE__ */ jsx14(
|
|
2687
|
+
"div",
|
|
2688
|
+
{
|
|
2689
|
+
className: `${cls.mixerChrome} z-25 justify-start px-[0.4em] font-semibold`,
|
|
2690
|
+
style: { left: 0, gridColumn: 1, gridRow: `${row} / span ${it.groupSpan}` },
|
|
2691
|
+
title: it.groupCommon,
|
|
2692
|
+
children: /* @__PURE__ */ jsx14("span", { className: "line-clamp-2 text-left", children: it.groupCommon })
|
|
2693
|
+
},
|
|
2694
|
+
`occ:${it.id}`
|
|
2695
|
+
)
|
|
2696
|
+
);
|
|
2697
|
+
}
|
|
2698
|
+
return nodes;
|
|
2699
|
+
}
|
|
2700
|
+
return [
|
|
2701
|
+
/* @__PURE__ */ jsx14(
|
|
2702
|
+
"div",
|
|
2703
|
+
{
|
|
2704
|
+
className: `${cls.mixerChrome} z-20 justify-start px-[0.4em]`,
|
|
2705
|
+
style: { left: 0, gridColumn: "1 / span 2", gridRow: row },
|
|
2706
|
+
title: it.channelLabel,
|
|
2707
|
+
children: /* @__PURE__ */ jsx14("span", { className: "line-clamp-2 text-left", children: it.channelLabel })
|
|
2708
|
+
},
|
|
2709
|
+
`os:${it.id}`
|
|
2710
|
+
)
|
|
2711
|
+
];
|
|
2712
|
+
}),
|
|
2713
|
+
outputs.map(
|
|
2714
|
+
(o, oo) => inputs.map((i, ii) => {
|
|
2715
|
+
const on = isOn(o.id, i.id);
|
|
2716
|
+
return /* @__PURE__ */ jsx14(
|
|
2717
|
+
"div",
|
|
2718
|
+
{
|
|
2719
|
+
"data-cell": true,
|
|
2720
|
+
"data-in": i.id,
|
|
2721
|
+
"data-out": o.id,
|
|
2722
|
+
className: `${cls.cell} ${on ? cls.cellOn : cls.cellOff}`,
|
|
2723
|
+
style: { gridColumn: 3 + ii, gridRow: 3 + oo },
|
|
2724
|
+
children: on && /* @__PURE__ */ jsx14(CrosspointOnIcon, { icon: icons.crosspointOn })
|
|
2725
|
+
},
|
|
2726
|
+
`x:${i.id}:${o.id}`
|
|
2727
|
+
);
|
|
2728
|
+
})
|
|
2729
|
+
)
|
|
2730
|
+
]
|
|
2731
|
+
}
|
|
2732
|
+
)
|
|
2733
|
+
}
|
|
2734
|
+
);
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2737
|
+
// src/components/dsp/DspPresets.tsx
|
|
2738
|
+
import { useRef as useRef6, useState as useState5 } from "react";
|
|
2739
|
+
import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
2740
|
+
var HOLD_MS = 2e3;
|
|
2741
|
+
var SAVED_MS = 1e3;
|
|
2742
|
+
function PresetButton({ num, name, active, onCall, onSave, cls }) {
|
|
2743
|
+
return /* @__PURE__ */ jsx15(
|
|
2744
|
+
CrisButton,
|
|
2745
|
+
{
|
|
2746
|
+
onTap: onCall,
|
|
2747
|
+
onLongPress: onSave,
|
|
2748
|
+
holdMs: HOLD_MS,
|
|
2749
|
+
selected: active,
|
|
2750
|
+
className: cls.preset,
|
|
2751
|
+
classActive: cls.presetActive,
|
|
2752
|
+
classPressed: cls.presetPressed,
|
|
2753
|
+
children: /* @__PURE__ */ jsxs12("span", { className: "flex items-center justify-center gap-[0.5em]", children: [
|
|
2754
|
+
/* @__PURE__ */ jsx15("span", { className: "text-[1.6em] font-bold leading-none", children: num }),
|
|
2755
|
+
/* @__PURE__ */ jsx15("span", { className: "text-[1.2em] leading-none", children: name })
|
|
2756
|
+
] })
|
|
2757
|
+
}
|
|
2758
|
+
);
|
|
2759
|
+
}
|
|
2760
|
+
function DspPresets({
|
|
2761
|
+
oid,
|
|
2762
|
+
send,
|
|
2763
|
+
presets,
|
|
2764
|
+
activeDevice,
|
|
2765
|
+
activeLocal,
|
|
2766
|
+
sustainedFeedback = true,
|
|
2767
|
+
cls
|
|
2768
|
+
}) {
|
|
2769
|
+
const [saved, setSaved] = useState5(false);
|
|
2770
|
+
const savedTimer = useRef6(null);
|
|
2771
|
+
const call = (id) => send(oid, { action: "preset.call", value: id });
|
|
2772
|
+
const save = (id) => {
|
|
2773
|
+
send(oid, { action: "preset.save", value: id });
|
|
2774
|
+
setSaved(true);
|
|
2775
|
+
if (savedTimer.current !== null) clearTimeout(savedTimer.current);
|
|
2776
|
+
savedTimer.current = window.setTimeout(() => setSaved(false), SAVED_MS);
|
|
2777
|
+
};
|
|
2778
|
+
const isActive = (p) => {
|
|
2779
|
+
if (!sustainedFeedback) return false;
|
|
2780
|
+
return p.kind === "local" ? activeLocal === p.id : activeDevice === p.id;
|
|
2781
|
+
};
|
|
2782
|
+
return /* @__PURE__ */ jsxs12("div", { className: cls.presetWrap, children: [
|
|
2783
|
+
/* @__PURE__ */ jsx15("span", { className: cls.presetLabel, children: "Preset" }),
|
|
2784
|
+
/* @__PURE__ */ jsxs12("div", { className: "relative flex-1 flex items-stretch gap-[0.3em] mt-[0.25em]", children: [
|
|
2785
|
+
presets.map((p) => /* @__PURE__ */ jsx15(
|
|
2786
|
+
PresetButton,
|
|
2787
|
+
{
|
|
2788
|
+
num: p.id,
|
|
2789
|
+
name: p.name,
|
|
2790
|
+
active: isActive(p),
|
|
2791
|
+
onCall: () => call(p.id),
|
|
2792
|
+
onSave: p.saveAllowed === false ? void 0 : () => save(p.id),
|
|
2793
|
+
cls
|
|
2794
|
+
},
|
|
2795
|
+
p.id
|
|
2796
|
+
)),
|
|
2797
|
+
saved && /* @__PURE__ */ jsx15("div", { className: cls.savedFlash, children: "Saved" })
|
|
2798
|
+
] })
|
|
2799
|
+
] });
|
|
2800
|
+
}
|
|
2801
|
+
|
|
2802
|
+
// src/components/dsp/dspClasses.ts
|
|
2803
|
+
var DSP_CLASS_DEFAULTS = {
|
|
2804
|
+
root: "w-full h-full flex flex-col",
|
|
2805
|
+
header: "relative w-full flex items-end justify-center gap-[0.3em] px-[1em] pt-[0.5em]",
|
|
2806
|
+
content: "w-full flex-1 min-h-0",
|
|
2807
|
+
tab: "w-[10.5em] h-[3.25em] rounded-b-none rounded-lg flex items-center justify-center transition-colors bg-[#4f5152] text-white text-[1.4em] font-bold",
|
|
2808
|
+
tabActive: "bg-[#007ca0]",
|
|
2809
|
+
message: "w-full h-full flex items-center justify-center text-gray-400 text-[2em]",
|
|
2810
|
+
presetWrap: "absolute left-[1em] bottom-[0.3em] h-[4.5em] flex flex-col items-center",
|
|
2811
|
+
presetLabel: "text-[#4f5152] text-[1.4em] leading-none",
|
|
2812
|
+
preset: "w-[8em] h-full rounded-lg flex items-center justify-center transition-colors bg-[#4f5152] text-white",
|
|
2813
|
+
presetActive: "bg-[#007ca0]",
|
|
2814
|
+
presetPressed: "bg-[#006080]",
|
|
2815
|
+
savedFlash: "absolute inset-0 flex items-center justify-center rounded bg-[#22c55e] text-white text-[1.2em] font-bold pointer-events-none",
|
|
2816
|
+
strip: "flex flex-col items-center gap-[0.4em] h-full w-[9em] flex-none px-[0.3em]",
|
|
2817
|
+
stripLabel: "text-[#4f5152] text-center leading-tight text-[1.1em] line-clamp-3",
|
|
2818
|
+
levelReadout: "text-[1.1em] text-[#4f5152] leading-none opacity-70 mt-[0.5em]",
|
|
2819
|
+
faderBar: "bg-[#c0c0c0] rounded",
|
|
2820
|
+
faderFill: "bg-[#007ca0] rounded",
|
|
2821
|
+
faderThumb: "bg-[#4f5152] rounded",
|
|
2822
|
+
mute: "w-full h-full rounded-lg flex items-center justify-center transition-colors bg-[#4f5152] text-white",
|
|
2823
|
+
muteActive: "bg-[#dc2626] text-white",
|
|
2824
|
+
mixerCorner: "sticky z-30 flex items-center justify-center bg-[#282C34] text-[#4f5152] text-[1.1em] font-bold border border-black/30",
|
|
2825
|
+
mixerChrome: "sticky flex items-center justify-center text-center bg-[#282C34] text-white text-[1.1em] leading-tight border border-black/30",
|
|
2826
|
+
cell: "z-10 flex items-center justify-center border border-black/30",
|
|
2827
|
+
cellOn: "bg-[#22c55e]",
|
|
2828
|
+
cellOff: "bg-[#4f5152]"
|
|
2829
|
+
};
|
|
2830
|
+
var DSP_ICON_DEFAULTS = {
|
|
2831
|
+
crosspointOn: "audio-volume-ok",
|
|
2832
|
+
muteOff: "audio-volume-high",
|
|
2833
|
+
muteOn: "audio-volume-mute",
|
|
2834
|
+
muteOffFilter: "invert(65%) sepia(70%) saturate(500%) hue-rotate(80deg) brightness(110%) contrast(95%)",
|
|
2835
|
+
muteOnFilter: "brightness(0) invert(1)"
|
|
2836
|
+
};
|
|
2837
|
+
function mergeDefined(defaults4, overrides) {
|
|
2838
|
+
if (!overrides) return { ...defaults4 };
|
|
2839
|
+
const out = { ...defaults4 };
|
|
2840
|
+
Object.keys(overrides).forEach((k) => {
|
|
2841
|
+
const v = overrides[k];
|
|
2842
|
+
if (v !== void 0) out[k] = v;
|
|
2843
|
+
});
|
|
2844
|
+
return out;
|
|
2845
|
+
}
|
|
2846
|
+
function resolveDspClasses(classes) {
|
|
2847
|
+
return mergeDefined(DSP_CLASS_DEFAULTS, classes);
|
|
2848
|
+
}
|
|
2849
|
+
function resolveDspIcons(icons) {
|
|
2850
|
+
return mergeDefined(DSP_ICON_DEFAULTS, icons);
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
// src/components/dsp/CrisViewDspFull.tsx
|
|
2854
|
+
import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
2855
|
+
function CrisViewDspFull({
|
|
2856
|
+
oid,
|
|
2857
|
+
presets,
|
|
2858
|
+
skipShowInput,
|
|
2859
|
+
skipShowOutput,
|
|
2860
|
+
skipCrosspoints = false,
|
|
2861
|
+
sustainedPresetFeedback = true,
|
|
2862
|
+
classes,
|
|
2863
|
+
icons,
|
|
2864
|
+
className,
|
|
2865
|
+
initialTab = "in"
|
|
2866
|
+
}) {
|
|
2867
|
+
const safeInitial = initialTab === "mix" && skipCrosspoints ? "in" : initialTab;
|
|
2868
|
+
const [tab, setTab] = useState6(safeInitial);
|
|
2869
|
+
const status = useCustomObject4(oid, { subscribe: true });
|
|
2870
|
+
const send = useCustomObjectSend4();
|
|
2871
|
+
const cls = resolveDspClasses(classes);
|
|
2872
|
+
const ic = resolveDspIcons(icons);
|
|
2873
|
+
const tabs = [
|
|
2874
|
+
{ id: "in", label: "Inputs" },
|
|
2875
|
+
...skipCrosspoints ? [] : [{ id: "mix", label: "Mixer" }],
|
|
2876
|
+
{ id: "out", label: "Outputs" }
|
|
2877
|
+
];
|
|
2878
|
+
const hasPresets = !!presets && presets.length > 0;
|
|
2879
|
+
const activeDevice = status?.pr?.dv ?? status?.ps?.dv;
|
|
2880
|
+
const activeLocal = status?.pr?.lc ?? status?.ps?.lc;
|
|
2881
|
+
return /* @__PURE__ */ jsxs13("div", { className: `${cls.root} ${className ?? ""}`, children: [
|
|
2882
|
+
/* @__PURE__ */ jsxs13("div", { className: cls.header, children: [
|
|
2883
|
+
hasPresets && /* @__PURE__ */ jsx16(
|
|
2884
|
+
DspPresets,
|
|
2885
|
+
{
|
|
2886
|
+
oid,
|
|
2887
|
+
send,
|
|
2888
|
+
presets,
|
|
2889
|
+
activeDevice,
|
|
2890
|
+
activeLocal,
|
|
2891
|
+
sustainedFeedback: sustainedPresetFeedback,
|
|
2892
|
+
cls
|
|
2893
|
+
}
|
|
2894
|
+
),
|
|
2895
|
+
tabs.map((t) => /* @__PURE__ */ jsx16(
|
|
2896
|
+
CrisButton,
|
|
2897
|
+
{
|
|
2898
|
+
text: t.label,
|
|
2899
|
+
selected: tab === t.id,
|
|
2900
|
+
onPress: () => setTab(t.id),
|
|
2901
|
+
className: cls.tab,
|
|
2902
|
+
classActive: cls.tabActive
|
|
2903
|
+
},
|
|
2904
|
+
t.id
|
|
2905
|
+
)),
|
|
2906
|
+
/* @__PURE__ */ jsx16(CrisViewComm, { comm: status?.cm, className: "absolute right-[1em] bottom-[0.3em] text-[1.25em]" })
|
|
2907
|
+
] }),
|
|
2908
|
+
/* @__PURE__ */ jsx16("div", { className: cls.content, children: status === void 0 ? /* @__PURE__ */ jsx16("div", { className: cls.message, children: "Conectando\u2026" }) : /* @__PURE__ */ jsxs13(Fragment4, { children: [
|
|
2909
|
+
tab === "in" && /* @__PURE__ */ jsx16(
|
|
2910
|
+
DspIoPage,
|
|
2911
|
+
{
|
|
2912
|
+
status,
|
|
2913
|
+
io: "in",
|
|
2914
|
+
oid,
|
|
2915
|
+
send,
|
|
2916
|
+
skip: skipShowInput,
|
|
2917
|
+
cls,
|
|
2918
|
+
icons: ic,
|
|
2919
|
+
emptyLabel: "Sin entradas"
|
|
2920
|
+
}
|
|
2921
|
+
),
|
|
2922
|
+
tab === "mix" && !skipCrosspoints && /* @__PURE__ */ jsx16(DspMixer, { status, oid, send, cls, icons: ic }),
|
|
2923
|
+
tab === "out" && /* @__PURE__ */ jsx16(
|
|
2924
|
+
DspIoPage,
|
|
2925
|
+
{
|
|
2926
|
+
status,
|
|
2927
|
+
io: "out",
|
|
2928
|
+
oid,
|
|
2929
|
+
send,
|
|
2930
|
+
skip: skipShowOutput,
|
|
2931
|
+
cls,
|
|
2932
|
+
icons: ic,
|
|
2933
|
+
emptyLabel: "Sin salidas"
|
|
2934
|
+
}
|
|
2935
|
+
)
|
|
2936
|
+
] }) })
|
|
2937
|
+
] });
|
|
2938
|
+
}
|
|
2123
2939
|
export {
|
|
2124
2940
|
CrisButton,
|
|
2125
2941
|
CrisCoDebug,
|
|
@@ -2131,9 +2947,18 @@ export {
|
|
|
2131
2947
|
CrisSpinner,
|
|
2132
2948
|
CrisText,
|
|
2133
2949
|
CrisTextInput,
|
|
2950
|
+
CrisViewComm,
|
|
2951
|
+
CrisViewDspFull,
|
|
2952
|
+
buildMixerAxis,
|
|
2953
|
+
clampLevel,
|
|
2954
|
+
collapseStrips,
|
|
2955
|
+
commIndicators,
|
|
2134
2956
|
configureIcons,
|
|
2135
2957
|
getIconConfig,
|
|
2136
2958
|
getIconFilter,
|
|
2137
|
-
getIconUrl
|
|
2959
|
+
getIconUrl,
|
|
2960
|
+
levelToPercent,
|
|
2961
|
+
linksFor,
|
|
2962
|
+
normalizeLinked
|
|
2138
2963
|
};
|
|
2139
2964
|
//# sourceMappingURL=index.mjs.map
|