Commit f2a90f18a129938987f5855cd073cde4d88c2e18
1 parent
79d58035
Json Schema Form: Add Fieldset, Array and Date fields.
Showing
12 changed files
with
445 additions
and
28 deletions
@@ -1331,6 +1331,19 @@ | @@ -1331,6 +1331,19 @@ | ||
1331 | } | 1331 | } |
1332 | } | 1332 | } |
1333 | }, | 1333 | }, |
1334 | + "@date-io/core": { | ||
1335 | + "version": "1.3.11", | ||
1336 | + "resolved": "https://registry.npmjs.org/@date-io/core/-/core-1.3.11.tgz", | ||
1337 | + "integrity": "sha512-Yxf2ei0vjU38Fizswr/Uwub5QeRiLOHiTRiHUuTdg+biVB+1EUk+h5szas9SEWA2pZDlSo73F5TPuu+zKqOIBQ==" | ||
1338 | + }, | ||
1339 | + "@date-io/date-fns": { | ||
1340 | + "version": "1.3.11", | ||
1341 | + "resolved": "https://registry.npmjs.org/@date-io/date-fns/-/date-fns-1.3.11.tgz", | ||
1342 | + "integrity": "sha512-6Pvk4gwCU4L19XYzDUrro861JCQjZkJQjugxAA+M8wsDTW75A5rmSZGa6g2rQQXfg6ox4B7HBx9p6JYDsSPX0g==", | ||
1343 | + "requires": { | ||
1344 | + "@date-io/core": "^1.3.11" | ||
1345 | + } | ||
1346 | + }, | ||
1334 | "@emotion/hash": { | 1347 | "@emotion/hash": { |
1335 | "version": "0.7.3", | 1348 | "version": "0.7.3", |
1336 | "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.7.3.tgz", | 1349 | "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.7.3.tgz", |
@@ -1366,6 +1379,26 @@ | @@ -1366,6 +1379,26 @@ | ||
1366 | "react-transition-group": "^4.3.0" | 1379 | "react-transition-group": "^4.3.0" |
1367 | } | 1380 | } |
1368 | }, | 1381 | }, |
1382 | + "@material-ui/icons": { | ||
1383 | + "version": "4.5.1", | ||
1384 | + "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.5.1.tgz", | ||
1385 | + "integrity": "sha512-YZ/BgJbXX4a0gOuKWb30mBaHaoXRqPanlePam83JQPZ/y4kl+3aW0Wv9tlR70hB5EGAkEJGW5m4ktJwMgxQAeA==", | ||
1386 | + "requires": { | ||
1387 | + "@babel/runtime": "^7.4.4" | ||
1388 | + } | ||
1389 | + }, | ||
1390 | + "@material-ui/pickers": { | ||
1391 | + "version": "3.2.7", | ||
1392 | + "resolved": "https://registry.npmjs.org/@material-ui/pickers/-/pickers-3.2.7.tgz", | ||
1393 | + "integrity": "sha512-dDi8G8TOXssXZQsGCRM4zoDnWMY4O/vvqVCH4ViIHflvS4ek4v30IlFcSONI5jGzL0dmJhNKso2UEn7qS3iZ3g==", | ||
1394 | + "requires": { | ||
1395 | + "@babel/runtime": "^7.6.0", | ||
1396 | + "@types/styled-jsx": "^2.2.8", | ||
1397 | + "clsx": "^1.0.2", | ||
1398 | + "react-transition-group": "^4.0.0", | ||
1399 | + "rifm": "^0.7.0" | ||
1400 | + } | ||
1401 | + }, | ||
1369 | "@material-ui/styles": { | 1402 | "@material-ui/styles": { |
1370 | "version": "4.5.0", | 1403 | "version": "4.5.0", |
1371 | "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.5.0.tgz", | 1404 | "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.5.0.tgz", |
@@ -1651,6 +1684,14 @@ | @@ -1651,6 +1684,14 @@ | ||
1651 | "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", | 1684 | "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", |
1652 | "dev": true | 1685 | "dev": true |
1653 | }, | 1686 | }, |
1687 | + "@types/styled-jsx": { | ||
1688 | + "version": "2.2.8", | ||
1689 | + "resolved": "https://registry.npmjs.org/@types/styled-jsx/-/styled-jsx-2.2.8.tgz", | ||
1690 | + "integrity": "sha512-Yjye9VwMdYeXfS71ihueWRSxrruuXTwKCbzue4+5b2rjnQ//AtyM7myZ1BEhNhBQ/nL/RE7bdToUoLln2miKvg==", | ||
1691 | + "requires": { | ||
1692 | + "@types/react": "*" | ||
1693 | + } | ||
1694 | + }, | ||
1654 | "@types/tinycolor2": { | 1695 | "@types/tinycolor2": { |
1655 | "version": "1.4.2", | 1696 | "version": "1.4.2", |
1656 | "resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.2.tgz", | 1697 | "resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.2.tgz", |
@@ -3725,6 +3766,11 @@ | @@ -3725,6 +3766,11 @@ | ||
3725 | "assert-plus": "^1.0.0" | 3766 | "assert-plus": "^1.0.0" |
3726 | } | 3767 | } |
3727 | }, | 3768 | }, |
3769 | + "date-fns": { | ||
3770 | + "version": "2.5.0", | ||
3771 | + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.5.0.tgz", | ||
3772 | + "integrity": "sha512-I6Tkis01//nRcmvMQw/MRE1HAtcuA5Ie6jGPb8bJZJub7494LGOObqkV3ParnsSVviAjk5C8mNKDqYVBzCopWg==" | ||
3773 | + }, | ||
3728 | "date-format": { | 3774 | "date-format": { |
3729 | "version": "2.1.0", | 3775 | "version": "2.1.0", |
3730 | "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", | 3776 | "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", |
@@ -9131,6 +9177,14 @@ | @@ -9131,6 +9177,14 @@ | ||
9131 | "integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==", | 9177 | "integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==", |
9132 | "dev": true | 9178 | "dev": true |
9133 | }, | 9179 | }, |
9180 | + "rifm": { | ||
9181 | + "version": "0.7.0", | ||
9182 | + "resolved": "https://registry.npmjs.org/rifm/-/rifm-0.7.0.tgz", | ||
9183 | + "integrity": "sha512-DSOJTWHD67860I5ojetXdEQRIBvF6YcpNe53j0vn1vp9EUb9N80EiZTxgP+FkDKorWC8PZw052kTF4C1GOivCQ==", | ||
9184 | + "requires": { | ||
9185 | + "@babel/runtime": "^7.3.1" | ||
9186 | + } | ||
9187 | + }, | ||
9134 | "rimraf": { | 9188 | "rimraf": { |
9135 | "version": "2.7.1", | 9189 | "version": "2.7.1", |
9136 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", | 9190 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", |
@@ -24,8 +24,11 @@ | @@ -24,8 +24,11 @@ | ||
24 | "@angular/platform-browser-dynamic": "~8.2.11", | 24 | "@angular/platform-browser-dynamic": "~8.2.11", |
25 | "@angular/router": "~8.2.11", | 25 | "@angular/router": "~8.2.11", |
26 | "@auth0/angular-jwt": "^3.0.0", | 26 | "@auth0/angular-jwt": "^3.0.0", |
27 | + "@date-io/date-fns": "^1.3.11", | ||
27 | "@mat-datetimepicker/core": "^2.0.1", | 28 | "@mat-datetimepicker/core": "^2.0.1", |
28 | "@material-ui/core": "^4.5.1", | 29 | "@material-ui/core": "^4.5.1", |
30 | + "@material-ui/icons": "^4.5.1", | ||
31 | + "@material-ui/pickers": "^3.2.7", | ||
29 | "@ngrx/effects": "^8.2.0", | 32 | "@ngrx/effects": "^8.2.0", |
30 | "@ngrx/store": "^8.2.0", | 33 | "@ngrx/store": "^8.2.0", |
31 | "@ngrx/store-devtools": "^8.2.0", | 34 | "@ngrx/store-devtools": "^8.2.0", |
@@ -38,6 +41,7 @@ | @@ -38,6 +41,7 @@ | ||
38 | "base64-js": "^1.3.1", | 41 | "base64-js": "^1.3.1", |
39 | "compass-sass-mixins": "^0.12.7", | 42 | "compass-sass-mixins": "^0.12.7", |
40 | "core-js": "^3.1.4", | 43 | "core-js": "^3.1.4", |
44 | + "date-fns": "^2.5.0", | ||
41 | "deep-equal": "^1.0.1", | 45 | "deep-equal": "^1.0.1", |
42 | "flot": "git://github.com/thingsboard/flot.git#0.9-work", | 46 | "flot": "git://github.com/thingsboard/flot.git#0.9-work", |
43 | "flot.curvedlines": "git://github.com/MichaelZinsmaier/CurvedLines.git#master", | 47 | "flot.curvedlines": "git://github.com/MichaelZinsmaier/CurvedLines.git#master", |
@@ -159,7 +159,7 @@ export class JsonFormComponent implements OnInit, ControlValueAccessor, Validato | @@ -159,7 +159,7 @@ export class JsonFormComponent implements OnInit, ControlValueAccessor, Validato | ||
159 | 159 | ||
160 | onFullscreenChanged() {} | 160 | onFullscreenChanged() {} |
161 | 161 | ||
162 | - private onModelChange(key: string | string[], val: any) { | 162 | + private onModelChange(key: (string | number)[], val: any) { |
163 | if (isString(val) && val === '') { | 163 | if (isString(val) && val === '') { |
164 | val = undefined; | 164 | val = undefined; |
165 | } | 165 | } |
1 | +/* | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +import * as React from 'react'; | ||
17 | +import JsonFormUtils from './json-form-utils'; | ||
18 | +import ThingsboardBaseComponent from './json-form-base-component'; | ||
19 | +import Button from '@material-ui/core/Button'; | ||
20 | +import _ from 'lodash'; | ||
21 | +import IconButton from '@material-ui/core/IconButton'; | ||
22 | +import ClearIcon from '@material-ui/icons/Clear'; | ||
23 | +import AddIcon from '@material-ui/icons/Add'; | ||
24 | +import Tooltip from '@material-ui/core/Tooltip'; | ||
25 | +import { | ||
26 | + JsonFormData, | ||
27 | + JsonFormFieldProps, | ||
28 | + JsonFormFieldState | ||
29 | +} from '@shared/components/json-form/react/json-form.models'; | ||
30 | + | ||
31 | +interface ThingsboardArrayState extends JsonFormFieldState { | ||
32 | + model: any[]; | ||
33 | + keys: number[]; | ||
34 | +} | ||
35 | + | ||
36 | +class ThingsboardArray extends React.Component<JsonFormFieldProps, ThingsboardArrayState> { | ||
37 | + | ||
38 | + constructor(props) { | ||
39 | + super(props); | ||
40 | + this.onAppend = this.onAppend.bind(this); | ||
41 | + this.onDelete = this.onDelete.bind(this); | ||
42 | + const model = JsonFormUtils.selectOrSet(this.props.form.key, this.props.model) || []; | ||
43 | + const keys: number[] = []; | ||
44 | + for (let i = 0; i < model.length; i++) { | ||
45 | + keys.push(i); | ||
46 | + } | ||
47 | + this.state = { | ||
48 | + model, | ||
49 | + keys | ||
50 | + }; | ||
51 | + } | ||
52 | + | ||
53 | + componentDidMount() { | ||
54 | + if (this.props.form.startEmpty !== true && this.state.model.length === 0) { | ||
55 | + this.onAppend(); | ||
56 | + } | ||
57 | + } | ||
58 | + | ||
59 | + onAppend() { | ||
60 | + let empty; | ||
61 | + if (this.props.form && this.props.form.schema && this.props.form.schema.items) { | ||
62 | + const items = this.props.form.schema.items; | ||
63 | + if (items.type && items.type.indexOf('object') !== -1) { | ||
64 | + empty = {}; | ||
65 | + if (!this.props.options || this.props.options.setSchemaDefaults !== false) { | ||
66 | + empty = typeof items.default !== 'undefined' ? items.default : empty; | ||
67 | + if (empty) { | ||
68 | + JsonFormUtils.traverseSchema(items, (prop, path) => { | ||
69 | + if (typeof prop.default !== 'undefined') { | ||
70 | + JsonFormUtils.selectOrSet(path, empty, prop.default); | ||
71 | + } | ||
72 | + }); | ||
73 | + } | ||
74 | + } | ||
75 | + } else if (items.type && items.type.indexOf('array') !== -1) { | ||
76 | + empty = []; | ||
77 | + if (!this.props.options || this.props.options.setSchemaDefaults !== false) { | ||
78 | + empty = items.default || empty; | ||
79 | + } | ||
80 | + } else { | ||
81 | + if (!this.props.options || this.props.options.setSchemaDefaults !== false) { | ||
82 | + empty = items.default || empty; | ||
83 | + } | ||
84 | + } | ||
85 | + } | ||
86 | + const newModel = this.state.model; | ||
87 | + newModel.push(empty); | ||
88 | + const newKeys = this.state.keys; | ||
89 | + let key = 0; | ||
90 | + if (newKeys.length > 0) { | ||
91 | + key = newKeys[newKeys.length - 1] + 1; | ||
92 | + } | ||
93 | + newKeys.push(key); | ||
94 | + this.setState({ | ||
95 | + model: newModel, | ||
96 | + keys: newKeys | ||
97 | + } | ||
98 | + ); | ||
99 | + this.props.onChangeValidate(this.state.model); | ||
100 | + } | ||
101 | + | ||
102 | + onDelete(index: number) { | ||
103 | + const newModel = this.state.model; | ||
104 | + newModel.splice(index, 1); | ||
105 | + const newKeys = this.state.keys; | ||
106 | + newKeys.splice(index, 1); | ||
107 | + this.setState( | ||
108 | + { | ||
109 | + model: newModel, | ||
110 | + keys: newKeys | ||
111 | + } | ||
112 | + ); | ||
113 | + this.props.onChangeValidate(this.state.model); | ||
114 | + } | ||
115 | + | ||
116 | + setIndex(index: number) { | ||
117 | + return (form: JsonFormData) => { | ||
118 | + if (form.key) { | ||
119 | + form.key[form.key.indexOf('')] = index; | ||
120 | + } | ||
121 | + }; | ||
122 | + } | ||
123 | + | ||
124 | + copyWithIndex(form: JsonFormData, index: number): JsonFormData { | ||
125 | + const copy: JsonFormData = _.cloneDeep(form); | ||
126 | + copy.arrayIndex = index; | ||
127 | + JsonFormUtils.traverseForm(copy, this.setIndex(index)); | ||
128 | + return copy; | ||
129 | + } | ||
130 | + | ||
131 | + render() { | ||
132 | + const arrays = []; | ||
133 | + const fields = []; | ||
134 | + const model = this.state.model; | ||
135 | + const keys = this.state.keys; | ||
136 | + const items = this.props.form.items; | ||
137 | + for (let i = 0; i < model.length; i++ ) { | ||
138 | + let removeButton: JSX.Element = null; | ||
139 | + if (!this.props.form.readonly) { | ||
140 | + const boundOnDelete = this.onDelete.bind(this, i); | ||
141 | + removeButton = <Tooltip title='Remove'><IconButton onClick={boundOnDelete}><ClearIcon/></IconButton></Tooltip>; | ||
142 | + } | ||
143 | + const forms = (this.props.form.items as JsonFormData[]).map((form, index) => { | ||
144 | + const copy = this.copyWithIndex(form, i); | ||
145 | + return this.props.builder(copy, this.props.model, index, this.props.onChange, | ||
146 | + this.props.onColorClick, this.props.onToggleFullscreen, this.props.mapper); | ||
147 | + }); | ||
148 | + arrays.push( | ||
149 | + <li key={keys[i]} className='list-group-item'> | ||
150 | + {removeButton} | ||
151 | + {forms} | ||
152 | + </li> | ||
153 | + ); | ||
154 | + } | ||
155 | + let addButton: JSX.Element = null; | ||
156 | + if (!this.props.form.readonly) { | ||
157 | + addButton = <Button variant='contained' | ||
158 | + color='primary' | ||
159 | + startIcon={<AddIcon/>} | ||
160 | + onClick={this.onAppend}>{this.props.form.add || 'New'}</Button>; | ||
161 | + } | ||
162 | + | ||
163 | + return ( | ||
164 | + <div> | ||
165 | + <div className='tb-container'> | ||
166 | + <div className='tb-head-label'>{this.props.form.title}</div> | ||
167 | + <ol className='list-group'> | ||
168 | + {arrays} | ||
169 | + </ol> | ||
170 | + </div> | ||
171 | + {addButton} | ||
172 | + </div> | ||
173 | + ); | ||
174 | + } | ||
175 | +} | ||
176 | + | ||
177 | +export default ThingsboardBaseComponent(ThingsboardArray); |
@@ -26,7 +26,7 @@ class ThingsboardCheckbox extends React.Component<JsonFormFieldProps, JsonFormFi | @@ -26,7 +26,7 @@ class ThingsboardCheckbox extends React.Component<JsonFormFieldProps, JsonFormFi | ||
26 | <FormControlLabel | 26 | <FormControlLabel |
27 | control={ | 27 | control={ |
28 | <Checkbox | 28 | <Checkbox |
29 | - name={this.props.form.key.slice(-1)[0]} | 29 | + name={this.props.form.key.slice(-1)[0] + ''} |
30 | value={this.props.form.key.slice(-1)[0]} | 30 | value={this.props.form.key.slice(-1)[0]} |
31 | defaultChecked={this.props.value || false} | 31 | defaultChecked={this.props.value || false} |
32 | disabled={this.props.form.readonly} | 32 | disabled={this.props.form.readonly} |
1 | +/* | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +import * as React from 'react'; | ||
17 | +import ThingsboardBaseComponent from './json-form-base-component'; | ||
18 | +import DateFnsUtils from '@date-io/date-fns'; | ||
19 | +import { | ||
20 | + MuiPickersUtilsProvider, | ||
21 | + KeyboardDatePicker | ||
22 | +} from '@material-ui/pickers'; | ||
23 | +import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; | ||
24 | + | ||
25 | +interface ThingsboardDateState extends JsonFormFieldState { | ||
26 | + currentValue: Date | null; | ||
27 | +} | ||
28 | + | ||
29 | +class ThingsboardDate extends React.Component<JsonFormFieldProps, ThingsboardDateState> { | ||
30 | + | ||
31 | + constructor(props) { | ||
32 | + super(props); | ||
33 | + this.onDatePicked = this.onDatePicked.bind(this); | ||
34 | + let value: Date | null = null; | ||
35 | + if (this.props.value && typeof this.props.value === 'number') { | ||
36 | + value = new Date(this.props.value); | ||
37 | + } | ||
38 | + this.state = { | ||
39 | + currentValue: value | ||
40 | + }; | ||
41 | + } | ||
42 | + | ||
43 | + | ||
44 | + onDatePicked(date: Date | null) { | ||
45 | + this.setState({ | ||
46 | + currentValue: date | ||
47 | + }); | ||
48 | + this.props.onChangeValidate(date ? date.getTime() : null); | ||
49 | + } | ||
50 | + | ||
51 | + render() { | ||
52 | + | ||
53 | + let fieldClass = 'tb-date-field'; | ||
54 | + if (this.props.form.required) { | ||
55 | + fieldClass += ' tb-required'; | ||
56 | + } | ||
57 | + if (this.props.form.readonly) { | ||
58 | + fieldClass += ' tb-readonly'; | ||
59 | + } | ||
60 | + | ||
61 | + return ( | ||
62 | + <MuiPickersUtilsProvider utils={DateFnsUtils}> | ||
63 | + <div style={{width: '100%', display: 'block'}}> | ||
64 | + <KeyboardDatePicker | ||
65 | + disableToolbar | ||
66 | + variant='inline' | ||
67 | + format='MM/dd/yyyy' | ||
68 | + margin='normal' | ||
69 | + className={fieldClass} | ||
70 | + label={this.props.form.title} | ||
71 | + value={this.state.currentValue} | ||
72 | + onChange={this.onDatePicked} | ||
73 | + disabled={this.props.form.readonly} | ||
74 | + style={this.props.form.style || {width: '100%'}}/> | ||
75 | + | ||
76 | + </div> | ||
77 | + </MuiPickersUtilsProvider> | ||
78 | + ); | ||
79 | + } | ||
80 | +} | ||
81 | + | ||
82 | +export default ThingsboardBaseComponent(ThingsboardDate); |
1 | +/* | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +import * as React from 'react'; | ||
17 | +import { | ||
18 | + JsonFormData, | ||
19 | + JsonFormFieldProps, | ||
20 | + JsonFormFieldState | ||
21 | +} from '@shared/components/json-form/react/json-form.models'; | ||
22 | + | ||
23 | +class ThingsboardFieldSet extends React.Component<JsonFormFieldProps, JsonFormFieldState> { | ||
24 | + | ||
25 | + render() { | ||
26 | + const forms = (this.props.form.items as JsonFormData[]).map((form: JsonFormData, index) => { | ||
27 | + return this.props.builder(form, this.props.model, index, this.props.onChange, | ||
28 | + this.props.onColorClick, this.props.onToggleFullscreen, this.props.mapper); | ||
29 | + }); | ||
30 | + | ||
31 | + return ( | ||
32 | + <div style={{paddingTop: '20px'}}> | ||
33 | + <div className='tb-head-label'> | ||
34 | + {this.props.form.title} | ||
35 | + </div> | ||
36 | + <div> | ||
37 | + {forms} | ||
38 | + </div> | ||
39 | + </div> | ||
40 | + ); | ||
41 | + } | ||
42 | +} | ||
43 | + | ||
44 | +export default ThingsboardFieldSet; |
@@ -39,7 +39,7 @@ class ThingsboardRcSelect extends React.Component<JsonFormFieldProps, Thingsboar | @@ -39,7 +39,7 @@ class ThingsboardRcSelect extends React.Component<JsonFormFieldProps, Thingsboar | ||
39 | this.onFocus = this.onFocus.bind(this); | 39 | this.onFocus = this.onFocus.bind(this); |
40 | this.state = { | 40 | this.state = { |
41 | currentValue: this.keyToCurrentValue(this.props.value, this.props.form.schema.type === 'array'), | 41 | currentValue: this.keyToCurrentValue(this.props.value, this.props.form.schema.type === 'array'), |
42 | - items: this.props.form.items, | 42 | + items: this.props.form.items as KeyLabelItem[], |
43 | focused: false | 43 | focused: false |
44 | }; | 44 | }; |
45 | } | 45 | } |
@@ -13,8 +13,6 @@ | @@ -13,8 +13,6 @@ | ||
13 | * See the License for the specific language governing permissions and | 13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. | 14 | * limitations under the License. |
15 | */ | 15 | */ |
16 | -import './json-form.scss'; | ||
17 | - | ||
18 | import * as React from 'react'; | 16 | import * as React from 'react'; |
19 | import { createMuiTheme, ThemeProvider } from '@material-ui/core/styles'; | 17 | import { createMuiTheme, ThemeProvider } from '@material-ui/core/styles'; |
20 | import thingsboardTheme from './styles/thingsboardTheme'; | 18 | import thingsboardTheme from './styles/thingsboardTheme'; |
@@ -16,8 +16,8 @@ | @@ -16,8 +16,8 @@ | ||
16 | import * as React from 'react'; | 16 | import * as React from 'react'; |
17 | import JsonFormUtils from './json-form-utils'; | 17 | import JsonFormUtils from './json-form-utils'; |
18 | 18 | ||
19 | -/*import ThingsboardArray from './json-form-array.jsx'; | ||
20 | -import ThingsboardJavaScript from './json-form-javascript.jsx'; | 19 | +import ThingsboardArray from './json-form-array'; |
20 | +/*import ThingsboardJavaScript from './json-form-javascript.jsx'; | ||
21 | import ThingsboardJson from './json-form-json.jsx'; | 21 | import ThingsboardJson from './json-form-json.jsx'; |
22 | import ThingsboardHtml from './json-form-html.jsx'; | 22 | import ThingsboardHtml from './json-form-html.jsx'; |
23 | import ThingsboardCss from './json-form-css.jsx'; | 23 | import ThingsboardCss from './json-form-css.jsx'; |
@@ -27,11 +27,11 @@ import ThingsboardNumber from './json-form-number'; | @@ -27,11 +27,11 @@ import ThingsboardNumber from './json-form-number'; | ||
27 | import ThingsboardText from './json-form-text'; | 27 | import ThingsboardText from './json-form-text'; |
28 | import ThingsboardSelect from './json-form-select'; | 28 | import ThingsboardSelect from './json-form-select'; |
29 | import ThingsboardRadios from './json-form-radios'; | 29 | import ThingsboardRadios from './json-form-radios'; |
30 | -/*import ThingsboardDate from './json-form-date.jsx'; | ||
31 | -import ThingsboardImage from './json-form-image.jsx';*/ | 30 | +import ThingsboardDate from './json-form-date'; |
31 | +/*import ThingsboardImage from './json-form-image.jsx';*/ | ||
32 | import ThingsboardCheckbox from './json-form-checkbox'; | 32 | import ThingsboardCheckbox from './json-form-checkbox'; |
33 | import ThingsboardHelp from './json-form-help'; | 33 | import ThingsboardHelp from './json-form-help'; |
34 | -/*import ThingsboardFieldSet from './json-form-fieldset.jsx';*/ | 34 | +import ThingsboardFieldSet from './json-form-fieldset'; |
35 | import { JsonFormProps, GroupInfo, JsonFormData } from './json-form.models'; | 35 | import { JsonFormProps, GroupInfo, JsonFormData } from './json-form.models'; |
36 | 36 | ||
37 | import _ from 'lodash'; | 37 | import _ from 'lodash'; |
@@ -51,18 +51,18 @@ class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> { | @@ -51,18 +51,18 @@ class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> { | ||
51 | textarea: ThingsboardText, | 51 | textarea: ThingsboardText, |
52 | select: ThingsboardSelect, | 52 | select: ThingsboardSelect, |
53 | radios: ThingsboardRadios, | 53 | radios: ThingsboardRadios, |
54 | - // date: ThingsboardDate, | 54 | + date: ThingsboardDate, |
55 | // image: ThingsboardImage, | 55 | // image: ThingsboardImage, |
56 | checkbox: ThingsboardCheckbox, | 56 | checkbox: ThingsboardCheckbox, |
57 | help: ThingsboardHelp, | 57 | help: ThingsboardHelp, |
58 | - // array: ThingsboardArray, | 58 | + array: ThingsboardArray, |
59 | // javascript: ThingsboardJavaScript, | 59 | // javascript: ThingsboardJavaScript, |
60 | // json: ThingsboardJson, | 60 | // json: ThingsboardJson, |
61 | // html: ThingsboardHtml, | 61 | // html: ThingsboardHtml, |
62 | // css: ThingsboardCss, | 62 | // css: ThingsboardCss, |
63 | // color: ThingsboardColor, | 63 | // color: ThingsboardColor, |
64 | 'rc-select': ThingsboardRcSelect, | 64 | 'rc-select': ThingsboardRcSelect, |
65 | - // fieldset: ThingsboardFieldSet | 65 | + fieldset: ThingsboardFieldSet |
66 | }; | 66 | }; |
67 | 67 | ||
68 | this.onChange = this.onChange.bind(this); | 68 | this.onChange = this.onChange.bind(this); |
@@ -71,7 +71,7 @@ class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> { | @@ -71,7 +71,7 @@ class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> { | ||
71 | this.hasConditions = false; | 71 | this.hasConditions = false; |
72 | } | 72 | } |
73 | 73 | ||
74 | - onChange(key: string | string[], val: any) { | 74 | + onChange(key: (string | number)[], val: any) { |
75 | this.props.onModelChange(key, val); | 75 | this.props.onModelChange(key, val); |
76 | if (this.hasConditions) { | 76 | if (this.hasConditions) { |
77 | this.forceUpdate(); | 77 | this.forceUpdate(); |
@@ -90,7 +90,7 @@ class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> { | @@ -90,7 +90,7 @@ class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> { | ||
90 | builder(form: JsonFormData, | 90 | builder(form: JsonFormData, |
91 | model: any, | 91 | model: any, |
92 | index: number, | 92 | index: number, |
93 | - onChange: (key: string | string[], val: any) => void, | 93 | + onChange: (key: (string | number)[], val: any) => void, |
94 | onColorClick: (event: MouseEvent, key: string, val: string) => void, | 94 | onColorClick: (event: MouseEvent, key: string, val: string) => void, |
95 | onToggleFullscreen: () => void, | 95 | onToggleFullscreen: () => void, |
96 | mapper: {[type: string]: any}): JSX.Element { | 96 | mapper: {[type: string]: any}): JSX.Element { |
@@ -17,7 +17,13 @@ | @@ -17,7 +17,13 @@ | ||
17 | import * as tv from 'tv4'; | 17 | import * as tv from 'tv4'; |
18 | import ObjectPath from 'objectpath'; | 18 | import ObjectPath from 'objectpath'; |
19 | import _ from 'lodash'; | 19 | import _ from 'lodash'; |
20 | -import { SchemaValidationResult, DefaultsFormOptions, FormOption } from './json-form.models'; | 20 | +import { |
21 | + SchemaValidationResult, | ||
22 | + DefaultsFormOptions, | ||
23 | + FormOption, | ||
24 | + JsonSchemaData, | ||
25 | + JsonFormData | ||
26 | +} from './json-form.models'; | ||
21 | import { isDefined, isString, isUndefined } from '@core/utils'; | 27 | import { isDefined, isString, isUndefined } from '@core/utils'; |
22 | import * as equal from 'deep-equal'; | 28 | import * as equal from 'deep-equal'; |
23 | 29 | ||
@@ -433,7 +439,7 @@ function merge(schema: any, form: any[], ignore: { [key: string]: boolean }, opt | @@ -433,7 +439,7 @@ function merge(schema: any, form: any[], ignore: { [key: string]: boolean }, opt | ||
433 | })); | 439 | })); |
434 | } | 440 | } |
435 | 441 | ||
436 | -function selectOrSet(projection: string | string[], obj: any, valueToSet?: any): any { | 442 | +function selectOrSet(projection: string | (string | number)[], obj: any, valueToSet?: any): any { |
437 | const numRe = /^\d+$/; | 443 | const numRe = /^\d+$/; |
438 | 444 | ||
439 | if (!obj) { | 445 | if (!obj) { |
@@ -474,7 +480,7 @@ function selectOrSet(projection: string | string[], obj: any, valueToSet?: any): | @@ -474,7 +480,7 @@ function selectOrSet(projection: string | string[], obj: any, valueToSet?: any): | ||
474 | return value; | 480 | return value; |
475 | } | 481 | } |
476 | 482 | ||
477 | -function updateValue(projection: string | string[], obj: any, valueToSet: any): boolean { | 483 | +function updateValue(projection: string | (string | number)[], obj: any, valueToSet: any): boolean { |
478 | const numRe = /^\d+$/; | 484 | const numRe = /^\d+$/; |
479 | 485 | ||
480 | if (!obj) { | 486 | if (!obj) { |
@@ -531,12 +537,55 @@ function setValue(obj: any, key: string, val: any): boolean { | @@ -531,12 +537,55 @@ function setValue(obj: any, key: string, val: any): boolean { | ||
531 | return changed; | 537 | return changed; |
532 | } | 538 | } |
533 | 539 | ||
540 | +function traverseSchema(schema: JsonSchemaData, fn: (prop: any, path: string[]) => any, path?: string[], ignoreArrays?: boolean) { | ||
541 | + ignoreArrays = typeof ignoreArrays !== 'undefined' ? ignoreArrays : true; | ||
542 | + | ||
543 | + path = path || []; | ||
544 | + | ||
545 | + const traverse = ($schema: JsonSchemaData, $fn: (prop: any, path: string[]) => any, $path: string[]) => { | ||
546 | + $fn($schema, $path); | ||
547 | + for (const k of Object.keys($schema.properties)) { | ||
548 | + if ($schema.properties.hasOwnProperty(k)) { | ||
549 | + const currentPath = $path.slice(); | ||
550 | + currentPath.push(k); | ||
551 | + traverse($schema.properties[k], $fn, currentPath); | ||
552 | + } | ||
553 | + } | ||
554 | + if (!ignoreArrays && $schema.items) { | ||
555 | + const arrPath = $path.slice(); | ||
556 | + arrPath.push(''); | ||
557 | + traverse($schema.items, $fn, arrPath); | ||
558 | + } | ||
559 | + }; | ||
560 | + | ||
561 | + traverse(schema, fn, path || []); | ||
562 | +} | ||
563 | + | ||
564 | +function traverseForm(form: JsonFormData, fn: (form: JsonFormData) => any) { | ||
565 | + fn(form); | ||
566 | + if (form.items) { | ||
567 | + form.items.forEach((f) => { | ||
568 | + traverseForm(f, fn); | ||
569 | + }); | ||
570 | + } | ||
571 | + | ||
572 | + if (form.tabs) { | ||
573 | + form.tabs.forEach((tab) => { | ||
574 | + tab.items.forEach((f) => { | ||
575 | + traverseForm(f, fn); | ||
576 | + }); | ||
577 | + }); | ||
578 | + } | ||
579 | +} | ||
580 | + | ||
534 | 581 | ||
535 | const utils = { | 582 | const utils = { |
536 | validateBySchema, | 583 | validateBySchema, |
537 | validate, | 584 | validate, |
538 | merge, | 585 | merge, |
539 | updateValue, | 586 | updateValue, |
540 | - selectOrSet | 587 | + selectOrSet, |
588 | + traverseSchema, | ||
589 | + traverseForm | ||
541 | }; | 590 | }; |
542 | export default utils; | 591 | export default utils; |
@@ -55,7 +55,7 @@ export interface JsonFormProps { | @@ -55,7 +55,7 @@ export interface JsonFormProps { | ||
55 | isFullscreen: boolean; | 55 | isFullscreen: boolean; |
56 | ignore?: {[key: string]: boolean}; | 56 | ignore?: {[key: string]: boolean}; |
57 | option: FormOption; | 57 | option: FormOption; |
58 | - onModelChange?: (key: string | string[], val: any) => void; | 58 | + onModelChange?: (key: (string | number)[], val: any) => void; |
59 | onColorClick?: (event: MouseEvent, key: string, val: string) => void; | 59 | onColorClick?: (event: MouseEvent, key: string, val: string) => void; |
60 | onToggleFullscreen?: () => void; | 60 | onToggleFullscreen?: () => void; |
61 | mapper?: {[type: string]: any}; | 61 | mapper?: {[type: string]: any}; |
@@ -67,9 +67,16 @@ export interface KeyLabelItem { | @@ -67,9 +67,16 @@ export interface KeyLabelItem { | ||
67 | value?: string; | 67 | value?: string; |
68 | } | 68 | } |
69 | 69 | ||
70 | +export interface JsonSchemaData { | ||
71 | + type: string; | ||
72 | + default: any; | ||
73 | + items?: JsonSchemaData; | ||
74 | + properties?: any; | ||
75 | +} | ||
76 | + | ||
70 | export interface JsonFormData { | 77 | export interface JsonFormData { |
71 | type: string; | 78 | type: string; |
72 | - key: string | string[]; | 79 | + key: (string | number)[]; |
73 | title: string; | 80 | title: string; |
74 | readonly: boolean; | 81 | readonly: boolean; |
75 | required: boolean; | 82 | required: boolean; |
@@ -79,16 +86,15 @@ export interface JsonFormData { | @@ -79,16 +86,15 @@ export interface JsonFormData { | ||
79 | rows?: number; | 86 | rows?: number; |
80 | rowsMax?: number; | 87 | rowsMax?: number; |
81 | placeholder?: string; | 88 | placeholder?: string; |
82 | - schema: { | ||
83 | - type: string; | ||
84 | - default: any; | ||
85 | - }; | 89 | + schema: JsonSchemaData; |
86 | titleMap: { | 90 | titleMap: { |
87 | value: any; | 91 | value: any; |
88 | name: string; | 92 | name: string; |
89 | }[]; | 93 | }[]; |
90 | - items?: Array<KeyLabelItem>; | 94 | + items?: Array<KeyLabelItem> | Array<JsonFormData>; |
95 | + tabs?: Array<JsonFormData>; | ||
91 | tags?: any; | 96 | tags?: any; |
97 | + startEmpty?: boolean; | ||
92 | [key: string]: any; | 98 | [key: string]: any; |
93 | } | 99 | } |
94 | 100 | ||
@@ -99,17 +105,20 @@ export interface JsonFormFieldProps { | @@ -99,17 +105,20 @@ export interface JsonFormFieldProps { | ||
99 | builder: (form: JsonFormData, | 105 | builder: (form: JsonFormData, |
100 | model: any, | 106 | model: any, |
101 | index: number, | 107 | index: number, |
102 | - onChange: (key: string | string[], val: any) => void, | 108 | + onChange: (key: (string | number)[], val: any) => void, |
103 | onColorClick: (event: MouseEvent, key: string, val: string) => void, | 109 | onColorClick: (event: MouseEvent, key: string, val: string) => void, |
104 | onToggleFullscreen: () => void, | 110 | onToggleFullscreen: () => void, |
105 | mapper: {[type: string]: any}) => JSX.Element; | 111 | mapper: {[type: string]: any}) => JSX.Element; |
106 | mapper?: {[type: string]: any}; | 112 | mapper?: {[type: string]: any}; |
107 | - onChange?: (key: string | string[], val: any) => void; | 113 | + onChange?: (key: (string | number)[], val: any) => void; |
108 | onColorClick?: (event: MouseEvent, key: string, val: string) => void; | 114 | onColorClick?: (event: MouseEvent, key: string, val: string) => void; |
109 | onChangeValidate?: (e: any) => void; | 115 | onChangeValidate?: (e: any) => void; |
110 | onToggleFullscreen?: () => void; | 116 | onToggleFullscreen?: () => void; |
111 | valid?: boolean; | 117 | valid?: boolean; |
112 | error?: string; | 118 | error?: string; |
119 | + options?: { | ||
120 | + setSchemaDefaults?: boolean; | ||
121 | + }; | ||
113 | } | 122 | } |
114 | 123 | ||
115 | export interface JsonFormFieldState { | 124 | export interface JsonFormFieldState { |