@imperosoft/cris-webui-components 1.2.0 → 1.3.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.js CHANGED
@@ -31,8 +31,15 @@ __export(index_exports, {
31
31
  CrisText: () => CrisText,
32
32
  CrisTextInput: () => CrisTextInput,
33
33
  CrisViewComm: () => CrisViewComm,
34
+ CrisViewDspChannels: () => CrisViewDspChannels,
34
35
  CrisViewDspFull: () => CrisViewDspFull,
36
+ CrisViewDspInputs: () => CrisViewDspInputs,
37
+ CrisViewDspMixer: () => CrisViewDspMixer,
38
+ CrisViewDspOutputs: () => CrisViewDspOutputs,
39
+ CrisViewDspPresets: () => CrisViewDspPresets,
40
+ buildIoSections: () => buildIoSections,
35
41
  buildMixerAxis: () => buildMixerAxis,
42
+ buildMixerAxisGrouped: () => buildMixerAxisGrouped,
36
43
  clampLevel: () => clampLevel,
37
44
  collapseStrips: () => collapseStrips,
38
45
  commIndicators: () => commIndicators,
@@ -40,6 +47,7 @@ __export(index_exports, {
40
47
  getIconConfig: () => getIconConfig,
41
48
  getIconFilter: () => getIconFilter,
42
49
  getIconUrl: () => getIconUrl,
50
+ groupsFor: () => groupsFor,
43
51
  levelToPercent: () => levelToPercent,
44
52
  linksFor: () => linksFor,
45
53
  normalizeLinked: () => normalizeLinked
@@ -818,11 +826,13 @@ function CrisSlider({
818
826
  }
819
827
  }
820
828
  touchDecidedRef.current = false;
829
+ touchingRef.current = false;
821
830
  };
822
831
  const handleTouchCancel = () => {
823
832
  touchEnd();
824
833
  touchDecidedRef.current = false;
825
834
  if (draggingRef.current) handleDragEnd();
835
+ touchingRef.current = false;
826
836
  };
827
837
  if (!isVisible) return null;
828
838
  const containerClasses = [
@@ -2386,6 +2396,9 @@ function CrisViewComm({ comm, classes, icons, className }) {
2386
2396
 
2387
2397
  // src/components/dsp/CrisViewDspFull.tsx
2388
2398
  var import_react9 = require("react");
2399
+ var import_cris_webui_ch5_core14 = require("@imperosoft/cris-webui-ch5-core");
2400
+
2401
+ // src/components/dsp/CrisViewDspInputs.tsx
2389
2402
  var import_cris_webui_ch5_core11 = require("@imperosoft/cris-webui-ch5-core");
2390
2403
 
2391
2404
  // src/components/dsp/DspIoPage.tsx
@@ -2437,6 +2450,127 @@ function collapseStrips(channels, links) {
2437
2450
  function linksFor(ln, io) {
2438
2451
  return io === "in" ? ln?.ip : ln?.op;
2439
2452
  }
2453
+ function groupsFor(dg, io) {
2454
+ return io === "in" ? dg?.ip : dg?.op;
2455
+ }
2456
+ function buildLinkIndex(links) {
2457
+ const idx = /* @__PURE__ */ new Map();
2458
+ for (const raw of links ?? []) {
2459
+ const { label, channels } = normalizeLinked(raw);
2460
+ if (channels.length === 0) continue;
2461
+ const info = { label, channels };
2462
+ for (const id of channels) idx.set(id, info);
2463
+ }
2464
+ return idx;
2465
+ }
2466
+ function collapseWithin(ids, byId, linkIdx, idSet, usedLink, consumed) {
2467
+ const strips = [];
2468
+ for (const id of ids) {
2469
+ if (consumed.has(id)) continue;
2470
+ const link = linkIdx.get(id);
2471
+ const contained = !!link && link.channels.every((m) => idSet.has(m));
2472
+ if (link && contained && !usedLink.has(link)) {
2473
+ usedLink.add(link);
2474
+ link.channels.forEach((m) => consumed.add(m));
2475
+ const primary = link.channels.find((m) => byId.has(m));
2476
+ const ref = primary !== void 0 ? byId.get(primary) : void 0;
2477
+ strips.push({
2478
+ kind: "group",
2479
+ key: `g:${link.channels.join("-")}`,
2480
+ label: link.label || ref?.lb || `Grupo ${link.channels[0]}`,
2481
+ channels: link.channels,
2482
+ lv: clampLevel(ref?.lv),
2483
+ mt: ref?.mt ?? false
2484
+ });
2485
+ } else {
2486
+ consumed.add(id);
2487
+ const c = byId.get(id);
2488
+ if (!c) continue;
2489
+ strips.push({
2490
+ kind: "single",
2491
+ key: `c:${c.id}`,
2492
+ label: c.lb ?? `Canal ${c.id}`,
2493
+ channels: [c.id],
2494
+ lv: clampLevel(c.lv),
2495
+ mt: c.mt
2496
+ });
2497
+ }
2498
+ }
2499
+ return strips;
2500
+ }
2501
+ function buildIoSections(channels, links, groups) {
2502
+ if (!groups || groups.length === 0) {
2503
+ return [{ strips: collapseStrips(channels, links) }];
2504
+ }
2505
+ const byId = new Map(channels.map((c) => [c.id, c]));
2506
+ const linkIdx = buildLinkIndex(links);
2507
+ const usedLink = /* @__PURE__ */ new Set();
2508
+ const consumed = /* @__PURE__ */ new Set();
2509
+ const assigned = /* @__PURE__ */ new Set();
2510
+ const sections = [];
2511
+ for (const g of groups) {
2512
+ g.ch.forEach((id) => assigned.add(id));
2513
+ const present = g.ch.filter((id) => byId.has(id));
2514
+ if (present.length === 0) continue;
2515
+ const strips = collapseWithin(present, byId, linkIdx, new Set(g.ch), usedLink, consumed);
2516
+ if (strips.length) sections.push({ groupName: g.id, strips });
2517
+ }
2518
+ const restIds = channels.filter((c) => !assigned.has(c.id)).map((c) => c.id);
2519
+ if (restIds.length) {
2520
+ const strips = collapseWithin(restIds, byId, linkIdx, new Set(restIds), usedLink, consumed);
2521
+ if (strips.length) sections.push({ strips });
2522
+ }
2523
+ return sections;
2524
+ }
2525
+ function buildMixerAxisGrouped(channels, links, groups) {
2526
+ const byId = new Map(channels.map((c) => [c.id, c]));
2527
+ const ordered = [];
2528
+ const groupOf = /* @__PURE__ */ new Map();
2529
+ const assigned = /* @__PURE__ */ new Set();
2530
+ for (const g of groups ?? []) {
2531
+ for (const id of g.ch) {
2532
+ assigned.add(id);
2533
+ const c = byId.get(id);
2534
+ if (c && !groupOf.has(id)) {
2535
+ ordered.push(c);
2536
+ groupOf.set(id, g.id);
2537
+ }
2538
+ }
2539
+ }
2540
+ for (const c of channels) if (!assigned.has(c.id)) ordered.push(c);
2541
+ const items = ordered.map((c) => ({
2542
+ id: c.id,
2543
+ channelLabel: c.lb ?? `${c.id}`,
2544
+ groupName: groupOf.get(c.id)
2545
+ }));
2546
+ const indexById = new Map(ordered.map((c, idx) => [c.id, idx]));
2547
+ let i = 0;
2548
+ while (i < items.length) {
2549
+ const g = items[i].groupName;
2550
+ if (g === void 0) {
2551
+ i++;
2552
+ continue;
2553
+ }
2554
+ let j = i;
2555
+ while (j < items.length && items[j].groupName === g) j++;
2556
+ items[i].groupStart = true;
2557
+ items[i].groupSpan = j - i;
2558
+ i = j;
2559
+ }
2560
+ for (const raw of links ?? []) {
2561
+ const { label, channels: members } = normalizeLinked(raw);
2562
+ if (members.length < 2) continue;
2563
+ const idxs = members.map((n) => indexById.get(n));
2564
+ if (idxs.some((x) => x === void 0)) continue;
2565
+ const indices = idxs.slice().sort((a, b) => a - b);
2566
+ const consecutive = indices.every((x, k) => k === 0 || x === indices[k - 1] + 1);
2567
+ if (!consecutive) continue;
2568
+ items[indices[0]].linkStart = true;
2569
+ items[indices[0]].linkSpan = indices.length;
2570
+ for (const idx of indices) items[idx].linkCommon = label;
2571
+ }
2572
+ return items;
2573
+ }
2440
2574
  function buildMixerAxis(channels, links) {
2441
2575
  const items = channels.map((c) => ({
2442
2576
  id: c.id,
@@ -2536,52 +2670,204 @@ function useHorizontalWheel(ref) {
2536
2670
 
2537
2671
  // src/components/dsp/DspIoPage.tsx
2538
2672
  var import_jsx_runtime13 = require("react/jsx-runtime");
2539
- function DspIoPage({ status, io, oid, send, skip, cls, icons, emptyLabel }) {
2673
+ function DspIoPage({
2674
+ status,
2675
+ io,
2676
+ oid,
2677
+ send,
2678
+ show,
2679
+ skip,
2680
+ cls,
2681
+ icons,
2682
+ emptyLabel,
2683
+ bare = false,
2684
+ suppressEmpty = false
2685
+ }) {
2540
2686
  const scrollRef = (0, import_react6.useRef)(null);
2541
2687
  useHorizontalWheel(scrollRef);
2688
+ const showSet = show ? new Set(show) : null;
2542
2689
  const skipSet = new Set(skip ?? []);
2690
+ const keep = (id) => (!showSet || showSet.has(id)) && !skipSet.has(id);
2543
2691
  const raw = (io === "in" ? status?.ip : status?.op) ?? [];
2544
- const channels = skipSet.size ? raw.filter((c) => !skipSet.has(c.id)) : raw;
2692
+ const channels = raw.filter((c) => keep(c.id));
2545
2693
  const allLinks = linksFor(status?.ln, io);
2546
- const links = skipSet.size ? allLinks?.filter((g) => (g.ch ?? []).some((id) => !skipSet.has(id))) : allLinks;
2547
- const strips = collapseStrips(channels, links);
2548
- if (strips.length === 0) {
2549
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: cls.message, children: emptyLabel });
2694
+ const links = allLinks?.filter((g) => (g.ch ?? []).some((id) => keep(id)));
2695
+ const groups = groupsFor(status?.dg, io)?.filter((g) => (g.ch ?? []).some((id) => keep(id)));
2696
+ const sections = buildIoSections(channels, links, groups);
2697
+ const hasHeaders = sections.some((s) => s.groupName);
2698
+ const totalStrips = sections.reduce((n, s) => n + s.strips.length, 0);
2699
+ if (totalStrips === 0) {
2700
+ return suppressEmpty ? null : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: cls.message, children: emptyLabel });
2550
2701
  }
2551
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { ref: scrollRef, className: "w-full h-full overflow-x-auto no-scrollbar", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2702
+ const row = /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2552
2703
  "div",
2553
2704
  {
2554
2705
  className: "flex h-full items-stretch gap-[0.3em] px-[0.5em] py-[0.5em]",
2555
2706
  style: { minWidth: "min-content" },
2556
- children: strips.map((strip) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2557
- DspChannelStrip,
2558
- {
2559
- strip,
2560
- io,
2561
- oid,
2562
- send,
2563
- cls,
2564
- icons
2565
- },
2566
- strip.key
2567
- ))
2707
+ children: sections.map((sec, i) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex flex-col h-full", children: [
2708
+ hasHeaders && // Header band: spans this group's strips. The 1px separator lives only
2709
+ // here (between groups), never down the full channel height.
2710
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2711
+ "div",
2712
+ {
2713
+ className: "flex items-center justify-center shrink-0 h-[2.2em]",
2714
+ style: { borderLeft: i > 0 ? "1px solid rgba(0,0,0,0.4)" : void 0 },
2715
+ children: sec.groupName && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: cls.groupHeader, title: sec.groupName, children: sec.groupName })
2716
+ }
2717
+ ),
2718
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "flex flex-1 min-h-0 items-stretch gap-[0.3em]", children: sec.strips.map((strip) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2719
+ DspChannelStrip,
2720
+ {
2721
+ strip,
2722
+ io,
2723
+ oid,
2724
+ send,
2725
+ cls,
2726
+ icons
2727
+ },
2728
+ strip.key
2729
+ )) })
2730
+ ] }, sec.groupName ?? `rest:${i}`))
2731
+ }
2732
+ );
2733
+ if (bare) return row;
2734
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { ref: scrollRef, className: "w-full h-full overflow-x-auto no-scrollbar", children: row });
2735
+ }
2736
+
2737
+ // src/components/dsp/dspClasses.ts
2738
+ var DSP_CLASS_DEFAULTS = {
2739
+ root: "w-full h-full flex flex-col",
2740
+ header: "relative w-full flex items-end justify-center gap-[0.3em] px-[1em] pt-[0.5em]",
2741
+ content: "w-full flex-1 min-h-0",
2742
+ 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",
2743
+ tabActive: "bg-[#007ca0]",
2744
+ message: "w-full h-full flex items-center justify-center text-gray-400 text-[2em]",
2745
+ presetWrap: "absolute left-[1em] bottom-[0.3em] h-[4.5em] flex flex-col items-center",
2746
+ presetLabel: "text-[#4f5152] text-[1.4em] leading-none",
2747
+ preset: "w-[8em] h-full rounded-lg flex items-center justify-center transition-colors bg-[#4f5152] text-white",
2748
+ presetActive: "bg-[#007ca0]",
2749
+ presetPressed: "bg-[#006080]",
2750
+ savedFlash: "absolute inset-0 flex items-center justify-center rounded bg-[#22c55e] text-white text-[1.2em] font-bold pointer-events-none",
2751
+ groupHeader: "text-[#4f5152] text-center font-semibold leading-tight text-[1.1em] line-clamp-2 px-[0.3em]",
2752
+ // NOTE: no font-size on the mixer chrome/group cells — their `em` sticky
2753
+ // offsets must match the grid's `em` track widths. Text size lives on the
2754
+ // inner label spans (text-[1.1em]) instead.
2755
+ mixerGroup: "sticky flex items-center justify-center text-center bg-[#282C34] text-white font-semibold leading-tight border border-black/30",
2756
+ mixerGroupV: "sticky flex items-center justify-center bg-[#282C34] text-white font-semibold leading-tight border border-black/30",
2757
+ strip: "flex flex-col items-center gap-[0.4em] h-full w-[9em] flex-none px-[0.3em]",
2758
+ stripLabel: "text-[#4f5152] text-center leading-tight text-[1.1em] line-clamp-3",
2759
+ levelReadout: "text-[1.1em] text-[#4f5152] leading-none opacity-70 mt-[0.5em]",
2760
+ faderBar: "bg-[#c0c0c0] rounded",
2761
+ faderFill: "bg-[#007ca0] rounded",
2762
+ faderThumb: "bg-[#4f5152] rounded",
2763
+ mute: "w-full h-full rounded-lg flex items-center justify-center transition-colors bg-[#4f5152] text-white",
2764
+ muteActive: "bg-[#dc2626] text-white",
2765
+ mixerCorner: "sticky z-30 flex items-center justify-center bg-[#282C34] text-[#4f5152] text-[1.1em] font-bold border border-black/30",
2766
+ mixerChrome: "sticky flex items-center justify-center text-center bg-[#282C34] text-white leading-tight border border-black/30",
2767
+ cell: "z-10 flex items-center justify-center border border-black/30",
2768
+ cellOn: "bg-[#22c55e]",
2769
+ cellOff: "bg-[#4f5152]"
2770
+ };
2771
+ var DSP_ICON_DEFAULTS = {
2772
+ crosspointOn: "audio-volume-ok",
2773
+ muteOff: "audio-volume-high",
2774
+ muteOn: "audio-volume-mute",
2775
+ muteOffFilter: "invert(65%) sepia(70%) saturate(500%) hue-rotate(80deg) brightness(110%) contrast(95%)",
2776
+ muteOnFilter: "brightness(0) invert(1)"
2777
+ };
2778
+ function mergeDefined(defaults4, overrides) {
2779
+ if (!overrides) return { ...defaults4 };
2780
+ const out = { ...defaults4 };
2781
+ Object.keys(overrides).forEach((k) => {
2782
+ const v = overrides[k];
2783
+ if (v !== void 0) out[k] = v;
2784
+ });
2785
+ return out;
2786
+ }
2787
+ function resolveDspClasses(classes) {
2788
+ return mergeDefined(DSP_CLASS_DEFAULTS, classes);
2789
+ }
2790
+ function resolveDspIcons(icons) {
2791
+ return mergeDefined(DSP_ICON_DEFAULTS, icons);
2792
+ }
2793
+
2794
+ // src/components/dsp/CrisViewDspInputs.tsx
2795
+ var import_jsx_runtime14 = require("react/jsx-runtime");
2796
+ function CrisViewDspInputs({
2797
+ oid,
2798
+ skip,
2799
+ emptyLabel = "Sin entradas",
2800
+ classes,
2801
+ icons,
2802
+ className
2803
+ }) {
2804
+ const status = (0, import_cris_webui_ch5_core11.useCustomObject)(oid, { subscribe: true });
2805
+ const send = (0, import_cris_webui_ch5_core11.useCustomObjectSend)();
2806
+ const cls = resolveDspClasses(classes);
2807
+ const ic = resolveDspIcons(icons);
2808
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: `w-full h-full ${className ?? ""}`, children: status === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: cls.message, children: "Conectando\u2026" }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2809
+ DspIoPage,
2810
+ {
2811
+ status,
2812
+ io: "in",
2813
+ oid,
2814
+ send,
2815
+ skip,
2816
+ cls,
2817
+ icons: ic,
2818
+ emptyLabel
2568
2819
  }
