feat(scenes): add scene darkness reveal and fix project reopen crash

Add darkenScene with Opening brush in presentation, persist reveal strokes per scene during a session, and close projects properly on return to home. Harden dnd asset streaming to avoid Windows main-process crashes when reopening projects.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-07-02 19:41:26 +08:00
parent d54e9ed02d
commit 4631a1bece
19 changed files with 526 additions and 11 deletions
+50
View File
@@ -1,6 +1,7 @@
import { app, BrowserWindow, dialog, Menu, protocol } from 'electron';
import { ipcChannels, type SessionState } from '../shared/ipc/contracts';
import type { Project } from '../shared/types';
import {
PROJECT_ZIP_OPEN_DIALOG_FILTER,
PROJECT_ZIP_SAVE_DIALOG_FILTER,
@@ -11,6 +12,7 @@ import {
} from '../shared/project/projectZipExtension';
import { EffectsStore } from './effects/effectsStore';
import { SceneDarknessStore } from './effects/sceneDarknessStore';
import { installIpcRouter, registerHandler, setLicenseAssert } from './ipc/router';
import { LicenseService } from './license/licenseService';
import { ZipProjectStore } from './project/zipStore';
@@ -117,6 +119,7 @@ function installAppMenuForSession(): void {
}
const effectsStore = new EffectsStore();
const sceneDarknessStore = new SceneDarknessStore();
const videoStore = new VideoPlaybackStore();
function emitEffectsState(): void {
@@ -126,6 +129,20 @@ function emitEffectsState(): void {
}
}
function emitSceneDarknessState(): void {
const state = sceneDarknessStore.getState();
for (const win of BrowserWindow.getAllWindows()) {
win.webContents.send(ipcChannels.sceneDarkness.stateChanged, { state });
}
}
function syncSceneDarknessForProject(project: Project): void {
const cacheKey = project.currentGraphNodeId ?? project.currentSceneId ?? null;
const scene = project.currentSceneId ? project.scenes[project.currentSceneId] : undefined;
const enabled = Boolean(scene?.darkenScene) && scene?.previewAssetType === 'image';
sceneDarknessStore.switchScene(cacheKey, enabled);
}
function emitVideoState(): void {
const state = videoStore.getState();
for (const win of BrowserWindow.getAllWindows()) {
@@ -267,7 +284,11 @@ async function main() {
registerHandler(ipcChannels.license.clearToken, () => licenseService.clearToken());
registerHandler(ipcChannels.license.acceptEula, ({ version }) => licenseService.acceptEula(version));
registerHandler(ipcChannels.windows.openMultiWindow, () => {
sceneDarknessStore.resetSession();
openMultiWindow();
const project = projectStore.getOpenProject();
if (project) syncSceneDarknessForProject(project);
emitSceneDarknessState();
return { ok: true };
});
registerHandler(ipcChannels.windows.closeMultiWindow, () => {
@@ -303,6 +324,15 @@ async function main() {
emitSessionState();
return { project };
});
registerHandler(ipcChannels.project.close, async () => {
await projectStore.closeOpenProject();
effectsStore.clear();
sceneDarknessStore.resetSession();
emitEffectsState();
emitSceneDarknessState();
emitSessionState();
return { ok: true };
});
registerHandler(ipcChannels.project.get, () => {
return { project: projectStore.getOpenProject() };
});
@@ -313,7 +343,10 @@ async function main() {
registerHandler(ipcChannels.project.setCurrentScene, async ({ sceneId }) => {
await projectStore.updateProject((p) => ({ ...p, currentSceneId: sceneId, currentGraphNodeId: null }));
effectsStore.clear();
const project = projectStore.getOpenProject();
if (project) syncSceneDarknessForProject(project);
emitEffectsState();
emitSceneDarknessState();
emitSessionState();
return { currentSceneId: projectStore.getOpenProject()?.currentSceneId ?? null };
});
@@ -327,7 +360,10 @@ async function main() {
currentSceneId: gn ? gn.sceneId : null,
}));
effectsStore.clear();
const project = projectStore.getOpenProject();
if (project) syncSceneDarknessForProject(project);
emitEffectsState();
emitSceneDarknessState();
emitSessionState();
const p = projectStore.getOpenProject();
return {
@@ -337,6 +373,11 @@ async function main() {
});
registerHandler(ipcChannels.project.updateScene, async ({ sceneId, patch }) => {
const next = await projectStore.updateScene(sceneId, patch);
const project = projectStore.getOpenProject();
if (project && project.currentSceneId === sceneId && patch.darkenScene !== undefined) {
syncSceneDarknessForProject(project);
emitSceneDarknessState();
}
emitSessionState();
return { scene: next };
});
@@ -524,6 +565,15 @@ async function main() {
return { ok: true };
});
registerHandler(ipcChannels.sceneDarkness.getState, () => {
return { state: sceneDarknessStore.getState() };
});
registerHandler(ipcChannels.sceneDarkness.dispatch, ({ event }) => {
sceneDarknessStore.dispatch(event);
emitSceneDarknessState();
return { ok: true };
});
registerHandler(ipcChannels.video.getState, () => {
return { state: videoStore.getState() };
});