fix(control): improve storyline history and graph launch

Append history on revisit instead of truncating, highlight the latest duplicate entry, add Start from this scene to the graph context menu, and keep storyline state isolated from project graph edits.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-07-02 19:14:01 +08:00
parent 657e3c862e
commit ca0cf9c0ce
6 changed files with 84 additions and 24 deletions
+15 -7
View File
@@ -9,7 +9,7 @@ import {
import { EULA_CURRENT_VERSION } from '../../shared/license/eulaVersion';
import type { LicenseSnapshot } from '../../shared/license/licenseSnapshot';
import { PROJECT_ZIP_EXTENSION } from '../../shared/project/projectZipExtension';
import type { AssetId, MediaAsset, Project, ProjectId, SceneAudioRef, SceneId } from '../../shared/types';
import type { AssetId, GraphNodeId, MediaAsset, Project, ProjectId, SceneAudioRef, SceneId } from '../../shared/types';
import { AppLogo } from '../shared/branding/AppLogo';
import { getDndApi } from '../shared/dndApi';
import { RotatedImage } from '../shared/RotatedImage';
@@ -122,6 +122,7 @@ export function EditorApp() {
closeMenu: t('common.closeMenu'),
startScene: t('graph.startScene'),
unsetStartScene: t('graph.unsetStartScene'),
runFromScene: t('graph.runFromScene'),
delete: t('common.delete'),
}),
[t],
@@ -204,6 +205,17 @@ export function EditorApp() {
return gn?.id ?? null;
}, [state.project]);
const launchFromGraphNode = useCallback(
(graphNodeId: GraphNodeId) => {
if (!licenseActive) return;
void (async () => {
await getDndApi().invoke(ipcChannels.project.setCurrentGraphNode, { graphNodeId });
await getDndApi().invoke(ipcChannels.windows.openMultiWindow, {});
})();
},
[licenseActive],
);
const currentProjectName = state.project?.meta.name ?? '';
const currentFileBaseName = state.project?.meta.fileBaseName ?? '';
const existingProjectNames = useMemo(() => state.projects.map((p) => p.name), [state.projects]);
@@ -501,12 +513,7 @@ export function EditorApp() {
}
onClick={() => {
if (!licenseActive || !graphStartGraphNodeId) return;
void (async () => {
await getDndApi().invoke(ipcChannels.project.setCurrentGraphNode, {
graphNodeId: graphStartGraphNodeId,
});
await getDndApi().invoke(ipcChannels.windows.openMultiWindow, {});
})();
launchFromGraphNode(graphStartGraphNodeId);
}}
>
{t('top.run')}
@@ -570,6 +577,7 @@ export function EditorApp() {
}}
onRemoveGraphNode={(id) => void actions.removeSceneGraphNode(id)}
onSetGraphNodeStart={(graphNodeId) => void actions.setSceneGraphNodeStart(graphNodeId)}
onRunFromGraphNode={launchFromGraphNode}
onDropSceneFromList={(sceneId, x, y) => void actions.addSceneGraphNode(sceneId, x, y)}
/>
) : (