@imperosoft/cris-webui-components 1.2.1 → 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
@@ -2388,6 +2396,9 @@ function CrisViewComm({ comm, classes, icons, className }) {
2388
2396
 
2389
2397
  // src/components/dsp/CrisViewDspFull.tsx
2390
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
2391
2402
  var import_cris_webui_ch5_core11 = require("@imperosoft/cris-webui-ch5-core");
2392
2403
 
2393
2404
  // src/components/dsp/DspIoPage.tsx
@@ -2439,6 +2450,127 @@ function collapseStrips(channels, links) {
2439
2450
  function linksFor(ln, io) {
2440
2451
  return io === "in" ? ln?.ip : ln?.op;
2441
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
+ }
2442
2574
  function buildMixerAxis(channels, links) {
2443
2575
  const items = channels.map((c) => ({
2444
2576
  id: c.id,
@@ -2538,52 +2670,204 @@ function useHorizontalWheel(ref) {
2538
2670
 
2539
2671
  // src/components/dsp/DspIoPage.tsx
2540
2672
  var import_jsx_runtime13 = require("react/jsx-runtime");
2541
- 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
+ }) {
2542
2686
  const scrollRef = (0, import_react6.useRef)(null);
2543
2687
  useHorizontalWheel(scrollRef);
2688
+ const showSet = show ? new Set(show) : null;
2544
2689
  const skipSet = new Set(skip ?? []);
2690
+ const keep = (id) => (!showSet || showSet.has(id)) && !skipSet.has(id);
2545
2691
  const raw = (io === "in" ? status?.ip : status?.op) ?? [];
2546
- const channels = skipSet.size ? raw.filter((c) => !skipSet.has(c.id)) : raw;
2692
+ const channels = raw.filter((c) => keep(c.id));
2547
2693
  const allLinks = linksFor(status?.ln, io);
2548
- const links = skipSet.size ? allLinks?.filter((g) => (g.ch ?? []).some((id) => !skipSet.has(id))) : allLinks;
2549
- const strips = collapseStrips(channels, links);
2550
- if (strips.length === 0) {
2551
- 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 });
2552
2701
  }
2553
- 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)(
2554
2703
  "div",
2555
2704
  {
2556
2705
  className: "flex h-full items-stretch gap-[0.3em] px-[0.5em] py-[0.5em]",
2557
2706
  style: { minWidth: "min-content" },
2558
- children: strips.map((strip) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2559
- DspChannelStrip,
2560
- {
2561
- strip,
2562
- io,
2563
- oid,
2564
- send,
2565
- cls,
2566
- icons
2567
- },
2568
- strip.key
2569
- ))
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
2570
2819
  }
2571
2820
  ) });
2572
2821
  }
2573
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
+
2574
2856
  // src/components/dsp/DspMixer.tsx
2575
2857
  var import_react7 = require("react");
2576
- var import_jsx_runtime14 = require("react/jsx-runtime");
2858
+ var import_jsx_runtime16 = require("react/jsx-runtime");
2859
+ var GROUP_W = "2.6em";
2577
2860
  var COMMON_W = "9em";
2578
2861
  var CHAN_W = "3em";
2862
+ var GROUP_H = "2.2em";
2579
2863
  var COMMON_H = "2.4em";
2580
2864
  var CHAN_H = "2.1em";
2581
2865
  var CELL_W = "7.5em";
2582
2866
  var ROW_H = "4.5em";
2583
2867
  var DRAG_FACTOR = 0.6;
