feat(control): 3D-кубики в отдельном окне

Бросок синхронизирован на пульте и презентации; d100 как десятки; оверлей скрывается только кнопкой «Убрать».

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-09-04 13:01:34 +08:00
parent 86d6d157f0
commit 9ac01d217f
137 changed files with 958 additions and 1 deletions
+46
View File
@@ -23,6 +23,7 @@ export type WindowKind =
| 'playlists'
| 'timer'
| 'initiative'
| 'dice'
| 'npcsEditor'
| 'sceneEditor'
| 'tokenPathEditor'
@@ -69,6 +70,10 @@ const TIMER_WINDOW_HEIGHT = 420;
const INITIATIVE_WINDOW_WIDTH = 920;
const INITIATIVE_WINDOW_HEIGHT = 420;
/** Компактное окно броска кубиков. */
const DICE_WINDOW_WIDTH = 380;
const DICE_WINDOW_HEIGHT = 420;
/** Редактор НПС — как основной редактор, шире инспектор. */
const NPCS_EDITOR_WINDOW_WIDTH = 1400;
const NPCS_EDITOR_WINDOW_HEIGHT = 860;
@@ -181,6 +186,7 @@ function closePlaySessionAuxiliaryWindows(): void {
closePlaylistsWindow();
closeTimerWindow();
closeInitiativeWindow();
closeDiceWindow();
closeNpcsWindow();
}
@@ -235,6 +241,8 @@ function pageNameForKind(kind: WindowKind): string {
return 'timer.html';
case 'initiative':
return 'initiative.html';
case 'dice':
return 'dice.html';
case 'npcsEditor':
return 'npcsEditor.html';
case 'sceneEditor':
@@ -313,6 +321,7 @@ function windowSizeForKind(kind: WindowKind): { width: number; height: number }
if (kind === 'playlists') return { width: PLAYLISTS_WINDOW_WIDTH, height: PLAYLISTS_WINDOW_HEIGHT };
if (kind === 'timer') return { width: TIMER_WINDOW_WIDTH, height: TIMER_WINDOW_HEIGHT };
if (kind === 'initiative') return { width: INITIATIVE_WINDOW_WIDTH, height: INITIATIVE_WINDOW_HEIGHT };
if (kind === 'dice') return { width: DICE_WINDOW_WIDTH, height: DICE_WINDOW_HEIGHT };
if (kind === 'npcsEditor') return { width: NPCS_EDITOR_WINDOW_WIDTH, height: NPCS_EDITOR_WINDOW_HEIGHT };
if (kind === 'sceneEditor') return { width: 1280, height: 800 };
if (kind === 'tokenPathEditor') return { width: 1100, height: 760 };
@@ -636,6 +645,13 @@ export function closeInitiativeWindow(): void {
}
}
export function closeDiceWindow(): void {
const win = windows.get('dice');
if (win && !win.isDestroyed()) {
win.close();
}
}
export function closeNpcsEditorWindow(): void {
const win = windows.get('npcsEditor');
if (win && !win.isDestroyed()) {
@@ -890,6 +906,36 @@ export function openInitiativeWindow(opts?: { onClosed?: () => void }): void {
});
}
export function openDiceWindow(): void {
const existing = windows.get('dice');
if (existing && !existing.isDestroyed()) {
if (existing.isMinimized()) existing.restore();
existing.show();
existing.focus();
existing.moveTop();
return;
}
const parent = windows.get('control') ?? windows.get('presentation');
const win = createWindow('dice', parent ? { parent } : undefined);
const { width, height } = win.getBounds();
const display = screen.getDisplayMatching(parent?.getBounds() ?? win.getBounds());
const { x, y, width: dw, height: dh } = display.workArea;
win.setBounds({
x: Math.round(x + Math.max(0, (dw - width) / 2)),
y: Math.round(y + Math.max(0, (dh - height) / 2)),
width,
height,
});
win.webContents.once('did-finish-load', () => {
if (!win.isDestroyed()) {
win.show();
win.focus();
win.moveTop();
}
});
}
function positionNpcsEditorWindow(win: BrowserWindow): void {
const parent = windows.get('editor');
const { width, height } = win.getBounds();