@imperosoft/cris-webui-components 1.2.1 → 1.4.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,232 @@ 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
- className: "flex h-full items-stretch gap-[0.3em] px-[0.5em] py-[0.5em]",
2705
+ className: `flex h-full items-stretch gap-[0.3em] px-[0.5em] py-[0.5em]${bare ? "" : " w-max mx-auto"}`,
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
+ mixerBackplate: "bg-[#282C34]",
2766
+ mixerCorner: "sticky z-30 flex items-center justify-center bg-[#282C34] text-[#4f5152] text-[1.1em] font-bold border border-black/30",
2767
+ mixerChrome: "sticky flex items-center justify-center text-center bg-[#282C34] text-white leading-tight border border-black/30",
2768
+ cell: "z-10 flex items-center justify-center border border-black/30",
2769
+ cellOn: "bg-[#22c55e]",
2770
+ cellOff: "bg-[#4f5152]"
2771
+ };
2772
+ var DSP_ICON_DEFAULTS = {
2773
+ crosspointOn: "audio-volume-ok",
2774
+ muteOff: "audio-volume-high",
2775
+ muteOn: "audio-volume-mute",
2776
+ muteOffFilter: "invert(65%) sepia(70%) saturate(500%) hue-rotate(80deg) brightness(110%) contrast(95%)",
2777
+ muteOnFilter: "brightness(0) invert(1)"
2778
+ };
2779
+ function mergeDefined(defaults4, overrides) {
2780
+ if (!overrides) return { ...defaults4 };
2781
+ const out = { ...defaults4 };
2782
+ Object.keys(overrides).forEach((k) => {
2783
+ const v = overrides[k];
2784
+ if (v !== void 0) out[k] = v;
2785
+ });
2786
+ return out;
2787
+ }
2788
+ function resolveDspClasses(classes) {
2789
+ return mergeDefined(DSP_CLASS_DEFAULTS, classes);
2790
+ }
2791
+ function resolveDspIcons(icons) {
2792
+ return mergeDefined(DSP_ICON_DEFAULTS, icons);
2793
+ }
2794
+
2795
+ // src/components/dsp/CrisViewDspInputs.tsx
2796
+ var import_jsx_runtime14 = require("react/jsx-runtime");
2797
+ function CrisViewDspInputs({
2798
+ oid,
2799
+ skip,
2800
+ emptyLabel = "Sin entradas",
2801
+ classes,
2802
+ icons,
2803
+ className
2804
+ }) {
2805
+ const status = (0, import_cris_webui_ch5_core11.useCustomObject)(oid, { subscribe: true });
2806
+ const send = (0, import_cris_webui_ch5_core11.useCustomObjectSend)();
2807
+ const cls = resolveDspClasses(classes);
2808
+ const ic = resolveDspIcons(icons);
2809
+ 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)(
2810
+ DspIoPage,
2811
+ {
2812
+ status,
2813
+ io: "in",
2814
+ oid,
2815
+ send,
2816
+ skip,
2817
+ cls,
2818
+ icons: ic,
2819
+ emptyLabel
2820
+ }
2821
+ ) });
2822
+ }
2823
+
2824
+ // src/components/dsp/CrisViewDspOutputs.tsx
2825
+ var import_cris_webui_ch5_core12 = require("@imperosoft/cris-webui-ch5-core");
2826
+ var import_jsx_runtime15 = require("react/jsx-runtime");
2827
+ function CrisViewDspOutputs({
2828
+ oid,
2829
+ skip,
2830
+ emptyLabel = "Sin salidas",
2831
+ classes,
2832
+ icons,
2833
+ className
2834
+ }) {
2835
+ const status = (0, import_cris_webui_ch5_core12.useCustomObject)(oid, { subscribe: true });
2836
+ const send = (0, import_cris_webui_ch5_core12.useCustomObjectSend)();
2837
+ const cls = resolveDspClasses(classes);
2838
+ const ic = resolveDspIcons(icons);
2839
+ 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)(
2840
+ DspIoPage,
2841
+ {
2842
+ status,
2843
+ io: "out",
2844
+ oid,
2845
+ send,
2846
+ skip,
2847
+ cls,
2848
+ icons: ic,
2849
+ emptyLabel
2570
2850
  }
2571
2851
  ) });
