feat: introduce Simulation Dashboard with core components for elements, blocks, control panel, and dashboard editing.
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
|
||||
@@ -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">
|
||||
<div className="layer-system">
|
||||
<InputWithDropDown
|
||||
label="Layer"
|
||||
value={String(currentElement.zIndex || 1)}
|
||||
placeholder={"Layer"}
|
||||
onChange={(newValue: string) => updateElementZIndex(selectedBlock, selectedElement, Number(newValue))}
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user