realtimecursor
Version:
Real-time collaboration system with cursor tracking and approval workflow
314 lines (250 loc) • 8.6 kB
Markdown
# 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