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

This commit is contained in:
2025-12-18 12:12:20 +05:30
8 changed files with 268 additions and 18 deletions

View File

@@ -1,4 +1,4 @@
import { useState, type RefObject } from "react";
import { useState, useRef, useEffect, type RefObject } from "react";
import { Block } from "../../../../types/exportedTypes";
import { getAlphaFromRgba, rgbaToHex } from "../../functions/helpers/colorHandlers";
import { getCurrentBlockStyleValue } from "../../functions/helpers/getCurrentBlockStyleValue";
@@ -9,7 +9,7 @@ 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 } from "../../../icons/ExportCommonIcons";
import { AddIcon, DeviceIcon, ParametersIcon, ResizeHeightIcon } from "../../../icons/ExportCommonIcons";
import DataDetailedDropdown from "../../../ui/inputs/DataDetailedDropdown";
import RenameInput from "../../../ui/inputs/RenameInput";
import DataSourceSelector from "../../../ui/inputs/DataSourceSelector";
@@ -38,9 +38,119 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
handleRemoveBlock,
}) => {
const [color, setColor] = useState("#000000");
// Dragging for block editor
const panelRef = useRef<HTMLDivElement | null>(null);
const [position, setPosition] = useState<{ x: number; y: number }>(() => {
if (typeof window !== "undefined") {
// approximate initial left using a default panel width of 300
return { x: Math.max(0, window.innerWidth - 300 - 40), y: 80 };
}
return { x: 120, y: 80 };
});
const initialPositionRef = useRef<{ x: number; y: number } | null>(null);
const draggingRef = useRef(false);
const startXRef = useRef(0);
const startYRef = useRef(0);
const startLeftRef = useRef(0);
const startTopRef = useRef(0);
// compute exact initial position once we have panel dimensions
useEffect(() => {
const panelEl = panelRef.current || (blockEditorRef && (blockEditorRef as any).current);
const width = panelEl?.offsetWidth || 300;
const nx = Math.max(0, window.innerWidth - width - 40);
const ny = 80;
initialPositionRef.current = { x: nx, y: ny };
setPosition({ x: nx, y: ny });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
return () => {
// cleanup in case
draggingRef.current = false;
};
}, []);
const startDrag = (ev: React.PointerEvent) => {
if (ev.detail > 1) return;
const panel = panelRef.current || (blockEditorRef && (blockEditorRef as any).current);
if (!panel) return;
panel.setPointerCapture?.(ev.pointerId);
draggingRef.current = true;
startXRef.current = ev.clientX;
startYRef.current = ev.clientY;
startLeftRef.current = position.x;
startTopRef.current = position.y;
const onPointerMove = (e: PointerEvent) => {
if (!draggingRef.current) return;
const dx = e.clientX - startXRef.current;
const dy = e.clientY - startYRef.current;
const panelEl = panelRef.current || (blockEditorRef && (blockEditorRef as any).current);
const width = panelEl?.offsetWidth || 300;
const height = panelEl?.offsetHeight || 300;
const maxX = window.innerWidth - width - 8;
const maxY = window.innerHeight - height - 8;
let nx = startLeftRef.current + dx;
let ny = startTopRef.current + dy;
nx = Math.max(0, Math.min(nx, maxX));
ny = Math.max(0, Math.min(ny, maxY));
setPosition({ x: nx, y: ny });
};
const onPointerUp = (e: PointerEvent) => {
draggingRef.current = false;
window.removeEventListener("pointermove", onPointerMove);
window.removeEventListener("pointerup", onPointerUp);
panel.releasePointerCapture?.(e.pointerId);
};
window.addEventListener("pointermove", onPointerMove);
window.addEventListener("pointerup", onPointerUp);
};
const resetPosition = () => {
console.log("adsasdasdadasd");
if (initialPositionRef.current) {
setPosition(initialPositionRef.current);
return;
}
const panelEl = panelRef.current || (blockEditorRef && (blockEditorRef as any).current);
const width = panelEl?.offsetWidth || 300;
const nx = Math.max(0, window.innerWidth - width - 40);
setPosition({ x: nx, y: 80 });
};
return (
<div ref={blockEditorRef} className="panel block-editor-panel">
<div
ref={(el) => {
panelRef.current = el;
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}
>
<ResizeHeightIcon />
</div>
<div className="header">
<h4>Block Style</h4>
<div

View File

@@ -56,7 +56,7 @@ const ElementDesign: React.FC<ElementDesignProps> = ({
setShowSwapUI,
}) => {
return (
<div>
<div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
{element?.type === "graph" && (
<div className="design-section">
<DataSourceSelector

View File

@@ -1,11 +1,11 @@
import { useCallback, useMemo, useState, type RefObject } from "react";
import { useCallback, useMemo, useState, useRef, useEffect, type RefObject } from "react";
import { ExtendedCSSProperties, UIElement } from "../../../../types/exportedTypes";
import { DeleteIcon } from "../../../icons/ContextMenuIcons";
import DataSourceSelector from "../../../ui/inputs/DataSourceSelector";
import InputWithDropDown from "../../../ui/inputs/InputWithDropDown";
import InputRange from "../../../ui/inputs/InputRange";
import RenameInput from "../../../ui/inputs/RenameInput";
import { AddIcon, DeviceIcon, ParametersIcon } from "../../../icons/ExportCommonIcons";
import { AddIcon, DeviceIcon, ParametersIcon, ResizeHeightIcon } from "../../../icons/ExportCommonIcons";
import DataDetailedDropdown from "../../../ui/inputs/DataDetailedDropdown";
import { useSceneContext } from "../../../../modules/scene/sceneContext";
import ElementDesign from "./ElementDesign";
@@ -64,6 +64,117 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
const [selectType, setSelectType] = useState("design");
const [selectDataMapping, setSelectDataMapping] = useState(element?.type === "graph" && element.dataBinding?.dataType === "multiple-machine" ? "multipleMachine" : "singleMachine");
// Dragging state for the panel
const panelRef = useRef<HTMLDivElement | null>(null);
const [position, setPosition] = useState<{ x: number; y: number }>(() => {
if (typeof window !== "undefined") {
// approximate initial left using a default panel width of 300
return { x: Math.max(0, window.innerWidth - 300 - 40), y: 80 };
}
return { x: 100, y: 80 };
});
const initialPositionRef = useRef<{ x: number; y: number } | null>(null);
// On mount, compute exact initial position based on panel width and store it
useEffect(() => {
const panelEl = panelRef.current || (elementEditorRef && (elementEditorRef as any).current);
const width = panelEl?.offsetWidth || 300;
const nx = Math.max(0, window.innerWidth - width - 40);
const ny = 80;
initialPositionRef.current = { x: nx, y: ny };
setPosition({ x: nx, y: ny });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const draggingRef = useRef(false);
const startXRef = useRef(0);
const startYRef = useRef(0);
const startLeftRef = useRef(0);
const startTopRef = useRef(0);
useEffect(() => {
const onPointerMove = (ev: PointerEvent) => {
if (!draggingRef.current) return;
const dx = ev.clientX - startXRef.current;
const dy = ev.clientY - startYRef.current;
const panel = panelRef.current;
const width = panel?.offsetWidth || 300;
const height = panel?.offsetHeight || 300;
const maxX = window.innerWidth - width - 8;
const maxY = window.innerHeight - height - 8;
let nx = startLeftRef.current + dx;
let ny = startTopRef.current + dy;
if (nx < 0) nx = 0;
if (ny < 0) ny = 0;
if (nx > maxX) nx = maxX;
if (ny > maxY) ny = maxY;
setPosition({ x: nx, y: ny });
};
const onPointerUp = () => {
draggingRef.current = false;
window.removeEventListener("pointermove", onPointerMove);
window.removeEventListener("pointerup", onPointerUp);
};
return () => {
window.removeEventListener("pointermove", onPointerMove);
window.removeEventListener("pointerup", onPointerUp);
};
}, []);
const startDrag = (ev: React.PointerEvent) => {
ev.preventDefault();
const panel = panelRef.current || (elementEditorRef && (elementEditorRef as any).current);
if (!panel) return;
panel.setPointerCapture?.(ev.pointerId);
draggingRef.current = true;
startXRef.current = ev.clientX;
startYRef.current = ev.clientY;
startLeftRef.current = position.x;
startTopRef.current = position.y;
const onPointerMove = (e: PointerEvent) => {
if (!draggingRef.current) return;
const dx = e.clientX - startXRef.current;
const dy = e.clientY - startYRef.current;
const panelEl = panelRef.current || (elementEditorRef && (elementEditorRef as any).current);
const width = panelEl?.offsetWidth || 300;
const height = panelEl?.offsetHeight || 300;
const maxX = window.innerWidth - width - 8;
const maxY = window.innerHeight - height - 8;
let nx = startLeftRef.current + dx;
let ny = startTopRef.current + dy;
if (nx < 0) nx = 0;
if (ny < 0) ny = 0;
if (nx > maxX) nx = maxX;
if (ny > maxY) ny = maxY;
setPosition({ x: nx, y: ny });
};
const onPointerUp = (e: PointerEvent) => {
draggingRef.current = false;
window.removeEventListener("pointermove", onPointerMove);
window.removeEventListener("pointerup", onPointerUp);
try {
(panel as Element).releasePointerCapture?.((e as any).pointerId);
} catch (err) { }
};
window.addEventListener("pointermove", onPointerMove);
window.addEventListener("pointerup", onPointerUp);
};
const resetPosition = () => {
if (initialPositionRef.current) {
setPosition(initialPositionRef.current);
return;
}
const panelEl = panelRef.current || (elementEditorRef && (elementEditorRef as any).current);
const width = panelEl?.offsetWidth || 300;
const nx = Math.max(0, window.innerWidth - width - 40);
setPosition({ x: nx, y: 80 });
};
const getAssetDropdownItems = useCallback(() => {
if (!product?.eventDatas) return [];
@@ -348,7 +459,19 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
};
return (
<div ref={elementEditorRef} className="panel element-editor-panel">
<div
ref={(el) => {
panelRef.current = el;
if (elementEditorRef && typeof elementEditorRef === "object" && "current" in elementEditorRef) {
(elementEditorRef as any).current = el;
}
}}
className="panel element-editor-panel"
style={{ position: "fixed", left: position.x, top: position.y, zIndex: 1000 }}
>
<div className="resize-icon" onPointerDown={startDrag} onDoubleClick={resetPosition}>
<ResizeHeightIcon />
</div>
<div className="header">
<h4>Element Style</h4>
<div
@@ -410,6 +533,7 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
updateElementData(selectedBlock, selectedElement, { label: value });
}}
/>
<InputWithDropDown label="Title" value={`title`} placeholder={"Label 1"} min={0.1} step={0.1} max={2} onChange={() => { }} />
<div className="data">
<DataDetailedDropdown
title="Data Source"

View File

@@ -65,7 +65,7 @@ const DataDetailedDropdown: React.FC<DataDetailedDropdownProps> = ({
<div className="input-container">
<div className="input-wrapper">
<div className="input" onClick={() => setOpen((v) => !v)}>
{value?.label || placeholder}
<div className="key">{value?.label || placeholder}</div>
<div className="icon"></div>
</div>

View File

@@ -92,13 +92,7 @@ const RegularDropDown: React.FC<DropdownProps> = ({
};
return (
<div
className="regularDropdown-container"
ref={dropdownRef}
onPointerLeave={() => {
setIsOpen(false);
}}
>
<div className="regularDropdown-container" ref={dropdownRef}>
{/* Header */}
<div className="dropdown-header flex-sb" onClick={toggleDropdown}>
<div className="key">{selectedOption || header}</div>

View File

@@ -74,7 +74,7 @@ const RegularDropDownID: React.FC<DropdownProps> = ({
};
return (
<div className="regularDropdown-container" ref={dropdownRef} onPointerLeave={() => setIsOpen(false)}>
<div className="regularDropdown-container" ref={dropdownRef}>
<div className="dropdown-header flex-sb" onClick={toggleDropdown}>
<div className="key">{selectedOption || header}</div>
<div className="icon"></div>

View File

@@ -225,6 +225,9 @@ input[type="number"] {
position: relative;
cursor: pointer;
.key{
text-overflow: none;
}
.dropdown-header {
height: 100%;
display: flex;
@@ -260,7 +263,7 @@ input[type="number"] {
}
.option {
padding: 2px 4px;
padding: 6px 6px;
cursor: pointer;
flex-direction: row !important;
border-radius: #{$border-radius-medium};

View File

@@ -282,6 +282,15 @@
min-height: 60vh;
padding: 12px;
.resize-icon {
display: flex;
justify-content: center;
align-items: center;
z-index: 100;
svg {
cursor: grab;
}
}
.header {
display: flex;
@@ -556,6 +565,7 @@
}
}
.data-details {
display: flex;
flex-direction: column;
@@ -683,6 +693,9 @@
max-width: 320px;
position: fixed;
top: 80px;
right: 40px;
.appearance {
.design-datas-wrapper {
@@ -930,7 +943,7 @@
}
.input-container {
width: 100%;
min-width: 100px;
display: flex;
align-items: center;
gap: 6px;
@@ -940,6 +953,12 @@
background: transparent;
}
.key {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.icon {
display: flex;
padding: 2px;