@imperosoft/cris-webui-components 1.3.0 → 1.5.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.mjs CHANGED
@@ -1038,7 +1038,7 @@ function CrisSpinner({
1038
1038
 
1039
1039
  // src/components/CrisOfflinePage.tsx
1040
1040
  import { useEffect as useEffect3, useState as useState3, useCallback as useCallback3 } from "react";
1041
- import { isNativeMode, useConnectionStore } from "@imperosoft/cris-webui-ch5-core";
1041
+ import { isNativeMode, getActiveTransport, useConnectionStore } from "@imperosoft/cris-webui-ch5-core";
1042
1042
  import { Fragment, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1043
1043
  var isFileProtocol = typeof window !== "undefined" && window.location.protocol === "file:";
1044
1044
  var isDevModeDefault = typeof window !== "undefined" && (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1");
@@ -1078,6 +1078,7 @@ function CrisOfflinePage({
1078
1078
  });
1079
1079
  const processorHost = processorHostProp ?? (typeof window !== "undefined" ? window.location.hostname : "localhost");
1080
1080
  const isError = connectionStatus === "error" || hasSeenError && connectionStatus !== "connected";
1081
+ const isWsTransport = getActiveTransport() === "ws";
1081
1082
  useEffect3(() => {
1082
1083
  if (connectionStatus === "error") {
1083
1084
  setHasSeenError(true);
@@ -1126,6 +1127,7 @@ function CrisOfflinePage({
1126
1127
  return () => clearTimeout(timer);
1127
1128
  }, [retryCountdown, retryConnection]);
1128
1129
  useEffect3(() => {
1130
+ if (isWsTransport) return;
1129
1131
  if (isError && isDeployedOnProcessor && loginRedirectDelay > 0 && !returnedFromLogin) {
1130
1132
  const timer = setTimeout(() => {
1131
1133
  try {
@@ -1136,7 +1138,7 @@ function CrisOfflinePage({
1136
1138
  }, loginRedirectDelay);
1137
1139
  return () => clearTimeout(timer);
1138
1140
  }
1139
- }, [isError, isDeployedOnProcessor, loginUrl, loginRedirectDelay, returnedFromLogin]);
1141
+ }, [isError, isDeployedOnProcessor, loginUrl, loginRedirectDelay, returnedFromLogin, isWsTransport]);
1140
1142
  const envInfo = {
1141
1143
  userAgent: typeof navigator !== "undefined" ? navigator.userAgent : "N/A",
1142
1144
  hasCrComLib: typeof window !== "undefined" && typeof window.CrComLib !== "undefined",
@@ -1157,9 +1159,9 @@ function CrisOfflinePage({
1157
1159
  return /* @__PURE__ */ jsx6("div", { className: `cris-offline-page absolute inset-0 bg-gray-900 flex flex-col items-center justify-center overflow-auto py-8 ${className}`, children: /* @__PURE__ */ jsxs5("div", { className: "text-center max-w-4xl px-4", children: [
1158
1160
  /* @__PURE__ */ jsx6("div", { className: "mb-8", children: /* @__PURE__ */ jsx6("div", { className: `w-16 h-16 mx-auto rounded-full flex items-center justify-center ${isError ? "bg-red-500/20" : "bg-yellow-500/20"}`, children: /* @__PURE__ */ jsx6("div", { className: `w-8 h-8 rounded-full ${isError ? "bg-red-500" : "bg-yellow-500 animate-pulse"}` }) }) }),
1159
1161
  /* @__PURE__ */ jsx6("h1", { className: "text-2xl font-bold text-white mb-2", children: isError ? isTimeoutError ? "Processor Unreachable" : "Connection Error" : "Connecting..." }),
1160
- /* @__PURE__ */ jsx6("p", { className: "text-gray-400", children: isError ? isDeployedOnProcessor ? returnedFromLogin ? "Connection failed after login. Please try again." : "Redirecting to login page..." : isTimeoutError ? "Could not connect to the processor. Check the IP address and network connection." : "WebSocket connection failed. SSL certificate may need to be accepted." : "Establishing connection to control system" }),
1161
- /* @__PURE__ */ jsx6("div", { className: "mt-8", children: /* @__PURE__ */ jsx6("span", { className: `px-4 py-2 rounded-full text-sm font-medium ${isError ? "bg-red-500/20 text-red-400" : "bg-yellow-500/20 text-yellow-400"}`, children: isError ? isTimeoutError ? "Connection Timeout" : isDevMode ? "Dev Mode - Auth Error" : isVC4 ? "VC4 - Not Authorized" : "Auth Error" : "Connecting" }) }),
1162
- isError && !isDeployedOnProcessor && isTimeoutError && /* @__PURE__ */ jsxs5("div", { className: "mt-[2em] bg-orange-500/10 border border-orange-500/30 rounded-xl", style: { overflow: "visible", padding: "1.5em 1.5em 2em 1.5em" }, children: [
1162
+ /* @__PURE__ */ jsx6("p", { className: "text-gray-400", children: isError ? isWsTransport ? "Control system is not responding. Reconnecting automatically..." : isDeployedOnProcessor ? returnedFromLogin ? "Connection failed after login. Please try again." : "Redirecting to login page..." : isTimeoutError ? "Could not connect to the processor. Check the IP address and network connection." : "WebSocket connection failed. SSL certificate may need to be accepted." : "Establishing connection to control system" }),
1163
+ /* @__PURE__ */ jsx6("div", { className: "mt-8", children: /* @__PURE__ */ jsx6("span", { className: `px-4 py-2 rounded-full text-sm font-medium ${isError ? "bg-red-500/20 text-red-400" : "bg-yellow-500/20 text-yellow-400"}`, children: isError ? isWsTransport ? "Reconnecting" : isTimeoutError ? "Connection Timeout" : isDevMode ? "Dev Mode - Auth Error" : isVC4 ? "VC4 - Not Authorized" : "Auth Error" : "Connecting" }) }),
1164
+ isError && !isWsTransport && !isDeployedOnProcessor && isTimeoutError && /* @__PURE__ */ jsxs5("div", { className: "mt-[2em] bg-orange-500/10 border border-orange-500/30 rounded-xl", style: { overflow: "visible", padding: "1.5em 1.5em 2em 1.5em" }, children: [
1163
1165
  /* @__PURE__ */ jsxs5("p", { className: "text-orange-400 font-medium", style: { fontSize: "1.2em", marginBottom: "1em" }, children: [
1164
1166
  "The processor at ",
1165
1167
  /* @__PURE__ */ jsx6("span", { className: "text-white", children: processorHost }),
@@ -1180,7 +1182,24 @@ function CrisOfflinePage({
1180
1182
  }
1181
1183
  ) })
1182
1184
  ] }),
1183
- isError && !isDeployedOnProcessor && !isTimeoutError && /* @__PURE__ */ jsx6("div", { className: "mt-[2em] bg-blue-500/10 border border-blue-500/30 rounded-xl", style: { overflow: "visible", padding: "1.5em 1.5em 2em 1.5em" }, children: retryCountdown !== null ? /* @__PURE__ */ jsxs5("div", { className: "text-center", style: { overflow: "visible" }, children: [
1185
+ isError && isWsTransport && /* @__PURE__ */ jsxs5("div", { className: "mt-[2em] bg-orange-500/10 border border-orange-500/30 rounded-xl", style: { overflow: "visible", padding: "1.5em 1.5em 2em 1.5em" }, children: [
1186
+ /* @__PURE__ */ jsxs5("p", { className: "text-orange-400 font-medium", style: { fontSize: "1.2em", marginBottom: "1em" }, children: [
1187
+ "The control system at ",
1188
+ /* @__PURE__ */ jsx6("span", { className: "text-white", children: processorHost }),
1189
+ " is not responding."
1190
+ ] }),
1191
+ /* @__PURE__ */ jsx6("p", { className: "text-gray-400", style: { fontSize: "1em", marginBottom: "1em" }, children: "It may be restarting (e.g. a program update). The connection retries automatically." }),
1192
+ /* @__PURE__ */ jsx6("div", { className: "flex gap-[1em] justify-center", style: { overflow: "visible" }, children: /* @__PURE__ */ jsx6(
1193
+ "button",
1194
+ {
1195
+ onClick: retryConnection,
1196
+ className: "bg-orange-600 hover:bg-orange-500 text-white font-medium rounded-xl transition-colors",
1197
+ style: { padding: "1em 2em", fontSize: "1.2em", whiteSpace: "nowrap", overflow: "visible" },
1198
+ children: "Retry Now"
1199
+ }
1200
+ ) })
1201
+ ] }),
1202
+ isError && !isWsTransport && !isDeployedOnProcessor && !isTimeoutError && /* @__PURE__ */ jsx6("div", { className: "mt-[2em] bg-blue-500/10 border border-blue-500/30 rounded-xl", style: { overflow: "visible", padding: "1.5em 1.5em 2em 1.5em" }, children: retryCountdown !== null ? /* @__PURE__ */ jsxs5("div", { className: "text-center", style: { overflow: "visible" }, children: [
1184
1203
  /* @__PURE__ */ jsxs5("p", { className: "text-blue-400 font-medium", style: { fontSize: "1.4em", marginBottom: "1em" }, children: [
1185
1204
  "Retrying connection in ",
1186
1205
  retryCountdown,
@@ -1219,7 +1238,7 @@ function CrisOfflinePage({
1219
1238
  ] }),
1220
1239
  certPageOpened && /* @__PURE__ */ jsx6("p", { className: "text-gray-400", style: { fontSize: "1em", marginTop: "1em" }, children: "Connection will auto-retry when you return to this page." })
1221
1240
  ] }) }),
1222
- isError && isDeployedOnProcessor && returnedFromLogin && /* @__PURE__ */ jsxs5("div", { className: "mt-[2em] bg-red-500/10 border border-red-500/30 rounded-xl", style: { overflow: "visible", padding: "1.5em 1.5em 2em 1.5em" }, children: [
1241
+ isError && !isWsTransport && isDeployedOnProcessor && returnedFromLogin && /* @__PURE__ */ jsxs5("div", { className: "mt-[2em] bg-red-500/10 border border-red-500/30 rounded-xl", style: { overflow: "visible", padding: "1.5em 1.5em 2em 1.5em" }, children: [
1223
1242
  /* @__PURE__ */ jsx6("p", { className: "text-red-400 font-medium", style: { fontSize: "1.2em", marginBottom: "1em" }, children: "Connection failed after authentication." }),
1224
1243
  /* @__PURE__ */ jsx6("p", { className: "text-gray-400", style: { fontSize: "1em", marginBottom: "1em" }, children: "This may be due to an expired session or WebSocket connection issue." }),
1225
1244
  /* @__PURE__ */ jsxs5("div", { className: "flex gap-[1em] justify-center flex-wrap", style: { overflow: "visible" }, children: [
@@ -2646,7 +2665,7 @@ function DspIoPage({
2646
2665
  const row = /* @__PURE__ */ jsx13(
2647
2666
  "div",
2648
2667
  {
2649
- className: "flex h-full items-stretch gap-[0.3em] px-[0.5em] py-[0.5em]",
2668
+ className: `flex h-full items-stretch gap-[0.3em] px-[0.5em] py-[0.5em]${bare ? "" : " w-max mx-auto"}`,
2650
2669
  style: { minWidth: "min-content" },
2651
2670
  children: sections.map((sec, i) => /* @__PURE__ */ jsxs11("div", { className: "flex flex-col h-full", children: [
2652
2671
  hasHeaders && // Header band: spans this group's strips. The 1px separator lives only
@@ -2706,6 +2725,7 @@ var DSP_CLASS_DEFAULTS = {
2706
2725
  faderThumb: "bg-[#4f5152] rounded",
2707
2726
  mute: "w-full h-full rounded-lg flex items-center justify-center transition-colors bg-[#4f5152] text-white",
2708
2727
  muteActive: "bg-[#dc2626] text-white",
2728
+ mixerBackplate: "bg-[#282C34]",
2709
2729
  mixerCorner: "sticky z-30 flex items-center justify-center bg-[#282C34] text-[#4f5152] text-[1.1em] font-bold border border-black/30",
2710
2730
  mixerChrome: "sticky flex items-center justify-center text-center bg-[#282C34] text-white leading-tight border border-black/30",
2711
2731
  cell: "z-10 flex items-center justify-center border border-black/30",
@@ -2800,15 +2820,42 @@ import { useCustomObject as useCustomObject6, useCustomObjectSend as useCustomOb
2800
2820
  // src/components/dsp/DspMixer.tsx
2801
2821
  import { useRef as useRef5 } from "react";
2802
2822
  import { Fragment as Fragment3, jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
2803
- var GROUP_W = "2.6em";
2804
- var COMMON_W = "9em";
2805
- var CHAN_W = "3em";
2806
- var GROUP_H = "2.2em";
2807
- var COMMON_H = "2.4em";
2808
- var CHAN_H = "2.1em";
2809
- var CELL_W = "7.5em";
2810
- var ROW_H = "4.5em";
2823
+ var DEFAULT_GROUP_W = "2.6em";
2824
+ var DEFAULT_COMMON_W = "9em";
2825
+ var DEFAULT_CHAN_W = "3em";
2826
+ var DEFAULT_GROUP_H = "2.2em";
2827
+ var DEFAULT_COMMON_H = "2.4em";
2828
+ var DEFAULT_CHAN_H = "2.1em";
2829
+ var DEFAULT_CELL_W = "7.5em";
2830
+ var DEFAULT_ROW_H = "4.5em";
2811
2831
  var DRAG_FACTOR = 0.6;
2832
+ function resolveGeometry(o, inputCount, outputCount) {
2833
+ const groupW = o?.groupW ?? DEFAULT_GROUP_W;
2834
+ const commonW = o?.commonW ?? DEFAULT_COMMON_W;
2835
+ const chanW = o?.chanW ?? DEFAULT_CHAN_W;
2836
+ const groupH = o?.groupH ?? DEFAULT_GROUP_H;
2837
+ const commonH = o?.commonH ?? DEFAULT_COMMON_H;
2838
+ const chanH = o?.chanH ?? DEFAULT_CHAN_H;
2839
+ const cellW = o?.cellW ?? DEFAULT_CELL_W;
2840
+ const rowH = o?.rowH ?? DEFAULT_ROW_H;
2841
+ return {
2842
+ groupW,
2843
+ commonW,
2844
+ chanW,
2845
+ groupH,
2846
+ commonH,
2847
+ chanH,
2848
+ cellW,
2849
+ rowH,
2850
+ topLink: groupH,
2851
+ topChan: `calc(${groupH} + ${commonH})`,
2852
+ leftLink: groupW,
2853
+ leftChan: `calc(${groupW} + ${commonW})`,
2854
+ z: { corner: 40, group: 30, link: 25, chan: 20 },
2855
+ inputCount,
2856
+ outputCount
2857
+ };
2858
+ }
2812
2859
  function CrosspointOnIcon({ icon }) {
2813
2860
  if (typeof icon !== "string") return /* @__PURE__ */ jsx16(Fragment3, { children: icon });
2814
2861
  return /* @__PURE__ */ jsx16(
@@ -2822,7 +2869,17 @@ function CrosspointOnIcon({ icon }) {
2822
2869
  }
2823
2870
  );
2824
2871
  }
2825
- function DspMixer({ status, oid, send, cls, icons }) {
2872
+ function DspMixer({
2873
+ status,
2874
+ oid,
2875
+ send,
2876
+ cls,
2877
+ icons,
2878
+ geometry,
2879
+ renderInputHeader,
2880
+ renderOutputHeader,
2881
+ renderCorner
2882
+ }) {
2826
2883
  const scrollRef = useRef5(null);
2827
2884
  const startRef = useRef5(null);
2828
2885
  const movedRef = useRef5(false);
@@ -2872,13 +2929,167 @@ function DspMixer({ status, oid, send, cls, icons }) {
2872
2929
  if (inputs.length === 0 || outputs.length === 0) {
2873
2930
  return /* @__PURE__ */ jsx16("div", { className: cls.message, children: "Sin crosspoints" });
2874
2931
  }
2932
+ const geom = resolveGeometry(geometry, inputs.length, outputs.length);
2875
2933
  const inAxis = buildMixerAxisGrouped(inputs, linksFor(status?.ln, "in"), groupsFor(status?.dg, "in"));
2876
2934
  const outAxis = buildMixerAxisGrouped(outputs, linksFor(status?.ln, "out"), groupsFor(status?.dg, "out"));
2877
- const topLink = GROUP_H;
2878
- const topChan = `calc(${GROUP_H} + ${COMMON_H})`;
2879
- const leftLink = GROUP_W;
2880
- const leftChan = `calc(${GROUP_W} + ${COMMON_W})`;
2881
- const Z = { corner: 40, group: 30, link: 25, chan: 20 };
2935
+ const { topLink, topChan, leftLink, leftChan, z: Z } = geom;
2936
+ const defaultInputHeader = () => inAxis.flatMap((it, ii) => {
2937
+ const col = 4 + ii;
2938
+ const nodes = [];
2939
+ if (it.groupStart) {
2940
+ nodes.push(
2941
+ /* @__PURE__ */ jsx16(
2942
+ "div",
2943
+ {
2944
+ className: cls.mixerGroup,
2945
+ style: { top: 0, zIndex: Z.group, gridRow: 1, gridColumn: `${col} / span ${it.groupSpan}` },
2946
+ title: it.groupName,
2947
+ children: /* @__PURE__ */ jsx16("span", { className: "text-[1.1em] line-clamp-2 px-[0.2em]", children: it.groupName })
2948
+ },
2949
+ `igrp:${it.id}`
2950
+ )
2951
+ );
2952
+ } else if (it.groupName === void 0) {
2953
+ nodes.push(
2954
+ /* @__PURE__ */ jsx16(
2955
+ "div",
2956
+ {
2957
+ className: cls.mixerChrome,
2958
+ style: { top: 0, zIndex: Z.group, gridRow: 1, gridColumn: col }
2959
+ },
2960
+ `ifill:${it.id}`
2961
+ )
2962
+ );
2963
+ }
2964
+ if (it.linkCommon !== void 0) {
2965
+ if (it.linkStart) {
2966
+ nodes.push(
2967
+ /* @__PURE__ */ jsx16(
2968
+ "div",
2969
+ {
2970
+ className: `${cls.mixerChrome} px-[0.2em] font-semibold`,
2971
+ style: { top: topLink, zIndex: Z.link, gridRow: 2, gridColumn: `${col} / span ${it.linkSpan}` },
2972
+ title: it.linkCommon,
2973
+ children: /* @__PURE__ */ jsx16("span", { className: "text-[1.1em] line-clamp-2", children: it.linkCommon })
2974
+ },
2975
+ `ilnk:${it.id}`
2976
+ )
2977
+ );
2978
+ }
2979
+ nodes.push(
2980
+ /* @__PURE__ */ jsx16(
2981
+ "div",
2982
+ {
2983
+ className: `${cls.mixerChrome} px-[0.2em]`,
2984
+ style: { top: topChan, zIndex: Z.chan, gridRow: 3, gridColumn: col },
2985
+ title: it.channelLabel,
2986
+ children: /* @__PURE__ */ jsx16("span", { className: "text-[1.1em] line-clamp-1", children: it.channelLabel })
2987
+ },
2988
+ `ichn:${it.id}`
2989
+ )
2990
+ );
2991
+ } else {
2992
+ nodes.push(
2993
+ /* @__PURE__ */ jsx16(
2994
+ "div",
2995
+ {
2996
+ className: cls.mixerChrome,
2997
+ style: { top: topLink, zIndex: Z.link, gridRow: 2, gridColumn: col }
2998
+ },
2999
+ `ilf:${it.id}`
3000
+ ),
3001
+ /* @__PURE__ */ jsx16(
3002
+ "div",
3003
+ {
3004
+ className: `${cls.mixerChrome} px-[0.2em]`,
3005
+ style: { top: topChan, zIndex: Z.chan, gridRow: 3, gridColumn: col },
3006
+ title: it.channelLabel,
3007
+ children: /* @__PURE__ */ jsx16("span", { className: "text-[1.1em] line-clamp-2", children: it.channelLabel })
3008
+ },
3009
+ `ichn:${it.id}`
3010
+ )
3011
+ );
3012
+ }
3013
+ return nodes;
3014
+ });
3015
+ const defaultOutputHeader = () => outAxis.flatMap((it, oo) => {
3016
+ const row = 4 + oo;
3017
+ const nodes = [];
3018
+ if (it.groupStart) {
3019
+ nodes.push(
3020
+ /* @__PURE__ */ jsx16(
3021
+ "div",
3022
+ {
3023
+ className: cls.mixerGroupV,
3024
+ style: {
3025
+ left: 0,
3026
+ zIndex: Z.group,
3027
+ gridColumn: 1,
3028
+ gridRow: `${row} / span ${it.groupSpan}`,
3029
+ writingMode: "vertical-rl",
3030
+ transform: "rotate(180deg)"
3031
+ },
3032
+ title: it.groupName,
3033
+ children: /* @__PURE__ */ jsx16("span", { className: "text-[1.1em] line-clamp-2", children: it.groupName })
3034
+ },
3035
+ `ogrp:${it.id}`
3036
+ )
3037
+ );
3038
+ } else if (it.groupName === void 0) {
3039
+ nodes.push(
3040
+ /* @__PURE__ */ jsx16(
3041
+ "div",
3042
+ {
3043
+ className: cls.mixerChrome,
3044
+ style: { left: 0, zIndex: Z.group, gridColumn: 1, gridRow: row }
3045
+ },
3046
+ `ofill:${it.id}`
3047
+ )
3048
+ );
3049
+ }
3050
+ if (it.linkCommon !== void 0) {
3051
+ if (it.linkStart) {
3052
+ nodes.push(
3053
+ /* @__PURE__ */ jsx16(
3054
+ "div",
3055
+ {
3056
+ className: `${cls.mixerChrome} justify-start px-[0.4em] font-semibold`,
3057
+ style: { left: leftLink, zIndex: Z.link, gridColumn: 2, gridRow: `${row} / span ${it.linkSpan}` },
3058
+ title: it.linkCommon,
3059
+ children: /* @__PURE__ */ jsx16("span", { className: "text-[1.1em] line-clamp-2 text-left", children: it.linkCommon })
3060
+ },
3061
+ `olnk:${it.id}`
3062
+ )
3063
+ );
3064
+ }
3065
+ nodes.push(
3066
+ /* @__PURE__ */ jsx16(
3067
+ "div",
3068
+ {
3069
+ className: cls.mixerChrome,
3070
+ style: { left: leftChan, zIndex: Z.chan, gridColumn: 3, gridRow: row },
3071
+ title: it.channelLabel,
3072
+ children: /* @__PURE__ */ jsx16("span", { className: "text-[1.1em] line-clamp-1", children: it.channelLabel })
3073
+ },
3074
+ `ochn:${it.id}`
3075
+ )
3076
+ );
3077
+ } else {
3078
+ nodes.push(
3079
+ /* @__PURE__ */ jsx16(
3080
+ "div",
3081
+ {
3082
+ className: `${cls.mixerChrome} justify-start px-[0.4em]`,
3083
+ style: { left: leftLink, zIndex: Z.chan, gridColumn: "2 / span 2", gridRow: row },
3084
+ title: it.channelLabel,
3085
+ children: /* @__PURE__ */ jsx16("span", { className: "text-[1.1em] line-clamp-2 text-left", children: it.channelLabel })
3086
+ },
3087
+ `ochn:${it.id}`
3088
+ )
3089
+ );
3090
+ }
3091
+ return nodes;
3092
+ });
2882
3093
  return /* @__PURE__ */ jsx16(
2883
3094
  "div",
2884
3095
  {
@@ -2887,15 +3098,15 @@ function DspMixer({ status, oid, send, cls, icons }) {
2887
3098
  onPointerMove,
2888
3099
  onPointerUp,
2889
3100
  onPointerCancel: onPointerUp,
2890
- className: "w-full h-full overflow-auto no-scrollbar relative touch-none select-none cursor-grab",
3101
+ className: "w-full h-full overflow-auto no-scrollbar relative touch-none select-none cursor-grab flex",
2891
3102
  children: /* @__PURE__ */ jsxs12(
2892
3103
  "div",
2893
3104
  {
2894
- className: "grid",
3105
+ className: "grid m-auto",
2895
3106
  style: {
2896
3107
  // cols: [out group][out link][out channel][inputs…] rows: [in group][in link][in channel][outputs…]
2897
- gridTemplateColumns: `${GROUP_W} ${COMMON_W} ${CHAN_W} repeat(${inputs.length}, ${CELL_W})`,
2898
- gridTemplateRows: `${GROUP_H} ${COMMON_H} ${CHAN_H} repeat(${outputs.length}, ${ROW_H})`,
3108
+ gridTemplateColumns: `${geom.groupW} ${geom.commonW} ${geom.chanW} repeat(${inputs.length}, ${geom.cellW})`,
3109
+ gridTemplateRows: `${geom.groupH} ${geom.commonH} ${geom.chanH} repeat(${outputs.length}, ${geom.rowH})`,
2899
3110
  minWidth: "min-content"
2900
3111
  },
2901
3112
  children: [
@@ -2903,7 +3114,7 @@ function DspMixer({ status, oid, send, cls, icons }) {
2903
3114
  "div",
2904
3115
  {
2905
3116
  "aria-hidden": true,
2906
- className: "sticky bg-[#282C34]",
3117
+ className: `sticky ${cls.mixerBackplate}`,
2907
3118
  style: { top: 0, zIndex: 15, gridColumn: "1 / -1", gridRow: "1 / span 3" }
2908
3119
  }
2909
3120
  ),
@@ -2911,7 +3122,7 @@ function DspMixer({ status, oid, send, cls, icons }) {
2911
3122
  "div",
2912
3123
  {
2913
3124
  "aria-hidden": true,
2914
- className: "sticky bg-[#282C34]",
3125
+ className: `sticky ${cls.mixerBackplate}`,
2915
3126
  style: { left: 0, zIndex: 15, gridColumn: "1 / span 3", gridRow: "1 / -1" }
2916
3127
  }
2917
3128
  ),
@@ -2920,166 +3131,11 @@ function DspMixer({ status, oid, send, cls, icons }) {
2920
3131
  {
2921
3132
  className: cls.mixerCorner,
2922
3133
  style: { top: 0, left: 0, zIndex: Z.corner, gridColumn: "1 / span 3", gridRow: "1 / span 3" },
2923
- children: "OUT \\ IN"
3134
+ children: renderCorner ? renderCorner(geom) : "OUT \\ IN"
2924
3135
  }
2925
3136
  ),
2926
- inAxis.flatMap((it, ii) => {
2927
- const col = 4 + ii;
2928
- const nodes = [];
2929
- if (it.groupStart) {
2930
- nodes.push(
2931
- /* @__PURE__ */ jsx16(
2932
- "div",
2933
- {
2934
- className: cls.mixerGroup,
2935
- style: { top: 0, zIndex: Z.group, gridRow: 1, gridColumn: `${col} / span ${it.groupSpan}` },
2936
- title: it.groupName,
2937
- children: /* @__PURE__ */ jsx16("span", { className: "text-[1.1em] line-clamp-2 px-[0.2em]", children: it.groupName })
2938
- },
2939
- `igrp:${it.id}`
2940
- )
2941
- );
2942
- } else if (it.groupName === void 0) {
2943
- nodes.push(
2944
- /* @__PURE__ */ jsx16(
2945
- "div",
2946
- {
2947
- className: cls.mixerChrome,
2948
- style: { top: 0, zIndex: Z.group, gridRow: 1, gridColumn: col }
2949
- },
2950
- `ifill:${it.id}`
2951
- )
2952
- );
2953
- }
2954
- if (it.linkCommon !== void 0) {
2955
- if (it.linkStart) {
2956
- nodes.push(
2957
- /* @__PURE__ */ jsx16(
2958
- "div",
2959
- {
2960
- className: `${cls.mixerChrome} px-[0.2em] font-semibold`,
2961
- style: { top: topLink, zIndex: Z.link, gridRow: 2, gridColumn: `${col} / span ${it.linkSpan}` },
2962
- title: it.linkCommon,
2963
- children: /* @__PURE__ */ jsx16("span", { className: "text-[1.1em] line-clamp-2", children: it.linkCommon })
2964
- },
2965
- `ilnk:${it.id}`
2966
- )
2967
- );
2968
- }
2969
- nodes.push(
2970
- /* @__PURE__ */ jsx16(
2971
- "div",
2972
- {
2973
- className: `${cls.mixerChrome} px-[0.2em]`,
2974
- style: { top: topChan, zIndex: Z.chan, gridRow: 3, gridColumn: col },
2975
- title: it.channelLabel,
2976
- children: /* @__PURE__ */ jsx16("span", { className: "text-[1.1em] line-clamp-1", children: it.channelLabel })
2977
- },
2978
- `ichn:${it.id}`
2979
- )
2980
- );
2981
- } else {
2982
- nodes.push(
2983
- /* @__PURE__ */ jsx16(
2984
- "div",
2985
- {
2986
- className: cls.mixerChrome,
2987
- style: { top: topLink, zIndex: Z.link, gridRow: 2, gridColumn: col }
2988
- },
2989
- `ilf:${it.id}`
2990
- ),
2991
- /* @__PURE__ */ jsx16(
2992
- "div",
2993
- {
2994
- className: `${cls.mixerChrome} px-[0.2em]`,
2995
- style: { top: topChan, zIndex: Z.chan, gridRow: 3, gridColumn: col },
2996
- title: it.channelLabel,
2997
- children: /* @__PURE__ */ jsx16("span", { className: "text-[1.1em] line-clamp-2", children: it.channelLabel })
2998
- },
2999
- `ichn:${it.id}`
3000
- )
3001
- );
3002
- }
3003
- return nodes;
3004
- }),
3005
- outAxis.flatMap((it, oo) => {
3006
- const row = 4 + oo;
3007
- const nodes = [];
3008
- if (it.groupStart) {
3009
- nodes.push(
3010
- /* @__PURE__ */ jsx16(
3011
- "div",
3012
- {
3013
- className: cls.mixerGroupV,
3014
- style: {
3015
- left: 0,
3016
- zIndex: Z.group,
3017
- gridColumn: 1,
3018
- gridRow: `${row} / span ${it.groupSpan}`,
3019
- writingMode: "vertical-rl",
3020
- transform: "rotate(180deg)"
3021
- },
3022
- title: it.groupName,
3023
- children: /* @__PURE__ */ jsx16("span", { className: "text-[1.1em] line-clamp-2", children: it.groupName })
3024
- },
3025
- `ogrp:${it.id}`
3026
- )
3027
- );
3028
- } else if (it.groupName === void 0) {
3029
- nodes.push(
3030
- /* @__PURE__ */ jsx16(
3031
- "div",
3032
- {
3033
- className: cls.mixerChrome,
3034
- style: { left: 0, zIndex: Z.group, gridColumn: 1, gridRow: row }
3035
- },
3036
- `ofill:${it.id}`
3037
- )
3038
- );
3039
- }
3040
- if (it.linkCommon !== void 0) {
3041
- if (it.linkStart) {
3042
- nodes.push(
3043
- /* @__PURE__ */ jsx16(
3044
- "div",
3045
- {
3046
- className: `${cls.mixerChrome} justify-start px-[0.4em] font-semibold`,
3047
- style: { left: leftLink, zIndex: Z.link, gridColumn: 2, gridRow: `${row} / span ${it.linkSpan}` },
3048
- title: it.linkCommon,
3049
- children: /* @__PURE__ */ jsx16("span", { className: "text-[1.1em] line-clamp-2 text-left", children: it.linkCommon })
3050
- },
3051
- `olnk:${it.id}`
3052
- )
3053
- );
3054
- }
3055
- nodes.push(
3056
- /* @__PURE__ */ jsx16(
3057
- "div",
3058
- {
3059
- className: cls.mixerChrome,
3060
- style: { left: leftChan, zIndex: Z.chan, gridColumn: 3, gridRow: row },
3061
- title: it.channelLabel,
3062
- children: /* @__PURE__ */ jsx16("span", { className: "text-[1.1em] line-clamp-1", children: it.channelLabel })
3063
- },
3064
- `ochn:${it.id}`
3065
- )
3066
- );
3067
- } else {
3068
- nodes.push(
3069
- /* @__PURE__ */ jsx16(
3070
- "div",
3071
- {
3072
- className: `${cls.mixerChrome} justify-start px-[0.4em]`,
3073
- style: { left: leftLink, zIndex: Z.chan, gridColumn: "2 / span 2", gridRow: row },
3074
- title: it.channelLabel,
3075
- children: /* @__PURE__ */ jsx16("span", { className: "text-[1.1em] line-clamp-2 text-left", children: it.channelLabel })
3076
- },
3077
- `ochn:${it.id}`
3078
- )
3079
- );
3080
- }
3081
- return nodes;
3082
- }),
3137
+ renderInputHeader ? renderInputHeader(inAxis, geom) : defaultInputHeader(),
3138
+ renderOutputHeader ? renderOutputHeader(outAxis, geom) : defaultOutputHeader(),
3083
3139
  outAxis.map(
3084
3140
  (orow, oo) => inAxis.map((icol, ii) => {
3085
3141
  const on = isOn(orow.id, icol.id);
@@ -3106,12 +3162,34 @@ function DspMixer({ status, oid, send, cls, icons }) {
3106
3162
 
3107
3163
  // src/components/dsp/CrisViewDspMixer.tsx
3108
3164
  import { jsx as jsx17 } from "react/jsx-runtime";
3109
- function CrisViewDspMixer({ oid, classes, icons, className }) {
3165
+ function CrisViewDspMixer({
3166
+ oid,
3167
+ classes,
3168
+ icons,
3169
+ className,
3170
+ geometry,
3171
+ renderInputHeader,
3172
+ renderOutputHeader,
3173
+ renderCorner
3174
+ }) {
3110
3175
  const status = useCustomObject6(oid, { subscribe: true });
3111
3176
  const send = useCustomObjectSend6();
3112
3177
  const cls = resolveDspClasses(classes);
3113
3178
  const ic = resolveDspIcons(icons);
3114
- return /* @__PURE__ */ jsx17("div", { className: `w-full h-full ${className ?? ""}`, children: status === void 0 ? /* @__PURE__ */ jsx17("div", { className: cls.message, children: "Conectando\u2026" }) : /* @__PURE__ */ jsx17(DspMixer, { status, oid, send, cls, icons: ic }) });
3179
+ return /* @__PURE__ */ jsx17("div", { className: `w-full h-full ${className ?? ""}`, children: status === void 0 ? /* @__PURE__ */ jsx17("div", { className: cls.message, children: "Conectando\u2026" }) : /* @__PURE__ */ jsx17(
3180
+ DspMixer,
3181
+ {
3182
+ status,
3183
+ oid,
3184
+ send,
3185
+ cls,
3186
+ icons: ic,
3187
+ geometry,
3188
+ renderInputHeader,
3189
+ renderOutputHeader,
3190
+ renderCorner
3191
+ }
3192
+ ) });
3115
3193
  }
3116
3194
 
3117
3195
  // src/components/dsp/DspPresets.tsx
@@ -3181,6 +3259,7 @@ function DspPresets({
3181
3259
 
3182
3260
  // src/components/dsp/CrisViewDspFull.tsx
3183
3261
  import { Fragment as Fragment4, jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
3262
+ var lastTabByOid = /* @__PURE__ */ new Map();
3184
3263
  function CrisViewDspFull({
3185
3264
  oid,
3186
3265
  presets,
@@ -3193,8 +3272,13 @@ function CrisViewDspFull({
3193
3272
  className,
3194
3273
  initialTab = "in"
3195
3274
  }) {
3196
- const safeInitial = initialTab === "mix" && skipCrosspoints ? "in" : initialTab;
3197
- const [tab, setTab] = useState6(safeInitial);
3275
+ const remembered = lastTabByOid.get(oid) ?? initialTab;
3276
+ const safeInitial = remembered === "mix" && skipCrosspoints ? "in" : remembered;
3277
+ const [tab, setTabState] = useState6(safeInitial);
3278
+ const setTab = (t) => {
3279
+ lastTabByOid.set(oid, t);
3280
+ setTabState(t);
3281
+ };
3198
3282
  const status = useCustomObject7(oid, { subscribe: true });
3199
3283
  const send = useCustomObjectSend7();
3200
3284
  const cls = resolveDspClasses(classes);