UNPKG

realtimecursor

Version:

Real-time collaboration system with cursor tracking and approval workflow

148 lines (133 loc) 4.16 kB
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;