From dd5e8b8c428dc998e95e07d44b71972192c63b6f Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Mon, 22 Dec 2025 10:18:28 +0530 Subject: [PATCH] feat: Add ElementEditor component for managing UI element properties and editor panel positioning. --- .../components/element/ElementData.tsx | 256 ++++++++++++++++++ .../components/element/ElementEditor.tsx | 236 ++-------------- .../modules/builder/asset/models/models.tsx | 3 +- 3 files changed, 283 insertions(+), 212 deletions(-) create mode 100644 app/src/components/SimulationDashboard/components/element/ElementData.tsx diff --git a/app/src/components/SimulationDashboard/components/element/ElementData.tsx b/app/src/components/SimulationDashboard/components/element/ElementData.tsx new file mode 100644 index 0000000..88ebf61 --- /dev/null +++ b/app/src/components/SimulationDashboard/components/element/ElementData.tsx @@ -0,0 +1,256 @@ +import { AddIcon, DeviceIcon, ParametersIcon } from "../../../icons/ExportCommonIcons"; +import DataDetailedDropdown from "../../../ui/inputs/DataDetailedDropdown"; +import DataSourceSelector from "../../../ui/inputs/DataSourceSelector"; +import InputWithDropDown from "../../../ui/inputs/InputWithDropDown"; +import { UIElement } from "../../../../types/exportedTypes"; + +interface ElementDataProps { + element: UIElement | null; + selectedBlock: string; + selectedElement: string; + updateElementData: (blockId: string, elementId: string, updates: Partial) => void; + selectedProduct: any; + getEventByModelUuid: (productUuid: string, modelUuid: string) => any; + getAssetDropdownItems: () => Array<{ id: string; label: string; icon: JSX.Element }>; + getLableValueDropdownItems: (assetId: string | undefined) => Array<{ title: string; items: Array<{ id: string; label: string; icon: JSX.Element }> }>; + singleSourceFields: Array<{ id: string; label: string; showEyeDropper: boolean; options: Array<{ id: string; label: string }> }>; + singleValueFields: Array<{ id: string; label: string; showEyeDropper: boolean; options: Array<{ id: string; label: string }> }>; + multipleSourceFields: Array<{ id: string; label: string; showEyeDropper: boolean; options: Array<{ id: string; label: string }> }>; + multipleValueFields: Array<{ id: string; label: string; showEyeDropper: boolean; options: Array<{ id: string; label: string }> }>; + selectDataMapping: "singleMachine" | "multipleMachine"; + handleDataTypeSwitch: (newDataType: "singleMachine" | "multipleMachine") => void; + updateDataSource: (blockId: string, elementId: string, dataSource: string | string[]) => void; + updateDataValue: (blockId: string, elementId: string, dataValue: string | string[]) => void; + updateCommonValue: (blockId: string, elementId: string, commonValue: string) => void; + addField?: () => void; +} + +const ElementData: React.FC = ({ + element, + selectedBlock, + selectedElement, + updateElementData, + selectedProduct, + getEventByModelUuid, + getAssetDropdownItems, + getLableValueDropdownItems, + singleSourceFields = [], + singleValueFields = [], + multipleSourceFields = [], + multipleValueFields = [], + selectDataMapping = "singleMachine", + handleDataTypeSwitch, + updateDataSource, + updateDataValue, + updateCommonValue, + addField, +}) => { + return ( +
+ {element?.type === "label-value" && ( + <> +
+
Data Handling
+ { + updateElementData(selectedBlock, selectedElement, { label: value }); + }} + /> +
+ }], + }, + { + title: "Assets", + items: getAssetDropdownItems(), + }, + ]} + 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: , + } + : null + } + onChange={(value) => { + updateElementData(selectedBlock, selectedElement, { dataSource: value.id }); + }} + dropDownHeader={"RT-Data"} + eyedroper={true} + /> +
+ +
+ section.items) + .find((item) => item.id === element.dataBinding?.dataValue)?.label ?? "", + icon: , + } + : null + } + onChange={(value) => { + updateElementData(selectedBlock, selectedElement, { dataValue: value.id, label: value.label }); + }} + dropDownHeader={"RT-Data-Value"} + /> +
+
+ + )} + + {/* Data Mapping */} + {element?.type === "graph" && ( +
+
Data Mapping
+ +
+
handleDataTypeSwitch("singleMachine")}> + Single Machine +
+
handleDataTypeSwitch("multipleMachine")}> + Multiple Machine +
+
+ + {selectDataMapping === "singleMachine" && element.dataBinding?.dataType === "single-machine" && ( +
+ {singleSourceFields.map((field) => ( + { + updateDataSource(selectedBlock, selectedElement, value.id); + }} + showEyeDropper={field.showEyeDropper} + /> + ))} + {/* add delete */} + {singleValueFields.map((field, index) => ( + option.id === element.dataBinding?.dataValue?.[index])?.label ?? ""} + onSelect={(value) => { + const currentDataValue = Array.isArray(element.dataBinding?.dataValue) + ? element.dataBinding!.dataValue + : element.dataBinding?.dataValue + ? [element.dataBinding.dataValue] + : []; + + const newDataValue = [...currentDataValue]; + newDataValue[index] = value.id; + + updateDataValue(selectedBlock, selectedElement, newDataValue); + }} + showEyeDropper={field.showEyeDropper} + showDeleteBtn={true} + onDelete={() => { + const current = Array.isArray(element.dataBinding?.dataValue) + ? element.dataBinding!.dataValue + : element.dataBinding?.dataValue + ? [element.dataBinding.dataValue] + : []; + const next = [...current]; + next.splice(index, 1); + updateDataValue(selectedBlock, selectedElement, next); + }} + /> + ))} + +
+
+ +
+
Add Field
+
+
+ )} + + {selectDataMapping === "multipleMachine" && element.dataBinding?.dataType === "multiple-machine" && ( +
+ {multipleValueFields.map((field) => ( + o.id === element.dataBinding?.commonValue)?.label ?? ""} + onSelect={(value) => { + updateCommonValue(selectedBlock, selectedElement, value.id); + }} + showEyeDropper={field.showEyeDropper} + /> + ))} + {/* add delete */} + {multipleSourceFields.map((field, index) => ( + { + const current = Array.isArray(element.dataBinding?.dataSource) + ? element.dataBinding!.dataSource + : element.dataBinding?.dataSource + ? [element.dataBinding.dataSource] + : []; + + const next = [...current]; + next[index] = value.id; + + updateDataSource(selectedBlock, selectedElement, next); + }} + showEyeDropper={field.showEyeDropper} + showDeleteBtn={true} + onDelete={() => { + const current = Array.isArray(element.dataBinding?.dataSource) + ? element.dataBinding!.dataSource + : element.dataBinding?.dataSource + ? [element.dataBinding.dataSource] + : []; + const next = [...current]; + next.splice(index, 1); + updateDataSource(selectedBlock, selectedElement, next); + }} + /> + ))} + +
+
+ +
+
Add Field
+
+
+ )} +
+ )} +
+ ); +}; + +export default ElementData; diff --git a/app/src/components/SimulationDashboard/components/element/ElementEditor.tsx b/app/src/components/SimulationDashboard/components/element/ElementEditor.tsx index 655b369..202e0cf 100644 --- a/app/src/components/SimulationDashboard/components/element/ElementEditor.tsx +++ b/app/src/components/SimulationDashboard/components/element/ElementEditor.tsx @@ -1,14 +1,10 @@ import { useCallback, useMemo, useState, useRef, useEffect, type RefObject } from "react"; import { ExtendedCSSProperties, UIElement } from "../../../../types/exportedTypes"; import { DeleteIcon } from "../../../icons/ContextMenuIcons"; -import DataSourceSelector from "../../../ui/inputs/DataSourceSelector"; -import InputWithDropDown from "../../../ui/inputs/InputWithDropDown"; -import InputRange from "../../../ui/inputs/InputRange"; -import RenameInput from "../../../ui/inputs/RenameInput"; -import { AddIcon, DeviceIcon, ParametersIcon, ResizeHeightIcon } from "../../../icons/ExportCommonIcons"; -import DataDetailedDropdown from "../../../ui/inputs/DataDetailedDropdown"; +import { DeviceIcon, ParametersIcon, ResizeHeightIcon } from "../../../icons/ExportCommonIcons"; import { useSceneContext } from "../../../../modules/scene/sceneContext"; import ElementDesign from "./ElementDesign"; +import ElementData from "./ElementData"; import { useVisualizationStore } from "../../../../store/visualization/useVisualizationStore"; interface ElementEditorProps { @@ -60,7 +56,9 @@ const ElementEditor: React.FC = ({ const { getElementById } = simulationDashBoardStore(); const element = getElementById(selectedBlock, selectedElement); const [selectType, setSelectType] = useState("design"); - const [selectDataMapping, setSelectDataMapping] = useState(element?.type === "graph" && element.dataBinding?.dataType === "multiple-machine" ? "multipleMachine" : "singleMachine"); + const [selectDataMapping, setSelectDataMapping] = useState<"singleMachine" | "multipleMachine">( + element?.type === "graph" && element.dataBinding?.dataType === "multiple-machine" ? "multipleMachine" : "singleMachine" + ); // Use shared position from VisualizationStore const { editorPosition, setEditorPosition } = useVisualizationStore(); @@ -573,210 +571,26 @@ const ElementEditor: React.FC = ({ /> )} {selectType === "data" && ( -
- {element?.type === "label-value" && ( - <> -
-
Data Handling
- { - updateElementData(selectedBlock, selectedElement, { label: value }); - }} - /> -
- }], - }, - { - title: "Assets", - items: getAssetDropdownItems(), - }, - ]} - 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: , - } - : null - } - onChange={(value) => { - updateElementData(selectedBlock, selectedElement, { dataSource: value.id }); - }} - dropDownHeader={"RT-Data"} - eyedroper={true} - /> -
- -
- section.items) - .find((item) => item.id === element.dataBinding?.dataValue)?.label ?? "", - icon: , - } - : null - } - onChange={(value) => { - updateElementData(selectedBlock, selectedElement, { dataValue: value.id, label: value.label }); - }} - dropDownHeader={"RT-Data-Value"} - /> -
-
- - )} - - {/* Data Mapping */} - {element?.type === "graph" && ( -
-
Data Mapping
- -
-
handleDataTypeSwitch("singleMachine")}> - Single Machine -
-
handleDataTypeSwitch("multipleMachine")}> - Multiple Machine -
-
- - {selectDataMapping === "singleMachine" && element.dataBinding?.dataType === "single-machine" && ( -
- {singleSourceFields.map((field) => ( - { - updateDataSource(selectedBlock, selectedElement, value.id); - }} - showEyeDropper={field.showEyeDropper} - /> - ))} - {/* add delete */} - {singleValueFields.map((field, index) => ( - option.id === element.dataBinding?.dataValue?.[index])?.label ?? ""} - onSelect={(value) => { - const currentDataValue = Array.isArray(element.dataBinding?.dataValue) - ? element.dataBinding!.dataValue - : element.dataBinding?.dataValue - ? [element.dataBinding.dataValue] - : []; - - const newDataValue = [...currentDataValue]; - newDataValue[index] = value.id; - - updateDataValue(selectedBlock, selectedElement, newDataValue); - }} - showEyeDropper={field.showEyeDropper} - showDeleteBtn={true} - onDelete={() => { - const current = Array.isArray(element.dataBinding?.dataValue) - ? element.dataBinding!.dataValue - : element.dataBinding?.dataValue - ? [element.dataBinding.dataValue] - : []; - const next = [...current]; - next.splice(index, 1); - updateDataValue(selectedBlock, selectedElement, next); - }} - /> - ))} - -
-
- -
-
Add Field
-
-
- )} - - {selectDataMapping === "multipleMachine" && element.dataBinding?.dataType === "multiple-machine" && ( -
- {multipleValueFields.map((field) => ( - o.id === element.dataBinding?.commonValue)?.label ?? ""} - onSelect={(value) => { - updateCommonValue(selectedBlock, selectedElement, value.id); - }} - showEyeDropper={field.showEyeDropper} - /> - ))} - {/* add delete */} - {multipleSourceFields.map((field, index) => ( - { - const current = Array.isArray(element.dataBinding?.dataSource) - ? element.dataBinding!.dataSource - : element.dataBinding?.dataSource - ? [element.dataBinding.dataSource] - : []; - - const next = [...current]; - next[index] = value.id; - - updateDataSource(selectedBlock, selectedElement, next); - }} - showEyeDropper={field.showEyeDropper} - showDeleteBtn={true} - onDelete={() => { - const current = Array.isArray(element.dataBinding?.dataSource) - ? element.dataBinding!.dataSource - : element.dataBinding?.dataSource - ? [element.dataBinding.dataSource] - : []; - const next = [...current]; - next.splice(index, 1); - updateDataSource(selectedBlock, selectedElement, next); - }} - /> - ))} - -
-
- -
-
Add Field
-
-
- )} -
- )} -
+ )} ); diff --git a/app/src/modules/builder/asset/models/models.tsx b/app/src/modules/builder/asset/models/models.tsx index 4ff601e..b94d78b 100644 --- a/app/src/modules/builder/asset/models/models.tsx +++ b/app/src/modules/builder/asset/models/models.tsx @@ -23,7 +23,7 @@ function Models({ loader }: { readonly loader: GLTFLoader }) { const { controls, camera } = useThree(); const assetGroupRef = useRef(null); const { assetStore, layout } = useSceneContext(); - const { assets, selectedAssets, getSelectedAssetUuids } = assetStore(); + const { assets, selectedAssets, getSelectedAssetUuids, clearSelectedAssets } = assetStore(); const { selectedAsset, clearSelectedAsset } = useSelectedAsset(); const { contextAction, setContextAction } = useContextActionStore(); const { limitDistance } = useLimitDistance(); @@ -126,6 +126,7 @@ function Models({ loader }: { readonly loader: GLTFLoader }) { if (selectedAssets.length > 0) { const target = (controls as CameraControls).getTarget(new Vector3()); (controls as CameraControls).setTarget(target.x, 0, target.z, true); + clearSelectedAssets(); } if (selectedAsset) { clearSelectedAsset();