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;
|
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>
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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 });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user