@cdc/editor 4.22.10 → 4.22.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -7
- package/dist/cdceditor.js +42 -42
- package/example/data-horizontal-filters.json +5 -6
- package/example/data-horizontal-multiseries-filters.json +13 -15
- package/example/data-horizontal-multiseries.json +4 -5
- package/example/data-horizontal.json +2 -3
- package/example/data-vertical-filters.json +7 -8
- package/example/data-vertical-multiseries-filters.json +13 -15
- package/example/data-vertical-multiseries-multirow-filters.json +49 -52
- package/example/data-vertical-multiseries-multirow.json +11 -11
- package/example/data-vertical-multiseries.json +4 -5
- package/example/data-vertical.json +4 -5
- package/example/private/city-state.json +434 -0
- package/example/private/example-city-state.json +86 -0
- package/example/private/map-issue.csv +11 -0
- package/example/private/test.html +26 -1
- package/example/region-map.json +32 -32
- package/example/valid-county-data.json +3047 -3047
- package/package.json +8 -8
- package/src/CdcEditor.js +72 -72
- package/src/components/ChooseTab.js +67 -88
- package/src/components/ConfigureTab.js +51 -51
- package/src/components/DataImport.js +248 -246
- package/src/components/PreviewDataTable.js +130 -149
- package/src/components/TabPane.js +3 -3
- package/src/components/Tabs.js +20 -27
- package/src/components/modal/Confirmation.js +16 -13
- package/src/components/modal/Modal.js +23 -35
- package/src/components/modal/UseModal.js +8 -8
- package/src/context.js +4 -4
- package/src/index.html +1 -4
- package/src/index.js +6 -7
- package/src/scss/_data-table.scss +1 -1
- package/src/scss/_variables.scss +1 -1
- package/src/scss/choose-vis-tab.scss +63 -63
- package/src/scss/configure-tab.scss +17 -17
- package/src/scss/data-import.scss +177 -177
- package/src/scss/main.scss +31 -32
|
@@ -16,7 +16,6 @@ import LinkIcon from '../assets/icons/link.svg'
|
|
|
16
16
|
import FileUploadIcon from '../assets/icons/file-upload-solid.svg'
|
|
17
17
|
import CloseIcon from '@cdc/core/assets/icon-close.svg'
|
|
18
18
|
|
|
19
|
-
|
|
20
19
|
import validMapData from '../../example/valid-data-map.csv'
|
|
21
20
|
import validChartData from '../../example/valid-data-chart.csv'
|
|
22
21
|
import validCountyMapData from '../../example/valid-county-data.csv'
|
|
@@ -29,50 +28,39 @@ import '../scss/data-import.scss'
|
|
|
29
28
|
import '@cdc/core/styles/v2/components/data-designer.scss'
|
|
30
29
|
|
|
31
30
|
export default function DataImport() {
|
|
32
|
-
const {
|
|
33
|
-
config,
|
|
34
|
-
setConfig,
|
|
35
|
-
errors,
|
|
36
|
-
setErrors,
|
|
37
|
-
errorMessages,
|
|
38
|
-
maxFileSize,
|
|
39
|
-
setGlobalActive,
|
|
40
|
-
tempConfig,
|
|
41
|
-
setTempConfig,
|
|
42
|
-
sharepath
|
|
43
|
-
} = useContext(GlobalState)
|
|
31
|
+
const { config, setConfig, errors, setErrors, errorMessages, maxFileSize, setGlobalActive, tempConfig, setTempConfig, sharepath } = useContext(GlobalState)
|
|
44
32
|
|
|
45
33
|
const { overlay } = useGlobalContext()
|
|
46
34
|
|
|
47
35
|
const transform = new DataTransform()
|
|
48
36
|
|
|
49
|
-
const [
|
|
37
|
+
const [externalURL, setExternalURL] = useState(config.dataFileSourceType === 'url' ? config.dataFileName : config.dataUrl || '')
|
|
50
38
|
|
|
51
|
-
const [
|
|
39
|
+
const [keepURL, setKeepURL] = useState(!!config.dataUrl)
|
|
52
40
|
|
|
53
|
-
const [
|
|
41
|
+
const [addingDataset, setAddingDataset] = useState(config.type === 'dashboard' || !config.data)
|
|
54
42
|
|
|
55
|
-
const [
|
|
43
|
+
const [editingDataset, setEditingDataset] = useState()
|
|
56
44
|
|
|
57
45
|
const supportedDataTypes = {
|
|
58
46
|
'.csv': 'text/csv',
|
|
59
47
|
'.json': 'application/json'
|
|
60
48
|
}
|
|
61
49
|
|
|
62
|
-
const displayFileName =
|
|
63
|
-
const nameParts = name.split('/')
|
|
64
|
-
return nameParts[nameParts.length - 1]
|
|
50
|
+
const displayFileName = name => {
|
|
51
|
+
const nameParts = name.split('/')
|
|
52
|
+
return nameParts[nameParts.length - 1]
|
|
65
53
|
}
|
|
66
54
|
|
|
67
|
-
const displaySize =
|
|
68
|
-
if(size === undefined) return ''
|
|
55
|
+
const displaySize = size => {
|
|
56
|
+
if (size === undefined) return ''
|
|
69
57
|
|
|
70
|
-
if(size > Math.pow(1024, 3)){
|
|
71
|
-
return Math.round(size / Math.pow(1024, 3) * 100) / 100 + ' GB'
|
|
72
|
-
} else if(size > Math.pow(1024, 2)){
|
|
73
|
-
return Math.round(size / Math.pow(1024, 2) * 100) / 100 + ' MB'
|
|
74
|
-
} else if(size > 1024){
|
|
75
|
-
return Math.round(size / 1024 * 100) / 100 + ' KB'
|
|
58
|
+
if (size > Math.pow(1024, 3)) {
|
|
59
|
+
return Math.round((size / Math.pow(1024, 3)) * 100) / 100 + ' GB'
|
|
60
|
+
} else if (size > Math.pow(1024, 2)) {
|
|
61
|
+
return Math.round((size / Math.pow(1024, 2)) * 100) / 100 + ' MB'
|
|
62
|
+
} else if (size > 1024) {
|
|
63
|
+
return Math.round((size / 1024) * 100) / 100 + ' KB'
|
|
76
64
|
} else {
|
|
77
65
|
return size + ' B'
|
|
78
66
|
}
|
|
@@ -82,16 +70,13 @@ export default function DataImport() {
|
|
|
82
70
|
* Check to see all series for the viz exists in the new dataset
|
|
83
71
|
*/
|
|
84
72
|
const dataExists = (newData, oldSeries, oldAxisX) => {
|
|
85
|
-
|
|
86
73
|
// Loop through old series to make sure each exists in the new data
|
|
87
74
|
oldSeries.map(function (currentValue, index, newData) {
|
|
88
|
-
if (!newData.find(element => element.dataKey === currentValue.dataKey))
|
|
89
|
-
return false
|
|
75
|
+
if (!newData.find(element => element.dataKey === currentValue.dataKey)) return false
|
|
90
76
|
})
|
|
91
77
|
|
|
92
78
|
// Is the X Axis still in the dataset?
|
|
93
|
-
if (newData.columns.indexOf(oldAxisX) < 0)
|
|
94
|
-
return false
|
|
79
|
+
if (newData.columns.indexOf(oldAxisX) < 0) return false
|
|
95
80
|
|
|
96
81
|
return true
|
|
97
82
|
}
|
|
@@ -109,20 +94,18 @@ export default function DataImport() {
|
|
|
109
94
|
const fileExtension = Object.keys(supportedDataTypes).find(extension => dataURL.pathname.endsWith(extension))
|
|
110
95
|
|
|
111
96
|
try {
|
|
112
|
-
const response = await get(dataURL,
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
})
|
|
97
|
+
const response = await get(dataURL, {
|
|
98
|
+
responseType: 'blob'
|
|
99
|
+
}).then(response => {
|
|
100
|
+
responseBlob = response.data
|
|
101
|
+
|
|
102
|
+
// Sometimes the files are coming in as plain text types... Maybe when saved from Macs
|
|
103
|
+
if (fileExtension === '.csv' && responseBlob.type === 'text/plain') {
|
|
104
|
+
responseBlob = responseBlob.slice(0, responseBlob.size, 'text/csv')
|
|
105
|
+
} else if (fileExtension === '.json' && responseBlob.type === 'text/plain') {
|
|
106
|
+
responseBlob = responseBlob.slice(0, responseBlob.size, 'application/json')
|
|
107
|
+
}
|
|
108
|
+
})
|
|
126
109
|
} catch (err) {
|
|
127
110
|
console.error(err)
|
|
128
111
|
|
|
@@ -138,7 +121,7 @@ export default function DataImport() {
|
|
|
138
121
|
return responseBlob
|
|
139
122
|
}
|
|
140
123
|
|
|
141
|
-
const onDrop = ([
|
|
124
|
+
const onDrop = ([uploadedFile]) => loadData(uploadedFile, editingDataset, editingDataset)
|
|
142
125
|
|
|
143
126
|
/**
|
|
144
127
|
* Handle loading data
|
|
@@ -155,24 +138,24 @@ export default function DataImport() {
|
|
|
155
138
|
// const rounded = new Date(date.getTime() - (date.getTime() % round));
|
|
156
139
|
// const trimmedDate = rounded.toString().replace(/\s+/g, "");
|
|
157
140
|
|
|
158
|
-
const newUrl = new URL(fileName)
|
|
141
|
+
const newUrl = new URL(fileName)
|
|
159
142
|
// newUrl.searchParams.append("v", trimmedDate);
|
|
160
143
|
|
|
161
|
-
fileSourceType =
|
|
144
|
+
fileSourceType = 'url'
|
|
162
145
|
try {
|
|
163
146
|
fileData = await loadExternal()
|
|
164
147
|
fileSource = externalURL
|
|
165
148
|
} catch (error) {
|
|
166
|
-
setErrors([
|
|
149
|
+
setErrors([error])
|
|
167
150
|
return
|
|
168
151
|
}
|
|
169
152
|
}
|
|
170
153
|
|
|
171
|
-
let fileSize = fileData.size
|
|
154
|
+
let fileSize = fileData.size
|
|
172
155
|
|
|
173
156
|
// Check if file is too big
|
|
174
|
-
if (fileSize >
|
|
175
|
-
setErrors([
|
|
157
|
+
if (fileSize > maxFileSize * 1048576) {
|
|
158
|
+
setErrors([errorMessages.fileTooLarge])
|
|
176
159
|
return
|
|
177
160
|
}
|
|
178
161
|
|
|
@@ -192,7 +175,7 @@ export default function DataImport() {
|
|
|
192
175
|
let filereader = new FileReader()
|
|
193
176
|
|
|
194
177
|
// Set encoding for CSV files - needed to render special characters properly
|
|
195
|
-
let encoding =
|
|
178
|
+
let encoding = mimeType === 'text/csv' ? 'ISO-8859-1' : ''
|
|
196
179
|
|
|
197
180
|
filereader.onload = function () {
|
|
198
181
|
let text = this.result
|
|
@@ -206,12 +189,12 @@ export default function DataImport() {
|
|
|
206
189
|
try {
|
|
207
190
|
text = JSON.parse(text)
|
|
208
191
|
} catch (errors) {
|
|
209
|
-
setErrors([
|
|
192
|
+
setErrors([errorMessages.formatting])
|
|
210
193
|
return
|
|
211
194
|
}
|
|
212
195
|
break
|
|
213
196
|
default:
|
|
214
|
-
setErrors([
|
|
197
|
+
setErrors([errorMessages.fileType])
|
|
215
198
|
return
|
|
216
199
|
}
|
|
217
200
|
|
|
@@ -221,22 +204,22 @@ export default function DataImport() {
|
|
|
221
204
|
|
|
222
205
|
if (config.data && config.series) {
|
|
223
206
|
if (dataExists(text, config.series, config?.xAxis.dataKey)) {
|
|
224
|
-
if(config.type === 'dashboard'){
|
|
225
|
-
let newDatasets = {...config.datasets}
|
|
207
|
+
if (config.type === 'dashboard') {
|
|
208
|
+
let newDatasets = { ...config.datasets }
|
|
226
209
|
|
|
227
|
-
Object.keys(newDatasets).forEach(datasetKey => newDatasets[datasetKey].preview = false)
|
|
210
|
+
Object.keys(newDatasets).forEach(datasetKey => (newDatasets[datasetKey].preview = false))
|
|
228
211
|
|
|
229
212
|
newDatasets[editingDatasetKey || fileSource] = {
|
|
230
213
|
data: text, // new data
|
|
231
214
|
dataFileSize: fileSize,
|
|
232
215
|
dataFileName: fileSource, // new file source
|
|
233
|
-
dataFileSourceType: fileSourceType
|
|
216
|
+
dataFileSourceType: fileSourceType, // new file source type
|
|
234
217
|
dataFileFormat: fileExtension.replace('.', '').toUpperCase(),
|
|
235
218
|
preview: true
|
|
236
219
|
}
|
|
237
220
|
|
|
238
|
-
if(keepURL){
|
|
239
|
-
newDatasets[editingDatasetKey || fileSource].dataUrl = fileSource
|
|
221
|
+
if (keepURL) {
|
|
222
|
+
newDatasets[editingDatasetKey || fileSource].dataUrl = fileSource
|
|
240
223
|
}
|
|
241
224
|
|
|
242
225
|
setConfig({
|
|
@@ -253,35 +236,38 @@ export default function DataImport() {
|
|
|
253
236
|
dataFileSourceType: fileSourceType, // new file source type
|
|
254
237
|
formattedData: transform.developerStandardize(text, config.dataDescription)
|
|
255
238
|
}
|
|
256
|
-
if(keepURL){
|
|
257
|
-
newConfig.dataUrl = fileSource
|
|
239
|
+
if (keepURL) {
|
|
240
|
+
newConfig.dataUrl = fileSource
|
|
258
241
|
}
|
|
259
242
|
setConfig(newConfig)
|
|
260
243
|
}
|
|
261
244
|
} else {
|
|
262
|
-
resetEditor(
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
245
|
+
resetEditor(
|
|
246
|
+
{
|
|
247
|
+
data: text,
|
|
248
|
+
dataFileName: fileSource,
|
|
249
|
+
dataFileSourceType: fileSourceType
|
|
250
|
+
},
|
|
251
|
+
'It appears that your data does not contain all of the columns that your last dataset contained. Continuing will reset your configuration. Do you want to continue?'
|
|
252
|
+
)
|
|
267
253
|
}
|
|
268
254
|
} else {
|
|
269
|
-
if(config.type === 'dashboard') {
|
|
270
|
-
let newDatasets = {...config.datasets}
|
|
255
|
+
if (config.type === 'dashboard') {
|
|
256
|
+
let newDatasets = { ...config.datasets }
|
|
271
257
|
|
|
272
|
-
Object.keys(newDatasets).forEach(datasetKey => newDatasets[datasetKey].preview = false)
|
|
258
|
+
Object.keys(newDatasets).forEach(datasetKey => (newDatasets[datasetKey].preview = false))
|
|
273
259
|
|
|
274
260
|
newDatasets[editingDatasetKey || fileSource] = {
|
|
275
261
|
data: text, // new data
|
|
276
262
|
dataFileSize: fileSize,
|
|
277
263
|
dataFileName: fileSource, // new file source
|
|
278
|
-
dataFileSourceType: fileSourceType
|
|
264
|
+
dataFileSourceType: fileSourceType, // new file source type
|
|
279
265
|
dataFileFormat: fileExtension.replace('.', '').toUpperCase(),
|
|
280
266
|
preview: true
|
|
281
267
|
}
|
|
282
268
|
|
|
283
|
-
if(keepURL){
|
|
284
|
-
newDatasets[editingDatasetKey || fileSource].dataUrl = fileSource
|
|
269
|
+
if (keepURL) {
|
|
270
|
+
newDatasets[editingDatasetKey || fileSource].dataUrl = fileSource
|
|
285
271
|
}
|
|
286
272
|
|
|
287
273
|
setConfig({ ...config, datasets: newDatasets })
|
|
@@ -292,25 +278,24 @@ export default function DataImport() {
|
|
|
292
278
|
data: text, // new data
|
|
293
279
|
dataFileName: fileSource, // new file source
|
|
294
280
|
dataFileSourceType: fileSourceType, // new file source type
|
|
295
|
-
formattedData: transform.developerStandardize(text, config.dataDescription)// new file source type
|
|
281
|
+
formattedData: transform.developerStandardize(text, config.dataDescription) // new file source type
|
|
296
282
|
}
|
|
297
|
-
if(keepURL){
|
|
298
|
-
newConfig.dataUrl = fileSource
|
|
283
|
+
if (keepURL) {
|
|
284
|
+
newConfig.dataUrl = fileSource
|
|
299
285
|
}
|
|
300
286
|
setConfig(newConfig)
|
|
301
287
|
}
|
|
302
288
|
}
|
|
303
289
|
|
|
304
|
-
if(editingDataset){
|
|
305
|
-
setEditingDataset(undefined)
|
|
290
|
+
if (editingDataset) {
|
|
291
|
+
setEditingDataset(undefined)
|
|
306
292
|
}
|
|
307
|
-
setAddingDataset(false)
|
|
308
|
-
setExternalURL('')
|
|
309
|
-
setKeepURL(false)
|
|
293
|
+
setAddingDataset(false)
|
|
294
|
+
setExternalURL('')
|
|
295
|
+
setKeepURL(false)
|
|
310
296
|
} catch (err) {
|
|
311
297
|
setErrors(err)
|
|
312
298
|
}
|
|
313
|
-
|
|
314
299
|
}
|
|
315
300
|
filereader.readAsText(fileData, encoding)
|
|
316
301
|
}
|
|
@@ -333,12 +318,12 @@ export default function DataImport() {
|
|
|
333
318
|
}, [])
|
|
334
319
|
|
|
335
320
|
const updateDescriptionProp = (visualizationKey, datasetKey, key, value) => {
|
|
336
|
-
if(config.type === 'dashboard') {
|
|
321
|
+
if (config.type === 'dashboard') {
|
|
337
322
|
let dataDescription = { ...config.datasets[datasetKey].dataDescription, [key]: value }
|
|
338
323
|
let formattedData = transform.developerStandardize(config.datasets[datasetKey].data, dataDescription)
|
|
339
324
|
|
|
340
|
-
let newDatasets = {...config.datasets}
|
|
341
|
-
newDatasets[datasetKey] = {...newDatasets[datasetKey], dataDescription, formattedData}
|
|
325
|
+
let newDatasets = { ...config.datasets }
|
|
326
|
+
newDatasets[datasetKey] = { ...newDatasets[datasetKey], dataDescription, formattedData }
|
|
342
327
|
|
|
343
328
|
setConfig({ ...config, datasets: newDatasets })
|
|
344
329
|
} else {
|
|
@@ -350,25 +335,25 @@ export default function DataImport() {
|
|
|
350
335
|
}
|
|
351
336
|
|
|
352
337
|
const changeKeepURL = (value, editingDatasetKey) => {
|
|
353
|
-
if(editingDatasetKey){
|
|
354
|
-
let newDatasets = {...config.datasets}
|
|
355
|
-
if(value === false){
|
|
356
|
-
delete newDatasets[editingDatasetKey].dataUrl
|
|
338
|
+
if (editingDatasetKey) {
|
|
339
|
+
let newDatasets = { ...config.datasets }
|
|
340
|
+
if (value === false) {
|
|
341
|
+
delete newDatasets[editingDatasetKey].dataUrl
|
|
357
342
|
} else {
|
|
358
|
-
newDatasets[editingDatasetKey].dataUrl = newDatasets[editingDatasetKey].dataFileName
|
|
343
|
+
newDatasets[editingDatasetKey].dataUrl = newDatasets[editingDatasetKey].dataFileName
|
|
359
344
|
}
|
|
360
|
-
setConfig({...config, datasets: newDatasets})
|
|
361
|
-
} else if(config.type !== 'dashboard') {
|
|
362
|
-
let newConfig = {...config}
|
|
363
|
-
if(value === false){
|
|
364
|
-
delete newConfig.dataUrl
|
|
345
|
+
setConfig({ ...config, datasets: newDatasets })
|
|
346
|
+
} else if (config.type !== 'dashboard') {
|
|
347
|
+
let newConfig = { ...config }
|
|
348
|
+
if (value === false) {
|
|
349
|
+
delete newConfig.dataUrl
|
|
365
350
|
} else {
|
|
366
|
-
newConfig.dataUrl = newConfig.dataFileName
|
|
351
|
+
newConfig.dataUrl = newConfig.dataFileName
|
|
367
352
|
}
|
|
368
|
-
setConfig(newConfig)
|
|
353
|
+
setConfig(newConfig)
|
|
369
354
|
}
|
|
370
|
-
setKeepURL(value)
|
|
371
|
-
}
|
|
355
|
+
setKeepURL(value)
|
|
356
|
+
}
|
|
372
357
|
|
|
373
358
|
const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop })
|
|
374
359
|
const { getRootProps: getRootProps2, getInputProps: getInputProps2, isDragActive: isDragActive2 } = useDropzone({ onDrop })
|
|
@@ -378,17 +363,14 @@ export default function DataImport() {
|
|
|
378
363
|
|
|
379
364
|
return (
|
|
380
365
|
<>
|
|
381
|
-
<form className=
|
|
382
|
-
<input id=
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
<button className="input-group-text btn btn-primary px-4" type="submit" id="load-data"
|
|
386
|
-
onClick={() => loadData(null, externalURL, editingDatasetKey)}>Load
|
|
366
|
+
<form className='input-group d-flex' onSubmit={e => e.preventDefault()}>
|
|
367
|
+
<input id='external-data' type='text' className='form-control flex-grow-1 border-right-0' placeholder='e.g., https://data.cdc.gov/resources/file.json' aria-label='Load data from external URL' aria-describedby='load-data' value={externalURL} onChange={e => setExternalURL(e.target.value)} />
|
|
368
|
+
<button className='input-group-text btn btn-primary px-4' type='submit' id='load-data' onClick={() => loadData(null, externalURL, editingDatasetKey)}>
|
|
369
|
+
Load
|
|
387
370
|
</button>
|
|
388
371
|
</form>
|
|
389
|
-
<label htmlFor=
|
|
390
|
-
<input type=
|
|
391
|
-
load from URL (normally will only pull once)
|
|
372
|
+
<label htmlFor='keep-url' className='mt-1 d-flex keep-url'>
|
|
373
|
+
<input type='checkbox' id='keep-url' checked={keepURL} onChange={() => changeKeepURL(!keepURL, editingDatasetKey)} /> Always load from URL (normally will only pull once)
|
|
392
374
|
</label>
|
|
393
375
|
</>
|
|
394
376
|
)
|
|
@@ -407,119 +389,145 @@ export default function DataImport() {
|
|
|
407
389
|
}
|
|
408
390
|
|
|
409
391
|
const resetButton = () => {
|
|
410
|
-
return (
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
392
|
+
return (
|
|
393
|
+
//todo convert to modal
|
|
394
|
+
<button className='btn danger' onClick={() => resetEditor({ type: config.type, visualizationType: config.visualizationType }, 'Reseting will remove your data and settings. Do you want to continue?')}>
|
|
395
|
+
Clear
|
|
396
|
+
<CloseIcon />
|
|
414
397
|
</button>
|
|
415
398
|
)
|
|
416
399
|
}
|
|
417
400
|
|
|
418
401
|
const setGlobalDatasetProp = (datasetKey, prop, value) => {
|
|
419
|
-
let newDatasets = {...config.datasets}
|
|
402
|
+
let newDatasets = { ...config.datasets }
|
|
420
403
|
|
|
421
|
-
if(value === true){
|
|
404
|
+
if (value === true) {
|
|
422
405
|
Object.keys(newDatasets).forEach(datasetKeyIter => {
|
|
423
|
-
if(datasetKeyIter !== datasetKey){
|
|
424
|
-
newDatasets[datasetKeyIter][prop] = false
|
|
406
|
+
if (datasetKeyIter !== datasetKey) {
|
|
407
|
+
newDatasets[datasetKeyIter][prop] = false
|
|
425
408
|
} else {
|
|
426
|
-
newDatasets[datasetKeyIter][prop] = true
|
|
409
|
+
newDatasets[datasetKeyIter][prop] = true
|
|
427
410
|
}
|
|
428
411
|
})
|
|
429
412
|
} else {
|
|
430
|
-
newDatasets[datasetKey][prop] = value
|
|
413
|
+
newDatasets[datasetKey][prop] = value
|
|
431
414
|
}
|
|
432
415
|
|
|
433
|
-
setConfig({...config, datasets: newDatasets})
|
|
434
|
-
}
|
|
416
|
+
setConfig({ ...config, datasets: newDatasets })
|
|
417
|
+
}
|
|
435
418
|
|
|
436
|
-
const removeDataset =
|
|
437
|
-
let newDatasets = {...config.datasets}
|
|
438
|
-
let newVisualizations = {...config.visualizations}
|
|
419
|
+
const removeDataset = datasetKey => {
|
|
420
|
+
let newDatasets = { ...config.datasets }
|
|
421
|
+
let newVisualizations = { ...config.visualizations }
|
|
439
422
|
|
|
440
423
|
Object.keys(newVisualizations).forEach(vizKey => {
|
|
441
|
-
if(newVisualizations[vizKey].dataKey === datasetKey) {
|
|
442
|
-
delete newVisualizations[vizKey].dataKey
|
|
424
|
+
if (newVisualizations[vizKey].dataKey === datasetKey) {
|
|
425
|
+
delete newVisualizations[vizKey].dataKey
|
|
443
426
|
}
|
|
444
|
-
})
|
|
427
|
+
})
|
|
445
428
|
|
|
446
|
-
delete newDatasets[datasetKey]
|
|
429
|
+
delete newDatasets[datasetKey]
|
|
447
430
|
|
|
448
|
-
setConfig({...config, datasets: newDatasets, visualizations: newVisualizations})
|
|
431
|
+
setConfig({ ...config, datasets: newDatasets, visualizations: newVisualizations })
|
|
449
432
|
}
|
|
450
433
|
|
|
451
434
|
const renameDataset = (oldName, newName) => {
|
|
452
|
-
if(oldName === newName) return
|
|
435
|
+
if (oldName === newName) return
|
|
453
436
|
|
|
454
|
-
let newDatasets = {...config.datasets}
|
|
455
|
-
let newVisualizations = {...config.visualizations}
|
|
437
|
+
let newDatasets = { ...config.datasets }
|
|
438
|
+
let newVisualizations = { ...config.visualizations }
|
|
456
439
|
|
|
457
|
-
let suffix = 2
|
|
458
|
-
let originalName = newName
|
|
459
|
-
while(newDatasets[newName]){
|
|
460
|
-
newName = originalName + '-' + suffix
|
|
461
|
-
suffix
|
|
440
|
+
let suffix = 2
|
|
441
|
+
let originalName = newName
|
|
442
|
+
while (newDatasets[newName]) {
|
|
443
|
+
newName = originalName + '-' + suffix
|
|
444
|
+
suffix++
|
|
462
445
|
}
|
|
463
446
|
|
|
464
|
-
newDatasets[newName] = newDatasets[oldName]
|
|
465
|
-
delete newDatasets[oldName]
|
|
447
|
+
newDatasets[newName] = newDatasets[oldName]
|
|
448
|
+
delete newDatasets[oldName]
|
|
466
449
|
|
|
467
450
|
Object.keys(newVisualizations).forEach(vizKey => {
|
|
468
|
-
if(newVisualizations[vizKey].dataKey === oldName) {
|
|
469
|
-
newVisualizations[vizKey].dataKey = newName
|
|
451
|
+
if (newVisualizations[vizKey].dataKey === oldName) {
|
|
452
|
+
newVisualizations[vizKey].dataKey = newName
|
|
470
453
|
}
|
|
471
|
-
})
|
|
454
|
+
})
|
|
472
455
|
|
|
473
|
-
setConfig({...config, datasets: newDatasets, visualizations: newVisualizations})
|
|
456
|
+
setConfig({ ...config, datasets: newDatasets, visualizations: newVisualizations })
|
|
474
457
|
}
|
|
475
458
|
|
|
476
|
-
let previewData,
|
|
477
|
-
|
|
478
|
-
readyToConfigure =
|
|
459
|
+
let previewData,
|
|
460
|
+
configureData,
|
|
461
|
+
readyToConfigure = false
|
|
462
|
+
if (config.type === 'dashboard') {
|
|
463
|
+
readyToConfigure = Object.keys(config.datasets).length > 0
|
|
479
464
|
Object.keys(config.datasets).forEach(datasetKey => {
|
|
480
|
-
if(config.datasets[datasetKey].preview){
|
|
481
|
-
previewData = config.datasets[datasetKey].data
|
|
465
|
+
if (config.datasets[datasetKey].preview) {
|
|
466
|
+
previewData = config.datasets[datasetKey].data
|
|
482
467
|
}
|
|
483
|
-
})
|
|
468
|
+
})
|
|
484
469
|
} else {
|
|
485
|
-
previewData = config.data
|
|
486
|
-
configureData = config
|
|
487
|
-
readyToConfigure = !!config.formattedData
|
|
470
|
+
previewData = config.data
|
|
471
|
+
configureData = config
|
|
472
|
+
readyToConfigure = !!config.formattedData
|
|
488
473
|
}
|
|
489
474
|
|
|
490
475
|
return (
|
|
491
476
|
<>
|
|
492
|
-
<div className=
|
|
477
|
+
<div className='left-col'>
|
|
493
478
|
{config.type === 'dashboard' && Object.keys(config.datasets).length > 0 && (
|
|
494
479
|
<>
|
|
495
|
-
<div className=
|
|
480
|
+
<div className='heading-3'>Data Sources</div>
|
|
496
481
|
<table>
|
|
497
482
|
<thead>
|
|
498
483
|
<tr>
|
|
499
|
-
<th>Name</th
|
|
484
|
+
<th>Name</th>
|
|
485
|
+
<th>Size</th>
|
|
486
|
+
<th>Type</th>
|
|
487
|
+
<th colSpan='4'>Actions</th>
|
|
500
488
|
</tr>
|
|
501
489
|
</thead>
|
|
502
490
|
<tbody>
|
|
503
|
-
{Object.keys(config.datasets).map(
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
491
|
+
{Object.keys(config.datasets).map(
|
|
492
|
+
datasetKey =>
|
|
493
|
+
config.datasets[datasetKey].dataFileName && (
|
|
494
|
+
<tr key={`tr-${datasetKey}`}>
|
|
495
|
+
<td>
|
|
496
|
+
<input className='dataset-name-input' type='text' defaultValue={datasetKey} onBlur={e => renameDataset(datasetKey, e.target.value)} />
|
|
497
|
+
</td>
|
|
498
|
+
<td>{displaySize(config.datasets[datasetKey].dataFileSize)}</td>
|
|
499
|
+
<td>{config.datasets[datasetKey].dataFileFormat}</td>
|
|
500
|
+
<td>
|
|
501
|
+
<button className='btn btn-primary' onClick={() => setGlobalDatasetProp(datasetKey, 'preview', true)}>
|
|
502
|
+
Preview Data
|
|
503
|
+
</button>
|
|
504
|
+
</td>
|
|
505
|
+
<td>
|
|
506
|
+
<button
|
|
507
|
+
className='btn btn-primary'
|
|
508
|
+
onClick={() => {
|
|
509
|
+
if (editingDataset === datasetKey) {
|
|
510
|
+
setEditingDataset(undefined)
|
|
511
|
+
setExternalURL('')
|
|
512
|
+
setKeepURL(false)
|
|
513
|
+
} else {
|
|
514
|
+
setEditingDataset(datasetKey)
|
|
515
|
+
setExternalURL(config.datasets[datasetKey].dataUrl || config.datasets[datasetKey].dataFileName)
|
|
516
|
+
setKeepURL(!!config.datasets[datasetKey].dataUrl)
|
|
517
|
+
}
|
|
518
|
+
}}
|
|
519
|
+
>
|
|
520
|
+
Edit Data
|
|
521
|
+
</button>
|
|
522
|
+
</td>
|
|
523
|
+
<td>
|
|
524
|
+
<button className='btn btn-primary' onClick={() => removeDataset(datasetKey)}>
|
|
525
|
+
X
|
|
526
|
+
</button>
|
|
527
|
+
</td>
|
|
528
|
+
</tr>
|
|
529
|
+
)
|
|
530
|
+
)}
|
|
523
531
|
</tbody>
|
|
524
532
|
</table>
|
|
525
533
|
</>
|
|
@@ -529,33 +537,28 @@ export default function DataImport() {
|
|
|
529
537
|
<>
|
|
530
538
|
{config.type !== 'dashboard' && (
|
|
531
539
|
<>
|
|
532
|
-
<div className=
|
|
533
|
-
<div className=
|
|
540
|
+
<div className='heading-3'>Data Source</div>
|
|
541
|
+
<div className='file-loaded-area'>
|
|
534
542
|
{config.dataFileSourceType === 'file' && (
|
|
535
|
-
<div className=
|
|
536
|
-
<div
|
|
537
|
-
className={isDragActive2 ? 'drag-active cdcdataviz-file-selector loaded-file' : 'cdcdataviz-file-selector loaded-file'} {...getRootProps2()}>
|
|
543
|
+
<div className='data-source-options'>
|
|
544
|
+
<div className={isDragActive2 ? 'drag-active cdcdataviz-file-selector loaded-file' : 'cdcdataviz-file-selector loaded-file'} {...getRootProps2()}>
|
|
538
545
|
<input {...getInputProps2()} />
|
|
539
|
-
{
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
{resetButton()}
|
|
546
|
+
{isDragActive2 ? (
|
|
547
|
+
<p>Drop file here</p>
|
|
548
|
+
) : (
|
|
549
|
+
<p>
|
|
550
|
+
<FileUploadIcon /> <span>{config.dataFileName ?? 'Replace data file'}</span>
|
|
551
|
+
</p>
|
|
552
|
+
)}
|
|
547
553
|
</div>
|
|
554
|
+
<div>{resetButton()}</div>
|
|
548
555
|
</div>
|
|
549
556
|
)}
|
|
550
557
|
|
|
551
558
|
{config.dataFileSourceType === 'url' && (
|
|
552
|
-
<div className=
|
|
553
|
-
<div>
|
|
554
|
-
|
|
555
|
-
</div>
|
|
556
|
-
<div>
|
|
557
|
-
{resetButton()}
|
|
558
|
-
</div>
|
|
559
|
+
<div className='url-source-options'>
|
|
560
|
+
<div>{loadFileFromUrl(externalURL)}</div>
|
|
561
|
+
<div>{resetButton()}</div>
|
|
559
562
|
</div>
|
|
560
563
|
)}
|
|
561
564
|
</div>
|
|
@@ -566,75 +569,74 @@ export default function DataImport() {
|
|
|
566
569
|
</>
|
|
567
570
|
)}
|
|
568
571
|
|
|
569
|
-
{(editingDataset || addingDataset) && (
|
|
570
|
-
<div className=
|
|
571
|
-
<div className=
|
|
572
|
+
{(editingDataset || addingDataset) && ( // dataFileSourceType needs to be checked here since earlier versions did not track this state
|
|
573
|
+
<div className='load-data-area'>
|
|
574
|
+
<div className='heading-3'>{editingDataset ? `Editing ${editingDataset}` : 'Add Dataset'}</div>
|
|
572
575
|
<Tabs startingTab={editingDataset && config.datasets[editingDataset].dataFileSourceType === 'url' ? 1 : 0}>
|
|
573
|
-
<TabPane title=
|
|
574
|
-
{sharepath &&
|
|
575
|
-
|
|
576
|
-
The share path set for this website is: {sharepath}
|
|
577
|
-
</p>
|
|
578
|
-
}
|
|
579
|
-
<div
|
|
580
|
-
className={isDragActive ? 'drag-active cdcdataviz-file-selector' : 'cdcdataviz-file-selector'} {...getRootProps()}>
|
|
576
|
+
<TabPane title='Upload File' icon={<FileUploadIcon className='inline-icon' />}>
|
|
577
|
+
{sharepath && <p className='alert--info'>The share path set for this website is: {sharepath}</p>}
|
|
578
|
+
<div className={isDragActive ? 'drag-active cdcdataviz-file-selector' : 'cdcdataviz-file-selector'} {...getRootProps()}>
|
|
581
579
|
<input {...getInputProps()} />
|
|
582
|
-
{
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
580
|
+
{isDragActive ? (
|
|
581
|
+
<p>Drop file here</p>
|
|
582
|
+
) : (
|
|
583
|
+
<p>
|
|
584
|
+
Drag file to this area, or <span>select a file</span>.
|
|
585
|
+
</p>
|
|
586
|
+
)}
|
|
587
587
|
</div>
|
|
588
588
|
</TabPane>
|
|
589
|
-
<TabPane title=
|
|
589
|
+
<TabPane title='Load from URL' icon={<LinkIcon className='inline-icon' />}>
|
|
590
590
|
{loadFileFromUrl(editingDataset && config.datasets[editingDataset].dataFileSourceType === 'url' ? config.datasets[editingDataset].dataFileName : externalURL, editingDataset)}
|
|
591
591
|
</TabPane>
|
|
592
592
|
</Tabs>
|
|
593
|
-
{errors &&
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
593
|
+
{errors &&
|
|
594
|
+
(errors.map
|
|
595
|
+
? errors.map((message, index) => (
|
|
596
|
+
<div className='error-box slim mt-2' key={`error-${message}`}>
|
|
597
|
+
<span>{message}</span> <CloseIcon className='inline-icon dismiss-error' onClick={() => setErrors(errors.filter((val, i) => i !== index))} />
|
|
598
|
+
</div>
|
|
599
|
+
))
|
|
600
|
+
: errors.message)}
|
|
601
|
+
<p className='footnote'>
|
|
602
|
+
Supported file types: {Object.keys(supportedDataTypes).join(', ')}. Maximum file size {maxFileSize}MB.
|
|
603
|
+
</p>
|
|
601
604
|
{/* TODO: Add more sample data in, but this will do for now. */}
|
|
602
|
-
<span className=
|
|
603
|
-
<ul className=
|
|
604
|
-
<li
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
</li>
|
|
608
|
-
<li
|
|
609
|
-
onClick={() => loadData(new Blob([ validChartData ], { type: 'text/csv' }), 'valid-data-chart.csv', editingDataset)}>Chart
|
|
610
|
-
Sample Data
|
|
611
|
-
</li>
|
|
612
|
-
<li
|
|
613
|
-
onClick={() => loadData(new Blob([ validCountyMapData ], { type: 'text/csv' }), 'valid-county-data.csv', editingDataset)}>United
|
|
614
|
-
States Counties Sample Data
|
|
615
|
-
</li>
|
|
616
|
-
<li
|
|
617
|
-
onClick={() => loadData(new Blob([ sampleGeoPoints ], { type: 'text/csv' }), 'supported-cities.csv', editingDataset)}>
|
|
618
|
-
Sample Geo Points
|
|
619
|
-
</li>
|
|
605
|
+
<span className='heading-3'>Load Sample Data:</span>
|
|
606
|
+
<ul className='sample-data-list'>
|
|
607
|
+
<li onClick={() => loadData(new Blob([validMapData], { type: 'text/csv' }), 'valid-data-map.csv', editingDataset)}>United States Sample Data #1</li>
|
|
608
|
+
<li onClick={() => loadData(new Blob([validChartData], { type: 'text/csv' }), 'valid-data-chart.csv', editingDataset)}>Chart Sample Data</li>
|
|
609
|
+
<li onClick={() => loadData(new Blob([validCountyMapData], { type: 'text/csv' }), 'valid-county-data.csv', editingDataset)}>United States Counties Sample Data</li>
|
|
610
|
+
<li onClick={() => loadData(new Blob([sampleGeoPoints], { type: 'text/csv' }), 'supported-cities.csv', editingDataset)}>Sample Geo Points</li>
|
|
620
611
|
</ul>
|
|
621
612
|
</div>
|
|
622
613
|
)}
|
|
623
614
|
|
|
624
|
-
{config.type === 'dashboard' && !addingDataset &&
|
|
615
|
+
{config.type === 'dashboard' && !addingDataset && (
|
|
616
|
+
<p>
|
|
617
|
+
<button className='btn btn-primary' onClick={() => setAddingDataset(true)}>
|
|
618
|
+
+ Add More Files
|
|
619
|
+
</button>
|
|
620
|
+
</p>
|
|
621
|
+
)}
|
|
625
622
|
|
|
626
|
-
{readyToConfigure &&
|
|
623
|
+
{readyToConfigure && (
|
|
624
|
+
<p>
|
|
625
|
+
<button className='btn btn-primary' onClick={() => setGlobalActive(2)}>
|
|
626
|
+
Configure your visualization
|
|
627
|
+
</button>
|
|
628
|
+
</p>
|
|
629
|
+
)}
|
|
627
630
|
|
|
628
|
-
<a href=
|
|
629
|
-
rel="noopener noreferrer" className="guidance-link">
|
|
631
|
+
<a href='https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/data-map.html' target='_blank' rel='noopener noreferrer' className='guidance-link'>
|
|
630
632
|
<div>
|
|
631
633
|
<h3>Get Help</h3>
|
|
632
634
|
<p>Documentation and examples on formatting data and configuring visualizations.</p>
|
|
633
635
|
</div>
|
|
634
636
|
</a>
|
|
635
637
|
</div>
|
|
636
|
-
<div className=
|
|
637
|
-
<PreviewDataTable data={previewData}/>
|
|
638
|
+
<div className='right-col'>
|
|
639
|
+
<PreviewDataTable data={previewData} />
|
|
638
640
|
</div>
|
|
639
641
|
</>
|
|
640
642
|
)
|