feat(control): таймер сессии поверх сцены
Отдельное окно countdown с «Показать игрокам»; на превью — move/resize/rotate, на презентации тот же layout. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
import {
|
||||
clampSessionTimerDurationMs,
|
||||
clampSessionTimerLayout,
|
||||
createInitialSessionTimerState,
|
||||
getSessionTimerRemainingMs,
|
||||
type SessionTimerEvent,
|
||||
type SessionTimerState,
|
||||
} from '../../shared/types/sessionTimer';
|
||||
|
||||
export class TimerSessionStore {
|
||||
private state: SessionTimerState = createInitialSessionTimerState();
|
||||
|
||||
getState(): SessionTimerState {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
clear(): SessionTimerState {
|
||||
this.state = createInitialSessionTimerState();
|
||||
return this.state;
|
||||
}
|
||||
|
||||
dispatch(event: SessionTimerEvent, now = Date.now()): SessionTimerState {
|
||||
switch (event.kind) {
|
||||
case 'setDuration': {
|
||||
const durationMs = clampSessionTimerDurationMs(event.durationMs);
|
||||
if (this.state.running) {
|
||||
const remaining = getSessionTimerRemainingMs(this.state, now);
|
||||
this.state = {
|
||||
...this.state,
|
||||
revision: this.state.revision + 1,
|
||||
durationMs,
|
||||
remainingMs: Math.min(remaining, durationMs),
|
||||
endsAt: now + Math.min(remaining, durationMs),
|
||||
};
|
||||
} else {
|
||||
this.state = {
|
||||
...this.state,
|
||||
revision: this.state.revision + 1,
|
||||
durationMs,
|
||||
remainingMs: durationMs,
|
||||
endsAt: null,
|
||||
running: false,
|
||||
};
|
||||
}
|
||||
return this.state;
|
||||
}
|
||||
case 'start': {
|
||||
const remaining = getSessionTimerRemainingMs(this.state, now);
|
||||
if (remaining <= 0) {
|
||||
// Restart from full duration if already at zero.
|
||||
const durationMs = this.state.durationMs;
|
||||
this.state = {
|
||||
...this.state,
|
||||
revision: this.state.revision + 1,
|
||||
remainingMs: durationMs,
|
||||
running: true,
|
||||
endsAt: now + durationMs,
|
||||
};
|
||||
return this.state;
|
||||
}
|
||||
this.state = {
|
||||
...this.state,
|
||||
revision: this.state.revision + 1,
|
||||
running: true,
|
||||
endsAt: now + remaining,
|
||||
remainingMs: remaining,
|
||||
};
|
||||
return this.state;
|
||||
}
|
||||
case 'pause': {
|
||||
if (!this.state.running) return this.state;
|
||||
const remaining = getSessionTimerRemainingMs(this.state, now);
|
||||
this.state = {
|
||||
...this.state,
|
||||
revision: this.state.revision + 1,
|
||||
running: false,
|
||||
endsAt: null,
|
||||
remainingMs: remaining,
|
||||
};
|
||||
return this.state;
|
||||
}
|
||||
case 'reset': {
|
||||
this.state = {
|
||||
...this.state,
|
||||
revision: this.state.revision + 1,
|
||||
running: false,
|
||||
endsAt: null,
|
||||
remainingMs: this.state.durationMs,
|
||||
};
|
||||
return this.state;
|
||||
}
|
||||
case 'setVisibleToPlayers': {
|
||||
if (this.state.visibleToPlayers === event.visible) return this.state;
|
||||
this.state = {
|
||||
...this.state,
|
||||
revision: this.state.revision + 1,
|
||||
visibleToPlayers: event.visible,
|
||||
};
|
||||
return this.state;
|
||||
}
|
||||
case 'layout.set': {
|
||||
const layout = clampSessionTimerLayout(event.layout);
|
||||
this.state = {
|
||||
...this.state,
|
||||
revision: this.state.revision + 1,
|
||||
layout,
|
||||
};
|
||||
return this.state;
|
||||
}
|
||||
default: {
|
||||
const _exhaustive: never = event;
|
||||
return _exhaustive;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Когда running и время вышло — зафиксировать 00:00 (вызывается из UI/main по желанию). */
|
||||
settleIfExpired(now = Date.now()): SessionTimerState {
|
||||
if (!this.state.running) return this.state;
|
||||
const remaining = getSessionTimerRemainingMs(this.state, now);
|
||||
if (remaining > 0) return this.state;
|
||||
this.state = {
|
||||
...this.state,
|
||||
revision: this.state.revision + 1,
|
||||
running: false,
|
||||
endsAt: null,
|
||||
remainingMs: 0,
|
||||
};
|
||||
return this.state;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user