From 5ee22dda3cca6cbfa8cb0047b487bf68c82f8f8d Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Sat, 20 Dec 2025 17:04:28 +0530 Subject: [PATCH 1/4] feat: implement ElementEditor and BlockEditor components, and a visualization store for editor state management. --- .../components/block/BlockEditor.tsx | 231 +++++++----------- .../components/element/ElementEditor.tsx | 148 +++++++---- .../visualization/useVisualizationStore.ts | 67 ++++- 3 files changed, 259 insertions(+), 187 deletions(-) diff --git a/app/src/components/SimulationDashboard/components/block/BlockEditor.tsx b/app/src/components/SimulationDashboard/components/block/BlockEditor.tsx index 24b2142..8705283 100644 --- a/app/src/components/SimulationDashboard/components/block/BlockEditor.tsx +++ b/app/src/components/SimulationDashboard/components/block/BlockEditor.tsx @@ -19,60 +19,105 @@ interface BlockEditorProps { updateBlockStyle: (blockId: string, style: React.CSSProperties) => void; updateBlockSize: (blockId: string, size: { width: number; height: number }) => void; updateBlockPosition: (blockId: string, position: { x: number; y: number }) => void; - updateBlockPositionType: ( - blockId: string, - positionType: "relative" | "absolute" | "fixed" - ) => void; + updateBlockPositionType: (blockId: string, positionType: "relative" | "absolute" | "fixed") => void; updateBlockZIndex: (blockId: string, zIndex: number) => void; handleRemoveBlock: (blockId: string) => void; } -const BlockEditor: React.FC = ({ - blockEditorRef, - currentBlock, - selectedBlock, - updateBlockStyle, - updateBlockSize, - updateBlockPositionType, - updateBlockZIndex, - handleRemoveBlock, -}) => { +const BlockEditor: React.FC = ({ blockEditorRef, currentBlock, selectedBlock, updateBlockStyle, updateBlockSize, updateBlockPositionType, updateBlockZIndex, handleRemoveBlock }) => { const [color, setColor] = useState("#000000"); // Use position from VisualizationStore const { editorPosition, setEditorPosition } = useVisualizationStore(); const panelRef = useRef(null); - const initialPositionRef = useRef<{ x: number; y: number } | null>(null); + const initialPositionRef = useRef<{ xPercent: number; yPercent: number } | null>(null); const draggingRef = useRef(false); const startXRef = useRef(0); const startYRef = useRef(0); const startLeftRef = useRef(0); const startTopRef = useRef(0); - const position = editorPosition || { x: 100, y: 80 }; - const setPosition = (newPosition: { x: number; y: number }) => { - setEditorPosition(newPosition); + // Get panel dimensions + const getPanelDimensions = () => { + const panelEl = panelRef.current || (blockEditorRef && (blockEditorRef as any).current); + const width = panelEl?.offsetWidth || 300; + const height = panelEl?.offsetHeight || 300; + return { width, height }; }; - // compute exact initial position once we have panel dimensions + // Convert percentage position to pixels for rendering + const getPixelPosition = (): { x: number; y: number } => { + if (!editorPosition) { + return { x: 100, y: 80 }; + } + + const { width, height } = getPanelDimensions(); + const viewportWidth = window.innerWidth; + const viewportHeight = window.innerHeight; + + let x = (editorPosition.xPercent / 100) * viewportWidth; + let y = (editorPosition.yPercent / 100) * viewportHeight; + + // Ensure panel stays within bounds + const maxX = viewportWidth - width - 8; + const maxY = viewportHeight - height - 8; + + x = Math.max(0, Math.min(x, maxX)); + y = Math.max(0, Math.min(y, maxY)); + + return { x, y }; + }; + + const position = getPixelPosition(); + + // Convert pixel position to percentage and save to store + const setPositionFromPixels = (pixelX: number, pixelY: number) => { + const { width, height } = getPanelDimensions(); + const viewportWidth = window.innerWidth; + const viewportHeight = window.innerHeight; + + // Calculate percentage, ensuring panel stays within bounds + const maxXPercent = ((viewportWidth - width) / viewportWidth) * 100; + const maxYPercent = ((viewportHeight - height) / viewportHeight) * 100; + + const xPercent = Math.max(0, Math.min((pixelX / viewportWidth) * 100, maxXPercent)); + const yPercent = Math.max(0, Math.min((pixelY / viewportHeight) * 100, maxYPercent)); + + setEditorPosition({ xPercent, yPercent }); + }; + + // Initialize position on mount useEffect(() => { if (!editorPosition) { - const panelEl = panelRef.current || (blockEditorRef && (blockEditorRef as any).current); - const width = panelEl?.offsetWidth || 300; - const nx = Math.max(0, window.innerWidth - width - 40); - const ny = 80; - initialPositionRef.current = { x: nx, y: ny }; - setPosition({ x: nx, y: ny }); + const { width } = getPanelDimensions(); + const defaultX = Math.max(0, window.innerWidth - width - 40); + const defaultY = 80; + + setPositionFromPixels(defaultX, defaultY); + initialPositionRef.current = { + xPercent: (defaultX / window.innerWidth) * 100, + yPercent: (defaultY / window.innerHeight) * 100, + }; } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + // Handle window resize - update position to maintain relative placement useEffect(() => { + const handleResize = () => { + if (editorPosition) { + // Force re-render with updated pixel position + // The percentage stays the same, but pixels recalculate + setEditorPosition({ ...editorPosition }); + } + }; + + window.addEventListener("resize", handleResize); return () => { - // cleanup in case + window.removeEventListener("resize", handleResize); draggingRef.current = false; }; - }, []); + }, [editorPosition, setEditorPosition]); const startDrag = (ev: React.PointerEvent) => { if (ev.detail > 1) return; @@ -93,10 +138,7 @@ const BlockEditor: React.FC = ({ const dx = e.clientX - startXRef.current; const dy = e.clientY - startYRef.current; - const panelEl = panelRef.current || (blockEditorRef && (blockEditorRef as any).current); - const width = panelEl?.offsetWidth || 300; - const height = panelEl?.offsetHeight || 300; - + const { width, height } = getPanelDimensions(); const maxX = window.innerWidth - width - 8; const maxY = window.innerHeight - height - 8; @@ -106,7 +148,7 @@ const BlockEditor: React.FC = ({ nx = Math.max(0, Math.min(nx, maxX)); ny = Math.max(0, Math.min(ny, maxY)); - setPosition({ x: nx, y: ny }); + setPositionFromPixels(nx, ny); }; const onPointerUp = (e: PointerEvent) => { @@ -122,35 +164,26 @@ const BlockEditor: React.FC = ({ const resetPosition = () => { if (initialPositionRef.current) { - setPosition(initialPositionRef.current); + setEditorPosition(initialPositionRef.current); return; } - const panelEl = panelRef.current || (blockEditorRef && (blockEditorRef as any).current); - const width = panelEl?.offsetWidth || 300; - const nx = Math.max(0, window.innerWidth - width - 40); - setPosition({ x: nx, y: 80 }); + const { width } = getPanelDimensions(); + const defaultX = Math.max(0, window.innerWidth - width - 40); + setPositionFromPixels(defaultX, 80); }; return (
{ panelRef.current = el; - if ( - blockEditorRef && - typeof blockEditorRef === "object" && - "current" in blockEditorRef - ) { + if (blockEditorRef && typeof blockEditorRef === "object" && "current" in blockEditorRef) { (blockEditorRef as any).current = el; } }} className="panel block-editor-panel" style={{ position: "fixed", left: position.x, top: position.y, zIndex: 999 }} > -
+
@@ -199,14 +232,8 @@ const BlockEditor: React.FC = ({ {["relative", "absolute"].map((position) => (
- updateBlockPositionType( - selectedBlock, - position as "relative" | "absolute" | "fixed" - ) - } + className={`type ${currentBlock.positionType === position ? "active" : ""}`} + onClick={() => updateBlockPositionType(selectedBlock, position as "relative" | "absolute" | "fixed")} > {position.charAt(0).toUpperCase() + position.slice(1)}
@@ -215,69 +242,27 @@ const BlockEditor: React.FC = ({
- +
- +
- +
- +
- - updateBlockZIndex(selectedBlock, Number(newValue)) - } - /> + updateBlockZIndex(selectedBlock, Number(newValue))} /> { updateBlockStyle(selectedBlock, { @@ -295,29 +280,17 @@ const BlockEditor: React.FC = ({
setColor(e.target.value)} onChange={(e) => { - handleBackgroundColorChange( - currentBlock, - selectedBlock, - updateBlockStyle, - e.target.value - ); + handleBackgroundColorChange(currentBlock, selectedBlock, updateBlockStyle, e.target.value); setColor(e.target.value); }} />
{ e.preventDefault(); - handleBackgroundColorChange( - currentBlock, - selectedBlock, - updateBlockStyle, - color - ); + handleBackgroundColorChange(currentBlock, selectedBlock, updateBlockStyle, color); }} > = ({ setColor(e.target.value); }} onBlur={(e) => { - handleBackgroundColorChange( - currentBlock, - selectedBlock, - updateBlockStyle, - color - ); + handleBackgroundColorChange(currentBlock, selectedBlock, updateBlockStyle, color); }} />
@@ -343,19 +311,8 @@ const BlockEditor: React.FC = ({ min={0} max={1} step={0.1} - value={Math.round( - getAlphaFromRgba( - getCurrentBlockStyleValue(currentBlock, "backgroundColor") - ) - )} - onChange={(value: number) => - handleBackgroundAlphaChange( - currentBlock, - selectedBlock, - updateBlockStyle, - Number(value) - ) - } + value={Math.round(getAlphaFromRgba(getCurrentBlockStyleValue(currentBlock, "backgroundColor")))} + onChange={(value: number) => handleBackgroundAlphaChange(currentBlock, selectedBlock, updateBlockStyle, Number(value))} /> = ({ // Use shared position from VisualizationStore const { editorPosition, setEditorPosition } = useVisualizationStore(); const panelRef = useRef(null); - const initialPositionRef = useRef<{ x: number; y: number } | null>(null); - - // Compute position from store or initialize - const position = editorPosition || { x: 100, y: 80 }; - const setPosition = (newPosition: { x: number; y: number }) => { - setEditorPosition(newPosition); - }; - - // On mount, compute exact initial position based on panel width and store it - useEffect(() => { - if (!editorPosition) { - const panelEl = panelRef.current || (elementEditorRef && (elementEditorRef as any).current); - const width = panelEl?.offsetWidth || 300; - const nx = Math.max(0, window.innerWidth - width - 40); - const ny = 80; - initialPositionRef.current = { x: nx, y: ny }; - setPosition({ x: nx, y: ny }); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const initialPositionRef = useRef<{ xPercent: number; yPercent: number } | null>(null); const draggingRef = useRef(false); const startXRef = useRef(0); @@ -90,14 +71,86 @@ const ElementEditor: React.FC = ({ const startLeftRef = useRef(0); const startTopRef = useRef(0); + // Get panel dimensions + const getPanelDimensions = () => { + const panelEl = panelRef.current || (elementEditorRef && (elementEditorRef as any).current); + const width = panelEl?.offsetWidth || 300; + const height = panelEl?.offsetHeight || 300; + return { width, height }; + }; + + // Convert percentage position to pixels for rendering + const getPixelPosition = (): { x: number; y: number } => { + if (!editorPosition) { + return { x: 100, y: 80 }; + } + + const { width, height } = getPanelDimensions(); + const viewportWidth = window.innerWidth; + const viewportHeight = window.innerHeight; + + let x = (editorPosition.xPercent / 100) * viewportWidth; + let y = (editorPosition.yPercent / 100) * viewportHeight; + + // Ensure panel stays within bounds + const maxX = viewportWidth - width - 8; + const maxY = viewportHeight - height - 8; + + x = Math.max(0, Math.min(x, maxX)); + y = Math.max(0, Math.min(y, maxY)); + + return { x, y }; + }; + + const position = getPixelPosition(); + + // Convert pixel position to percentage and save to store + const setPositionFromPixels = (pixelX: number, pixelY: number) => { + const { width, height } = getPanelDimensions(); + const viewportWidth = window.innerWidth; + const viewportHeight = window.innerHeight; + + // Calculate percentage, ensuring panel stays within bounds + const maxXPercent = ((viewportWidth - width) / viewportWidth) * 100; + const maxYPercent = ((viewportHeight - height) / viewportHeight) * 100; + + const xPercent = Math.max(0, Math.min((pixelX / viewportWidth) * 100, maxXPercent)); + const yPercent = Math.max(0, Math.min((pixelY / viewportHeight) * 100, maxYPercent)); + + setEditorPosition({ xPercent, yPercent }); + }; + + // On mount, compute exact initial position based on panel width and store it useEffect(() => { + if (!editorPosition) { + const { width } = getPanelDimensions(); + const defaultX = Math.max(0, window.innerWidth - width - 40); + const defaultY = 80; + + setPositionFromPixels(defaultX, defaultY); + initialPositionRef.current = { + xPercent: (defaultX / window.innerWidth) * 100, + yPercent: (defaultY / window.innerHeight) * 100, + }; + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // Handle window resize - update position to maintain relative placement + useEffect(() => { + const handleResize = () => { + if (editorPosition) { + // Force re-render with updated pixel position + // The percentage stays the same, but pixels recalculate + setEditorPosition({ ...editorPosition }); + } + }; + const onPointerMove = (ev: PointerEvent) => { if (!draggingRef.current) return; const dx = ev.clientX - startXRef.current; const dy = ev.clientY - startYRef.current; - const panel = panelRef.current; - const width = panel?.offsetWidth || 300; - const height = panel?.offsetHeight || 300; + const { width, height } = getPanelDimensions(); const maxX = window.innerWidth - width - 8; const maxY = window.innerHeight - height - 8; let nx = startLeftRef.current + dx; @@ -106,7 +159,7 @@ const ElementEditor: React.FC = ({ if (ny < 0) ny = 0; if (nx > maxX) nx = maxX; if (ny > maxY) ny = maxY; - setPosition({ x: nx, y: ny }); + setPositionFromPixels(nx, ny); }; const onPointerUp = () => { @@ -115,11 +168,13 @@ const ElementEditor: React.FC = ({ window.removeEventListener("pointerup", onPointerUp); }; + window.addEventListener("resize", handleResize); return () => { + window.removeEventListener("resize", handleResize); window.removeEventListener("pointermove", onPointerMove); window.removeEventListener("pointerup", onPointerUp); }; - }, []); + }, [editorPosition, setEditorPosition]); const startDrag = (ev: React.PointerEvent) => { ev.preventDefault(); @@ -136,9 +191,7 @@ const ElementEditor: React.FC = ({ if (!draggingRef.current) return; const dx = e.clientX - startXRef.current; const dy = e.clientY - startYRef.current; - const panelEl = panelRef.current || (elementEditorRef && (elementEditorRef as any).current); - const width = panelEl?.offsetWidth || 300; - const height = panelEl?.offsetHeight || 300; + const { width, height } = getPanelDimensions(); const maxX = window.innerWidth - width - 8; const maxY = window.innerHeight - height - 8; let nx = startLeftRef.current + dx; @@ -147,7 +200,7 @@ const ElementEditor: React.FC = ({ if (ny < 0) ny = 0; if (nx > maxX) nx = maxX; if (ny > maxY) ny = maxY; - setPosition({ x: nx, y: ny }); + setPositionFromPixels(nx, ny); }; const onPointerUp = (e: PointerEvent) => { @@ -156,7 +209,7 @@ const ElementEditor: React.FC = ({ window.removeEventListener("pointerup", onPointerUp); try { (panel as Element).releasePointerCapture?.((e as any).pointerId); - } catch (err) { } + } catch (err) {} }; window.addEventListener("pointermove", onPointerMove); @@ -165,13 +218,12 @@ const ElementEditor: React.FC = ({ const resetPosition = () => { if (initialPositionRef.current) { - setPosition(initialPositionRef.current); + setEditorPosition(initialPositionRef.current); return; } - const panelEl = panelRef.current || (elementEditorRef && (elementEditorRef as any).current); - const width = panelEl?.offsetWidth || 300; - const nx = Math.max(0, window.innerWidth - width - 40); - setPosition({ x: nx, y: 80 }); + const { width } = getPanelDimensions(); + const defaultX = Math.max(0, window.innerWidth - width - 40); + setPositionFromPixels(defaultX, 80); }; const getAssetDropdownItems = useCallback(() => { @@ -547,12 +599,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) => { @@ -571,13 +623,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) => { diff --git a/app/src/store/visualization/useVisualizationStore.ts b/app/src/store/visualization/useVisualizationStore.ts index 0ccdc85..bae0e6c 100644 --- a/app/src/store/visualization/useVisualizationStore.ts +++ b/app/src/store/visualization/useVisualizationStore.ts @@ -1,14 +1,19 @@ import { create } from "zustand"; import { immer } from "zustand/middleware/immer"; +/** + * Panel position stored as percentages (0-100) of viewport dimensions + * This ensures panels stay within bounds when screen size changes + */ interface PanelPosition { - x: number; - y: number; + xPercent: number; // 0-100, percentage from left edge + yPercent: number; // 0-100, percentage from top edge } interface VisualizationState { editorPosition: PanelPosition | null; setEditorPosition: (position: PanelPosition) => void; + resetEditorPosition: () => void; } export const useVisualizationStore = create()( @@ -18,5 +23,63 @@ export const useVisualizationStore = create()( set((state) => { state.editorPosition = position; }), + resetEditorPosition: () => + set((state) => { + state.editorPosition = null; + }), })) ); + +/** + * Utility functions to convert between pixels and percentages + */ +export const positionUtils = { + /** + * Convert pixel position to percentage position + */ + pixelsToPercent: (pixelX: number, pixelY: number, panelWidth: number, panelHeight: number): PanelPosition => { + const viewportWidth = window.innerWidth; + const viewportHeight = window.innerHeight; + + // Calculate percentage, ensuring panel stays within bounds + const maxXPercent = ((viewportWidth - panelWidth) / viewportWidth) * 100; + const maxYPercent = ((viewportHeight - panelHeight) / viewportHeight) * 100; + + const xPercent = Math.max(0, Math.min((pixelX / viewportWidth) * 100, maxXPercent)); + const yPercent = Math.max(0, Math.min((pixelY / viewportHeight) * 100, maxYPercent)); + + return { xPercent, yPercent }; + }, + + /** + * Convert percentage position to pixel position + */ + percentToPixels: (position: PanelPosition, panelWidth: number, panelHeight: number): { x: number; y: number } => { + const viewportWidth = window.innerWidth; + const viewportHeight = window.innerHeight; + + // Calculate pixel position + let x = (position.xPercent / 100) * viewportWidth; + let y = (position.yPercent / 100) * viewportHeight; + + // Ensure panel stays within bounds + const maxX = viewportWidth - panelWidth - 8; + const maxY = viewportHeight - panelHeight - 8; + + x = Math.max(0, Math.min(x, maxX)); + y = Math.max(0, Math.min(y, maxY)); + + return { x, y }; + }, + + /** + * Get default position (top-right corner with some margin) + */ + getDefaultPosition: (panelWidth: number): PanelPosition => { + const viewportWidth = window.innerWidth; + const defaultX = Math.max(0, viewportWidth - panelWidth - 40); + const defaultY = 80; + + return positionUtils.pixelsToPercent(defaultX, defaultY, panelWidth, 300); + }, +}; From e377438895d918d971370ef16cca39df505053a5 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Sat, 20 Dec 2025 17:07:42 +0530 Subject: [PATCH 2/4] feat: Add BlockEditor component with UI for managing block styles, size, and a draggable panel. --- .../components/block/BlockEditor.tsx | 44 ++++++++++++++----- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/app/src/components/SimulationDashboard/components/block/BlockEditor.tsx b/app/src/components/SimulationDashboard/components/block/BlockEditor.tsx index 8705283..0626915 100644 --- a/app/src/components/SimulationDashboard/components/block/BlockEditor.tsx +++ b/app/src/components/SimulationDashboard/components/block/BlockEditor.tsx @@ -112,21 +112,41 @@ const BlockEditor: React.FC = ({ blockEditorRef, currentBlock, } }; + const onPointerMove = (ev: PointerEvent) => { + if (!draggingRef.current) return; + const dx = ev.clientX - startXRef.current; + const dy = ev.clientY - startYRef.current; + const { width, height } = getPanelDimensions(); + const maxX = window.innerWidth - width - 8; + const maxY = window.innerHeight - height - 8; + let nx = startLeftRef.current + dx; + let ny = startTopRef.current + dy; + if (nx < 0) nx = 0; + if (ny < 0) ny = 0; + if (nx > maxX) nx = maxX; + if (ny > maxY) ny = maxY; + setPositionFromPixels(nx, ny); + }; + + const onPointerUp = () => { + draggingRef.current = false; + window.removeEventListener("pointermove", onPointerMove); + window.removeEventListener("pointerup", onPointerUp); + }; + window.addEventListener("resize", handleResize); return () => { window.removeEventListener("resize", handleResize); - draggingRef.current = false; + window.removeEventListener("pointermove", onPointerMove); + window.removeEventListener("pointerup", onPointerUp); }; }, [editorPosition, setEditorPosition]); const startDrag = (ev: React.PointerEvent) => { - if (ev.detail > 1) return; - + ev.preventDefault(); const panel = panelRef.current || (blockEditorRef && (blockEditorRef as any).current); if (!panel) return; - panel.setPointerCapture?.(ev.pointerId); - draggingRef.current = true; startXRef.current = ev.clientX; startYRef.current = ev.clientY; @@ -137,17 +157,15 @@ const BlockEditor: React.FC = ({ blockEditorRef, currentBlock, if (!draggingRef.current) return; const dx = e.clientX - startXRef.current; const dy = e.clientY - startYRef.current; - const { width, height } = getPanelDimensions(); const maxX = window.innerWidth - width - 8; const maxY = window.innerHeight - height - 8; - let nx = startLeftRef.current + dx; let ny = startTopRef.current + dy; - - nx = Math.max(0, Math.min(nx, maxX)); - ny = Math.max(0, Math.min(ny, maxY)); - + if (nx < 0) nx = 0; + if (ny < 0) ny = 0; + if (nx > maxX) nx = maxX; + if (ny > maxY) ny = maxY; setPositionFromPixels(nx, ny); }; @@ -155,7 +173,9 @@ const BlockEditor: React.FC = ({ blockEditorRef, currentBlock, draggingRef.current = false; window.removeEventListener("pointermove", onPointerMove); window.removeEventListener("pointerup", onPointerUp); - panel.releasePointerCapture?.(e.pointerId); + try { + (panel as Element).releasePointerCapture?.((e as any).pointerId); + } catch (err) {} }; window.addEventListener("pointermove", onPointerMove); From b10afc975c6bc186126729bce3e9a8256d60976d Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Sat, 20 Dec 2025 17:26:51 +0530 Subject: [PATCH 3/4] feat: implement UI for editing simulation block properties and element design. --- .../components/block/BlockEditor.tsx | 92 +++++++++++++------ .../components/element/ElementDesign.tsx | 65 +++++++------ .../components/element/ElementDropdown.tsx | 1 - app/src/types/simulationDashboard.d.ts | 2 +- 4 files changed, 100 insertions(+), 60 deletions(-) diff --git a/app/src/components/SimulationDashboard/components/block/BlockEditor.tsx b/app/src/components/SimulationDashboard/components/block/BlockEditor.tsx index 3d4e769..713aafd 100644 --- a/app/src/components/SimulationDashboard/components/block/BlockEditor.tsx +++ b/app/src/components/SimulationDashboard/components/block/BlockEditor.tsx @@ -26,7 +26,17 @@ interface BlockEditorProps { handleRemoveBlock: (blockId: string) => void; } -const BlockEditor: React.FC = ({ blockEditorRef, currentBlock, selectedBlock, updateBlockStyle, updateBlockSize, updateBlockPositionType, updateBlockZIndex, handleRemoveBlock }) => { +const BlockEditor: React.FC = ({ + blockEditorRef, + currentBlock, + selectedBlock, + updateBlockStyle, + updateBlockSize, + updateBlockPosition, + updateBlockPositionType, + updateBlockZIndex, + handleRemoveBlock, +}) => { const [color, setColor] = useState("#000000"); useEffect(() => { @@ -227,32 +237,6 @@ const BlockEditor: React.FC = ({ blockEditorRef, currentBlock,
-
-
Size
- { - updateBlockSize(selectedBlock, { - ...currentBlock.size!, - width: Number(newValue), // Make sure to convert the string back to a number here - }); - }} - /> - { - updateBlockSize(selectedBlock, { - ...currentBlock.size!, - height: Number(newValue), - }); - }} - /> -
-
Position
@@ -319,8 +303,58 @@ const BlockEditor: React.FC = ({ blockEditorRef, currentBlock,
-
-
Apperance
+
+
Appearance
+
+ {currentBlock.positionType === "absolute" && ( + <> + { + updateBlockPosition(selectedBlock, { + ...(currentBlock.position || { x: 0, y: 0 }), + x: Number(newValue), + }); + }} + /> + { + updateBlockPosition(selectedBlock, { + ...(currentBlock.position || { x: 0, y: 0 }), + y: Number(newValue), + }); + }} + /> + + )} + { + updateBlockSize(selectedBlock, { + ...currentBlock.size!, + width: Number(newValue), + }); + }} + /> + { + updateBlockSize(selectedBlock, { + ...currentBlock.size!, + height: Number(newValue), + }); + }} + /> +
= ({ setColor(rgbaToHex(getCurrentElementStyleValue(currentElement, "backgroundColor") || "#000000")); }, [currentElement]); + const graphOptions = [ + { id: "line", label: "Line Chart" }, + { id: "bar", label: "Bar Chart" }, + { id: "pie", label: "Pie Chart" }, + { id: "area", label: "Area Chart" }, + { id: "radar", label: "Radar Chart" }, + ]; + return (
{element?.type === "graph" && (
option.id === currentElement.graphType)?.label} onSelect={(newValue) => { updateGraphType(selectedBlock, selectedElement, newValue.id as GraphTypes); }} @@ -218,28 +221,32 @@ const ElementDesign: React.FC = ({
Appearance
- { - updateElementPosition(selectedBlock, selectedElement, { - ...(currentElement.position || { x: 0, y: 0 }), - x: Number(newValue), - }); - }} - /> - { - updateElementPosition(selectedBlock, selectedElement, { - ...(currentElement.position || { x: 0, y: 0 }), - y: Number(newValue), - }); - }} - /> + {currentElement.positionType === "absolute" && ( + <> + { + updateElementPosition(selectedBlock, selectedElement, { + ...(currentElement.position || { x: 0, y: 0 }), + x: Number(newValue), + }); + }} + /> + { + updateElementPosition(selectedBlock, selectedElement, { + ...(currentElement.position || { x: 0, y: 0 }), + y: Number(newValue), + }); + }} + /> + + )} = ({ showElementDropdown, key={elementType.label} onClick={() => { handleAddElement(showElementDropdown, elementType.type, elementType.graphType); - console.log('showElementDropdown: ', showElementDropdown); }} className="dropdown-button" > diff --git a/app/src/types/simulationDashboard.d.ts b/app/src/types/simulationDashboard.d.ts index 4cf1b4a..06533eb 100644 --- a/app/src/types/simulationDashboard.d.ts +++ b/app/src/types/simulationDashboard.d.ts @@ -11,7 +11,7 @@ type ElementDataBinding = { dataSource?: string | string[]; dataValue?: string | string[]; commonValue?: string; - dataType?: "single-machine" | "multiple-machine"; + dataType?: DataType; } type Position = { From 0d6bdf61575299b0aa31834f56bcd8b2d13823e4 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Sat, 20 Dec 2025 17:30:39 +0530 Subject: [PATCH 4/4] feat: Implement simulation dashboard with Zustand store, types, and UI components for block and element management. --- .../SimulationDashboard/DashboardEditor.tsx | 8 ++++++ .../components/element/ElementDesign.tsx | 15 ++++++++++ .../components/element/ElementEditor.tsx | 3 ++ .../functions/helpers/resolveElementValue.ts | 5 ++++ .../simulation/useSimulationDashBoardStore.ts | 28 +++++++++++++++++++ app/src/types/exportedTypes.ts | 1 + 6 files changed, 60 insertions(+) diff --git a/app/src/components/SimulationDashboard/DashboardEditor.tsx b/app/src/components/SimulationDashboard/DashboardEditor.tsx index 7a2dcfc..5479d15 100644 --- a/app/src/components/SimulationDashboard/DashboardEditor.tsx +++ b/app/src/components/SimulationDashboard/DashboardEditor.tsx @@ -55,6 +55,7 @@ const DashboardEditor: React.FC = () => { peekUpdateGraphData, peekUpdateGraphTitle, peekUpdateGraphType, + peekUpdateTextValue, peekUpdateDataType, peekUpdateCommonValue, peekUpdateDataValue, @@ -611,6 +612,13 @@ const DashboardEditor: React.FC = () => { await updateBackend(updatedBlock); } }} + updateTextValue={async (blockId, elementId, textValue) => { + const updatedBlocks = peekUpdateTextValue(blockId, elementId, textValue); + const updatedBlock = getBlockFromPeekedBlocks(updatedBlocks, blockId); + if (updatedBlock) { + await updateBackend(updatedBlock); + } + }} updateDataType={async (blockId, elementId, dataType) => { const updatedBlocks = peekUpdateDataType(blockId, elementId, dataType); const updatedBlock = getBlockFromPeekedBlocks(updatedBlocks, blockId); diff --git a/app/src/components/SimulationDashboard/components/element/ElementDesign.tsx b/app/src/components/SimulationDashboard/components/element/ElementDesign.tsx index fc36e45..1475462 100644 --- a/app/src/components/SimulationDashboard/components/element/ElementDesign.tsx +++ b/app/src/components/SimulationDashboard/components/element/ElementDesign.tsx @@ -25,6 +25,7 @@ interface ElementDesignProps { updateGraphData: (blockId: string, elementId: string, newData: GraphDataPoint[]) => void; 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; updateCommonValue: (blockId: string, elementId: string, commonValue: string) => void; updateDataValue: (blockId: string, elementId: string, dataValue: string | string[]) => void; @@ -45,6 +46,7 @@ const ElementDesign: React.FC = ({ updateElementZIndex, updateGraphTitle, updateGraphType, + updateTextValue, }) => { const [color, setColor] = useState("#000000"); @@ -76,6 +78,19 @@ const ElementDesign: React.FC = ({
)} + {element?.type === "text" && ( +
+ { + updateTextValue(selectedBlock, selectedElement, newValue); + }} + /> +
+ )} +
Position
diff --git a/app/src/components/SimulationDashboard/components/element/ElementEditor.tsx b/app/src/components/SimulationDashboard/components/element/ElementEditor.tsx index d5106b8..b2bee0f 100644 --- a/app/src/components/SimulationDashboard/components/element/ElementEditor.tsx +++ b/app/src/components/SimulationDashboard/components/element/ElementEditor.tsx @@ -25,6 +25,7 @@ interface ElementEditorProps { updateGraphData: (blockId: string, elementId: string, newData: GraphDataPoint[]) => void; 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; updateCommonValue: (blockId: string, elementId: string, commonValue: string) => void; updateDataValue: (blockId: string, elementId: string, dataValue: string | string[]) => void; @@ -46,6 +47,7 @@ const ElementEditor: React.FC = ({ updateGraphData, updateGraphTitle, updateGraphType, + updateTextValue, updateDataType, updateCommonValue, updateDataValue, @@ -562,6 +564,7 @@ const ElementEditor: React.FC = ({ updateGraphData={updateGraphData} updateGraphTitle={updateGraphTitle} updateGraphType={updateGraphType} + updateTextValue={updateTextValue} updateDataType={updateDataType} updateCommonValue={updateCommonValue} updateDataValue={updateDataValue} diff --git a/app/src/components/SimulationDashboard/functions/helpers/resolveElementValue.ts b/app/src/components/SimulationDashboard/functions/helpers/resolveElementValue.ts index 754b05c..6b1d8bf 100644 --- a/app/src/components/SimulationDashboard/functions/helpers/resolveElementValue.ts +++ b/app/src/components/SimulationDashboard/functions/helpers/resolveElementValue.ts @@ -1,6 +1,11 @@ import type { UIElement } from "../../../../types/exportedTypes"; export const resolveElementValue = (element: UIElement) => { + // For text elements, return the textValue + if (element.type === "text") { + return { value: element.textValue || "Text" }; + } + if (!element.dataBinding) { return { value: "No data" }; } diff --git a/app/src/store/simulation/useSimulationDashBoardStore.ts b/app/src/store/simulation/useSimulationDashBoardStore.ts index c10806b..d396e52 100644 --- a/app/src/store/simulation/useSimulationDashBoardStore.ts +++ b/app/src/store/simulation/useSimulationDashBoardStore.ts @@ -48,6 +48,7 @@ interface SimulationDashboardStore { updateGraphData: (blockId: string, elementId: string, newData: GraphDataPoint[]) => void; updateGraphTitle: (blockId: string, elementId: string, title: string) => void; updateGraphType: (blockId: string, elementId: string, graphType: GraphTypes) => void; + updateTextValue: (blockId: string, elementId: string, textValue: string) => void; // Element swapping swapElements: (blockId: string, elementId1: string, elementId2: string) => void; @@ -73,6 +74,7 @@ interface SimulationDashboardStore { peekUpdateGraphData: (blockId: string, elementId: string, newData: GraphDataPoint[]) => Block[]; 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[]; peekUpdateCommonValue: (blockId: string, elementId: string, commonValue: string) => Block[]; peekUpdateDataValue: (blockId: string, elementId: string, dataValue: string | string[]) => Block[]; @@ -321,6 +323,7 @@ export const createSimulationDashboardStore = () => { newElement = { ...commonProps, type: "text", + textValue: "Text", style: { color: "#ffffff", fontSize: 14, @@ -487,6 +490,18 @@ export const createSimulationDashboardStore = () => { }); }, + updateTextValue: (blockId, elementId, textValue) => { + set((state) => { + const block = state.blocks.find((b) => b.blockUuid === blockId); + if (block) { + const element = block.elements.find((el) => el.elementUuid === elementId); + if (element && element.type === "text") { + element.textValue = textValue; + } + } + }); + }, + // Element swapping swapElements: (blockId, elementId1, elementId2) => { set((state) => { @@ -685,6 +700,7 @@ export const createSimulationDashboardStore = () => { newElement = { ...commonProps, type: "text", + textValue: "Text", style: { color: "#ffffff", fontSize: 14, @@ -846,6 +862,18 @@ export const createSimulationDashboardStore = () => { return blocks; }, + peekUpdateTextValue: (blockId, elementId, textValue) => { + 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 === "text") { + element.textValue = textValue; + } + } + return blocks; + }, + peekUpdateDataType: (blockId: string, elementId: string, dataType: DataType) => { const blocks = cloneBlocks(get().blocks); diff --git a/app/src/types/exportedTypes.ts b/app/src/types/exportedTypes.ts index f108d79..759e7be 100644 --- a/app/src/types/exportedTypes.ts +++ b/app/src/types/exportedTypes.ts @@ -20,6 +20,7 @@ export type UIElement = { type: UIType; graphType?: GraphTypes; graphTitle?: string; + textValue?: string; style: ExtendedCSSProperties; dataBinding?: ElementDataBinding; position?: Position;