feat: [US-047] - Live cursor positions on canvas

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Gustavo Henrique Santos Souza de Miranda
2026-01-23 21:03:17 -03:00
co-authored by Claude Opus 4.5
parent b6d85b1a47
commit 7fe10544a1
4 changed files with 225 additions and 67 deletions
+36
View File
@@ -8,10 +8,23 @@ export type PresenceUser = {
displayName: string
}
export type CursorPosition = {
x: number
y: number
}
export type RemoteCursor = {
userId: string
displayName: string
position: CursorPosition
lastUpdated: number
}
type RealtimeCallbacks = {
onConnectionStateChange: (state: ConnectionState) => void
onPresenceSync?: (users: PresenceUser[]) => void
onChannelSubscribed?: (channel: RealtimeChannel) => void
onCursorUpdate?: (cursor: RemoteCursor) => void
}
const HEARTBEAT_INTERVAL_MS = 30_000
@@ -63,6 +76,15 @@ export class RealtimeConnection {
this.callbacks.onPresenceSync?.(users)
}
})
.on('broadcast', { event: 'cursor' }, ({ payload }) => {
if (payload.userId === this.userId) return
this.callbacks.onCursorUpdate?.({
userId: payload.userId,
displayName: payload.displayName,
position: { x: payload.x, y: payload.y },
lastUpdated: Date.now(),
})
})
.subscribe(async (status) => {
if (this.isDestroyed) return
@@ -107,6 +129,20 @@ export class RealtimeConnection {
return this.channel
}
broadcastCursor(position: CursorPosition): void {
if (!this.channel) return
this.channel.send({
type: 'broadcast',
event: 'cursor',
payload: {
userId: this.userId,
displayName: this.displayName,
x: position.x,
y: position.y,
},
})
}
private async createSession(): Promise<void> {
try {
await this.supabase.from('collaboration_sessions').upsert(