@cdc/dashboard 1.1.4 → 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 +40 -28
- package/examples/default-filter-control.json +175 -0
- package/examples/default-multi-dataset.json +498 -0
- 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/package.json +9 -8
- package/src/CdcDashboard.js +16 -1
- 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 +6 -9
- package/src/components/EditorPanel.js +200 -44
- 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 +6 -2
- package/src/components/Widget.jsx +191 -0
- package/src/index.html +5 -4
- package/src/scss/editor-panel.scss +0 -1
- package/src/scss/grid.scss +61 -14
- package/src/scss/main.scss +67 -6
- package/src/components/Header.js +0 -15
- package/src/images/icon-close.svg +0 -1
- package/src/images/icon-code.svg +0 -3
- package/src/images/icon-down.svg +0 -1
- package/src/images/icon-edit.svg +0 -3
- package/src/images/icon-grid.svg +0 -4
- 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,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
|
package/src/components/Widget.js
CHANGED
|
@@ -12,6 +12,7 @@ import PieIcon from '@cdc/core/assets/chart-pie-solid.svg';
|
|
|
12
12
|
import UsaIcon from '@cdc/core/assets/usa-graphic.svg';
|
|
13
13
|
import WorldIcon from '@cdc/core/assets/world-graphic.svg';
|
|
14
14
|
import AlabamaIcon from '@cdc/core/assets/alabama-graphic.svg';
|
|
15
|
+
import FilteredText from '@cdc/core/assets/filtered-text.svg';
|
|
15
16
|
|
|
16
17
|
import Context from '../context';
|
|
17
18
|
|
|
@@ -26,7 +27,8 @@ const iconHash = {
|
|
|
26
27
|
'us' : <UsaIcon />,
|
|
27
28
|
'us-county': <UsaIcon />,
|
|
28
29
|
'world' : <WorldIcon />,
|
|
29
|
-
'single-state': <AlabamaIcon
|
|
30
|
+
'single-state': <AlabamaIcon />,
|
|
31
|
+
'filtered-text' : <FilteredText />,
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
const labelHash = {
|
|
@@ -40,7 +42,9 @@ const labelHash = {
|
|
|
40
42
|
'us': 'United States (State- or County-Level)',
|
|
41
43
|
'us-county': 'United States (State- or County-Level)',
|
|
42
44
|
'world' : 'World',
|
|
43
|
-
'single-state': 'U.S. State'
|
|
45
|
+
'single-state': 'U.S. State',
|
|
46
|
+
'filtered-text':'Filtered Text'
|
|
47
|
+
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
const Widget = ({ data = {}, addVisualization, type }) => {
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import React, { useContext, useRef, useEffect } from 'react'
|
|
2
|
+
import { useDrag } from 'react-dnd'
|
|
3
|
+
|
|
4
|
+
import { useGlobalContext } from '@cdc/core/components/GlobalContext'
|
|
5
|
+
import ConfigContext from '../ConfigContext'
|
|
6
|
+
|
|
7
|
+
import { DataTransform } from '@cdc/core/helpers/DataTransform'
|
|
8
|
+
import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
|
|
9
|
+
|
|
10
|
+
import DataDesigner from '@cdc/core/components/managers/DataDesigner'
|
|
11
|
+
import Icon from '@cdc/core/components/ui/Icon'
|
|
12
|
+
import Modal from '@cdc/core/components/ui/Modal'
|
|
13
|
+
|
|
14
|
+
const iconHash = {
|
|
15
|
+
'data-bite': <Icon display="databite" base/>,
|
|
16
|
+
'Bar': <Icon display="chartBar" base/>,
|
|
17
|
+
'waffle-chart': <Icon display="grid" base/>,
|
|
18
|
+
'markup-include': <Icon display="code" base/>,
|
|
19
|
+
'Line': <Icon display="chartLine" base/>,
|
|
20
|
+
'Pie': <Icon display="chartPie" base/>,
|
|
21
|
+
'us': <Icon display="mapUsa" base/>,
|
|
22
|
+
'us-county': <Icon display="mapUsa" base/>,
|
|
23
|
+
'world': <Icon display="mapWorld" base/>,
|
|
24
|
+
'single-state': <Icon display="mapAl" base/>,
|
|
25
|
+
'gear': <Icon display="gear" base/>,
|
|
26
|
+
'tools': <Icon display="tools" base/>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const labelHash = {
|
|
30
|
+
'data-bite': 'Data Bite',
|
|
31
|
+
'waffle-chart': 'Waffle Chart',
|
|
32
|
+
'markup-include': 'Markup Include',
|
|
33
|
+
'Bar': 'Bar',
|
|
34
|
+
'Line': 'Line',
|
|
35
|
+
'Pie': 'Pie',
|
|
36
|
+
'us': 'United States (State- or County-Level)',
|
|
37
|
+
'us-county': 'United States (State- or County-Level)',
|
|
38
|
+
'world': 'World',
|
|
39
|
+
'single-state': 'U.S. State'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const Widget = ({ data = {}, addVisualization, type }) => {
|
|
43
|
+
const { overlay } = useGlobalContext()
|
|
44
|
+
const { rows, visualizations, config, updateConfig } = useContext(ConfigContext)
|
|
45
|
+
|
|
46
|
+
const dataRef = useRef();
|
|
47
|
+
dataRef.current = data;
|
|
48
|
+
|
|
49
|
+
const transform = new DataTransform()
|
|
50
|
+
|
|
51
|
+
const handleWidgetMove = (item, monitor) => {
|
|
52
|
+
let result = monitor.getDropResult()
|
|
53
|
+
|
|
54
|
+
if (!result) return null
|
|
55
|
+
|
|
56
|
+
const { rowIdx, colIdx } = result
|
|
57
|
+
|
|
58
|
+
if (undefined !== data.rowIdx) {
|
|
59
|
+
rows[data.rowIdx][data.colIdx].widget = null // Wipe from old position
|
|
60
|
+
|
|
61
|
+
rows[rowIdx][colIdx].widget = data.uid // Add to new row and col
|
|
62
|
+
} else {
|
|
63
|
+
// Item does not exist, instantiate a new one
|
|
64
|
+
const newViz = addVisualization()
|
|
65
|
+
visualizations[newViz.uid] = newViz // Add to widgets collection
|
|
66
|
+
rows[rowIdx][colIdx].widget = newViz.uid // Store reference in rows collection under the specific column
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
updateConfig({ ...config, rows, visualizations })
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const [ { isDragging, ...collected }, drag ] = useDrag({
|
|
73
|
+
type: 'vis-widget',
|
|
74
|
+
end: handleWidgetMove,
|
|
75
|
+
collect: (monitor) => ({
|
|
76
|
+
isDragging: monitor.isDragging()
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
const deleteWidget = () => {
|
|
81
|
+
rows[data.rowIdx][data.colIdx].widget = null
|
|
82
|
+
|
|
83
|
+
delete visualizations[data.uid]
|
|
84
|
+
|
|
85
|
+
updateConfig({ ...config, rows, visualizations })
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const editWidget = () => {
|
|
89
|
+
visualizations[data.uid].editing = true
|
|
90
|
+
|
|
91
|
+
updateConfig({ ...config, visualizations })
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const changeDataset = (uid, value) => {
|
|
95
|
+
delete visualizations[uid].dataDescription
|
|
96
|
+
delete visualizations[uid].formattedData
|
|
97
|
+
|
|
98
|
+
visualizations[uid].dataKey = value
|
|
99
|
+
|
|
100
|
+
updateConfig({ ...config, visualizations })
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const updateDescriptionProp = async (visualizationKey, datasetKey, key, value) => {
|
|
104
|
+
let dataDescription = { ...dataRef.current.dataDescription, [key]: value }
|
|
105
|
+
|
|
106
|
+
let newData
|
|
107
|
+
if (!config.datasets[datasetKey].data && config.datasets[datasetKey].dataUrl) {
|
|
108
|
+
newData = await fetchRemoteData(config.datasets[datasetKey].dataUrl)
|
|
109
|
+
newData = transform.autoStandardize(newData)
|
|
110
|
+
} else {
|
|
111
|
+
newData = config.datasets[datasetKey].data
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let formattedData = transform.developerStandardize(newData, dataDescription)
|
|
115
|
+
|
|
116
|
+
let newVisualizations = { ...config.visualizations }
|
|
117
|
+
newVisualizations[visualizationKey] = { ...newVisualizations[visualizationKey], data: newData, dataDescription, formattedData }
|
|
118
|
+
|
|
119
|
+
updateConfig({ ...config, visualizations: newVisualizations })
|
|
120
|
+
|
|
121
|
+
overlay?.actions.openOverlay(dataDesignerModal(newVisualizations[visualizationKey]))
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const dataDesignerModal = (configureData, dataKeyOverride) => {
|
|
125
|
+
const dataKey = !dataKeyOverride && dataKeyOverride !== '' ? (data.dataKey || dataRef.current.dataKey) : dataKeyOverride;
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<Modal>
|
|
129
|
+
<Modal.Content>
|
|
130
|
+
<div className="dataset-selector-container">
|
|
131
|
+
Select a dataset:
|
|
132
|
+
<select className="dataset-selector" defaultValue={dataKey} onChange={(e) => {
|
|
133
|
+
changeDataset(data.uid, e.target.value)
|
|
134
|
+
overlay?.actions.openOverlay(dataDesignerModal(data, e.target.value || ''))
|
|
135
|
+
}}>
|
|
136
|
+
<option value="">Select a dataset</option>
|
|
137
|
+
{config.datasets && Object.keys(config.datasets).map(datasetKey => (
|
|
138
|
+
<option key={datasetKey}>{datasetKey}</option>
|
|
139
|
+
))}
|
|
140
|
+
</select>
|
|
141
|
+
</div>
|
|
142
|
+
{dataKey && <DataDesigner {...{
|
|
143
|
+
configureData,
|
|
144
|
+
visualizationKey: data.uid,
|
|
145
|
+
dataKey: dataKey,
|
|
146
|
+
updateDescriptionProp
|
|
147
|
+
}}/>}
|
|
148
|
+
{configureData.formattedData && <button style={{margin: '1em'}} className="cove-button" onClick={() => overlay?.actions.toggleOverlay()}>Continue</button>}
|
|
149
|
+
</Modal.Content>
|
|
150
|
+
</Modal>
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
if(data.openModal){
|
|
156
|
+
overlay?.actions.openOverlay(dataDesignerModal(dataRef.current))
|
|
157
|
+
|
|
158
|
+
visualizations[data.uid].openModal = false
|
|
159
|
+
|
|
160
|
+
updateConfig({ ...config, visualizations })
|
|
161
|
+
}
|
|
162
|
+
}, [data.openModal])
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<>
|
|
166
|
+
<div className="widget" ref={drag} style={{ opacity: isDragging ? 0.5 : 1 }} {...collected}>
|
|
167
|
+
<Icon display="move" className="drag-icon"/>
|
|
168
|
+
<div className="widget__content">
|
|
169
|
+
{data.rowIdx !== undefined && (
|
|
170
|
+
<div className="widget-menu">
|
|
171
|
+
{data.dataKey && data.dataDescription && data.formattedData &&
|
|
172
|
+
<button title="Configure Visualization" className="btn btn-configure" onClick={editWidget}>{iconHash['tools']}</button>
|
|
173
|
+
}
|
|
174
|
+
<button title="Configure Data" className="btn btn-configure" onClick={() => {
|
|
175
|
+
overlay?.actions.openOverlay(dataDesignerModal(data))
|
|
176
|
+
}}>{iconHash['gear']}</button>
|
|
177
|
+
<div className="widget-menu-item" onClick={deleteWidget}>
|
|
178
|
+
<Icon display="close" base/>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
)}
|
|
182
|
+
{iconHash[type]}
|
|
183
|
+
<span>{labelHash[type]}</span>
|
|
184
|
+
{data.newViz && <span onClick={editWidget} className="config-needed">Configuration needed</span>}
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
</>
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export default Widget
|
package/src/index.html
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
<!DOCTYPE html>
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8" />
|
|
@@ -20,8 +20,9 @@
|
|
|
20
20
|
</style>
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
<
|
|
23
|
+
<!--<div class="react-container" data-config="/examples/default.json"></div>-->
|
|
24
|
+
<!--<div class="react-container" data-config="/examples/default-multi-dataset.json"></div>-->
|
|
25
|
+
<div class="react-container" data-config="/examples/default-filter-control.json"></div>
|
|
26
|
+
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
26
27
|
</body>
|
|
27
28
|
</html>
|