@imperosoft/cris-webui-components 1.1.0-beta.0 → 1.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -58,8 +58,10 @@ interface CrisButtonProps {
58
58
  onPress?: () => void;
59
59
  /** Custom release handler */
60
60
  onRelease?: () => void;
61
+ /** Enable debug logging for this button */
62
+ debug?: boolean;
61
63
  }
62
- declare function CrisButton({ join, joinFeedback, joinEnable, joinVisible, text, textPressed, textSelected, icon, iconName, iconClass, iconSize, iconContainerSize, iconStyle, iconPosition, iconNameActive, iconClassActive, iconStyleActive, showControlFeedback, showLocalFeedback, suppressKeyClicks, smartId, className, classActive, classPressed, classDisabled, children, onPress, onRelease, }: CrisButtonProps): react_jsx_runtime.JSX.Element | null;
64
+ declare function CrisButton({ join, joinFeedback, joinEnable, joinVisible, text, textPressed, textSelected, icon, iconName, iconClass, iconSize, iconContainerSize, iconStyle, iconPosition, iconNameActive, iconClassActive, iconStyleActive, showControlFeedback, showLocalFeedback, suppressKeyClicks, smartId, className, classActive, classPressed, classDisabled, children, onPress, onRelease, debug, }: CrisButtonProps): react_jsx_runtime.JSX.Element | null;
63
65
 
