Files
DndGamePlayer/app/renderer/shared/sessionTimer/SessionTimerLayer.tsx
T
Ivan Fontosh db1168a46c feat(control): таймер сессии поверх сцены
Отдельное окно countdown с «Показать игрокам»; на превью — move/resize/rotate, на презентации тот же layout.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-03 16:51:56 +08:00

56 lines
1.6 KiB
TypeScript

import React, { useEffect, useMemo, useState } from 'react';
import { getSessionTimerRemainingMs } from '../../../shared/types/sessionTimer';
import { SessionTimerOverlay } from './SessionTimerOverlay';
import { useSessionTimerState } from './useSessionTimerState';
/** Оверлей таймера с тиком remaining; editable только на пульте. */
export function SessionTimerLayer({
viewport,
editable = false,
}: {
viewport: { x: number; y: number; w: number; h: number } | null;
editable?: boolean;
}) {
const [state, timerApi] = useSessionTimerState();
const [now, setNow] = useState(() => Date.now());
useEffect(() => {
if (!state?.visibleToPlayers) return;
if (!state.running) return;
const id = window.setInterval(() => setNow(Date.now()), 200);
return () => window.clearInterval(id);
}, [state?.running, state?.visibleToPlayers]);
useEffect(() => {
if (!state?.running || !state.visibleToPlayers) return;
const remaining = getSessionTimerRemainingMs(state, now);
if (remaining > 0) return;
void timerApi.dispatch({ kind: 'pause' });
}, [now, state, timerApi]);
const remainingMs = useMemo(() => {
if (!state) return 0;
return getSessionTimerRemainingMs(state, now);
}, [now, state]);
if (!viewport || !state?.visibleToPlayers) return null;
return (
<SessionTimerOverlay
remainingMs={remainingMs}
layout={state.layout}
viewport={viewport}
editable={editable}
onLayoutChange={
editable
? (layout) => {
void timerApi.dispatch({ kind: 'layout.set', layout });
}
: undefined
}
/>
);
}