const socket = io({ timeout: 500 }); const canvas = document.getElementById('drawingCanvas'); const canvasContainer = document.getElementById('canvasContainer'); const buttons = document.getElementById('buttons'); const tx = document.getElementById('x'); const ty = document.getElementById('y'); const context = canvas.getContext('2d'); const chat = document.querySelector('.chat-container'); const nameinput = document.getElementById('username'); const drawingEmitDelay = 500; let lastDrawingEmitTime = 0; let drawings = []; let announcementIDs = []; let currentDrawing = []; let isDrawing = false; let uiVisible = true; let currentColor = '#000000'; let announceCount = 1; let connectionTimer = null; let connectionSeconds = 0; let versionSet = false; let ping = null; let uuid = ""; context.imageSmoothingEnabled = true; canvasContainer.style.transform = `scale(0.08)`; function getCookie(name) { const cookies = document.cookie.split(';').map(cookie => cookie.trim()); for (const cookie of cookies) { const [cookieName, cookieValue] = cookie.split('=').map(part => decodeURIComponent(part)); if (cookieName === name) { return cookieValue; } } return null; } function setCookie(name, value, expire) { const expirationDate = new Date(); expirationDate.setDate(expirationDate.getDate() + expire); const cookieValue = encodeURIComponent(value) + (expire ? `; expires=${expirationDate.toUTCString()}` : ''); document.cookie = `${name}=${cookieValue}; path=/`; } function pinger() { const startTime = Date.now(); const pingtext = document.getElementById('ping').querySelector('p'); socket.on('pong', () => { const latency = Date.now() - startTime; pingtext.textContent = 'Ping: ' + latency + 'ms'; document.getElementById('ping').style.opacity = 0.25; }); socket.emit('ping'); } ping = setInterval(pinger, 1000); function startConnectionTimer() { const messages = document.querySelector('.messages'); if(messages.querySelectorAll('[id="connection"]').length > 0) { return; } clearInterval(ping); const parentMessage = document.createElement('div'); parentMessage.setAttribute('class', 'message fadeup red'); parentMessage.setAttribute('id', 'connection'); parentMessage.style.boxShadow = "0 4px 8px rgba(0, 0, 0, 0.1)"; parentMessage.style.width = "300px"; const message = document.createElement('p'); message.setAttribute('id', 'timeout'); message.setAttribute('style', 'white-space: nowrap;margin: 25px 50px 25px 50px'); message.textContent = "Server Connection Timeout: " + connectionSeconds.toFixed(1); parentMessage.appendChild(message); messages.appendChild(parentMessage); canvas.style.opacity = 0; buttons.style.opacity = 0; canvas.classList.add('fadedown'); drawings = []; currentDrawing = []; draw(); connectionTimer = setInterval(() => { connectionSeconds += 0.1; document.getElementById('timeout').textContent = "Server Connection Timeout: " + connectionSeconds.toFixed(1); }, 100); } function stopConnectionTimer() { clearInterval(connectionTimer); ping = setInterval(pinger, 1000); connectionSeconds = 0; const messages = document.querySelector('.messages'); messages.querySelectorAll('[id="connection"]').forEach((child) => { child.classList.remove('fadeup'); child.classList.add('fadedown'); setTimeout(() => { child.remove(); }, 500); }); const parentMessage = document.createElement('div'); parentMessage.setAttribute('class', 'message fadeup green'); parentMessage.setAttribute('id', 'success'); parentMessage.style.boxShadow = "0 4px 8px rgba(0, 0, 0, 0.1)"; parentMessage.setAttribute('style', "min-width: auto;"); const messagetext = document.createElement('p'); messagetext.setAttribute('id', 'timeout'); messagetext.setAttribute('style', 'white-space: nowrap;margin: 25px 50px 25px 50px'); messagetext.textContent = "Server connection has been restored"; parentMessage.appendChild(messagetext); messages.appendChild(parentMessage); canvas.style.opacity = 1; buttons.style.opacity = 1; canvas.classList.remove('fadedown'); canvas.classList.add('fadeup'); const message = document.getElementById('success'); setTimeout(() => { message.classList.remove('fadeup'); message.classList.add('fadedown'); setTimeout(() => { message.remove(); }, 500); }, 2500); } socket.on('connect', () => { if(connectionSeconds != 0) { stopConnectionTimer(); } }); socket.on('connect_error', () => { startConnectionTimer(); }); socket.on('connect_timeout', () => { startConnectionTimer(); }); function toggleOP() { uiVisible = !uiVisible; buttons.style.display = uiVisible ? '' : 'none'; document.getElementById('resetView').style.display = uiVisible ? '' : 'none'; document.getElementById('xy').style.display = uiVisible ? '' : 'none'; document.getElementById('connectedClients').style.display = uiVisible ? '' : 'none'; document.getElementById('version').style.display = uiVisible ? '' : 'none'; document.getElementById('ping').style.display = uiVisible ? '' : 'none'; document.getElementById('data').style.display = uiVisible ? '' : 'none'; chat.style.display = uiVisible ? 'flex' : 'none'; } function resetView() { canvasContainer.style.transform = `scale(0.08)`; canvas.style.transform = `translate(0px, 0px)`; zoomLevel = 0.08; offsetX = 0; offsetY = 0; } function changeColor(event) { currentColor = event.target.value; } document.addEventListener('mouseup', () => { isDrawing = false; }); canvas.addEventListener('mouseleave', () => { isDrawing = false; }); canvas.addEventListener('mousedown', (event) => { isDrawing = true; addPoint(event); }); canvas.addEventListener('mouseup', () => { isDrawing = false; if (currentDrawing.length > 0) { socket.emit('draw', { type: 'draw', drawing: currentDrawing, valid: uuid }); drawings.push(currentDrawing); draw(); currentDrawing = []; lastDrawingEmitTime = Date.now(); } }); canvas.addEventListener('mousemove', (event) => { const rect = canvas.getBoundingClientRect(); const scaleX = canvas.width / rect.width; const scaleY = canvas.height / rect.height; const x = Math.round((event.clientX - rect.left) * scaleX); const y = Math.round((event.clientY - rect.top) * scaleY); tx.innerHTML = x; ty.innerHTML = y; if (isDrawing) { addPoint(event); const currentTime = Date.now(); if (currentTime - lastDrawingEmitTime >= drawingEmitDelay) { //socket.emit('draw', { type: 'draw', drawing: currentDrawing, valid: uuid }); lastDrawingEmitTime = currentTime; } } }); document.getElementById('chat-input').addEventListener('keydown', function(event) { if(event.keyCode == 13) { socket.emit('message', { uuid: uuid, content: document.getElementById('chat-input').value }); document.getElementById('chat-input').value = ""; } }); nameinput.addEventListener('keydown', function(event) { if(event.keyCode == 13) { socket.emit('changeName', { uuid: uuid, name: nameinput.value }); } }); document.addEventListener('keydown', function(event) { if (event.key === 'ArrowUp') { pan('up'); } else if (event.key === 'ArrowDown') { pan('down'); } else if (event.key === 'ArrowLeft') { pan('left'); } else if (event.key === 'ArrowRight') { pan('right'); } }); function addPoint(event) { if (!isDrawing) return; const rect = canvas.getBoundingClientRect(); const scaleX = canvas.width / rect.width; const scaleY = canvas.height / rect.height; const x = (event.clientX - rect.left) * scaleX; const y = (event.clientY - rect.top) * scaleY; currentDrawing.push({ x: Math.round(x), y: Math.round(y), color: currentColor }); draw(); } function draw() { context.clearRect(0, 0, canvas.width, canvas.height); for (const drawing of drawings) { if (drawing.length < 2) continue; for (let i = 1; i < drawing.length; i++) { const startPoint = drawing[i - 1]; const endPoint = drawing[i]; context.beginPath(); context.strokeStyle = startPoint.color; context.moveTo(Math.round(startPoint.x), Math.round(startPoint.y)); context.lineTo(Math.round(endPoint.x), Math.round(endPoint.y)); context.stroke(); } } context.stroke(); if (currentDrawing.length > 0) { context.beginPath(); context.strokeStyle = currentColor; context.moveTo(Math.round(currentDrawing[0].x), Math.round(currentDrawing[0].y)); for (let i = 1; i < currentDrawing.length; i++) { const point = currentDrawing[i]; context.lineTo(Math.round(point.x), Math.round(point.y)); } context.stroke(); } } socket.on('changeName', (data) => { document.getElementById('nameStatus').textContent = data.status.message; document.getElementById('nameStatus').style.display = ""; setTimeout(() => { document.getElementById('nameStatus').style.display = "none"; }, 5000); }); socket.on('chat', (data) => { if(data.type == "message") { const message = document.createElement('li'); message.textContent = "<" + data.name + "> " + data.content; chat.querySelector('ul').appendChild(message); chat.querySelector('.chat-messages').querySelector('ul').scrollTop = chat.querySelector('.chat-messages').querySelector('ul').scrollHeight; } else if(data.type == "existingMessages") { const messages = JSON.parse(data.messages); chat.querySelector('ul').innerHTML = ""; messages.forEach(entry => { const message = document.createElement('li'); message.textContent = "<" + entry.name + "> " + entry.content; chat.querySelector('ul').appendChild(message); chat.querySelector('.chat-messages').querySelector('ul').scrollTop = chat.querySelector('.chat-messages').querySelector('ul').scrollHeight; }); } }); socket.on('dataSize', (data) => { document.getElementById('data').querySelector('p').textContent = "Size: " + data.size + "MB"; document.getElementById('data').style.opacity = 0.25; }); socket.on('play', (data) => { if(data.audio != "stop") { document.getElementById('audio-player').src = data.audio; document.getElementById('audio-player').play(); } else { document.getElementById('audio-player').pause(); } }); socket.on('announcement', (data) => { if (data.message && data.color) { const announcementMessage = data.message; const messages = document.querySelector('.messages'); const announcement = document.createElement('div'); announcement.setAttribute('class', 'message fadeup ' + data.color); announcement.setAttribute('id', announceCount); announcement.style.boxShadow = "0 4px 8px rgba(0, 0, 0, 0.1)"; const message = document.createElement('p'); message.setAttribute('style', 'white-space: normal;margin: 25px 50px 25px 50px'); message.textContent = announcementMessage; announcement.appendChild(message); messages.appendChild(announcement); announceCount++; announcementIDs.push(announceCount - 1); setTimeout(() => { const el = document.getElementById(announceCount - 1); el.classList.remove('fadeup'); el.classList.add('fadedown'); setTimeout(() => { el.style.display = "none"; el.remove(); }, 500); }, 5000); } }); function hideMessages() { const parentDiv = document.querySelector('.messages'); if (parentDiv) { const childDivs = parentDiv.querySelectorAll('div'); if(childDivs.length > 1) { childDivs.forEach((child) => { if (child.getAttribute('id') != "connection") { child.classList.add('fadedown'); } }); setTimeout(() => { childDivs.forEach((child) => { if (child.getAttribute('id') != "connection") { child.style.display = 'none'; child.remove(); } }); }, 500); } } } setInterval(hideMessages, 5000); socket.on('connectedClients', (count) => { document.getElementById('clientNum').textContent = `Connected Clients: ${count}`; document.getElementById('connectedClients').style.opacity = 0.25; }); socket.on('reload', (count) => { location.reload(true); }); socket.on('version', (version) => { document.title = "Collavas v" + version; document.getElementById('version').querySelector('p').textContent = "Version: " + version; document.getElementById('version').style.opacity = 0.25; versionSet = true; document.getElementById('w-version').textContent = "Collavas v" + version; }); socket.on('draw', (data) => { if (data.type === 'draw' && data.drawing) { drawings.push(data.drawing); draw(); } else if (data.type === 'existingDrawings' && data.drawings) { const parsedDrawings = JSON.parse(data.drawings); const messages = JSON.parse(data.chat); drawings = []; chat.querySelector('ul').innerHTML = ""; messages.forEach(entry => { const message = document.createElement('li'); message.textContent = "<" + entry.name + "> " + entry.content; chat.querySelector('ul').appendChild(message); chat.querySelector('.chat-messages').querySelector('ul').scrollTop = chat.querySelector('.chat-messages').querySelector('ul').scrollHeight; }); uuid = data.id; nameinput.value = data.name; setCookie("uuid", uuid, 1); drawings.push(...parsedDrawings); draw(); document.getElementById('transition').style.opacity = 1; document.getElementById('transition').style.visibility = "hidden"; canvas.style.opacity = 1; canvas.style.visibility = "visible"; buttons.style.opacity = 1; chat.style.display = "flex"; if(!getCookie("visited")) { var welcome = document.getElementById('welcome'); welcome.style.display = "flex"; welcome.style.opacity = 1; welcome.querySelector('div').classList.add('fadeup'); setCookie("visited", "true", 1); document.getElementById('w-close').addEventListener('click', (event) => { welcome.style.opacity = 0; welcome.querySelector('div').classList.remove('fadeup'); welcome.querySelector('div').classList.add('fadedown'); setTimeout(() => { welcome.style.display = "none"; }, 500); }); } } }); let zoomLevel = 0.08; const zoomFactor = 0.1; let offsetX = 0; let offsetY = 0; function pan(direction) { const panSpeed = 40; switch (direction) { case 'left': offsetX += panSpeed; break; case 'right': offsetX -= panSpeed; break; case 'up': offsetY += panSpeed; break; case 'down': offsetY -= panSpeed; break; default: break; } updateCanvasContainerPosition(); } function updateCanvasContainerPosition() { if(zoomLevel == 0.08) { canvas.style.transform = `0px, 0px)`; offsetX = 0; offsetY = 0; } else { canvas.style.transform = `translate(${offsetX}px, ${offsetY}px)`; } } canvasContainer.addEventListener('wheel', (event) => { event.preventDefault(); const scrollAmount = event.deltaY > 0 ? -1 : 1; const scaleFactor = 1 + scrollAmount * zoomFactor; zoomLevel *= scaleFactor; if (zoomLevel < 0.08) { zoomLevel = 0.08; canvas.style.transform = `translate(0px, 0px)`; updateCanvasContainerPosition(); } if (zoomLevel > 20.0) zoomLevel = 20.0; canvasContainer.style.transform = `scale(${zoomLevel})`; });