fix(materials): синхронизация легенды и маркеров на превью и презентации

Легенда и цифры на материале масштабируются от экрана игроков и не обрезаются жёлтой рамкой. Переключение сцен без записи project.json на диск.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-09-02 10:57:50 +08:00
parent ea718a26df
commit 153be98780
17 changed files with 531 additions and 128 deletions
+46 -44
View File
@@ -49,7 +49,7 @@ import type { EffectInstance, EffectToolType, ExplosionInstance } from '../../sh
import { SceneDarknessOverlay } from '../shared/effects/SceneDarknessOverlay';
import { useEffectsState } from '../shared/effects/useEffectsState';
import { useSceneDarknessState } from '../shared/effects/useSceneDarknessState';
import { DEFAULT_MATERIALS_OVERLAY_LAYOUT, DEFAULT_NPCS_OVERLAY_LAYOUT } from '../../shared/types';
import { DEFAULT_MATERIALS_OVERLAY_LAYOUT, DEFAULT_NPCS_OVERLAY_LAYOUT, defaultLegendLayoutForMaterial } from '../../shared/types';
import { MaterialLegendPanel } from '../shared/materials/MaterialLegendPanel';
import { MaterialOverlay } from '../shared/materials/MaterialOverlay';
import { useMaterialsOverlayState } from '../shared/materials/useMaterialsOverlayState';
@@ -2549,12 +2549,7 @@ export function ControlApp() {
material,
layout: materialsOverlay?.layouts[id] ?? DEFAULT_MATERIALS_OVERLAY_LAYOUT,
legendLayout:
materialsOverlay?.legendLayouts[id] ?? {
...DEFAULT_MATERIALS_OVERLAY_LAYOUT,
cx: 0.82,
cy: 0.5,
scale: 0.85,
},
materialsOverlay?.legendLayouts[id] ?? defaultLegendLayoutForMaterial(),
};
})
.filter((x): x is NonNullable<typeof x> => x !== null)
@@ -2577,6 +2572,7 @@ export function ControlApp() {
const showMaterial = materialItems.length > 0;
const showNpcs = npcItems.length > 0;
const screenRect = presentationScreenRect;
const overlayViewport = screenRect;
const showGuide = Boolean(screenRect);
if (!showMaterial && !showNpcs && !showGuide) return null;
const closes = [
@@ -2614,7 +2610,7 @@ export function ControlApp() {
<>
<SceneOverlayHost
active={showMaterial || showNpcs}
viewport={screenRect}
viewport={overlayViewport}
showViewportGuide={showGuide}
zoomTool={materialsZoom}
{...(materialsZoom
@@ -2631,43 +2627,49 @@ export function ControlApp() {
}
: {})}
closes={closes}
legendLayer={
<>
{materialItems.map(({ material, legendLayout }) =>
material.legend?.enabled ? (
<MaterialLegendPanel
key={material.id}
legend={material.legend}
layout={legendLayout}
editable
onLayoutChange={(nextLayout) => {
void materialsApi.dispatch({
kind: 'legendLayout.set',
materialId: material.id,
layout: nextLayout,
});
}}
/>
) : null,
)}
</>
}
>
{materialItems.map(({ material, layout, legendLayout }) => (
<React.Fragment key={material.id}>
<MaterialOverlay
embedded
assetId={material.assetId}
materialId={material.id}
layout={layout}
editable
zoomTool={materialsZoom}
rotateLabel={t('materials.rotateOverlay')}
onLayoutChange={(nextLayout) => {
void materialsApi.dispatch({
kind: 'layout.set',
materialId: material.id,
layout: nextLayout,
});
}}
{...(material.legend?.enabled
? { legendMarkers: material.legend.markers ?? [] }
: {})}
/>
{material.legend?.enabled ? (
<MaterialLegendPanel
legend={material.legend}
layout={legendLayout}
editable
onLayoutChange={(nextLayout) => {
void materialsApi.dispatch({
kind: 'legendLayout.set',
materialId: material.id,
layout: nextLayout,
});
}}
/>
) : null}
</React.Fragment>
{materialItems.map(({ material, layout }) => (
<MaterialOverlay
key={material.id}
embedded
assetId={material.assetId}
materialId={material.id}
layout={layout}
editable
zoomTool={materialsZoom}
rotateLabel={t('materials.rotateOverlay')}
onLayoutChange={(nextLayout) => {
void materialsApi.dispatch({
kind: 'layout.set',
materialId: material.id,
layout: nextLayout,
});
}}
{...(material.legend?.enabled && (material.legend.markers?.length ?? 0) > 0
? { legendMarkers: material.legend.markers }
: {})}
/>
))}
{showNpcs ? (
<NpcsSceneOverlay