Fix notes
This commit is contained in:
@@ -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>
|
||||||
|
Reference in New Issue
Block a user