File size: 4,217 Bytes
f234112
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
// context/LiveDataContext.jsx
import { createContext, useContext, useEffect, useReducer, useRef } from 'react'
import { createWebSocket, fetchStatus, fetchJunctions, fetchDecisions,
         fetchSafety, fetchEmergencies, fetchMetrics, fetchLogs } from '../services/api'

const LiveDataContext = createContext(null)

const initialState = {
  wsStatus: 'connecting',   // connecting | connected | disconnected
  status: null,
  junctions: [],
  decisions: [],
  safety: null,
  emergencies: [],
  metrics: null,
  logs: [],
  latestDecision: null,
}

function reducer(state, action) {
  switch (action.type) {
    case 'WS_OPEN':
      return { ...state, wsStatus: 'connected' }
    case 'WS_CLOSE':
      return { ...state, wsStatus: 'disconnected' }
    case 'INITIAL_STATE':
      return {
        ...state,
        status: action.payload.status,
        junctions: action.payload.junctions ?? state.junctions,
        decisions: action.payload.decisions ?? state.decisions,
        safety: action.payload.safety ?? state.safety,
        emergencies: action.payload.emergencies ?? state.emergencies,
        metrics: action.payload.metrics ?? state.metrics,
        logs: action.payload.logs ?? state.logs,
        latestDecision: action.payload.decisions?.[0] ?? state.latestDecision,
      }
    case 'LIVE_UPDATE':
      return {
        ...state,
        status: action.payload.status ?? state.status,
        junctions: action.payload.junctions ?? state.junctions,
        safety: action.payload.safety ?? state.safety,
        emergencies: action.payload.emergencies ?? state.emergencies,
        logs: action.payload.logs ?? state.logs,
        latestDecision: action.payload.latest_decision?.[0] ?? state.latestDecision,
      }
    case 'REST_STATUS':
      return { ...state, status: action.payload }
    case 'REST_JUNCTIONS':
      return { ...state, junctions: action.payload }
    case 'REST_DECISIONS':
      return { ...state, decisions: action.payload }
    case 'REST_SAFETY':
      return { ...state, safety: action.payload }
    case 'REST_EMERGENCIES':
      return { ...state, emergencies: action.payload }
    case 'REST_METRICS':
      return { ...state, metrics: action.payload }
    case 'REST_LOGS':
      return { ...state, logs: action.payload }
    default:
      return state
  }
}

export function LiveDataProvider({ children }) {
  const [data, dispatch] = useReducer(reducer, initialState)
  const wsRef = useRef(null)

  // Load initial REST data
  useEffect(() => {
    async function loadInitial() {
      try {
        const [status, junctions, decisions, safety, emergencies, metrics, logs] =
          await Promise.allSettled([
            fetchStatus(), fetchJunctions(), fetchDecisions(),
            fetchSafety(), fetchEmergencies(), fetchMetrics(), fetchLogs(),
          ])
        if (status.status === 'fulfilled') dispatch({ type: 'REST_STATUS', payload: status.value })
        if (junctions.status === 'fulfilled') dispatch({ type: 'REST_JUNCTIONS', payload: junctions.value })
        if (decisions.status === 'fulfilled') dispatch({ type: 'REST_DECISIONS', payload: decisions.value })
        if (safety.status === 'fulfilled') dispatch({ type: 'REST_SAFETY', payload: safety.value })
        if (emergencies.status === 'fulfilled') dispatch({ type: 'REST_EMERGENCIES', payload: emergencies.value })
        if (metrics.status === 'fulfilled') dispatch({ type: 'REST_METRICS', payload: metrics.value })
        if (logs.status === 'fulfilled') dispatch({ type: 'REST_LOGS', payload: logs.value })
      } catch (_) {}
    }
    loadInitial()
  }, [])

  // WebSocket
  useEffect(() => {
    wsRef.current = createWebSocket(
      (msg) => {
        if (msg.type === 'initial_state') dispatch({ type: 'INITIAL_STATE', payload: msg })
        else if (msg.type === 'live_update') dispatch({ type: 'LIVE_UPDATE', payload: msg })
      },
      () => dispatch({ type: 'WS_OPEN' }),
      () => dispatch({ type: 'WS_CLOSE' }),
    )
    return () => wsRef.current?.close()
  }, [])

  return (
    <LiveDataContext.Provider value={data}>
      {children}
    </LiveDataContext.Provider>
  )
}

export function useLiveData() {
  return useContext(LiveDataContext)
}