@cdc/dashboard 1.1.2 → 9.22.9
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/cdcdashboard.js +159 -48
- package/examples/default-filter-control.json +175 -0
- package/examples/default-multi-dataset.json +498 -0
- package/examples/default.json +36 -348
- package/examples/private/chart-issue.json +3467 -0
- package/examples/private/no-issue.json +3467 -0
- package/examples/private/totals-two.json +104 -0
- package/examples/private/totals.json +103 -0
- package/examples/temp-example-data.json +130 -0
- package/examples/test-example.json +1 -0
- package/package.json +14 -8
- package/src/CdcDashboard.js +282 -156
- package/src/CdcDashboard.jsx +668 -0
- package/src/{context.tsx → ConfigContext.js} +0 -0
- package/src/components/{Column.js → Column.jsx} +9 -7
- package/src/components/DataTable.tsx +55 -54
- package/src/components/EditorPanel.js +207 -45
- package/src/components/{Grid.js → Grid.jsx} +5 -4
- package/src/components/Header.jsx +242 -0
- package/src/components/Row.js +1 -0
- package/src/components/Row.jsx +181 -0
- package/src/components/Row.jsx~HEAD +212 -0
- package/src/components/Widget.js +24 -5
- package/src/components/Widget.jsx +191 -0
- package/src/index.html +14 -11
- package/src/scss/editor-panel.scss +53 -49
- package/src/scss/grid.scss +61 -14
- package/src/scss/main.scss +73 -9
- package/LICENSE +0 -201
- package/src/components/Header.js +0 -15
- package/src/images/icon-close.svg +0 -1
- package/src/images/icon-down.svg +0 -1
- package/src/images/icon-edit.svg +0 -1
- package/src/images/icon-move.svg +0 -8
- package/src/images/icon-up.svg +0 -1
- package/src/images/warning.svg +0 -1
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import React, { useState, useEffect, useContext } from 'react'
|
|
2
|
+
|
|
3
|
+
import ConfigContext from '../ConfigContext'
|
|
4
|
+
|
|
5
|
+
import { useGlobalContext } from '@cdc/core/components/GlobalContext'
|
|
6
|
+
import Modal from '@cdc/core/components/ui/Modal'
|
|
7
|
+
|
|
8
|
+
const Header = ({setPreview, tabSelected, setTabSelected, back, subEditor = null}) => {
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
config,
|
|
12
|
+
updateConfig,
|
|
13
|
+
setParentConfig
|
|
14
|
+
} = useContext(ConfigContext);
|
|
15
|
+
|
|
16
|
+
const { overlay } = useGlobalContext()
|
|
17
|
+
|
|
18
|
+
const [ columns, setColumns ] = useState([])
|
|
19
|
+
|
|
20
|
+
const changeConfigValue = (parentObj, key, value) => {
|
|
21
|
+
|
|
22
|
+
let newConfig = {...config};
|
|
23
|
+
if(!newConfig[parentObj]) newConfig[parentObj] = {};
|
|
24
|
+
newConfig[parentObj][key] = value;
|
|
25
|
+
|
|
26
|
+
updateConfig(newConfig)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const setTab = (index) => {
|
|
30
|
+
setTabSelected(index)
|
|
31
|
+
if(index === 3){
|
|
32
|
+
setPreview(true)
|
|
33
|
+
} else {
|
|
34
|
+
setPreview(false)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const addNewFilter = () => {
|
|
39
|
+
let dashboardConfig = {...config.dashboard};
|
|
40
|
+
|
|
41
|
+
dashboardConfig.sharedFilters = dashboardConfig.sharedFilters || [];
|
|
42
|
+
|
|
43
|
+
dashboardConfig.sharedFilters.push({key: 'Dashboard Filter ' + (dashboardConfig.sharedFilters.length + 1), values: []});
|
|
44
|
+
|
|
45
|
+
updateConfig({...config, dashboard: dashboardConfig});
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const removeFilter = (index) => {
|
|
50
|
+
let dashboardConfig = {...config.dashboard};
|
|
51
|
+
|
|
52
|
+
dashboardConfig.sharedFilters.splice(index, 1);
|
|
53
|
+
|
|
54
|
+
updateConfig({...config, dashboard: dashboardConfig});
|
|
55
|
+
|
|
56
|
+
overlay?.actions.toggleOverlay();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const convertStateToConfig = (type = "JSON") => {
|
|
60
|
+
let strippedState = JSON.parse(JSON.stringify(config))
|
|
61
|
+
delete strippedState.newViz
|
|
62
|
+
delete strippedState.runtime
|
|
63
|
+
|
|
64
|
+
if(type === "JSON") {
|
|
65
|
+
return JSON.stringify( strippedState )
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return strippedState
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
const parsedData = convertStateToConfig()
|
|
73
|
+
|
|
74
|
+
// Emit the data in a regular JS event so it can be consumed by anything.
|
|
75
|
+
const event = new CustomEvent('updateVizConfig', { detail: parsedData})
|
|
76
|
+
|
|
77
|
+
window.dispatchEvent(event)
|
|
78
|
+
|
|
79
|
+
// Pass up to Editor if needed
|
|
80
|
+
if(setParentConfig) {
|
|
81
|
+
const newConfig = convertStateToConfig("object")
|
|
82
|
+
setParentConfig(newConfig)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
86
|
+
}, [config]);
|
|
87
|
+
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
const runSetColumns = async () => {
|
|
90
|
+
let columns = {};
|
|
91
|
+
let dataKeys = Object.keys(config.datasets);
|
|
92
|
+
|
|
93
|
+
for(let i = 0; i < dataKeys.length; i++){
|
|
94
|
+
if(!config.datasets[dataKeys[i]].data && config.datasets[dataKeys[i]].dataUrl){
|
|
95
|
+
config.datasets[dataKeys[i]].data = await fetchRemoteData(config.datasets[dataKeys[i]].dataUrl);
|
|
96
|
+
if(config.datasets[dataKeys[i]].dataDescription) {
|
|
97
|
+
try {
|
|
98
|
+
config.datasets[dataKeys[i]].data = transform.autoStandardize(config.datasets[dataKeys[i]].data);
|
|
99
|
+
config.datasets[dataKeys[i]].data = transform.developerStandardize(config.datasets[dataKeys[i]].data, config.datasets[dataKey].dataDescription);
|
|
100
|
+
} catch(e) {
|
|
101
|
+
//Data not able to be standardized, leave as is
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if(config.datasets[dataKeys[i]].data) {
|
|
107
|
+
config.datasets[dataKeys[i]].data.map(row => {
|
|
108
|
+
Object.keys(row).forEach(columnName => columns[columnName] = true)
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
setColumns(Object.keys(columns))
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
runSetColumns()
|
|
117
|
+
}, [config.datasets]);
|
|
118
|
+
|
|
119
|
+
const filterModal = (filter, index) => {
|
|
120
|
+
|
|
121
|
+
const saveChanges = () => {
|
|
122
|
+
let tempConfig = {...config.dashboard};
|
|
123
|
+
tempConfig.sharedFilters[index] = filter;
|
|
124
|
+
|
|
125
|
+
updateConfig({...config, dashboard: tempConfig});
|
|
126
|
+
|
|
127
|
+
overlay?.actions.toggleOverlay()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const updateFilterProp = (name, index, value) => {
|
|
131
|
+
let newFilter = {...filter};
|
|
132
|
+
|
|
133
|
+
newFilter[name] = value;
|
|
134
|
+
|
|
135
|
+
overlay?.actions.openOverlay(filterModal(newFilter, index))
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const addFilterUsedBy = (filter, index, value) => {
|
|
139
|
+
if(!filter.usedBy) filter.usedBy = [];
|
|
140
|
+
filter.usedBy.push(value);
|
|
141
|
+
updateFilterProp('usedBy', index, filter.usedBy);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const removeFilterUsedBy = (filter, index, value) => {
|
|
145
|
+
let usedByIndex = filter.usedBy.indexOf(value);
|
|
146
|
+
if(usedByIndex !== -1){
|
|
147
|
+
filter.usedBy.splice(usedByIndex, 1);
|
|
148
|
+
updateFilterProp('usedBy', index, filter.usedBy);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<Modal>
|
|
155
|
+
<Modal.Content>
|
|
156
|
+
<h2>Dashboard Filter Settings</h2>
|
|
157
|
+
<fieldset className="shared-filter-modal" key={filter.columnName + index}>
|
|
158
|
+
<button type="button" className="btn btn-primary remove-column" onClick={() => {removeFilter(index)}}>Remove Filter</button>
|
|
159
|
+
<label>
|
|
160
|
+
<span className="edit-label column-heading">Filter: </span>
|
|
161
|
+
<select value={filter.columnName} onChange={(e) => {updateFilterProp('columnName', index, e.target.value)}}>
|
|
162
|
+
<option value="">- Select Option -</option>
|
|
163
|
+
{columns.map((dataKey) => (
|
|
164
|
+
<option value={dataKey} key={`filter-column-select-item-${dataKey}`}>{dataKey}</option>
|
|
165
|
+
))}
|
|
166
|
+
</select>
|
|
167
|
+
</label>
|
|
168
|
+
<label>
|
|
169
|
+
<span className="edit-label column-heading">Label: </span>
|
|
170
|
+
<input type="text" value={filter.key} onChange={(e) => {updateFilterProp('key', index, e.target.value)}}/>
|
|
171
|
+
</label>
|
|
172
|
+
<label>
|
|
173
|
+
<span className="edit-label column-heading">Show Dropdown</span>
|
|
174
|
+
<input type="checkbox" defaultChecked={filter.showDropdown === true} onChange={(e) => {updateFilterProp('showDropdown', index, !filter.showDropdown)}}/>
|
|
175
|
+
</label>
|
|
176
|
+
<label>
|
|
177
|
+
<span className="edit-label column-heading">Set By: </span>
|
|
178
|
+
<select value={filter.setBy} onChange={e => updateFilterProp('setBy', index, e.target.value)}>
|
|
179
|
+
<option value="">- Select Option -</option>
|
|
180
|
+
{Object.keys(config.visualizations).map((vizKey) => (
|
|
181
|
+
<option value={vizKey} key={`set-by-select-item-${vizKey}`}>{config.visualizations[vizKey].general && config.visualizations[vizKey].general.title ? config.visualizations[vizKey].general.title : (config.visualizations[vizKey].title || vizKey)}</option>
|
|
182
|
+
))}
|
|
183
|
+
</select>
|
|
184
|
+
</label>
|
|
185
|
+
<label>
|
|
186
|
+
<span className="edit-label column-heading">Used By:</span>
|
|
187
|
+
<ul>
|
|
188
|
+
{filter.usedBy && filter.usedBy.map(vizKey => (
|
|
189
|
+
<li key={`used-by-list-item-${vizKey}`}><span>{config.visualizations[vizKey].general && config.visualizations[vizKey].general.title ? config.visualizations[vizKey].general.title : (config.visualizations[vizKey].title || vizKey)}</span> <button onClick={(e) => {e.preventDefault();removeFilterUsedBy(filter, index, vizKey)}}>X</button></li>
|
|
190
|
+
))}
|
|
191
|
+
</ul>
|
|
192
|
+
<select onChange={e => addFilterUsedBy(filter, index, e.target.value)}>
|
|
193
|
+
<option value="">- Select Option -</option>
|
|
194
|
+
{Object.keys(config.visualizations).filter(vizKey => filter.setBy !== vizKey && (!filter.usedBy || filter.usedBy.indexOf(vizKey) === -1) && !config.visualizations[vizKey].usesSharedFilter).map((vizKey) => (
|
|
195
|
+
<option value={vizKey} key={`used-by-select-item-${vizKey}`}>{config.visualizations[vizKey].general && config.visualizations[vizKey].general.title ? config.visualizations[vizKey].general.title : (config.visualizations[vizKey].title || vizKey)}</option>
|
|
196
|
+
))}
|
|
197
|
+
</select>
|
|
198
|
+
</label>
|
|
199
|
+
</fieldset>
|
|
200
|
+
<button type="button" className="btn btn-primary" style={{display: 'inline-block', 'margin-right': '1em'}} onClick={overlay?.actions.toggleOverlay}>Cancel</button>
|
|
201
|
+
<button type="button" className="btn btn-primary" style={{display: 'inline-block'}} onClick={saveChanges}>Save</button>
|
|
202
|
+
</Modal.Content>
|
|
203
|
+
</Modal>
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<div aria-level="2" role="heading" className={`editor-heading${subEditor ? ' sub-dashboard-viz' : ''}`}>
|
|
209
|
+
{subEditor ? <div className="heading-1 back-to" onClick={back} style={{cursor: 'pointer'}}><span>←</span> Back to Dashboard</div> : <div className="heading-1">Dashboard Editor<br/>{<input type="text" placeholder="Enter Dashboard Name Here" defaultValue={config.dashboard.title} onChange={e => changeConfigValue('dashboard', 'title', e.target.value)}/>}</div>}
|
|
210
|
+
{!subEditor && <div>
|
|
211
|
+
<ul className="toggle-bar">
|
|
212
|
+
<li className={tabSelected === 0 ? 'active' : 'inactive'} onClick={() => {setTab(0)}}>Dashboard Description</li>
|
|
213
|
+
<li className={tabSelected === 1 ? 'active' : 'inactive'} onClick={() => {setTab(1)}}>Dashboard Filters</li>
|
|
214
|
+
<li className={tabSelected === 2 ? 'active' : 'inactive'} onClick={() => {setTab(2)}}>Data Table Settings</li>
|
|
215
|
+
<li className={tabSelected === 3 ? 'active' : 'inactive'} onClick={() => {setTab(3)}}>Dashboard Preview</li>
|
|
216
|
+
</ul>
|
|
217
|
+
<div className="heading-body">
|
|
218
|
+
{tabSelected === 0 && <input type="text" className="description-input" placeholder="Type a dashboard description here." defaultValue={config.dashboard.description} onChange={e => changeConfigValue('dashboard', 'description', e.target.value)} />}
|
|
219
|
+
{tabSelected === 1 && (
|
|
220
|
+
<>
|
|
221
|
+
{config.dashboard.sharedFilters && config.dashboard.sharedFilters.map((sharedFilter, index) => (
|
|
222
|
+
<span className="shared-filter-button" key={`shared-filter-${sharedFilter.key}`}>
|
|
223
|
+
<a href="#" onClick={(e) => {e.preventDefault(); overlay?.actions.openOverlay(filterModal(sharedFilter, index))}}>{sharedFilter.key}</a>
|
|
224
|
+
<button onClick={() => removeFilter(index)}>X</button>
|
|
225
|
+
</span>
|
|
226
|
+
))}
|
|
227
|
+
<button onClick={addNewFilter}>Add New Filter</button></>
|
|
228
|
+
)}
|
|
229
|
+
{tabSelected === 2 && (
|
|
230
|
+
<>
|
|
231
|
+
<label>Show Table</label><input type="checkbox" defaultChecked={config.table.show} onChange={e => changeConfigValue('table', 'show', e.target.checked)} />
|
|
232
|
+
<label>Expanded by Default</label><input type="checkbox" defaultChecked={config.table.expanded} onChange={e => changeConfigValue('table', 'expanded', e.target.checked)} />
|
|
233
|
+
<label>Display Download Button</label><input type="checkbox" defaultChecked={config.table.download} onChange={e => changeConfigValue('table', 'download', e.target.checked)} />
|
|
234
|
+
</>
|
|
235
|
+
)}
|
|
236
|
+
</div>
|
|
237
|
+
</div>}
|
|
238
|
+
</div>
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export default Header
|
package/src/components/Row.js
CHANGED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import React, { useContext, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import { useGlobalContext } from '@cdc/core/components/GlobalContext'
|
|
4
|
+
import ConfigContext from '../ConfigContext'
|
|
5
|
+
|
|
6
|
+
import Modal from '@cdc/core/components/ui/Modal'
|
|
7
|
+
import InputToggle from '@cdc/core/components/inputs/InputToggle'
|
|
8
|
+
import Icon from '@cdc/core/components/ui/Icon'
|
|
9
|
+
|
|
10
|
+
import Column from './Column'
|
|
11
|
+
|
|
12
|
+
import OneColIcon from '../images/icon-col-12.svg'
|
|
13
|
+
import TwoColIcon from '../images/icon-col-6.svg'
|
|
14
|
+
import ThreeColIcon from '../images/icon-col-4.svg'
|
|
15
|
+
import FourEightColIcon from '../images/icon-col-4-8.svg'
|
|
16
|
+
import EightFourColIcon from '../images/icon-col-8-4.svg'
|
|
17
|
+
|
|
18
|
+
const RowMenu = ({ rowIdx, row }) => {
|
|
19
|
+
const { overlay } = useGlobalContext()
|
|
20
|
+
const { rows, config, updateConfig } = useContext(ConfigContext)
|
|
21
|
+
|
|
22
|
+
const getCurr = () => {
|
|
23
|
+
let res = []
|
|
24
|
+
|
|
25
|
+
for (let i = 0; i < row.length; i++) {
|
|
26
|
+
if (row[i].width) res.push(row[i].width)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return res.join('')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const [ curr, setCurr ] = useState(getCurr())
|
|
33
|
+
const [ equalHeight, setEqualHeight ] = useState(false)
|
|
34
|
+
|
|
35
|
+
const setRowLayout = (layout) => {
|
|
36
|
+
const newRows = [ ...rows ]
|
|
37
|
+
const r = newRows[rowIdx]
|
|
38
|
+
|
|
39
|
+
for (let i = 0; i < r.length; i++) {
|
|
40
|
+
r[i].width = layout[i] ?? null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
updateConfig({ ...config, rows: newRows })
|
|
44
|
+
setCurr(layout.join(''))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const moveRow = (dir = 'down') => {
|
|
48
|
+
if (rowIdx === rows.length - 1 && dir === 'down') return
|
|
49
|
+
|
|
50
|
+
let newIdx = dir === 'down' ? rowIdx + 1 : rowIdx - 1
|
|
51
|
+
|
|
52
|
+
// Swap
|
|
53
|
+
const temp = rows[newIdx]
|
|
54
|
+
|
|
55
|
+
rows[newIdx] = row
|
|
56
|
+
rows[rowIdx] = temp
|
|
57
|
+
|
|
58
|
+
rows[newIdx].uuid = Date.now()
|
|
59
|
+
rows[rowIdx].uuid = Date.now()
|
|
60
|
+
|
|
61
|
+
updateConfig({ ...config, rows })
|
|
62
|
+
|
|
63
|
+
// TODO: Migrate this animation to a React animation library once one is selected for COVE. This is pretty minor so can stay for now.
|
|
64
|
+
let calcRowMove = dir === 'down' ? 202 : -202
|
|
65
|
+
let calcRowMove2 = dir === 'down' ? -202 : 202
|
|
66
|
+
|
|
67
|
+
let rowEle = document.querySelector('[data-row-id=\'' + rowIdx + '\']')
|
|
68
|
+
let rowNewEle = document.querySelector('[data-row-id=\'' + newIdx + '\']')
|
|
69
|
+
|
|
70
|
+
rowEle.style.pointerEvents = 'none'
|
|
71
|
+
rowNewEle.style.pointerEvents = 'none'
|
|
72
|
+
rowEle.style.top = calcRowMove + 'px'
|
|
73
|
+
rowNewEle.style.top = calcRowMove2 + 'px'
|
|
74
|
+
|
|
75
|
+
setTimeout(() => {
|
|
76
|
+
rowEle.style.transition = 'top 500ms cubic-bezier(0.16, 1, 0.3, 1)'
|
|
77
|
+
rowNewEle.style.transition = 'top 500ms cubic-bezier(0.16, 1, 0.3, 1)'
|
|
78
|
+
rowEle.style.top = '0'
|
|
79
|
+
rowNewEle.style.top = '0'
|
|
80
|
+
}, 0)
|
|
81
|
+
|
|
82
|
+
setTimeout(() => {
|
|
83
|
+
rowEle.style = null
|
|
84
|
+
rowNewEle.style = null
|
|
85
|
+
}, 500)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const deleteRow = () => {
|
|
89
|
+
rows.splice(rowIdx, 1) // Just delete the row. Don't delete the instantiated widgets for now.
|
|
90
|
+
|
|
91
|
+
updateConfig({ ...config, rows })
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const rowItemsHeight = () => {
|
|
95
|
+
setEqualHeight(!equalHeight)
|
|
96
|
+
|
|
97
|
+
row.equalHeight = !equalHeight
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const layoutList = [
|
|
101
|
+
<li className={curr === '12' ? `current row-menu__list--item` : `row-menu__list--item`}
|
|
102
|
+
onClick={() => setRowLayout([ 12 ])} key="12" title="1 Column">
|
|
103
|
+
<OneColIcon/>
|
|
104
|
+
</li>,
|
|
105
|
+
<li className={curr === '66' ? `current row-menu__list--item` : `row-menu__list--item`}
|
|
106
|
+
onClick={() => setRowLayout([ 6, 6 ])} key="66" title="2 Columns">
|
|
107
|
+
<TwoColIcon/>
|
|
108
|
+
</li>,
|
|
109
|
+
<li className={curr === '444' ? `current row-menu__list--item` : `row-menu__list--item`}
|
|
110
|
+
onClick={() => setRowLayout([ 4, 4, 4 ])} key="444" title="3 Columns">
|
|
111
|
+
<ThreeColIcon/>
|
|
112
|
+
</li>,
|
|
113
|
+
<li className={curr === '48' ? `current row-menu__list--item` : `row-menu__list--item`}
|
|
114
|
+
onClick={() => setRowLayout([ 4, 8 ])} key="48" title="2 Columns">
|
|
115
|
+
<FourEightColIcon/>
|
|
116
|
+
</li>,
|
|
117
|
+
<li className={curr === '84' ? `current row-menu__list--item` : `row-menu__list--item`}
|
|
118
|
+
onClick={() => setRowLayout([ 8, 4 ])} key="84" title="2 Columns">
|
|
119
|
+
<EightFourColIcon/>
|
|
120
|
+
</li>
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
const rowSettings = (
|
|
124
|
+
<Modal>
|
|
125
|
+
<Modal.Header>
|
|
126
|
+
Row Settings
|
|
127
|
+
</Modal.Header>
|
|
128
|
+
<Modal.Content>
|
|
129
|
+
<InputToggle
|
|
130
|
+
label="Visualizations in this row should be equal height"
|
|
131
|
+
fieldName={`toggleEqualHeight${rowIdx}`}
|
|
132
|
+
value={row.equalHeight ? row.equalHeight : false}
|
|
133
|
+
updateField={rowItemsHeight}
|
|
134
|
+
></InputToggle>
|
|
135
|
+
</Modal.Content>
|
|
136
|
+
</Modal>
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<nav className="row-menu">
|
|
141
|
+
<div className="row-menu__btn">
|
|
142
|
+
<ul className="row-menu__flyout">
|
|
143
|
+
{layoutList}
|
|
144
|
+
</ul>
|
|
145
|
+
</div>
|
|
146
|
+
<div className="spacer"></div>
|
|
147
|
+
{/*<button className={'row-menu__btn'} title="Row Settings"*/}
|
|
148
|
+
{/* onClick={() => overlay?.actions.openOverlay(rowSettings)}>*/}
|
|
149
|
+
{/* <Icon display="edit" color="#fff" size={25}/>*/}
|
|
150
|
+
{/*</button>*/}
|
|
151
|
+
<button className={rowIdx === 0 ? 'row-menu__btn row-menu__btn-disabled' : 'row-menu__btn'} title="Move Row Up"
|
|
152
|
+
onClick={() => moveRow('up')}>
|
|
153
|
+
<Icon display="caretUp" color="#fff" size={25}/>
|
|
154
|
+
</button>
|
|
155
|
+
<button className={rowIdx + 1 === rows.length ? 'row-menu__btn row-menu__btn-disabled' : 'row-menu__btn'}
|
|
156
|
+
title="Move Row Down" onClick={() => moveRow('down')}>
|
|
157
|
+
<Icon display="caretDown" color="#fff" size={25}/>
|
|
158
|
+
</button>
|
|
159
|
+
<button
|
|
160
|
+
className={rowIdx === 0 && rows.length === 1 ? 'row-menu__btn row-menu__btn--remove row-menu__btn-disabled' : 'row-menu__btn row-menu__btn--remove'}
|
|
161
|
+
title="Delete Row" onClick={deleteRow}>
|
|
162
|
+
<Icon display="close" color="#fff" size={25}/>
|
|
163
|
+
</button>
|
|
164
|
+
</nav>
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const Row = ({ row, idx: rowIdx, uuid }) => {
|
|
169
|
+
return (
|
|
170
|
+
<div className="builder-row" data-row-id={rowIdx}>
|
|
171
|
+
<RowMenu rowIdx={rowIdx} row={row}/>
|
|
172
|
+
<div className="column-container">
|
|
173
|
+
{row.filter(column => column.width).map((column, colIdx) => <Column data={column}
|
|
174
|
+
key={`row-${uuid}-col-${colIdx}`}
|
|
175
|
+
rowIdx={rowIdx} colIdx={colIdx}/>)}
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export default Row
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import React, { useContext, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import { useGlobalContext } from '@cdc/core/components/GlobalContext'
|
|
4
|
+
import ConfigContext from '../ConfigContext'
|
|
5
|
+
|
|
6
|
+
import Modal from '@cdc/core/components/ui/Modal'
|
|
7
|
+
import InputToggle from '@cdc/core/components/inputs/InputToggle'
|
|
8
|
+
import Icon from '@cdc/core/components/ui/Icon'
|
|
9
|
+
|
|
10
|
+
import Column from './Column'
|
|
11
|
+
<<<<<<< HEAD:packages/dashboard/src/components/Row.jsx
|
|
12
|
+
|
|
13
|
+
=======
|
|
14
|
+
import Context from '../context'
|
|
15
|
+
import CloseIcon from '../images/icon-close.svg'
|
|
16
|
+
import RowUp from '../images/icon-up.svg'
|
|
17
|
+
import RowDown from '../images/icon-down.svg'
|
|
18
|
+
>>>>>>> test:packages/dashboard/src/components/Row.js
|
|
19
|
+
import OneColIcon from '../images/icon-col-12.svg'
|
|
20
|
+
import TwoColIcon from '../images/icon-col-6.svg'
|
|
21
|
+
import ThreeColIcon from '../images/icon-col-4.svg'
|
|
22
|
+
import FourEightColIcon from '../images/icon-col-4-8.svg'
|
|
23
|
+
import EightFourColIcon from '../images/icon-col-8-4.svg'
|
|
24
|
+
|
|
25
|
+
const RowMenu = ({ rowIdx, row }) => {
|
|
26
|
+
<<<<<<< HEAD:packages/dashboard/src/components/Row.jsx
|
|
27
|
+
const { overlay } = useGlobalContext()
|
|
28
|
+
const { rows, config, updateConfig } = useContext(ConfigContext)
|
|
29
|
+
=======
|
|
30
|
+
const { rows, config, updateConfig } = useContext(Context)
|
|
31
|
+
>>>>>>> test:packages/dashboard/src/components/Row.js
|
|
32
|
+
|
|
33
|
+
const getCurr = () => {
|
|
34
|
+
let res = []
|
|
35
|
+
|
|
36
|
+
for (let i = 0; i < row.length; i++) {
|
|
37
|
+
if (row[i].width) res.push(row[i].width)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return res.join('')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
<<<<<<< HEAD:packages/dashboard/src/components/Row.jsx
|
|
44
|
+
const [ curr, setCurr ] = useState(getCurr())
|
|
45
|
+
const [ equalHeight, setEqualHeight ] = useState(false)
|
|
46
|
+
=======
|
|
47
|
+
const [curr, setCurr] = useState(getCurr())
|
|
48
|
+
>>>>>>> test:packages/dashboard/src/components/Row.js
|
|
49
|
+
|
|
50
|
+
const setRowLayout = (layout) => {
|
|
51
|
+
const newRows = [ ...rows ]
|
|
52
|
+
const r = newRows[rowIdx]
|
|
53
|
+
|
|
54
|
+
for (let i = 0; i < r.length; i++) {
|
|
55
|
+
r[i].width = layout[i] ?? null
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
updateConfig({ ...config, rows: newRows })
|
|
59
|
+
setCurr(layout.join(''))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const moveRow = (dir = 'down') => {
|
|
63
|
+
if (rowIdx === rows.length - 1 && dir === 'down') return
|
|
64
|
+
|
|
65
|
+
let newIdx = dir === 'down' ? rowIdx + 1 : rowIdx - 1
|
|
66
|
+
|
|
67
|
+
// Swap
|
|
68
|
+
const temp = rows[newIdx]
|
|
69
|
+
|
|
70
|
+
rows[newIdx] = row
|
|
71
|
+
rows[rowIdx] = temp
|
|
72
|
+
|
|
73
|
+
rows[newIdx].uuid = Date.now()
|
|
74
|
+
rows[rowIdx].uuid = Date.now()
|
|
75
|
+
|
|
76
|
+
updateConfig({ ...config, rows })
|
|
77
|
+
|
|
78
|
+
// TODO: Migrate this animation to a React animation library once one is selected for COVE. This is pretty minor so can stay for now.
|
|
79
|
+
let calcRowMove = dir === 'down' ? 202 : -202
|
|
80
|
+
let calcRowMove2 = dir === 'down' ? -202 : 202
|
|
81
|
+
|
|
82
|
+
let rowEle = document.querySelector('[data-row-id=\'' + rowIdx + '\']')
|
|
83
|
+
let rowNewEle = document.querySelector('[data-row-id=\'' + newIdx + '\']')
|
|
84
|
+
|
|
85
|
+
rowEle.style.pointerEvents = 'none'
|
|
86
|
+
rowNewEle.style.pointerEvents = 'none'
|
|
87
|
+
rowEle.style.top = calcRowMove + 'px'
|
|
88
|
+
rowNewEle.style.top = calcRowMove2 + 'px'
|
|
89
|
+
|
|
90
|
+
setTimeout(() => {
|
|
91
|
+
rowEle.style.transition = 'top 500ms cubic-bezier(0.16, 1, 0.3, 1)'
|
|
92
|
+
rowNewEle.style.transition = 'top 500ms cubic-bezier(0.16, 1, 0.3, 1)'
|
|
93
|
+
rowEle.style.top = '0'
|
|
94
|
+
rowNewEle.style.top = '0'
|
|
95
|
+
}, 0)
|
|
96
|
+
|
|
97
|
+
setTimeout(() => {
|
|
98
|
+
rowEle.style = null
|
|
99
|
+
rowNewEle.style = null
|
|
100
|
+
}, 500)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const deleteRow = () => {
|
|
104
|
+
rows.splice(rowIdx, 1) // Just delete the row. Don't delete the instantiated widgets for now.
|
|
105
|
+
|
|
106
|
+
updateConfig({ ...config, rows })
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
<<<<<<< HEAD:packages/dashboard/src/components/Row.jsx
|
|
110
|
+
const rowItemsHeight = () => {
|
|
111
|
+
console.log('hit')
|
|
112
|
+
setEqualHeight(!equalHeight)
|
|
113
|
+
|
|
114
|
+
row.equalHeight = !equalHeight
|
|
115
|
+
|
|
116
|
+
console.log('equalHeight var', equalHeight)
|
|
117
|
+
console.log('equalHeight', row.equalHeight)
|
|
118
|
+
}
|
|
119
|
+
console.log('equal height var', row.equalHeight)
|
|
120
|
+
|
|
121
|
+
=======
|
|
122
|
+
>>>>>>> test:packages/dashboard/src/components/Row.js
|
|
123
|
+
const layoutList = [
|
|
124
|
+
<li className={curr === '12' ? `current row-menu__list--item` : `row-menu__list--item`}
|
|
125
|
+
onClick={() => setRowLayout([ 12 ])} key="12" title="1 Column">
|
|
126
|
+
<OneColIcon/>
|
|
127
|
+
</li>,
|
|
128
|
+
<li className={curr === '66' ? `current row-menu__list--item` : `row-menu__list--item`}
|
|
129
|
+
onClick={() => setRowLayout([ 6, 6 ])} key="66" title="2 Columns">
|
|
130
|
+
<TwoColIcon/>
|
|
131
|
+
</li>,
|
|
132
|
+
<li className={curr === '444' ? `current row-menu__list--item` : `row-menu__list--item`}
|
|
133
|
+
onClick={() => setRowLayout([ 4, 4, 4 ])} key="444" title="3 Columns">
|
|
134
|
+
<ThreeColIcon/>
|
|
135
|
+
</li>,
|
|
136
|
+
<li className={curr === '48' ? `current row-menu__list--item` : `row-menu__list--item`}
|
|
137
|
+
onClick={() => setRowLayout([ 4, 8 ])} key="48" title="2 Columns">
|
|
138
|
+
<FourEightColIcon/>
|
|
139
|
+
</li>,
|
|
140
|
+
<li className={curr === '84' ? `current row-menu__list--item` : `row-menu__list--item`}
|
|
141
|
+
onClick={() => setRowLayout([ 8, 4 ])} key="84" title="2 Columns">
|
|
142
|
+
<EightFourColIcon/>
|
|
143
|
+
</li>
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
<<<<<<< HEAD:packages/dashboard/src/components/Row.jsx
|
|
147
|
+
const rowSettings = (
|
|
148
|
+
<Modal>
|
|
149
|
+
<Modal.Header>
|
|
150
|
+
Row Settings
|
|
151
|
+
</Modal.Header>
|
|
152
|
+
<Modal.Content>
|
|
153
|
+
<InputToggle
|
|
154
|
+
label="Visualizations in this row should be equal height"
|
|
155
|
+
fieldName={`toggleEqualHeight${rowIdx}`}
|
|
156
|
+
value={row.equalHeight ? row.equalHeight : false}
|
|
157
|
+
updateField={rowItemsHeight}
|
|
158
|
+
></InputToggle>
|
|
159
|
+
</Modal.Content>
|
|
160
|
+
</Modal>
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
=======
|
|
164
|
+
>>>>>>> test:packages/dashboard/src/components/Row.js
|
|
165
|
+
return (
|
|
166
|
+
<nav className="row-menu">
|
|
167
|
+
<div className="row-menu__btn">
|
|
168
|
+
<ul className="row-menu__flyout">
|
|
169
|
+
{layoutList}
|
|
170
|
+
</ul>
|
|
171
|
+
</div>
|
|
172
|
+
<div className="spacer"></div>
|
|
173
|
+
<<<<<<< HEAD:packages/dashboard/src/components/Row.jsx
|
|
174
|
+
<button className={'row-menu__btn'} title="Row Settings"
|
|
175
|
+
onClick={() => overlay?.actions.openOverlay(rowSettings)}>
|
|
176
|
+
<Icon display="edit" color="#fff" size={25}/>
|
|
177
|
+
</button>
|
|
178
|
+
<button className={rowIdx === 0 ? 'row-menu__btn row-menu__btn-disabled' : 'row-menu__btn'} title="Move Row Up"
|
|
179
|
+
onClick={() => moveRow('up')}>
|
|
180
|
+
<Icon display="caretUp" color="#fff" size={25}/>
|
|
181
|
+
=======
|
|
182
|
+
<button className={rowIdx === 0 ? 'row-menu__btn row-menu__btn-disabled' : 'row-menu__btn'} title="Move Row Up" onClick={() => moveRow('up')}>
|
|
183
|
+
<RowUp />
|
|
184
|
+
>>>>>>> test:packages/dashboard/src/components/Row.js
|
|
185
|
+
</button>
|
|
186
|
+
<button className={rowIdx + 1 === rows.length ? 'row-menu__btn row-menu__btn-disabled' : 'row-menu__btn'}
|
|
187
|
+
title="Move Row Down" onClick={() => moveRow('down')}>
|
|
188
|
+
<Icon display="caretDown" color="#fff" size={25}/>
|
|
189
|
+
</button>
|
|
190
|
+
<button
|
|
191
|
+
className={rowIdx === 0 && rows.length === 1 ? 'row-menu__btn row-menu__btn--remove row-menu__btn-disabled' : 'row-menu__btn row-menu__btn--remove'}
|
|
192
|
+
title="Delete Row" onClick={deleteRow}>
|
|
193
|
+
<Icon display="close" color="#fff" size={25}/>
|
|
194
|
+
</button>
|
|
195
|
+
</nav>
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const Row = ({ row, idx: rowIdx, uuid }) => {
|
|
200
|
+
return (
|
|
201
|
+
<div className="builder-row" data-row-id={rowIdx}>
|
|
202
|
+
<RowMenu rowIdx={rowIdx} row={row}/>
|
|
203
|
+
<div className="column-container">
|
|
204
|
+
{row.filter(column => column.width).map((column, colIdx) => <Column data={column}
|
|
205
|
+
key={`row-${uuid}-col-${colIdx}`}
|
|
206
|
+
rowIdx={rowIdx} colIdx={colIdx}/>)}
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export default Row
|