2569
2820
  ) });
2570
2821
  }
2571
2822
 
2823
+ // src/components/dsp/CrisViewDspOutputs.tsx
2824
+ var import_cris_webui_ch5_core12 = require("@imperosoft/cris-webui-ch5-core");
2825
+ var import_jsx_runtime15 = require("react/jsx-runtime");
2826
+ function CrisViewDspOutputs({
2827
+ oid,
2828
+ skip,
2829
+ emptyLabel = "Sin salidas",
2830
+ classes,
2831
+ icons,
2832
+ className
2833
+ }) {
2834
+ const status = (0, import_cris_webui_ch5_core12.useCustomObject)(oid, { subscribe: true });
2835
+ const send = (0, import_cris_webui_ch5_core12.useCustomObjectSend)();
2836
+ const cls = resolveDspClasses(classes);
2837
+ const ic = resolveDspIcons(icons);
2838
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: `w-full h-full ${className ?? ""}`, children: status === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: cls.message, children: "Conectando\u2026" }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2839
+ DspIoPage,
2840
+ {
2841
+ status,
2842
+ io: "out",
2843
+ oid,
2844
+ send,
2845
+ skip,
2846
+ cls,
2847
+ icons: ic,
2848
+ emptyLabel
2849
+ }
2850
+ ) });
2851
+ }
2852
+
2853
+ // src/components/dsp/CrisViewDspMixer.tsx
2854
+ var import_cris_webui_ch5_core13 = require("@imperosoft/cris-webui-ch5-core");
2855
+
2572
2856
  // src/components/dsp/DspMixer.tsx
