feat: multi-material overlays, presentation guides, and release prebuilds

Align control/presentation with presentation screen rect and darkness z-order; sync window titles and session window cleanup. Pack Win/Mac/Linux with npmRebuild disabled and release-native-prep for classic-level and sharp.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-08-03 11:28:18 +08:00
parent 61446dacfc
commit 2979d06f1c
41 changed files with 1060 additions and 252 deletions
+73 -1
View File
@@ -3,6 +3,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { ipcChannels, type SessionState } from '../../shared/ipc/contracts';
import { buildNpcGroupForest, type NpcGroupTreeNode } from '../../shared/npcs/npcGroups';
import type { NpcGroupId, NpcId, ProjectNpc } from '../../shared/types';
import matStyles from '../editor/MaterialsModals.module.css';
import { useEditorI18n } from '../editor/i18n/EditorI18nContext';
import { sanitizeSceneDescriptionHtml } from '../editor/sceneDescriptionHtml';
import { getDndApi } from '../shared/dndApi';
@@ -12,6 +13,32 @@ import { useAssetUrl } from '../shared/useAssetImageUrl';
import styles from './NpcsApp.module.css';
function ZoomInIcon() {
return (
<svg viewBox="0 0 24 24" width="20" height="20" aria-hidden>
<circle cx="10.5" cy="10.5" r="6.25" fill="none" stroke="currentColor" strokeWidth="1.8" />
<path d="M15.2 15.2 20 20" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" />
<path
d="M10.5 7.8v5.4M7.8 10.5h5.4"
fill="none"
stroke="currentColor"
strokeWidth="1.8"
strokeLinecap="round"
/>
</svg>
);
}
function ZoomOutIcon() {
return (
<svg viewBox="0 0 24 24" width="20" height="20" aria-hidden>
<circle cx="10.5" cy="10.5" r="6.25" fill="none" stroke="currentColor" strokeWidth="1.8" />
<path d="M15.2 15.2 20 20" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" />
<path d="M7.8 10.5h5.4" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" />
</svg>
);
}
/** Убрать пустые ветки групп (удобно при поиске). */
function pruneEmptyGroupNodes(nodes: NpcGroupTreeNode[]): NpcGroupTreeNode[] {
const out: NpcGroupTreeNode[] = [];
@@ -137,12 +164,18 @@ export function NpcsApp() {
void overlayApi.dispatch({ kind: 'hide' });
return;
}
if (overlay?.zoomTool) {
void overlayApi.dispatch({ kind: 'zoomTool.set', tool: null });
return;
}
window.close();
}
};
window.addEventListener('keydown', onKey);
return () => window.removeEventListener('keydown', onKey);
}, [hasActive, overlayApi]);
}, [hasActive, overlay?.zoomTool, overlayApi]);
const zoomTool = overlay?.zoomTool ?? null;
const npcs = useMemo(() => session?.project?.npcs ?? [], [session?.project?.npcs]);
const npcGroups = useMemo(() => session?.project?.npcGroups ?? [], [session?.project?.npcGroups]);
@@ -215,6 +248,45 @@ export function NpcsApp() {
return (
<div className={styles.page}>
<div className={styles.toolbar}>
<div className={matStyles.browserToolbarRow}>
<Button
variant={zoomTool === 'zoomIn' ? 'primary' : 'ghost'}
iconOnly
title={t('npcs.zoomIn')}
ariaLabel={t('npcs.zoomIn')}
tooltipPlacement="bottom"
onClick={() => {
void overlayApi.dispatch({
kind: 'zoomTool.set',
tool: zoomTool === 'zoomIn' ? null : 'zoomIn',
});
}}
>
<ZoomInIcon />
</Button>
<Button
variant={zoomTool === 'zoomOut' ? 'primary' : 'ghost'}
iconOnly
title={t('npcs.zoomOut')}
ariaLabel={t('npcs.zoomOut')}
tooltipPlacement="bottom"
onClick={() => {
void overlayApi.dispatch({
kind: 'zoomTool.set',
tool: zoomTool === 'zoomOut' ? null : 'zoomOut',
});
}}
>
<ZoomOutIcon />
</Button>
</div>
<div className={matStyles.browserToolbarHint}>
{zoomTool === 'zoomIn'
? t('npcs.zoomInHint')
: zoomTool === 'zoomOut'
? t('npcs.zoomOutHint')
: t('npcs.zoomIdleHint')}
</div>
<div className={styles.toolbarRow}>
<Button
title={t('npcs.closeOverlay')}