UNPKG

realtimecursor

Version:

Real-time collaboration system with cursor tracking and approval workflow

156 lines (133 loc) 4.7 kB
// Browser-compatible version of the SDK (function(global, factory) { if (typeof exports === 'object' && typeof module !== 'undefined') { // CommonJS module.exports = factory(); } else { // Browser globals global.RealtimeCursor = factory().RealtimeCursor; } }(this, function() { 'use strict'; // Simple implementation of the RealtimeCursor class for the browser class RealtimeCursor { constructor(options) { this.options = { ...options, socketUrl: options.socketUrl || options.apiUrl }; this.socket = null; this.collaborators = []; this.cursors = {}; this.typingUsers = new Set(); } connect() { if (this.socket) { this.disconnect(); } // Connect to socket server using socket.io this.socket = io(this.options.socketUrl); // Join project room this.socket.emit('join-project', { projectId: this.options.projectId, user: { id: this.options.user.id, name: this.options.user.name, color: this.options.user.color || this.getRandomColor() } }); // Set up event listeners this.setupEventListeners(); } disconnect() { if (this.socket) { this.socket.disconnect(); this.socket = null; this.collaborators = []; this.cursors = {}; this.typingUsers.clear(); } } updateCursor(position) { if (!this.socket) throw new Error('Socket not connected'); this.socket.emit('cursor-move', position); } updateCursorPosition(textPosition) { if (!this.socket) throw new Error('Socket not connected'); this.socket.emit('cursor-position', { textPosition }); } updateContent(content, cursorPosition) { if (!this.socket) throw new Error('Socket not connected'); this.socket.emit('content-change', { content, cursorPosition }); } setTyping(isTyping) { if (!this.socket) throw new Error('Socket not connected'); this.socket.emit('user-typing', { isTyping }); } setupEventListeners() { if (!this.socket) return; this.socket.on('room-users', (users) => { this.collaborators = users; if (this.onCollaboratorsChange) this.onCollaboratorsChange(users); }); this.socket.on('user-joined', ({ user }) => { this.collaborators.push(user); if (this.onCollaboratorsChange) this.onCollaboratorsChange(this.collaborators); if (this.onUserJoined) this.onUserJoined(user); }); this.socket.on('user-left', ({ socketId }) => { this.collaborators = this.collaborators.filter(u => u.socketId !== socketId); delete this.cursors[socketId]; this.typingUsers.delete(socketId); if (this.onCollaboratorsChange) this.onCollaboratorsChange(this.collaborators); if (this.onUserLeft) this.onUserLeft({ socketId }); }); this.socket.on('content-update', (data) => { if (this.onContentUpdate) this.onContentUpdate(data); }); this.socket.on('cursor-update', (data) => { const { socketId, user, x, y, textPosition } = data; this.cursors[socketId] = { x, y, user, textPosition }; if (this.onCursorUpdate) this.onCursorUpdate(data); }); this.socket.on('user-typing', ({ socketId, isTyping }) => { if (isTyping) { this.typingUsers.add(socketId); } else { this.typingUsers.delete(socketId); } if (this.onTypingStatusChange) this.onTypingStatusChange(Array.from(this.typingUsers)); }); this.socket.on('cursor-position', (data) => { const { socketId, textPosition, user } = data; if (this.cursors[socketId]) { this.cursors[socketId].textPosition = textPosition; this.cursors[socketId].user = user; } else { this.cursors[socketId] = { textPosition, user }; } if (this.onCursorPositionUpdate) this.onCursorPositionUpdate(data); }); } getRandomColor() { const colors = [ '#3b82f6', '#ef4444', '#10b981', '#f59e0b', '#8b5cf6', '#06b6d4', '#f97316', '#84cc16', '#ec4899', '#6366f1', '#14b8a6', '#f43f5e' ]; return colors[Math.floor(Math.random() * colors.length)]; } getCollaborators() { return this.collaborators; } getCursors() { return this.cursors; } getTypingUsers() { return Array.from(this.typingUsers); } } return { RealtimeCursor: RealtimeCursor }; }));