Skip to content
This repository was archived by the owner on Oct 22, 2025. It is now read-only.

Latest commit

 

History

History
117 lines (97 loc) · 3.2 KB

File metadata and controls

117 lines (97 loc) · 3.2 KB
import { createClient } from "actor-core/client";
import { createReactActorCore } from "@actor-core/react";
import { useState, useEffect } from "react";
import type {
  App, Cursor, TextUpdatedEvent,
  CursorUpdateEvent, UserDisconnectedEvent
} from "../actors/app";

const client = createClient<App>("http://localhost:6420");
const { useActor, useActorEvent } = createReactActorCore(client);

export function DocumentEditor() {
  // Connect to actor for this document ID from URL
  const documentId = new URLSearchParams(window.location.search).get('id') || 'default-doc';
  const [{ actor, state }] = useActor("document", { tags: { id: documentId } });

  // Local state
  const [text, setText] = useState("");
  const [cursorPos, setCursorPos] = useState({ x: 0, y: 0 });
  const [otherCursors, setOtherCursors] = useState<Record<string, Cursor>>({});
  
  // Load initial document state
  useEffect(() => {
    if (actor && state === "created") {
      actor.getText().then(setText);
      actor.getCursors().then(setOtherCursors);
    }
  }, [actor, state]);
  
  // Listen for updates from other users
  useActorEvent({ actor, event: "textUpdated" }, (event) => {
    const { text: newText, userId: _senderId } = event as TextUpdatedEvent;

    setText(newText);
  });
  
  useActorEvent({ actor, event: "cursorUpdated" }, (event) => {
    const { userId: cursorUserId, x, y } = event as CursorUpdateEvent;
    
    setOtherCursors(prev => ({
      ...prev,
      [cursorUserId]: { x, y, userId: cursorUserId }
    }));
  });

  useActorEvent({ actor, event: "userDisconnected" }, (event) => {
    const { userId } = event as UserDisconnectedEvent;
    
    setOtherCursors(prev => {
      const newCursors = { ...prev };
      delete newCursors[userId];
      return newCursors;
    });
  });  


  useEffect(() => {
    if (!actor || state !== "created") return;

    const updateCursor = ({ x, y }: { x: number, y: number }) => {
      
      if (x !== cursorPos.x || y !== cursorPos.y) {
        setCursorPos({ x, y });
        actor.updateCursor(x, y);
      }
    };

    window.addEventListener("mousemove", (e) => {
      const x = e.clientX;
      const y = e.clientY;
      
      updateCursor({ x, y });
    });
  }, [actor, state]);
  
  return (
    <div className="document-editor">
      <h2>Document: {documentId}</h2>
      
      <div>
        <textarea
          value={text}
          onChange={(e) => {
            const newText = e.target.value;
            setText(newText);
            if (actor && state === "created") {
              actor.setText(newText);
            }
          }}
          placeholder="Start typing..."
        />
        
        {/* Other users' cursors */}
        {Object.values(otherCursors).map((cursor) => (
          <div 
            key={cursor.userId}
            style={{
              position: 'absolute',
              left: `${cursor.x}px`,
              top: `${cursor.y}px`,
              width: '10px',
              height: '10px',
              backgroundColor: 'red',
              borderRadius: '50%'
            }}
          />
        ))}
      </div>
      
      <div>
        <p>Connected users: You and {Object.keys(otherCursors).length} others</p>
      </div>
    </div>
  );
}