fix(tokens): импорт неигровых токенов из zip и плавные пути на macOS
Полный импорт/открытие проекта подхватывает app-tokens. Движение по путям пишется в DOM без ре-рендера кадра, у пульта и презентации выключен background throttling. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+22
-3
@@ -21,6 +21,7 @@ import {
|
||||
import type { Project } from '../shared/types';
|
||||
import { asNpcId } from '../shared/types/ids';
|
||||
import { listVisibleSceneNpcTokens } from '../shared/types/appPlayers';
|
||||
import { remapProjectSceneTokenIds } from '../shared/graph/storylineExportImport';
|
||||
import {
|
||||
asPreviewRotationDeg,
|
||||
previewRotationStepsCw,
|
||||
@@ -390,6 +391,18 @@ function emitTokensState(): void {
|
||||
}
|
||||
}
|
||||
|
||||
/** Подтянуть `app-tokens/` из открытого кэша (экспорт сюжетной линии / полный zip). */
|
||||
async function importAppTokensFromOpenProjectCache(): Promise<Project | null> {
|
||||
const cacheDir = projectStore.getOpenCacheDir();
|
||||
if (!tokensStore || !cacheDir) return null;
|
||||
const remap = await tokensStore.importFromExportDir(cacheDir);
|
||||
if (remap.size === 0) return null;
|
||||
emitTokensState();
|
||||
const needsRemap = [...remap.entries()].some(([from, to]) => from !== to);
|
||||
if (!needsRemap) return null;
|
||||
return projectStore.updateProject((p) => remapProjectSceneTokenIds(p, remap));
|
||||
}
|
||||
|
||||
function emitSceneTokensSessionState(): void {
|
||||
const state = sceneTokensSessionStore.getState();
|
||||
for (const win of BrowserWindow.getAllWindows()) {
|
||||
@@ -882,7 +895,9 @@ async function main() {
|
||||
return { project };
|
||||
});
|
||||
registerHandler(ipcChannels.project.open, async ({ projectId }) => {
|
||||
const project = await projectStore.openProjectById(projectId);
|
||||
let project = await projectStore.openProjectById(projectId);
|
||||
const remapped = await importAppTokensFromOpenProjectCache();
|
||||
if (remapped) project = remapped;
|
||||
sceneViewStore.reset();
|
||||
sceneTokensSessionStore.reset();
|
||||
sceneNpcTokensSessionStore.reset();
|
||||
@@ -1445,7 +1460,7 @@ async function main() {
|
||||
}
|
||||
const srcPath = filePaths[0];
|
||||
emitZipProgress({ kind: 'import', stage: 'copy', percent: 0, detail: 'Копирование…' });
|
||||
const project = await projectStore.importProjectFromExternalZip(srcPath, (p) => {
|
||||
let project = await projectStore.importProjectFromExternalZip(srcPath, (p) => {
|
||||
emitZipProgress({
|
||||
kind: 'import',
|
||||
stage: p.stage,
|
||||
@@ -1454,6 +1469,8 @@ async function main() {
|
||||
});
|
||||
});
|
||||
const cacheDir = projectStore.getOpenCacheDir();
|
||||
const remapped = await importAppTokensFromOpenProjectCache();
|
||||
if (remapped) project = remapped;
|
||||
if (playlistsStore && cacheDir) {
|
||||
await playlistsStore.importFromExportDir(cacheDir);
|
||||
emitPlaylistsState();
|
||||
@@ -1564,7 +1581,7 @@ async function main() {
|
||||
);
|
||||
registerHandler(ipcChannels.project.importZipFromPath, async ({ filePath }) => {
|
||||
emitZipProgress({ kind: 'import', stage: 'copy', percent: 0, detail: 'Копирование…' });
|
||||
const project = await projectStore.importProjectFromExternalZip(filePath, (p) => {
|
||||
let project = await projectStore.importProjectFromExternalZip(filePath, (p) => {
|
||||
emitZipProgress({
|
||||
kind: 'import',
|
||||
stage: p.stage,
|
||||
@@ -1573,6 +1590,8 @@ async function main() {
|
||||
});
|
||||
});
|
||||
const cacheDir = projectStore.getOpenCacheDir();
|
||||
const remapped = await importAppTokensFromOpenProjectCache();
|
||||
if (remapped) project = remapped;
|
||||
if (playlistsStore && cacheDir) {
|
||||
await playlistsStore.importFromExportDir(cacheDir);
|
||||
emitPlaylistsState();
|
||||
|
||||
@@ -122,6 +122,23 @@ void test('main index: setCurrentGraphNode uses setSessionPointer not updateProj
|
||||
assert.doesNotMatch(m[0], /updateProject/);
|
||||
});
|
||||
|
||||
void test('main index: full zip import loads app-tokens like playlists', () => {
|
||||
const src = fs.readFileSync(path.join(here, '..', 'index.ts'), 'utf8');
|
||||
assert.match(src, /async function importAppTokensFromOpenProjectCache/);
|
||||
assert.match(src, /tokensStore\.importFromExportDir\(cacheDir\)/);
|
||||
assert.match(src, /remapProjectSceneTokenIds/);
|
||||
const importZip = src.match(
|
||||
/registerHandler\(ipcChannels\.project\.importZip,[\s\S]*?return \{ canceled: false as const, project \}/,
|
||||
);
|
||||
assert.ok(importZip);
|
||||
assert.match(importZip[0], /importAppTokensFromOpenProjectCache/);
|
||||
const importFromPath = src.match(
|
||||
/registerHandler\(ipcChannels\.project\.importZipFromPath,[\s\S]*?return \{ project \}/,
|
||||
);
|
||||
assert.ok(importFromPath);
|
||||
assert.match(importFromPath[0], /importAppTokensFromOpenProjectCache/);
|
||||
});
|
||||
|
||||
void test('atomicReplace: replaceFileAtomic must not rm destination before successful commit', () => {
|
||||
const src = fs.readFileSync(path.join(here, 'atomicReplace.ts'), 'utf8');
|
||||
const i = src.indexOf('export async function replaceFileAtomic');
|
||||
|
||||
@@ -113,3 +113,10 @@ void test('createWindows: логи окон не валят main через EPIP
|
||||
assert.ok(src.includes('errorCode === -3'));
|
||||
assert.ok(src.includes('isMainFrame'));
|
||||
});
|
||||
|
||||
void test('createWindows: presentation/control не троттлят RAF в фоне (macOS paths)', () => {
|
||||
const src = readCreateWindows();
|
||||
assert.match(src, /kind === 'presentation' \|\| kind === 'control'/);
|
||||
assert.match(src, /backgroundThrottling:\s*false/);
|
||||
assert.match(src, /setBackgroundThrottling\(false\)/);
|
||||
});
|
||||
|
||||
@@ -398,6 +398,10 @@ 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),
|
||||
// macOS throttles RAF in unfocused windows — token paths and video stutter on presentation.
|
||||
...(kind === 'presentation' || kind === 'control' || kind === 'tokenPathEditor'
|
||||
? { backgroundThrottling: false }
|
||||
: {}),
|
||||
...(kind === 'npcs'
|
||||
? {
|
||||
webviewTag: true,
|
||||
@@ -416,6 +420,9 @@ function createWindow(kind: WindowKind, opts?: CreateWindowOpts): BrowserWindow
|
||||
}
|
||||
|
||||
bindWindowChromeTitle(win, kind);
|
||||
if (kind === 'presentation' || kind === 'control' || kind === 'tokenPathEditor') {
|
||||
win.webContents.setBackgroundThrottling(false);
|
||||
}
|
||||
if (kind === 'npcs') {
|
||||
win.webContents.on('will-attach-webview', (event, guestPrefs, params) => {
|
||||
delete guestPrefs.preload;
|
||||
|
||||
Reference in New Issue
Block a user