feat: Add ElementEditor component for managing UI element properties and editor panel positioning.
This commit is contained in:
@@ -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<ElementDataBinding>) => 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<ElementDataProps> = ({
|
||||
element,
|
||||
selectedBlock,
|
||||
selectedElement,
|
||||
updateElementData,
|
||||
selectedProduct,
|
||||
getEventByModelUuid,
|
||||
getAssetDropdownItems,
|
||||
getLableValueDropdownItems,
|
||||
singleSourceFields = [],
|
||||
singleValueFields = [],
|
||||
multipleSourceFields = [],
|
||||
multipleValueFields = [],
|
||||
selectDataMapping = "singleMachine",
|
||||
handleDataTypeSwitch,
|
||||
updateDataSource,
|
||||
updateDataValue,
|
||||
updateCommonValue,
|
||||
addField,
|
||||
}) => {
|
||||
return (
|
||||
<div className="data-details">
|
||||
{element?.type === "label-value" && (
|
||||
<>
|
||||
<div className="design-section">
|
||||
<div className="sub-header">Data Handling</div>
|
||||
<InputWithDropDown
|
||||
label="Label"
|
||||
type="string"
|
||||
value={element.dataBinding?.label || ""}
|
||||
placeholder={"Label 1"}
|
||||
onChange={(value) => {
|
||||
updateElementData(selectedBlock, selectedElement, { label: value });
|
||||
}}
|
||||
/>
|
||||
<div className="data">
|
||||
<DataDetailedDropdown
|
||||
title="Data Source"
|
||||
placeholder="Select assets"
|
||||
sections={[
|
||||
{
|
||||
title: "Global",
|
||||
items: [{ id: "global", label: "Global", icon: <DeviceIcon /> }],
|
||||
},
|
||||
{
|
||||
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: <DeviceIcon />,
|
||||
}
|
||||
: null
|
||||
}
|
||||
onChange={(value) => {
|
||||
updateElementData(selectedBlock, selectedElement, { dataSource: value.id });
|
||||
}}
|
||||
dropDownHeader={"RT-Data"}
|
||||
eyedroper={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="data">
|
||||
<DataDetailedDropdown
|
||||
title="Data Value"
|
||||
placeholder="Select Value"
|
||||
sections={getLableValueDropdownItems(element.dataBinding?.dataSource as string | undefined)}
|
||||
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: <ParametersIcon />,
|
||||
}
|
||||
: null
|
||||
}
|
||||
onChange={(value) => {
|
||||
updateElementData(selectedBlock, selectedElement, { dataValue: value.id, label: value.label });
|
||||
}}
|
||||
dropDownHeader={"RT-Data-Value"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Data Mapping */}
|
||||
{element?.type === "graph" && (
|
||||
<div className="data-mapping">
|
||||
<div className="heading">Data Mapping</div>
|
||||
|
||||
<div className="type-switch">
|
||||
<div className={`type ${selectDataMapping === "singleMachine" ? "active" : ""}`} onClick={() => handleDataTypeSwitch("singleMachine")}>
|
||||
Single Machine
|
||||
</div>
|
||||
<div className={`type ${selectDataMapping === "multipleMachine" ? "active" : ""}`} onClick={() => handleDataTypeSwitch("multipleMachine")}>
|
||||
Multiple Machine
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectDataMapping === "singleMachine" && element.dataBinding?.dataType === "single-machine" && (
|
||||
<div className="fields-wrapper design-section">
|
||||
{singleSourceFields.map((field) => (
|
||||
<DataSourceSelector
|
||||
key={field.id}
|
||||
label={field.label}
|
||||
options={field.options}
|
||||
selected={getEventByModelUuid(selectedProduct.productUuid, element.dataBinding?.dataSource as string)?.modelName ?? ""}
|
||||
onSelect={(value) => {
|
||||
updateDataSource(selectedBlock, selectedElement, value.id);
|
||||
}}
|
||||
showEyeDropper={field.showEyeDropper}
|
||||
/>
|
||||
))}
|
||||
{/* add delete */}
|
||||
{singleValueFields.map((field, index) => (
|
||||
<DataSourceSelector
|
||||
key={field.id}
|
||||
label={field.label}
|
||||
options={field.options}
|
||||
selected={field.options.find((option) => 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);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
<div className="add-field" onClick={addField}>
|
||||
<div className="icon">
|
||||
<AddIcon />
|
||||
</div>
|
||||
<div className="label">Add Field</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectDataMapping === "multipleMachine" && element.dataBinding?.dataType === "multiple-machine" && (
|
||||
<div className="fields-wrapper design-section">
|
||||
{multipleValueFields.map((field) => (
|
||||
<DataSourceSelector
|
||||
key={field.id}
|
||||
label={field.label}
|
||||
options={field.options}
|
||||
selected={field.options.find((o) => o.id === element.dataBinding?.commonValue)?.label ?? ""}
|
||||
onSelect={(value) => {
|
||||
updateCommonValue(selectedBlock, selectedElement, value.id);
|
||||
}}
|
||||
showEyeDropper={field.showEyeDropper}
|
||||
/>
|
||||
))}
|
||||
{/* add delete */}
|
||||
{multipleSourceFields.map((field, index) => (
|
||||
<DataSourceSelector
|
||||
key={field.id}
|
||||
label={field.label}
|
||||
options={field.options}
|
||||
selected={getEventByModelUuid(selectedProduct.productUuid, (element.dataBinding?.dataSource as string[])?.[index] || "")?.modelName ?? ""}
|
||||
onSelect={(value) => {
|
||||
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);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
<div className="add-field" onClick={addField}>
|
||||
<div className="icon">
|
||||
<AddIcon />
|
||||
</div>
|
||||
<div className="label">Add Field</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ElementData;
|
||||
@@ -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<ElementEditorProps> = ({
|
||||
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<ElementEditorProps> = ({
|
||||
/>
|
||||
)}
|
||||
{selectType === "data" && (
|
||||
<div className="data-details">
|
||||
{element?.type === "label-value" && (
|
||||
<>
|
||||
<div className="design-section">
|
||||
<div className="sub-header">Data Handling</div>
|
||||
<InputWithDropDown
|
||||
label="Label"
|
||||
type="string"
|
||||
value={element.dataBinding?.label || ""}
|
||||
placeholder={"Label 1"}
|
||||
onChange={(value) => {
|
||||
updateElementData(selectedBlock, selectedElement, { label: value });
|
||||
}}
|
||||
/>
|
||||
<div className="data">
|
||||
<DataDetailedDropdown
|
||||
title="Data Source"
|
||||
placeholder="Select assets"
|
||||
sections={[
|
||||
{
|
||||
title: "Global",
|
||||
items: [{ id: "global", label: "Global", icon: <DeviceIcon /> }],
|
||||
},
|
||||
{
|
||||
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: <DeviceIcon />,
|
||||
}
|
||||
: null
|
||||
}
|
||||
onChange={(value) => {
|
||||
updateElementData(selectedBlock, selectedElement, { dataSource: value.id });
|
||||
}}
|
||||
dropDownHeader={"RT-Data"}
|
||||
eyedroper={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="data">
|
||||
<DataDetailedDropdown
|
||||
title="Data Value"
|
||||
placeholder="Select Value"
|
||||
sections={getLableValueDropdownItems(element.dataBinding?.dataSource as string | undefined)}
|
||||
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: <ParametersIcon />,
|
||||
}
|
||||
: null
|
||||
}
|
||||
onChange={(value) => {
|
||||
updateElementData(selectedBlock, selectedElement, { dataValue: value.id, label: value.label });
|
||||
}}
|
||||
dropDownHeader={"RT-Data-Value"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Data Mapping */}
|
||||
{element?.type === "graph" && (
|
||||
<div className="data-mapping">
|
||||
<div className="heading">Data Mapping</div>
|
||||
|
||||
<div className="type-switch">
|
||||
<div className={`type ${selectDataMapping === "singleMachine" ? "active" : ""}`} onClick={() => handleDataTypeSwitch("singleMachine")}>
|
||||
Single Machine
|
||||
</div>
|
||||
<div className={`type ${selectDataMapping === "multipleMachine" ? "active" : ""}`} onClick={() => handleDataTypeSwitch("multipleMachine")}>
|
||||
Multiple Machine
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectDataMapping === "singleMachine" && element.dataBinding?.dataType === "single-machine" && (
|
||||
<div className="fields-wrapper design-section">
|
||||
{singleSourceFields.map((field) => (
|
||||
<DataSourceSelector
|
||||
key={field.id}
|
||||
label={field.label}
|
||||
options={field.options}
|
||||
selected={getEventByModelUuid(selectedProduct.productUuid, element.dataBinding?.dataSource as string)?.modelName ?? ""}
|
||||
onSelect={(value) => {
|
||||
updateDataSource(selectedBlock, selectedElement, value.id);
|
||||
}}
|
||||
showEyeDropper={field.showEyeDropper}
|
||||
/>
|
||||
))}
|
||||
{/* add delete */}
|
||||
{singleValueFields.map((field, index) => (
|
||||
<DataSourceSelector
|
||||
key={field.id}
|
||||
label={field.label}
|
||||
options={field.options}
|
||||
selected={field.options.find((option) => 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);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
<div className="add-field" onClick={addField}>
|
||||
<div className="icon">
|
||||
<AddIcon />
|
||||
</div>
|
||||
<div className="label">Add Field</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectDataMapping === "multipleMachine" && element.dataBinding?.dataType === "multiple-machine" && (
|
||||
<div className="fields-wrapper design-section">
|
||||
{multipleValueFields.map((field) => (
|
||||
<DataSourceSelector
|
||||
key={field.id}
|
||||
label={field.label}
|
||||
options={field.options}
|
||||
selected={field.options.find((o) => o.id === element.dataBinding?.commonValue)?.label ?? ""}
|
||||
onSelect={(value) => {
|
||||
updateCommonValue(selectedBlock, selectedElement, value.id);
|
||||
}}
|
||||
showEyeDropper={field.showEyeDropper}
|
||||
/>
|
||||
))}
|
||||
{/* add delete */}
|
||||
{multipleSourceFields.map((field, index) => (
|
||||
<DataSourceSelector
|
||||
key={field.id}
|
||||
label={field.label}
|
||||
options={field.options}
|
||||
selected={getEventByModelUuid(selectedProduct.productUuid, (element.dataBinding?.dataSource as string[])?.[index] || "")?.modelName ?? ""}
|
||||
onSelect={(value) => {
|
||||
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);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
<div className="add-field" onClick={addField}>
|
||||
<div className="icon">
|
||||
<AddIcon />
|
||||
</div>
|
||||
<div className="label">Add Field</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<ElementData
|
||||
element={element || null}
|
||||
selectedBlock={selectedBlock}
|
||||
selectedElement={selectedElement}
|
||||
updateElementData={updateElementData}
|
||||
selectedProduct={selectedProduct}
|
||||
getEventByModelUuid={getEventByModelUuid}
|
||||
getAssetDropdownItems={getAssetDropdownItems}
|
||||
getLableValueDropdownItems={getLableValueDropdownItems}
|
||||
singleSourceFields={singleSourceFields}
|
||||
singleValueFields={singleValueFields}
|
||||
multipleSourceFields={multipleSourceFields}
|
||||
multipleValueFields={multipleValueFields}
|
||||
selectDataMapping={selectDataMapping}
|
||||
handleDataTypeSwitch={handleDataTypeSwitch}
|
||||
updateDataSource={updateDataSource}
|
||||
updateDataValue={updateDataValue}
|
||||
updateCommonValue={updateCommonValue}
|
||||
addField={addField}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -23,7 +23,7 @@ function Models({ loader }: { readonly loader: GLTFLoader }) {
|
||||
const { controls, camera } = useThree();
|
||||
const assetGroupRef = useRef<Group>(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();
|
||||
|
||||
Reference in New Issue
Block a user