1<!DOCTYPE html> 2<html lang="ru"> 3<head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>ТеÑÑ 01</title> 7 <link rel="stylesheet" href="/static/style.css"> 8 9<style> 10 #whiteboard-container { position: relative; width: 100%; max-width: 1200px; margin: 0 auto; background: #fff; border: 1px solid #ddd; } 11 #drawing-canvas { display: block; width: 100%; height: 600px; cursor: crosshair; background: white; } 12 .toolbar { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-bottom: 10px; padding: 10px; background: #f0f0f0; border-radius: 4px; } 13 .toolbar .color-picker { width: 40px; height: 40px; border: none; padding: 0; cursor: pointer; } 14 .users-online { font-size: 14px; color: #555; margin-left: auto; } 15</style> 16 17</head> 18<body> 19 <header> 20 <div class="header-title"> 21 <h1>СовмеÑÑÐ½Ð°Ñ Ð´Ð¾Ñка</h1> 22 </div> 23 <nav class="header-nav"> 24 <ul class="nav-menu"> 25 <li><a href="/">ÐлавнаÑ</a></li> 26 27 <li><a href="/login">ÐÑ Ð¾Ð´</a></li> 28 29 <li><a href="/register">РегиÑÑÑаÑиÑ</a></li> 30 31 32 </ul> 33 </nav> 34 </header> 35 <main> 36 37 38 39 40<div class="whiteboard-page"> 41 <h2>ТеÑÑ 01 <small>(ID: 1, оÑкÑÑÑаÑ)</small></h2> 42 <div class="toolbar"> 43 <button id="pen-tool" class="btn active">ðï¸ ÐиÑÑÑ</button> 44 <button id="eraser-tool" class="btn">ð§¹ ÐаÑÑик</button> 45 <input type="color" id="color-picker" value="#000000" class="color-picker" title="Ð¦Ð²ÐµÑ ÐºÐ¸ÑÑи"> 46 <label>РазмеÑ: <input type="range" id="size-slider" min="1" max="50" value="3"></label> 47 <button id="clear-board" class="btn secondary">ðï¸ ÐÑиÑÑиÑÑ</button> 48 <button id="export-png" class="btn secondary">ð¸ СкаÑаÑÑ PNG</button> 49 <button id="copy-link-btn" class="btn secondary">ð ÐопиÑоваÑÑ ÑÑÑлкÑ</button> 50 <span class="users-online">ÐолÑзоваÑелей онлайн: <span id="online-count">1</span></span> 51 </div> 52 <div id="whiteboard-container"> 53 <canvas id="drawing-canvas" width="1200" height="600"></canvas> 54 </div> 55</div> 56 57 </main> 58
59<script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
vendor: 1 bytes, line 59
59
60<script> 61 const canvas = document.getElementById('drawing-canvas'); 62 const ctx = canvas.getContext('2d'); 63 const boardId = 1; 64 const boardLink = "http://doska.guwi.ru:5010/board/PeaktcazdYA"; 65 66 let currentTool = 'pen'; 67 let currentColor = '#000000'; 68 let currentSize = 3; 69 let drawing = false; 70 let currentPoints = []; 71 72 const socket = io(); 73 socket.emit('join_board', { board_id: boardId }); 74 75 socket.on('init_canvas', (data) => { redrawAll(data.actions); }); 76 socket.on('draw', (action) => { drawAction(action); }); 77 socket.on('clear', () => { clearCanvas(); }); 78 socket.on('online_count', (data) => { document.getElementById('online-count').textContent = data.count; }); 79 80 function redrawAll(actions) { clearCanvas(); actions.forEach(a => drawAction(a)); } 81 function drawAction(action) { 82 if (action.type === 'draw') { 83 ctx.save(); 84 ctx.globalCompositeOperation = (action.tool === 'eraser') ? 'destination-out' : 'source-over'; 85 ctx.strokeStyle = (action.tool === 'eraser') ? 'rgba(0,0,0,1)' : action.color; 86 ctx.lineWidth = action.size; 87 ctx.lineCap = 'round'; 88 ctx.lineJoin = 'round'; 89 const pts = action.points; 90 if (pts.length < 2) { 91 ctx.beginPath(); 92 ctx.arc(pts[0].x, pts[0].y, ctx.lineWidth/2, 0, 2*Math.PI); 93 ctx.fillStyle = ctx.strokeStyle; 94 ctx.fill(); 95 } else { 96 ctx.beginPath(); 97 ctx.moveTo(pts[0].x, pts[0].y); 98 for (let i=1; i<pts.length; i++) ctx.lineTo(pts[i].x, pts[i].y); 99 ctx.stroke(); 100 } 101 ctx.restore(); 102 } 103 } 104 function clearCanvas() { ctx.clearRect(0, 0, canvas.width, canvas.height); } 105 106 function getPos(e) { 107 const rect = canvas.getBoundingClientRect(); 108 const scaleX = canvas.width / rect.width; 109 const scaleY = canvas.height / rect.height; 110 let cx, cy; 111 if (e.touches) { cx = e.touches[0].clientX; cy = e.touches[0].clientY; } 112 else { cx = e.clientX; cy = e.clientY; } 113 return { x: (cx - rect.left) * scaleX, y: (cy - rect.top) * scaleY }; 114 } 115 116 function startDrawing(e) { 117 drawing = true; 118 currentPoints = []; 119 const pos = getPos(e); 120 currentPoints.push(pos); 121 drawAction({ type:'draw', tool:currentTool, color:currentTool==='eraser'?'#000':currentColor, size:currentSize, points:[pos] }); 122 e.preventDefault(); 123 } 124 function draw(e) { 125 if (!drawing) return; 126 const pos = getPos(e); 127 currentPoints.push(pos); 128 drawAction({ type:'draw', tool:currentTool, color:currentTool==='eraser'?'#000':currentColor, size:currentSize, points:currentPoints.slice(-2) }); 129 e.preventDefault(); 130 } 131 function stopDrawing(e) { 132 if (!drawing) return; 133 drawing = false; 134 if (currentPoints.length > 0) { 135 socket.emit('draw', { board_id: boardId, action: { 136 type:'draw', tool:currentTool, color:currentTool==='eraser'?'#000':currentColor, 137 size:currentSize, points: currentPoints 138 }}); 139 } 140 currentPoints = []; 141 e.preventDefault(); 142 } 143 144 canvas.addEventListener('mousedown', startDrawing); 145 canvas.addEventListener('mousemove', draw); 146 canvas.addEventListener('mouseup', stopDrawing); 147 canvas.addEventListener('mouseleave', stopDrawing); 148 canvas.addEventListener('touchstart', startDrawing, {passive:false}); 149 canvas.addEventListener('touchmove', draw, {passive:false}); 150 canvas.addEventListener('touchend', stopDrawing); 151 152 document.getElementById('pen-tool').addEventListener('click', ()=>{ 153 currentTool='pen'; 154 document.getElementById('pen-tool').classList.add('active'); 155 document.getElementById('eraser-tool').classList.remove('active'); 156 }); 157 document.getElementById('eraser-tool').addEventListener('click', ()=>{ 158 currentTool='eraser'; 159 document.getElementById('eraser-tool').classList.add('active');
160 document.getElementById('pen-tool').classList.remove('active'); 161 }); 162 document.getElementById('color-picker').addEventListener('input', e=>{ currentColor=e.target.value; }); 163 document.getElementById('size-slider').addEventListener('input', e=>{ currentSize=parseInt(e.target.value); }); 164 document.getElementById('clear-board').addEventListener('click', ()=>{ 165 if (confirm('ÐÑиÑÑиÑÑ Ð´Ð¾ÑкÑ?')) { 166 socket.emit('clear', { board_id: boardId }); 167 clearCanvas(); 168 } 169 }); 170 document.getElementById('export-png').addEventListener('click', ()=>{ 171 const link = document.createElement('a'); 172 link.download = 'whiteboard.png'; 173 link.href = canvas.toDataURL('image/png'); 174 link.click(); 175 }); 176 177 // ÐопиÑование ÑÑÑлки 178 function copyToClipboard(text) { 179 if (navigator.clipboard && window.isSecureContext) { 180 navigator.clipboard.writeText(text).then(()=>alert('СÑÑлка ÑкопиÑована!')).catch(()=>fallbackCopy(text)); 181 } else { fallbackCopy(text); } 182 } 183 function fallbackCopy(text) { 184 const ta = document.createElement('textarea'); 185 ta.value = text; 186 document.body.appendChild(ta); 187 ta.select(); 188 try { document.execCommand('copy'); alert('СÑÑлка ÑкопиÑована!'); } catch(e){ alert('Ðе ÑдалоÑÑ ÑкопиÑоваÑÑ'); } 189 document.body.removeChild(ta); 190 } 191 document.getElementById('copy-link-btn').addEventListener('click', ()=>{ copyToClipboard(boardLink); }); 192 193 window.addEventListener('beforeunload', ()=>{ socket.emit('leave_board', { board_id: boardId }); }); 194</script>
194 195 196</body> 197</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.