@cdc/editor 4.25.6 → 4.25.7-2
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/dist/cdceditor.js +107540 -72477
- package/package.json +9 -9
- package/src/components/ChooseTab.tsx +81 -9
- package/src/components/DataImport/components/DataImport.tsx +135 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cdc/editor",
|
|
3
|
-
"version": "4.25.
|
|
3
|
+
"version": "4.25.7-2",
|
|
4
4
|
"description": "React component for generating a new component entry",
|
|
5
5
|
"moduleName": "CdcEditor",
|
|
6
6
|
"main": "dist/cdceditor",
|
|
@@ -25,13 +25,13 @@
|
|
|
25
25
|
},
|
|
26
26
|
"license": "Apache-2.0",
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@cdc/chart": "^4.25.
|
|
29
|
-
"@cdc/core": "^4.25.
|
|
30
|
-
"@cdc/dashboard": "^4.25.
|
|
31
|
-
"@cdc/data-bite": "^4.25.
|
|
32
|
-
"@cdc/map": "^4.25.
|
|
33
|
-
"@cdc/markup-include": "^4.25.
|
|
34
|
-
"@cdc/waffle-chart": "^4.25.
|
|
28
|
+
"@cdc/chart": "^4.25.7",
|
|
29
|
+
"@cdc/core": "^4.25.7",
|
|
30
|
+
"@cdc/dashboard": "^4.25.7-2",
|
|
31
|
+
"@cdc/data-bite": "^4.25.7",
|
|
32
|
+
"@cdc/map": "^4.25.7-2",
|
|
33
|
+
"@cdc/markup-include": "^4.25.7",
|
|
34
|
+
"@cdc/waffle-chart": "^4.25.7",
|
|
35
35
|
"axios": "^1.9.0",
|
|
36
36
|
"d3": "^7.9.0",
|
|
37
37
|
"react-dropzone": "^14.3.8",
|
|
@@ -41,5 +41,5 @@
|
|
|
41
41
|
"react": "^18.2.0",
|
|
42
42
|
"react-dom": "^18.2.0"
|
|
43
43
|
},
|
|
44
|
-
"gitHead": "
|
|
44
|
+
"gitHead": "432a2d1acab22915fafe793cb9da1f10318ff793"
|
|
45
45
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useContext } from 'react'
|
|
1
|
+
import React, { useContext, useState } from 'react'
|
|
2
2
|
import '../scss/choose-vis-tab.scss'
|
|
3
3
|
|
|
4
4
|
import ConfigContext, { EditorDispatchContext } from '../ConfigContext'
|
|
@@ -28,6 +28,15 @@ import EpiChartIcon from '@cdc/core/assets/icon-epi-chart.svg'
|
|
|
28
28
|
import TableIcon from '@cdc/core/assets/icon-table.svg'
|
|
29
29
|
import Icon from '@cdc/core/components/ui/Icon'
|
|
30
30
|
|
|
31
|
+
import {
|
|
32
|
+
convertVegaConfig,
|
|
33
|
+
getVegaConfigType,
|
|
34
|
+
getVegaErrors,
|
|
35
|
+
getVegaWarnings,
|
|
36
|
+
isVegaConfig,
|
|
37
|
+
parseVegaConfig
|
|
38
|
+
} from '@cdc/core/helpers/vegaConfig'
|
|
39
|
+
|
|
31
40
|
interface ButtonProps {
|
|
32
41
|
icon: React.ReactElement
|
|
33
42
|
id: number
|
|
@@ -41,6 +50,8 @@ interface ButtonProps {
|
|
|
41
50
|
const ChooseTab: React.FC = (): JSX.Element => {
|
|
42
51
|
const { config, tempConfig } = useContext(ConfigContext)
|
|
43
52
|
|
|
53
|
+
const [pastedConfig, setPastedConfig] = useState('')
|
|
54
|
+
|
|
44
55
|
const dispatch = useContext(EditorDispatchContext)
|
|
45
56
|
const rowLabels = ['General', , 'Charts', 'Maps']
|
|
46
57
|
|
|
@@ -49,17 +60,57 @@ const ChooseTab: React.FC = (): JSX.Element => {
|
|
|
49
60
|
const reader = new FileReader()
|
|
50
61
|
reader.onload = e => {
|
|
51
62
|
const text = e.target.result
|
|
52
|
-
|
|
53
|
-
const newConfig = JSON.parse(text as string)
|
|
54
|
-
dispatch({ type: 'EDITOR_SET_CONFIG', payload: newConfig })
|
|
55
|
-
dispatch({ type: 'EDITOR_SET_GLOBALACTIVE', payload: 1 })
|
|
56
|
-
} catch (e) {
|
|
57
|
-
alert('Invalid JSON')
|
|
58
|
-
}
|
|
63
|
+
importConfig(text as string)
|
|
59
64
|
}
|
|
60
65
|
reader.readAsText(file)
|
|
61
66
|
}
|
|
62
67
|
|
|
68
|
+
const importConfig = text => {
|
|
69
|
+
let newConfig
|
|
70
|
+
try {
|
|
71
|
+
newConfig = JSON.parse(text)
|
|
72
|
+
} catch (e) {
|
|
73
|
+
alert('The JSON that was entered is invalid.')
|
|
74
|
+
throw new Error()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const isVega = isVegaConfig(newConfig)
|
|
78
|
+
if (isVega) {
|
|
79
|
+
newConfig = importVegaConfig(newConfig)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
dispatch({ type: 'EDITOR_SET_CONFIG', payload: newConfig })
|
|
83
|
+
dispatch({ type: 'EDITOR_SET_GLOBALACTIVE', payload: isVega && newConfig.data?.length ? 2 : 1 })
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const importVegaConfig = newConfig => {
|
|
87
|
+
let vegaErrors
|
|
88
|
+
try {
|
|
89
|
+
const vegaConfig = parseVegaConfig(newConfig)
|
|
90
|
+
vegaErrors = getVegaErrors(newConfig, vegaConfig)
|
|
91
|
+
if (vegaErrors.length === 0) {
|
|
92
|
+
const configType = getVegaConfigType(vegaConfig)
|
|
93
|
+
const configSubType = configType === 'Map' ? 'United States (State- or County-Level)' : configType
|
|
94
|
+
const button = buttons.find(b => b.label === configSubType)
|
|
95
|
+
const coveConfig = generateNewConfig(JSON.parse(JSON.stringify(button)))
|
|
96
|
+
const vegaWarnings = getVegaWarnings(newConfig, vegaConfig)
|
|
97
|
+
const config = convertVegaConfig(configType, vegaConfig, coveConfig)
|
|
98
|
+
if (vegaWarnings.length) {
|
|
99
|
+
alert(vegaWarnings.join('\n\n'))
|
|
100
|
+
}
|
|
101
|
+
return config
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
vegaErrors = [
|
|
105
|
+
'An unknown error occurred while importing this Vega config. Reach out to the COVE team if you think it should be supported.'
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const errorText = vegaErrors.join('\n\n')
|
|
110
|
+
alert(errorText)
|
|
111
|
+
throw new Error(errorText)
|
|
112
|
+
}
|
|
113
|
+
|
|
63
114
|
const generateNewConfig = props => {
|
|
64
115
|
let newConfig = {}
|
|
65
116
|
switch (props.category) {
|
|
@@ -180,7 +231,7 @@ const ChooseTab: React.FC = (): JSX.Element => {
|
|
|
180
231
|
<hr />
|
|
181
232
|
<div className='form-group'>
|
|
182
233
|
<label htmlFor='uploadConfig'>
|
|
183
|
-
|
|
234
|
+
Select configuration file{' '}
|
|
184
235
|
<Tooltip style={{ textTransform: 'none' }}>
|
|
185
236
|
<Tooltip.Target>
|
|
186
237
|
<Icon display='warningCircle' style={{ marginLeft: '0.5rem' }} />
|
|
@@ -197,6 +248,27 @@ const ChooseTab: React.FC = (): JSX.Element => {
|
|
|
197
248
|
id='uploadConfig'
|
|
198
249
|
onChange={handleUpload}
|
|
199
250
|
/>
|
|
251
|
+
<div className='d-flex align-items-start mt-1'>
|
|
252
|
+
<label htmlFor='uploadConfig'>or paste configuration JSON</label>
|
|
253
|
+
<textarea
|
|
254
|
+
id='pasteConfig'
|
|
255
|
+
className='ms-2 '
|
|
256
|
+
onChange={e => {
|
|
257
|
+
setPastedConfig(e.target.value)
|
|
258
|
+
}}
|
|
259
|
+
placeholder='{ }'
|
|
260
|
+
value={pastedConfig}
|
|
261
|
+
/>
|
|
262
|
+
<button
|
|
263
|
+
className='btn btn-primary px-4 ms-2'
|
|
264
|
+
type='submit'
|
|
265
|
+
id='load-data'
|
|
266
|
+
disabled={!pastedConfig}
|
|
267
|
+
onClick={() => importConfig(pastedConfig)}
|
|
268
|
+
>
|
|
269
|
+
Load
|
|
270
|
+
</button>
|
|
271
|
+
</div>
|
|
200
272
|
</div>
|
|
201
273
|
</div>
|
|
202
274
|
)
|
|
@@ -32,6 +32,13 @@ import { supportedDataTypes } from '../helpers/supportedDataTypes'
|
|
|
32
32
|
import { getFileExtension } from '../helpers/getFileExtension'
|
|
33
33
|
import { parseTextByMimeType } from '../helpers/parseTextByMimeType'
|
|
34
34
|
import { getMimeType } from '../helpers/getMimeType'
|
|
35
|
+
import {
|
|
36
|
+
extractCoveData,
|
|
37
|
+
getSampleVegaJson,
|
|
38
|
+
loadedVegaConfigData,
|
|
39
|
+
parseVegaConfig,
|
|
40
|
+
updateVegaData
|
|
41
|
+
} from '@cdc/core/helpers/vegaConfig'
|
|
35
42
|
|
|
36
43
|
const DataImport = () => {
|
|
37
44
|
const { config, errors, tempConfig, sharepath } = useContext(ConfigContext)
|
|
@@ -49,10 +56,11 @@ const DataImport = () => {
|
|
|
49
56
|
config.dataFileSourceType === 'url' ? config.dataFileName : config.dataUrl || ''
|
|
50
57
|
)
|
|
51
58
|
|
|
52
|
-
const [keepURL, setKeepURL] = useState(!!config.dataUrl)
|
|
59
|
+
const [keepURL, setKeepURL] = useState(!!config.dataUrl || !!config.vegaType)
|
|
53
60
|
const [addingDataset, setAddingDataset] = useState(config.type === 'dashboard' || !config.data)
|
|
54
61
|
const [editingDataset, _setEditingDataset] = useState<string>(undefined)
|
|
55
|
-
const [newDatasetName, setNewDatasetName] = useState<string>(undefined)
|
|
62
|
+
const [newDatasetName, setNewDatasetName] = useState<string>(config.vegaType ? 'vega_data' : undefined)
|
|
63
|
+
const [pastedConfig, setPastedConfig] = useState<string>(undefined)
|
|
56
64
|
const setEditingDataset = (datasetKey: string) => {
|
|
57
65
|
_setEditingDataset(datasetKey)
|
|
58
66
|
setNewDatasetName(datasetKey)
|
|
@@ -69,7 +77,8 @@ const DataImport = () => {
|
|
|
69
77
|
})
|
|
70
78
|
|
|
71
79
|
// Is the X Axis still in the dataset?
|
|
72
|
-
|
|
80
|
+
const columns = newData.columns || Object.keys(newData[0])
|
|
81
|
+
if (columns.indexOf(oldAxisX) < 0) return false
|
|
73
82
|
|
|
74
83
|
return true
|
|
75
84
|
}
|
|
@@ -214,7 +223,10 @@ const DataImport = () => {
|
|
|
214
223
|
|
|
215
224
|
// Validate parsed data and set if no issues.
|
|
216
225
|
try {
|
|
217
|
-
|
|
226
|
+
let result = parseTextByMimeType(this.result.toString(), mimeType, externalURL, setErrors)
|
|
227
|
+
if (config.vegaConfig) {
|
|
228
|
+
return updateDataFromVegaData(result, fileSource, fileSourceType)
|
|
229
|
+
}
|
|
218
230
|
const text = transform.autoStandardize(result)
|
|
219
231
|
if (config.data && config.series) {
|
|
220
232
|
if (dataExists(text, config.series, config?.xAxis.dataKey)) {
|
|
@@ -320,7 +332,7 @@ const DataImport = () => {
|
|
|
320
332
|
/>
|
|
321
333
|
</label>
|
|
322
334
|
<label htmlFor='external-datas' className='col-12 mt-2'>
|
|
323
|
-
<span>URL</span>
|
|
335
|
+
<span>URL </span>
|
|
324
336
|
<textarea
|
|
325
337
|
id='external-datas'
|
|
326
338
|
className='form-control'
|
|
@@ -341,7 +353,7 @@ const DataImport = () => {
|
|
|
341
353
|
/>{' '}
|
|
342
354
|
Always load from URL (normally will only pull once)
|
|
343
355
|
</label>
|
|
344
|
-
<div className='d-flex justify-content-end mt-2'>
|
|
356
|
+
<div className='d-flex justify-content-end mt-2 mb-3'>
|
|
345
357
|
<button
|
|
346
358
|
className='btn btn-primary px-4'
|
|
347
359
|
type='submit'
|
|
@@ -418,6 +430,30 @@ const DataImport = () => {
|
|
|
418
430
|
dispatch({ type: 'DELETE_DASHBOARD_DATASET', payload: { datasetKey } })
|
|
419
431
|
}
|
|
420
432
|
|
|
433
|
+
const updateDataFromVegaConfig = pastedConfig => {
|
|
434
|
+
const vegaConfig = parseVegaConfig(JSON.parse(pastedConfig))
|
|
435
|
+
const newData = extractCoveData(vegaConfig)
|
|
436
|
+
let newConfig = {
|
|
437
|
+
...config,
|
|
438
|
+
data: newData
|
|
439
|
+
}
|
|
440
|
+
loadedVegaConfigData(newConfig)
|
|
441
|
+
setConfig(newConfig)
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const updateDataFromVegaData = (vegaData, fileSource, fileSourceType) => {
|
|
445
|
+
const newData = extractCoveData(updateVegaData(config.vegaConfig, vegaData))
|
|
446
|
+
let newConfig = {
|
|
447
|
+
...config,
|
|
448
|
+
dataFileName: fileSource,
|
|
449
|
+
dataFileSourceType: fileSourceType,
|
|
450
|
+
dataUrl: fileSourceType === 'url' ? fileSource : null,
|
|
451
|
+
data: newData
|
|
452
|
+
}
|
|
453
|
+
loadedVegaConfigData(newConfig)
|
|
454
|
+
setConfig(newConfig)
|
|
455
|
+
}
|
|
456
|
+
|
|
421
457
|
let configureData,
|
|
422
458
|
readyToConfigure: boolean | Object[] = false
|
|
423
459
|
|
|
@@ -654,7 +690,7 @@ const DataImport = () => {
|
|
|
654
690
|
|
|
655
691
|
{configureData?.data && (
|
|
656
692
|
<>
|
|
657
|
-
{config.type !== 'dashboard' && (
|
|
693
|
+
{config.type !== 'dashboard' && !config.vegaType && (
|
|
658
694
|
<>
|
|
659
695
|
<div className='heading-3'>Data Source</div>
|
|
660
696
|
<div className='file-loaded-area'>
|
|
@@ -694,6 +730,98 @@ const DataImport = () => {
|
|
|
694
730
|
</>
|
|
695
731
|
)}
|
|
696
732
|
|
|
733
|
+
{config.vegaType && (
|
|
734
|
+
<>
|
|
735
|
+
<div className='heading-3'>Update Dataset</div>
|
|
736
|
+
<Tabs startingTab={0}>
|
|
737
|
+
<TabPane title='Update with Vega config' icon={<FileUploadIcon className='inline-icon' />}>
|
|
738
|
+
<div>
|
|
739
|
+
<label htmlFor='uploadConfig'>Paste full Vega configuration JSON:</label>
|
|
740
|
+
|
|
741
|
+
<textarea
|
|
742
|
+
id='pasteConfig'
|
|
743
|
+
className='w-100 mb-2'
|
|
744
|
+
onChange={e => setPastedConfig(e.target.value)}
|
|
745
|
+
placeholder='{ }'
|
|
746
|
+
/>
|
|
747
|
+
<div className='mb-3 d-flex justify-content-end'>
|
|
748
|
+
<button
|
|
749
|
+
className='btn btn-primary px-4'
|
|
750
|
+
type='submit'
|
|
751
|
+
id='load-data'
|
|
752
|
+
disabled={!pastedConfig}
|
|
753
|
+
onClick={() => updateDataFromVegaConfig(pastedConfig)}
|
|
754
|
+
>
|
|
755
|
+
Save & Load
|
|
756
|
+
</button>
|
|
757
|
+
</div>
|
|
758
|
+
</div>
|
|
759
|
+
</TabPane>
|
|
760
|
+
<TabPane title='Update with JSON data' icon={<LinkIcon className='inline-icon' />}>
|
|
761
|
+
<div>
|
|
762
|
+
To change this chart to use a JSON file as its data source, the file should be formatted like this
|
|
763
|
+
sample:
|
|
764
|
+
<pre style={{ backgroundColor: '#f2f2f2', fontFamily: 'monospace' }}>
|
|
765
|
+
{getSampleVegaJson(config.vegaConfig)}
|
|
766
|
+
</pre>
|
|
767
|
+
</div>
|
|
768
|
+
<div> </div>
|
|
769
|
+
<div>Upload JSON file:</div>
|
|
770
|
+
<div className='data-source-options'>
|
|
771
|
+
<div
|
|
772
|
+
className={
|
|
773
|
+
isDragActive2
|
|
774
|
+
? 'drag-active cdcdataviz-file-selector loaded-file'
|
|
775
|
+
: 'cdcdataviz-file-selector loaded-file'
|
|
776
|
+
}
|
|
777
|
+
{...getRootProps2()}
|
|
778
|
+
>
|
|
779
|
+
<input {...getInputProps2()} />
|
|
780
|
+
{isDragActive2 ? (
|
|
781
|
+
<p>Drop file here</p>
|
|
782
|
+
) : (
|
|
783
|
+
<p>
|
|
784
|
+
<FileUploadIcon /> <span>{config.dataFileName ?? 'Replace data file'}</span>
|
|
785
|
+
</p>
|
|
786
|
+
)}
|
|
787
|
+
</div>
|
|
788
|
+
<div className='link link-replace' {...getRootProps2()}>
|
|
789
|
+
<input {...getInputProps2()} />
|
|
790
|
+
<p>
|
|
791
|
+
<span>Replace file</span>
|
|
792
|
+
</p>
|
|
793
|
+
</div>
|
|
794
|
+
</div>
|
|
795
|
+
|
|
796
|
+
<label htmlFor='external-datas' className='col-12 mt-2'>
|
|
797
|
+
<span>Or connect chart to URL:</span>
|
|
798
|
+
<textarea
|
|
799
|
+
id='external-datas'
|
|
800
|
+
className='form-control'
|
|
801
|
+
placeholder='e.g., https://data.cdc.gov/resources/file.json'
|
|
802
|
+
aria-label='Load data from external URL'
|
|
803
|
+
aria-describedby='load-data'
|
|
804
|
+
rows={2}
|
|
805
|
+
value={externalURL}
|
|
806
|
+
onChange={e => setExternalURL(e.target.value)}
|
|
807
|
+
/>
|
|
808
|
+
</label>
|
|
809
|
+
<div className='d-flex justify-content-end mt-2 mb-3'>
|
|
810
|
+
<button
|
|
811
|
+
className='btn btn-primary px-4'
|
|
812
|
+
type='submit'
|
|
813
|
+
id='load-data'
|
|
814
|
+
disabled={!newDatasetName || !externalURL}
|
|
815
|
+
onClick={() => loadData(null, externalURL, editingDataset)}
|
|
816
|
+
>
|
|
817
|
+
Save & Load
|
|
818
|
+
</button>
|
|
819
|
+
</div>
|
|
820
|
+
</TabPane>
|
|
821
|
+
</Tabs>
|
|
822
|
+
</>
|
|
823
|
+
)}
|
|
824
|
+
|
|
697
825
|
{showDataDesigner && (
|
|
698
826
|
<DataDesigner
|
|
699
827
|
visualizationKey={null}
|