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

Commit 102b1e4

Browse files
committed
chore(examples): cursors example
1 parent 3999ee4 commit 102b1e4

File tree

13 files changed

+864
-0
lines changed

13 files changed

+864
-0
lines changed

examples/cursors/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.actorcore
2+
node_modules

examples/cursors/README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Real-time Collaborative Cursors for RivetKit
2+
3+
Example project demonstrating real-time cursor tracking and collaborative canvas with [RivetKit](https://rivetkit.org).
4+
5+
[Learn More →](https://github.com/rivet-dev/rivetkit)
6+
7+
[Discord](https://rivet.dev/discord)[Documentation](https://rivetkit.org)[Issues](https://github.com/rivet-dev/rivetkit/issues)
8+
9+
## Getting Started
10+
11+
### Prerequisites
12+
13+
- Node.js 18+
14+
15+
### Installation
16+
17+
```sh
18+
git clone https://github.com/rivet-dev/rivetkit
19+
cd rivetkit/examples/cursors
20+
npm install
21+
```
22+
23+
### Development
24+
25+
```sh
26+
npm run dev
27+
```
28+
29+
Open your browser to `http://localhost:5173`. Open multiple tabs or windows to see real-time cursor tracking and text placement across different users.
30+
31+
## Features
32+
33+
- Real-time cursor position tracking
34+
- Multiple users with color-coded cursors
35+
- Click-to-place text on canvas
36+
- Multiple room support for different collaborative spaces
37+
- Persistent text labels across sessions
38+
- Event-driven architecture with RivetKit actors
39+
- TypeScript support throughout
40+
41+
## License
42+
43+
Apache 2.0

examples/cursors/package.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "example-cursors",
3+
"version": "2.0.20",
4+
"private": true,
5+
"type": "module",
6+
"scripts": {
7+
"dev": "concurrently \"npm run dev:backend\" \"npm run dev:frontend\"",
8+
"dev:backend": "tsx --watch src/backend/server.ts",
9+
"dev:frontend": "vite",
10+
"check-types": "tsc --noEmit",
11+
"test": "vitest run"
12+
},
13+
"devDependencies": {
14+
"@types/node": "^22.13.9",
15+
"@types/prompts": "^2",
16+
"@types/react": "^18.2.0",
17+
"@types/react-dom": "^18.2.0",
18+
"@vitejs/plugin-react": "^4.2.0",
19+
"concurrently": "^8.2.2",
20+
"prompts": "^2.4.2",
21+
"tsx": "^3.12.7",
22+
"typescript": "^5.5.2",
23+
"vite": "^5.0.0",
24+
"vitest": "^3.1.1",
25+
"@rivetkit/react": "workspace:*",
26+
"react": "^18.2.0",
27+
"react-dom": "^18.2.0"
28+
},
29+
"dependencies": {
30+
"rivetkit": "workspace:*"
31+
},
32+
"stableVersion": "0.8.0"
33+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { actor, setup } from "rivetkit";
2+
3+
export type CursorPosition = {
4+
userId: string;
5+
x: number;
6+
y: number;
7+
timestamp: number;
8+
};
9+
10+
export type TextLabel = {
11+
id: string;
12+
userId: string;
13+
text: string;
14+
x: number;
15+
y: number;
16+
timestamp: number;
17+
};
18+
19+
export const cursorRoom = actor({
20+
// Persistent state that survives restarts: https://rivet.dev/docs/actors/state
21+
state: {
22+
cursors: {} as Record<string, CursorPosition>,
23+
textLabels: [] as TextLabel[],
24+
},
25+
26+
actions: {
27+
// Update cursor position
28+
updateCursor: (c, userId: string, x: number, y: number) => {
29+
const cursor: CursorPosition = { userId, x, y, timestamp: Date.now() };
30+
c.state.cursors[userId] = cursor;
31+
// Send events to all connected clients: https://rivet.dev/docs/actors/events
32+
c.broadcast("cursorMoved", cursor);
33+
return cursor;
34+
},
35+
36+
// Place text on the canvas
37+
placeText: (c, userId: string, text: string, x: number, y: number) => {
38+
const textLabel: TextLabel = {
39+
id: `${userId}-${Date.now()}`,
40+
userId,
41+
text,
42+
x,
43+
y,
44+
timestamp: Date.now(),
45+
};
46+
c.state.textLabels.push(textLabel);
47+
c.broadcast("textPlaced", textLabel);
48+
return textLabel;
49+
},
50+
51+
// Get all cursors
52+
getCursors: (c) => c.state.cursors,
53+
54+
// Get all text labels
55+
getTextLabels: (c) => c.state.textLabels,
56+
57+
// Remove cursor when user disconnects
58+
removeCursor: (c, userId: string) => {
59+
delete c.state.cursors[userId];
60+
c.broadcast("cursorRemoved", userId);
61+
},
62+
},
63+
});
64+
65+
// Register actors for use: https://rivet.dev/docs/setup
66+
export const registry = setup({
67+
use: { cursorRoom },
68+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { registry } from "./registry";
2+
3+
registry.start({
4+
cors: {
5+
origin: "http://localhost:5173",
6+
credentials: true,
7+
},
8+
});

0 commit comments

Comments
 (0)