1
0

Fix notes

This commit is contained in:
2025-07-18 19:41:21 +02:00
parent edd1478aa5
commit dfa54ae883

View File

@@ -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 { FiEdit3, FiTrash, FiChevronDown } from 'react-icons/fi'
import { TbEraser } from 'react-icons/tb' import { TbEraser } from 'react-icons/tb'
import './Notes.sass' import './Notes.sass'
const Notes = () => { const Notes = () => {
const canvasRef = useRef(null) const canvasRef = useRef(null)
const ctxRef = useRef(null)
const rectRef = useRef(null)
const saveTimeoutRef = useRef(null)
const [isDrawing, setIsDrawing] = useState(false) const [isDrawing, setIsDrawing] = useState(false)
const [tool, setTool] = useState('pen') // 'pen' or 'eraser' const [tool, setTool] = useState('pen') // 'pen' or 'eraser'
const [penColor, setPenColor] = useState('#2d3748') const [penColor, setPenColor] = useState('#2d3748')
@@ -23,45 +26,11 @@ const Notes = () => {
'#00acc1' // Cyan '#00acc1' // Cyan
] ]
useEffect(() => { const loadCanvasFromStorage = useCallback(() => {
const canvas = canvasRef.current 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') const savedData = localStorage.getItem('notes-canvas')
if (savedData) { if (savedData) {
@@ -72,53 +41,121 @@ const Notes = () => {
} }
img.src = savedData 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) setIsDrawing(true)
const canvas = canvasRef.current const ctx = ctxRef.current
const ctx = canvas.getContext('2d') const rect = rectRef.current || canvasRef.current.getBoundingClientRect()
const rect = canvas.getBoundingClientRect()
if (!ctx) return
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 x = e.clientX - rect.left const x = e.clientX - rect.left
const y = e.clientY - rect.top const y = e.clientY - rect.top
// Set tool properties once at the start of drawing
if (tool === 'pen') { if (tool === 'pen') {
ctx.globalCompositeOperation = 'source-over' ctx.globalCompositeOperation = 'source-over'
ctx.strokeStyle = penColor ctx.strokeStyle = penColor
ctx.lineWidth = 3 ctx.lineWidth = 3
ctx.lineTo(x, y)
ctx.stroke()
} else if (tool === 'eraser') { } else if (tool === 'eraser') {
ctx.globalCompositeOperation = 'destination-out' ctx.globalCompositeOperation = 'destination-out'
ctx.lineWidth = 40 // Increased eraser size ctx.lineWidth = 40
ctx.lineTo(x, y)
ctx.stroke()
} }
}
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) setIsDrawing(false)
// Save canvas state after drawing // Save canvas state after drawing (debounced)
saveCanvasToStorage() saveCanvasToStorage()
} }, [saveCanvasToStorage])
// Touch event handlers for mobile/tablet support // Optimized touch event handlers
const handleTouchStart = (e) => { const handleTouchStart = useCallback((e) => {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
@@ -133,9 +170,9 @@ const Notes = () => {
cancelable: true cancelable: true
}) })
startDrawing(mouseEvent) startDrawing(mouseEvent)
} }, [startDrawing])
const handleTouchMove = (e) => { const handleTouchMove = useCallback((e) => {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
@@ -150,21 +187,36 @@ const Notes = () => {
cancelable: true cancelable: true
}) })
draw(mouseEvent) draw(mouseEvent)
} }, [draw])
const handleTouchEnd = (e) => { const handleTouchEnd = useCallback((e) => {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
stopDrawing() stopDrawing()
} }, [stopDrawing])
const clearCanvas = () => { const clearCanvas = useCallback(() => {
const canvas = canvasRef.current 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.clearRect(0, 0, canvas.width, canvas.height)
ctx.fillStyle = 'white'
ctx.fillRect(0, 0, canvas.width, canvas.height)
// Clear saved state // Clear saved state
localStorage.removeItem('notes-canvas') 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 ( return (
<div className="notes-page"> <div className="notes-page">
@@ -230,13 +282,17 @@ const Notes = () => {
<canvas <canvas
ref={canvasRef} ref={canvasRef}
className="drawing-canvas" className="drawing-canvas"
onMouseDown={startDrawing} onMouseDown={(e) => {
updateRect()
startDrawing(e)
}}
onMouseMove={draw} onMouseMove={draw}
onMouseUp={stopDrawing} onMouseUp={stopDrawing}
onMouseLeave={stopDrawing} onMouseLeave={stopDrawing}
onTouchStart={handleTouchStart} onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove} onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd} onTouchEnd={handleTouchEnd}
style={{ touchAction: 'none' }}
/> />
</div> </div>
</div> </div>