@cdc/markup-include 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/dist/cdcmarkupinclude.js +8997 -4945
- package/index.html +6 -5
- package/package.json +3 -3
- package/src/CdcMarkupInclude.tsx +92 -36
- package/src/ConfigContext.ts +16 -0
- package/src/_stories/MarkupInclude.stories.tsx +44 -5
- package/src/_stories/_mock/button-and-text.json +69 -0
- package/src/_stories/_mock/icon-no-text.json +59 -0
- package/src/_stories/_mock/image-with-text.json +59 -0
- package/src/_stories/_mock/no-conditions.json +61 -0
- package/src/_stories/_mock/primary.json +23 -0
- package/src/_stories/_mock/with-conditions.json +49 -0
- package/src/cdcMarkupInclude.style.css +39 -0
- package/src/components/Conditions.tsx +99 -0
- package/src/components/EditorPanel.tsx +201 -0
- package/src/components/Variables.tsx +171 -0
- package/src/components/editorPanel.style.css +17 -0
- package/src/data/initial-state.js +13 -4
- package/src/scss/main.scss +30 -2
- package/src/store/{mi.actions.ts → markupInclude.actions.ts} +2 -2
- package/src/store/{mi.reducer.ts → markupInclude.reducer.ts} +3 -3
- package/src/types/Condition.ts +5 -0
- package/src/types/Config.ts +18 -6
- package/src/types/Variable.ts +7 -0
- package/examples/example-config.json +0 -7
- package/examples/gallery/button-and-text.json +0 -31
- package/examples/gallery/icon-no-text.json +0 -31
- package/examples/gallery/image-with-text.json +0 -31
- package/src/ConfigContext.jsx +0 -5
- package/src/components/EditorPanel.jsx +0 -167
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"contentEditor": {
|
|
3
|
+
"inlineHTML": "<div>{{state}} does have a rate 130 compared to the over all rate of {{overall-rate}}</div>",
|
|
4
|
+
"markupVariables": [
|
|
5
|
+
{
|
|
6
|
+
"name": "state",
|
|
7
|
+
"tag": "{{state}}",
|
|
8
|
+
"columnName": "STATE",
|
|
9
|
+
"conditions": [{ "columnName": "Rate", "isOrIsNotEqualTo": "is", "value": "130" }]
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"name": "overall-rate",
|
|
13
|
+
"tag": "{{overall-rate}}",
|
|
14
|
+
"columnName": "Rate",
|
|
15
|
+
"conditions": [{ "columnName": "STATE", "isOrIsNotEqualTo": "is", "value": "Overall" }]
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"showHeader": true,
|
|
19
|
+
"srcUrl": "#example",
|
|
20
|
+
"title": "",
|
|
21
|
+
"useInlineHTML": true
|
|
22
|
+
},
|
|
23
|
+
"data": [
|
|
24
|
+
{
|
|
25
|
+
"STATE": "Overall",
|
|
26
|
+
"Rate": "80",
|
|
27
|
+
"Location": "Vehicle",
|
|
28
|
+
"URL": "https://www.cdc.gov/"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"STATE": "Alabama",
|
|
32
|
+
"Rate": "130",
|
|
33
|
+
"Location": "Vehicle",
|
|
34
|
+
"URL": "https://www.cdc.gov/"
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"legend": {},
|
|
38
|
+
"newViz": true,
|
|
39
|
+
"theme": "theme-amber",
|
|
40
|
+
"type": "markup-include",
|
|
41
|
+
"runtime": null,
|
|
42
|
+
"visual": {
|
|
43
|
+
"border": false,
|
|
44
|
+
"accent": true,
|
|
45
|
+
"background": true,
|
|
46
|
+
"hideBackgroundColor": false,
|
|
47
|
+
"borderColorTheme": false
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
.cdc-open-viz-module.markup-include {
|
|
2
|
+
.spacing-wrapper {
|
|
3
|
+
background-color: var(--lightGray) !important;
|
|
4
|
+
}
|
|
5
|
+
.cove-tooltip-variable {
|
|
6
|
+
display: none;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.markup-include-content-container.cove-component__content {
|
|
10
|
+
.markup-include-component.cove-component__content:not(.component--hideBackgroundColor, .component--has-background) {
|
|
11
|
+
background-color: white;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.cdc-open-viz-module.markup-include.isEditor {
|
|
17
|
+
.cove-tooltip-variable {
|
|
18
|
+
position: relative;
|
|
19
|
+
display: inline;
|
|
20
|
+
background: var(--orange-tertiary);
|
|
21
|
+
.cove-tooltip-value {
|
|
22
|
+
display: none;
|
|
23
|
+
position: absolute;
|
|
24
|
+
background: var(--white);
|
|
25
|
+
border: 1px solid black;
|
|
26
|
+
font-size: 16px;
|
|
27
|
+
padding: 10px;
|
|
28
|
+
width: 100vw;
|
|
29
|
+
max-width: 500px;
|
|
30
|
+
z-index: 9999;
|
|
31
|
+
}
|
|
32
|
+
&:hover > .cove-tooltip-value {
|
|
33
|
+
display: block;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
.cove-markup-include-variable-value {
|
|
37
|
+
display: none !important;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
import { Condition } from '../types/Condition'
|
|
3
|
+
import Icon from '@cdc/core/components/ui/Icon'
|
|
4
|
+
|
|
5
|
+
type OpenControls = [boolean[], Function] // useState type
|
|
6
|
+
|
|
7
|
+
type CondtionsProps = {
|
|
8
|
+
conditionControls: OpenControls
|
|
9
|
+
conditionLookup: Record<string, string[] | number[]>
|
|
10
|
+
conditionSettings: Condition
|
|
11
|
+
conditionIndex: number
|
|
12
|
+
removeCondition: Function
|
|
13
|
+
selectedColumn: string
|
|
14
|
+
updateConditionsList: Function
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const Conditions: React.FC<CondtionsProps> = ({ conditionControls, conditionLookup, conditionSettings, conditionIndex, removeCondition, selectedColumn, updateConditionsList }) => {
|
|
18
|
+
const [openConditionControls, setOpenConditionControls] = conditionControls
|
|
19
|
+
const showCondition = openConditionControls[conditionIndex]
|
|
20
|
+
const setShowCondition = (index, value) => {
|
|
21
|
+
const newOpenConditionsControls = [...openConditionControls]
|
|
22
|
+
newOpenConditionsControls[index] = value
|
|
23
|
+
setOpenConditionControls(newOpenConditionsControls)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const columnNames = Object.keys(conditionLookup)
|
|
27
|
+
const columnNameConditionOptions = columnNames.filter(optionName => optionName !== selectedColumn)
|
|
28
|
+
|
|
29
|
+
const { columnName, isOrIsNotEqualTo, value } = conditionSettings
|
|
30
|
+
|
|
31
|
+
const handleConditionChange = (selectionValue: string | number, conditionSetting: string) => {
|
|
32
|
+
const conditionSettingUpdate = _.cloneDeep(conditionSettings)
|
|
33
|
+
if (conditionSetting === 'columnName') {
|
|
34
|
+
conditionSettingUpdate['value'] = ''
|
|
35
|
+
}
|
|
36
|
+
conditionSettingUpdate[conditionSetting] = selectionValue
|
|
37
|
+
updateConditionsList(conditionSettingUpdate, conditionIndex)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return !showCondition ? (
|
|
41
|
+
<>
|
|
42
|
+
<div className='mb-1'>
|
|
43
|
+
<button onClick={() => setShowCondition(conditionIndex, true)}>
|
|
44
|
+
<Icon display='caretDown' />
|
|
45
|
+
</button>
|
|
46
|
+
<span> {value ? `${columnName} ${isOrIsNotEqualTo === 'is' ? 'is' : 'is not'} ${value}` : 'New Condition'}</span>
|
|
47
|
+
</div>
|
|
48
|
+
</>
|
|
49
|
+
) : (
|
|
50
|
+
<>
|
|
51
|
+
<div className='d-flex justify-content-between'>
|
|
52
|
+
<button
|
|
53
|
+
onClick={() => {
|
|
54
|
+
const newOpenConditionsControls = [...openConditionControls]
|
|
55
|
+
newOpenConditionsControls[conditionIndex] = false
|
|
56
|
+
setOpenConditionControls(newOpenConditionsControls)
|
|
57
|
+
}}
|
|
58
|
+
>
|
|
59
|
+
<Icon display='caretDown' />
|
|
60
|
+
</button>
|
|
61
|
+
<button className='btn btn-danger btn-sm mt-0 ml-2' onClick={() => removeCondition(conditionIndex)}>
|
|
62
|
+
Remove
|
|
63
|
+
</button>
|
|
64
|
+
</div>
|
|
65
|
+
<div id={`condition_${conditionIndex}`}>
|
|
66
|
+
<label className='d-block'>
|
|
67
|
+
<span>Condition : </span>
|
|
68
|
+
<div className='pt-1'>
|
|
69
|
+
<select className='ml-1' value={columnName} onChange={e => handleConditionChange(e.target.value, 'columnName')}>
|
|
70
|
+
<option value=''>Select</option>
|
|
71
|
+
{columnNameConditionOptions?.map(columnName => (
|
|
72
|
+
<option key={columnName} value={columnName}>
|
|
73
|
+
{columnName}
|
|
74
|
+
</option>
|
|
75
|
+
))}
|
|
76
|
+
</select>
|
|
77
|
+
<select className='ml-1' value={isOrIsNotEqualTo} onChange={e => handleConditionChange(e.target.value, 'isOrIsNotEqualTo')}>
|
|
78
|
+
<option value='is'>is</option>
|
|
79
|
+
<option value='isNot'>is not</option>
|
|
80
|
+
</select>
|
|
81
|
+
<select className='ml-1' value={value} onChange={e => handleConditionChange(e.target.value, 'value')}>
|
|
82
|
+
<option value=''>Select</option>
|
|
83
|
+
|
|
84
|
+
{conditionLookup[columnName]?.map(valueItem => {
|
|
85
|
+
return (
|
|
86
|
+
<option key={valueItem} value={valueItem}>
|
|
87
|
+
{valueItem}
|
|
88
|
+
</option>
|
|
89
|
+
)
|
|
90
|
+
})}
|
|
91
|
+
</select>
|
|
92
|
+
</div>
|
|
93
|
+
</label>
|
|
94
|
+
</div>
|
|
95
|
+
</>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export default Conditions
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import React, { useState, useEffect, memo, useContext, useRef, useMemo, useReducer } from 'react'
|
|
2
|
+
|
|
3
|
+
// Third Party
|
|
4
|
+
import _ from 'lodash'
|
|
5
|
+
|
|
6
|
+
// Context
|
|
7
|
+
import { Variable } from '../types/Variable'
|
|
8
|
+
import ConfigContext from '../ConfigContext'
|
|
9
|
+
|
|
10
|
+
// Helpers
|
|
11
|
+
import { updateFieldFactory } from '@cdc/core/helpers/updateFieldFactory'
|
|
12
|
+
|
|
13
|
+
// Components
|
|
14
|
+
import InputCheckbox from '@cdc/core/components/inputs/InputCheckbox'
|
|
15
|
+
import ErrorBoundary from '@cdc/core/components/ErrorBoundary'
|
|
16
|
+
import Icon from '@cdc/core/components/ui/Icon'
|
|
17
|
+
import InputText from '@cdc/core/components/inputs/InputText'
|
|
18
|
+
import Layout from '@cdc/core/components/Layout'
|
|
19
|
+
import Tooltip from '@cdc/core/components/ui/Tooltip'
|
|
20
|
+
import Accordion from '@cdc/core/components/ui/Accordion'
|
|
21
|
+
|
|
22
|
+
// styles
|
|
23
|
+
import '@cdc/core/styles/v2/components/editor.scss'
|
|
24
|
+
import './editorPanel.style.css'
|
|
25
|
+
import VariableSection from './Variables'
|
|
26
|
+
|
|
27
|
+
const headerColors = ['theme-blue', 'theme-purple', 'theme-brown', 'theme-teal', 'theme-pink', 'theme-orange', 'theme-slate', 'theme-indigo', 'theme-cyan', 'theme-green', 'theme-amber']
|
|
28
|
+
|
|
29
|
+
const EditorPanel: React.FC = () => {
|
|
30
|
+
const { config, data, isDashboard, loading, setParentConfig, updateConfig } = useContext(ConfigContext)
|
|
31
|
+
const { contentEditor, theme, visual } = config
|
|
32
|
+
const { inlineHTML, markupVariables, srcUrl, title, useInlineHTML } = contentEditor
|
|
33
|
+
const [displayPanel, setDisplayPanel] = useState(true)
|
|
34
|
+
const updateField = updateFieldFactory(config, updateConfig, true)
|
|
35
|
+
const hasData = data?.[0] !== undefined ?? false
|
|
36
|
+
|
|
37
|
+
const openVariableControls = useState<boolean[]>([])
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
// Pass up to Editor if needed
|
|
41
|
+
if (setParentConfig) {
|
|
42
|
+
const newConfig = convertStateToConfig()
|
|
43
|
+
|
|
44
|
+
setParentConfig(newConfig)
|
|
45
|
+
}
|
|
46
|
+
}, [config])
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
const newConfig = { ...config }
|
|
50
|
+
delete newConfig.newViz
|
|
51
|
+
updateConfig(newConfig)
|
|
52
|
+
}, [])
|
|
53
|
+
|
|
54
|
+
const onBackClick = () => {
|
|
55
|
+
setDisplayPanel(!displayPanel)
|
|
56
|
+
updateConfig({
|
|
57
|
+
...config,
|
|
58
|
+
showEditorPanel: !displayPanel
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const convertStateToConfig = () => {
|
|
63
|
+
const strippedState = JSON.parse(JSON.stringify(config))
|
|
64
|
+
delete strippedState.newViz
|
|
65
|
+
delete strippedState.runtime
|
|
66
|
+
|
|
67
|
+
return strippedState
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const [variableArray, setVariableArray] = useState<Variable[]>([...markupVariables])
|
|
71
|
+
const [isCreatingVariable, setIsCreatingVariable] = useState(false)
|
|
72
|
+
|
|
73
|
+
const textAreaInEditorContainer = useRef(null)
|
|
74
|
+
const [controls, setControls] = openVariableControls
|
|
75
|
+
|
|
76
|
+
const handleCreateNewVariableButtonClick = () => {
|
|
77
|
+
const newVariableArray = [..._.cloneDeep(variableArray)]
|
|
78
|
+
const newVariable = {
|
|
79
|
+
columnName: '',
|
|
80
|
+
conditions: [],
|
|
81
|
+
name: '',
|
|
82
|
+
tag: ''
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
setControls({ ...controls, [variableArray.length + 1]: true })
|
|
86
|
+
|
|
87
|
+
newVariableArray.push(newVariable)
|
|
88
|
+
setVariableArray(newVariableArray)
|
|
89
|
+
setIsCreatingVariable(!isCreatingVariable)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const updateVariableArray = (newVariable: Variable, variableIndex: number) => {
|
|
93
|
+
const newVariableArray = _.cloneDeep(variableArray)
|
|
94
|
+
newVariableArray[variableIndex] = newVariable
|
|
95
|
+
setVariableArray(newVariableArray)
|
|
96
|
+
updateField('contentEditor', null, 'markupVariables', newVariableArray)
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const deleteVariable = (variableIndex: number) => {
|
|
101
|
+
const newVariableArray = _.cloneDeep(variableArray)
|
|
102
|
+
newVariableArray.splice(variableIndex, 1)
|
|
103
|
+
setVariableArray(newVariableArray)
|
|
104
|
+
updateField('contentEditor', null, 'markupVariables', newVariableArray)
|
|
105
|
+
|
|
106
|
+
const newControls = _.cloneDeep(controls)
|
|
107
|
+
delete newControls[variableIndex]
|
|
108
|
+
setControls(newControls)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const editorContent = (
|
|
112
|
+
<Accordion>
|
|
113
|
+
<Accordion.Section title='General'>
|
|
114
|
+
<InputText value={title || ''} section='contentEditor' fieldName='title' label='Title' placeholder='Markup Include Title' updateField={updateField} />
|
|
115
|
+
</Accordion.Section>
|
|
116
|
+
<Accordion.Section title='Content Editor'>
|
|
117
|
+
<span className='divider-heading'>Enter Markup</span>
|
|
118
|
+
<InputCheckbox inline value={useInlineHTML} section='contentEditor' fieldName='useInlineHTML' label='Use Inline HTML ' updateField={updateField} />
|
|
119
|
+
<div className='column-edit'>
|
|
120
|
+
{useInlineHTML ? (
|
|
121
|
+
<>
|
|
122
|
+
{/* HTML Textbox */}
|
|
123
|
+
<div ref={textAreaInEditorContainer}>
|
|
124
|
+
<InputText value={inlineHTML} section='contentEditor' fieldName='inlineHTML' label='HTML' placeholder='Add HTML here' type='textarea' rows={10} updateField={updateField} />
|
|
125
|
+
|
|
126
|
+
<hr className='accordion__divider' />
|
|
127
|
+
</div>
|
|
128
|
+
{/* Create New Variable*/}
|
|
129
|
+
|
|
130
|
+
{/* Variable Options List */}
|
|
131
|
+
<fieldset>
|
|
132
|
+
<label>
|
|
133
|
+
<span className='edit-label'>
|
|
134
|
+
Variables
|
|
135
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
136
|
+
<Tooltip.Target>
|
|
137
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
138
|
+
</Tooltip.Target>
|
|
139
|
+
<Tooltip.Content>{`To use created variables wrap the variable name in curly brackets, e.g. {{some_variable}}, and place the variable directly in your Inline HTML`}</Tooltip.Content>
|
|
140
|
+
</Tooltip>
|
|
141
|
+
</span>
|
|
142
|
+
</label>
|
|
143
|
+
{hasData === false && <span className='need-data-source-prompt'>To use variables, add data source.</span>}
|
|
144
|
+
{variableArray && variableArray.length > 0 && (
|
|
145
|
+
<div className='section-border'>
|
|
146
|
+
{variableArray?.map((variableObject, index) => {
|
|
147
|
+
return <VariableSection key={`${variableObject.name}-${index}`} controls={openVariableControls} data={data} deleteVariable={deleteVariable} updateVariableArray={updateVariableArray} variableConfig={variableObject} variableIndex={index} />
|
|
148
|
+
})}
|
|
149
|
+
</div>
|
|
150
|
+
)}
|
|
151
|
+
<div className='mb-1 d-flex'>
|
|
152
|
+
<button className={'btn btn-primary'} onClick={handleCreateNewVariableButtonClick} disabled={!hasData}>
|
|
153
|
+
Create New Variable
|
|
154
|
+
</button>
|
|
155
|
+
</div>
|
|
156
|
+
</fieldset>
|
|
157
|
+
</>
|
|
158
|
+
) : (
|
|
159
|
+
<InputText value={srcUrl || ''} section='contentEditor' fieldName='srcUrl' label='Source URL;' placeholder='https://www.example.com/file.html' updateField={updateField} />
|
|
160
|
+
)}
|
|
161
|
+
</div>
|
|
162
|
+
</Accordion.Section>
|
|
163
|
+
<Accordion.Section title='Visual'>
|
|
164
|
+
<div className='input-group'>
|
|
165
|
+
<label>Theme</label>
|
|
166
|
+
<ul className='color-palette'>
|
|
167
|
+
{headerColors.map(palette => (
|
|
168
|
+
<li
|
|
169
|
+
title={palette}
|
|
170
|
+
key={palette}
|
|
171
|
+
onClick={() => {
|
|
172
|
+
updateConfig({ ...config, theme: palette })
|
|
173
|
+
}}
|
|
174
|
+
className={theme === palette ? 'selected ' + palette : palette}
|
|
175
|
+
></li>
|
|
176
|
+
))}
|
|
177
|
+
</ul>
|
|
178
|
+
</div>
|
|
179
|
+
<div className='cove-accordion__panel-section checkbox-group'>
|
|
180
|
+
<InputCheckbox value={visual.border} section='visual' fieldName='border' label='Display Border ' updateField={updateField} />
|
|
181
|
+
<InputCheckbox value={visual.borderColorTheme} section='visual' fieldName='borderColorTheme' label='Use Border Color Theme ' updateField={updateField} />
|
|
182
|
+
<InputCheckbox value={visual.accent} section='visual' fieldName='accent' label='Use Accent Style ' updateField={updateField} />
|
|
183
|
+
<InputCheckbox value={visual.background} section='visual' fieldName='background' label='Use Theme Background Color ' updateField={updateField} />
|
|
184
|
+
<InputCheckbox value={visual.hideBackgroundColor} section='visual' fieldName='hideBackgroundColor' label='Hide Background Color ' updateField={updateField} />
|
|
185
|
+
</div>
|
|
186
|
+
</Accordion.Section>
|
|
187
|
+
</Accordion>
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
if (loading) return null
|
|
191
|
+
|
|
192
|
+
return (
|
|
193
|
+
<ErrorBoundary component='EditorPanel'>
|
|
194
|
+
<Layout.Sidebar displayPanel={displayPanel} isDashboard={isDashboard} title={'Configure Markup Include'} onBackClick={onBackClick}>
|
|
195
|
+
{editorContent}
|
|
196
|
+
</Layout.Sidebar>
|
|
197
|
+
</ErrorBoundary>
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export default EditorPanel
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { useMemo, useState } from 'react'
|
|
2
|
+
import Conditions from './Conditions'
|
|
3
|
+
import { Variable } from '../types/Variable'
|
|
4
|
+
import { Condition } from '../types/Condition'
|
|
5
|
+
import _ from 'lodash'
|
|
6
|
+
import Icon from '@cdc/core/components/ui/Icon'
|
|
7
|
+
|
|
8
|
+
type OpenControls = [boolean[], Function] // useState type
|
|
9
|
+
|
|
10
|
+
type VariableSectionProps = {
|
|
11
|
+
controls: OpenControls
|
|
12
|
+
data: Object[]
|
|
13
|
+
deleteVariable: Function
|
|
14
|
+
updateVariableArray: Function
|
|
15
|
+
variableConfig: Variable
|
|
16
|
+
variableIndex: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const VariableSection: React.FC<VariableSectionProps> = ({ controls, data, deleteVariable, updateVariableArray, variableConfig, variableIndex }) => {
|
|
20
|
+
const [openVariableControls, setOpenVariableControls] = controls
|
|
21
|
+
const show = openVariableControls[variableIndex]
|
|
22
|
+
const setShow = (key, value) => {
|
|
23
|
+
setOpenVariableControls({ openVariableControls, [key]: value })
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const openConditionControls = useState([])
|
|
27
|
+
|
|
28
|
+
const columnNames = Object.keys(data?.[0] || {})
|
|
29
|
+
const [selectedColumn, setNewVariableColumnName] = useState(variableConfig.columnName)
|
|
30
|
+
const [conditionsList, setConditionsList] = useState(variableConfig.conditions)
|
|
31
|
+
const [variableName, setVariableName] = useState(variableConfig.name)
|
|
32
|
+
|
|
33
|
+
const conditionLookup: Record<string, string[] | number[]> = useMemo(() => {
|
|
34
|
+
return columnNames.reduce((acc, column) => {
|
|
35
|
+
acc[column] = _.uniq(data.map(row => row[column]))
|
|
36
|
+
return acc
|
|
37
|
+
}, {})
|
|
38
|
+
}, [columnNames])
|
|
39
|
+
|
|
40
|
+
const handleVariableColumnChange = (columnName: string) => {
|
|
41
|
+
setNewVariableColumnName(columnName)
|
|
42
|
+
setConditionsList([])
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const updateConditionsList = (conditionSettings: Condition, conditionIndex: number) => {
|
|
46
|
+
const { columnName, isOrIsNotEqualTo, value } = conditionSettings
|
|
47
|
+
const newConditionsList = _.cloneDeep(conditionsList)
|
|
48
|
+
newConditionsList[conditionIndex] = {
|
|
49
|
+
columnName: columnName,
|
|
50
|
+
isOrIsNotEqualTo: isOrIsNotEqualTo,
|
|
51
|
+
value: value
|
|
52
|
+
}
|
|
53
|
+
setConditionsList(newConditionsList)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const removeCondition = (conditionIndex: number) => {
|
|
57
|
+
const updatedConditionsList = _.cloneDeep(conditionsList)
|
|
58
|
+
updatedConditionsList.splice(conditionIndex, 1)
|
|
59
|
+
setConditionsList(updatedConditionsList)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const handleAddConditionClick = () => {
|
|
63
|
+
setConditionsList([
|
|
64
|
+
...conditionsList,
|
|
65
|
+
{
|
|
66
|
+
columnName: '',
|
|
67
|
+
isOrIsNotEqualTo: 'is',
|
|
68
|
+
value: ''
|
|
69
|
+
}
|
|
70
|
+
])
|
|
71
|
+
|
|
72
|
+
const [conditionControls, setConditionControls] = openConditionControls
|
|
73
|
+
|
|
74
|
+
const newConditionsControls = [...conditionControls]
|
|
75
|
+
newConditionsControls[conditionsList.length + 1] = true
|
|
76
|
+
setConditionControls(newConditionsControls)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const handleVariableDoneClick = () => {
|
|
80
|
+
const filteredConditionsList = conditionsList.filter(condition => condition.columnName !== '' && condition.value !== '')
|
|
81
|
+
const newVariable = {
|
|
82
|
+
columnName: selectedColumn,
|
|
83
|
+
conditions: filteredConditionsList,
|
|
84
|
+
name: variableName,
|
|
85
|
+
tag: `{{${variableName}}}`
|
|
86
|
+
}
|
|
87
|
+
updateVariableArray(newVariable, variableIndex)
|
|
88
|
+
setShow(variableIndex, false)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const columnSelectDisabled = variableName === ''
|
|
92
|
+
const addConditionDisabled = columnSelectDisabled || selectedColumn === ''
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<>
|
|
96
|
+
{!show ? (
|
|
97
|
+
<>
|
|
98
|
+
<div className='mb-2'>
|
|
99
|
+
<button onClick={() => setShow(variableIndex, true)}>
|
|
100
|
+
<Icon display='caretDown' />
|
|
101
|
+
</button>
|
|
102
|
+
<span> {variableName ? `${variableName}` : 'New Variable'}</span>
|
|
103
|
+
</div>
|
|
104
|
+
</>
|
|
105
|
+
) : (
|
|
106
|
+
<fieldset className='edit-block mb-1' key={variableIndex}>
|
|
107
|
+
<div className='d-flex justify-content-between'>
|
|
108
|
+
<button onClick={handleVariableDoneClick} disabled={addConditionDisabled}>
|
|
109
|
+
<Icon display='caretUp' />
|
|
110
|
+
</button>
|
|
111
|
+
<button
|
|
112
|
+
className='btn btn-danger btn-sm mt-0 ml-2'
|
|
113
|
+
onClick={event => {
|
|
114
|
+
event.preventDefault()
|
|
115
|
+
deleteVariable(variableIndex)
|
|
116
|
+
}}
|
|
117
|
+
>
|
|
118
|
+
Delete
|
|
119
|
+
</button>
|
|
120
|
+
</div>
|
|
121
|
+
<label className='d-block'>
|
|
122
|
+
<span className='edit-label column-heading'>Variable Name:</span>
|
|
123
|
+
<input className={`variable-${variableIndex} ml-1`} type='text' value={variableName} onChange={e => setVariableName(e.target.value)} />
|
|
124
|
+
</label>
|
|
125
|
+
<div className='pt-2'>
|
|
126
|
+
<label className='d-block'>
|
|
127
|
+
<span className='edit-label column-heading'>Column:</span>
|
|
128
|
+
<select className={`variable-${variableIndex} ml-1`} onChange={e => handleVariableColumnChange(e.target.value)} value={selectedColumn} disabled={columnSelectDisabled}>
|
|
129
|
+
<option value=''>Select</option>
|
|
130
|
+
{columnNames.map(columnName => (
|
|
131
|
+
<option key={columnName} value={columnName}>
|
|
132
|
+
{columnName}
|
|
133
|
+
</option>
|
|
134
|
+
))}
|
|
135
|
+
</select>
|
|
136
|
+
</label>
|
|
137
|
+
</div>
|
|
138
|
+
<label className='d-block py-2'>
|
|
139
|
+
<span className='edit-label column-heading'>Conditions:</span>
|
|
140
|
+
{conditionsList.map((condition, index) => {
|
|
141
|
+
return (
|
|
142
|
+
<div className='condition-section mt-2'>
|
|
143
|
+
<Conditions
|
|
144
|
+
key={variableName + '-condition-' + index}
|
|
145
|
+
conditionControls={openConditionControls}
|
|
146
|
+
conditionLookup={conditionLookup}
|
|
147
|
+
conditionSettings={condition}
|
|
148
|
+
conditionIndex={index}
|
|
149
|
+
removeCondition={removeCondition}
|
|
150
|
+
selectedColumn={selectedColumn}
|
|
151
|
+
updateConditionsList={updateConditionsList}
|
|
152
|
+
/>
|
|
153
|
+
</div>
|
|
154
|
+
)
|
|
155
|
+
})}
|
|
156
|
+
</label>
|
|
157
|
+
<div className='mt-1'>
|
|
158
|
+
<button onClick={handleAddConditionClick} disabled={addConditionDisabled}>
|
|
159
|
+
Add Condition
|
|
160
|
+
</button>
|
|
161
|
+
<button className='ml-2' onClick={handleVariableDoneClick} disabled={addConditionDisabled}>
|
|
162
|
+
Done
|
|
163
|
+
</button>
|
|
164
|
+
</div>
|
|
165
|
+
</fieldset>
|
|
166
|
+
)}
|
|
167
|
+
</>
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export default VariableSection
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
.cdc-open-viz-module.markup-include {
|
|
2
|
+
.editor-panel .condition-section :is(label) {
|
|
3
|
+
font-size: 1em;
|
|
4
|
+
}
|
|
5
|
+
:is(span).edit-label {
|
|
6
|
+
margin-bottom: 0.3em;
|
|
7
|
+
display: block;
|
|
8
|
+
}
|
|
9
|
+
.react-tooltip {
|
|
10
|
+
position: absolute;
|
|
11
|
+
width: 250px;
|
|
12
|
+
}
|
|
13
|
+
.need-data-source-prompt {
|
|
14
|
+
color: var(--gray);
|
|
15
|
+
font-size: 0.8rem;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
export default {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
contentEditor: {
|
|
3
|
+
inlineHTML: '<h2>Inline HTML</h2>',
|
|
4
|
+
markupVariables: [],
|
|
5
|
+
showHeader: true,
|
|
6
|
+
srcUrl: '#example',
|
|
7
|
+
title: 'Markup Include',
|
|
8
|
+
useInlineHTML: false
|
|
9
|
+
},
|
|
10
|
+
data: [],
|
|
11
|
+
legend: {},
|
|
12
|
+
newViz: true,
|
|
6
13
|
theme: 'theme-blue',
|
|
14
|
+
type: 'markup-include',
|
|
15
|
+
runtime: null,
|
|
7
16
|
visual: {
|
|
8
17
|
border: false,
|
|
9
18
|
accent: false,
|
package/src/scss/main.scss
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
@import '@cdc/core/styles/
|
|
1
|
+
@import '@cdc/core/styles/base';
|
|
2
2
|
|
|
3
3
|
.markup-include {
|
|
4
4
|
.checkbox-group {
|
|
@@ -9,13 +9,37 @@
|
|
|
9
9
|
margin-bottom: 24px;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
.cove-component__content-wrap {
|
|
13
|
+
padding: 1rem;
|
|
14
|
+
|
|
15
|
+
h1,
|
|
16
|
+
h2,
|
|
17
|
+
h3 {
|
|
18
|
+
margin-top: 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
p {
|
|
22
|
+
margin-top: auto;
|
|
23
|
+
margin-bottom: auto;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
12
27
|
.cove-component__content {
|
|
13
|
-
background-color: #f2f2f2;
|
|
14
28
|
font-size: 14px;
|
|
15
29
|
}
|
|
16
30
|
.cove-component__content.component--hideBackgroundColor {
|
|
17
31
|
background: transparent;
|
|
18
32
|
}
|
|
33
|
+
|
|
34
|
+
.cove-editor__content .cove-editor__content-wrap {
|
|
35
|
+
margin: 0 auto;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.cove-editor .cove-editor__content {
|
|
39
|
+
position: unset;
|
|
40
|
+
left: unset;
|
|
41
|
+
width: 100%;
|
|
42
|
+
}
|
|
19
43
|
}
|
|
20
44
|
|
|
21
45
|
.cove-accordion__panel-section {
|
|
@@ -26,3 +50,7 @@
|
|
|
26
50
|
float: left;
|
|
27
51
|
}
|
|
28
52
|
}
|
|
53
|
+
.cove-editor .cove-editor__content {
|
|
54
|
+
padding-left: 350px;
|
|
55
|
+
display: flex;
|
|
56
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { MarkupIncludeConfig } from '@cdc/core/types/MarkupInclude'
|
|
2
2
|
|
|
3
3
|
type Action<T, P> = { type: T; payload: P }
|
|
4
4
|
|
|
5
|
-
type SET_CONFIG = Action<'SET_CONFIG',
|
|
5
|
+
type SET_CONFIG = Action<'SET_CONFIG', MarkupIncludeConfig>
|
|
6
6
|
type SET_LOADING = Action<'SET_LOADING', boolean>
|
|
7
7
|
type SET_URL_MARKUP = Action<'SET_URL_MARKUP', string>
|
|
8
8
|
type SET_MARKUP_ERROR = Action<'SET_MARKUP_ERROR', any>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import MarkupIncludeActions from './
|
|
1
|
+
import { MarkupIncludeConfig } from '@cdc/core/types/MarkupInclude'
|
|
2
|
+
import MarkupIncludeActions from './markupInclude.actions'
|
|
3
3
|
|
|
4
4
|
export type MarkupIncludeState = {
|
|
5
|
-
config?:
|
|
5
|
+
config?: MarkupIncludeConfig
|
|
6
6
|
loading: boolean
|
|
7
7
|
urlMarkup: string
|
|
8
8
|
markupError: any
|