Merge remote-tracking branch 'origin/main-demo' into main-dev

This commit is contained in:
2025-12-20 17:09:42 +05:30
7 changed files with 278 additions and 184 deletions

View File

@@ -8,9 +8,11 @@ import { handleBlurAmountChange } from "../../functions/helpers/handleBlurAmount
import InputRange from "../../../ui/inputs/InputRange";
import { DeleteIcon } from "../../../icons/ContextMenuIcons";
import InputWithDropDown from "../../../ui/inputs/InputWithDropDown";
import { ResizeHeightIcon } from "../../../icons/ExportCommonIcons";
import { ArrowIcon, ResizeHeightIcon } from "../../../icons/ExportCommonIcons";
import RenameInput from "../../../ui/inputs/RenameInput";
import { useVisualizationStore } from "../../../../store/visualization/useVisualizationStore";
import { ResetIcon } from "../../../icons/SimulationIcons";
import { Color } from "../../../ui/inputs/Color";
interface BlockEditorProps {
blockEditorRef: RefObject<HTMLDivElement>;
@@ -27,6 +29,9 @@ interface BlockEditorProps {
const BlockEditor: React.FC<BlockEditorProps> = ({ blockEditorRef, currentBlock, selectedBlock, updateBlockStyle, updateBlockSize, updateBlockPositionType, updateBlockZIndex, handleRemoveBlock }) => {
const [color, setColor] = useState("#000000");
useEffect(() => {
setColor(rgbaToHex(getCurrentBlockStyleValue(currentBlock, "backgroundColor") || "#000000"));
}, [currentBlock]);
// Use position from VisualizationStore
const { editorPosition, setEditorPosition } = useVisualizationStore();
const panelRef = useRef<HTMLDivElement | null>(null);
@@ -87,6 +92,8 @@ const BlockEditor: React.FC<BlockEditorProps> = ({ blockEditorRef, currentBlock,
};
// Initialize position on mount
// compute exact initial position once we have panel dimensions
useEffect(() => {
if (!editorPosition) {
const { width } = getPanelDimensions();
@@ -277,55 +284,73 @@ const BlockEditor: React.FC<BlockEditorProps> = ({ blockEditorRef, currentBlock,
</div>
<div className="design-section-footer">
<InputWithDropDown label="Layer" value={String(currentBlock.zIndex || 1)} placeholder={"Layer"} onChange={(newValue) => updateBlockZIndex(selectedBlock, Number(newValue))} />
<InputWithDropDown
label="Border Radius"
min={0}
value={String(parseInt(getCurrentBlockStyleValue(currentBlock, "borderRadius")) || 8)}
placeholder={"Width"}
onChange={(newValue) => {
updateBlockStyle(selectedBlock, {
borderRadius: Number(newValue),
});
}}
/>
<div className="layer-system">
<InputWithDropDown
label="Layer"
value={String(currentBlock.zIndex ?? 1)}
placeholder={"Layer"}
onChange={(newValue) => updateBlockZIndex(selectedBlock, Number(newValue))}
/>
<button
className="increase-z"
onClick={() => {
updateBlockZIndex(selectedBlock, Number(currentBlock.zIndex) + 1);
}}
>
<ArrowIcon />
</button>
<button
className="decrease-z"
onClick={() => {
updateBlockZIndex(selectedBlock, Number(currentBlock.zIndex) - 1);
}}
>
<ArrowIcon />
</button>
<button
className="reset"
onClick={() => {
updateBlockZIndex(selectedBlock, Number(1));
}}
>
<ResetIcon />
</button>
</div>
</div>
</div>
<div className="design-section">
<div className="section-header">Apperance</div>
<InputRange
label={"Border Radius"}
min={0}
max={100}
value={parseInt(getCurrentBlockStyleValue(currentBlock, "borderRadius")) || 8}
onChange={(newValue) => {
updateBlockStyle(selectedBlock, {
borderRadius: Number(newValue),
});
}}
/>
</div>
<div className="design-section">
<div className="section-header">Background</div>
<div className="data-picker">
<div className="label">Color</div>
<div className="left">
<input
type="color"
value={rgbaToHex(getCurrentBlockStyleValue(currentBlock, "backgroundColor"))}
// onChange={(e) => setColor(e.target.value)}
onChange={(e) => {
handleBackgroundColorChange(currentBlock, selectedBlock, updateBlockStyle, e.target.value);
setColor(e.target.value);
}}
/>
<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>
<Color
label="Color"
value={color}
onChange={(value) => {
setColor(value);
if (/^#?([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/.test(value)) {
handleBackgroundColorChange(currentBlock, selectedBlock, updateBlockStyle, value);
}
}}
onUpdate={(value) => {
handleBackgroundColorChange(currentBlock, selectedBlock, updateBlockStyle, value);
}}
/>
<InputRange
label={"Opacity"}
min={0}

View File

@@ -8,6 +8,7 @@ import { getAlphaFromRgba, hexToRgba, rgbaToHex } from "../../functions/helpers/
import { ExtendedCSSProperties, UIElement } from "../../../../types/exportedTypes";
import { getCurrentElementStyleValue } from "../../functions/helpers/getCurrentElementStyleValue";
import { ResetIcon } from "../../../icons/SimulationIcons";
import { Color } from "../../../ui/inputs/Color";
interface ElementDesignProps {
element: UIElement | undefined;
@@ -277,43 +278,27 @@ const ElementDesign: React.FC<ElementDesignProps> = ({
<div className="design-section element-color">
<div className="section-header">Background</div>
<div className="data-picker">
<div className="label">Color</div>
<div className="left">
<input
type="color"
value={color}
onChange={(e) => {
updateElementStyle(selectedBlock, selectedElement, {
backgroundColor: hexToRgba(e.target.value),
});
setColor(e.target.value);
}}
/>
<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>
<Color
label="Color"
value={color}
onChange={(value) => {
const currentBg = getCurrentElementStyleValue(currentElement, "backgroundColor") || "rgba(0,0,0,1)";
const currentAlpha = getAlphaFromRgba(currentBg);
updateElementStyle(selectedBlock, selectedElement, {
backgroundColor: hexToRgba(value, currentAlpha),
});
setColor(value);
}}
onUpdate={(value) => {
const currentBg = getCurrentElementStyleValue(currentElement, "backgroundColor") || "rgba(0,0,0,1)";
const currentAlpha = getAlphaFromRgba(currentBg);
updateElementStyle(selectedBlock, selectedElement, {
backgroundColor: hexToRgba(value, currentAlpha),
});
}}
/>
<InputRange
label={"Opacity"}
min={0}

View File

@@ -572,73 +572,76 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
{selectType === "data" && (
<div className="data-details">
{element?.type === "label-value" && (
<div className="data-wrapper">
<InputWithDropDown
label="Label"
type="string"
value={element.dataBinding?.label || ""}
placeholder={"Label 1"}
onChange={(value) => {
updateElementData(selectedBlock, selectedElement, { label: value });
}}
/>
<div className="data">
<DataDetailedDropdown
title="Data Source"
placeholder="Select assets"
sections={[
{
title: "Global",
items: [{ id: "global", label: "Global", icon: <DeviceIcon /> }],
},
{
title: "Assets",
items: getAssetDropdownItems(),
},
]}
value={
element.dataBinding?.dataSource
? {
id: element.dataBinding.dataSource as string,
label:
getEventByModelUuid(selectedProduct.productUuid, element.dataBinding.dataSource as string)?.modelName ??
(element.dataBinding.dataSource === "global" ? "Global" : ""),
icon: <DeviceIcon />,
}
: null
}
<>
<div className="design-section">
<div className="sub-header">Data Handling</div>
<InputWithDropDown
label="Label"
type="string"
value={element.dataBinding?.label || ""}
placeholder={"Label 1"}
onChange={(value) => {
updateElementData(selectedBlock, selectedElement, { dataSource: value.id });
updateElementData(selectedBlock, selectedElement, { label: value });
}}
dropDownHeader={"RT-Data"}
eyedroper={true}
/>
</div>
<div className="data">
<DataDetailedDropdown
title="Data Source"
placeholder="Select assets"
sections={[
{
title: "Global",
items: [{ id: "global", label: "Global", icon: <DeviceIcon /> }],
},
{
title: "Assets",
items: getAssetDropdownItems(),
},
]}
value={
element.dataBinding?.dataSource
? {
id: element.dataBinding.dataSource as string,
label:
getEventByModelUuid(selectedProduct.productUuid, element.dataBinding.dataSource as string)?.modelName ??
(element.dataBinding.dataSource === "global" ? "Global" : ""),
icon: <DeviceIcon />,
}
: null
}
onChange={(value) => {
updateElementData(selectedBlock, selectedElement, { dataSource: value.id });
}}
dropDownHeader={"RT-Data"}
eyedroper={true}
/>
</div>
<div className="data">
<DataDetailedDropdown
title="Data Value"
placeholder="Select Value"
sections={getLableValueDropdownItems(element.dataBinding?.dataSource as string | undefined)}
value={
element.dataBinding?.dataValue
? {
id: element.dataBinding.dataValue as string,
label:
getLableValueDropdownItems(element.dataBinding?.dataSource as string | undefined)
.flatMap((section) => section.items)
.find((item) => item.id === element.dataBinding?.dataValue)?.label ?? "",
icon: <ParametersIcon />,
}
: null
}
onChange={(value) => {
updateElementData(selectedBlock, selectedElement, { dataValue: value.id, label: value.label });
}}
dropDownHeader={"RT-Data-Value"}
/>
<div className="data">
<DataDetailedDropdown
title="Data Value"
placeholder="Select Value"
sections={getLableValueDropdownItems(element.dataBinding?.dataSource as string | undefined)}
value={
element.dataBinding?.dataValue
? {
id: element.dataBinding.dataValue as string,
label:
getLableValueDropdownItems(element.dataBinding?.dataSource as string | undefined)
.flatMap((section) => section.items)
.find((item) => item.id === element.dataBinding?.dataValue)?.label ?? "",
icon: <ParametersIcon />,
}
: null
}
onChange={(value) => {
updateElementData(selectedBlock, selectedElement, { dataValue: value.id, label: value.label });
}}
dropDownHeader={"RT-Data-Value"}
/>
</div>
</div>
</div>
</>
)}
{/* Data Mapping */}

View File

@@ -0,0 +1,70 @@
import React from "react";
interface ColorProps {
value: string;
onChange: (value: string) => void;
label?: string;
className?: string;
disabled?: boolean;
onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void;
onFocus?: (e: React.FocusEvent<HTMLInputElement>) => void;
onUpdate?: (value: string) => void;
}
export const Color: React.FC<ColorProps> = ({
value,
onChange,
label = "Color",
className = "",
disabled = false,
onBlur,
onFocus,
onUpdate,
}) => {
const handleTextChange = (e: React.ChangeEvent<HTMLInputElement>) => {
onChange(e.target.value);
};
const handleColorPickerChange = (e: React.ChangeEvent<HTMLInputElement>) => {
onChange(e.target.value);
};
const handleSubmit = (e?: React.FormEvent) => {
if (e) e.preventDefault();
if (onUpdate) {
onUpdate(value);
}
};
const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {
handleSubmit();
if (onBlur) onBlur(e);
};
return (
<div className={`data-picker ${className}`}>
{label && <div className="label">{label}</div>}
<div className="left">
<input
type="color"
value={value}
onChange={handleColorPickerChange}
onBlur={handleBlur}
disabled={disabled}
className="cursor-pointer"
/>
<form onSubmit={handleSubmit}>
<input
type="text"
className="colorValue"
value={value}
onChange={handleTextChange}
onBlur={handleBlur}
onFocus={onFocus}
disabled={disabled}
/>
</form>
</div>
</div>
);
};

View File

@@ -46,6 +46,7 @@ textarea {
}
input[type="number"] {
// Chrome, Safari, Edge, Opera
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
@@ -225,25 +226,29 @@ input[type="number"] {
position: relative;
cursor: pointer;
.key{
.key {
text-overflow: none;
}
.dropdown-header {
height: 100%;
display: flex;
justify-content: space-between;
cursor: pointer;
border-radius: #{$border-radius-large};
.key{
.key {
width: calc(100% - 18px);
overflow: hidden;
text-overflow: ellipsis;
}
}
}
.icon {
height: auto;
}
}
.dropdown-options {
position: absolute;
width: 100%;
@@ -787,3 +792,39 @@ input[type="number"] {
}
}
}
.data-picker {
display: flex;
justify-content: space-between;
align-items: center;
.label {
width: 50%;
white-space: nowrap;
}
input[type="color"] {
width: 42px;
padding: 0;
background: transparent;
outline: none;
}
.left {
width: 100%;
display: flex;
align-items: center;
gap: 6px;
.colorValue {
width: 100%;
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;
}
}
}

View File

@@ -18,7 +18,7 @@
transition: none !important;
}
* > {
*> {
pointer-events: auto;
}
@@ -167,10 +167,10 @@
padding: 8px;
user-select: none;
border: 1px solid transparent;
outline: 1px solid transparent;
&.edit-mode {
transition: all 0.2s;
outline: 1px solid transparent;
&:hover {
outline-color: var(--border-color);
@@ -285,7 +285,7 @@
gap: 11px;
min-width: 280px;
height: fit-content;
min-width: 320px;
max-width: 320px;
min-height: 60vh;
padding: 12px;
@@ -460,39 +460,6 @@
padding: 0;
}
.data-picker {
display: flex;
justify-content: space-between;
align-items: center;
.label {
width: 50%;
white-space: nowrap;
}
input[type="color"] {
width: 42px;
padding: 0;
background: transparent;
outline: none;
}
.left {
width: 100%;
display: flex;
align-items: center;
gap: 6px;
.colorValue {
width: 100%;
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;
}
}
}
.design-section-footer {
display: flex;
flex-direction: column;
@@ -1120,4 +1087,4 @@
}
}
}
}
}

View File

@@ -24,7 +24,7 @@ import { useSceneContext } from "../../modules/scene/sceneContext";
const KeyPressListener: React.FC = () => {
const { comparisonScene, clearComparisonState } = useSimulationState();
const { activeModule, setActiveModule } = useModuleStore();
const { assetStore, versionStore } = useSceneContext();
const { assetStore, versionStore, simulationDashBoardStore } = useSceneContext();
const { selectedAssets } = assetStore();
const { setSubModule } = useSubModuleStore();
const { setActiveSubTool } = useActiveSubTool();
@@ -46,6 +46,7 @@ const KeyPressListener: React.FC = () => {
const { setSelectedComment } = useSelectedComment();
const { setDfxUploaded } = useDfxUpload();
const isTextInput = (element: Element | null): boolean => element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element?.getAttribute("contenteditable") === "true";
const { setSelectedBlock, setSelectedElement } = simulationDashBoardStore();
const handleModuleSwitch = (keyCombination: string) => {
const modules: Record<string, string> = {
@@ -189,6 +190,8 @@ const KeyPressListener: React.FC = () => {
setIsRenameMode(false);
setDfxUploaded([]);
setSelectedComment(null);
setSelectedBlock(null);
setSelectedElement(null);
}
if (!keyCombination || ["F5", "F11", "F12"].includes(event.key) || keyCombination === "Ctrl+R") return;