diff --git a/frontend/apps/editor3d/app/src/pages/PageDraw/PaintSurface.tsx b/frontend/apps/editor3d/app/src/pages/PageDraw/PaintSurface.tsx index 7f304da80..4a882c280 100644 --- a/frontend/apps/editor3d/app/src/pages/PageDraw/PaintSurface.tsx +++ b/frontend/apps/editor3d/app/src/pages/PageDraw/PaintSurface.tsx @@ -306,7 +306,7 @@ export const PaintSurface = ({ y: stagePoint.y, width: 1, height: 1, - fill: "#e0e0e0", + fill: store.shapeColor, // Use current shape color from store stroke: "#444", strokeWidth: 2, draggable: true, diff --git a/frontend/apps/editor3d/app/src/pages/PageDraw/components/ui/SideToolbar.tsx b/frontend/apps/editor3d/app/src/pages/PageDraw/components/ui/SideToolbar.tsx index 448125b9a..155b8b1c6 100644 --- a/frontend/apps/editor3d/app/src/pages/PageDraw/components/ui/SideToolbar.tsx +++ b/frontend/apps/editor3d/app/src/pages/PageDraw/components/ui/SideToolbar.tsx @@ -14,6 +14,7 @@ import { faCircle, faSquare, faTriangle, + faDroplet, } from "@fortawesome/pro-regular-svg-icons"; import "../../App.css"; import { HsvaColorPicker, HsvaColor } from "react-colorful"; @@ -62,6 +63,7 @@ const SideToolbar: React.FC = ({ className = "", }) => { /* ------------------------------------------------ state ---------- */ + const store = useSceneStore(); const [open, setOpen] = useState(null); const [brushSize, setBrushSize] = useState(16); @@ -79,6 +81,52 @@ const SideToolbar: React.FC = ({ a: 1, }); + // Get selected nodes for shape color picker + const selectedNodes = + store.selectedNodeIds.length > 0 + ? store.nodes.filter((node) => store.selectedNodeIds.includes(node.id)) + : []; + + // Get first selected node for color preview (when multiple selected, show first one's color) + const firstSelectedNode = selectedNodes.length > 0 ? selectedNodes[0] : null; + + // State for shape color picker + const [shapeHsva, setShapeHsva] = useState({ + h: 240, // Blue hue + s: 100, // Full saturation + v: 70, // Medium brightness blue + a: 1, + }); + + // Update shape color picker when selection changes or store's shapeColor changes + React.useEffect(() => { + // Use selected node's color if available, otherwise use store's default shapeColor + const colorToUse = firstSelectedNode?.fill || store.shapeColor; + + if (colorToUse && colorToUse.startsWith("#")) { + // Convert hex to HSVA (simplified - might want to change to proper converter later) + const hex = colorToUse; + const r = parseInt(hex.slice(1, 3), 16); + const g = parseInt(hex.slice(3, 5), 16); + const b = parseInt(hex.slice(5, 7), 16); + + // Convert RGB to HSV (simplified) + const max = Math.max(r, g, b); + const min = Math.min(r, g, b); + const v = max / 255; + const s = max === 0 ? 0 : (max - min) / max; + + let h = 0; + if (max !== min) { + if (max === r) h = (60 * ((g - b) / (max - min)) + 360) % 360; + else if (max === g) h = (60 * ((b - r) / (max - min)) + 120) % 360; + else h = (60 * ((r - g) / (max - min)) + 240) % 360; + } + + setShapeHsva({ h, s: s * 100, v: v * 100, a: 1 }); + } + }, [firstSelectedNode?.fill, store.shapeColor]); + /* debounced parent calls */ const sendPaint = useDebounced< (hex: string, size: number, opacity: number) => void, @@ -90,6 +138,24 @@ const SideToolbar: React.FC = ({ 75, ); + // Debounced shape color update - now updates ALL selected nodes AND store's shapeColor + const sendShapeColor = useDebounced<(hex: string) => void, [string]>( + (hex: string) => { + // Update the store's default shape color for future shapes + store.setShapeColor(hex); + + // Update all selected nodes + selectedNodes.forEach((node) => { + store.updateNode(node.id, { fill: hex }, false); // Don't save state for each individual update + }); + // Save state once after all updates + if (selectedNodes.length > 0) { + store.saveState(); + } + }, + 75, + ); + /* picker helper */ const makePicker = ( hsva: HsvaColor, @@ -138,9 +204,11 @@ const SideToolbar: React.FC = ({ const BgPopout = makePicker(bgHsva, setBgHsva, sendBg); + // Shape color popout + const ShapePopout = makePicker(shapeHsva, setShapeHsva, sendShapeColor); + /* ------------------------------------------------ tools ---------- */ - const store = useSceneStore(); // Use store directly const tools = [ { id: "select", @@ -215,10 +283,19 @@ const SideToolbar: React.FC = ({ id: "background", label: "Canvas Background", icon: ( - +
+ + +
), popout: BgPopout, }, @@ -248,6 +325,32 @@ const SideToolbar: React.FC = ({