diff --git a/apps/web/src/components/vision/AbacusVisionBridge.tsx b/apps/web/src/components/vision/AbacusVisionBridge.tsx index 726f8c0b..b170f955 100644 --- a/apps/web/src/components/vision/AbacusVisionBridge.tsx +++ b/apps/web/src/components/vision/AbacusVisionBridge.tsx @@ -44,6 +44,16 @@ export interface AbacusVisionBridgeProps { onConfigurationChange?: (config: VisionConfigurationChange) => void /** Initial camera source to show (defaults to 'local', but should be 'phone' if remote session is active) */ initialCameraSource?: CameraSource + /** Whether to show vision control buttons (enable/disable, clear settings) */ + showVisionControls?: boolean + /** Whether vision is currently enabled (for showVisionControls) */ + isVisionEnabled?: boolean + /** Whether vision setup is complete (for showVisionControls) */ + isVisionSetupComplete?: boolean + /** Called when user toggles vision on/off */ + onToggleVision?: () => void + /** Called when user clicks clear settings */ + onClearSettings?: () => void } /** @@ -62,6 +72,11 @@ export function AbacusVisionBridge({ onError, onConfigurationChange, initialCameraSource = 'local', + showVisionControls = false, + isVisionEnabled = false, + isVisionSetupComplete = false, + onToggleVision, + onClearSettings, }: AbacusVisionBridgeProps): ReactNode { const [videoDimensions, setVideoDimensions] = useState<{ width: number @@ -1339,6 +1354,72 @@ export function AbacusVisionBridge({ {vision.cameraError} )} + + {/* Vision control buttons (when embedded in modal) */} + {showVisionControls && ( +
+ {isVisionSetupComplete && ( + + )} + + {isVisionSetupComplete && ( + + )} +
+ )} ) } diff --git a/apps/web/src/components/vision/VisionSetupModal.tsx b/apps/web/src/components/vision/VisionSetupModal.tsx index 7ba80011..8500900c 100644 --- a/apps/web/src/components/vision/VisionSetupModal.tsx +++ b/apps/web/src/components/vision/VisionSetupModal.tsx @@ -1,16 +1,14 @@ 'use client' -import { useState } from 'react' import { useMyAbacus } from '@/contexts/MyAbacusContext' -import { Modal, ModalContent } from '@/components/common/Modal' -import { AbacusVisionBridge } from './AbacusVisionBridge' import { css } from '../../../styled-system/css' +import { AbacusVisionBridge } from './AbacusVisionBridge' /** * Modal for configuring abacus vision settings * - * Shows status and launches AbacusVisionBridge for configuration. - * AbacusVisionBridge saves camera/calibration to MyAbacusContext. + * Renders AbacusVisionBridge directly in a draggable modal. + * The bridge component handles all camera/calibration configuration. */ export function VisionSetupModal() { const { @@ -25,9 +23,6 @@ export function VisionSetupModal() { dock, } = useMyAbacus() - // State for showing the configuration UI - const [isConfiguring, setIsConfiguring] = useState(false) - const handleClearSettings = () => { setVisionCamera(null) setVisionCalibration(null) @@ -35,223 +30,64 @@ export function VisionSetupModal() { setVisionEnabled(false) } - return ( - - -
- {/* Status display */} -
-

- Status -

-
- - - - -
-
+ const handleToggleVision = () => { + setVisionEnabled(!visionConfig.enabled) + } - {/* Actions */} -
- {isVisionSetupComplete && ( - - )} + if (!isVisionSetupOpen) return null - - - {isVisionSetupComplete && ( - - )} -
- - {/* Close button */} - -
-
- - {/* AbacusVisionBridge overlay for configuration */} - {isConfiguring && ( -
- { - // Value detected - configuration is working - }} - onClose={() => setIsConfiguring(false)} - onConfigurationChange={(config) => { - // Save configuration to context as it changes - if (config.cameraDeviceId !== undefined) { - setVisionCamera(config.cameraDeviceId) - } - if (config.calibration !== undefined) { - setVisionCalibration(config.calibration) - } - if (config.remoteCameraSessionId !== undefined) { - setVisionRemoteSession(config.remoteCameraSessionId) - } - }} - // Start with phone camera selected if remote session is configured but no local camera - initialCameraSource={ - visionConfig.remoteCameraSessionId && !visionConfig.cameraDeviceId ? 'phone' : 'local' - } - /> -
- )} -
- ) -} - -/** - * Status row component - */ -function StatusRow({ - label, - value, - isConfigured, -}: { - label: string - value: string - isConfigured: boolean -}) { return (
{ + if (e.key === 'Escape') { + closeVisionSetup() + } + }} > - {label} - - {value} - + {/* AbacusVisionBridge is a motion.div with drag - stopPropagation prevents backdrop close */} +
e.stopPropagation()}> + { + // Value detected - configuration is working + }} + onClose={closeVisionSetup} + onConfigurationChange={(config) => { + // Save configuration to context as it changes + if (config.cameraDeviceId !== undefined) { + setVisionCamera(config.cameraDeviceId) + } + if (config.calibration !== undefined) { + setVisionCalibration(config.calibration) + } + if (config.remoteCameraSessionId !== undefined) { + setVisionRemoteSession(config.remoteCameraSessionId) + } + }} + // Start with phone camera selected if remote session is configured but no local camera + initialCameraSource={ + visionConfig.remoteCameraSessionId && !visionConfig.cameraDeviceId ? 'phone' : 'local' + } + // Show enable/disable and clear buttons + showVisionControls={true} + isVisionEnabled={visionConfig.enabled} + isVisionSetupComplete={isVisionSetupComplete} + onToggleVision={handleToggleVision} + onClearSettings={handleClearSettings} + /> +
) }