2573
2857
  var import_react7 = require("react");
2574
- var import_jsx_runtime14 = require("react/jsx-runtime");
2858
+ var import_jsx_runtime16 = require("react/jsx-runtime");
2859
+ var GROUP_W = "2.6em";
2575
2860
  var COMMON_W = "9em";
2576
2861
  var CHAN_W = "3em";
2862
+ var GROUP_H = "2.2em";
2577
2863
  var COMMON_H = "2.4em";
2578
2864
  var CHAN_H = "2.1em";
2579
2865
  var CELL_W = "7.5em";
2580
2866
  var ROW_H = "4.5em";
2581
2867
  var DRAG_FACTOR = 0.6;
2582
2868
  function CrosspointOnIcon({ icon }) {
2583
- if (typeof icon !== "string") return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_jsx_runtime14.Fragment, { children: icon });
2584
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2869
+ if (typeof icon !== "string") return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_jsx_runtime16.Fragment, { children: icon });
2870
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2585
2871
  "img",
2586
2872
  {
2587
2873
  src: getIconUrl(icon),
@@ -2640,11 +2926,16 @@ function DspMixer({ status, oid, send, cls, icons }) {
2640
2926
  pendingRef.current = null;
2641
2927
  };
2642
2928
  if (inputs.length === 0 || outputs.length === 0) {
2643
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: cls.message, children: "Sin crosspoints" });
2929
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: cls.message, children: "Sin crosspoints" });
2644
2930
  }
