feat: introduce Simulation Dashboard with core components for elements, blocks, control panel, and dashboard editing.

This commit is contained in:
2025-12-20 12:55:43 +05:30
parent e9d0a98a49
commit 9997134e5e
8 changed files with 523 additions and 180 deletions

View File

@@ -4,11 +4,9 @@ interface ControlPanelProps {
editMode: boolean; editMode: boolean;
setEditMode: (mode: boolean) => void; setEditMode: (mode: boolean) => void;
addBlock: () => void; addBlock: () => void;
showDataModelPanel: boolean;
setShowDataModelPanel: (show: boolean) => void;
} }
const ControlPanel: React.FC<ControlPanelProps> = ({ editMode, setEditMode, addBlock, showDataModelPanel, setShowDataModelPanel }) => { const ControlPanel: React.FC<ControlPanelProps> = ({ editMode, setEditMode, addBlock }) => {
return ( return (
<div className="control-panel"> <div className="control-panel">
<button onClick={() => setEditMode(!editMode)} className={`control-button ${editMode ? "edit-mode-active" : ""}`}> <button onClick={() => setEditMode(!editMode)} className={`control-button ${editMode ? "edit-mode-active" : ""}`}>
@@ -16,12 +14,9 @@ const ControlPanel: React.FC<ControlPanelProps> = ({ editMode, setEditMode, addB
</button> </button>
{editMode && ( {editMode && (
<> <>
<button onClick={addBlock} className="control-button primary"> <button onClick={addBlock} className="control-button secondary">
Add Block Add Block
</button> </button>
<button onClick={() => setShowDataModelPanel(!showDataModelPanel)} className="control-button secondary">
{showDataModelPanel ? "Hide Data Model" : "Show Data Model"}
</button>
</> </>
)} )}
</div> </div>

View File

@@ -1,10 +1,8 @@
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import React, { useState, useRef, useEffect } from "react"; import React, { useState, useRef, useEffect } from "react";
import { dataModelManager } from "./data/dataModel";
import ControlPanel from "./ControlPanel"; import ControlPanel from "./ControlPanel";
import SwapModal from "./SwapModal"; import SwapModal from "./SwapModal";
import { Block } from "../../types/exportedTypes"; import { Block } from "../../types/exportedTypes";
import DataModelPanel from "./components/models/DataModelPanel";
import AnalyzerManager from "./AnalyzerManager"; import AnalyzerManager from "./AnalyzerManager";
import { useSceneContext } from "../../modules/scene/sceneContext"; import { useSceneContext } from "../../modules/scene/sceneContext";
@@ -43,9 +41,7 @@ const DashboardEditor: React.FC = () => {
updateBlock, updateBlock,
// Peek methods // Peek methods
peekAddBlock, peekAddBlock,
peekRemoveBlock,
peekAddElement, peekAddElement,
peekRemoveElement,
peekUpdateBlockStyle, peekUpdateBlockStyle,
peekUpdateBlockSize, peekUpdateBlockSize,
peekUpdateBlockPosition, peekUpdateBlockPosition,
@@ -79,8 +75,6 @@ const DashboardEditor: React.FC = () => {
} | null>(null); } | null>(null);
const [showSwapUI, setShowSwapUI] = useState(false); const [showSwapUI, setShowSwapUI] = useState(false);
const [swapSource, setSwapSource] = useState<string | null>(null); const [swapSource, setSwapSource] = useState<string | null>(null);
const [dataModel, setDataModel] = useState<DataModel>(dataModelManager.getDataSnapshot());
const [showDataModelPanel, setShowDataModelPanel] = useState(false);
const [showElementDropdown, setShowElementDropdown] = useState<string | null>(null); const [showElementDropdown, setShowElementDropdown] = useState<string | null>(null);
const [draggingBlock, setDraggingBlock] = useState<string | null>(null); const [draggingBlock, setDraggingBlock] = useState<string | null>(null);
const [elementDragOffset, setElementDragOffset] = useState<Position>({ x: 0, y: 0 }); const [elementDragOffset, setElementDragOffset] = useState<Position>({ x: 0, y: 0 });
@@ -162,40 +156,6 @@ const DashboardEditor: React.FC = () => {
{ dependencies: [blocks, projectId, selectedVersion], noRepeat: true, allowOnInput: true } { dependencies: [blocks, projectId, selectedVersion], noRepeat: true, allowOnInput: true }
); );
// Subscribe to data model changes
useEffect(() => {
const handleDataChange = (): void => {
setDataModel(dataModelManager.getDataSnapshot());
};
const keys = dataModelManager.getAvailableKeys();
const subscriptions: Array<[string, () => void]> = [];
for (const key of keys) {
const callback = () => handleDataChange();
dataModelManager.subscribe(key, callback);
subscriptions.push([key, callback]);
}
const interval = setInterval(() => {
const currentKeys = dataModelManager.getAvailableKeys();
const newKeys = currentKeys.filter((key) => !keys.includes(key));
for (const key of newKeys) {
const callback = () => handleDataChange();
dataModelManager.subscribe(key, callback);
subscriptions.push([key, callback]);
}
}, 1000);
return () => {
for (const [key, callback] of subscriptions) {
dataModelManager.unsubscribe(key, callback);
}
clearInterval(interval);
};
}, []);
// Click outside handler // Click outside handler
useEffect(() => { useEffect(() => {
const handleClickOutside = (event: MouseEvent): void => { const handleClickOutside = (event: MouseEvent): void => {
@@ -409,8 +369,6 @@ const DashboardEditor: React.FC = () => {
await updateBackend(newBlock); await updateBackend(newBlock);
} }
}} }}
showDataModelPanel={showDataModelPanel}
setShowDataModelPanel={setShowDataModelPanel}
/> />
)} )}
@@ -648,8 +606,6 @@ const DashboardEditor: React.FC = () => {
)} )}
</div> </div>
{showDataModelPanel && editMode && <DataModelPanel dataModel={dataModel} dataModelManager={dataModelManager} />}
{showSwapUI && <SwapModal setShowSwapUI={setShowSwapUI} setSwapSource={setSwapSource} />} {showSwapUI && <SwapModal setShowSwapUI={setShowSwapUI} setSwapSource={setSwapSource} />}
</div> </div>
); );

