@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.
Files changed (36) hide show
  1. package/dist/cdcdashboard.js +159 -48
  2. package/examples/default-filter-control.json +175 -0
  3. package/examples/default-multi-dataset.json +498 -0
  4. package/examples/default.json +36 -348
  5. package/examples/private/chart-issue.json +3467 -0
  6. package/examples/private/no-issue.json +3467 -0
  7. package/examples/private/totals-two.json +104 -0
  8. package/examples/private/totals.json +103 -0
  9. package/examples/temp-example-data.json +130 -0
  10. package/examples/test-example.json +1 -0
  11. package/package.json +14 -8
  12. package/src/CdcDashboard.js +282 -156
  13. package/src/CdcDashboard.jsx +668 -0
  14. package/src/{context.tsx → ConfigContext.js} +0 -0
  15. package/src/components/{Column.js → Column.jsx} +9 -7
  16. package/src/components/DataTable.tsx +55 -54
  17. package/src/components/EditorPanel.js +207 -45
  18. package/src/components/{Grid.js → Grid.jsx} +5 -4
  19. package/src/components/Header.jsx +242 -0
  20. package/src/components/Row.js +1 -0
  21. package/src/components/Row.jsx +181 -0
  22. package/src/components/Row.jsx~HEAD +212 -0
  23. package/src/components/Widget.js +24 -5
  24. package/src/components/Widget.jsx +191 -0
  25. package/src/index.html +14 -11
  26. package/src/scss/editor-panel.scss +53 -49
  27. package/src/scss/grid.scss +61 -14
  28. package/src/scss/main.scss +73 -9
  29. package/LICENSE +0 -201
  30. package/src/components/Header.js +0 -15
  31. package/src/images/icon-close.svg +0 -1
  32. package/src/images/icon-down.svg +0 -1
  33. package/src/images/icon-edit.svg +0 -1
  34. package/src/images/icon-move.svg +0 -8
  35. package/src/images/icon-up.svg +0 -1
  36. 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>&#8592;</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
@@ -135,3 +135,4 @@ const Row = ({ row, idx: rowIdx, uuid}) => {
135
135
  }
136
136
 
137
137
  export default Row
138
+
@@ -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