2645
- const inAxis = buildMixerAxis(inputs, linksFor(status?.ln, "in"));
2646
- const outAxis = buildMixerAxis(outputs, linksFor(status?.ln, "out"));
2647
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2931
+ const inAxis = buildMixerAxisGrouped(inputs, linksFor(status?.ln, "in"), groupsFor(status?.dg, "in"));
2932
+ const outAxis = buildMixerAxisGrouped(outputs, linksFor(status?.ln, "out"), groupsFor(status?.dg, "out"));
2933
+ const topLink = GROUP_H;
2934
+ const topChan = `calc(${GROUP_H} + ${COMMON_H})`;
2935
+ const leftLink = GROUP_W;
2936
+ const leftChan = `calc(${GROUP_W} + ${COMMON_W})`;
2937
+ const Z = { corner: 40, group: 30, link: 25, chan: 20 };
2938
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2648
2939
  "div",
2649
2940
  {
2650
2941
  ref: scrollRef,
@@ -2653,125 +2944,212 @@ function DspMixer({ status, oid, send, cls, icons }) {
2653
2944
  onPointerUp,
2654
2945
  onPointerCancel: onPointerUp,
2655
2946
  className: "w-full h-full overflow-auto no-scrollbar relative touch-none select-none cursor-grab",
2656
- children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2947
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
2657
2948
  "div",
2658
2949
  {
2659
2950
  className: "grid",
2660
2951
  style: {
2661
- // cols: [output common][output channel][inputs…] rows: [in common][in channel][outputs…]
2662
- gridTemplateColumns: `${COMMON_W} ${CHAN_W} repeat(${inputs.length}, ${CELL_W})`,
2663
- gridTemplateRows: `${COMMON_H} ${CHAN_H} repeat(${outputs.length}, ${ROW_H})`,
2952
+ // cols: [out group][out link][out channel][inputs…] rows: [in group][in link][in channel][outputs…]
2953
+ gridTemplateColumns: `${GROUP_W} ${COMMON_W} ${CHAN_W} repeat(${inputs.length}, ${CELL_W})`,
2954
+ gridTemplateRows: `${GROUP_H} ${COMMON_H} ${CHAN_H} repeat(${outputs.length}, ${ROW_H})`,
2664
2955
  minWidth: "min-content"
2665
2956
  },
2666
2957
  children: [
2667
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2958
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2959
+ "div",
2960
+ {
2961
+ "aria-hidden": true,
2962
+ className: "sticky bg-[#282C34]",
2963
+ style: { top: 0, zIndex: 15, gridColumn: "1 / -1", gridRow: "1 / span 3" }
2964
+ }
2965
+ ),
2966
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2967
+ "div",
2968
+ {
2969
+ "aria-hidden": true,
2970
+ className: "sticky bg-[#282C34]",
2971
+ style: { left: 0, zIndex: 15, gridColumn: "1 / span 3", gridRow: "1 / -1" }
2972
+ }
2973
+ ),
2974
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2668
2975
  "div",
2669
2976
  {
2670
2977
  className: cls.mixerCorner,
2671
- style: { top: 0, left: 0, gridColumn: "1 / span 2", gridRow: "1 / span 2" },
2978
+ style: { top: 0, left: 0, zIndex: Z.corner, gridColumn: "1 / span 3", gridRow: "1 / span 3" },
2672
2979
  children: "OUT \\ IN"
2673
2980
  }
2674
2981
  ),
2675
2982
  inAxis.flatMap((it, ii) => {
2676
- const col = 3 + ii;
2677
- if (it.groupCommon !== void 0) {
2678
- const nodes = [
2679
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2983
+ const col = 4 + ii;
2984
+ const nodes = [];
2985
+ if (it.groupStart) {
2986
+ nodes.push(
2987
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2680
2988
  "div",
2681
2989
  {
2682
- className: `${cls.mixerChrome} z-20 px-[0.2em]`,
2683
- style: { top: 0, gridRow: "1 / span 2", gridColumn: col, alignItems: "flex-end", paddingBottom: "0.3em" },
2684
- children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "line-clamp-1", children: it.channelLabel })
2990
+ className: cls.mixerGroup,
2991
+ style: { top: 0, zIndex: Z.group, gridRow: 1, gridColumn: `${col} / span ${it.groupSpan}` },
2992
+ title: it.groupName,
2993
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-[1.1em] line-clamp-2 px-[0.2em]", children: it.groupName })
2685
2994
  },
2686
- `ich:${it.id}`
2995
+ `igrp:${it.id}`
2687
2996
  )
2688
- ];
2689
- if (it.groupStart) {
2690
- nodes.unshift(
2691
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2997
+ );
2998
+ } else if (it.groupName === void 0) {
2999
+ nodes.push(
3000
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3001
+ "div",
3002
+ {
3003
+ className: cls.mixerChrome,
3004
+ style: { top: 0, zIndex: Z.group, gridRow: 1, gridColumn: col }
3005
+ },
3006
+ `ifill:${it.id}`
3007
+ )
3008
+ );
3009
+ }
3010
+ if (it.linkCommon !== void 0) {
3011
+ if (it.linkStart) {
3012
+ nodes.push(
3013
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2692
3014
  "div",
2693
3015
  {
2694
- className: `${cls.mixerChrome} z-25 px-[0.2em] font-semibold`,
2695
- style: { top: 0, gridRow: 1, gridColumn: `${col} / span ${it.groupSpan}` },
2696
- title: it.groupCommon,
2697
- children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "line-clamp-2", children: it.groupCommon })
3016
+ className: `${cls.mixerChrome} px-[0.2em] font-semibold`,
3017
+ style: { top: topLink, zIndex: Z.link, gridRow: 2, gridColumn: `${col} / span ${it.linkSpan}` },
3018
+ title: it.linkCommon,
3019
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-[1.1em] line-clamp-2", children: it.linkCommon })
2698
3020
  },
2699
- `icc:${it.id}`
3021
+ `ilnk:${it.id}`
2700
3022
  )
2701
3023
  );
2702
3024
  }
