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
+47
View File
@@ -62,6 +62,7 @@ import { MaterialEditModal, MaterialsManagerModal } from './MaterialsModals';
import { PlayersManagerModal } from './PlayersModals';
import { LaunchPlayersModal } from './LaunchPlayersModal';
import { PresentationSettingsModal } from './PresentationSettingsModal';
import { GameSystemsModal } from './GameSystemsModal';
import { PlaylistsManagerModal } from './PlaylistsModal';
import { isSceneDescriptionEmpty, sanitizeSceneDescriptionHtml } from './sceneDescriptionHtml';
import { SceneDescriptionModal } from './SceneDescriptionModal';
@@ -166,10 +167,28 @@ export function EditorApp() {
const [launchPlayersOpen, setLaunchPlayersOpen] = useState(false);
const [playlistsManagerOpen, setPlaylistsManagerOpen] = useState(false);
const [presentationSettingsOpen, setPresentationSettingsOpen] = useState(false);
const [gameSystemsOpen, setGameSystemsOpen] = useState(false);
const [runMenuOpen, setRunMenuOpen] = useState(false);
const [runMenuPos, setRunMenuPos] = useState<{ left: number; top: number } | null>(null);
const runSplitRef = useRef<HTMLDivElement | null>(null);
const [materialEdit, setMaterialEdit] = useState<ProjectMaterial | null | 'new'>(null);
const listGameSystems = useCallback(async () => {
const res = await getDndApi().invoke(ipcChannels.gameSystems.list, {});
return res.systems;
}, []);
const importGameSystem = useCallback(async () => {
const res = await getDndApi().invoke(ipcChannels.gameSystems.importFromFile, {});
return res.canceled ? null : res.system;
}, []);
const deleteGameSystem = useCallback(async (id: string) => {
await getDndApi().invoke(ipcChannels.gameSystems.delete, { id });
}, []);
const previewGameSystem = useCallback(async (id: string) => {
console.log('[gameSystems] preview stub', id);
window.alert(`Preview (stub): ${id}`);
}, []);
const onProjectNotice = useCallback(
(code: ProjectNoticeCode) => {
const handlers: Record<ProjectNoticeCode, () => void> = {
@@ -1360,6 +1379,18 @@ export function EditorApp() {
>
{t('menu.presentationSettings')}
</button>
<button
type="button"
role="menuitem"
className={styles.fileMenuItem}
onClick={() => {
setSettingsMenuOpen(false);
setSettingsLangSubOpen(false);
setGameSystemsOpen(true);
}}
>
{t('menu.gameSystems')}
</button>
<div className={styles.fileMenuSubHost} role="presentation">
<button
type="button"
@@ -1682,6 +1713,22 @@ export function EditorApp() {
open={presentationSettingsOpen}
onClose={() => setPresentationSettingsOpen(false)}
/>
<GameSystemsModal
open={gameSystemsOpen}
onClose={() => setGameSystemsOpen(false)}
labels={{
title: t('gameSystems.title'),
import: t('gameSystems.import'),
preview: t('gameSystems.preview'),
remove: t('gameSystems.remove'),
empty: t('gameSystems.empty'),
close: t('gameSystems.close'),
}}
listSystems={listGameSystems}
importSystem={importGameSystem}
deleteSystem={deleteGameSystem}
previewSystem={previewGameSystem}
/>
<PlaylistsManagerModal
open={playlistsManagerOpen}
onClose={() => setPlaylistsManagerOpen(false)}