64
66
  interface CrisTextProps {
65
67
  /** Serial join for indirect text */
package/dist/index.d.ts CHANGED
@@ -58,8 +58,10 @@ interface CrisButtonProps {
58
58
  onPress?: () => void;
59
59
  /** Custom release handler */
60
60
  onRelease?: () => void;
61
+ /** Enable debug logging for this button */
62
+ debug?: boolean;
61
63
  }
62
- declare function CrisButton({ join, joinFeedback, joinEnable, joinVisible, text, textPressed, textSelected, icon, iconName, iconClass, iconSize, iconContainerSize, iconStyle, iconPosition, iconNameActive, iconClassActive, iconStyleActive, showControlFeedback, showLocalFeedback, suppressKeyClicks, smartId, className, classActive, classPressed, classDisabled, children, onPress, onRelease, }: CrisButtonProps): react_jsx_runtime.JSX.Element | null;
64
+ declare function CrisButton({ join, joinFeedback, joinEnable, joinVisible, text, textPressed, textSelected, icon, iconName, iconClass, iconSize, iconContainerSize, iconStyle, iconPosition, iconNameActive, iconClassActive, iconStyleActive, showControlFeedback, showLocalFeedback, suppressKeyClicks, smartId, className, classActive, classPressed, classDisabled, children, onPress, onRelease, debug, }: CrisButtonProps): react_jsx_runtime.JSX.Element | null;
63
65
 
64
66
  interface CrisTextProps {
65
67
  /** Serial join for indirect text */
package/dist/index.js CHANGED
@@ -118,8 +118,11 @@ function CrisButton({
118
118
  classDisabled = "",
119
119
  children,
120
120
  onPress,
121
- onRelease
121
+ onRelease,
122
+ debug = false
122
123
  }) {
124
+ const log = debug ? (msg, data) => console.log(`[CrisButton:${join}] ${msg}`, data ?? "") : () => {
125
+ };
123
126
  const [pressed, setPressed] = (0, import_react.useState)(false);
124
127
  const pressedRef = (0, import_react.useRef)(false);
125
128
  const touchingRef = (0, import_react.useRef)(false);
@@ -132,16 +135,19 @@ function CrisButton({
132
135
  const isEnabled = joinEnable == null ? true : enabled;
133
136
  const isVisible = joinVisible == null ? true : visible;
134
137
  (0, import_react.useEffect)(() => {
138
+ log("visibility effect", { isVisible, pressedRef: pressedRef.current });
135
139
  if (!isVisible && pressedRef.current) {
140
+ log("VISIBILITY RELEASE - button hidden while pressed");
136
141
  pressedRef.current = false;
137
142
  setPressed(false);
138
143
  touchingRef.current = false;
139
144
  touchStartedHereRef.current = false;
140
145
  if (join != null && smartId == null) {
146
+ log("sending release via dSet(false)");
141
147
  dSet(join, false);
142
148
  }
143
149
  }
144
- }, [isVisible, join, smartId, dSet]);
150
+ }, [isVisible, join, smartId, dSet, log]);
145
151
  const hasControlFeedback = showControlFeedback && feedbackJoin != null && feedback;
146
152
  const hasPressedFeedback = showLocalFeedback && pressed && isEnabled;
147
153
  const isActive = hasControlFeedback || hasPressedFeedback;
@@ -152,42 +158,69 @@ function CrisButton({
152
158
  currentText = textSelected;
153
159
  }
154
160
  const handlePress = () => {
155
- if (suppressKeyClicks) return;
156
- if (pressedRef.current) return;
161
+ log("handlePress called", { suppressKeyClicks, pressedRef: pressedRef.current, isEnabled });
162
+ if (suppressKeyClicks) {
163
+ log("BLOCKED: suppressKeyClicks");
164
+ return;
165
+ }
166
+ if (pressedRef.current) {
167
+ log("BLOCKED: already pressed");
168
+ return;
169
+ }
157
170
  pressedRef.current = true;
158
171
  setPressed(true);
159
- if (!isEnabled) return;
172
+ if (!isEnabled) {
173
+ log("SKIPPED dSet: not enabled");
174
+ return;
175
+ }
160
176
  onPress?.();
161
177
  if (join != null && smartId == null) {
178
+ log("SENDING PRESS via dSet(true)");
162
179
  dSet(join, true);
163
180
  }
164
181
  };
165
182
  const handleRelease = () => {
166
- if (suppressKeyClicks) return;
167
- if (!pressedRef.current) return;
183
+ log("handleRelease called", { suppressKeyClicks, pressedRef: pressedRef.current, isEnabled });
184
+ if (suppressKeyClicks) {
185
+ log("BLOCKED: suppressKeyClicks");
186
+ return;
187
+ }
188
+ if (!pressedRef.current) {
189
+ log("BLOCKED: not pressed");
190
+ return;
191
+ }
168
192
  pressedRef.current = false;
169
193
  setPressed(false);
170
- if (!isEnabled) return;
194
+ if (!isEnabled) {
195
+ log("SKIPPED dSet: not enabled");
196
+ return;
197
+ }
171
198
  onRelease?.();
172
199
  if (join != null && smartId == null) {
200
+ log("SENDING RELEASE via dSet(false)");
173
201
  dSet(join, false);
174
202
  }
175
203
  };
176
204
  const handleTouchStart = () => {
205
+ log("handleTouchStart");
177
206
  touchStart();
178
207
  touchingRef.current = true;
179
208
  touchStartedHereRef.current = true;
180
209
  handlePress();
181
210
  };
182
211
  const handleTouchEnd = () => {
212
+ log("handleTouchEnd", { touchStartedHereRef: touchStartedHereRef.current });
183
213
  touchEnd();
184
214
  touchingRef.current = true;
185
215
  if (touchStartedHereRef.current) {
186
216
  touchStartedHereRef.current = false;
187
217
  handleRelease();
218
+ } else {
219
+ log("SKIPPED handleRelease: touch did not start here");
188
220
  }
189
221
  };
190
222
  const handleTouchCancel = () => {
223
+ log("handleTouchCancel");
191
224
  touchEnd();
192
225
  touchingRef.current = true;
193
226
  touchStartedHereRef.current = false;
@@ -805,7 +838,9 @@ function CrisOfflinePage({
805
838
  className = ""
806
839
  }) {
807
840
  const connectionStatus = (0, import_cris_webui_ch5_core6.useConnectionStore)((state) => state.status);
841
+ const errorReason = (0, import_cris_webui_ch5_core6.useConnectionStore)((state) => state.errorReason);
808
842
  const [hasSeenError, setHasSeenError] = (0, import_react3.useState)(false);
843
+ const [lastErrorReason, setLastErrorReason] = (0, import_react3.useState)(null);
809
844
  const [certPageOpened, setCertPageOpened] = (0, import_react3.useState)(false);
810
845
  const [retryCountdown, setRetryCountdown] = (0, import_react3.useState)(null);
811
846
  const processorHost = processorHostProp ?? (typeof window !== "undefined" ? window.location.hostname : "localhost");
@@ -813,10 +848,13 @@ function CrisOfflinePage({
813
848
  (0, import_react3.useEffect)(() => {
814
849
  if (connectionStatus === "error") {
815
850
  setHasSeenError(true);
851
+ setLastErrorReason(errorReason);
816
852
  } else if (connectionStatus === "connected") {
817
853
  setHasSeenError(false);
854
+ setLastErrorReason(null);
818
855
  }
819
- }, [connectionStatus]);
856
+ }, [connectionStatus, errorReason]);
857
+ const isTimeoutError = lastErrorReason === "timeout";
820
858
  const loginUrl = `https://${processorHost}${loginPath}`;
821
859
  const certUrl = `https://${processorHost}:${wsPort}/`;
822
860
  const openCertPage = (0, import_react3.useCallback)(() => {
@@ -874,14 +912,36 @@ function CrisOfflinePage({
874
912
  isDevMode,
875
913
  isVC4,
876
914
  processorHost: isDevMode ? processorHost : void 0,
877
- hasAuthToken: isDevMode ? hasAuthToken : void 0
915
+ hasAuthToken: isDevMode ? hasAuthToken : void 0,
916
+ errorReason: lastErrorReason
878
917
  };
879
918
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("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__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "text-center max-w-4xl px-4", children: [
880
919
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mb-8", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("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__ */ (0, import_jsx_runtime6.jsx)("div", { className: `w-8 h-8 rounded-full ${isError ? "bg-red-500" : "bg-yellow-500 animate-pulse"}` }) }) }),
881
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h1", { className: "text-2xl font-bold text-white mb-2", children: isError ? "Connection Error" : "Connecting..." }),
882
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-gray-400", children: isError ? isDeployedOnProcessor ? "Redirecting to login page..." : "WebSocket connection failed. SSL certificate may need to be accepted." : "Establishing connection to control system" }),
883
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mt-8", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("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 ? isDevMode ? "Dev Mode - Auth Error" : isVC4 ? "VC4 - Not Authorized" : "Auth Error" : "Connecting" }) }),
884
- isError && !isDeployedOnProcessor && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("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__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "text-center", style: { overflow: "visible" }, children: [
920
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h1", { className: "text-2xl font-bold text-white mb-2", children: isError ? isTimeoutError ? "Processor Unreachable" : "Connection Error" : "Connecting..." }),
921
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-gray-400", children: isError ? isDeployedOnProcessor ? "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" }),
922
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mt-8", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("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" }) }),
923
+ isError && !isDeployedOnProcessor && isTimeoutError && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("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: [
924
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "text-orange-400 font-medium", style: { fontSize: "1.2em", marginBottom: "1em" }, children: [
925
+ "The processor at ",
926
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-white", children: processorHost }),
927
+ " is not responding."
928
+ ] }),
929
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("ul", { className: "text-gray-400 text-left mb-[1em]", style: { fontSize: "1em", listStyleType: "disc", paddingLeft: "1.5em" }, children: [
930
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("li", { children: "Verify the IP address is correct" }),
931
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("li", { children: "Check that the processor is powered on" }),
932
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("li", { children: "Ensure you are on the same network" })
933
+ ] }),
934
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex gap-[1em] justify-center", style: { overflow: "visible" }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
935
+ "button",
936
+ {
937
+ onClick: retryConnection,
938
+ className: "bg-orange-600 hover:bg-orange-500 text-white font-medium rounded-xl transition-colors",
939
+ style: { padding: "1em 2em", fontSize: "1.2em", whiteSpace: "nowrap", overflow: "visible" },
940
+ children: "Retry Connection"
941
+ }
942
+ ) })
943
+ ] }),
944
+ isError && !isDeployedOnProcessor && !isTimeoutError && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("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__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "text-center", style: { overflow: "visible" }, children: [
885
945
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "text-blue-400 font-medium", style: { fontSize: "1.4em", marginBottom: "1em" }, children: [
886
946
  "Retrying connection in ",
887
947
  retryCountdown,
@@ -985,6 +1045,11 @@ function CrisOfflinePage({
985
1045
  envInfo.pathname,
986
1046
  envInfo.search
987
1047
  ] })
1048
+ ] }),
1049
+ envInfo.errorReason && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
1050
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-gray-500", children: "Error Reason:" }),
1051
+ " ",
1052
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: envInfo.errorReason === "timeout" ? "text-orange-400" : "text-red-400", children: envInfo.errorReason })
988
1053
  ] })
989
1054
  ] })
990
1055
  ] })