fix(tokens): импорт неигровых токенов из zip и плавные пути на macOS

Полный импорт/открытие проекта подхватывает app-tokens. Движение по путям пишется в DOM без ре-рендера кадра, у пульта и презентации выключен background throttling.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-09-07 16:16:30 +08:00
parent 586166cacd
commit fdccc0deb5
16 changed files with 285 additions and 111 deletions
+22 -3
View File
@@ -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();