feat(control-station): configure page-specific shell
This commit is contained in:
@@ -12,10 +12,19 @@ export interface EnvironmentBackground {
|
||||
fileName: string | null;
|
||||
}
|
||||
|
||||
export interface EnvironmentPage {
|
||||
headerLabel: string;
|
||||
eyebrow: string;
|
||||
title: string;
|
||||
description: string;
|
||||
primaryWorkspaceId: string | null;
|
||||
secondaryWorkspaceId: string | null;
|
||||
background: EnvironmentBackground;
|
||||
}
|
||||
|
||||
export interface EnvironmentSettings {
|
||||
revision: number;
|
||||
headerLabels: Record<RootId, string>;
|
||||
backgrounds: Record<EnvironmentSurfaceId, EnvironmentBackground>;
|
||||
pages: Record<EnvironmentSurfaceId, EnvironmentPage>;
|
||||
}
|
||||
|
||||
export interface UploadedEnvironmentMedia {
|
||||
@@ -30,7 +39,6 @@ export interface UploadedEnvironmentMedia {
|
||||
|
||||
const surfaceIds: readonly EnvironmentSurfaceId[] = [
|
||||
"home",
|
||||
"center",
|
||||
"fleet",
|
||||
"observation",
|
||||
"missions",
|
||||
@@ -39,8 +47,6 @@ const surfaceIds: readonly EnvironmentSurfaceId[] = [
|
||||
"polygon",
|
||||
];
|
||||
|
||||
const rootIds = surfaceIds.filter((value): value is RootId => value !== "home");
|
||||
|
||||
function emptyBackground(): EnvironmentBackground {
|
||||
return {
|
||||
enabled: false,
|
||||
@@ -51,15 +57,37 @@ function emptyBackground(): EnvironmentBackground {
|
||||
};
|
||||
}
|
||||
|
||||
const defaultQuickActions: Record<
|
||||
EnvironmentSurfaceId,
|
||||
readonly [string | null, string | null]
|
||||
> = {
|
||||
home: ["spatial-scene", "local-device"],
|
||||
fleet: ["contour-health", "local-device"],
|
||||
observation: ["spatial-scene", "cameras"],
|
||||
missions: ["mission-planner", null],
|
||||
data: ["recordings", "datasets"],
|
||||
system: ["modules", "integrations"],
|
||||
polygon: ["lab-archive", null],
|
||||
};
|
||||
|
||||
export function defaultEnvironmentSettings(): EnvironmentSettings {
|
||||
const rootById = new Map(roots.map((root) => [root.id, root]));
|
||||
return {
|
||||
revision: 0,
|
||||
headerLabels: Object.fromEntries(
|
||||
roots.map((root) => [root.id, root.label]),
|
||||
) as Record<RootId, string>,
|
||||
backgrounds: Object.fromEntries(
|
||||
surfaceIds.map((surfaceId) => [surfaceId, emptyBackground()]),
|
||||
) as Record<EnvironmentSurfaceId, EnvironmentBackground>,
|
||||
pages: Object.fromEntries(surfaceIds.map((surfaceId) => {
|
||||
const root = surfaceId === "home" ? null : rootById.get(surfaceId) ?? null;
|
||||
const [primaryWorkspaceId, secondaryWorkspaceId] = defaultQuickActions[surfaceId];
|
||||
return [surfaceId, {
|
||||
headerLabel: root?.label ?? "Mission Core",
|
||||
eyebrow: root?.eyebrow ?? "NODEDC / MISSION CORE",
|
||||
title: root?.title ?? "Mission Core",
|
||||
description: root?.statement
|
||||
?? "Наблюдение, планирование и корректировка миссий в одной модульной рабочей области.",
|
||||
primaryWorkspaceId,
|
||||
secondaryWorkspaceId,
|
||||
background: emptyBackground(),
|
||||
}];
|
||||
})) as Record<EnvironmentSurfaceId, EnvironmentPage>,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -70,7 +98,11 @@ function requireRecord(value: unknown, path: string): Record<string, unknown> {
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function requireString(value: unknown, path: string, nullable = false): string | null {
|
||||
function requireString(
|
||||
value: unknown,
|
||||
path: string,
|
||||
nullable = false,
|
||||
): string | null {
|
||||
if (nullable && value === null) return null;
|
||||
if (typeof value !== "string" || !value.trim()) {
|
||||
throw new Error(`${path} должен быть непустой строкой.`);
|
||||
@@ -80,7 +112,9 @@ function requireString(value: unknown, path: string, nullable = false): string |
|
||||
|
||||
function decodeBackground(value: unknown, path: string): EnvironmentBackground {
|
||||
const record = requireRecord(value, path);
|
||||
if (typeof record.enabled !== "boolean") throw new Error(`${path}.enabled должен быть boolean.`);
|
||||
if (typeof record.enabled !== "boolean") {
|
||||
throw new Error(`${path}.enabled должен быть boolean.`);
|
||||
}
|
||||
if (!["file", "url"].includes(String(record.source))) {
|
||||
throw new Error(`${path}.source не поддерживается.`);
|
||||
}
|
||||
@@ -99,9 +133,30 @@ function decodeBackground(value: unknown, path: string): EnvironmentBackground {
|
||||
};
|
||||
}
|
||||
|
||||
function decodePage(value: unknown, path: string): EnvironmentPage {
|
||||
const record = requireRecord(value, path);
|
||||
return {
|
||||
headerLabel: requireString(record.header_label, `${path}.header_label`)!,
|
||||
eyebrow: requireString(record.eyebrow, `${path}.eyebrow`)!,
|
||||
title: requireString(record.title, `${path}.title`)!,
|
||||
description: requireString(record.description, `${path}.description`)!,
|
||||
primaryWorkspaceId: requireString(
|
||||
record.primary_workspace_id,
|
||||
`${path}.primary_workspace_id`,
|
||||
true,
|
||||
),
|
||||
secondaryWorkspaceId: requireString(
|
||||
record.secondary_workspace_id,
|
||||
`${path}.secondary_workspace_id`,
|
||||
true,
|
||||
),
|
||||
background: decodeBackground(record.background, `${path}.background`),
|
||||
};
|
||||
}
|
||||
|
||||
export function decodeEnvironmentSettings(value: unknown): EnvironmentSettings {
|
||||
const record = requireRecord(value, "environment");
|
||||
if (record.schema_version !== "missioncore.operator-environment/v1") {
|
||||
if (record.schema_version !== "missioncore.operator-environment/v2") {
|
||||
throw new Error("Версия настроек окружения не поддерживается.");
|
||||
}
|
||||
if (
|
||||
@@ -111,36 +166,35 @@ export function decodeEnvironmentSettings(value: unknown): EnvironmentSettings {
|
||||
) {
|
||||
throw new Error("Ревизия настроек окружения некорректна.");
|
||||
}
|
||||
const labels = requireRecord(record.header_labels, "environment.header_labels");
|
||||
const backgrounds = requireRecord(record.backgrounds, "environment.backgrounds");
|
||||
const pages = requireRecord(record.pages, "environment.pages");
|
||||
return {
|
||||
revision: record.revision,
|
||||
headerLabels: Object.fromEntries(rootIds.map((rootId) => [
|
||||
rootId,
|
||||
requireString(labels[rootId], `environment.header_labels.${rootId}`),
|
||||
])) as Record<RootId, string>,
|
||||
backgrounds: Object.fromEntries(surfaceIds.map((surfaceId) => [
|
||||
pages: Object.fromEntries(surfaceIds.map((surfaceId) => [
|
||||
surfaceId,
|
||||
decodeBackground(
|
||||
backgrounds[surfaceId],
|
||||
`environment.backgrounds.${surfaceId}`,
|
||||
),
|
||||
])) as Record<EnvironmentSurfaceId, EnvironmentBackground>,
|
||||
decodePage(pages[surfaceId], `environment.pages.${surfaceId}`),
|
||||
])) as Record<EnvironmentSurfaceId, EnvironmentPage>,
|
||||
};
|
||||
}
|
||||
|
||||
export function encodeEnvironmentSettings(settings: EnvironmentSettings): unknown {
|
||||
return {
|
||||
revision: settings.revision,
|
||||
header_labels: settings.headerLabels,
|
||||
backgrounds: Object.fromEntries(surfaceIds.map((surfaceId) => {
|
||||
const background = settings.backgrounds[surfaceId];
|
||||
pages: Object.fromEntries(surfaceIds.map((surfaceId) => {
|
||||
const page = settings.pages[surfaceId];
|
||||
return [surfaceId, {
|
||||
enabled: background.enabled,
|
||||
source: background.source,
|
||||
url: background.url,
|
||||
media_kind: background.mediaKind,
|
||||
file_name: background.fileName,
|
||||
header_label: page.headerLabel,
|
||||
eyebrow: page.eyebrow,
|
||||
title: page.title,
|
||||
description: page.description,
|
||||
primary_workspace_id: page.primaryWorkspaceId,
|
||||
secondary_workspace_id: page.secondaryWorkspaceId,
|
||||
background: {
|
||||
enabled: page.background.enabled,
|
||||
source: page.background.source,
|
||||
url: page.background.url,
|
||||
media_kind: page.background.mediaKind,
|
||||
file_name: page.background.fileName,
|
||||
},
|
||||
}];
|
||||
})),
|
||||
};
|
||||
@@ -182,11 +236,13 @@ export function cloneEnvironmentSettings(
|
||||
): EnvironmentSettings {
|
||||
return {
|
||||
revision: settings.revision,
|
||||
headerLabels: { ...settings.headerLabels },
|
||||
backgrounds: Object.fromEntries(surfaceIds.map((surfaceId) => [
|
||||
pages: Object.fromEntries(surfaceIds.map((surfaceId) => [
|
||||
surfaceId,
|
||||
{ ...settings.backgrounds[surfaceId] },
|
||||
])) as Record<EnvironmentSurfaceId, EnvironmentBackground>,
|
||||
{
|
||||
...settings.pages[surfaceId],
|
||||
background: { ...settings.pages[surfaceId].background },
|
||||
},
|
||||
])) as Record<EnvironmentSurfaceId, EnvironmentPage>,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user