2584
2868
  function CrosspointOnIcon({ icon }) {
2585
- if (typeof icon !== "string") return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_jsx_runtime14.Fragment, { children: icon });
2586
- 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)(
2587
2871
  "img",
2588
2872
  {
2589
2873
  src: getIconUrl(icon),
@@ -2642,11 +2926,16 @@ function DspMixer({ status, oid, send, cls, icons }) {
2642
2926
  pendingRef.current = null;
2643
2927
  };
2644
2928
  if (inputs.length === 0 || outputs.length === 0) {
2645
- 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" });
2646
2930
  }
2647
- const inAxis = buildMixerAxis(inputs, linksFor(status?.ln, "in"));
2648
- const outAxis = buildMixerAxis(outputs, linksFor(status?.ln, "out"));
2649
- 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)(
2650
2939
  "div",
2651
2940
  {
2652
2941
  ref: scrollRef,
@@ -2655,125 +2944,212 @@ function DspMixer({ status, oid, send, cls, icons }) {
2655
2944
  onPointerUp,
2656
2945
  onPointerCancel: onPointerUp,
2657
2946
  className: "w-full h-full overflow-auto no-scrollbar relative touch-none select-none cursor-grab",
2658
- children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2947
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
2659
2948
  "div",
2660
2949
  {
2661
2950
  className: "grid",
2662
2951
  style: {
2663
- // cols: [output common][output channel][inputs…] rows: [in common][in channel][outputs…]
2664
- gridTemplateColumns: `${COMMON_W} ${CHAN_W} repeat(${inputs.length}, ${CELL_W})`,
2665
- 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})`,
2666
2955
  minWidth: "min-content"
2667
2956
  },
2668
2957
  children: [
2669
- /* @__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)(
2670
2975
  "div",
2671
2976
  {
2672
2977
  className: cls.mixerCorner,
2673
- 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" },
2674
2979
  children: "OUT \\ IN"
2675
2980
  }
2676
2981
  ),
2677
2982
  inAxis.flatMap((it, ii) => {
2678
- const col = 3 + ii;
2679
- if (it.groupCommon !== void 0) {
2680
- const nodes = [
2681
- /* @__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)(
2682
2988
  "div",
2683
2989
  {
2684
- className: `${cls.mixerChrome} z-20 px-[0.2em]`,
2685
- style: { top: 0, gridRow: "1 / span 2", gridColumn: col, alignItems: "flex-end", paddingBottom: "0.3em" },
2686
- 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 })
2687
2994
  },
2688
- `ich:${it.id}`
2995
+ `igrp:${it.id}`
2689
2996
  )
2690
- ];
2691
- if (it.groupStart) {
2692
- nodes.unshift(
2693
- /* @__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)(
2694
3014
  "div",
2695
3015
  {
2696
- className: `${cls.mixerChrome} z-25 px-[0.2em] font-semibold`,
2697
- style: { top: 0, gridRow: 1, gridColumn: `${col} / span ${it.groupSpan}` },
2698
- title: it.groupCommon,
2699
- 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 })
2700
3020
  },
2701
- `icc:${it.id}`
3021
+ `ilnk:${it.id}`
2702
3022
  )
2703
3023
  );
2704
3024
  }
2705
- 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
+ );
2706
3058
  }
2707
- return [
2708
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2709
- "div",
2710
- {
2711
- className: `${cls.mixerChrome} z-20 px-[0.2em]`,
2712
- style: { top: 0, gridRow: "1 / span 2", gridColumn: col, alignItems: "center" },
2713
- title: it.channelLabel,
2714
- children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "line-clamp-3", children: it.channelLabel })
2715
- },
2716
- `is:${it.id}`
2717
- )
2718
- ];
3059
+ return nodes;
2719
3060
  }),
2720
3061
  outAxis.flatMap((it, oo) => {
2721
- const row = 3 + oo;
2722
- if (it.groupCommon !== void 0) {
2723
- const nodes = [
2724
- /* @__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)(
2725
3087
  "div",
2726
3088
  {
2727
- className: `${cls.mixerChrome} z-20`,
2728
- style: { left: 0, gridColumn: "1 / span 2", gridRow: row, justifyContent: "flex-end", paddingRight: "0.6em" },
2729
- 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 }
2730
3091
  },
2731
- `och:${it.id}`
3092
+ `ofill:${it.id}`
2732
3093
  )
2733
- ];
2734
- if (it.groupStart) {
2735
- nodes.unshift(
2736
- /* @__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)(
2737
3100
  "div",
2738
3101
  {
2739
- className: `${cls.mixerChrome} z-25 justify-start px-[0.4em] font-semibold`,
2740
- style: { left: 0, gridColumn: 1, gridRow: `${row} / span ${it.groupSpan}` },
2741
- title: it.groupCommon,
2742
- 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 })
2743
3106
  },
2744
- `occ:${it.id}`
3107
+ `olnk:${it.id}`
2745
3108
  )
2746
3109
  );
2747
3110
  }
2748
- 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
+ );
2749
3136
  }
2750
- return [
2751
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2752
- "div",
2753
- {
2754
- className: `${cls.mixerChrome} z-20 justify-start px-[0.4em]`,
2755
- style: { left: 0, gridColumn: "1 / span 2", gridRow: row },
2756
- title: it.channelLabel,
2757
- children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "line-clamp-2 text-left", children: it.channelLabel })
2758
- },
2759
- `os:${it.id}`
2760
- )
2761
- ];
3137
+ return nodes;
2762
3138
  }),
2763
- outputs.map(
2764
- (o, oo) => inputs.map((i, ii) => {
2765
- const on = isOn(o.id, i.id);
2766
- 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)(
2767
3143
  "div",
2768
3144
  {
2769
3145
  "data-cell": true,
2770
- "data-in": i.id,
2771
- "data-out": o.id,
3146
+ "data-in": icol.id,
3147
+ "data-out": orow.id,
2772
3148
  className: `${cls.cell} ${on ? cls.cellOn : cls.cellOff}`,
2773
- style: { gridColumn: 3 + ii, gridRow: 3 + oo },
2774
- 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 })
2775
3151
  },
2776
- `x:${i.id}:${o.id}`
3152
+ `x:${icol.id}:${orow.id}`
2777
3153
  );
2778
3154
  })
2779
3155
  )
@@ -2784,13 +3160,23 @@ function DspMixer({ status, oid, send, cls, icons }) {
2784
3160
  );
2785
3161
  }
2786
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
+
2787
3173
  // src/components/dsp/DspPresets.tsx
2788
3174
  var import_react8 = require("react");
2789
- var import_jsx_runtime15 = require("react/jsx-runtime");
3175
+ var import_jsx_runtime18 = require("react/jsx-runtime");
2790
3176
  var HOLD_MS = 2e3;
2791
3177
  var SAVED_MS = 1e3;
2792
3178
  function PresetButton({ num, name, active, onCall, onSave, cls }) {
2793
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3179
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2794
3180
  CrisButton,
2795
3181
  {
2796
3182
  onTap: onCall,
@@ -2800,9 +3186,9 @@ function PresetButton({ num, name, active, onCall, onSave, cls }) {
2800
3186
  className: cls.preset,
2801
3187
  classActive: cls.presetActive,
2802
3188
  classPressed: cls.presetPressed,
2803
- children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "flex items-center justify-center gap-[0.5em]", children: [
2804
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "text-[1.6em] font-bold leading-none", children: num }),
2805
- /* @__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 })
2806
3192
  ] })
2807
3193
  }
2808
3194
  );
@@ -2829,10 +3215,10 @@ function DspPresets({
2829
3215
  if (!sustainedFeedback) return false;
2830
3216
  return p.kind === "local" ? activeLocal === p.id : activeDevice === p.id;
2831
3217
  };
2832
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: cls.presetWrap, children: [
2833
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: cls.presetLabel, children: "Preset" }),
2834
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "relative flex-1 flex items-stretch gap-[0.3em] mt-[0.25em]", children: [
2835
- 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)(
2836
3222
  PresetButton,
2837
3223
  {
2838
3224
  num: p.id,
@@ -2844,64 +3230,13 @@ function DspPresets({
2844
3230
  },
2845
3231
  p.id
2846
3232
  )),
2847
- 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" })
2848
3234
  ] })
2849
3235
  ] });
2850
3236
  }
2851
3237
 
2852
- // src/components/dsp/dspClasses.ts
2853
- var DSP_CLASS_DEFAULTS = {
2854
- root: "w-full h-full flex flex-col",
2855
- header: "relative w-full flex items-end justify-center gap-[0.3em] px-[1em] pt-[0.5em]",
2856
- content: "w-full flex-1 min-h-0",
2857
- 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",
2858
- tabActive: "bg-[#007ca0]",
2859
- message: "w-full h-full flex items-center justify-center text-gray-400 text-[2em]",
2860
- presetWrap: "absolute left-[1em] bottom-[0.3em] h-[4.5em] flex flex-col items-center",
2861
- presetLabel: "text-[#4f5152] text-[1.4em] leading-none",
2862
- preset: "w-[8em] h-full rounded-lg flex items-center justify-center transition-colors bg-[#4f5152] text-white",
2863
- presetActive: "bg-[#007ca0]",
2864
- presetPressed: "bg-[#006080]",
2865
- savedFlash: "absolute inset-0 flex items-center justify-center rounded bg-[#22c55e] text-white text-[1.2em] font-bold pointer-events-none",
2866
- strip: "flex flex-col items-center gap-[0.4em] h-full w-[9em] flex-none px-[0.3em]",
2867
- stripLabel: "text-[#4f5152] text-center leading-tight text-[1.1em] line-clamp-3",
2868
- levelReadout: "text-[1.1em] text-[#4f5152] leading-none opacity-70 mt-[0.5em]",
2869
- faderBar: "bg-[#c0c0c0] rounded",
2870
- faderFill: "bg-[#007ca0] rounded",
2871
- faderThumb: "bg-[#4f5152] rounded",
2872
- mute: "w-full h-full rounded-lg flex items-center justify-center transition-colors bg-[#4f5152] text-white",
2873
- muteActive: "bg-[#dc2626] text-white",
2874
- mixerCorner: "sticky z-30 flex items-center justify-center bg-[#282C34] text-[#4f5152] text-[1.1em] font-bold border border-black/30",
2875
- mixerChrome: "sticky flex items-center justify-center text-center bg-[#282C34] text-white text-[1.1em] leading-tight border border-black/30",
2876
- cell: "z-10 flex items-center justify-center border border-black/30",
2877
- cellOn: "bg-[#22c55e]",
2878
- cellOff: "bg-[#4f5152]"
2879
- };
2880
- var DSP_ICON_DEFAULTS = {
2881
- crosspointOn: "audio-volume-ok",
2882
- muteOff: "audio-volume-high",
2883
- muteOn: "audio-volume-mute",
2884
- muteOffFilter: "invert(65%) sepia(70%) saturate(500%) hue-rotate(80deg) brightness(110%) contrast(95%)",
2885
- muteOnFilter: "brightness(0) invert(1)"
2886
- };
2887
- function mergeDefined(defaults4, overrides) {
2888
- if (!overrides) return { ...defaults4 };
2889
- const out = { ...defaults4 };
2890
- Object.keys(overrides).forEach((k) => {
2891
- const v = overrides[k];
2892
- if (v !== void 0) out[k] = v;
2893
- });
2894
- return out;
2895
- }
2896
- function resolveDspClasses(classes) {
2897
- return mergeDefined(DSP_CLASS_DEFAULTS, classes);
2898
- }
2899
- function resolveDspIcons(icons) {
2900
- return mergeDefined(DSP_ICON_DEFAULTS, icons);
2901
- }
2902
-
2903
3238
  // src/components/dsp/CrisViewDspFull.tsx
2904
- var import_jsx_runtime16 = require("react/jsx-runtime");
3239
+ var import_jsx_runtime19 = require("react/jsx-runtime");
2905
3240
  function CrisViewDspFull({
2906
3241
  oid,
2907
3242
  presets,
@@ -2916,10 +3251,9 @@ function CrisViewDspFull({
2916
3251
  }) {
2917
3252
  const safeInitial = initialTab === "mix" && skipCrosspoints ? "in" : initialTab;
2918
3253
  const [tab, setTab] = (0, import_react9.useState)(safeInitial);
2919
- const status = (0, import_cris_webui_ch5_core11.useCustomObject)(oid, { subscribe: true });
2920
- 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)();
2921
3256
  const cls = resolveDspClasses(classes);
2922
- const ic = resolveDspIcons(icons);
2923
3257
  const tabs = [
2924
3258
  { id: "in", label: "Inputs" },
2925
3259
  ...skipCrosspoints ? [] : [{ id: "mix", label: "Mixer" }],
@@ -2928,9 +3262,9 @@ function CrisViewDspFull({
2928
3262
  const hasPresets = !!presets && presets.length > 0;
2929
3263
  const activeDevice = status?.pr?.dv ?? status?.ps?.dv;
2930
3264
  const activeLocal = status?.pr?.lc ?? status?.ps?.lc;
2931
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: `${cls.root} ${className ?? ""}`, children: [
2932
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: cls.header, children: [
2933
- 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)(
2934
3268
  DspPresets,
2935
3269
  {
2936
3270
  oid,
@@ -2942,7 +3276,7 @@ function CrisViewDspFull({
2942
3276
  cls
2943
3277
  }
2944
3278
  ),
2945
- tabs.map((t) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3279
+ tabs.map((t) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2946
3280
  CrisButton,
2947
3281
  {
2948
3282
  text: t.label,
@@ -2953,39 +3287,82 @@ function CrisViewDspFull({
2953
3287
  },
2954
3288
  t.id
2955
3289
  )),
2956
- /* @__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]" })
2957
3291
  ] }),
2958
- /* @__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: [
2959
- tab === "in" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2960
- DspIoPage,
2961
- {
2962
- status,
2963
- io: "in",
2964
- oid,
2965
- send,
2966
- skip: skipShowInput,
2967
- cls,
2968
- icons: ic,
2969
- emptyLabel: "Sin entradas"
2970
- }
2971
- ),
2972
- tab === "mix" && !skipCrosspoints && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DspMixer, { status, oid, send, cls, icons: ic }),
2973
- tab === "out" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2974
- DspIoPage,
2975
- {
2976
- status,
2977
- io: "out",
2978
- oid,
2979
- send,
2980
- skip: skipShowOutput,
2981
- cls,
2982
- icons: ic,
2983
- emptyLabel: "Sin salidas"
2984
- }
2985
- )
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 })
2986
3296
  ] }) })
2987
3297
  ] });
2988
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
+ }
2989
3366
  // Annotate the CommonJS export names for ESM import in node:
2990
3367
  0 && (module.exports = {
2991
3368
  CrisButton,
@@ -2999,8 +3376,15 @@ function CrisViewDspFull({
2999
3376
  CrisText,
3000
3377
  CrisTextInput,
3001
3378
  CrisViewComm,
3379
+ CrisViewDspChannels,
3002
3380
  CrisViewDspFull,
3381
+ CrisViewDspInputs,
3382
+ CrisViewDspMixer,
3383
+ CrisViewDspOutputs,
3384
+ CrisViewDspPresets,
3385
+ buildIoSections,
3003
3386
  buildMixerAxis,
3387
+ buildMixerAxisGrouped,
3004
3388
  clampLevel,
3005
3389
  collapseStrips,
3006
3390
  commIndicators,
@@ -3008,6 +3392,7 @@ function CrisViewDspFull({
3008
3392
  getIconConfig,
3009
3393
  getIconFilter,
3010
3394
  getIconUrl,
3395
+ groupsFor,
3011
3396
  levelToPercent,
3012
3397
  linksFor,
3013
3398
  normalizeLinked