feat: multi-material overlays, presentation guides, and release prebuilds

Align control/presentation with presentation screen rect and darkness z-order; sync window titles and session window cleanup. Pack Win/Mac/Linux with npmRebuild disabled and release-native-prep for classic-level and sharp.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-08-03 11:28:18 +08:00
parent 61446dacfc
commit 2979d06f1c
41 changed files with 1060 additions and 252 deletions
+31 -23
View File
@@ -56,11 +56,21 @@ export function PresentationView({
);
const scene =
session?.project && session.currentSceneId ? session.project.scenes[session.currentSceneId] : undefined;
const activeMaterial =
session?.project && materialsOverlay?.activeMaterialId
? (session.project.materials ?? []).find((m) => m.id === materialsOverlay.activeMaterialId)
: undefined;
const project = session?.project;
const activeMaterialItems =
project && (materialsOverlay?.activeMaterialIds?.length ?? 0) > 0
? (materialsOverlay?.activeMaterialIds ?? [])
.map((id) => {
const material = (project.materials ?? []).find((m) => m.id === id);
if (!material) return null;
return {
material,
layout: materialsOverlay?.layouts[id] ?? DEFAULT_MATERIALS_OVERLAY_LAYOUT,
legendLayout: materialsOverlay?.legendLayouts[id] ?? DEFAULT_MATERIALS_OVERLAY_LAYOUT,
};
})
.filter((x): x is NonNullable<typeof x> => x !== null)
: [];
const activeNpcItems =
project && (npcsOverlay?.activeNpcIds?.length ?? 0) > 0
? (npcsOverlay?.activeNpcIds ?? [])
@@ -156,7 +166,7 @@ export function PresentationView({
src={originalUrl}
muted
playsInline
loop={false}
loop={Boolean(scene?.settings?.loopVideo)}
preload="auto"
onError={() => {
// noop: status surfaced in control app; keep presentation clean
@@ -200,25 +210,23 @@ export function PresentationView({
<ExplosionVideoOverlay state={fxState} viewport={contentRect} />
) : null}
{showEffects && scene?.previewAssetType === 'image' && scene.darkenScene && contentRect ? (
<SceneDarknessOverlay state={sdState} overlayAlpha={1} viewport={contentRect} />
<SceneDarknessOverlay state={sdState} overlayAlpha={1} viewport={contentRect} style={{ zIndex: 30 }} />
) : null}
<SceneOverlayHost active={Boolean(activeMaterial) || activeNpcItems.length > 0}>
{activeMaterial ? (
<MaterialOverlay
embedded
assetId={activeMaterial.assetId}
layout={materialsOverlay?.layout ?? DEFAULT_MATERIALS_OVERLAY_LAYOUT}
{...(activeMaterial.legend?.enabled
? { legendMarkers: activeMaterial.legend.markers ?? [] }
: {})}
/>
) : null}
{activeMaterial?.legend?.enabled ? (
<MaterialLegendPanel
legend={activeMaterial.legend}
layout={materialsOverlay?.legendLayout ?? DEFAULT_MATERIALS_OVERLAY_LAYOUT}
/>
) : null}
<SceneOverlayHost active={activeMaterialItems.length > 0 || activeNpcItems.length > 0}>
{activeMaterialItems.map(({ material, layout, legendLayout }) => (
<React.Fragment key={material.id}>
<MaterialOverlay
embedded
assetId={material.assetId}
layout={layout}
materialId={material.id}
{...(material.legend?.enabled ? { legendMarkers: material.legend.markers ?? [] } : {})}
/>
{material.legend?.enabled ? (
<MaterialLegendPanel legend={material.legend} layout={legendLayout} />
) : null}
</React.Fragment>
))}
{activeNpcItems.length > 0 ? <NpcsSceneOverlay embedded items={activeNpcItems} /> : null}
</SceneOverlayHost>
{showTitle ? (