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/block/BlockEditor.tsx b/app/src/components/SimulationDashboard/components/block/BlockEditor.tsx index 233b479..c07f4e3 100644 --- a/app/src/components/SimulationDashboard/components/block/BlockEditor.tsx +++ b/app/src/components/SimulationDashboard/components/block/BlockEditor.tsx @@ -21,10 +21,7 @@ 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; } @@ -35,6 +32,7 @@ const BlockEditor: React.FC = ({ selectedBlock, updateBlockStyle, updateBlockSize, + updateBlockPosition, updateBlockPositionType, updateBlockZIndex, handleRemoveBlock, @@ -42,56 +40,130 @@ const BlockEditor: React.FC = ({ const [color, setColor] = useState("#000000"); useEffect(() => { - setColor( - rgbaToHex( - getCurrentBlockStyleValue(currentBlock, "backgroundColor") || "#000000" - ) - ); + setColor(rgbaToHex(getCurrentBlockStyleValue(currentBlock, "backgroundColor") || "#000000")); }, [currentBlock]); // 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 }; }; + // 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 // compute exact initial position once we have panel dimensions 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(() => { - return () => { - // cleanup in case - draggingRef.current = false; + 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 { 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); + 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; @@ -102,28 +174,25 @@ const BlockEditor: React.FC = ({ if (!draggingRef.current) return; 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; - 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)); - - setPosition({ x: nx, y: ny }); + 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 = (e: PointerEvent) => { 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); @@ -132,35 +201,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 }} > -
+
@@ -177,48 +237,14 @@ const BlockEditor: React.FC = ({
-
-
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
{["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)}
@@ -227,48 +253,16 @@ const BlockEditor: React.FC = ({
- +
- +
- +
- +
@@ -279,9 +273,7 @@ const BlockEditor: React.FC = ({ label="Layer" value={String(currentBlock.zIndex ?? 1)} placeholder={"Layer"} - onChange={(newValue) => - updateBlockZIndex(selectedBlock, Number(newValue)) - } + onChange={(newValue) => updateBlockZIndex(selectedBlock, Number(newValue))} />
-
-
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), + }); + }} + /> +
{ updateBlockStyle(selectedBlock, { borderRadius: Number(newValue), @@ -338,43 +377,21 @@ const BlockEditor: React.FC = ({ onChange={(value) => { setColor(value); if (/^#?([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/.test(value)) { - handleBackgroundColorChange( - currentBlock, - selectedBlock, - updateBlockStyle, - value - ); + handleBackgroundColorChange(currentBlock, selectedBlock, updateBlockStyle, value); } }} onUpdate={(value) => { - handleBackgroundColorChange( - currentBlock, - selectedBlock, - updateBlockStyle, - value - ); + handleBackgroundColorChange(currentBlock, selectedBlock, updateBlockStyle, value); }} /> - - handleBackgroundAlphaChange( - currentBlock, - selectedBlock, - updateBlockStyle, - Number(value) - ) - } + value={Math.round(getAlphaFromRgba(getCurrentBlockStyleValue(currentBlock, "backgroundColor")))} + onChange={(value: number) => handleBackgroundAlphaChange(currentBlock, selectedBlock, updateBlockStyle, Number(value))} /> 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"); @@ -52,19 +54,22 @@ const ElementDesign: React.FC = ({ 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); }} @@ -73,6 +78,19 @@ const ElementDesign: React.FC = ({
)} + {element?.type === "text" && ( +
+ { + updateTextValue(selectedBlock, selectedElement, newValue); + }} + /> +
+ )} +
Position
@@ -218,28 +236,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/components/SimulationDashboard/components/element/ElementEditor.tsx b/app/src/components/SimulationDashboard/components/element/ElementEditor.tsx index 7e3a20d..9494296 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, @@ -63,26 +65,7 @@ const ElementEditor: React.FC = ({ // 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 +73,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 +161,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 +170,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 +193,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 +202,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 +211,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 +220,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(() => { @@ -510,6 +564,7 @@ const ElementEditor: React.FC = ({ updateGraphData={updateGraphData} updateGraphTitle={updateGraphTitle} updateGraphType={updateGraphType} + updateTextValue={updateTextValue} updateDataType={updateDataType} updateCommonValue={updateCommonValue} updateDataValue={updateDataValue} @@ -549,12 +604,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) => { @@ -573,13 +628,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/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/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); + }, +}; 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; 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 = {