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:
@@ -18,9 +18,11 @@ import { PROJECT_ZIP_EXTENSION } from '../../shared/project/projectZipExtension'
|
||||
import type {
|
||||
AssetId,
|
||||
GraphNodeId,
|
||||
MaterialId,
|
||||
MediaAsset,
|
||||
Project,
|
||||
ProjectId,
|
||||
ProjectMaterial,
|
||||
SceneAudioRef,
|
||||
SceneId,
|
||||
} from '../../shared/types';
|
||||
@@ -50,6 +52,7 @@ import {
|
||||
import type { HelpSectionId } from './help/helpSections';
|
||||
import { useEditorI18n } from './i18n/EditorI18nContext';
|
||||
import { EulaModal, LicenseAboutModal, LicenseTokenModal } from './license/EditorLicenseModals';
|
||||
import { MaterialEditModal, MaterialsManagerModal } from './MaterialsModals';
|
||||
import { isSceneDescriptionEmpty, sanitizeSceneDescriptionHtml } from './sceneDescriptionHtml';
|
||||
import { SceneDescriptionModal } from './SceneDescriptionModal';
|
||||
import type { ProjectNoticeCode } from './state/projectState';
|
||||
@@ -138,6 +141,8 @@ export function EditorApp() {
|
||||
const [openKeyAfterEula, setOpenKeyAfterEula] = useState(false);
|
||||
const licenseActive = licenseSnap?.active === true;
|
||||
const [appNotice, setAppNotice] = useState<{ title?: string; message: string } | null>(null);
|
||||
const [materialsManagerOpen, setMaterialsManagerOpen] = useState(false);
|
||||
const [materialEdit, setMaterialEdit] = useState<ProjectMaterial | null | 'new'>(null);
|
||||
const onProjectNotice = useCallback(
|
||||
(code: ProjectNoticeCode) => {
|
||||
const handlers: Record<ProjectNoticeCode, () => void> = {
|
||||
@@ -961,6 +966,8 @@ export function EditorApp() {
|
||||
})();
|
||||
}}
|
||||
/>
|
||||
<div className={styles.spacer6} />
|
||||
<Button onClick={() => setMaterialsManagerOpen(true)}>{t('materials.open')}</Button>
|
||||
<div className={styles.spacer18} />
|
||||
<div className={styles.inspectorTitle}>{t('scenes.inspectorScene')}</div>
|
||||
{state.selectedSceneId ? (
|
||||
@@ -1411,6 +1418,56 @@ export function EditorApp() {
|
||||
}}
|
||||
/>
|
||||
<CheckUpdatesModal open={checkUpdatesOpen} onClose={() => setCheckUpdatesOpen(false)} />
|
||||
<MaterialsManagerModal
|
||||
open={materialsManagerOpen}
|
||||
materials={state.project?.materials ?? []}
|
||||
onClose={() => setMaterialsManagerOpen(false)}
|
||||
onAdd={() => setMaterialEdit('new')}
|
||||
onEdit={(m) => setMaterialEdit(m)}
|
||||
onDelete={async (materialId: MaterialId) => {
|
||||
try {
|
||||
await actions.deleteMaterial(materialId);
|
||||
} catch (e) {
|
||||
setAppNotice({
|
||||
title: t('common.error'),
|
||||
message: e instanceof Error ? e.message : String(e),
|
||||
});
|
||||
}
|
||||
}}
|
||||
onReorder={async (materialIds) => {
|
||||
try {
|
||||
await actions.setMaterialsOrder(materialIds);
|
||||
} catch (e) {
|
||||
setAppNotice({
|
||||
title: t('common.error'),
|
||||
message: e instanceof Error ? e.message : String(e),
|
||||
});
|
||||
}
|
||||
}}
|
||||
onRotate={(materialId, rotationDeg) => {
|
||||
void actions.setMaterialRotation(materialId, rotationDeg).catch((e) => {
|
||||
setAppNotice({
|
||||
title: t('common.error'),
|
||||
message: e instanceof Error ? e.message : String(e),
|
||||
});
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<MaterialEditModal
|
||||
open={materialEdit !== null}
|
||||
initial={materialEdit && materialEdit !== 'new' ? materialEdit : null}
|
||||
existingNames={(state.project?.materials ?? []).map((m) => m.name)}
|
||||
onClose={() => setMaterialEdit(null)}
|
||||
onPickImage={() => actions.pickMaterialImage()}
|
||||
onSave={async ({ name, filePath }) => {
|
||||
const materialId = materialEdit && materialEdit !== 'new' ? materialEdit.id : undefined;
|
||||
await actions.upsertMaterial({
|
||||
...(materialId ? { materialId } : {}),
|
||||
name,
|
||||
...(filePath ? { filePath } : {}),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<SimpleMessageModal
|
||||
open={appNotice !== null}
|
||||
title={appNotice?.title ?? t('common.message')}
|
||||
|
||||
Reference in New Issue
Block a user