Merge remote-tracking branch 'origin/main-demo' into main-dev
This commit is contained in:
1
app/package-lock.json
generated
1
app/package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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" && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -246,6 +246,7 @@ input[type="number"] {
|
|||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
height: auto;
|
height: auto;
|
||||||
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user