feat(map): adopt DC Default scene

This commit is contained in:
DCCONSTRUCTIONS
2026-07-29 17:55:44 +03:00
parent 5a06194962
commit b91d3b913e
9 changed files with 1299 additions and 331 deletions
@@ -16,36 +16,24 @@ import { mapPageTemplate } from "@nodedc/page-patterns";
import {
Button,
Checker,
ControlRow,
Icon,
IconButton,
Inspector,
MapGlassSurface,
RangeControl,
StatusBadge,
Window,
WindowFooterActions,
} from "@nodedc/ui-react";
import { MapSettingsInspector } from "../../components/map/MapSettingsInspector";
import {
cloneMapViewDocument,
defaultMapViewDocument,
type MapInspectorSection,
type MapViewDocument,
} from "../../core/map/mapView";
import { useMapGatewayHealth } from "../../core/map/useMapGatewayHealth";
import { useMapView } from "../../core/map/useMapView";
import type { WorkspaceDefinition } from "../../productModel";
const editableInspectorSections = new Set<MapInspectorSection>([
"base-terrain",
"atmosphere-light",
"buildings",
"grid-lod",
"camera",
"tile-cache",
]);
export function WorldMapWorkspace({
definition,
}: {
@@ -174,12 +162,57 @@ export function WorldMapWorkspace({
layers={draft.view.layerVisibility}
settings={{
atmosphere_enabled: draft.view.visualSettings.atmosphereEnabled,
lighting_enabled: draft.view.visualSettings.lightingEnabled,
atmosphere_hue: draft.view.visualSettings.atmosphereHue,
atmosphere_saturation:
draft.view.visualSettings.atmosphereSaturation,
atmosphere_brightness:
draft.view.visualSettings.atmosphereBrightness,
fog_enabled: draft.view.visualSettings.fogEnabled,
fog_density: draft.view.visualSettings.fogDensity,
sun_enabled: draft.view.visualSettings.sunEnabled,
sun_hour: draft.view.visualSettings.sunHour,
sun_intensity: draft.view.visualSettings.sunIntensity,
shadows_enabled: draft.view.visualSettings.shadowsEnabled,
monochrome_enabled: draft.view.visualSettings.monochromeEnabled,
terrain_exaggeration: draft.view.visualSettings.terrainExaggeration,
monochrome_color: draft.view.visualSettings.monochromeColor,
imagery_brightness: draft.view.visualSettings.imageryBrightness,
imagery_contrast: draft.view.visualSettings.imageryContrast,
imagery_saturation: draft.view.visualSettings.imagerySaturation,
imagery_gamma: draft.view.visualSettings.imageryGamma,
imagery_hue: draft.view.visualSettings.imageryHue,
imagery_alpha: draft.view.visualSettings.imageryAlpha,
globe_color: draft.view.visualSettings.globeColor,
background_color: draft.view.visualSettings.backgroundColor,
terrain_exaggeration:
draft.view.visualSettings.terrainExaggeration,
buildings_color: draft.view.visualSettings.buildingsColor,
buildings_opacity: draft.view.visualSettings.buildingsOpacity,
buildings_maximum_screen_space_error:
draft.view.visualSettings.buildingsMaximumScreenSpaceError,
camera_animation_enabled: draft.view.visualSettings.cameraAnimationEnabled,
grid_lod_enabled: draft.view.visualSettings.gridLodEnabled,
grid_height_meters: draft.view.visualSettings.gridHeightMeters,
grid_lod_1_max_height_km:
draft.view.visualSettings.gridLod1MaxHeightKm,
grid_lod_1_step_km:
draft.view.visualSettings.gridLod1StepKm,
grid_lod_2_max_height_km:
draft.view.visualSettings.gridLod2MaxHeightKm,
grid_lod_2_step_km:
draft.view.visualSettings.gridLod2StepKm,
grid_lod_3_step_km:
draft.view.visualSettings.gridLod3StepKm,
grid_radius_km: draft.view.visualSettings.gridRadiusKm,
grid_line_width: draft.view.visualSettings.gridLineWidth,
grid_color: draft.view.visualSettings.gridColor,
grid_opacity: draft.view.visualSettings.gridOpacity,
grid_dots_enabled:
draft.view.visualSettings.gridDotsEnabled,
grid_dots_size: draft.view.visualSettings.gridDotsSize,
grid_dots_color: draft.view.visualSettings.gridDotsColor,
grid_dots_opacity:
draft.view.visualSettings.gridDotsOpacity,
camera_animation_enabled:
draft.view.visualSettings.cameraAnimationEnabled,
}}
cacheIntent={{
enabled: draft.view.cacheIntent.enabled,
@@ -338,22 +371,10 @@ export function WorldMapWorkspace({
</WindowFooterActions>
)}
>
<Inspector
singleOpen
sections={createInspectorSections(draft, updateDraft, gatewayHealth)}
openSections={draft.view.inspectorOpenSections.filter(
(section) => editableInspectorSections.has(section),
)}
activeId={draft.view.inspectorOpenSections[0]}
onOpenSectionsChange={(sections) => {
updateDraft((current) => {
current.view.inspectorOpenSections = sections.filter(
(section): section is MapInspectorSection =>
editableInspectorSections.has(section as MapInspectorSection),
).slice(0, 1);
return current;
});
}}
<MapSettingsInspector
draft={draft}
updateDraft={updateDraft}
gatewayHealth={gatewayHealth}
/>
</Window>
</div>
@@ -380,249 +401,6 @@ function LayerControl({
);
}
function createInspectorSections(
draft: MapViewDocument,
updateDraft: (update: (current: MapViewDocument) => MapViewDocument) => void,
gatewayHealth: ReturnType<typeof useMapGatewayHealth>,
) {
const view = draft.view;
return [
{
id: "base-terrain",
label: "Подложка и terrain",
description: "provider-neutral surface",
content: (
<div className="mission-map__inspector-controls">
<Checker
label="Спутниковая подложка"
checked={view.layerVisibility.imagery}
onChange={(checked) => updateDraft((current) => {
current.view.layerVisibility.imagery = checked;
return current;
})}
/>
<Checker
label="Рельеф"
checked={view.layerVisibility.terrain}
onChange={(checked) => updateDraft((current) => {
current.view.layerVisibility.terrain = checked;
return current;
})}
/>
<Checker
label="Монохромная подложка"
checked={view.visualSettings.monochromeEnabled}
onChange={(checked) => updateDraft((current) => {
current.view.visualSettings.monochromeEnabled = checked;
return current;
})}
/>
<RangeControl
label="Высота карты"
value={view.mapHeight}
min={420}
max={1080}
step={20}
formatValue={(value) => `${value} px`}
onChange={(value) => updateDraft((current) => {
current.view.mapHeight = value;
return current;
})}
/>
</div>
),
},
{
id: "atmosphere-light",
label: "Атмосфера и освещение",
description: "scene / color correction",
content: (
<div className="mission-map__inspector-controls">
<Checker
label="Атмосфера"
checked={view.visualSettings.atmosphereEnabled}
onChange={(checked) => updateDraft((current) => {
current.view.visualSettings.atmosphereEnabled = checked;
return current;
})}
/>
<Checker
label="Освещение глобуса"
checked={view.visualSettings.lightingEnabled}
onChange={(checked) => updateDraft((current) => {
current.view.visualSettings.lightingEnabled = checked;
return current;
})}
/>
</div>
),
},
{
id: "buildings",
label: "3D здания",
description: "3D Tiles / detail",
content: (
<div className="mission-map__inspector-controls">
<Checker
label="Показывать 3D-здания"
checked={view.layerVisibility.buildings}
onChange={(checked) => updateDraft((current) => {
current.view.layerVisibility.buildings = checked;
return current;
})}
/>
<RangeControl
label="Детализация"
value={view.visualSettings.buildingsMaximumScreenSpaceError}
min={1}
max={64}
step={1}
formatValue={(value) => `${value} SSE`}
onChange={(value) => updateDraft((current) => {
current.view.visualSettings.buildingsMaximumScreenSpaceError = value;
return current;
})}
/>
</div>
),
},
{
id: "grid-lod",
label: "Сетка и LOD",
description: "first adapter control",
content: (
<div className="mission-map__inspector-controls">
<Checker
label="Планетарная сетка"
checked={view.layerVisibility.grid}
onChange={(checked) => updateDraft((current) => {
current.view.layerVisibility.grid = checked;
return current;
})}
/>
<RangeControl
label="Масштаб рельефа"
value={view.visualSettings.terrainExaggeration}
min={0.1}
max={8}
step={0.1}
formatValue={(value) => `${value.toFixed(1)}×`}
onChange={(value) => updateDraft((current) => {
current.view.visualSettings.terrainExaggeration = value;
return current;
})}
/>
</div>
),
},
{
id: "camera",
label: "Анимация камеры",
description: "geodesic spiral survey",
content: (
<div className="mission-map__inspector-controls">
<Checker
label="Разрешить анимацию камеры"
checked={view.visualSettings.cameraAnimationEnabled}
onChange={(checked) => updateDraft((current) => {
current.view.visualSettings.cameraAnimationEnabled = checked;
return current;
})}
/>
<ControlRow label="Сохранённая позиция">
<StatusBadge tone={view.camera ? "success" : "neutral"}>
{view.camera ? "Есть" : "Не задана"}
</StatusBadge>
</ControlRow>
</div>
),
},
{
id: "tile-cache",
label: "TileCache",
description: "Platform Map Gateway",
content: (
<div
className="mission-map__inspector-controls"
data-health-code={gatewayHealth.code ?? undefined}
>
<Checker
label="Использовать общий TileCache"
checked={view.cacheIntent.enabled}
onChange={(checked) => updateDraft((current) => {
current.view.cacheIntent.enabled = checked;
return current;
})}
/>
<Checker
label="Не перезаписывать существующие тайлы"
checked={view.cacheIntent.noOverwrite}
disabled={!view.cacheIntent.enabled}
onChange={(checked) => updateDraft((current) => {
current.view.cacheIntent.noOverwrite = checked;
return current;
})}
/>
<CacheHealth health={gatewayHealth} />
</div>
),
},
];
}
function CacheHealth({
health,
}: {
health: ReturnType<typeof useMapGatewayHealth>;
}) {
if (health.state === "loading" || health.state === "idle") {
return (
<ControlRow label="Состояние">
<StatusBadge tone="neutral">Проверяем</StatusBadge>
</ControlRow>
);
}
if (!health.snapshot) {
return (
<ControlRow label="Состояние">
<StatusBadge tone="danger">Недоступен</StatusBadge>
</ControlRow>
);
}
const { cache } = health.snapshot;
return (
<dl className="mission-map__cache-health">
<div>
<dt>Хранилище</dt>
<dd>
<StatusBadge tone={cache.persistent ? "success" : "warning"}>
{cache.persistent ? "Постоянное" : "Временное"}
</StatusBadge>
</dd>
</div>
<div>
<dt>Объекты</dt>
<dd>{cache.entries.toLocaleString("ru-RU")}</dd>
</div>
<div>
<dt>Объём</dt>
<dd>
{formatBytes(cache.bytes)}
{cache.maxBytes === null ? "" : ` / ${formatBytes(cache.maxBytes)}`}
</dd>
</div>
<div>
<dt>Заполнение</dt>
<dd>
<StatusBadge tone={cache.atCapacity ? "warning" : health.state === "stale" ? "warning" : "success"}>
{cache.atCapacity ? "Лимит" : health.state === "stale" ? "Устарело" : "Норма"}
</StatusBadge>
</dd>
</div>
</dl>
);
}
function runtimePresentation(state: MapRuntimeState): {
label: string;
tone: "neutral" | "success" | "warning" | "danger";
@@ -650,15 +428,3 @@ function providerPresentation(
if (provider.phase === "error") return { label: "Недоступен", tone: "danger" };
return { label: "Загрузка", tone: "neutral" };
}
function formatBytes(value: number): string {
if (value < 1024) return `${value} Б`;
const units = ["КБ", "МБ", "ГБ", "ТБ"];
let amount = value / 1024;
let unit = units[0];
for (let index = 1; index < units.length && amount >= 1024; index += 1) {
amount /= 1024;
unit = units[index];
}
return `${amount.toFixed(amount >= 10 ? 1 : 2)} ${unit}`;
}