2703
- return nodes;
3025
+ nodes.push(
3026
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3027
+ "div",
3028
+ {
3029
+ className: `${cls.mixerChrome} px-[0.2em]`,
3030
+ style: { top: topChan, zIndex: Z.chan, gridRow: 3, gridColumn: col },
3031
+ title: it.channelLabel,
3032
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-[1.1em] line-clamp-1", children: it.channelLabel })
3033
+ },
3034
+ `ichn:${it.id}`
3035
+ )
3036
+ );
3037
+ } else {
3038
+ nodes.push(
3039
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3040
+ "div",
3041
+ {
3042
+ className: cls.mixerChrome,
3043
+ style: { top: topLink, zIndex: Z.link, gridRow: 2, gridColumn: col }
3044
+ },
3045
+ `ilf:${it.id}`
3046
+ ),
3047
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3048
+ "div",
3049
+ {
3050
+ className: `${cls.mixerChrome} px-[0.2em]`,
3051
+ style: { top: topChan, zIndex: Z.chan, gridRow: 3, gridColumn: col },
3052
+ title: it.channelLabel,
3053
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-[1.1em] line-clamp-2", children: it.channelLabel })
3054
+ },
3055
+ `ichn:${it.id}`
3056
+ )
3057
+ );
2704
3058
  }
2705
- return [
2706
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2707
- "div",
2708
- {
2709
- className: `${cls.mixerChrome} z-20 px-[0.2em]`,
2710
- style: { top: 0, gridRow: "1 / span 2", gridColumn: col, alignItems: "center" },
2711
- title: it.channelLabel,
2712
- children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "line-clamp-3", children: it.channelLabel })
2713
- },
2714
- `is:${it.id}`
2715
- )
2716
- ];
3059
+ return nodes;
2717
3060
  }),
2718
3061
  outAxis.flatMap((it, oo) => {
2719
- const row = 3 + oo;
2720
- if (it.groupCommon !== void 0) {
2721
- const nodes = [
2722
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3062
+ const row = 4 + oo;
3063
+ const nodes = [];
3064
+ if (it.groupStart) {
3065
+ nodes.push(
3066
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3067
+ "div",
3068
+ {
3069
+ className: cls.mixerGroupV,
3070
+ style: {
3071
+ left: 0,
3072
+ zIndex: Z.group,
3073
+ gridColumn: 1,
3074
+ gridRow: `${row} / span ${it.groupSpan}`,
3075
+ writingMode: "vertical-rl",
3076
+ transform: "rotate(180deg)"
3077
+ },
3078
+ title: it.groupName,
3079
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-[1.1em] line-clamp-2", children: it.groupName })
3080
+ },
3081
+ `ogrp:${it.id}`
3082
+ )
3083
+ );
3084
+ } else if (it.groupName === void 0) {
3085
+ nodes.push(
3086
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2723
3087
  "div",
2724
3088
  {
2725
- className: `${cls.mixerChrome} z-20`,
2726
- style: { left: 0, gridColumn: "1 / span 2", gridRow: row, justifyContent: "flex-end", paddingRight: "0.6em" },
2727
- children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "line-clamp-1", children: it.channelLabel })
3089
+ className: cls.mixerChrome,
3090
+ style: { left: 0, zIndex: Z.group, gridColumn: 1, gridRow: row }
2728
3091
  },
2729
- `och:${it.id}`
3092
+ `ofill:${it.id}`
2730
3093
  )
2731
- ];
2732
- if (it.groupStart) {
2733
- nodes.unshift(
2734
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3094
+ );
3095
+ }
3096
+ if (it.linkCommon !== void 0) {
3097
+ if (it.linkStart) {
3098
+ nodes.push(
3099
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2735
3100
  "div",
2736
3101
  {
2737
- className: `${cls.mixerChrome} z-25 justify-start px-[0.4em] font-semibold`,
2738
- style: { left: 0, gridColumn: 1, gridRow: `${row} / span ${it.groupSpan}` },
2739
- title: it.groupCommon,
2740
- children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "line-clamp-2 text-left", children: it.groupCommon })
3102
+ className: `${cls.mixerChrome} justify-start px-[0.4em] font-semibold`,
3103
+ style: { left: leftLink, zIndex: Z.link, gridColumn: 2, gridRow: `${row} / span ${it.linkSpan}` },
3104
+ title: it.linkCommon,
3105
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-[1.1em] line-clamp-2 text-left", children: it.linkCommon })
2741
3106
  },
2742
- `occ:${it.id}`
3107
+ `olnk:${it.id}`
2743
3108
  )
2744
3109
  );