2572
2852
  }
2573
2853
 
2854
+ // src/components/dsp/CrisViewDspMixer.tsx
2855
+ var import_cris_webui_ch5_core13 = require("@imperosoft/cris-webui-ch5-core");
2856
+
2574
2857
  // src/components/dsp/DspMixer.tsx
2575
2858
  var import_react7 = require("react");
2576
- var import_jsx_runtime14 = require("react/jsx-runtime");
2577
- var COMMON_W = "9em";
2578
- var CHAN_W = "3em";
2579
- var COMMON_H = "2.4em";
2580
- var CHAN_H = "2.1em";
2581
- var CELL_W = "7.5em";
2582
- var ROW_H = "4.5em";
2859
+ var import_jsx_runtime16 = require("react/jsx-runtime");
2860
+ var DEFAULT_GROUP_W = "2.6em";
2861
+ var DEFAULT_COMMON_W = "9em";
2862
+ var DEFAULT_CHAN_W = "3em";
2863
+ var DEFAULT_GROUP_H = "2.2em";
2864
+ var DEFAULT_COMMON_H = "2.4em";
2865
+ var DEFAULT_CHAN_H = "2.1em";
2866
+ var DEFAULT_CELL_W = "7.5em";
2867
+ var DEFAULT_ROW_H = "4.5em";
2583
2868
  var DRAG_FACTOR = 0.6;
2869
+ function resolveGeometry(o, inputCount, outputCount) {
2870
+ const groupW = o?.groupW ?? DEFAULT_GROUP_W;
2871
+ const commonW = o?.commonW ?? DEFAULT_COMMON_W;
2872
+ const chanW = o?.chanW ?? DEFAULT_CHAN_W;
2873
+ const groupH = o?.groupH ?? DEFAULT_GROUP_H;
2874
+ const commonH = o?.commonH ?? DEFAULT_COMMON_H;
2875
+ const chanH = o?.chanH ?? DEFAULT_CHAN_H;
2876
+ const cellW = o?.cellW ?? DEFAULT_CELL_W;
2877
+ const rowH = o?.rowH ?? DEFAULT_ROW_H;
2878
+ return {
2879
+ groupW,
2880
+ commonW,
2881
+ chanW,
2882
+ groupH,
2883
+ commonH,
2884
+ chanH,
2885
+ cellW,
2886
+ rowH,
2887
+ topLink: groupH,
2888
+ topChan: `calc(${groupH} + ${commonH})`,
2889
+ leftLink: groupW,
2890
+ leftChan: `calc(${groupW} + ${commonW})`,
2891
+ z: { corner: 40, group: 30, link: 25, chan: 20 },
2892
+ inputCount,
2893
+ outputCount
2894
+ };
2895
+ }
2584
2896
  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)(
2897
+ if (typeof icon !== "string") return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_jsx_runtime16.Fragment, { children: icon });
2898
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2587
2899
  "img",
2588
2900
  {
2589
2901
  src: getIconUrl(icon),
@@ -2594,7 +2906,17 @@ function CrosspointOnIcon({ icon }) {
2594
2906
  }
2595
2907
  );
2596
2908
  }
