feat(control-station): add configurable environment shell

This commit is contained in:
DCCONSTRUCTIONS
2026-07-26 14:18:08 +03:00
parent 42041b37cd
commit 453d760be4
14 changed files with 1473 additions and 246 deletions
+43 -14
View File
@@ -8,10 +8,8 @@ import {
Checker,
ColorField,
ControlRow,
HeaderAvatar,
HeaderNavigation,
HeaderProfile,
HeaderProfileButton,
HeaderWorkspace,
Icon,
Inspector,
@@ -19,6 +17,7 @@ import {
Select,
StatusBadge,
TextField,
UserProfileMenu,
Window,
WindowFooterActions,
useApplicationWorkspace,
@@ -26,7 +25,9 @@ import {
} from "@nodedc/ui-react";
import { LandingStage } from "./components/LandingStage";
import { EnvironmentSettingsWindow } from "./components/EnvironmentSettingsWindow";
import { ObservationSessionSelect } from "./components/ObservationSessionSelect";
import { useEnvironmentSettings } from "./core/environment/useEnvironmentSettings";
import { useDevicePluginHost } from "./core/device-plugins/DevicePluginHost";
import { useMissionRuntime } from "./core/runtime/MissionRuntimeContext";
import type { ViewerSettings } from "./core/runtime/contracts";
@@ -149,6 +150,7 @@ function mergeViewerSettings(
export default function App() {
const runtime = useMissionRuntime();
const environment = useEnvironmentSettings();
const { selection } = useDevicePluginHost();
const polygonDatasetRoute = useMemo(
() => resolvePolygonRunRoute(typeof window === "undefined" ? "" : window.location.search),
@@ -162,6 +164,7 @@ export default function App() {
const [activeRoot, setActiveRoot] = useState<RootId | null>(
polygonDatasetRoute.active ? "data" : null,
);
const [environmentSettingsOpen, setEnvironmentSettingsOpen] = useState(false);
const [sourceUrl, setSourceUrl] = useState("");
const [recordedReplay, setRecordedReplay] = useState<ObservationSessionReplayLaunch | null>(null);
const [recordedReplayLabel, setRecordedReplayLabel] = useState<string | null>(null);
@@ -423,11 +426,6 @@ export default function App() {
const selectRoot = (rootId: RootId) => {
setActiveRoot(rootId);
if (rootId === "polygon") {
workspace.openView("lab-archive");
workspace.openNavigation();
return;
}
workspace.closeView();
workspace.openNavigation();
};
@@ -626,6 +624,7 @@ export default function App() {
const header = (
<AppHeader
brand={<img src="/nodedc-logo.svg" alt="NODE.DC" />}
brandHref="/"
brandLabel="NODEDC MISSION CORE"
center={
<>
@@ -633,18 +632,35 @@ export default function App() {
<HeaderNavigation
label="Архитектурные блоки пункта управления"
value={activeRoot ?? undefined}
items={visibleRoots.map((root) => ({ value: root.id, label: root.label }))}
items={visibleRoots.map((root) => ({
value: root.id,
label: environment.settings.headerLabels[root.id],
}))}
onChange={selectRoot}
/>
</>
}
right={
<HeaderProfile>
<HeaderProfileButton onClick={() => void runtime.refresh()} title="Обновить локальный контур">
<span className="api-dot" data-status={runtime.backendStatus} aria-hidden="true" />
{backendLabel(runtime.backendStatus)}
</HeaderProfileButton>
<HeaderAvatar label="DC" />
<UserProfileMenu
displayName="DC"
subtitle="Mission Core"
triggerLabel={null}
actions={[
{
id: "refresh",
label: "Обновить контур",
icon: "refresh",
onSelect: () => void runtime.refresh(),
},
{
id: "settings",
label: "Настройки",
icon: "settings",
onSelect: () => setEnvironmentSettingsOpen(true),
},
]}
/>
</HeaderProfile>
}
/>
@@ -668,6 +684,9 @@ export default function App() {
backendStatus={runtime.backendStatus}
phase={runtime.state?.phase}
message={runtime.state?.message}
background={
environment.settings.backgrounds[activeRoot ?? "home"]
}
onOpenObservation={() => openView("spatial-scene")}
onOpenDevice={() => openView("local-device")}
/>
@@ -764,7 +783,7 @@ export default function App() {
) : activeDefinition.kind === "datasets" ? (
<StatusBadge tone="neutral">Offline evaluation</StatusBadge>
) : activeDefinition.kind === "lab-archive" ? (
<StatusBadge tone="accent">Лаборатория</StatusBadge>
null
) : (
<StatusBadge tone="warning">Интерфейс готов</StatusBadge>
)
@@ -821,6 +840,16 @@ export default function App() {
) : null}
/>
<EnvironmentSettingsWindow
open={environmentSettingsOpen}
settings={environment.settings}
state={environment.state}
error={environment.error}
onClose={() => setEnvironmentSettingsOpen(false)}
onSave={environment.save}
onUpload={environment.upload}
/>
<Window
open={sceneWorkspaceActive && sourceWindowOpen}
title="Визуальный движок"