rmaps-online/src/stores/room.ts

171 lines
4.2 KiB
TypeScript

import { create } from 'zustand';
import { nanoid } from 'nanoid';
import type {
Room,
Participant,
ParticipantLocation,
ParticipantStatus,
Waypoint,
RoomSettings,
PrecisionLevel,
} from '@/types';
// Color palette for participants
const COLORS = [
'#10b981', // emerald
'#6366f1', // indigo
'#f59e0b', // amber
'#ef4444', // red
'#8b5cf6', // violet
'#ec4899', // pink
'#14b8a6', // teal
'#f97316', // orange
'#84cc16', // lime
'#06b6d4', // cyan
];
interface RoomState {
room: Room | null;
participants: Participant[];
currentParticipantId: string | null;
isConnected: boolean;
error: string | null;
// Actions
joinRoom: (slug: string, name: string, emoji: string) => void;
leaveRoom: () => void;
updateParticipant: (updates: Partial<Participant>) => void;
updateLocation: (location: ParticipantLocation) => void;
setStatus: (status: ParticipantStatus) => void;
addWaypoint: (waypoint: Omit<Waypoint, 'id' | 'createdAt' | 'createdBy'>) => void;
removeWaypoint: (waypointId: string) => void;
// Internal
_syncFromDocument: (doc: unknown) => void;
}
export const useRoomStore = create<RoomState>((set, get) => ({
room: null,
participants: [],
currentParticipantId: null,
isConnected: false,
error: null,
joinRoom: (slug: string, name: string, emoji: string) => {
const participantId = nanoid();
const colorIndex = Math.floor(Math.random() * COLORS.length);
const participant: Participant = {
id: participantId,
name,
emoji,
color: COLORS[colorIndex],
joinedAt: new Date(),
lastSeen: new Date(),
status: 'online',
privacySettings: {
sharingEnabled: true,
defaultPrecision: 'exact' as PrecisionLevel,
showIndoorFloor: true,
ghostMode: false,
},
};
// Create or join room
const room: Room = {
id: nanoid(),
slug,
name: slug,
createdAt: new Date(),
createdBy: participantId,
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days
settings: {
maxParticipants: 10,
defaultPrecision: 'exact' as PrecisionLevel,
allowGuestJoin: true,
showC3NavIndoor: true,
},
participants: new Map([[participantId, participant]]),
waypoints: [],
};
set({
room,
participants: [participant],
currentParticipantId: participantId,
isConnected: true,
error: null,
});
// TODO: Connect to Automerge sync server
console.log(`Joined room: ${slug} as ${name} (${emoji})`);
},
leaveRoom: () => {
const { room, currentParticipantId } = get();
if (room && currentParticipantId) {
room.participants.delete(currentParticipantId);
}
set({
room: null,
participants: [],
currentParticipantId: null,
isConnected: false,
});
},
updateParticipant: (updates: Partial<Participant>) => {
const { room, currentParticipantId, participants } = get();
if (!room || !currentParticipantId) return;
const current = room.participants.get(currentParticipantId);
if (!current) return;
const updated = { ...current, ...updates, lastSeen: new Date() };
room.participants.set(currentParticipantId, updated);
set({
participants: participants.map((p) =>
p.id === currentParticipantId ? updated : p
),
});
},
updateLocation: (location: ParticipantLocation) => {
get().updateParticipant({ location });
},
setStatus: (status: ParticipantStatus) => {
get().updateParticipant({ status });
},
addWaypoint: (waypoint) => {
const { room, currentParticipantId } = get();
if (!room || !currentParticipantId) return;
const newWaypoint: Waypoint = {
...waypoint,
id: nanoid(),
createdAt: new Date(),
createdBy: currentParticipantId,
};
room.waypoints.push(newWaypoint);
set({ room: { ...room } });
},
removeWaypoint: (waypointId: string) => {
const { room } = get();
if (!room) return;
room.waypoints = room.waypoints.filter((w) => w.id !== waypointId);
set({ room: { ...room } });
},
_syncFromDocument: (doc: unknown) => {
// TODO: Implement Automerge document sync
console.log('Sync from document:', doc);
},
}));