2597
- function DspMixer({ status, oid, send, cls, icons }) {
2909
+ function DspMixer({
2910
+ status,
2911
+ oid,
2912
+ send,
2913
+ cls,
2914
+ icons,
2915
+ geometry,
2916
+ renderInputHeader,
2917
+ renderOutputHeader,
2918
+ renderCorner
2919
+ }) {
2598
2920
  const scrollRef = (0, import_react7.useRef)(null);
2599
2921
  const startRef = (0, import_react7.useRef)(null);
2600
2922
  const movedRef = (0, import_react7.useRef)(false);
@@ -2642,11 +2964,170 @@ function DspMixer({ status, oid, send, cls, icons }) {
2642
2964
  pendingRef.current = null;
2643
2965
  };
2644
2966
  if (inputs.length === 0 || outputs.length === 0) {
2645
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: cls.message, children: "Sin crosspoints" });
2967
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: cls.message, children: "Sin crosspoints" });
2646
2968
  }
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)(
2969
+ const geom = resolveGeometry(geometry, inputs.length, outputs.length);
2970
+ const inAxis = buildMixerAxisGrouped(inputs, linksFor(status?.ln, "in"), groupsFor(status?.dg, "in"));
2971
+ const outAxis = buildMixerAxisGrouped(outputs, linksFor(status?.ln, "out"), groupsFor(status?.dg, "out"));
2972
+ const { topLink, topChan, leftLink, leftChan, z: Z } = geom;
2973
+ const defaultInputHeader = () => inAxis.flatMap((it, ii) => {
2974
+ const col = 4 + ii;
2975
+ const nodes = [];
2976
+ if (it.groupStart) {
2977
+ nodes.push(
2978
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2979
+ "div",
2980
+ {
2981
+ className: cls.mixerGroup,
2982
+ style: { top: 0, zIndex: Z.group, gridRow: 1, gridColumn: `${col} / span ${it.groupSpan}` },
2983
+ title: it.groupName,
2984
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-[1.1em] line-clamp-2 px-[0.2em]", children: it.groupName })
2985
+ },
2986
+ `igrp:${it.id}`
2987
+ )
2988
+ );
2989
+ } else if (it.groupName === void 0) {
2990
+ nodes.push(
2991
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2992
+ "div",
2993
+ {
2994
+ className: cls.mixerChrome,
2995
+ style: { top: 0, zIndex: Z.group, gridRow: 1, gridColumn: col }
2996
+ },
2997
+ `ifill:${it.id}`
2998
+ )
2999
+ );
3000
+ }
3001
+ if (it.linkCommon !== void 0) {
3002
+ if (it.linkStart) {
3003
+ nodes.push(
3004
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3005
+ "div",
3006
+ {
3007
+ className: `${cls.mixerChrome} px-[0.2em] font-semibold`,
3008
+ style: { top: topLink, zIndex: Z.link, gridRow: 2, gridColumn: `${col} / span ${it.linkSpan}` },
3009
+ title: it.linkCommon,
3010
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-[1.1em] line-clamp-2", children: it.linkCommon })
3011
+ },
3012
+ `ilnk:${it.id}`
3013
+ )
3014
+ );
3015
+ }
3016
+ nodes.push(
3017
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3018
+ "div",
3019
+ {
3020
+ className: `${cls.mixerChrome} px-[0.2em]`,
3021
+ style: { top: topChan, zIndex: Z.chan, gridRow: 3, gridColumn: col },
3022
+ title: it.channelLabel,
3023
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-[1.1em] line-clamp-1", children: it.channelLabel })
3024
+ },
3025
+ `ichn:${it.id}`
3026
+ )
3027
+ );
3028
+ } else {
3029
+ nodes.push(
3030
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3031
+ "div",
3032
+ {
3033
+ className: cls.mixerChrome,
3034
+ style: { top: topLink, zIndex: Z.link, gridRow: 2, gridColumn: col }
3035
+ },
3036
+ `ilf:${it.id}`
3037
+ ),
3038
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3039
+ "div",
3040
+ {
3041
+ className: `${cls.mixerChrome} px-[0.2em]`,
3042
+ style: { top: topChan, zIndex: Z.chan, gridRow: 3, gridColumn: col },
3043
+ title: it.channelLabel,
3044
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-[1.1em] line-clamp-2", children: it.channelLabel })
3045
+ },
3046
+ `ichn:${it.id}`
3047
+ )
3048
+ );
3049
+ }
3050
+ return nodes;
3051
+ });
3052
+ const defaultOutputHeader = () => outAxis.flatMap((it, oo) => {
3053
+ const row = 4 + oo;
3054
+ const nodes = [];
3055
+ if (it.groupStart) {
3056
+ nodes.push(
3057
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3058
+ "div",
3059
+ {
3060
+ className: cls.mixerGroupV,
3061
+ style: {
3062
+ left: 0,
3063
+ zIndex: Z.group,
3064
+ gridColumn: 1,
3065
+ gridRow: `${row} / span ${it.groupSpan}`,
3066
+ writingMode: "vertical-rl",
3067
+ transform: "rotate(180deg)"
3068
+ },
3069
+ title: it.groupName,
3070
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-[1.1em] line-clamp-2", children: it.groupName })
3071
+ },
3072
+ `ogrp:${it.id}`
3073
+ )
3074
+ );
3075
+ } else if (it.groupName === void 0) {
3076
+ nodes.push(
3077
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3078
+ "div",
3079
+ {
3080
+ className: cls.mixerChrome,
3081
+ style: { left: 0, zIndex: Z.group, gridColumn: 1, gridRow: row }
3082
+ },
3083
+ `ofill:${it.id}`
3084
+ )
3085
+ );
3086
+ }
3087
+ if (it.linkCommon !== void 0) {
3088
+ if (it.linkStart) {
3089
+ nodes.push(
3090
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3091
+ "div",
3092
+ {
3093
+ className: `${cls.mixerChrome} justify-start px-[0.4em] font-semibold`,
3094
+ style: { left: leftLink, zIndex: Z.link, gridColumn: 2, gridRow: `${row} / span ${it.linkSpan}` },
3095
+ title: it.linkCommon,
3096
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-[1.1em] line-clamp-2 text-left", children: it.linkCommon })
3097
+ },
3098
+ `olnk:${it.id}`
3099
+ )
3100
+ );
3101
+ }
3102
+ nodes.push(
3103
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3104
+ "div",
3105
+ {
3106
+ className: cls.mixerChrome,
3107
+ style: { left: leftChan, zIndex: Z.chan, gridColumn: 3, gridRow: row },
3108
+ title: it.channelLabel,
3109
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-[1.1em] line-clamp-1", children: it.channelLabel })
3110
+ },
3111
+ `ochn:${it.id}`
3112
+ )
3113
+ );
3114
+ } else {
3115
+ nodes.push(
3116
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3117
+ "div",
3118
+ {
3119
+ className: `${cls.mixerChrome} justify-start px-[0.4em]`,
3120
+ style: { left: leftLink, zIndex: Z.chan, gridColumn: "2 / span 2", gridRow: row },
3121
+ title: it.channelLabel,
3122
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-[1.1em] line-clamp-2 text-left", children: it.channelLabel })
3123
+ },
3124
+ `ochn:${it.id}`
3125
+ )
3126
+ );
3127
+ }
3128
+ return nodes;
3129
+ });
3130
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2650
3131
  "div",
