UNPKG

realtimecursor

Version:

Real-time collaboration system with cursor tracking and approval workflow

314 lines (250 loc) 8.6 kB
# RealtimeCursor SDK - Fixed Version (v1.1.0) A lightweight SDK for adding real-time cursor tracking and collaboration features to your web applications. ## Features - Real-time cursor tracking - Collaborator presence - Content synchronization - React hooks and components - Vanilla JavaScript support ## Installation ```bash npm install realtimecursor-sdk-fixed ``` ## Usage ### React ```jsx import React, { useState, useRef, useEffect } from 'react'; import { useRealtimeCursor, CursorOverlay, CollaboratorsList } from 'realtimecursor-sdk-fixed'; import 'realtimecursor-sdk-fixed/dist/cursor.css'; function CollaborativeEditor() { const [content, setContent] = useState(''); const editorRef = useRef(null); const { cursors, collaborators, updateCursor, updateContent, connect, disconnect } = useRealtimeCursor({ apiUrl: 'http://localhost:3001', projectId: 'your-project-id', user: { id: 'user-123', name: 'John Doe', color: '#3b82f6' // Optional } }); // Connect on mount useEffect(() => { connect(); return () => disconnect(); }, [connect, disconnect]); // Update cursor position on mouse move const handleMouseMove = (e) => { if (!editorRef.current) return; const rect = editorRef.current.getBoundingClientRect(); updateCursor({ x: e.clientX, y: e.clientY, relativeX: e.clientX - rect.left, relativeY: e.clientY - rect.top }); }; // Update content when changed const handleContentChange = (e) => { const newContent = e.target.value; setContent(newContent); updateContent(newContent); }; return ( <div className="collaborative-editor"> <h2>Collaborative Editor</h2> <div className="editor-container" ref={editorRef} onMouseMove={handleMouseMove}> <textarea value={content} onChange={handleContentChange} placeholder="Start typing..." /> <CursorOverlay cursors={cursors} /> </div> <div className="collaborators-panel"> <h3>Active Collaborators ({collaborators.length})</h3> <CollaboratorsList collaborators={collaborators} /> </div> </div> ); } ``` ### Vanilla JavaScript ```javascript import { RealtimeCursor } from 'realtimecursor-sdk-fixed'; import 'realtimecursor-sdk-fixed/dist/cursor.css'; // Initialize the cursor client const cursorClient = new RealtimeCursor({ apiUrl: 'http://localhost:3001', projectId: 'your-project-id', user: { id: 'user-123', name: 'John Doe', color: '#3b82f6' // Optional } }); // Connect to the real-time service cursorClient.connect(); // Set up event handlers cursorClient.onConnect = () => { console.log('Connected to real-time service'); }; cursorClient.onDisconnect = () => { console.log('Disconnected from real-time service'); }; cursorClient.onCursorsChange = (cursors) => { console.log('Cursors updated:', cursors); renderCursors(cursors); }; cursorClient.onCollaboratorsChange = (collaborators) => { console.log('Collaborators updated:', collaborators); renderCollaborators(collaborators); }; cursorClient.onContentUpdate = (data) => { console.log('Content updated:', data); document.getElementById('editor').value = data.content; }; // Update cursor position on mouse move document.getElementById('editor-container').addEventListener('mousemove', (e) => { const rect = e.currentTarget.getBoundingClientRect(); cursorClient.updateCursor({ x: e.clientX, y: e.clientY, relativeX: e.clientX - rect.left, relativeY: e.clientY - rect.top }); }); // Update content when changed document.getElementById('editor').addEventListener('input', (e) => { cursorClient.updateContent(e.target.value); }); // Disconnect when done window.addEventListener('beforeunload', () => { cursorClient.disconnect(); }); // Helper functions to render cursors and collaborators function renderCursors(cursors) { const container = document.getElementById('cursors-container'); container.innerHTML = ''; Object.values(cursors).forEach(cursor => { const cursorElement = document.createElement('div'); cursorElement.className = 'realtimecursor-cursor'; cursorElement.style.left = `${cursor.position.x || cursor.position.relativeX || 0}px`; cursorElement.style.top = `${cursor.position.y || cursor.position.relativeY || 0}px`; // Add cursor SVG and label cursorElement.innerHTML = ` <svg width="24" height="24" viewBox="0 0 24 24" fill="none" style="transform: rotate(-45deg)"> <path d="M1 1L11 11V19L7 21V13L1 1Z" fill="${cursor.user.color || '#3b82f6'}" stroke="white" stroke-width="1" /> </svg> <div class="realtimecursor-label" style="background-color: ${cursor.user.color || '#3b82f6'}"> ${cursor.user.name} </div> `; container.appendChild(cursorElement); }); } function renderCollaborators(collaborators) { const container = document.getElementById('collaborators-container'); container.innerHTML = ''; if (collaborators.length === 0) { container.innerHTML = '<div class="realtimecursor-no-collaborators">No active collaborators</div>'; return; } collaborators.forEach(user => { const userElement = document.createElement('div'); userElement.className = 'realtimecursor-collaborator'; userElement.innerHTML = ` <div class="realtimecursor-collaborator-avatar" style="background-color: ${user.color || '#3b82f6'}"> ${user.name ? user.name.charAt(0).toUpperCase() : '?'} </div> <div class="realtimecursor-collaborator-name"> ${user.name} </div> `; container.appendChild(userElement); }); } ``` ## API Reference ### RealtimeCursor Class ```javascript const cursorClient = new RealtimeCursor(options); ``` #### Options - `apiUrl` (string): URL of the RealtimeCursor API server - `projectId` (string): Unique identifier for the project - `user` (object): User information - `id` (string): Unique identifier for the user - `name` (string): Display name for the user - `color` (string, optional): Color for the user's cursor (hex code) #### Methods - `connect()`: Connect to the real-time service - `disconnect()`: Disconnect from the real-time service - `updateCursor(position)`: Update cursor position - `position` (object): Cursor position - `x` (number, optional): Absolute X position - `y` (number, optional): Absolute Y position - `relativeX` (number, optional): Relative X position within container - `relativeY` (number, optional): Relative Y position within container - `textPosition` (number, optional): Position in text (for text editors) - `updateContent(content, version)`: Update content - `content` (string): New content - `version` (number, optional): Content version #### Event Handlers - `onConnect`: Called when connected to the service - `onDisconnect`: Called when disconnected from the service - `onCursorsChange(cursors)`: Called when cursors are updated - `onCollaboratorsChange(collaborators)`: Called when collaborators list changes - `onContentUpdate(data)`: Called when content is updated by another user - `onUserJoined(user)`: Called when a user joins - `onUserLeft(user)`: Called when a user leaves - `onError(error)`: Called when an error occurs ### useRealtimeCursor Hook ```javascript const { cursors, collaborators, connected, connect, disconnect, updateCursor, updateContent } = useRealtimeCursor(options); ``` #### Returns - `cursors` (object): Map of cursor objects by user ID - `collaborators` (array): List of active collaborators - `connected` (boolean): Connection status - `connect()`: Function to connect to the service - `disconnect()`: Function to disconnect from the service - `updateCursor(position)`: Function to update cursor position - `updateContent(content, version)`: Function to update content ### CursorOverlay Component ```jsx <CursorOverlay cursors={cursors} containerRef={containerRef} /> ``` #### Props - `cursors` (object): Map of cursor objects by user ID - `containerRef` (React.RefObject, optional): Reference to the container element ### CollaboratorsList Component ```jsx <CollaboratorsList collaborators={collaborators} /> ``` #### Props - `collaborators` (array): List of active collaborators ## Backend Server This SDK requires a backend server for real-time communication. The server is included in the package and can be started with: ```bash cd api npm install npm start ``` ## License MIT