Skip to content

Commit d63cb9c

Browse files
authored
Data model (#35)
* regorganize code folder structure * more code structure moving * remove duplicate algoOptions from App state * switch to use new data model for algos in App.js instead of Data.js, enabled sharing links of custom algos * remove unused algo data in data.js, fixed bug with seed data, added comments * change add button to allow add and update depending on the name field * fix caching issue and download file name
1 parent 0b96d99 commit d63cb9c

11 files changed

+347
-291
lines changed

src/App.js

+96-135
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,19 @@
2121
* propagation for state changes into the non-react map
2222
*/
2323

24-
import { find, debounce, findIndex, filter } from "lodash";
24+
import { debounce, find, filter } from "lodash";
2525
import React from "react";
26-
import JSONInput from "react-json-editor-ajrm/index";
27-
import locale from "react-json-editor-ajrm/locale/en";
2826
import Loader from "react-loader-spinner";
2927
import "react-loader-spinner/dist/loader/css/react-spinner-loader.css";
30-
import ReactModal from "react-modal";
3128
import Select from "react-select";
3229

33-
import { GetRoutes, getCacheKey } from "./Algos";
34-
import { algoOptions, metroOptions } from "./Data";
35-
import { computeRoutesDirectionsJsSDK } from "./DirectionsJsSDK";
30+
import { GetRoutes } from "./dataModel/algoFns";
31+
import { seedAlgos, addOrUpdateAlgo } from "./dataModel/algoModel";
32+
import { metroOptions } from "./dataModel/data";
33+
import JsonEditor from "./JsonEditor";
3634
import Map from "./Map";
37-
import { getQueryStringValue, setQueryStringValue } from "./queryString";
35+
import { getQueryStringValue, setQueryStringValue } from "./utils/queryString";
3836
import { RouteCharts } from "./RouteCharts";
39-
import { computeRoutesPreferred } from "./RoutesPreferred";
4037

4138
let keyboardListener;
4239

@@ -57,10 +54,22 @@ class App extends React.Component {
5754
return defaultSelection;
5855
}
5956

57+
let allAlgos = seedAlgos();
58+
let selectedAlgoId = Object.keys(allAlgos)[0];
59+
const urlAlgoStr = getQueryStringValue("algo");
60+
if (urlAlgoStr) {
61+
const { id, newAlgos } = addOrUpdateAlgo(
62+
allAlgos,
63+
JSON.parse(urlAlgoStr)
64+
);
65+
allAlgos = newAlgos;
66+
selectedAlgoId = id;
67+
}
68+
6069
this.state = {
61-
algoOptions: [...algoOptions],
70+
algos: allAlgos,
71+
selectedAlgoId: selectedAlgoId,
6272
selectedMetroOption: getDefault(metroOptions, "metro"),
63-
selectedAlgoOption: getDefault(algoOptions, "algo"),
6473
showSpinner: true,
6574
regenData: false,
6675
showEditor: false,
@@ -69,15 +78,7 @@ class App extends React.Component {
6978
durationData: [],
7079
distanceData: [],
7180
},
72-
jsonContent: {
73-
value: "create_your_algo_name",
74-
method: "RoutesPreferred or DirectionsJsSDK",
75-
travelMode: "DRIVE (or DRIVING if using DirectionsJsSDK)",
76-
routingPreference: "TRAFFIC_AWARE",
77-
numRoutes: 10,
78-
numWaypoints: 2,
79-
options: {},
80-
},
81+
jsonContent: {},
8182
};
8283

8384
if (!keyboardListener) {
@@ -88,20 +89,16 @@ class App extends React.Component {
8889
console.log(
8990
`Key: ${event.key} with keycode ${event.keyCode} has been pressed`
9091
);
91-
const curAlgoIdx = findIndex(this.state.algoOptions, {
92-
value: this.state.selectedAlgoOption.value,
93-
});
92+
const algoIds = Object.keys(this.state.algos);
93+
const curAlgoIdx = algoIds.indexOf(this.state.selectedAlgoId);
9494
console.log("gots curAlgoIdx", curAlgoIdx);
95-
if (
96-
event.key == "ArrowDown" &&
97-
curAlgoIdx < this.state.algoOptions.length - 1
98-
) {
99-
console.log("new Alog", this.state.algoOptions[curAlgoIdx + 1]);
100-
this.handleAlgoChange(this.state.algoOptions[curAlgoIdx + 1]);
95+
if (event.key == "ArrowDown" && curAlgoIdx < algoIds.length - 1) {
96+
console.log("new Algo", this.state.algos[algoIds[curAlgoIdx + 1]]);
97+
this.handleAlgoChange({ value: algoIds[curAlgoIdx + 1] });
10198
}
10299
if (event.key == "ArrowUp" && curAlgoIdx > 0) {
103-
console.log("new Alog", this.state.algoOptions[curAlgoIdx - 1]);
104-
this.handleAlgoChange(this.state.algoOptions[curAlgoIdx - 1]);
100+
console.log("new Algo", this.state.algos[algoIds[curAlgoIdx - 1]]);
101+
this.handleAlgoChange({ value: algoIds[curAlgoIdx - 1] });
105102
}
106103
}),
107104
50
@@ -114,9 +111,10 @@ class App extends React.Component {
114111
});
115112
};
116113

