import React, { useRef, useMemo, useCallback, useState } from "react"; import { InfoIcon, AddIcon, RemoveIcon, ResizeHeightIcon } from "../../../icons/ExportCommonIcons"; import InputWithDropDown from "../../../ui/inputs/InputWithDropDown"; import { useSelectedActionSphere, useSimulationStates, useSocketStore } from "../../../../store/store"; import * as SimulationTypes from '../../../../types/simulation'; import LabledDropdown from "../../../ui/inputs/LabledDropdown"; import { handleResize } from "../../../../functions/handleResizePannel"; interface ConnectedModel { modelUUID: string; modelName: string; points: { uuid: string; position: [number, number, number]; index?: number; }[]; triggers?: { uuid: string; name: string; type: string; isUsed: boolean; }[]; } const ArmBotMechanics: React.FC = () => { const { selectedActionSphere } = useSelectedActionSphere(); const { simulationStates, setSimulationStates } = useSimulationStates(); const { socket } = useSocketStore(); const [selectedProcessIndex, setSelectedProcessIndex] = useState(null); const actionsContainerRef = useRef(null); // Get connected models and their triggers const connectedModels = useMemo(() => { if (!selectedActionSphere?.points?.uuid) return []; const armBotPaths = simulationStates.filter( (path): path is SimulationTypes.ArmBotEventsSchema => path.type === "ArmBot" ); const currentPoint = armBotPaths.find( (path) => path.points.uuid === selectedActionSphere.points.uuid )?.points; if (!currentPoint?.connections?.targets) return []; return currentPoint.connections.targets.reduce((acc, target) => { const connectedModel = simulationStates.find( (model) => model.modeluuid === target.modelUUID ); if (!connectedModel) return acc; let triggers: { uuid: string; name: string; type: string; isUsed: boolean }[] = []; let points: { uuid: string; position: [number, number, number] }[] = []; if (connectedModel.type === "Conveyor") { const conveyor = connectedModel as SimulationTypes.ConveyorEventsSchema; const connectedPointUUIDs = currentPoint?.connections?.targets .filter(t => t.modelUUID === connectedModel.modeluuid) .map(t => t.pointUUID) || []; points = conveyor.points .map((point, idx) => ({ uuid: point.uuid, position: point.position, index: idx })) .filter(point => connectedPointUUIDs.includes(point.uuid)); triggers = conveyor.points.flatMap(p => p.triggers?.filter(t => t.isUsed) || []); } else if (connectedModel.type === "StaticMachine") { const staticMachine = connectedModel as SimulationTypes.StaticMachineEventsSchema; points = [{ uuid: staticMachine.points.uuid, position: staticMachine.points.position }]; triggers = staticMachine.points.triggers ? [{ uuid: staticMachine.points.triggers.uuid, name: staticMachine.points.triggers.name, type: staticMachine.points.triggers.type, isUsed: true // StaticMachine triggers are always considered used }] : []; } if (!acc.some(m => m.modelUUID === connectedModel.modeluuid)) { acc.push({ modelUUID: connectedModel.modeluuid, modelName: connectedModel.modelName, points, triggers }); } return acc; }, []); }, [selectedActionSphere, simulationStates]); // Get triggers from connected models const connectedTriggers = useMemo(() => { return connectedModels.flatMap(model => (model.triggers || []).map(trigger => ({ ...trigger, displayName: `${model.modelName} - ${trigger.name}`, modelUUID: model.modelUUID })) ); }, [connectedModels]); // Get all points from connected models const connectedPoints = useMemo(() => { return connectedModels.flatMap(model => model.points.map(point => ({ ...point, displayName: `${model.modelName} - Point${typeof point.index === 'number' ? ` ${point.index}` : ''}`, modelUUID: model.modelUUID })) ); }, [connectedModels]); const { selectedPoint } = useMemo(() => { if (!selectedActionSphere?.points?.uuid) return { selectedPoint: null }; const armBotPaths = simulationStates.filter( (path): path is SimulationTypes.ArmBotEventsSchema => path.type === "ArmBot" ); const points = armBotPaths.find( (path) => path.points.uuid === selectedActionSphere.points.uuid )?.points; return { selectedPoint: points || null }; }, [selectedActionSphere, simulationStates]); const updateBackend = async (updatedPath: SimulationTypes.ArmBotEventsSchema | undefined) => { if (!updatedPath) return; const email = localStorage.getItem("email"); const organization = email ? email.split("@")[1].split(".")[0] : ""; const data = { organization: organization, modeluuid: updatedPath.modeluuid, eventData: { type: "ArmBot", points: updatedPath.points } } socket.emit('v2:model-asset:updateEventData', data); } const handleActionUpdate = useCallback((updatedAction: Partial) => { if (!selectedActionSphere?.points?.uuid || !selectedPoint) return; const updatedPaths = simulationStates.map((path) => { if (path.type === "ArmBot" && path.points.uuid === selectedActionSphere.points.uuid) { return { ...path, points: { ...path.points, actions: { ...path.points.actions, ...updatedAction } } }; } return path; }); const updatedPath = updatedPaths.find( (path): path is SimulationTypes.ArmBotEventsSchema => path.type === "ArmBot" && path.points.uuid === selectedActionSphere.points.uuid ); updateBackend(updatedPath); setSimulationStates(updatedPaths); }, [selectedActionSphere?.points?.uuid, selectedPoint, simulationStates, setSimulationStates]); const handleSpeedChange = useCallback((speed: number) => { handleActionUpdate({ speed }); }, [handleActionUpdate]); const handleProcessChange = useCallback((processes: SimulationTypes.ArmBotEventsSchema['points']['actions']['processes']) => { handleActionUpdate({ processes }); }, [handleActionUpdate]); const handleAddProcess = useCallback(() => { if (!selectedPoint) return; const newProcess: any = { triggerId: "", startPoint: "", endPoint: "" }; const updatedProcesses = selectedPoint.actions.processes ? [...selectedPoint.actions.processes, newProcess] : [newProcess]; handleProcessChange(updatedProcesses); setSelectedProcessIndex(updatedProcesses.length - 1); }, [selectedPoint, handleProcessChange]); const handleDeleteProcess = useCallback((index: number) => { if (!selectedPoint?.actions.processes) return; const updatedProcesses = [...selectedPoint.actions.processes]; updatedProcesses.splice(index, 1); handleProcessChange(updatedProcesses); // Reset selection if deleting the currently selected process if (selectedProcessIndex === index) { setSelectedProcessIndex(null); } else if (selectedProcessIndex !== null && selectedProcessIndex > index) { // Adjust selection index if needed setSelectedProcessIndex(selectedProcessIndex - 1); } }, [selectedPoint, selectedProcessIndex, handleProcessChange]); const handleTriggerSelect = useCallback((displayName: string, index: number) => { const selected = connectedTriggers.find(t => t.displayName === displayName); if (!selected || !selectedPoint?.actions.processes) return; const oldProcess = selectedPoint.actions.processes[index]; const updatedProcesses = [...selectedPoint.actions.processes]; // Only reset start/end if new trigger invalidates them (your logic can expand this) updatedProcesses[index] = { ...oldProcess, triggerId: selected.uuid, startPoint: oldProcess.startPoint || "", // preserve if exists endPoint: oldProcess.endPoint || "" // preserve if exists }; handleProcessChange(updatedProcesses); }, [connectedTriggers, selectedPoint, handleProcessChange]); const handleStartPointSelect = useCallback((displayName: string, index: number) => { if (!selectedPoint?.actions.processes) return; const point = connectedPoints.find(p => p.displayName === displayName); if (!point) return; const updatedProcesses = [...selectedPoint.actions.processes]; updatedProcesses[index] = { ...updatedProcesses[index], startPoint: point.uuid }; handleProcessChange(updatedProcesses); }, [selectedPoint, connectedPoints, handleProcessChange]); const handleEndPointSelect = useCallback((displayName: string, index: number) => { if (!selectedPoint?.actions.processes) return; const point = connectedPoints.find(p => p.displayName === displayName); if (!point) return; const updatedProcesses = [...selectedPoint.actions.processes]; updatedProcesses[index] = { ...updatedProcesses[index], endPoint: point.uuid }; handleProcessChange(updatedProcesses); }, [selectedPoint, connectedPoints, handleProcessChange]); const getProcessByIndex = useCallback((index: number) => { if (!selectedPoint?.actions.processes || index >= selectedPoint.actions.processes.length) return null; return selectedPoint.actions.processes[index]; }, [selectedPoint]); const getFilteredTriggerOptions = (currentIndex: number) => { const usedTriggerUUIDs = selectedPoint?.actions.processes?.filter((_, i) => i !== currentIndex).map(p => p.triggerId).filter(Boolean) ?? []; return connectedTriggers.filter(trigger => !usedTriggerUUIDs.includes(trigger.uuid)).map(trigger => trigger.displayName); }; return (
{selectedActionSphere?.path?.modelName || "ArmBot point not found"}
ArmBot Properties
{selectedPoint && ( <> handleSpeedChange(parseInt(value))} />
Processes
Add
{selectedPoint.actions.processes?.map((process, index) => (
setSelectedProcessIndex(index)} > Process {index + 1}
handleDeleteProcess(index)} >
))}
handleResize(e, actionsContainerRef)} >
{selectedProcessIndex !== null && (
t.uuid === getProcessByIndex(selectedProcessIndex)?.triggerId )?.displayName || 'Select a trigger' } onSelect={(value) => handleTriggerSelect(value, selectedProcessIndex)} options={getFilteredTriggerOptions(selectedProcessIndex)} /> p.uuid === getProcessByIndex(selectedProcessIndex)?.startPoint )?.displayName || 'Select start point' } onSelect={(value) => handleStartPointSelect(value, selectedProcessIndex)} options={connectedPoints.map(point => point.displayName)} /> p.uuid === getProcessByIndex(selectedProcessIndex)?.endPoint )?.displayName || 'Select end point' } onSelect={(value) => handleEndPointSelect(value, selectedProcessIndex)} options={connectedPoints.map(point => point.displayName)} />
)} )}
Configure ArmBot properties and trigger-based processes.
); }; export default React.memo(ArmBotMechanics);