main
parent
2f32d1a166
commit
1b79fae385
|
@ -33,8 +33,6 @@ const DrawingCanvas = () => {
|
||||||
const canvasRef = useRef(null);
|
const canvasRef = useRef(null);
|
||||||
const [isDrawing, setIsDrawing] = useState(false);
|
const [isDrawing, setIsDrawing] = useState(false);
|
||||||
const [lastPosition, setLastPosition] = useState(null);
|
const [lastPosition, setLastPosition] = useState(null);
|
||||||
const [history, setHistory] = useState([]);
|
|
||||||
const [undoHistory, setUndoHistory] = useState([]);
|
|
||||||
const [shapes, setShapes] = useState([]);
|
const [shapes, setShapes] = useState([]);
|
||||||
const [currentTool, setCurrentTool] = useState('pencil');
|
const [currentTool, setCurrentTool] = useState('pencil');
|
||||||
const [color, setColor] = useState('#000000');
|
const [color, setColor] = useState('#000000');
|
||||||
|
@ -45,6 +43,7 @@ const DrawingCanvas = () => {
|
||||||
const [textInput, setTextInput] = useState('');
|
const [textInput, setTextInput] = useState('');
|
||||||
const [isTextInputVisible, setIsTextInputVisible] = useState(false);
|
const [isTextInputVisible, setIsTextInputVisible] = useState(false);
|
||||||
const [textPosition, setTextPosition] = useState({ x: 0, y: 0 });
|
const [textPosition, setTextPosition] = useState({ x: 0, y: 0 });
|
||||||
|
const [showColorPicker, setShowColorPicker] = useState(false);
|
||||||
|
|
||||||
// Resize canvas on window resize
|
// Resize canvas on window resize
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -68,11 +67,6 @@ const DrawingCanvas = () => {
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
shapes.forEach(shape => drawShape(ctx, shape));
|
shapes.forEach(shape => drawShape(ctx, shape));
|
||||||
history.forEach(action => {
|
|
||||||
if (action.type === 'stroke') {
|
|
||||||
ctx.putImageData(action.imageData, 0, 0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// After drawing all shapes
|
// After drawing all shapes
|
||||||
if (selectedIndex !== null && shapes[selectedIndex]) {
|
if (selectedIndex !== null && shapes[selectedIndex]) {
|
||||||
|
@ -81,40 +75,20 @@ const DrawingCanvas = () => {
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.strokeStyle = 'red';
|
ctx.strokeStyle = 'red';
|
||||||
ctx.lineWidth = 2;
|
ctx.lineWidth = 2;
|
||||||
if (shape.type === 'rectangle' || shape.type === 'image' || shape.type === 'text') {
|
if (shape.type === 'rectangle' || shape.type === 'image') {
|
||||||
ctx.strokeRect(shape.x, shape.y, shape.width, shape.height);
|
ctx.strokeRect(shape.x, shape.y, shape.width, shape.height);
|
||||||
// Draw resize handles (corners)
|
|
||||||
const handleSize = 8;
|
|
||||||
const handles = [
|
|
||||||
[shape.x, shape.y],
|
|
||||||
[shape.x + shape.width, shape.y],
|
|
||||||
[shape.x, shape.y + shape.height],
|
|
||||||
[shape.x + shape.width, shape.y + shape.height],
|
|
||||||
];
|
|
||||||
ctx.fillStyle = 'white';
|
|
||||||
ctx.strokeStyle = 'black';
|
|
||||||
handles.forEach(([hx, hy]) => {
|
|
||||||
ctx.fillRect(hx - handleSize/2, hy - handleSize/2, handleSize, handleSize);
|
|
||||||
ctx.strokeRect(hx - handleSize/2, hy - handleSize/2, handleSize, handleSize);
|
|
||||||
});
|
|
||||||
} else if (shape.type === 'circle') {
|
} else if (shape.type === 'circle') {
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(shape.x, shape.y, shape.radius, 0, Math.PI * 2);
|
ctx.arc(shape.x, shape.y, shape.radius, 0, Math.PI * 2);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
// Draw handle at the edge (right)
|
|
||||||
const handleSize = 8;
|
|
||||||
ctx.fillStyle = 'white';
|
|
||||||
ctx.strokeStyle = 'black';
|
|
||||||
ctx.fillRect(shape.x + shape.radius - handleSize/2, shape.y - handleSize/2, handleSize, handleSize);
|
|
||||||
ctx.strokeRect(shape.x + shape.radius - handleSize/2, shape.y - handleSize/2, handleSize, handleSize);
|
|
||||||
}
|
}
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
}, [shapes, history, selectedIndex]);
|
}, [shapes, selectedIndex]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
redraw();
|
redraw();
|
||||||
}, [shapes, history, redraw]);
|
}, [shapes, redraw]);
|
||||||
|
|
||||||
// Get mouse or touch position
|
// Get mouse or touch position
|
||||||
const getPos = (e) => {
|
const getPos = (e) => {
|
||||||
|
@ -253,33 +227,11 @@ const DrawingCanvas = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const deepCopyShapes = (shapesArr) => JSON.parse(JSON.stringify(shapesArr));
|
// Update handlePointerUp to not save history/undoHistory
|
||||||
|
|
||||||
const handlePointerUp = () => {
|
const handlePointerUp = () => {
|
||||||
setIsDrawing(false);
|
setIsDrawing(false);
|
||||||
setLastPosition(null);
|
setLastPosition(null);
|
||||||
setSelectedIndex(null);
|
setSelectedIndex(null);
|
||||||
// Save a deep copy of shapes for undo/redo
|
|
||||||
setHistory(prev => [...prev, deepCopyShapes(shapes)]);
|
|
||||||
setUndoHistory([]);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Undo: restore the previous snapshot
|
|
||||||
const handleUndo = () => {
|
|
||||||
if (history.length === 0) return;
|
|
||||||
setUndoHistory(prev => [...prev, deepCopyShapes(shapes)]);
|
|
||||||
const prevShapes = history[history.length - 1];
|
|
||||||
setShapes(deepCopyShapes(prevShapes));
|
|
||||||
setHistory(prev => prev.slice(0, -1));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Redo: restore the next snapshot
|
|
||||||
const handleRedo = () => {
|
|
||||||
if (undoHistory.length === 0) return;
|
|
||||||
setHistory(prev => [...prev, deepCopyShapes(shapes)]);
|
|
||||||
const nextShapes = undoHistory[undoHistory.length - 1];
|
|
||||||
setShapes(deepCopyShapes(nextShapes));
|
|
||||||
setUndoHistory(prev => prev.slice(0, -1));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Touch events for mobile
|
// Touch events for mobile
|
||||||
|
@ -316,9 +268,6 @@ const DrawingCanvas = () => {
|
||||||
image: img
|
image: img
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
// Save to history for undo
|
|
||||||
setHistory(prev => [...prev, deepCopyShapes([...shapes, { type: 'image', x, y, width, height, image: img }])]);
|
|
||||||
setUndoHistory([]);
|
|
||||||
};
|
};
|
||||||
img.src = URL.createObjectURL(file);
|
img.src = URL.createObjectURL(file);
|
||||||
// Reset input so same file can be uploaded again
|
// Reset input so same file can be uploaded again
|
||||||
|
@ -356,8 +305,6 @@ const DrawingCanvas = () => {
|
||||||
<button onClick={() => setCurrentTool('circle')} className={`tool-button ${currentTool === 'circle' ? 'active' : ''}`}>⭕</button>
|
<button onClick={() => setCurrentTool('circle')} className={`tool-button ${currentTool === 'circle' ? 'active' : ''}`}>⭕</button>
|
||||||
<button onClick={() => setCurrentTool('select')} className={`tool-button ${currentTool === 'select' ? 'active' : ''}`}>🖱️</button>
|
<button onClick={() => setCurrentTool('select')} className={`tool-button ${currentTool === 'select' ? 'active' : ''}`}>🖱️</button>
|
||||||
<button onClick={() => setCurrentTool('text')} className={`tool-button ${currentTool === 'text' ? 'active' : ''}`}>🔤</button>
|
<button onClick={() => setCurrentTool('text')} className={`tool-button ${currentTool === 'text' ? 'active' : ''}`}>🔤</button>
|
||||||
<button onClick={handleUndo} className="tool-button" disabled={history.length === 0}>↺</button>
|
|
||||||
<button onClick={handleRedo} className="tool-button" disabled={undoHistory.length === 0}>↻</button>
|
|
||||||
<button onClick={() => fileInputRef.current.click()} className="tool-button">🖼️ Insert Image</button>
|
<button onClick={() => fileInputRef.current.click()} className="tool-button">🖼️ Insert Image</button>
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
|
@ -367,9 +314,17 @@ const DrawingCanvas = () => {
|
||||||
onChange={handleInsertImage}
|
onChange={handleInsertImage}
|
||||||
/>
|
/>
|
||||||
<button onClick={handleSaveCanvas} className="tool-button">💾 Save</button>
|
<button onClick={handleSaveCanvas} className="tool-button">💾 Save</button>
|
||||||
<div className="color-picker">
|
<button
|
||||||
<ChromePicker color={color} onChangeComplete={(c) => setColor(c.hex)} />
|
onClick={() => setShowColorPicker(v => !v)}
|
||||||
</div>
|
className="tool-button"
|
||||||
|
>
|
||||||
|
🎨 Color
|
||||||
|
</button>
|
||||||
|
{showColorPicker && (
|
||||||
|
<div className="color-picker">
|
||||||
|
<ChromePicker color={color} onChangeComplete={(c) => setColor(c.hex)} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="thickness-controls">
|
<div className="thickness-controls">
|
||||||
<button onClick={() => setThickness(t => Math.max(1, t - 1))} disabled={thickness <= 1}>➖</button>
|
<button onClick={() => setThickness(t => Math.max(1, t - 1))} disabled={thickness <= 1}>➖</button>
|
||||||
<span>{thickness}</span>
|
<span>{thickness}</span>
|
||||||
|
@ -411,15 +366,6 @@ const DrawingCanvas = () => {
|
||||||
thickness,
|
thickness,
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
setHistory(prev => [...prev, deepCopyShapes([...shapes, {
|
|
||||||
type: 'text',
|
|
||||||
x: textPosition.x,
|
|
||||||
y: textPosition.y,
|
|
||||||
text: textInput,
|
|
||||||
color,
|
|
||||||
thickness,
|
|
||||||
}])]);
|
|
||||||
setUndoHistory([]);
|
|
||||||
}
|
}
|
||||||
setIsTextInputVisible(false);
|
setIsTextInputVisible(false);
|
||||||
setTextInput('');
|
setTextInput('');
|
||||||
|
|
Loading…
Reference in New Issue