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:
@@ -14,6 +14,13 @@ import type {
|
||||
VideoPlaybackEvent,
|
||||
VideoPlaybackState,
|
||||
} from '../types';
|
||||
import type {
|
||||
SceneImportResolution,
|
||||
StorylineImportMergeReport,
|
||||
StorylineLabels,
|
||||
StorylineListItem,
|
||||
StorylineSelection,
|
||||
} from '../graph/storylineExportImport';
|
||||
|
||||
export const ipcChannels = {
|
||||
app: {
|
||||
@@ -48,10 +55,20 @@ export const ipcChannels = {
|
||||
addSceneGraphEdge: 'project.addSceneGraphEdge',
|
||||
removeSceneGraphEdge: 'project.removeSceneGraphEdge',
|
||||
setSceneGraphNodeStart: 'project.setSceneGraphNodeStart',
|
||||
setSceneGraphNodeSideStoryStart: 'project.setSceneGraphNodeSideStoryStart',
|
||||
updateSideStoryLineTitle: 'project.updateSideStoryLineTitle',
|
||||
deleteScene: 'project.deleteScene',
|
||||
rename: 'project.rename',
|
||||
importZip: 'project.importZip',
|
||||
exportZip: 'project.exportZip',
|
||||
getProjectStorylines: 'project.getProjectStorylines',
|
||||
peekImportZip: 'project.peekImportZip',
|
||||
pickImportZipFile: 'project.pickImportZipFile',
|
||||
peekImportZipPath: 'project.peekImportZipPath',
|
||||
peekImportFromProject: 'project.peekImportFromProject',
|
||||
mergeImportZip: 'project.mergeImportZip',
|
||||
mergeImportFromProject: 'project.mergeImportFromProject',
|
||||
importZipFromPath: 'project.importZipFromPath',
|
||||
deleteProject: 'project.deleteProject',
|
||||
importZipProgress: 'project.importZipProgress',
|
||||
exportZipProgress: 'project.exportZipProgress',
|
||||
@@ -249,6 +266,14 @@ export type IpcInvokeMap = {
|
||||
req: { graphNodeId: GraphNodeId | null };
|
||||
res: { project: Project };
|
||||
};
|
||||
[ipcChannels.project.setSceneGraphNodeSideStoryStart]: {
|
||||
req: { graphNodeId: GraphNodeId };
|
||||
res: { project: Project };
|
||||
};
|
||||
[ipcChannels.project.updateSideStoryLineTitle]: {
|
||||
req: { graphNodeId: GraphNodeId; title: string };
|
||||
res: { project: Project };
|
||||
};
|
||||
[ipcChannels.project.deleteScene]: {
|
||||
req: { sceneId: SceneId };
|
||||
res: { project: Project };
|
||||
@@ -261,8 +286,66 @@ export type IpcInvokeMap = {
|
||||
req: Record<string, never>;
|
||||
res: { canceled: true } | { canceled: false; project: Project };
|
||||
};
|
||||
[ipcChannels.project.getProjectStorylines]: {
|
||||
req: { projectId: ProjectId; labels: StorylineLabels };
|
||||
res: { storylines: StorylineListItem[] };
|
||||
};
|
||||
[ipcChannels.project.peekImportZip]: {
|
||||
req: { labels: StorylineLabels; targetHasMainStart: boolean };
|
||||
res:
|
||||
| { canceled: true }
|
||||
| {
|
||||
canceled: false;
|
||||
filePath: string;
|
||||
projectName: string;
|
||||
storylines: StorylineListItem[];
|
||||
sourceProject: Project;
|
||||
};
|
||||
};
|
||||
[ipcChannels.project.pickImportZipFile]: {
|
||||
req: Record<string, never>;
|
||||
res: { canceled: true } | { canceled: false; filePath: string };
|
||||
};
|
||||
[ipcChannels.project.peekImportZipPath]: {
|
||||
req: { filePath: string; labels: StorylineLabels; targetHasMainStart: boolean };
|
||||
res: {
|
||||
filePath: string;
|
||||
projectName: string;
|
||||
storylines: StorylineListItem[];
|
||||
sourceProject: Project;
|
||||
};
|
||||
};
|
||||
[ipcChannels.project.peekImportFromProject]: {
|
||||
req: { sourceProjectId: ProjectId; labels: StorylineLabels; targetHasMainStart: boolean };
|
||||
res: {
|
||||
sourceProjectId: ProjectId;
|
||||
projectName: string;
|
||||
storylines: StorylineListItem[];
|
||||
sourceProject: Project;
|
||||
};
|
||||
};
|
||||
[ipcChannels.project.mergeImportZip]: {
|
||||
req: {
|
||||
filePath: string;
|
||||
storylineSelections: StorylineSelection[];
|
||||
sceneResolutions: SceneImportResolution[];
|
||||
};
|
||||
res: { project: Project; report: StorylineImportMergeReport };
|
||||
};
|
||||
[ipcChannels.project.mergeImportFromProject]: {
|
||||
req: {
|
||||
sourceProjectId: ProjectId;
|
||||
storylineSelections: StorylineSelection[];
|
||||
sceneResolutions: SceneImportResolution[];
|
||||
};
|
||||
res: { project: Project; report: StorylineImportMergeReport };
|
||||
};
|
||||
[ipcChannels.project.importZipFromPath]: {
|
||||
req: { filePath: string };
|
||||
res: { project: Project };
|
||||
};
|
||||
[ipcChannels.project.exportZip]: {
|
||||
req: { projectId: ProjectId };
|
||||
req: { projectId: ProjectId; storylineSelections: StorylineSelection[]; labels: StorylineLabels };
|
||||
res: { canceled: true } | { canceled: false };
|
||||
};
|
||||
[ipcChannels.project.deleteProject]: {
|
||||
|
||||
Reference in New Issue
Block a user