import './App.css'; import {useLayoutEffect, useRef, useState} from "react"; let mouseDownMenuBar = false let mouseDownCanvas = false let mouseMenuBarOffsetX, mouseMenuBarOffsetY = 0 let context let hintContext let canvas let downLink let lastPositions = [] let strokeWidth = 2 let lastMouse = 0 let eraserColor = "rgb(255, 255, 255)" let lastStrokeWidthPencil = 2, lastStrokeWidthEraser = 20, lastStrokeWidthRect = 2, lastStrokeWidthCircle = 2, lastStrokeWidthLine = 2 let formAnchor = null; let textInput let fileInput let moverActive = false let moverWidth = 0, moverHeight = 0 export default function App() { useLayoutEffect(() => { window.addEventListener("beforeunload", e => { e.preventDefault() e.returnValue = "" }) canvas = document.getElementById("canvas") context = canvas.getContext("2d") fileInput = document.getElementById("fileInput") downLink = document.createElement("a") downLink.className = "no-display" textInput = document.getElementById("textInput") let hintCanvas = document.getElementById("hintCanvas") hintContext = hintCanvas.getContext("2d") window.scrollTo((5000 / window.innerWidth) * 500, (3000 / window.innerHeight) * 300) setInterval(() => { if (selectedTool === "pencil" || selectedTool === "eraser") { if (lastPositions.length >= 2) { let point1 = lastPositions.shift() let point2 = lastPositions[lastPositions.length - 1] let connection = new Position(point1.x - point2.x, point1.y - point2.y) for (let i = 0; i < 1; i += 0.001) { context.beginPath() context.arc(point1.x - (connection.x * i), point1.y - (connection.y * i), strokeWidth, 0, 2 * Math.PI, false) context.fill() } } else if (Date.now() - lastMouse > 100 && lastPositions.length >= 1) { let point = lastPositions.shift() context.beginPath() context.arc(point.x, point.y, strokeWidth, 0, 2 * Math.PI, false) context.fill() } } }, 1) }, []) const [menuBarLeft, setMenuBarLeft] = useState(50) const [menuBarTop, setMenuBarTop] = useState(50) const [strokeWidthSliderValue, setStrokeWidthSliderValue] = useState(strokeWidth) const [colorValue, setColorValue] = useState("#000000") const [menuBarShown, setMenuBarShown] = useState(true) const [selectedTool, setSelectedTool] = useState("pencil") const [textInputX, setTextInputX] = useState(0) const [textInputY, setTextInputY] = useState(0) const [showTextInput, setShowTextInput] = useState(false) class Position { constructor(x, y) { this.x = x this.y = y } } function handleMouseDownCanvas(e) { if (e.button === 0) { if(!moverActive) { mouseDownCanvas = true hintContext.clearRect(0, 0, 5000, 3000) setShowTextInput(false) context.fillStyle = selectedTool !== "eraser" ? colorValue : eraserColor if (selectedTool === "pencil" || selectedTool === "eraser") { context.beginPath() context.arc(e.pageX, e.pageY, strokeWidthSliderValue, 0, 2 * Math.PI, false) context.fill() lastMouse = Date.now() lastPositions.push(new Position(e.pageX, e.pageY)) } else { formAnchor = new Position(e.pageX, e.pageY) } if (selectedTool === "text") { mouseDownCanvas = false setTextInputX(e.pageX + 10) setTextInputY(e.pageY + 10) setShowTextInput(true) hintContext.beginPath() hintContext.fillStyle = "black" hintContext.arc(e.pageX, e.pageY, 5, 0, 2 * Math.PI, false) hintContext.fill() } } else { context.drawImage(canvas, formAnchor.x, formAnchor.y, moverWidth, moverHeight, e.pageX - moverWidth, e.pageY - moverHeight, moverWidth, moverHeight) context.clearRect(formAnchor.x, formAnchor.y, moverWidth, moverHeight) hintContext.clearRect(0, 0, 5000, 3000) moverActive = false mouseDownCanvas = false canvas.style.cursor = "crosshair" } } } function handleMouseUpCanvas(e) { if (e.button === 0 && mouseDownCanvas) { mouseDownCanvas = false if (selectedTool === "rect") { context.strokeStyle = colorValue context.beginPath() context.rect(formAnchor.x, formAnchor.y, e.pageX - formAnchor.x, e.pageY - formAnchor.y) context.lineWidth = strokeWidthSliderValue context.stroke() } else if (selectedTool === "circle") { context.strokeStyle = colorValue context.beginPath() let radius = (e.pageX - formAnchor.x) / 2 let yRad = (e.pageY - formAnchor.y) / 2 context.arc(formAnchor.x + radius, formAnchor.y + yRad, Math.abs(radius), 0, 2 * Math.PI, false) context.lineWidth = strokeWidthSliderValue context.stroke() } else if (selectedTool === "selector") { context.fillStyle = eraserColor context.fillRect(formAnchor.x, formAnchor.y, e.pageX - formAnchor.x, e.pageY - formAnchor.y) } else if (selectedTool === "line") { context.strokeStyle = colorValue context.beginPath() context.lineWidth = strokeWidthSliderValue context.moveTo(formAnchor.x, formAnchor.y) context.lineTo(e.pageX, e.pageY) context.stroke() context.moveTo(0, 0) } else if (selectedTool === "mover") { canvas.style.cursor = "move" moverActive = true moverWidth = e.pageX - formAnchor.x moverHeight = e.pageY - formAnchor.y } if (selectedTool !== "text" && selectedTool !== "mover") { hintContext.clearRect(0, 0, 5000, 3000) } else if(selectedTool === "text") { textInput.focus() } } } function handleMouseMove(e) { let mouseX = e.pageX let mouseY = e.pageY if (selectedTool === "pencil" || selectedTool === "eraser") { hintContext.clearRect(0, 0, 5000, 3000) hintContext.beginPath() hintContext.fillStyle = colorValue hintContext.arc(mouseX, mouseY, strokeWidthSliderValue, 0, 2 * Math.PI, false) hintContext.fill() } if (mouseDownMenuBar) { setMenuBarLeft(mouseX - mouseMenuBarOffsetX) setMenuBarTop(mouseY - mouseMenuBarOffsetY) } else if (mouseDownCanvas) { hintContext.clearRect(0, 0, 5000, 3000) if (selectedTool === "pencil" || selectedTool === "eraser") { context.fillStyle = selectedTool !== "eraser" ? colorValue : eraserColor lastPositions.push(new Position(mouseX, mouseY)) lastMouse = Date.now() } else if (selectedTool === "rect" || selectedTool === "circle" || selectedTool === "selector" || selectedTool === "line" || selectedTool === "mover") { hintContext.strokeStyle = colorValue hintContext.lineWidth = strokeWidthSliderValue if (selectedTool === "selector") { hintContext.setLineDash([10, 15]) hintContext.lineWidth = 3 hintContext.strokeStyle = "rgb(200, 0, 0)" } else if(selectedTool === "mover") { hintContext.setLineDash([10, 15]) hintContext.lineWidth = 3 hintContext.strokeStyle = "rgb(0, 0, 0)" } else { hintContext.setLineDash([]) } hintContext.beginPath() if (selectedTool === "rect" || selectedTool === "selector" || selectedTool === "mover") { hintContext.rect(formAnchor.x, formAnchor.y, mouseX - formAnchor.x, mouseY - formAnchor.y) hintContext.stroke() } if (selectedTool === "circle") { let radius = (mouseX - formAnchor.x) / 2 let yRad = (mouseY - formAnchor.y) / 2 hintContext.arc(formAnchor.x + radius, formAnchor.y + yRad, Math.abs(radius), 0, 2 * Math.PI, false) hintContext.stroke() } if (selectedTool === "line") { hintContext.moveTo(formAnchor.x, formAnchor.y) hintContext.lineTo(mouseX, mouseY) hintContext.stroke() } } } else if (moverActive) { hintContext.clearRect(0, 0, 5000, 3000) hintContext.setLineDash([10, 15]) hintContext.lineWidth = 3 hintContext.strokeStyle = "rgb(0, 0, 0)" hintContext.drawImage(canvas, formAnchor.x, formAnchor.y, moverWidth, moverHeight, e.pageX - moverWidth, e.pageY - moverHeight, moverWidth, moverHeight) hintContext.beginPath() hintContext.rect(e.pageX - moverWidth, e.pageY - moverHeight, moverWidth, moverHeight) hintContext.stroke() } } function onExportButtonClick(e) { let img = canvas.toDataURL("image/png") let image = new Image() image.src = "data:image/png;base64," + img downLink.href = img downLink.download = "export.png" downLink.click() } function onSaveButtonClick() { let img = canvas.toDataURL("image/png") let blob = new Blob([img], {type: "text/plain"}) var url = URL.createObjectURL(blob) downLink.href = url downLink.download = "save.redraw" downLink.click() } function onOpenButtonClick() { fileInput.click() } function handleToolChange(e) { setShowTextInput(false) hintContext.clearRect(0, 0, 5000, 3000) setSelectedTool(e.target.value) if (e.target.value === "pencil") { setStrokeWidthSliderValue(lastStrokeWidthPencil) strokeWidth = lastStrokeWidthPencil } else if (e.target.value === "eraser") { setStrokeWidthSliderValue(lastStrokeWidthEraser) strokeWidth = lastStrokeWidthEraser } else if (e.target.value === "rect") { setStrokeWidthSliderValue(lastStrokeWidthRect) strokeWidth = lastStrokeWidthRect } else if (e.target.value === "circle") { setStrokeWidthSliderValue(lastStrokeWidthCircle) strokeWidth = lastStrokeWidthCircle } else if (e.target.value === "line") { setStrokeWidthSliderValue(lastStrokeWidthLine) strokeWidth = lastStrokeWidthLine } } return (