|
| 1 | +/* eslint-disable react-hooks/exhaustive-deps */ |
1 | 2 | import { useEffect, useState } from "react";
|
2 |
| -import { cityIOSettings, generalSettings } from "../../settings/settings"; |
| 3 | +import { cityIOSettings } from "../../settings/settings"; |
3 | 4 | import {
|
4 | 5 | updateCityIOdata,
|
5 | 6 | toggleCityIOisDone,
|
6 | 7 | } from "../../redux/reducers/cityIOdataSlice";
|
7 | 8 | import { useSelector, useDispatch } from "react-redux";
|
8 |
| -import { getAPICall } from "../../utils/utils"; |
| 9 | +import useWebSocket, { ReadyState } from "react-use-websocket" |
9 | 10 | import LoadingProgressBar from "../LoadingProgressBar";
|
10 | 11 |
|
11 |
| -const removeElement = (array, elem) => { |
12 |
| - var index = array.indexOf(elem); |
13 |
| - if (index > -1) { |
14 |
| - array.splice(index, 1); |
15 |
| - } |
16 |
| - return array; |
17 |
| -}; |
18 |
| - |
19 | 12 | const CityIO = (props) => {
|
| 13 | + |
20 | 14 | const verbose = true; // set to true to see console logs
|
21 |
| - const waitTimeMS = 5000; |
22 | 15 | const dispatch = useDispatch();
|
23 | 16 | const cityIOdata = useSelector((state) => state.cityIOdataState.cityIOdata);
|
24 |
| - const cityscopeProjectURL = generalSettings.csjsURL; |
25 | 17 | const { tableName } = props;
|
26 |
| - const [mainHash, setMainHash] = useState(null); |
27 |
| - const [hashes, setHashes] = useState({}); |
| 18 | + const possibleModules = cityIOSettings.cityIO.cityIOmodules.map(module => module.name) |
28 | 19 | const [arrLoadingModules, setArrLoadingModules] = useState([]);
|
29 |
| - const cityioURL = `${cityIOSettings.cityIO.baseURL}table/${tableName}/`; |
30 | 20 |
|
31 |
| - // test if cityIO is up and this table exists |
| 21 | + // Creation of the websocket connection. TODO: change WS_URL to env or property |
| 22 | + // sendJsonMessage: function that sends a message through the websocket channel |
| 23 | + // lastJsonMessage: object that contains the last message received through the websocket |
| 24 | + // readyState: indicates whether the WS is ready or not |
| 25 | + const { sendJsonMessage, lastJsonMessage, readyState } = useWebSocket( |
| 26 | + cityIOSettings.cityIO.websocketURL, |
| 27 | + { |
| 28 | + share: true, |
| 29 | + shouldReconnect: () => true, |
| 30 | + }, |
| 31 | + ) |
| 32 | + |
| 33 | + // When the WS connection state (readyState) changes to OPEN, |
| 34 | + // the UI sends a LISTEN (SUBSCRIBE) message to CityIO with the tableName prop |
32 | 35 | useEffect(() => {
|
33 |
| - const testCityIO = async () => { |
34 |
| - let test = await getAPICall(cityioURL + "meta/"); |
35 |
| - if (test) { |
36 |
| - // start fetching API hashes to check for new data |
37 |
| - getCityIOmetaHash(); |
38 |
| - verbose && |
39 |
| - console.log( |
40 |
| - "%c cityIO is up, reading cityIO every " + |
41 |
| - cityIOSettings.cityIO.interval + |
42 |
| - "ms", |
43 |
| - "color: red" |
44 |
| - ); |
45 |
| - } else { |
46 |
| - setArrLoadingModules([ |
47 |
| - `cityIO might be down, please check { ${tableName} } is correct. Returning to cityScopeJS at ${cityscopeProjectURL} in ${ |
48 |
| - waitTimeMS / 1000 |
49 |
| - } seconds`, |
50 |
| - ]); |
51 |
| - |
52 |
| - new Promise((resolve) => { |
53 |
| - setTimeout(() => { |
54 |
| - window.location.assign(cityscopeProjectURL); |
55 |
| - }, waitTimeMS); |
56 |
| - resolve(); |
57 |
| - }); |
58 |
| - } |
59 |
| - }; |
60 |
| - testCityIO(); |
61 |
| - // eslint-disable-next-line react-hooks/exhaustive-deps |
62 |
| - }, [cityioURL]); |
63 |
| - |
64 |
| - /** |
65 |
| - * gets the main hash of this cityIO table |
66 |
| - * on a constant loop to check for updates |
67 |
| - */ |
68 |
| - async function getCityIOmetaHash() { |
69 |
| - // recursively get hashes |
70 |
| - await getAPICall(cityioURL + "meta/id/").then(async (res) => { |
71 |
| - // is it a new hash? |
72 |
| - if (mainHash !== res) { |
73 |
| - setMainHash(res); |
74 |
| - } |
75 |
| - }); |
76 |
| - // do it forever |
77 |
| - setTimeout(getCityIOmetaHash, cityIOSettings.cityIO.interval); |
78 |
| - } |
| 36 | + console.log("Connection state changed") |
| 37 | + if (readyState === ReadyState.OPEN) { |
| 38 | + sendJsonMessage({ |
| 39 | + type: "LISTEN", |
| 40 | + content: { |
| 41 | + gridId: tableName, |
| 42 | + }, |
| 43 | + }) |
| 44 | + setArrLoadingModules([ |
| 45 | + `Loading ${tableName} data.`, |
| 46 | + ]); |
| 47 | + } |
| 48 | + }, [readyState]) |
| 49 | + |
79 | 50 |
|
| 51 | + // When a new WebSocket message is received (lastJsonMessage) the UI checks |
| 52 | + // the type of the message and performs the suitable operation |
80 | 53 | useEffect(() => {
|
81 |
| - //! only update if hashId changes |
82 |
| - if (!mainHash) { |
83 |
| - return; |
| 54 | + |
| 55 | + if(lastJsonMessage == null) return; |
| 56 | + console.log(`Got a new message: ${JSON.stringify(lastJsonMessage)}`) |
| 57 | + |
| 58 | + let messageType = lastJsonMessage.type; |
| 59 | + |
| 60 | + // If the message is of type GRID, the UI updates the GEOGRID and |
| 61 | + // GEOGRIDDATA, optionally, CityIO can send saved modules |
| 62 | + if (messageType === 'GRID'){ |
| 63 | + verbose && console.log( |
| 64 | + ` --- trying to update GEOGRID --- ${JSON.stringify(lastJsonMessage.content)}` |
| 65 | + ); |
| 66 | + setArrLoadingModules([]); |
| 67 | + |
| 68 | + let m = {...cityIOdata, "GEOGRID": lastJsonMessage.content.GEOGRID, "GEOGRIDDATA":lastJsonMessage.content.GEOGRIDDATA, tableName: tableName }; |
| 69 | + |
| 70 | + Object.keys(lastJsonMessage.content).forEach((key)=>{ |
| 71 | + if(possibleModules.includes(key) && key !== 'scenarios' && key !== 'indicators'){ |
| 72 | + m[key] = lastJsonMessage.content[key] |
| 73 | + } else if(key === 'deckgl'){ |
| 74 | + lastJsonMessage.content.deckgl |
| 75 | + .forEach((layer) => { |
| 76 | + m[layer.type]={ data: layer.data, properties: layer.properties } |
| 77 | + }); |
| 78 | + } |
| 79 | + } |
| 80 | + ); |
| 81 | + // When we receive a GRID message, we ask for the scenarios of the table we´re |
| 82 | + // connected, and for the core modules |
| 83 | + sendJsonMessage({ |
| 84 | + type: "REQUEST_CORE_MODULES_LIST", |
| 85 | + content: {}, |
| 86 | + }) |
| 87 | + sendJsonMessage({ |
| 88 | + type: "LIST_SCENARIOS", |
| 89 | + content: {}, |
| 90 | + }) |
| 91 | + dispatch(updateCityIOdata(m)); |
| 92 | + verbose && |
| 93 | + console.log( |
| 94 | + "%c --- done updating from cityIO ---", |
| 95 | + "color: rgb(0, 255, 0)" |
| 96 | + ); |
| 97 | + dispatch(toggleCityIOisDone(true)); |
84 | 98 | }
|
85 |
| - // if we have a new hash, start getting submodules |
86 |
| - getModules(); |
87 |
| - // eslint-disable-next-line react-hooks/exhaustive-deps |
88 |
| - }, [mainHash]); |
89 |
| - |
90 |
| - async function getModules() { |
91 |
| - // wait to get all of this table's hashes |
92 |
| - const newHashes = await getAPICall(cityioURL + "meta/hashes/"); |
93 |
| - // init array of GET promises |
94 |
| - const promises = []; |
95 |
| - // init array of modules names |
96 |
| - const loadingModulesArray = []; |
97 |
| - // get an array of modules to update |
98 |
| - const modulesToUpdate = cityIOSettings.cityIO.cityIOmodules.map( |
99 |
| - (x) => x.name |
100 |
| - ); |
101 |
| - // for each of the modules in settings, add api call to promises |
102 |
| - modulesToUpdate.forEach((module) => { |
| 99 | + // If we receive a GEOGRIDDATA_UPDATE, the UI needs to refresh |
| 100 | + // the GEOGRIDDATA object |
| 101 | + else if (messageType === 'GEOGRIDDATA_UPDATE'){ |
| 102 | + verbose && console.log( |
| 103 | + ` --- trying to update GEOGRIDDATA --- ${JSON.stringify(lastJsonMessage.content)}` |
| 104 | + ); |
| 105 | + let m = {...cityIOdata, "GEOGRIDDATA":lastJsonMessage.content }; |
| 106 | + dispatch(updateCityIOdata(m)); |
103 | 107 | verbose &&
|
104 | 108 | console.log(
|
105 |
| - "%c checking {" + module + "} for updates...", |
106 |
| - "color:rgb(200, 200, 0)" |
| 109 | + "%c --- done updating from cityIO ---", |
| 110 | + "color: rgb(0, 255, 0)" |
107 | 111 | );
|
| 112 | + dispatch(toggleCityIOisDone(true)); |
| 113 | + } |
108 | 114 |
|
109 |
| - //add this module name to array |
110 |
| - // of modules that we await for |
111 |
| - loadingModulesArray.push(module); |
112 |
| - |
113 |
| - // if this module has an old hash |
114 |
| - // we assume it is about to be updated |
115 |
| - |
116 |
| - if (hashes[module] !== newHashes[module]) { |
117 |
| - // add this module URL to an array of GET requests |
118 |
| - promises.push(getAPICall(`${cityioURL}${module}/`)); |
119 |
| - } else { |
120 |
| - promises.push(null); |
| 115 | + // If we receive a INDICATOR (MODULE) message, the UI needs to load |
| 116 | + // the module data |
| 117 | + // WIP |
| 118 | + else if (messageType === 'INDICATOR'){ |
| 119 | + verbose && console.log( |
| 120 | + ` --- trying to update INDICATOR --- ${JSON.stringify(lastJsonMessage.content)}` |
| 121 | + ); |
| 122 | + let m = {...cityIOdata} |
| 123 | + if('numeric' in lastJsonMessage.content.moduleData){ |
| 124 | + m = {...m, "indicators":lastJsonMessage.content.moduleData.numeric, tableName: tableName }; |
| 125 | + } |
| 126 | + if('heatmap' in lastJsonMessage.content.moduleData){ |
| 127 | + m = {...m, "heatmap":lastJsonMessage.content.moduleData.heatmap, tableName: tableName }; |
121 | 128 | }
|
122 |
| - setArrLoadingModules(loadingModulesArray); |
123 |
| - }); |
124 |
| - |
125 |
| - // GET all modules data |
126 |
| - const modulesFromCityIO = await Promise.all(promises); |
127 |
| - setHashes(newHashes); |
128 |
| - |
129 |
| - // update cityio object with modules data |
130 |
| - let modulesData = modulesToUpdate.reduce((obj, moduleName, index) => { |
131 |
| - // if this module has data |
132 |
| - if (modulesFromCityIO[index]) { |
133 |
| - verbose && |
134 |
| - console.log( |
135 |
| - "%c {" + |
136 |
| - moduleName + |
137 |
| - "} state has changed on cityIO. Getting new data...", |
138 |
| - "color: rgb(0, 200, 255)" |
139 |
| - ); |
140 |
| - setArrLoadingModules(removeElement(arrLoadingModules, moduleName)); |
141 |
| - |
142 |
| - return { ...obj, [moduleName]: modulesFromCityIO[index] }; |
143 |
| - } else { |
144 |
| - return obj; |
| 129 | + if('deckgl' in lastJsonMessage.content.moduleData){ |
| 130 | + lastJsonMessage.content.moduleData.deckgl |
| 131 | + .forEach((layer) => { |
| 132 | + m[layer.type]={ data: layer.data, properties: layer.properties } |
| 133 | + }); |
145 | 134 | }
|
146 |
| - }, cityIOdata); |
147 |
| - let m = { ...modulesData, tableName: tableName }; |
148 |
| - dispatch(updateCityIOdata(m)); |
149 |
| - verbose && |
150 |
| - console.log( |
151 |
| - "%c --- done updating from cityIO ---", |
152 |
| - "color: rgb(0, 255, 0)" |
| 135 | + |
| 136 | + dispatch(updateCityIOdata(m)); |
| 137 | + verbose && |
| 138 | + console.log( |
| 139 | + "%c --- done updating from cityIO ---", |
| 140 | + "color: rgb(0, 255, 0)" |
| 141 | + ); |
| 142 | + dispatch(toggleCityIOisDone(true)); |
| 143 | + } |
| 144 | + |
| 145 | + // If we receive a CORE_MODULES_LIST message, the UI loads |
| 146 | + // the available modules data |
| 147 | + else if (messageType === 'CORE_MODULES_LIST'){ |
| 148 | + verbose && console.log( |
| 149 | + ` --- trying to update CORE_MODULES_LIST --- ${JSON.stringify(lastJsonMessage.content)}` |
153 | 150 | );
|
154 |
| - dispatch(toggleCityIOisDone(true)); |
155 |
| - } |
| 151 | + let m = {...cityIOdata, 'core_modules':lastJsonMessage.content } |
| 152 | + dispatch(updateCityIOdata(m)); |
| 153 | + verbose && |
| 154 | + console.log( |
| 155 | + "%c --- done updating from cityIO ---", |
| 156 | + "color: rgb(0, 255, 0)" |
| 157 | + ); |
| 158 | + dispatch(toggleCityIOisDone(true)); |
| 159 | + } |
| 160 | + |
| 161 | + // If we receive a SCENARIOS message, the UI loads |
| 162 | + // the available scenarios |
| 163 | + else if (messageType === 'SCENARIOS'){ |
| 164 | + verbose && console.log( |
| 165 | + ` --- trying to update SCENARIOS --- ${JSON.stringify(lastJsonMessage.content)}` |
| 166 | + ); |
| 167 | + let m = {...cityIOdata, 'scenarios':lastJsonMessage.content } |
| 168 | + dispatch(updateCityIOdata(m)); |
| 169 | + verbose && |
| 170 | + console.log( |
| 171 | + "%c --- done updating from cityIO ---", |
| 172 | + "color: rgb(0, 255, 0)" |
| 173 | + ); |
| 174 | + dispatch(toggleCityIOisDone(true)); |
| 175 | + } |
| 176 | + |
| 177 | + }, [lastJsonMessage]) |
156 | 178 |
|
157 | 179 | return <LoadingProgressBar loadingModules={arrLoadingModules} />;
|
| 180 | + |
158 | 181 | };
|
159 | 182 |
|
160 | 183 | export default CityIO;
|
0 commit comments