From dfa54ae883a8fad28aa7f8a2f8bb89e736bf3aae Mon Sep 17 00:00:00 2001 From: Mathias Wagner Date: Fri, 18 Jul 2025 19:41:21 +0200 Subject: [PATCH] Fix notes --- dashboard/src/renderer/src/pages/Notes.jsx | 210 +++++++++++++-------- 1 file changed, 133 insertions(+), 77 deletions(-) diff --git a/dashboard/src/renderer/src/pages/Notes.jsx b/dashboard/src/renderer/src/pages/Notes.jsx index 3444870..72aff29 100644 --- a/dashboard/src/renderer/src/pages/Notes.jsx +++ b/dashboard/src/renderer/src/pages/Notes.jsx @@ -1,10 +1,13 @@ -import { useRef, useEffect, useState } from 'react' +import { useRef, useEffect, useState, useCallback } from 'react' import { FiEdit3, FiTrash, FiChevronDown } from 'react-icons/fi' import { TbEraser } from 'react-icons/tb' import './Notes.sass' const Notes = () => { const canvasRef = useRef(null) + const ctxRef = useRef(null) + const rectRef = useRef(null) + const saveTimeoutRef = useRef(null) const [isDrawing, setIsDrawing] = useState(false) const [tool, setTool] = useState('pen') // 'pen' or 'eraser' const [penColor, setPenColor] = useState('#2d3748') @@ -23,45 +26,11 @@ const Notes = () => { '#00acc1' // Cyan ] - useEffect(() => { + const loadCanvasFromStorage = useCallback(() => { const canvas = canvasRef.current - const ctx = canvas.getContext('2d', { willReadFrequently: true }) + const ctx = ctxRef.current + if (!canvas || !ctx) return - // Set canvas size - const resizeCanvas = () => { - const container = canvas.parentElement - const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height) - - canvas.width = container.clientWidth - canvas.height = container.clientHeight - - // Set drawing properties - ctx.lineCap = 'round' - ctx.lineJoin = 'round' - ctx.imageSmoothingEnabled = true - - // Restore canvas content after resize - ctx.putImageData(imageData, 0, 0) - - // Load saved canvas data - loadCanvasFromStorage() - } - - resizeCanvas() - window.addEventListener('resize', resizeCanvas) - - return () => window.removeEventListener('resize', resizeCanvas) - }, []) - - const saveCanvasToStorage = () => { - const canvas = canvasRef.current - const dataURL = canvas.toDataURL() - localStorage.setItem('notes-canvas', dataURL) - } - - const loadCanvasFromStorage = () => { - const canvas = canvasRef.current - const ctx = canvas.getContext('2d') const savedData = localStorage.getItem('notes-canvas') if (savedData) { @@ -72,53 +41,121 @@ const Notes = () => { } img.src = savedData } - } + }, []) - const startDrawing = (e) => { + useEffect(() => { + const canvas = canvasRef.current + if (!canvas) return + + const ctx = canvas.getContext('2d') + ctxRef.current = ctx + + // Set canvas size and cache the rect + const resizeCanvas = () => { + const container = canvas.parentElement + if (!container) return + + canvas.width = container.clientWidth + canvas.height = container.clientHeight + + // Set white background + ctx.fillStyle = 'white' + ctx.fillRect(0, 0, canvas.width, canvas.height) + + // Set drawing properties + ctx.lineCap = 'round' + ctx.lineJoin = 'round' + ctx.imageSmoothingEnabled = true + + // Cache the bounding rect + rectRef.current = canvas.getBoundingClientRect() + + // Load saved canvas data from storage + loadCanvasFromStorage() + } + + resizeCanvas() + + // Debounced resize handler + let resizeTimeout + const handleResize = () => { + clearTimeout(resizeTimeout) + resizeTimeout = setTimeout(resizeCanvas, 150) + } + + window.addEventListener('resize', handleResize) + + return () => { + window.removeEventListener('resize', handleResize) + clearTimeout(resizeTimeout) + if (saveTimeoutRef.current) { + clearTimeout(saveTimeoutRef.current) + } + } + }, [loadCanvasFromStorage]) + + // Debounced save function to prevent excessive localStorage writes + const debouncedSave = useCallback(() => { + if (saveTimeoutRef.current) { + clearTimeout(saveTimeoutRef.current) + } + saveTimeoutRef.current = setTimeout(() => { + const canvas = canvasRef.current + const dataURL = canvas.toDataURL() + localStorage.setItem('notes-canvas', dataURL) + }, 500) // Save 500ms after user stops drawing + }, []) + + const saveCanvasToStorage = useCallback(() => { + debouncedSave() + }, [debouncedSave]) + + const startDrawing = useCallback((e) => { setIsDrawing(true) - const canvas = canvasRef.current - const ctx = canvas.getContext('2d') - const rect = canvas.getBoundingClientRect() - - const x = e.clientX - rect.left - const y = e.clientY - rect.top - - ctx.beginPath() - ctx.moveTo(x, y) - } - - const draw = (e) => { - if (!isDrawing) return - - const canvas = canvasRef.current - const ctx = canvas.getContext('2d') - const rect = canvas.getBoundingClientRect() + const ctx = ctxRef.current + const rect = rectRef.current || canvasRef.current.getBoundingClientRect() + + if (!ctx) return const x = e.clientX - rect.left const y = e.clientY - rect.top + // Set tool properties once at the start of drawing if (tool === 'pen') { ctx.globalCompositeOperation = 'source-over' ctx.strokeStyle = penColor ctx.lineWidth = 3 - ctx.lineTo(x, y) - ctx.stroke() } else if (tool === 'eraser') { ctx.globalCompositeOperation = 'destination-out' - ctx.lineWidth = 40 // Increased eraser size - ctx.lineTo(x, y) - ctx.stroke() + ctx.lineWidth = 40 } - } + + ctx.beginPath() + ctx.moveTo(x, y) + }, [tool, penColor]) - const stopDrawing = () => { + const draw = useCallback((e) => { + if (!isDrawing) return + + const ctx = ctxRef.current + const rect = rectRef.current || canvasRef.current.getBoundingClientRect() + + const x = e.clientX - rect.left + const y = e.clientY - rect.top + + // Tool properties are already set in startDrawing + ctx.lineTo(x, y) + ctx.stroke() + }, [isDrawing]) + + const stopDrawing = useCallback(() => { setIsDrawing(false) - // Save canvas state after drawing + // Save canvas state after drawing (debounced) saveCanvasToStorage() - } + }, [saveCanvasToStorage]) - // Touch event handlers for mobile/tablet support - const handleTouchStart = (e) => { + // Optimized touch event handlers + const handleTouchStart = useCallback((e) => { e.preventDefault() e.stopPropagation() @@ -133,9 +170,9 @@ const Notes = () => { cancelable: true }) startDrawing(mouseEvent) - } + }, [startDrawing]) - const handleTouchMove = (e) => { + const handleTouchMove = useCallback((e) => { e.preventDefault() e.stopPropagation() @@ -150,21 +187,36 @@ const Notes = () => { cancelable: true }) draw(mouseEvent) - } + }, [draw]) - const handleTouchEnd = (e) => { + const handleTouchEnd = useCallback((e) => { e.preventDefault() e.stopPropagation() stopDrawing() - } + }, [stopDrawing]) - const clearCanvas = () => { + const clearCanvas = useCallback(() => { const canvas = canvasRef.current - const ctx = canvas.getContext('2d') + const ctx = ctxRef.current + if (!canvas || !ctx) return + + // Clear and set white background ctx.clearRect(0, 0, canvas.width, canvas.height) + ctx.fillStyle = 'white' + ctx.fillRect(0, 0, canvas.width, canvas.height) + // Clear saved state localStorage.removeItem('notes-canvas') - } + // Clear any pending save + if (saveTimeoutRef.current) { + clearTimeout(saveTimeoutRef.current) + } + }, []) + + // Update rect when canvas is clicked (in case of layout changes) + const updateRect = useCallback(() => { + rectRef.current = canvasRef.current.getBoundingClientRect() + }, []) return (
@@ -230,13 +282,17 @@ const Notes = () => { { + updateRect() + startDrawing(e) + }} onMouseMove={draw} onMouseUp={stopDrawing} onMouseLeave={stopDrawing} onTouchStart={handleTouchStart} onTouchMove={handleTouchMove} onTouchEnd={handleTouchEnd} + style={{ touchAction: 'none' }} />