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>
);
}
This repository was archived by the owner on Oct 22, 2025. It is now read-only.