@cdc/editor 4.25.6 → 4.25.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cdc/editor",
3
- "version": "4.25.6",
3
+ "version": "4.25.7",
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.6",
29
- "@cdc/core": "^4.25.6",
30
- "@cdc/dashboard": "^4.25.6",
31
- "@cdc/data-bite": "^4.25.6",
32
- "@cdc/map": "^4.25.6",
33
- "@cdc/markup-include": "^4.25.6",
34
- "@cdc/waffle-chart": "^4.25.6",
28
+ "@cdc/chart": "^4.25.7",
29
+ "@cdc/core": "^4.25.7",
30
+ "@cdc/dashboard": "^4.25.7",
31
+ "@cdc/data-bite": "^4.25.7",
32
+ "@cdc/map": "^4.25.7",
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": "6097de1ff814001880d9ac64bd66becdc092d63c"
44
+ "gitHead": "9062881d50c824ee6cdd71868bafd016a5e5694d"
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
- try {
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
- Upload Custom Configuration{' '}
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
- if (newData.columns.indexOf(oldAxisX) < 0) return false
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
- const result = parseTextByMimeType(this.result.toString(), mimeType, externalURL, setErrors)
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>&nbsp;</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}