@cdc/core 4.24.3 → 4.24.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/assets/icon-command.svg +3 -0
- package/assets/icon-rotate-left.svg +3 -0
- package/components/AdvancedEditor.jsx +9 -0
- package/components/DataTable/DataTable.tsx +20 -15
- package/components/DataTable/DataTableStandAlone.tsx +51 -4
- package/components/DataTable/components/ChartHeader.tsx +3 -2
- package/components/DataTable/components/DataTableEditorPanel.tsx +20 -3
- package/components/DataTable/components/ExpandCollapse.tsx +22 -16
- package/components/DataTable/helpers/chartCellMatrix.tsx +3 -2
- package/components/DataTable/helpers/getChartCellValue.ts +21 -1
- package/components/DataTable/helpers/getDataSeriesColumns.ts +37 -16
- package/components/DataTable/helpers/getSeriesName.ts +2 -1
- package/components/DataTable/helpers/mapCellMatrix.tsx +2 -2
- package/components/DataTable/helpers/{customColumns.ts → removeNullColumns.ts} +3 -3
- package/components/DataTable/types/TableConfig.ts +11 -21
- package/components/EditorPanel/ColumnsEditor.tsx +304 -260
- package/components/EditorPanel/DataTableEditor.tsx +119 -48
- package/components/EditorPanel/VizFilterEditor.tsx +234 -0
- package/components/EditorWrapper/EditorWrapper.tsx +48 -0
- package/components/EditorWrapper/editor-wrapper.style.css +14 -0
- package/components/Filters.jsx +78 -61
- package/components/Layout/components/Responsive.tsx +184 -0
- package/components/Layout/components/Sidebar/components/Sidebar.tsx +47 -0
- package/components/Layout/components/Sidebar/components/sidebar.styles.scss +902 -0
- package/components/Layout/components/Sidebar/index.tsx +3 -0
- package/components/Layout/components/Visualization/index.tsx +81 -0
- package/components/Layout/components/Visualization/visualizations.scss +33 -0
- package/components/Layout/index.tsx +11 -0
- package/components/Layout/styles/editor-grid-view.scss +156 -0
- package/components/Layout/styles/editor-utils.scss +197 -0
- package/components/Layout/styles/editor.scss +144 -0
- package/components/LegendCircle.jsx +4 -3
- package/components/MediaControls.jsx +1 -1
- package/components/Table/Table.tsx +7 -5
- package/components/Table/components/Row.tsx +6 -2
- package/components/Table/types/RowType.ts +3 -0
- package/components/Waiting.jsx +11 -1
- package/components/_stories/DataTable.stories.tsx +1 -2
- package/components/_stories/EditorPanel.stories.tsx +1 -0
- package/components/createBarElement.jsx +37 -34
- package/components/elements/SkipTo.tsx +37 -5
- package/components/managers/DataDesigner.tsx +18 -18
- package/components/ui/Icon.tsx +5 -1
- package/helpers/DataTransform.ts +9 -32
- package/helpers/coveUpdateWorker.ts +21 -0
- package/helpers/footnoteSymbols.ts +11 -0
- package/helpers/useDataVizClasses.js +1 -5
- package/helpers/ver/4.23.4.ts +27 -0
- package/helpers/ver/4.24.3.ts +56 -0
- package/helpers/ver/4.24.5.ts +32 -0
- package/package.json +2 -2
- package/styles/_data-table.scss +8 -0
- package/styles/_global.scss +7 -4
- package/styles/_reset.scss +7 -6
- package/styles/_variables.scss +3 -0
- package/styles/base.scss +0 -18
- package/styles/v2/base/index.scss +1 -1
- package/styles/v2/components/ui/tooltip.scss +0 -21
- package/types/Axis.ts +4 -0
- package/types/Column.ts +1 -0
- package/types/ConfigureData.ts +8 -0
- package/types/DataDescription.ts +9 -0
- package/types/Legend.ts +1 -0
- package/types/MarkupInclude.ts +26 -0
- package/types/Runtime.ts +1 -0
- package/types/Series.ts +1 -1
- package/types/Table.ts +15 -13
- package/types/Visualization.ts +14 -10
- package/types/VizFilter.ts +13 -0
- package/helpers/coveUpdateWorker.js +0 -19
- package/helpers/ver/4.23.js +0 -10
- package/helpers/ver/4.24.3.js +0 -25
|
@@ -1,21 +1,66 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React, { useMemo } from 'react'
|
|
2
2
|
import Tooltip from '@cdc/core/components/ui/Tooltip'
|
|
3
3
|
import Icon from '../ui/Icon'
|
|
4
4
|
import { CheckBox, TextField } from './Inputs'
|
|
5
|
-
import type { Table } from '@cdc/core/types/Table'
|
|
6
5
|
import MultiSelect from '../MultiSelect'
|
|
7
6
|
import { UpdateFieldFunc } from '../../types/UpdateFieldFunc'
|
|
8
7
|
import { Visualization } from '../../types/Visualization'
|
|
8
|
+
import _ from 'lodash'
|
|
9
|
+
import { Column } from '../../types/Column'
|
|
9
10
|
|
|
10
11
|
interface DataTableProps {
|
|
11
|
-
config: Visualization
|
|
12
|
-
updateField: UpdateFieldFunc<string | boolean | string[] | number
|
|
12
|
+
config: Partial<Visualization>
|
|
13
|
+
updateField: UpdateFieldFunc<string | boolean | string[] | number | Record<string, Partial<Column>>>
|
|
13
14
|
isDashboard: boolean
|
|
14
15
|
columns: string[]
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
const
|
|
18
|
+
const PLACEHOLDER = '-Select-'
|
|
19
|
+
|
|
20
|
+
const DataTableEditor: React.FC<DataTableProps> = ({ config, updateField, isDashboard, columns: dataColumns }) => {
|
|
18
21
|
const isLoadedFromUrl = config.dataKey?.includes('http://') || config?.dataKey?.includes('https://')
|
|
22
|
+
const excludedColumns = useMemo(() => {
|
|
23
|
+
return Object.keys(config.columns)
|
|
24
|
+
.map<[string, boolean]>(key => [key, config.columns[key].dataTable])
|
|
25
|
+
.filter(([key, dataTable]) => !dataTable)
|
|
26
|
+
.map(([key]) => key)
|
|
27
|
+
}, [config.columns])
|
|
28
|
+
|
|
29
|
+
const getGroupableColumns = () => {
|
|
30
|
+
const columns: string[] = config.data.flatMap(Object.keys)
|
|
31
|
+
const configuredColumns = Object.values(config.columns).map(col => col.name)
|
|
32
|
+
const cols = _.uniq(columns).filter(key => {
|
|
33
|
+
if (configuredColumns.includes(key)) return false
|
|
34
|
+
return true
|
|
35
|
+
})
|
|
36
|
+
return cols
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const changeGroupBy = (value: string) => {
|
|
40
|
+
if (value === PLACEHOLDER) value = undefined
|
|
41
|
+
updateField('table', null, 'groupBy', value)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const excludeColumns = (section, subSection, fieldName, excludedColNames: string[]) => {
|
|
45
|
+
const newColumns = _.cloneDeep(config.columns)
|
|
46
|
+
const colNames: string[] = []
|
|
47
|
+
for (let colKey in newColumns) {
|
|
48
|
+
const col = newColumns[colKey]
|
|
49
|
+
colNames.push(col.name) // keep track of all column names
|
|
50
|
+
if (excludedColNames.includes(col.name)) {
|
|
51
|
+
// ensure all excluded columns are set to false
|
|
52
|
+
newColumns[colKey].dataTable = false
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
excludedColNames.forEach(colName => {
|
|
56
|
+
if (!colNames.includes(colName)) {
|
|
57
|
+
// make sure there is a column config to set to dataTable: false
|
|
58
|
+
newColumns[colName] = { name: colName, dataTable: false }
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
updateField(null, null, 'columns', newColumns)
|
|
62
|
+
}
|
|
63
|
+
|
|
19
64
|
return (
|
|
20
65
|
<>
|
|
21
66
|
<TextField
|
|
@@ -37,25 +82,28 @@ const DataTable: React.FC<DataTableProps> = ({ config, updateField, isDashboard,
|
|
|
37
82
|
</Tooltip>
|
|
38
83
|
}
|
|
39
84
|
/>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
<Tooltip
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
85
|
+
{config.type !== 'table' && (
|
|
86
|
+
<CheckBox
|
|
87
|
+
value={config.table.show}
|
|
88
|
+
fieldName='show'
|
|
89
|
+
label='Show Data Table'
|
|
90
|
+
section='table'
|
|
91
|
+
updateField={updateField}
|
|
92
|
+
className='column-heading'
|
|
93
|
+
tooltip={
|
|
94
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
95
|
+
<Tooltip.Target>
|
|
96
|
+
<Icon display='question' style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }} />
|
|
97
|
+
</Tooltip.Target>
|
|
98
|
+
<Tooltip.Content>
|
|
99
|
+
<p>Hiding the data table may affect accessibility. An alternate form of accessing visualization data is a 508 requirement.</p>
|
|
100
|
+
</Tooltip.Content>
|
|
101
|
+
</Tooltip>
|
|
102
|
+
}
|
|
103
|
+
/>
|
|
104
|
+
)}
|
|
105
|
+
|
|
106
|
+
{config.visualizationType !== 'Box Plot' && config.type !== 'table' && (
|
|
59
107
|
<CheckBox
|
|
60
108
|
value={config.table.showVertical}
|
|
61
109
|
fieldName='showVertical'
|
|
@@ -75,7 +123,25 @@ const DataTable: React.FC<DataTableProps> = ({ config, updateField, isDashboard,
|
|
|
75
123
|
}
|
|
76
124
|
/>
|
|
77
125
|
)}
|
|
78
|
-
|
|
126
|
+
{config.type !== 'table' && (
|
|
127
|
+
<TextField
|
|
128
|
+
value={config.table.indexLabel}
|
|
129
|
+
section='table'
|
|
130
|
+
fieldName='indexLabel'
|
|
131
|
+
label='Index Column Header'
|
|
132
|
+
updateField={updateField}
|
|
133
|
+
tooltip={
|
|
134
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
135
|
+
<Tooltip.Target>
|
|
136
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
137
|
+
</Tooltip.Target>
|
|
138
|
+
<Tooltip.Content>
|
|
139
|
+
<p>To comply with 508 standards, if the first column in the data table has no header, enter a brief one here.</p>
|
|
140
|
+
</Tooltip.Content>
|
|
141
|
+
</Tooltip>
|
|
142
|
+
}
|
|
143
|
+
/>
|
|
144
|
+
)}
|
|
79
145
|
<TextField
|
|
80
146
|
value={config.table.caption}
|
|
81
147
|
updateField={updateField}
|
|
@@ -95,29 +161,11 @@ const DataTable: React.FC<DataTableProps> = ({ config, updateField, isDashboard,
|
|
|
95
161
|
</Tooltip>
|
|
96
162
|
}
|
|
97
163
|
/>
|
|
98
|
-
<CheckBox value={config.table.limitHeight} section='table' fieldName='limitHeight' label='Limit Table Height' updateField={updateField} />
|
|
99
|
-
{config.type !== 'table' && (
|
|
100
|
-
<CheckBox
|
|
101
|
-
value={config.table.customTableConfig}
|
|
102
|
-
fieldName='customTableConfig'
|
|
103
|
-
label='Customize Table Config'
|
|
104
|
-
section='table'
|
|
105
|
-
updateField={updateField}
|
|
106
|
-
tooltip={
|
|
107
|
-
<Tooltip style={{ textTransform: 'none' }}>
|
|
108
|
-
<Tooltip.Target>
|
|
109
|
-
<Icon display='question' style={{ marginLeft: '0.5rem', display: 'inline-block', whiteSpace: 'nowrap' }} />
|
|
110
|
-
</Tooltip.Target>
|
|
111
|
-
<Tooltip.Content>
|
|
112
|
-
<p>This will display all available columns in the data set. It will not show any columns where all of the column cells are null.</p>
|
|
113
|
-
</Tooltip.Content>
|
|
114
|
-
</Tooltip>
|
|
115
|
-
}
|
|
116
|
-
/>
|
|
117
|
-
)}
|
|
118
|
-
{config.table.customTableConfig && <MultiSelect options={columns.map(c => ({ label: c, value: c }))} fieldName='excludeColumns' label='Exclude Columns' section='table' updateField={updateField} />}
|
|
164
|
+
<CheckBox value={config.table.limitHeight} section='table' fieldName='limitHeight' label=' Limit Table Height' updateField={updateField} />
|
|
119
165
|
{config.table.limitHeight && <TextField value={config.table.height} section='table' fieldName='height' label='Data Table Height' type='number' min={0} max={500} placeholder='Height(px)' updateField={updateField} />}
|
|
120
|
-
<
|
|
166
|
+
<MultiSelect key={excludedColumns.join('') + 'excluded'} options={dataColumns.map(c => ({ label: c, value: c }))} selected={excludedColumns} fieldName='dataTable' label='Exclude Columns' section='columns' updateField={excludeColumns} />
|
|
167
|
+
<CheckBox value={config.table.collapsible} fieldName='collapsible' label=' Collapsible' section='table' updateField={updateField} />
|
|
168
|
+
{config.table.collapsible !== false && <CheckBox value={config.table.expanded} fieldName='expanded' label=' Expanded by Default' section='table' updateField={updateField} />}
|
|
121
169
|
{isDashboard && config.type !== 'table' && <CheckBox value={config.table.showDataTableLink} fieldName='showDataTableLink' label='Show Data Table Name & Link' section='table' updateField={updateField} />}
|
|
122
170
|
{isLoadedFromUrl && <CheckBox value={config.table.showDownloadUrl} fieldName='showDownloadUrl' label='Show URL to Automatically Updated Data' section='table' updateField={updateField} />}
|
|
123
171
|
{config.type !== 'table' && <CheckBox value={config.table.showDownloadImgButton} fieldName='showDownloadImgButton' label='Display Image Button' section='table' updateField={updateField} />}
|
|
@@ -125,8 +173,31 @@ const DataTable: React.FC<DataTableProps> = ({ config, updateField, isDashboard,
|
|
|
125
173
|
<span className='edit-label column-heading'>Table Cell Min Width</span>
|
|
126
174
|
<input type='number' value={config.table.cellMinWidth ? config.table.cellMinWidth : 0} onChange={e => updateField('table', null, 'cellMinWidth', e.target.value)} />
|
|
127
175
|
</label>
|
|
176
|
+
<label>
|
|
177
|
+
<span className='edit-label column-heading'>
|
|
178
|
+
Group By{' '}
|
|
179
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
180
|
+
<Tooltip.Target>
|
|
181
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
182
|
+
</Tooltip.Target>
|
|
183
|
+
<Tooltip.Content>
|
|
184
|
+
<p>Choose a column to use for grouping data rows. The selected column will not be shown in the data table. You will only be able to choose a column which does not have a column configuration.</p>
|
|
185
|
+
</Tooltip.Content>
|
|
186
|
+
</Tooltip>
|
|
187
|
+
</span>
|
|
188
|
+
<select
|
|
189
|
+
value={config.table.groupBy}
|
|
190
|
+
onChange={event => {
|
|
191
|
+
changeGroupBy(event.target.value)
|
|
192
|
+
}}
|
|
193
|
+
>
|
|
194
|
+
{[PLACEHOLDER, ...getGroupableColumns()].map(option => (
|
|
195
|
+
<option key={option}>{option}</option>
|
|
196
|
+
))}
|
|
197
|
+
</select>
|
|
198
|
+
</label>
|
|
128
199
|
</>
|
|
129
200
|
)
|
|
130
201
|
}
|
|
131
202
|
|
|
132
|
-
export default
|
|
203
|
+
export default DataTableEditor
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { AccordionItem, AccordionItemHeading, AccordionItemPanel, AccordionItemButton } from 'react-accessible-accordion'
|
|
2
|
+
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd'
|
|
3
|
+
import { Select } from './Inputs'
|
|
4
|
+
import Tooltip from '../ui/Tooltip'
|
|
5
|
+
import Icon from '../ui/Icon'
|
|
6
|
+
import { Visualization } from '../../types/Visualization'
|
|
7
|
+
import { UpdateFieldFunc } from '../../types/UpdateFieldFunc'
|
|
8
|
+
import _ from 'lodash'
|
|
9
|
+
import { VizFilter } from '../../types/VizFilter'
|
|
10
|
+
import { filterStyleOptions, filterOrderOptions, handleSorting } from '../Filters'
|
|
11
|
+
|
|
12
|
+
type VizFilterProps = {
|
|
13
|
+
config: Visualization
|
|
14
|
+
updateField: UpdateFieldFunc<string | VizFilter[] | VizFilter>
|
|
15
|
+
rawData: Object[]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawData }) => {
|
|
19
|
+
const getFilters = () => {
|
|
20
|
+
return _.uniq(_.flatten(rawData.map(row => Object.keys(row))))
|
|
21
|
+
}
|
|
22
|
+
const removeFilter = index => {
|
|
23
|
+
let filters = _.cloneDeep(config.filters)
|
|
24
|
+
|
|
25
|
+
filters.splice(index, 1)
|
|
26
|
+
|
|
27
|
+
updateField(null, null, 'filters', filters)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const updateFilterProp = (prop, index, value) => {
|
|
31
|
+
updateField('filters', index, prop, value)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const handleNameChange = (filterIndex, columnName) => {
|
|
35
|
+
const values = _.uniq(rawData.map(row => row[columnName]))
|
|
36
|
+
const copiedFilter = { ..._.cloneDeep(config.filters[filterIndex]), columnName, values }
|
|
37
|
+
handleSorting(copiedFilter) // sorts dropdown values in place
|
|
38
|
+
copiedFilter.active = copiedFilter.values[0]
|
|
39
|
+
const newFilters = config.filters.map((filter, index) => {
|
|
40
|
+
if (index === filterIndex) return copiedFilter
|
|
41
|
+
return filter
|
|
42
|
+
})
|
|
43
|
+
updateField(null, null, 'filters', newFilters)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const addNewFilter = () => {
|
|
47
|
+
const filters = config.filters ? [...config.filters] : []
|
|
48
|
+
const newVizFilter: VizFilter = { values: [], filterStyle: 'dropdown' } as VizFilter
|
|
49
|
+
filters.push(newVizFilter)
|
|
50
|
+
updateField(null, null, 'filters', filters)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const handleFilterOrder = (idx1, idx2, filterIndex, filter) => {
|
|
54
|
+
// Create a shallow copy of the filter values array & update position of the values
|
|
55
|
+
const updatedValues = [...filter.values]
|
|
56
|
+
const [movedItem] = updatedValues.splice(idx1, 1)
|
|
57
|
+
updatedValues.splice(idx2, 0, movedItem)
|
|
58
|
+
|
|
59
|
+
const filtersCopy = _.cloneDeep(config.filters)
|
|
60
|
+
const filterItem = { ...filtersCopy[filterIndex] }
|
|
61
|
+
|
|
62
|
+
// Overwrite filterItem.values since thats what we map through in the editor panel
|
|
63
|
+
filterItem.values = updatedValues
|
|
64
|
+
filterItem.orderedValues = updatedValues
|
|
65
|
+
filterItem.active = updatedValues[0]
|
|
66
|
+
filterItem.order = 'cust'
|
|
67
|
+
|
|
68
|
+
// Update the filters
|
|
69
|
+
filtersCopy[filterIndex] = filterItem
|
|
70
|
+
|
|
71
|
+
updateField(null, null, 'filters', filtersCopy)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<>
|
|
76
|
+
{config.filters && (
|
|
77
|
+
<>
|
|
78
|
+
<Select
|
|
79
|
+
value={config.filterBehavior}
|
|
80
|
+
fieldName='filterBehavior'
|
|
81
|
+
label='Filter Behavior'
|
|
82
|
+
updateField={updateField}
|
|
83
|
+
options={['Apply Button', 'Filter Change']}
|
|
84
|
+
tooltip={
|
|
85
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
86
|
+
<Tooltip.Target>
|
|
87
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
88
|
+
</Tooltip.Target>
|
|
89
|
+
<Tooltip.Content>
|
|
90
|
+
<p>The Apply Button option changes the visualization when the user clicks "apply". The Filter Change option immediately changes the visualization when the selection is changed.</p>
|
|
91
|
+
</Tooltip.Content>
|
|
92
|
+
</Tooltip>
|
|
93
|
+
}
|
|
94
|
+
/>
|
|
95
|
+
<br />
|
|
96
|
+
</>
|
|
97
|
+
)}
|
|
98
|
+
{config.filters && (
|
|
99
|
+
<ul className='filters-list'>
|
|
100
|
+
{/* Whether filters should apply onChange or Apply Button */}
|
|
101
|
+
|
|
102
|
+
{config.filters.map((filter, index) => {
|
|
103
|
+
if (filter.type === 'url') return <></>
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<fieldset className='edit-block' key={index}>
|
|
107
|
+
<button
|
|
108
|
+
type='button'
|
|
109
|
+
className='remove-column'
|
|
110
|
+
onClick={() => {
|
|
111
|
+
removeFilter(index)
|
|
112
|
+
}}
|
|
113
|
+
>
|
|
114
|
+
Remove
|
|
115
|
+
</button>
|
|
116
|
+
<label>
|
|
117
|
+
<span className='edit-label column-heading'>Filter</span>
|
|
118
|
+
<select
|
|
119
|
+
value={filter.columnName}
|
|
120
|
+
onChange={e => {
|
|
121
|
+
handleNameChange(index, e.target.value)
|
|
122
|
+
}}
|
|
123
|
+
>
|
|
124
|
+
<option value=''>- Select Option -</option>
|
|
125
|
+
{getFilters().map((dataKey, index) => (
|
|
126
|
+
<option value={dataKey} key={index}>
|
|
127
|
+
{dataKey}
|
|
128
|
+
</option>
|
|
129
|
+
))}
|
|
130
|
+
</select>
|
|
131
|
+
</label>
|
|
132
|
+
|
|
133
|
+
<label>
|
|
134
|
+
<span className='edit-showDropdown column-heading'>Show Filter Input</span>
|
|
135
|
+
<input
|
|
136
|
+
type='checkbox'
|
|
137
|
+
checked={filter.showDropdown === undefined ? true : filter.showDropdown}
|
|
138
|
+
onChange={e => {
|
|
139
|
+
updateFilterProp('showDropdown', index, e.target.checked)
|
|
140
|
+
}}
|
|
141
|
+
/>
|
|
142
|
+
</label>
|
|
143
|
+
|
|
144
|
+
<label>
|
|
145
|
+
<span className='edit-label column-heading'>Filter Style</span>
|
|
146
|
+
|
|
147
|
+
<select
|
|
148
|
+
value={filter.filterStyle}
|
|
149
|
+
onChange={e => {
|
|
150
|
+
updateFilterProp('filterStyle', index, e.target.value)
|
|
151
|
+
}}
|
|
152
|
+
>
|
|
153
|
+
{filterStyleOptions.map((item, index) => {
|
|
154
|
+
return (
|
|
155
|
+
<option key={`filter-style-${index}`} value={item}>
|
|
156
|
+
{item}
|
|
157
|
+
</option>
|
|
158
|
+
)
|
|
159
|
+
})}
|
|
160
|
+
</select>
|
|
161
|
+
</label>
|
|
162
|
+
<label>
|
|
163
|
+
<span className='edit-label column-heading'>Label</span>
|
|
164
|
+
<input
|
|
165
|
+
type='text'
|
|
166
|
+
value={filter.label}
|
|
167
|
+
onChange={e => {
|
|
168
|
+
updateFilterProp('label', index, e.target.value)
|
|
169
|
+
}}
|
|
170
|
+
/>
|
|
171
|
+
</label>
|
|
172
|
+
|
|
173
|
+
<label>
|
|
174
|
+
<span className='edit-label column-heading'>Default Value Set By Query String Parameter</span>
|
|
175
|
+
<input
|
|
176
|
+
type='text'
|
|
177
|
+
value={filter.setByQueryParameter}
|
|
178
|
+
onChange={e => {
|
|
179
|
+
updateFilterProp('setByQueryParameter', index, e.target.value)
|
|
180
|
+
}}
|
|
181
|
+
/>
|
|
182
|
+
</label>
|
|
183
|
+
|
|
184
|
+
<label>
|
|
185
|
+
<span className='edit-filterOrder column-heading'>Filter Order</span>
|
|
186
|
+
<select value={filter.order ? filter.order : 'asc'} onChange={e => updateFilterProp('order', index, e.target.value)}>
|
|
187
|
+
{filterOrderOptions.map((option, index) => {
|
|
188
|
+
return (
|
|
189
|
+
<option value={option.value} key={`filter-${index}`}>
|
|
190
|
+
{option.label}
|
|
191
|
+
</option>
|
|
192
|
+
)
|
|
193
|
+
})}
|
|
194
|
+
</select>
|
|
195
|
+
|
|
196
|
+
{filter.order === 'cust' && (
|
|
197
|
+
<DragDropContext onDragEnd={({ source, destination }) => handleFilterOrder(source.index, destination.index, index, config.filters[index])}>
|
|
198
|
+
<Droppable droppableId='filter_order'>
|
|
199
|
+
{provided => (
|
|
200
|
+
<ul {...provided.droppableProps} className='sort-list' ref={provided.innerRef} style={{ marginTop: '1em' }}>
|
|
201
|
+
{config.filters[index]?.values.map((value, index) => {
|
|
202
|
+
return (
|
|
203
|
+
<Draggable key={value} draggableId={`draggableFilter-${value}`} index={index}>
|
|
204
|
+
{(provided, snapshot) => (
|
|
205
|
+
<li>
|
|
206
|
+
<div className={snapshot.isDragging ? 'currently-dragging' : ''} style={provided.draggableProps.style} ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
|
|
207
|
+
{value}
|
|
208
|
+
</div>
|
|
209
|
+
</li>
|
|
210
|
+
)}
|
|
211
|
+
</Draggable>
|
|
212
|
+
)
|
|
213
|
+
})}
|
|
214
|
+
{provided.placeholder}
|
|
215
|
+
</ul>
|
|
216
|
+
)}
|
|
217
|
+
</Droppable>
|
|
218
|
+
</DragDropContext>
|
|
219
|
+
)}
|
|
220
|
+
</label>
|
|
221
|
+
</fieldset>
|
|
222
|
+
)
|
|
223
|
+
})}
|
|
224
|
+
</ul>
|
|
225
|
+
)}
|
|
226
|
+
{!config.filters && <p style={{ textAlign: 'center' }}>There are currently no filters.</p>}
|
|
227
|
+
<button type='button' onClick={addNewFilter} className='btn btn-primary full-width'>
|
|
228
|
+
Add Filter
|
|
229
|
+
</button>
|
|
230
|
+
</>
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export default VizFilterEditor
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Visualization } from '../../types/Visualization'
|
|
3
|
+
import { ViewPort } from '../../types/ViewPort'
|
|
4
|
+
import './editor-wrapper.style.css'
|
|
5
|
+
import { Accordion } from 'react-accessible-accordion'
|
|
6
|
+
|
|
7
|
+
type StandAloneComponentProps = {
|
|
8
|
+
visualizationKey: string
|
|
9
|
+
config
|
|
10
|
+
updateConfig: (Visualization) => void
|
|
11
|
+
configUrl: string
|
|
12
|
+
setEditing: Function
|
|
13
|
+
hostname: string
|
|
14
|
+
viewport?: ViewPort
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type EditorProps = {
|
|
18
|
+
component: React.JSXElementConstructor<StandAloneComponentProps>
|
|
19
|
+
type: string
|
|
20
|
+
visualizationKey: string
|
|
21
|
+
visualizationConfig: Visualization
|
|
22
|
+
updateConfig: (Visualization) => void
|
|
23
|
+
viewport?: ViewPort
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const EditorWrapper: React.FC<React.PropsWithChildren<EditorProps>> = ({ children, visualizationKey, visualizationConfig, type, component: Component, updateConfig, viewport }) => {
|
|
27
|
+
const [displayPanel, setDisplayPanel] = React.useState(true)
|
|
28
|
+
return (
|
|
29
|
+
<>
|
|
30
|
+
<div className='editor-wrapper'>
|
|
31
|
+
<button className={`editor-toggle ${displayPanel ? '' : 'collapsed'}`} title={displayPanel ? `Collapse Editor` : `Expand Editor`} onClick={() => setDisplayPanel(!displayPanel)} />
|
|
32
|
+
<section className={`${displayPanel ? '' : 'hidden'} editor-panel cove`}>
|
|
33
|
+
<div aria-level={2} role='heading' className='heading-2'>
|
|
34
|
+
Configure {type}
|
|
35
|
+
</div>
|
|
36
|
+
<section>
|
|
37
|
+
<Accordion allowZeroExpanded={true}>{children}</Accordion>
|
|
38
|
+
</section>
|
|
39
|
+
</section>
|
|
40
|
+
<div className='preview-wrapper'>
|
|
41
|
+
<Component visualizationKey={visualizationKey} config={visualizationConfig} updateConfig={updateConfig} configUrl={undefined} setEditing={undefined} hostname={undefined} viewport={viewport} />
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default EditorWrapper
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
.editor-wrapper {
|
|
2
|
+
--editorPanelWidth: 350px;
|
|
3
|
+
position: relative;
|
|
4
|
+
min-height: 80vh;
|
|
5
|
+
.editor-panel {
|
|
6
|
+
:is(form) {
|
|
7
|
+
border-right: var(--lightGray) 1px solid;
|
|
8
|
+
flex-grow: 1;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
.preview-wrapper {
|
|
12
|
+
padding-left: var(--editorPanelWidth);
|
|
13
|
+
}
|
|
14
|
+
}
|