feat(materials): add campaign materials overlay for sessions

Let GMs manage and show images over the scene from the editor and control panel, with zoom tools, help docs, and full ru/en i18n.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-07-17 11:05:38 +08:00
parent 195d4be086
commit 61875be857
39 changed files with 2521 additions and 22 deletions
+55
View File
@@ -4,6 +4,9 @@ import type {
EffectsEvent,
EffectsState,
GraphNodeId,
MaterialId,
MaterialsOverlayEvent,
MaterialsOverlayState,
MediaAsset,
Project,
ProjectId,
@@ -46,6 +49,11 @@ export const ipcChannels = {
importMedia: 'project.importMedia',
importCampaignAudio: 'project.importCampaignAudio',
updateCampaignAudios: 'project.updateCampaignAudios',
upsertMaterial: 'project.upsertMaterial',
setMaterialRotation: 'project.setMaterialRotation',
deleteMaterial: 'project.deleteMaterial',
setMaterialsOrder: 'project.setMaterialsOrder',
pickMaterialImage: 'project.pickMaterialImage',
importScenePreview: 'project.importScenePreview',
clearScenePreview: 'project.clearScenePreview',
assetFileUrl: 'project.assetFileUrl',
@@ -85,6 +93,8 @@ export const ipcChannels = {
closeSceneDescription: 'windows.closeSceneDescription',
getSceneDescriptionContent: 'windows.getSceneDescriptionContent',
sceneDescriptionContent: 'windows.sceneDescriptionContent',
openMaterials: 'windows.openMaterials',
closeMaterials: 'windows.closeMaterials',
},
session: {
stateChanged: 'session.stateChanged',
@@ -94,6 +104,11 @@ export const ipcChannels = {
dispatch: 'effects.dispatch',
stateChanged: 'effects.stateChanged',
},
materialsOverlay: {
getState: 'materialsOverlay.getState',
dispatch: 'materialsOverlay.dispatch',
stateChanged: 'materialsOverlay.stateChanged',
},
sceneDarkness: {
getState: 'sceneDarkness.getState',
dispatch: 'sceneDarkness.dispatch',
@@ -156,6 +171,7 @@ export type UpdaterProgressEvent = {
export type IpcEventMap = {
[ipcChannels.session.stateChanged]: { state: SessionState };
[ipcChannels.effects.stateChanged]: { state: EffectsState };
[ipcChannels.materialsOverlay.stateChanged]: { state: MaterialsOverlayState };
[ipcChannels.sceneDarkness.stateChanged]: { state: SceneDarknessState };
[ipcChannels.video.stateChanged]: { state: VideoPlaybackState };
[ipcChannels.license.statusChanged]: { snapshot: LicenseSnapshot };
@@ -236,6 +252,28 @@ export type IpcInvokeMap = {
req: { audios: Project['campaignAudios'] };
res: { project: Project };
};
[ipcChannels.project.upsertMaterial]: {
req: { materialId?: MaterialId; name: string; filePath?: string };
res: { project: Project };
};
[ipcChannels.project.setMaterialRotation]: {
req: { materialId: MaterialId; rotationDeg: 0 | 90 | 180 | 270 };
res: { project: Project };
};
[ipcChannels.project.deleteMaterial]: {
req: { materialId: MaterialId };
res: { project: Project };
};
[ipcChannels.project.setMaterialsOrder]: {
req: { materialIds: MaterialId[] };
res: { project: Project };
};
[ipcChannels.project.pickMaterialImage]: {
req: Record<string, never>;
res:
| { canceled: true }
| { canceled: false; filePath: string; previewDataUrl: string };
};
[ipcChannels.project.importScenePreview]: {
req: { sceneId: SceneId; filePath?: string };
res: { project: Project; assetId: AssetId | null; background: boolean };
@@ -390,6 +428,22 @@ export type IpcInvokeMap = {
req: Record<string, never>;
res: { html: string };
};
[ipcChannels.windows.openMaterials]: {
req: Record<string, never>;
res: { ok: true };
};
[ipcChannels.windows.closeMaterials]: {
req: Record<string, never>;
res: { ok: true };
};
[ipcChannels.materialsOverlay.getState]: {
req: Record<string, never>;
res: { state: MaterialsOverlayState };
};
[ipcChannels.materialsOverlay.dispatch]: {
req: { event: MaterialsOverlayEvent };
res: { ok: true };
};
[ipcChannels.effects.getState]: {
req: Record<string, never>;
res: { state: EffectsState };
@@ -440,6 +494,7 @@ export type SessionState = {
export type LegacyIpcEventMap = {
[ipcChannels.session.stateChanged]: { state: SessionState };
[ipcChannels.effects.stateChanged]: { state: EffectsState };
[ipcChannels.materialsOverlay.stateChanged]: { state: MaterialsOverlayState };
[ipcChannels.sceneDarkness.stateChanged]: { state: SceneDarknessState };
[ipcChannels.video.stateChanged]: { state: VideoPlaybackState };
[ipcChannels.license.statusChanged]: Record<string, never>;