feat(workbench): expand SCADA monitoring and investigation presentation
This commit is contained in:
@@ -127,6 +127,7 @@ export function useInvestigationPresentation(options: Options) {
|
||||
"connecting" | "live" | "history" | "disconnected" | "error"
|
||||
>("disconnected");
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [playbackSpeed, setPlaybackSpeed] = useState(1);
|
||||
const [activity, setActivity] = useState<InvestigationPresentationActivity[]>([]);
|
||||
const activityIdRef = useRef(0);
|
||||
const previewRef = useRef<AbortController | null>(null);
|
||||
@@ -171,7 +172,7 @@ export function useInvestigationPresentation(options: Options) {
|
||||
[publish, source]
|
||||
);
|
||||
const selectHistory = useCallback(
|
||||
(id: number | null) => {
|
||||
(id: number | null, pin = false) => {
|
||||
const store = storeRef.current;
|
||||
if (store.selectedId === null)
|
||||
store.update({
|
||||
@@ -179,7 +180,7 @@ export function useInvestigationPresentation(options: Options) {
|
||||
camera:
|
||||
capturePresentationCamera(valuesRef.current.mapRef.current) ?? store.current.camera
|
||||
});
|
||||
if (!store.select(id === store.currentEntryId ? null : id)) return;
|
||||
if (!store.select(!pin && id === store.currentEntryId ? null : id)) return;
|
||||
publish();
|
||||
const map = valuesRef.current.mapRef.current;
|
||||
if (map && valuesRef.current.mapReady) renderPresentationSnapshot(scene, map, store.visible);
|
||||
@@ -228,8 +229,9 @@ export function useInvestigationPresentation(options: Options) {
|
||||
signal = AbortSignal.any([signal, mapCommandsRef.current.signal]);
|
||||
signal.throwIfAborted();
|
||||
const store = storeRef.current;
|
||||
const previousView = store.current;
|
||||
const eventKey = event ? presentationEventKey(event.task_id, event.event_seq) : undefined;
|
||||
if (eventKey && store.entries.some((entry) => entry.key === eventKey))
|
||||
if (eventKey && store.entries.some((entry) => entry.key === eventKey && entry.status === "completed"))
|
||||
return { recorded: true, duplicate: true };
|
||||
update((view) => ({
|
||||
...view,
|
||||
@@ -252,6 +254,7 @@ export function useInvestigationPresentation(options: Options) {
|
||||
valuesRef.current.task?.title ?? null,
|
||||
{ key: eventKey, taskId: valuesRef.current.task?.task_id }
|
||||
);
|
||||
const pendingEntry = store.entries.find((entry) => entry.id === historyId);
|
||||
const id = ++activityIdRef.current;
|
||||
setActivity((current) => [
|
||||
...current,
|
||||
@@ -259,6 +262,7 @@ export function useInvestigationPresentation(options: Options) {
|
||||
]);
|
||||
let actionSnapshot = store.current;
|
||||
const apply = (updater: (view: PresentationView) => PresentationView) => {
|
||||
signal.throwIfAborted();
|
||||
update(updater);
|
||||
actionSnapshot = store.current;
|
||||
};
|
||||
@@ -285,7 +289,7 @@ export function useInvestigationPresentation(options: Options) {
|
||||
})
|
||||
);
|
||||
signal.throwIfAborted();
|
||||
if (historyId !== null) store.finish(historyId, "completed", undefined, actionSnapshot);
|
||||
if (historyId !== null) store.finish(historyId, "completed", undefined, actionSnapshot, pendingEntry);
|
||||
publish();
|
||||
setActivity((current) =>
|
||||
current.map((item) => (item.id === id ? { ...item, status: "completed" } : item))
|
||||
@@ -300,13 +304,22 @@ export function useInvestigationPresentation(options: Options) {
|
||||
viewing_history: true
|
||||
};
|
||||
} catch (cause) {
|
||||
// An unfinished display may already have applied a chart or geometry before
|
||||
// waiting for its frame. Roll back only its own view, never a newer scope.
|
||||
if (signal.aborted && store.current === actionSnapshot) {
|
||||
store.update(previousView);
|
||||
actionSnapshot = previousView;
|
||||
const map = valuesRef.current.mapRef.current;
|
||||
if (map && store.selectedId === null && valuesRef.current.mapReady)
|
||||
renderPresentationSnapshot(scene, map, previousView);
|
||||
}
|
||||
const message = signal.aborted
|
||||
? "展示已取消"
|
||||
: cause instanceof Error
|
||||
? cause.message
|
||||
: "展示失败";
|
||||
if (historyId !== null)
|
||||
store.finish(historyId, signal.aborted ? "cancelled" : "error", message, actionSnapshot);
|
||||
store.finish(historyId, signal.aborted ? "cancelled" : "error", message, actionSnapshot, pendingEntry);
|
||||
publish();
|
||||
setActivity((current) =>
|
||||
current.map((item) => (item.id === id ? { ...item, status: "error", message } : item))
|
||||
@@ -339,6 +352,8 @@ export function useInvestigationPresentation(options: Options) {
|
||||
loadHistory: async (cursor, signal) => {
|
||||
const task = valuesRef.current.task;
|
||||
if (!task || task.task_id !== taskId) return;
|
||||
if (task.execution_generation !== cursor.execution_generation)
|
||||
throw new Error("调查展示代次正在同步,请稍后重试");
|
||||
const events = await fetchOperationalTaskPresentationHistory(
|
||||
taskId,
|
||||
cursor.event_cursor,
|
||||
@@ -365,6 +380,9 @@ export function useInvestigationPresentation(options: Options) {
|
||||
})
|
||||
});
|
||||
signal.throwIfAborted();
|
||||
if (valuesRef.current.task?.task_id !== task.task_id ||
|
||||
valuesRef.current.task.execution_generation !== task.execution_generation)
|
||||
throw new Error("调查任务已切换,忽略旧展示结果");
|
||||
storeRef.current.update(next);
|
||||
publish();
|
||||
const map = valuesRef.current.mapRef.current;
|
||||
@@ -514,25 +532,26 @@ export function useInvestigationPresentation(options: Options) {
|
||||
const createSample = useCallback((caseId: InvestigationDevCaseId = "combined") => {
|
||||
const map = valuesRef.current.mapRef.current;
|
||||
if (!map) return "";
|
||||
const task = valuesRef.current.task;
|
||||
const progress =
|
||||
task?.latest_progress?.execution_generation === task?.execution_generation
|
||||
? task?.latest_progress
|
||||
: null;
|
||||
const target = progress?.progress.diagram?.node_refs.flatMap(
|
||||
(node) => node.map_targets ?? []
|
||||
)[0];
|
||||
return createInvestigationDevCase(
|
||||
caseId,
|
||||
map.getCenter().toArray() as [number, number],
|
||||
target
|
||||
);
|
||||
return createInvestigationDevCase(caseId);
|
||||
}, []);
|
||||
|
||||
const taskEntries =
|
||||
!options.scope || options.scope === "investigation"
|
||||
? storeRef.current.entries.filter((entry) => entry.taskId === taskId)
|
||||
: storeRef.current.entries;
|
||||
const playing = storeRef.current.playing;
|
||||
const selectedHistoryId = storeRef.current.selectedId;
|
||||
useEffect(() => {
|
||||
if (!playing) return;
|
||||
const timer = window.setTimeout(() => {
|
||||
const store = storeRef.current;
|
||||
store.advancePlayback();
|
||||
publish();
|
||||
const map = valuesRef.current.mapRef.current;
|
||||
if (map && valuesRef.current.mapReady) renderPresentationSnapshot(scene, map, store.visible);
|
||||
}, 1500 / playbackSpeed);
|
||||
return () => window.clearTimeout(timer);
|
||||
}, [playing, selectedHistoryId, playbackSpeed, publish, scene]);
|
||||
const visible = storeRef.current.visible;
|
||||
const reviewing = storeRef.current.selectedId !== null;
|
||||
return {
|
||||
@@ -552,7 +571,27 @@ export function useInvestigationPresentation(options: Options) {
|
||||
currentEntryId: storeRef.current.currentEntryId,
|
||||
entries: taskEntries,
|
||||
selectedId: storeRef.current.selectedId,
|
||||
onSelect: selectHistory
|
||||
onSelect: selectHistory,
|
||||
playback: {
|
||||
playing,
|
||||
speed: playbackSpeed,
|
||||
onToggle: () => {
|
||||
const store = storeRef.current;
|
||||
if (store.playing) store.pausePlayback();
|
||||
else {
|
||||
if (store.selectedId === null)
|
||||
store.update({ ...store.current, camera: capturePresentationCamera(valuesRef.current.mapRef.current) ?? store.current.camera });
|
||||
store.startPlayback(taskEntries);
|
||||
}
|
||||
publish();
|
||||
const map = valuesRef.current.mapRef.current;
|
||||
if (map && valuesRef.current.mapReady) renderPresentationSnapshot(scene, map, store.visible);
|
||||
},
|
||||
onSpeedChange: (speed: number) => {
|
||||
if ([0.5, 1, 2].includes(speed)) setPlaybackSpeed(speed);
|
||||
},
|
||||
onSeek: (id: number) => selectHistory(id, true)
|
||||
}
|
||||
},
|
||||
executeCommand,
|
||||
runPreview,
|
||||
|
||||
Reference in New Issue
Block a user