2651
3132
  {
2652
3133
  ref: scrollRef,
@@ -2654,126 +3135,58 @@ function DspMixer({ status, oid, send, cls, icons }) {
2654
3135
  onPointerMove,
2655
3136
  onPointerUp,
2656
3137
  onPointerCancel: onPointerUp,
2657
- className: "w-full h-full overflow-auto no-scrollbar relative touch-none select-none cursor-grab",
2658
- children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
3138
+ className: "w-full h-full overflow-auto no-scrollbar relative touch-none select-none cursor-grab flex",
3139
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
2659
3140
  "div",
2660
3141
  {
2661
- className: "grid",
3142
+ className: "grid m-auto",
2662
3143
  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})`,
3144
+ // cols: [out group][out link][out channel][inputs…] rows: [in group][in link][in channel][outputs…]
3145
+ gridTemplateColumns: `${geom.groupW} ${geom.commonW} ${geom.chanW} repeat(${inputs.length}, ${geom.cellW})`,
3146
+ gridTemplateRows: `${geom.groupH} ${geom.commonH} ${geom.chanH} repeat(${outputs.length}, ${geom.rowH})`,
2666
3147
  minWidth: "min-content"
2667
3148
  },
2668
3149
  children: [
2669
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3150
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2670
3151
  "div",
2671
3152
  {
2672
- className: cls.mixerCorner,
2673
- style: { top: 0, left: 0, gridColumn: "1 / span 2", gridRow: "1 / span 2" },
2674
- children: "OUT \\ IN"
3153
+ "aria-hidden": true,
3154
+ className: `sticky ${cls.mixerBackplate}`,
3155
+ style: { top: 0, zIndex: 15, gridColumn: "1 / -1", gridRow: "1 / span 3" }
2675
3156
  }
2676
3157
  ),
2677
- 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)(
2682
- "div",
2683
- {
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 })
2687
- },
2688
- `ich:${it.id}`
2689
- )
2690
- ];
2691
- if (it.groupStart) {
2692
- nodes.unshift(
2693
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2694
- "div",
2695
- {
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 })
2700
- },
2701
- `icc:${it.id}`
2702
- )
2703
- );
2704
- }
2705
- return nodes;
3158
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3159
+ "div",
3160
+ {
3161
+ "aria-hidden": true,
3162
+ className: `sticky ${cls.mixerBackplate}`,
3163
+ style: { left: 0, zIndex: 15, gridColumn: "1 / span 3", gridRow: "1 / -1" }
2706
3164
  }
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
- ];
2719
- }),
2720
- 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)(
2725
- "div",
2726
- {
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 })
2730
- },
2731
- `och:${it.id}`
2732
- )
2733
- ];
2734
- if (it.groupStart) {
2735
- nodes.unshift(
2736
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2737
- "div",
2738
- {
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 })
2743
- },
2744
- `occ:${it.id}`
2745
- )
2746
- );
2747
- }
2748
- return nodes;
3165
+ ),
3166
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3167
+ "div",
3168
+ {
3169
+ className: cls.mixerCorner,
3170
+ style: { top: 0, left: 0, zIndex: Z.corner, gridColumn: "1 / span 3", gridRow: "1 / span 3" },
3171
+ children: renderCorner ? renderCorner(geom) : "OUT \\ IN"
2749
3172
  }
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
- ];
2762
- }),
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)(
3173
+ ),
3174
+ renderInputHeader ? renderInputHeader(inAxis, geom) : defaultInputHeader(),
3175
+ renderOutputHeader ? renderOutputHeader(outAxis, geom) : defaultOutputHeader(),
3176
+ outAxis.map(
3177
+ (orow, oo) => inAxis.map((icol, ii) => {
3178
+ const on = isOn(orow.id, icol.id);
3179
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2767
3180
  "div",
2768
3181
  {
2769
3182
  "data-cell": true,
2770
- "data-in": i.id,
2771
- "data-out": o.id,
3183
+ "data-in": icol.id,
3184
+ "data-out": orow.id,
2772
3185
  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 })
3186
+ style: { gridColumn: 4 + ii, gridRow: 4 + oo },
3187
+ children: on && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(CrosspointOnIcon, { icon: icons.crosspointOn })
2775
3188
  },
2776
- `x:${i.id}:${o.id}`
3189
+ `x:${icol.id}:${orow.id}`
2777
3190
  );
2778
3191
  })
2779
3192
  )
@@ -2784,13 +3197,45 @@ function DspMixer({ status, oid, send, cls, icons }) {
2784
3197
  );
2785
3198
  }
2786
3199
 
3200
+ // src/components/dsp/CrisViewDspMixer.tsx
3201
+ var import_jsx_runtime17 = require("react/jsx-runtime");
3202
+ function CrisViewDspMixer({
3203
+ oid,
3204
+ classes,
3205
+ icons,
3206
+ className,
3207
+ geometry,
3208
+ renderInputHeader,
3209
+ renderOutputHeader,
3210
+ renderCorner
3211
+ }) {
3212
+ const status = (0, import_cris_webui_ch5_core13.useCustomObject)(oid, { subscribe: true });
3213
+ const send = (0, import_cris_webui_ch5_core13.useCustomObjectSend)();
3214
+ const cls = resolveDspClasses(classes);
3215
+ const ic = resolveDspIcons(icons);
3216
+ 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)(
3217
+ DspMixer,
3218
+ {
3219
+ status,
3220
+ oid,
3221
+ send,
3222
+ cls,
3223
+ icons: ic,
3224
+ geometry,
3225
+ renderInputHeader,
3226
+ renderOutputHeader,
3227
+ renderCorner
3228
+ }
3229
+ ) });
3230
+ }
3231
+
2787
3232
  // src/components/dsp/DspPresets.tsx
2788
3233
  var import_react8 = require("react");
2789
- var import_jsx_runtime15 = require("react/jsx-runtime");
3234
+ var import_jsx_runtime18 = require("react/jsx-runtime");
2790
3235
  var HOLD_MS = 2e3;
2791
3236
  var SAVED_MS = 1e3;
2792
3237
  function PresetButton({ num, name, active, onCall, onSave, cls }) {
2793
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3238
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2794
3239
  CrisButton,
2795
3240
  {
2796
3241
  onTap: onCall,
@@ -2800,9 +3245,9 @@ function PresetButton({ num, name, active, onCall, onSave, cls }) {
2800
3245
  className: cls.preset,
2801
3246
  classActive: cls.presetActive,
2802
3247
  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 })
3248
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "flex items-center justify-center gap-[0.5em]", children: [
3249
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "text-[1.6em] font-bold leading-none", children: num }),
3250
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "text-[1.2em] leading-none", children: name })
2806
3251
  ] })
2807
3252
  }
2808
3253
  );
@@ -2829,10 +3274,10 @@ function DspPresets({
2829
3274
  if (!sustainedFeedback) return false;
2830
3275
  return p.kind === "local" ? activeLocal === p.id : activeDevice === p.id;
2831
3276
  };
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)(
3277
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: cls.presetWrap, children: [
3278
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: cls.presetLabel, children: "Preset" }),
3279
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "relative flex-1 flex items-stretch gap-[0.3em] mt-[0.25em]", children: [
3280
+ presets.map((p) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2836
3281
  PresetButton,
2837
3282
  {
2838
3283
  num: p.id,
@@ -2844,64 +3289,14 @@ function DspPresets({
2844
3289
  },
2845
3290
  p.id
2846
3291
  )),
2847
- saved && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: cls.savedFlash, children: "Saved" })
3292
+ saved && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: cls.savedFlash, children: "Saved" })
2848
3293
  ] })
2849
3294
  ] });
2850
3295
  }
2851
3296
 
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
3297
  // src/components/dsp/CrisViewDspFull.tsx
2904
- var import_jsx_runtime16 = require("react/jsx-runtime");
3298
+ var import_jsx_runtime19 = require("react/jsx-runtime");
3299
+ var lastTabByOid = /* @__PURE__ */ new Map();
2905
3300
  function CrisViewDspFull({
2906
3301
  oid,
2907
3302
  presets,
@@ -2914,12 +3309,16 @@ function CrisViewDspFull({
2914
3309
  className,
2915
3310
  initialTab = "in"
2916
3311
  }) {
2917
- const safeInitial = initialTab === "mix" && skipCrosspoints ? "in" : initialTab;
2918
- 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)();
3312
+ const remembered = lastTabByOid.get(oid) ?? initialTab;
3313
+ const safeInitial = remembered === "mix" && skipCrosspoints ? "in" : remembered;
3314
+ const [tab, setTabState] = (0, import_react9.useState)(safeInitial);
3315
+ const setTab = (t) => {
3316
+ lastTabByOid.set(oid, t);
3317
+ setTabState(t);
3318
+ };
3319
+ const status = (0, import_cris_webui_ch5_core14.useCustomObject)(oid, { subscribe: true });
3320
+ const send = (0, import_cris_webui_ch5_core14.useCustomObjectSend)();
2921
3321
  const cls = resolveDspClasses(classes);
2922
- const ic = resolveDspIcons(icons);
2923
3322
  const tabs = [
2924
3323
  { id: "in", label: "Inputs" },
2925
3324
  ...skipCrosspoints ? [] : [{ id: "mix", label: "Mixer" }],
@@ -2928,9 +3327,9 @@ function CrisViewDspFull({
2928
3327
  const hasPresets = !!presets && presets.length > 0;
2929
3328
  const activeDevice = status?.pr?.dv ?? status?.ps?.dv;
2930
3329
  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)(
3330
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: `${cls.root} ${className ?? ""}`, children: [
3331
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: cls.header, children: [
3332
+ hasPresets && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2934
3333
  DspPresets,
2935
3334
  {
2936
3335
  oid,
@@ -2942,7 +3341,7 @@ function CrisViewDspFull({
2942
3341
  cls
2943
3342
  }
2944
3343
  ),
2945
- tabs.map((t) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3344
+ tabs.map((t) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2946
3345
  CrisButton,
2947
3346
  {
2948
3347
  text: t.label,
@@ -2953,39 +3352,82 @@ function CrisViewDspFull({
2953
3352
  },
2954
3353
  t.id
2955
3354
  )),
2956
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(CrisViewComm, { comm: status?.cm, className: "absolute right-[1em] bottom-[0.3em] text-[1.25em]" })
3355
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(CrisViewComm, { comm: status?.cm, className: "absolute right-[1em] bottom-[0.3em] text-[1.25em]" })
2957
3356
  ] }),
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
- )
3357
+ /* @__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: [
3358
+ tab === "in" && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(CrisViewDspInputs, { oid, skip: skipShowInput, classes, icons }),
3359
+ tab === "mix" && !skipCrosspoints && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(CrisViewDspMixer, { oid, classes, icons }),
3360
+ tab === "out" && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(CrisViewDspOutputs, { oid, skip: skipShowOutput, classes, icons })
2986
3361
  ] }) })
2987
3362
  ] });
2988
3363
  }
3364
+
3365
+ // src/components/dsp/CrisViewDspPresets.tsx
3366
+ var import_cris_webui_ch5_core15 = require("@imperosoft/cris-webui-ch5-core");
3367
+ var import_jsx_runtime20 = require("react/jsx-runtime");
3368
+ function CrisViewDspPresets({
3369
+ oid,
3370
+ presets,
3371
+ sustainedPresetFeedback = true,
3372
+ classes,
3373
+ className
3374
+ }) {
3375
+ const status = (0, import_cris_webui_ch5_core15.useCustomObject)(oid, { subscribe: true });
3376
+ const send = (0, import_cris_webui_ch5_core15.useCustomObjectSend)();
3377
+ const cls = resolveDspClasses({ presetWrap: "flex flex-col items-center", ...classes });
3378
+ const activeDevice = status?.pr?.dv ?? status?.ps?.dv;
3379
+ const activeLocal = status?.pr?.lc ?? status?.ps?.lc;
3380
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3381
+ DspPresets,
3382
+ {
3383
+ oid,
3384
+ send,
3385
+ presets,
3386
+ activeDevice,
3387
+ activeLocal,
3388
+ sustainedFeedback: sustainedPresetFeedback,
3389
+ cls
3390
+ }
3391
+ ) });
3392
+ }
3393
+
3394
+ // src/components/dsp/CrisViewDspChannels.tsx
3395
+ var import_cris_webui_ch5_core16 = require("@imperosoft/cris-webui-ch5-core");
3396
+ var import_jsx_runtime21 = require("react/jsx-runtime");
3397
+ function CrisViewDspChannels({
3398
+ oid,
3399
+ io,
3400
+ show,
3401
+ scroll = false,
3402
+ showEmpty = false,
3403
+ emptyLabel = "",
3404
+ classes,
3405
+ icons,
3406
+ className
3407
+ }) {
3408
+ const status = (0, import_cris_webui_ch5_core16.useCustomObject)(oid, { subscribe: true });
3409
+ const send = (0, import_cris_webui_ch5_core16.useCustomObjectSend)();
3410
+ const cls = resolveDspClasses(classes);
3411
+ const ic = resolveDspIcons(icons);
3412
+ if (status === void 0) {
3413
+ return showEmpty ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: cls.message, children: "Conectando\u2026" }) : null;
3414
+ }
3415
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
3416
+ DspIoPage,
3417
+ {
3418
+ status,
3419
+ io,
3420
+ oid,
3421
+ send,
3422
+ show,
3423
+ cls,
3424
+ icons: ic,
3425
+ emptyLabel,
3426
+ bare: !scroll,
3427
+ suppressEmpty: !showEmpty
3428
+ }
3429
+ ) });
3430
+ }
2989
3431
  // Annotate the CommonJS export names for ESM import in node:
2990
3432
  0 && (module.exports = {
2991
3433
  CrisButton,
@@ -2999,8 +3441,15 @@ function CrisViewDspFull({
2999
3441
  CrisText,
3000
3442
  CrisTextInput,
3001
3443
  CrisViewComm,
3444
+ CrisViewDspChannels,
3002
3445
  CrisViewDspFull,
3446
+ CrisViewDspInputs,
3447
+ CrisViewDspMixer,
3448
+ CrisViewDspOutputs,
3449
+ CrisViewDspPresets,
3450
+ buildIoSections,
3003
3451
  buildMixerAxis,
3452
+ buildMixerAxisGrouped,
3004
3453
  clampLevel,
3005
3454
  collapseStrips,
3006
3455
  commIndicators,
@@ -3008,6 +3457,7 @@ function CrisViewDspFull({
3008
3457
  getIconConfig,
3009
3458
  getIconFilter,
3010
3459
  getIconUrl,
3460
+ groupsFor,
3011
3461
  levelToPercent,
3012
3462
  linksFor,
3013
3463
  normalizeLinked