From 917a601c73bce40371dc24cbf5266a51e3aba54a Mon Sep 17 00:00:00 2001 From: Travis Bennett Date: Fri, 13 Oct 2023 13:15:22 -0400 Subject: [PATCH 01/15] Canvas, initial buttons, and base draw functionality --- .../src/examples/virtualWhiteboard.tsx | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 packages/kitchen-sink/src/examples/virtualWhiteboard.tsx diff --git a/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx b/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx new file mode 100644 index 0000000000..58a8d729fc --- /dev/null +++ b/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx @@ -0,0 +1,106 @@ +import React, { useRef, useState } from 'react'; + +const VirtualBoard: React.FC = () => { + const [coordinates, setCoordinates] = useState< + Array<{ x: number; y: number }[]> + >([]); + const [currentLine, setCurrentLine] = useState<{ x: number; y: number }[]>([]); + const [isDrawing, setIsDrawing] = useState(false); // New state to track drawing mode + const [mode, setMode] = useState< + 'draw' | 'text' | 'line' | 'erase' | 'clear' + >('draw'); + const [color, setColor] = useState('#000000'); + const colorInputRef = useRef(null); + + const handleModeChange = ( + newMode: 'draw' | 'text' | 'line' | 'erase' | 'clear', + ) => { + setMode(newMode); + }; + + const handleColorChange = (e: React.ChangeEvent) => { + setColor(e.target.value); + }; + + // const handleColorButtonClick = (e: React.MouseEvent) => { + // colorInputRef.current?.click(); + // }; + + const handleMouseDown = (e: React.MouseEvent) => { + setIsDrawing(true); + }; + + const handleMouseUp = (e: React.MouseEvent) => { + setIsDrawing(false); + setCoordinates([...coordinates, currentLine]); + setCurrentLine([]) + }; + + const handleDraw = (e: React.MouseEvent) => { + if (!isDrawing) return; // Only draw when isDrawing is true + + const rect = e.currentTarget.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + setCurrentLine([...currentLine, { x, y }]); + }; + + const drawOnCanvas = (canvas: HTMLCanvasElement | null) => { + if (!canvas) return; + const ctx = canvas.getContext('2d'); + if (!ctx) return; + + ctx.clearRect(0, 0, canvas.width, canvas.height); + + coordinates.forEach((line) => { + ctx.beginPath(); + line.forEach((point, i) => { + if (i === 0) { + ctx.moveTo(point.x, point.y); + } else { + ctx.lineTo(point.x, point.y); + } + }); + ctx.stroke(); + }); + }; + + return ( + <> +
+ + + + + + + {/* */} +
+
+ +
+ + ); +}; + +export default VirtualBoard; From b01fc8e67daa8b75e90185f4588718045f7160c8 Mon Sep 17 00:00:00 2001 From: Travis Bennett Date: Fri, 13 Oct 2023 13:43:27 -0400 Subject: [PATCH 02/15] code updates --- .../src/examples/virtualWhiteboard.tsx | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx b/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx index 58a8d729fc..4d6202e657 100644 --- a/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx +++ b/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx @@ -1,4 +1,5 @@ import React, { useRef, useState } from 'react'; +import {block} from 'million/react' const VirtualBoard: React.FC = () => { const [coordinates, setCoordinates] = useState< @@ -18,6 +19,10 @@ const VirtualBoard: React.FC = () => { setMode(newMode); }; + const handleClear = () => { + setCoordinates([]); + } + const handleColorChange = (e: React.ChangeEvent) => { setColor(e.target.value); }; @@ -50,8 +55,10 @@ const VirtualBoard: React.FC = () => { const ctx = canvas.getContext('2d'); if (!ctx) return; + // Clear the canvas ctx.clearRect(0, 0, canvas.width, canvas.height); + // Draw the lines from the coordinates state coordinates.forEach((line) => { ctx.beginPath(); line.forEach((point, i) => { @@ -63,7 +70,19 @@ const VirtualBoard: React.FC = () => { }); ctx.stroke(); }); + + // Draw the current line + ctx.beginPath(); + currentLine.forEach((point, i) => { + if (i === 0) { + ctx.moveTo(point.x, point.y); + } else { + ctx.lineTo(point.x, point.y); + } + }); + ctx.stroke(); }; + return ( <> @@ -72,7 +91,7 @@ const VirtualBoard: React.FC = () => { - + {/* */} -
+
{ height={600} style={{ border: '2px solid black' }} /> - {textInput.show && ( - - )} - +
+ {textInput.show ? ( + setInputText(e.target.value)} + onBlur={handleAddTextToCanvas} + onKeyDown={(e) => { + if (e.key === 'Enter') { + handleAddTextToCanvas(); + e.preventDefault(); // Prevents the default action of a newline + } + }} + autoFocus + /> + ) : ( + setInputText(e.target.value)} + /> + )} +
); From e0d8f90a2aca26f38860e0c19bf0d62b12f48b12 Mon Sep 17 00:00:00 2001 From: Travis Bennett Date: Fri, 13 Oct 2023 19:55:42 -0400 Subject: [PATCH 05/15] Added some line functionality --- .../src/examples/virtualWhiteboard.tsx | 75 +++++++++++++++---- 1 file changed, 61 insertions(+), 14 deletions(-) diff --git a/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx b/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx index 80fb805bee..4fa2b1b9cc 100644 --- a/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx +++ b/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx @@ -23,6 +23,12 @@ const VirtualBoard: React.FC = () => { const [textElements, setTextElements] = useState< Array<{ x: number; y: number; text: string }> >([]); + const [lineStart, setLineStart] = useState<{ x: number; y: number } | null>( + null, + ); + const [startPoint, setStartPoint] = useState<{ x: number; y: number } | null>( + null, + ); const colorInputRef = useRef(null); const canvasRef = useRef(null); @@ -48,13 +54,30 @@ const VirtualBoard: React.FC = () => { // }; const handleMouseDown = (e: React.MouseEvent) => { - setIsDrawing(true); + const rect = e.currentTarget.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + + if (mode === 'line') { + setStartPoint({ x, y }); + } else { + setIsDrawing(true); + } }; const handleMouseUp = (e: React.MouseEvent) => { - setIsDrawing(false); - setCoordinates([...coordinates, currentLine]); - setCurrentLine([]); + const rect = e.currentTarget.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + + if (mode === 'line' && startPoint) { + setCoordinates([...coordinates, [startPoint, { x, y }]]); + setStartPoint(null); // Reset the starting point + } else { + setIsDrawing(false); + setCoordinates([...coordinates, currentLine]); + setCurrentLine([]); + } }; const handleText = (e: React.MouseEvent) => { @@ -86,14 +109,14 @@ const VirtualBoard: React.FC = () => { textInputRef.current.focus(); } }, [textInput.show]); - const handleDraw = (e: React.MouseEvent) => { +const handleDraw = (e: React.MouseEvent) => { if (mode !== 'draw' || !isDrawing) return; // Only draw when isDrawing is true const rect = e.currentTarget.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; setCurrentLine([...currentLine, { x, y }]); - }; +}; useEffect(() => { const canvas = canvasRef.current; @@ -107,13 +130,18 @@ const VirtualBoard: React.FC = () => { // Draw the lines from the coordinates state coordinates.forEach((line) => { ctx.beginPath(); - line.forEach((point, i) => { - if (i === 0) { - ctx.moveTo(point.x, point.y); - } else { - ctx.lineTo(point.x, point.y); - } - }); + if (line.length === 2 && mode === 'line') { + ctx.moveTo(line[0].x, line[0].y); + ctx.lineTo(line[1].x, line[1].y); + } else { + line.forEach((point, i) => { + if (i === 0) { + ctx.moveTo(point.x, point.y); + } else { + ctx.lineTo(point.x, point.y); + } + }); + } ctx.stroke(); }); @@ -133,7 +161,26 @@ const VirtualBoard: React.FC = () => { } }); ctx.stroke(); - }, [coordinates, textElements, currentLine]); // Added currentLine to dependency array + + + // Draw the start point for line mode + if (startPoint && mode === 'line') { + ctx.fillStyle = 'black'; + ctx.fillRect(startPoint.x - 2, startPoint.y - 2, 4, 4); + } + + // Draw the preview line for line mode + if (isDrawing && startPoint && mode === 'line') { + ctx.beginPath(); + ctx.moveTo(startPoint.x, startPoint.y); + ctx.lineTo( + currentLine[0]?.x || startPoint.x, + currentLine[0]?.y || startPoint.y, + ); + ctx.stroke(); + } + + }, [coordinates, textElements, currentLine, mode, startPoint, isDrawing]); return ( <> From d51f29f20731e388b9682fce2984d2b226ad947d Mon Sep 17 00:00:00 2001 From: Travis Bennett Date: Fri, 13 Oct 2023 20:32:56 -0400 Subject: [PATCH 06/15] Updated line functionality --- .../src/examples/virtualWhiteboard.tsx | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx b/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx index 4fa2b1b9cc..8777f3e689 100644 --- a/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx +++ b/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx @@ -54,26 +54,28 @@ const VirtualBoard: React.FC = () => { // }; const handleMouseDown = (e: React.MouseEvent) => { - const rect = e.currentTarget.getBoundingClientRect(); - const x = e.clientX - rect.left; - const y = e.clientY - rect.top; - if (mode === 'line') { - setStartPoint({ x, y }); + if (!startPoint) { + const rect = e.currentTarget.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + setStartPoint({ x, y }); + setIsDrawing(true); + } else { + const rect = e.currentTarget.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + setCoordinates([...coordinates, [startPoint, { x, y }]]); + setStartPoint(null); // Reset the starting point + setIsDrawing(false); // Stop drawing the preview line + } } else { setIsDrawing(true); } }; const handleMouseUp = (e: React.MouseEvent) => { - const rect = e.currentTarget.getBoundingClientRect(); - const x = e.clientX - rect.left; - const y = e.clientY - rect.top; - - if (mode === 'line' && startPoint) { - setCoordinates([...coordinates, [startPoint, { x, y }]]); - setStartPoint(null); // Reset the starting point - } else { + if (mode !== 'line') { setIsDrawing(false); setCoordinates([...coordinates, currentLine]); setCurrentLine([]); @@ -81,15 +83,12 @@ const VirtualBoard: React.FC = () => { }; const handleText = (e: React.MouseEvent) => { - console.log('handleText called, current mode:', mode); // Debug line + if (mode !== 'text') return; // Only add text when the mode is 'text' const rect = e.currentTarget.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; - - console.log('Setting textInput to show, with coordinates:', x, y); // Debug line - const canvas = e.currentTarget; const ctx = canvas.getContext('2d'); From 32f57b9c1ad40dea2664489e13acd995e6c7d269 Mon Sep 17 00:00:00 2001 From: Travis Bennett Date: Fri, 13 Oct 2023 21:32:54 -0400 Subject: [PATCH 07/15] Further updates for line & draw func --- .../src/examples/virtualWhiteboard.tsx | 47 ++++++++++++------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx b/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx index 8777f3e689..e98ef2ee58 100644 --- a/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx +++ b/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx @@ -23,9 +23,7 @@ const VirtualBoard: React.FC = () => { const [textElements, setTextElements] = useState< Array<{ x: number; y: number; text: string }> >([]); - const [lineStart, setLineStart] = useState<{ x: number; y: number } | null>( - null, - ); + const [startPoint, setStartPoint] = useState<{ x: number; y: number } | null>( null, ); @@ -60,7 +58,7 @@ const VirtualBoard: React.FC = () => { const x = e.clientX - rect.left; const y = e.clientY - rect.top; setStartPoint({ x, y }); - setIsDrawing(true); + setIsDrawing(true); } else { const rect = e.currentTarget.getBoundingClientRect(); const x = e.clientX - rect.left; @@ -82,8 +80,21 @@ const VirtualBoard: React.FC = () => { } }; + const handleMouseMove = (e: React.MouseEvent) => { + if (mode === 'line' && startPoint) { + const rect = e.currentTarget.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + setCurrentLine([{ x, y }]); + } + }; + + const mouseMoveHandler = (e: React.MouseEvent) => { + handleDraw(e); + handleMouseMove(e); + } + const handleText = (e: React.MouseEvent) => { - if (mode !== 'text') return; // Only add text when the mode is 'text' const rect = e.currentTarget.getBoundingClientRect(); @@ -108,24 +119,24 @@ const VirtualBoard: React.FC = () => { textInputRef.current.focus(); } }, [textInput.show]); -const handleDraw = (e: React.MouseEvent) => { + const handleDraw = (e: React.MouseEvent) => { if (mode !== 'draw' || !isDrawing) return; // Only draw when isDrawing is true const rect = e.currentTarget.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; setCurrentLine([...currentLine, { x, y }]); -}; + }; useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); if (!ctx) return; - + // Clear the canvas ctx.clearRect(0, 0, canvas.width, canvas.height); - + // Draw the lines from the coordinates state coordinates.forEach((line) => { ctx.beginPath(); @@ -142,14 +153,15 @@ const handleDraw = (e: React.MouseEvent) => { }); } ctx.stroke(); + ctx.closePath(); }); - + // Draw text elements textElements.forEach((textElement) => { ctx.font = '20px sans-serif'; ctx.fillText(textElement.text, textElement.x, textElement.y); }); - + // Draw the current line ctx.beginPath(); currentLine.forEach((point, i) => { @@ -160,14 +172,14 @@ const handleDraw = (e: React.MouseEvent) => { } }); ctx.stroke(); - - + ctx.closePath(); + // Draw the start point for line mode if (startPoint && mode === 'line') { ctx.fillStyle = 'black'; ctx.fillRect(startPoint.x - 2, startPoint.y - 2, 4, 4); } - + // Draw the preview line for line mode if (isDrawing && startPoint && mode === 'line') { ctx.beginPath(); @@ -177,9 +189,10 @@ const handleDraw = (e: React.MouseEvent) => { currentLine[0]?.y || startPoint.y, ); ctx.stroke(); + ctx.closePath(); } - - }, [coordinates, textElements, currentLine, mode, startPoint, isDrawing]); + }, [coordinates, textElements, currentLine, mode, startPoint, isDrawing]); + return ( <> @@ -209,7 +222,7 @@ const handleDraw = (e: React.MouseEvent) => { ref={canvasRef} onMouseDown={handleMouseDown} onMouseUp={handleMouseUp} - onMouseMove={handleDraw} + onMouseMove={mouseMoveHandler} onClick={handleText} width={800} height={600} From 9b97dd7543d5166e17fe33b1908547986cd2a589 Mon Sep 17 00:00:00 2001 From: Travis Bennett Date: Fri, 13 Oct 2023 22:03:08 -0400 Subject: [PATCH 08/15] saved before debugging line flash --- .../src/examples/virtualWhiteboard.tsx | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx b/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx index e98ef2ee58..2c8aabda91 100644 --- a/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx +++ b/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx @@ -53,23 +53,22 @@ const VirtualBoard: React.FC = () => { const handleMouseDown = (e: React.MouseEvent) => { if (mode === 'line') { + const rect = e.currentTarget.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + if (!startPoint) { - const rect = e.currentTarget.getBoundingClientRect(); - const x = e.clientX - rect.left; - const y = e.clientY - rect.top; setStartPoint({ x, y }); setIsDrawing(true); } else { - const rect = e.currentTarget.getBoundingClientRect(); - const x = e.clientX - rect.left; - const y = e.clientY - rect.top; setCoordinates([...coordinates, [startPoint, { x, y }]]); - setStartPoint(null); // Reset the starting point + setStartPoint(null); // Reset the starting point immediately setIsDrawing(false); // Stop drawing the preview line } } else { setIsDrawing(true); } + console.log('MouseDown:', startPoint, isDrawing); }; const handleMouseUp = (e: React.MouseEvent) => { @@ -78,6 +77,7 @@ const VirtualBoard: React.FC = () => { setCoordinates([...coordinates, currentLine]); setCurrentLine([]); } + console.log('MouseUp:', startPoint, isDrawing); }; const handleMouseMove = (e: React.MouseEvent) => { @@ -92,7 +92,7 @@ const VirtualBoard: React.FC = () => { const mouseMoveHandler = (e: React.MouseEvent) => { handleDraw(e); handleMouseMove(e); - } + }; const handleText = (e: React.MouseEvent) => { if (mode !== 'text') return; // Only add text when the mode is 'text' @@ -133,10 +133,10 @@ const VirtualBoard: React.FC = () => { if (!canvas) return; const ctx = canvas.getContext('2d'); if (!ctx) return; - + // Clear the canvas ctx.clearRect(0, 0, canvas.width, canvas.height); - + // Draw the lines from the coordinates state coordinates.forEach((line) => { ctx.beginPath(); @@ -155,13 +155,13 @@ const VirtualBoard: React.FC = () => { ctx.stroke(); ctx.closePath(); }); - + // Draw text elements textElements.forEach((textElement) => { ctx.font = '20px sans-serif'; ctx.fillText(textElement.text, textElement.x, textElement.y); }); - + // Draw the current line ctx.beginPath(); currentLine.forEach((point, i) => { @@ -173,13 +173,13 @@ const VirtualBoard: React.FC = () => { }); ctx.stroke(); ctx.closePath(); - + // Draw the start point for line mode if (startPoint && mode === 'line') { ctx.fillStyle = 'black'; ctx.fillRect(startPoint.x - 2, startPoint.y - 2, 4, 4); } - + // Draw the preview line for line mode if (isDrawing && startPoint && mode === 'line') { ctx.beginPath(); @@ -192,7 +192,6 @@ const VirtualBoard: React.FC = () => { ctx.closePath(); } }, [coordinates, textElements, currentLine, mode, startPoint, isDrawing]); - return ( <> From f1b7296aa8e43991c4f4337db35a6b3442ad7c9a Mon Sep 17 00:00:00 2001 From: Travis Bennett Date: Fri, 13 Oct 2023 23:35:53 -0400 Subject: [PATCH 09/15] erase functionality --- .../src/examples/virtualWhiteboard.tsx | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx b/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx index 2c8aabda91..39db99e92f 100644 --- a/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx +++ b/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx @@ -90,8 +90,15 @@ const VirtualBoard: React.FC = () => { }; const mouseMoveHandler = (e: React.MouseEvent) => { - handleDraw(e); - handleMouseMove(e); + if (mode === 'draw') { + handleDraw(e); + } + if (mode === 'erase') { + handleErase(e); + } + if (mode === 'line') { + handleMouseMove(e); + } }; const handleText = (e: React.MouseEvent) => { @@ -114,6 +121,26 @@ const VirtualBoard: React.FC = () => { setTextInput({ ...textInput, show: false }); setInputText(''); // Clear the input text }; + + const handleErase = (e: React.MouseEvent) => { + if (mode !== 'erase') return; + + const distance = (x1: number, y1: number, x2: number, y2: number) => + Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2); + + const rect = e.currentTarget.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + + const newCoordinates = coordinates + .map( + (line) => line.filter((point) => distance(x, y, point.x, point.y) > 10), // 10 is the radius of the eraser + ) + .filter((line) => line.length > 0); // Remove empty lines + + setCoordinates(newCoordinates); + }; + useEffect(() => { if (textInput.show && textInputRef.current) { textInputRef.current.focus(); @@ -137,6 +164,15 @@ const VirtualBoard: React.FC = () => { // Clear the canvas ctx.clearRect(0, 0, canvas.width, canvas.height); + // Draw eraser preview if mode is 'erase' + if (mode === 'erase' && currentLine.length > 0) { + const lastPoint = currentLine[currentLine.length - 1]; + ctx.fillStyle = 'rgba(0, 0, 0, 0.2)'; // semi-transparent black + ctx.beginPath(); + ctx.arc(lastPoint.x, lastPoint.y, 20 /* radius */, 0, Math.PI * 2); + ctx.fill(); + } + // Draw the lines from the coordinates state coordinates.forEach((line) => { ctx.beginPath(); From de01fb91857e4af0397cd7f45cf7c1868ba556e0 Mon Sep 17 00:00:00 2001 From: Travis Bennett Date: Sat, 14 Oct 2023 21:52:49 -0400 Subject: [PATCH 10/15] Solved line functionality --- .../kitchen-sink/src/examples/virtualWhiteboard.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx b/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx index 39db99e92f..3d0d62b96f 100644 --- a/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx +++ b/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx @@ -4,6 +4,7 @@ const VirtualBoard: React.FC = () => { const [coordinates, setCoordinates] = useState< Array<{ x: number; y: number }[]> >([]); + // Maintains the current lines state with x, y coordinates const [currentLine, setCurrentLine] = useState<{ x: number; y: number }[]>( [], ); @@ -24,6 +25,7 @@ const VirtualBoard: React.FC = () => { Array<{ x: number; y: number; text: string }> >([]); + // Sets state for the start point of a line const [startPoint, setStartPoint] = useState<{ x: number; y: number } | null>( null, ); @@ -50,7 +52,7 @@ const VirtualBoard: React.FC = () => { // const handleColorButtonClick = (e: React.MouseEvent) => { // colorInputRef.current?.click(); // }; - +// Maybe we don't need to draw current line because it is saved to state on mouseDown & mouseUp? const handleMouseDown = (e: React.MouseEvent) => { if (mode === 'line') { const rect = e.currentTarget.getBoundingClientRect(); @@ -68,16 +70,21 @@ const VirtualBoard: React.FC = () => { } else { setIsDrawing(true); } + setCurrentLine([]) console.log('MouseDown:', startPoint, isDrawing); }; const handleMouseUp = (e: React.MouseEvent) => { + const newCoordinates = [...coordinates, currentLine]; if (mode !== 'line') { setIsDrawing(false); - setCoordinates([...coordinates, currentLine]); + setCoordinates(newCoordinates); setCurrentLine([]); } console.log('MouseUp:', startPoint, isDrawing); + console.log('MouseUp: Coordinates', coordinates) + console.log('MouseUp: newCoordinates', newCoordinates) + console.log('MouseUp: Current Line', currentLine) }; const handleMouseMove = (e: React.MouseEvent) => { @@ -86,6 +93,7 @@ const VirtualBoard: React.FC = () => { const x = e.clientX - rect.left; const y = e.clientY - rect.top; setCurrentLine([{ x, y }]); + console.log('handleMouseMove:', currentLine) } }; @@ -139,6 +147,7 @@ const VirtualBoard: React.FC = () => { .filter((line) => line.length > 0); // Remove empty lines setCoordinates(newCoordinates); + }; useEffect(() => { From e19893ea94555b75f3d9b99d74539be788d68fdb Mon Sep 17 00:00:00 2001 From: Travis Bennett Date: Sun, 15 Oct 2023 10:19:24 -0400 Subject: [PATCH 11/15] Corrected text box functionality --- .../src/examples/virtualWhiteboard.tsx | 91 ++++++++++++------- 1 file changed, 57 insertions(+), 34 deletions(-) diff --git a/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx b/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx index 3d0d62b96f..6c7286059d 100644 --- a/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx +++ b/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx @@ -49,10 +49,6 @@ const VirtualBoard: React.FC = () => { setColor(e.target.value); }; - // const handleColorButtonClick = (e: React.MouseEvent) => { - // colorInputRef.current?.click(); - // }; -// Maybe we don't need to draw current line because it is saved to state on mouseDown & mouseUp? const handleMouseDown = (e: React.MouseEvent) => { if (mode === 'line') { const rect = e.currentTarget.getBoundingClientRect(); @@ -70,7 +66,7 @@ const VirtualBoard: React.FC = () => { } else { setIsDrawing(true); } - setCurrentLine([]) + setCurrentLine([]); console.log('MouseDown:', startPoint, isDrawing); }; @@ -82,9 +78,9 @@ const VirtualBoard: React.FC = () => { setCurrentLine([]); } console.log('MouseUp:', startPoint, isDrawing); - console.log('MouseUp: Coordinates', coordinates) - console.log('MouseUp: newCoordinates', newCoordinates) - console.log('MouseUp: Current Line', currentLine) + console.log('MouseUp: Coordinates', coordinates); + console.log('MouseUp: newCoordinates', newCoordinates); + console.log('MouseUp: Current Line', currentLine); }; const handleMouseMove = (e: React.MouseEvent) => { @@ -93,7 +89,7 @@ const VirtualBoard: React.FC = () => { const x = e.clientX - rect.left; const y = e.clientY - rect.top; setCurrentLine([{ x, y }]); - console.log('handleMouseMove:', currentLine) + console.log('handleMouseMove:', currentLine); } }; @@ -130,6 +126,21 @@ const VirtualBoard: React.FC = () => { setInputText(''); // Clear the input text }; + const generatePointsAlongLine = ( + start: { x: number; y: number }, + end: { x: number; y: number }, + numPoints: number, + ) => { + const points = []; + for (let i = 0; i <= numPoints; i++) { + const t = i / numPoints; + const x = start.x + t * (end.x - start.x); + const y = start.y + t * (end.y - start.y); + points.push({ x, y }); + } + return points; + }; + const handleErase = (e: React.MouseEvent) => { if (mode !== 'erase') return; @@ -141,13 +152,33 @@ const VirtualBoard: React.FC = () => { const y = e.clientY - rect.top; const newCoordinates = coordinates - .map( - (line) => line.filter((point) => distance(x, y, point.x, point.y) > 10), // 10 is the radius of the eraser - ) + .map((line) => { + let closestPoint = null; + let closestDistance = Infinity; + let closestIndex = -1; + + line.forEach((point, index) => { + const d = distance(x, y, point.x, point.y); + if (d < closestDistance) { + closestDistance = d; + closestPoint = point; + closestIndex = index; + } + }); + + if (closestDistance < 20) { + // Sets eraser size + const firstHalf = line.slice(0, closestIndex); + const secondHalf = line.slice(closestIndex + 1); + return [firstHalf, secondHalf]; + } + + return [line]; + }) + .flat() .filter((line) => line.length > 0); // Remove empty lines setCoordinates(newCoordinates); - }; useEffect(() => { @@ -170,6 +201,8 @@ const VirtualBoard: React.FC = () => { const ctx = canvas.getContext('2d'); if (!ctx) return; + // Set the color here + ctx.strokeStyle = color; // Clear the canvas ctx.clearRect(0, 0, canvas.width, canvas.height); @@ -203,6 +236,7 @@ const VirtualBoard: React.FC = () => { // Draw text elements textElements.forEach((textElement) => { + ctx.fillStyle = color; ctx.font = '20px sans-serif'; ctx.fillText(textElement.text, textElement.x, textElement.y); }); @@ -236,7 +270,15 @@ const VirtualBoard: React.FC = () => { ctx.stroke(); ctx.closePath(); } - }, [coordinates, textElements, currentLine, mode, startPoint, isDrawing]); + }, [ + coordinates, + textElements, + currentLine, + mode, + startPoint, + isDrawing, + color, + ]); return ( <> @@ -247,19 +289,6 @@ const VirtualBoard: React.FC = () => { - {/* */}
{ style={{ border: '2px solid black' }} />
- {textInput.show ? ( + {textInput.show && ( { }} autoFocus /> - ) : ( - setInputText(e.target.value)} - /> )}
From 0a6a361730ad16bd8567b389299940a4cc06c677 Mon Sep 17 00:00:00 2001 From: Travis Bennett Date: Sun, 15 Oct 2023 10:50:56 -0400 Subject: [PATCH 12/15] Corrected TS errors --- .../src/examples/virtualWhiteboard.tsx | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx b/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx index 6c7286059d..ccc70b2070 100644 --- a/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx +++ b/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx @@ -1,9 +1,10 @@ import React, { useRef, useEffect, useState } from 'react'; const VirtualBoard: React.FC = () => { - const [coordinates, setCoordinates] = useState< - Array<{ x: number; y: number }[]> + const [coordinates, setCoordinates] = useState< + Array<{ points: { x: number; y: number }[]; color: string }> >([]); + // Maintains the current lines state with x, y coordinates const [currentLine, setCurrentLine] = useState<{ x: number; y: number }[]>( [], @@ -59,7 +60,8 @@ const VirtualBoard: React.FC = () => { setStartPoint({ x, y }); setIsDrawing(true); } else { - setCoordinates([...coordinates, [startPoint, { x, y }]]); + setCoordinates([...coordinates, { points: [startPoint, { x, y }], color }]); + setStartPoint(null); // Reset the starting point immediately setIsDrawing(false); // Stop drawing the preview line } @@ -71,7 +73,8 @@ const VirtualBoard: React.FC = () => { }; const handleMouseUp = (e: React.MouseEvent) => { - const newCoordinates = [...coordinates, currentLine]; + const newLine = { points: currentLine, color: color }; + const newCoordinates = [...coordinates, newLine]; if (mode !== 'line') { setIsDrawing(false); setCoordinates(newCoordinates); @@ -152,12 +155,12 @@ const VirtualBoard: React.FC = () => { const y = e.clientY - rect.top; const newCoordinates = coordinates - .map((line) => { + .map((lineData) => { let closestPoint = null; let closestDistance = Infinity; let closestIndex = -1; - line.forEach((point, index) => { + lineData.points.forEach((point, index) => { const d = distance(x, y, point.x, point.y); if (d < closestDistance) { closestDistance = d; @@ -168,15 +171,18 @@ const VirtualBoard: React.FC = () => { if (closestDistance < 20) { // Sets eraser size - const firstHalf = line.slice(0, closestIndex); - const secondHalf = line.slice(closestIndex + 1); - return [firstHalf, secondHalf]; + const firstHalf = lineData.points.slice(0, closestIndex); + const secondHalf = lineData.points.slice(closestIndex + 1); + return [ + { points: firstHalf, color: lineData.color }, + { points: secondHalf, color: lineData.color } + ]; } - return [line]; + return [lineData]; }) .flat() - .filter((line) => line.length > 0); // Remove empty lines + .filter((lineData) => lineData.points.length > 0); // Remove empty lines setCoordinates(newCoordinates); }; @@ -216,23 +222,19 @@ const VirtualBoard: React.FC = () => { } // Draw the lines from the coordinates state - coordinates.forEach((line) => { - ctx.beginPath(); - if (line.length === 2 && mode === 'line') { - ctx.moveTo(line[0].x, line[0].y); - ctx.lineTo(line[1].x, line[1].y); - } else { - line.forEach((point, i) => { + coordinates.forEach((lineData) => { + ctx.strokeStyle = lineData.color; + ctx.beginPath(); + lineData.points.forEach((point, i) => { if (i === 0) { ctx.moveTo(point.x, point.y); } else { ctx.lineTo(point.x, point.y); } }); - } - ctx.stroke(); - ctx.closePath(); - }); + ctx.stroke(); + ctx.closePath(); + }); // Draw text elements textElements.forEach((textElement) => { From 9e802aa2989c4bb0b13558990a575877a9928539 Mon Sep 17 00:00:00 2001 From: Travis Bennett Date: Sun, 15 Oct 2023 15:29:33 -0400 Subject: [PATCH 13/15] Functionality for all modes complete --- .../src/examples/virtualWhiteboard.tsx | 165 +++++++++++++----- 1 file changed, 124 insertions(+), 41 deletions(-) diff --git a/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx b/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx index ccc70b2070..15bd8f1cbe 100644 --- a/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx +++ b/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx @@ -1,18 +1,28 @@ import React, { useRef, useEffect, useState } from 'react'; const VirtualBoard: React.FC = () => { - const [coordinates, setCoordinates] = useState< + +// Enum responsible for defining modes +enum Mode { + Draw = 'draw', + Text = 'text', + Line = 'line', + Erase = 'erase', + Clear = 'clear' +} + +type CoordinateType = Array<{points:{ x: number, y: number}[]; color: string}>; + +const [coordinates, setCoordinates] = useState< Array<{ points: { x: number; y: number }[]; color: string }> >([]); - + // Maintains the current lines state with x, y coordinates const [currentLine, setCurrentLine] = useState<{ x: number; y: number }[]>( [], ); const [isDrawing, setIsDrawing] = useState(false); // New state to track drawing mode - const [mode, setMode] = useState< - 'draw' | 'text' | 'line' | 'erase' | 'clear' - >('draw'); + const [mode, setMode] = useState< Mode | null>(Mode.Draw) const [color, setColor] = useState('#000000'); // Responsible for controlling position and visibility of the text input field const [textInput, setTextInput] = useState<{ @@ -30,13 +40,51 @@ const VirtualBoard: React.FC = () => { const [startPoint, setStartPoint] = useState<{ x: number; y: number } | null>( null, ); + const [mousePosition, setMousePosition] = useState<{ + x: number; + y: number; + } | null>(null); + const [showEraserPreview, setShowEraserPreview] = useState(false); + const [undoStack, setUndoStack] = useState([]); + const [redoStack, setRedoStack] = useState([]); - const colorInputRef = useRef(null); const canvasRef = useRef(null); const textInputRef = useRef(null); + const handleChange = () => { + setUndoStack([...undoStack, coordinates]); + setRedoStack([]); + }; + + const handleUndo = () => { + if (undoStack.length > 0) { + const newUndoStack = [...undoStack]; + const lastState = newUndoStack.pop(); + + const newRedoStack = [...redoStack, coordinates]; + + setCoordinates(lastState || []); + setUndoStack(newUndoStack); + setRedoStack(newRedoStack); + } + }; + + const handleRedo = () => { + if (redoStack.length > 0) { + const newRedoStack = [...redoStack]; + const nextState = newRedoStack.pop(); + + const newUndoStack = [...undoStack, coordinates]; + + setCoordinates(nextState || []); + setUndoStack(newUndoStack); + setRedoStack(newRedoStack); + } + }; + + const handleModeChange = ( - newMode: 'draw' | 'text' | 'line' | 'erase' | 'clear', + newMode: Mode ) => { setMode(newMode); }; @@ -60,7 +108,10 @@ const VirtualBoard: React.FC = () => { setStartPoint({ x, y }); setIsDrawing(true); } else { - setCoordinates([...coordinates, { points: [startPoint, { x, y }], color }]); + setCoordinates([ + ...coordinates, + { points: [startPoint, { x, y }], color }, + ]); setStartPoint(null); // Reset the starting point immediately setIsDrawing(false); // Stop drawing the preview line @@ -69,21 +120,17 @@ const VirtualBoard: React.FC = () => { setIsDrawing(true); } setCurrentLine([]); - console.log('MouseDown:', startPoint, isDrawing); }; - const handleMouseUp = (e: React.MouseEvent) => { + const handleMouseUp = (e:React.MouseEvent) => { const newLine = { points: currentLine, color: color }; const newCoordinates = [...coordinates, newLine]; if (mode !== 'line') { setIsDrawing(false); setCoordinates(newCoordinates); setCurrentLine([]); + handleChange(); } - console.log('MouseUp:', startPoint, isDrawing); - console.log('MouseUp: Coordinates', coordinates); - console.log('MouseUp: newCoordinates', newCoordinates); - console.log('MouseUp: Current Line', currentLine); }; const handleMouseMove = (e: React.MouseEvent) => { @@ -92,7 +139,6 @@ const VirtualBoard: React.FC = () => { const x = e.clientX - rect.left; const y = e.clientY - rect.top; setCurrentLine([{ x, y }]); - console.log('handleMouseMove:', currentLine); } }; @@ -102,13 +148,25 @@ const VirtualBoard: React.FC = () => { } if (mode === 'erase') { handleErase(e); + handleEraserPreview(e); } if (mode === 'line') { handleMouseMove(e); } }; + const handleEraserPreview = (e: React.MouseEvent) => { + const rect = e.currentTarget.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + setMousePosition({ x, y }); + }; + const handleText = (e: React.MouseEvent) => { + if (mode === 'erase') { + setMode(null); + return; + } if (mode !== 'text') return; // Only add text when the mode is 'text' const rect = e.currentTarget.getBoundingClientRect(); @@ -146,6 +204,7 @@ const VirtualBoard: React.FC = () => { const handleErase = (e: React.MouseEvent) => { if (mode !== 'erase') return; + setShowEraserPreview(true); const distance = (x1: number, y1: number, x2: number, y2: number) => Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2); @@ -175,7 +234,7 @@ const VirtualBoard: React.FC = () => { const secondHalf = lineData.points.slice(closestIndex + 1); return [ { points: firstHalf, color: lineData.color }, - { points: secondHalf, color: lineData.color } + { points: secondHalf, color: lineData.color }, ]; } @@ -212,29 +271,20 @@ const VirtualBoard: React.FC = () => { // Clear the canvas ctx.clearRect(0, 0, canvas.width, canvas.height); - // Draw eraser preview if mode is 'erase' - if (mode === 'erase' && currentLine.length > 0) { - const lastPoint = currentLine[currentLine.length - 1]; - ctx.fillStyle = 'rgba(0, 0, 0, 0.2)'; // semi-transparent black - ctx.beginPath(); - ctx.arc(lastPoint.x, lastPoint.y, 20 /* radius */, 0, Math.PI * 2); - ctx.fill(); - } - // Draw the lines from the coordinates state coordinates.forEach((lineData) => { - ctx.strokeStyle = lineData.color; - ctx.beginPath(); - lineData.points.forEach((point, i) => { - if (i === 0) { - ctx.moveTo(point.x, point.y); - } else { - ctx.lineTo(point.x, point.y); - } - }); - ctx.stroke(); - ctx.closePath(); + ctx.strokeStyle = lineData.color; + ctx.beginPath(); + lineData.points.forEach((point, i) => { + if (i === 0) { + ctx.moveTo(point.x, point.y); + } else { + ctx.lineTo(point.x, point.y); + } }); + ctx.stroke(); + ctx.closePath(); + }); // Draw text elements textElements.forEach((textElement) => { @@ -244,6 +294,7 @@ const VirtualBoard: React.FC = () => { }); // Draw the current line + ctx.strokeStyle = color; ctx.beginPath(); currentLine.forEach((point, i) => { if (i === 0) { @@ -272,6 +323,19 @@ const VirtualBoard: React.FC = () => { ctx.stroke(); ctx.closePath(); } + // Draw eraser preview if mode is 'erase' + if (mode === 'erase' && mousePosition && showEraserPreview) { + ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; // semi-transparent black + ctx.beginPath(); + ctx.arc( + mousePosition.x, + mousePosition.y, + 20 /* radius */, + 0, + Math.PI * 2, + ); + ctx.fill(); + } }, [ coordinates, textElements, @@ -280,17 +344,36 @@ const VirtualBoard: React.FC = () => { startPoint, isDrawing, color, + mousePosition, + showEraserPreview, ]); + useEffect(() => { + const onClickOutsideCanvas = (e: MouseEvent) => { + const canvas = canvasRef.current; + if (canvas && !canvas.contains(e.target as Node)) { + setShowEraserPreview(false); + } + }; + + document.addEventListener('click', onClickOutsideCanvas); + + return () => { + document.removeEventListener('click', onClickOutsideCanvas); + }; + }, []); return ( <>
- - - - + + + + + - + + +
Date: Sun, 15 Oct 2023 19:06:47 -0400 Subject: [PATCH 14/15] Added broswer specific stylings for color picker btn --- .../src/examples/virtualWhiteboard.tsx | 55 ++++++++++++------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx b/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx index 15bd8f1cbe..1cc6d31263 100644 --- a/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx +++ b/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx @@ -1,19 +1,21 @@ import React, { useRef, useEffect, useState } from 'react'; const VirtualBoard: React.FC = () => { - -// Enum responsible for defining modes -enum Mode { + // Enum responsible for defining modes + enum Mode { Draw = 'draw', Text = 'text', Line = 'line', Erase = 'erase', - Clear = 'clear' -} + Clear = 'clear', + } + + type CoordinateType = Array<{ + points: { x: number; y: number }[]; + color: string; + }>; -type CoordinateType = Array<{points:{ x: number, y: number}[]; color: string}>; - -const [coordinates, setCoordinates] = useState< + const [coordinates, setCoordinates] = useState< Array<{ points: { x: number; y: number }[]; color: string }> >([]); @@ -22,7 +24,7 @@ const [coordinates, setCoordinates] = useState< [], ); const [isDrawing, setIsDrawing] = useState(false); // New state to track drawing mode - const [mode, setMode] = useState< Mode | null>(Mode.Draw) + const [mode, setMode] = useState(Mode.Draw); const [color, setColor] = useState('#000000'); // Responsible for controlling position and visibility of the text input field const [textInput, setTextInput] = useState<{ @@ -48,6 +50,11 @@ const [coordinates, setCoordinates] = useState< const [undoStack, setUndoStack] = useState([]); const [redoStack, setRedoStack] = useState([]); + const isChrome = /Chrome/.test(navigator.userAgent) + const isFirefox = /Firefox/.test(navigator.userAgent); + + + const canvasRef = useRef(null); const textInputRef = useRef(null); @@ -60,32 +67,29 @@ const [coordinates, setCoordinates] = useState< if (undoStack.length > 0) { const newUndoStack = [...undoStack]; const lastState = newUndoStack.pop(); - + const newRedoStack = [...redoStack, coordinates]; - + setCoordinates(lastState || []); setUndoStack(newUndoStack); setRedoStack(newRedoStack); } }; - + const handleRedo = () => { if (redoStack.length > 0) { const newRedoStack = [...redoStack]; const nextState = newRedoStack.pop(); - + const newUndoStack = [...undoStack, coordinates]; - + setCoordinates(nextState || []); setUndoStack(newUndoStack); setRedoStack(newRedoStack); } }; - - const handleModeChange = ( - newMode: Mode - ) => { + const handleModeChange = (newMode: Mode) => { setMode(newMode); }; @@ -122,7 +126,7 @@ const [coordinates, setCoordinates] = useState< setCurrentLine([]); }; - const handleMouseUp = (e:React.MouseEvent) => { + const handleMouseUp = (e: React.MouseEvent) => { const newLine = { points: currentLine, color: color }; const newCoordinates = [...coordinates, newLine]; if (mode !== 'line') { @@ -365,13 +369,22 @@ const [coordinates, setCoordinates] = useState< return ( <>
- + - +
From f3fb67da17686f37be5a9b0ffcd3c6c85ff253ca Mon Sep 17 00:00:00 2001 From: Travis Bennett Date: Sun, 15 Oct 2023 19:35:16 -0400 Subject: [PATCH 15/15] Added crosshair on draw --- packages/kitchen-sink/src/examples/virtualWhiteboard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx b/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx index 1cc6d31263..bc638321d1 100644 --- a/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx +++ b/packages/kitchen-sink/src/examples/virtualWhiteboard.tsx @@ -397,7 +397,7 @@ const VirtualBoard: React.FC = () => { onClick={handleText} width={800} height={600} - style={{ border: '2px solid black' }} + style={{ border: '2px solid black', cursor: mode === 'draw' ? 'crosshair' : 'default' }} />
{textInput.show && (