feat(editor): побочные сюжетные линии и импорт/экспорт линий

Добавлена полноценная поддержка побочных сюжетных линий в редакторе и на
пульте: визуальное выделение компонент графа, запрет недопустимых связей
между основной и побочными линиями, метки «ПОБОЧНАЯ» и названия линий.

Реализован partial export/import сюжетных линий:
- экспорт выбранных линий в урезанный .ttrpg.zip с manifest в project.json;
- импорт в открытый проект с выбором линий, разрешением конфликтов названий
  сцен (одна модалка на операцию) и отчётом о результате;
- новое окно источника импорта: «Из проекта» (dropdown, без текущего) или
  «Из файла»; на главном экране — только полный импорт из файла.

Исправлены гонки при открытии/закрытии проекта (сериализация open/close в
main, сброс зависших состояний UI), залипание оверлея прогресса экспорта и
старт Electron в dev после готовности Vite.

Добавлены unit-тесты для lineage, export/import и контрактов zipStore.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-07-09 14:47:28 +08:00
parent de9190959c
commit c8ab9dd567
26 changed files with 3507 additions and 266 deletions
+15 -1
View File
@@ -1,6 +1,6 @@
import type { AssetId, GraphNodeId, ProjectId, SceneId } from './ids';
export const PROJECT_SCHEMA_VERSION = 4 as const;
export const PROJECT_SCHEMA_VERSION = 5 as const;
export type IsoDateTimeString = string;
@@ -69,6 +69,10 @@ export type SceneGraphNode = {
y: number;
/** Ровно один узел в проекте может быть начальной сценой для входа в граф. */
isStartScene: boolean;
/** Стартовый узел побочной сюжетной линии (синяя метка «ПОБОЧНАЯ»). */
isSideStoryStart: boolean;
/** Название побочной линии; только на узле с `isSideStoryStart`. */
sideStoryLineTitle: string;
};
export type SceneGraphEdge = {
@@ -98,6 +102,14 @@ export type Scene = {
layout: SceneLayout;
};
/** Запись о сюжетной линии в partial-экспорте (manifest в project.json). */
export type ExportedStorylineRef = {
kind: 'main' | 'side';
/** Для побочной линии — id стартового узла графа в этом архиве. */
startGraphNodeId: GraphNodeId | null;
label: string;
};
export type ProjectMeta = {
name: string;
/** Имя файла проекта без суффикса `.ttrpg.zip` (то, что пользователь редактирует). */
@@ -124,4 +136,6 @@ export type Project = {
/** Позиции карточек на графе; логические связи сцен по-прежнему в `Scene.connections`. */
sceneGraphNodes: SceneGraphNode[];
sceneGraphEdges: SceneGraphEdge[];
/** При partial-экспорте: какие сюжетные линии включены в архив. */
exportedStorylines?: ExportedStorylineRef[];
};