From ff9901d1695b72d5df0014d232a19a3b05f04505 Mon Sep 17 00:00:00 2001 From: arnav1296 Date: Fri, 15 Aug 2025 01:14:11 +0530 Subject: [PATCH] feat: Add pen options panel to drawing board Introduces a new PenOptions component for selecting pen color and size in KonvaDrawingBoard. Updates the drawing logic to use the selected pen color and size, and adds UI logic to toggle the pen options panel from the toolbar. --- src/components/HomePage.jsx | 2 +- src/components/KonvaDrawingBoard.jsx | 229 +++++++++++++++++++-------- src/components/PenOptions.jsx | 93 +++++++++++ 3 files changed, 256 insertions(+), 68 deletions(-) create mode 100644 src/components/PenOptions.jsx diff --git a/src/components/HomePage.jsx b/src/components/HomePage.jsx index 55120fc..239afdb 100644 --- a/src/components/HomePage.jsx +++ b/src/components/HomePage.jsx @@ -158,7 +158,7 @@ const HomePage = () => {
{/* Header */} -
+
{/* Main Content */}
diff --git a/src/components/KonvaDrawingBoard.jsx b/src/components/KonvaDrawingBoard.jsx index 675a4c9..d1bc89a 100644 --- a/src/components/KonvaDrawingBoard.jsx +++ b/src/components/KonvaDrawingBoard.jsx @@ -9,12 +9,19 @@ import { MousePointerClick, Save, } from "lucide-react"; +import PenOptions from "./PenOptions"; -const DEFAULT_STROKE_COLOR = 'white'; +const DEFAULT_STROKE_COLOR = "white"; const DEFAULT_STROKE_WIDTH = 5; const ERASER_STROKE_WIDTH = 20; -function KonvaDrawingBoard({ initialStrokes, onSaveStroke, onSaveAllBoardContent, boardId, onClearAllStrokes }) { +function KonvaDrawingBoard({ + initialStrokes, + onSaveStroke, + onSaveAllBoardContent, + boardId, + onClearAllStrokes, +}) { const stageRef = useRef(null); const containerRef = useRef(null); const [tool, setTool] = useState("pen"); @@ -23,7 +30,9 @@ function KonvaDrawingBoard({ initialStrokes, onSaveStroke, onSaveAllBoardContent const [stagePos, setStagePos] = useState({ x: 0, y: 0 }); const [stageScale, setStageScale] = useState(1); const [containerSize, setContainerSize] = useState({ width: 0, height: 0 }); - + const [toolOptions, setToolOptions] = useState(false); + const [penColor, setPenColor] = useState("white"); + const [penSize, setPenSize] = useState(5); // --- Resizing the Stage when container size changes --- const checkSize = useCallback(() => { if (containerRef.current) { @@ -36,17 +45,17 @@ function KonvaDrawingBoard({ initialStrokes, onSaveStroke, onSaveAllBoardContent useEffect(() => { checkSize(); - window.addEventListener('resize', checkSize); - return () => window.removeEventListener('resize', checkSize); + window.addEventListener("resize", checkSize); + return () => window.removeEventListener("resize", checkSize); }, [checkSize]); // --- Load initial strokes --- useEffect(() => { const loadedLines = (initialStrokes || []) - .filter(s => s.points && s.points.length > 0) - .map(s => ({ + .filter((s) => s.points && s.points.length > 0) + .map((s) => ({ id: s.id, - points: typeof s.points === 'string' ? JSON.parse(s.points) : s.points, + points: typeof s.points === "string" ? JSON.parse(s.points) : s.points, tool: s.tool || "pen", color: s.color || DEFAULT_STROKE_COLOR, strokeWidth: s.strokeWidth || DEFAULT_STROKE_WIDTH, @@ -55,46 +64,55 @@ function KonvaDrawingBoard({ initialStrokes, onSaveStroke, onSaveAllBoardContent }, [initialStrokes]); // --- Drawing Event Handlers --- - const handleMouseDown = useCallback((e) => { - if (tool === "pan" || tool === "pointer") return; - setIsDrawing(true); - const stage = e.target.getStage(); - const pos = stage.getPointerPosition(); - - //convert screen coordinates into stage coordinates - const transform = stage.getAbsoluteTransform().copy(); - transform.invert(); - const relativePos = transform.point(pos); - setLines((prevLines) => [ - ...prevLines, - { - tool, - color: tool === "eraser" ? "black" : DEFAULT_STROKE_COLOR, - strokeWidth: tool === "eraser" ? ERASER_STROKE_WIDTH : DEFAULT_STROKE_WIDTH, - points: [relativePos.x, relativePos.y], - id: Date.now() + Math.random(), - }, - ]); - }, [tool]); - - const handleMouseMove = useCallback((e) => { - if (!isDrawing || tool === "pan" || tool === "pointer") return; - const stage = stageRef.current; - const point = stage.getPointerPosition(); - - //convert screen coordinates into stage coordinates - const transform = stage.getAbsoluteTransform().copy(); - transform.invert(); - const relativePos = transform.point(point); - - setLines((prevLines) => { - const lastLine = { ...prevLines[prevLines.length - 1] }; - lastLine.points = lastLine.points.concat([relativePos.x, relativePos.y]); - const newLines = [...prevLines]; - newLines[newLines.length - 1] = lastLine; - return newLines; - }); - }, [isDrawing, tool]); + const handleMouseDown = useCallback( + (e) => { + if (tool === "pan" || tool === "pointer") return; + setIsDrawing(true); + const stage = e.target.getStage(); + const pos = stage.getPointerPosition(); + + //convert screen coordinates into stage coordinates + const transform = stage.getAbsoluteTransform().copy(); + transform.invert(); + const relativePos = transform.point(pos); + setLines((prevLines) => [ + ...prevLines, + { + tool, + color: tool === "eraser" ? "black" : penColor, + strokeWidth: tool === "eraser" ? ERASER_STROKE_WIDTH : penSize, + points: [relativePos.x, relativePos.y], + id: Date.now() + Math.random(), + }, + ]); + }, + [tool] + ); + + const handleMouseMove = useCallback( + (e) => { + if (!isDrawing || tool === "pan" || tool === "pointer") return; + const stage = stageRef.current; + const point = stage.getPointerPosition(); + + //convert screen coordinates into stage coordinates + const transform = stage.getAbsoluteTransform().copy(); + transform.invert(); + const relativePos = transform.point(point); + + setLines((prevLines) => { + const lastLine = { ...prevLines[prevLines.length - 1] }; + lastLine.points = lastLine.points.concat([ + relativePos.x, + relativePos.y, + ]); + const newLines = [...prevLines]; + newLines[newLines.length - 1] = lastLine; + return newLines; + }); + }, + [isDrawing, tool] + ); const handleMouseUp = useCallback(async () => { setIsDrawing(false); @@ -114,17 +132,21 @@ function KonvaDrawingBoard({ initialStrokes, onSaveStroke, onSaveAllBoardContent // --- Explicit Save Handler --- const handleExplicitSave = useCallback(() => { - if (onSaveAllBoardContent) { - onSaveAllBoardContent(boardId, lines); - alert("All changes saved!"); - } else { - alert("Save feature not fully connected. Check console."); - } + if (onSaveAllBoardContent) { + onSaveAllBoardContent(boardId, lines); + alert("All changes saved!"); + } else { + alert("Save feature not fully connected. Check console."); + } }, [onSaveAllBoardContent, boardId, lines]); // --- Clear Canvas Function --- const clearCanvas = useCallback(async () => { - if (window.confirm("Are you sure you want to clear the entire canvas? This action cannot be undone.")) { + if ( + window.confirm( + "Are you sure you want to clear the entire canvas? This action cannot be undone." + ) + ) { setLines([]); // Clear locally first for immediate feedback await onClearAllStrokes(boardId); alert("Canvas cleared permanently!"); @@ -132,7 +154,8 @@ function KonvaDrawingBoard({ initialStrokes, onSaveStroke, onSaveAllBoardContent }, [boardId, onClearAllStrokes]); // --- View Control Handlers (MOVED HERE) --- - const handleWheel = useCallback((e) => { // <--- MOVED AND WRAPPED IN useCallback + const handleWheel = useCallback((e) => { + // <--- MOVED AND WRAPPED IN useCallback e.evt.preventDefault(); const stage = stageRef.current; if (!stage) return; @@ -157,31 +180,84 @@ function KonvaDrawingBoard({ initialStrokes, onSaveStroke, onSaveAllBoardContent }); }, []); // No external dependencies, stageRef is ref - const handleDragEnd = useCallback((e) => { // <--- MOVED AND WRAPPED IN useCallback + const handleDragEnd = useCallback((e) => { + // <--- MOVED AND WRAPPED IN useCallback setStagePos({ x: e.target.x(), y: e.target.y(), }); }, []); - const resetView = useCallback(() => { // <--- MOVED AND WRAPPED IN useCallback + const resetView = useCallback(() => { + // <--- MOVED AND WRAPPED IN useCallback setStagePos({ x: 0, y: 0 }); setStageScale(1); }, []); + const handleToolClick = (toolName) => { + if (tool !== toolName) { + setTool(toolName); + setToolOptions(false); + } else if (tool === toolName) { + setToolOptions(!toolOptions); + } + }; return (
{/* Toolbar */}
- - - - + + + +
- +
- +
+ {toolOptions && tool == "pen" && ( +
+ +
+ )} + {/* Canvas Area */}
{lines.map((line) => ( @@ -238,4 +333,4 @@ function KonvaDrawingBoard({ initialStrokes, onSaveStroke, onSaveAllBoardContent ); } -export default KonvaDrawingBoard; \ No newline at end of file +export default KonvaDrawingBoard; diff --git a/src/components/PenOptions.jsx b/src/components/PenOptions.jsx new file mode 100644 index 0000000..39fd6ba --- /dev/null +++ b/src/components/PenOptions.jsx @@ -0,0 +1,93 @@ + // src/components/KonvaDrawingBoard.jsx + import React, { useState, useRef, useEffect, useCallback } from "react"; + import { Stage, Layer, Line, Rect } from "react-konva"; + import { + Pencil, + Eraser, + Move, + RotateCcw, + MousePointerClick, + Save, + } from "lucide-react"; + + function PenOptions({currentColor, currentSize, onColorChange, onSizeChange}) { + const colors = [ + { name: "White", value: "#ffffff" }, + { name: "Black", value: "#000000" }, + { name: "Red", value: "#ef4444" }, + { name: "Blue", value: "#3b82f6" }, + { name: "Green", value: "#22c55e" }, + { name: "Yellow", value: "#eab308" }, + { name: "Purple", value: "#a855f7" }, + { name: "Orange", value: "#f97316" }, + { name: "Pink", value: "#ec4899" }, + { name: "Cyan", value: "#06b6d4" }, + ]; + + const brushSizes = [ + { name: "Extra Small", value: 2 }, + { name: "Small", value: 5 }, + { name: "Medium", value: 10 }, + { name: "Large", value: 15 }, + { name: "Extra Large", value: 20 }, + { name: "Huge", value: 30 }, + ]; + + return ( +
+

Pen Options

+ + {/* Color Selection */} +
+

Color:

+
+ {colors.map(color => ( +
+
+ + {/* Brush Size Selection */} +
+

Brush Size:

+
+ {brushSizes.map(size => ( + + ))} +
+
+
+ ); + } + + export default PenOptions;