@cdc/editor 4.23.4 → 4.23.5
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 +171957 -176322
- package/package.json +9 -9
- package/src/components/ChooseTab.jsx +2 -2
- package/src/components/DataImport.jsx +202 -39
- package/src/components/SampleData.jsx +5 -5
- package/src/samples/valid-forecast-data.csv +366 -0
- package/src/scss/data-import.scss +55 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cdc/editor",
|
|
3
|
-
"version": "4.23.
|
|
3
|
+
"version": "4.23.5",
|
|
4
4
|
"description": "React component for generating a new component entry",
|
|
5
5
|
"moduleName": "CdcEditor",
|
|
6
6
|
"main": "dist/cdceditor",
|
|
@@ -24,13 +24,13 @@
|
|
|
24
24
|
},
|
|
25
25
|
"license": "Apache-2.0",
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@cdc/chart": "^4.23.
|
|
28
|
-
"@cdc/core": "^4.23.
|
|
29
|
-
"@cdc/dashboard": "^4.23.
|
|
30
|
-
"@cdc/data-bite": "^4.23.
|
|
31
|
-
"@cdc/map": "^4.23.
|
|
32
|
-
"@cdc/markup-include": "^4.23.
|
|
33
|
-
"@cdc/waffle-chart": "^4.23.
|
|
27
|
+
"@cdc/chart": "^4.23.5",
|
|
28
|
+
"@cdc/core": "^4.23.5",
|
|
29
|
+
"@cdc/dashboard": "^4.23.5",
|
|
30
|
+
"@cdc/data-bite": "^4.23.5",
|
|
31
|
+
"@cdc/map": "^4.23.5",
|
|
32
|
+
"@cdc/markup-include": "^4.23.5",
|
|
33
|
+
"@cdc/waffle-chart": "^4.23.5",
|
|
34
34
|
"axios": "^0.21.1",
|
|
35
35
|
"d3": "^7.0.0",
|
|
36
36
|
"html-react-parser": "^3.0.8",
|
|
@@ -43,5 +43,5 @@
|
|
|
43
43
|
"react": "^18.2.0",
|
|
44
44
|
"react-dom": "^18.2.0"
|
|
45
45
|
},
|
|
46
|
-
"gitHead": "
|
|
46
|
+
"gitHead": "34add3436994ca3cf13e51f313add4d70377f53e"
|
|
47
47
|
}
|
|
@@ -206,14 +206,14 @@ export default function ChooseTab() {
|
|
|
206
206
|
<Tooltip.Content>Display a scatter plot</Tooltip.Content>
|
|
207
207
|
</Tooltip>
|
|
208
208
|
</li>
|
|
209
|
-
|
|
209
|
+
<li>
|
|
210
210
|
<Tooltip>
|
|
211
211
|
<Tooltip.Target>
|
|
212
212
|
<IconButton label='Area Chart' type='chart' subType='Area Chart' orientation='vertical' icon={<AreaChartIcon />} />
|
|
213
213
|
</Tooltip.Target>
|
|
214
214
|
<Tooltip.Content>Display an area chart</Tooltip.Content>
|
|
215
215
|
</Tooltip>
|
|
216
|
-
</li>
|
|
216
|
+
</li>
|
|
217
217
|
</ul>
|
|
218
218
|
|
|
219
219
|
<div className='heading-2'>Maps</div>
|
|
@@ -17,7 +17,10 @@ import SampleData from './SampleData'
|
|
|
17
17
|
import FileUploadIcon from '../assets/icons/file-upload-solid.svg'
|
|
18
18
|
import CloseIcon from '@cdc/core/assets/icon-close.svg'
|
|
19
19
|
|
|
20
|
+
import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
|
|
20
21
|
import DataDesigner from '@cdc/core/components/managers/DataDesigner'
|
|
22
|
+
import Tooltip from '@cdc/core/components/ui/Tooltip'
|
|
23
|
+
import Icon from '@cdc/core/components/ui/Icon'
|
|
21
24
|
|
|
22
25
|
import '../scss/data-import.scss'
|
|
23
26
|
|
|
@@ -36,6 +39,8 @@ export default function DataImport() {
|
|
|
36
39
|
|
|
37
40
|
const [editingDataset, setEditingDataset] = useState()
|
|
38
41
|
|
|
42
|
+
const [asyncPreviewData, setAsyncPreviewData] = useState()
|
|
43
|
+
|
|
39
44
|
const supportedDataTypes = {
|
|
40
45
|
'.csv': 'text/csv',
|
|
41
46
|
'.json': 'application/json'
|
|
@@ -294,8 +299,6 @@ export default function DataImport() {
|
|
|
294
299
|
setEditingDataset(undefined)
|
|
295
300
|
}
|
|
296
301
|
setAddingDataset(false)
|
|
297
|
-
setExternalURL('')
|
|
298
|
-
setKeepURL(false)
|
|
299
302
|
} catch (err) {
|
|
300
303
|
setErrors(err)
|
|
301
304
|
}
|
|
@@ -320,6 +323,34 @@ export default function DataImport() {
|
|
|
320
323
|
setConfig(newConfig)
|
|
321
324
|
}, []) // eslint-disable-line
|
|
322
325
|
|
|
326
|
+
useEffect(() => {
|
|
327
|
+
const asyncWrapper = async () => {
|
|
328
|
+
if (config.type === 'dashboard') {
|
|
329
|
+
Object.keys(config.datasets).forEach(async datasetKey => {
|
|
330
|
+
if (config.datasets[datasetKey].preview) {
|
|
331
|
+
if (config.datasets[datasetKey].dataUrl) {
|
|
332
|
+
const remoteData = await fetchRemoteData(config.datasets[datasetKey].dataUrl)
|
|
333
|
+
if (Array.isArray(remoteData)) {
|
|
334
|
+
setAsyncPreviewData(remoteData)
|
|
335
|
+
}
|
|
336
|
+
} else if (Array.isArray(config.datasets[datasetKey].data)) {
|
|
337
|
+
setAsyncPreviewData(config.datasets[datasetKey].data)
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
})
|
|
341
|
+
} else {
|
|
342
|
+
if (config.dataUrl) {
|
|
343
|
+
const remoteData = await fetchRemoteData(config.dataUrl)
|
|
344
|
+
if (Array.isArray(remoteData)) {
|
|
345
|
+
setAsyncPreviewData(remoteData)
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
asyncWrapper()
|
|
352
|
+
}, [config.datasets]) // eslint-disable-line
|
|
353
|
+
|
|
323
354
|
const updateDescriptionProp = (visualizationKey, datasetKey, key, value) => {
|
|
324
355
|
if (config.type === 'dashboard') {
|
|
325
356
|
let dataDescription = { ...config.datasets[datasetKey].dataDescription, [key]: value }
|
|
@@ -395,9 +426,9 @@ export default function DataImport() {
|
|
|
395
426
|
return (
|
|
396
427
|
//todo convert to modal
|
|
397
428
|
<>
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
429
|
+
<button className='btn danger' onClick={() => resetEditor({ type: config.type, visualizationType: config.visualizationType }, 'Resetting will remove your data and settings. Do you want to continue?')}>
|
|
430
|
+
Clear
|
|
431
|
+
<CloseIcon />
|
|
401
432
|
</button>
|
|
402
433
|
{/* DEV-851 link to replace file should pop file dialog */}
|
|
403
434
|
{config.dataFileSourceType === 'file' && (
|
|
@@ -406,7 +437,8 @@ export default function DataImport() {
|
|
|
406
437
|
<p>
|
|
407
438
|
<span>or replace file</span>
|
|
408
439
|
</p>
|
|
409
|
-
</div>
|
|
440
|
+
</div>
|
|
441
|
+
)}
|
|
410
442
|
</>
|
|
411
443
|
)
|
|
412
444
|
}
|
|
@@ -475,7 +507,7 @@ export default function DataImport() {
|
|
|
475
507
|
if (config.type === 'dashboard') {
|
|
476
508
|
readyToConfigure = Object.keys(config.datasets).length > 0
|
|
477
509
|
Object.keys(config.datasets).forEach(datasetKey => {
|
|
478
|
-
if (config.datasets[datasetKey].preview) {
|
|
510
|
+
if (config.datasets[datasetKey].preview && Array.isArray(config.datasets[datasetKey].data)) {
|
|
479
511
|
previewData = config.datasets[datasetKey].data
|
|
480
512
|
}
|
|
481
513
|
})
|
|
@@ -491,6 +523,139 @@ export default function DataImport() {
|
|
|
491
523
|
readyToConfigure = true
|
|
492
524
|
}
|
|
493
525
|
|
|
526
|
+
const urlFilters = (
|
|
527
|
+
<>
|
|
528
|
+
{config.filters &&
|
|
529
|
+
config.filters.map((filter, i) =>
|
|
530
|
+
filter.type !== 'url' ? (
|
|
531
|
+
<></>
|
|
532
|
+
) : (
|
|
533
|
+
<fieldset key={filter.key} className='edit-block url-filters-block'>
|
|
534
|
+
<button
|
|
535
|
+
onClick={e => {
|
|
536
|
+
let newFilters = [...config.filters]
|
|
537
|
+
newFilters.splice(i, 1)
|
|
538
|
+
setConfig({ ...config, filters: newFilters, runtimeDataUrl: undefined })
|
|
539
|
+
}}
|
|
540
|
+
>
|
|
541
|
+
Remove
|
|
542
|
+
</button>
|
|
543
|
+
<label>
|
|
544
|
+
<span class='edit-label column-heading'>
|
|
545
|
+
Label
|
|
546
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
547
|
+
<Tooltip.Target>
|
|
548
|
+
<Icon display='question' />
|
|
549
|
+
</Tooltip.Target>
|
|
550
|
+
<Tooltip.Content>
|
|
551
|
+
<p style={{ padding: '0.5rem' }}>The label that will appear above the dropdown filter.</p>
|
|
552
|
+
</Tooltip.Content>
|
|
553
|
+
</Tooltip>
|
|
554
|
+
</span>{' '}
|
|
555
|
+
<input
|
|
556
|
+
type='text'
|
|
557
|
+
defaultValue={filter.label}
|
|
558
|
+
onChange={e => {
|
|
559
|
+
let newFilters = [...config.filters]
|
|
560
|
+
newFilters[i].label = e.target.value
|
|
561
|
+
setConfig({ ...config, filters: newFilters })
|
|
562
|
+
}}
|
|
563
|
+
/>
|
|
564
|
+
</label>
|
|
565
|
+
<label>
|
|
566
|
+
<span class='edit-label column-heading'>
|
|
567
|
+
Query string parameter
|
|
568
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
569
|
+
<Tooltip.Target>
|
|
570
|
+
<Icon display='question' />
|
|
571
|
+
</Tooltip.Target>
|
|
572
|
+
<Tooltip.Content>
|
|
573
|
+
<p style={{ padding: '0.5rem' }}>Name of the query string parameter that will be appended to the URL above with the values provided below.</p>
|
|
574
|
+
</Tooltip.Content>
|
|
575
|
+
</Tooltip>
|
|
576
|
+
</span>{' '}
|
|
577
|
+
<input
|
|
578
|
+
type='text'
|
|
579
|
+
defaultValue={filter.queryParameter}
|
|
580
|
+
onChange={e => {
|
|
581
|
+
let newFilters = [...config.filters]
|
|
582
|
+
newFilters[i].queryParameter = e.target.value
|
|
583
|
+
setConfig({ ...config, filters: newFilters })
|
|
584
|
+
}}
|
|
585
|
+
/>
|
|
586
|
+
</label>
|
|
587
|
+
<label>
|
|
588
|
+
<span class='edit-label column-heading'>Values</span>{' '}
|
|
589
|
+
</label>
|
|
590
|
+
<ul className='value-list'>
|
|
591
|
+
{filter.orderedValues &&
|
|
592
|
+
filter.orderedValues.map((value, valueIndex) => (
|
|
593
|
+
<li>
|
|
594
|
+
{value}
|
|
595
|
+
<input
|
|
596
|
+
type='text'
|
|
597
|
+
placeholder='Enter value display name here'
|
|
598
|
+
value={filter.labels ? filter.labels[value] : undefined}
|
|
599
|
+
className='url-value-label'
|
|
600
|
+
onChange={e => {
|
|
601
|
+
let newFilters = [...config.filters]
|
|
602
|
+
|
|
603
|
+
newFilters[i].labels = newFilters[i].labels || {}
|
|
604
|
+
newFilters[i].labels[value] = e.target.value
|
|
605
|
+
|
|
606
|
+
setConfig({ ...config, filters: newFilters })
|
|
607
|
+
}}
|
|
608
|
+
/>
|
|
609
|
+
<button
|
|
610
|
+
onClick={() => {
|
|
611
|
+
let newFilters = [...config.filters]
|
|
612
|
+
|
|
613
|
+
if (newFilters[i].labels) {
|
|
614
|
+
delete newFilters[i].labels[newFilters[i].orderedValues[valueIndex]]
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
newFilters[i].orderedValues.splice(valueIndex, 1)
|
|
618
|
+
setConfig({ ...config, filters: newFilters })
|
|
619
|
+
}}
|
|
620
|
+
>
|
|
621
|
+
X
|
|
622
|
+
</button>
|
|
623
|
+
</li>
|
|
624
|
+
))}
|
|
625
|
+
</ul>
|
|
626
|
+
<form
|
|
627
|
+
onSubmit={e => {
|
|
628
|
+
e.preventDefault()
|
|
629
|
+
if (!config.filters[i].orderedValues || config.filters[i].orderedValues.indexOf(e.target[0].value) === -1) {
|
|
630
|
+
let newFilters = [...config.filters]
|
|
631
|
+
newFilters[i].orderedValues = newFilters[i].orderedValues || []
|
|
632
|
+
newFilters[i].orderedValues.push(e.target[0].value)
|
|
633
|
+
newFilters[i].values = newFilters[i].orderedValues
|
|
634
|
+
if (!newFilters[i].active) newFilters[i].active = e.target[0].value
|
|
635
|
+
e.target[0].value = ''
|
|
636
|
+
setConfig({ ...config, filters: newFilters })
|
|
637
|
+
}
|
|
638
|
+
}}
|
|
639
|
+
>
|
|
640
|
+
<input type='text' placeholder='Enter new value name here' />{' '}
|
|
641
|
+
<button type='submit' style={{ marginTop: '1em' }}>
|
|
642
|
+
Add New Value
|
|
643
|
+
</button>
|
|
644
|
+
</form>
|
|
645
|
+
</fieldset>
|
|
646
|
+
)
|
|
647
|
+
)}
|
|
648
|
+
<button
|
|
649
|
+
className='btn full-width'
|
|
650
|
+
onClick={() => {
|
|
651
|
+
setConfig({ ...config, filters: config.filters ? [...config.filters, { type: 'url', key: Date.now() }] : [{ type: 'url', key: Date.now() }] })
|
|
652
|
+
}}
|
|
653
|
+
>
|
|
654
|
+
Add New URL Filter
|
|
655
|
+
</button>
|
|
656
|
+
</>
|
|
657
|
+
)
|
|
658
|
+
|
|
494
659
|
const showDataDesigner = config.visualizationType !== 'Box Plot' && config.visualizationType !== 'Scatter Plot'
|
|
495
660
|
|
|
496
661
|
return (
|
|
@@ -560,7 +725,7 @@ export default function DataImport() {
|
|
|
560
725
|
<>
|
|
561
726
|
<div className='heading-3'>Data Source</div>
|
|
562
727
|
<div className='file-loaded-area'>
|
|
563
|
-
{config.dataFileSourceType === 'file' && (
|
|
728
|
+
{(config.dataFileSourceType === 'file' || !config.dataFileSourceType) && (
|
|
564
729
|
<div className='data-source-options'>
|
|
565
730
|
<div className={isDragActive2 ? 'drag-active cdcdataviz-file-selector loaded-file' : 'cdcdataviz-file-selector loaded-file'} {...getRootProps2()}>
|
|
566
731
|
<input {...getInputProps2()} />
|
|
@@ -577,10 +742,13 @@ export default function DataImport() {
|
|
|
577
742
|
)}
|
|
578
743
|
|
|
579
744
|
{config.dataFileSourceType === 'url' && (
|
|
580
|
-
|
|
581
|
-
<div>
|
|
582
|
-
|
|
583
|
-
|
|
745
|
+
<>
|
|
746
|
+
<div className='url-source-options'>
|
|
747
|
+
<div>{loadFileFromUrl(externalURL)}</div>
|
|
748
|
+
<div>{resetButton()}</div>
|
|
749
|
+
</div>
|
|
750
|
+
{config.dataUrl && (config.type === 'chart' || config.type === 'map') && urlFilters}
|
|
751
|
+
</>
|
|
584
752
|
)}
|
|
585
753
|
</div>
|
|
586
754
|
</>
|
|
@@ -614,10 +782,10 @@ export default function DataImport() {
|
|
|
614
782
|
{errors &&
|
|
615
783
|
(errors.map
|
|
616
784
|
? errors.map((message, index) => (
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
785
|
+
<div className='error-box slim mt-2' key={`error-${message}`}>
|
|
786
|
+
<span>{message}</span> <CloseIcon className='inline-icon dismiss-error' onClick={() => setErrors(errors.filter((val, i) => i !== index))} />
|
|
787
|
+
</div>
|
|
788
|
+
))
|
|
621
789
|
: errors.message)}
|
|
622
790
|
<p className='footnote'>
|
|
623
791
|
Supported file types: {Object.keys(supportedDataTypes).join(', ')}. Maximum file size {maxFileSize}MB.
|
|
@@ -627,29 +795,24 @@ export default function DataImport() {
|
|
|
627
795
|
<SampleDataContext.Provider value={{ loadData, editingDataset, config }}>
|
|
628
796
|
<SampleData.Buttons />
|
|
629
797
|
</SampleDataContext.Provider>
|
|
630
|
-
</div
|
|
631
|
-
)
|
|
632
|
-
}
|
|
798
|
+
</div>
|
|
799
|
+
)}
|
|
633
800
|
|
|
634
|
-
{
|
|
635
|
-
|
|
636
|
-
<
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
)
|
|
642
|
-
}
|
|
801
|
+
{config.type === 'dashboard' && !addingDataset && (
|
|
802
|
+
<p>
|
|
803
|
+
<button className='btn btn-primary' onClick={() => setAddingDataset(true)}>
|
|
804
|
+
+ Add More Files
|
|
805
|
+
</button>
|
|
806
|
+
</p>
|
|
807
|
+
)}
|
|
643
808
|
|
|
644
|
-
{
|
|
645
|
-
|
|
646
|
-
<
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
)
|
|
652
|
-
}
|
|
809
|
+
{readyToConfigure && (
|
|
810
|
+
<p>
|
|
811
|
+
<button className='btn btn-primary' onClick={() => setGlobalActive(2)}>
|
|
812
|
+
Configure your visualization
|
|
813
|
+
</button>
|
|
814
|
+
</p>
|
|
815
|
+
)}
|
|
653
816
|
|
|
654
817
|
<a href='https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/data-map.html' target='_blank' rel='noopener noreferrer' className='guidance-link'>
|
|
655
818
|
<div>
|
|
@@ -657,9 +820,9 @@ export default function DataImport() {
|
|
|
657
820
|
<p>Documentation and examples on formatting data and configuring visualizations.</p>
|
|
658
821
|
</div>
|
|
659
822
|
</a>
|
|
660
|
-
</div
|
|
823
|
+
</div>
|
|
661
824
|
<div className='right-col'>
|
|
662
|
-
<PreviewDataTable data={previewData} />
|
|
825
|
+
<PreviewDataTable data={asyncPreviewData || previewData} />
|
|
663
826
|
</div>
|
|
664
827
|
</>
|
|
665
828
|
)
|
|
@@ -28,12 +28,12 @@ const sampleData = {
|
|
|
28
28
|
text: 'Scatter Plot Sample Data',
|
|
29
29
|
fileName: 'valid-scatterplot.csv',
|
|
30
30
|
data: validScatterPlot
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
text: 'Area Chart Sample Data',
|
|
34
|
+
fileName: 'valid-area-chart.json',
|
|
35
|
+
data: validAreaChart
|
|
31
36
|
}
|
|
32
|
-
// {
|
|
33
|
-
// text: 'Area Chart Sample Data',
|
|
34
|
-
// fileName: 'valid-area-chart.json',
|
|
35
|
-
// data: validAreaChart
|
|
36
|
-
// }
|
|
37
37
|
],
|
|
38
38
|
maps: [
|
|
39
39
|
{
|