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

This commit is contained in:
2025-12-20 18:12:19 +05:30
7 changed files with 207 additions and 52 deletions

1
app/package-lock.json generated
View File

@@ -43,6 +43,7 @@
"r3f-perf": "^7.2.3", "r3f-perf": "^7.2.3",
"react": "^18.3.1", "react": "^18.3.1",
"react-chartjs-2": "^5.3.0", "react-chartjs-2": "^5.3.0",
"react-colorful": "^5.6.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-router-dom": "^7.4.0", "react-router-dom": "^7.4.0",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",

View File

@@ -38,6 +38,7 @@
"r3f-perf": "^7.2.3", "r3f-perf": "^7.2.3",
"react": "^18.3.1", "react": "^18.3.1",
"react-chartjs-2": "^5.3.0", "react-chartjs-2": "^5.3.0",
"react-colorful": "^5.6.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-router-dom": "^7.4.0", "react-router-dom": "^7.4.0",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",

View File

@@ -192,7 +192,7 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
window.removeEventListener("pointerup", onPointerUp); window.removeEventListener("pointerup", onPointerUp);
try { try {
(panel as Element).releasePointerCapture?.((e as any).pointerId); (panel as Element).releasePointerCapture?.((e as any).pointerId);
} catch (err) {} } catch (err) { }
}; };
window.addEventListener("pointermove", onPointerMove); window.addEventListener("pointermove", onPointerMove);
@@ -305,6 +305,7 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
<div className="design-section appearance"> <div className="design-section appearance">
<div className="section-header">Appearance</div> <div className="section-header">Appearance</div>
<div className="design-datas-wrapper"> <div className="design-datas-wrapper">
{currentBlock.positionType === "absolute" && ( {currentBlock.positionType === "absolute" && (
<> <>

View File

@@ -662,7 +662,7 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
</div> </div>
{selectDataMapping === "singleMachine" && element.dataBinding?.dataType === "single-machine" && ( {selectDataMapping === "singleMachine" && element.dataBinding?.dataType === "single-machine" && (
<div className="fields-wrapper"> <div className="fields-wrapper design-section">
{singleSourceFields.map((field) => ( {singleSourceFields.map((field) => (
<DataSourceSelector <DataSourceSelector
key={field.id} key={field.id}
@@ -711,7 +711,7 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
)} )}
{selectDataMapping === "multipleMachine" && element.dataBinding?.dataType === "multiple-machine" && ( {selectDataMapping === "multipleMachine" && element.dataBinding?.dataType === "multiple-machine" && (
<div className="fields-wrapper"> <div className="fields-wrapper design-section">
{multipleValueFields.map((field) => ( {multipleValueFields.map((field) => (
<DataSourceSelector <DataSourceSelector
key={field.id} key={field.id}

View File

@@ -1,4 +1,6 @@
import React from "react"; import React, { useState, useRef, useEffect } from "react";
import { HexColorPicker } from "react-colorful";
import { useOuterClick } from "../../../utils/useOuterClick";
interface ColorProps { interface ColorProps {
value: string; value: string;
@@ -11,6 +13,24 @@ interface ColorProps {
onUpdate?: (value: string) => void; onUpdate?: (value: string) => void;
} }
const hexToRgb = (hex: string) => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
}
: { r: 0, g: 0, b: 0 };
};
const rgbToHex = (r: number, g: number, b: number) => {
return (
"#" +
((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase()
);
};
export const Color: React.FC<ColorProps> = ({ export const Color: React.FC<ColorProps> = ({
value, value,
onChange, onChange,
@@ -21,50 +41,180 @@ export const Color: React.FC<ColorProps> = ({
onFocus, onFocus,
onUpdate, onUpdate,
}) => { }) => {
const handleTextChange = (e: React.ChangeEvent<HTMLInputElement>) => { const [isOpen, setIsOpen] = useState(false);
onChange(e.target.value); const popoverRef = useRef<HTMLDivElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const [localValue, setLocalValue] = useState(value);
// Sync local value when prop changes
useEffect(() => {
setLocalValue(value);
}, [value]);
const handleChange = (newColor: string) => {
setLocalValue(newColor);
onChange(newColor);
}; };
const handleColorPickerChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleRgbChange = (part: "r" | "g" | "b", val: string) => {
onChange(e.target.value); const rgb = hexToRgb(localValue);
}; let num = parseInt(val, 10);
if (isNaN(num)) num = 0;
if (num < 0) num = 0;
if (num > 255) num = 255;
const handleSubmit = (e?: React.FormEvent) => { rgb[part] = num;
if (e) e.preventDefault(); const newHex = rgbToHex(rgb.r, rgb.g, rgb.b);
if (onUpdate) { handleChange(newHex);
onUpdate(value);
}
}; };
const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => { const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {
handleSubmit();
if (onBlur) onBlur(e); if (onBlur) onBlur(e);
if (onUpdate) onUpdate(localValue);
}; };
useOuterClick(
() => setIsOpen(false),
["color-picker-popover", "color-trigger"],
isOpen
);
const rgb = hexToRgb(localValue);
return ( return (
<div className={`data-picker ${className}`}> <div className={`data-picker ${className}`} style={{ position: "relative" }} ref={containerRef}>
{label && <div className="label">{label}</div>} {label && <div className="label">{label}</div>}
<div className="left"> <div className="left">
<input <div
type="color" className="color-trigger cursor-pointer"
value={value} onClick={() => !disabled && setIsOpen(!isOpen)}
onChange={handleColorPickerChange} style={{
onBlur={handleBlur} backgroundColor: localValue,
disabled={disabled} width: "52px",
className="cursor-pointer" height: "22px",
display: "inline-block",
borderRadius: "24px",
border: "1px solid var(--border-color, #ccc)",
position: "relative",
}}
/>
<input
type="text"
className="colorValue"
value={localValue}
onChange={(e) => handleChange(e.target.value)}
onBlur={handleBlur}
onFocus={onFocus}
disabled={disabled}
/> />
<form onSubmit={handleSubmit}>
<input
type="text"
className="colorValue"
value={value}
onChange={handleTextChange}
onBlur={handleBlur}
onFocus={onFocus}
disabled={disabled}
/>
</form>
</div> </div>
{isOpen && (
<div
ref={popoverRef}
className="color-picker-popover"
style={{
position: "absolute",
top: "100%",
left: "0",
zIndex: 1000,
marginTop: "8px",
backgroundColor: "#1a1a1a", // Deep dark background
borderRadius: "12px",
padding: "12px",
boxShadow: "0 10px 30px rgba(0,0,0,0.5)",
width: "240px",
border: "1px solid #333",
}}
>
<style>{`
.react-colorful {
width: 100%;
height: 180px;
}
.react-colorful__saturation {
border-radius: 8px 8px 0 0;
border-bottom: none;
}
.react-colorful__hue {
height: 16px;
border-radius: 0 0 8px 8px;
margin-top: 2px;
}
.react-colorful__pointer {
width: 16px;
height: 16px;
}
`}</style>
<HexColorPicker color={localValue} onChange={handleChange} />
<div style={{
display: "flex",
alignItems: "center",
marginTop: "12px",
gap: "10px"
}}>
{/* Eyedropper placeholder icon */}
<div style={{ color: "#aaa" }}>
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
<path d="M12.8536 0.146447C12.6583 -0.0488155 12.3417 -0.0488155 12.1464 0.146447L10.5 1.7929L14.2071 5.5L15.8536 3.85355C16.0488 3.65829 16.0488 3.34171 15.8536 3.14645L12.8536 0.146447ZM9.79289 2.5L1.5 10.7929V14.5H5.20711L13.5 6.20711L9.79289 2.5Z" />
</svg>
</div>
<div style={{
width: "32px",
height: "32px",
borderRadius: "50%",
backgroundColor: localValue,
border: "2px solid #fff",
boxShadow: "0 0 0 1px #333",
flexShrink: 0
}}></div>
<div style={{ flexGrow: 1, display: "flex", flexDirection: "column", gap: "2px" }}>
{/* Small hex input representation usually goes here, but we have separated RGB below */}
</div>
</div>
{/* RGB Inputs */}
<div style={{
display: "grid",
gridTemplateColumns: "1fr 1fr 1fr",
gap: "8px",
marginTop: "12px"
}}>
{["r", "g", "b"].map((key) => (
<div key={key} style={{ display: "flex", flexDirection: "column", alignItems: "center" }}>
<input
type="number"
value={rgb[key as keyof typeof rgb]}
onChange={(e) => handleRgbChange(key as "r" | "g" | "b", e.target.value)}
style={{
width: "100%",
backgroundColor: "#333",
border: "1px solid #444",
borderRadius: "4px",
color: "#eee",
textAlign: "center",
fontSize: "12px",
padding: "4px 0",
appearance: "textfield",
}}
/>
<span style={{
color: "#888",
fontSize: "10px",
marginTop: "4px",
textTransform: "uppercase"
}}>
{key}
</span>
</div>
))}
</div>
</div>
)}
</div> </div>
); );
}; };

View File

@@ -246,6 +246,7 @@ input[type="number"] {
.icon { .icon {
height: auto; height: auto;
border-radius: 4px;
} }
} }

View File

@@ -357,6 +357,7 @@
.select-type { .select-type {
display: flex; display: flex;
gap: 6px;
.type { .type {
flex: 1; flex: 1;
@@ -547,7 +548,6 @@
.type-switch { .type-switch {
display: flex; display: flex;
padding: 6px 12px;
.type { .type {
flex: 1; flex: 1;
@@ -670,8 +670,8 @@
.icon { .icon {
display: flex; display: flex;
padding: 3px; padding: 5px 4px;
border-radius: 2px; border-radius: 6px;
cursor: pointer; cursor: pointer;
&.active { &.active {
@@ -709,6 +709,22 @@
} }
} }
.appearance {
.design-datas-wrapper {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px 16px;
.value-field-container {
label {
width: 80%;
text-wrap: nowrap;
}
}
}
}
&.element-editor-panel { &.element-editor-panel {
right: 20px; right: 20px;
top: 50px; top: 50px;
@@ -722,21 +738,6 @@
top: 80px; top: 80px;
right: 40px; right: 40px;
.appearance {
.design-datas-wrapper {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px 16px;
.value-field-container {
label {
width: 80%;
text-wrap: nowrap;
}
}
}
}
.swap-button { .swap-button {
text-align: center; text-align: center;
color: #ccacff; color: #ccacff;