feat: кубики/таймер/кисть/спонсоры и задел игровых систем

Показ анимации кубиков игрокам, закрытие оверлеев с окнами, ручной ввод количества, меньший радиус кисти, блок спонсоров в «О программе»; плюс черновик game systems / character sheet.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-09-13 15:37:44 +08:00
parent 3d0474fe97
commit 712086ddca
32 changed files with 1712 additions and 34 deletions
+37 -2
View File
@@ -1,6 +1,6 @@
import path from 'node:path';
import { app, BrowserWindow, screen } from 'electron';
import { app, BrowserWindow, screen, shell } from 'electron';
import type { PresentationSettings } from '../../shared/types/presentationSettings';
import { readPresentationSettings } from '../preferences/appPreferences';
@@ -66,13 +66,27 @@ const PLAYLISTS_WINDOW_HEIGHT = 800;
const TIMER_WINDOW_WIDTH = 360;
const TIMER_WINDOW_HEIGHT = 420;
/** Колбэк при закрытии окна таймера (Esc / крестик / programmatic). */
let timerWindowClosedListener: (() => void) | null = null;
export function setTimerWindowClosedListener(listener: (() => void) | null): void {
timerWindowClosedListener = listener;
}
/** Окно трекера инициативы. */
const INITIATIVE_WINDOW_WIDTH = 920;
const INITIATIVE_WINDOW_HEIGHT = 420;
/** Компактное окно броска кубиков. */
const DICE_WINDOW_WIDTH = 380;
const DICE_WINDOW_HEIGHT = 420;
const DICE_WINDOW_HEIGHT = 460;
/** Колбэк при закрытии окна кубиков (Esc / крестик / programmatic). */
let diceWindowClosedListener: (() => void) | null = null;
export function setDiceWindowClosedListener(listener: (() => void) | null): void {
diceWindowClosedListener = listener;
}
/** Редактор НПС — как основной редактор, шире инспектор. */
const NPCS_EDITOR_WINDOW_WIDTH = 1400;
@@ -452,6 +466,17 @@ function createWindow(kind: WindowKind, opts?: CreateWindowOpts): BrowserWindow
win.webContents.on('preload-error', (_event, preloadPath, error) => {
safeConsoleError(`[preload-error] ${preloadPath}:`, error);
});
win.webContents.setWindowOpenHandler(({ url }) => {
try {
const parsed = new URL(url);
if (parsed.protocol === 'http:' || parsed.protocol === 'https:') {
void shell.openExternal(parsed.toString());
}
} catch {
/* ignore invalid urls */
}
return { action: 'deny' };
});
win.webContents.on('did-fail-load', (_event, errorCode, errorDescription, validatedURL, isMainFrame) => {
// -3 ERR_ABORTED: частый артефакт при navigate/maximize/закрытии — не шумим.
if (errorCode === -3) return;
@@ -491,6 +516,16 @@ function createWindow(kind: WindowKind, opts?: CreateWindowOpts): BrowserWindow
});
}
win.on('closed', () => windows.delete(kind));
if (kind === 'timer') {
win.on('closed', () => {
timerWindowClosedListener?.();
});
}
if (kind === 'dice') {
win.on('closed', () => {
diceWindowClosedListener?.();
});
}
if (kind === 'sceneEditor') {
win.on('closed', () => {
closeTokenPathEditorWindow();