realtimecursor
Version:
Real-time collaboration system with cursor tracking and approval workflow
123 lines (105 loc) • 3.48 kB
text/typescript
import { useState, useEffect, useRef } from 'react';
import RealtimeCursor, { RealtimeCursorOptions, CollaboratorInfo } from './realtimecursor';
export interface UseRealtimeCursorOptions extends RealtimeCursorOptions {
autoConnect?: boolean;
}
export function useRealtimeCursor(options: UseRealtimeCursorOptions) {
const [collaborators, setCollaborators] = useState<CollaboratorInfo[]>([]);
const [cursors, setCursors] = useState<Record<string, any>>({});
const [typingUsers, setTypingUsers] = useState<string[]>([]);
const [isConnected, setIsConnected] = useState<boolean>(false);
const cursorClientRef = useRef<RealtimeCursor | null>(null);
useEffect(() => {
// Create the RealtimeCursor instance
const cursorClient = new RealtimeCursor(options);
cursorClientRef.current = cursorClient;
// Set up event handlers
cursorClient.onCollaboratorsChange = (newCollaborators) => {
setCollaborators(newCollaborators);
};
cursorClient.onCursorUpdate = ({ socketId, user, x, y, textPosition }) => {
setCursors((prev: Record<string, any>) => ({
...prev,
[socketId]: { x, y, user, textPosition }
}));
};
cursorClient.onCursorPositionUpdate = ({ socketId, textPosition, user }) => {
setCursors((prev: Record<string, any>) => ({
...prev,
[socketId]: { ...prev[socketId], textPosition, user }
}));
};
cursorClient.onUserLeft = ({ socketId }) => {
setCursors((prev: Record<string, any>) => {
const newCursors = { ...prev };
delete newCursors[socketId];
return newCursors;
});
};
cursorClient.onTypingStatusChange = (typingUserIds) => {
setTypingUsers(typingUserIds);
};
// Auto-connect if enabled
if (options.autoConnect !== false) {
cursorClient.connect();
setIsConnected(true);
}
// Clean up on unmount
return () => {
cursorClient.disconnect();
setIsConnected(false);
};
}, [options.projectId, options.user.id]); // Reconnect if project or user changes
// Connect function
const connect = () => {
if (cursorClientRef.current) {
cursorClientRef.current.connect();
setIsConnected(true);
}
};
// Disconnect function
const disconnect = () => {
if (cursorClientRef.current) {
cursorClientRef.current.disconnect();
setIsConnected(false);
}
};
// Update cursor position
const updateCursor = (position: { x: number; y: number; textPosition?: number }) => {
if (cursorClientRef.current) {
cursorClientRef.current.updateCursor(position);
}
};
// Update cursor position in text
const updateCursorPosition = (textPosition: number) => {
if (cursorClientRef.current) {
cursorClientRef.current.updateCursorPosition(textPosition);
}
};
// Update content
const updateContent = (content: string, cursorPosition?: number) => {
if (cursorClientRef.current) {
cursorClientRef.current.updateContent(content, cursorPosition);
}
};
// Set typing indicator
const setTyping = (isTyping: boolean) => {
if (cursorClientRef.current) {
cursorClientRef.current.setTyping(isTyping);
}
};
return {
collaborators,
cursors,
typingUsers,
isConnected,
connect,
disconnect,
updateCursor,
updateCursorPosition,
updateContent,
setTyping,
client: cursorClientRef.current
};
}
export default useRealtimeCursor;