UNPKG

realtimecursor

Version:

Real-time collaboration system with cursor tracking and approval workflow

418 lines (361 loc) 10.4 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>RealtimeCursor Demo</title> <style> body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; line-height: 1.6; color: #333; max-width: 1000px; margin: 0 auto; padding: 20px; } h1, h2, h3 { color: #2563eb; } .collaborative-editor { display: flex; flex-direction: column; gap: 20px; } .editor-header { display: flex; justify-content: space-between; align-items: center; } .connection-status { padding: 6px 12px; border-radius: 20px; font-weight: 500; } .status-connected { background-color: #dcfce7; color: #166534; } .status-disconnected { background-color: #fee2e2; color: #b91c1c; } .user-settings { display: flex; gap: 15px; padding: 15px; background-color: #f8fafc; border-radius: 8px; border: 1px solid #e2e8f0; } .form-group { display: flex; flex-direction: column; gap: 5px; flex: 1; } .form-group label { font-weight: 500; font-size: 14px; } .form-group input { padding: 8px 12px; border: 1px solid #cbd5e1; border-radius: 6px; font-size: 16px; } .form-actions { display: flex; align-items: flex-end; gap: 10px; } button { padding: 8px 16px; background-color: #2563eb; color: white; border: none; border-radius: 6px; font-weight: 500; cursor: pointer; transition: background-color 0.2s; } button:hover { background-color: #1d4ed8; } button:disabled { background-color: #94a3b8; cursor: not-allowed; } .collaborators-section { padding: 15px; background-color: #f0f9ff; border-radius: 8px; border: 1px solid #bae6fd; } .collaborators-section h3 { margin-top: 0; color: #0369a1; } #collaborators-list { display: flex; flex-wrap: wrap; gap: 10px; } .collaborator-item { display: flex; align-items: center; gap: 8px; padding: 6px 12px; background-color: white; border-radius: 20px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); } .collaborator-avatar { width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; font-size: 12px; } .typing-indicator { display: flex; align-items: center; margin-left: 5px; } .dot { width: 4px; height: 4px; border-radius: 50%; background-color: #64748b; margin: 0 1px; animation: bounce 1s infinite; } .dot:nth-child(2) { animation-delay: 0.2s; } .dot:nth-child(3) { animation-delay: 0.4s; } @keyframes bounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-2px); } } .editor-container { position: relative; height: 400px; } .editor { width: 100%; height: 100%; padding: 16px; font-family: monospace; font-size: 16px; line-height: 1.5; border: 1px solid #cbd5e1; border-radius: 8px; resize: none; box-sizing: border-box; } .editor-footer { display: flex; justify-content: space-between; padding: 10px; background-color: #f8fafc; border-radius: 8px; border: 1px solid #e2e8f0; } .stats { display: flex; gap: 15px; color: #64748b; font-size: 14px; } /* Cursor styles */ .realtimecursor-cursor { position: fixed; z-index: 9999; pointer-events: none; transition: transform 75ms cubic-bezier(0.25, 0.46, 0.45, 0.94); will-change: transform; } .realtimecursor-pointer { width: 12px; height: 12px; border-radius: 50%; box-shadow: 0 0 5px rgba(0,0,0,0.3); } .realtimecursor-name { padding: 2px 6px; border-radius: 4px; font-size: 12px; margin-top: 4px; white-space: nowrap; color: #fff; } </style> </head> <body> <div class="collaborative-editor"> <div class="editor-header"> <h1>RealtimeCursor Demo</h1> <div id="connection-status" class="connection-status status-disconnected"> Disconnected </div> </div> <div class="user-settings"> <div class="form-group"> <label for="user-name">Your Name:</label> <input id="user-name" type="text" placeholder="Enter your name" /> </div> <div class="form-group"> <label for="project-id">Project ID:</label> <input id="project-id" type="text" placeholder="Enter project ID" /> </div> <div class="form-actions"> <button id="connect-button"> Connect </button> <button id="disconnect-button" disabled> Disconnect </button> </div> </div> <div class="collaborators-section"> <h3>Active Collaborators (<span id="collaborators-count">0</span>)</h3> <div id="collaborators-list"></div> </div> <div class="editor-container"> <textarea id="editor" class="editor" placeholder="Start typing to collaborate in real-time..." ></textarea> </div> <div class="editor-footer"> <div class="stats"> <span><strong>Characters:</strong> <span id="char-count">0</span></span> <span><strong>Words:</strong> <span id="word-count">0</span></span> <span><strong>Lines:</strong> <span id="line-count">1</span></span> </div> </div> </div> <!-- In a real project, you would include the SDK from npm --> <!-- <script src="node_modules/realtimecursor-sdk/dist/index.js"></script> --> <!-- For this demo, we'll include a mock implementation --> <script> // Mock RealtimeCursor implementation for demo purposes class RealtimeCursor { constructor(options) { this.options = options; this.isConnected = false; this.collaborators = []; this.cursors = {}; this.typingUsers = []; } connect() { this.isConnected = true; console.log('Connected to project:', this.options.projectId); // Simulate other users joining setTimeout(() => { const mockUsers = [ { id: 'user-1', name: 'Alice', color: '#3b82f6', socketId: 'socket-1' }, { id: 'user-2', name: 'Bob', color: '#ef4444', socketId: 'socket-2' } ]; this.collaborators = [ ...mockUsers, { id: this.options.user.id, name: this.options.user.name, color: this.options.user.color || '#10b981', socketId: 'your-socket-id' } ]; if (this.onCollaboratorsChange) { this.onCollaboratorsChange(this.collaborators); } }, 1000); // Simulate cursor movements this.cursorInterval = setInterval(() => { if (!this.isConnected) return; const randomUser = Math.random() > 0.5 ? 'socket-1' : 'socket-2'; const x = Math.floor(Math.random() * window.innerWidth); const y = Math.floor(Math.random() * window.innerHeight); if (this.onCursorUpdate) { this.onCursorUpdate({ socketId: randomUser, user: randomUser === 'socket-1' ? { name: 'Alice', color: '#3b82f6' } : { name: 'Bob', color: '#ef4444' }, x, y }); } }, 3000); // Simulate typing this.typingInterval = setInterval(() => { if (!this.isConnected) return; const randomUser = Math.random() > 0.5 ? 'socket-1' : 'socket-2'; const isTyping = Math.random() > 0.7; if (isTyping) { if (!this.typingUsers.includes(randomUser)) { this.typingUsers.push(randomUser); if (this.onTypingStatusChange) { this.onTypingStatusChange(this.typingUsers); } } } else { this.typingUsers = this.typingUsers.filter(id => id !== randomUser); if (this.onTypingStatusChange) { this.onTypingStatusChange(this.typingUsers); } } }, 2000); } disconnect() { this.isConnected = false; this.collaborators = []; this.cursors = {}; this.typingUsers = []; clearInterval(this.cursorInterval); clearInterval(this.typingInterval); console.log('Disconnected'); } updateCursor() { // In a real implementation, this would send cursor position to the server console.log('Cursor updated'); } updateCursorPosition() { // In a real implementation, this would send cursor text position to the server console.log('Cursor position updated'); } updateContent(content) { // In a real implementation, this would send content to the server console.log('Content updated:', content.substring(0, 20) + '...'); } setTyping(isTyping) { // In a real implementation, this would send typing status to the server console.log('Typing status:', isTyping); } getCollaborators() { return this.collaborators; } getTypingUsers() { return this.typingUsers; } } </script> <!-- Include our example code --> <script src="vanilla-example.js"></script> </body> </html>