feat: Introduce Simulation Dashboard with element management, data binding, and analysis capabilities.
This commit is contained in:
@@ -32,7 +32,7 @@ const AnalyzerManager: React.FC = () => {
|
||||
useEffect(() => {
|
||||
blocksRef.current.forEach((block) => {
|
||||
block.elements.forEach((element) => {
|
||||
if (element.type === "graph" && element.dataBinding?.dataType === "single-machine") {
|
||||
if (element.type === "graph" && (element.dataBinding?.dataType === "single-machine" || element.dataBinding?.dataType === "global")) {
|
||||
// Check for default data (ElementContent uses Jan, Feb etc as fallback)
|
||||
// If graphData is undefined, ElementContent shows defaults. We explicitly set to [] to show empty.
|
||||
// If graphData has "Jan" data, we also clear it.
|
||||
@@ -154,12 +154,7 @@ const AnalyzerManager: React.FC = () => {
|
||||
const rawDataValue = element.dataBinding.dataValue;
|
||||
const dataKeys = Array.isArray(rawDataValue) ? rawDataValue : [rawDataValue as string];
|
||||
|
||||
let dataObject: any = null;
|
||||
if (assetId === "global") {
|
||||
dataObject = getSystemMetrics();
|
||||
} else {
|
||||
dataObject = getAssetAnalysis(assetId);
|
||||
}
|
||||
const dataObject = getAssetAnalysis(assetId);
|
||||
|
||||
if (dataObject) {
|
||||
let newGraphData: GraphDataPoint[] = [];
|
||||
@@ -171,7 +166,51 @@ const AnalyzerManager: React.FC = () => {
|
||||
|
||||
const dataPoint: GraphDataPoint = { name: timeStr, value: 0 };
|
||||
dataKeys.forEach((key) => {
|
||||
const path = assetId === "global" && key.startsWith("global.") ? key.replace("global.", "") : key;
|
||||
const val = resolvePath(dataObject, key);
|
||||
dataPoint[key] = typeof val === "number" ? val : 0;
|
||||
});
|
||||
|
||||
history.push(dataPoint);
|
||||
if (history.length > 20) history.shift();
|
||||
|
||||
newGraphData = [...history];
|
||||
lineChartHistory.current.set(element.elementUuid, history);
|
||||
} else {
|
||||
newGraphData = dataKeys.map((key) => {
|
||||
const val = resolvePath(dataObject, key);
|
||||
return {
|
||||
// Make the key readable or just use it as name
|
||||
name: key.split(".").pop() || key,
|
||||
value: typeof val === "number" ? val : 0,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Deep check to avoid unnecessary updates
|
||||
if (JSON.stringify(newGraphData) !== JSON.stringify(element.graphData)) {
|
||||
updateGraphData(block.blockUuid, element.elementUuid, newGraphData);
|
||||
}
|
||||
}
|
||||
} else if (element.dataBinding?.dataType === "global") {
|
||||
if (!element.dataBinding.dataSource || !element.dataBinding.dataValue) return;
|
||||
|
||||
// Ensure dataValue is an array
|
||||
const rawDataValue = element.dataBinding.dataValue;
|
||||
const dataKeys = Array.isArray(rawDataValue) ? rawDataValue : [rawDataValue as string];
|
||||
|
||||
const dataObject = getSystemMetrics();
|
||||
|
||||
if (dataObject) {
|
||||
let newGraphData: GraphDataPoint[] = [];
|
||||
|
||||
if (element.graphType === "line") {
|
||||
const history: GraphDataPoint[] = lineChartHistory.current.get(element.elementUuid) || [];
|
||||
const now = new Date();
|
||||
const timeStr = now.toLocaleTimeString([], { hour12: false });
|
||||
|
||||
const dataPoint: GraphDataPoint = { name: timeStr, value: 0 };
|
||||
dataKeys.forEach((key) => {
|
||||
const path = key.startsWith("global.") ? key.replace("global.", "") : key;
|
||||
const val = resolvePath(dataObject, path);
|
||||
dataPoint[key] = typeof val === "number" ? val : 0;
|
||||
});
|
||||
@@ -183,7 +222,7 @@ const AnalyzerManager: React.FC = () => {
|
||||
lineChartHistory.current.set(element.elementUuid, history);
|
||||
} else {
|
||||
newGraphData = dataKeys.map((key) => {
|
||||
const path = assetId === "global" && key.startsWith("global.") ? key.replace("global.", "") : key;
|
||||
const path = key.startsWith("global.") ? key.replace("global.", "") : key;
|
||||
const val = resolvePath(dataObject, path);
|
||||
return {
|
||||
// Make the key readable or just use it as name
|
||||
|
||||
@@ -44,7 +44,7 @@ const ElementContent: React.FC<ElementContentProps> = ({ element, resolvedData }
|
||||
{ name: "Jun", value: 900 },
|
||||
];
|
||||
|
||||
if (element.dataBinding?.dataType === "single-machine" && element.dataBinding.dataValue) {
|
||||
if ((element.dataBinding?.dataType === "single-machine" || element.dataBinding?.dataType === "global") && element.dataBinding.dataValue) {
|
||||
const keys = Array.isArray(element.dataBinding.dataValue) ? element.dataBinding.dataValue : [element.dataBinding.dataValue as string];
|
||||
return defaultData.map((item, i) => {
|
||||
const newItem: any = { ...item };
|
||||
@@ -145,12 +145,12 @@ const ElementContent: React.FC<ElementContentProps> = ({ element, resolvedData }
|
||||
<XAxis dataKey="name" stroke="rgba(255,255,255,0.6)" fontSize={12} />
|
||||
<YAxis stroke="rgba(255,255,255,0.6)" fontSize={12} />
|
||||
<Tooltip {...tooltipStyle} />
|
||||
{element.dataBinding?.dataType === "single-machine" &&
|
||||
{(element.dataBinding?.dataType === "single-machine" || element.dataBinding?.dataType === "global") &&
|
||||
element.dataBinding.dataValue &&
|
||||
(!Array.isArray(element.dataBinding.dataValue) || element.dataBinding.dataValue.length > 0) ? (
|
||||
(Array.isArray(element.dataBinding.dataValue) ? element.dataBinding.dataValue : [element.dataBinding.dataValue as string]).map((key, index) => (
|
||||
<Line
|
||||
key={key}
|
||||
key={`${key}-${index}`}
|
||||
type="monotone"
|
||||
dataKey={key}
|
||||
stroke={COLORS[index % COLORS.length]}
|
||||
|
||||
@@ -16,10 +16,11 @@ interface ElementDataProps {
|
||||
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 }> }>;
|
||||
globalValueFields: 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;
|
||||
selectDataMapping: "singleMachine" | "multipleMachine" | "global";
|
||||
handleDataTypeSwitch: (newDataType: "singleMachine" | "multipleMachine" | "global") => 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;
|
||||
@@ -38,6 +39,7 @@ const ElementData: React.FC<ElementDataProps> = ({
|
||||
getLableValueDropdownItems,
|
||||
singleSourceFields = [],
|
||||
singleValueFields = [],
|
||||
globalValueFields = [],
|
||||
multipleSourceFields = [],
|
||||
multipleValueFields = [],
|
||||
selectDataMapping = "singleMachine",
|
||||
@@ -180,6 +182,9 @@ const ElementData: React.FC<ElementDataProps> = ({
|
||||
/>
|
||||
|
||||
<div className="type-switch">
|
||||
<div className={`type ${selectDataMapping === "global" ? "active" : ""}`} onClick={() => handleDataTypeSwitch("global")}>
|
||||
Global
|
||||
</div>
|
||||
<div className={`type ${selectDataMapping === "singleMachine" ? "active" : ""}`} onClick={() => handleDataTypeSwitch("singleMachine")}>
|
||||
Single Machine
|
||||
</div>
|
||||
@@ -188,6 +193,54 @@ const ElementData: React.FC<ElementDataProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectDataMapping === "global" && element.dataBinding?.dataType === "global" && (
|
||||
<div className="fields-wrapper design-section">
|
||||
{globalValueFields.map((field, index) => (
|
||||
<DataSourceSelector
|
||||
key={field.id}
|
||||
label={field.label}
|
||||
options={getFilteredOptions(field.options, element.dataBinding?.dataValue, index)}
|
||||
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}
|
||||
eyeDropperActive={isEyedropperActive("dataValue", index)}
|
||||
onEyeDrop={() => handleEyeDrop("dataValue", index)}
|
||||
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);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
{globalValueFields.length < totalValueOptions && (
|
||||
<div className="add-field" onClick={addField}>
|
||||
<div className="add-icon">
|
||||
<AddIcon />
|
||||
</div>
|
||||
<div className="label">Add Field</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectDataMapping === "singleMachine" && element.dataBinding?.dataType === "single-machine" && (
|
||||
<div className="fields-wrapper design-section">
|
||||
{singleSourceFields.map((field) => (
|
||||
|
||||
@@ -22,7 +22,7 @@ interface ElementEditorProps {
|
||||
updateGraphTitle: (blockId: string, elementId: string, title: string) => void;
|
||||
updateGraphType: (blockId: string, elementId: string, type: GraphTypes) => void;
|
||||
updateTextValue: (blockId: string, elementId: string, textValue: string) => void;
|
||||
updateDataType: (blockId: string, elementId: string, dataType: "single-machine" | "multiple-machine") => void;
|
||||
updateDataType: (blockId: string, elementId: string, dataType: "single-machine" | "multiple-machine" | "global") => void;
|
||||
updateCommonValue: (blockId: string, elementId: string, commonValue: string) => void;
|
||||
updateDataValue: (blockId: string, elementId: string, dataValue: string | string[]) => void;
|
||||
updateDataSource: (blockId: string, elementId: string, dataSource: string | string[]) => void;
|
||||
@@ -56,8 +56,12 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
|
||||
const { getElementById } = simulationDashBoardStore();
|
||||
const element = getElementById(selectedBlock, selectedElement);
|
||||
const [selectType, setSelectType] = useState("design");
|
||||
const [selectDataMapping, setSelectDataMapping] = useState<"singleMachine" | "multipleMachine">(
|
||||
element?.type === "graph" && element.dataBinding?.dataType === "multiple-machine" ? "multipleMachine" : "singleMachine"
|
||||
const [selectDataMapping, setSelectDataMapping] = useState<"singleMachine" | "multipleMachine" | "global">(
|
||||
element?.type === "graph" && element.dataBinding?.dataType === "multiple-machine"
|
||||
? "multipleMachine"
|
||||
: element?.type === "graph" && element.dataBinding?.dataType === "global"
|
||||
? "global"
|
||||
: "singleMachine"
|
||||
);
|
||||
|
||||
// Use shared position from VisualizationStore
|
||||
@@ -241,7 +245,7 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
|
||||
icon: <DeviceIcon />,
|
||||
}));
|
||||
|
||||
return [{ id: "global", label: "Global System", icon: <DeviceIcon /> }, ...assetItems];
|
||||
return [...assetItems];
|
||||
}, [product?.eventDatas]);
|
||||
|
||||
const getLableValueDropdownItems = useCallback(
|
||||
@@ -491,7 +495,7 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
|
||||
|
||||
return dataValues.map((value, index) => ({
|
||||
id: `data-value-${index}`,
|
||||
label: `Value ${index + 1}`,
|
||||
label: `Data Value ${index + 1}`,
|
||||
showEyeDropper: false,
|
||||
options: valueOptions,
|
||||
}));
|
||||
@@ -500,7 +504,32 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
|
||||
return [
|
||||
{
|
||||
id: "data-value",
|
||||
label: "Value",
|
||||
label: "Data Value",
|
||||
showEyeDropper: false,
|
||||
options: [],
|
||||
},
|
||||
];
|
||||
}, [element, getLableValueDropdownItems]);
|
||||
|
||||
const globalValueFields = useMemo(() => {
|
||||
if (element?.type === "graph" && element.dataBinding?.dataType === "global") {
|
||||
const dataValues = Array.isArray(element.dataBinding.dataValue) ? element.dataBinding.dataValue : element.dataBinding.dataValue ? [element.dataBinding.dataValue] : [];
|
||||
|
||||
// Implicitly use 'global' as source
|
||||
const valueOptions = getLableValueDropdownItems("global").flatMap((section) => section.items.map((item) => ({ id: item.id, label: item.label })));
|
||||
|
||||
return dataValues.map((value, index) => ({
|
||||
id: `data-value-${index}`,
|
||||
label: `Data Value ${index + 1}`,
|
||||
showEyeDropper: false,
|
||||
options: valueOptions,
|
||||
}));
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
id: "data-value",
|
||||
label: "Data Value",
|
||||
showEyeDropper: false,
|
||||
options: [],
|
||||
},
|
||||
@@ -513,7 +542,7 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
|
||||
|
||||
return dataSources.map((_, index) => ({
|
||||
id: `data-source-${index}`,
|
||||
label: `Source ${index + 1}`,
|
||||
label: `Data Source ${index + 1}`,
|
||||
showEyeDropper: true,
|
||||
options: getAssetDropdownItems().map((item) => ({
|
||||
id: item.id,
|
||||
@@ -526,19 +555,21 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
|
||||
}, [element, getAssetDropdownItems]);
|
||||
|
||||
const multipleValueFields = useMemo(
|
||||
() => [{ id: "data-value", label: "Value", showEyeDropper: false, options: getCommonValueDropdownItems().map((item) => ({ id: item.id, label: item.label })) }],
|
||||
() => [{ id: "data-value", label: "Data Value", showEyeDropper: false, options: getCommonValueDropdownItems().map((item) => ({ id: item.id, label: item.label })) }],
|
||||
[getCommonValueDropdownItems]
|
||||
);
|
||||
|
||||
const handleDataTypeSwitch = (newDataType: "singleMachine" | "multipleMachine") => {
|
||||
const handleDataTypeSwitch = (newDataType: "singleMachine" | "multipleMachine" | "global") => {
|
||||
if (!element || element.type !== "graph") return;
|
||||
|
||||
setSelectDataMapping(newDataType);
|
||||
|
||||
if (newDataType === "singleMachine" && element.dataBinding?.dataType === "multiple-machine") {
|
||||
if (newDataType === "singleMachine" && element.dataBinding?.dataType !== "single-machine") {
|
||||
updateDataType(selectedBlock, selectedElement, "single-machine");
|
||||
} else if (newDataType === "multipleMachine" && element.dataBinding?.dataType === "single-machine") {
|
||||
} else if (newDataType === "multipleMachine" && element.dataBinding?.dataType !== "multiple-machine") {
|
||||
updateDataType(selectedBlock, selectedElement, "multiple-machine");
|
||||
} else if (newDataType === "global" && element.dataBinding?.dataType !== "global") {
|
||||
updateDataType(selectedBlock, selectedElement, "global");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -553,6 +584,11 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
|
||||
const newDataSource = [...currentDataSource, ""];
|
||||
|
||||
updateDataSource(selectedBlock, selectedElement, newDataSource);
|
||||
} else if (selectDataMapping === "global" && element?.type === "graph" && element.dataBinding?.dataType === "global") {
|
||||
const currentDataValue = Array.isArray(element.dataBinding.dataValue) ? element.dataBinding.dataValue : element.dataBinding.dataValue ? [element.dataBinding.dataValue] : [];
|
||||
const newDataValue = [...currentDataValue, ""];
|
||||
|
||||
updateDataValue(selectedBlock, selectedElement, newDataValue);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -629,6 +665,7 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
|
||||
getLableValueDropdownItems={getLableValueDropdownItems}
|
||||
singleSourceFields={singleSourceFields}
|
||||
singleValueFields={singleValueFields}
|
||||
globalValueFields={globalValueFields}
|
||||
multipleSourceFields={multipleSourceFields}
|
||||
multipleValueFields={multipleValueFields}
|
||||
selectDataMapping={selectDataMapping}
|
||||
|
||||
@@ -7,77 +7,6 @@ const TimeManager = () => {
|
||||
const { speed } = useAnimationPlaySpeed();
|
||||
const { simulationTimeRef } = useSceneContext();
|
||||
|
||||
const lastSpeedUpdateRef = useRef<number>(Date.now());
|
||||
const prevSpeedRef = useRef<number>(speed);
|
||||
|
||||
// Reset accumulated time when simulation stops
|
||||
useEffect(() => {
|
||||
if (!isPlaying) {
|
||||
simulationTimeRef.current = 0;
|
||||
lastSpeedUpdateRef.current = Date.now();
|
||||
} else {
|
||||
lastSpeedUpdateRef.current = Date.now();
|
||||
}
|
||||
}, [isPlaying, simulationTimeRef]);
|
||||
|
||||
// Track accumulated simulation time
|
||||
useEffect(() => {
|
||||
if (!isPlaying) {
|
||||
prevSpeedRef.current = speed;
|
||||
return;
|
||||
}
|
||||
|
||||
// Set up an interval to update time continuously
|
||||
const intervalId = setInterval(() => {
|
||||
const now = Date.now();
|
||||
const deltaReal = now - lastSpeedUpdateRef.current;
|
||||
|
||||
// Add time segment using current speed
|
||||
// We use interval instead of useFrame because TimeManager might run outside of Canvas or we want independent tick
|
||||
// Actually, if we want frame-perfect sync, useFrame is better.
|
||||
// But this component might not be inside Canvas in all setups (though we plan to put it there).
|
||||
// Let's stick to useEffect loop for now to mirror the logic we had, or better yet, use requestAnimationFrame
|
||||
|
||||
simulationTimeRef.current += deltaReal * Math.max(1, speed);
|
||||
lastSpeedUpdateRef.current = now;
|
||||
}, 16); // ~60fps
|
||||
|
||||
return () => clearInterval(intervalId);
|
||||
}, [speed, isPlaying, simulationTimeRef]);
|
||||
|
||||
// Handle speed changes accurately by updating before speed changes
|
||||
// The interval handles the continuous update. This effect ensures we capture the exact moment speed changes if we want higher precision,
|
||||
// but the interval driven approach with small steps is usually sufficient for "game time".
|
||||
// Actually, "prevSpeedRef" logic was better for exact transitions.
|
||||
|
||||
// Let's refine the logic to match what we did in Analyzer:
|
||||
|
||||
useEffect(() => {
|
||||
if (!isPlaying) {
|
||||
prevSpeedRef.current = speed;
|
||||
return;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const deltaReal = now - lastSpeedUpdateRef.current;
|
||||
|
||||
// Add time segment using previous speed (before this update)
|
||||
// Wait, if we use interval, we are continuously updating.
|
||||
// If we mix interval and this effect, we double count.
|
||||
|
||||
// Let's ONLY use requestAnimationFrame (or a tight interval) to update time.
|
||||
// And update refs.
|
||||
}, [speed, isPlaying]); // This is tricky with intervals.
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
// Re-implementing correctly with requestAnimationFrame for smooth time
|
||||
const TimeManagerRAF = () => {
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
const { speed } = useAnimationPlaySpeed();
|
||||
const { simulationTimeRef } = useSceneContext();
|
||||
|
||||
const lastTimeRef = useRef<number>(Date.now());
|
||||
|
||||
useEffect(() => {
|
||||
@@ -109,4 +38,4 @@ const TimeManagerRAF = () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
export default TimeManagerRAF;
|
||||
export default TimeManager;
|
||||
|
||||
@@ -75,7 +75,7 @@ interface SimulationDashboardStore {
|
||||
peekUpdateGraphTitle: (blockId: string, elementId: string, title: string) => Block[];
|
||||
peekUpdateGraphType: (blockId: string, elementId: string, graphType: GraphTypes) => Block[];
|
||||
peekUpdateTextValue: (blockId: string, elementId: string, textValue: string) => Block[];
|
||||
peekUpdateDataType: (blockId: string, elementId: string, dataType: "single-machine" | "multiple-machine") => Block[];
|
||||
peekUpdateDataType: (blockId: string, elementId: string, dataType: "global" | "single-machine" | "multiple-machine") => Block[];
|
||||
peekUpdateCommonValue: (blockId: string, elementId: string, commonValue: string) => Block[];
|
||||
peekUpdateDataValue: (blockId: string, elementId: string, dataValue: string | string[]) => Block[];
|
||||
peekUpdateDataSource: (blockId: string, elementId: string, dataSource: string | string[]) => Block[];
|
||||
@@ -892,11 +892,16 @@ export const createSimulationDashboardStore = () => {
|
||||
element.dataBinding.dataSource = "";
|
||||
element.dataBinding.dataValue = [];
|
||||
delete element.dataBinding.commonValue;
|
||||
} else {
|
||||
} else if (dataType === "multiple-machine") {
|
||||
element.dataBinding.dataType = "multiple-machine";
|
||||
element.dataBinding.dataSource = [];
|
||||
element.dataBinding.commonValue = "";
|
||||
delete element.dataBinding.dataValue;
|
||||
} else if (dataType === "global") {
|
||||
element.dataBinding.dataType = "global";
|
||||
element.dataBinding.dataSource = "global";
|
||||
element.dataBinding.dataValue = [];
|
||||
delete element.dataBinding.commonValue;
|
||||
}
|
||||
|
||||
return blocks;
|
||||
|
||||
4
app/src/types/simulationDashboard.d.ts
vendored
4
app/src/types/simulationDashboard.d.ts
vendored
@@ -4,7 +4,7 @@ type DataModel = Record<string, DataModelValue>;
|
||||
|
||||
type UIType = "label-value" | "text" | "graph" | "icon";
|
||||
|
||||
type DataType = "single-machine" | "multiple-machine";
|
||||
type DataType = "global" | "single-machine" | "multiple-machine";
|
||||
|
||||
type ElementDataBinding = {
|
||||
label?: string;
|
||||
@@ -12,7 +12,7 @@ type ElementDataBinding = {
|
||||
dataValue?: string | string[];
|
||||
commonValue?: string;
|
||||
dataType?: DataType;
|
||||
}
|
||||
};
|
||||
|
||||
type Position = {
|
||||
x: number;
|
||||
|
||||
Reference in New Issue
Block a user