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:
@@ -17,6 +17,9 @@ import { PixiEffectsOverlay } from '../shared/effects/PxiEffectsOverlay';
|
||||
import { SceneDarknessOverlay } from '../shared/effects/SceneDarknessOverlay';
|
||||
import { useEffectsState } from '../shared/effects/useEffectsState';
|
||||
import { useSceneDarknessState } from '../shared/effects/useSceneDarknessState';
|
||||
import { DEFAULT_MATERIALS_OVERLAY_LAYOUT } from '../../shared/types';
|
||||
import { MaterialOverlay } from '../shared/materials/MaterialOverlay';
|
||||
import { useMaterialsOverlayState } from '../shared/materials/useMaterialsOverlayState';
|
||||
import { Button } from '../shared/ui/controls';
|
||||
import { Surface } from '../shared/ui/Surface';
|
||||
import { useAssetUrl } from '../shared/useAssetImageUrl';
|
||||
@@ -87,6 +90,7 @@ export function ControlApp() {
|
||||
tRef.current = t;
|
||||
const [fxState, fx] = useEffectsState();
|
||||
const [sdState, sd] = useSceneDarknessState();
|
||||
const [materialsOverlay, materialsApi] = useMaterialsOverlayState();
|
||||
const [session, setSession] = useState<SessionState | null>(null);
|
||||
const historyRef = useRef<GraphNodeId[]>([]);
|
||||
const [history, setHistory] = useState<GraphNodeId[]>([]);
|
||||
@@ -1167,6 +1171,38 @@ export function ControlApp() {
|
||||
📖
|
||||
</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
iconOnly
|
||||
title={t('control.materialsTool')}
|
||||
ariaLabel={t('control.materialsTool')}
|
||||
onClick={() => {
|
||||
void api.invoke(ipcChannels.windows.openMaterials, {}).catch((err) => {
|
||||
console.error('[control] openMaterials failed', err);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<span className={styles.iconGlyph} aria-hidden>
|
||||
<svg viewBox="0 0 24 24" width="20" height="20" aria-hidden>
|
||||
<path
|
||||
fill="#c9a227"
|
||||
d="M4.2 5.4c1.6-.9 3.5-.7 5 .5l1.3 1.1c.4.3 1 .3 1.4 0L13.2 5.9c1.5-1.2 3.4-1.4 5-.5l1.4.8c.8.5 1.3 1.4 1.3 2.3v8.6c0 1.4-1.4 2.3-2.7 1.8l-2.3-.9c-.7-.3-1.5-.2-2.1.2l-1.3.9c-.6.4-1.4.4-2 0l-1.3-.9c-.6-.4-1.4-.5-2.1-.2l-2.3.9c-1.3.5-2.7-.4-2.7-1.8V8.5c0-.9.5-1.8 1.3-2.3l1.5-.8z"
|
||||
/>
|
||||
<path
|
||||
fill="#7a4e1d"
|
||||
d="M8.2 10.2c1.3-.4 2.5.2 3.3 1.1.3.3.8.3 1.1 0 .8-.9 2-1.5 3.3-1.1.5.2.8.7.6 1.2-.5 1.4-1.7 2.5-3.1 3.1-.5.2-1 .2-1.4 0-1.4-.6-2.6-1.7-3.1-3.1-.2-.5.1-1 .6-1.2z"
|
||||
/>
|
||||
<circle cx="12" cy="12.2" r="1.15" fill="#e8c547" />
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#5c3a16"
|
||||
strokeWidth="1.1"
|
||||
strokeLinecap="round"
|
||||
d="M7.5 15.8c1.2.7 2.7 1.1 4.5 1.1s3.3-.4 4.5-1.1"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles.spacer12} />
|
||||
{!isVideoPreviewScene ? (
|
||||
@@ -1523,6 +1559,33 @@ export function ControlApp() {
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
{(() => {
|
||||
const activeMaterial =
|
||||
session?.project && materialsOverlay?.activeMaterialId
|
||||
? (session.project.materials ?? []).find((m) => m.id === materialsOverlay.activeMaterialId)
|
||||
: undefined;
|
||||
if (!activeMaterial) return null;
|
||||
return (
|
||||
<MaterialOverlay
|
||||
assetId={activeMaterial.assetId}
|
||||
rotationDeg={activeMaterial.rotationDeg ?? 0}
|
||||
layout={materialsOverlay?.layout ?? DEFAULT_MATERIALS_OVERLAY_LAYOUT}
|
||||
editable
|
||||
zoomTool={materialsOverlay?.zoomTool ?? null}
|
||||
showClose
|
||||
closeLabel={t('materials.closeOverlay')}
|
||||
onClose={() => {
|
||||
void materialsApi.dispatch({ kind: 'hide' });
|
||||
}}
|
||||
onLayoutChange={(layout) => {
|
||||
void materialsApi.dispatch({ kind: 'layout.set', layout });
|
||||
}}
|
||||
onZoomAt={(nx, ny) => {
|
||||
void materialsApi.dispatch({ kind: 'zoomAt', nx, ny });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
</Surface>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user