Spaces:
Paused
Paused
| class Whiteboard { | |
| constructor() { | |
| this.canvas = document.getElementById('whiteboard'); | |
| this.ctx = this.canvas.getContext('2d'); | |
| this.isDrawing = false; | |
| this.history = new History(); | |
| this.tools = new DrawingTools(this.canvas, this.ctx); | |
| this.pages = new Pages(); | |
| this.setupCanvas(); | |
| this.setupEventListeners(); | |
| this.setupBrushPreview(); | |
| this.setupToolbarToggle(); | |
| } | |
| setupCanvas() { | |
| const resize = () => { | |
| this.canvas.width = window.innerWidth; | |
| this.canvas.height = window.innerHeight; | |
| this.setupDrawingStyle(); | |
| // Restore the current page | |
| const pageContent = this.pages.getPageContent(); | |
| if (pageContent) { | |
| this.ctx.putImageData(pageContent, 0, 0); // Use the drawing content from the current page | |
| } | |
| }; | |
| window.addEventListener('resize', resize); | |
| resize(); | |
| } | |
| setupToolbarToggle() { | |
| const toolbar = document.querySelector('.bottom-toolbar'); | |
| const toggleBtn = document.getElementById('toggleToolbar'); | |
| toggleBtn.addEventListener('click', () => { | |
| toolbar.classList.toggle('hidden'); | |
| }); | |
| } | |
| setupBrushPreview() { | |
| this.brushPreview = document.getElementById('brushPreview'); | |
| this.canvas.addEventListener('mousemove', (e) => { | |
| const { x, y } = getMousePos(this.canvas, e); | |
| this.updateBrushPreview(x, y); | |
| }); | |
| this.canvas.addEventListener('mouseenter', () => { | |
| this.brushPreview.style.display = 'block'; | |
| }); | |
| this.canvas.addEventListener('mouseleave', () => { | |
| this.brushPreview.style.display = 'none'; | |
| }); | |
| } | |
| updateBrushPreview(x, y) { | |
| const size = this.tools.brushSize; | |
| this.brushPreview.style.width = size + 'px'; | |
| this.brushPreview.style.height = size + 'px'; | |
| this.brushPreview.style.left = (x - size/2) + 'px'; | |
| this.brushPreview.style.top = (y - size/2) + 'px'; | |
| this.brushPreview.style.borderColor = this.tools.isEraser ? '#000' : this.tools.color; | |
| } | |
| setupDrawingStyle() { | |
| this.ctx.lineCap = 'round'; | |
| this.ctx.lineJoin = 'round'; | |
| this.ctx.font = '24px Arial'; | |
| this.tools.applyToolSettings(); | |
| } | |
| setupEventListeners() { | |
| // Mouse events | |
| this.canvas.addEventListener('mousedown', this.startDrawing.bind(this)); | |
| this.canvas.addEventListener('mousemove', this.draw.bind(this)); | |
| this.canvas.addEventListener('mouseup', this.stopDrawing.bind(this)); | |
| this.canvas.addEventListener('mouseout', this.stopDrawing.bind(this)); | |
| // Touch events | |
| this.canvas.addEventListener('touchstart', this.handleTouch.bind(this)); | |
| this.canvas.addEventListener('touchmove', this.handleTouch.bind(this)); | |
| this.canvas.addEventListener('touchend', this.handleTouchEnd.bind(this)); | |
| // Button events | |
| document.getElementById('aiBtn').addEventListener('click', this.processDrawing.bind(this)); | |
| document.getElementById('submitBtn').addEventListener('click', this.generatePDF.bind(this)); | |
| document.getElementById('clearBtn').addEventListener('click', this.clearCanvas.bind(this)); | |
| document.getElementById('undoBtn').addEventListener('click', this.undo.bind(this)); | |
| document.getElementById('redoBtn').addEventListener('click', this.redo.bind(this)); | |
| // Keyboard shortcuts | |
| document.addEventListener('keydown', (e) => { | |
| if (e.ctrlKey || e.metaKey) { | |
| if (e.key === 'z') { | |
| e.preventDefault(); | |
| this.undo(); | |
| } else if (e.key === 'y') { | |
| e.preventDefault(); | |
| this.redo(); | |
| } | |
| } | |
| }); | |
| } | |
| handleTouch(e) { | |
| e.preventDefault(); | |
| const touch = e.touches[0]; | |
| const eventType = e.type === 'touchstart' ? 'mousedown' : 'mousemove'; | |
| const mouseEvent = new MouseEvent(eventType, { | |
| clientX: touch.clientX, | |
| clientY: touch.clientY | |
| }); | |
| this.canvas.dispatchEvent(mouseEvent); | |
| } | |
| handleTouchEnd(e) { | |
| e.preventDefault(); | |
| const mouseEvent = new MouseEvent('mouseup', {}); | |
| this.canvas.dispatchEvent(mouseEvent); | |
| } | |
| startDrawing(e) { | |
| this.isDrawing = true; | |
| const { x, y } = getMousePos(this.canvas, e); | |
| this.tools.applyToolSettings(); | |
| this.ctx.beginPath(); | |
| this.ctx.moveTo(x, y); | |
| } | |
| draw(e) { | |
| if (!this.isDrawing) return; | |
| const { x, y } = getMousePos(this.canvas, e); | |
| this.ctx.lineTo(x, y); | |
| this.ctx.stroke(); | |
| } | |
| stopDrawing() { | |
| if (this.isDrawing) { | |
| this.isDrawing = false; | |
| this.saveState(); | |
| } | |
| } | |
| saveState() { | |
| const imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height); | |
| this.history.push(new DrawingState(imageData)); | |
| this.pages.setPageContent(this.history.states); | |
| } | |
| undo() { | |
| const state = this.history.undo(); | |
| if (state) { | |
| this.ctx.putImageData(state.imageData, 0, 0); | |
| this.pages.setPageContent(this.history.states); | |
| } | |
| } | |
| redo() { | |
| const state = this.history.redo(); | |
| if (state) { | |
| this.ctx.putImageData(state.imageData, 0, 0); | |
| this.pages.setPageContent(this.history.states); | |
| } | |
| } | |
| clearCanvas() { | |
| this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); | |
| this.saveState(); | |
| } | |
| processDrawing() { | |
| const tempCanvas = document.createElement('canvas'); | |
| const tempContext = tempCanvas.getContext('2d'); | |
| tempCanvas.width = this.canvas.width; | |
| tempCanvas.height = this.canvas.height; | |
| tempContext.fillStyle = 'white'; | |
| tempContext.fillRect(0, 0, tempCanvas.width, tempCanvas.height); | |
| tempContext.drawImage(this.canvas, 0, 0); | |
| const imageData = tempCanvas.toDataURL('image/png'); | |
| console.log('Processing drawing with AI...'); | |
| console.log(imageData.substring(0, 100) + '...'); | |
| fetch('/whitebai', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ | |
| image: imageData | |
| }) | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| if (data.status && Array.isArray(data.data)) { | |
| data.data.forEach(item => { | |
| const result = item.result; | |
| const x = item.location.x; | |
| const y = item.location.y; | |
| const size = item.size; | |
| const sizePx = `${size}px`; | |
| writeOnCanvasDynamic(whiteboard.pages, result, x, y, null, "Arial", sizePx, "black", false); | |
| }); | |
| } else { | |
| console.error("Invalid data object or empty data array"); | |
| } | |
| }) | |
| .catch(error => { | |
| console.error('Error:', error); | |
| }); | |
| } | |
| async generatePDF() { | |
| const pages = this.pages.getAllPages(); | |
| for (let i = 0; i < pages.length; i++) { | |
| const pageContent = pages[i]; | |
| const tempCanvas = document.createElement('canvas'); | |
| tempCanvas.width = this.canvas.width; | |
| tempCanvas.height = this.canvas.height; | |
| const tempCtx = tempCanvas.getContext('2d'); | |
| tempCtx.fillStyle = 'white'; | |
| tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height); | |
| if (pageContent && pageContent.drawing) { | |
| const imageData = pageContent.drawing; | |
| if (imageData) { | |
| try { | |
| tempCtx.putImageData(imageData, 0, 0); | |
| console.log("Image applied to canvas for page " + i); | |
| } catch (error) { | |
| console.log("Error putting image data on page " + i, error); | |
| } | |
| } else { | |
| console.log("No image data for page " + i); | |
| } | |
| } | |
| const imgData = tempCanvas.toDataURL('image/png'); | |
| console.log(`Base64 data for page ${i}:`, imgData); | |
| } | |
| } | |
| writeText(text, x = 20, y = 40) { | |
| this.setupDrawingStyle(); | |
| this.ctx.fillStyle = this.tools.color; | |
| this.ctx.fillText(text.toString(), x, y); | |
| this.saveState(); | |
| } | |
| } | |
| const whiteboard = new Whiteboard(); | |
| const canvas = document.getElementById('whiteboard'); | |
| document.addEventListener('keydown', function(event) { | |
| if (event.ctrlKey && event.key === 's') { | |
| event.preventDefault(); | |
| const tempCanvas = document.createElement('canvas'); | |
| const tempContext = tempCanvas.getContext('2d'); | |
| tempCanvas.width = canvas.width; | |
| tempCanvas.height = canvas.height; | |
| tempContext.fillStyle = 'white'; | |
| tempContext.fillRect(0, 0, tempCanvas.width, tempCanvas.height); | |
| tempContext.drawImage(canvas, 0, 0); | |
| const imageData = tempCanvas.toDataURL("image/png"); | |
| const link = document.createElement('a'); | |
| link.href = imageData; | |
| link.download = 'canvas-image.png'; | |
| link.click(); | |
| console.log("Canvas saved as image with white background!"); | |
| } | |
| }); | |
| function writeOnCanvasDynamic(pagesInstance, text, x, y, fontUrl, fontFamily, fontSize = "16px", color = "black", clear = false) { | |
| const ctx = pagesInstance.ctx; | |
| if (!ctx) return; | |
| const applyFontAndWrite = () => { | |
| ctx.globalCompositeOperation = 'source-over'; | |
| if (clear) { | |
| const textWidth = ctx.measureText(text).width; | |
| const lineHeight = parseInt(fontSize, 10) || 16; | |
| ctx.clearRect(x, y - lineHeight, textWidth, lineHeight); | |
| return; | |
| } | |
| ctx.font = `${fontSize} ${fontFamily}`; | |
| ctx.fillStyle = color; | |
| ctx.fillText(text, x, y); | |
| }; | |
| if (fontUrl) { | |
| const fontFace = new FontFace(fontFamily, `url(${fontUrl})`); | |
| fontFace.load().then((loadedFont) => { | |
| document.fonts.add(loadedFont); | |
| applyFontAndWrite(); | |
| }).catch((err) => { | |
| console.error("Font loading failed:", err); | |
| }); | |
| } else { | |
| applyFontAndWrite(); | |
| } | |
| } | |