feat(npcs): inspect cards, Show overlay, and optional external link

Clicking an NPC tile opens description cards (multi-select accordion); Show puts avatars on screen. Optional URL opens a webview tab. Avatar pan in the editor commits on pointer up.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-08-19 10:42:57 +08:00
parent 1a9f0f2557
commit 0f4bb76b41
35 changed files with 1189 additions and 259 deletions
@@ -61,6 +61,8 @@ void test('createWindows: окно НПС закрывается с multi-window
assert.ok(src.includes("createWindow('npcs'"));
assert.ok(src.includes("createWindow('npcsEditor'"));
assert.match(src, /export function closeMultiWindow[\s\S]*closeNpcsWindow/);
assert.ok(src.includes('webviewTag: true'));
assert.ok(src.includes('will-attach-webview'));
});
void test('createWindows: закрытие пульта закрывает сессионные окна и презентацию', () => {
+27 -7
View File
@@ -54,9 +54,9 @@ const MATERIALS_WINDOW_HEIGHT = 720;
const NPCS_EDITOR_WINDOW_WIDTH = 1400;
const NPCS_EDITOR_WINDOW_HEIGHT = 860;
/** Пульт НПС: деталь слева + список справа. */
const NPCS_WINDOW_WIDTH = 720;
const NPCS_WINDOW_HEIGHT = 720;
/** Пульт НПС: деталь слева (webview) + список справа. */
const NPCS_WINDOW_WIDTH = 960;
const NPCS_WINDOW_HEIGHT = 800;
/** Учитываем окна, которые уже уничтожены при каскадном закрытии (родитель → дочернее). */
function broadcastMultiWindowStateChanged(open: boolean): void {
@@ -340,9 +340,9 @@ function createWindow(kind: WindowKind, opts?: CreateWindowOpts): BrowserWindow
? {
width: NPCS_WINDOW_WIDTH,
height: NPCS_WINDOW_HEIGHT,
minWidth: 560,
maxWidth: 900,
minHeight: 480,
minWidth: 720,
maxWidth: 1280,
minHeight: 560,
}
: {}),
show: false,
@@ -358,6 +358,13 @@ function createWindow(kind: WindowKind, opts?: CreateWindowOpts): BrowserWindow
autoplayPolicy: 'no-user-gesture-required',
// file:// + бандл Vite: без этого на Windows часто не грузятся чанки; http:// (dev server) оставляем строгим.
webSecurity: Boolean(process.env.VITE_DEV_SERVER_URL),
...(kind === 'npcs'
? {
webviewTag: true,
// webviewTag в Electron не сочетается с sandbox хоста.
sandbox: false,
}
: {}),
},
});
if (icon) {
@@ -369,6 +376,19 @@ function createWindow(kind: WindowKind, opts?: CreateWindowOpts): BrowserWindow
}
bindWindowChromeTitle(win, kind);
if (kind === 'npcs') {
win.webContents.on('will-attach-webview', (event, guestPrefs, params) => {
delete guestPrefs.preload;
guestPrefs.nodeIntegration = false;
guestPrefs.contextIsolation = true;
guestPrefs.sandbox = true;
guestPrefs.webSecurity = true;
const src = String(params.src ?? '');
if (!src.startsWith('https:') && !src.startsWith('http:')) {
event.preventDefault();
}
});
}
if (
kind === 'sceneDescription' ||
kind === 'materials' ||
@@ -799,7 +819,7 @@ export function openNpcsWindow(): void {
existing.setBounds({
x: b.x,
y: b.y,
width: Math.min(NPCS_WINDOW_WIDTH, Math.max(560, b.width)),
width: Math.min(1280, Math.max(720, b.width)),
height: Math.max(NPCS_WINDOW_HEIGHT, b.height),
});
existing.show();