View File

@@ -6,13 +6,10 @@ import { handleBackgroundColorChange } from "../../functions/helpers/handleBackg
import { handleBackgroundAlphaChange } from "../../functions/helpers/handleBackgroundAlphaChange"; import { handleBackgroundAlphaChange } from "../../functions/helpers/handleBackgroundAlphaChange";
import { handleBlurAmountChange } from "../../functions/helpers/handleBlurAmountChange"; import { handleBlurAmountChange } from "../../functions/helpers/handleBlurAmountChange";
import InputRange from "../../../ui/inputs/InputRange"; import InputRange from "../../../ui/inputs/InputRange";
import RegularDropDown from "../../../ui/inputs/RegularDropDown";
import { DeleteIcon } from "../../../icons/ContextMenuIcons"; import { DeleteIcon } from "../../../icons/ContextMenuIcons";
import InputWithDropDown from "../../../ui/inputs/InputWithDropDown"; import InputWithDropDown from "../../../ui/inputs/InputWithDropDown";
import { AddIcon, DeviceIcon, ParametersIcon, ResizeHeightIcon } from "../../../icons/ExportCommonIcons"; import { ResizeHeightIcon } from "../../../icons/ExportCommonIcons";
import DataDetailedDropdown from "../../../ui/inputs/DataDetailedDropdown";
import RenameInput from "../../../ui/inputs/RenameInput"; import RenameInput from "../../../ui/inputs/RenameInput";
import DataSourceSelector from "../../../ui/inputs/DataSourceSelector";
import { useVisualizationStore } from "../../../../store/visualization/useVisualizationStore"; import { useVisualizationStore } from "../../../../store/visualization/useVisualizationStore";
interface BlockEditorProps { interface BlockEditorProps {
@@ -22,7 +19,10 @@ interface BlockEditorProps {
updateBlockStyle: (blockId: string, style: React.CSSProperties) => void; updateBlockStyle: (blockId: string, style: React.CSSProperties) => void;
updateBlockSize: (blockId: string, size: { width: number; height: number }) => void; updateBlockSize: (blockId: string, size: { width: number; height: number }) => void;
updateBlockPosition: (blockId: string, position: { x: number; y: 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; updateBlockZIndex: (blockId: string, zIndex: number) => void;
handleRemoveBlock: (blockId: string) => void; handleRemoveBlock: (blockId: string) => void;
} }
@@ -33,7 +33,6 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
selectedBlock, selectedBlock,
updateBlockStyle, updateBlockStyle,
updateBlockSize, updateBlockSize,
updateBlockPosition,
updateBlockPositionType, updateBlockPositionType,
updateBlockZIndex, updateBlockZIndex,
handleRemoveBlock, handleRemoveBlock,
@@ -136,14 +135,22 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
<div <div
ref={(el) => { ref={(el) => {
panelRef.current = el; panelRef.current = el;
if (blockEditorRef && typeof blockEditorRef === "object" && "current" in blockEditorRef) { if (
blockEditorRef &&
typeof blockEditorRef === "object" &&
"current" in blockEditorRef
) {
(blockEditorRef as any).current = el; (blockEditorRef as any).current = el;
} }
}} }}
className="panel block-editor-panel" className="panel block-editor-panel"
style={{ position: "fixed", left: position.x, top: position.y, zIndex: 999 }} style={{ position: "fixed", left: position.x, top: position.y, zIndex: 999 }}
> >
<div className="resize-icon" onDoubleClick={resetPosition} onPointerDown={startDrag}> <div
className={`free-move-button`}
onDoubleClick={resetPosition}
onPointerDown={startDrag}
>
<ResizeHeightIcon /> <ResizeHeightIcon />
</div> </div>
@@ -192,8 +199,15 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
{["relative", "absolute"].map((position) => ( {["relative", "absolute"].map((position) => (
<div <div
key={position} key={position}
className={`type ${currentBlock.positionType === position ? "active" : ""}`} className={`type ${
onClick={() => updateBlockPositionType(selectedBlock, position as "relative" | "absolute" | "fixed")} currentBlock.positionType === position ? "active" : ""
}`}
onClick={() =>
updateBlockPositionType(
selectedBlock,
position as "relative" | "absolute" | "fixed"
)
}
> >
{position.charAt(0).toUpperCase() + position.slice(1)} {position.charAt(0).toUpperCase() + position.slice(1)}
</div> </div>
@@ -202,28 +216,70 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
<div className="position-canvas"> <div className="position-canvas">
<div className="canvas"> <div className="canvas">
<div className="value padding-top"> <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>
<div className="value padding-right"> <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>
<div className="value padding-bottom"> <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>
<div className="value padding-left"> <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>
</div> </div>
<div className="design-section-footer"> <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 <InputWithDropDown
label={"Radius"} label="Corner Radius"
min={0} min={0}
max={8} value={String(
value={parseInt(getCurrentBlockStyleValue(currentBlock, "borderRadius")) || 8} parseInt(getCurrentBlockStyleValue(currentBlock, "borderRadius")) ||
8
)}
placeholder={"Width"}
onChange={(newValue) => { onChange={(newValue) => {
updateBlockStyle(selectedBlock, { updateBlockStyle(selectedBlock, {
borderRadius: Number(newValue), borderRadius: Number(newValue),
@@ -240,23 +296,81 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
<div className="left"> <div className="left">
<input <input
type="color" type="color"
value={rgbaToHex(getCurrentBlockStyleValue(currentBlock, "backgroundColor"))} value={rgbaToHex(
getCurrentBlockStyleValue(currentBlock, "backgroundColor")
)}
// onChange={(e) => setColor(e.target.value)} // onChange={(e) => setColor(e.target.value)}
onChange={(e) => handleBackgroundColorChange(currentBlock, selectedBlock, updateBlockStyle, e.target.value)} onChange={(e) => {
handleBackgroundColorChange(
currentBlock,
selectedBlock,
updateBlockStyle,
e.target.value
);
setColor(e.target.value);
}}
/> />
<form
<div className="colorValue">{color}</div> onSubmit={(e) => {
e.preventDefault();
handleBackgroundColorChange(
currentBlock,
selectedBlock,
updateBlockStyle,
color
);
}}
>
<input
className="colorValue"
value={color}
onChange={(e) => {
setColor(e.target.value);
}}
onBlur={(e) => {
handleBackgroundColorChange(
currentBlock,
selectedBlock,
updateBlockStyle,
color
);
}}
/>
</form>
</div> </div>
</div> </div>
<InputRange <InputRange
label={"Opacity"} label={"Opacity"}
min={0} min={0}
max={8} max={1}
value={Math.round(getAlphaFromRgba(getCurrentBlockStyleValue(currentBlock, "backgroundColor")) * 100)} step={0.1}
// onChange={(value: number) => handleBackgroundAlphaChange(currentBlock, selectedBlock, updateBlockStyle, Number(value))} value={Math.round(
onChange={(value: number) => handleBlurAmountChange(selectedBlock, updateBlockStyle, Number(value))} getAlphaFromRgba(
getCurrentBlockStyleValue(currentBlock, "backgroundColor")
)
)}
onChange={(value: number) =>
handleBackgroundAlphaChange(
currentBlock,
selectedBlock,
updateBlockStyle,
Number(value)
)
}
/>
<InputRange
label={"Blur"}
min={0}
max={8}
value={parseInt(
getCurrentBlockStyleValue(currentBlock, "backdropFilter")?.match(
/\d+/
)?.[0] || "10"
)}
onChange={(value: number) =>
handleBlurAmountChange(selectedBlock, updateBlockStyle, Number(value))
}
/> />
<InputRange label={"Blur"} min={0} max={8} value={parseInt(getCurrentBlockStyleValue(currentBlock, "backdropFilter")?.match(/\d+/)?.[0] || "10")} />
</div> </div>
</div> </div>
</div> </div>

View File

@@ -54,7 +54,8 @@ const ElementComponent: React.FC<ElementComponentProps> = ({
style={{ style={{
...element.style, ...element.style,
position: element.positionType || "relative", position: element.positionType || "relative",
left: element.positionType === "absolute" ? `${element.position?.x || 0}px` : "auto", left:
element.positionType === "absolute" ? `${element.position?.x || 0}px` : "auto",
top: element.positionType === "absolute" ? `${element.position?.y || 0}px` : "auto", top: element.positionType === "absolute" ? `${element.position?.y || 0}px` : "auto",
width: element.size?.width || "auto", width: element.size?.width || "auto",
height: element.size?.height || "auto", height: element.size?.height || "auto",
@@ -75,12 +76,10 @@ const ElementComponent: React.FC<ElementComponentProps> = ({
{editMode && ( {editMode && (
<> <>
{element.positionType === "relative" && ( <div
<button onClick={(e) => handleSwapStart(element.elementUuid, e)} className="swap-button"> className="resize-handle"
Swap onMouseDown={(e) => handleElementResizeStart(element.elementUuid, e)}
</button> >
)}
<div className="resize-handle" onMouseDown={(e) => handleElementResizeStart(element.elementUuid, e)}>
<ResizeIcon /> <ResizeIcon />
</div> </div>
</> </>

View File

@@ -1,12 +1,21 @@
import React, { RefObject } from "react"; import React, { RefObject, useState } from "react";
import DataSourceSelector from "../../../ui/inputs/DataSourceSelector"; import DataSourceSelector from "../../../ui/inputs/DataSourceSelector";
import RenameInput from "../../../ui/inputs/RenameInput"; import RenameInput from "../../../ui/inputs/RenameInput";
import { AlignJustifyIcon, AlignLeftIcon, AlignRightIcon, FlexColumnIcon, FlexRowIcon, FlexRowReverseIcon } from "../../../icons/ExportCommonIcons"; import {
AlignJustifyIcon,
AlignLeftIcon,
AlignRightIcon,
ArrowIcon,
FlexColumnIcon,
FlexRowIcon,
FlexRowReverseIcon,
} from "../../../icons/ExportCommonIcons";
import InputWithDropDown from "../../../ui/inputs/InputWithDropDown"; import InputWithDropDown from "../../../ui/inputs/InputWithDropDown";
import InputRange from "../../../ui/inputs/InputRange"; import InputRange from "../../../ui/inputs/InputRange";
import { getAlphaFromRgba, hexToRgba, rgbaToHex } from "../../functions/helpers/colorHandlers"; import { rgbaToHex } from "../../functions/helpers/colorHandlers";
import { ExtendedCSSProperties, UIElement } from "../../../../types/exportedTypes"; import { ExtendedCSSProperties, UIElement } from "../../../../types/exportedTypes";
import { getCurrentElementStyleValue } from "../../functions/helpers/getCurrentElementStyleValue"; import { getCurrentElementStyleValue } from "../../functions/helpers/getCurrentElementStyleValue";
import { ResetIcon } from "../../../icons/SimulationIcons";
interface ElementDesignProps { interface ElementDesignProps {
element: UIElement | undefined; element: UIElement | undefined;
@@ -15,15 +24,35 @@ interface ElementDesignProps {
selectedBlock: string; selectedBlock: string;
selectedElement: string; selectedElement: string;
updateElementStyle: (blockId: string, elementId: string, style: ExtendedCSSProperties) => void; updateElementStyle: (blockId: string, elementId: string, style: ExtendedCSSProperties) => void;
updateElementSize: (blockId: string, elementId: string, size: { width: number; height: number }) => void; updateElementSize: (
updateElementPosition: (blockId: string, elementId: string, position: { x: number; y: number }) => void; blockId: string,
updateElementPositionType: (blockId: string, elementId: string, positionType: "relative" | "absolute" | "fixed") => void; elementId: string,
size: { width: number; height: number }
) => void;
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; updateElementZIndex: (blockId: string, elementId: string, zIndex: number) => void;
updateElementData: (blockId: string, elementId: string, updates: Partial<ElementDataBinding>) => void; updateElementData: (
blockId: string,
elementId: string,
updates: Partial<ElementDataBinding>
) => void;
updateGraphData: (blockId: string, elementId: string, newData: GraphDataPoint[]) => void; updateGraphData: (blockId: string, elementId: string, newData: GraphDataPoint[]) => void;
updateGraphTitle: (blockId: string, elementId: string, title: string) => void; updateGraphTitle: (blockId: string, elementId: string, title: string) => void;
updateGraphType: (blockId: string, elementId: string, type: GraphTypes) => void; updateGraphType: (blockId: string, elementId: string, type: GraphTypes) => void;
updateDataType: (blockId: string, elementId: string, dataType: "single-machine" | "multiple-machine") => void; updateDataType: (
blockId: string,
elementId: string,
dataType: "single-machine" | "multiple-machine"
) => void;
updateCommonValue: (blockId: string, elementId: string, commonValue: string) => void; updateCommonValue: (blockId: string, elementId: string, commonValue: string) => void;
updateDataValue: (blockId: string, elementId: string, dataValue: string | string[]) => void; updateDataValue: (blockId: string, elementId: string, dataValue: string | string[]) => void;
updateDataSource: (blockId: string, elementId: string, dataSource: string | string[]) => void; updateDataSource: (blockId: string, elementId: string, dataSource: string | string[]) => void;
@@ -43,20 +72,14 @@ const ElementDesign: React.FC<ElementDesignProps> = ({
updateElementPosition, updateElementPosition,
updateElementPositionType, updateElementPositionType,
updateElementZIndex, updateElementZIndex,
updateElementData,
updateGraphData,
updateGraphTitle, updateGraphTitle,
updateGraphType, updateGraphType,
updateDataType,
updateCommonValue,
updateDataValue,
updateDataSource,
handleRemoveElement,
setSwapSource, setSwapSource,
setShowSwapUI, setShowSwapUI,
}) => { }) => {
const [color, setColor] = useState("#000000");
return ( return (
<div style={{ display: "flex", flexDirection: "column", gap: 6 }}> <div style={{ display: "flex", flexDirection: "column", gap: 6 }} ref={elementEditorRef}>
{element?.type === "graph" && ( {element?.type === "graph" && (
<div className="design-section"> <div className="design-section">
<DataSourceSelector <DataSourceSelector
@@ -69,7 +92,11 @@ const ElementDesign: React.FC<ElementDesignProps> = ({
{ id: "radar", label: "Radar Chart" }, { id: "radar", label: "Radar Chart" },
]} ]}
onSelect={(newValue) => { onSelect={(newValue) => {
updateGraphType(selectedBlock, selectedElement, newValue.id as GraphTypes); updateGraphType(
selectedBlock,
selectedElement,
newValue.id as GraphTypes
);
}} }}
showEyeDropper={false} showEyeDropper={false}
/> />
@@ -82,8 +109,16 @@ const ElementDesign: React.FC<ElementDesignProps> = ({
{["relative", "absolute"].map((position) => ( {["relative", "absolute"].map((position) => (
<div <div
key={position} key={position}
className={`type ${currentElement.positionType === position ? "active" : ""}`} className={`type ${
onClick={() => updateElementPositionType(selectedBlock, selectedElement, position as "relative" | "absolute" | "fixed")} currentElement.positionType === position ? "active" : ""
}`}
onClick={() =>
updateElementPositionType(
selectedBlock,
selectedElement,
position as "relative" | "absolute" | "fixed"
)
}
> >
{position.charAt(0).toUpperCase() + position.slice(1)} {position.charAt(0).toUpperCase() + position.slice(1)}
</div> </div>
@@ -92,16 +127,48 @@ const ElementDesign: React.FC<ElementDesignProps> = ({
<div className="position-canvas"> <div className="position-canvas">
<div className="canvas"> <div className="canvas">
<div className="value padding-top"> <div className="value padding-top">
<RenameInput value={getCurrentElementStyleValue(currentElement, "padding") ? String(getCurrentElementStyleValue(currentElement, "padding")) : "120"} /> <RenameInput
value={
getCurrentElementStyleValue(currentElement, "padding")
? String(
getCurrentElementStyleValue(currentElement, "padding")
)
: "120"
}
/>
</div> </div>
<div className="value padding-right"> <div className="value padding-right">
<RenameInput value={getCurrentElementStyleValue(currentElement, "padding") ? String(getCurrentElementStyleValue(currentElement, "padding")) : "120"} /> <RenameInput
value={
getCurrentElementStyleValue(currentElement, "padding")
? String(
getCurrentElementStyleValue(currentElement, "padding")
)
: "120"
}
/>
</div> </div>
<div className="value padding-bottom"> <div className="value padding-bottom">
<RenameInput value={getCurrentElementStyleValue(currentElement, "padding") ? String(getCurrentElementStyleValue(currentElement, "padding")) : "120"} /> <RenameInput
value={
getCurrentElementStyleValue(currentElement, "padding")
? String(
getCurrentElementStyleValue(currentElement, "padding")
)
: "120"
}
/>
</div> </div>
<div className="value padding-left"> <div className="value padding-left">
<RenameInput value={getCurrentElementStyleValue(currentElement, "padding") ? String(getCurrentElementStyleValue(currentElement, "padding")) : "120"} /> <RenameInput
value={
getCurrentElementStyleValue(currentElement, "padding")
? String(
getCurrentElementStyleValue(currentElement, "padding")
)
: "120"
}
/>
</div> </div>
</div> </div>
</div> </div>
@@ -109,72 +176,184 @@ const ElementDesign: React.FC<ElementDesignProps> = ({
<div className="alignments-section"> <div className="alignments-section">
<div className="section"> <div className="section">
<div <div
className={`icon ${getCurrentElementStyleValue(currentElement, "textAlign") === "right" ? "active" : ""}`} className={`icon ${
onClick={() => updateElementStyle(selectedBlock, selectedElement, { textAlign: "right" })} getCurrentElementStyleValue(currentElement, "textAlign") === "right"
? "active"
: ""
}`}
onClick={() =>
updateElementStyle(selectedBlock, selectedElement, {
textAlign: "right",
})
}
> >
<AlignRightIcon /> <AlignRightIcon />
</div> </div>
<div <div
className={`icon ${getCurrentElementStyleValue(currentElement, "textAlign") === "justify" ? "active" : ""}`} className={`icon ${
onClick={() => updateElementStyle(selectedBlock, selectedElement, { textAlign: "justify" })} getCurrentElementStyleValue(currentElement, "textAlign") ===
"justify"
? "active"
: ""
}`}
onClick={() =>
updateElementStyle(selectedBlock, selectedElement, {
textAlign: "justify",
})
}
> >
<AlignJustifyIcon /> <AlignJustifyIcon />
</div> </div>
<div <div
className={`icon ${getCurrentElementStyleValue(currentElement, "textAlign") === "left" ? "active" : ""}`} className={`icon ${
onClick={() => updateElementStyle(selectedBlock, selectedElement, { textAlign: "left" })} getCurrentElementStyleValue(currentElement, "textAlign") === "left"
? "active"
: ""
}`}
onClick={() =>
updateElementStyle(selectedBlock, selectedElement, {
textAlign: "left",
})
}
> >
<AlignLeftIcon /> <AlignLeftIcon />
</div> </div>
</div> </div>
<div className="section"> <div className="section">
<div <div
className={`icon ${((currentElement.style.flexDirection as string) || "row") === "row" ? "active" : ""}`} className={`icon ${
onClick={() => updateElementStyle(selectedBlock, selectedElement, { flexDirection: "row" })} ((currentElement.style.flexDirection as string) || "row") === "row"
? "active"
: ""
}`}
onClick={() =>
updateElementStyle(selectedBlock, selectedElement, {
flexDirection: "row",
})
}
> >
<FlexRowIcon /> <FlexRowIcon />
</div> </div>
<div <div
className={`icon ${((currentElement.style.flexDirection as string) || "row") === "column" ? "active" : ""}`} className={`icon ${
onClick={() => updateElementStyle(selectedBlock, selectedElement, { flexDirection: "column" })} ((currentElement.style.flexDirection as string) || "row") ===
"column"
? "active"
: ""
}`}
onClick={() =>
updateElementStyle(selectedBlock, selectedElement, {
flexDirection: "column",
})
}
> >
<FlexColumnIcon /> <FlexColumnIcon />
</div> </div>
<div <div
className={`icon ${((currentElement.style.flexDirection as string) || "row") === "row-reverse" ? "active" : ""}`} className={`icon ${
onClick={() => updateElementStyle(selectedBlock, selectedElement, { flexDirection: "row-reverse" })} ((currentElement.style.flexDirection as string) || "row") ===
"row-reverse"
? "active"
: ""
}`}
onClick={() =>
updateElementStyle(selectedBlock, selectedElement, {
flexDirection: "row-reverse",
})
}
> >
<FlexRowReverseIcon /> <FlexRowReverseIcon />
</div> </div>
<div <div
className={`icon ${((currentElement.style.flexDirection as string) || "row") === "column-reverse" ? "active" : ""}`} className={`icon ${
onClick={() => updateElementStyle(selectedBlock, selectedElement, { flexDirection: "column-reverse" })} ((currentElement.style.flexDirection as string) || "row") ===
"column-reverse"
? "active"
: ""
}`}
onClick={() =>
updateElementStyle(selectedBlock, selectedElement, {
flexDirection: "column-reverse",
})
}
> >
<FlexRowReverseIcon /> <FlexRowReverseIcon />
</div> </div>
</div> </div>
</div> </div>
<div className="design-section-footer"> <div className="design-section-footer">
<InputWithDropDown <div className="layer-system">
label="Layer" <InputWithDropDown
value={String(currentElement.zIndex || 1)} label="Layer"
placeholder={"Layer"} value={String(currentElement.zIndex || 1)}
onChange={(newValue: string) => updateElementZIndex(selectedBlock, selectedElement, Number(newValue))} placeholder={"Layer"}
/> onChange={(newValue: string) =>
updateElementZIndex(
selectedBlock,
selectedElement,
Number(newValue)
)
}
/>
<button
className="increase-z"
onClick={() => {
updateElementZIndex(
selectedBlock,
selectedElement,
Number(currentElement.zIndex ? currentElement.zIndex + 1 : 1)
);
}}
>
<ArrowIcon />
</button>
<button
className="decrease-z"
onClick={() => {
updateElementZIndex(
selectedBlock,
selectedElement,
Number(currentElement.zIndex ? currentElement.zIndex - 1 : 1)
);
}}
>
<ArrowIcon />
</button>
<button
className="reset"
onClick={() => {
updateElementZIndex(selectedBlock, selectedElement, Number(1));
}}
>
<ResetIcon />
</button>
</div>
<InputRange <InputRange
label={"Blur"} label={"Blur"}
min={0} min={0}
max={40} max={40}
value={parseInt(getCurrentElementStyleValue(currentElement, "backdropFilter")?.match(/\d+/)?.[0] || "0")} value={parseInt(
onChange={(value: number) => updateElementStyle(selectedBlock, selectedElement, { backdropFilter: `blur(${Number(value)}px)` })} getCurrentElementStyleValue(currentElement, "backdropFilter")?.match(
/\d+/
)?.[0] || "0"
)}
onChange={(value: number) =>
updateElementStyle(selectedBlock, selectedElement, {
backdropFilter: `blur(${Number(value)}px)`,
})
}
/> />
<InputRange <InputRange
label={"Radius"} label={"Radius"}
min={0} min={0}
max={8} max={8}
value={parseInt(getCurrentElementStyleValue(currentElement, "borderRadius") || "") || 8} value={
parseInt(
getCurrentElementStyleValue(currentElement, "borderRadius") || ""
) || 8
}
onChange={(newValue: number) => { onChange={(newValue: number) => {
updateElementStyle(selectedBlock, selectedElement, { updateElementStyle(selectedBlock, selectedElement, {
borderRadius: Number(newValue), borderRadius: Number(newValue),
@@ -211,7 +390,10 @@ const ElementDesign: React.FC<ElementDesignProps> = ({
/> />
<InputWithDropDown <InputWithDropDown
label="Width" label="Width"
value={String(currentElement.size?.width ?? (currentElement.type === "graph" ? 400 : 200))} value={String(
currentElement.size?.width ??
(currentElement.type === "graph" ? 400 : 200)
)}
placeholder={"Width"} placeholder={"Width"}
onChange={(newValue: string) => { onChange={(newValue: string) => {
updateElementSize(selectedBlock, selectedElement, { updateElementSize(selectedBlock, selectedElement, {
@@ -222,7 +404,10 @@ const ElementDesign: React.FC<ElementDesignProps> = ({
/> />
<InputWithDropDown <InputWithDropDown
label="Height" label="Height"
value={String(currentElement.size?.height ?? (currentElement.type === "graph" ? 200 : 60))} value={String(
currentElement.size?.height ??
(currentElement.type === "graph" ? 200 : 60)
)}
placeholder={"Height"} placeholder={"Height"}
onChange={(newValue: string) => { onChange={(newValue: string) => {
updateElementSize(selectedBlock, selectedElement, { updateElementSize(selectedBlock, selectedElement, {
@@ -241,20 +426,42 @@ const ElementDesign: React.FC<ElementDesignProps> = ({
<div className="left"> <div className="left">
<input <input
type="color" type="color"
value={rgbaToHex(getCurrentElementStyleValue(currentElement, "backgroundColor") || "rgba(50,50,50,1)")} defaultValue={rgbaToHex(
getCurrentElementStyleValue(currentElement, "backgroundColor")
)}
value={color}
onChange={(e) => { onChange={(e) => {
const currentBg = getCurrentElementStyleValue(currentElement, "backgroundColor") || "rgba(50,50,50,1)"; updateElementStyle(selectedBlock, selectedElement, {
const currentAlpha = getAlphaFromRgba(currentBg); backgroundColor: color,
const newBg = hexToRgba(e.target.value, currentAlpha); });
updateElementStyle(selectedBlock, selectedElement, { backgroundColor: newBg }); setColor(e.target.value);
}} }}
/> />
<div className="colorValue">{rgbaToHex(getCurrentElementStyleValue(currentElement, "backgroundColor") || "rgba(50,50,50,1)")}</div> <form
onSubmit={(e) => {
e.preventDefault();
updateElementStyle(selectedBlock, selectedElement, {
backgroundColor: color,
});
}}
>
<input
className="colorValue"
value={color}
onChange={(e) => {
setColor(e.target.value);
}}
onBlur={(e) => {
updateElementStyle(selectedBlock, selectedElement, {
backgroundColor: color,
});
}}
/>
</form>
</div> </div>
</div> </div>
</div> </div>
<div className="footer">Swap with Another Element</div>
</div> </div>
); );
}; };

View File

@@ -472,7 +472,7 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
className="panel element-editor-panel" className="panel element-editor-panel"
style={{ position: "fixed", left: position.x, top: position.y, zIndex: 1000 }} style={{ position: "fixed", left: position.x, top: position.y, zIndex: 1000 }}
> >
<div className="resize-icon" onPointerDown={startDrag} onDoubleClick={resetPosition}> <div className="free-move-button" onPointerDown={startDrag} onDoubleClick={resetPosition}>
<ResizeHeightIcon /> <ResizeHeightIcon />
</div> </div>
<div className="header"> <div className="header">

View File

@@ -10,7 +10,7 @@ export const handleBackgroundAlphaChange = (
): void => { ): void => {
if (selectedBlock) { if (selectedBlock) {
const currentHex = rgbaToHex(getCurrentBlockStyleValue(currentBlock, "backgroundColor")); const currentHex = rgbaToHex(getCurrentBlockStyleValue(currentBlock, "backgroundColor"));
const newBackgroundColor = hexToRgba(currentHex, alpha / 100); const newBackgroundColor = hexToRgba(currentHex, alpha);
updateBlockStyle(selectedBlock, { backgroundColor: newBackgroundColor }); updateBlockStyle(selectedBlock, { backgroundColor: newBackgroundColor });
} }
}; };

View File

@@ -38,7 +38,8 @@
} }
&.edit-mode-active { &.edit-mode-active {
background-color: #ff4444; background-color: var(--log-error-background-color);
color: var(--log-error-text-color);
} }
&.primary { &.primary {
@@ -87,6 +88,9 @@
&.edit-mode { &.edit-mode {
cursor: pointer; cursor: pointer;
&:hover {
outline: 1px solid var(--border-color-accent);
}
} }
&.draggable { &.draggable {
@@ -175,18 +179,30 @@
margin: 5px; margin: 5px;
border-radius: 4px; border-radius: 4px;
padding: 8px; padding: 8px;
// transition: all 0.2s ease;
user-select: none; user-select: none;
border: 1px solid transparent; border: 1px solid transparent;
&.edit-mode {
transition: all 0.2s;
outline: 1px solid transparent;
&:hover {
outline-color: var(--border-color);
transform: translateY(-2px);
opacity: 0.8;
}
}
&.absolute { &.absolute {
position: absolute; position: absolute;
margin: 0; margin: 0;
} }
&.selected { &.selected {
border: 2px solid #ff9800; outline: 2px solid var(--border-color-accent) !important;
background-color: rgba(255, 152, 0, 0.1); background-color: rgba(98, 0, 255, 0.089);
&:hover {
transform: translateY(0);
}
} }
&.swap-source { &.swap-source {
@@ -195,7 +211,7 @@
} }
&.swap-target { &.swap-target {
border: 2px dashed #ff9800; border: 2px dashed var(--border-color-accent);
} }
&.graph { &.graph {
@@ -240,7 +256,7 @@
position: absolute; position: absolute;
bottom: 2px; bottom: 2px;
right: 2px; right: 2px;
background-color: rgba(33, 150, 243, 0.8); background-color: var(--background-color-accent);
cursor: nwse-resize; cursor: nwse-resize;
border-radius: 2px; border-radius: 2px;
opacity: 0; opacity: 0;
@@ -285,13 +301,23 @@
min-height: 60vh; min-height: 60vh;
padding: 12px; padding: 12px;
.resize-icon { .free-move-button {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
z-index: 100; z-index: 100;
svg { height: 28px;
cursor: grab; width: 68px;
top: 2px;
left: 50%;
border-radius: 28px;
transition: all 0.2s;
transform: translateX(-50%);
position: absolute;
cursor: grab;
&:hover {
background-color: var(--background-color-drop-down);
} }
} }
@@ -361,11 +387,9 @@
background: #8d70ad33; background: #8d70ad33;
height: 110px; height: 110px;
border-radius: 17px; border-radius: 17px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
position: relative; position: relative;
.canvas { .canvas {
@@ -376,35 +400,43 @@
.value { .value {
position: absolute; position: absolute;
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 24px;
text-align: center;
input { input,
// border: none; span {
// outline: none; width: 36px;
width: 20px; height: auto;
padding: 0;
text-align: center;
background: transparent; background: transparent;
} }
&:nth-child(1) { &.padding-top {
top: 20px; top: 12px;
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
} }
&:nth-child(2) { &.padding-right {
top: 50%; top: 50%;
right: 35px; right: 12px;
transform: translateY(-50%); transform: translateY(-50%);
} }
&:nth-child(3) { &.padding-bottom {
bottom: 20px; bottom: 12px;
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
} }
&:nth-child(4) { &.padding-left {
top: 50%; top: 50%;
left: 35px; left: 12px;
transform: translateY(-50%); transform: translateY(-50%);
} }
} }
@@ -420,14 +452,18 @@
border-radius: 100px; border-radius: 100px;
padding: 6px; padding: 6px;
display: flex; display: flex;
align-items: center;
justify-content: space-evenly;
gap: 10px; gap: 10px;
width: fit-content; width: 100%;
margin: 0;
} }
.icon { .icon {
&.active { &.active {
background-color: var(--background-color-button); background-color: var(--background-color-button);
border-radius: 2px; border-radius: 8px;
outline: 2px solid var(--border-color-accent);
} }
} }
} }
@@ -462,7 +498,11 @@
.colorValue { .colorValue {
width: 100%; width: 100%;
background: linear-gradient(90.85deg, rgba(240, 228, 255, 0.3) 3.6%, rgba(211, 174, 253, 0.3) 96.04%); background: linear-gradient(
90.85deg,
rgba(240, 228, 255, 0.3) 3.6%,
rgba(211, 174, 253, 0.3) 96.04%
);
text-align: center; text-align: center;
padding: 4px 0; padding: 4px 0;
border-radius: 100px; border-radius: 100px;
@@ -472,22 +512,52 @@
.design-section-footer { .design-section-footer {
display: flex; display: flex;
flex-direction: column;
gap: 7px; gap: 7px;
.layer-system {
label, display: flex;
.input { align-items: center;
width: auto; gap: 3px;
.increase-z,
.decrease-z,
.reset {
min-width: 26px;
height: 22px;
border-radius: 14px;
background: var(--background-color-drop-down);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background 0.2s;
&:hover {
background: var(--background-color-accent);
}
svg {
pointer-events: none;
}
}
.increase-z {
svg {
rotate: 180deg;
}
}
} }
.input,
input { input {
width: 40px; width: 100%;
} }
.input-range-container, .input-range-container,
.value-field-container { .value-field-container {
width: 100%; width: 100%;
display: flex; display: flex;
gap: 6px; gap: 0px;
.label {
min-width: 82px;
}
.input-container { .input-container {
width: 100%; width: 100%;
@@ -496,7 +566,11 @@
input[type="range"] { input[type="range"] {
margin: 0; margin: 0;
width: 70px; width: 136px;
}
input[type="number"] {
margin: 0;
width: 48px;
} }
} }
} }
@@ -613,9 +687,6 @@
text-align: center; text-align: center;
} }
.type-switch {
}
.fields-wrapper { .fields-wrapper {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -704,9 +775,10 @@
} }
} }
.footer { .swap-button {
text-align: center; text-align: center;
color: #ccacff; color: #ccacff;
cursor: pointer;
} }
} }
} }