@cdc/editor 4.26.2 → 4.26.4
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-CY9IcPSi.es.js +6 -0
- package/dist/cdceditor-DlpiY3fQ.es.js +4 -0
- package/dist/cdceditor.js +84436 -77912
- package/package.json +9 -9
- package/src/CdcEditor.tsx +2 -3
- package/src/_stories/Editor.stories.tsx +173 -6
- package/src/components/ChooseTab.test.tsx +36 -0
- package/src/components/ChooseTab.tsx +39 -26
- package/src/components/DataImport/components/DataImport.tsx +124 -35
- package/src/components/DataImport/helpers/applyAutoDetectedDateParseFormat.ts +35 -0
- package/src/components/DataImport/tests/applyAutoDetectedDateParseFormat.test.ts +128 -0
- package/src/components/PreviewDataTable.test.tsx +184 -0
- package/src/components/PreviewDataTable.tsx +55 -49
- package/src/components/modal/Confirmation.jsx +5 -4
- package/src/scss/main.scss +15 -2
- package/dist/cdceditor-Cf9_fbQf.es.js +0 -6
- package/example/data-horizontal-filters.json +0 -8
- package/example/data-horizontal-multiseries-filters.json +0 -18
- package/example/data-horizontal-multiseries.json +0 -6
- package/example/data-horizontal.json +0 -4
- package/example/data-vertical-filters.json +0 -10
- package/example/data-vertical-multiseries-filters.json +0 -18
- package/example/data-vertical-multiseries-multirow-filters.json +0 -50
- package/example/data-vertical-multiseries-multirow.json +0 -14
- package/example/data-vertical-multiseries.json +0 -6
- package/example/data-vertical.json +0 -6
- package/example/region-map.json +0 -33
- package/example/test.json +0 -110280
- package/example/valid-county-data.json +0 -3049
- package/example/valid-scatterplot.csv +0 -17
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cdc/editor",
|
|
3
|
-
"version": "4.26.
|
|
3
|
+
"version": "4.26.4",
|
|
4
4
|
"description": "React component for generating a new component entry",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"bugs": "https://github.com/CDCgov/cdc-open-viz/issues",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@cdc/chart": "^4.26.
|
|
9
|
-
"@cdc/core": "^4.26.
|
|
10
|
-
"@cdc/dashboard": "^4.26.
|
|
11
|
-
"@cdc/data-bite": "^4.26.
|
|
12
|
-
"@cdc/map": "^4.26.
|
|
13
|
-
"@cdc/markup-include": "^4.26.
|
|
14
|
-
"@cdc/waffle-chart": "^4.26.
|
|
8
|
+
"@cdc/chart": "^4.26.4",
|
|
9
|
+
"@cdc/core": "^4.26.4",
|
|
10
|
+
"@cdc/dashboard": "^4.26.4",
|
|
11
|
+
"@cdc/data-bite": "^4.26.4",
|
|
12
|
+
"@cdc/map": "^4.26.4",
|
|
13
|
+
"@cdc/markup-include": "^4.26.4",
|
|
14
|
+
"@cdc/waffle-chart": "^4.26.4",
|
|
15
15
|
"axios": "^1.13.2",
|
|
16
16
|
"d3": "^7.9.0",
|
|
17
17
|
"react-dropzone": "^14.3.8",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"vite-plugin-css-injected-by-js": "^2.4.0",
|
|
24
24
|
"vite-plugin-svgr": "^4.2.0"
|
|
25
25
|
},
|
|
26
|
-
"gitHead": "
|
|
26
|
+
"gitHead": "6097de1ff814001880d9ac64bd66becdc092d63c",
|
|
27
27
|
"main": "dist/cdceditor",
|
|
28
28
|
"moduleName": "CdcEditor",
|
|
29
29
|
"peerDependencies": {
|
package/src/CdcEditor.tsx
CHANGED
|
@@ -19,7 +19,6 @@ import { legacyConfigSupport } from './helpers/legacyConfigSupport'
|
|
|
19
19
|
|
|
20
20
|
import './scss/main.scss'
|
|
21
21
|
import editorReducer, { EditorState } from '@cdc/core/contexts/editor.reducer'
|
|
22
|
-
import _ from 'lodash'
|
|
23
22
|
import { cloneConfig } from '@cdc/core/helpers/cloneConfig'
|
|
24
23
|
import { WCMSProps } from '@cdc/core/types/WCMSProps'
|
|
25
24
|
import { devToolsStore } from '@cdc/core/helpers/withDevTools'
|
|
@@ -73,7 +72,7 @@ const CdcEditor: React.FC<WCMSProps> = ({ config: configObj, hostname, container
|
|
|
73
72
|
}, [])
|
|
74
73
|
|
|
75
74
|
useEffect(() => {
|
|
76
|
-
let strippedConfig = stripConfig(state.config)
|
|
75
|
+
let strippedConfig = stripConfig(state.config, true)
|
|
77
76
|
|
|
78
77
|
const parsedData = JSON.stringify(strippedConfig)
|
|
79
78
|
// Emit the data in a regular JS event so it can be consumed by anything.
|
|
@@ -99,7 +98,7 @@ const CdcEditor: React.FC<WCMSProps> = ({ config: configObj, hostname, container
|
|
|
99
98
|
<GlobalContextProvider>
|
|
100
99
|
<ConfigContext.Provider value={{ ...state, setTempConfig: setTempConfigAndUpdate }}>
|
|
101
100
|
<EditorDispatchContext.Provider value={dispatch}>
|
|
102
|
-
<div className={`
|
|
101
|
+
<div className={`cove-visualization cdc-editor ${state.currentViewport}`} ref={outerContainerRef}>
|
|
103
102
|
<Tabs className='top-level'>
|
|
104
103
|
<TabPane title='1. Choose Visualization Type' className='choose-type'>
|
|
105
104
|
<ChooseTab />
|
|
@@ -1,9 +1,29 @@
|
|
|
1
1
|
import { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
2
|
import CdcEditor from '../CdcEditor'
|
|
3
|
-
import { within, userEvent } from 'storybook/test'
|
|
3
|
+
import { within, userEvent, expect } from 'storybook/test'
|
|
4
|
+
import ChartEditorConfig from '../../../chart/src/_stories/_mock/editor-tests/bar-chart-editor-test.json'
|
|
5
|
+
import MapConfig from '../../../map/src/_stories/_mock/default-patterns.json'
|
|
6
|
+
import DashboardConfig from '../../../dashboard/src/_stories/_mock/dashboard_no_filter.json'
|
|
7
|
+
import DataTableConfig from '../../../data-table/examples/data-table-example.json'
|
|
4
8
|
|
|
5
|
-
const
|
|
6
|
-
|
|
9
|
+
const loadConfigFromTextArea = async (canvasElement, config) => {
|
|
10
|
+
const user = userEvent.setup()
|
|
11
|
+
const textArea = canvasElement.querySelector('#pasteConfig') as HTMLTextAreaElement
|
|
12
|
+
const loadButton = canvasElement.querySelector('#load-data') as HTMLButtonElement
|
|
13
|
+
|
|
14
|
+
expect(textArea).toBeTruthy()
|
|
15
|
+
expect(loadButton).toBeTruthy()
|
|
16
|
+
|
|
17
|
+
await user.click(textArea)
|
|
18
|
+
await user.clear(textArea)
|
|
19
|
+
await user.paste(JSON.stringify(config))
|
|
20
|
+
await user.click(loadButton)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const assertImportDataTabAccessible = async canvas => {
|
|
24
|
+
const user = userEvent.setup()
|
|
25
|
+
await user.click(canvas.getByText('2. Import Data'))
|
|
26
|
+
await expect(canvas.findByText('Data Preview')).resolves.toBeTruthy()
|
|
7
27
|
}
|
|
8
28
|
|
|
9
29
|
const meta: Meta<typeof CdcEditor> = {
|
|
@@ -26,10 +46,157 @@ export const PreviewTableTests: Story = {
|
|
|
26
46
|
play: async ({ canvasElement }) => {
|
|
27
47
|
const canvas = within(canvasElement)
|
|
28
48
|
const user = userEvent.setup()
|
|
29
|
-
|
|
30
|
-
const mapButton = canvas.
|
|
49
|
+
|
|
50
|
+
const mapButton = await canvas.findByRole('button', { name: 'United States (State- or County-Level)' })
|
|
31
51
|
await user.click(mapButton)
|
|
32
|
-
|
|
52
|
+
|
|
53
|
+
await user.click(canvas.getByText('2. Import Data'))
|
|
54
|
+
|
|
55
|
+
const sampleData = await canvas.findByText('United States: County Sample Data')
|
|
33
56
|
await user.click(sampleData)
|
|
57
|
+
|
|
58
|
+
await expect(canvas.findByText('Data Preview')).resolves.toBeTruthy()
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const LoadChartJsonConfig: Story = {
|
|
63
|
+
args: {
|
|
64
|
+
config: {}
|
|
65
|
+
},
|
|
66
|
+
play: async ({ canvasElement }) => {
|
|
67
|
+
const canvas = within(canvasElement)
|
|
68
|
+
await loadConfigFromTextArea(canvasElement, ChartEditorConfig)
|
|
69
|
+
await assertImportDataTabAccessible(canvas)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const LoadMapJsonConfig: Story = {
|
|
74
|
+
args: {
|
|
75
|
+
config: {}
|
|
76
|
+
},
|
|
77
|
+
play: async ({ canvasElement }) => {
|
|
78
|
+
const canvas = within(canvasElement)
|
|
79
|
+
await loadConfigFromTextArea(canvasElement, MapConfig)
|
|
80
|
+
await assertImportDataTabAccessible(canvas)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const LoadDashboardJsonConfig: Story = {
|
|
85
|
+
args: {
|
|
86
|
+
config: {}
|
|
87
|
+
},
|
|
88
|
+
play: async ({ canvasElement }) => {
|
|
89
|
+
const canvas = within(canvasElement)
|
|
90
|
+
await loadConfigFromTextArea(canvasElement, DashboardConfig)
|
|
91
|
+
await assertImportDataTabAccessible(canvas)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const LoadDataTableJsonConfig: Story = {
|
|
96
|
+
args: {
|
|
97
|
+
config: {}
|
|
98
|
+
},
|
|
99
|
+
play: async ({ canvasElement }) => {
|
|
100
|
+
const canvas = within(canvasElement)
|
|
101
|
+
await loadConfigFromTextArea(canvasElement, DataTableConfig)
|
|
102
|
+
await assertImportDataTabAccessible(canvas)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const DownloadDashboardDatasetCSV: Story = {
|
|
107
|
+
args: { config: {} },
|
|
108
|
+
play: async ({ canvasElement }) => {
|
|
109
|
+
const canvas = within(canvasElement)
|
|
110
|
+
const user = userEvent.setup()
|
|
111
|
+
|
|
112
|
+
await loadConfigFromTextArea(canvasElement, DashboardConfig)
|
|
113
|
+
await user.click(canvas.getByText('2. Import Data'))
|
|
114
|
+
await expect(canvas.findByText('Data Sources')).resolves.toBeTruthy()
|
|
115
|
+
|
|
116
|
+
const originalCreateObjectURL = URL.createObjectURL
|
|
117
|
+
let capturedBlob: Blob | null = null
|
|
118
|
+
URL.createObjectURL = (blob: Blob) => {
|
|
119
|
+
capturedBlob = blob
|
|
120
|
+
return 'blob:test-mock'
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const downloadBtn = await canvas.findByRole('button', { name: 'Download' })
|
|
125
|
+
await user.click(downloadBtn)
|
|
126
|
+
|
|
127
|
+
expect(capturedBlob).toBeTruthy()
|
|
128
|
+
const text = await capturedBlob!.text()
|
|
129
|
+
expect(text).toContain('Location')
|
|
130
|
+
expect(text).toContain('Rate')
|
|
131
|
+
} finally {
|
|
132
|
+
URL.createObjectURL = originalCreateObjectURL
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export const DownloadSingleVizCSV: Story = {
|
|
138
|
+
args: { config: {} },
|
|
139
|
+
play: async ({ canvasElement }) => {
|
|
140
|
+
const canvas = within(canvasElement)
|
|
141
|
+
const user = userEvent.setup()
|
|
142
|
+
|
|
143
|
+
await loadConfigFromTextArea(canvasElement, ChartEditorConfig)
|
|
144
|
+
await user.click(canvas.getByText('2. Import Data'))
|
|
145
|
+
await expect(canvas.findByText('Data Preview')).resolves.toBeTruthy()
|
|
146
|
+
|
|
147
|
+
const originalCreateObjectURL = URL.createObjectURL
|
|
148
|
+
let capturedBlob: Blob | null = null
|
|
149
|
+
URL.createObjectURL = (blob: Blob) => {
|
|
150
|
+
capturedBlob = blob
|
|
151
|
+
return 'blob:test-mock'
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
const downloadBtn = await canvas.findByRole('button', { name: 'Download CSV' })
|
|
156
|
+
await user.click(downloadBtn)
|
|
157
|
+
|
|
158
|
+
expect(capturedBlob).toBeTruthy()
|
|
159
|
+
const text = await capturedBlob!.text()
|
|
160
|
+
expect(text).toContain('Year')
|
|
161
|
+
expect(text).toContain('Category')
|
|
162
|
+
} finally {
|
|
163
|
+
URL.createObjectURL = originalCreateObjectURL
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export const InvalidJsonShowsValidationAlert: Story = {
|
|
169
|
+
args: {
|
|
170
|
+
config: {}
|
|
171
|
+
},
|
|
172
|
+
play: async ({ canvasElement }) => {
|
|
173
|
+
const user = userEvent.setup()
|
|
174
|
+
const textArea = canvasElement.querySelector('#pasteConfig') as HTMLTextAreaElement
|
|
175
|
+
const loadButton = canvasElement.querySelector('#load-data') as HTMLButtonElement
|
|
176
|
+
|
|
177
|
+
expect(textArea).toBeTruthy()
|
|
178
|
+
expect(loadButton).toBeTruthy()
|
|
179
|
+
|
|
180
|
+
const originalAlert = window.alert
|
|
181
|
+
const originalOnError = window.onerror
|
|
182
|
+
let alertText = ''
|
|
183
|
+
|
|
184
|
+
window.alert = message => {
|
|
185
|
+
alertText = String(message)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
window.onerror = () => true
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
await user.click(textArea)
|
|
192
|
+
await user.clear(textArea)
|
|
193
|
+
await user.paste('{"broken": true, }')
|
|
194
|
+
await user.click(loadButton)
|
|
195
|
+
|
|
196
|
+
await expect(alertText).toBe('The JSON that was entered is invalid.')
|
|
197
|
+
} finally {
|
|
198
|
+
window.alert = originalAlert
|
|
199
|
+
window.onerror = originalOnError
|
|
200
|
+
}
|
|
34
201
|
}
|
|
35
202
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { render, waitFor } from '@testing-library/react'
|
|
3
|
+
|
|
4
|
+
import ConfigContext, { EditorDispatchContext } from '@cdc/core/contexts/EditorContext'
|
|
5
|
+
import ChooseTab from './ChooseTab'
|
|
6
|
+
|
|
7
|
+
describe('ChooseTab', () => {
|
|
8
|
+
it('dispatches EDITOR_SAVE once when tempConfig is present', async () => {
|
|
9
|
+
const dispatch = vi.fn()
|
|
10
|
+
const tempConfig = { type: 'chart', data: [{ x: 'A', y: 1 }] }
|
|
11
|
+
|
|
12
|
+
render(
|
|
13
|
+
<ConfigContext.Provider
|
|
14
|
+
value={
|
|
15
|
+
{
|
|
16
|
+
config: { type: 'chart' },
|
|
17
|
+
tempConfig,
|
|
18
|
+
errors: [],
|
|
19
|
+
currentViewport: 'lg',
|
|
20
|
+
globalActive: 0,
|
|
21
|
+
setTempConfig: vi.fn()
|
|
22
|
+
} as any
|
|
23
|
+
}
|
|
24
|
+
>
|
|
25
|
+
<EditorDispatchContext.Provider value={dispatch}>
|
|
26
|
+
<ChooseTab />
|
|
27
|
+
</EditorDispatchContext.Provider>
|
|
28
|
+
</ConfigContext.Provider>
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
await waitFor(() => {
|
|
32
|
+
expect(dispatch).toHaveBeenCalledTimes(1)
|
|
33
|
+
expect(dispatch).toHaveBeenCalledWith({ type: 'EDITOR_SAVE', payload: tempConfig })
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
})
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import React, { useContext, useState } from 'react'
|
|
1
|
+
import React, { useContext, useEffect, useState } from 'react'
|
|
2
2
|
import '../scss/choose-vis-tab.scss'
|
|
3
3
|
|
|
4
4
|
import ConfigContext, { EditorDispatchContext } from '@cdc/core/contexts/EditorContext'
|
|
5
5
|
import Tooltip from '@cdc/core/components/ui/Tooltip'
|
|
6
|
+
import Button from '@cdc/core/components/elements/Button'
|
|
6
7
|
|
|
7
8
|
import AlabamaGraphic from '@cdc/core/assets/icon-map-alabama.svg'
|
|
8
9
|
import AreaChartIcon from '@cdc/core/assets/icon-area-chart.svg'
|
|
@@ -51,6 +52,23 @@ interface ButtonProps {
|
|
|
51
52
|
orientation?: string | null
|
|
52
53
|
}
|
|
53
54
|
|
|
55
|
+
interface VizButtonProps extends ButtonProps {
|
|
56
|
+
activeVizButtonID?: number
|
|
57
|
+
onConfigure: (props: Record<string, unknown>) => void
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const VizButton: React.FC<VizButtonProps> = ({ activeVizButtonID, onConfigure, ...buttonProps }) => {
|
|
61
|
+
const { label, icon, id } = buttonProps
|
|
62
|
+
const isActive = id === activeVizButtonID || 0
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<button className={isActive ? 'active' : ''} onClick={() => onConfigure(buttonProps)} aria-label={label}>
|
|
66
|
+
{icon}
|
|
67
|
+
<span className='mt-1'>{label}</span>
|
|
68
|
+
</button>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
54
72
|
const ChooseTab: React.FC = (): JSX.Element => {
|
|
55
73
|
const { config, tempConfig } = useContext(ConfigContext)
|
|
56
74
|
|
|
@@ -59,6 +77,12 @@ const ChooseTab: React.FC = (): JSX.Element => {
|
|
|
59
77
|
const dispatch = useContext(EditorDispatchContext)
|
|
60
78
|
const rowLabels = ['General', , 'Charts', 'Maps']
|
|
61
79
|
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
if (tempConfig) {
|
|
82
|
+
dispatch({ type: 'EDITOR_SAVE', payload: tempConfig })
|
|
83
|
+
}
|
|
84
|
+
}, [dispatch, tempConfig])
|
|
85
|
+
|
|
62
86
|
const handleUpload = e => {
|
|
63
87
|
const file = e.target.files[0]
|
|
64
88
|
const reader = new FileReader()
|
|
@@ -75,12 +99,15 @@ const ChooseTab: React.FC = (): JSX.Element => {
|
|
|
75
99
|
newConfig = JSON.parse(text)
|
|
76
100
|
} catch (e) {
|
|
77
101
|
alert('The JSON that was entered is invalid.')
|
|
78
|
-
|
|
102
|
+
return
|
|
79
103
|
}
|
|
80
104
|
|
|
81
105
|
const isVega = isVegaConfig(newConfig)
|
|
82
106
|
if (isVega) {
|
|
83
107
|
newConfig = importVegaConfig(newConfig)
|
|
108
|
+
if (!newConfig) {
|
|
109
|
+
return
|
|
110
|
+
}
|
|
84
111
|
}
|
|
85
112
|
|
|
86
113
|
dispatch({ type: 'EDITOR_SET_CONFIG', payload: newConfig })
|
|
@@ -112,7 +139,7 @@ const ChooseTab: React.FC = (): JSX.Element => {
|
|
|
112
139
|
|
|
113
140
|
const errorText = vegaErrors.join('\n\n')
|
|
114
141
|
alert(errorText)
|
|
115
|
-
|
|
142
|
+
return null
|
|
116
143
|
}
|
|
117
144
|
|
|
118
145
|
const generateNewConfig = props => {
|
|
@@ -174,25 +201,6 @@ const ChooseTab: React.FC = (): JSX.Element => {
|
|
|
174
201
|
dispatch({ type: 'EDITOR_SET_GLOBALACTIVE', payload: 1 })
|
|
175
202
|
}
|
|
176
203
|
|
|
177
|
-
const VizButton: React.FC<ButtonProps> = props => {
|
|
178
|
-
const { label, icon, id } = props
|
|
179
|
-
const isActive = id === config?.activeVizButtonID || 0
|
|
180
|
-
const handleClick = () => {
|
|
181
|
-
configureTabs(props)
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (tempConfig) {
|
|
185
|
-
dispatch({ type: 'EDITOR_SAVE', payload: tempConfig })
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return (
|
|
189
|
-
<button className={isActive ? 'active' : ''} onClick={handleClick} aria-label={label}>
|
|
190
|
-
{icon}
|
|
191
|
-
<span className='mt-1'>{label}</span>
|
|
192
|
-
</button>
|
|
193
|
-
)
|
|
194
|
-
}
|
|
195
|
-
|
|
196
204
|
return (
|
|
197
205
|
<div className='choose-vis'>
|
|
198
206
|
<a
|
|
@@ -221,7 +229,11 @@ const ChooseTab: React.FC = (): JSX.Element => {
|
|
|
221
229
|
<li key={`${label}-button-${buttonIndex}`}>
|
|
222
230
|
<Tooltip position='right'>
|
|
223
231
|
<Tooltip.Target>
|
|
224
|
-
<VizButton
|
|
232
|
+
<VizButton
|
|
233
|
+
{...button}
|
|
234
|
+
activeVizButtonID={config?.activeVizButtonID}
|
|
235
|
+
onConfigure={configureTabs}
|
|
236
|
+
/>
|
|
225
237
|
</Tooltip.Target>
|
|
226
238
|
<Tooltip.Content>{button.content}</Tooltip.Content>
|
|
227
239
|
</Tooltip>
|
|
@@ -263,15 +275,16 @@ const ChooseTab: React.FC = (): JSX.Element => {
|
|
|
263
275
|
placeholder='{ }'
|
|
264
276
|
value={pastedConfig}
|
|
265
277
|
/>
|
|
266
|
-
<
|
|
267
|
-
|
|
278
|
+
<Button
|
|
279
|
+
variant='primary'
|
|
280
|
+
className='px-4 ms-2'
|
|
268
281
|
type='submit'
|
|
269
282
|
id='load-data'
|
|
270
283
|
disabled={!pastedConfig}
|
|
271
284
|
onClick={() => importConfig(pastedConfig)}
|
|
272
285
|
>
|
|
273
286
|
Load
|
|
274
|
-
</
|
|
287
|
+
</Button>
|
|
275
288
|
</div>
|
|
276
289
|
</div>
|
|
277
290
|
</div>
|