feat: Implement the Simulation Dashboard editor with core components, state management, and types for blocks and elements.

This commit is contained in:
2025-12-18 11:35:49 +05:30
parent 5b9f3f1728
commit 3746fccd19
11 changed files with 202 additions and 291 deletions

View File

@@ -29,16 +29,16 @@ const AnalyzerManager: React.FC = () => {
block.elements.forEach((element) => {
// 1. Handle Label-Value (Direct DOM Manipulation)
if (element.type === "label-value") {
if (!element.dataSource || !element.dataValue) return;
if (!element.dataBinding?.dataSource || !element.dataBinding?.dataValue) return;
let value: any = "-";
// Ensure dataValue is treated as string if possible, or take first if array
const mixedDataValue = element.dataValue;
const mixedDataValue = element.dataBinding.dataValue;
const dataValueStr = Array.isArray(mixedDataValue) ? mixedDataValue[0] : mixedDataValue;
if (typeof dataValueStr !== "string") return;
if (element.dataSource === "global") {
if (element.dataBinding.dataSource === "global") {
// Handle global metrics
// Expected format: "global.systemPerformance.overallOEE" -> we need last part relative to metrics
// But dataValue usually comes as "systemPerformance.overallOEE" if dataSource is global?
@@ -58,7 +58,7 @@ const AnalyzerManager: React.FC = () => {
} else {
// Handle Asset Specific
const assetAnalysis = getAssetAnalysis(element.dataSource);
const assetAnalysis = getAssetAnalysis(element.dataBinding.dataSource as string);
if (assetAnalysis) {
value = resolvePath(assetAnalysis, dataValueStr);
}
@@ -77,13 +77,13 @@ const AnalyzerManager: React.FC = () => {
// Assuming we want to visualize historical data or current state trends.
else if (element.type === "graph") {
if (element.dataType === "single-machine") {
if (!element.dataSource || !element.dataValue) return;
if (element.dataBinding?.dataType === "single-machine") {
if (!element.dataBinding.dataSource || !element.dataBinding.dataValue) return;
// Single machine, multiple values potentially?
// element.dataValue is string[] for single-machine graph
const assetId = element.dataSource as string;
const dataKeys = element.dataValue as string[]; // e.g. ["efficiency.overallEffectiveness"]
const assetId = element.dataBinding.dataSource as string;
const dataKeys = element.dataBinding.dataValue as string[]; // e.g. ["efficiency.overallEffectiveness"]
// For now support getting the FIRST value as the main value to plot?
// Or if we track history?
@@ -138,10 +138,10 @@ const AnalyzerManager: React.FC = () => {
}
}
} else if (element.dataType === "multiple-machine") {
} else if (element.dataBinding?.dataType === "multiple-machine") {
// Multiple machines, single common value
const assetIds = element.dataSource as string[];
const commonValueKey = element.commonValue as string;
const assetIds = element.dataBinding.dataSource as string[];
const commonValueKey = element.dataBinding.commonValue as string;
if (assetIds && commonValueKey) {
// Bar chart comparing machines?

View File

@@ -21,6 +21,7 @@ import { handleBlockDragStart } from "./functions/block/handleBlockDragStart";
import { getDashBoardBlocksApi } from "../../services/visulization/dashBoard/getDashBoardBlocks";
import { upsetDashBoardBlocksApi } from "../../services/visulization/dashBoard/upsertDashBoardBlocks";
import { deleteDashBoardBlocksApi } from "../../services/visulization/dashBoard/deleteDashBoardBlocks";
import { deleteDashBoardElementsApi } from "../../services/visulization/dashBoard/deleteDashBoardElements";
const DashboardEditor: React.FC = () => {
const { simulationDashBoardStore, versionStore } = useSceneContext();
@@ -622,10 +623,22 @@ const DashboardEditor: React.FC = () => {
}
}}
handleRemoveElement={async (blockId, elementId) => {
const updatedBlocks = peekRemoveElement(blockId, elementId);
const updatedBlock = getBlockFromPeekedBlocks(updatedBlocks, blockId);
if (updatedBlock) {
// await updateBackend(updatedBlock);
try {
const data = await deleteDashBoardElementsApi({
projectId: projectId!,
versionId: selectedVersion!.versionId,
elementDatas: [{ blockUuid: blockId, elementUuid: elementId }],
});
if (data.elements.length > 0) {
data.elements.forEach((element: any) => {
if (element.message === "Element deleted successfully") {
removeElement(element.blockUuid, element.elementUuid);
}
});
}
} catch (error) {
console.error("Failed to delete element:", error);
}
}}
setSwapSource={setSwapSource}

View File

@@ -37,10 +37,8 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
updateBlockZIndex,
handleRemoveBlock,
}) => {
const [color, setColor] = useState("#000000");
return (
<div ref={blockEditorRef} className="panel block-editor-panel">
<div className="header">
@@ -55,20 +53,18 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
</div>
</div>
<div className="design-section-wrapper">
<div className="design-section">
<div className="section-header">Size</div>
<InputWithDropDown
label="Width"
value={String(currentBlock.size?.width || 400)} // Ensure the value is a string
value={String(currentBlock.size?.width || 400)} // Ensure the value is a string
placeholder={"Width"}
onChange={(newValue) => {
updateBlockSize(selectedBlock, {
...currentBlock.size!,
width: Number(newValue), // Make sure to convert the string back to a number here
})
width: Number(newValue), // Make sure to convert the string back to a number here
});
}}
/>
<InputWithDropDown
@@ -79,7 +75,7 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
updateBlockSize(selectedBlock, {
...currentBlock.size!,
height: Number(newValue),
})
});
}}
/>
</div>
@@ -100,56 +96,22 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
<div className="position-canvas">
<div className="canvas">
<div className="value padding-top">
<RenameInput
value={
getCurrentBlockStyleValue(currentBlock, "padding")
? String(getCurrentBlockStyleValue(currentBlock, "padding"))
: "120"
}
/>
<RenameInput value={getCurrentBlockStyleValue(currentBlock, "padding") ? String(getCurrentBlockStyleValue(currentBlock, "padding")) : "120"} />
</div>
<div className="value padding-right">
<RenameInput
value={
getCurrentBlockStyleValue(currentBlock, "padding")
? String(getCurrentBlockStyleValue(currentBlock, "padding"))
: "120"
}
/>
<RenameInput value={getCurrentBlockStyleValue(currentBlock, "padding") ? String(getCurrentBlockStyleValue(currentBlock, "padding")) : "120"} />
</div>
<div className="value padding-bottom">
<RenameInput
value={
getCurrentBlockStyleValue(currentBlock, "padding")
? String(getCurrentBlockStyleValue(currentBlock, "padding"))
: "120"
}
/>
<RenameInput value={getCurrentBlockStyleValue(currentBlock, "padding") ? String(getCurrentBlockStyleValue(currentBlock, "padding")) : "120"} />
</div>
<div className="value padding-left">
<RenameInput
value={
getCurrentBlockStyleValue(currentBlock, "padding")
? String(getCurrentBlockStyleValue(currentBlock, "padding"))
: "120"
}
/>
<RenameInput value={getCurrentBlockStyleValue(currentBlock, "padding") ? String(getCurrentBlockStyleValue(currentBlock, "padding")) : "120"} />
</div>
</div>
</div>
<div className="design-section-footer">
<InputWithDropDown
label="Layer"
value={String(currentBlock.zIndex || 1)}
placeholder={"Layer"}
onChange={(newValue) => updateBlockZIndex(selectedBlock, Number(newValue))}
/>
<InputWithDropDown label="Layer" value={String(currentBlock.zIndex || 1)} placeholder={"Layer"} onChange={(newValue) => updateBlockZIndex(selectedBlock, Number(newValue))} />
<InputRange
label={"Radius"}
@@ -159,10 +121,9 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
onChange={(newValue) => {
updateBlockStyle(selectedBlock, {
borderRadius: Number(newValue),
})
});
}}
/>
</div>
</div>
@@ -176,7 +137,6 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
value={rgbaToHex(getCurrentBlockStyleValue(currentBlock, "backgroundColor"))}
// onChange={(e) => setColor(e.target.value)}
onChange={(e) => handleBackgroundColorChange(currentBlock, selectedBlock, updateBlockStyle, e.target.value)}
/>
<div className="colorValue">{color}</div>
@@ -189,23 +149,10 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
value={Math.round(getAlphaFromRgba(getCurrentBlockStyleValue(currentBlock, "backgroundColor")) * 100)}
// onChange={(value: number) => handleBackgroundAlphaChange(currentBlock, selectedBlock, updateBlockStyle, Number(value))}
onChange={(value: number) => handleBlurAmountChange(selectedBlock, updateBlockStyle, Number(value))}
/>
<InputRange
label={"Blur"}
min={0}
max={8}
value={parseInt(getCurrentBlockStyleValue(currentBlock, "backdropFilter")?.match(/\d+/)?.[0] || "10")}
/>
<InputRange label={"Blur"} min={0} max={8} value={parseInt(getCurrentBlockStyleValue(currentBlock, "backdropFilter")?.match(/\d+/)?.[0] || "10")} />
</div>
</div>
</div>
);
};

View File

@@ -19,7 +19,7 @@ interface ElementDesignProps {
updateElementPosition: (blockId: string, elementId: string, position: { x: number; y: number }) => void;
updateElementPositionType: (blockId: string, elementId: string, positionType: "relative" | "absolute" | "fixed") => void;
updateElementZIndex: (blockId: string, elementId: string, zIndex: number) => void;
updateElementData: (blockId: string, elementId: string, updates: Partial<DataBinding>) => void;
updateElementData: (blockId: string, elementId: string, updates: Partial<ElementDataBinding>) => void;
updateGraphData: (blockId: string, elementId: string, newData: GraphDataPoint[]) => void;
updateGraphTitle: (blockId: string, elementId: string, title: string) => void;
updateGraphType: (blockId: string, elementId: string, type: GraphTypes) => void;

View File

@@ -20,7 +20,7 @@ interface ElementEditorProps {
updateElementPosition: (blockId: string, elementId: string, position: { x: number; y: number }) => void;
updateElementPositionType: (blockId: string, elementId: string, positionType: "relative" | "absolute" | "fixed") => void;
updateElementZIndex: (blockId: string, elementId: string, zIndex: number) => void;
updateElementData: (blockId: string, elementId: string, updates: Partial<DataBinding>) => void;
updateElementData: (blockId: string, elementId: string, updates: Partial<ElementDataBinding>) => void;
updateGraphData: (blockId: string, elementId: string, newData: GraphDataPoint[]) => void;
updateGraphTitle: (blockId: string, elementId: string, title: string) => void;
updateGraphType: (blockId: string, elementId: string, type: GraphTypes) => void;
@@ -62,7 +62,7 @@ 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.dataType === "multiple-machine" ? "multipleMachine" : "singleMachine");
const [selectDataMapping, setSelectDataMapping] = useState(element?.type === "graph" && element.dataBinding?.dataType === "multiple-machine" ? "multipleMachine" : "singleMachine");
const getAssetDropdownItems = useCallback(() => {
if (!product?.eventDatas) return [];
@@ -273,10 +273,12 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
);
const singleValueFields = useMemo(() => {
if (element?.type === "graph" && element.dataType === "single-machine") {
const dataValues = Array.isArray(element.dataValue) ? element.dataValue : [];
if (element?.type === "graph" && element.dataBinding?.dataType === "single-machine") {
const dataValues = Array.isArray(element.dataBinding.dataValue) ? element.dataBinding.dataValue : [];
const valueOptions = element.dataSource ? getLableValueDropdownItems(element.dataSource).flatMap((section) => section.items.map((item) => ({ id: item.id, label: item.label }))) : [];
const valueOptions = element.dataBinding.dataSource
? getLableValueDropdownItems(element.dataBinding.dataSource as string).flatMap((section) => section.items.map((item) => ({ id: item.id, label: item.label })))
: [];
return dataValues.map((value, index) => ({
id: `data-value-${index}`,
@@ -297,8 +299,8 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
}, [element, getLableValueDropdownItems]);
const multipleSourceFields = useMemo(() => {
if (element?.type === "graph" && element.dataType === "multiple-machine") {
const dataSources = Array.isArray(element.dataSource) ? element.dataSource : [];
if (element?.type === "graph" && element.dataBinding?.dataType === "multiple-machine") {
const dataSources = Array.isArray(element.dataBinding.dataSource) ? element.dataBinding.dataSource : [];
return dataSources.map((_, index) => ({
id: `data-source-${index}`,
@@ -324,21 +326,21 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
setSelectDataMapping(newDataType);
if (newDataType === "singleMachine" && element.dataType === "multiple-machine") {
if (newDataType === "singleMachine" && element.dataBinding?.dataType === "multiple-machine") {
updateDataType(selectedBlock, selectedElement, "single-machine");
} else if (newDataType === "multipleMachine" && element.dataType === "single-machine") {
} else if (newDataType === "multipleMachine" && element.dataBinding?.dataType === "single-machine") {
updateDataType(selectedBlock, selectedElement, "multiple-machine");
}
};
const addField = () => {
if (selectDataMapping === "singleMachine" && element?.type === "graph" && element.dataType === "single-machine") {
const currentDataValue = Array.isArray(element.dataValue) ? element.dataValue : [];
if (selectDataMapping === "singleMachine" && element?.type === "graph" && element.dataBinding?.dataType === "single-machine") {
const currentDataValue = Array.isArray(element.dataBinding.dataValue) ? element.dataBinding.dataValue : [];
const newDataValue = [...currentDataValue, ""];
updateDataValue(selectedBlock, selectedElement, newDataValue);
} else if (selectDataMapping === "multipleMachine" && element?.type === "graph" && element.dataType === "multiple-machine") {
const currentDataSource = Array.isArray(element.dataSource) ? element.dataSource : [];
} else if (selectDataMapping === "multipleMachine" && element?.type === "graph" && element.dataBinding?.dataType === "multiple-machine") {
const currentDataSource = Array.isArray(element.dataBinding.dataSource) ? element.dataBinding.dataSource : [];
const newDataSource = [...currentDataSource, ""];
updateDataSource(selectedBlock, selectedElement, newDataSource);
@@ -399,7 +401,15 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
<div className="data-details">
{element?.type === "label-value" && (
<div className="data-wrapper">
<InputWithDropDown label="Title" value={`title`} placeholder={"Label 1"} min={0.1} step={0.1} max={2} onChange={() => {}} />
<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"
@@ -415,12 +425,18 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
},
]}
value={
element.dataSource
? { id: element.dataSource, label: getEventByModelUuid(selectedProduct.productUuid, element.dataSource)?.modelName ?? "", icon: <DeviceIcon /> }
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) => {
updateDataSource(selectedBlock, selectedElement, value.id);
updateElementData(selectedBlock, selectedElement, { dataSource: value.id });
}}
dropDownHeader={"RT-Data"}
eyedroper={true}
@@ -431,10 +447,21 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
<DataDetailedDropdown
title="Data Value"
placeholder="Select Value"
sections={getLableValueDropdownItems(element.dataSource as string | undefined)}
value={element.dataValue ? { id: element.dataValue, label: element.dataValue, icon: <ParametersIcon /> } : null}
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) => {
updateDataValue(selectedBlock, selectedElement, value.id);
updateElementData(selectedBlock, selectedElement, { dataValue: value.id, label: value.label });
}}
dropDownHeader={"RT-Data-Value"}
/>
@@ -456,14 +483,14 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
</div>
</div>
{selectDataMapping === "singleMachine" && element.dataType === "single-machine" && (
{selectDataMapping === "singleMachine" && element.dataBinding?.dataType === "single-machine" && (
<div className="fields-wrapper">
{singleSourceFields.map((field) => (
<DataSourceSelector
key={field.id}
label={field.label}
options={field.options}
selected={getEventByModelUuid(selectedProduct.productUuid, element.dataSource)?.modelName ?? ""}
selected={getEventByModelUuid(selectedProduct.productUuid, element.dataBinding?.dataSource as string)?.modelName ?? ""}
onSelect={(value) => {
updateDataSource(selectedBlock, selectedElement, value.id);
}}
@@ -476,9 +503,9 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
key={field.id}
label={field.label}
options={field.options}
selected={field.options.find((option) => option.id === element.dataValue?.[index])?.label ?? ""}
selected={field.options.find((option) => option.id === element.dataBinding?.dataValue?.[index])?.label ?? ""}
onSelect={(value) => {
const currentDataValue = Array.isArray(element.dataValue) ? element.dataValue : [];
const currentDataValue = Array.isArray(element.dataBinding?.dataValue) ? element.dataBinding!.dataValue : [];
const newDataValue = [...currentDataValue];
newDataValue[index] = value.id;
@@ -498,14 +525,14 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
</div>
)}
{selectDataMapping === "multipleMachine" && element.dataType === "multiple-machine" && (
{selectDataMapping === "multipleMachine" && element.dataBinding?.dataType === "multiple-machine" && (
<div className="fields-wrapper">
{multipleValueFields.map((field) => (
<DataSourceSelector
key={field.id}
label={field.label}
options={field.options}
selected={field.options.find((o) => o.id === element.commonValue)?.label ?? ""}
selected={field.options.find((o) => o.id === element.dataBinding?.commonValue)?.label ?? ""}
onSelect={(value) => {
updateCommonValue(selectedBlock, selectedElement, value.id);
}}
@@ -518,9 +545,9 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
key={field.id}
label={field.label}
options={field.options}
selected={getEventByModelUuid(selectedProduct.productUuid, element.dataSource?.[index])?.modelName ?? ""}
selected={getEventByModelUuid(selectedProduct.productUuid, (element.dataBinding?.dataSource as string[])?.[index] || "")?.modelName ?? ""}
onSelect={(value) => {
const current = Array.isArray(element.dataSource) ? element.dataSource : [];
const current = Array.isArray(element.dataBinding?.dataSource) ? element.dataBinding!.dataSource : [];
const next = [...current];
next[index] = value.id;

View File

@@ -1,57 +1,12 @@
import { dataModelManager } from "../../data/dataModel";
import type { UIElement } from "../../../../types/exportedTypes";
export const resolveElementValue = (element: UIElement) => {
// Implementation from original code
if (!element.data) {
if (!element.dataBinding) {
return { value: "No data" };
}
const binding = element.data;
switch (binding.dataSource) {
case "static":
return {
label: binding.label,
value: binding.staticValue,
graphData: element.graphData,
};
case "dynamic":
if (binding.dynamicKey) {
const dynamicValue = dataModelManager.getValue(binding.dynamicKey);
if (dynamicValue !== undefined) {
return {
label: binding.label,
value: dynamicValue as string,
graphData: dataModelManager.parseGraphData(dynamicValue),
};
}
}
break;
case "formula":
if (binding.formula) {
const formulaValue = dataModelManager.evaluateFormula(binding.formula);
return {
label: binding.label,
value: formulaValue,
graphData: element.graphData,
};
}
break;
case "api":
return {
label: binding.label,
value: binding.staticValue || "API Data",
graphData: element.graphData,
};
}
return {
label: binding.label,
value: binding.staticValue,
label: element.dataBinding.label,
graphData: element.graphData,
};
};

View File

@@ -15,6 +15,7 @@ type InputWithDropDownProps = {
onChange: (newValue: string) => void;
editableLabel?: boolean;
placeholder?: string;
type?: string;
};
const InputWithDropDown: React.FC<InputWithDropDownProps> = ({
@@ -31,6 +32,7 @@ const InputWithDropDown: React.FC<InputWithDropDownProps> = ({
onChange,
editableLabel = false,
placeholder = "Inherit",
type = "number",
}) => {
const [openDropdown, setOpenDropdown] = useState(false);
@@ -54,7 +56,7 @@ const InputWithDropDown: React.FC<InputWithDropDownProps> = ({
min={min}
max={max}
step={step}
type="number"
type={type}
value={value} // Controlled input
disabled={disabled}
onChange={(e) => onChange(e.target.value)}
@@ -62,19 +64,12 @@ const InputWithDropDown: React.FC<InputWithDropDownProps> = ({
/>
{activeOption && (
<div
className="dropdown"
onClick={() => setOpenDropdown((prev) => !prev)}
>
<div className="dropdown" onClick={() => setOpenDropdown((prev) => !prev)}>
<div className="active-option">{activeOption}</div>
{options && openDropdown && (
<div className="dropdown-options-list">
{options.map((option, index) => (
<div
key={index}
className="dropdown-option"
onClick={onClick}
>
<div key={index} className="dropdown-option" onClick={onClick}>
{option}
</div>
))}
@@ -88,7 +83,3 @@ const InputWithDropDown: React.FC<InputWithDropDownProps> = ({
};
export default InputWithDropDown;

View File

@@ -0,0 +1,28 @@
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`;
export const deleteDashBoardElementsApi = async ({ projectId, versionId, elementDatas }: { projectId: string; versionId: string; elementDatas: { blockUuid: string; elementUuid: string }[] }) => {
try {
const response = await fetch(`${url_Backend_dwinzo}/api/V1/simulation/elements`, {
method: "PATCH",
headers: {
Authorization: "Bearer <access_token>",
"Content-Type": "application/json",
token: localStorage.getItem("token") || "",
refresh_token: localStorage.getItem("refreshToken") || "",
},
body: JSON.stringify({ projectId, versionId, elementDatas }),
});
const newAccessToken = response.headers.get("x-access-token");
if (newAccessToken) {
localStorage.setItem("token", newAccessToken);
}
if (!response.ok) {
echo.error("Failed to delete dashBoardElements");
}
return await response.json();
} catch {
echo.error("Failed to delete dashBoardElements");
}
};

View File

@@ -45,7 +45,7 @@ interface SimulationDashboardStore {
updateElementZIndex: (blockId: string, elementId: string, zIndex: number) => void;
// Element data operations
updateElementData: (blockId: string, elementId: string, updates: Partial<DataBinding>) => void;
updateElementData: (blockId: string, elementId: string, updates: Partial<ElementDataBinding>) => void;
updateGraphData: (blockId: string, elementId: string, newData: GraphDataPoint[]) => void;
updateGraphTitle: (blockId: string, elementId: string, title: string) => void;
updateGraphType: (blockId: string, elementId: string, graphType: GraphTypes) => void;
@@ -70,7 +70,7 @@ interface SimulationDashboardStore {
peekUpdateElementPosition: (blockId: string, elementId: string, position: Position) => Block[];
peekUpdateElementPositionType: (blockId: string, elementId: string, positionType: "relative" | "absolute" | "fixed") => Block[];
peekUpdateElementZIndex: (blockId: string, elementId: string, zIndex: number) => Block[];
peekUpdateElementData: (blockId: string, elementId: string, updates: Partial<DataBinding>) => Block[];
peekUpdateElementData: (blockId: string, elementId: string, updates: Partial<ElementDataBinding>) => Block[];
peekUpdateGraphData: (blockId: string, elementId: string, newData: GraphDataPoint[]) => Block[];
peekUpdateGraphTitle: (blockId: string, elementId: string, title: string) => Block[];
peekUpdateGraphType: (blockId: string, elementId: string, graphType: GraphTypes) => Block[];
@@ -244,6 +244,7 @@ export const createSimulationDashboardStore = () => {
staticValue: "",
label: undefined,
},
dataBinding: {},
};
switch (type) {
@@ -251,9 +252,6 @@ export const createSimulationDashboardStore = () => {
newElement = {
...commonProps,
type: "label-value",
title: "Label Value",
dataSource: "",
dataValue: "",
style: {
color: "#ffffff",
fontSize: 14,
@@ -266,10 +264,11 @@ export const createSimulationDashboardStore = () => {
labelColor: "#ffffff",
valueColor: "#ffffff",
},
data: {
...commonProps.data,
staticValue: "Value",
label: "Label",
dataBinding: {
label: "Label Value",
dataSource: "",
dataValue: "",
dataType: "single-machine",
},
size: { width: 200, height: 60 },
};
@@ -299,18 +298,22 @@ export const createSimulationDashboardStore = () => {
if (dataType === "multiple-machine") {
newElement = {
...baseGraphProps,
dataType: "multiple-machine" as const,
title: "Multi Machine Chart",
dataSource: [],
commonValue: "",
dataBinding: {
dataType: "multiple-machine" as const,
label: "Multi Machine Chart",
dataSource: [],
commonValue: "",
},
};
} else {
newElement = {
...baseGraphProps,
dataType: "single-machine" as const,
title: "Single Machine Chart",
dataSource: "",
dataValue: [],
dataBinding: {
dataType: "single-machine" as const,
label: "Single Machine Chart",
dataSource: "",
dataValue: [],
},
};
}
break;
@@ -324,10 +327,6 @@ export const createSimulationDashboardStore = () => {
fontSize: 14,
textAlign: "left" as const,
},
data: {
...commonProps.data,
staticValue: "Text",
},
size: { width: 200, height: 40 },
};
break;
@@ -444,8 +443,8 @@ export const createSimulationDashboardStore = () => {
const block = state.blocks.find((b) => b.blockUuid === blockId);
if (block) {
const element = block.elements.find((el) => el.elementUuid === elementId);
if (element?.data) {
element.data = { ...element.data, ...updates };
if (element?.dataBinding) {
element.dataBinding = { ...element.dataBinding, ...updates };
}
}
});
@@ -618,9 +617,6 @@ export const createSimulationDashboardStore = () => {
newElement = {
...commonProps,
type: "label-value",
title: "Label Value",
dataSource: "",
dataValue: "metric-1",
style: {
color: "#ffffff",
fontSize: 14,
@@ -633,10 +629,10 @@ export const createSimulationDashboardStore = () => {
labelColor: "#ffffff",
valueColor: "#ffffff",
},
data: {
...commonProps.data,
staticValue: "Value",
label: "Label",
dataBinding: {
label: "Label Value",
dataSource: "",
dataValue: "",
},
size: { width: 200, height: 60 },
};
@@ -666,18 +662,22 @@ export const createSimulationDashboardStore = () => {
if (dataType === "multiple-machine") {
newElement = {
...baseGraphProps,
dataType: "multiple-machine" as const,
title: "Multi Machine Chart",
dataSource: [],
commonValue: "",
dataBinding: {
dataType: "multiple-machine" as const,
label: "Multi Machine Chart",
dataSource: [],
commonValue: "",
},
};
} else {
newElement = {
...baseGraphProps,
dataType: "single-machine" as const,
title: "Single Machine Chart",
dataSource: "",
dataValue: [],
dataBinding: {
dataType: "single-machine" as const,
label: "Single Machine Chart",
dataSource: "",
dataValue: [],
},
};
}
break;
@@ -691,10 +691,6 @@ export const createSimulationDashboardStore = () => {
fontSize: 14,
textAlign: "left" as const,
},
data: {
...commonProps.data,
staticValue: "Text",
},
size: { width: 200, height: 40 },
};
break;
@@ -806,8 +802,8 @@ export const createSimulationDashboardStore = () => {
const block = blocks.find((b) => b.blockUuid === blockId);
if (block) {
const element = block.elements.find((el) => el.elementUuid === elementId);
if (element?.data) {
element.data = { ...element.data, ...updates };
if (element?.dataBinding) {
element.dataBinding = { ...element.dataBinding, ...updates };
}
}
return blocks;
@@ -851,7 +847,7 @@ export const createSimulationDashboardStore = () => {
return blocks;
},
peekUpdateDataType: (blockId, elementId, dataType) => {
peekUpdateDataType: (blockId: string, elementId: string, dataType: DataType) => {
const blocks = cloneBlocks(get().blocks);
const block = blocks.find((b) => b.blockUuid === blockId);
@@ -861,70 +857,58 @@ export const createSimulationDashboardStore = () => {
if (index === -1) return blocks;
const element = block.elements[index];
if (element.type !== "graph" || element.dataType === dataType) return blocks;
let newElement: UIElement;
if (element.type !== "graph" || !element.dataBinding || element.dataBinding.dataType === dataType) return blocks;
// We are updating the dataBinding object deeply
if (dataType === "single-machine") {
const { commonValue, ...rest } = element as Extract<UIElement, { type: "graph"; dataType: "multiple-machine" }>;
newElement = {
...rest,
dataType: "single-machine",
dataSource: "",
dataValue: [],
};
element.dataBinding.dataType = "single-machine";
element.dataBinding.dataSource = "";
element.dataBinding.dataValue = [];
delete element.dataBinding.commonValue;
} else {
const { dataValue, ...rest } = element as Extract<UIElement, { type: "graph"; dataType: "single-machine" }>;
newElement = {
...rest,
dataType: "multiple-machine",
dataSource: [],
commonValue: "",
};
element.dataBinding.dataType = "multiple-machine";
element.dataBinding.dataSource = [];
element.dataBinding.commonValue = "";
delete element.dataBinding.dataValue;
}
block.elements[index] = newElement;
return blocks;
},
peekUpdateCommonValue: (blockId, elementId, commonValue) => {
peekUpdateCommonValue: (blockId: string, elementId: string, commonValue: string) => {
const blocks = cloneBlocks(get().blocks);
const block = blocks.find((b) => b.blockUuid === blockId);
if (block) {
const element = block.elements.find((el) => el.elementUuid === elementId);
if (element && element.type === "graph" && element.dataType === "multiple-machine") {
element.commonValue = commonValue;
if (element && element.dataBinding) {
element.dataBinding.commonValue = commonValue;
}
}
return blocks;
},
peekUpdateDataValue: (blockId, elementId, dataValue) => {
peekUpdateDataValue: (blockId: string, elementId: string, dataValue: string | string[]) => {
const blocks = cloneBlocks(get().blocks);
const block = blocks.find((b) => b.blockUuid === blockId);
if (block) {
const element = block.elements.find((el) => el.elementUuid === elementId);
if (element && element.type === "graph" && element.dataType === "single-machine") {
element.dataValue = dataValue as string[];
} else if (element && element.type === "label-value") {
element.dataValue = dataValue as string;
if (element && element.dataBinding) {
element.dataBinding.dataValue = dataValue;
}
}
return blocks;
},
peekUpdateDataSource: (blockId, elementId, dataSource) => {
peekUpdateDataSource: (blockId: string, elementId: string, dataSource: string | string[]) => {
const blocks = cloneBlocks(get().blocks);
const block = blocks.find((b) => b.blockUuid === blockId);
if (block) {
const element = block.elements.find((el) => el.elementUuid === elementId);
if (element?.type === "label-value") {
element.dataValue = "";
if (element?.type === "label-value" && element.dataBinding) {
element.dataBinding.dataValue = "";
}
if (element) {
element.dataSource = dataSource;
if (element && element.dataBinding) {
element.dataBinding.dataSource = dataSource;
}
}
return blocks;

View File

@@ -21,39 +21,10 @@ export type UIElement = {
graphType?: GraphTypes;
graphTitle?: string;
style: ExtendedCSSProperties;
data?: DataBinding;
dataBinding?: ElementDataBinding;
position?: Position;
positionType?: "relative" | "absolute" | "fixed";
zIndex?: number;
graphData?: GraphDataPoint[];
size?: Size;
} & (
| {
type: "label-value";
title: string;
dataSource: string;
dataValue: string;
}
| {
type: "graph";
dataType: "single-machine";
title: string;
dataSource: string;
dataValue: string[];
}
| {
type: "graph";
dataType: "multiple-machine";
title: string;
dataSource: string[];
commonValue: string;
}
| {
type: Exclude<UIType, "label-value" | "graph">;
title?: never;
dataSource?: never;
dataValue?: never;
commonValue?: never;
dataType?: never;
}
);
};

View File

@@ -1,16 +1,3 @@
type DataSourceType = "static" | "dynamic" | "api" | "formula";
type DataBinding = {
key: string;
label?: string;
value?: string | number;
dataSource: DataSourceType;
staticValue?: string | number;
dynamicKey?: string;
formula?: string;
apiEndpoint?: string;
};
type DataModelValue = string | number | GraphDataPoint[];
type DataModel = Record<string, DataModelValue>;
@@ -19,6 +6,14 @@ type UIType = "label-value" | "text" | "graph" | "icon";
type DataType = "single-machine" | "multiple-machine";
type ElementDataBinding = {
label?: string;
dataSource?: string | string[];
dataValue?: string | string[];
commonValue?: string;
dataType?: "single-machine" | "multiple-machine";
}
type Position = {
x: number;
y: number;