db1168a46c
Отдельное окно countdown с «Показать игрокам»; на превью — move/resize/rotate, на презентации тот же layout. Co-authored-by: Cursor <cursoragent@cursor.com>
56 lines
1.6 KiB
TypeScript
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
|
|
}
|
|
/>
|
|
);
|
|
}
|