From 3b645336bf98a2875a3cbee061dde3da63ed595b Mon Sep 17 00:00:00 2001 From: Nalvazhuthi Date: Thu, 18 Dec 2025 17:55:01 +0530 Subject: [PATCH 1/2] feat: Add delete functionality to DataSourceSelector and ElementEditor components --- .../components/element/ElementEditor.tsx | 44 ++++++++++++------- .../ui/inputs/DataSourceSelector.tsx | 11 ++++- .../_simulationDashBoard.scss | 7 +++ 3 files changed, 46 insertions(+), 16 deletions(-) diff --git a/app/src/components/SimulationDashboard/components/element/ElementEditor.tsx b/app/src/components/SimulationDashboard/components/element/ElementEditor.tsx index 28991e3..141d35a 100644 --- a/app/src/components/SimulationDashboard/components/element/ElementEditor.tsx +++ b/app/src/components/SimulationDashboard/components/element/ElementEditor.tsx @@ -550,12 +550,12 @@ const ElementEditor: React.FC = ({ value={ element.dataBinding?.dataSource ? { - id: element.dataBinding.dataSource as string, - label: - getEventByModelUuid(selectedProduct.productUuid, element.dataBinding.dataSource as string)?.modelName ?? - (element.dataBinding.dataSource === "global" ? "Global" : ""), - icon: , - } + id: element.dataBinding.dataSource as string, + label: + getEventByModelUuid(selectedProduct.productUuid, element.dataBinding.dataSource as string)?.modelName ?? + (element.dataBinding.dataSource === "global" ? "Global" : ""), + icon: , + } : null } onChange={(value) => { @@ -574,13 +574,13 @@ const ElementEditor: React.FC = ({ value={ element.dataBinding?.dataValue ? { - id: element.dataBinding.dataValue as string, - label: - getLableValueDropdownItems(element.dataBinding?.dataSource as string | undefined) - .flatMap((section) => section.items) - .find((item) => item.id === element.dataBinding?.dataValue)?.label ?? "", - icon: , - } + id: element.dataBinding.dataValue as string, + label: + getLableValueDropdownItems(element.dataBinding?.dataSource as string | undefined) + .flatMap((section) => section.items) + .find((item) => item.id === element.dataBinding?.dataValue)?.label ?? "", + icon: , + } : null } onChange={(value) => { @@ -620,7 +620,7 @@ const ElementEditor: React.FC = ({ showEyeDropper={field.showEyeDropper} /> ))} - + {/* add delete */} {singleValueFields.map((field, index) => ( = ({ updateDataValue(selectedBlock, selectedElement, newDataValue); }} showEyeDropper={field.showEyeDropper} + showDeleteBtn={true} + onDelete={() => { + const current = Array.isArray(element.dataBinding?.dataValue) ? element.dataBinding!.dataValue : []; + const next = [...current]; + next.splice(index, 1); + updateDataValue(selectedBlock, selectedElement, next); + }} /> ))} @@ -662,7 +669,7 @@ const ElementEditor: React.FC = ({ showEyeDropper={field.showEyeDropper} /> ))} - + {/* add delete */} {multipleSourceFields.map((field, index) => ( = ({ updateDataSource(selectedBlock, selectedElement, next); }} showEyeDropper={field.showEyeDropper} + showDeleteBtn={true} + onDelete={() => { + const current = Array.isArray(element.dataBinding?.dataSource) ? element.dataBinding!.dataSource : []; + const next = [...current]; + next.splice(index, 1); + updateDataSource(selectedBlock, selectedElement, next); + }} /> ))} diff --git a/app/src/components/ui/inputs/DataSourceSelector.tsx b/app/src/components/ui/inputs/DataSourceSelector.tsx index 11557d4..5201d74 100644 --- a/app/src/components/ui/inputs/DataSourceSelector.tsx +++ b/app/src/components/ui/inputs/DataSourceSelector.tsx @@ -1,6 +1,7 @@ import React, { useState } from "react"; import { EyeDroperIcon } from "../../icons/ExportCommonIcons"; import RegularDropDownID from "./RegularDropDownID"; +import { DeleteIcon } from "../../icons/ContextMenuIcons"; type DataSourceSelectorProps = { label?: string; @@ -12,9 +13,11 @@ type DataSourceSelectorProps = { onSelect: (value: { id: string; label: string }) => void; eyeDropperActive?: boolean; // initial state showEyeDropper?: boolean; + showDeleteBtn?: boolean; + onDelete?: () => void; }; -const DataSourceSelector: React.FC = ({ label = "Data Source", options, selected, onSelect, showEyeDropper = true }) => { +const DataSourceSelector: React.FC = ({ label = "Data Source", options, selected, onSelect, showEyeDropper = true, showDeleteBtn, onDelete }) => { // Local state to toggle EyeDropper const [isEyeActive, setIsEyeActive] = useState(false); @@ -31,6 +34,12 @@ const DataSourceSelector: React.FC = ({ label = "Data S )} + + {showDeleteBtn && ( +
onDelete?.()} style={{ cursor: onDelete ? "pointer" : "default" }}> + +
+ )} ); diff --git a/app/src/styles/components/simulationDashboard/_simulationDashBoard.scss b/app/src/styles/components/simulationDashboard/_simulationDashBoard.scss index af2a618..b60097e 100644 --- a/app/src/styles/components/simulationDashboard/_simulationDashBoard.scss +++ b/app/src/styles/components/simulationDashboard/_simulationDashBoard.scss @@ -287,6 +287,7 @@ justify-content: center; align-items: center; z-index: 100; + svg { cursor: grab; } @@ -651,6 +652,7 @@ display: flex; padding: 3px; border-radius: 2px; + cursor: pointer; &.active { @@ -658,6 +660,10 @@ } } + .delete { + cursor: pointer; + } + .regularDropdown-container { .icon { padding: 0; @@ -696,6 +702,7 @@ position: fixed; top: 80px; right: 40px; + .appearance { .design-datas-wrapper { From 7dd795af57688d9ba60f0dd63ca632c374cf18c2 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Sat, 20 Dec 2025 15:30:10 +0530 Subject: [PATCH 2/2] feat: Add simulation analyzer with comprehensive metric tracking and a utility for calculating minimum block size. --- .../functions/block/calculateMinBlockSize.ts | 8 +++- .../modules/simulation/analyzer/analyzer.tsx | 47 +++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/app/src/components/SimulationDashboard/functions/block/calculateMinBlockSize.ts b/app/src/components/SimulationDashboard/functions/block/calculateMinBlockSize.ts index 4a5cbe0..f0816e9 100644 --- a/app/src/components/SimulationDashboard/functions/block/calculateMinBlockSize.ts +++ b/app/src/components/SimulationDashboard/functions/block/calculateMinBlockSize.ts @@ -4,6 +4,7 @@ export const calculateMinBlockSize = (block: Block): Size => { let minWidth = 100; let minHeight = 50; let stackedHeight = 0; // Track cumulative height for stacked elements + let stackedElementCount = 0; // Track number of stacked elements for gap calculation block.elements.forEach((element) => { if (element.positionType === "absolute") { @@ -17,12 +18,15 @@ export const calculateMinBlockSize = (block: Block): Size => { minWidth = Math.max(minWidth, (element.size?.width || 200) + 40); // Height: sum them up since they stack stackedHeight += element.size?.height || 60; + stackedElementCount++; } }); - // Add padding to stacked height and compare with minHeight from absolute elements + // Add padding to stacked height and gaps between elements if (stackedHeight > 0) { - minHeight = Math.max(minHeight, stackedHeight + 40); + // Add 10px gap between each pair of stacked elements + const gapsBetweenElements = Math.max(0, stackedElementCount - 1) * 5; + minHeight = Math.max(minHeight, stackedHeight + gapsBetweenElements + 40); } return { width: minWidth, height: minHeight }; diff --git a/app/src/modules/simulation/analyzer/analyzer.tsx b/app/src/modules/simulation/analyzer/analyzer.tsx index 9cb2273..28a3d09 100644 --- a/app/src/modules/simulation/analyzer/analyzer.tsx +++ b/app/src/modules/simulation/analyzer/analyzer.tsx @@ -193,6 +193,9 @@ function Analyzer() { // Track previous vehicle phases to detect trip completion const previousVehiclePhasesRef = useRef>({}); + // Track previous crane phases to detect cycle completion + const previousCranePhasesRef = useRef>({}); + // Material lifecycle tracking const materialLifecycleRef = useRef< Record< @@ -227,6 +230,7 @@ function Analyzer() { previousArmBotActionsRef.current = {}; previousHumanCountsRef.current = {}; previousVehiclePhasesRef.current = {}; + previousCranePhasesRef.current = {}; materialLifecycleRef.current = {}; setAnalysis(null); setAnalyzing(false); @@ -2487,6 +2491,49 @@ function Analyzer() { }); }, [vehicles, isPlaying]); + // Monitor Crane phase changes to track completed cycles, loads, and lifts + useEffect(() => { + if (!isPlaying) return; + + cranes.forEach((crane) => { + const previousPhase = previousCranePhasesRef.current[crane.modelUuid]; + const currentPhase = crane.currentPhase; + + // Check for transition from 'pickup-drop' to 'init' (Cycle completed) + if (previousPhase === "pickup-drop" && currentPhase === "init") { + // Increment cycles completed + if (!completedActionsRef.current[crane.modelUuid]) { + completedActionsRef.current[crane.modelUuid] = 0; + } + completedActionsRef.current[crane.modelUuid]++; + + // Track loads handled (number of materials carried in this cycle) + const loadsInCycle = crane.currentMaterials?.length || 1; + if (!completedActionsRef.current[`${crane.modelUuid}_loads`]) { + completedActionsRef.current[`${crane.modelUuid}_loads`] = 0; + } + completedActionsRef.current[`${crane.modelUuid}_loads`] += loadsInCycle; + } + + // Track lifts (picking phase indicates a lift operation) + if (previousPhase !== "picking" && currentPhase === "picking") { + if (!completedActionsRef.current[`${crane.modelUuid}_lifts`]) { + completedActionsRef.current[`${crane.modelUuid}_lifts`] = 0; + } + completedActionsRef.current[`${crane.modelUuid}_lifts`]++; + + // Track lift height (assuming a default lift height of 5 meters for now) + // In a real scenario, this would be calculated from crane constraints + if (!completedActionsRef.current[`${crane.modelUuid}_lift_height`]) { + completedActionsRef.current[`${crane.modelUuid}_lift_height`] = 0; + } + completedActionsRef.current[`${crane.modelUuid}_lift_height`] += 5; + } + + previousCranePhasesRef.current[crane.modelUuid] = currentPhase; + }); + }, [cranes, isPlaying]); + return null; }