feat(tokens): app-local non-player tokens with session moves and UI polish

Add token library/placements, keep play-time moves for the session, lock presentation interactions, and fix export/import modal layout plus freeform trap label.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-07-27 11:04:12 +08:00
parent bdeb64e356
commit f270812219
44 changed files with 2617 additions and 341 deletions
+34 -1
View File
@@ -11,7 +11,7 @@ import type {
SceneGraphNode,
SceneId,
} from '../types';
import type { AssetId, NpcGroupId, ProjectId } from '../types/ids';
import type { AssetId, NpcGroupId, ProjectId, TokenId } from '../types/ids';
import {
asAssetId,
asGraphNodeId,
@@ -21,6 +21,7 @@ import {
asNpcRelationId,
asProjectId,
asSceneId,
asTokenId,
} from '../types/ids';
import {
@@ -812,3 +813,35 @@ function remapNpcBinding(
export function newExportBundleProjectId(): ProjectId {
return asProjectId(`p_${generateId()}`);
}
/** Id app-токенов, реально стоящих на сценах проекта (для экспорта с линией). */
export function collectTokenIdsFromProject(project: Project): TokenId[] {
const ids = new Set<string>();
for (const scene of Object.values(project.scenes)) {
for (const t of scene.tokens ?? []) {
if (t.tokenId) ids.add(t.tokenId);
}
}
return [...ids].map((id) => asTokenId(id));
}
/** Переписать tokenId на сценах после импорта в app-пул. */
export function remapProjectSceneTokenIds(
project: Project,
remap: ReadonlyMap<string, string>,
): Project {
if (remap.size === 0) return project;
const scenes: Record<SceneId, Scene> = { ...project.scenes };
for (const sid of Object.keys(scenes) as SceneId[]) {
const scene = scenes[sid];
if (!scene?.tokens?.length) continue;
scenes[sid] = {
...scene,
tokens: scene.tokens.map((t) => ({
...t,
tokenId: asTokenId(remap.get(t.tokenId) ?? t.tokenId),
})),
};
}
return { ...project, scenes };
}