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

View File

@@ -1,10 +1,8 @@
import { useParams } from "react-router-dom";
import React, { useState, useRef, useEffect } from "react";
import { dataModelManager } from "./data/dataModel";
import ControlPanel from "./ControlPanel";
import SwapModal from "./SwapModal";
import { Block } from "../../types/exportedTypes";
import DataModelPanel from "./components/models/DataModelPanel";
import AnalyzerManager from "./AnalyzerManager";
import { useSceneContext } from "../../modules/scene/sceneContext";
@@ -43,9 +41,7 @@ const DashboardEditor: React.FC = () => {
updateBlock,
// Peek methods
peekAddBlock,
peekRemoveBlock,
peekAddElement,
peekRemoveElement,
peekUpdateBlockStyle,
peekUpdateBlockSize,
peekUpdateBlockPosition,
@@ -79,8 +75,6 @@ const DashboardEditor: React.FC = () => {
} | null>(null);
const [showSwapUI, setShowSwapUI] = useState(false);
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 [draggingBlock, setDraggingBlock] = useState<string | null>(null);
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 }
);
// 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
useEffect(() => {
const handleClickOutside = (event: MouseEvent): void => {
@@ -409,8 +369,6 @@ const DashboardEditor: React.FC = () => {
await updateBackend(newBlock);
}
}}
showDataModelPanel={showDataModelPanel}
setShowDataModelPanel={setShowDataModelPanel}
/>
)}
@@ -648,8 +606,6 @@ const DashboardEditor: React.FC = () => {
)}
</div>
{showDataModelPanel && editMode && <DataModelPanel dataModel={dataModel} dataModelManager={dataModelManager} />}
{showSwapUI && <SwapModal setShowSwapUI={setShowSwapUI} setSwapSource={setSwapSource} />}
</div>
);

View File

@@ -6,13 +6,10 @@ import { handleBackgroundColorChange } from "../../functions/helpers/handleBackg
import { handleBackgroundAlphaChange } from "../../functions/helpers/handleBackgroundAlphaChange";
import { handleBlurAmountChange } from "../../functions/helpers/handleBlurAmountChange";
import InputRange from "../../../ui/inputs/InputRange";
import RegularDropDown from "../../../ui/inputs/RegularDropDown";
import { DeleteIcon } from "../../../icons/ContextMenuIcons";
import InputWithDropDown from "../../../ui/inputs/InputWithDropDown";
import { AddIcon, DeviceIcon, ParametersIcon, ResizeHeightIcon } from "../../../icons/ExportCommonIcons";
import DataDetailedDropdown from "../../../ui/inputs/DataDetailedDropdown";
import { ResizeHeightIcon } from "../../../icons/ExportCommonIcons";
import RenameInput from "../../../ui/inputs/RenameInput";
import DataSourceSelector from "../../../ui/inputs/DataSourceSelector";
import { useVisualizationStore } from "../../../../store/visualization/useVisualizationStore";
interface BlockEditorProps {
@@ -22,7 +19,10 @@ 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;
}
@@ -33,7 +33,6 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
selectedBlock,
updateBlockStyle,
updateBlockSize,
updateBlockPosition,
updateBlockPositionType,
updateBlockZIndex,
handleRemoveBlock,
@@ -136,14 +135,22 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
<div
ref={(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;
}
}}
className="panel block-editor-panel"
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 />
</div>
@@ -192,8 +199,15 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
{["relative", "absolute"].map((position) => (
<div
key={position}
className={`type ${currentBlock.positionType === position ? "active" : ""}`}
onClick={() => 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)}
</div>
@@ -202,28 +216,70 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
<div className="position-canvas">
<div className="canvas">
<div className="value padding-top">
<RenameInput value={getCurrentBlockStyleValue(currentBlock, "padding") ? String(getCurrentBlockStyleValue(currentBlock, "padding")) : "120"} />
<RenameInput
value={
getCurrentBlockStyleValue(currentBlock, "padding")
? String(
getCurrentBlockStyleValue(currentBlock, "padding")
)
: "120"
}
/>
</div>
<div className="value padding-right">
<RenameInput value={getCurrentBlockStyleValue(currentBlock, "padding") ? String(getCurrentBlockStyleValue(currentBlock, "padding")) : "120"} />
<RenameInput
value={
getCurrentBlockStyleValue(currentBlock, "padding")
? String(
getCurrentBlockStyleValue(currentBlock, "padding")
)
: "120"
}
/>
</div>
<div className="value padding-bottom">
<RenameInput value={getCurrentBlockStyleValue(currentBlock, "padding") ? String(getCurrentBlockStyleValue(currentBlock, "padding")) : "120"} />
<RenameInput
value={
getCurrentBlockStyleValue(currentBlock, "padding")
? String(
getCurrentBlockStyleValue(currentBlock, "padding")
)
: "120"
}
/>
</div>
<div className="value padding-left">
<RenameInput value={getCurrentBlockStyleValue(currentBlock, "padding") ? String(getCurrentBlockStyleValue(currentBlock, "padding")) : "120"} />
<RenameInput
value={
getCurrentBlockStyleValue(currentBlock, "padding")
? String(
getCurrentBlockStyleValue(currentBlock, "padding")
)
: "120"
}
/>
</div>
</div>
</div>
<div className="design-section-footer">
<InputWithDropDown label="Layer" value={String(currentBlock.zIndex || 1)} placeholder={"Layer"} onChange={(newValue) => updateBlockZIndex(selectedBlock, Number(newValue))} />
<InputWithDropDown
label="Layer"
value={String(currentBlock.zIndex || 1)}
placeholder={"Layer"}
onChange={(newValue) =>
updateBlockZIndex(selectedBlock, Number(newValue))
}
/>
<InputRange
label={"Radius"}
<InputWithDropDown
label="Corner Radius"
min={0}
max={8}
value={parseInt(getCurrentBlockStyleValue(currentBlock, "borderRadius")) || 8}
value={String(
parseInt(getCurrentBlockStyleValue(currentBlock, "borderRadius")) ||
8
)}
placeholder={"Width"}
onChange={(newValue) => {
updateBlockStyle(selectedBlock, {
borderRadius: Number(newValue),
@@ -240,23 +296,81 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
<div className="left">
<input
type="color"
value={rgbaToHex(getCurrentBlockStyleValue(currentBlock, "backgroundColor"))}
value={rgbaToHex(
getCurrentBlockStyleValue(currentBlock, "backgroundColor")
)}
// 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);
}}
/>
<div className="colorValue">{color}</div>
<form
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>
<InputRange
label={"Opacity"}
min={0}
max={8}
value={Math.round(getAlphaFromRgba(getCurrentBlockStyleValue(currentBlock, "backgroundColor")) * 100)}
// onChange={(value: number) => handleBackgroundAlphaChange(currentBlock, selectedBlock, updateBlockStyle, Number(value))}
onChange={(value: number) => handleBlurAmountChange(selectedBlock, updateBlockStyle, Number(value))}
max={1}
step={0.1}
value={Math.round(
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>

View File

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

View File

@@ -472,7 +472,7 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
className="panel element-editor-panel"
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 />
</div>
<div className="header">

View File

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

View File

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