WizardOptionsField.jsx 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. /*
  2. Copyright (C) 2017 Cloudbase Solutions SRL
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. */
  14. // @flow
  15. import React from 'react'
  16. import styled from 'styled-components'
  17. import { observer } from 'mobx-react'
  18. import Switch from '../../atoms/Switch'
  19. import TextInput from '../../atoms/TextInput'
  20. import AutocompleteDropdown from '../../molecules/AutocompleteDropdown'
  21. import Dropdown from '../../molecules/Dropdown'
  22. import InfoIcon from '../../atoms/InfoIcon'
  23. import PropertiesTable from '../../molecules/PropertiesTable'
  24. import StyleProps from '../../styleUtils/StyleProps'
  25. import LabelDictionary from '../../../utils/LabelDictionary'
  26. import type { Field } from '../../../types/Field'
  27. import asteriskImage from './images/asterisk.svg'
  28. const getDirection = props => {
  29. if (props.type === 'strict-boolean' || props.type === 'boolean') {
  30. return 'row'
  31. }
  32. return 'column'
  33. }
  34. const Wrapper = styled.div`
  35. display: flex;
  36. flex-direction: ${props => getDirection(props)};
  37. ${props => getDirection(props) === 'row' ? '' : 'justify-content: center;'}
  38. `
  39. const Label = styled.div`
  40. font-weight: ${StyleProps.fontWeights.medium};
  41. margin-bottom: 8px;
  42. ${props => getDirection(props) === 'column' ? 'margin-bottom: 8px;' : ''}
  43. `
  44. const LabelText = styled.span`
  45. margin-right: ${props => props.noMargin ? 0 : 24}px;
  46. `
  47. const Asterisk = styled.div`
  48. ${StyleProps.exactSize('16px')}
  49. display: inline-block;
  50. background: url('${asteriskImage}') center no-repeat;
  51. margin-bottom: -3px;
  52. margin-left: ${props => props.marginLeft || '0px'};
  53. `
  54. type Props = {
  55. type: 'replica' | 'migration',
  56. name: string,
  57. value: any,
  58. onChange: (value: any) => void,
  59. valueCallback: (prop: Field, value: any) => void,
  60. className?: string,
  61. properties: Field[],
  62. enum: string[],
  63. required: boolean,
  64. width?: number,
  65. skipNullValue?: boolean,
  66. 'data-test-id'?: string,
  67. style?: { [string]: mixed },
  68. }
  69. @observer
  70. class WizardOptionsField extends React.Component<Props> {
  71. renderSwitch(propss: { triState: boolean }) {
  72. return (
  73. <Switch
  74. width="112px"
  75. justifyContent="flex-end"
  76. height={16}
  77. triState={propss.triState}
  78. checked={this.props.value}
  79. onChange={checked => { this.props.onChange(checked) }}
  80. style={{ marginTop: '-8px' }}
  81. leftLabel
  82. data-test-id="wOptionsField-switch"
  83. />
  84. )
  85. }
  86. renderTextInput() {
  87. return (
  88. <TextInput
  89. width={`${this.props.width || StyleProps.inputSizes.wizard.width}px`}
  90. value={this.props.value}
  91. onChange={e => { this.props.onChange(e.target.value) }}
  92. placeholder={LabelDictionary.get(this.props.name)}
  93. data-test-id="wOptionsField-textInput"
  94. />
  95. )
  96. }
  97. renderObjectTable() {
  98. if (!this.props.properties || !this.props.properties.length) {
  99. return null
  100. }
  101. return (
  102. <PropertiesTable
  103. properties={this.props.properties}
  104. valueCallback={this.props.valueCallback}
  105. onChange={this.props.onChange}
  106. data-test-id="wOptionsField-propertiesTable"
  107. />
  108. )
  109. }
  110. renderEnumDropdown() {
  111. const useDictionary = LabelDictionary.enumFields.find(f => f === this.props.name)
  112. let items = this.props.enum.map(e => {
  113. if (typeof e !== 'string' && e.separator === true) {
  114. return e
  115. }
  116. return {
  117. label: typeof e === 'string' ? (useDictionary ? LabelDictionary.get(e) : e) : e.name,
  118. value: typeof e === 'string' ? e : e.id,
  119. }
  120. })
  121. if (!this.props.skipNullValue) {
  122. items = [
  123. { label: 'Choose a value', value: null },
  124. ...items,
  125. ]
  126. }
  127. let selectedItem = items.find(i => i.value === this.props.value)
  128. if (items.length < 10) {
  129. return (
  130. <Dropdown
  131. data-test-id={`wOptionsField-enumDropdown-${this.props.name}`}
  132. width={this.props.width || StyleProps.inputSizes.wizard.width}
  133. noSelectionMessage="Choose a value"
  134. selectedItem={selectedItem}
  135. items={items}
  136. dimFirstItem
  137. onChange={item => this.props.onChange(item.value)}
  138. />
  139. )
  140. }
  141. return (
  142. <AutocompleteDropdown
  143. width={this.props.width || StyleProps.inputSizes.wizard.width}
  144. selectedItem={selectedItem}
  145. items={items}
  146. dimNullValue
  147. onChange={item => this.props.onChange(item.value)}
  148. />
  149. )
  150. }
  151. renderArrayDropdown() {
  152. let items = this.props.enum.map(e => {
  153. if (typeof e !== 'string' && e.separator === true) {
  154. return e
  155. }
  156. return {
  157. label: typeof e === 'string' ? LabelDictionary.get(e) : e.name,
  158. value: typeof e === 'string' ? e : e.id,
  159. }
  160. })
  161. let selectedItems = this.props.value || []
  162. return (
  163. <Dropdown
  164. multipleSelection
  165. items={items}
  166. selectedItems={selectedItems}
  167. width={this.props.width || StyleProps.inputSizes.wizard.width}
  168. noSelectionMessage="Choose values"
  169. onChange={item => this.props.onChange(item.value)}
  170. />
  171. )
  172. }
  173. renderField() {
  174. let field = null
  175. switch (this.props.type) {
  176. case 'strict-boolean':
  177. field = this.renderSwitch({ triState: false })
  178. break
  179. case 'boolean':
  180. field = this.renderSwitch({ triState: true })
  181. break
  182. case 'string':
  183. if (this.props.enum && this.props.enum.length) {
  184. field = this.renderEnumDropdown()
  185. } else {
  186. field = this.renderTextInput()
  187. }
  188. break
  189. case 'object':
  190. field = this.renderObjectTable()
  191. break
  192. case 'array':
  193. field = this.renderArrayDropdown()
  194. break
  195. default:
  196. }
  197. return field
  198. }
  199. renderLabel() {
  200. let description = LabelDictionary.getDescription(this.props.name)
  201. return (
  202. <Label>
  203. <LabelText data-test-id="wOptionsField-label" noMargin={!description && !this.props.required}>
  204. {LabelDictionary.get(this.props.name)}
  205. </LabelText>
  206. {description ? <InfoIcon text={description} marginLeft={-20} /> : null}
  207. {this.props.required ? <Asterisk data-test-id="wOptionsField-required" marginLeft={description ? '4px' : '-16px'} /> : null}
  208. </Label>
  209. )
  210. }
  211. render() {
  212. let field = this.renderField()
  213. if (!field) {
  214. return null
  215. }
  216. return (
  217. <Wrapper
  218. data-test-id={this.props['data-test-id'] || 'wOptionsField-wrapper'}
  219. type={this.props.type}
  220. className={this.props.className}
  221. style={this.props.style}
  222. >
  223. {this.renderLabel()}
  224. {field}
  225. </Wrapper>
  226. )
  227. }
  228. }
  229. export default WizardOptionsField