Files
TJWaterFrontend_Refine/src/components/threeDimensional/sceneProtocol.ts
T
jiang 4a522bf42a
Generic Container CI/CD / test-build-publish (push) Successful in 1m11s
Frontend CI/CD v2 / build-test-publish-and-deploy (push) Successful in 1m11s
feat(frontend): cache 3D assets and restore loading feedback
Persist versioned models and validated network metadata in IndexedDB. Show scene download progress and recoverable errors with the existing frosted material.

Connect existing skeletons to pending navigation and route boundaries while preserving the map. Previously only dynamic imports showed fallbacks, and rejected model promises prevented retries.

Verified: 32 focused unit tests; 4 navigation and scene browser checks; 7 cache browser checks including invalid-metadata recovery; isolated production build and type checks.
2026-10-16 18:13:04 +08:00

168 lines
4.1 KiB
TypeScript

import type { SceneResultsPayload } from "./sceneData";
export const SCENE_CHANNEL = "tjwater:zjb-scene";
export const SCENE_PROTOCOL_VERSION = 2;
export type SceneMode =
| "network"
| "hydraulic"
| "map"
| "detail"
| "pump"
| "meters";
export type SceneDisplayMode = "global" | "coordinated";
export type SceneMetricMode = "uniform" | "velocity" | "pressure" | "direction";
export type SceneDirectionMode = "none" | "topology" | "results";
export type SceneLightingPreset = "day" | "studio" | "evening";
export type SceneQuality = "standard" | "high";
export type SceneNetworkStyle = {
scale: number;
mode: SceneMetricMode;
color: string;
missingColor: string;
lowColor: string;
highColor: string;
opacity: number;
roughness: number;
metalness: number;
nodes: boolean;
direction: SceneDirectionMode;
arrowColor: string;
autoRange: boolean;
min: number;
max: number;
};
export type SceneAppearance = {
preset: SceneLightingPreset;
exposure: number;
contextOpacity: number;
shadows: boolean;
effects: boolean;
quality: SceneQuality;
};
export type SceneCameraView = {
id: string;
label: string;
mode: SceneMode;
note?: string;
saved: boolean;
};
export type SceneCameraState = {
active: string | null;
note: string;
views: SceneCameraView[];
};
export type SceneStyleSummary = {
mode?: SceneMetricMode;
min?: number;
max?: number;
dataLinks?: number;
totalLinks?: number;
arrows?: number;
hasResults?: boolean;
resultTime?: string | null;
scale?: number;
};
export type SceneRuntimeState = {
mode: SceneMode;
status: string;
contextVisible: boolean;
roofVisible: boolean;
displayMode: SceneDisplayMode;
style: SceneNetworkStyle;
styleSummary: SceneStyleSummary;
appearance: SceneAppearance;
camera: SceneCameraState;
};
export type SceneAssetField = {
label: string;
value: string;
};
export type SceneAssetSection = {
title: string;
fields: SceneAssetField[];
};
export type SceneAssetNeighbor = {
id: string;
label: string;
};
export type SceneAssetSelection = {
assetId: string;
elementId: string;
kind: string;
title: string;
sections: SceneAssetSection[];
neighbors: SceneAssetNeighbor[];
};
export type SceneCommand =
| { name: "set-mode"; mode: SceneMode }
| { name: "visit-camera"; viewId: string }
| { name: "save-camera"; label: string }
| { name: "remove-camera"; viewId: string }
| { name: "set-style"; patch: Partial<SceneNetworkStyle> }
| { name: "reset-style" }
| { name: "set-display-mode"; mode: SceneDisplayMode }
| { name: "set-appearance"; patch: Partial<SceneAppearance> }
| { name: "toggle-context" }
| { name: "toggle-roof" }
| { name: "fit-view" }
| { name: "select-asset"; assetId: string }
| { name: "locate-selection" }
| { name: "clear-selection" };
type HostMessageBase = {
channel: typeof SCENE_CHANNEL;
version: typeof SCENE_PROTOCOL_VERSION;
projectCode: string;
modelId: string;
};
export type SceneHostMessage = HostMessageBase &
(
| { type: "results"; payload: SceneResultsPayload }
| { type: "clear-results" }
| { type: "command"; command: SceneCommand }
);
export type SceneRuntimeMessage = HostMessageBase &
(
| {
type: "ready";
nodeIds: string[];
linkIds: string[];
state: SceneRuntimeState;
}
| { type: "scene-state"; state: SceneRuntimeState }
| { type: "loading"; active: boolean; failed?: boolean }
| { type: "selection-changed"; selection: SceneAssetSelection | null }
| { type: "results-applied"; summary: SceneStyleSummary }
| { type: "results-cleared" }
| { type: "error"; message: string }
);
export const isSceneRuntimeMessage = (
input: unknown,
): input is SceneRuntimeMessage => {
if (!input || typeof input !== "object") return false;
const message = input as Partial<SceneRuntimeMessage>;
return (
message.channel === SCENE_CHANNEL &&
message.version === SCENE_PROTOCOL_VERSION &&
typeof message.projectCode === "string" &&
typeof message.modelId === "string" &&
typeof message.type === "string"
);
};