117-
this.handleAlgoChange = (selectedAlgoOption) => {
118-
this.setState({ showSpinner: true, selectedAlgoOption }, () => {
119-
setQueryStringValue("algo", this.state.selectedAlgoOption.value);
114+
this.handleAlgoChange = (algoOption) => {
115+
const algoId = algoOption.value;
116+
this.setState({ showSpinner: true, selectedAlgoId: algoId }, () => {
117+
setQueryStringValue("algo", JSON.stringify(this.state.algos[algoId]));
120118
});
121119
};
122120

@@ -132,87 +130,50 @@ class App extends React.Component {
132130
this.setState({ showEditor: false });
133131
};
134132

135-
this.submitJson = () => {
133+
this.submitJson = (content) => {
136134
// TODO: Perform more validation
137-
const customObject = this.state.jsonContent;
138-
const value = customObject.value;
139-
if (find(this.state.algoOptions, { value: value })) {
135+
if (
136+
content.api !== "RoutesPreferred" &&
137+
content.api !== "DirectionsJsSDK"
138+
) {
140139
alert(
141-
"Algo value " +
142-
value +
143-
" already in use. Please create a different value."
140+
"Please enter either 'RoutesPreferred' or 'DirectionsJsSDK' for api."
144141
);
145142
return;
146143
}
147144

148-
const label = customObject.label || customObject.value;
149-
const travelMode = customObject.travelMode;
150-
const routingPreference = customObject.routingPreference;
151-
const options = customObject.options || {};
152-
const numRoutes = customObject.numRoutes || 1;
153-
const numWaypoints = customObject.numWaypoints;
154-
let computeFn;
155-
if (customObject.method == "RoutesPreferred") {
156-
computeFn = async (pairs) =>
157-
computeRoutesPreferred(pairs, travelMode, routingPreference, options);
158-
} else if (customObject.method == "DirectionsJsSDK") {
159-
computeFn = async (pairs) =>
160-
computeRoutesDirectionsJsSDK(pairs, travelMode, options);
161-
} else {
162-
alert(
163-
"Please enter either 'RoutesPreferred' or 'DirectionsJsSDK' for method."
164-
);
165-
return;
166-
}
167-
const newAlgoOption = {
168-
enabled: true,
169-
value: value,
170-
label: label,
171-
numRoutes: numRoutes,
172-
numWaypoints: numWaypoints,
173-
compute: computeFn,
174-
};
175-
algoOptions.push(newAlgoOption);
145+
const { id, newAlgos } = addOrUpdateAlgo(this.state.algos, content);
176146
this.setState(
177147
{
178148
showEditor: false,
179149
showSpinner: true,
180-
algoOptions: [...this.state.algoOptions, newAlgoOption],
181-
selectedAlgoOption: newAlgoOption,
150+
algos: newAlgos,
151+
selectedAlgoId: id,
182152
},
183153
() => {
184-
setQueryStringValue("algo", newAlgoOption.value);
154+
setQueryStringValue("algo", JSON.stringify(content));
185155
}
186156
);
187157
};
188158

189-
this.onJsonChange = (content) => {
190-
this.setState({ jsonContent: content.jsObject });
191-
};
192-
193159
this.onChartDataUpdate = (chartData) => {
194-
this.setState((prevState) => {
195-
return {
196-
selectedMetroOption: prevState.selectedMetroOption,
197-
selectedAlgoOption: prevState.selectedAlgoOption,
198-
showSpinner: false,
199-
regenData: false,
200-
chartData: {
201-
latencyData: chartData.latencyData,
202-
distanceData: chartData.distanceData,
203-
durationData: chartData.durationData,
204-
},
205-
};
160+
this.setState({
161+
showSpinner: false,
162+
regenData: false,
163+
chartData: {
164+
latencyData: chartData.latencyData,
165+
distanceData: chartData.distanceData,
166+
durationData: chartData.durationData,
167+
},
206168
});
207169
};
208170

209171
this.downloadData = async () => {
210172
const metro = this.state.selectedMetroOption.value;
211-
const algo = this.state.selectedAlgoOption.value;
212-
const algoDefinition = find(this.state.algoOptions, { value: algo });
213-
const fileName =
214-
getCacheKey(metro, algo, algoDefinition.numRoutes) + ".json";
215-
const text = JSON.stringify(await GetRoutes({}, metro, algo));
173+
const algoId = this.state.selectedAlgoId;
174+
const algoDefinition = this.state.algos[algoId];
175+
const fileName = `${metro}_${algoId}_route.json`;
176+
const text = JSON.stringify(await GetRoutes({}, metro, algoDefinition));
216177
let element = document.createElement("a");
217178
element.setAttribute(
218179
"href",
@@ -230,58 +191,58 @@ class App extends React.Component {
230191
}
231192

232193
render() {
194+
const selectAlgoOptions = Object.entries(this.state.algos).map(
195+
([algoId, algoDefinition]) => ({
196+
value: algoId,
197+
label: algoDefinition.name,
198+
})
199+
);
200+
const selectedOption = find(selectAlgoOptions, {
201+
value: this.state.selectedAlgoId,
202+
});
203+
233204
return (
234205
<div>
235-
<div style={{ width: "100%" }}>
236-
<ReactModal
206+
<div>
207+
<JsonEditor
237208
isOpen={this.state.showEditor}
238-
contentLabel="Minimal Modal Example"
239-
>
240-
<JSONInput
241-
locale={locale}
242-
placeholder={this.state.jsonContent}
243-
waitAfterKeyPress={2000}
244-
theme={"light_mitsuketa_tribute"}
245-
colors={{ default: "#888888" }}
246-
style={{ body: { fontSize: "16px" } }}
247-
onChange={this.onJsonChange}
248-
/>
249-
<button onClick={this.closeEditor}>Cancel</button>
250-
<button onClick={this.submitJson}>OK</button>
251-
</ReactModal>
209+
initialJson={this.state.algos[this.state.selectedAlgoId]}
210+
onSubmit={this.submitJson}
211+
onCancel={this.closeEditor}
212+
/>
213+
</div>
214+
<div style={{ width: "300px", float: "left" }}>
252215
<Select
253-
value={this.state.selectedAlgoOption}
216+
value={selectedOption}
254217
onChange={this.handleAlgoChange}
255-
options={filter(this.state.algoOptions, { enabled: true })}
218+
options={selectAlgoOptions}
256219
/>
257-
<div style={{ width: "300px", float: "left" }}>
258-
<Select
259-
value={this.state.selectedMetroOption}
260-
onChange={this.handleMetroChange}
261-
options={filter(metroOptions, { enabled: true })}
262-
/>
263-
<button onClick={this.regenerateData}>Regenerate</button>
264-
<button onClick={this.openEditor}>Custom Algo</button>
265-
266-
<Loader
267-
type="Audio"
268-
color="#00BFFF"
269-
height={100}
270-
width={100}
271-
timeout={60000} //60 secs
272-
visible={this.state.showSpinner}
273-
/>
274-
<RouteCharts
275-
hideCharts={this.state.showSpinner}
276-
chartData={this.state.chartData}
277-
/>
278-
<button onClick={this.downloadData}>Download</button>
279-
</div>
220+
<button onClick={this.openEditor}>Add or update...</button>
221+
<Select
222+
value={this.state.selectedMetroOption}
223+
onChange={this.handleMetroChange}
224+
options={filter(metroOptions, { enabled: true })}
225+
/>
226+
<button onClick={this.regenerateData}>Regenerate</button>
227+
<Loader
228+
type="Audio"
229+
color="#00BFFF"
230+
height={100}
231+
width={100}
232+
timeout={60000} //60 secs
233+
visible={this.state.showSpinner}
234+
/>
235+
<RouteCharts
236+
hideCharts={this.state.showSpinner}
237+
chartData={this.state.chartData}
238+
/>
239+
<button onClick={this.downloadData}>Download</button>
280240
</div>
281241
<div style={{ marginLeft: "300px" }}>
282242
<Map
283243
metro={this.state.selectedMetroOption.value}
284-
algo={this.state.selectedAlgoOption.value}
244+
algoId={this.state.selectedAlgoId}
245+
algoDefinition={this.state.algos[this.state.selectedAlgoId]}
285246
regenData={this.state.regenData}
286247
onChartDataUpdate={this.onChartDataUpdate}
287248
/>

src/JsonEditor.js

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* Copyright 2022 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/*
18+
* JsonEditor.js
19+
*
20+
* A modal that allows the user to edit json content.
21+
*/
22+
23+
import { useState } from "react";
24+
import JSONInput from "react-json-editor-ajrm/index";
25+
import locale from "react-json-editor-ajrm/locale/en";
26+
import ReactModal from "react-modal";
27+
28+
function JsonEditor({ isOpen, initialJson, onSubmit, onCancel }) {
29+
const [jsonContent, setJsonContent] = useState(initialJson);
30+
31+
function onJsonChange(content) {
32+
setJsonContent(content.jsObject);
33+
}
34+
35+
return (
36+
<ReactModal isOpen={isOpen} contentLabel="Editor dialog">
37+
<JSONInput
38+
locale={locale}
39+
placeholder={initialJson}
40+
waitAfterKeyPress={2000}
41+
theme={"light_mitsuketa_tribute"}
42+
colors={{ default: "#888888" }}
43+
style={{ body: { fontSize: "16px" } }}
44+
onChange={onJsonChange}
45+
/>
46+
<button onClick={onCancel}>Cancel</button>
47+
<button onClick={() => onSubmit(jsonContent)}>OK</button>
48+
</ReactModal>
49+
);
50+
}
51+
52+
export { JsonEditor as default };

0 commit comments

Comments
 (0)