realtimecursor
Version:
Real-time collaboration system with cursor tracking and approval workflow
148 lines (133 loc) • 4.16 kB
JSX
import React, { useState, useRef, useEffect } from 'react';
import { useRealtimeCursor, CursorOverlay, CollaboratorsList } from 'realtimecursor-sdk';
import 'realtimecursor-sdk/dist/cursor.css';
function CollaborativeEditor() {
const [content, setContent] = useState('');
const [projectId, setProjectId] = useState('demo-project');
const [userId, setUserId] = useState(`user-${Math.floor(Math.random() * 10000)}`);
const [userName, setUserName] = useState('Anonymous User');
const editorRef = useRef(null);
const {
collaborators,
cursors,
typingUsers,
isConnected,
connect,
disconnect,
updateCursor,
updateContent,
updateCursorPosition,
setTyping
} = useRealtimeCursor({
apiUrl: process.env.REACT_APP_API_URL || 'http://localhost:3000',
projectId,
user: {
id: userId,
name: userName
},
autoConnect: true
});
const handleMouseMove = (e) => {
if (!editorRef.current) return;
const rect = editorRef.current.getBoundingClientRect();
updateCursor({
x: e.clientX,
y: e.clientY,
textPosition: editorRef.current.selectionStart
});
};
const handleChange = (e) => {
const newContent = e.target.value;
setContent(newContent);
updateContent(newContent, e.target.selectionStart);
setTyping(true);
// Reset typing indicator after 1 second of inactivity
clearTimeout(window.typingTimeout);
window.typingTimeout = setTimeout(() => setTyping(false), 1000);
};
const handleSelect = () => {
if (!editorRef.current) return;
updateCursorPosition(editorRef.current.selectionStart);
};
// Clean up on unmount
useEffect(() => {
return () => {
disconnect();
clearTimeout(window.typingTimeout);
};
}, [disconnect]);
return (
<div className="collaborative-editor">
<div className="editor-header">
<h2>RealtimeCursor Demo</h2>
<div className="connection-status">
{isConnected ? (
<span className="status-connected">Connected</span>
) : (
<span className="status-disconnected">Disconnected</span>
)}
</div>
</div>
<div className="user-settings">
<div className="form-group">
<label>Your Name:</label>
<input
type="text"
value={userName}
onChange={(e) => setUserName(e.target.value)}
placeholder="Enter your name"
/>
</div>
<div className="form-group">
<label>Project ID:</label>
<input
type="text"
value={projectId}
onChange={(e) => setProjectId(e.target.value)}
placeholder="Enter project ID"
/>
</div>
<div className="form-actions">
<button onClick={connect} disabled={isConnected}>
Connect
</button>
<button onClick={disconnect} disabled={!isConnected}>
Disconnect
</button>
</div>
</div>
<div className="collaborators-section">
<h3>Active Collaborators ({collaborators.length})</h3>
<CollaboratorsList
collaborators={collaborators}
typingUsers={typingUsers}
/>
</div>
<div className="editor-container">
<textarea
ref={editorRef}
value={content}
onChange={handleChange}
onMouseMove={handleMouseMove}
onSelect={handleSelect}
className="editor"
placeholder="Start typing to collaborate in real-time..."
/>
{/* Render other users' cursors */}
<CursorOverlay
cursors={cursors}
content={content}
editorRef={editorRef}
/>
</div>
<div className="editor-footer">
<div className="stats">
<span>{content.length} characters</span>
<span>{content.split(/\s+/).filter(Boolean).length} words</span>
<span>{content.split('\n').length} lines</span>
</div>
</div>
</div>
);
}
export default CollaborativeEditor;