Interactive Whiteboard
Draw, write, and collaborate in real-time.
Could not connect to the collaboration service. ${e.message}
`; return; } // --- CANVAS SETUP --- const resizeCanvas = () => { const parent = canvas.parentElement; canvas.width = parent.clientWidth; canvas.height = parent.clientHeight; redrawAll(); }; // --- DRAWING LOGIC --- const drawLine = (x0, y0, x1, y1, color, size, tool) => { ctx.beginPath(); ctx.moveTo(x0, y0); ctx.lineTo(x1, y1); ctx.strokeStyle = tool === 'eraser' ? '#FFFFFF' : color; ctx.lineWidth = size; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; ctx.stroke(); }; const startDrawing = (e) => { drawing = true; [lastX, lastY] = getMousePos(e); }; const stopDrawing = () => { drawing = false; }; const draw = async (e) => { if (!drawing) return; const [currentX, currentY] = getMousePos(e); const action = { x0: lastX, y0: lastY, x1: currentX, y1: currentY, color: colorPicker.value, size: brushSize.value, tool: currentTool, timestamp: Date.now() }; // Add to Firestore await addDoc(collection(db, drawingCollectionPath), action); [lastX, lastY] = [currentX, currentY]; }; const getMousePos = (e) => { const rect = canvas.getBoundingClientRect(); const scaleX = canvas.width / rect.width; const scaleY = canvas.height / rect.height; // Handle both mouse and touch events const clientX = e.touches ? e.touches[0].clientX : e.clientX; const clientY = e.touches ? e.touches[0].clientY : e.clientY; return [(clientX - rect.left) * scaleX, (clientY - rect.top) * scaleY]; }; // --- FIRESTORE REAL-TIME LISTENER --- const drawingQuery = query(collection(db, drawingCollectionPath)); let allDrawingActions = []; onSnapshot(drawingQuery, (snapshot) => { snapshot.docChanges().forEach((change) => { if (change.type === "added") { const action = change.doc.data(); allDrawingActions.push(action); drawLine(action.x0, action.y0, action.x1, action.y1, action.color, action.size, action.tool); } if (change.type === "removed") { // This handles the 'clear' action allDrawingActions = []; ctx.clearRect(0, 0, canvas.width, canvas.height); } }); }); const redrawAll = () => { ctx.clearRect(0, 0, canvas.width, canvas.height); allDrawingActions.sort((a, b) => a.timestamp - b.timestamp).forEach(action => { drawLine(action.x0, action.y0, action.x1, action.y1, action.color, action.size, action.tool); }); }; // --- TOOLBAR EVENT LISTENERS --- penToolBtn.addEventListener('click', () => { currentTool = 'pen'; penToolBtn.classList.add('active'); eraserToolBtn.classList.remove('active'); }); eraserToolBtn.addEventListener('click', () => { currentTool = 'eraser'; eraserToolBtn.classList.add('active'); penToolBtn.classList.remove('active'); }); clearCanvasBtn.addEventListener('click', async () => { if (confirm('Are you sure you want to clear the entire whiteboard for everyone?')) { // Delete all documents in the collection const snapshot = await getDocs(drawingQuery); snapshot.forEach(doc => deleteDoc(doc.ref)); } }); // --- CANVAS EVENT LISTENERS --- canvas.addEventListener('mousedown', startDrawing); canvas.addEventListener('mouseup', stopDrawing); canvas.addEventListener('mouseout', stopDrawing); canvas.addEventListener('mousemove', draw); // Touch events canvas.addEventListener('touchstart', startDrawing); canvas.addEventListener('touchend', stopDrawing); canvas.addEventListener('touchcancel', stopDrawing); canvas.addEventListener('touchmove', draw); // --- PDF DOWNLOAD --- downloadPdfBtn.addEventListener('click', () => { const { jsPDF } = window.jspdf; const doc = new jsPDF({ orientation: 'l', unit: 'px', format: [canvas.width, canvas.height] }); const imgData = canvas.toDataURL('image/png'); doc.addImage(imgData, 'PNG', 0, 0, canvas.width, canvas.height); // Add a title and footer for a more professional report look doc.setFontSize(10); doc.setTextColor(150); doc.text(`Whiteboard Session - ${new Date().toLocaleDateString()}`, 10, 10); doc.text('Exported from Interactive Whiteboard Tool', 10, canvas.height - 10); doc.save('whiteboard-session.pdf'); }); // --- INITIALIZATION --- window.addEventListener('resize', resizeCanvas); resizeCanvas(); });