@imperosoft/cris-webui-components 1.1.2-beta.0 → 1.1.2-beta.1
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 +8 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +19 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -121,8 +121,13 @@ function CrisButton({
|
|
|
121
121
|
onRelease,
|
|
122
122
|
debug = false
|
|
123
123
|
}) {
|
|
124
|
-
const
|
|
125
|
-
|
|
124
|
+
const debugRef = (0, import_react.useRef)(debug);
|
|
125
|
+
debugRef.current = debug;
|
|
126
|
+
const joinRef = (0, import_react.useRef)(join);
|
|
127
|
+
joinRef.current = join;
|
|
128
|
+
const log = (0, import_react.useCallback)((msg, data) => {
|
|
129
|
+
if (debugRef.current) console.log(`[CrisButton:${joinRef.current}] ${msg}`, data ?? "");
|
|
130
|
+
}, []);
|
|
126
131
|
const [pressed, setPressed] = (0, import_react.useState)(false);
|
|
127
132
|
const pressedRef = (0, import_react.useRef)(false);
|
|
128
133
|
const touchingRef = (0, import_react.useRef)(false);
|
|
@@ -147,7 +152,7 @@ function CrisButton({
|
|
|
147
152
|
dSet(join, false);
|
|
148
153
|
}
|
|
149
154
|
}
|
|
150
|
-
}, [isVisible, join, smartId, dSet
|
|
155
|
+
}, [isVisible, join, smartId, dSet]);
|
|
151
156
|
(0, import_react.useEffect)(() => {
|
|
152
157
|
return () => {
|
|
153
158
|
if (pressedRef.current && join != null && smartId == null) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/components/CrisButton.tsx","../src/utils/icons.ts","../src/utils/touchGuard.ts","../src/components/CrisText.tsx","../src/components/CrisSlider.tsx","../src/components/CrisGauge.tsx","../src/components/CrisSpinner.tsx","../src/components/CrisOfflinePage.tsx"],"sourcesContent":["/**\r\n * @imperosoft/cris-webui-components\r\n *\r\n * CRIS - Crestron React Impero Soft WebUI components library\r\n *\r\n * Provides reusable UI components for Crestron touch panel interfaces.\r\n */\r\n\r\n// Components\r\nexport { CrisButton } from './components/CrisButton';\r\nexport type { CrisButtonProps } from './components/CrisButton';\r\n\r\nexport { CrisText } from './components/CrisText';\r\nexport type { CrisTextProps } from './components/CrisText';\r\n\r\nexport { CrisSlider } from './components/CrisSlider';\r\nexport type { CrisSliderProps } from './components/CrisSlider';\r\n\r\nexport { CrisGauge } from './components/CrisGauge';\r\nexport type { CrisGaugeProps } from './components/CrisGauge';\r\n\r\nexport { CrisSpinner } from './components/CrisSpinner';\r\nexport type { CrisSpinnerProps, SpinnerSpeed } from './components/CrisSpinner';\r\n\r\nexport { CrisOfflinePage } from './components/CrisOfflinePage';\r\nexport type { CrisOfflinePageProps } from './components/CrisOfflinePage';\r\n\r\n// Utilities\r\nexport { configureIcons, getIconUrl, getIconFilter, getIconConfig } from './utils/icons';\r\nexport type { IconConfig } from './utils/icons';\r\n","import { useState, useRef, useEffect, ReactNode } from 'react';\r\nimport { useDigital, useJoinsStore } from '@imperosoft/cris-webui-ch5-core';\r\nimport { getIconUrl, getIconFilter } from '../utils/icons';\r\nimport { touchStart, touchEnd, isTouchActive } from '../utils/touchGuard';\r\n\r\nexport interface CrisButtonProps {\r\n /** Digital join for press action */\r\n join?: number;\r\n /** Digital join for feedback (defaults to join) */\r\n joinFeedback?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Button text */\r\n text?: string;\r\n /** Text when pressed (local feedback) */\r\n textPressed?: string;\r\n /** Text when selected (controller feedback) */\r\n textSelected?: string;\r\n\r\n /** Icon as ReactNode (for custom SVG components) */\r\n icon?: ReactNode;\r\n /** Icon name (loads SVG from configured path, e.g., 'motor-stop') */\r\n iconName?: string;\r\n /** Icon CSS class (for CSS-based icons, e.g., 'ico-motor-stop') */\r\n iconClass?: string;\r\n /** Icon size - number (px), string ('50%', '2rem'), or preset ('xs'|'sm'|'md'|'lg'|'xl') */\r\n iconSize?: number | string | 'xs' | 'sm' | 'md' | 'lg' | 'xl';\r\n /** Icon container size as percentage (default: 80% for top/bottom, 20% for left/right) */\r\n iconContainerSize?: string;\r\n /** Icon inline style */\r\n iconStyle?: React.CSSProperties;\r\n /** Icon position relative to text */\r\n iconPosition?: 'left' | 'right' | 'top' | 'bottom';\r\n\r\n /** Icon name when active (overrides iconName when button is active) */\r\n iconNameActive?: string;\r\n /** Icon CSS class when active (overrides iconClass when button is active) */\r\n iconClassActive?: string;\r\n /** Icon inline style when active (merged with iconStyle when button is active) */\r\n iconStyleActive?: React.CSSProperties;\r\n\r\n /** Show controller feedback styling */\r\n showControlFeedback?: boolean;\r\n /** Show local press feedback styling */\r\n showLocalFeedback?: boolean;\r\n /** Suppress click actions (display only) */\r\n suppressKeyClicks?: boolean;\r\n\r\n /** Smart object ID (for smarts instead of joins) */\r\n smartId?: number;\r\n\r\n /** Custom class names */\r\n className?: string;\r\n /** Class when active (controller feedback) */\r\n classActive?: string;\r\n /** Class when pressed (local feedback) */\r\n classPressed?: string;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n\r\n /** Children content */\r\n children?: ReactNode;\r\n\r\n /** Custom click handler (called on press) */\r\n onPress?: () => void;\r\n /** Custom release handler */\r\n onRelease?: () => void;\r\n\r\n /** Enable debug logging for this button */\r\n debug?: boolean;\r\n}\r\n\r\nexport function CrisButton({\r\n join,\r\n joinFeedback,\r\n joinEnable,\r\n joinVisible,\r\n text,\r\n textPressed,\r\n textSelected,\r\n icon,\r\n iconName,\r\n iconClass,\r\n iconSize,\r\n iconContainerSize,\r\n iconStyle,\r\n iconPosition = 'top',\r\n iconNameActive,\r\n iconClassActive,\r\n iconStyleActive,\r\n showControlFeedback = true,\r\n showLocalFeedback = true,\r\n suppressKeyClicks = false,\r\n smartId,\r\n className = '',\r\n classActive = '',\r\n classPressed = '',\r\n classDisabled = '',\r\n children,\r\n onPress,\r\n onRelease,\r\n debug = false,\r\n}: CrisButtonProps) {\r\n // Debug logging helper\r\n const log = debug\r\n ? (msg: string, data?: unknown) => console.log(`[CrisButton:${join}] ${msg}`, data ?? '')\r\n : () => {};\r\n const [pressed, setPressed] = useState(false);\r\n const pressedRef = useRef(false);\r\n const touchingRef = useRef(false);\r\n const touchStartedHereRef = useRef(false);\r\n\r\n // Get join values reactively\r\n const feedbackJoin = joinFeedback ?? join;\r\n const feedback = useDigital(feedbackJoin ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Get action method\r\n const dSet = useJoinsStore((state) => state.dSet);\r\n\r\n // Determine if button is enabled\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n\r\n // Determine if button is visible\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Release if button becomes invisible while pressed\r\n useEffect(() => {\r\n log('visibility effect', { isVisible, pressedRef: pressedRef.current });\r\n if (!isVisible && pressedRef.current) {\r\n log('VISIBILITY RELEASE - button hidden while pressed');\r\n pressedRef.current = false;\r\n setPressed(false);\r\n touchingRef.current = false;\r\n touchStartedHereRef.current = false;\r\n if (join != null && smartId == null) {\r\n log('sending release via dSet(false)');\r\n dSet(join, false);\r\n }\r\n }\r\n }, [isVisible, join, smartId, dSet, log]);\r\n\r\n // Release on unmount if button was pressed (handles page transitions)\r\n useEffect(() => {\r\n return () => {\r\n if (pressedRef.current && join != null && smartId == null) {\r\n log('UNMOUNT RELEASE - component unmounting while pressed');\r\n dSet(join, false);\r\n }\r\n };\r\n }, [join, smartId, dSet, log]);\r\n\r\n // Determine current feedback state\r\n const hasControlFeedback = showControlFeedback && feedbackJoin != null && feedback;\r\n const hasPressedFeedback = showLocalFeedback && pressed && isEnabled;\r\n const isActive = hasControlFeedback || hasPressedFeedback;\r\n\r\n // Determine current text\r\n let currentText = text ?? '';\r\n if (hasPressedFeedback && textPressed != null) {\r\n currentText = textPressed;\r\n } else if (hasControlFeedback && textSelected != null) {\r\n currentText = textSelected;\r\n }\r\n\r\n // Event handlers\r\n const handlePress = () => {\r\n log('handlePress called', { suppressKeyClicks, pressedRef: pressedRef.current, isEnabled });\r\n if (suppressKeyClicks) {\r\n log('BLOCKED: suppressKeyClicks');\r\n return;\r\n }\r\n if (pressedRef.current) {\r\n log('BLOCKED: already pressed');\r\n return;\r\n }\r\n\r\n pressedRef.current = true;\r\n setPressed(true);\r\n\r\n if (!isEnabled) {\r\n log('SKIPPED dSet: not enabled');\r\n return;\r\n }\r\n\r\n // Custom handler\r\n onPress?.();\r\n\r\n // Send to controller\r\n if (join != null && smartId == null) {\r\n log('SENDING PRESS via dSet(true)');\r\n dSet(join, true);\r\n }\r\n // TODO: Add smartId support when smarts are implemented in core\r\n };\r\n\r\n const handleRelease = () => {\r\n log('handleRelease called', { suppressKeyClicks, pressedRef: pressedRef.current, isEnabled });\r\n if (suppressKeyClicks) {\r\n log('BLOCKED: suppressKeyClicks');\r\n return;\r\n }\r\n if (!pressedRef.current) {\r\n log('BLOCKED: not pressed');\r\n return;\r\n }\r\n\r\n pressedRef.current = false;\r\n setPressed(false);\r\n\r\n if (!isEnabled) {\r\n log('SKIPPED dSet: not enabled');\r\n return;\r\n }\r\n\r\n // Custom handler\r\n onRelease?.();\r\n\r\n // Send to controller\r\n if (join != null && smartId == null) {\r\n log('SENDING RELEASE via dSet(false)');\r\n dSet(join, false);\r\n }\r\n // TODO: Add smartId support when smarts are implemented in core\r\n };\r\n\r\n // Touch event handlers - use global touch guard to prevent phantom clicks\r\n // when buttons swap visibility (e.g., back button hides, power button appears)\r\n const handleTouchStart = () => {\r\n log('handleTouchStart');\r\n touchStart(); // Global guard\r\n touchingRef.current = true;\r\n touchStartedHereRef.current = true;\r\n handlePress();\r\n };\r\n\r\n const handleTouchEnd = () => {\r\n log('handleTouchEnd', { touchStartedHereRef: touchStartedHereRef.current });\r\n touchEnd(); // Global guard with delayed reset\r\n touchingRef.current = true;\r\n // Only fire release if touch actually started on this button\r\n if (touchStartedHereRef.current) {\r\n touchStartedHereRef.current = false;\r\n handleRelease();\r\n } else {\r\n log('SKIPPED handleRelease: touch did not start here');\r\n }\r\n };\r\n\r\n const handleTouchCancel = () => {\r\n log('handleTouchCancel');\r\n touchEnd(); // Global guard with delayed reset\r\n touchingRef.current = true;\r\n touchStartedHereRef.current = false;\r\n handleRelease();\r\n };\r\n\r\n // Mouse event handlers - skip if any touch event is active (global check)\r\n const handleMouseDown = () => {\r\n if (isTouchActive() || touchingRef.current) return;\r\n handlePress();\r\n };\r\n\r\n const handleMouseUp = () => {\r\n if (isTouchActive() || touchingRef.current) return;\r\n handleRelease();\r\n };\r\n\r\n const handleMouseLeave = () => {\r\n if (isTouchActive() || touchingRef.current) return;\r\n handleRelease();\r\n };\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const classes = [\r\n 'cris-button',\r\n className,\r\n hasControlFeedback && classActive,\r\n hasPressedFeedback && classPressed,\r\n !isEnabled && classDisabled,\r\n hasControlFeedback && 'active',\r\n hasPressedFeedback && 'pressed',\r\n !isEnabled && 'disabled',\r\n suppressKeyClicks && 'no-hover',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Flex direction based on icon position\r\n const flexDirection =\r\n iconPosition === 'top'\r\n ? 'flex-col'\r\n : iconPosition === 'bottom'\r\n ? 'flex-col-reverse'\r\n : iconPosition === 'left'\r\n ? 'flex-row'\r\n : 'flex-row-reverse';\r\n\r\n // Determine if we have any icon to show\r\n const hasIcon = icon != null || iconName != null || iconClass != null || iconNameActive != null || iconClassActive != null;\r\n\r\n // Determine current icon properties based on active state\r\n const currentIconName = isActive && iconNameActive != null ? iconNameActive : iconName;\r\n const currentIconClass = isActive && iconClassActive != null ? iconClassActive : iconClass;\r\n const currentIconStyle = isActive && iconStyleActive != null ? { ...iconStyle, ...iconStyleActive } : iconStyle;\r\n\r\n // Size presets mapping\r\n const sizePresets: Record<string, string> = {\r\n xs: '16px',\r\n sm: '24px',\r\n md: '32px',\r\n lg: '48px',\r\n xl: '64px',\r\n };\r\n\r\n // Calculate icon size value\r\n const getIconSizeValue = (): string | undefined => {\r\n if (iconSize == null) return undefined;\r\n if (typeof iconSize === 'number') return `${iconSize}px`;\r\n if (iconSize in sizePresets) return sizePresets[iconSize];\r\n return iconSize; // Already a string like '50%' or '2rem'\r\n };\r\n\r\n const iconSizeValue = getIconSizeValue();\r\n\r\n // Build icon classes\r\n const iconClasses = [\r\n 'cris-button-icon',\r\n currentIconClass,\r\n hasControlFeedback && 'active',\r\n hasPressedFeedback && 'pressed',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Build icon style with size and filter support\r\n const computedIconStyle: React.CSSProperties = {\r\n ...currentIconStyle,\r\n };\r\n\r\n // Apply size\r\n if (iconSizeValue) {\r\n computedIconStyle.width = iconSizeValue;\r\n computedIconStyle.height = iconSizeValue;\r\n }\r\n\r\n // Apply filter from config if using iconName (only if not overridden by iconStyle/iconStyleActive)\r\n if (currentIconName && !currentIconStyle?.filter) {\r\n const filter = getIconFilter(isActive);\r\n if (filter) {\r\n computedIconStyle.filter = filter;\r\n }\r\n }\r\n\r\n // Render icon element\r\n const renderIconElement = () => {\r\n if (icon) {\r\n // ReactNode icon (custom component)\r\n return <span className={iconClasses} style={computedIconStyle}>{icon}</span>;\r\n }\r\n\r\n if (currentIconName) {\r\n // Icon by name (loads from configured path)\r\n return (\r\n <img\r\n className={iconClasses}\r\n style={{ ...computedIconStyle, pointerEvents: 'none' }}\r\n src={getIconUrl(currentIconName)}\r\n alt=\"\"\r\n draggable={false}\r\n />\r\n );\r\n }\r\n\r\n if (currentIconClass) {\r\n // CSS-based icon (like Angular version)\r\n return <img className={iconClasses} style={{ ...computedIconStyle, pointerEvents: 'none' }} alt=\"\" draggable={false} />;\r\n }\r\n\r\n return null;\r\n };\r\n\r\n // Calculate icon container size (default: 100% if no text, otherwise 80% for top/bottom, 20% for left/right)\r\n const isVertical = iconPosition === 'top' || iconPosition === 'bottom';\r\n const hasText = currentText !== '';\r\n const defaultContainerSize = hasText ? (isVertical ? '80%' : '20%') : '100%';\r\n const containerSize = iconContainerSize ?? defaultContainerSize;\r\n\r\n // Calculate text container size (remaining space)\r\n const textContainerSize = `calc(100% - ${containerSize})`;\r\n\r\n // Container style for icon area\r\n const iconContainerStyle: React.CSSProperties = isVertical\r\n ? { height: containerSize, width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }\r\n : { width: containerSize, height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' };\r\n\r\n // Container style for text area\r\n const textContainerStyle: React.CSSProperties = isVertical\r\n ? { height: textContainerSize, width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }\r\n : { width: textContainerSize, height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' };\r\n\r\n // Check if button has 'free' class (absolute positioning)\r\n const isFreePositioned = className.includes('free');\r\n\r\n return (\r\n <div\r\n className={`${classes} flex items-center justify-center ${flexDirection}`}\r\n style={{\r\n cursor: suppressKeyClicks ? 'default' : 'pointer',\r\n position: isFreePositioned ? undefined : 'relative',\r\n overflow: isFreePositioned ? undefined : 'hidden',\r\n touchAction: 'none',\r\n userSelect: 'none',\r\n WebkitUserSelect: 'none',\r\n }}\r\n onMouseDown={handleMouseDown}\r\n onMouseUp={handleMouseUp}\r\n onMouseLeave={handleMouseLeave}\r\n onTouchStart={handleTouchStart}\r\n onTouchEnd={handleTouchEnd}\r\n onTouchCancel={handleTouchCancel}\r\n >\r\n {/* Press overlay - shows on press for both desktop and touch devices */}\r\n <div\r\n className=\"cris-button-press-overlay\"\r\n style={{\r\n position: 'absolute',\r\n inset: 0,\r\n backgroundColor: 'black',\r\n opacity: pressed ? 0.1 : 0,\r\n transition: 'opacity 0.1s',\r\n pointerEvents: 'none',\r\n borderRadius: 'inherit',\r\n }}\r\n />\r\n {children}\r\n {hasIcon && (\r\n <div className=\"cris-button-icon-container\" style={iconContainerStyle}>\r\n {renderIconElement()}\r\n </div>\r\n )}\r\n {currentText && (\r\n <div className=\"cris-button-text-container\" style={textContainerStyle}>\r\n <span className=\"cris-button-text\">{currentText}</span>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n","/**\r\n * Icon configuration and utilities for CRIS components\r\n *\r\n * Supports different base paths for dev vs production (Crestron controller)\r\n */\r\n\r\nexport interface IconConfig {\r\n /** Base path for icon assets (e.g., '/assets/icons/' or 'http://controller/icons/') */\r\n basePath: string;\r\n /** File extension (default: 'svg') */\r\n extension: string;\r\n /** Default filter for inactive state */\r\n defaultFilter?: string;\r\n /** Default filter for active state */\r\n activeFilter?: string;\r\n}\r\n\r\n// Default configuration\r\nlet iconConfig: IconConfig = {\r\n basePath: '/assets/icons/',\r\n extension: 'svg',\r\n defaultFilter: undefined,\r\n activeFilter: undefined,\r\n};\r\n\r\n/**\r\n * Configure icon paths and defaults\r\n * Call this once at app startup\r\n *\r\n * @example\r\n * // Development\r\n * configureIcons({ basePath: '/assets/icons/' });\r\n *\r\n * // Production on Crestron controller\r\n * configureIcons({ basePath: '/html/icons/' });\r\n *\r\n * // With color filters\r\n * configureIcons({\r\n * basePath: '/assets/icons/',\r\n * defaultFilter: 'brightness(0) invert(1)', // white\r\n * activeFilter: 'brightness(0) invert(0.5) sepia(1) saturate(5) hue-rotate(0deg)', // red\r\n * });\r\n */\r\nexport function configureIcons(config: Partial<IconConfig>): void {\r\n iconConfig = { ...iconConfig, ...config };\r\n}\r\n\r\n/**\r\n * Get current icon configuration\r\n */\r\nexport function getIconConfig(): IconConfig {\r\n return iconConfig;\r\n}\r\n\r\n/**\r\n * Get full URL for an icon\r\n *\r\n * @param name - Icon name (without path or extension), e.g., 'motor-stop'\r\n * @returns Full URL to the icon\r\n */\r\nexport function getIconUrl(name: string): string {\r\n if (!name) return '';\r\n\r\n // If already a full URL or path, return as-is\r\n if (name.startsWith('http') || name.startsWith('/') || name.startsWith('.')) {\r\n return name;\r\n }\r\n\r\n const { basePath, extension } = iconConfig;\r\n const normalizedBase = basePath.endsWith('/') ? basePath : `${basePath}/`;\r\n return `${normalizedBase}${name}.${extension}`;\r\n}\r\n\r\n/**\r\n * Get CSS filter for icon state\r\n */\r\nexport function getIconFilter(active: boolean): string | undefined {\r\n return active ? iconConfig.activeFilter : iconConfig.defaultFilter;\r\n}\r\n","/**\r\n * Global touch state guard - shared across all CRIS components\r\n *\r\n * This prevents phantom clicks when elements swap visibility during touch interactions.\r\n * When a button is touched and triggers a visibility change (e.g., back button hides,\r\n * power button appears), the newly visible element would receive the synthetic click\r\n * event from the browser. The global touch flag blocks this.\r\n */\r\n\r\nlet globalTouchActive = false;\r\nlet touchResetTimeout: number | null = null;\r\n\r\n/** Duration to keep touch guard active after touch ends (blocks synthetic click) */\r\nconst TOUCH_GUARD_DURATION_MS = 300;\r\n\r\n/**\r\n * Mark that a touch interaction has started.\r\n * Call this in touchStart handlers.\r\n */\r\nexport function touchStart(): void {\r\n globalTouchActive = true;\r\n // Clear any pending reset\r\n if (touchResetTimeout !== null) {\r\n window.clearTimeout(touchResetTimeout);\r\n touchResetTimeout = null;\r\n }\r\n}\r\n\r\n/**\r\n * Mark that a touch interaction has ended.\r\n * The guard remains active for a short duration to block synthetic click events.\r\n * Call this in touchEnd/touchCancel handlers.\r\n */\r\nexport function touchEnd(): void {\r\n globalTouchActive = true;\r\n // Reset after delay to block synthetic click\r\n if (touchResetTimeout !== null) {\r\n window.clearTimeout(touchResetTimeout);\r\n }\r\n touchResetTimeout = window.setTimeout(() => {\r\n globalTouchActive = false;\r\n touchResetTimeout = null;\r\n }, TOUCH_GUARD_DURATION_MS);\r\n}\r\n\r\n/**\r\n * Check if a touch interaction is currently active.\r\n * Use this in mouse handlers to skip if touch is active.\r\n */\r\nexport function isTouchActive(): boolean {\r\n return globalTouchActive;\r\n}\r\n","import { ReactNode } from 'react';\r\nimport { useDigital, useSerial, useCipDecode } from '@imperosoft/cris-webui-ch5-core';\r\n\r\nexport interface CrisTextProps {\r\n /** Serial join for indirect text */\r\n joinIndirectText?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n /** Static text (used if no joinIndirectText) */\r\n text?: string;\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n /** Text element CSS class */\r\n textClassName?: string;\r\n /** Text element inline style */\r\n textStyle?: React.CSSProperties;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n /** Children content */\r\n children?: ReactNode;\r\n}\r\n\r\nexport function CrisText({\r\n joinIndirectText,\r\n joinEnable,\r\n joinVisible,\r\n text,\r\n className = '',\r\n style,\r\n textClassName = '',\r\n textStyle,\r\n classDisabled = '',\r\n children,\r\n}: CrisTextProps) {\r\n // Get join values reactively\r\n const indirectText = useSerial(joinIndirectText ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine if text is enabled\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n\r\n // Determine if text is visible\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Get the raw text - indirect text or static text\r\n const rawText = joinIndirectText != null ? indirectText : (text ?? '');\r\n\r\n // Decode CIP patterns in the text\r\n const currentText = useCipDecode(rawText);\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const containerClasses = [\r\n 'cris-text',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n return (\r\n <div className={containerClasses} style={style}>\r\n {children}\r\n {currentText && (\r\n <span className={`cris-text-text ${textClassName}`} style={textStyle}>\r\n {currentText}\r\n </span>\r\n )}\r\n </div>\r\n );\r\n}\r\n","import { useState, useRef, useEffect, useCallback } from 'react';\r\nimport { useDigital, useAnalog, useJoinsStore } from '@imperosoft/cris-webui-ch5-core';\r\nimport { touchStart, touchEnd, isTouchActive } from '../utils/touchGuard';\r\n\r\nexport interface CrisSliderProps {\r\n /** Analog join for value (shared with digital if joinDigital not set) */\r\n join?: number;\r\n /** Digital join for press/release feedback */\r\n joinDigital?: number;\r\n /** Analog join for value (overrides join) */\r\n joinAnalog?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Minimum value (default 0) */\r\n minValue?: number;\r\n /** Maximum value (default 65535) */\r\n maxValue?: number;\r\n /** Horizontal orientation (default false = vertical) */\r\n horizontal?: boolean;\r\n /** Hide fill bar */\r\n fillHidden?: boolean;\r\n /** Track size as percentage of container (default 20) */\r\n trackSizePercent?: number;\r\n /** Thumb size as percentage of container (default 4) */\r\n thumbSizePercent?: number;\r\n /** Delay in ms after drag before updating from feedback (default 1000) */\r\n delayMsAfterDragUpdateFeedback?: number;\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n /** Bar/track CSS class */\r\n barClassName?: string;\r\n /** Bar/track inline style */\r\n barStyle?: React.CSSProperties;\r\n /** Fill CSS class */\r\n fillClassName?: string;\r\n /** Fill inline style */\r\n fillStyle?: React.CSSProperties;\r\n /** Thumb CSS class */\r\n thumbClassName?: string;\r\n /** Thumb inline style */\r\n thumbStyle?: React.CSSProperties;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n}\r\n\r\nexport function CrisSlider({\r\n join,\r\n joinDigital,\r\n joinAnalog,\r\n joinEnable,\r\n joinVisible,\r\n minValue = 0,\r\n maxValue = 65535,\r\n horizontal = false,\r\n fillHidden = false,\r\n trackSizePercent = 20,\r\n thumbSizePercent = 4,\r\n delayMsAfterDragUpdateFeedback = 1000,\r\n className = '',\r\n style,\r\n barClassName = '',\r\n barStyle,\r\n fillClassName = '',\r\n fillStyle,\r\n thumbClassName = '',\r\n thumbStyle,\r\n classDisabled = '',\r\n}: CrisSliderProps) {\r\n // Effective joins\r\n const effectiveAnalogJoin = joinAnalog ?? join;\r\n const effectiveDigitalJoin = joinDigital ?? join;\r\n\r\n // Get join values reactively\r\n const analogValue = useAnalog(effectiveAnalogJoin ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Action methods\r\n const dSet = useJoinsStore((state) => state.dSet);\r\n const aSet = useJoinsStore((state) => state.aSet);\r\n\r\n // Local state\r\n const [ratioCurrent, setRatioCurrent] = useState(0);\r\n const [isDragging, setIsDragging] = useState(false);\r\n const isDraggingOrJustAfterRef = useRef(false);\r\n const ratioBeforeDragRef = useRef(0);\r\n const afterDragTimeoutRef = useRef<number | null>(null);\r\n const touchingRef = useRef(false);\r\n\r\n // Determine if enabled and visible\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Release if slider becomes invisible while dragging\r\n useEffect(() => {\r\n if (!isVisible && isDraggingOrJustAfterRef.current) {\r\n setIsDragging(false);\r\n isDraggingOrJustAfterRef.current = false;\r\n touchingRef.current = false;\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, false);\r\n }\r\n }\r\n }, [isVisible, effectiveDigitalJoin, dSet]);\r\n\r\n // Release on unmount if slider was being dragged (handles page transitions)\r\n useEffect(() => {\r\n return () => {\r\n if (isDraggingOrJustAfterRef.current && effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, false);\r\n }\r\n };\r\n }, [effectiveDigitalJoin, dSet]);\r\n\r\n // Convert analog value to ratio\r\n const analogToRatio = useCallback(\r\n (value: number) => {\r\n const range = maxValue - minValue;\r\n if (range <= 0) return 0;\r\n return Math.max(0, Math.min(1, (value - minValue) / range));\r\n },\r\n [minValue, maxValue]\r\n );\r\n\r\n // Convert ratio to analog value\r\n const ratioToAnalog = useCallback(\r\n (ratio: number) => {\r\n const range = maxValue - minValue;\r\n return Math.round(minValue + ratio * range);\r\n },\r\n [minValue, maxValue]\r\n );\r\n\r\n // Update ratio from controller feedback\r\n const updateFromFeedback = useCallback(() => {\r\n if (!isDraggingOrJustAfterRef.current && effectiveAnalogJoin != null) {\r\n setRatioCurrent(analogToRatio(analogValue));\r\n }\r\n }, [analogValue, analogToRatio, effectiveAnalogJoin]);\r\n\r\n // Update from feedback when analog value changes\r\n useEffect(() => {\r\n updateFromFeedback();\r\n }, [updateFromFeedback]);\r\n\r\n // Handle drag start\r\n const handleDragStart = useCallback(() => {\r\n if (!isEnabled) return;\r\n\r\n setIsDragging(true);\r\n\r\n // Send digital press\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, true);\r\n }\r\n\r\n // Save ratio before drag\r\n if (!isDraggingOrJustAfterRef.current) {\r\n ratioBeforeDragRef.current = ratioCurrent;\r\n }\r\n isDraggingOrJustAfterRef.current = true;\r\n\r\n // Clear any pending timeout\r\n if (afterDragTimeoutRef.current !== null) {\r\n window.clearTimeout(afterDragTimeoutRef.current);\r\n afterDragTimeoutRef.current = null;\r\n }\r\n }, [isEnabled, effectiveDigitalJoin, dSet, ratioCurrent]);\r\n\r\n // Handle drag end\r\n const handleDragEnd = useCallback(() => {\r\n if (!isDragging) return;\r\n\r\n setIsDragging(false);\r\n\r\n // Send digital release\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, false);\r\n }\r\n\r\n // Schedule feedback update after delay\r\n if (delayMsAfterDragUpdateFeedback > 0) {\r\n afterDragTimeoutRef.current = window.setTimeout(() => {\r\n isDraggingOrJustAfterRef.current = false;\r\n updateFromFeedback();\r\n afterDragTimeoutRef.current = null;\r\n }, delayMsAfterDragUpdateFeedback);\r\n } else {\r\n isDraggingOrJustAfterRef.current = false;\r\n updateFromFeedback();\r\n }\r\n }, [isDragging, effectiveDigitalJoin, dSet, delayMsAfterDragUpdateFeedback, updateFromFeedback]);\r\n\r\n // Handle move/drag\r\n const handleMove = useCallback(\r\n (clientX: number, clientY: number, bounds: DOMRect) => {\r\n if (!isDragging) return;\r\n\r\n let newRatio: number;\r\n if (horizontal) {\r\n newRatio = (clientX - bounds.left) / bounds.width;\r\n } else {\r\n newRatio = 1 - (clientY - bounds.top) / bounds.height;\r\n }\r\n newRatio = Math.max(0, Math.min(1, newRatio));\r\n\r\n setRatioCurrent(newRatio);\r\n\r\n // Send analog value\r\n if (effectiveAnalogJoin != null) {\r\n aSet(effectiveAnalogJoin, ratioToAnalog(newRatio));\r\n }\r\n },\r\n [isDragging, horizontal, effectiveAnalogJoin, aSet, ratioToAnalog]\r\n );\r\n\r\n // Mouse handlers - skip if any touch event is active (global check)\r\n const handleMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (isTouchActive() || touchingRef.current) return;\r\n if (event.button !== 0) return;\r\n\r\n handleDragStart();\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n };\r\n\r\n const handleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (!isDragging) return;\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n };\r\n\r\n const handleMouseUp = () => {\r\n handleDragEnd();\r\n };\r\n\r\n const handleMouseLeave = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (isDragging) {\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n }\r\n handleDragEnd();\r\n };\r\n\r\n // Touch handlers - use global touch guard\r\n const handleTouchStart = (event: React.TouchEvent<HTMLDivElement>) => {\r\n touchStart(); // Global guard\r\n touchingRef.current = true;\r\n handleDragStart();\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n };\r\n\r\n const handleTouchMove = (event: React.TouchEvent<HTMLDivElement>) => {\r\n if (!isDragging) return;\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n };\r\n\r\n const handleTouchEnd = () => {\r\n touchEnd(); // Global guard with delayed reset\r\n handleDragEnd();\r\n };\r\n\r\n const handleTouchCancel = (event: React.TouchEvent<HTMLDivElement>) => {\r\n touchEnd(); // Global guard with delayed reset\r\n if (isDragging && event.touches.length > 0) {\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n }\r\n handleDragEnd();\r\n };\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const containerClasses = [\r\n 'cris-slider',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Calculate bar style\r\n const computedBarStyle: React.CSSProperties = {\r\n ...barStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const width = 100 - thumbSizePercent;\r\n const left = (100 - width) / 2;\r\n computedBarStyle.height = `${trackSizePercent}%`;\r\n computedBarStyle.top = `${(100 - trackSizePercent) / 2}%`;\r\n computedBarStyle.width = `${width}%`;\r\n computedBarStyle.left = `${left}%`;\r\n } else {\r\n const height = 100 - thumbSizePercent;\r\n const top = (100 - height) / 2;\r\n computedBarStyle.width = `${trackSizePercent}%`;\r\n computedBarStyle.left = `${(100 - trackSizePercent) / 2}%`;\r\n computedBarStyle.height = `${height}%`;\r\n computedBarStyle.top = `${top}%`;\r\n }\r\n\r\n // Calculate fill style\r\n const computedFillStyle: React.CSSProperties = {\r\n ...fillStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const range = 100 - thumbSizePercent;\r\n const left = thumbSizePercent / 2;\r\n const width = range * ratioCurrent;\r\n computedFillStyle.height = `${trackSizePercent}%`;\r\n computedFillStyle.top = `${(100 - trackSizePercent) / 2}%`;\r\n computedFillStyle.width = `${width}%`;\r\n computedFillStyle.left = `${left}%`;\r\n } else {\r\n const range = 100 - thumbSizePercent;\r\n const top = 100 - (range * ratioCurrent + thumbSizePercent / 2);\r\n const height = range + thumbSizePercent / 2 - top;\r\n computedFillStyle.width = `${trackSizePercent}%`;\r\n computedFillStyle.left = `${(100 - trackSizePercent) / 2}%`;\r\n computedFillStyle.height = `${height}%`;\r\n computedFillStyle.top = `${top}%`;\r\n }\r\n\r\n // Calculate thumb style\r\n const computedThumbStyle: React.CSSProperties = {\r\n ...thumbStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const maxLeft = 100 - thumbSizePercent;\r\n const left = maxLeft * ratioCurrent;\r\n computedThumbStyle.width = `${thumbSizePercent}%`;\r\n computedThumbStyle.height = '100%';\r\n computedThumbStyle.left = `${left}%`;\r\n } else {\r\n const maxTop = 100 - thumbSizePercent;\r\n const top = maxTop - maxTop * ratioCurrent;\r\n computedThumbStyle.width = '100%';\r\n computedThumbStyle.height = `${thumbSizePercent}%`;\r\n computedThumbStyle.top = `${top}%`;\r\n }\r\n\r\n return (\r\n <div\r\n className={containerClasses}\r\n style={{\r\n ...style,\r\n position: 'relative',\r\n cursor: isEnabled ? 'pointer' : 'default',\r\n touchAction: 'none',\r\n userSelect: 'none',\r\n }}\r\n onMouseDown={handleMouseDown}\r\n onMouseMove={handleMouseMove}\r\n onMouseUp={handleMouseUp}\r\n onMouseLeave={handleMouseLeave}\r\n onTouchStart={handleTouchStart}\r\n onTouchMove={handleTouchMove}\r\n onTouchEnd={handleTouchEnd}\r\n onTouchCancel={handleTouchCancel}\r\n >\r\n {/* Bar/Track */}\r\n <div className={`cris-slider-bar ${barClassName}`} style={computedBarStyle} />\r\n\r\n {/* Fill */}\r\n {!fillHidden && (\r\n <div className={`cris-slider-fill ${fillClassName}`} style={computedFillStyle} />\r\n )}\r\n\r\n {/* Thumb */}\r\n <div className={`cris-slider-thumb ${thumbClassName}`} style={computedThumbStyle} />\r\n </div>\r\n );\r\n}\r\n","import { useAnalog, useDigital } from '@imperosoft/cris-webui-ch5-core';\r\n\r\nexport interface CrisGaugeProps {\r\n /** Static value (used if no join) */\r\n value?: number;\r\n /** Analog join for value */\r\n join?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Minimum value (default 0) */\r\n minValue?: number;\r\n /** Maximum value (default 65535) */\r\n maxValue?: number;\r\n /** Number of segments (default 20) */\r\n segments?: number;\r\n /** Invert direction (default false) */\r\n inverted?: boolean;\r\n /** Orientation (default vertical) */\r\n orientation?: 'vertical' | 'horizontal';\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n\r\n /** Inactive segment CSS class */\r\n inactiveSegmentClassName?: string;\r\n /** Inactive segment inline style */\r\n inactiveSegmentStyle?: React.CSSProperties;\r\n\r\n /** Active segment CSS class (all levels) */\r\n segmentClassName?: string;\r\n /** Active segment inline style (all levels) */\r\n segmentStyle?: React.CSSProperties;\r\n\r\n /** Low level segment CSS class (0-60%) */\r\n lowSegmentClassName?: string;\r\n /** Low level segment inline style */\r\n lowSegmentStyle?: React.CSSProperties;\r\n\r\n /** Medium level segment CSS class (60-80%) */\r\n mediumSegmentClassName?: string;\r\n /** Medium level segment inline style */\r\n mediumSegmentStyle?: React.CSSProperties;\r\n\r\n /** High level segment CSS class (80-100%) */\r\n highSegmentClassName?: string;\r\n /** High level segment inline style */\r\n highSegmentStyle?: React.CSSProperties;\r\n\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n}\r\n\r\nexport function CrisGauge({\r\n value,\r\n join,\r\n joinEnable,\r\n joinVisible,\r\n minValue = 0,\r\n maxValue = 65535,\r\n segments = 20,\r\n inverted = false,\r\n orientation = 'vertical',\r\n className = '',\r\n style,\r\n inactiveSegmentClassName = '',\r\n inactiveSegmentStyle,\r\n segmentClassName = '',\r\n segmentStyle,\r\n lowSegmentClassName = '',\r\n lowSegmentStyle,\r\n mediumSegmentClassName = '',\r\n mediumSegmentStyle,\r\n highSegmentClassName = '',\r\n highSegmentStyle,\r\n classDisabled = '',\r\n}: CrisGaugeProps) {\r\n // Get join values reactively\r\n const joinValue = useAnalog(join ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine if enabled and visible\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Get current value\r\n const currentValue = join != null ? joinValue : (value ?? 0);\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Determine segment type (low, medium, high)\r\n const getSegmentType = (segment: number): 'low' | 'medium' | 'high' => {\r\n const lowMax = segments * 0.6;\r\n const mediumMax = segments * 0.8;\r\n\r\n const seg =\r\n (inverted && orientation === 'vertical') || (!inverted && orientation === 'horizontal')\r\n ? segment + 1\r\n : segments - segment;\r\n\r\n if (seg <= lowMax) return 'low';\r\n if (seg <= mediumMax) return 'medium';\r\n return 'high';\r\n };\r\n\r\n // Check if segment is active\r\n const isSegmentActive = (segment: number): boolean => {\r\n if (currentValue < minValue) return false;\r\n if (currentValue > maxValue) return true;\r\n\r\n const ratio = currentValue / (maxValue - minValue);\r\n let segMax = Math.round(segments * ratio);\r\n if (ratio !== 0 && segMax === 0) segMax = 1;\r\n\r\n const seg =\r\n (inverted && orientation === 'vertical') || (!inverted && orientation === 'horizontal')\r\n ? segment + 1\r\n : segments - segment;\r\n\r\n return seg <= segMax;\r\n };\r\n\r\n // Get segment class\r\n const getSegmentClass = (segment: number): string => {\r\n const type = getSegmentType(segment);\r\n const active = isSegmentActive(segment);\r\n\r\n const classes = ['cris-gauge-segment', type, orientation];\r\n\r\n if (active) {\r\n classes.push('active');\r\n // Add type-specific or general active class\r\n if (type === 'low' && (lowSegmentClassName || segmentClassName)) {\r\n classes.push(lowSegmentClassName || segmentClassName);\r\n } else if (type === 'medium' && (mediumSegmentClassName || segmentClassName)) {\r\n classes.push(mediumSegmentClassName || segmentClassName);\r\n } else if (type === 'high' && (highSegmentClassName || segmentClassName)) {\r\n classes.push(highSegmentClassName || segmentClassName);\r\n }\r\n } else {\r\n if (inactiveSegmentClassName) {\r\n classes.push(inactiveSegmentClassName);\r\n }\r\n }\r\n\r\n return classes.filter(Boolean).join(' ');\r\n };\r\n\r\n // Get segment style\r\n const getSegmentStyle = (segment: number): React.CSSProperties | undefined => {\r\n const type = getSegmentType(segment);\r\n const active = isSegmentActive(segment);\r\n\r\n if (active) {\r\n if (type === 'low' && (lowSegmentStyle || segmentStyle)) {\r\n return lowSegmentStyle || segmentStyle;\r\n } else if (type === 'medium' && (mediumSegmentStyle || segmentStyle)) {\r\n return mediumSegmentStyle || segmentStyle;\r\n } else if (type === 'high' && (highSegmentStyle || segmentStyle)) {\r\n return highSegmentStyle || segmentStyle;\r\n }\r\n } else {\r\n return inactiveSegmentStyle;\r\n }\r\n\r\n return undefined;\r\n };\r\n\r\n // Build container classes\r\n const containerClasses = [\r\n 'cris-gauge',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Container flex style\r\n const containerStyle: React.CSSProperties = {\r\n ...style,\r\n display: 'flex',\r\n flexDirection: orientation === 'horizontal' ? 'row' : 'column',\r\n justifyContent: 'center',\r\n alignItems: 'center',\r\n };\r\n\r\n return (\r\n <div className={containerClasses} style={containerStyle}>\r\n {Array.from({ length: segments }, (_, segment) => (\r\n <div\r\n key={segment}\r\n className={getSegmentClass(segment)}\r\n style={getSegmentStyle(segment)}\r\n />\r\n ))}\r\n </div>\r\n );\r\n}\r\n","import { useAnalog, useDigital } from '@imperosoft/cris-webui-ch5-core';\r\n\r\nexport type SpinnerSpeed = 'slow' | 'medium' | 'fast';\r\n\r\nexport interface CrisSpinnerProps {\r\n /** Analog join for position (0-65535 maps to 0-360 degrees) */\r\n joinPosition?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Static position value (0-65535, used if no joinPosition) */\r\n position?: number;\r\n\r\n /** Endless rotation mode (default: false) */\r\n endless?: boolean;\r\n /** Speed for endless rotation (default: 'medium') */\r\n endlessSpeed?: SpinnerSpeed;\r\n\r\n /** Spinner color (default: '#bf2b23') */\r\n color?: string;\r\n /** Background/track color (default: 'rgba(0, 0, 0, 0.1)') */\r\n trackColor?: string;\r\n /** Spinner line width in pixels (default: 3) */\r\n lineWidth?: number;\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n}\r\n\r\nconst SPEED_DURATIONS: Record<SpinnerSpeed, string> = {\r\n slow: '1.5s',\r\n medium: '0.8s',\r\n fast: '0.4s',\r\n};\r\n\r\nexport function CrisSpinner({\r\n joinPosition,\r\n joinVisible,\r\n position,\r\n endless = false,\r\n endlessSpeed = 'medium',\r\n color = '#bf2b23',\r\n trackColor = 'rgba(0, 0, 0, 0.1)',\r\n lineWidth = 3,\r\n className = '',\r\n style,\r\n}: CrisSpinnerProps) {\r\n // Get join values reactively\r\n const joinPositionValue = useAnalog(joinPosition ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine visibility\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Get current position (0-65535 -> 0-360 degrees)\r\n const currentPosition = joinPosition != null ? joinPositionValue : (position ?? 0);\r\n const rotationDegrees = (currentPosition / 65535) * 360;\r\n\r\n // Build container classes\r\n const containerClasses = ['cris-spinner', className].filter(Boolean).join(' ');\r\n\r\n // Spinner styles\r\n const spinnerStyle: React.CSSProperties = {\r\n width: '100%',\r\n height: '100%',\r\n borderRadius: '50%',\r\n border: `${lineWidth}px solid ${trackColor}`,\r\n borderTopColor: color,\r\n boxSizing: 'border-box',\r\n };\r\n\r\n if (endless) {\r\n // Endless rotation animation\r\n spinnerStyle.animation = `cris-spinner-rotate ${SPEED_DURATIONS[endlessSpeed]} linear infinite`;\r\n } else {\r\n // Position-based rotation\r\n spinnerStyle.transform = `rotate(${rotationDegrees}deg)`;\r\n spinnerStyle.transition = 'transform 0.1s linear';\r\n }\r\n\r\n return (\r\n <div className={containerClasses} style={style}>\r\n <div style={spinnerStyle} />\r\n <style>{`\r\n @keyframes cris-spinner-rotate {\r\n to { transform: rotate(360deg); }\r\n }\r\n `}</style>\r\n </div>\r\n );\r\n}\r\n","import { useEffect, useState, useCallback } from 'react';\r\nimport { isNativeMode, useConnectionStore, ConnectionErrorReason } from '@imperosoft/cris-webui-ch5-core';\r\n\r\n// Environment detection utilities\r\nconst isFileProtocol = typeof window !== 'undefined' && window.location.protocol === 'file:';\r\nconst isDevModeDefault = typeof window !== 'undefined' &&\r\n (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1');\r\nconst isVC4Default = typeof window !== 'undefined' && window.location.pathname.includes('/VirtualControl/');\r\nconst isDeployedOnProcessorDefault = !isFileProtocol && !isDevModeDefault && !isVC4Default;\r\n\r\n// Session storage key to track login redirect\r\nconst LOGIN_REDIRECT_KEY = 'cris_login_redirect';\r\n\r\nexport interface CrisOfflinePageProps {\r\n /**\r\n * Processor hostname for dev mode (used to build login/cert URLs).\r\n * In production, uses window.location.hostname.\r\n */\r\n processorHost?: string;\r\n\r\n /**\r\n * Whether an auth token is configured (for dev mode diagnostics).\r\n */\r\n hasAuthToken?: boolean;\r\n\r\n /**\r\n * Override dev mode detection.\r\n */\r\n isDevMode?: boolean;\r\n\r\n /**\r\n * Override VC4 detection.\r\n */\r\n isVC4?: boolean;\r\n\r\n /**\r\n * Override deployed on processor detection.\r\n */\r\n isDeployedOnProcessor?: boolean;\r\n\r\n /**\r\n * WebSocket port for certificate acceptance (default: 49200).\r\n */\r\n wsPort?: number;\r\n\r\n /**\r\n * Show debug information panel (default: true).\r\n */\r\n showDebugInfo?: boolean;\r\n\r\n /**\r\n * Auto-redirect delay to login page in ms when deployed on processor (default: 1500).\r\n * Set to 0 to disable auto-redirect.\r\n */\r\n loginRedirectDelay?: number;\r\n\r\n /**\r\n * Custom login URL path (default: '/userlogin.html').\r\n */\r\n loginPath?: string;\r\n\r\n /**\r\n * Auto-retry countdown seconds when returning from cert page (default: 3).\r\n */\r\n retryCountdown?: number;\r\n\r\n /**\r\n * Custom className for the root container.\r\n */\r\n className?: string;\r\n}\r\n\r\nexport function CrisOfflinePage({\r\n processorHost: processorHostProp,\r\n hasAuthToken = false,\r\n isDevMode = isDevModeDefault,\r\n isVC4 = isVC4Default,\r\n isDeployedOnProcessor = isDeployedOnProcessorDefault,\r\n wsPort = 49200,\r\n showDebugInfo = true,\r\n loginRedirectDelay = 1500,\r\n loginPath = '/userlogin.html',\r\n retryCountdown: retryCountdownInit = 3,\r\n className = '',\r\n}: CrisOfflinePageProps) {\r\n const connectionStatus = useConnectionStore((state) => state.status);\r\n const errorReason = useConnectionStore((state) => state.errorReason);\r\n const [hasSeenError, setHasSeenError] = useState(false);\r\n const [lastErrorReason, setLastErrorReason] = useState<ConnectionErrorReason>(null);\r\n const [certPageOpened, setCertPageOpened] = useState(false);\r\n const [retryCountdown, setRetryCountdown] = useState<number | null>(null);\r\n\r\n // Check if we just came back from login page (prevents redirect loop)\r\n // Using a ref-like pattern with useState to only compute once on mount\r\n const [returnedFromLogin] = useState(() => {\r\n if (typeof window === 'undefined') return false;\r\n try {\r\n const wasRedirected = sessionStorage.getItem(LOGIN_REDIRECT_KEY);\r\n if (wasRedirected) {\r\n sessionStorage.removeItem(LOGIN_REDIRECT_KEY);\r\n return true;\r\n }\r\n } catch {\r\n // sessionStorage not available\r\n }\r\n return false;\r\n });\r\n\r\n // Determine processor host\r\n const processorHost = processorHostProp ??\r\n (typeof window !== 'undefined' ? window.location.hostname : 'localhost');\r\n\r\n // Make error state \"sticky\" - once we've seen an error, keep showing error UI\r\n // until the user manually retries or connection succeeds\r\n const isError = connectionStatus === 'error' || (hasSeenError && connectionStatus !== 'connected');\r\n\r\n // Track when we first see an error and capture the reason\r\n useEffect(() => {\r\n if (connectionStatus === 'error') {\r\n setHasSeenError(true);\r\n setLastErrorReason(errorReason);\r\n } else if (connectionStatus === 'connected') {\r\n setHasSeenError(false);\r\n setLastErrorReason(null);\r\n }\r\n }, [connectionStatus, errorReason]);\r\n\r\n // Determine if this is a timeout error (processor unreachable)\r\n const isTimeoutError = lastErrorReason === 'timeout';\r\n\r\n // Build URLs\r\n const loginUrl = `https://${processorHost}${loginPath}`;\r\n const certUrl = `https://${processorHost}:${wsPort}/`;\r\n\r\n // Open certificate page in new tab\r\n const openCertPage = useCallback(() => {\r\n window.open(certUrl, '_blank');\r\n setCertPageOpened(true);\r\n }, [certUrl]);\r\n\r\n // Retry connection by reloading the page\r\n const retryConnection = useCallback(() => {\r\n window.location.reload();\r\n }, []);\r\n\r\n // Auto-retry when user returns after opening cert page\r\n useEffect(() => {\r\n if (!certPageOpened || !isError) return;\r\n\r\n const handleVisibilityChange = () => {\r\n if (document.visibilityState === 'visible') {\r\n // Start countdown when user returns\r\n setRetryCountdown(retryCountdownInit);\r\n }\r\n };\r\n\r\n const handleFocus = () => {\r\n setRetryCountdown(retryCountdownInit);\r\n };\r\n\r\n document.addEventListener('visibilitychange', handleVisibilityChange);\r\n window.addEventListener('focus', handleFocus);\r\n\r\n return () => {\r\n document.removeEventListener('visibilitychange', handleVisibilityChange);\r\n window.removeEventListener('focus', handleFocus);\r\n };\r\n }, [certPageOpened, isError, retryCountdownInit]);\r\n\r\n // Countdown timer for auto-retry\r\n useEffect(() => {\r\n if (retryCountdown === null) return;\r\n if (retryCountdown <= 0) {\r\n retryConnection();\r\n return;\r\n }\r\n\r\n const timer = setTimeout(() => {\r\n setRetryCountdown(retryCountdown - 1);\r\n }, 1000);\r\n\r\n return () => clearTimeout(timer);\r\n }, [retryCountdown, retryConnection]);\r\n\r\n // Auto-redirect to login page ONLY when deployed on processor (not dev mode, not CrestronOne)\r\n // Don't redirect if we just came back from login (prevents redirect loop)\r\n useEffect(() => {\r\n if (isError && isDeployedOnProcessor && loginRedirectDelay > 0 && !returnedFromLogin) {\r\n const timer = setTimeout(() => {\r\n // Set flag before redirecting so we know we came from here\r\n try {\r\n sessionStorage.setItem(LOGIN_REDIRECT_KEY, 'true');\r\n } catch {\r\n // sessionStorage not available\r\n }\r\n window.location.href = loginUrl;\r\n }, loginRedirectDelay);\r\n return () => clearTimeout(timer);\r\n }\r\n }, [isError, isDeployedOnProcessor, loginUrl, loginRedirectDelay, returnedFromLogin]);\r\n\r\n // Gather environment info for debugging\r\n const envInfo = {\r\n userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'N/A',\r\n hasCrComLib: typeof window !== 'undefined' && typeof (window as any).CrComLib !== 'undefined',\r\n hasCrestronPanel: typeof window !== 'undefined' && 'CrestronPanel' in window,\r\n isNativeMode: isNativeMode(),\r\n hostname: typeof window !== 'undefined' ? window.location.hostname : 'N/A',\r\n pathname: typeof window !== 'undefined' ? window.location.pathname : 'N/A',\r\n search: typeof window !== 'undefined' ? window.location.search : '',\r\n protocol: typeof window !== 'undefined' ? window.location.protocol : 'N/A',\r\n isDevMode,\r\n isVC4,\r\n isDeployedOnProcessor,\r\n returnedFromLogin,\r\n processorHost: isDevMode ? processorHost : undefined,\r\n hasAuthToken: isDevMode ? hasAuthToken : undefined,\r\n errorReason: lastErrorReason,\r\n };\r\n\r\n return (\r\n <div className={`cris-offline-page absolute inset-0 bg-gray-900 flex flex-col items-center justify-center overflow-auto py-8 ${className}`}>\r\n <div className=\"text-center max-w-4xl px-4\">\r\n {/* Status indicator */}\r\n <div className=\"mb-8\">\r\n <div className={`w-16 h-16 mx-auto rounded-full flex items-center justify-center ${isError ? 'bg-red-500/20' : 'bg-yellow-500/20'}`}>\r\n <div className={`w-8 h-8 rounded-full ${isError ? 'bg-red-500' : 'bg-yellow-500 animate-pulse'}`} />\r\n </div>\r\n </div>\r\n\r\n {/* Message */}\r\n <h1 className=\"text-2xl font-bold text-white mb-2\">\r\n {isError\r\n ? (isTimeoutError ? 'Processor Unreachable' : 'Connection Error')\r\n : 'Connecting...'}\r\n </h1>\r\n <p className=\"text-gray-400\">\r\n {isError\r\n ? (isDeployedOnProcessor\r\n ? (returnedFromLogin\r\n ? 'Connection failed after login. Please try again.'\r\n : 'Redirecting to login page...')\r\n : isTimeoutError\r\n ? 'Could not connect to the processor. Check the IP address and network connection.'\r\n : 'WebSocket connection failed. SSL certificate may need to be accepted.')\r\n : 'Establishing connection to control system'}\r\n </p>\r\n\r\n {/* Status badge */}\r\n <div className=\"mt-8\">\r\n <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'}`}>\r\n {isError\r\n ? (isTimeoutError\r\n ? 'Connection Timeout'\r\n : isDevMode\r\n ? 'Dev Mode - Auth Error'\r\n : isVC4\r\n ? 'VC4 - Not Authorized'\r\n : 'Auth Error')\r\n : 'Connecting'}\r\n </span>\r\n </div>\r\n\r\n {/* Timeout error UI - processor unreachable */}\r\n {isError && !isDeployedOnProcessor && isTimeoutError && (\r\n <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' }}>\r\n <p className=\"text-orange-400 font-medium\" style={{ fontSize: '1.2em', marginBottom: '1em' }}>\r\n The processor at <span className=\"text-white\">{processorHost}</span> is not responding.\r\n </p>\r\n <ul className=\"text-gray-400 text-left mb-[1em]\" style={{ fontSize: '1em', listStyleType: 'disc', paddingLeft: '1.5em' }}>\r\n <li>Verify the IP address is correct</li>\r\n <li>Check that the processor is powered on</li>\r\n <li>Ensure you are on the same network</li>\r\n </ul>\r\n <div className=\"flex gap-[1em] justify-center\" style={{ overflow: 'visible' }}>\r\n <button\r\n onClick={retryConnection}\r\n className=\"bg-orange-600 hover:bg-orange-500 text-white font-medium rounded-xl transition-colors\"\r\n style={{ padding: '1em 2em', fontSize: '1.2em', whiteSpace: 'nowrap', overflow: 'visible' }}\r\n >\r\n Retry Connection\r\n </button>\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Certificate acceptance UI - for websocket/auth errors in dev mode and VC4 */}\r\n {isError && !isDeployedOnProcessor && !isTimeoutError && (\r\n <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' }}>\r\n {retryCountdown !== null ? (\r\n <div className=\"text-center\" style={{ overflow: 'visible' }}>\r\n <p className=\"text-blue-400 font-medium\" style={{ fontSize: '1.4em', marginBottom: '1em' }}>Retrying connection in {retryCountdown}...</p>\r\n <button\r\n onClick={() => setRetryCountdown(null)}\r\n className=\"bg-gray-600 hover:bg-gray-500 text-white font-medium rounded-xl transition-colors\"\r\n style={{ padding: '1em 2em', fontSize: '1.2em', whiteSpace: 'nowrap', overflow: 'visible' }}\r\n >\r\n Cancel\r\n </button>\r\n </div>\r\n ) : (\r\n <>\r\n <p className=\"text-blue-400 font-medium\" style={{ fontSize: '1.2em', marginBottom: '1em' }}>\r\n {certPageOpened\r\n ? 'Accept the certificate in the new tab, then return here.'\r\n : 'Click below to accept the WebSocket SSL certificate:'}\r\n </p>\r\n <div className=\"flex gap-[1em] justify-center flex-wrap\" style={{ overflow: 'visible' }}>\r\n <button\r\n onClick={openCertPage}\r\n className=\"bg-blue-600 hover:bg-blue-500 text-white font-medium rounded-xl transition-colors\"\r\n style={{ padding: '1em 2em', fontSize: '1.2em', whiteSpace: 'nowrap', overflow: 'visible' }}\r\n >\r\n {certPageOpened ? 'Open Certificate Page Again' : 'Accept Certificate'}\r\n </button>\r\n <button\r\n onClick={retryConnection}\r\n className=\"bg-green-600 hover:bg-green-500 text-white font-medium rounded-xl transition-colors\"\r\n style={{ padding: '1em 2em', fontSize: '1.2em', whiteSpace: 'nowrap', overflow: 'visible' }}\r\n >\r\n Retry Connection\r\n </button>\r\n </div>\r\n {certPageOpened && (\r\n <p className=\"text-gray-400\" style={{ fontSize: '1em', marginTop: '1em' }}>\r\n Connection will auto-retry when you return to this page.\r\n </p>\r\n )}\r\n </>\r\n )}\r\n </div>\r\n )}\r\n\r\n {/* Deployed on processor - returned from login but still failed */}\r\n {isError && isDeployedOnProcessor && returnedFromLogin && (\r\n <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' }}>\r\n <p className=\"text-red-400 font-medium\" style={{ fontSize: '1.2em', marginBottom: '1em' }}>\r\n Connection failed after authentication.\r\n </p>\r\n <p className=\"text-gray-400\" style={{ fontSize: '1em', marginBottom: '1em' }}>\r\n This may be due to an expired session or WebSocket connection issue.\r\n </p>\r\n <div className=\"flex gap-[1em] justify-center flex-wrap\" style={{ overflow: 'visible' }}>\r\n <button\r\n onClick={retryConnection}\r\n className=\"bg-green-600 hover:bg-green-500 text-white font-medium rounded-xl transition-colors\"\r\n style={{ padding: '1em 2em', fontSize: '1.2em', whiteSpace: 'nowrap', overflow: 'visible' }}\r\n >\r\n Retry Connection\r\n </button>\r\n <button\r\n onClick={() => {\r\n try {\r\n sessionStorage.setItem(LOGIN_REDIRECT_KEY, 'true');\r\n } catch {\r\n // sessionStorage not available\r\n }\r\n window.location.href = loginUrl;\r\n }}\r\n className=\"bg-blue-600 hover:bg-blue-500 text-white font-medium rounded-xl transition-colors\"\r\n style={{ padding: '1em 2em', fontSize: '1.2em', whiteSpace: 'nowrap', overflow: 'visible' }}\r\n >\r\n Go to Login\r\n </button>\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Dev mode auth token status */}\r\n {isDevMode && isError && (\r\n <div className=\"mt-6 p-4 bg-yellow-500/10 border border-yellow-500/30 rounded-lg text-left\">\r\n <p className=\"text-yellow-400 text-sm font-medium mb-2\">Dev Mode Configuration:</p>\r\n <p className=\"text-gray-300 text-sm\">\r\n Processor: <span className=\"text-blue-400\">{processorHost}</span>\r\n </p>\r\n <p className=\"text-gray-300 text-sm\">\r\n AuthToken: <span className={hasAuthToken ? 'text-green-400' : 'text-red-400'}>\r\n {hasAuthToken ? 'Present (may be expired)' : 'Missing'}\r\n </span>\r\n </p>\r\n {!hasAuthToken && (\r\n <p className=\"text-yellow-400 text-xs mt-2\">\r\n Add authToken to crestron.config.json or generate new: <code className=\"bg-black/30 px-1 rounded\">websockettoken generate</code>\r\n </p>\r\n )}\r\n </div>\r\n )}\r\n\r\n {/* Debug Info */}\r\n {showDebugInfo && (\r\n <div className=\"mt-12 text-left bg-gray-800 rounded-lg p-4 text-xs font-mono\">\r\n <h2 className=\"text-yellow-400 font-bold mb-3 text-sm\">Environment Debug Info</h2>\r\n <div className=\"space-y-2 text-gray-300\">\r\n <div>\r\n <span className=\"text-gray-500\">CrComLib:</span>{' '}\r\n <span className={envInfo.hasCrComLib ? 'text-green-400' : 'text-red-400'}>\r\n {envInfo.hasCrComLib ? 'Available' : 'Not Found'}\r\n </span>\r\n </div>\r\n <div>\r\n <span className=\"text-gray-500\">CrestronPanel:</span>{' '}\r\n <span className={envInfo.hasCrestronPanel ? 'text-green-400' : 'text-gray-400'}>\r\n {envInfo.hasCrestronPanel ? 'Yes' : 'No'}\r\n </span>\r\n </div>\r\n <div>\r\n <span className=\"text-gray-500\">Native Mode:</span>{' '}\r\n <span className={envInfo.isNativeMode ? 'text-green-400' : 'text-gray-400'}>\r\n {envInfo.isNativeMode ? 'Yes' : 'No (Browser)'}\r\n </span>\r\n </div>\r\n <div>\r\n <span className=\"text-gray-500\">Dev Mode:</span>{' '}\r\n <span className={envInfo.isDevMode ? 'text-yellow-400' : 'text-gray-400'}>\r\n {envInfo.isDevMode ? 'Yes' : 'No'}\r\n </span>\r\n </div>\r\n <div>\r\n <span className=\"text-gray-500\">VC4:</span>{' '}\r\n <span className={envInfo.isVC4 ? 'text-purple-400' : 'text-gray-400'}>\r\n {envInfo.isVC4 ? 'Yes' : 'No'}\r\n </span>\r\n </div>\r\n <div>\r\n <span className=\"text-gray-500\">Deployed on Processor:</span>{' '}\r\n <span className={envInfo.isDeployedOnProcessor ? 'text-cyan-400' : 'text-gray-400'}>\r\n {envInfo.isDeployedOnProcessor ? 'Yes' : 'No'}\r\n </span>\r\n </div>\r\n {envInfo.returnedFromLogin && (\r\n <div>\r\n <span className=\"text-gray-500\">Returned from Login:</span>{' '}\r\n <span className=\"text-yellow-400\">Yes</span>\r\n </div>\r\n )}\r\n <div>\r\n <span className=\"text-gray-500\">Protocol:</span>{' '}\r\n <span className=\"text-blue-400\">{envInfo.protocol}</span>\r\n </div>\r\n <div>\r\n <span className=\"text-gray-500\">Hostname:</span>{' '}\r\n <span className=\"text-blue-400\">{envInfo.hostname}</span>\r\n </div>\r\n {envInfo.isDevMode && envInfo.processorHost && (\r\n <div>\r\n <span className=\"text-gray-500\">Target Processor:</span>{' '}\r\n <span className=\"text-blue-400\">{envInfo.processorHost}</span>\r\n </div>\r\n )}\r\n <div>\r\n <span className=\"text-gray-500\">Path:</span>{' '}\r\n <span className=\"text-blue-400\">{envInfo.pathname}{envInfo.search}</span>\r\n </div>\r\n {envInfo.errorReason && (\r\n <div>\r\n <span className=\"text-gray-500\">Error Reason:</span>{' '}\r\n <span className={envInfo.errorReason === 'timeout' ? 'text-orange-400' : 'text-red-400'}>\r\n {envInfo.errorReason}\r\n </span>\r\n </div>\r\n )}\r\n </div>\r\n </div>\r\n )}\r\n </div>\r\n </div>\r\n );\r\n}\r\n"],"mappings":"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","names":["import_cris_webui_ch5_core","import_jsx_runtime","import_react","import_cris_webui_ch5_core","import_jsx_runtime","import_cris_webui_ch5_core","import_jsx_runtime","import_cris_webui_ch5_core","import_jsx_runtime","import_react","import_cris_webui_ch5_core","import_jsx_runtime"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/components/CrisButton.tsx","../src/utils/icons.ts","../src/utils/touchGuard.ts","../src/components/CrisText.tsx","../src/components/CrisSlider.tsx","../src/components/CrisGauge.tsx","../src/components/CrisSpinner.tsx","../src/components/CrisOfflinePage.tsx"],"sourcesContent":["/**\r\n * @imperosoft/cris-webui-components\r\n *\r\n * CRIS - Crestron React Impero Soft WebUI components library\r\n *\r\n * Provides reusable UI components for Crestron touch panel interfaces.\r\n */\r\n\r\n// Components\r\nexport { CrisButton } from './components/CrisButton';\r\nexport type { CrisButtonProps } from './components/CrisButton';\r\n\r\nexport { CrisText } from './components/CrisText';\r\nexport type { CrisTextProps } from './components/CrisText';\r\n\r\nexport { CrisSlider } from './components/CrisSlider';\r\nexport type { CrisSliderProps } from './components/CrisSlider';\r\n\r\nexport { CrisGauge } from './components/CrisGauge';\r\nexport type { CrisGaugeProps } from './components/CrisGauge';\r\n\r\nexport { CrisSpinner } from './components/CrisSpinner';\r\nexport type { CrisSpinnerProps, SpinnerSpeed } from './components/CrisSpinner';\r\n\r\nexport { CrisOfflinePage } from './components/CrisOfflinePage';\r\nexport type { CrisOfflinePageProps } from './components/CrisOfflinePage';\r\n\r\n// Utilities\r\nexport { configureIcons, getIconUrl, getIconFilter, getIconConfig } from './utils/icons';\r\nexport type { IconConfig } from './utils/icons';\r\n","import { useState, useRef, useEffect, useCallback, ReactNode } from 'react';\r\nimport { useDigital, useJoinsStore } from '@imperosoft/cris-webui-ch5-core';\r\nimport { getIconUrl, getIconFilter } from '../utils/icons';\r\nimport { touchStart, touchEnd, isTouchActive } from '../utils/touchGuard';\r\n\r\nexport interface CrisButtonProps {\r\n /** Digital join for press action */\r\n join?: number;\r\n /** Digital join for feedback (defaults to join) */\r\n joinFeedback?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Button text */\r\n text?: string;\r\n /** Text when pressed (local feedback) */\r\n textPressed?: string;\r\n /** Text when selected (controller feedback) */\r\n textSelected?: string;\r\n\r\n /** Icon as ReactNode (for custom SVG components) */\r\n icon?: ReactNode;\r\n /** Icon name (loads SVG from configured path, e.g., 'motor-stop') */\r\n iconName?: string;\r\n /** Icon CSS class (for CSS-based icons, e.g., 'ico-motor-stop') */\r\n iconClass?: string;\r\n /** Icon size - number (px), string ('50%', '2rem'), or preset ('xs'|'sm'|'md'|'lg'|'xl') */\r\n iconSize?: number | string | 'xs' | 'sm' | 'md' | 'lg' | 'xl';\r\n /** Icon container size as percentage (default: 80% for top/bottom, 20% for left/right) */\r\n iconContainerSize?: string;\r\n /** Icon inline style */\r\n iconStyle?: React.CSSProperties;\r\n /** Icon position relative to text */\r\n iconPosition?: 'left' | 'right' | 'top' | 'bottom';\r\n\r\n /** Icon name when active (overrides iconName when button is active) */\r\n iconNameActive?: string;\r\n /** Icon CSS class when active (overrides iconClass when button is active) */\r\n iconClassActive?: string;\r\n /** Icon inline style when active (merged with iconStyle when button is active) */\r\n iconStyleActive?: React.CSSProperties;\r\n\r\n /** Show controller feedback styling */\r\n showControlFeedback?: boolean;\r\n /** Show local press feedback styling */\r\n showLocalFeedback?: boolean;\r\n /** Suppress click actions (display only) */\r\n suppressKeyClicks?: boolean;\r\n\r\n /** Smart object ID (for smarts instead of joins) */\r\n smartId?: number;\r\n\r\n /** Custom class names */\r\n className?: string;\r\n /** Class when active (controller feedback) */\r\n classActive?: string;\r\n /** Class when pressed (local feedback) */\r\n classPressed?: string;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n\r\n /** Children content */\r\n children?: ReactNode;\r\n\r\n /** Custom click handler (called on press) */\r\n onPress?: () => void;\r\n /** Custom release handler */\r\n onRelease?: () => void;\r\n\r\n /** Enable debug logging for this button */\r\n debug?: boolean;\r\n}\r\n\r\nexport function CrisButton({\r\n join,\r\n joinFeedback,\r\n joinEnable,\r\n joinVisible,\r\n text,\r\n textPressed,\r\n textSelected,\r\n icon,\r\n iconName,\r\n iconClass,\r\n iconSize,\r\n iconContainerSize,\r\n iconStyle,\r\n iconPosition = 'top',\r\n iconNameActive,\r\n iconClassActive,\r\n iconStyleActive,\r\n showControlFeedback = true,\r\n showLocalFeedback = true,\r\n suppressKeyClicks = false,\r\n smartId,\r\n className = '',\r\n classActive = '',\r\n classPressed = '',\r\n classDisabled = '',\r\n children,\r\n onPress,\r\n onRelease,\r\n debug = false,\r\n}: CrisButtonProps) {\r\n // Debug logging helper — stable ref to avoid re-triggering effects\r\n const debugRef = useRef(debug);\r\n debugRef.current = debug;\r\n const joinRef = useRef(join);\r\n joinRef.current = join;\r\n const log = useCallback((msg: string, data?: unknown) => {\r\n if (debugRef.current) console.log(`[CrisButton:${joinRef.current}] ${msg}`, data ?? '');\r\n }, []);\r\n const [pressed, setPressed] = useState(false);\r\n const pressedRef = useRef(false);\r\n const touchingRef = useRef(false);\r\n const touchStartedHereRef = useRef(false);\r\n\r\n // Get join values reactively\r\n const feedbackJoin = joinFeedback ?? join;\r\n const feedback = useDigital(feedbackJoin ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Get action method\r\n const dSet = useJoinsStore((state) => state.dSet);\r\n\r\n // Determine if button is enabled\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n\r\n // Determine if button is visible\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Release if button becomes invisible while pressed\r\n useEffect(() => {\r\n log('visibility effect', { isVisible, pressedRef: pressedRef.current });\r\n if (!isVisible && pressedRef.current) {\r\n log('VISIBILITY RELEASE - button hidden while pressed');\r\n pressedRef.current = false;\r\n setPressed(false);\r\n touchingRef.current = false;\r\n touchStartedHereRef.current = false;\r\n if (join != null && smartId == null) {\r\n log('sending release via dSet(false)');\r\n dSet(join, false);\r\n }\r\n }\r\n }, [isVisible, join, smartId, dSet]);\r\n\r\n // Release on unmount if button was pressed (handles page transitions)\r\n useEffect(() => {\r\n return () => {\r\n if (pressedRef.current && join != null && smartId == null) {\r\n log('UNMOUNT RELEASE - component unmounting while pressed');\r\n dSet(join, false);\r\n }\r\n };\r\n }, [join, smartId, dSet, log]);\r\n\r\n // Determine current feedback state\r\n const hasControlFeedback = showControlFeedback && feedbackJoin != null && feedback;\r\n const hasPressedFeedback = showLocalFeedback && pressed && isEnabled;\r\n const isActive = hasControlFeedback || hasPressedFeedback;\r\n\r\n // Determine current text\r\n let currentText = text ?? '';\r\n if (hasPressedFeedback && textPressed != null) {\r\n currentText = textPressed;\r\n } else if (hasControlFeedback && textSelected != null) {\r\n currentText = textSelected;\r\n }\r\n\r\n // Event handlers\r\n const handlePress = () => {\r\n log('handlePress called', { suppressKeyClicks, pressedRef: pressedRef.current, isEnabled });\r\n if (suppressKeyClicks) {\r\n log('BLOCKED: suppressKeyClicks');\r\n return;\r\n }\r\n if (pressedRef.current) {\r\n log('BLOCKED: already pressed');\r\n return;\r\n }\r\n\r\n pressedRef.current = true;\r\n setPressed(true);\r\n\r\n if (!isEnabled) {\r\n log('SKIPPED dSet: not enabled');\r\n return;\r\n }\r\n\r\n // Custom handler\r\n onPress?.();\r\n\r\n // Send to controller\r\n if (join != null && smartId == null) {\r\n log('SENDING PRESS via dSet(true)');\r\n dSet(join, true);\r\n }\r\n // TODO: Add smartId support when smarts are implemented in core\r\n };\r\n\r\n const handleRelease = () => {\r\n log('handleRelease called', { suppressKeyClicks, pressedRef: pressedRef.current, isEnabled });\r\n if (suppressKeyClicks) {\r\n log('BLOCKED: suppressKeyClicks');\r\n return;\r\n }\r\n if (!pressedRef.current) {\r\n log('BLOCKED: not pressed');\r\n return;\r\n }\r\n\r\n pressedRef.current = false;\r\n setPressed(false);\r\n\r\n if (!isEnabled) {\r\n log('SKIPPED dSet: not enabled');\r\n return;\r\n }\r\n\r\n // Custom handler\r\n onRelease?.();\r\n\r\n // Send to controller\r\n if (join != null && smartId == null) {\r\n log('SENDING RELEASE via dSet(false)');\r\n dSet(join, false);\r\n }\r\n // TODO: Add smartId support when smarts are implemented in core\r\n };\r\n\r\n // Touch event handlers - use global touch guard to prevent phantom clicks\r\n // when buttons swap visibility (e.g., back button hides, power button appears)\r\n const handleTouchStart = () => {\r\n log('handleTouchStart');\r\n touchStart(); // Global guard\r\n touchingRef.current = true;\r\n touchStartedHereRef.current = true;\r\n handlePress();\r\n };\r\n\r\n const handleTouchEnd = () => {\r\n log('handleTouchEnd', { touchStartedHereRef: touchStartedHereRef.current });\r\n touchEnd(); // Global guard with delayed reset\r\n touchingRef.current = true;\r\n // Only fire release if touch actually started on this button\r\n if (touchStartedHereRef.current) {\r\n touchStartedHereRef.current = false;\r\n handleRelease();\r\n } else {\r\n log('SKIPPED handleRelease: touch did not start here');\r\n }\r\n };\r\n\r\n const handleTouchCancel = () => {\r\n log('handleTouchCancel');\r\n touchEnd(); // Global guard with delayed reset\r\n touchingRef.current = true;\r\n touchStartedHereRef.current = false;\r\n handleRelease();\r\n };\r\n\r\n // Mouse event handlers - skip if any touch event is active (global check)\r\n const handleMouseDown = () => {\r\n if (isTouchActive() || touchingRef.current) return;\r\n handlePress();\r\n };\r\n\r\n const handleMouseUp = () => {\r\n if (isTouchActive() || touchingRef.current) return;\r\n handleRelease();\r\n };\r\n\r\n const handleMouseLeave = () => {\r\n if (isTouchActive() || touchingRef.current) return;\r\n handleRelease();\r\n };\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const classes = [\r\n 'cris-button',\r\n className,\r\n hasControlFeedback && classActive,\r\n hasPressedFeedback && classPressed,\r\n !isEnabled && classDisabled,\r\n hasControlFeedback && 'active',\r\n hasPressedFeedback && 'pressed',\r\n !isEnabled && 'disabled',\r\n suppressKeyClicks && 'no-hover',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Flex direction based on icon position\r\n const flexDirection =\r\n iconPosition === 'top'\r\n ? 'flex-col'\r\n : iconPosition === 'bottom'\r\n ? 'flex-col-reverse'\r\n : iconPosition === 'left'\r\n ? 'flex-row'\r\n : 'flex-row-reverse';\r\n\r\n // Determine if we have any icon to show\r\n const hasIcon = icon != null || iconName != null || iconClass != null || iconNameActive != null || iconClassActive != null;\r\n\r\n // Determine current icon properties based on active state\r\n const currentIconName = isActive && iconNameActive != null ? iconNameActive : iconName;\r\n const currentIconClass = isActive && iconClassActive != null ? iconClassActive : iconClass;\r\n const currentIconStyle = isActive && iconStyleActive != null ? { ...iconStyle, ...iconStyleActive } : iconStyle;\r\n\r\n // Size presets mapping\r\n const sizePresets: Record<string, string> = {\r\n xs: '16px',\r\n sm: '24px',\r\n md: '32px',\r\n lg: '48px',\r\n xl: '64px',\r\n };\r\n\r\n // Calculate icon size value\r\n const getIconSizeValue = (): string | undefined => {\r\n if (iconSize == null) return undefined;\r\n if (typeof iconSize === 'number') return `${iconSize}px`;\r\n if (iconSize in sizePresets) return sizePresets[iconSize];\r\n return iconSize; // Already a string like '50%' or '2rem'\r\n };\r\n\r\n const iconSizeValue = getIconSizeValue();\r\n\r\n // Build icon classes\r\n const iconClasses = [\r\n 'cris-button-icon',\r\n currentIconClass,\r\n hasControlFeedback && 'active',\r\n hasPressedFeedback && 'pressed',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Build icon style with size and filter support\r\n const computedIconStyle: React.CSSProperties = {\r\n ...currentIconStyle,\r\n };\r\n\r\n // Apply size\r\n if (iconSizeValue) {\r\n computedIconStyle.width = iconSizeValue;\r\n computedIconStyle.height = iconSizeValue;\r\n }\r\n\r\n // Apply filter from config if using iconName (only if not overridden by iconStyle/iconStyleActive)\r\n if (currentIconName && !currentIconStyle?.filter) {\r\n const filter = getIconFilter(isActive);\r\n if (filter) {\r\n computedIconStyle.filter = filter;\r\n }\r\n }\r\n\r\n // Render icon element\r\n const renderIconElement = () => {\r\n if (icon) {\r\n // ReactNode icon (custom component)\r\n return <span className={iconClasses} style={computedIconStyle}>{icon}</span>;\r\n }\r\n\r\n if (currentIconName) {\r\n // Icon by name (loads from configured path)\r\n return (\r\n <img\r\n className={iconClasses}\r\n style={{ ...computedIconStyle, pointerEvents: 'none' }}\r\n src={getIconUrl(currentIconName)}\r\n alt=\"\"\r\n draggable={false}\r\n />\r\n );\r\n }\r\n\r\n if (currentIconClass) {\r\n // CSS-based icon (like Angular version)\r\n return <img className={iconClasses} style={{ ...computedIconStyle, pointerEvents: 'none' }} alt=\"\" draggable={false} />;\r\n }\r\n\r\n return null;\r\n };\r\n\r\n // Calculate icon container size (default: 100% if no text, otherwise 80% for top/bottom, 20% for left/right)\r\n const isVertical = iconPosition === 'top' || iconPosition === 'bottom';\r\n const hasText = currentText !== '';\r\n const defaultContainerSize = hasText ? (isVertical ? '80%' : '20%') : '100%';\r\n const containerSize = iconContainerSize ?? defaultContainerSize;\r\n\r\n // Calculate text container size (remaining space)\r\n const textContainerSize = `calc(100% - ${containerSize})`;\r\n\r\n // Container style for icon area\r\n const iconContainerStyle: React.CSSProperties = isVertical\r\n ? { height: containerSize, width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }\r\n : { width: containerSize, height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' };\r\n\r\n // Container style for text area\r\n const textContainerStyle: React.CSSProperties = isVertical\r\n ? { height: textContainerSize, width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }\r\n : { width: textContainerSize, height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' };\r\n\r\n // Check if button has 'free' class (absolute positioning)\r\n const isFreePositioned = className.includes('free');\r\n\r\n return (\r\n <div\r\n className={`${classes} flex items-center justify-center ${flexDirection}`}\r\n style={{\r\n cursor: suppressKeyClicks ? 'default' : 'pointer',\r\n position: isFreePositioned ? undefined : 'relative',\r\n overflow: isFreePositioned ? undefined : 'hidden',\r\n touchAction: 'none',\r\n userSelect: 'none',\r\n WebkitUserSelect: 'none',\r\n }}\r\n onMouseDown={handleMouseDown}\r\n onMouseUp={handleMouseUp}\r\n onMouseLeave={handleMouseLeave}\r\n onTouchStart={handleTouchStart}\r\n onTouchEnd={handleTouchEnd}\r\n onTouchCancel={handleTouchCancel}\r\n >\r\n {/* Press overlay - shows on press for both desktop and touch devices */}\r\n <div\r\n className=\"cris-button-press-overlay\"\r\n style={{\r\n position: 'absolute',\r\n inset: 0,\r\n backgroundColor: 'black',\r\n opacity: pressed ? 0.1 : 0,\r\n transition: 'opacity 0.1s',\r\n pointerEvents: 'none',\r\n borderRadius: 'inherit',\r\n }}\r\n />\r\n {children}\r\n {hasIcon && (\r\n <div className=\"cris-button-icon-container\" style={iconContainerStyle}>\r\n {renderIconElement()}\r\n </div>\r\n )}\r\n {currentText && (\r\n <div className=\"cris-button-text-container\" style={textContainerStyle}>\r\n <span className=\"cris-button-text\">{currentText}</span>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n","/**\r\n * Icon configuration and utilities for CRIS components\r\n *\r\n * Supports different base paths for dev vs production (Crestron controller)\r\n */\r\n\r\nexport interface IconConfig {\r\n /** Base path for icon assets (e.g., '/assets/icons/' or 'http://controller/icons/') */\r\n basePath: string;\r\n /** File extension (default: 'svg') */\r\n extension: string;\r\n /** Default filter for inactive state */\r\n defaultFilter?: string;\r\n /** Default filter for active state */\r\n activeFilter?: string;\r\n}\r\n\r\n// Default configuration\r\nlet iconConfig: IconConfig = {\r\n basePath: '/assets/icons/',\r\n extension: 'svg',\r\n defaultFilter: undefined,\r\n activeFilter: undefined,\r\n};\r\n\r\n/**\r\n * Configure icon paths and defaults\r\n * Call this once at app startup\r\n *\r\n * @example\r\n * // Development\r\n * configureIcons({ basePath: '/assets/icons/' });\r\n *\r\n * // Production on Crestron controller\r\n * configureIcons({ basePath: '/html/icons/' });\r\n *\r\n * // With color filters\r\n * configureIcons({\r\n * basePath: '/assets/icons/',\r\n * defaultFilter: 'brightness(0) invert(1)', // white\r\n * activeFilter: 'brightness(0) invert(0.5) sepia(1) saturate(5) hue-rotate(0deg)', // red\r\n * });\r\n */\r\nexport function configureIcons(config: Partial<IconConfig>): void {\r\n iconConfig = { ...iconConfig, ...config };\r\n}\r\n\r\n/**\r\n * Get current icon configuration\r\n */\r\nexport function getIconConfig(): IconConfig {\r\n return iconConfig;\r\n}\r\n\r\n/**\r\n * Get full URL for an icon\r\n *\r\n * @param name - Icon name (without path or extension), e.g., 'motor-stop'\r\n * @returns Full URL to the icon\r\n */\r\nexport function getIconUrl(name: string): string {\r\n if (!name) return '';\r\n\r\n // If already a full URL or path, return as-is\r\n if (name.startsWith('http') || name.startsWith('/') || name.startsWith('.')) {\r\n return name;\r\n }\r\n\r\n const { basePath, extension } = iconConfig;\r\n const normalizedBase = basePath.endsWith('/') ? basePath : `${basePath}/`;\r\n return `${normalizedBase}${name}.${extension}`;\r\n}\r\n\r\n/**\r\n * Get CSS filter for icon state\r\n */\r\nexport function getIconFilter(active: boolean): string | undefined {\r\n return active ? iconConfig.activeFilter : iconConfig.defaultFilter;\r\n}\r\n","/**\r\n * Global touch state guard - shared across all CRIS components\r\n *\r\n * This prevents phantom clicks when elements swap visibility during touch interactions.\r\n * When a button is touched and triggers a visibility change (e.g., back button hides,\r\n * power button appears), the newly visible element would receive the synthetic click\r\n * event from the browser. The global touch flag blocks this.\r\n */\r\n\r\nlet globalTouchActive = false;\r\nlet touchResetTimeout: number | null = null;\r\n\r\n/** Duration to keep touch guard active after touch ends (blocks synthetic click) */\r\nconst TOUCH_GUARD_DURATION_MS = 300;\r\n\r\n/**\r\n * Mark that a touch interaction has started.\r\n * Call this in touchStart handlers.\r\n */\r\nexport function touchStart(): void {\r\n globalTouchActive = true;\r\n // Clear any pending reset\r\n if (touchResetTimeout !== null) {\r\n window.clearTimeout(touchResetTimeout);\r\n touchResetTimeout = null;\r\n }\r\n}\r\n\r\n/**\r\n * Mark that a touch interaction has ended.\r\n * The guard remains active for a short duration to block synthetic click events.\r\n * Call this in touchEnd/touchCancel handlers.\r\n */\r\nexport function touchEnd(): void {\r\n globalTouchActive = true;\r\n // Reset after delay to block synthetic click\r\n if (touchResetTimeout !== null) {\r\n window.clearTimeout(touchResetTimeout);\r\n }\r\n touchResetTimeout = window.setTimeout(() => {\r\n globalTouchActive = false;\r\n touchResetTimeout = null;\r\n }, TOUCH_GUARD_DURATION_MS);\r\n}\r\n\r\n/**\r\n * Check if a touch interaction is currently active.\r\n * Use this in mouse handlers to skip if touch is active.\r\n */\r\nexport function isTouchActive(): boolean {\r\n return globalTouchActive;\r\n}\r\n","import { ReactNode } from 'react';\r\nimport { useDigital, useSerial, useCipDecode } from '@imperosoft/cris-webui-ch5-core';\r\n\r\nexport interface CrisTextProps {\r\n /** Serial join for indirect text */\r\n joinIndirectText?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n /** Static text (used if no joinIndirectText) */\r\n text?: string;\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n /** Text element CSS class */\r\n textClassName?: string;\r\n /** Text element inline style */\r\n textStyle?: React.CSSProperties;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n /** Children content */\r\n children?: ReactNode;\r\n}\r\n\r\nexport function CrisText({\r\n joinIndirectText,\r\n joinEnable,\r\n joinVisible,\r\n text,\r\n className = '',\r\n style,\r\n textClassName = '',\r\n textStyle,\r\n classDisabled = '',\r\n children,\r\n}: CrisTextProps) {\r\n // Get join values reactively\r\n const indirectText = useSerial(joinIndirectText ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine if text is enabled\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n\r\n // Determine if text is visible\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Get the raw text - indirect text or static text\r\n const rawText = joinIndirectText != null ? indirectText : (text ?? '');\r\n\r\n // Decode CIP patterns in the text\r\n const currentText = useCipDecode(rawText);\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const containerClasses = [\r\n 'cris-text',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n return (\r\n <div className={containerClasses} style={style}>\r\n {children}\r\n {currentText && (\r\n <span className={`cris-text-text ${textClassName}`} style={textStyle}>\r\n {currentText}\r\n </span>\r\n )}\r\n </div>\r\n );\r\n}\r\n","import { useState, useRef, useEffect, useCallback } from 'react';\r\nimport { useDigital, useAnalog, useJoinsStore } from '@imperosoft/cris-webui-ch5-core';\r\nimport { touchStart, touchEnd, isTouchActive } from '../utils/touchGuard';\r\n\r\nexport interface CrisSliderProps {\r\n /** Analog join for value (shared with digital if joinDigital not set) */\r\n join?: number;\r\n /** Digital join for press/release feedback */\r\n joinDigital?: number;\r\n /** Analog join for value (overrides join) */\r\n joinAnalog?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Minimum value (default 0) */\r\n minValue?: number;\r\n /** Maximum value (default 65535) */\r\n maxValue?: number;\r\n /** Horizontal orientation (default false = vertical) */\r\n horizontal?: boolean;\r\n /** Hide fill bar */\r\n fillHidden?: boolean;\r\n /** Track size as percentage of container (default 20) */\r\n trackSizePercent?: number;\r\n /** Thumb size as percentage of container (default 4) */\r\n thumbSizePercent?: number;\r\n /** Delay in ms after drag before updating from feedback (default 1000) */\r\n delayMsAfterDragUpdateFeedback?: number;\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n /** Bar/track CSS class */\r\n barClassName?: string;\r\n /** Bar/track inline style */\r\n barStyle?: React.CSSProperties;\r\n /** Fill CSS class */\r\n fillClassName?: string;\r\n /** Fill inline style */\r\n fillStyle?: React.CSSProperties;\r\n /** Thumb CSS class */\r\n thumbClassName?: string;\r\n /** Thumb inline style */\r\n thumbStyle?: React.CSSProperties;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n}\r\n\r\nexport function CrisSlider({\r\n join,\r\n joinDigital,\r\n joinAnalog,\r\n joinEnable,\r\n joinVisible,\r\n minValue = 0,\r\n maxValue = 65535,\r\n horizontal = false,\r\n fillHidden = false,\r\n trackSizePercent = 20,\r\n thumbSizePercent = 4,\r\n delayMsAfterDragUpdateFeedback = 1000,\r\n className = '',\r\n style,\r\n barClassName = '',\r\n barStyle,\r\n fillClassName = '',\r\n fillStyle,\r\n thumbClassName = '',\r\n thumbStyle,\r\n classDisabled = '',\r\n}: CrisSliderProps) {\r\n // Effective joins\r\n const effectiveAnalogJoin = joinAnalog ?? join;\r\n const effectiveDigitalJoin = joinDigital ?? join;\r\n\r\n // Get join values reactively\r\n const analogValue = useAnalog(effectiveAnalogJoin ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Action methods\r\n const dSet = useJoinsStore((state) => state.dSet);\r\n const aSet = useJoinsStore((state) => state.aSet);\r\n\r\n // Local state\r\n const [ratioCurrent, setRatioCurrent] = useState(0);\r\n const [isDragging, setIsDragging] = useState(false);\r\n const isDraggingOrJustAfterRef = useRef(false);\r\n const ratioBeforeDragRef = useRef(0);\r\n const afterDragTimeoutRef = useRef<number | null>(null);\r\n const touchingRef = useRef(false);\r\n\r\n // Determine if enabled and visible\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Release if slider becomes invisible while dragging\r\n useEffect(() => {\r\n if (!isVisible && isDraggingOrJustAfterRef.current) {\r\n setIsDragging(false);\r\n isDraggingOrJustAfterRef.current = false;\r\n touchingRef.current = false;\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, false);\r\n }\r\n }\r\n }, [isVisible, effectiveDigitalJoin, dSet]);\r\n\r\n // Release on unmount if slider was being dragged (handles page transitions)\r\n useEffect(() => {\r\n return () => {\r\n if (isDraggingOrJustAfterRef.current && effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, false);\r\n }\r\n };\r\n }, [effectiveDigitalJoin, dSet]);\r\n\r\n // Convert analog value to ratio\r\n const analogToRatio = useCallback(\r\n (value: number) => {\r\n const range = maxValue - minValue;\r\n if (range <= 0) return 0;\r\n return Math.max(0, Math.min(1, (value - minValue) / range));\r\n },\r\n [minValue, maxValue]\r\n );\r\n\r\n // Convert ratio to analog value\r\n const ratioToAnalog = useCallback(\r\n (ratio: number) => {\r\n const range = maxValue - minValue;\r\n return Math.round(minValue + ratio * range);\r\n },\r\n [minValue, maxValue]\r\n );\r\n\r\n // Update ratio from controller feedback\r\n const updateFromFeedback = useCallback(() => {\r\n if (!isDraggingOrJustAfterRef.current && effectiveAnalogJoin != null) {\r\n setRatioCurrent(analogToRatio(analogValue));\r\n }\r\n }, [analogValue, analogToRatio, effectiveAnalogJoin]);\r\n\r\n // Update from feedback when analog value changes\r\n useEffect(() => {\r\n updateFromFeedback();\r\n }, [updateFromFeedback]);\r\n\r\n // Handle drag start\r\n const handleDragStart = useCallback(() => {\r\n if (!isEnabled) return;\r\n\r\n setIsDragging(true);\r\n\r\n // Send digital press\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, true);\r\n }\r\n\r\n // Save ratio before drag\r\n if (!isDraggingOrJustAfterRef.current) {\r\n ratioBeforeDragRef.current = ratioCurrent;\r\n }\r\n isDraggingOrJustAfterRef.current = true;\r\n\r\n // Clear any pending timeout\r\n if (afterDragTimeoutRef.current !== null) {\r\n window.clearTimeout(afterDragTimeoutRef.current);\r\n afterDragTimeoutRef.current = null;\r\n }\r\n }, [isEnabled, effectiveDigitalJoin, dSet, ratioCurrent]);\r\n\r\n // Handle drag end\r\n const handleDragEnd = useCallback(() => {\r\n if (!isDragging) return;\r\n\r\n setIsDragging(false);\r\n\r\n // Send digital release\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, false);\r\n }\r\n\r\n // Schedule feedback update after delay\r\n if (delayMsAfterDragUpdateFeedback > 0) {\r\n afterDragTimeoutRef.current = window.setTimeout(() => {\r\n isDraggingOrJustAfterRef.current = false;\r\n updateFromFeedback();\r\n afterDragTimeoutRef.current = null;\r\n }, delayMsAfterDragUpdateFeedback);\r\n } else {\r\n isDraggingOrJustAfterRef.current = false;\r\n updateFromFeedback();\r\n }\r\n }, [isDragging, effectiveDigitalJoin, dSet, delayMsAfterDragUpdateFeedback, updateFromFeedback]);\r\n\r\n // Handle move/drag\r\n const handleMove = useCallback(\r\n (clientX: number, clientY: number, bounds: DOMRect) => {\r\n if (!isDragging) return;\r\n\r\n let newRatio: number;\r\n if (horizontal) {\r\n newRatio = (clientX - bounds.left) / bounds.width;\r\n } else {\r\n newRatio = 1 - (clientY - bounds.top) / bounds.height;\r\n }\r\n newRatio = Math.max(0, Math.min(1, newRatio));\r\n\r\n setRatioCurrent(newRatio);\r\n\r\n // Send analog value\r\n if (effectiveAnalogJoin != null) {\r\n aSet(effectiveAnalogJoin, ratioToAnalog(newRatio));\r\n }\r\n },\r\n [isDragging, horizontal, effectiveAnalogJoin, aSet, ratioToAnalog]\r\n );\r\n\r\n // Mouse handlers - skip if any touch event is active (global check)\r\n const handleMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (isTouchActive() || touchingRef.current) return;\r\n if (event.button !== 0) return;\r\n\r\n handleDragStart();\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n };\r\n\r\n const handleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (!isDragging) return;\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n };\r\n\r\n const handleMouseUp = () => {\r\n handleDragEnd();\r\n };\r\n\r\n const handleMouseLeave = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (isDragging) {\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n }\r\n handleDragEnd();\r\n };\r\n\r\n // Touch handlers - use global touch guard\r\n const handleTouchStart = (event: React.TouchEvent<HTMLDivElement>) => {\r\n touchStart(); // Global guard\r\n touchingRef.current = true;\r\n handleDragStart();\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n };\r\n\r\n const handleTouchMove = (event: React.TouchEvent<HTMLDivElement>) => {\r\n if (!isDragging) return;\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n };\r\n\r\n const handleTouchEnd = () => {\r\n touchEnd(); // Global guard with delayed reset\r\n handleDragEnd();\r\n };\r\n\r\n const handleTouchCancel = (event: React.TouchEvent<HTMLDivElement>) => {\r\n touchEnd(); // Global guard with delayed reset\r\n if (isDragging && event.touches.length > 0) {\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n }\r\n handleDragEnd();\r\n };\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const containerClasses = [\r\n 'cris-slider',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Calculate bar style\r\n const computedBarStyle: React.CSSProperties = {\r\n ...barStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const width = 100 - thumbSizePercent;\r\n const left = (100 - width) / 2;\r\n computedBarStyle.height = `${trackSizePercent}%`;\r\n computedBarStyle.top = `${(100 - trackSizePercent) / 2}%`;\r\n computedBarStyle.width = `${width}%`;\r\n computedBarStyle.left = `${left}%`;\r\n } else {\r\n const height = 100 - thumbSizePercent;\r\n const top = (100 - height) / 2;\r\n computedBarStyle.width = `${trackSizePercent}%`;\r\n computedBarStyle.left = `${(100 - trackSizePercent) / 2}%`;\r\n computedBarStyle.height = `${height}%`;\r\n computedBarStyle.top = `${top}%`;\r\n }\r\n\r\n // Calculate fill style\r\n const computedFillStyle: React.CSSProperties = {\r\n ...fillStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const range = 100 - thumbSizePercent;\r\n const left = thumbSizePercent / 2;\r\n const width = range * ratioCurrent;\r\n computedFillStyle.height = `${trackSizePercent}%`;\r\n computedFillStyle.top = `${(100 - trackSizePercent) / 2}%`;\r\n computedFillStyle.width = `${width}%`;\r\n computedFillStyle.left = `${left}%`;\r\n } else {\r\n const range = 100 - thumbSizePercent;\r\n const top = 100 - (range * ratioCurrent + thumbSizePercent / 2);\r\n const height = range + thumbSizePercent / 2 - top;\r\n computedFillStyle.width = `${trackSizePercent}%`;\r\n computedFillStyle.left = `${(100 - trackSizePercent) / 2}%`;\r\n computedFillStyle.height = `${height}%`;\r\n computedFillStyle.top = `${top}%`;\r\n }\r\n\r\n // Calculate thumb style\r\n const computedThumbStyle: React.CSSProperties = {\r\n ...thumbStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const maxLeft = 100 - thumbSizePercent;\r\n const left = maxLeft * ratioCurrent;\r\n computedThumbStyle.width = `${thumbSizePercent}%`;\r\n computedThumbStyle.height = '100%';\r\n computedThumbStyle.left = `${left}%`;\r\n } else {\r\n const maxTop = 100 - thumbSizePercent;\r\n const top = maxTop - maxTop * ratioCurrent;\r\n computedThumbStyle.width = '100%';\r\n computedThumbStyle.height = `${thumbSizePercent}%`;\r\n computedThumbStyle.top = `${top}%`;\r\n }\r\n\r\n return (\r\n <div\r\n className={containerClasses}\r\n style={{\r\n ...style,\r\n position: 'relative',\r\n cursor: isEnabled ? 'pointer' : 'default',\r\n touchAction: 'none',\r\n userSelect: 'none',\r\n }}\r\n onMouseDown={handleMouseDown}\r\n onMouseMove={handleMouseMove}\r\n onMouseUp={handleMouseUp}\r\n onMouseLeave={handleMouseLeave}\r\n onTouchStart={handleTouchStart}\r\n onTouchMove={handleTouchMove}\r\n onTouchEnd={handleTouchEnd}\r\n onTouchCancel={handleTouchCancel}\r\n >\r\n {/* Bar/Track */}\r\n <div className={`cris-slider-bar ${barClassName}`} style={computedBarStyle} />\r\n\r\n {/* Fill */}\r\n {!fillHidden && (\r\n <div className={`cris-slider-fill ${fillClassName}`} style={computedFillStyle} />\r\n )}\r\n\r\n {/* Thumb */}\r\n <div className={`cris-slider-thumb ${thumbClassName}`} style={computedThumbStyle} />\r\n </div>\r\n );\r\n}\r\n","import { useAnalog, useDigital } from '@imperosoft/cris-webui-ch5-core';\r\n\r\nexport interface CrisGaugeProps {\r\n /** Static value (used if no join) */\r\n value?: number;\r\n /** Analog join for value */\r\n join?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Minimum value (default 0) */\r\n minValue?: number;\r\n /** Maximum value (default 65535) */\r\n maxValue?: number;\r\n /** Number of segments (default 20) */\r\n segments?: number;\r\n /** Invert direction (default false) */\r\n inverted?: boolean;\r\n /** Orientation (default vertical) */\r\n orientation?: 'vertical' | 'horizontal';\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n\r\n /** Inactive segment CSS class */\r\n inactiveSegmentClassName?: string;\r\n /** Inactive segment inline style */\r\n inactiveSegmentStyle?: React.CSSProperties;\r\n\r\n /** Active segment CSS class (all levels) */\r\n segmentClassName?: string;\r\n /** Active segment inline style (all levels) */\r\n segmentStyle?: React.CSSProperties;\r\n\r\n /** Low level segment CSS class (0-60%) */\r\n lowSegmentClassName?: string;\r\n /** Low level segment inline style */\r\n lowSegmentStyle?: React.CSSProperties;\r\n\r\n /** Medium level segment CSS class (60-80%) */\r\n mediumSegmentClassName?: string;\r\n /** Medium level segment inline style */\r\n mediumSegmentStyle?: React.CSSProperties;\r\n\r\n /** High level segment CSS class (80-100%) */\r\n highSegmentClassName?: string;\r\n /** High level segment inline style */\r\n highSegmentStyle?: React.CSSProperties;\r\n\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n}\r\n\r\nexport function CrisGauge({\r\n value,\r\n join,\r\n joinEnable,\r\n joinVisible,\r\n minValue = 0,\r\n maxValue = 65535,\r\n segments = 20,\r\n inverted = false,\r\n orientation = 'vertical',\r\n className = '',\r\n style,\r\n inactiveSegmentClassName = '',\r\n inactiveSegmentStyle,\r\n segmentClassName = '',\r\n segmentStyle,\r\n lowSegmentClassName = '',\r\n lowSegmentStyle,\r\n mediumSegmentClassName = '',\r\n mediumSegmentStyle,\r\n highSegmentClassName = '',\r\n highSegmentStyle,\r\n classDisabled = '',\r\n}: CrisGaugeProps) {\r\n // Get join values reactively\r\n const joinValue = useAnalog(join ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine if enabled and visible\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Get current value\r\n const currentValue = join != null ? joinValue : (value ?? 0);\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Determine segment type (low, medium, high)\r\n const getSegmentType = (segment: number): 'low' | 'medium' | 'high' => {\r\n const lowMax = segments * 0.6;\r\n const mediumMax = segments * 0.8;\r\n\r\n const seg =\r\n (inverted && orientation === 'vertical') || (!inverted && orientation === 'horizontal')\r\n ? segment + 1\r\n : segments - segment;\r\n\r\n if (seg <= lowMax) return 'low';\r\n if (seg <= mediumMax) return 'medium';\r\n return 'high';\r\n };\r\n\r\n // Check if segment is active\r\n const isSegmentActive = (segment: number): boolean => {\r\n if (currentValue < minValue) return false;\r\n if (currentValue > maxValue) return true;\r\n\r\n const ratio = currentValue / (maxValue - minValue);\r\n let segMax = Math.round(segments * ratio);\r\n if (ratio !== 0 && segMax === 0) segMax = 1;\r\n\r\n const seg =\r\n (inverted && orientation === 'vertical') || (!inverted && orientation === 'horizontal')\r\n ? segment + 1\r\n : segments - segment;\r\n\r\n return seg <= segMax;\r\n };\r\n\r\n // Get segment class\r\n const getSegmentClass = (segment: number): string => {\r\n const type = getSegmentType(segment);\r\n const active = isSegmentActive(segment);\r\n\r\n const classes = ['cris-gauge-segment', type, orientation];\r\n\r\n if (active) {\r\n classes.push('active');\r\n // Add type-specific or general active class\r\n if (type === 'low' && (lowSegmentClassName || segmentClassName)) {\r\n classes.push(lowSegmentClassName || segmentClassName);\r\n } else if (type === 'medium' && (mediumSegmentClassName || segmentClassName)) {\r\n classes.push(mediumSegmentClassName || segmentClassName);\r\n } else if (type === 'high' && (highSegmentClassName || segmentClassName)) {\r\n classes.push(highSegmentClassName || segmentClassName);\r\n }\r\n } else {\r\n if (inactiveSegmentClassName) {\r\n classes.push(inactiveSegmentClassName);\r\n }\r\n }\r\n\r\n return classes.filter(Boolean).join(' ');\r\n };\r\n\r\n // Get segment style\r\n const getSegmentStyle = (segment: number): React.CSSProperties | undefined => {\r\n const type = getSegmentType(segment);\r\n const active = isSegmentActive(segment);\r\n\r\n if (active) {\r\n if (type === 'low' && (lowSegmentStyle || segmentStyle)) {\r\n return lowSegmentStyle || segmentStyle;\r\n } else if (type === 'medium' && (mediumSegmentStyle || segmentStyle)) {\r\n return mediumSegmentStyle || segmentStyle;\r\n } else if (type === 'high' && (highSegmentStyle || segmentStyle)) {\r\n return highSegmentStyle || segmentStyle;\r\n }\r\n } else {\r\n return inactiveSegmentStyle;\r\n }\r\n\r\n return undefined;\r\n };\r\n\r\n // Build container classes\r\n const containerClasses = [\r\n 'cris-gauge',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Container flex style\r\n const containerStyle: React.CSSProperties = {\r\n ...style,\r\n display: 'flex',\r\n flexDirection: orientation === 'horizontal' ? 'row' : 'column',\r\n justifyContent: 'center',\r\n alignItems: 'center',\r\n };\r\n\r\n return (\r\n <div className={containerClasses} style={containerStyle}>\r\n {Array.from({ length: segments }, (_, segment) => (\r\n <div\r\n key={segment}\r\n className={getSegmentClass(segment)}\r\n style={getSegmentStyle(segment)}\r\n />\r\n ))}\r\n </div>\r\n );\r\n}\r\n","import { useAnalog, useDigital } from '@imperosoft/cris-webui-ch5-core';\r\n\r\nexport type SpinnerSpeed = 'slow' | 'medium' | 'fast';\r\n\r\nexport interface CrisSpinnerProps {\r\n /** Analog join for position (0-65535 maps to 0-360 degrees) */\r\n joinPosition?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Static position value (0-65535, used if no joinPosition) */\r\n position?: number;\r\n\r\n /** Endless rotation mode (default: false) */\r\n endless?: boolean;\r\n /** Speed for endless rotation (default: 'medium') */\r\n endlessSpeed?: SpinnerSpeed;\r\n\r\n /** Spinner color (default: '#bf2b23') */\r\n color?: string;\r\n /** Background/track color (default: 'rgba(0, 0, 0, 0.1)') */\r\n trackColor?: string;\r\n /** Spinner line width in pixels (default: 3) */\r\n lineWidth?: number;\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n}\r\n\r\nconst SPEED_DURATIONS: Record<SpinnerSpeed, string> = {\r\n slow: '1.5s',\r\n medium: '0.8s',\r\n fast: '0.4s',\r\n};\r\n\r\nexport function CrisSpinner({\r\n joinPosition,\r\n joinVisible,\r\n position,\r\n endless = false,\r\n endlessSpeed = 'medium',\r\n color = '#bf2b23',\r\n trackColor = 'rgba(0, 0, 0, 0.1)',\r\n lineWidth = 3,\r\n className = '',\r\n style,\r\n}: CrisSpinnerProps) {\r\n // Get join values reactively\r\n const joinPositionValue = useAnalog(joinPosition ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine visibility\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Get current position (0-65535 -> 0-360 degrees)\r\n const currentPosition = joinPosition != null ? joinPositionValue : (position ?? 0);\r\n const rotationDegrees = (currentPosition / 65535) * 360;\r\n\r\n // Build container classes\r\n const containerClasses = ['cris-spinner', className].filter(Boolean).join(' ');\r\n\r\n // Spinner styles\r\n const spinnerStyle: React.CSSProperties = {\r\n width: '100%',\r\n height: '100%',\r\n borderRadius: '50%',\r\n border: `${lineWidth}px solid ${trackColor}`,\r\n borderTopColor: color,\r\n boxSizing: 'border-box',\r\n };\r\n\r\n if (endless) {\r\n // Endless rotation animation\r\n spinnerStyle.animation = `cris-spinner-rotate ${SPEED_DURATIONS[endlessSpeed]} linear infinite`;\r\n } else {\r\n // Position-based rotation\r\n spinnerStyle.transform = `rotate(${rotationDegrees}deg)`;\r\n spinnerStyle.transition = 'transform 0.1s linear';\r\n }\r\n\r\n return (\r\n <div className={containerClasses} style={style}>\r\n <div style={spinnerStyle} />\r\n <style>{`\r\n @keyframes cris-spinner-rotate {\r\n to { transform: rotate(360deg); }\r\n }\r\n `}</style>\r\n </div>\r\n );\r\n}\r\n","import { useEffect, useState, useCallback } from 'react';\r\nimport { isNativeMode, useConnectionStore, ConnectionErrorReason } from '@imperosoft/cris-webui-ch5-core';\r\n\r\n// Environment detection utilities\r\nconst isFileProtocol = typeof window !== 'undefined' && window.location.protocol === 'file:';\r\nconst isDevModeDefault = typeof window !== 'undefined' &&\r\n (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1');\r\nconst isVC4Default = typeof window !== 'undefined' && window.location.pathname.includes('/VirtualControl/');\r\nconst isDeployedOnProcessorDefault = !isFileProtocol && !isDevModeDefault && !isVC4Default;\r\n\r\n// Session storage key to track login redirect\r\nconst LOGIN_REDIRECT_KEY = 'cris_login_redirect';\r\n\r\nexport interface CrisOfflinePageProps {\r\n /**\r\n * Processor hostname for dev mode (used to build login/cert URLs).\r\n * In production, uses window.location.hostname.\r\n */\r\n processorHost?: string;\r\n\r\n /**\r\n * Whether an auth token is configured (for dev mode diagnostics).\r\n */\r\n hasAuthToken?: boolean;\r\n\r\n /**\r\n * Override dev mode detection.\r\n */\r\n isDevMode?: boolean;\r\n\r\n /**\r\n * Override VC4 detection.\r\n */\r\n isVC4?: boolean;\r\n\r\n /**\r\n * Override deployed on processor detection.\r\n */\r\n isDeployedOnProcessor?: boolean;\r\n\r\n /**\r\n * WebSocket port for certificate acceptance (default: 49200).\r\n */\r\n wsPort?: number;\r\n\r\n /**\r\n * Show debug information panel (default: true).\r\n */\r\n showDebugInfo?: boolean;\r\n\r\n /**\r\n * Auto-redirect delay to login page in ms when deployed on processor (default: 1500).\r\n * Set to 0 to disable auto-redirect.\r\n */\r\n loginRedirectDelay?: number;\r\n\r\n /**\r\n * Custom login URL path (default: '/userlogin.html').\r\n */\r\n loginPath?: string;\r\n\r\n /**\r\n * Auto-retry countdown seconds when returning from cert page (default: 3).\r\n */\r\n retryCountdown?: number;\r\n\r\n /**\r\n * Custom className for the root container.\r\n */\r\n className?: string;\r\n}\r\n\r\nexport function CrisOfflinePage({\r\n processorHost: processorHostProp,\r\n hasAuthToken = false,\r\n isDevMode = isDevModeDefault,\r\n isVC4 = isVC4Default,\r\n isDeployedOnProcessor = isDeployedOnProcessorDefault,\r\n wsPort = 49200,\r\n showDebugInfo = true,\r\n loginRedirectDelay = 1500,\r\n loginPath = '/userlogin.html',\r\n retryCountdown: retryCountdownInit = 3,\r\n className = '',\r\n}: CrisOfflinePageProps) {\r\n const connectionStatus = useConnectionStore((state) => state.status);\r\n const errorReason = useConnectionStore((state) => state.errorReason);\r\n const [hasSeenError, setHasSeenError] = useState(false);\r\n const [lastErrorReason, setLastErrorReason] = useState<ConnectionErrorReason>(null);\r\n const [certPageOpened, setCertPageOpened] = useState(false);\r\n const [retryCountdown, setRetryCountdown] = useState<number | null>(null);\r\n\r\n // Check if we just came back from login page (prevents redirect loop)\r\n // Using a ref-like pattern with useState to only compute once on mount\r\n const [returnedFromLogin] = useState(() => {\r\n if (typeof window === 'undefined') return false;\r\n try {\r\n const wasRedirected = sessionStorage.getItem(LOGIN_REDIRECT_KEY);\r\n if (wasRedirected) {\r\n sessionStorage.removeItem(LOGIN_REDIRECT_KEY);\r\n return true;\r\n }\r\n } catch {\r\n // sessionStorage not available\r\n }\r\n return false;\r\n });\r\n\r\n // Determine processor host\r\n const processorHost = processorHostProp ??\r\n (typeof window !== 'undefined' ? window.location.hostname : 'localhost');\r\n\r\n // Make error state \"sticky\" - once we've seen an error, keep showing error UI\r\n // until the user manually retries or connection succeeds\r\n const isError = connectionStatus === 'error' || (hasSeenError && connectionStatus !== 'connected');\r\n\r\n // Track when we first see an error and capture the reason\r\n useEffect(() => {\r\n if (connectionStatus === 'error') {\r\n setHasSeenError(true);\r\n setLastErrorReason(errorReason);\r\n } else if (connectionStatus === 'connected') {\r\n setHasSeenError(false);\r\n setLastErrorReason(null);\r\n }\r\n }, [connectionStatus, errorReason]);\r\n\r\n // Determine if this is a timeout error (processor unreachable)\r\n const isTimeoutError = lastErrorReason === 'timeout';\r\n\r\n // Build URLs\r\n const loginUrl = `https://${processorHost}${loginPath}`;\r\n const certUrl = `https://${processorHost}:${wsPort}/`;\r\n\r\n // Open certificate page in new tab\r\n const openCertPage = useCallback(() => {\r\n window.open(certUrl, '_blank');\r\n setCertPageOpened(true);\r\n }, [certUrl]);\r\n\r\n // Retry connection by reloading the page\r\n const retryConnection = useCallback(() => {\r\n window.location.reload();\r\n }, []);\r\n\r\n // Auto-retry when user returns after opening cert page\r\n useEffect(() => {\r\n if (!certPageOpened || !isError) return;\r\n\r\n const handleVisibilityChange = () => {\r\n if (document.visibilityState === 'visible') {\r\n // Start countdown when user returns\r\n setRetryCountdown(retryCountdownInit);\r\n }\r\n };\r\n\r\n const handleFocus = () => {\r\n setRetryCountdown(retryCountdownInit);\r\n };\r\n\r\n document.addEventListener('visibilitychange', handleVisibilityChange);\r\n window.addEventListener('focus', handleFocus);\r\n\r\n return () => {\r\n document.removeEventListener('visibilitychange', handleVisibilityChange);\r\n window.removeEventListener('focus', handleFocus);\r\n };\r\n }, [certPageOpened, isError, retryCountdownInit]);\r\n\r\n // Countdown timer for auto-retry\r\n useEffect(() => {\r\n if (retryCountdown === null) return;\r\n if (retryCountdown <= 0) {\r\n retryConnection();\r\n return;\r\n }\r\n\r\n const timer = setTimeout(() => {\r\n setRetryCountdown(retryCountdown - 1);\r\n }, 1000);\r\n\r\n return () => clearTimeout(timer);\r\n }, [retryCountdown, retryConnection]);\r\n\r\n // Auto-redirect to login page ONLY when deployed on processor (not dev mode, not CrestronOne)\r\n // Don't redirect if we just came back from login (prevents redirect loop)\r\n useEffect(() => {\r\n if (isError && isDeployedOnProcessor && loginRedirectDelay > 0 && !returnedFromLogin) {\r\n const timer = setTimeout(() => {\r\n // Set flag before redirecting so we know we came from here\r\n try {\r\n sessionStorage.setItem(LOGIN_REDIRECT_KEY, 'true');\r\n } catch {\r\n // sessionStorage not available\r\n }\r\n window.location.href = loginUrl;\r\n }, loginRedirectDelay);\r\n return () => clearTimeout(timer);\r\n }\r\n }, [isError, isDeployedOnProcessor, loginUrl, loginRedirectDelay, returnedFromLogin]);\r\n\r\n // Gather environment info for debugging\r\n const envInfo = {\r\n userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'N/A',\r\n hasCrComLib: typeof window !== 'undefined' && typeof (window as any).CrComLib !== 'undefined',\r\n hasCrestronPanel: typeof window !== 'undefined' && 'CrestronPanel' in window,\r\n isNativeMode: isNativeMode(),\r\n hostname: typeof window !== 'undefined' ? window.location.hostname : 'N/A',\r\n pathname: typeof window !== 'undefined' ? window.location.pathname : 'N/A',\r\n search: typeof window !== 'undefined' ? window.location.search : '',\r\n protocol: typeof window !== 'undefined' ? window.location.protocol : 'N/A',\r\n isDevMode,\r\n isVC4,\r\n isDeployedOnProcessor,\r\n returnedFromLogin,\r\n processorHost: isDevMode ? processorHost : undefined,\r\n hasAuthToken: isDevMode ? hasAuthToken : undefined,\r\n errorReason: lastErrorReason,\r\n };\r\n\r\n return (\r\n <div className={`cris-offline-page absolute inset-0 bg-gray-900 flex flex-col items-center justify-center overflow-auto py-8 ${className}`}>\r\n <div className=\"text-center max-w-4xl px-4\">\r\n {/* Status indicator */}\r\n <div className=\"mb-8\">\r\n <div className={`w-16 h-16 mx-auto rounded-full flex items-center justify-center ${isError ? 'bg-red-500/20' : 'bg-yellow-500/20'}`}>\r\n <div className={`w-8 h-8 rounded-full ${isError ? 'bg-red-500' : 'bg-yellow-500 animate-pulse'}`} />\r\n </div>\r\n </div>\r\n\r\n {/* Message */}\r\n <h1 className=\"text-2xl font-bold text-white mb-2\">\r\n {isError\r\n ? (isTimeoutError ? 'Processor Unreachable' : 'Connection Error')\r\n : 'Connecting...'}\r\n </h1>\r\n <p className=\"text-gray-400\">\r\n {isError\r\n ? (isDeployedOnProcessor\r\n ? (returnedFromLogin\r\n ? 'Connection failed after login. Please try again.'\r\n : 'Redirecting to login page...')\r\n : isTimeoutError\r\n ? 'Could not connect to the processor. Check the IP address and network connection.'\r\n : 'WebSocket connection failed. SSL certificate may need to be accepted.')\r\n : 'Establishing connection to control system'}\r\n </p>\r\n\r\n {/* Status badge */}\r\n <div className=\"mt-8\">\r\n <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'}`}>\r\n {isError\r\n ? (isTimeoutError\r\n ? 'Connection Timeout'\r\n : isDevMode\r\n ? 'Dev Mode - Auth Error'\r\n : isVC4\r\n ? 'VC4 - Not Authorized'\r\n : 'Auth Error')\r\n : 'Connecting'}\r\n </span>\r\n </div>\r\n\r\n {/* Timeout error UI - processor unreachable */}\r\n {isError && !isDeployedOnProcessor && isTimeoutError && (\r\n <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' }}>\r\n <p className=\"text-orange-400 font-medium\" style={{ fontSize: '1.2em', marginBottom: '1em' }}>\r\n The processor at <span className=\"text-white\">{processorHost}</span> is not responding.\r\n </p>\r\n <ul className=\"text-gray-400 text-left mb-[1em]\" style={{ fontSize: '1em', listStyleType: 'disc', paddingLeft: '1.5em' }}>\r\n <li>Verify the IP address is correct</li>\r\n <li>Check that the processor is powered on</li>\r\n <li>Ensure you are on the same network</li>\r\n </ul>\r\n <div className=\"flex gap-[1em] justify-center\" style={{ overflow: 'visible' }}>\r\n <button\r\n onClick={retryConnection}\r\n className=\"bg-orange-600 hover:bg-orange-500 text-white font-medium rounded-xl transition-colors\"\r\n style={{ padding: '1em 2em', fontSize: '1.2em', whiteSpace: 'nowrap', overflow: 'visible' }}\r\n >\r\n Retry Connection\r\n </button>\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Certificate acceptance UI - for websocket/auth errors in dev mode and VC4 */}\r\n {isError && !isDeployedOnProcessor && !isTimeoutError && (\r\n <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' }}>\r\n {retryCountdown !== null ? (\r\n <div className=\"text-center\" style={{ overflow: 'visible' }}>\r\n <p className=\"text-blue-400 font-medium\" style={{ fontSize: '1.4em', marginBottom: '1em' }}>Retrying connection in {retryCountdown}...</p>\r\n <button\r\n onClick={() => setRetryCountdown(null)}\r\n className=\"bg-gray-600 hover:bg-gray-500 text-white font-medium rounded-xl transition-colors\"\r\n style={{ padding: '1em 2em', fontSize: '1.2em', whiteSpace: 'nowrap', overflow: 'visible' }}\r\n >\r\n Cancel\r\n </button>\r\n </div>\r\n ) : (\r\n <>\r\n <p className=\"text-blue-400 font-medium\" style={{ fontSize: '1.2em', marginBottom: '1em' }}>\r\n {certPageOpened\r\n ? 'Accept the certificate in the new tab, then return here.'\r\n : 'Click below to accept the WebSocket SSL certificate:'}\r\n </p>\r\n <div className=\"flex gap-[1em] justify-center flex-wrap\" style={{ overflow: 'visible' }}>\r\n <button\r\n onClick={openCertPage}\r\n className=\"bg-blue-600 hover:bg-blue-500 text-white font-medium rounded-xl transition-colors\"\r\n style={{ padding: '1em 2em', fontSize: '1.2em', whiteSpace: 'nowrap', overflow: 'visible' }}\r\n >\r\n {certPageOpened ? 'Open Certificate Page Again' : 'Accept Certificate'}\r\n </button>\r\n <button\r\n onClick={retryConnection}\r\n className=\"bg-green-600 hover:bg-green-500 text-white font-medium rounded-xl transition-colors\"\r\n style={{ padding: '1em 2em', fontSize: '1.2em', whiteSpace: 'nowrap', overflow: 'visible' }}\r\n >\r\n Retry Connection\r\n </button>\r\n </div>\r\n {certPageOpened && (\r\n <p className=\"text-gray-400\" style={{ fontSize: '1em', marginTop: '1em' }}>\r\n Connection will auto-retry when you return to this page.\r\n </p>\r\n )}\r\n </>\r\n )}\r\n </div>\r\n )}\r\n\r\n {/* Deployed on processor - returned from login but still failed */}\r\n {isError && isDeployedOnProcessor && returnedFromLogin && (\r\n <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' }}>\r\n <p className=\"text-red-400 font-medium\" style={{ fontSize: '1.2em', marginBottom: '1em' }}>\r\n Connection failed after authentication.\r\n </p>\r\n <p className=\"text-gray-400\" style={{ fontSize: '1em', marginBottom: '1em' }}>\r\n This may be due to an expired session or WebSocket connection issue.\r\n </p>\r\n <div className=\"flex gap-[1em] justify-center flex-wrap\" style={{ overflow: 'visible' }}>\r\n <button\r\n onClick={retryConnection}\r\n className=\"bg-green-600 hover:bg-green-500 text-white font-medium rounded-xl transition-colors\"\r\n style={{ padding: '1em 2em', fontSize: '1.2em', whiteSpace: 'nowrap', overflow: 'visible' }}\r\n >\r\n Retry Connection\r\n </button>\r\n <button\r\n onClick={() => {\r\n try {\r\n sessionStorage.setItem(LOGIN_REDIRECT_KEY, 'true');\r\n } catch {\r\n // sessionStorage not available\r\n }\r\n window.location.href = loginUrl;\r\n }}\r\n className=\"bg-blue-600 hover:bg-blue-500 text-white font-medium rounded-xl transition-colors\"\r\n style={{ padding: '1em 2em', fontSize: '1.2em', whiteSpace: 'nowrap', overflow: 'visible' }}\r\n >\r\n Go to Login\r\n </button>\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Dev mode auth token status */}\r\n {isDevMode && isError && (\r\n <div className=\"mt-6 p-4 bg-yellow-500/10 border border-yellow-500/30 rounded-lg text-left\">\r\n <p className=\"text-yellow-400 text-sm font-medium mb-2\">Dev Mode Configuration:</p>\r\n <p className=\"text-gray-300 text-sm\">\r\n Processor: <span className=\"text-blue-400\">{processorHost}</span>\r\n </p>\r\n <p className=\"text-gray-300 text-sm\">\r\n AuthToken: <span className={hasAuthToken ? 'text-green-400' : 'text-red-400'}>\r\n {hasAuthToken ? 'Present (may be expired)' : 'Missing'}\r\n </span>\r\n </p>\r\n {!hasAuthToken && (\r\n <p className=\"text-yellow-400 text-xs mt-2\">\r\n Add authToken to crestron.config.json or generate new: <code className=\"bg-black/30 px-1 rounded\">websockettoken generate</code>\r\n </p>\r\n )}\r\n </div>\r\n )}\r\n\r\n {/* Debug Info */}\r\n {showDebugInfo && (\r\n <div className=\"mt-12 text-left bg-gray-800 rounded-lg p-4 text-xs font-mono\">\r\n <h2 className=\"text-yellow-400 font-bold mb-3 text-sm\">Environment Debug Info</h2>\r\n <div className=\"space-y-2 text-gray-300\">\r\n <div>\r\n <span className=\"text-gray-500\">CrComLib:</span>{' '}\r\n <span className={envInfo.hasCrComLib ? 'text-green-400' : 'text-red-400'}>\r\n {envInfo.hasCrComLib ? 'Available' : 'Not Found'}\r\n </span>\r\n </div>\r\n <div>\r\n <span className=\"text-gray-500\">CrestronPanel:</span>{' '}\r\n <span className={envInfo.hasCrestronPanel ? 'text-green-400' : 'text-gray-400'}>\r\n {envInfo.hasCrestronPanel ? 'Yes' : 'No'}\r\n </span>\r\n </div>\r\n <div>\r\n <span className=\"text-gray-500\">Native Mode:</span>{' '}\r\n <span className={envInfo.isNativeMode ? 'text-green-400' : 'text-gray-400'}>\r\n {envInfo.isNativeMode ? 'Yes' : 'No (Browser)'}\r\n </span>\r\n </div>\r\n <div>\r\n <span className=\"text-gray-500\">Dev Mode:</span>{' '}\r\n <span className={envInfo.isDevMode ? 'text-yellow-400' : 'text-gray-400'}>\r\n {envInfo.isDevMode ? 'Yes' : 'No'}\r\n </span>\r\n </div>\r\n <div>\r\n <span className=\"text-gray-500\">VC4:</span>{' '}\r\n <span className={envInfo.isVC4 ? 'text-purple-400' : 'text-gray-400'}>\r\n {envInfo.isVC4 ? 'Yes' : 'No'}\r\n </span>\r\n </div>\r\n <div>\r\n <span className=\"text-gray-500\">Deployed on Processor:</span>{' '}\r\n <span className={envInfo.isDeployedOnProcessor ? 'text-cyan-400' : 'text-gray-400'}>\r\n {envInfo.isDeployedOnProcessor ? 'Yes' : 'No'}\r\n </span>\r\n </div>\r\n {envInfo.returnedFromLogin && (\r\n <div>\r\n <span className=\"text-gray-500\">Returned from Login:</span>{' '}\r\n <span className=\"text-yellow-400\">Yes</span>\r\n </div>\r\n )}\r\n <div>\r\n <span className=\"text-gray-500\">Protocol:</span>{' '}\r\n <span className=\"text-blue-400\">{envInfo.protocol}</span>\r\n </div>\r\n <div>\r\n <span className=\"text-gray-500\">Hostname:</span>{' '}\r\n <span className=\"text-blue-400\">{envInfo.hostname}</span>\r\n </div>\r\n {envInfo.isDevMode && envInfo.processorHost && (\r\n <div>\r\n <span className=\"text-gray-500\">Target Processor:</span>{' '}\r\n <span className=\"text-blue-400\">{envInfo.processorHost}</span>\r\n </div>\r\n )}\r\n <div>\r\n <span className=\"text-gray-500\">Path:</span>{' '}\r\n <span className=\"text-blue-400\">{envInfo.pathname}{envInfo.search}</span>\r\n </div>\r\n {envInfo.errorReason && (\r\n <div>\r\n <span className=\"text-gray-500\">Error Reason:</span>{' '}\r\n <span className={envInfo.errorReason === 'timeout' ? 'text-orange-400' : 'text-red-400'}>\r\n {envInfo.errorReason}\r\n </span>\r\n </div>\r\n )}\r\n </div>\r\n </div>\r\n )}\r\n </div>\r\n </div>\r\n );\r\n}\r\n"],"mappings":"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","names":["import_cris_webui_ch5_core","import_jsx_runtime","import_react","import_cris_webui_ch5_core","import_jsx_runtime","import_cris_webui_ch5_core","import_jsx_runtime","import_cris_webui_ch5_core","import_jsx_runtime","import_react","import_cris_webui_ch5_core","import_jsx_runtime"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/components/CrisButton.tsx
|
|
2
|
-
import { useState, useRef, useEffect } from "react";
|
|
2
|
+
import { useState, useRef, useEffect, useCallback } from "react";
|
|
3
3
|
import { useDigital, useJoinsStore } from "@imperosoft/cris-webui-ch5-core";
|
|
4
4
|
|
|
5
5
|
// src/utils/icons.ts
|
|
@@ -86,8 +86,13 @@ function CrisButton({
|
|
|
86
86
|
onRelease,
|
|
87
87
|
debug = false
|
|
88
88
|
}) {
|
|
89
|
-
const
|
|
90
|
-
|
|
89
|
+
const debugRef = useRef(debug);
|
|
90
|
+
debugRef.current = debug;
|
|
91
|
+
const joinRef = useRef(join);
|
|
92
|
+
joinRef.current = join;
|
|
93
|
+
const log = useCallback((msg, data) => {
|
|
94
|
+
if (debugRef.current) console.log(`[CrisButton:${joinRef.current}] ${msg}`, data ?? "");
|
|
95
|
+
}, []);
|
|
91
96
|
const [pressed, setPressed] = useState(false);
|
|
92
97
|
const pressedRef = useRef(false);
|
|
93
98
|
const touchingRef = useRef(false);
|
|
@@ -112,7 +117,7 @@ function CrisButton({
|
|
|
112
117
|
dSet(join, false);
|
|
113
118
|
}
|
|
114
119
|
}
|
|
115
|
-
}, [isVisible, join, smartId, dSet
|
|
120
|
+
}, [isVisible, join, smartId, dSet]);
|
|
116
121
|
useEffect(() => {
|
|
117
122
|
return () => {
|
|
118
123
|
if (pressedRef.current && join != null && smartId == null) {
|
|
@@ -368,7 +373,7 @@ function CrisText({
|
|
|
368
373
|
}
|
|
369
374
|
|
|
370
375
|
// src/components/CrisSlider.tsx
|
|
371
|
-
import { useState as useState2, useRef as useRef2, useEffect as useEffect2, useCallback } from "react";
|
|
376
|
+
import { useState as useState2, useRef as useRef2, useEffect as useEffect2, useCallback as useCallback2 } from "react";
|
|
372
377
|
import { useDigital as useDigital3, useAnalog, useJoinsStore as useJoinsStore2 } from "@imperosoft/cris-webui-ch5-core";
|
|
373
378
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
374
379
|
function CrisSlider({
|
|
@@ -426,7 +431,7 @@ function CrisSlider({
|
|
|
426
431
|
}
|
|
427
432
|
};
|
|
428
433
|
}, [effectiveDigitalJoin, dSet]);
|
|
429
|
-
const analogToRatio =
|
|
434
|
+
const analogToRatio = useCallback2(
|
|
430
435
|
(value) => {
|
|
431
436
|
const range = maxValue - minValue;
|
|
432
437
|
if (range <= 0) return 0;
|
|
@@ -434,14 +439,14 @@ function CrisSlider({
|
|
|
434
439
|
},
|
|
435
440
|
[minValue, maxValue]
|
|
436
441
|
);
|
|
437
|
-
const ratioToAnalog =
|
|
442
|
+
const ratioToAnalog = useCallback2(
|
|
438
443
|
(ratio) => {
|
|
439
444
|
const range = maxValue - minValue;
|
|
440
445
|
return Math.round(minValue + ratio * range);
|
|
441
446
|
},
|
|
442
447
|
[minValue, maxValue]
|
|
443
448
|
);
|
|
444
|
-
const updateFromFeedback =
|
|
449
|
+
const updateFromFeedback = useCallback2(() => {
|
|
445
450
|
if (!isDraggingOrJustAfterRef.current && effectiveAnalogJoin != null) {
|
|
446
451
|
setRatioCurrent(analogToRatio(analogValue));
|
|
447
452
|
}
|
|
@@ -449,7 +454,7 @@ function CrisSlider({
|
|
|
449
454
|
useEffect2(() => {
|
|
450
455
|
updateFromFeedback();
|
|
451
456
|
}, [updateFromFeedback]);
|
|
452
|
-
const handleDragStart =
|
|
457
|
+
const handleDragStart = useCallback2(() => {
|
|
453
458
|
if (!isEnabled) return;
|
|
454
459
|
setIsDragging(true);
|
|
455
460
|
if (effectiveDigitalJoin != null) {
|
|
@@ -464,7 +469,7 @@ function CrisSlider({
|
|
|
464
469
|
afterDragTimeoutRef.current = null;
|
|
465
470
|
}
|
|
466
471
|
}, [isEnabled, effectiveDigitalJoin, dSet, ratioCurrent]);
|
|
467
|
-
const handleDragEnd =
|
|
472
|
+
const handleDragEnd = useCallback2(() => {
|
|
468
473
|
if (!isDragging) return;
|
|
469
474
|
setIsDragging(false);
|
|
470
475
|
if (effectiveDigitalJoin != null) {
|
|
@@ -481,7 +486,7 @@ function CrisSlider({
|
|
|
481
486
|
updateFromFeedback();
|
|
482
487
|
}
|
|
483
488
|
}, [isDragging, effectiveDigitalJoin, dSet, delayMsAfterDragUpdateFeedback, updateFromFeedback]);
|
|
484
|
-
const handleMove =
|
|
489
|
+
const handleMove = useCallback2(
|
|
485
490
|
(clientX, clientY, bounds) => {
|
|
486
491
|
if (!isDragging) return;
|
|
487
492
|
let newRatio;
|
|
@@ -801,7 +806,7 @@ function CrisSpinner({
|
|
|
801
806
|
}
|
|
802
807
|
|
|
803
808
|
// src/components/CrisOfflinePage.tsx
|
|
804
|
-
import { useEffect as useEffect3, useState as useState3, useCallback as
|
|
809
|
+
import { useEffect as useEffect3, useState as useState3, useCallback as useCallback3 } from "react";
|
|
805
810
|
import { isNativeMode, useConnectionStore } from "@imperosoft/cris-webui-ch5-core";
|
|
806
811
|
import { Fragment, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
807
812
|
var isFileProtocol = typeof window !== "undefined" && window.location.protocol === "file:";
|
|
@@ -854,11 +859,11 @@ function CrisOfflinePage({
|
|
|
854
859
|
const isTimeoutError = lastErrorReason === "timeout";
|
|
855
860
|
const loginUrl = `https://${processorHost}${loginPath}`;
|
|
856
861
|
const certUrl = `https://${processorHost}:${wsPort}/`;
|
|
857
|
-
const openCertPage =
|
|
862
|
+
const openCertPage = useCallback3(() => {
|
|
858
863
|
window.open(certUrl, "_blank");
|
|
859
864
|
setCertPageOpened(true);
|
|
860
865
|
}, [certUrl]);
|
|
861
|
-
const retryConnection =
|
|
866
|
+
const retryConnection = useCallback3(() => {
|
|
862
867
|
window.location.reload();
|
|
863
868
|
}, []);
|
|
864
869
|
useEffect3(() => {
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/CrisButton.tsx","../src/utils/icons.ts","../src/utils/touchGuard.ts","../src/components/CrisText.tsx","../src/components/CrisSlider.tsx","../src/components/CrisGauge.tsx","../src/components/CrisSpinner.tsx","../src/components/CrisOfflinePage.tsx"],"sourcesContent":["import { useState, useRef, useEffect, ReactNode } from 'react';\r\nimport { useDigital, useJoinsStore } from '@imperosoft/cris-webui-ch5-core';\r\nimport { getIconUrl, getIconFilter } from '../utils/icons';\r\nimport { touchStart, touchEnd, isTouchActive } from '../utils/touchGuard';\r\n\r\nexport interface CrisButtonProps {\r\n /** Digital join for press action */\r\n join?: number;\r\n /** Digital join for feedback (defaults to join) */\r\n joinFeedback?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Button text */\r\n text?: string;\r\n /** Text when pressed (local feedback) */\r\n textPressed?: string;\r\n /** Text when selected (controller feedback) */\r\n textSelected?: string;\r\n\r\n /** Icon as ReactNode (for custom SVG components) */\r\n icon?: ReactNode;\r\n /** Icon name (loads SVG from configured path, e.g., 'motor-stop') */\r\n iconName?: string;\r\n /** Icon CSS class (for CSS-based icons, e.g., 'ico-motor-stop') */\r\n iconClass?: string;\r\n /** Icon size - number (px), string ('50%', '2rem'), or preset ('xs'|'sm'|'md'|'lg'|'xl') */\r\n iconSize?: number | string | 'xs' | 'sm' | 'md' | 'lg' | 'xl';\r\n /** Icon container size as percentage (default: 80% for top/bottom, 20% for left/right) */\r\n iconContainerSize?: string;\r\n /** Icon inline style */\r\n iconStyle?: React.CSSProperties;\r\n /** Icon position relative to text */\r\n iconPosition?: 'left' | 'right' | 'top' | 'bottom';\r\n\r\n /** Icon name when active (overrides iconName when button is active) */\r\n iconNameActive?: string;\r\n /** Icon CSS class when active (overrides iconClass when button is active) */\r\n iconClassActive?: string;\r\n /** Icon inline style when active (merged with iconStyle when button is active) */\r\n iconStyleActive?: React.CSSProperties;\r\n\r\n /** Show controller feedback styling */\r\n showControlFeedback?: boolean;\r\n /** Show local press feedback styling */\r\n showLocalFeedback?: boolean;\r\n /** Suppress click actions (display only) */\r\n suppressKeyClicks?: boolean;\r\n\r\n /** Smart object ID (for smarts instead of joins) */\r\n smartId?: number;\r\n\r\n /** Custom class names */\r\n className?: string;\r\n /** Class when active (controller feedback) */\r\n classActive?: string;\r\n /** Class when pressed (local feedback) */\r\n classPressed?: string;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n\r\n /** Children content */\r\n children?: ReactNode;\r\n\r\n /** Custom click handler (called on press) */\r\n onPress?: () => void;\r\n /** Custom release handler */\r\n onRelease?: () => void;\r\n\r\n /** Enable debug logging for this button */\r\n debug?: boolean;\r\n}\r\n\r\nexport function CrisButton({\r\n join,\r\n joinFeedback,\r\n joinEnable,\r\n joinVisible,\r\n text,\r\n textPressed,\r\n textSelected,\r\n icon,\r\n iconName,\r\n iconClass,\r\n iconSize,\r\n iconContainerSize,\r\n iconStyle,\r\n iconPosition = 'top',\r\n iconNameActive,\r\n iconClassActive,\r\n iconStyleActive,\r\n showControlFeedback = true,\r\n showLocalFeedback = true,\r\n suppressKeyClicks = false,\r\n smartId,\r\n className = '',\r\n classActive = '',\r\n classPressed = '',\r\n classDisabled = '',\r\n children,\r\n onPress,\r\n onRelease,\r\n debug = false,\r\n}: CrisButtonProps) {\r\n // Debug logging helper\r\n const log = debug\r\n ? (msg: string, data?: unknown) => console.log(`[CrisButton:${join}] ${msg}`, data ?? '')\r\n : () => {};\r\n const [pressed, setPressed] = useState(false);\r\n const pressedRef = useRef(false);\r\n const touchingRef = useRef(false);\r\n const touchStartedHereRef = useRef(false);\r\n\r\n // Get join values reactively\r\n const feedbackJoin = joinFeedback ?? join;\r\n const feedback = useDigital(feedbackJoin ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Get action method\r\n const dSet = useJoinsStore((state) => state.dSet);\r\n\r\n // Determine if button is enabled\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n\r\n // Determine if button is visible\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Release if button becomes invisible while pressed\r\n useEffect(() => {\r\n log('visibility effect', { isVisible, pressedRef: pressedRef.current });\r\n if (!isVisible && pressedRef.current) {\r\n log('VISIBILITY RELEASE - button hidden while pressed');\r\n pressedRef.current = false;\r\n setPressed(false);\r\n touchingRef.current = false;\r\n touchStartedHereRef.current = false;\r\n if (join != null && smartId == null) {\r\n log('sending release via dSet(false)');\r\n dSet(join, false);\r\n }\r\n }\r\n }, [isVisible, join, smartId, dSet, log]);\r\n\r\n // Release on unmount if button was pressed (handles page transitions)\r\n useEffect(() => {\r\n return () => {\r\n if (pressedRef.current && join != null && smartId == null) {\r\n log('UNMOUNT RELEASE - component unmounting while pressed');\r\n dSet(join, false);\r\n }\r\n };\r\n }, [join, smartId, dSet, log]);\r\n\r\n // Determine current feedback state\r\n const hasControlFeedback = showControlFeedback && feedbackJoin != null && feedback;\r\n const hasPressedFeedback = showLocalFeedback && pressed && isEnabled;\r\n const isActive = hasControlFeedback || hasPressedFeedback;\r\n\r\n // Determine current text\r\n let currentText = text ?? '';\r\n if (hasPressedFeedback && textPressed != null) {\r\n currentText = textPressed;\r\n } else if (hasControlFeedback && textSelected != null) {\r\n currentText = textSelected;\r\n }\r\n\r\n // Event handlers\r\n const handlePress = () => {\r\n log('handlePress called', { suppressKeyClicks, pressedRef: pressedRef.current, isEnabled });\r\n if (suppressKeyClicks) {\r\n log('BLOCKED: suppressKeyClicks');\r\n return;\r\n }\r\n if (pressedRef.current) {\r\n log('BLOCKED: already pressed');\r\n return;\r\n }\r\n\r\n pressedRef.current = true;\r\n setPressed(true);\r\n\r\n if (!isEnabled) {\r\n log('SKIPPED dSet: not enabled');\r\n return;\r\n }\r\n\r\n // Custom handler\r\n onPress?.();\r\n\r\n // Send to controller\r\n if (join != null && smartId == null) {\r\n log('SENDING PRESS via dSet(true)');\r\n dSet(join, true);\r\n }\r\n // TODO: Add smartId support when smarts are implemented in core\r\n };\r\n\r\n const handleRelease = () => {\r\n log('handleRelease called', { suppressKeyClicks, pressedRef: pressedRef.current, isEnabled });\r\n if (suppressKeyClicks) {\r\n log('BLOCKED: suppressKeyClicks');\r\n return;\r\n }\r\n if (!pressedRef.current) {\r\n log('BLOCKED: not pressed');\r\n return;\r\n }\r\n\r\n pressedRef.current = false;\r\n setPressed(false);\r\n\r\n if (!isEnabled) {\r\n log('SKIPPED dSet: not enabled');\r\n return;\r\n }\r\n\r\n // Custom handler\r\n onRelease?.();\r\n\r\n // Send to controller\r\n if (join != null && smartId == null) {\r\n log('SENDING RELEASE via dSet(false)');\r\n dSet(join, false);\r\n }\r\n // TODO: Add smartId support when smarts are implemented in core\r\n };\r\n\r\n // Touch event handlers - use global touch guard to prevent phantom clicks\r\n // when buttons swap visibility (e.g., back button hides, power button appears)\r\n const handleTouchStart = () => {\r\n log('handleTouchStart');\r\n touchStart(); // Global guard\r\n touchingRef.current = true;\r\n touchStartedHereRef.current = true;\r\n handlePress();\r\n };\r\n\r\n const handleTouchEnd = () => {\r\n log('handleTouchEnd', { touchStartedHereRef: touchStartedHereRef.current });\r\n touchEnd(); // Global guard with delayed reset\r\n touchingRef.current = true;\r\n // Only fire release if touch actually started on this button\r\n if (touchStartedHereRef.current) {\r\n touchStartedHereRef.current = false;\r\n handleRelease();\r\n } else {\r\n log('SKIPPED handleRelease: touch did not start here');\r\n }\r\n };\r\n\r\n const handleTouchCancel = () => {\r\n log('handleTouchCancel');\r\n touchEnd(); // Global guard with delayed reset\r\n touchingRef.current = true;\r\n touchStartedHereRef.current = false;\r\n handleRelease();\r\n };\r\n\r\n // Mouse event handlers - skip if any touch event is active (global check)\r\n const handleMouseDown = () => {\r\n if (isTouchActive() || touchingRef.current) return;\r\n handlePress();\r\n };\r\n\r\n const handleMouseUp = () => {\r\n if (isTouchActive() || touchingRef.current) return;\r\n handleRelease();\r\n };\r\n\r\n const handleMouseLeave = () => {\r\n if (isTouchActive() || touchingRef.current) return;\r\n handleRelease();\r\n };\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const classes = [\r\n 'cris-button',\r\n className,\r\n hasControlFeedback && classActive,\r\n hasPressedFeedback && classPressed,\r\n !isEnabled && classDisabled,\r\n hasControlFeedback && 'active',\r\n hasPressedFeedback && 'pressed',\r\n !isEnabled && 'disabled',\r\n suppressKeyClicks && 'no-hover',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Flex direction based on icon position\r\n const flexDirection =\r\n iconPosition === 'top'\r\n ? 'flex-col'\r\n : iconPosition === 'bottom'\r\n ? 'flex-col-reverse'\r\n : iconPosition === 'left'\r\n ? 'flex-row'\r\n : 'flex-row-reverse';\r\n\r\n // Determine if we have any icon to show\r\n const hasIcon = icon != null || iconName != null || iconClass != null || iconNameActive != null || iconClassActive != null;\r\n\r\n // Determine current icon properties based on active state\r\n const currentIconName = isActive && iconNameActive != null ? iconNameActive : iconName;\r\n const currentIconClass = isActive && iconClassActive != null ? iconClassActive : iconClass;\r\n const currentIconStyle = isActive && iconStyleActive != null ? { ...iconStyle, ...iconStyleActive } : iconStyle;\r\n\r\n // Size presets mapping\r\n const sizePresets: Record<string, string> = {\r\n xs: '16px',\r\n sm: '24px',\r\n md: '32px',\r\n lg: '48px',\r\n xl: '64px',\r\n };\r\n\r\n // Calculate icon size value\r\n const getIconSizeValue = (): string | undefined => {\r\n if (iconSize == null) return undefined;\r\n if (typeof iconSize === 'number') return `${iconSize}px`;\r\n if (iconSize in sizePresets) return sizePresets[iconSize];\r\n return iconSize; // Already a string like '50%' or '2rem'\r\n };\r\n\r\n const iconSizeValue = getIconSizeValue();\r\n\r\n // Build icon classes\r\n const iconClasses = [\r\n 'cris-button-icon',\r\n currentIconClass,\r\n hasControlFeedback && 'active',\r\n hasPressedFeedback && 'pressed',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Build icon style with size and filter support\r\n const computedIconStyle: React.CSSProperties = {\r\n ...currentIconStyle,\r\n };\r\n\r\n // Apply size\r\n if (iconSizeValue) {\r\n computedIconStyle.width = iconSizeValue;\r\n computedIconStyle.height = iconSizeValue;\r\n }\r\n\r\n // Apply filter from config if using iconName (only if not overridden by iconStyle/iconStyleActive)\r\n if (currentIconName && !currentIconStyle?.filter) {\r\n const filter = getIconFilter(isActive);\r\n if (filter) {\r\n computedIconStyle.filter = filter;\r\n }\r\n }\r\n\r\n // Render icon element\r\n const renderIconElement = () => {\r\n if (icon) {\r\n // ReactNode icon (custom component)\r\n return <span className={iconClasses} style={computedIconStyle}>{icon}</span>;\r\n }\r\n\r\n if (currentIconName) {\r\n // Icon by name (loads from configured path)\r\n return (\r\n <img\r\n className={iconClasses}\r\n style={{ ...computedIconStyle, pointerEvents: 'none' }}\r\n src={getIconUrl(currentIconName)}\r\n alt=\"\"\r\n draggable={false}\r\n />\r\n );\r\n }\r\n\r\n if (currentIconClass) {\r\n // CSS-based icon (like Angular version)\r\n return <img className={iconClasses} style={{ ...computedIconStyle, pointerEvents: 'none' }} alt=\"\" draggable={false} />;\r\n }\r\n\r\n return null;\r\n };\r\n\r\n // Calculate icon container size (default: 100% if no text, otherwise 80% for top/bottom, 20% for left/right)\r\n const isVertical = iconPosition === 'top' || iconPosition === 'bottom';\r\n const hasText = currentText !== '';\r\n const defaultContainerSize = hasText ? (isVertical ? '80%' : '20%') : '100%';\r\n const containerSize = iconContainerSize ?? defaultContainerSize;\r\n\r\n // Calculate text container size (remaining space)\r\n const textContainerSize = `calc(100% - ${containerSize})`;\r\n\r\n // Container style for icon area\r\n const iconContainerStyle: React.CSSProperties = isVertical\r\n ? { height: containerSize, width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }\r\n : { width: containerSize, height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' };\r\n\r\n // Container style for text area\r\n const textContainerStyle: React.CSSProperties = isVertical\r\n ? { height: textContainerSize, width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }\r\n : { width: textContainerSize, height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' };\r\n\r\n // Check if button has 'free' class (absolute positioning)\r\n const isFreePositioned = className.includes('free');\r\n\r\n return (\r\n <div\r\n className={`${classes} flex items-center justify-center ${flexDirection}`}\r\n style={{\r\n cursor: suppressKeyClicks ? 'default' : 'pointer',\r\n position: isFreePositioned ? undefined : 'relative',\r\n overflow: isFreePositioned ? undefined : 'hidden',\r\n touchAction: 'none',\r\n userSelect: 'none',\r\n WebkitUserSelect: 'none',\r\n }}\r\n onMouseDown={handleMouseDown}\r\n onMouseUp={handleMouseUp}\r\n onMouseLeave={handleMouseLeave}\r\n onTouchStart={handleTouchStart}\r\n onTouchEnd={handleTouchEnd}\r\n onTouchCancel={handleTouchCancel}\r\n >\r\n {/* Press overlay - shows on press for both desktop and touch devices */}\r\n <div\r\n className=\"cris-button-press-overlay\"\r\n style={{\r\n position: 'absolute',\r\n inset: 0,\r\n backgroundColor: 'black',\r\n opacity: pressed ? 0.1 : 0,\r\n transition: 'opacity 0.1s',\r\n pointerEvents: 'none',\r\n borderRadius: 'inherit',\r\n }}\r\n />\r\n {children}\r\n {hasIcon && (\r\n <div className=\"cris-button-icon-container\" style={iconContainerStyle}>\r\n {renderIconElement()}\r\n </div>\r\n )}\r\n {currentText && (\r\n <div className=\"cris-button-text-container\" style={textContainerStyle}>\r\n <span className=\"cris-button-text\">{currentText}</span>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n","/**\r\n * Icon configuration and utilities for CRIS components\r\n *\r\n * Supports different base paths for dev vs production (Crestron controller)\r\n */\r\n\r\nexport interface IconConfig {\r\n /** Base path for icon assets (e.g., '/assets/icons/' or 'http://controller/icons/') */\r\n basePath: string;\r\n /** File extension (default: 'svg') */\r\n extension: string;\r\n /** Default filter for inactive state */\r\n defaultFilter?: string;\r\n /** Default filter for active state */\r\n activeFilter?: string;\r\n}\r\n\r\n// Default configuration\r\nlet iconConfig: IconConfig = {\r\n basePath: '/assets/icons/',\r\n extension: 'svg',\r\n defaultFilter: undefined,\r\n activeFilter: undefined,\r\n};\r\n\r\n/**\r\n * Configure icon paths and defaults\r\n * Call this once at app startup\r\n *\r\n * @example\r\n * // Development\r\n * configureIcons({ basePath: '/assets/icons/' });\r\n *\r\n * // Production on Crestron controller\r\n * configureIcons({ basePath: '/html/icons/' });\r\n *\r\n * // With color filters\r\n * configureIcons({\r\n * basePath: '/assets/icons/',\r\n * defaultFilter: 'brightness(0) invert(1)', // white\r\n * activeFilter: 'brightness(0) invert(0.5) sepia(1) saturate(5) hue-rotate(0deg)', // red\r\n * });\r\n */\r\nexport function configureIcons(config: Partial<IconConfig>): void {\r\n iconConfig = { ...iconConfig, ...config };\r\n}\r\n\r\n/**\r\n * Get current icon configuration\r\n */\r\nexport function getIconConfig(): IconConfig {\r\n return iconConfig;\r\n}\r\n\r\n/**\r\n * Get full URL for an icon\r\n *\r\n * @param name - Icon name (without path or extension), e.g., 'motor-stop'\r\n * @returns Full URL to the icon\r\n */\r\nexport function getIconUrl(name: string): string {\r\n if (!name) return '';\r\n\r\n // If already a full URL or path, return as-is\r\n if (name.startsWith('http') || name.startsWith('/') || name.startsWith('.')) {\r\n return name;\r\n }\r\n\r\n const { basePath, extension } = iconConfig;\r\n const normalizedBase = basePath.endsWith('/') ? basePath : `${basePath}/`;\r\n return `${normalizedBase}${name}.${extension}`;\r\n}\r\n\r\n/**\r\n * Get CSS filter for icon state\r\n */\r\nexport function getIconFilter(active: boolean): string | undefined {\r\n return active ? iconConfig.activeFilter : iconConfig.defaultFilter;\r\n}\r\n","/**\r\n * Global touch state guard - shared across all CRIS components\r\n *\r\n * This prevents phantom clicks when elements swap visibility during touch interactions.\r\n * When a button is touched and triggers a visibility change (e.g., back button hides,\r\n * power button appears), the newly visible element would receive the synthetic click\r\n * event from the browser. The global touch flag blocks this.\r\n */\r\n\r\nlet globalTouchActive = false;\r\nlet touchResetTimeout: number | null = null;\r\n\r\n/** Duration to keep touch guard active after touch ends (blocks synthetic click) */\r\nconst TOUCH_GUARD_DURATION_MS = 300;\r\n\r\n/**\r\n * Mark that a touch interaction has started.\r\n * Call this in touchStart handlers.\r\n */\r\nexport function touchStart(): void {\r\n globalTouchActive = true;\r\n // Clear any pending reset\r\n if (touchResetTimeout !== null) {\r\n window.clearTimeout(touchResetTimeout);\r\n touchResetTimeout = null;\r\n }\r\n}\r\n\r\n/**\r\n * Mark that a touch interaction has ended.\r\n * The guard remains active for a short duration to block synthetic click events.\r\n * Call this in touchEnd/touchCancel handlers.\r\n */\r\nexport function touchEnd(): void {\r\n globalTouchActive = true;\r\n // Reset after delay to block synthetic click\r\n if (touchResetTimeout !== null) {\r\n window.clearTimeout(touchResetTimeout);\r\n }\r\n touchResetTimeout = window.setTimeout(() => {\r\n globalTouchActive = false;\r\n touchResetTimeout = null;\r\n }, TOUCH_GUARD_DURATION_MS);\r\n}\r\n\r\n/**\r\n * Check if a touch interaction is currently active.\r\n * Use this in mouse handlers to skip if touch is active.\r\n */\r\nexport function isTouchActive(): boolean {\r\n return globalTouchActive;\r\n}\r\n","import { ReactNode } from 'react';\r\nimport { useDigital, useSerial, useCipDecode } from '@imperosoft/cris-webui-ch5-core';\r\n\r\nexport interface CrisTextProps {\r\n /** Serial join for indirect text */\r\n joinIndirectText?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n /** Static text (used if no joinIndirectText) */\r\n text?: string;\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n /** Text element CSS class */\r\n textClassName?: string;\r\n /** Text element inline style */\r\n textStyle?: React.CSSProperties;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n /** Children content */\r\n children?: ReactNode;\r\n}\r\n\r\nexport function CrisText({\r\n joinIndirectText,\r\n joinEnable,\r\n joinVisible,\r\n text,\r\n className = '',\r\n style,\r\n textClassName = '',\r\n textStyle,\r\n classDisabled = '',\r\n children,\r\n}: CrisTextProps) {\r\n // Get join values reactively\r\n const indirectText = useSerial(joinIndirectText ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine if text is enabled\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n\r\n // Determine if text is visible\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Get the raw text - indirect text or static text\r\n const rawText = joinIndirectText != null ? indirectText : (text ?? '');\r\n\r\n // Decode CIP patterns in the text\r\n const currentText = useCipDecode(rawText);\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const containerClasses = [\r\n 'cris-text',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n return (\r\n <div className={containerClasses} style={style}>\r\n {children}\r\n {currentText && (\r\n <span className={`cris-text-text ${textClassName}`} style={textStyle}>\r\n {currentText}\r\n </span>\r\n )}\r\n </div>\r\n );\r\n}\r\n","import { useState, useRef, useEffect, useCallback } from 'react';\r\nimport { useDigital, useAnalog, useJoinsStore } from '@imperosoft/cris-webui-ch5-core';\r\nimport { touchStart, touchEnd, isTouchActive } from '../utils/touchGuard';\r\n\r\nexport interface CrisSliderProps {\r\n /** Analog join for value (shared with digital if joinDigital not set) */\r\n join?: number;\r\n /** Digital join for press/release feedback */\r\n joinDigital?: number;\r\n /** Analog join for value (overrides join) */\r\n joinAnalog?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Minimum value (default 0) */\r\n minValue?: number;\r\n /** Maximum value (default 65535) */\r\n maxValue?: number;\r\n /** Horizontal orientation (default false = vertical) */\r\n horizontal?: boolean;\r\n /** Hide fill bar */\r\n fillHidden?: boolean;\r\n /** Track size as percentage of container (default 20) */\r\n trackSizePercent?: number;\r\n /** Thumb size as percentage of container (default 4) */\r\n thumbSizePercent?: number;\r\n /** Delay in ms after drag before updating from feedback (default 1000) */\r\n delayMsAfterDragUpdateFeedback?: number;\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n /** Bar/track CSS class */\r\n barClassName?: string;\r\n /** Bar/track inline style */\r\n barStyle?: React.CSSProperties;\r\n /** Fill CSS class */\r\n fillClassName?: string;\r\n /** Fill inline style */\r\n fillStyle?: React.CSSProperties;\r\n /** Thumb CSS class */\r\n thumbClassName?: string;\r\n /** Thumb inline style */\r\n thumbStyle?: React.CSSProperties;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n}\r\n\r\nexport function CrisSlider({\r\n join,\r\n joinDigital,\r\n joinAnalog,\r\n joinEnable,\r\n joinVisible,\r\n minValue = 0,\r\n maxValue = 65535,\r\n horizontal = false,\r\n fillHidden = false,\r\n trackSizePercent = 20,\r\n thumbSizePercent = 4,\r\n delayMsAfterDragUpdateFeedback = 1000,\r\n className = '',\r\n style,\r\n barClassName = '',\r\n barStyle,\r\n fillClassName = '',\r\n fillStyle,\r\n thumbClassName = '',\r\n thumbStyle,\r\n classDisabled = '',\r\n}: CrisSliderProps) {\r\n // Effective joins\r\n const effectiveAnalogJoin = joinAnalog ?? join;\r\n const effectiveDigitalJoin = joinDigital ?? join;\r\n\r\n // Get join values reactively\r\n const analogValue = useAnalog(effectiveAnalogJoin ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Action methods\r\n const dSet = useJoinsStore((state) => state.dSet);\r\n const aSet = useJoinsStore((state) => state.aSet);\r\n\r\n // Local state\r\n const [ratioCurrent, setRatioCurrent] = useState(0);\r\n const [isDragging, setIsDragging] = useState(false);\r\n const isDraggingOrJustAfterRef = useRef(false);\r\n const ratioBeforeDragRef = useRef(0);\r\n const afterDragTimeoutRef = useRef<number | null>(null);\r\n const touchingRef = useRef(false);\r\n\r\n // Determine if enabled and visible\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Release if slider becomes invisible while dragging\r\n useEffect(() => {\r\n if (!isVisible && isDraggingOrJustAfterRef.current) {\r\n setIsDragging(false);\r\n isDraggingOrJustAfterRef.current = false;\r\n touchingRef.current = false;\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, false);\r\n }\r\n }\r\n }, [isVisible, effectiveDigitalJoin, dSet]);\r\n\r\n // Release on unmount if slider was being dragged (handles page transitions)\r\n useEffect(() => {\r\n return () => {\r\n if (isDraggingOrJustAfterRef.current && effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, false);\r\n }\r\n };\r\n }, [effectiveDigitalJoin, dSet]);\r\n\r\n // Convert analog value to ratio\r\n const analogToRatio = useCallback(\r\n (value: number) => {\r\n const range = maxValue - minValue;\r\n if (range <= 0) return 0;\r\n return Math.max(0, Math.min(1, (value - minValue) / range));\r\n },\r\n [minValue, maxValue]\r\n );\r\n\r\n // Convert ratio to analog value\r\n const ratioToAnalog = useCallback(\r\n (ratio: number) => {\r\n const range = maxValue - minValue;\r\n return Math.round(minValue + ratio * range);\r\n },\r\n [minValue, maxValue]\r\n );\r\n\r\n // Update ratio from controller feedback\r\n const updateFromFeedback = useCallback(() => {\r\n if (!isDraggingOrJustAfterRef.current && effectiveAnalogJoin != null) {\r\n setRatioCurrent(analogToRatio(analogValue));\r\n }\r\n }, [analogValue, analogToRatio, effectiveAnalogJoin]);\r\n\r\n // Update from feedback when analog value changes\r\n useEffect(() => {\r\n updateFromFeedback();\r\n }, [updateFromFeedback]);\r\n\r\n // Handle drag start\r\n const handleDragStart = useCallback(() => {\r\n if (!isEnabled) return;\r\n\r\n setIsDragging(true);\r\n\r\n // Send digital press\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, true);\r\n }\r\n\r\n // Save ratio before drag\r\n if (!isDraggingOrJustAfterRef.current) {\r\n ratioBeforeDragRef.current = ratioCurrent;\r\n }\r\n isDraggingOrJustAfterRef.current = true;\r\n\r\n // Clear any pending timeout\r\n if (afterDragTimeoutRef.current !== null) {\r\n window.clearTimeout(afterDragTimeoutRef.current);\r\n afterDragTimeoutRef.current = null;\r\n }\r\n }, [isEnabled, effectiveDigitalJoin, dSet, ratioCurrent]);\r\n\r\n // Handle drag end\r\n const handleDragEnd = useCallback(() => {\r\n if (!isDragging) return;\r\n\r\n setIsDragging(false);\r\n\r\n // Send digital release\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, false);\r\n }\r\n\r\n // Schedule feedback update after delay\r\n if (delayMsAfterDragUpdateFeedback > 0) {\r\n afterDragTimeoutRef.current = window.setTimeout(() => {\r\n isDraggingOrJustAfterRef.current = false;\r\n updateFromFeedback();\r\n afterDragTimeoutRef.current = null;\r\n }, delayMsAfterDragUpdateFeedback);\r\n } else {\r\n isDraggingOrJustAfterRef.current = false;\r\n updateFromFeedback();\r\n }\r\n }, [isDragging, effectiveDigitalJoin, dSet, delayMsAfterDragUpdateFeedback, updateFromFeedback]);\r\n\r\n // Handle move/drag\r\n const handleMove = useCallback(\r\n (clientX: number, clientY: number, bounds: DOMRect) => {\r\n if (!isDragging) return;\r\n\r\n let newRatio: number;\r\n if (horizontal) {\r\n newRatio = (clientX - bounds.left) / bounds.width;\r\n } else {\r\n newRatio = 1 - (clientY - bounds.top) / bounds.height;\r\n }\r\n newRatio = Math.max(0, Math.min(1, newRatio));\r\n\r\n setRatioCurrent(newRatio);\r\n\r\n // Send analog value\r\n if (effectiveAnalogJoin != null) {\r\n aSet(effectiveAnalogJoin, ratioToAnalog(newRatio));\r\n }\r\n },\r\n [isDragging, horizontal, effectiveAnalogJoin, aSet, ratioToAnalog]\r\n );\r\n\r\n // Mouse handlers - skip if any touch event is active (global check)\r\n const handleMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (isTouchActive() || touchingRef.current) return;\r\n if (event.button !== 0) return;\r\n\r\n handleDragStart();\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n };\r\n\r\n const handleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (!isDragging) return;\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n };\r\n\r\n const handleMouseUp = () => {\r\n handleDragEnd();\r\n };\r\n\r\n const handleMouseLeave = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (isDragging) {\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n }\r\n handleDragEnd();\r\n };\r\n\r\n // Touch handlers - use global touch guard\r\n const handleTouchStart = (event: React.TouchEvent<HTMLDivElement>) => {\r\n touchStart(); // Global guard\r\n touchingRef.current = true;\r\n handleDragStart();\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n };\r\n\r\n const handleTouchMove = (event: React.TouchEvent<HTMLDivElement>) => {\r\n if (!isDragging) return;\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n };\r\n\r\n const handleTouchEnd = () => {\r\n touchEnd(); // Global guard with delayed reset\r\n handleDragEnd();\r\n };\r\n\r\n const handleTouchCancel = (event: React.TouchEvent<HTMLDivElement>) => {\r\n touchEnd(); // Global guard with delayed reset\r\n if (isDragging && event.touches.length > 0) {\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n }\r\n handleDragEnd();\r\n };\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const containerClasses = [\r\n 'cris-slider',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Calculate bar style\r\n const computedBarStyle: React.CSSProperties = {\r\n ...barStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const width = 100 - thumbSizePercent;\r\n const left = (100 - width) / 2;\r\n computedBarStyle.height = `${trackSizePercent}%`;\r\n computedBarStyle.top = `${(100 - trackSizePercent) / 2}%`;\r\n computedBarStyle.width = `${width}%`;\r\n computedBarStyle.left = `${left}%`;\r\n } else {\r\n const height = 100 - thumbSizePercent;\r\n const top = (100 - height) / 2;\r\n computedBarStyle.width = `${trackSizePercent}%`;\r\n computedBarStyle.left = `${(100 - trackSizePercent) / 2}%`;\r\n computedBarStyle.height = `${height}%`;\r\n computedBarStyle.top = `${top}%`;\r\n }\r\n\r\n // Calculate fill style\r\n const computedFillStyle: React.CSSProperties = {\r\n ...fillStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const range = 100 - thumbSizePercent;\r\n const left = thumbSizePercent / 2;\r\n const width = range * ratioCurrent;\r\n computedFillStyle.height = `${trackSizePercent}%`;\r\n computedFillStyle.top = `${(100 - trackSizePercent) / 2}%`;\r\n computedFillStyle.width = `${width}%`;\r\n computedFillStyle.left = `${left}%`;\r\n } else {\r\n const range = 100 - thumbSizePercent;\r\n const top = 100 - (range * ratioCurrent + thumbSizePercent / 2);\r\n const height = range + thumbSizePercent / 2 - top;\r\n computedFillStyle.width = `${trackSizePercent}%`;\r\n computedFillStyle.left = `${(100 - trackSizePercent) / 2}%`;\r\n computedFillStyle.height = `${height}%`;\r\n computedFillStyle.top = `${top}%`;\r\n }\r\n\r\n // Calculate thumb style\r\n const computedThumbStyle: React.CSSProperties = {\r\n ...thumbStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const maxLeft = 100 - thumbSizePercent;\r\n const left = maxLeft * ratioCurrent;\r\n computedThumbStyle.width = `${thumbSizePercent}%`;\r\n computedThumbStyle.height = '100%';\r\n computedThumbStyle.left = `${left}%`;\r\n } else {\r\n const maxTop = 100 - thumbSizePercent;\r\n const top = maxTop - maxTop * ratioCurrent;\r\n computedThumbStyle.width = '100%';\r\n computedThumbStyle.height = `${thumbSizePercent}%`;\r\n computedThumbStyle.top = `${top}%`;\r\n }\r\n\r\n return (\r\n <div\r\n className={containerClasses}\r\n style={{\r\n ...style,\r\n position: 'relative',\r\n cursor: isEnabled ? 'pointer' : 'default',\r\n touchAction: 'none',\r\n userSelect: 'none',\r\n }}\r\n onMouseDown={handleMouseDown}\r\n onMouseMove={handleMouseMove}\r\n onMouseUp={handleMouseUp}\r\n onMouseLeave={handleMouseLeave}\r\n onTouchStart={handleTouchStart}\r\n onTouchMove={handleTouchMove}\r\n onTouchEnd={handleTouchEnd}\r\n onTouchCancel={handleTouchCancel}\r\n >\r\n {/* Bar/Track */}\r\n <div className={`cris-slider-bar ${barClassName}`} style={computedBarStyle} />\r\n\r\n {/* Fill */}\r\n {!fillHidden && (\r\n <div className={`cris-slider-fill ${fillClassName}`} style={computedFillStyle} />\r\n )}\r\n\r\n {/* Thumb */}\r\n <div className={`cris-slider-thumb ${thumbClassName}`} style={computedThumbStyle} />\r\n </div>\r\n );\r\n}\r\n","import { useAnalog, useDigital } from '@imperosoft/cris-webui-ch5-core';\r\n\r\nexport interface CrisGaugeProps {\r\n /** Static value (used if no join) */\r\n value?: number;\r\n /** Analog join for value */\r\n join?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Minimum value (default 0) */\r\n minValue?: number;\r\n /** Maximum value (default 65535) */\r\n maxValue?: number;\r\n /** Number of segments (default 20) */\r\n segments?: number;\r\n /** Invert direction (default false) */\r\n inverted?: boolean;\r\n /** Orientation (default vertical) */\r\n orientation?: 'vertical' | 'horizontal';\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n\r\n /** Inactive segment CSS class */\r\n inactiveSegmentClassName?: string;\r\n /** Inactive segment inline style */\r\n inactiveSegmentStyle?: React.CSSProperties;\r\n\r\n /** Active segment CSS class (all levels) */\r\n segmentClassName?: string;\r\n /** Active segment inline style (all levels) */\r\n segmentStyle?: React.CSSProperties;\r\n\r\n /** Low level segment CSS class (0-60%) */\r\n lowSegmentClassName?: string;\r\n /** Low level segment inline style */\r\n lowSegmentStyle?: React.CSSProperties;\r\n\r\n /** Medium level segment CSS class (60-80%) */\r\n mediumSegmentClassName?: string;\r\n /** Medium level segment inline style */\r\n mediumSegmentStyle?: React.CSSProperties;\r\n\r\n /** High level segment CSS class (80-100%) */\r\n highSegmentClassName?: string;\r\n /** High level segment inline style */\r\n highSegmentStyle?: React.CSSProperties;\r\n\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n}\r\n\r\nexport function CrisGauge({\r\n value,\r\n join,\r\n joinEnable,\r\n joinVisible,\r\n minValue = 0,\r\n maxValue = 65535,\r\n segments = 20,\r\n inverted = false,\r\n orientation = 'vertical',\r\n className = '',\r\n style,\r\n inactiveSegmentClassName = '',\r\n inactiveSegmentStyle,\r\n segmentClassName = '',\r\n segmentStyle,\r\n lowSegmentClassName = '',\r\n lowSegmentStyle,\r\n mediumSegmentClassName = '',\r\n mediumSegmentStyle,\r\n highSegmentClassName = '',\r\n highSegmentStyle,\r\n classDisabled = '',\r\n}: CrisGaugeProps) {\r\n // Get join values reactively\r\n const joinValue = useAnalog(join ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine if enabled and visible\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Get current value\r\n const currentValue = join != null ? joinValue : (value ?? 0);\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Determine segment type (low, medium, high)\r\n const getSegmentType = (segment: number): 'low' | 'medium' | 'high' => {\r\n const lowMax = segments * 0.6;\r\n const mediumMax = segments * 0.8;\r\n\r\n const seg =\r\n (inverted && orientation === 'vertical') || (!inverted && orientation === 'horizontal')\r\n ? segment + 1\r\n : segments - segment;\r\n\r\n if (seg <= lowMax) return 'low';\r\n if (seg <= mediumMax) return 'medium';\r\n return 'high';\r\n };\r\n\r\n // Check if segment is active\r\n const isSegmentActive = (segment: number): boolean => {\r\n if (currentValue < minValue) return false;\r\n if (currentValue > maxValue) return true;\r\n\r\n const ratio = currentValue / (maxValue - minValue);\r\n let segMax = Math.round(segments * ratio);\r\n if (ratio !== 0 && segMax === 0) segMax = 1;\r\n\r\n const seg =\r\n (inverted && orientation === 'vertical') || (!inverted && orientation === 'horizontal')\r\n ? segment + 1\r\n : segments - segment;\r\n\r\n return seg <= segMax;\r\n };\r\n\r\n // Get segment class\r\n const getSegmentClass = (segment: number): string => {\r\n const type = getSegmentType(segment);\r\n const active = isSegmentActive(segment);\r\n\r\n const classes = ['cris-gauge-segment', type, orientation];\r\n\r\n if (active) {\r\n classes.push('active');\r\n // Add type-specific or general active class\r\n if (type === 'low' && (lowSegmentClassName || segmentClassName)) {\r\n classes.push(lowSegmentClassName || segmentClassName);\r\n } else if (type === 'medium' && (mediumSegmentClassName || segmentClassName)) {\r\n classes.push(mediumSegmentClassName || segmentClassName);\r\n } else if (type === 'high' && (highSegmentClassName || segmentClassName)) {\r\n classes.push(highSegmentClassName || segmentClassName);\r\n }\r\n } else {\r\n if (inactiveSegmentClassName) {\r\n classes.push(inactiveSegmentClassName);\r\n }\r\n }\r\n\r\n return classes.filter(Boolean).join(' ');\r\n };\r\n\r\n // Get segment style\r\n const getSegmentStyle = (segment: number): React.CSSProperties | undefined => {\r\n const type = getSegmentType(segment);\r\n const active = isSegmentActive(segment);\r\n\r\n if (active) {\r\n if (type === 'low' && (lowSegmentStyle || segmentStyle)) {\r\n return lowSegmentStyle || segmentStyle;\r\n } else if (type === 'medium' && (mediumSegmentStyle || segmentStyle)) {\r\n return mediumSegmentStyle || segmentStyle;\r\n } else if (type === 'high' && (highSegmentStyle || segmentStyle)) {\r\n return highSegmentStyle || segmentStyle;\r\n }\r\n } else {\r\n return inactiveSegmentStyle;\r\n }\r\n\r\n return undefined;\r\n };\r\n\r\n // Build container classes\r\n const containerClasses = [\r\n 'cris-gauge',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Container flex style\r\n const containerStyle: React.CSSProperties = {\r\n ...style,\r\n display: 'flex',\r\n flexDirection: orientation === 'horizontal' ? 'row' : 'column',\r\n justifyContent: 'center',\r\n alignItems: 'center',\r\n };\r\n\r\n return (\r\n <div className={containerClasses} style={containerStyle}>\r\n {Array.from({ length: segments }, (_, segment) => (\r\n <div\r\n key={segment}\r\n className={getSegmentClass(segment)}\r\n style={getSegmentStyle(segment)}\r\n />\r\n ))}\r\n </div>\r\n );\r\n}\r\n","import { useAnalog, useDigital } from '@imperosoft/cris-webui-ch5-core';\r\n\r\nexport type SpinnerSpeed = 'slow' | 'medium' | 'fast';\r\n\r\nexport interface CrisSpinnerProps {\r\n /** Analog join for position (0-65535 maps to 0-360 degrees) */\r\n joinPosition?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Static position value (0-65535, used if no joinPosition) */\r\n position?: number;\r\n\r\n /** Endless rotation mode (default: false) */\r\n endless?: boolean;\r\n /** Speed for endless rotation (default: 'medium') */\r\n endlessSpeed?: SpinnerSpeed;\r\n\r\n /** Spinner color (default: '#bf2b23') */\r\n color?: string;\r\n /** Background/track color (default: 'rgba(0, 0, 0, 0.1)') */\r\n trackColor?: string;\r\n /** Spinner line width in pixels (default: 3) */\r\n lineWidth?: number;\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n}\r\n\r\nconst SPEED_DURATIONS: Record<SpinnerSpeed, string> = {\r\n slow: '1.5s',\r\n medium: '0.8s',\r\n fast: '0.4s',\r\n};\r\n\r\nexport function CrisSpinner({\r\n joinPosition,\r\n joinVisible,\r\n position,\r\n endless = false,\r\n endlessSpeed = 'medium',\r\n color = '#bf2b23',\r\n trackColor = 'rgba(0, 0, 0, 0.1)',\r\n lineWidth = 3,\r\n className = '',\r\n style,\r\n}: CrisSpinnerProps) {\r\n // Get join values reactively\r\n const joinPositionValue = useAnalog(joinPosition ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine visibility\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Get current position (0-65535 -> 0-360 degrees)\r\n const currentPosition = joinPosition != null ? joinPositionValue : (position ?? 0);\r\n const rotationDegrees = (currentPosition / 65535) * 360;\r\n\r\n // Build container classes\r\n const containerClasses = ['cris-spinner', className].filter(Boolean).join(' ');\r\n\r\n // Spinner styles\r\n const spinnerStyle: React.CSSProperties = {\r\n width: '100%',\r\n height: '100%',\r\n borderRadius: '50%',\r\n border: `${lineWidth}px solid ${trackColor}`,\r\n borderTopColor: color,\r\n boxSizing: 'border-box',\r\n };\r\n\r\n if (endless) {\r\n // Endless rotation animation\r\n spinnerStyle.animation = `cris-spinner-rotate ${SPEED_DURATIONS[endlessSpeed]} linear infinite`;\r\n } else {\r\n // Position-based rotation\r\n spinnerStyle.transform = `rotate(${rotationDegrees}deg)`;\r\n spinnerStyle.transition = 'transform 0.1s linear';\r\n }\r\n\r\n return (\r\n <div className={containerClasses} style={style}>\r\n <div style={spinnerStyle} />\r\n <style>{`\r\n @keyframes cris-spinner-rotate {\r\n to { transform: rotate(360deg); }\r\n }\r\n `}</style>\r\n </div>\r\n );\r\n}\r\n","import { useEffect, useState, useCallback } from 'react';\r\nimport { isNativeMode, useConnectionStore, ConnectionErrorReason } from '@imperosoft/cris-webui-ch5-core';\r\n\r\n// Environment detection utilities\r\nconst isFileProtocol = typeof window !== 'undefined' && window.location.protocol === 'file:';\r\nconst isDevModeDefault = typeof window !== 'undefined' &&\r\n (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1');\r\nconst isVC4Default = typeof window !== 'undefined' && window.location.pathname.includes('/VirtualControl/');\r\nconst isDeployedOnProcessorDefault = !isFileProtocol && !isDevModeDefault && !isVC4Default;\r\n\r\n// Session storage key to track login redirect\r\nconst LOGIN_REDIRECT_KEY = 'cris_login_redirect';\r\n\r\nexport interface CrisOfflinePageProps {\r\n /**\r\n * Processor hostname for dev mode (used to build login/cert URLs).\r\n * In production, uses window.location.hostname.\r\n */\r\n processorHost?: string;\r\n\r\n /**\r\n * Whether an auth token is configured (for dev mode diagnostics).\r\n */\r\n hasAuthToken?: boolean;\r\n\r\n /**\r\n * Override dev mode detection.\r\n */\r\n isDevMode?: boolean;\r\n\r\n /**\r\n * Override VC4 detection.\r\n */\r\n isVC4?: boolean;\r\n\r\n /**\r\n * Override deployed on processor detection.\r\n */\r\n isDeployedOnProcessor?: boolean;\r\n\r\n /**\r\n * WebSocket port for certificate acceptance (default: 49200).\r\n */\r\n wsPort?: number;\r\n\r\n /**\r\n * Show debug information panel (default: true).\r\n */\r\n showDebugInfo?: boolean;\r\n\r\n /**\r\n * Auto-redirect delay to login page in ms when deployed on processor (default: 1500).\r\n * Set to 0 to disable auto-redirect.\r\n */\r\n loginRedirectDelay?: number;\r\n\r\n /**\r\n * Custom login URL path (default: '/userlogin.html').\r\n */\r\n loginPath?: string;\r\n\r\n /**\r\n * Auto-retry countdown seconds when returning from cert page (default: 3).\r\n */\r\n retryCountdown?: number;\r\n\r\n /**\r\n * Custom className for the root container.\r\n */\r\n className?: string;\r\n}\r\n\r\nexport function CrisOfflinePage({\r\n processorHost: processorHostProp,\r\n hasAuthToken = false,\r\n isDevMode = isDevModeDefault,\r\n isVC4 = isVC4Default,\r\n isDeployedOnProcessor = isDeployedOnProcessorDefault,\r\n wsPort = 49200,\r\n showDebugInfo = true,\r\n loginRedirectDelay = 1500,\r\n loginPath = '/userlogin.html',\r\n retryCountdown: retryCountdownInit = 3,\r\n className = '',\r\n}: CrisOfflinePageProps) {\r\n const connectionStatus = useConnectionStore((state) => state.status);\r\n const errorReason = useConnectionStore((state) => state.errorReason);\r\n const [hasSeenError, setHasSeenError] = useState(false);\r\n const [lastErrorReason, setLastErrorReason] = useState<ConnectionErrorReason>(null);\r\n const [certPageOpened, setCertPageOpened] = useState(false);\r\n const [retryCountdown, setRetryCountdown] = useState<number | null>(null);\r\n\r\n // Check if we just came back from login page (prevents redirect loop)\r\n // Using a ref-like pattern with useState to only compute once on mount\r\n const [returnedFromLogin] = useState(() => {\r\n if (typeof window === 'undefined') return false;\r\n try {\r\n const wasRedirected = sessionStorage.getItem(LOGIN_REDIRECT_KEY);\r\n if (wasRedirected) {\r\n sessionStorage.removeItem(LOGIN_REDIRECT_KEY);\r\n return true;\r\n }\r\n } catch {\r\n // sessionStorage not available\r\n }\r\n return false;\r\n });\r\n\r\n // Determine processor host\r\n const processorHost = processorHostProp ??\r\n (typeof window !== 'undefined' ? window.location.hostname : 'localhost');\r\n\r\n // Make error state \"sticky\" - once we've seen an error, keep showing error UI\r\n // until the user manually retries or connection succeeds\r\n const isError = connectionStatus === 'error' || (hasSeenError && connectionStatus !== 'connected');\r\n\r\n // Track when we first see an error and capture the reason\r\n useEffect(() => {\r\n if (connectionStatus === 'error') {\r\n setHasSeenError(true);\r\n setLastErrorReason(errorReason);\r\n } else if (connectionStatus === 'connected') {\r\n setHasSeenError(false);\r\n setLastErrorReason(null);\r\n }\r\n }, [connectionStatus, errorReason]);\r\n\r\n // Determine if this is a timeout error (processor unreachable)\r\n const isTimeoutError = lastErrorReason === 'timeout';\r\n\r\n // Build URLs\r\n const loginUrl = `https://${processorHost}${loginPath}`;\r\n const certUrl = `https://${processorHost}:${wsPort}/`;\r\n\r\n // Open certificate page in new tab\r\n const openCertPage = useCallback(() => {\r\n window.open(certUrl, '_blank');\r\n setCertPageOpened(true);\r\n }, [certUrl]);\r\n\r\n // Retry connection by reloading the page\r\n const retryConnection = useCallback(() => {\r\n window.location.reload();\r\n }, []);\r\n\r\n // Auto-retry when user returns after opening cert page\r\n useEffect(() => {\r\n if (!certPageOpened || !isError) return;\r\n\r\n const handleVisibilityChange = () => {\r\n if (document.visibilityState === 'visible') {\r\n // Start countdown when user returns\r\n setRetryCountdown(retryCountdownInit);\r\n }\r\n };\r\n\r\n const handleFocus = () => {\r\n setRetryCountdown(retryCountdownInit);\r\n };\r\n\r\n document.addEventListener('visibilitychange', handleVisibilityChange);\r\n window.addEventListener('focus', handleFocus);\r\n\r\n return () => {\r\n document.removeEventListener('visibilitychange', handleVisibilityChange);\r\n window.removeEventListener('focus', handleFocus);\r\n };\r\n }, [certPageOpened, isError, retryCountdownInit]);\r\n\r\n // Countdown timer for auto-retry\r\n useEffect(() => {\r\n if (retryCountdown === null) return;\r\n if (retryCountdown <= 0) {\r\n retryConnection();\r\n return;\r\n }\r\n\r\n const timer = setTimeout(() => {\r\n setRetryCountdown(retryCountdown - 1);\r\n }, 1000);\r\n\r\n return () => clearTimeout(timer);\r\n }, [retryCountdown, retryConnection]);\r\n\r\n // Auto-redirect to login page ONLY when deployed on processor (not dev mode, not CrestronOne)\r\n // Don't redirect if we just came back from login (prevents redirect loop)\r\n useEffect(() => {\r\n if (isError && isDeployedOnProcessor && loginRedirectDelay > 0 && !returnedFromLogin) {\r\n const timer = setTimeout(() => {\r\n // Set flag before redirecting so we know we came from here\r\n try {\r\n sessionStorage.setItem(LOGIN_REDIRECT_KEY, 'true');\r\n } catch {\r\n // sessionStorage not available\r\n }\r\n window.location.href = loginUrl;\r\n }, loginRedirectDelay);\r\n return () => clearTimeout(timer);\r\n }\r\n }, [isError, isDeployedOnProcessor, loginUrl, loginRedirectDelay, returnedFromLogin]);\r\n\r\n // Gather environment info for debugging\r\n const envInfo = {\r\n userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'N/A',\r\n hasCrComLib: typeof window !== 'undefined' && typeof (window as any).CrComLib !== 'undefined',\r\n hasCrestronPanel: typeof window !== 'undefined' && 'CrestronPanel' in window,\r\n isNativeMode: isNativeMode(),\r\n hostname: typeof window !== 'undefined' ? window.location.hostname : 'N/A',\r\n pathname: typeof window !== 'undefined' ? window.location.pathname : 'N/A',\r\n search: typeof window !== 'undefined' ? window.location.search : '',\r\n protocol: typeof window !== 'undefined' ? window.location.protocol : 'N/A',\r\n isDevMode,\r\n isVC4,\r\n isDeployedOnProcessor,\r\n returnedFromLogin,\r\n processorHost: isDevMode ? processorHost : undefined,\r\n hasAuthToken: isDevMode ? hasAuthToken : undefined,\r\n errorReason: lastErrorReason,\r\n };\r\n\r\n return (\r\n <div className={`cris-offline-page absolute inset-0 bg-gray-900 flex flex-col items-center justify-center overflow-auto py-8 ${className}`}>\r\n <div className=\"text-center max-w-4xl px-4\">\r\n {/* Status indicator */}\r\n <div className=\"mb-8\">\r\n <div className={`w-16 h-16 mx-auto rounded-full flex items-center justify-center ${isError ? 'bg-red-500/20' : 'bg-yellow-500/20'}`}>\r\n <div className={`w-8 h-8 rounded-full ${isError ? 'bg-red-500' : 'bg-yellow-500 animate-pulse'}`} />\r\n </div>\r\n </div>\r\n\r\n {/* Message */}\r\n <h1 className=\"text-2xl font-bold text-white mb-2\">\r\n {isError\r\n ? (isTimeoutError ? 'Processor Unreachable' : 'Connection Error')\r\n : 'Connecting...'}\r\n </h1>\r\n <p className=\"text-gray-400\">\r\n {isError\r\n ? (isDeployedOnProcessor\r\n ? (returnedFromLogin\r\n ? 'Connection failed after login. Please try again.'\r\n : 'Redirecting to login page...')\r\n : isTimeoutError\r\n ? 'Could not connect to the processor. Check the IP address and network connection.'\r\n : 'WebSocket connection failed. SSL certificate may need to be accepted.')\r\n : 'Establishing connection to control system'}\r\n </p>\r\n\r\n {/* Status badge */}\r\n <div className=\"mt-8\">\r\n <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'}`}>\r\n {isError\r\n ? (isTimeoutError\r\n ? 'Connection Timeout'\r\n : isDevMode\r\n ? 'Dev Mode - Auth Error'\r\n : isVC4\r\n ? 'VC4 - Not Authorized'\r\n : 'Auth Error')\r\n : 'Connecting'}\r\n </span>\r\n </div>\r\n\r\n {/* Timeout error UI - processor unreachable */}\r\n {isError && !isDeployedOnProcessor && isTimeoutError && (\r\n <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' }}>\r\n <p className=\"text-orange-400 font-medium\" style={{ fontSize: '1.2em', marginBottom: '1em' }}>\r\n The processor at <span className=\"text-white\">{processorHost}</span> is not responding.\r\n </p>\r\n <ul className=\"text-gray-400 text-left mb-[1em]\" style={{ fontSize: '1em', listStyleType: 'disc', paddingLeft: '1.5em' }}>\r\n <li>Verify the IP address is correct</li>\r\n <li>Check that the processor is powered on</li>\r\n <li>Ensure you are on the same network</li>\r\n </ul>\r\n <div className=\"flex gap-[1em] justify-center\" style={{ overflow: 'visible' }}>\r\n <button\r\n onClick={retryConnection}\r\n className=\"bg-orange-600 hover:bg-orange-500 text-white font-medium rounded-xl transition-colors\"\r\n style={{ padding: '1em 2em', fontSize: '1.2em', whiteSpace: 'nowrap', overflow: 'visible' }}\r\n >\r\n Retry Connection\r\n </button>\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Certificate acceptance UI - for websocket/auth errors in dev mode and VC4 */}\r\n {isError && !isDeployedOnProcessor && !isTimeoutError && (\r\n <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' }}>\r\n {retryCountdown !== null ? (\r\n <div className=\"text-center\" style={{ overflow: 'visible' }}>\r\n <p className=\"text-blue-400 font-medium\" style={{ fontSize: '1.4em', marginBottom: '1em' }}>Retrying connection in {retryCountdown}...</p>\r\n <button\r\n onClick={() => setRetryCountdown(null)}\r\n className=\"bg-gray-600 hover:bg-gray-500 text-white font-medium rounded-xl transition-colors\"\r\n style={{ padding: '1em 2em', fontSize: '1.2em', whiteSpace: 'nowrap', overflow: 'visible' }}\r\n >\r\n Cancel\r\n </button>\r\n </div>\r\n ) : (\r\n <>\r\n <p className=\"text-blue-400 font-medium\" style={{ fontSize: '1.2em', marginBottom: '1em' }}>\r\n {certPageOpened\r\n ? 'Accept the certificate in the new tab, then return here.'\r\n : 'Click below to accept the WebSocket SSL certificate:'}\r\n </p>\r\n <div className=\"flex gap-[1em] justify-center flex-wrap\" style={{ overflow: 'visible' }}>\r\n <button\r\n onClick={openCertPage}\r\n className=\"bg-blue-600 hover:bg-blue-500 text-white font-medium rounded-xl transition-colors\"\r\n style={{ padding: '1em 2em', fontSize: '1.2em', whiteSpace: 'nowrap', overflow: 'visible' }}\r\n >\r\n {certPageOpened ? 'Open Certificate Page Again' : 'Accept Certificate'}\r\n </button>\r\n <button\r\n onClick={retryConnection}\r\n className=\"bg-green-600 hover:bg-green-500 text-white font-medium rounded-xl transition-colors\"\r\n style={{ padding: '1em 2em', fontSize: '1.2em', whiteSpace: 'nowrap', overflow: 'visible' }}\r\n >\r\n Retry Connection\r\n </button>\r\n </div>\r\n {certPageOpened && (\r\n <p className=\"text-gray-400\" style={{ fontSize: '1em', marginTop: '1em' }}>\r\n Connection will auto-retry when you return to this page.\r\n </p>\r\n )}\r\n </>\r\n )}\r\n </div>\r\n )}\r\n\r\n {/* Deployed on processor - returned from login but still failed */}\r\n {isError && isDeployedOnProcessor && returnedFromLogin && (\r\n <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' }}>\r\n <p className=\"text-red-400 font-medium\" style={{ fontSize: '1.2em', marginBottom: '1em' }}>\r\n Connection failed after authentication.\r\n </p>\r\n <p className=\"text-gray-400\" style={{ fontSize: '1em', marginBottom: '1em' }}>\r\n This may be due to an expired session or WebSocket connection issue.\r\n </p>\r\n <div className=\"flex gap-[1em] justify-center flex-wrap\" style={{ overflow: 'visible' }}>\r\n <button\r\n onClick={retryConnection}\r\n className=\"bg-green-600 hover:bg-green-500 text-white font-medium rounded-xl transition-colors\"\r\n style={{ padding: '1em 2em', fontSize: '1.2em', whiteSpace: 'nowrap', overflow: 'visible' }}\r\n >\r\n Retry Connection\r\n </button>\r\n <button\r\n onClick={() => {\r\n try {\r\n sessionStorage.setItem(LOGIN_REDIRECT_KEY, 'true');\r\n } catch {\r\n // sessionStorage not available\r\n }\r\n window.location.href = loginUrl;\r\n }}\r\n className=\"bg-blue-600 hover:bg-blue-500 text-white font-medium rounded-xl transition-colors\"\r\n style={{ padding: '1em 2em', fontSize: '1.2em', whiteSpace: 'nowrap', overflow: 'visible' }}\r\n >\r\n Go to Login\r\n </button>\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Dev mode auth token status */}\r\n {isDevMode && isError && (\r\n <div className=\"mt-6 p-4 bg-yellow-500/10 border border-yellow-500/30 rounded-lg text-left\">\r\n <p className=\"text-yellow-400 text-sm font-medium mb-2\">Dev Mode Configuration:</p>\r\n <p className=\"text-gray-300 text-sm\">\r\n Processor: <span className=\"text-blue-400\">{processorHost}</span>\r\n </p>\r\n <p className=\"text-gray-300 text-sm\">\r\n AuthToken: <span className={hasAuthToken ? 'text-green-400' : 'text-red-400'}>\r\n {hasAuthToken ? 'Present (may be expired)' : 'Missing'}\r\n </span>\r\n </p>\r\n {!hasAuthToken && (\r\n <p className=\"text-yellow-400 text-xs mt-2\">\r\n Add authToken to crestron.config.json or generate new: <code className=\"bg-black/30 px-1 rounded\">websockettoken generate</code>\r\n </p>\r\n )}\r\n </div>\r\n )}\r\n\r\n {/* Debug Info */}\r\n {showDebugInfo && (\r\n <div className=\"mt-12 text-left bg-gray-800 rounded-lg p-4 text-xs font-mono\">\r\n <h2 className=\"text-yellow-400 font-bold mb-3 text-sm\">Environment Debug Info</h2>\r\n <div className=\"space-y-2 text-gray-300\">\r\n <div>\r\n <span className=\"text-gray-500\">CrComLib:</span>{' '}\r\n <span className={envInfo.hasCrComLib ? 'text-green-400' : 'text-red-400'}>\r\n {envInfo.hasCrComLib ? 'Available' : 'Not Found'}\r\n </span>\r\n </div>\r\n <div>\r\n <span className=\"text-gray-500\">CrestronPanel:</span>{' '}\r\n <span className={envInfo.hasCrestronPanel ? 'text-green-400' : 'text-gray-400'}>\r\n {envInfo.hasCrestronPanel ? 'Yes' : 'No'}\r\n </span>\r\n </div>\r\n <div>\r\n <span className=\"text-gray-500\">Native Mode:</span>{' '}\r\n <span className={envInfo.isNativeMode ? 'text-green-400' : 'text-gray-400'}>\r\n {envInfo.isNativeMode ? 'Yes' : 'No (Browser)'}\r\n </span>\r\n </div>\r\n <div>\r\n <span className=\"text-gray-500\">Dev Mode:</span>{' '}\r\n <span className={envInfo.isDevMode ? 'text-yellow-400' : 'text-gray-400'}>\r\n {envInfo.isDevMode ? 'Yes' : 'No'}\r\n </span>\r\n </div>\r\n <div>\r\n <span className=\"text-gray-500\">VC4:</span>{' '}\r\n <span className={envInfo.isVC4 ? 'text-purple-400' : 'text-gray-400'}>\r\n {envInfo.isVC4 ? 'Yes' : 'No'}\r\n </span>\r\n </div>\r\n <div>\r\n <span className=\"text-gray-500\">Deployed on Processor:</span>{' '}\r\n <span className={envInfo.isDeployedOnProcessor ? 'text-cyan-400' : 'text-gray-400'}>\r\n {envInfo.isDeployedOnProcessor ? 'Yes' : 'No'}\r\n </span>\r\n </div>\r\n {envInfo.returnedFromLogin && (\r\n <div>\r\n <span className=\"text-gray-500\">Returned from Login:</span>{' '}\r\n <span className=\"text-yellow-400\">Yes</span>\r\n </div>\r\n )}\r\n <div>\r\n <span className=\"text-gray-500\">Protocol:</span>{' '}\r\n <span className=\"text-blue-400\">{envInfo.protocol}</span>\r\n </div>\r\n <div>\r\n <span className=\"text-gray-500\">Hostname:</span>{' '}\r\n <span className=\"text-blue-400\">{envInfo.hostname}</span>\r\n </div>\r\n {envInfo.isDevMode && envInfo.processorHost && (\r\n <div>\r\n <span className=\"text-gray-500\">Target Processor:</span>{' '}\r\n <span className=\"text-blue-400\">{envInfo.processorHost}</span>\r\n </div>\r\n )}\r\n <div>\r\n <span className=\"text-gray-500\">Path:</span>{' '}\r\n <span className=\"text-blue-400\">{envInfo.pathname}{envInfo.search}</span>\r\n </div>\r\n {envInfo.errorReason && (\r\n <div>\r\n <span className=\"text-gray-500\">Error Reason:</span>{' '}\r\n <span className={envInfo.errorReason === 'timeout' ? 'text-orange-400' : 'text-red-400'}>\r\n {envInfo.errorReason}\r\n </span>\r\n </div>\r\n )}\r\n </div>\r\n </div>\r\n )}\r\n </div>\r\n </div>\r\n );\r\n}\r\n"],"mappings":"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","names":["useDigital","jsx","jsxs","useState","useRef","useEffect","useDigital","useJoinsStore","jsx","jsxs","useDigital","useJoinsStore","useState","useRef","useEffect","useAnalog","useDigital","jsx","useAnalog","useDigital","jsx","jsxs","useEffect","useState","useCallback","jsx","jsxs"]}
|
|
1
|
+
{"version":3,"sources":["../src/components/CrisButton.tsx","../src/utils/icons.ts","../src/utils/touchGuard.ts","../src/components/CrisText.tsx","../src/components/CrisSlider.tsx","../src/components/CrisGauge.tsx","../src/components/CrisSpinner.tsx","../src/components/CrisOfflinePage.tsx"],"sourcesContent":["import { useState, useRef, useEffect, useCallback, ReactNode } from 'react';\r\nimport { useDigital, useJoinsStore } from '@imperosoft/cris-webui-ch5-core';\r\nimport { getIconUrl, getIconFilter } from '../utils/icons';\r\nimport { touchStart, touchEnd, isTouchActive } from '../utils/touchGuard';\r\n\r\nexport interface CrisButtonProps {\r\n /** Digital join for press action */\r\n join?: number;\r\n /** Digital join for feedback (defaults to join) */\r\n joinFeedback?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Button text */\r\n text?: string;\r\n /** Text when pressed (local feedback) */\r\n textPressed?: string;\r\n /** Text when selected (controller feedback) */\r\n textSelected?: string;\r\n\r\n /** Icon as ReactNode (for custom SVG components) */\r\n icon?: ReactNode;\r\n /** Icon name (loads SVG from configured path, e.g., 'motor-stop') */\r\n iconName?: string;\r\n /** Icon CSS class (for CSS-based icons, e.g., 'ico-motor-stop') */\r\n iconClass?: string;\r\n /** Icon size - number (px), string ('50%', '2rem'), or preset ('xs'|'sm'|'md'|'lg'|'xl') */\r\n iconSize?: number | string | 'xs' | 'sm' | 'md' | 'lg' | 'xl';\r\n /** Icon container size as percentage (default: 80% for top/bottom, 20% for left/right) */\r\n iconContainerSize?: string;\r\n /** Icon inline style */\r\n iconStyle?: React.CSSProperties;\r\n /** Icon position relative to text */\r\n iconPosition?: 'left' | 'right' | 'top' | 'bottom';\r\n\r\n /** Icon name when active (overrides iconName when button is active) */\r\n iconNameActive?: string;\r\n /** Icon CSS class when active (overrides iconClass when button is active) */\r\n iconClassActive?: string;\r\n /** Icon inline style when active (merged with iconStyle when button is active) */\r\n iconStyleActive?: React.CSSProperties;\r\n\r\n /** Show controller feedback styling */\r\n showControlFeedback?: boolean;\r\n /** Show local press feedback styling */\r\n showLocalFeedback?: boolean;\r\n /** Suppress click actions (display only) */\r\n suppressKeyClicks?: boolean;\r\n\r\n /** Smart object ID (for smarts instead of joins) */\r\n smartId?: number;\r\n\r\n /** Custom class names */\r\n className?: string;\r\n /** Class when active (controller feedback) */\r\n classActive?: string;\r\n /** Class when pressed (local feedback) */\r\n classPressed?: string;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n\r\n /** Children content */\r\n children?: ReactNode;\r\n\r\n /** Custom click handler (called on press) */\r\n onPress?: () => void;\r\n /** Custom release handler */\r\n onRelease?: () => void;\r\n\r\n /** Enable debug logging for this button */\r\n debug?: boolean;\r\n}\r\n\r\nexport function CrisButton({\r\n join,\r\n joinFeedback,\r\n joinEnable,\r\n joinVisible,\r\n text,\r\n textPressed,\r\n textSelected,\r\n icon,\r\n iconName,\r\n iconClass,\r\n iconSize,\r\n iconContainerSize,\r\n iconStyle,\r\n iconPosition = 'top',\r\n iconNameActive,\r\n iconClassActive,\r\n iconStyleActive,\r\n showControlFeedback = true,\r\n showLocalFeedback = true,\r\n suppressKeyClicks = false,\r\n smartId,\r\n className = '',\r\n classActive = '',\r\n classPressed = '',\r\n classDisabled = '',\r\n children,\r\n onPress,\r\n onRelease,\r\n debug = false,\r\n}: CrisButtonProps) {\r\n // Debug logging helper — stable ref to avoid re-triggering effects\r\n const debugRef = useRef(debug);\r\n debugRef.current = debug;\r\n const joinRef = useRef(join);\r\n joinRef.current = join;\r\n const log = useCallback((msg: string, data?: unknown) => {\r\n if (debugRef.current) console.log(`[CrisButton:${joinRef.current}] ${msg}`, data ?? '');\r\n }, []);\r\n const [pressed, setPressed] = useState(false);\r\n const pressedRef = useRef(false);\r\n const touchingRef = useRef(false);\r\n const touchStartedHereRef = useRef(false);\r\n\r\n // Get join values reactively\r\n const feedbackJoin = joinFeedback ?? join;\r\n const feedback = useDigital(feedbackJoin ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Get action method\r\n const dSet = useJoinsStore((state) => state.dSet);\r\n\r\n // Determine if button is enabled\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n\r\n // Determine if button is visible\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Release if button becomes invisible while pressed\r\n useEffect(() => {\r\n log('visibility effect', { isVisible, pressedRef: pressedRef.current });\r\n if (!isVisible && pressedRef.current) {\r\n log('VISIBILITY RELEASE - button hidden while pressed');\r\n pressedRef.current = false;\r\n setPressed(false);\r\n touchingRef.current = false;\r\n touchStartedHereRef.current = false;\r\n if (join != null && smartId == null) {\r\n log('sending release via dSet(false)');\r\n dSet(join, false);\r\n }\r\n }\r\n }, [isVisible, join, smartId, dSet]);\r\n\r\n // Release on unmount if button was pressed (handles page transitions)\r\n useEffect(() => {\r\n return () => {\r\n if (pressedRef.current && join != null && smartId == null) {\r\n log('UNMOUNT RELEASE - component unmounting while pressed');\r\n dSet(join, false);\r\n }\r\n };\r\n }, [join, smartId, dSet, log]);\r\n\r\n // Determine current feedback state\r\n const hasControlFeedback = showControlFeedback && feedbackJoin != null && feedback;\r\n const hasPressedFeedback = showLocalFeedback && pressed && isEnabled;\r\n const isActive = hasControlFeedback || hasPressedFeedback;\r\n\r\n // Determine current text\r\n let currentText = text ?? '';\r\n if (hasPressedFeedback && textPressed != null) {\r\n currentText = textPressed;\r\n } else if (hasControlFeedback && textSelected != null) {\r\n currentText = textSelected;\r\n }\r\n\r\n // Event handlers\r\n const handlePress = () => {\r\n log('handlePress called', { suppressKeyClicks, pressedRef: pressedRef.current, isEnabled });\r\n if (suppressKeyClicks) {\r\n log('BLOCKED: suppressKeyClicks');\r\n return;\r\n }\r\n if (pressedRef.current) {\r\n log('BLOCKED: already pressed');\r\n return;\r\n }\r\n\r\n pressedRef.current = true;\r\n setPressed(true);\r\n\r\n if (!isEnabled) {\r\n log('SKIPPED dSet: not enabled');\r\n return;\r\n }\r\n\r\n // Custom handler\r\n onPress?.();\r\n\r\n // Send to controller\r\n if (join != null && smartId == null) {\r\n log('SENDING PRESS via dSet(true)');\r\n dSet(join, true);\r\n }\r\n // TODO: Add smartId support when smarts are implemented in core\r\n };\r\n\r\n const handleRelease = () => {\r\n log('handleRelease called', { suppressKeyClicks, pressedRef: pressedRef.current, isEnabled });\r\n if (suppressKeyClicks) {\r\n log('BLOCKED: suppressKeyClicks');\r\n return;\r\n }\r\n if (!pressedRef.current) {\r\n log('BLOCKED: not pressed');\r\n return;\r\n }\r\n\r\n pressedRef.current = false;\r\n setPressed(false);\r\n\r\n if (!isEnabled) {\r\n log('SKIPPED dSet: not enabled');\r\n return;\r\n }\r\n\r\n // Custom handler\r\n onRelease?.();\r\n\r\n // Send to controller\r\n if (join != null && smartId == null) {\r\n log('SENDING RELEASE via dSet(false)');\r\n dSet(join, false);\r\n }\r\n // TODO: Add smartId support when smarts are implemented in core\r\n };\r\n\r\n // Touch event handlers - use global touch guard to prevent phantom clicks\r\n // when buttons swap visibility (e.g., back button hides, power button appears)\r\n const handleTouchStart = () => {\r\n log('handleTouchStart');\r\n touchStart(); // Global guard\r\n touchingRef.current = true;\r\n touchStartedHereRef.current = true;\r\n handlePress();\r\n };\r\n\r\n const handleTouchEnd = () => {\r\n log('handleTouchEnd', { touchStartedHereRef: touchStartedHereRef.current });\r\n touchEnd(); // Global guard with delayed reset\r\n touchingRef.current = true;\r\n // Only fire release if touch actually started on this button\r\n if (touchStartedHereRef.current) {\r\n touchStartedHereRef.current = false;\r\n handleRelease();\r\n } else {\r\n log('SKIPPED handleRelease: touch did not start here');\r\n }\r\n };\r\n\r\n const handleTouchCancel = () => {\r\n log('handleTouchCancel');\r\n touchEnd(); // Global guard with delayed reset\r\n touchingRef.current = true;\r\n touchStartedHereRef.current = false;\r\n handleRelease();\r\n };\r\n\r\n // Mouse event handlers - skip if any touch event is active (global check)\r\n const handleMouseDown = () => {\r\n if (isTouchActive() || touchingRef.current) return;\r\n handlePress();\r\n };\r\n\r\n const handleMouseUp = () => {\r\n if (isTouchActive() || touchingRef.current) return;\r\n handleRelease();\r\n };\r\n\r\n const handleMouseLeave = () => {\r\n if (isTouchActive() || touchingRef.current) return;\r\n handleRelease();\r\n };\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const classes = [\r\n 'cris-button',\r\n className,\r\n hasControlFeedback && classActive,\r\n hasPressedFeedback && classPressed,\r\n !isEnabled && classDisabled,\r\n hasControlFeedback && 'active',\r\n hasPressedFeedback && 'pressed',\r\n !isEnabled && 'disabled',\r\n suppressKeyClicks && 'no-hover',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Flex direction based on icon position\r\n const flexDirection =\r\n iconPosition === 'top'\r\n ? 'flex-col'\r\n : iconPosition === 'bottom'\r\n ? 'flex-col-reverse'\r\n : iconPosition === 'left'\r\n ? 'flex-row'\r\n : 'flex-row-reverse';\r\n\r\n // Determine if we have any icon to show\r\n const hasIcon = icon != null || iconName != null || iconClass != null || iconNameActive != null || iconClassActive != null;\r\n\r\n // Determine current icon properties based on active state\r\n const currentIconName = isActive && iconNameActive != null ? iconNameActive : iconName;\r\n const currentIconClass = isActive && iconClassActive != null ? iconClassActive : iconClass;\r\n const currentIconStyle = isActive && iconStyleActive != null ? { ...iconStyle, ...iconStyleActive } : iconStyle;\r\n\r\n // Size presets mapping\r\n const sizePresets: Record<string, string> = {\r\n xs: '16px',\r\n sm: '24px',\r\n md: '32px',\r\n lg: '48px',\r\n xl: '64px',\r\n };\r\n\r\n // Calculate icon size value\r\n const getIconSizeValue = (): string | undefined => {\r\n if (iconSize == null) return undefined;\r\n if (typeof iconSize === 'number') return `${iconSize}px`;\r\n if (iconSize in sizePresets) return sizePresets[iconSize];\r\n return iconSize; // Already a string like '50%' or '2rem'\r\n };\r\n\r\n const iconSizeValue = getIconSizeValue();\r\n\r\n // Build icon classes\r\n const iconClasses = [\r\n 'cris-button-icon',\r\n currentIconClass,\r\n hasControlFeedback && 'active',\r\n hasPressedFeedback && 'pressed',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Build icon style with size and filter support\r\n const computedIconStyle: React.CSSProperties = {\r\n ...currentIconStyle,\r\n };\r\n\r\n // Apply size\r\n if (iconSizeValue) {\r\n computedIconStyle.width = iconSizeValue;\r\n computedIconStyle.height = iconSizeValue;\r\n }\r\n\r\n // Apply filter from config if using iconName (only if not overridden by iconStyle/iconStyleActive)\r\n if (currentIconName && !currentIconStyle?.filter) {\r\n const filter = getIconFilter(isActive);\r\n if (filter) {\r\n computedIconStyle.filter = filter;\r\n }\r\n }\r\n\r\n // Render icon element\r\n const renderIconElement = () => {\r\n if (icon) {\r\n // ReactNode icon (custom component)\r\n return <span className={iconClasses} style={computedIconStyle}>{icon}</span>;\r\n }\r\n\r\n if (currentIconName) {\r\n // Icon by name (loads from configured path)\r\n return (\r\n <img\r\n className={iconClasses}\r\n style={{ ...computedIconStyle, pointerEvents: 'none' }}\r\n src={getIconUrl(currentIconName)}\r\n alt=\"\"\r\n draggable={false}\r\n />\r\n );\r\n }\r\n\r\n if (currentIconClass) {\r\n // CSS-based icon (like Angular version)\r\n return <img className={iconClasses} style={{ ...computedIconStyle, pointerEvents: 'none' }} alt=\"\" draggable={false} />;\r\n }\r\n\r\n return null;\r\n };\r\n\r\n // Calculate icon container size (default: 100% if no text, otherwise 80% for top/bottom, 20% for left/right)\r\n const isVertical = iconPosition === 'top' || iconPosition === 'bottom';\r\n const hasText = currentText !== '';\r\n const defaultContainerSize = hasText ? (isVertical ? '80%' : '20%') : '100%';\r\n const containerSize = iconContainerSize ?? defaultContainerSize;\r\n\r\n // Calculate text container size (remaining space)\r\n const textContainerSize = `calc(100% - ${containerSize})`;\r\n\r\n // Container style for icon area\r\n const iconContainerStyle: React.CSSProperties = isVertical\r\n ? { height: containerSize, width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }\r\n : { width: containerSize, height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' };\r\n\r\n // Container style for text area\r\n const textContainerStyle: React.CSSProperties = isVertical\r\n ? { height: textContainerSize, width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }\r\n : { width: textContainerSize, height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' };\r\n\r\n // Check if button has 'free' class (absolute positioning)\r\n const isFreePositioned = className.includes('free');\r\n\r\n return (\r\n <div\r\n className={`${classes} flex items-center justify-center ${flexDirection}`}\r\n style={{\r\n cursor: suppressKeyClicks ? 'default' : 'pointer',\r\n position: isFreePositioned ? undefined : 'relative',\r\n overflow: isFreePositioned ? undefined : 'hidden',\r\n touchAction: 'none',\r\n userSelect: 'none',\r\n WebkitUserSelect: 'none',\r\n }}\r\n onMouseDown={handleMouseDown}\r\n onMouseUp={handleMouseUp}\r\n onMouseLeave={handleMouseLeave}\r\n onTouchStart={handleTouchStart}\r\n onTouchEnd={handleTouchEnd}\r\n onTouchCancel={handleTouchCancel}\r\n >\r\n {/* Press overlay - shows on press for both desktop and touch devices */}\r\n <div\r\n className=\"cris-button-press-overlay\"\r\n style={{\r\n position: 'absolute',\r\n inset: 0,\r\n backgroundColor: 'black',\r\n opacity: pressed ? 0.1 : 0,\r\n transition: 'opacity 0.1s',\r\n pointerEvents: 'none',\r\n borderRadius: 'inherit',\r\n }}\r\n />\r\n {children}\r\n {hasIcon && (\r\n <div className=\"cris-button-icon-container\" style={iconContainerStyle}>\r\n {renderIconElement()}\r\n </div>\r\n )}\r\n {currentText && (\r\n <div className=\"cris-button-text-container\" style={textContainerStyle}>\r\n <span className=\"cris-button-text\">{currentText}</span>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n","/**\r\n * Icon configuration and utilities for CRIS components\r\n *\r\n * Supports different base paths for dev vs production (Crestron controller)\r\n */\r\n\r\nexport interface IconConfig {\r\n /** Base path for icon assets (e.g., '/assets/icons/' or 'http://controller/icons/') */\r\n basePath: string;\r\n /** File extension (default: 'svg') */\r\n extension: string;\r\n /** Default filter for inactive state */\r\n defaultFilter?: string;\r\n /** Default filter for active state */\r\n activeFilter?: string;\r\n}\r\n\r\n// Default configuration\r\nlet iconConfig: IconConfig = {\r\n basePath: '/assets/icons/',\r\n extension: 'svg',\r\n defaultFilter: undefined,\r\n activeFilter: undefined,\r\n};\r\n\r\n/**\r\n * Configure icon paths and defaults\r\n * Call this once at app startup\r\n *\r\n * @example\r\n * // Development\r\n * configureIcons({ basePath: '/assets/icons/' });\r\n *\r\n * // Production on Crestron controller\r\n * configureIcons({ basePath: '/html/icons/' });\r\n *\r\n * // With color filters\r\n * configureIcons({\r\n * basePath: '/assets/icons/',\r\n * defaultFilter: 'brightness(0) invert(1)', // white\r\n * activeFilter: 'brightness(0) invert(0.5) sepia(1) saturate(5) hue-rotate(0deg)', // red\r\n * });\r\n */\r\nexport function configureIcons(config: Partial<IconConfig>): void {\r\n iconConfig = { ...iconConfig, ...config };\r\n}\r\n\r\n/**\r\n * Get current icon configuration\r\n */\r\nexport function getIconConfig(): IconConfig {\r\n return iconConfig;\r\n}\r\n\r\n/**\r\n * Get full URL for an icon\r\n *\r\n * @param name - Icon name (without path or extension), e.g., 'motor-stop'\r\n * @returns Full URL to the icon\r\n */\r\nexport function getIconUrl(name: string): string {\r\n if (!name) return '';\r\n\r\n // If already a full URL or path, return as-is\r\n if (name.startsWith('http') || name.startsWith('/') || name.startsWith('.')) {\r\n return name;\r\n }\r\n\r\n const { basePath, extension } = iconConfig;\r\n const normalizedBase = basePath.endsWith('/') ? basePath : `${basePath}/`;\r\n return `${normalizedBase}${name}.${extension}`;\r\n}\r\n\r\n/**\r\n * Get CSS filter for icon state\r\n */\r\nexport function getIconFilter(active: boolean): string | undefined {\r\n return active ? iconConfig.activeFilter : iconConfig.defaultFilter;\r\n}\r\n","/**\r\n * Global touch state guard - shared across all CRIS components\r\n *\r\n * This prevents phantom clicks when elements swap visibility during touch interactions.\r\n * When a button is touched and triggers a visibility change (e.g., back button hides,\r\n * power button appears), the newly visible element would receive the synthetic click\r\n * event from the browser. The global touch flag blocks this.\r\n */\r\n\r\nlet globalTouchActive = false;\r\nlet touchResetTimeout: number | null = null;\r\n\r\n/** Duration to keep touch guard active after touch ends (blocks synthetic click) */\r\nconst TOUCH_GUARD_DURATION_MS = 300;\r\n\r\n/**\r\n * Mark that a touch interaction has started.\r\n * Call this in touchStart handlers.\r\n */\r\nexport function touchStart(): void {\r\n globalTouchActive = true;\r\n // Clear any pending reset\r\n if (touchResetTimeout !== null) {\r\n window.clearTimeout(touchResetTimeout);\r\n touchResetTimeout = null;\r\n }\r\n}\r\n\r\n/**\r\n * Mark that a touch interaction has ended.\r\n * The guard remains active for a short duration to block synthetic click events.\r\n * Call this in touchEnd/touchCancel handlers.\r\n */\r\nexport function touchEnd(): void {\r\n globalTouchActive = true;\r\n // Reset after delay to block synthetic click\r\n if (touchResetTimeout !== null) {\r\n window.clearTimeout(touchResetTimeout);\r\n }\r\n touchResetTimeout = window.setTimeout(() => {\r\n globalTouchActive = false;\r\n touchResetTimeout = null;\r\n }, TOUCH_GUARD_DURATION_MS);\r\n}\r\n\r\n/**\r\n * Check if a touch interaction is currently active.\r\n * Use this in mouse handlers to skip if touch is active.\r\n */\r\nexport function isTouchActive(): boolean {\r\n return globalTouchActive;\r\n}\r\n","import { ReactNode } from 'react';\r\nimport { useDigital, useSerial, useCipDecode } from '@imperosoft/cris-webui-ch5-core';\r\n\r\nexport interface CrisTextProps {\r\n /** Serial join for indirect text */\r\n joinIndirectText?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n /** Static text (used if no joinIndirectText) */\r\n text?: string;\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n /** Text element CSS class */\r\n textClassName?: string;\r\n /** Text element inline style */\r\n textStyle?: React.CSSProperties;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n /** Children content */\r\n children?: ReactNode;\r\n}\r\n\r\nexport function CrisText({\r\n joinIndirectText,\r\n joinEnable,\r\n joinVisible,\r\n text,\r\n className = '',\r\n style,\r\n textClassName = '',\r\n textStyle,\r\n classDisabled = '',\r\n children,\r\n}: CrisTextProps) {\r\n // Get join values reactively\r\n const indirectText = useSerial(joinIndirectText ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine if text is enabled\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n\r\n // Determine if text is visible\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Get the raw text - indirect text or static text\r\n const rawText = joinIndirectText != null ? indirectText : (text ?? '');\r\n\r\n // Decode CIP patterns in the text\r\n const currentText = useCipDecode(rawText);\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const containerClasses = [\r\n 'cris-text',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n return (\r\n <div className={containerClasses} style={style}>\r\n {children}\r\n {currentText && (\r\n <span className={`cris-text-text ${textClassName}`} style={textStyle}>\r\n {currentText}\r\n </span>\r\n )}\r\n </div>\r\n );\r\n}\r\n","import { useState, useRef, useEffect, useCallback } from 'react';\r\nimport { useDigital, useAnalog, useJoinsStore } from '@imperosoft/cris-webui-ch5-core';\r\nimport { touchStart, touchEnd, isTouchActive } from '../utils/touchGuard';\r\n\r\nexport interface CrisSliderProps {\r\n /** Analog join for value (shared with digital if joinDigital not set) */\r\n join?: number;\r\n /** Digital join for press/release feedback */\r\n joinDigital?: number;\r\n /** Analog join for value (overrides join) */\r\n joinAnalog?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Minimum value (default 0) */\r\n minValue?: number;\r\n /** Maximum value (default 65535) */\r\n maxValue?: number;\r\n /** Horizontal orientation (default false = vertical) */\r\n horizontal?: boolean;\r\n /** Hide fill bar */\r\n fillHidden?: boolean;\r\n /** Track size as percentage of container (default 20) */\r\n trackSizePercent?: number;\r\n /** Thumb size as percentage of container (default 4) */\r\n thumbSizePercent?: number;\r\n /** Delay in ms after drag before updating from feedback (default 1000) */\r\n delayMsAfterDragUpdateFeedback?: number;\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n /** Bar/track CSS class */\r\n barClassName?: string;\r\n /** Bar/track inline style */\r\n barStyle?: React.CSSProperties;\r\n /** Fill CSS class */\r\n fillClassName?: string;\r\n /** Fill inline style */\r\n fillStyle?: React.CSSProperties;\r\n /** Thumb CSS class */\r\n thumbClassName?: string;\r\n /** Thumb inline style */\r\n thumbStyle?: React.CSSProperties;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n}\r\n\r\nexport function CrisSlider({\r\n join,\r\n joinDigital,\r\n joinAnalog,\r\n joinEnable,\r\n joinVisible,\r\n minValue = 0,\r\n maxValue = 65535,\r\n horizontal = false,\r\n fillHidden = false,\r\n trackSizePercent = 20,\r\n thumbSizePercent = 4,\r\n delayMsAfterDragUpdateFeedback = 1000,\r\n className = '',\r\n style,\r\n barClassName = '',\r\n barStyle,\r\n fillClassName = '',\r\n fillStyle,\r\n thumbClassName = '',\r\n thumbStyle,\r\n classDisabled = '',\r\n}: CrisSliderProps) {\r\n // Effective joins\r\n const effectiveAnalogJoin = joinAnalog ?? join;\r\n const effectiveDigitalJoin = joinDigital ?? join;\r\n\r\n // Get join values reactively\r\n const analogValue = useAnalog(effectiveAnalogJoin ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Action methods\r\n const dSet = useJoinsStore((state) => state.dSet);\r\n const aSet = useJoinsStore((state) => state.aSet);\r\n\r\n // Local state\r\n const [ratioCurrent, setRatioCurrent] = useState(0);\r\n const [isDragging, setIsDragging] = useState(false);\r\n const isDraggingOrJustAfterRef = useRef(false);\r\n const ratioBeforeDragRef = useRef(0);\r\n const afterDragTimeoutRef = useRef<number | null>(null);\r\n const touchingRef = useRef(false);\r\n\r\n // Determine if enabled and visible\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Release if slider becomes invisible while dragging\r\n useEffect(() => {\r\n if (!isVisible && isDraggingOrJustAfterRef.current) {\r\n setIsDragging(false);\r\n isDraggingOrJustAfterRef.current = false;\r\n touchingRef.current = false;\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, false);\r\n }\r\n }\r\n }, [isVisible, effectiveDigitalJoin, dSet]);\r\n\r\n // Release on unmount if slider was being dragged (handles page transitions)\r\n useEffect(() => {\r\n return () => {\r\n if (isDraggingOrJustAfterRef.current && effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, false);\r\n }\r\n };\r\n }, [effectiveDigitalJoin, dSet]);\r\n\r\n // Convert analog value to ratio\r\n const analogToRatio = useCallback(\r\n (value: number) => {\r\n const range = maxValue - minValue;\r\n if (range <= 0) return 0;\r\n return Math.max(0, Math.min(1, (value - minValue) / range));\r\n },\r\n [minValue, maxValue]\r\n );\r\n\r\n // Convert ratio to analog value\r\n const ratioToAnalog = useCallback(\r\n (ratio: number) => {\r\n const range = maxValue - minValue;\r\n return Math.round(minValue + ratio * range);\r\n },\r\n [minValue, maxValue]\r\n );\r\n\r\n // Update ratio from controller feedback\r\n const updateFromFeedback = useCallback(() => {\r\n if (!isDraggingOrJustAfterRef.current && effectiveAnalogJoin != null) {\r\n setRatioCurrent(analogToRatio(analogValue));\r\n }\r\n }, [analogValue, analogToRatio, effectiveAnalogJoin]);\r\n\r\n // Update from feedback when analog value changes\r\n useEffect(() => {\r\n updateFromFeedback();\r\n }, [updateFromFeedback]);\r\n\r\n // Handle drag start\r\n const handleDragStart = useCallback(() => {\r\n if (!isEnabled) return;\r\n\r\n setIsDragging(true);\r\n\r\n // Send digital press\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, true);\r\n }\r\n\r\n // Save ratio before drag\r\n if (!isDraggingOrJustAfterRef.current) {\r\n ratioBeforeDragRef.current = ratioCurrent;\r\n }\r\n isDraggingOrJustAfterRef.current = true;\r\n\r\n // Clear any pending timeout\r\n if (afterDragTimeoutRef.current !== null) {\r\n window.clearTimeout(afterDragTimeoutRef.current);\r\n afterDragTimeoutRef.current = null;\r\n }\r\n }, [isEnabled, effectiveDigitalJoin, dSet, ratioCurrent]);\r\n\r\n // Handle drag end\r\n const handleDragEnd = useCallback(() => {\r\n if (!isDragging) return;\r\n\r\n setIsDragging(false);\r\n\r\n // Send digital release\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, false);\r\n }\r\n\r\n // Schedule feedback update after delay\r\n if (delayMsAfterDragUpdateFeedback > 0) {\r\n afterDragTimeoutRef.current = window.setTimeout(() => {\r\n isDraggingOrJustAfterRef.current = false;\r\n updateFromFeedback();\r\n afterDragTimeoutRef.current = null;\r\n }, delayMsAfterDragUpdateFeedback);\r\n } else {\r\n isDraggingOrJustAfterRef.current = false;\r\n updateFromFeedback();\r\n }\r\n }, [isDragging, effectiveDigitalJoin, dSet, delayMsAfterDragUpdateFeedback, updateFromFeedback]);\r\n\r\n // Handle move/drag\r\n const handleMove = useCallback(\r\n (clientX: number, clientY: number, bounds: DOMRect) => {\r\n if (!isDragging) return;\r\n\r\n let newRatio: number;\r\n if (horizontal) {\r\n newRatio = (clientX - bounds.left) / bounds.width;\r\n } else {\r\n newRatio = 1 - (clientY - bounds.top) / bounds.height;\r\n }\r\n newRatio = Math.max(0, Math.min(1, newRatio));\r\n\r\n setRatioCurrent(newRatio);\r\n\r\n // Send analog value\r\n if (effectiveAnalogJoin != null) {\r\n aSet(effectiveAnalogJoin, ratioToAnalog(newRatio));\r\n }\r\n },\r\n [isDragging, horizontal, effectiveAnalogJoin, aSet, ratioToAnalog]\r\n );\r\n\r\n // Mouse handlers - skip if any touch event is active (global check)\r\n const handleMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (isTouchActive() || touchingRef.current) return;\r\n if (event.button !== 0) return;\r\n\r\n handleDragStart();\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n };\r\n\r\n const handleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (!isDragging) return;\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n };\r\n\r\n const handleMouseUp = () => {\r\n handleDragEnd();\r\n };\r\n\r\n const handleMouseLeave = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (isDragging) {\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n }\r\n handleDragEnd();\r\n };\r\n\r\n // Touch handlers - use global touch guard\r\n const handleTouchStart = (event: React.TouchEvent<HTMLDivElement>) => {\r\n touchStart(); // Global guard\r\n touchingRef.current = true;\r\n handleDragStart();\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n };\r\n\r\n const handleTouchMove = (event: React.TouchEvent<HTMLDivElement>) => {\r\n if (!isDragging) return;\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n };\r\n\r\n const handleTouchEnd = () => {\r\n touchEnd(); // Global guard with delayed reset\r\n handleDragEnd();\r\n };\r\n\r\n const handleTouchCancel = (event: React.TouchEvent<HTMLDivElement>) => {\r\n touchEnd(); // Global guard with delayed reset\r\n if (isDragging && event.touches.length > 0) {\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n }\r\n handleDragEnd();\r\n };\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const containerClasses = [\r\n 'cris-slider',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Calculate bar style\r\n const computedBarStyle: React.CSSProperties = {\r\n ...barStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const width = 100 - thumbSizePercent;\r\n const left = (100 - width) / 2;\r\n computedBarStyle.height = `${trackSizePercent}%`;\r\n computedBarStyle.top = `${(100 - trackSizePercent) / 2}%`;\r\n computedBarStyle.width = `${width}%`;\r\n computedBarStyle.left = `${left}%`;\r\n } else {\r\n const height = 100 - thumbSizePercent;\r\n const top = (100 - height) / 2;\r\n computedBarStyle.width = `${trackSizePercent}%`;\r\n computedBarStyle.left = `${(100 - trackSizePercent) / 2}%`;\r\n computedBarStyle.height = `${height}%`;\r\n computedBarStyle.top = `${top}%`;\r\n }\r\n\r\n // Calculate fill style\r\n const computedFillStyle: React.CSSProperties = {\r\n ...fillStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const range = 100 - thumbSizePercent;\r\n const left = thumbSizePercent / 2;\r\n const width = range * ratioCurrent;\r\n computedFillStyle.height = `${trackSizePercent}%`;\r\n computedFillStyle.top = `${(100 - trackSizePercent) / 2}%`;\r\n computedFillStyle.width = `${width}%`;\r\n computedFillStyle.left = `${left}%`;\r\n } else {\r\n const range = 100 - thumbSizePercent;\r\n const top = 100 - (range * ratioCurrent + thumbSizePercent / 2);\r\n const height = range + thumbSizePercent / 2 - top;\r\n computedFillStyle.width = `${trackSizePercent}%`;\r\n computedFillStyle.left = `${(100 - trackSizePercent) / 2}%`;\r\n computedFillStyle.height = `${height}%`;\r\n computedFillStyle.top = `${top}%`;\r\n }\r\n\r\n // Calculate thumb style\r\n const computedThumbStyle: React.CSSProperties = {\r\n ...thumbStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const maxLeft = 100 - thumbSizePercent;\r\n const left = maxLeft * ratioCurrent;\r\n computedThumbStyle.width = `${thumbSizePercent}%`;\r\n computedThumbStyle.height = '100%';\r\n computedThumbStyle.left = `${left}%`;\r\n } else {\r\n const maxTop = 100 - thumbSizePercent;\r\n const top = maxTop - maxTop * ratioCurrent;\r\n computedThumbStyle.width = '100%';\r\n computedThumbStyle.height = `${thumbSizePercent}%`;\r\n computedThumbStyle.top = `${top}%`;\r\n }\r\n\r\n return (\r\n <div\r\n className={containerClasses}\r\n style={{\r\n ...style,\r\n position: 'relative',\r\n cursor: isEnabled ? 'pointer' : 'default',\r\n touchAction: 'none',\r\n userSelect: 'none',\r\n }}\r\n onMouseDown={handleMouseDown}\r\n onMouseMove={handleMouseMove}\r\n onMouseUp={handleMouseUp}\r\n onMouseLeave={handleMouseLeave}\r\n onTouchStart={handleTouchStart}\r\n onTouchMove={handleTouchMove}\r\n onTouchEnd={handleTouchEnd}\r\n onTouchCancel={handleTouchCancel}\r\n >\r\n {/* Bar/Track */}\r\n <div className={`cris-slider-bar ${barClassName}`} style={computedBarStyle} />\r\n\r\n {/* Fill */}\r\n {!fillHidden && (\r\n <div className={`cris-slider-fill ${fillClassName}`} style={computedFillStyle} />\r\n )}\r\n\r\n {/* Thumb */}\r\n <div className={`cris-slider-thumb ${thumbClassName}`} style={computedThumbStyle} />\r\n </div>\r\n );\r\n}\r\n","import { useAnalog, useDigital } from '@imperosoft/cris-webui-ch5-core';\r\n\r\nexport interface CrisGaugeProps {\r\n /** Static value (used if no join) */\r\n value?: number;\r\n /** Analog join for value */\r\n join?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Minimum value (default 0) */\r\n minValue?: number;\r\n /** Maximum value (default 65535) */\r\n maxValue?: number;\r\n /** Number of segments (default 20) */\r\n segments?: number;\r\n /** Invert direction (default false) */\r\n inverted?: boolean;\r\n /** Orientation (default vertical) */\r\n orientation?: 'vertical' | 'horizontal';\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n\r\n /** Inactive segment CSS class */\r\n inactiveSegmentClassName?: string;\r\n /** Inactive segment inline style */\r\n inactiveSegmentStyle?: React.CSSProperties;\r\n\r\n /** Active segment CSS class (all levels) */\r\n segmentClassName?: string;\r\n /** Active segment inline style (all levels) */\r\n segmentStyle?: React.CSSProperties;\r\n\r\n /** Low level segment CSS class (0-60%) */\r\n lowSegmentClassName?: string;\r\n /** Low level segment inline style */\r\n lowSegmentStyle?: React.CSSProperties;\r\n\r\n /** Medium level segment CSS class (60-80%) */\r\n mediumSegmentClassName?: string;\r\n /** Medium level segment inline style */\r\n mediumSegmentStyle?: React.CSSProperties;\r\n\r\n /** High level segment CSS class (80-100%) */\r\n highSegmentClassName?: string;\r\n /** High level segment inline style */\r\n highSegmentStyle?: React.CSSProperties;\r\n\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n}\r\n\r\nexport function CrisGauge({\r\n value,\r\n join,\r\n joinEnable,\r\n joinVisible,\r\n minValue = 0,\r\n maxValue = 65535,\r\n segments = 20,\r\n inverted = false,\r\n orientation = 'vertical',\r\n className = '',\r\n style,\r\n inactiveSegmentClassName = '',\r\n inactiveSegmentStyle,\r\n segmentClassName = '',\r\n segmentStyle,\r\n lowSegmentClassName = '',\r\n lowSegmentStyle,\r\n mediumSegmentClassName = '',\r\n mediumSegmentStyle,\r\n highSegmentClassName = '',\r\n highSegmentStyle,\r\n classDisabled = '',\r\n}: CrisGaugeProps) {\r\n // Get join values reactively\r\n const joinValue = useAnalog(join ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine if enabled and visible\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Get current value\r\n const currentValue = join != null ? joinValue : (value ?? 0);\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Determine segment type (low, medium, high)\r\n const getSegmentType = (segment: number): 'low' | 'medium' | 'high' => {\r\n const lowMax = segments * 0.6;\r\n const mediumMax = segments * 0.8;\r\n\r\n const seg =\r\n (inverted && orientation === 'vertical') || (!inverted && orientation === 'horizontal')\r\n ? segment + 1\r\n : segments - segment;\r\n\r\n if (seg <= lowMax) return 'low';\r\n if (seg <= mediumMax) return 'medium';\r\n return 'high';\r\n };\r\n\r\n // Check if segment is active\r\n const isSegmentActive = (segment: number): boolean => {\r\n if (currentValue < minValue) return false;\r\n if (currentValue > maxValue) return true;\r\n\r\n const ratio = currentValue / (maxValue - minValue);\r\n let segMax = Math.round(segments * ratio);\r\n if (ratio !== 0 && segMax === 0) segMax = 1;\r\n\r\n const seg =\r\n (inverted && orientation === 'vertical') || (!inverted && orientation === 'horizontal')\r\n ? segment + 1\r\n : segments - segment;\r\n\r\n return seg <= segMax;\r\n };\r\n\r\n // Get segment class\r\n const getSegmentClass = (segment: number): string => {\r\n const type = getSegmentType(segment);\r\n const active = isSegmentActive(segment);\r\n\r\n const classes = ['cris-gauge-segment', type, orientation];\r\n\r\n if (active) {\r\n classes.push('active');\r\n // Add type-specific or general active class\r\n if (type === 'low' && (lowSegmentClassName || segmentClassName)) {\r\n classes.push(lowSegmentClassName || segmentClassName);\r\n } else if (type === 'medium' && (mediumSegmentClassName || segmentClassName)) {\r\n classes.push(mediumSegmentClassName || segmentClassName);\r\n } else if (type === 'high' && (highSegmentClassName || segmentClassName)) {\r\n classes.push(highSegmentClassName || segmentClassName);\r\n }\r\n } else {\r\n if (inactiveSegmentClassName) {\r\n classes.push(inactiveSegmentClassName);\r\n }\r\n }\r\n\r\n return classes.filter(Boolean).join(' ');\r\n };\r\n\r\n // Get segment style\r\n const getSegmentStyle = (segment: number): React.CSSProperties | undefined => {\r\n const type = getSegmentType(segment);\r\n const active = isSegmentActive(segment);\r\n\r\n if (active) {\r\n if (type === 'low' && (lowSegmentStyle || segmentStyle)) {\r\n return lowSegmentStyle || segmentStyle;\r\n } else if (type === 'medium' && (mediumSegmentStyle || segmentStyle)) {\r\n return mediumSegmentStyle || segmentStyle;\r\n } else if (type === 'high' && (highSegmentStyle || segmentStyle)) {\r\n return highSegmentStyle || segmentStyle;\r\n }\r\n } else {\r\n return inactiveSegmentStyle;\r\n }\r\n\r\n return undefined;\r\n };\r\n\r\n // Build container classes\r\n const containerClasses = [\r\n 'cris-gauge',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Container flex style\r\n const containerStyle: React.CSSProperties = {\r\n ...style,\r\n display: 'flex',\r\n flexDirection: orientation === 'horizontal' ? 'row' : 'column',\r\n justifyContent: 'center',\r\n alignItems: 'center',\r\n };\r\n\r\n return (\r\n <div className={containerClasses} style={containerStyle}>\r\n {Array.from({ length: segments }, (_, segment) => (\r\n <div\r\n key={segment}\r\n className={getSegmentClass(segment)}\r\n style={getSegmentStyle(segment)}\r\n />\r\n ))}\r\n </div>\r\n );\r\n}\r\n","import { useAnalog, useDigital } from '@imperosoft/cris-webui-ch5-core';\r\n\r\nexport type SpinnerSpeed = 'slow' | 'medium' | 'fast';\r\n\r\nexport interface CrisSpinnerProps {\r\n /** Analog join for position (0-65535 maps to 0-360 degrees) */\r\n joinPosition?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Static position value (0-65535, used if no joinPosition) */\r\n position?: number;\r\n\r\n /** Endless rotation mode (default: false) */\r\n endless?: boolean;\r\n /** Speed for endless rotation (default: 'medium') */\r\n endlessSpeed?: SpinnerSpeed;\r\n\r\n /** Spinner color (default: '#bf2b23') */\r\n color?: string;\r\n /** Background/track color (default: 'rgba(0, 0, 0, 0.1)') */\r\n trackColor?: string;\r\n /** Spinner line width in pixels (default: 3) */\r\n lineWidth?: number;\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n}\r\n\r\nconst SPEED_DURATIONS: Record<SpinnerSpeed, string> = {\r\n slow: '1.5s',\r\n medium: '0.8s',\r\n fast: '0.4s',\r\n};\r\n\r\nexport function CrisSpinner({\r\n joinPosition,\r\n joinVisible,\r\n position,\r\n endless = false,\r\n endlessSpeed = 'medium',\r\n color = '#bf2b23',\r\n trackColor = 'rgba(0, 0, 0, 0.1)',\r\n lineWidth = 3,\r\n className = '',\r\n style,\r\n}: CrisSpinnerProps) {\r\n // Get join values reactively\r\n const joinPositionValue = useAnalog(joinPosition ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine visibility\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Get current position (0-65535 -> 0-360 degrees)\r\n const currentPosition = joinPosition != null ? joinPositionValue : (position ?? 0);\r\n const rotationDegrees = (currentPosition / 65535) * 360;\r\n\r\n // Build container classes\r\n const containerClasses = ['cris-spinner', className].filter(Boolean).join(' ');\r\n\r\n // Spinner styles\r\n const spinnerStyle: React.CSSProperties = {\r\n width: '100%',\r\n height: '100%',\r\n borderRadius: '50%',\r\n border: `${lineWidth}px solid ${trackColor}`,\r\n borderTopColor: color,\r\n boxSizing: 'border-box',\r\n };\r\n\r\n if (endless) {\r\n // Endless rotation animation\r\n spinnerStyle.animation = `cris-spinner-rotate ${SPEED_DURATIONS[endlessSpeed]} linear infinite`;\r\n } else {\r\n // Position-based rotation\r\n spinnerStyle.transform = `rotate(${rotationDegrees}deg)`;\r\n spinnerStyle.transition = 'transform 0.1s linear';\r\n }\r\n\r\n return (\r\n <div className={containerClasses} style={style}>\r\n <div style={spinnerStyle} />\r\n <style>{`\r\n @keyframes cris-spinner-rotate {\r\n to { transform: rotate(360deg); }\r\n }\r\n `}</style>\r\n </div>\r\n );\r\n}\r\n","import { useEffect, useState, useCallback } from 'react';\r\nimport { isNativeMode, useConnectionStore, ConnectionErrorReason } from '@imperosoft/cris-webui-ch5-core';\r\n\r\n// Environment detection utilities\r\nconst isFileProtocol = typeof window !== 'undefined' && window.location.protocol === 'file:';\r\nconst isDevModeDefault = typeof window !== 'undefined' &&\r\n (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1');\r\nconst isVC4Default = typeof window !== 'undefined' && window.location.pathname.includes('/VirtualControl/');\r\nconst isDeployedOnProcessorDefault = !isFileProtocol && !isDevModeDefault && !isVC4Default;\r\n\r\n// Session storage key to track login redirect\r\nconst LOGIN_REDIRECT_KEY = 'cris_login_redirect';\r\n\r\nexport interface CrisOfflinePageProps {\r\n /**\r\n * Processor hostname for dev mode (used to build login/cert URLs).\r\n * In production, uses window.location.hostname.\r\n */\r\n processorHost?: string;\r\n\r\n /**\r\n * Whether an auth token is configured (for dev mode diagnostics).\r\n */\r\n hasAuthToken?: boolean;\r\n\r\n /**\r\n * Override dev mode detection.\r\n */\r\n isDevMode?: boolean;\r\n\r\n /**\r\n * Override VC4 detection.\r\n */\r\n isVC4?: boolean;\r\n\r\n /**\r\n * Override deployed on processor detection.\r\n */\r\n isDeployedOnProcessor?: boolean;\r\n\r\n /**\r\n * WebSocket port for certificate acceptance (default: 49200).\r\n */\r\n wsPort?: number;\r\n\r\n /**\r\n * Show debug information panel (default: true).\r\n */\r\n showDebugInfo?: boolean;\r\n\r\n /**\r\n * Auto-redirect delay to login page in ms when deployed on processor (default: 1500).\r\n * Set to 0 to disable auto-redirect.\r\n */\r\n loginRedirectDelay?: number;\r\n\r\n /**\r\n * Custom login URL path (default: '/userlogin.html').\r\n */\r\n loginPath?: string;\r\n\r\n /**\r\n * Auto-retry countdown seconds when returning from cert page (default: 3).\r\n */\r\n retryCountdown?: number;\r\n\r\n /**\r\n * Custom className for the root container.\r\n */\r\n className?: string;\r\n}\r\n\r\nexport function CrisOfflinePage({\r\n processorHost: processorHostProp,\r\n hasAuthToken = false,\r\n isDevMode = isDevModeDefault,\r\n isVC4 = isVC4Default,\r\n isDeployedOnProcessor = isDeployedOnProcessorDefault,\r\n wsPort = 49200,\r\n showDebugInfo = true,\r\n loginRedirectDelay = 1500,\r\n loginPath = '/userlogin.html',\r\n retryCountdown: retryCountdownInit = 3,\r\n className = '',\r\n}: CrisOfflinePageProps) {\r\n const connectionStatus = useConnectionStore((state) => state.status);\r\n const errorReason = useConnectionStore((state) => state.errorReason);\r\n const [hasSeenError, setHasSeenError] = useState(false);\r\n const [lastErrorReason, setLastErrorReason] = useState<ConnectionErrorReason>(null);\r\n const [certPageOpened, setCertPageOpened] = useState(false);\r\n const [retryCountdown, setRetryCountdown] = useState<number | null>(null);\r\n\r\n // Check if we just came back from login page (prevents redirect loop)\r\n // Using a ref-like pattern with useState to only compute once on mount\r\n const [returnedFromLogin] = useState(() => {\r\n if (typeof window === 'undefined') return false;\r\n try {\r\n const wasRedirected = sessionStorage.getItem(LOGIN_REDIRECT_KEY);\r\n if (wasRedirected) {\r\n sessionStorage.removeItem(LOGIN_REDIRECT_KEY);\r\n return true;\r\n }\r\n } catch {\r\n // sessionStorage not available\r\n }\r\n return false;\r\n });\r\n\r\n // Determine processor host\r\n const processorHost = processorHostProp ??\r\n (typeof window !== 'undefined' ? window.location.hostname : 'localhost');\r\n\r\n // Make error state \"sticky\" - once we've seen an error, keep showing error UI\r\n // until the user manually retries or connection succeeds\r\n const isError = connectionStatus === 'error' || (hasSeenError && connectionStatus !== 'connected');\r\n\r\n // Track when we first see an error and capture the reason\r\n useEffect(() => {\r\n if (connectionStatus === 'error') {\r\n setHasSeenError(true);\r\n setLastErrorReason(errorReason);\r\n } else if (connectionStatus === 'connected') {\r\n setHasSeenError(false);\r\n setLastErrorReason(null);\r\n }\r\n }, [connectionStatus, errorReason]);\r\n\r\n // Determine if this is a timeout error (processor unreachable)\r\n const isTimeoutError = lastErrorReason === 'timeout';\r\n\r\n // Build URLs\r\n const loginUrl = `https://${processorHost}${loginPath}`;\r\n const certUrl = `https://${processorHost}:${wsPort}/`;\r\n\r\n // Open certificate page in new tab\r\n const openCertPage = useCallback(() => {\r\n window.open(certUrl, '_blank');\r\n setCertPageOpened(true);\r\n }, [certUrl]);\r\n\r\n // Retry connection by reloading the page\r\n const retryConnection = useCallback(() => {\r\n window.location.reload();\r\n }, []);\r\n\r\n // Auto-retry when user returns after opening cert page\r\n useEffect(() => {\r\n if (!certPageOpened || !isError) return;\r\n\r\n const handleVisibilityChange = () => {\r\n if (document.visibilityState === 'visible') {\r\n // Start countdown when user returns\r\n setRetryCountdown(retryCountdownInit);\r\n }\r\n };\r\n\r\n const handleFocus = () => {\r\n setRetryCountdown(retryCountdownInit);\r\n };\r\n\r\n document.addEventListener('visibilitychange', handleVisibilityChange);\r\n window.addEventListener('focus', handleFocus);\r\n\r\n return () => {\r\n document.removeEventListener('visibilitychange', handleVisibilityChange);\r\n window.removeEventListener('focus', handleFocus);\r\n };\r\n }, [certPageOpened, isError, retryCountdownInit]);\r\n\r\n // Countdown timer for auto-retry\r\n useEffect(() => {\r\n if (retryCountdown === null) return;\r\n if (retryCountdown <= 0) {\r\n retryConnection();\r\n return;\r\n }\r\n\r\n const timer = setTimeout(() => {\r\n setRetryCountdown(retryCountdown - 1);\r\n }, 1000);\r\n\r\n return () => clearTimeout(timer);\r\n }, [retryCountdown, retryConnection]);\r\n\r\n // Auto-redirect to login page ONLY when deployed on processor (not dev mode, not CrestronOne)\r\n // Don't redirect if we just came back from login (prevents redirect loop)\r\n useEffect(() => {\r\n if (isError && isDeployedOnProcessor && loginRedirectDelay > 0 && !returnedFromLogin) {\r\n const timer = setTimeout(() => {\r\n // Set flag before redirecting so we know we came from here\r\n try {\r\n sessionStorage.setItem(LOGIN_REDIRECT_KEY, 'true');\r\n } catch {\r\n // sessionStorage not available\r\n }\r\n window.location.href = loginUrl;\r\n }, loginRedirectDelay);\r\n return () => clearTimeout(timer);\r\n }\r\n }, [isError, isDeployedOnProcessor, loginUrl, loginRedirectDelay, returnedFromLogin]);\r\n\r\n // Gather environment info for debugging\r\n const envInfo = {\r\n userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'N/A',\r\n hasCrComLib: typeof window !== 'undefined' && typeof (window as any).CrComLib !== 'undefined',\r\n hasCrestronPanel: typeof window !== 'undefined' && 'CrestronPanel' in window,\r\n isNativeMode: isNativeMode(),\r\n hostname: typeof window !== 'undefined' ? window.location.hostname : 'N/A',\r\n pathname: typeof window !== 'undefined' ? window.location.pathname : 'N/A',\r\n search: typeof window !== 'undefined' ? window.location.search : '',\r\n protocol: typeof window !== 'undefined' ? window.location.protocol : 'N/A',\r\n isDevMode,\r\n isVC4,\r\n isDeployedOnProcessor,\r\n returnedFromLogin,\r\n processorHost: isDevMode ? processorHost : undefined,\r\n hasAuthToken: isDevMode ? hasAuthToken : undefined,\r\n errorReason: lastErrorReason,\r\n };\r\n\r\n return (\r\n <div className={`cris-offline-page absolute inset-0 bg-gray-900 flex flex-col items-center justify-center overflow-auto py-8 ${className}`}>\r\n <div className=\"text-center max-w-4xl px-4\">\r\n {/* Status indicator */}\r\n <div className=\"mb-8\">\r\n <div className={`w-16 h-16 mx-auto rounded-full flex items-center justify-center ${isError ? 'bg-red-500/20' : 'bg-yellow-500/20'}`}>\r\n <div className={`w-8 h-8 rounded-full ${isError ? 'bg-red-500' : 'bg-yellow-500 animate-pulse'}`} />\r\n </div>\r\n </div>\r\n\r\n {/* Message */}\r\n <h1 className=\"text-2xl font-bold text-white mb-2\">\r\n {isError\r\n ? (isTimeoutError ? 'Processor Unreachable' : 'Connection Error')\r\n : 'Connecting...'}\r\n </h1>\r\n <p className=\"text-gray-400\">\r\n {isError\r\n ? (isDeployedOnProcessor\r\n ? (returnedFromLogin\r\n ? 'Connection failed after login. Please try again.'\r\n : 'Redirecting to login page...')\r\n : isTimeoutError\r\n ? 'Could not connect to the processor. Check the IP address and network connection.'\r\n : 'WebSocket connection failed. SSL certificate may need to be accepted.')\r\n : 'Establishing connection to control system'}\r\n </p>\r\n\r\n {/* Status badge */}\r\n <div className=\"mt-8\">\r\n <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'}`}>\r\n {isError\r\n ? (isTimeoutError\r\n ? 'Connection Timeout'\r\n : isDevMode\r\n ? 'Dev Mode - Auth Error'\r\n : isVC4\r\n ? 'VC4 - Not Authorized'\r\n : 'Auth Error')\r\n : 'Connecting'}\r\n </span>\r\n </div>\r\n\r\n {/* Timeout error UI - processor unreachable */}\r\n {isError && !isDeployedOnProcessor && isTimeoutError && (\r\n <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' }}>\r\n <p className=\"text-orange-400 font-medium\" style={{ fontSize: '1.2em', marginBottom: '1em' }}>\r\n The processor at <span className=\"text-white\">{processorHost}</span> is not responding.\r\n </p>\r\n <ul className=\"text-gray-400 text-left mb-[1em]\" style={{ fontSize: '1em', listStyleType: 'disc', paddingLeft: '1.5em' }}>\r\n <li>Verify the IP address is correct</li>\r\n <li>Check that the processor is powered on</li>\r\n <li>Ensure you are on the same network</li>\r\n </ul>\r\n <div className=\"flex gap-[1em] justify-center\" style={{ overflow: 'visible' }}>\r\n <button\r\n onClick={retryConnection}\r\n className=\"bg-orange-600 hover:bg-orange-500 text-white font-medium rounded-xl transition-colors\"\r\n style={{ padding: '1em 2em', fontSize: '1.2em', whiteSpace: 'nowrap', overflow: 'visible' }}\r\n >\r\n Retry Connection\r\n </button>\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Certificate acceptance UI - for websocket/auth errors in dev mode and VC4 */}\r\n {isError && !isDeployedOnProcessor && !isTimeoutError && (\r\n <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' }}>\r\n {retryCountdown !== null ? (\r\n <div className=\"text-center\" style={{ overflow: 'visible' }}>\r\n <p className=\"text-blue-400 font-medium\" style={{ fontSize: '1.4em', marginBottom: '1em' }}>Retrying connection in {retryCountdown}...</p>\r\n <button\r\n onClick={() => setRetryCountdown(null)}\r\n className=\"bg-gray-600 hover:bg-gray-500 text-white font-medium rounded-xl transition-colors\"\r\n style={{ padding: '1em 2em', fontSize: '1.2em', whiteSpace: 'nowrap', overflow: 'visible' }}\r\n >\r\n Cancel\r\n </button>\r\n </div>\r\n ) : (\r\n <>\r\n <p className=\"text-blue-400 font-medium\" style={{ fontSize: '1.2em', marginBottom: '1em' }}>\r\n {certPageOpened\r\n ? 'Accept the certificate in the new tab, then return here.'\r\n : 'Click below to accept the WebSocket SSL certificate:'}\r\n </p>\r\n <div className=\"flex gap-[1em] justify-center flex-wrap\" style={{ overflow: 'visible' }}>\r\n <button\r\n onClick={openCertPage}\r\n className=\"bg-blue-600 hover:bg-blue-500 text-white font-medium rounded-xl transition-colors\"\r\n style={{ padding: '1em 2em', fontSize: '1.2em', whiteSpace: 'nowrap', overflow: 'visible' }}\r\n >\r\n {certPageOpened ? 'Open Certificate Page Again' : 'Accept Certificate'}\r\n </button>\r\n <button\r\n onClick={retryConnection}\r\n className=\"bg-green-600 hover:bg-green-500 text-white font-medium rounded-xl transition-colors\"\r\n style={{ padding: '1em 2em', fontSize: '1.2em', whiteSpace: 'nowrap', overflow: 'visible' }}\r\n >\r\n Retry Connection\r\n </button>\r\n </div>\r\n {certPageOpened && (\r\n <p className=\"text-gray-400\" style={{ fontSize: '1em', marginTop: '1em' }}>\r\n Connection will auto-retry when you return to this page.\r\n </p>\r\n )}\r\n </>\r\n )}\r\n </div>\r\n )}\r\n\r\n {/* Deployed on processor - returned from login but still failed */}\r\n {isError && isDeployedOnProcessor && returnedFromLogin && (\r\n <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' }}>\r\n <p className=\"text-red-400 font-medium\" style={{ fontSize: '1.2em', marginBottom: '1em' }}>\r\n Connection failed after authentication.\r\n </p>\r\n <p className=\"text-gray-400\" style={{ fontSize: '1em', marginBottom: '1em' }}>\r\n This may be due to an expired session or WebSocket connection issue.\r\n </p>\r\n <div className=\"flex gap-[1em] justify-center flex-wrap\" style={{ overflow: 'visible' }}>\r\n <button\r\n onClick={retryConnection}\r\n className=\"bg-green-600 hover:bg-green-500 text-white font-medium rounded-xl transition-colors\"\r\n style={{ padding: '1em 2em', fontSize: '1.2em', whiteSpace: 'nowrap', overflow: 'visible' }}\r\n >\r\n Retry Connection\r\n </button>\r\n <button\r\n onClick={() => {\r\n try {\r\n sessionStorage.setItem(LOGIN_REDIRECT_KEY, 'true');\r\n } catch {\r\n // sessionStorage not available\r\n }\r\n window.location.href = loginUrl;\r\n }}\r\n className=\"bg-blue-600 hover:bg-blue-500 text-white font-medium rounded-xl transition-colors\"\r\n style={{ padding: '1em 2em', fontSize: '1.2em', whiteSpace: 'nowrap', overflow: 'visible' }}\r\n >\r\n Go to Login\r\n </button>\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Dev mode auth token status */}\r\n {isDevMode && isError && (\r\n <div className=\"mt-6 p-4 bg-yellow-500/10 border border-yellow-500/30 rounded-lg text-left\">\r\n <p className=\"text-yellow-400 text-sm font-medium mb-2\">Dev Mode Configuration:</p>\r\n <p className=\"text-gray-300 text-sm\">\r\n Processor: <span className=\"text-blue-400\">{processorHost}</span>\r\n </p>\r\n <p className=\"text-gray-300 text-sm\">\r\n AuthToken: <span className={hasAuthToken ? 'text-green-400' : 'text-red-400'}>\r\n {hasAuthToken ? 'Present (may be expired)' : 'Missing'}\r\n </span>\r\n </p>\r\n {!hasAuthToken && (\r\n <p className=\"text-yellow-400 text-xs mt-2\">\r\n Add authToken to crestron.config.json or generate new: <code className=\"bg-black/30 px-1 rounded\">websockettoken generate</code>\r\n </p>\r\n )}\r\n </div>\r\n )}\r\n\r\n {/* Debug Info */}\r\n {showDebugInfo && (\r\n <div className=\"mt-12 text-left bg-gray-800 rounded-lg p-4 text-xs font-mono\">\r\n <h2 className=\"text-yellow-400 font-bold mb-3 text-sm\">Environment Debug Info</h2>\r\n <div className=\"space-y-2 text-gray-300\">\r\n <div>\r\n <span className=\"text-gray-500\">CrComLib:</span>{' '}\r\n <span className={envInfo.hasCrComLib ? 'text-green-400' : 'text-red-400'}>\r\n {envInfo.hasCrComLib ? 'Available' : 'Not Found'}\r\n </span>\r\n </div>\r\n <div>\r\n <span className=\"text-gray-500\">CrestronPanel:</span>{' '}\r\n <span className={envInfo.hasCrestronPanel ? 'text-green-400' : 'text-gray-400'}>\r\n {envInfo.hasCrestronPanel ? 'Yes' : 'No'}\r\n </span>\r\n </div>\r\n <div>\r\n <span className=\"text-gray-500\">Native Mode:</span>{' '}\r\n <span className={envInfo.isNativeMode ? 'text-green-400' : 'text-gray-400'}>\r\n {envInfo.isNativeMode ? 'Yes' : 'No (Browser)'}\r\n </span>\r\n </div>\r\n <div>\r\n <span className=\"text-gray-500\">Dev Mode:</span>{' '}\r\n <span className={envInfo.isDevMode ? 'text-yellow-400' : 'text-gray-400'}>\r\n {envInfo.isDevMode ? 'Yes' : 'No'}\r\n </span>\r\n </div>\r\n <div>\r\n <span className=\"text-gray-500\">VC4:</span>{' '}\r\n <span className={envInfo.isVC4 ? 'text-purple-400' : 'text-gray-400'}>\r\n {envInfo.isVC4 ? 'Yes' : 'No'}\r\n </span>\r\n </div>\r\n <div>\r\n <span className=\"text-gray-500\">Deployed on Processor:</span>{' '}\r\n <span className={envInfo.isDeployedOnProcessor ? 'text-cyan-400' : 'text-gray-400'}>\r\n {envInfo.isDeployedOnProcessor ? 'Yes' : 'No'}\r\n </span>\r\n </div>\r\n {envInfo.returnedFromLogin && (\r\n <div>\r\n <span className=\"text-gray-500\">Returned from Login:</span>{' '}\r\n <span className=\"text-yellow-400\">Yes</span>\r\n </div>\r\n )}\r\n <div>\r\n <span className=\"text-gray-500\">Protocol:</span>{' '}\r\n <span className=\"text-blue-400\">{envInfo.protocol}</span>\r\n </div>\r\n <div>\r\n <span className=\"text-gray-500\">Hostname:</span>{' '}\r\n <span className=\"text-blue-400\">{envInfo.hostname}</span>\r\n </div>\r\n {envInfo.isDevMode && envInfo.processorHost && (\r\n <div>\r\n <span className=\"text-gray-500\">Target Processor:</span>{' '}\r\n <span className=\"text-blue-400\">{envInfo.processorHost}</span>\r\n </div>\r\n )}\r\n <div>\r\n <span className=\"text-gray-500\">Path:</span>{' '}\r\n <span className=\"text-blue-400\">{envInfo.pathname}{envInfo.search}</span>\r\n </div>\r\n {envInfo.errorReason && (\r\n <div>\r\n <span className=\"text-gray-500\">Error Reason:</span>{' '}\r\n <span className={envInfo.errorReason === 'timeout' ? 'text-orange-400' : 'text-red-400'}>\r\n {envInfo.errorReason}\r\n </span>\r\n </div>\r\n )}\r\n </div>\r\n </div>\r\n )}\r\n </div>\r\n </div>\r\n );\r\n}\r\n"],"mappings":";AAAA,SAAS,UAAU,QAAQ,WAAW,mBAA8B;AACpE,SAAS,YAAY,qBAAqB;;;ACiB1C,IAAI,aAAyB;AAAA,EAC3B,UAAU;AAAA,EACV,WAAW;AAAA,EACX,eAAe;AAAA,EACf,cAAc;AAChB;AAoBO,SAAS,eAAe,QAAmC;AAChE,eAAa,EAAE,GAAG,YAAY,GAAG,OAAO;AAC1C;AAKO,SAAS,gBAA4B;AAC1C,SAAO;AACT;AAQO,SAAS,WAAW,MAAsB;AAC/C,MAAI,CAAC,KAAM,QAAO;AAGlB,MAAI,KAAK,WAAW,MAAM,KAAK,KAAK,WAAW,GAAG,KAAK,KAAK,WAAW,GAAG,GAAG;AAC3E,WAAO;AAAA,EACT;AAEA,QAAM,EAAE,UAAU,UAAU,IAAI;AAChC,QAAM,iBAAiB,SAAS,SAAS,GAAG,IAAI,WAAW,GAAG,QAAQ;AACtE,SAAO,GAAG,cAAc,GAAG,IAAI,IAAI,SAAS;AAC9C;AAKO,SAAS,cAAc,QAAqC;AACjE,SAAO,SAAS,WAAW,eAAe,WAAW;AACvD;;;ACrEA,IAAI,oBAAoB;AACxB,IAAI,oBAAmC;AAGvC,IAAM,0BAA0B;AAMzB,SAAS,aAAmB;AACjC,sBAAoB;AAEpB,MAAI,sBAAsB,MAAM;AAC9B,WAAO,aAAa,iBAAiB;AACrC,wBAAoB;AAAA,EACtB;AACF;AAOO,SAAS,WAAiB;AAC/B,sBAAoB;AAEpB,MAAI,sBAAsB,MAAM;AAC9B,WAAO,aAAa,iBAAiB;AAAA,EACvC;AACA,sBAAoB,OAAO,WAAW,MAAM;AAC1C,wBAAoB;AACpB,wBAAoB;AAAA,EACtB,GAAG,uBAAuB;AAC5B;AAMO,SAAS,gBAAyB;AACvC,SAAO;AACT;;;AF8Ta,cA+CT,YA/CS;AAtSN,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,eAAe;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EACA,sBAAsB;AAAA,EACtB,oBAAoB;AAAA,EACpB,oBAAoB;AAAA,EACpB;AAAA,EACA,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AACV,GAAoB;AAElB,QAAM,WAAW,OAAO,KAAK;AAC7B,WAAS,UAAU;AACnB,QAAM,UAAU,OAAO,IAAI;AAC3B,UAAQ,UAAU;AAClB,QAAM,MAAM,YAAY,CAAC,KAAa,SAAmB;AACvD,QAAI,SAAS,QAAS,SAAQ,IAAI,eAAe,QAAQ,OAAO,KAAK,GAAG,IAAI,QAAQ,EAAE;AAAA,EACxF,GAAG,CAAC,CAAC;AACL,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAC5C,QAAM,aAAa,OAAO,KAAK;AAC/B,QAAM,cAAc,OAAO,KAAK;AAChC,QAAM,sBAAsB,OAAO,KAAK;AAGxC,QAAM,eAAe,gBAAgB;AACrC,QAAM,WAAW,WAAW,gBAAgB,CAAC;AAC7C,QAAM,UAAU,WAAW,cAAc,CAAC;AAC1C,QAAM,UAAU,WAAW,eAAe,CAAC;AAG3C,QAAM,OAAO,cAAc,CAAC,UAAU,MAAM,IAAI;AAGhD,QAAM,YAAY,cAAc,OAAO,OAAO;AAG9C,QAAM,YAAY,eAAe,OAAO,OAAO;AAG/C,YAAU,MAAM;AACd,QAAI,qBAAqB,EAAE,WAAW,YAAY,WAAW,QAAQ,CAAC;AACtE,QAAI,CAAC,aAAa,WAAW,SAAS;AACpC,UAAI,kDAAkD;AACtD,iBAAW,UAAU;AACrB,iBAAW,KAAK;AAChB,kBAAY,UAAU;AACtB,0BAAoB,UAAU;AAC9B,UAAI,QAAQ,QAAQ,WAAW,MAAM;AACnC,YAAI,iCAAiC;AACrC,aAAK,MAAM,KAAK;AAAA,MAClB;AAAA,IACF;AAAA,EACF,GAAG,CAAC,WAAW,MAAM,SAAS,IAAI,CAAC;AAGnC,YAAU,MAAM;AACd,WAAO,MAAM;AACX,UAAI,WAAW,WAAW,QAAQ,QAAQ,WAAW,MAAM;AACzD,YAAI,sDAAsD;AAC1D,aAAK,MAAM,KAAK;AAAA,MAClB;AAAA,IACF;AAAA,EACF,GAAG,CAAC,MAAM,SAAS,MAAM,GAAG,CAAC;AAG7B,QAAM,qBAAqB,uBAAuB,gBAAgB,QAAQ;AAC1E,QAAM,qBAAqB,qBAAqB,WAAW;AAC3D,QAAM,WAAW,sBAAsB;AAGvC,MAAI,cAAc,QAAQ;AAC1B,MAAI,sBAAsB,eAAe,MAAM;AAC7C,kBAAc;AAAA,EAChB,WAAW,sBAAsB,gBAAgB,MAAM;AACrD,kBAAc;AAAA,EAChB;AAGA,QAAM,cAAc,MAAM;AACxB,QAAI,sBAAsB,EAAE,mBAAmB,YAAY,WAAW,SAAS,UAAU,CAAC;AAC1F,QAAI,mBAAmB;AACrB,UAAI,4BAA4B;AAChC;AAAA,IACF;AACA,QAAI,WAAW,SAAS;AACtB,UAAI,0BAA0B;AAC9B;AAAA,IACF;AAEA,eAAW,UAAU;AACrB,eAAW,IAAI;AAEf,QAAI,CAAC,WAAW;AACd,UAAI,2BAA2B;AAC/B;AAAA,IACF;AAGA,cAAU;AAGV,QAAI,QAAQ,QAAQ,WAAW,MAAM;AACnC,UAAI,8BAA8B;AAClC,WAAK,MAAM,IAAI;AAAA,IACjB;AAAA,EAEF;AAEA,QAAM,gBAAgB,MAAM;AAC1B,QAAI,wBAAwB,EAAE,mBAAmB,YAAY,WAAW,SAAS,UAAU,CAAC;AAC5F,QAAI,mBAAmB;AACrB,UAAI,4BAA4B;AAChC;AAAA,IACF;AACA,QAAI,CAAC,WAAW,SAAS;AACvB,UAAI,sBAAsB;AAC1B;AAAA,IACF;AAEA,eAAW,UAAU;AACrB,eAAW,KAAK;AAEhB,QAAI,CAAC,WAAW;AACd,UAAI,2BAA2B;AAC/B;AAAA,IACF;AAGA,gBAAY;AAGZ,QAAI,QAAQ,QAAQ,WAAW,MAAM;AACnC,UAAI,iCAAiC;AACrC,WAAK,MAAM,KAAK;AAAA,IAClB;AAAA,EAEF;AAIA,QAAM,mBAAmB,MAAM;AAC7B,QAAI,kBAAkB;AACtB,eAAW;AACX,gBAAY,UAAU;AACtB,wBAAoB,UAAU;AAC9B,gBAAY;AAAA,EACd;AAEA,QAAM,iBAAiB,MAAM;AAC3B,QAAI,kBAAkB,EAAE,qBAAqB,oBAAoB,QAAQ,CAAC;AAC1E,aAAS;AACT,gBAAY,UAAU;AAEtB,QAAI,oBAAoB,SAAS;AAC/B,0BAAoB,UAAU;AAC9B,oBAAc;AAAA,IAChB,OAAO;AACL,UAAI,iDAAiD;AAAA,IACvD;AAAA,EACF;AAEA,QAAM,oBAAoB,MAAM;AAC9B,QAAI,mBAAmB;AACvB,aAAS;AACT,gBAAY,UAAU;AACtB,wBAAoB,UAAU;AAC9B,kBAAc;AAAA,EAChB;AAGA,QAAM,kBAAkB,MAAM;AAC5B,QAAI,cAAc,KAAK,YAAY,QAAS;AAC5C,gBAAY;AAAA,EACd;AAEA,QAAM,gBAAgB,MAAM;AAC1B,QAAI,cAAc,KAAK,YAAY,QAAS;AAC5C,kBAAc;AAAA,EAChB;AAEA,QAAM,mBAAmB,MAAM;AAC7B,QAAI,cAAc,KAAK,YAAY,QAAS;AAC5C,kBAAc;AAAA,EAChB;AAGA,MAAI,CAAC,UAAW,QAAO;AAGvB,QAAM,UAAU;AAAA,IACd;AAAA,IACA;AAAA,IACA,sBAAsB;AAAA,IACtB,sBAAsB;AAAA,IACtB,CAAC,aAAa;AAAA,IACd,sBAAsB;AAAA,IACtB,sBAAsB;AAAA,IACtB,CAAC,aAAa;AAAA,IACd,qBAAqB;AAAA,EACvB,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAGX,QAAM,gBACJ,iBAAiB,QACb,aACA,iBAAiB,WACf,qBACA,iBAAiB,SACf,aACA;AAGV,QAAM,UAAU,QAAQ,QAAQ,YAAY,QAAQ,aAAa,QAAQ,kBAAkB,QAAQ,mBAAmB;AAGtH,QAAM,kBAAkB,YAAY,kBAAkB,OAAO,iBAAiB;AAC9E,QAAM,mBAAmB,YAAY,mBAAmB,OAAO,kBAAkB;AACjF,QAAM,mBAAmB,YAAY,mBAAmB,OAAO,EAAE,GAAG,WAAW,GAAG,gBAAgB,IAAI;AAGtG,QAAM,cAAsC;AAAA,IAC1C,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,EACN;AAGA,QAAM,mBAAmB,MAA0B;AACjD,QAAI,YAAY,KAAM,QAAO;AAC7B,QAAI,OAAO,aAAa,SAAU,QAAO,GAAG,QAAQ;AACpD,QAAI,YAAY,YAAa,QAAO,YAAY,QAAQ;AACxD,WAAO;AAAA,EACT;AAEA,QAAM,gBAAgB,iBAAiB;AAGvC,QAAM,cAAc;AAAA,IAClB;AAAA,IACA;AAAA,IACA,sBAAsB;AAAA,IACtB,sBAAsB;AAAA,EACxB,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAGX,QAAM,oBAAyC;AAAA,IAC7C,GAAG;AAAA,EACL;AAGA,MAAI,eAAe;AACjB,sBAAkB,QAAQ;AAC1B,sBAAkB,SAAS;AAAA,EAC7B;AAGA,MAAI,mBAAmB,CAAC,kBAAkB,QAAQ;AAChD,UAAM,SAAS,cAAc,QAAQ;AACrC,QAAI,QAAQ;AACV,wBAAkB,SAAS;AAAA,IAC7B;AAAA,EACF;AAGA,QAAM,oBAAoB,MAAM;AAC9B,QAAI,MAAM;AAER,aAAO,oBAAC,UAAK,WAAW,aAAa,OAAO,mBAAoB,gBAAK;AAAA,IACvE;AAEA,QAAI,iBAAiB;AAEnB,aACE;AAAA,QAAC;AAAA;AAAA,UACC,WAAW;AAAA,UACX,OAAO,EAAE,GAAG,mBAAmB,eAAe,OAAO;AAAA,UACrD,KAAK,WAAW,eAAe;AAAA,UAC/B,KAAI;AAAA,UACJ,WAAW;AAAA;AAAA,MACb;AAAA,IAEJ;AAEA,QAAI,kBAAkB;AAEpB,aAAO,oBAAC,SAAI,WAAW,aAAa,OAAO,EAAE,GAAG,mBAAmB,eAAe,OAAO,GAAG,KAAI,IAAG,WAAW,OAAO;AAAA,IACvH;AAEA,WAAO;AAAA,EACT;AAGA,QAAM,aAAa,iBAAiB,SAAS,iBAAiB;AAC9D,QAAM,UAAU,gBAAgB;AAChC,QAAM,uBAAuB,UAAW,aAAa,QAAQ,QAAS;AACtE,QAAM,gBAAgB,qBAAqB;AAG3C,QAAM,oBAAoB,eAAe,aAAa;AAGtD,QAAM,qBAA0C,aAC5C,EAAE,QAAQ,eAAe,OAAO,QAAQ,SAAS,QAAQ,YAAY,UAAU,gBAAgB,SAAS,IACxG,EAAE,OAAO,eAAe,QAAQ,QAAQ,SAAS,QAAQ,YAAY,UAAU,gBAAgB,SAAS;AAG5G,QAAM,qBAA0C,aAC5C,EAAE,QAAQ,mBAAmB,OAAO,QAAQ,SAAS,QAAQ,YAAY,UAAU,gBAAgB,SAAS,IAC5G,EAAE,OAAO,mBAAmB,QAAQ,QAAQ,SAAS,QAAQ,YAAY,UAAU,gBAAgB,SAAS;AAGhH,QAAM,mBAAmB,UAAU,SAAS,MAAM;AAElD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,GAAG,OAAO,qCAAqC,aAAa;AAAA,MACvE,OAAO;AAAA,QACL,QAAQ,oBAAoB,YAAY;AAAA,QACxC,UAAU,mBAAmB,SAAY;AAAA,QACzC,UAAU,mBAAmB,SAAY;AAAA,QACzC,aAAa;AAAA,QACb,YAAY;AAAA,QACZ,kBAAkB;AAAA,MACpB;AAAA,MACA,aAAa;AAAA,MACb,WAAW;AAAA,MACX,cAAc;AAAA,MACd,cAAc;AAAA,MACd,YAAY;AAAA,MACZ,eAAe;AAAA,MAGf;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,OAAO;AAAA,cACL,UAAU;AAAA,cACV,OAAO;AAAA,cACP,iBAAiB;AAAA,cACjB,SAAS,UAAU,MAAM;AAAA,cACzB,YAAY;AAAA,cACZ,eAAe;AAAA,cACf,cAAc;AAAA,YAChB;AAAA;AAAA,QACF;AAAA,QACC;AAAA,QACA,WACC,oBAAC,SAAI,WAAU,8BAA6B,OAAO,oBAChD,4BAAkB,GACrB;AAAA,QAED,eACC,oBAAC,SAAI,WAAU,8BAA6B,OAAO,oBACjD,8BAAC,UAAK,WAAU,oBAAoB,uBAAY,GAClD;AAAA;AAAA;AAAA,EAEJ;AAEJ;;;AG1cA,SAAS,cAAAA,aAAY,WAAW,oBAAoB;AAoEhD,SAGI,OAAAC,MAHJ,QAAAC,aAAA;AA3CG,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA,gBAAgB;AAAA,EAChB;AAAA,EACA,gBAAgB;AAAA,EAChB;AACF,GAAkB;AAEhB,QAAM,eAAe,UAAU,oBAAoB,CAAC;AACpD,QAAM,UAAUF,YAAW,cAAc,CAAC;AAC1C,QAAM,UAAUA,YAAW,eAAe,CAAC;AAG3C,QAAM,YAAY,cAAc,OAAO,OAAO;AAG9C,QAAM,YAAY,eAAe,OAAO,OAAO;AAG/C,QAAM,UAAU,oBAAoB,OAAO,eAAgB,QAAQ;AAGnE,QAAM,cAAc,aAAa,OAAO;AAGxC,MAAI,CAAC,UAAW,QAAO;AAGvB,QAAM,mBAAmB;AAAA,IACvB;AAAA,IACA;AAAA,IACA,CAAC,aAAa;AAAA,IACd,CAAC,aAAa;AAAA,EAChB,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAEX,SACE,gBAAAE,MAAC,SAAI,WAAW,kBAAkB,OAC/B;AAAA;AAAA,IACA,eACC,gBAAAD,KAAC,UAAK,WAAW,kBAAkB,aAAa,IAAI,OAAO,WACxD,uBACH;AAAA,KAEJ;AAEJ;;;AC9EA,SAAS,YAAAE,WAAU,UAAAC,SAAQ,aAAAC,YAAW,eAAAC,oBAAmB;AACzD,SAAS,cAAAC,aAAY,WAAW,iBAAAC,sBAAqB;AAyWjD,SAmBE,OAAAC,MAnBF,QAAAC,aAAA;AAvTG,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,WAAW;AAAA,EACX,aAAa;AAAA,EACb,aAAa;AAAA,EACb,mBAAmB;AAAA,EACnB,mBAAmB;AAAA,EACnB,iCAAiC;AAAA,EACjC,YAAY;AAAA,EACZ;AAAA,EACA,eAAe;AAAA,EACf;AAAA,EACA,gBAAgB;AAAA,EAChB;AAAA,EACA,iBAAiB;AAAA,EACjB;AAAA,EACA,gBAAgB;AAClB,GAAoB;AAElB,QAAM,sBAAsB,cAAc;AAC1C,QAAM,uBAAuB,eAAe;AAG5C,QAAM,cAAc,UAAU,uBAAuB,CAAC;AACtD,QAAM,UAAUC,YAAW,cAAc,CAAC;AAC1C,QAAM,UAAUA,YAAW,eAAe,CAAC;AAG3C,QAAM,OAAOC,eAAc,CAAC,UAAU,MAAM,IAAI;AAChD,QAAM,OAAOA,eAAc,CAAC,UAAU,MAAM,IAAI;AAGhD,QAAM,CAAC,cAAc,eAAe,IAAIC,UAAS,CAAC;AAClD,QAAM,CAAC,YAAY,aAAa,IAAIA,UAAS,KAAK;AAClD,QAAM,2BAA2BC,QAAO,KAAK;AAC7C,QAAM,qBAAqBA,QAAO,CAAC;AACnC,QAAM,sBAAsBA,QAAsB,IAAI;AACtD,QAAM,cAAcA,QAAO,KAAK;AAGhC,QAAM,YAAY,cAAc,OAAO,OAAO;AAC9C,QAAM,YAAY,eAAe,OAAO,OAAO;AAG/C,EAAAC,WAAU,MAAM;AACd,QAAI,CAAC,aAAa,yBAAyB,SAAS;AAClD,oBAAc,KAAK;AACnB,+BAAyB,UAAU;AACnC,kBAAY,UAAU;AACtB,UAAI,wBAAwB,MAAM;AAChC,aAAK,sBAAsB,KAAK;AAAA,MAClC;AAAA,IACF;AAAA,EACF,GAAG,CAAC,WAAW,sBAAsB,IAAI,CAAC;AAG1C,EAAAA,WAAU,MAAM;AACd,WAAO,MAAM;AACX,UAAI,yBAAyB,WAAW,wBAAwB,MAAM;AACpE,aAAK,sBAAsB,KAAK;AAAA,MAClC;AAAA,IACF;AAAA,EACF,GAAG,CAAC,sBAAsB,IAAI,CAAC;AAG/B,QAAM,gBAAgBC;AAAA,IACpB,CAAC,UAAkB;AACjB,YAAM,QAAQ,WAAW;AACzB,UAAI,SAAS,EAAG,QAAO;AACvB,aAAO,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,QAAQ,YAAY,KAAK,CAAC;AAAA,IAC5D;AAAA,IACA,CAAC,UAAU,QAAQ;AAAA,EACrB;AAGA,QAAM,gBAAgBA;AAAA,IACpB,CAAC,UAAkB;AACjB,YAAM,QAAQ,WAAW;AACzB,aAAO,KAAK,MAAM,WAAW,QAAQ,KAAK;AAAA,IAC5C;AAAA,IACA,CAAC,UAAU,QAAQ;AAAA,EACrB;AAGA,QAAM,qBAAqBA,aAAY,MAAM;AAC3C,QAAI,CAAC,yBAAyB,WAAW,uBAAuB,MAAM;AACpE,sBAAgB,cAAc,WAAW,CAAC;AAAA,IAC5C;AAAA,EACF,GAAG,CAAC,aAAa,eAAe,mBAAmB,CAAC;AAGpD,EAAAD,WAAU,MAAM;AACd,uBAAmB;AAAA,EACrB,GAAG,CAAC,kBAAkB,CAAC;AAGvB,QAAM,kBAAkBC,aAAY,MAAM;AACxC,QAAI,CAAC,UAAW;AAEhB,kBAAc,IAAI;AAGlB,QAAI,wBAAwB,MAAM;AAChC,WAAK,sBAAsB,IAAI;AAAA,IACjC;AAGA,QAAI,CAAC,yBAAyB,SAAS;AACrC,yBAAmB,UAAU;AAAA,IAC/B;AACA,6BAAyB,UAAU;AAGnC,QAAI,oBAAoB,YAAY,MAAM;AACxC,aAAO,aAAa,oBAAoB,OAAO;AAC/C,0BAAoB,UAAU;AAAA,IAChC;AAAA,EACF,GAAG,CAAC,WAAW,sBAAsB,MAAM,YAAY,CAAC;AAGxD,QAAM,gBAAgBA,aAAY,MAAM;AACtC,QAAI,CAAC,WAAY;AAEjB,kBAAc,KAAK;AAGnB,QAAI,wBAAwB,MAAM;AAChC,WAAK,sBAAsB,KAAK;AAAA,IAClC;AAGA,QAAI,iCAAiC,GAAG;AACtC,0BAAoB,UAAU,OAAO,WAAW,MAAM;AACpD,iCAAyB,UAAU;AACnC,2BAAmB;AACnB,4BAAoB,UAAU;AAAA,MAChC,GAAG,8BAA8B;AAAA,IACnC,OAAO;AACL,+BAAyB,UAAU;AACnC,yBAAmB;AAAA,IACrB;AAAA,EACF,GAAG,CAAC,YAAY,sBAAsB,MAAM,gCAAgC,kBAAkB,CAAC;AAG/F,QAAM,aAAaA;AAAA,IACjB,CAAC,SAAiB,SAAiB,WAAoB;AACrD,UAAI,CAAC,WAAY;AAEjB,UAAI;AACJ,UAAI,YAAY;AACd,oBAAY,UAAU,OAAO,QAAQ,OAAO;AAAA,MAC9C,OAAO;AACL,mBAAW,KAAK,UAAU,OAAO,OAAO,OAAO;AAAA,MACjD;AACA,iBAAW,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,QAAQ,CAAC;AAE5C,sBAAgB,QAAQ;AAGxB,UAAI,uBAAuB,MAAM;AAC/B,aAAK,qBAAqB,cAAc,QAAQ,CAAC;AAAA,MACnD;AAAA,IACF;AAAA,IACA,CAAC,YAAY,YAAY,qBAAqB,MAAM,aAAa;AAAA,EACnE;AAGA,QAAM,kBAAkB,CAAC,UAA4C;AACnE,QAAI,cAAc,KAAK,YAAY,QAAS;AAC5C,QAAI,MAAM,WAAW,EAAG;AAExB,oBAAgB;AAChB,UAAM,SAAS,MAAM,cAAc,sBAAsB;AACzD,eAAW,MAAM,SAAS,MAAM,SAAS,MAAM;AAAA,EACjD;AAEA,QAAM,kBAAkB,CAAC,UAA4C;AACnE,QAAI,CAAC,WAAY;AACjB,UAAM,SAAS,MAAM,cAAc,sBAAsB;AACzD,eAAW,MAAM,SAAS,MAAM,SAAS,MAAM;AAAA,EACjD;AAEA,QAAM,gBAAgB,MAAM;AAC1B,kBAAc;AAAA,EAChB;AAEA,QAAM,mBAAmB,CAAC,UAA4C;AACpE,QAAI,YAAY;AACd,YAAM,SAAS,MAAM,cAAc,sBAAsB;AACzD,iBAAW,MAAM,SAAS,MAAM,SAAS,MAAM;AAAA,IACjD;AACA,kBAAc;AAAA,EAChB;AAGA,QAAM,mBAAmB,CAAC,UAA4C;AACpE,eAAW;AACX,gBAAY,UAAU;AACtB,oBAAgB;AAChB,UAAM,SAAS,MAAM,cAAc,sBAAsB;AACzD,UAAM,QAAQ,MAAM,QAAQ,CAAC;AAC7B,eAAW,MAAM,SAAS,MAAM,SAAS,MAAM;AAAA,EACjD;AAEA,QAAM,kBAAkB,CAAC,UAA4C;AACnE,QAAI,CAAC,WAAY;AACjB,UAAM,SAAS,MAAM,cAAc,sBAAsB;AACzD,UAAM,QAAQ,MAAM,QAAQ,CAAC;AAC7B,eAAW,MAAM,SAAS,MAAM,SAAS,MAAM;AAAA,EACjD;AAEA,QAAM,iBAAiB,MAAM;AAC3B,aAAS;AACT,kBAAc;AAAA,EAChB;AAEA,QAAM,oBAAoB,CAAC,UAA4C;AACrE,aAAS;AACT,QAAI,cAAc,MAAM,QAAQ,SAAS,GAAG;AAC1C,YAAM,SAAS,MAAM,cAAc,sBAAsB;AACzD,YAAM,QAAQ,MAAM,QAAQ,CAAC;AAC7B,iBAAW,MAAM,SAAS,MAAM,SAAS,MAAM;AAAA,IACjD;AACA,kBAAc;AAAA,EAChB;AAGA,MAAI,CAAC,UAAW,QAAO;AAGvB,QAAM,mBAAmB;AAAA,IACvB;AAAA,IACA;AAAA,IACA,CAAC,aAAa;AAAA,IACd,CAAC,aAAa;AAAA,EAChB,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAGX,QAAM,mBAAwC;AAAA,IAC5C,GAAG;AAAA,IACH,UAAU;AAAA,EACZ;AAEA,MAAI,YAAY;AACd,UAAM,QAAQ,MAAM;AACpB,UAAM,QAAQ,MAAM,SAAS;AAC7B,qBAAiB,SAAS,GAAG,gBAAgB;AAC7C,qBAAiB,MAAM,IAAI,MAAM,oBAAoB,CAAC;AACtD,qBAAiB,QAAQ,GAAG,KAAK;AACjC,qBAAiB,OAAO,GAAG,IAAI;AAAA,EACjC,OAAO;AACL,UAAM,SAAS,MAAM;AACrB,UAAM,OAAO,MAAM,UAAU;AAC7B,qBAAiB,QAAQ,GAAG,gBAAgB;AAC5C,qBAAiB,OAAO,IAAI,MAAM,oBAAoB,CAAC;AACvD,qBAAiB,SAAS,GAAG,MAAM;AACnC,qBAAiB,MAAM,GAAG,GAAG;AAAA,EAC/B;AAGA,QAAM,oBAAyC;AAAA,IAC7C,GAAG;AAAA,IACH,UAAU;AAAA,EACZ;AAEA,MAAI,YAAY;AACd,UAAM,QAAQ,MAAM;AACpB,UAAM,OAAO,mBAAmB;AAChC,UAAM,QAAQ,QAAQ;AACtB,sBAAkB,SAAS,GAAG,gBAAgB;AAC9C,sBAAkB,MAAM,IAAI,MAAM,oBAAoB,CAAC;AACvD,sBAAkB,QAAQ,GAAG,KAAK;AAClC,sBAAkB,OAAO,GAAG,IAAI;AAAA,EAClC,OAAO;AACL,UAAM,QAAQ,MAAM;AACpB,UAAM,MAAM,OAAO,QAAQ,eAAe,mBAAmB;AAC7D,UAAM,SAAS,QAAQ,mBAAmB,IAAI;AAC9C,sBAAkB,QAAQ,GAAG,gBAAgB;AAC7C,sBAAkB,OAAO,IAAI,MAAM,oBAAoB,CAAC;AACxD,sBAAkB,SAAS,GAAG,MAAM;AACpC,sBAAkB,MAAM,GAAG,GAAG;AAAA,EAChC;AAGA,QAAM,qBAA0C;AAAA,IAC9C,GAAG;AAAA,IACH,UAAU;AAAA,EACZ;AAEA,MAAI,YAAY;AACd,UAAM,UAAU,MAAM;AACtB,UAAM,OAAO,UAAU;AACvB,uBAAmB,QAAQ,GAAG,gBAAgB;AAC9C,uBAAmB,SAAS;AAC5B,uBAAmB,OAAO,GAAG,IAAI;AAAA,EACnC,OAAO;AACL,UAAM,SAAS,MAAM;AACrB,UAAM,MAAM,SAAS,SAAS;AAC9B,uBAAmB,QAAQ;AAC3B,uBAAmB,SAAS,GAAG,gBAAgB;AAC/C,uBAAmB,MAAM,GAAG,GAAG;AAAA,EACjC;AAEA,SACE,gBAAAN;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,MACX,OAAO;AAAA,QACL,GAAG;AAAA,QACH,UAAU;AAAA,QACV,QAAQ,YAAY,YAAY;AAAA,QAChC,aAAa;AAAA,QACb,YAAY;AAAA,MACd;AAAA,MACA,aAAa;AAAA,MACb,aAAa;AAAA,MACb,WAAW;AAAA,MACX,cAAc;AAAA,MACd,cAAc;AAAA,MACd,aAAa;AAAA,MACb,YAAY;AAAA,MACZ,eAAe;AAAA,MAGf;AAAA,wBAAAD,KAAC,SAAI,WAAW,mBAAmB,YAAY,IAAI,OAAO,kBAAkB;AAAA,QAG3E,CAAC,cACA,gBAAAA,KAAC,SAAI,WAAW,oBAAoB,aAAa,IAAI,OAAO,mBAAmB;AAAA,QAIjF,gBAAAA,KAAC,SAAI,WAAW,qBAAqB,cAAc,IAAI,OAAO,oBAAoB;AAAA;AAAA;AAAA,EACpF;AAEJ;;;ACxYA,SAAS,aAAAQ,YAAW,cAAAC,mBAAkB;AAoM9B,gBAAAC,YAAA;AA3ID,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,cAAc;AAAA,EACd,YAAY;AAAA,EACZ;AAAA,EACA,2BAA2B;AAAA,EAC3B;AAAA,EACA,mBAAmB;AAAA,EACnB;AAAA,EACA,sBAAsB;AAAA,EACtB;AAAA,EACA,yBAAyB;AAAA,EACzB;AAAA,EACA,uBAAuB;AAAA,EACvB;AAAA,EACA,gBAAgB;AAClB,GAAmB;AAEjB,QAAM,YAAYF,WAAU,QAAQ,CAAC;AACrC,QAAM,UAAUC,YAAW,cAAc,CAAC;AAC1C,QAAM,UAAUA,YAAW,eAAe,CAAC;AAG3C,QAAM,YAAY,cAAc,OAAO,OAAO;AAC9C,QAAM,YAAY,eAAe,OAAO,OAAO;AAG/C,QAAM,eAAe,QAAQ,OAAO,YAAa,SAAS;AAG1D,MAAI,CAAC,UAAW,QAAO;AAGvB,QAAM,iBAAiB,CAAC,YAA+C;AACrE,UAAM,SAAS,WAAW;AAC1B,UAAM,YAAY,WAAW;AAE7B,UAAM,MACH,YAAY,gBAAgB,cAAgB,CAAC,YAAY,gBAAgB,eACtE,UAAU,IACV,WAAW;AAEjB,QAAI,OAAO,OAAQ,QAAO;AAC1B,QAAI,OAAO,UAAW,QAAO;AAC7B,WAAO;AAAA,EACT;AAGA,QAAM,kBAAkB,CAAC,YAA6B;AACpD,QAAI,eAAe,SAAU,QAAO;AACpC,QAAI,eAAe,SAAU,QAAO;AAEpC,UAAM,QAAQ,gBAAgB,WAAW;AACzC,QAAI,SAAS,KAAK,MAAM,WAAW,KAAK;AACxC,QAAI,UAAU,KAAK,WAAW,EAAG,UAAS;AAE1C,UAAM,MACH,YAAY,gBAAgB,cAAgB,CAAC,YAAY,gBAAgB,eACtE,UAAU,IACV,WAAW;AAEjB,WAAO,OAAO;AAAA,EAChB;AAGA,QAAM,kBAAkB,CAAC,YAA4B;AACnD,UAAM,OAAO,eAAe,OAAO;AACnC,UAAM,SAAS,gBAAgB,OAAO;AAEtC,UAAM,UAAU,CAAC,sBAAsB,MAAM,WAAW;AAExD,QAAI,QAAQ;AACV,cAAQ,KAAK,QAAQ;AAErB,UAAI,SAAS,UAAU,uBAAuB,mBAAmB;AAC/D,gBAAQ,KAAK,uBAAuB,gBAAgB;AAAA,MACtD,WAAW,SAAS,aAAa,0BAA0B,mBAAmB;AAC5E,gBAAQ,KAAK,0BAA0B,gBAAgB;AAAA,MACzD,WAAW,SAAS,WAAW,wBAAwB,mBAAmB;AACxE,gBAAQ,KAAK,wBAAwB,gBAAgB;AAAA,MACvD;AAAA,IACF,OAAO;AACL,UAAI,0BAA0B;AAC5B,gBAAQ,KAAK,wBAAwB;AAAA,MACvC;AAAA,IACF;AAEA,WAAO,QAAQ,OAAO,OAAO,EAAE,KAAK,GAAG;AAAA,EACzC;AAGA,QAAM,kBAAkB,CAAC,YAAqD;AAC5E,UAAM,OAAO,eAAe,OAAO;AACnC,UAAM,SAAS,gBAAgB,OAAO;AAEtC,QAAI,QAAQ;AACV,UAAI,SAAS,UAAU,mBAAmB,eAAe;AACvD,eAAO,mBAAmB;AAAA,MAC5B,WAAW,SAAS,aAAa,sBAAsB,eAAe;AACpE,eAAO,sBAAsB;AAAA,MAC/B,WAAW,SAAS,WAAW,oBAAoB,eAAe;AAChE,eAAO,oBAAoB;AAAA,MAC7B;AAAA,IACF,OAAO;AACL,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AAGA,QAAM,mBAAmB;AAAA,IACvB;AAAA,IACA;AAAA,IACA,CAAC,aAAa;AAAA,IACd,CAAC,aAAa;AAAA,EAChB,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAGX,QAAM,iBAAsC;AAAA,IAC1C,GAAG;AAAA,IACH,SAAS;AAAA,IACT,eAAe,gBAAgB,eAAe,QAAQ;AAAA,IACtD,gBAAgB;AAAA,IAChB,YAAY;AAAA,EACd;AAEA,SACE,gBAAAC,KAAC,SAAI,WAAW,kBAAkB,OAAO,gBACtC,gBAAM,KAAK,EAAE,QAAQ,SAAS,GAAG,CAAC,GAAG,YACpC,gBAAAA;AAAA,IAAC;AAAA;AAAA,MAEC,WAAW,gBAAgB,OAAO;AAAA,MAClC,OAAO,gBAAgB,OAAO;AAAA;AAAA,IAFzB;AAAA,EAGP,CACD,GACH;AAEJ;;;AC5MA,SAAS,aAAAC,YAAW,cAAAC,mBAAkB;AAsFlC,SACE,OAAAC,MADF,QAAAC,aAAA;AAvDJ,IAAM,kBAAgD;AAAA,EACpD,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,MAAM;AACR;AAEO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,eAAe;AAAA,EACf,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ;AACF,GAAqB;AAEnB,QAAM,oBAAoBH,WAAU,gBAAgB,CAAC;AACrD,QAAM,UAAUC,YAAW,eAAe,CAAC;AAG3C,QAAM,YAAY,eAAe,OAAO,OAAO;AAG/C,MAAI,CAAC,UAAW,QAAO;AAGvB,QAAM,kBAAkB,gBAAgB,OAAO,oBAAqB,YAAY;AAChF,QAAM,kBAAmB,kBAAkB,QAAS;AAGpD,QAAM,mBAAmB,CAAC,gBAAgB,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAG7E,QAAM,eAAoC;AAAA,IACxC,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,cAAc;AAAA,IACd,QAAQ,GAAG,SAAS,YAAY,UAAU;AAAA,IAC1C,gBAAgB;AAAA,IAChB,WAAW;AAAA,EACb;AAEA,MAAI,SAAS;AAEX,iBAAa,YAAY,uBAAuB,gBAAgB,YAAY,CAAC;AAAA,EAC/E,OAAO;AAEL,iBAAa,YAAY,UAAU,eAAe;AAClD,iBAAa,aAAa;AAAA,EAC5B;AAEA,SACE,gBAAAE,MAAC,SAAI,WAAW,kBAAkB,OAChC;AAAA,oBAAAD,KAAC,SAAI,OAAO,cAAc;AAAA,IAC1B,gBAAAA,KAAC,WAAO;AAAA;AAAA;AAAA;AAAA,SAIN;AAAA,KACJ;AAEJ;;;AC/FA,SAAS,aAAAE,YAAW,YAAAC,WAAU,eAAAC,oBAAmB;AACjD,SAAS,cAAc,0BAAiD;AAiO5D,SA2EE,UA3EF,OAAAC,MAwCA,QAAAC,aAxCA;AA9NZ,IAAM,iBAAiB,OAAO,WAAW,eAAe,OAAO,SAAS,aAAa;AACrF,IAAM,mBAAmB,OAAO,WAAW,gBACxC,OAAO,SAAS,aAAa,eAAe,OAAO,SAAS,aAAa;AAC5E,IAAM,eAAe,OAAO,WAAW,eAAe,OAAO,SAAS,SAAS,SAAS,kBAAkB;AAC1G,IAAM,+BAA+B,CAAC,kBAAkB,CAAC,oBAAoB,CAAC;AAG9E,IAAM,qBAAqB;AA6DpB,SAAS,gBAAgB;AAAA,EAC9B,eAAe;AAAA,EACf,eAAe;AAAA,EACf,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,wBAAwB;AAAA,EACxB,SAAS;AAAA,EACT,gBAAgB;AAAA,EAChB,qBAAqB;AAAA,EACrB,YAAY;AAAA,EACZ,gBAAgB,qBAAqB;AAAA,EACrC,YAAY;AACd,GAAyB;AACvB,QAAM,mBAAmB,mBAAmB,CAAC,UAAU,MAAM,MAAM;AACnE,QAAM,cAAc,mBAAmB,CAAC,UAAU,MAAM,WAAW;AACnE,QAAM,CAAC,cAAc,eAAe,IAAIH,UAAS,KAAK;AACtD,QAAM,CAAC,iBAAiB,kBAAkB,IAAIA,UAAgC,IAAI;AAClF,QAAM,CAAC,gBAAgB,iBAAiB,IAAIA,UAAS,KAAK;AAC1D,QAAM,CAAC,gBAAgB,iBAAiB,IAAIA,UAAwB,IAAI;AAIxE,QAAM,CAAC,iBAAiB,IAAIA,UAAS,MAAM;AACzC,QAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,QAAI;AACF,YAAM,gBAAgB,eAAe,QAAQ,kBAAkB;AAC/D,UAAI,eAAe;AACjB,uBAAe,WAAW,kBAAkB;AAC5C,eAAO;AAAA,MACT;AAAA,IACF,QAAQ;AAAA,IAER;AACA,WAAO;AAAA,EACT,CAAC;AAGD,QAAM,gBAAgB,sBACnB,OAAO,WAAW,cAAc,OAAO,SAAS,WAAW;AAI9D,QAAM,UAAU,qBAAqB,WAAY,gBAAgB,qBAAqB;AAGtF,EAAAD,WAAU,MAAM;AACd,QAAI,qBAAqB,SAAS;AAChC,sBAAgB,IAAI;AACpB,yBAAmB,WAAW;AAAA,IAChC,WAAW,qBAAqB,aAAa;AAC3C,sBAAgB,KAAK;AACrB,yBAAmB,IAAI;AAAA,IACzB;AAAA,EACF,GAAG,CAAC,kBAAkB,WAAW,CAAC;AAGlC,QAAM,iBAAiB,oBAAoB;AAG3C,QAAM,WAAW,WAAW,aAAa,GAAG,SAAS;AACrD,QAAM,UAAU,WAAW,aAAa,IAAI,MAAM;AAGlD,QAAM,eAAeE,aAAY,MAAM;AACrC,WAAO,KAAK,SAAS,QAAQ;AAC7B,sBAAkB,IAAI;AAAA,EACxB,GAAG,CAAC,OAAO,CAAC;AAGZ,QAAM,kBAAkBA,aAAY,MAAM;AACxC,WAAO,SAAS,OAAO;AAAA,EACzB,GAAG,CAAC,CAAC;AAGL,EAAAF,WAAU,MAAM;AACd,QAAI,CAAC,kBAAkB,CAAC,QAAS;AAEjC,UAAM,yBAAyB,MAAM;AACnC,UAAI,SAAS,oBAAoB,WAAW;AAE1C,0BAAkB,kBAAkB;AAAA,MACtC;AAAA,IACF;AAEA,UAAM,cAAc,MAAM;AACxB,wBAAkB,kBAAkB;AAAA,IACtC;AAEA,aAAS,iBAAiB,oBAAoB,sBAAsB;AACpE,WAAO,iBAAiB,SAAS,WAAW;AAE5C,WAAO,MAAM;AACX,eAAS,oBAAoB,oBAAoB,sBAAsB;AACvE,aAAO,oBAAoB,SAAS,WAAW;AAAA,IACjD;AAAA,EACF,GAAG,CAAC,gBAAgB,SAAS,kBAAkB,CAAC;AAGhD,EAAAA,WAAU,MAAM;AACd,QAAI,mBAAmB,KAAM;AAC7B,QAAI,kBAAkB,GAAG;AACvB,sBAAgB;AAChB;AAAA,IACF;AAEA,UAAM,QAAQ,WAAW,MAAM;AAC7B,wBAAkB,iBAAiB,CAAC;AAAA,IACtC,GAAG,GAAI;AAEP,WAAO,MAAM,aAAa,KAAK;AAAA,EACjC,GAAG,CAAC,gBAAgB,eAAe,CAAC;AAIpC,EAAAA,WAAU,MAAM;AACd,QAAI,WAAW,yBAAyB,qBAAqB,KAAK,CAAC,mBAAmB;AACpF,YAAM,QAAQ,WAAW,MAAM;AAE7B,YAAI;AACF,yBAAe,QAAQ,oBAAoB,MAAM;AAAA,QACnD,QAAQ;AAAA,QAER;AACA,eAAO,SAAS,OAAO;AAAA,MACzB,GAAG,kBAAkB;AACrB,aAAO,MAAM,aAAa,KAAK;AAAA,IACjC;AAAA,EACF,GAAG,CAAC,SAAS,uBAAuB,UAAU,oBAAoB,iBAAiB,CAAC;AAGpF,QAAM,UAAU;AAAA,IACd,WAAW,OAAO,cAAc,cAAc,UAAU,YAAY;AAAA,IACpE,aAAa,OAAO,WAAW,eAAe,OAAQ,OAAe,aAAa;AAAA,IAClF,kBAAkB,OAAO,WAAW,eAAe,mBAAmB;AAAA,IACtE,cAAc,aAAa;AAAA,IAC3B,UAAU,OAAO,WAAW,cAAc,OAAO,SAAS,WAAW;AAAA,IACrE,UAAU,OAAO,WAAW,cAAc,OAAO,SAAS,WAAW;AAAA,IACrE,QAAQ,OAAO,WAAW,cAAc,OAAO,SAAS,SAAS;AAAA,IACjE,UAAU,OAAO,WAAW,cAAc,OAAO,SAAS,WAAW;AAAA,IACrE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,eAAe,YAAY,gBAAgB;AAAA,IAC3C,cAAc,YAAY,eAAe;AAAA,IACzC,aAAa;AAAA,EACf;AAEA,SACE,gBAAAG,KAAC,SAAI,WAAW,+GAA+G,SAAS,IACtI,0BAAAC,MAAC,SAAI,WAAU,8BAEb;AAAA,oBAAAD,KAAC,SAAI,WAAU,QACb,0BAAAA,KAAC,SAAI,WAAW,mEAAmE,UAAU,kBAAkB,kBAAkB,IAC/H,0BAAAA,KAAC,SAAI,WAAW,wBAAwB,UAAU,eAAe,6BAA6B,IAAI,GACpG,GACF;AAAA,IAGA,gBAAAA,KAAC,QAAG,WAAU,sCACX,oBACI,iBAAiB,0BAA0B,qBAC5C,iBACN;AAAA,IACA,gBAAAA,KAAC,OAAE,WAAU,iBACV,oBACI,wBACI,oBACG,qDACA,iCACJ,iBACE,qFACA,0EACN,6CACN;AAAA,IAGA,gBAAAA,KAAC,SAAI,WAAU,QACb,0BAAAA,KAAC,UAAK,WAAW,8CAA8C,UAAU,+BAA+B,kCAAkC,IACvI,oBACI,iBACG,uBACA,YACE,0BACA,QACE,yBACA,eACR,cACN,GACF;AAAA,IAGC,WAAW,CAAC,yBAAyB,kBACpC,gBAAAC,MAAC,SAAI,WAAU,oEAAmE,OAAO,EAAE,UAAU,WAAW,SAAS,wBAAwB,GAC/I;AAAA,sBAAAA,MAAC,OAAE,WAAU,+BAA8B,OAAO,EAAE,UAAU,SAAS,cAAc,MAAM,GAAG;AAAA;AAAA,QAC3E,gBAAAD,KAAC,UAAK,WAAU,cAAc,yBAAc;AAAA,QAAO;AAAA,SACtE;AAAA,MACA,gBAAAC,MAAC,QAAG,WAAU,oCAAmC,OAAO,EAAE,UAAU,OAAO,eAAe,QAAQ,aAAa,QAAQ,GACrH;AAAA,wBAAAD,KAAC,QAAG,8CAAgC;AAAA,QACpC,gBAAAA,KAAC,QAAG,oDAAsC;AAAA,QAC1C,gBAAAA,KAAC,QAAG,gDAAkC;AAAA,SACxC;AAAA,MACA,gBAAAA,KAAC,SAAI,WAAU,iCAAgC,OAAO,EAAE,UAAU,UAAU,GAC1E,0BAAAA;AAAA,QAAC;AAAA;AAAA,UACC,SAAS;AAAA,UACT,WAAU;AAAA,UACV,OAAO,EAAE,SAAS,WAAW,UAAU,SAAS,YAAY,UAAU,UAAU,UAAU;AAAA,UAC3F;AAAA;AAAA,MAED,GACF;AAAA,OACF;AAAA,IAID,WAAW,CAAC,yBAAyB,CAAC,kBACrC,gBAAAA,KAAC,SAAI,WAAU,gEAA+D,OAAO,EAAE,UAAU,WAAW,SAAS,wBAAwB,GAC1I,6BAAmB,OAClB,gBAAAC,MAAC,SAAI,WAAU,eAAc,OAAO,EAAE,UAAU,UAAU,GACxD;AAAA,sBAAAA,MAAC,OAAE,WAAU,6BAA4B,OAAO,EAAE,UAAU,SAAS,cAAc,MAAM,GAAG;AAAA;AAAA,QAAwB;AAAA,QAAe;AAAA,SAAG;AAAA,MACtI,gBAAAD;AAAA,QAAC;AAAA;AAAA,UACC,SAAS,MAAM,kBAAkB,IAAI;AAAA,UACrC,WAAU;AAAA,UACV,OAAO,EAAE,SAAS,WAAW,UAAU,SAAS,YAAY,UAAU,UAAU,UAAU;AAAA,UAC3F;AAAA;AAAA,MAED;AAAA,OACF,IAEA,gBAAAC,MAAA,YACE;AAAA,sBAAAD,KAAC,OAAE,WAAU,6BAA4B,OAAO,EAAE,UAAU,SAAS,cAAc,MAAM,GACtF,2BACG,6DACA,wDACN;AAAA,MACA,gBAAAC,MAAC,SAAI,WAAU,2CAA0C,OAAO,EAAE,UAAU,UAAU,GACpF;AAAA,wBAAAD;AAAA,UAAC;AAAA;AAAA,YACC,SAAS;AAAA,YACT,WAAU;AAAA,YACV,OAAO,EAAE,SAAS,WAAW,UAAU,SAAS,YAAY,UAAU,UAAU,UAAU;AAAA,YAEzF,2BAAiB,gCAAgC;AAAA;AAAA,QACpD;AAAA,QACA,gBAAAA;AAAA,UAAC;AAAA;AAAA,YACC,SAAS;AAAA,YACT,WAAU;AAAA,YACV,OAAO,EAAE,SAAS,WAAW,UAAU,SAAS,YAAY,UAAU,UAAU,UAAU;AAAA,YAC3F;AAAA;AAAA,QAED;AAAA,SACF;AAAA,MACC,kBACC,gBAAAA,KAAC,OAAE,WAAU,iBAAgB,OAAO,EAAE,UAAU,OAAO,WAAW,MAAM,GAAG,sEAE3E;AAAA,OAEJ,GAEJ;AAAA,IAID,WAAW,yBAAyB,qBACnC,gBAAAC,MAAC,SAAI,WAAU,8DAA6D,OAAO,EAAE,UAAU,WAAW,SAAS,wBAAwB,GACzI;AAAA,sBAAAD,KAAC,OAAE,WAAU,4BAA2B,OAAO,EAAE,UAAU,SAAS,cAAc,MAAM,GAAG,qDAE3F;AAAA,MACA,gBAAAA,KAAC,OAAE,WAAU,iBAAgB,OAAO,EAAE,UAAU,OAAO,cAAc,MAAM,GAAG,kFAE9E;AAAA,MACA,gBAAAC,MAAC,SAAI,WAAU,2CAA0C,OAAO,EAAE,UAAU,UAAU,GACpF;AAAA,wBAAAD;AAAA,UAAC;AAAA;AAAA,YACC,SAAS;AAAA,YACT,WAAU;AAAA,YACV,OAAO,EAAE,SAAS,WAAW,UAAU,SAAS,YAAY,UAAU,UAAU,UAAU;AAAA,YAC3F;AAAA;AAAA,QAED;AAAA,QACA,gBAAAA;AAAA,UAAC;AAAA;AAAA,YACC,SAAS,MAAM;AACb,kBAAI;AACF,+BAAe,QAAQ,oBAAoB,MAAM;AAAA,cACnD,QAAQ;AAAA,cAER;AACA,qBAAO,SAAS,OAAO;AAAA,YACzB;AAAA,YACA,WAAU;AAAA,YACV,OAAO,EAAE,SAAS,WAAW,UAAU,SAAS,YAAY,UAAU,UAAU,UAAU;AAAA,YAC3F;AAAA;AAAA,QAED;AAAA,SACF;AAAA,OACF;AAAA,IAID,aAAa,WACZ,gBAAAC,MAAC,SAAI,WAAU,8EACb;AAAA,sBAAAD,KAAC,OAAE,WAAU,4CAA2C,qCAAuB;AAAA,MAC/E,gBAAAC,MAAC,OAAE,WAAU,yBAAwB;AAAA;AAAA,QACxB,gBAAAD,KAAC,UAAK,WAAU,iBAAiB,yBAAc;AAAA,SAC5D;AAAA,MACA,gBAAAC,MAAC,OAAE,WAAU,yBAAwB;AAAA;AAAA,QACxB,gBAAAD,KAAC,UAAK,WAAW,eAAe,mBAAmB,gBAC3D,yBAAe,6BAA6B,WAC/C;AAAA,SACF;AAAA,MACC,CAAC,gBACA,gBAAAC,MAAC,OAAE,WAAU,gCAA+B;AAAA;AAAA,QACa,gBAAAD,KAAC,UAAK,WAAU,4BAA2B,qCAAuB;AAAA,SAC3H;AAAA,OAEJ;AAAA,IAID,iBACC,gBAAAC,MAAC,SAAI,WAAU,gEACb;AAAA,sBAAAD,KAAC,QAAG,WAAU,0CAAyC,oCAAsB;AAAA,MAC7E,gBAAAC,MAAC,SAAI,WAAU,2BACb;AAAA,wBAAAA,MAAC,SACC;AAAA,0BAAAD,KAAC,UAAK,WAAU,iBAAgB,uBAAS;AAAA,UAAQ;AAAA,UACjD,gBAAAA,KAAC,UAAK,WAAW,QAAQ,cAAc,mBAAmB,gBACvD,kBAAQ,cAAc,cAAc,aACvC;AAAA,WACF;AAAA,QACA,gBAAAC,MAAC,SACC;AAAA,0BAAAD,KAAC,UAAK,WAAU,iBAAgB,4BAAc;AAAA,UAAQ;AAAA,UACtD,gBAAAA,KAAC,UAAK,WAAW,QAAQ,mBAAmB,mBAAmB,iBAC5D,kBAAQ,mBAAmB,QAAQ,MACtC;AAAA,WACF;AAAA,QACA,gBAAAC,MAAC,SACC;AAAA,0BAAAD,KAAC,UAAK,WAAU,iBAAgB,0BAAY;AAAA,UAAQ;AAAA,UACpD,gBAAAA,KAAC,UAAK,WAAW,QAAQ,eAAe,mBAAmB,iBACxD,kBAAQ,eAAe,QAAQ,gBAClC;AAAA,WACF;AAAA,QACA,gBAAAC,MAAC,SACC;AAAA,0BAAAD,KAAC,UAAK,WAAU,iBAAgB,uBAAS;AAAA,UAAQ;AAAA,UACjD,gBAAAA,KAAC,UAAK,WAAW,QAAQ,YAAY,oBAAoB,iBACtD,kBAAQ,YAAY,QAAQ,MAC/B;AAAA,WACF;AAAA,QACA,gBAAAC,MAAC,SACC;AAAA,0BAAAD,KAAC,UAAK,WAAU,iBAAgB,kBAAI;AAAA,UAAQ;AAAA,UAC5C,gBAAAA,KAAC,UAAK,WAAW,QAAQ,QAAQ,oBAAoB,iBAClD,kBAAQ,QAAQ,QAAQ,MAC3B;AAAA,WACF;AAAA,QACA,gBAAAC,MAAC,SACC;AAAA,0BAAAD,KAAC,UAAK,WAAU,iBAAgB,oCAAsB;AAAA,UAAQ;AAAA,UAC9D,gBAAAA,KAAC,UAAK,WAAW,QAAQ,wBAAwB,kBAAkB,iBAChE,kBAAQ,wBAAwB,QAAQ,MAC3C;AAAA,WACF;AAAA,QACC,QAAQ,qBACP,gBAAAC,MAAC,SACC;AAAA,0BAAAD,KAAC,UAAK,WAAU,iBAAgB,kCAAoB;AAAA,UAAQ;AAAA,UAC5D,gBAAAA,KAAC,UAAK,WAAU,mBAAkB,iBAAG;AAAA,WACvC;AAAA,QAEF,gBAAAC,MAAC,SACC;AAAA,0BAAAD,KAAC,UAAK,WAAU,iBAAgB,uBAAS;AAAA,UAAQ;AAAA,UACjD,gBAAAA,KAAC,UAAK,WAAU,iBAAiB,kBAAQ,UAAS;AAAA,WACpD;AAAA,QACA,gBAAAC,MAAC,SACC;AAAA,0BAAAD,KAAC,UAAK,WAAU,iBAAgB,uBAAS;AAAA,UAAQ;AAAA,UACjD,gBAAAA,KAAC,UAAK,WAAU,iBAAiB,kBAAQ,UAAS;AAAA,WACpD;AAAA,QACC,QAAQ,aAAa,QAAQ,iBAC5B,gBAAAC,MAAC,SACC;AAAA,0BAAAD,KAAC,UAAK,WAAU,iBAAgB,+BAAiB;AAAA,UAAQ;AAAA,UACzD,gBAAAA,KAAC,UAAK,WAAU,iBAAiB,kBAAQ,eAAc;AAAA,WACzD;AAAA,QAEF,gBAAAC,MAAC,SACC;AAAA,0BAAAD,KAAC,UAAK,WAAU,iBAAgB,mBAAK;AAAA,UAAQ;AAAA,UAC7C,gBAAAC,MAAC,UAAK,WAAU,iBAAiB;AAAA,oBAAQ;AAAA,YAAU,QAAQ;AAAA,aAAO;AAAA,WACpE;AAAA,QACC,QAAQ,eACP,gBAAAA,MAAC,SACC;AAAA,0BAAAD,KAAC,UAAK,WAAU,iBAAgB,2BAAa;AAAA,UAAQ;AAAA,UACrD,gBAAAA,KAAC,UAAK,WAAW,QAAQ,gBAAgB,YAAY,oBAAoB,gBACtE,kBAAQ,aACX;AAAA,WACF;AAAA,SAEJ;AAAA,OACF;AAAA,KAEJ,GACF;AAEJ;","names":["useDigital","jsx","jsxs","useState","useRef","useEffect","useCallback","useDigital","useJoinsStore","jsx","jsxs","useDigital","useJoinsStore","useState","useRef","useEffect","useCallback","useAnalog","useDigital","jsx","useAnalog","useDigital","jsx","jsxs","useEffect","useState","useCallback","jsx","jsxs"]}
|
package/package.json
CHANGED