feat: scene traps, material legends, and scene editor window

Add trap overlays with session state, material legend editor/panel, and a dedicated scene editor window wired through IPC and project persistence.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-07-24 16:29:43 +08:00
parent d3b1c4660d
commit 02d73ddf81
39 changed files with 2563 additions and 36 deletions
+17
View File
@@ -58,6 +58,10 @@ type Actions = {
deleteMaterial: (materialId: MaterialId) => Promise<void>;
setMaterialsOrder: (materialIds: MaterialId[]) => Promise<void>;
setMaterialRotation: (materialId: MaterialId, rotationDeg: 0 | 90 | 180 | 270) => Promise<void>;
setMaterialLegend: (
materialId: MaterialId,
legend: import('../../shared/types').MaterialLegend | null,
) => Promise<void>;
pickMaterialImage: () => Promise<{ filePath: string; previewDataUrl: string } | null>;
updateScene: (
sceneId: SceneId,
@@ -344,6 +348,7 @@ export function useProjectState(licenseActive: boolean, opts?: ProjectStateOpts)
previewVideoAutostart: false,
previewRotationDeg: 0,
darkenScene: false,
traps: [],
media: { videos: [], audios: [] },
settings: { autoplayVideo: false, autoplayAudio: true, loopVideo: true, loopAudio: true },
connections: [],
@@ -531,6 +536,15 @@ export function useProjectState(licenseActive: boolean, opts?: ProjectStateOpts)
await refreshProjects();
};
const setMaterialLegend = async (
materialId: MaterialId,
legend: import('../../shared/types').MaterialLegend | null,
) => {
const res = await api.invoke(ipcChannels.project.setMaterialLegend, { materialId, legend });
// Список проектов не меняется — не дергаем refreshProjects на каждое обновление легенды.
setState((s) => ({ ...s, project: res.project }));
};
const pickMaterialImage = async () => {
const res = await api.invoke(ipcChannels.project.pickMaterialImage, {});
if (res.canceled) return null;
@@ -548,6 +562,7 @@ export function useProjectState(licenseActive: boolean, opts?: ProjectStateOpts)
previewVideoAutostart?: boolean;
previewRotationDeg?: 0 | 90 | 180 | 270;
darkenScene?: boolean;
traps?: import('../../shared/types').SceneTrap[];
settings?: Partial<Scene['settings']>;
media?: Partial<Scene['media']>;
layout?: { x: number; y: number };
@@ -574,6 +589,7 @@ export function useProjectState(licenseActive: boolean, opts?: ProjectStateOpts)
? { previewRotationDeg: patch.previewRotationDeg }
: null),
...(patch.darkenScene !== undefined ? { darkenScene: patch.darkenScene } : null),
...(patch.traps !== undefined ? { traps: patch.traps } : null),
...(patch.settings ? { settings: { ...scene.settings, ...patch.settings } } : null),
...(patch.media ? { media: { ...scene.media, ...patch.media } } : null),
layout: patch.layout ? { ...scene.layout, ...patch.layout } : scene.layout,
@@ -896,6 +912,7 @@ export function useProjectState(licenseActive: boolean, opts?: ProjectStateOpts)
deleteMaterial,
setMaterialsOrder,
setMaterialRotation,
setMaterialLegend,
pickMaterialImage,
updateScene,
updateConnections,