EndpointField.jsx 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  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 { observer } from 'mobx-react'
  17. import styled from 'styled-components'
  18. import Switch from '../../atoms/Switch'
  19. import TextInput from '../../atoms/TextInput'
  20. import RadioInput from '../../atoms/RadioInput'
  21. import InfoIcon from '../../atoms/InfoIcon'
  22. import Dropdown from '../../molecules/Dropdown'
  23. import DropdownInput from '../../molecules/DropdownInput'
  24. import TextArea from '../../atoms/TextArea'
  25. import PropertiesTable from '../../molecules/PropertiesTable'
  26. import type { Field as FieldType } from '../../../types/Field'
  27. import LabelDictionary from '../../../utils/LabelDictionary'
  28. import StyleProps from '../../styleUtils/StyleProps'
  29. import Palette from '../../styleUtils/Palette'
  30. const Wrapper = styled.div``
  31. const Label = styled.div`
  32. font-size: 10px;
  33. font-weight: ${StyleProps.fontWeights.medium};
  34. color: ${Palette.grayscale[3]};
  35. text-transform: uppercase;
  36. margin-bottom: 2px;
  37. display: flex;
  38. align-items: center;
  39. `
  40. const LabelText = styled.span`
  41. margin-right: 24px;
  42. `
  43. type Props = {
  44. name: string,
  45. type: string,
  46. value: any,
  47. onChange?: (value: any, fieldName?: string) => void,
  48. valueCallback?: (fieldName: string) => void,
  49. getFieldValue?: (fieldName: string) => string,
  50. onFieldChange?: (fieldName: string, fieldValue: string) => void,
  51. className?: string,
  52. minimum?: number,
  53. maximum?: number,
  54. password?: boolean,
  55. required?: boolean,
  56. large?: boolean,
  57. highlight?: boolean,
  58. properties?: FieldType[],
  59. disabled?: boolean,
  60. // $FlowIssue
  61. enum?: string[] | { label: string, value: string }[],
  62. items?: any[],
  63. useTextArea?: boolean,
  64. noSelectionMessage?: string,
  65. noItemsMessage?: string,
  66. selectedItems?: string[],
  67. 'data-test-id'?: string,
  68. }
  69. @observer
  70. class Field extends React.Component<Props> {
  71. renderSwitch(propss: { triState: boolean }) {
  72. return (
  73. <Switch
  74. data-test-id={`endpointField-switch-${this.props.name}`}
  75. disabled={this.props.disabled}
  76. triState={propss.triState}
  77. checked={this.props.value}
  78. onChange={checked => { if (this.props.onChange) this.props.onChange(checked) }}
  79. />
  80. )
  81. }
  82. renderTextInput() {
  83. return (
  84. <TextInput
  85. data-test-id={`endpointField-textInput-${this.props.name}`}
  86. highlight={this.props.highlight}
  87. type={this.props.password ? 'password' : 'text'}
  88. large={this.props.large}
  89. value={this.props.value}
  90. onChange={e => { if (this.props.onChange) this.props.onChange(e.target.value) }}
  91. placeholder={LabelDictionary.get(this.props.name)}
  92. disabled={this.props.disabled}
  93. required={this.props.required}
  94. />
  95. )
  96. }
  97. renderIntInput() {
  98. return (
  99. <TextInput
  100. highlight={this.props.highlight}
  101. large={this.props.large}
  102. value={this.props.value}
  103. onChange={e => {
  104. let value = Number(e.target.value.replace(/\D/g, '')) || ''
  105. if (this.props.onChange) {
  106. this.props.onChange(value)
  107. }
  108. }}
  109. placeholder={LabelDictionary.get(this.props.name)}
  110. disabled={this.props.disabled}
  111. />
  112. )
  113. }
  114. renderObjectTable() {
  115. if (!this.props.properties || !this.props.properties.length) {
  116. return null
  117. }
  118. return (
  119. <PropertiesTable
  120. properties={this.props.properties}
  121. valueCallback={field => { if (this.props.valueCallback) { this.props.valueCallback(field.name) } }}
  122. onChange={(field, value) => {
  123. let fieldName = field.name.substr(field.name.lastIndexOf('/') + 1)
  124. if (this.props.onChange) {
  125. this.props.onChange(value, fieldName)
  126. }
  127. }}
  128. />
  129. )
  130. }
  131. renderTextArea() {
  132. return (
  133. <TextArea
  134. data-test-id={`endpointField-textArea-${this.props.name}`}
  135. style={{ width: '100%' }}
  136. highlight={this.props.highlight}
  137. value={this.props.value}
  138. onChange={e => { console.log('changing', e); if (this.props.onChange) this.props.onChange(e.target.value) }}
  139. placeholder={LabelDictionary.get(this.props.name)}
  140. disabled={this.props.disabled}
  141. required={this.props.required}
  142. />
  143. )
  144. }
  145. renderEnumDropdown() {
  146. if (!this.props.enum) {
  147. return null
  148. }
  149. let items = this.props.enum.map(e => {
  150. if (typeof e === 'string') {
  151. return {
  152. label: LabelDictionary.get(e),
  153. value: e,
  154. }
  155. }
  156. return e
  157. })
  158. let selectedItem = items.find(i => i.value === this.props.value)
  159. return (
  160. <Dropdown
  161. data-test-id={`endpointField-dropdown-${this.props.name}`}
  162. large={this.props.large}
  163. selectedItem={selectedItem}
  164. noSelectionMessage={this.props.noSelectionMessage}
  165. noItemsMessage={this.props.noItemsMessage}
  166. items={items}
  167. onChange={item => { if (this.props.onChange) this.props.onChange(item.value) }}
  168. disabled={this.props.disabled}
  169. highlight={this.props.highlight}
  170. required={this.props.required}
  171. />
  172. )
  173. }
  174. renderArrayDropdown() {
  175. return (
  176. <Dropdown
  177. data-test-id={`endpointField-multidropdown-${this.props.name}`}
  178. multipleSelection
  179. large={this.props.large}
  180. disabled={this.props.disabled}
  181. noSelectionMessage={this.props.noSelectionMessage}
  182. noItemsMessage={this.props.noItemsMessage}
  183. items={this.props.items}
  184. selectedItems={this.props.selectedItems}
  185. onChange={item => { if (this.props.onChange) this.props.onChange(item.value) }}
  186. highlight={this.props.highlight}
  187. required={this.props.required}
  188. />
  189. )
  190. }
  191. renderIntDropdown() {
  192. if (!this.props.minimum || !this.props.maximum) {
  193. return null
  194. }
  195. let items = []
  196. for (let i = this.props.minimum; i <= this.props.maximum; i += 1) {
  197. items.push({
  198. label: i.toString(),
  199. value: i,
  200. })
  201. }
  202. return (
  203. <Dropdown
  204. data-test-id={`endpointField-dropdown-${this.props.name}`}
  205. large={this.props.large}
  206. selectedItem={this.props.value}
  207. items={items}
  208. onChange={item => { if (this.props.onChange) this.props.onChange(item.value) }}
  209. disabled={this.props.disabled}
  210. highlight={this.props.highlight}
  211. required={this.props.required}
  212. />
  213. )
  214. }
  215. renderRadioInput() {
  216. return (
  217. <RadioInput
  218. data-test-id={`endpointField-radioInput-${this.props.name}`}
  219. checked={this.props.value}
  220. label={LabelDictionary.get(this.props.name)}
  221. onChange={e => { if (this.props.onChange) this.props.onChange(e.target.checked) }}
  222. disabled={this.props.disabled}
  223. />
  224. )
  225. }
  226. renderDropdownInput() {
  227. if (!this.props.items) {
  228. return null
  229. }
  230. let items = this.props.items.map(field => {
  231. return {
  232. value: field.name,
  233. label: field.label || LabelDictionary.get(field.name),
  234. }
  235. })
  236. let fieldName = this.props.value || items[0].value
  237. return (
  238. <DropdownInput
  239. items={items}
  240. selectedItem={fieldName}
  241. onItemChange={item => { if (this.props.onChange) this.props.onChange(item.value) }}
  242. inputValue={this.props.getFieldValue ? this.props.getFieldValue(fieldName) : ''}
  243. onInputChange={value => { if (this.props.onFieldChange) this.props.onFieldChange(fieldName, value) }}
  244. placeholder={LabelDictionary.get(fieldName)}
  245. highlight={this.props.highlight}
  246. disabled={this.props.disabled}
  247. required={this.props.required}
  248. />
  249. )
  250. }
  251. renderInput() {
  252. switch (this.props.type) {
  253. case 'input-choice':
  254. return this.renderDropdownInput()
  255. case 'boolean':
  256. return this.renderSwitch({ triState: false })
  257. case 'optional-boolean':
  258. return this.renderSwitch({ triState: true })
  259. case 'string':
  260. if (this.props.enum) {
  261. return this.renderEnumDropdown()
  262. }
  263. if (this.props.useTextArea) {
  264. return this.renderTextArea()
  265. }
  266. return this.renderTextInput()
  267. case 'integer':
  268. if (this.props.minimum || this.props.maximum) {
  269. return this.renderIntDropdown()
  270. }
  271. return this.renderIntInput()
  272. case 'radio':
  273. return this.renderRadioInput()
  274. case 'array':
  275. return this.renderArrayDropdown()
  276. case 'object':
  277. return this.renderObjectTable()
  278. default:
  279. return null
  280. }
  281. }
  282. renderLabel() {
  283. if (this.props.type === 'radio') {
  284. return null
  285. }
  286. let description = LabelDictionary.getDescription(this.props.name)
  287. let infoIcon = null
  288. if (description) {
  289. infoIcon = <InfoIcon text={description} marginLeft={-20} marginBottom={0} />
  290. }
  291. return (
  292. <Label>
  293. <LabelText data-test-id="endpointField-label">{LabelDictionary.get(this.props.name)}</LabelText>
  294. {infoIcon}
  295. </Label>
  296. )
  297. }
  298. render() {
  299. return (
  300. <Wrapper data-test-id={this.props['data-test-id'] || 'endpointField-wrapper'} className={this.props.className}>
  301. {this.renderLabel()}
  302. {this.renderInput()}
  303. </Wrapper>
  304. )
  305. }
  306. }
  307. export default Field