Files
sternboard/web/src/useBoard.ts
T

106 lines
4.8 KiB
TypeScript
Raw Normal View History

import { useEffect, useRef, useState } from "react";
import { io, type Socket } from "socket.io-client";
import type { BoardState } from "./types";
import { getModeratorToken, setModeratorToken } from "./identity";
export type BoardStatus = "connecting" | "ready" | "notfound" | "destroyed";
export function useBoard(boardId: string, participantId: string, name: string | null) {
const [state, setState] = useState<BoardState | null>(null);
const [status, setStatus] = useState<BoardStatus>("connecting");
const socketRef = useRef<Socket | null>(null);
useEffect(() => {
if (!name) return;
const socket = io({ path: "/socket.io" });
socketRef.current = socket;
socket.on("connect", () =>
socket.emit("board:join", { boardId, participantId, name, moderatorToken: getModeratorToken(boardId) }),
);
socket.on("board:state", (s: BoardState) => {
setState(s);
setStatus("ready");
});
socket.on("board:claimed", (p: { moderatorToken: string }) => {
setModeratorToken(boardId, p.moderatorToken);
});
socket.on("error", (e: { message: string }) => {
if (e.message === "board_not_found") setStatus("notfound");
});
socket.on("board:destroyed", () => setStatus("destroyed"));
return () => {
socket.disconnect();
socketRef.current = null;
};
}, [boardId, participantId, name]);
const mod = () => getModeratorToken(boardId);
const emit = (event: string, payload: Record<string, unknown> = {}) =>
socketRef.current?.emit(event, payload);
const modEmit = (event: string, payload: Record<string, unknown> = {}) =>
emit(event, { ...payload, moderatorToken: mod() });
return {
state,
status,
isModerator: state?.youAreModerator ?? false,
actions: {
// notas
addNote: (columnId: string, text: string, color?: string) =>
emit("note:add", { columnId, text, color }),
updateNote: (noteId: string, text: string) => emit("note:update", { noteId, text }),
deleteNote: (noteId: string) => emit("note:delete", { noteId }),
revealNote: (noteId: string, revealed: boolean) => emit("note:reveal", { noteId, revealed }),
publishMine: (columnId: string) => emit("note:publishMine", { columnId }),
recolorMine: (color: string) => emit("note:recolorMine", { color }),
moveNote: (noteId: string, columnId: string, groupId: string | null, position: number) =>
emit("note:move", { noteId, columnId, groupId, position }),
// grupos
createGroup: (noteId: string, targetNoteId: string) =>
emit("group:create", { noteId, targetNoteId }),
renameGroup: (groupId: string, title: string) => emit("group:rename", { groupId, title }),
moveGroup: (groupId: string, columnId: string, position: number) =>
emit("group:move", { groupId, columnId, position }),
deleteGroup: (groupId: string) => emit("group:delete", { groupId }),
// votos
addVote: (targetType: "note" | "group", targetId: string) =>
emit("vote:add", { targetType, targetId }),
removeVote: (targetId: string) => emit("vote:remove", { targetId }),
// ajustes y moderación
updateBoard: (patch: {
title?: string;
votesPerUser?: number;
allowMultiVote?: boolean;
moderationMode?: "everyone" | "single";
showCardCreators?: boolean;
locked?: boolean;
}) => modEmit("board:update", patch),
reveal: (revealed: boolean) => modEmit("board:reveal", { revealed }),
revealVotes: (revealed: boolean) => modEmit("board:revealVotes", { revealed }),
setPhase: (phase: string) => modEmit("board:phase", { phase }),
// columnas (moderador)
addColumn: (title: string) => modEmit("column:add", { title }),
renameColumn: (columnId: string, title: string) => modEmit("column:rename", { columnId, title }),
deleteColumn: (columnId: string) => modEmit("column:delete", { columnId }),
reorderColumns: (orderedIds: string[]) => modEmit("column:reorder", { orderedIds }),
startTimer: (seconds: number) => modEmit("timer:start", { seconds }),
stopTimer: () => modEmit("timer:stop"),
claim: () => modEmit("board:claim"),
clearVotes: () => modEmit("votes:clear"),
removeBoard: () => modEmit("board:remove"),
// participante
renameMe: (name: string) => emit("participant:rename", { name }),
// action items
addAction: (text: string) => emit("actionitem:add", { text }),
updateAction: (id: string, patch: { text?: string; owner?: string | null }) =>
emit("actionitem:update", { id, ...patch }),
toggleAction: (id: string, done: boolean) => emit("actionitem:toggle", { id, done }),
deleteAction: (id: string) => emit("actionitem:delete", { id }),
},
};
}
export type BoardActions = ReturnType<typeof useBoard>["actions"];