2745
3110
  }
2746
- return nodes;
3111
+ nodes.push(
3112
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3113
+ "div",
3114
+ {
3115
+ className: cls.mixerChrome,
3116
+ style: { left: leftChan, zIndex: Z.chan, gridColumn: 3, gridRow: row },
3117
+ title: it.channelLabel,
3118
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-[1.1em] line-clamp-1", children: it.channelLabel })
3119
+ },
3120
+ `ochn:${it.id}`
3121
+ )
3122
+ );
3123
+ } else {
3124
+ nodes.push(
3125
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3126
+ "div",
3127
+ {
3128
+ className: `${cls.mixerChrome} justify-start px-[0.4em]`,
3129
+ style: { left: leftLink, zIndex: Z.chan, gridColumn: "2 / span 2", gridRow: row },
3130
+ title: it.channelLabel,
3131
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-[1.1em] line-clamp-2 text-left", children: it.channelLabel })
3132
+ },
3133
+ `ochn:${it.id}`
3134
+ )
3135
+ );
2747
3136
  }
2748
- return [
2749
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2750
- "div",
2751
- {
2752
- className: `${cls.mixerChrome} z-20 justify-start px-[0.4em]`,
2753
- style: { left: 0, gridColumn: "1 / span 2", gridRow: row },
2754
- title: it.channelLabel,
2755
- children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "line-clamp-2 text-left", children: it.channelLabel })
2756
- },
2757
- `os:${it.id}`
2758
- )
2759
- ];
3137
+ return nodes;
2760
3138
  }),
2761
- outputs.map(
2762
- (o, oo) => inputs.map((i, ii) => {
2763
- const on = isOn(o.id, i.id);
2764
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3139
+ outAxis.map(
3140
+ (orow, oo) => inAxis.map((icol, ii) => {
3141
+ const on = isOn(orow.id, icol.id);
3142
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2765
3143
  "div",
2766
3144
  {
2767
3145
  "data-cell": true,
2768
- "data-in": i.id,
2769
- "data-out": o.id,
3146
+ "data-in": icol.id,
3147
+ "data-out": orow.id,
2770
3148
  className: `${cls.cell} ${on ? cls.cellOn : cls.cellOff}`,
2771
- style: { gridColumn: 3 + ii, gridRow: 3 + oo },
2772
- children: on && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(CrosspointOnIcon, { icon: icons.crosspointOn })
3149
+ style: { gridColumn: 4 + ii, gridRow: 4 + oo },
3150
+ children: on && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(CrosspointOnIcon, { icon: icons.crosspointOn })
2773
3151
  },
2774
- `x:${i.id}:${o.id}`
3152
+ `x:${icol.id}:${orow.id}`
2775
3153
  );
2776
3154
  })
2777
3155
  )
@@ -2782,13 +3160,23 @@ function DspMixer({ status, oid, send, cls, icons }) {
2782
3160
  );
2783
3161
  }
2784
3162
 
3163
+ // src/components/dsp/CrisViewDspMixer.tsx
3164
+ var import_jsx_runtime17 = require("react/jsx-runtime");
3165
+ function CrisViewDspMixer({ oid, classes, icons, className }) {
3166
+ const status = (0, import_cris_webui_ch5_core13.useCustomObject)(oid, { subscribe: true });
3167
+ const send = (0, import_cris_webui_ch5_core13.useCustomObjectSend)();
3168
+ const cls = resolveDspClasses(classes);
3169
+ const ic = resolveDspIcons(icons);
3170
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: `w-full h-full ${className ?? ""}`, children: status === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: cls.message, children: "Conectando\u2026" }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(DspMixer, { status, oid, send, cls, icons: ic }) });
3171
+ }
3172
+
2785
3173
  // src/components/dsp/DspPresets.tsx
2786
3174
  var import_react8 = require("react");
2787
- var import_jsx_runtime15 = require("react/jsx-runtime");
3175
+ var import_jsx_runtime18 = require("react/jsx-runtime");
2788
3176
  var HOLD_MS = 2e3;
2789
3177
  var SAVED_MS = 1e3;
2790
3178
  function PresetButton({ num, name, active, onCall, onSave, cls }) {
2791
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3179
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2792
3180
  CrisButton,
2793
3181
  {
2794
3182
  onTap: onCall,
@@ -2798,9 +3186,9 @@ function PresetButton({ num, name, active, onCall, onSave, cls }) {
2798
3186
  className: cls.preset,
2799
3187
  classActive: cls.presetActive,
2800
3188
  classPressed: cls.presetPressed,
2801
- children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "flex items-center justify-center gap-[0.5em]", children: [
2802
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "text-[1.6em] font-bold leading-none", children: num }),
2803
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "text-[1.2em] leading-none", children: name })
3189
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "flex items-center justify-center gap-[0.5em]", children: [
3190
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "text-[1.6em] font-bold leading-none", children: num }),
3191
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "text-[1.2em] leading-none", children: name })
2804
3192
  ] })
2805
3193
  }
2806
3194
  );
