feat(control): таймер сессии поверх сцены

Отдельное окно countdown с «Показать игрокам»; на превью — move/resize/rotate, на презентации тот же layout.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-09-03 16:51:56 +08:00
parent a4d7ab77e7
commit db1168a46c
23 changed files with 1313 additions and 3 deletions
+34
View File
@@ -34,6 +34,7 @@ import { LicenseService } from './license/licenseService';
import { readPresentationSettings, writePresentationSettings } from './preferences/appPreferences';
import { MaterialsOverlayStore } from './materials/materialsOverlayStore';
import { NpcsOverlayStore } from './npcs/npcsOverlayStore';
import { TimerSessionStore } from './sessionTimer/timerSessionStore';
import { ZipProjectStore } from './project/zipStore';
import { SceneViewStore } from './sceneView/sceneViewStore';
import { registerDndAssetProtocol } from './protocol/dndAssetProtocol';
@@ -67,6 +68,7 @@ import {
markAppQuitting,
openMaterialsWindow,
openPlaylistsWindow,
openTimerWindow,
openMultiWindow,
openNpcsEditorWindow,
openSceneEditorWindow,
@@ -75,6 +77,7 @@ import {
openSceneDescriptionWindow,
closeMaterialsWindow,
closePlaylistsWindow,
closeTimerWindow,
closeNpcsEditorWindow,
closeSceneEditorWindow,
closeTokenPathEditorWindow,
@@ -178,6 +181,7 @@ const sceneViewStore = new SceneViewStore();
const videoStore = new VideoPlaybackStore();
const materialsOverlayStore = new MaterialsOverlayStore();
const npcsOverlayStore = new NpcsOverlayStore();
const timerSessionStore = new TimerSessionStore();
const sceneTokensSessionStore = new SceneTokensSessionStore();
const tokenPathSessionStore = new TokenPathSessionStore();
const sceneNpcTokensSessionStore = new SceneNpcTokensSessionStore();
@@ -272,6 +276,13 @@ function syncNpcsOverlayWithProject(project: Project | null): void {
npcsOverlayStore.ensureNpcStillExists(ids);
}
function emitSessionTimerState(): void {
const state = timerSessionStore.getState();
for (const win of BrowserWindow.getAllWindows()) {
win.webContents.send(ipcChannels.sessionTimer.stateChanged, { state });
}
}
function emitSceneDarknessState(): void {
const state = sceneDarknessStore.getState();
for (const win of BrowserWindow.getAllWindows()) {
@@ -561,6 +572,7 @@ async function main() {
clearNpcMapSpawnDrag();
scenePlayerTokensSessionStore.reset();
tokenGridSnapSessionStore.reset();
timerSessionStore.clear();
if (USERS_BRANCH_FEATURES_ENABLED) {
const playerIds = Array.isArray(req?.playerIds) ? req.playerIds.map(String).filter(Boolean) : [];
if (playerIds.length > 0) {
@@ -586,11 +598,14 @@ async function main() {
emitScenePlayerTokensSessionState();
emitTokenGridSnapState();
emitEffectsState();
emitSessionTimerState();
return { ok: true };
});
registerHandler(ipcChannels.windows.closeMultiWindow, () => {
closeMultiWindow();
timerSessionStore.clear();
tokenGridSnapSessionStore.reset();
emitSessionTimerState();
emitTokenGridSnapState();
return { ok: true };
});
@@ -646,6 +661,14 @@ async function main() {
closePlaylistsWindow();
return { ok: true };
});
registerHandler(ipcChannels.windows.openTimer, () => {
openTimerWindow();
return { ok: true };
});
registerHandler(ipcChannels.windows.closeTimer, () => {
closeTimerWindow();
return { ok: true };
});
registerHandler(ipcChannels.windows.openNpcsEditor, () => {
openNpcsEditorWindow();
return { ok: true };
@@ -701,6 +724,15 @@ async function main() {
emitNpcsOverlayState();
return { ok: true };
});
registerHandler(ipcChannels.sessionTimer.getState, () => {
return { state: timerSessionStore.getState() };
});
registerHandler(ipcChannels.sessionTimer.dispatch, ({ event }) => {
timerSessionStore.dispatch(event);
timerSessionStore.settleIfExpired();
emitSessionTimerState();
return { state: timerSessionStore.getState() };
});
registerHandler(ipcChannels.project.list, async () => {
const projects = await projectStore.listProjects();
@@ -742,6 +774,7 @@ async function main() {
effectsStore.clear();
materialsOverlayStore.clear();
npcsOverlayStore.clear();
timerSessionStore.clear();
sceneDarknessStore.resetSession();
sceneMarkerStore.resetSession();
sceneTrapsStore.resetSession();
@@ -754,6 +787,7 @@ async function main() {
emitEffectsState();
emitMaterialsOverlayState();
emitNpcsOverlayState();
emitSessionTimerState();
emitSceneDarknessState();
emitSceneMarkerState();
emitSceneTrapsState();