@@ -2827,10 +3215,10 @@ function DspPresets({
2827
3215
  if (!sustainedFeedback) return false;
2828
3216
  return p.kind === "local" ? activeLocal === p.id : activeDevice === p.id;
2829
3217
  };
2830
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: cls.presetWrap, children: [
2831
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: cls.presetLabel, children: "Preset" }),
2832
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "relative flex-1 flex items-stretch gap-[0.3em] mt-[0.25em]", children: [
2833
- presets.map((p) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3218
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: cls.presetWrap, children: [
3219
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: cls.presetLabel, children: "Preset" }),
3220
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "relative flex-1 flex items-stretch gap-[0.3em] mt-[0.25em]", children: [
3221
+ presets.map((p) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2834
3222
  PresetButton,
2835
3223
  {
2836
3224
  num: p.id,
@@ -2842,64 +3230,13 @@ function DspPresets({
2842
3230
  },
2843
3231
  p.id
2844
3232
  )),
2845
- saved && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: cls.savedFlash, children: "Saved" })
3233
+ saved && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: cls.savedFlash, children: "Saved" })
2846
3234
  ] })
2847
3235
  ] });
2848
3236
  }
2849
3237
 
2850
- // src/components/dsp/dspClasses.ts
2851
- var DSP_CLASS_DEFAULTS = {
2852
- root: "w-full h-full flex flex-col",
2853
- header: "relative w-full flex items-end justify-center gap-[0.3em] px-[1em] pt-[0.5em]",
2854
- content: "w-full flex-1 min-h-0",
2855
- 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",
2856
- tabActive: "bg-[#007ca0]",
2857
- message: "w-full h-full flex items-center justify-center text-gray-400 text-[2em]",
2858
- presetWrap: "absolute left-[1em] bottom-[0.3em] h-[4.5em] flex flex-col items-center",
2859
- presetLabel: "text-[#4f5152] text-[1.4em] leading-none",
2860
- preset: "w-[8em] h-full rounded-lg flex items-center justify-center transition-colors bg-[#4f5152] text-white",
2861
- presetActive: "bg-[#007ca0]",
2862
- presetPressed: "bg-[#006080]",
2863
- savedFlash: "absolute inset-0 flex items-center justify-center rounded bg-[#22c55e] text-white text-[1.2em] font-bold pointer-events-none",
2864
- strip: "flex flex-col items-center gap-[0.4em] h-full w-[9em] flex-none px-[0.3em]",
2865
- stripLabel: "text-[#4f5152] text-center leading-tight text-[1.1em] line-clamp-3",
2866
- levelReadout: "text-[1.1em] text-[#4f5152] leading-none opacity-70 mt-[0.5em]",
2867
- faderBar: "bg-[#c0c0c0] rounded",
2868
- faderFill: "bg-[#007ca0] rounded",
2869
- faderThumb: "bg-[#4f5152] rounded",
2870
- mute: "w-full h-full rounded-lg flex items-center justify-center transition-colors bg-[#4f5152] text-white",
2871
- muteActive: "bg-[#dc2626] text-white",
2872
- mixerCorner: "sticky z-30 flex items-center justify-center bg-[#282C34] text-[#4f5152] text-[1.1em] font-bold border border-black/30",
2873
- mixerChrome: "sticky flex items-center justify-center text-center bg-[#282C34] text-white text-[1.1em] leading-tight border border-black/30",
2874
- cell: "z-10 flex items-center justify-center border border-black/30",
2875
- cellOn: "bg-[#22c55e]",
2876
- cellOff: "bg-[#4f5152]"
2877
- };
2878
- var DSP_ICON_DEFAULTS = {
2879
- crosspointOn: "audio-volume-ok",
2880
- muteOff: "audio-volume-high",
2881
- muteOn: "audio-volume-mute",
2882
- muteOffFilter: "invert(65%) sepia(70%) saturate(500%) hue-rotate(80deg) brightness(110%) contrast(95%)",
2883
- muteOnFilter: "brightness(0) invert(1)"
2884
- };
2885
- function mergeDefined(defaults4, overrides) {
2886
- if (!overrides) return { ...defaults4 };
2887
- const out = { ...defaults4 };
2888
- Object.keys(overrides).forEach((k) => {
2889
- const v = overrides[k];
2890
- if (v !== void 0) out[k] = v;
2891
- });
2892
- return out;
2893
- }
2894
- function resolveDspClasses(classes) {
2895
- return mergeDefined(DSP_CLASS_DEFAULTS, classes);
2896
- }
2897
- function resolveDspIcons(icons) {
2898
- return mergeDefined(DSP_ICON_DEFAULTS, icons);
2899
- }
2900
-
2901
3238
  // src/components/dsp/CrisViewDspFull.tsx
2902
- var import_jsx_runtime16 = require("react/jsx-runtime");
3239
+ var import_jsx_runtime19 = require("react/jsx-runtime");
2903
3240
  function CrisViewDspFull({
2904
3241
  oid,
2905
3242
  presets,
@@ -2914,10 +3251,9 @@ function CrisViewDspFull({
2914
3251
  }) {
2915
3252
  const safeInitial = initialTab === "mix" && skipCrosspoints ? "in" : initialTab;
2916
3253
  const [tab, setTab] = (0, import_react9.useState)(safeInitial);
2917
- const status = (0, import_cris_webui_ch5_core11.useCustomObject)(oid, { subscribe: true });
2918
- const send = (0, import_cris_webui_ch5_core11.useCustomObjectSend)();
3254
+ const status = (0, import_cris_webui_ch5_core14.useCustomObject)(oid, { subscribe: true });
3255
+ const send = (0, import_cris_webui_ch5_core14.useCustomObjectSend)();
2919
3256
  const cls = resolveDspClasses(classes);
2920
- const ic = resolveDspIcons(icons);
2921
3257
  const tabs = [
2922
3258
  { id: "in", label: "Inputs" },
2923
3259
  ...skipCrosspoints ? [] : [{ id: "mix", label: "Mixer" }],
@@ -2926,9 +3262,9 @@ function CrisViewDspFull({
2926
3262
  const hasPresets = !!presets && presets.length > 0;
2927
3263
  const activeDevice = status?.pr?.dv ?? status?.ps?.dv;
2928
3264
  const activeLocal = status?.pr?.lc ?? status?.ps?.lc;
2929
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: `${cls.root} ${className ?? ""}`, children: [
2930
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: cls.header, children: [
2931
- hasPresets && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3265
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: `${cls.root} ${className ?? ""}`, children: [
3266
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: cls.header, children: [
3267
+ hasPresets && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2932
3268
  DspPresets,
2933
3269
  {
2934
3270
  oid,
@@ -2940,7 +3276,7 @@ function CrisViewDspFull({
2940
3276
  cls
2941
3277
  }
2942
3278
  ),
2943
- tabs.map((t) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3279
+ tabs.map((t) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2944
3280
  CrisButton,
2945
3281
  {
2946
3282
  text: t.label,
@@ -2951,39 +3287,82 @@ function CrisViewDspFull({
2951
3287
  },
2952
3288
  t.id
2953
3289
  )),
2954
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(CrisViewComm, { comm: status?.cm, className: "absolute right-[1em] bottom-[0.3em] text-[1.25em]" })
3290
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(CrisViewComm, { comm: status?.cm, className: "absolute right-[1em] bottom-[0.3em] text-[1.25em]" })
2955
3291
  ] }),
2956
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: cls.content, children: status === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: cls.message, children: "Conectando\u2026" }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
2957
- tab === "in" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2958
- DspIoPage,
2959
- {
2960
- status,
2961
- io: "in",
2962
- oid,
2963
- send,
2964
- skip: skipShowInput,
2965
- cls,
2966
- icons: ic,
2967
- emptyLabel: "Sin entradas"
2968
- }
2969
- ),
2970
- tab === "mix" && !skipCrosspoints && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DspMixer, { status, oid, send, cls, icons: ic }),
2971
- tab === "out" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2972
- DspIoPage,
2973
- {
2974
- status,
2975
- io: "out",
2976
- oid,
2977
- send,
2978
- skip: skipShowOutput,
2979
- cls,
2980
- icons: ic,
2981
- emptyLabel: "Sin salidas"
2982
- }
2983
- )
3292
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: cls.content, children: status === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: cls.message, children: "Conectando\u2026" }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
3293
+ tab === "in" && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(CrisViewDspInputs, { oid, skip: skipShowInput, classes, icons }),
3294
+ tab === "mix" && !skipCrosspoints && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(CrisViewDspMixer, { oid, classes, icons }),
3295
+ tab === "out" && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(CrisViewDspOutputs, { oid, skip: skipShowOutput, classes, icons })
2984
3296
  ] }) })
2985
3297
  ] });
2986
3298
  }
3299
+
3300
+ // src/components/dsp/CrisViewDspPresets.tsx
3301
+ var import_cris_webui_ch5_core15 = require("@imperosoft/cris-webui-ch5-core");
3302
+ var import_jsx_runtime20 = require("react/jsx-runtime");
3303
+ function CrisViewDspPresets({
3304
+ oid,
3305
+ presets,
3306
+ sustainedPresetFeedback = true,
3307
+ classes,
3308
+ className
3309
+ }) {
3310
+ const status = (0, import_cris_webui_ch5_core15.useCustomObject)(oid, { subscribe: true });
3311
+ const send = (0, import_cris_webui_ch5_core15.useCustomObjectSend)();
3312
+ const cls = resolveDspClasses({ presetWrap: "flex flex-col items-center", ...classes });
3313
+ const activeDevice = status?.pr?.dv ?? status?.ps?.dv;
3314
+ const activeLocal = status?.pr?.lc ?? status?.ps?.lc;
3315
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3316
+ DspPresets,
3317
+ {
3318
+ oid,
3319
+ send,
3320
+ presets,
3321
+ activeDevice,
3322
+ activeLocal,
3323
+ sustainedFeedback: sustainedPresetFeedback,
3324
+ cls
3325
+ }
3326
+ ) });
3327
+ }
3328
+
3329
+ // src/components/dsp/CrisViewDspChannels.tsx
3330
+ var import_cris_webui_ch5_core16 = require("@imperosoft/cris-webui-ch5-core");
3331
+ var import_jsx_runtime21 = require("react/jsx-runtime");
3332
+ function CrisViewDspChannels({
3333
+ oid,
3334
+ io,
3335
+ show,
3336
+ scroll = false,
3337
+ showEmpty = false,
3338
+ emptyLabel = "",
3339
+ classes,
3340
+ icons,
3341
+ className
3342
+ }) {
3343
+ const status = (0, import_cris_webui_ch5_core16.useCustomObject)(oid, { subscribe: true });
3344
+ const send = (0, import_cris_webui_ch5_core16.useCustomObjectSend)();
3345
+ const cls = resolveDspClasses(classes);
3346
+ const ic = resolveDspIcons(icons);
3347
+ if (status === void 0) {
3348
+ return showEmpty ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: cls.message, children: "Conectando\u2026" }) : null;
3349
+ }
3350
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
3351
+ DspIoPage,
3352
+ {
3353
+ status,
3354
+ io,
3355
+ oid,
3356
+ send,
3357
+ show,
3358
+ cls,
3359
+ icons: ic,
3360
+ emptyLabel,
3361
+ bare: !scroll,
3362
+ suppressEmpty: !showEmpty
3363
+ }
3364
+ ) });
3365
+ }
2987
3366
  // Annotate the CommonJS export names for ESM import in node:
2988
3367
  0 && (module.exports = {
2989
3368
  CrisButton,
@@ -2997,8 +3376,15 @@ function CrisViewDspFull({
2997
3376
  CrisText,
2998
3377
  CrisTextInput,
2999
3378
  CrisViewComm,
3379
+ CrisViewDspChannels,
3000
3380
  CrisViewDspFull,
3381
+ CrisViewDspInputs,
3382
+ CrisViewDspMixer,
3383
+ CrisViewDspOutputs,
3384
+ CrisViewDspPresets,
3385
+ buildIoSections,
3001
3386
  buildMixerAxis,
3387
+ buildMixerAxisGrouped,
3002
3388
  clampLevel,
3003
3389
  collapseStrips,
3004
3390
  commIndicators,
@@ -3006,6 +3392,7 @@ function CrisViewDspFull({
3006
3392
  getIconConfig,
3007
3393
  getIconFilter,
3008
3394
  getIconUrl,
3395
+ groupsFor,
3009
3396
  levelToPercent,
3010
3397
  linksFor,
3011
3398
  normalizeLinked