Showing
34 changed files
with
1151 additions
and
360 deletions
@@ -3767,9 +3767,9 @@ | @@ -3767,9 +3767,9 @@ | ||
3767 | } | 3767 | } |
3768 | }, | 3768 | }, |
3769 | "date-fns": { | 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==" | 3770 | + "version": "2.1.0", |
3771 | + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.1.0.tgz", | ||
3772 | + "integrity": "sha512-eKeLk3sLCnxB/0PN4t1+zqDtSs4jb4mXRSTZ2okmx/myfWyDqeO4r5nnmA5LClJiCwpuTMeK2v5UQPuE4uMaxA==" | ||
3773 | }, | 3773 | }, |
3774 | "date-format": { | 3774 | "date-format": { |
3775 | "version": "2.1.0", | 3775 | "version": "2.1.0", |
@@ -4164,9 +4164,9 @@ | @@ -4164,9 +4164,9 @@ | ||
4164 | "dev": true | 4164 | "dev": true |
4165 | }, | 4165 | }, |
4166 | "electron-to-chromium": { | 4166 | "electron-to-chromium": { |
4167 | - "version": "1.3.284", | ||
4168 | - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.284.tgz", | ||
4169 | - "integrity": "sha512-duOA4IWKH4R8ttiE8q/7xfg6eheRvMKlGqOOcGlDukdHEDJ26Wf7cMrCiK9Am11mswR6E/a23jXVA4UPDthTIw==", | 4167 | + "version": "1.3.285", |
4168 | + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.285.tgz", | ||
4169 | + "integrity": "sha512-DYR9KW723sUbGK++DCmCmM95AbNXT4Q0tlCFMcYijFjayhuDqlGYR68OemlP8MJj0gjkwdeItIUfd0oLCgw+4A==", | ||
4170 | "dev": true | 4170 | "dev": true |
4171 | }, | 4171 | }, |
4172 | "elliptic": { | 4172 | "elliptic": { |
@@ -41,7 +41,7 @@ | @@ -41,7 +41,7 @@ | ||
41 | "base64-js": "^1.3.1", | 41 | "base64-js": "^1.3.1", |
42 | "compass-sass-mixins": "^0.12.7", | 42 | "compass-sass-mixins": "^0.12.7", |
43 | "core-js": "^3.1.4", | 43 | "core-js": "^3.1.4", |
44 | - "date-fns": "^2.5.0", | 44 | + "date-fns": "2.1.0", |
45 | "deep-equal": "^1.0.1", | 45 | "deep-equal": "^1.0.1", |
46 | "flot": "git://github.com/thingsboard/flot.git#0.9-work", | 46 | "flot": "git://github.com/thingsboard/flot.git#0.9-work", |
47 | "flot.curvedlines": "git://github.com/MichaelZinsmaier/CurvedLines.git#master", | 47 | "flot.curvedlines": "git://github.com/MichaelZinsmaier/CurvedLines.git#master", |
@@ -21,7 +21,7 @@ | @@ -21,7 +21,7 @@ | ||
21 | #entityAliasInput | 21 | #entityAliasInput |
22 | formControlName="entityAlias" | 22 | formControlName="entityAlias" |
23 | (focusin)="onFocus()" | 23 | (focusin)="onFocus()" |
24 | - [required]="required" | 24 | + [required]="tbRequired" |
25 | (keydown)="entityAliasEnter($event)" | 25 | (keydown)="entityAliasEnter($event)" |
26 | (keypress)="entityAliasEnter($event)" | 26 | (keypress)="entityAliasEnter($event)" |
27 | [matAutocomplete]="entityAliasAutocomplete"> | 27 | [matAutocomplete]="entityAliasAutocomplete"> |
@@ -54,7 +54,7 @@ | @@ -54,7 +54,7 @@ | ||
54 | </div> | 54 | </div> |
55 | </mat-option> | 55 | </mat-option> |
56 | </mat-autocomplete> | 56 | </mat-autocomplete> |
57 | - <mat-error *ngIf="!modelValue && required"> | 57 | + <mat-error *ngIf="!modelValue && tbRequired"> |
58 | {{ 'entity.alias-required' | translate }} | 58 | {{ 'entity.alias-required' | translate }} |
59 | </mat-error> | 59 | </mat-error> |
60 | </mat-form-field> | 60 | </mat-form-field> |
@@ -75,11 +75,11 @@ export class EntityAliasSelectComponent implements ControlValueAccessor, OnInit, | @@ -75,11 +75,11 @@ export class EntityAliasSelectComponent implements ControlValueAccessor, OnInit, | ||
75 | 75 | ||
76 | 76 | ||
77 | private requiredValue: boolean; | 77 | private requiredValue: boolean; |
78 | - get required(): boolean { | 78 | + get tbRequired(): boolean { |
79 | return this.requiredValue; | 79 | return this.requiredValue; |
80 | } | 80 | } |
81 | @Input() | 81 | @Input() |
82 | - set required(value: boolean) { | 82 | + set tbRequired(value: boolean) { |
83 | this.requiredValue = coerceBooleanProperty(value); | 83 | this.requiredValue = coerceBooleanProperty(value); |
84 | } | 84 | } |
85 | 85 | ||
@@ -151,7 +151,7 @@ export class EntityAliasSelectComponent implements ControlValueAccessor, OnInit, | @@ -151,7 +151,7 @@ export class EntityAliasSelectComponent implements ControlValueAccessor, OnInit, | ||
151 | 151 | ||
152 | isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { | 152 | isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { |
153 | const originalErrorState = this.errorStateMatcher.isErrorState(control, form); | 153 | const originalErrorState = this.errorStateMatcher.isErrorState(control, form); |
154 | - const customErrorState = this.required && !this.modelValue; | 154 | + const customErrorState = this.tbRequired && !this.modelValue; |
155 | return originalErrorState || customErrorState; | 155 | return originalErrorState || customErrorState; |
156 | } | 156 | } |
157 | 157 |
@@ -16,9 +16,8 @@ | @@ -16,9 +16,8 @@ | ||
16 | 16 | ||
17 | --> | 17 | --> |
18 | <mat-tab-group class="tb-datakey-config" [ngClass]="{'tb-headless': !displayAdvanced}" | 18 | <mat-tab-group class="tb-datakey-config" [ngClass]="{'tb-headless': !displayAdvanced}" |
19 | - [formGroup]="dataKeyFormGroup" | ||
20 | class="tb-datakey-config"> | 19 | class="tb-datakey-config"> |
21 | - <mat-tab label="{{ 'datakey.settings' | translate }}"> | 20 | + <mat-tab [formGroup]="dataKeyFormGroup" label="{{ 'datakey.settings' | translate }}"> |
22 | <div class="mat-content mat-padding" fxLayout="column"> | 21 | <div class="mat-content mat-padding" fxLayout="column"> |
23 | <mat-form-field class="mat-block" *ngIf="modelValue.type !== dataKeyTypes.function"> | 22 | <mat-form-field class="mat-block" *ngIf="modelValue.type !== dataKeyTypes.function"> |
24 | <mat-label>{{ 'entity.key' | translate }}</mat-label> | 23 | <mat-label>{{ 'entity.key' | translate }}</mat-label> |
@@ -94,12 +93,10 @@ | @@ -94,12 +93,10 @@ | ||
94 | </section> | 93 | </section> |
95 | </div> | 94 | </div> |
96 | </mat-tab> | 95 | </mat-tab> |
97 | - <mat-tab label="{{ 'datakey.advanced' | translate }}" *ngIf="displayAdvanced"> | 96 | + <mat-tab [formGroup]="dataKeySettingsFormGroup" label="{{ 'datakey.advanced' | translate }}" *ngIf="displayAdvanced"> |
98 | <div class="mat-content mat-padding" fxLayout="column"> | 97 | <div class="mat-content mat-padding" fxLayout="column"> |
99 | <div style="overflow: auto;"> | 98 | <div style="overflow: auto;"> |
100 | <tb-json-form | 99 | <tb-json-form |
101 | - [schema]="dataKeySchema" | ||
102 | - [form]="dataKeyForm" | ||
103 | formControlName="settings"> | 100 | formControlName="settings"> |
104 | </tb-json-form> | 101 | </tb-json-form> |
105 | </div> | 102 | </div> |
@@ -39,6 +39,7 @@ import { Observable, of } from 'rxjs'; | @@ -39,6 +39,7 @@ import { Observable, of } from 'rxjs'; | ||
39 | import { map, mergeMap, tap } from 'rxjs/operators'; | 39 | import { map, mergeMap, tap } from 'rxjs/operators'; |
40 | import { alarmFields } from '@shared/models/alarm.models'; | 40 | import { alarmFields } from '@shared/models/alarm.models'; |
41 | import { JsFuncComponent } from '@shared/components/js-func.component'; | 41 | import { JsFuncComponent } from '@shared/components/js-func.component'; |
42 | +import { JsonFormComponentData } from '@shared/components/json-form/json-form-component.models'; | ||
42 | 43 | ||
43 | @Component({ | 44 | @Component({ |
44 | selector: 'tb-data-key-config', | 45 | selector: 'tb-data-key-config', |
@@ -77,15 +78,16 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con | @@ -77,15 +78,16 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con | ||
77 | 78 | ||
78 | displayAdvanced = false; | 79 | displayAdvanced = false; |
79 | 80 | ||
80 | - dataKeySchema: any; | ||
81 | - dataKeyForm: any; | ||
82 | - | ||
83 | private modelValue: DataKey; | 81 | private modelValue: DataKey; |
84 | 82 | ||
85 | private propagateChange = null; | 83 | private propagateChange = null; |
86 | 84 | ||
87 | public dataKeyFormGroup: FormGroup; | 85 | public dataKeyFormGroup: FormGroup; |
88 | 86 | ||
87 | + public dataKeySettingsFormGroup: FormGroup; | ||
88 | + | ||
89 | + private dataKeySettingsData: JsonFormComponentData; | ||
90 | + | ||
89 | private alarmKeys: Array<DataKey>; | 91 | private alarmKeys: Array<DataKey>; |
90 | 92 | ||
91 | filteredKeys: Observable<Array<string>>; | 93 | filteredKeys: Observable<Array<string>>; |
@@ -112,8 +114,16 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con | @@ -112,8 +114,16 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con | ||
112 | } | 114 | } |
113 | if (this.dataKeySettingsSchema && this.dataKeySettingsSchema.schema) { | 115 | if (this.dataKeySettingsSchema && this.dataKeySettingsSchema.schema) { |
114 | this.displayAdvanced = true; | 116 | this.displayAdvanced = true; |
115 | - this.dataKeySchema = this.dataKeySettingsSchema.schema; | ||
116 | - this.dataKeyForm = this.dataKeySettingsSchema.form || ['*']; | 117 | + this.dataKeySettingsData = { |
118 | + schema: this.dataKeySettingsSchema.schema, | ||
119 | + form: this.dataKeySettingsSchema.form || ['*'] | ||
120 | + }; | ||
121 | + this.dataKeySettingsFormGroup = this.fb.group({ | ||
122 | + settings: [null, []] | ||
123 | + }); | ||
124 | + this.dataKeySettingsFormGroup.valueChanges.subscribe(() => { | ||
125 | + this.updateModel(); | ||
126 | + }); | ||
117 | } | 127 | } |
118 | this.dataKeyFormGroup = this.fb.group({ | 128 | this.dataKeyFormGroup = this.fb.group({ |
119 | name: [null, []], | 129 | name: [null, []], |
@@ -123,8 +133,7 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con | @@ -123,8 +133,7 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con | ||
123 | decimals: [null, [Validators.min(0), Validators.max(15), Validators.pattern(/^\d*$/)]], | 133 | decimals: [null, [Validators.min(0), Validators.max(15), Validators.pattern(/^\d*$/)]], |
124 | funcBody: [null, []], | 134 | funcBody: [null, []], |
125 | usePostProcessing: [null, []], | 135 | usePostProcessing: [null, []], |
126 | - postFuncBody: [null, []], | ||
127 | - settings: [null, []] | 136 | + postFuncBody: [null, []] |
128 | }); | 137 | }); |
129 | 138 | ||
130 | this.dataKeyFormGroup.valueChanges.subscribe(() => { | 139 | this.dataKeyFormGroup.valueChanges.subscribe(() => { |
@@ -162,10 +171,19 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con | @@ -162,10 +171,19 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con | ||
162 | this.dataKeyFormGroup.patchValue(this.modelValue, {emitEvent: false}); | 171 | this.dataKeyFormGroup.patchValue(this.modelValue, {emitEvent: false}); |
163 | this.dataKeyFormGroup.get('name').setValidators(this.modelValue.type !== DataKeyType.function ? [Validators.required] : []); | 172 | this.dataKeyFormGroup.get('name').setValidators(this.modelValue.type !== DataKeyType.function ? [Validators.required] : []); |
164 | this.dataKeyFormGroup.get('name').updateValueAndValidity({emitEvent: false}); | 173 | this.dataKeyFormGroup.get('name').updateValueAndValidity({emitEvent: false}); |
174 | + if (this.displayAdvanced) { | ||
175 | + this.dataKeySettingsData.model = this.modelValue.settings; | ||
176 | + this.dataKeySettingsFormGroup.patchValue({ | ||
177 | + settings: this.dataKeySettingsData | ||
178 | + }, {emitEvent: false}); | ||
179 | + } | ||
165 | } | 180 | } |
166 | 181 | ||
167 | private updateModel() { | 182 | private updateModel() { |
168 | this.modelValue = {...this.modelValue, ...this.dataKeyFormGroup.value}; | 183 | this.modelValue = {...this.modelValue, ...this.dataKeyFormGroup.value}; |
184 | + if (this.displayAdvanced) { | ||
185 | + this.modelValue.settings = this.dataKeySettingsFormGroup.get('settings').value.model; | ||
186 | + } | ||
169 | this.propagateChange(this.modelValue); | 187 | this.propagateChange(this.modelValue); |
170 | } | 188 | } |
171 | 189 | ||
@@ -222,6 +240,13 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con | @@ -222,6 +240,13 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con | ||
222 | } | 240 | } |
223 | }; | 241 | }; |
224 | } | 242 | } |
243 | + if (this.displayAdvanced && !this.dataKeySettingsFormGroup.valid) { | ||
244 | + return { | ||
245 | + dataKeySettings: { | ||
246 | + valid: false | ||
247 | + } | ||
248 | + }; | ||
249 | + } | ||
225 | return null; | 250 | return null; |
226 | } | 251 | } |
227 | } | 252 | } |
@@ -14,15 +14,6 @@ | @@ -14,15 +14,6 @@ | ||
14 | * limitations under the License. | 14 | * limitations under the License. |
15 | */ | 15 | */ |
16 | 16 | ||
17 | -@mixin tb-checkered-bg() { | ||
18 | - background-color: #fff; | ||
19 | - background-image: | ||
20 | - linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd), | ||
21 | - linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd); | ||
22 | - background-position: 0 0, 4px 4px; | ||
23 | - background-size: 8px 8px; | ||
24 | -} | ||
25 | - | ||
26 | :host { | 17 | :host { |
27 | .mat-chip.mat-standard-chip { | 18 | .mat-chip.mat-standard-chip { |
28 | .tb-attribute-chip { | 19 | .tb-attribute-chip { |
@@ -56,27 +47,6 @@ | @@ -56,27 +47,6 @@ | ||
56 | color: inherit; | 47 | color: inherit; |
57 | opacity: inherit; | 48 | opacity: inherit; |
58 | } | 49 | } |
59 | - | ||
60 | - .tb-color-preview { | ||
61 | - cursor: pointer; | ||
62 | - box-sizing: border-box; | ||
63 | - position: relative; | ||
64 | - width: 24px; | ||
65 | - min-width: 24px; | ||
66 | - height: 24px; | ||
67 | - overflow: hidden; | ||
68 | - content: ""; | ||
69 | - border: 2px solid #fff; | ||
70 | - border-radius: 50%; | ||
71 | - box-shadow: 0 3px 1px -2px rgba(0, 0, 0, .14), 0 2px 2px 0 rgba(0, 0, 0, .098), 0 1px 5px 0 rgba(0, 0, 0, .084); | ||
72 | - | ||
73 | - @include tb-checkered-bg(); | ||
74 | - | ||
75 | - .tb-color-result { | ||
76 | - width: 100%; | ||
77 | - height: 100%; | ||
78 | - } | ||
79 | - } | ||
80 | } | 50 | } |
81 | } | 51 | } |
82 | } | 52 | } |
@@ -31,7 +31,7 @@ | @@ -31,7 +31,7 @@ | ||
31 | </mat-checkbox> | 31 | </mat-checkbox> |
32 | </div> | 32 | </div> |
33 | <section fxFlex fxLayout="row" fxLayoutAlign="start center" style="margin-bottom: 16px;"> | 33 | <section fxFlex fxLayout="row" fxLayoutAlign="start center" style="margin-bottom: 16px;"> |
34 | - <span [ngClass]="{'tb-disabled-label': useDashboardTimewindow}" translate style="padding-right: 8px;">widget-config.timewindow</span> | 34 | + <span [ngClass]="{'tb-disabled-label': dataSettings.get('useDashboardTimewindow').value}" translate style="padding-right: 8px;">widget-config.timewindow</span> |
35 | <tb-timewindow asButton="true" | 35 | <tb-timewindow asButton="true" |
36 | aggregation="{{ widgetType === widgetTypes.timeseries }}" | 36 | aggregation="{{ widgetType === widgetTypes.timeseries }}" |
37 | fxFlex formControlName="timewindow"></tb-timewindow> | 37 | fxFlex formControlName="timewindow"></tb-timewindow> |
@@ -64,12 +64,12 @@ | @@ -64,12 +64,12 @@ | ||
64 | <mat-expansion-panel class="tb-datasources" *ngIf="widgetType !== widgetTypes.rpc && | 64 | <mat-expansion-panel class="tb-datasources" *ngIf="widgetType !== widgetTypes.rpc && |
65 | widgetType !== widgetTypes.alarm && | 65 | widgetType !== widgetTypes.alarm && |
66 | widgetType !== widgetTypes.static && | 66 | widgetType !== widgetTypes.static && |
67 | - isDataEnabled" [expanded]="true"> | 67 | + modelValue?.isDataEnabled" [expanded]="true"> |
68 | <mat-expansion-panel-header> | 68 | <mat-expansion-panel-header> |
69 | <mat-panel-title fxLayout="column"> | 69 | <mat-panel-title fxLayout="column"> |
70 | <div class="tb-panel-title" translate>widget-config.datasources</div> | 70 | <div class="tb-panel-title" translate>widget-config.datasources</div> |
71 | - <div *ngIf="typeParameters && typeParameters.maxDatasources > -1" | ||
72 | - class="tb-hint">{{ 'widget-config.maximum-datasources' | translate:{count: typeParameters.maxDatasources} }}</div> | 71 | + <div *ngIf="modelValue?.typeParameters && modelValue?.typeParameters.maxDatasources > -1" |
72 | + class="tb-hint">{{ 'widget-config.maximum-datasources' | translate:{count: modelValue?.typeParameters.maxDatasources} }}</div> | ||
73 | </mat-panel-title> | 73 | </mat-panel-title> |
74 | </mat-expansion-panel-header> | 74 | </mat-expansion-panel-header> |
75 | <div *ngIf="dataSettings.get('datasources').length === 0; else datasourcesTemplate"> | 75 | <div *ngIf="dataSettings.get('datasources').length === 0; else datasourcesTemplate"> |
@@ -90,7 +90,7 @@ | @@ -90,7 +90,7 @@ | ||
90 | <div style="overflow: auto; padding-bottom: 15px;"> | 90 | <div style="overflow: auto; padding-bottom: 15px;"> |
91 | <div fxFlex fxLayout="row" fxLayoutAlign="start center" | 91 | <div fxFlex fxLayout="row" fxLayoutAlign="start center" |
92 | formArrayName="datasources" | 92 | formArrayName="datasources" |
93 | - *ngFor="let datasourceControl of dataSettings.get('datasources').controls; let $index = index"> | 93 | + *ngFor="let datasourceControl of dataSettings.get('datasources').controls; let $index = index;"> |
94 | <span fxFlex="5">{{$index + 1}}.</span> | 94 | <span fxFlex="5">{{$index + 1}}.</span> |
95 | <div [formGroupName]="$index" class="mat-elevation-z4" fxFlex | 95 | <div [formGroupName]="$index" class="mat-elevation-z4" fxFlex |
96 | fxLayout="row" | 96 | fxLayout="row" |
@@ -120,7 +120,7 @@ | @@ -120,7 +120,7 @@ | ||
120 | </ng-template> | 120 | </ng-template> |
121 | <ng-template [ngSwitchCase]="datasourceType.entity"> | 121 | <ng-template [ngSwitchCase]="datasourceType.entity"> |
122 | <tb-entity-alias-select | 122 | <tb-entity-alias-select |
123 | - [required]="datasourceControl.get('type').value === datasourceType.entity" | 123 | + [tbRequired]="datasourceControl.get('type').value === datasourceType.entity" |
124 | [aliasController]="aliasController" | 124 | [aliasController]="aliasController" |
125 | formControlName="entityAliasId" | 125 | formControlName="entityAliasId" |
126 | [callbacks]="widgetConfigCallbacks"> | 126 | [callbacks]="widgetConfigCallbacks"> |
@@ -130,25 +130,15 @@ | @@ -130,25 +130,15 @@ | ||
130 | <tb-data-keys class="tb-data-keys" fxFlex | 130 | <tb-data-keys class="tb-data-keys" fxFlex |
131 | [widgetType]="widgetType" | 131 | [widgetType]="widgetType" |
132 | [datasourceType]="datasourceControl.get('type').value" | 132 | [datasourceType]="datasourceControl.get('type').value" |
133 | - [maxDataKeys]="typeParameters?.maxDataKeys" | ||
134 | - [optDataKeys]="typeParameters?.dataKeysOptional" | 133 | + [maxDataKeys]="modelValue?.typeParameters?.maxDataKeys" |
134 | + [optDataKeys]="modelValue?.typeParameters?.dataKeysOptional" | ||
135 | [aliasController]="aliasController" | 135 | [aliasController]="aliasController" |
136 | - [datakeySettingsSchema]="dataKeySettingsSchema" | 136 | + [datakeySettingsSchema]="modelValue?.dataKeySettingsSchema" |
137 | [callbacks]="widgetConfigCallbacks" | 137 | [callbacks]="widgetConfigCallbacks" |
138 | [entityAliasId]="datasourceControl.get('entityAliasId').value" | 138 | [entityAliasId]="datasourceControl.get('entityAliasId').value" |
139 | formControlName="dataKeys"> | 139 | formControlName="dataKeys"> |
140 | </tb-data-keys> | 140 | </tb-data-keys> |
141 | </section> | 141 | </section> |
142 | - <!--tb-datasource fxFlex | ||
143 | - [widgetType]="widgetType" | ||
144 | - [maxDataKeys]="typeParameters?.maxDataKeys" | ||
145 | - [optDataKeys]="typeParameters?.dataKeysOptional" | ||
146 | - [aliasController]="aliasController" | ||
147 | - [functionsOnly]="functionsOnly" | ||
148 | - [datakeySettingsSchema]="dataKeySettingsSchema" | ||
149 | - [callbacks]="widgetConfigCallbacks" | ||
150 | - [formControl]="datasourceControl" | ||
151 | - ></tb-datasource--> | ||
152 | <button [disabled]="isLoading$ | async" | 142 | <button [disabled]="isLoading$ | async" |
153 | mat-button mat-icon-button color="primary" | 143 | mat-button mat-icon-button color="primary" |
154 | style="min-width: 40px;" | 144 | style="min-width: 40px;" |
@@ -164,8 +154,8 @@ | @@ -164,8 +154,8 @@ | ||
164 | <div fxFlex fxLayout="row" fxLayoutAlign="start center"> | 154 | <div fxFlex fxLayout="row" fxLayoutAlign="start center"> |
165 | <button [disabled]="isLoading$ | async" | 155 | <button [disabled]="isLoading$ | async" |
166 | mat-button mat-raised-button color="primary" | 156 | mat-button mat-raised-button color="primary" |
167 | - [fxShow]="typeParameters && | ||
168 | - (typeParameters.maxDatasources == -1 || dataSettings.get('datasources').controls.length < typeParameters.maxDatasources)" | 157 | + [fxShow]="modelValue?.typeParameters && |
158 | + (modelValue?.typeParameters.maxDatasources == -1 || dataSettings.get('datasources').controls.length < modelValue?.typeParameters.maxDatasources)" | ||
169 | (click)="addDatasource()" | 159 | (click)="addDatasource()" |
170 | matTooltip="{{ 'widget-config.add-datasource' | translate }}" | 160 | matTooltip="{{ 'widget-config.add-datasource' | translate }}" |
171 | matTooltipPosition="above"> | 161 | matTooltipPosition="above"> |
@@ -175,7 +165,7 @@ | @@ -175,7 +165,7 @@ | ||
175 | </div> | 165 | </div> |
176 | </mat-expansion-panel> | 166 | </mat-expansion-panel> |
177 | <mat-expansion-panel class="tb-datasources" *ngIf="widgetType === widgetTypes.rpc && | 167 | <mat-expansion-panel class="tb-datasources" *ngIf="widgetType === widgetTypes.rpc && |
178 | - isDataEnabled" [expanded]="true"> | 168 | + modelValue?.isDataEnabled" [expanded]="true"> |
179 | <mat-expansion-panel-header> | 169 | <mat-expansion-panel-header> |
180 | <mat-panel-title> | 170 | <mat-panel-title> |
181 | {{ 'widget-config.target-device' | translate }} | 171 | {{ 'widget-config.target-device' | translate }} |
@@ -183,7 +173,7 @@ | @@ -183,7 +173,7 @@ | ||
183 | </mat-expansion-panel-header> | 173 | </mat-expansion-panel-header> |
184 | <div [formGroup]="targetDeviceSettings" style="padding: 0 5px;"> | 174 | <div [formGroup]="targetDeviceSettings" style="padding: 0 5px;"> |
185 | <tb-entity-alias-select fxFlex | 175 | <tb-entity-alias-select fxFlex |
186 | - [required]="!widgetEditMode" | 176 | + [tbRequired]="!widgetEditMode" |
187 | [aliasController]="aliasController" | 177 | [aliasController]="aliasController" |
188 | [allowedEntityTypes]="[entityTypes.DEVICE]" | 178 | [allowedEntityTypes]="[entityTypes.DEVICE]" |
189 | [callbacks]="widgetConfigCallbacks" | 179 | [callbacks]="widgetConfigCallbacks" |
@@ -193,6 +183,14 @@ | @@ -193,6 +183,14 @@ | ||
193 | </mat-expansion-panel> | 183 | </mat-expansion-panel> |
194 | </div> | 184 | </div> |
195 | </mat-tab> | 185 | </mat-tab> |
196 | - <mat-tab label="{{ 'widget-config.settings' | translate }}"> | 186 | + <mat-tab *ngIf="displayAdvanced()" label="{{ 'widget-config.advanced' | translate }}"> |
187 | + <div [formGroup]="advancedSettings" class="mat-content mat-padding tb-advanced-widget-config" | ||
188 | + fxLayout="column"> | ||
189 | + <tb-json-form | ||
190 | + formControlName="settings"> | ||
191 | + </tb-json-form> | ||
192 | + </div> | ||
193 | + </mat-tab> | ||
194 | + <mat-tab label="{{ 'widget-config.actions' | translate }}"> | ||
197 | </mat-tab> | 195 | </mat-tab> |
198 | </mat-tab-group> | 196 | </mat-tab-group> |
@@ -25,7 +25,6 @@ import { | @@ -25,7 +25,6 @@ import { | ||
25 | LegendConfig, | 25 | LegendConfig, |
26 | WidgetActionDescriptor, | 26 | WidgetActionDescriptor, |
27 | WidgetActionSource, | 27 | WidgetActionSource, |
28 | - WidgetConfigSettings, | ||
29 | widgetType, | 28 | widgetType, |
30 | WidgetTypeParameters | 29 | WidgetTypeParameters |
31 | } from '@shared/models/widget.models'; | 30 | } from '@shared/models/widget.models'; |
@@ -38,7 +37,7 @@ import { | @@ -38,7 +37,7 @@ import { | ||
38 | FormGroup, | 37 | FormGroup, |
39 | NG_VALIDATORS, | 38 | NG_VALIDATORS, |
40 | NG_VALUE_ACCESSOR, | 39 | NG_VALUE_ACCESSOR, |
41 | - Validator, | 40 | + Validator, ValidatorFn, |
42 | Validators | 41 | Validators |
43 | } from '@angular/forms'; | 42 | } from '@angular/forms'; |
44 | import { WidgetConfigComponentData } from '@home/models/widget-component.models'; | 43 | import { WidgetConfigComponentData } from '@home/models/widget-component.models'; |
@@ -59,6 +58,16 @@ import { | @@ -59,6 +58,16 @@ import { | ||
59 | import { tap, mergeMap, map, catchError } from 'rxjs/operators'; | 58 | import { tap, mergeMap, map, catchError } from 'rxjs/operators'; |
60 | import { MatDialog } from '@angular/material/dialog'; | 59 | import { MatDialog } from '@angular/material/dialog'; |
61 | import { EntityService } from '@core/http/entity.service'; | 60 | import { EntityService } from '@core/http/entity.service'; |
61 | +import { JsonFormComponentData } from '@shared/components/json-form/json-form-component.models'; | ||
62 | + | ||
63 | +const emptySettingsSchema = { | ||
64 | + type: 'object', | ||
65 | + properties: {} | ||
66 | +}; | ||
67 | +const emptySettingsGroupInfoes = []; | ||
68 | +const defaultSettingsForm = [ | ||
69 | + '*' | ||
70 | +]; | ||
62 | 71 | ||
63 | @Component({ | 72 | @Component({ |
64 | selector: 'tb-widget-config', | 73 | selector: 'tb-widget-config', |
@@ -89,27 +98,12 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | @@ -89,27 +98,12 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | ||
89 | forceExpandDatasources: boolean; | 98 | forceExpandDatasources: boolean; |
90 | 99 | ||
91 | @Input() | 100 | @Input() |
92 | - isDataEnabled: boolean; | ||
93 | - | ||
94 | - @Input() | ||
95 | - typeParameters: WidgetTypeParameters; | ||
96 | - | ||
97 | - @Input() | ||
98 | - actionSources: {[key: string]: WidgetActionSource}; | ||
99 | - | ||
100 | - @Input() | ||
101 | aliasController: IAliasController; | 101 | aliasController: IAliasController; |
102 | 102 | ||
103 | @Input() | 103 | @Input() |
104 | entityAliases: EntityAliases; | 104 | entityAliases: EntityAliases; |
105 | 105 | ||
106 | @Input() | 106 | @Input() |
107 | - widgetSettingsSchema: any; | ||
108 | - | ||
109 | - @Input() | ||
110 | - dataKeySettingsSchema: any; | ||
111 | - | ||
112 | - @Input() | ||
113 | functionsOnly: boolean; | 107 | functionsOnly: boolean; |
114 | 108 | ||
115 | @Input() disabled: boolean; | 109 | @Input() disabled: boolean; |
@@ -149,37 +143,20 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | @@ -149,37 +143,20 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | ||
149 | legendConfig: LegendConfig; | 143 | legendConfig: LegendConfig; |
150 | actions: {[actionSourceId: string]: Array<WidgetActionDescriptor>}; | 144 | actions: {[actionSourceId: string]: Array<WidgetActionDescriptor>}; |
151 | alarmSource: Datasource; | 145 | alarmSource: Datasource; |
152 | - settings: WidgetConfigSettings; | ||
153 | mobileOrder: number; | 146 | mobileOrder: number; |
154 | mobileHeight: number; | 147 | mobileHeight: number; |
155 | 148 | ||
156 | - emptySettingsSchema = { | ||
157 | - type: 'object', | ||
158 | - properties: {} | ||
159 | - }; | ||
160 | - | ||
161 | - emptySettingsGroupInfoes = []; | ||
162 | - | ||
163 | - defaultSettingsForm = [ | ||
164 | - '*' | ||
165 | - ]; | ||
166 | - | ||
167 | - currentSettingsSchema = deepClone(this.emptySettingsSchema); | ||
168 | - | ||
169 | - currentSettings: WidgetConfigSettings = {}; | ||
170 | - currentSettingsGroupInfoes = deepClone(this.emptySettingsGroupInfoes); | ||
171 | - | ||
172 | - currentSettingsForm: any; | ||
173 | - | ||
174 | private modelValue: WidgetConfigComponentData; | 149 | private modelValue: WidgetConfigComponentData; |
175 | 150 | ||
176 | private propagateChange = null; | 151 | private propagateChange = null; |
177 | 152 | ||
178 | public dataSettings: FormGroup; | 153 | public dataSettings: FormGroup; |
179 | public targetDeviceSettings: FormGroup; | 154 | public targetDeviceSettings: FormGroup; |
155 | + public advancedSettings: FormGroup; | ||
180 | 156 | ||
181 | private dataSettingsChangesSubscription: Subscription; | 157 | private dataSettingsChangesSubscription: Subscription; |
182 | private targetDeviceSettingsSubscription: Subscription; | 158 | private targetDeviceSettingsSubscription: Subscription; |
159 | + private advancedSettingsSubscription: Subscription; | ||
183 | 160 | ||
184 | constructor(protected store: Store<AppState>, | 161 | constructor(protected store: Store<AppState>, |
185 | private utils: UtilsService, | 162 | private utils: UtilsService, |
@@ -207,6 +184,10 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | @@ -207,6 +184,10 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | ||
207 | this.targetDeviceSettingsSubscription.unsubscribe(); | 184 | this.targetDeviceSettingsSubscription.unsubscribe(); |
208 | this.targetDeviceSettingsSubscription = null; | 185 | this.targetDeviceSettingsSubscription = null; |
209 | } | 186 | } |
187 | + if (this.advancedSettingsSubscription) { | ||
188 | + this.advancedSettingsSubscription.unsubscribe(); | ||
189 | + this.advancedSettingsSubscription = null; | ||
190 | + } | ||
210 | } | 191 | } |
211 | 192 | ||
212 | private createChangeSubscriptions() { | 193 | private createChangeSubscriptions() { |
@@ -216,11 +197,15 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | @@ -216,11 +197,15 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | ||
216 | this.targetDeviceSettingsSubscription = this.targetDeviceSettings.valueChanges.subscribe( | 197 | this.targetDeviceSettingsSubscription = this.targetDeviceSettings.valueChanges.subscribe( |
217 | () => this.updateTargetDeviceSettings() | 198 | () => this.updateTargetDeviceSettings() |
218 | ); | 199 | ); |
200 | + this.advancedSettingsSubscription = this.advancedSettings.valueChanges.subscribe( | ||
201 | + () => this.updateAdvancedSettings() | ||
202 | + ); | ||
219 | } | 203 | } |
220 | 204 | ||
221 | private buildForms() { | 205 | private buildForms() { |
222 | this.dataSettings = this.fb.group({}); | 206 | this.dataSettings = this.fb.group({}); |
223 | this.targetDeviceSettings = this.fb.group({}); | 207 | this.targetDeviceSettings = this.fb.group({}); |
208 | + this.advancedSettings = this.fb.group({}); | ||
224 | if (this.widgetType === widgetType.timeseries || this.widgetType === widgetType.alarm) { | 209 | if (this.widgetType === widgetType.timeseries || this.widgetType === widgetType.alarm) { |
225 | this.dataSettings.addControl('useDashboardTimewindow', this.fb.control(null)); | 210 | this.dataSettings.addControl('useDashboardTimewindow', this.fb.control(null)); |
226 | this.dataSettings.addControl('displayTimewindow', this.fb.control(null)); | 211 | this.dataSettings.addControl('displayTimewindow', this.fb.control(null)); |
@@ -240,7 +225,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | @@ -240,7 +225,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | ||
240 | [Validators.required, Validators.min(1)])); | 225 | [Validators.required, Validators.min(1)])); |
241 | } | 226 | } |
242 | } | 227 | } |
243 | - if (this.isDataEnabled) { | 228 | + if (this.modelValue.isDataEnabled) { |
244 | if (this.widgetType !== widgetType.rpc && | 229 | if (this.widgetType !== widgetType.rpc && |
245 | this.widgetType !== widgetType.alarm && | 230 | this.widgetType !== widgetType.alarm && |
246 | this.widgetType !== widgetType.static) { | 231 | this.widgetType !== widgetType.static) { |
@@ -252,6 +237,8 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | @@ -252,6 +237,8 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | ||
252 | this.widgetEditMode ? [] : [Validators.required])); | 237 | this.widgetEditMode ? [] : [Validators.required])); |
253 | } | 238 | } |
254 | } | 239 | } |
240 | + this.advancedSettings.addControl('settings', | ||
241 | + this.fb.control(null, [])); | ||
255 | } | 242 | } |
256 | 243 | ||
257 | registerOnChange(fn: any): void { | 244 | registerOnChange(fn: any): void { |
@@ -323,7 +310,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | @@ -323,7 +310,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | ||
323 | { timewindow: config.timewindow }, {emitEvent: false} | 310 | { timewindow: config.timewindow }, {emitEvent: false} |
324 | ); | 311 | ); |
325 | } | 312 | } |
326 | - if (this.isDataEnabled) { | 313 | + if (this.modelValue.isDataEnabled) { |
327 | if (this.widgetType !== widgetType.rpc && | 314 | if (this.widgetType !== widgetType.rpc && |
328 | this.widgetType !== widgetType.alarm && | 315 | this.widgetType !== widgetType.alarm && |
329 | this.widgetType !== widgetType.static) { | 316 | this.widgetType !== widgetType.static) { |
@@ -366,9 +353,8 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | @@ -366,9 +353,8 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | ||
366 | } | 353 | } |
367 | } | 354 | } |
368 | } | 355 | } |
369 | - this.settings = config.settings; | ||
370 | 356 | ||
371 | - this.updateSchemaForm(); | 357 | + this.updateSchemaForm(config.settings); |
372 | 358 | ||
373 | if (layout) { | 359 | if (layout) { |
374 | this.mobileOrder = layout.mobileOrder; | 360 | this.mobileOrder = layout.mobileOrder; |
@@ -383,7 +369,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | @@ -383,7 +369,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | ||
383 | } | 369 | } |
384 | 370 | ||
385 | private buildDatasourceForm(datasource?: Datasource): AbstractControl { | 371 | private buildDatasourceForm(datasource?: Datasource): AbstractControl { |
386 | - const dataKeysRequired = !this.typeParameters || !this.typeParameters.dataKeysOptional; | 372 | + const dataKeysRequired = !this.modelValue.typeParameters || !this.modelValue.typeParameters.dataKeysOptional; |
387 | const datasourceFormGroup = this.fb.group( | 373 | const datasourceFormGroup = this.fb.group( |
388 | { | 374 | { |
389 | type: [datasource ? datasource.type : null, [Validators.required]], | 375 | type: [datasource ? datasource.type : null, [Validators.required]], |
@@ -402,18 +388,20 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | @@ -402,18 +388,20 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | ||
402 | return datasourceFormGroup; | 388 | return datasourceFormGroup; |
403 | } | 389 | } |
404 | 390 | ||
405 | - private updateSchemaForm() { | ||
406 | - if (this.widgetSettingsSchema && this.widgetSettingsSchema.schema) { | ||
407 | - this.currentSettingsSchema = this.widgetSettingsSchema.schema; | ||
408 | - this.currentSettingsForm = this.widgetSettingsSchema.form || deepClone(this.defaultSettingsForm); | ||
409 | - this.currentSettingsGroupInfoes = this.widgetSettingsSchema.groupInfoes; | ||
410 | - this.currentSettings = this.settings; | 391 | + private updateSchemaForm(settings?: any) { |
392 | + const widgetSettingsFormData: JsonFormComponentData = {}; | ||
393 | + if (this.modelValue.settingsSchema && this.modelValue.settingsSchema.schema) { | ||
394 | + widgetSettingsFormData.schema = this.modelValue.settingsSchema.schema; | ||
395 | + widgetSettingsFormData.form = this.modelValue.settingsSchema.form || deepClone(defaultSettingsForm); | ||
396 | + widgetSettingsFormData.groupInfoes = this.modelValue.settingsSchema.groupInfoes; | ||
397 | + widgetSettingsFormData.model = settings; | ||
411 | } else { | 398 | } else { |
412 | - this.currentSettingsForm = deepClone(this.defaultSettingsForm); | ||
413 | - this.currentSettingsSchema = deepClone(this.emptySettingsSchema); | ||
414 | - this.currentSettingsGroupInfoes = deepClone(this.emptySettingsGroupInfoes); | ||
415 | - this.currentSettings = {}; | 399 | + widgetSettingsFormData.schema = deepClone(emptySettingsSchema); |
400 | + widgetSettingsFormData.form = deepClone(defaultSettingsForm); | ||
401 | + widgetSettingsFormData.groupInfoes = deepClone(emptySettingsGroupInfoes); | ||
402 | + widgetSettingsFormData.model = {}; | ||
416 | } | 403 | } |
404 | + this.advancedSettings.patchValue({ settings: widgetSettingsFormData }, {emitEvent: false}); | ||
417 | } | 405 | } |
418 | 406 | ||
419 | private updateDataSettings() { | 407 | private updateDataSettings() { |
@@ -439,8 +427,18 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | @@ -439,8 +427,18 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | ||
439 | } | 427 | } |
440 | } | 428 | } |
441 | 429 | ||
430 | + private updateAdvancedSettings() { | ||
431 | + if (this.modelValue) { | ||
432 | + if (this.modelValue.config) { | ||
433 | + const settings = this.advancedSettings.get('settings').value.model; | ||
434 | + this.modelValue.config.settings = settings; | ||
435 | + } | ||
436 | + this.propagateChange(this.modelValue); | ||
437 | + } | ||
438 | + } | ||
439 | + | ||
442 | public displayAdvanced(): boolean { | 440 | public displayAdvanced(): boolean { |
443 | - return this.widgetSettingsSchema && this.widgetSettingsSchema.schema; | 441 | + return this.modelValue.settingsSchema && this.modelValue.settingsSchema.schema; |
444 | } | 442 | } |
445 | 443 | ||
446 | public removeDatasource(index: number) { | 444 | public removeDatasource(index: number) { |
@@ -450,7 +448,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | @@ -450,7 +448,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | ||
450 | public addDatasource() { | 448 | public addDatasource() { |
451 | let newDatasource: Datasource; | 449 | let newDatasource: Datasource; |
452 | if (this.functionsOnly) { | 450 | if (this.functionsOnly) { |
453 | - newDatasource = deepClone(this.utils.getDefaultDatasource(this.dataKeySettingsSchema.schema)); | 451 | + newDatasource = deepClone(this.utils.getDefaultDatasource(this.modelValue.dataKeySettingsSchema.schema)); |
454 | newDatasource.dataKeys = [this.generateDataKey('Sin', DataKeyType.function)]; | 452 | newDatasource.dataKeys = [this.generateDataKey('Sin', DataKeyType.function)]; |
455 | } else { | 453 | } else { |
456 | newDatasource = { type: DatasourceType.entity, | 454 | newDatasource = { type: DatasourceType.entity, |
@@ -489,8 +487,8 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | @@ -489,8 +487,8 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | ||
489 | result.funcBody = 'return prevValue + 1;'; | 487 | result.funcBody = 'return prevValue + 1;'; |
490 | } | 488 | } |
491 | } | 489 | } |
492 | - if (isDefined(this.dataKeySettingsSchema.schema)) { | ||
493 | - result.settings = this.utils.generateObjectFromJsonSchema(this.dataKeySettingsSchema.schema); | 490 | + if (isDefined(this.modelValue.dataKeySettingsSchema.schema)) { |
491 | + result.settings = this.utils.generateObjectFromJsonSchema(this.modelValue.dataKeySettingsSchema.schema); | ||
494 | } | 492 | } |
495 | return result; | 493 | return result; |
496 | } | 494 | } |
@@ -605,9 +603,15 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | @@ -605,9 +603,15 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | ||
605 | valid: false | 603 | valid: false |
606 | } | 604 | } |
607 | }; | 605 | }; |
606 | + } else if (!this.advancedSettings.valid) { | ||
607 | + return { | ||
608 | + advancedSettings: { | ||
609 | + valid: false | ||
610 | + } | ||
611 | + }; | ||
608 | } else { | 612 | } else { |
609 | const config = this.modelValue.config; | 613 | const config = this.modelValue.config; |
610 | - if (this.widgetType === widgetType.rpc && this.isDataEnabled) { | 614 | + if (this.widgetType === widgetType.rpc && this.modelValue.isDataEnabled) { |
611 | if (!config.targetDeviceAliasIds || !config.targetDeviceAliasIds.length) { | 615 | if (!config.targetDeviceAliasIds || !config.targetDeviceAliasIds.length) { |
612 | return { | 616 | return { |
613 | targetDeviceAliasIds: { | 617 | targetDeviceAliasIds: { |
@@ -615,7 +619,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | @@ -615,7 +619,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | ||
615 | } | 619 | } |
616 | }; | 620 | }; |
617 | } | 621 | } |
618 | - } else if (this.widgetType === widgetType.alarm && this.isDataEnabled) { | 622 | + } else if (this.widgetType === widgetType.alarm && this.modelValue.isDataEnabled) { |
619 | if (!config.alarmSource) { | 623 | if (!config.alarmSource) { |
620 | return { | 624 | return { |
621 | alarmSource: { | 625 | alarmSource: { |
@@ -623,7 +627,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | @@ -623,7 +627,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont | ||
623 | } | 627 | } |
624 | }; | 628 | }; |
625 | } | 629 | } |
626 | - } else if (this.widgetType !== widgetType.static && this.isDataEnabled) { | 630 | + } else if (this.widgetType !== widgetType.static && this.modelValue.isDataEnabled) { |
627 | if (!config.datasources || !config.datasources.length) { | 631 | if (!config.datasources || !config.datasources.length) { |
628 | return { | 632 | return { |
629 | datasources: { | 633 | datasources: { |
@@ -22,7 +22,6 @@ import { | @@ -22,7 +22,6 @@ import { | ||
22 | WidgetActionDescriptor, | 22 | WidgetActionDescriptor, |
23 | WidgetActionSource, | 23 | WidgetActionSource, |
24 | WidgetConfig, | 24 | WidgetConfig, |
25 | - WidgetConfigSettings, | ||
26 | WidgetControllerDescriptor, | 25 | WidgetControllerDescriptor, |
27 | WidgetType, | 26 | WidgetType, |
28 | widgetType, | 27 | widgetType, |
@@ -74,7 +73,7 @@ export interface WidgetContext { | @@ -74,7 +73,7 @@ export interface WidgetContext { | ||
74 | isMobile?: boolean; | 73 | isMobile?: boolean; |
75 | dashboard?: IDashboardComponent; | 74 | dashboard?: IDashboardComponent; |
76 | widgetConfig?: WidgetConfig; | 75 | widgetConfig?: WidgetConfig; |
77 | - settings?: WidgetConfigSettings; | 76 | + settings?: any; |
78 | units?: string; | 77 | units?: string; |
79 | decimals?: number; | 78 | decimals?: number; |
80 | subscriptions?: {[id: string]: IWidgetSubscription}; | 79 | subscriptions?: {[id: string]: IWidgetSubscription}; |
@@ -122,6 +121,11 @@ export interface WidgetConfigComponentData { | @@ -122,6 +121,11 @@ export interface WidgetConfigComponentData { | ||
122 | config: WidgetConfig; | 121 | config: WidgetConfig; |
123 | layout: WidgetLayout; | 122 | layout: WidgetLayout; |
124 | widgetType: widgetType; | 123 | widgetType: widgetType; |
124 | + typeParameters: WidgetTypeParameters; | ||
125 | + actionSources: {[key: string]: WidgetActionSource}; | ||
126 | + isDataEnabled: boolean; | ||
127 | + settingsSchema: any; | ||
128 | + dataKeySettingsSchema: any; | ||
125 | } | 129 | } |
126 | 130 | ||
127 | export const MissingWidgetType: WidgetInfo = { | 131 | export const MissingWidgetType: WidgetInfo = { |
@@ -172,7 +172,7 @@ | @@ -172,7 +172,7 @@ | ||
172 | [opened]="isEditingWidget" | 172 | [opened]="isEditingWidget" |
173 | mode="over" | 173 | mode="over" |
174 | position="end"> | 174 | position="end"> |
175 | - <tb-details-panel fxFlex | 175 | + <tb-details-panel *ngIf="isEditingWidget" fxFlex |
176 | headerTitle="{{editingWidget?.config.title}}" | 176 | headerTitle="{{editingWidget?.config.title}}" |
177 | headerSubtitle="{{ editingWidgetSubtitle }}" | 177 | headerSubtitle="{{ editingWidgetSubtitle }}" |
178 | [isReadOnly]="false" | 178 | [isReadOnly]="false" |
@@ -180,20 +180,17 @@ | @@ -180,20 +180,17 @@ | ||
180 | (closeDetails)="onEditWidgetClosed()" | 180 | (closeDetails)="onEditWidgetClosed()" |
181 | (toggleDetailsEditMode)="onRevertWidgetEdit()" | 181 | (toggleDetailsEditMode)="onRevertWidgetEdit()" |
182 | (applyDetails)="saveWidget()" | 182 | (applyDetails)="saveWidget()" |
183 | - [theForm]="widgetForm"> | 183 | + [theForm]="tbEditWidget.widgetForm"> |
184 | <div class="details-buttons"> | 184 | <div class="details-buttons"> |
185 | <div [tb-help]="helpLinkIdForWidgetType()"></div> | 185 | <div [tb-help]="helpLinkIdForWidgetType()"></div> |
186 | </div> | 186 | </div> |
187 | - <form #widgetForm="ngForm" [formGroup]="editingWidgetFormGroup"> | ||
188 | - <tb-edit-widget *ngIf="isEditingWidget" | ||
189 | - [dashboard]="dashboard" | ||
190 | - [aliasController]="dashboardCtx.aliasController" | ||
191 | - [widgetEditMode]="widgetEditMode" | ||
192 | - [widget]="editingWidget" | ||
193 | - [widgetLayout]="editingWidgetLayout" | ||
194 | - [widgetFormGroup]="editingWidgetFormGroup"> | ||
195 | - </tb-edit-widget> | ||
196 | - </form> | 187 | + <tb-edit-widget #tbEditWidget |
188 | + [dashboard]="dashboard" | ||
189 | + [aliasController]="dashboardCtx.aliasController" | ||
190 | + [widgetEditMode]="widgetEditMode" | ||
191 | + [widget]="editingWidget" | ||
192 | + [widgetLayout]="editingWidgetLayout"> | ||
193 | + </tb-edit-widget> | ||
197 | </tb-details-panel> | 194 | </tb-details-panel> |
198 | </mat-drawer> | 195 | </mat-drawer> |
199 | </mat-drawer-container> | 196 | </mat-drawer-container> |
@@ -71,6 +71,7 @@ import { | @@ -71,6 +71,7 @@ import { | ||
71 | EntityAliasesDialogData | 71 | EntityAliasesDialogData |
72 | } from '@home/components/alias/entity-aliases-dialog.component'; | 72 | } from '@home/components/alias/entity-aliases-dialog.component'; |
73 | import { EntityAliases } from '@app/shared/models/alias.models'; | 73 | import { EntityAliases } from '@app/shared/models/alias.models'; |
74 | +import { EditWidgetComponent } from '@home/pages/dashboard/edit-widget.component'; | ||
74 | 75 | ||
75 | @Component({ | 76 | @Component({ |
76 | selector: 'tb-dashboard-page', | 77 | selector: 'tb-dashboard-page', |
@@ -109,7 +110,6 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC | @@ -109,7 +110,6 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC | ||
109 | editingWidgetLayoutOriginal: WidgetLayout = null; | 110 | editingWidgetLayoutOriginal: WidgetLayout = null; |
110 | editingWidgetSubtitle: string = null; | 111 | editingWidgetSubtitle: string = null; |
111 | editingLayoutCtx: DashboardPageLayoutContext = null; | 112 | editingLayoutCtx: DashboardPageLayoutContext = null; |
112 | - editingWidgetFormGroup: FormGroup; | ||
113 | 113 | ||
114 | thingsboardVersion: string = env.tbVersion; | 114 | thingsboardVersion: string = env.tbVersion; |
115 | 115 | ||
@@ -188,6 +188,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC | @@ -188,6 +188,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC | ||
188 | set rightLayoutOpened(rightLayoutOpened: boolean) { | 188 | set rightLayoutOpened(rightLayoutOpened: boolean) { |
189 | } | 189 | } |
190 | 190 | ||
191 | + @ViewChild('tbEditWidget', {static: false}) editWidgetComponent: EditWidgetComponent; | ||
192 | + | ||
191 | constructor(protected store: Store<AppState>, | 193 | constructor(protected store: Store<AppState>, |
192 | @Inject(WINDOW) private window: Window, | 194 | @Inject(WINDOW) private window: Window, |
193 | private breakpointObserver: BreakpointObserver, | 195 | private breakpointObserver: BreakpointObserver, |
@@ -205,10 +207,6 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC | @@ -205,10 +207,6 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC | ||
205 | private dialog: MatDialog) { | 207 | private dialog: MatDialog) { |
206 | super(store); | 208 | super(store); |
207 | 209 | ||
208 | - this.editingWidgetFormGroup = this.fb.group({ | ||
209 | - widgetConfig: [null] | ||
210 | - }); | ||
211 | - | ||
212 | this.rxSubscriptions.push(this.route.data.subscribe( | 210 | this.rxSubscriptions.push(this.route.data.subscribe( |
213 | (data) => { | 211 | (data) => { |
214 | this.init(data); | 212 | this.init(data); |
@@ -630,15 +628,15 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC | @@ -630,15 +628,15 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC | ||
630 | } | 628 | } |
631 | 629 | ||
632 | onRevertWidgetEdit() { | 630 | onRevertWidgetEdit() { |
633 | - if (this.editingWidgetFormGroup.dirty) { | ||
634 | - this.editingWidgetFormGroup.markAsPristine(); | 631 | + if (this.editWidgetComponent.widgetFormGroup.dirty) { |
632 | + this.editWidgetComponent.widgetFormGroup.markAsPristine(); | ||
635 | this.editingWidget = deepClone(this.editingWidgetOriginal); | 633 | this.editingWidget = deepClone(this.editingWidgetOriginal); |
636 | this.editingWidgetLayout = deepClone(this.editingWidgetLayoutOriginal); | 634 | this.editingWidgetLayout = deepClone(this.editingWidgetLayoutOriginal); |
637 | } | 635 | } |
638 | } | 636 | } |
639 | 637 | ||
640 | saveWidget() { | 638 | saveWidget() { |
641 | - this.editingWidgetFormGroup.markAsPristine(); | 639 | + this.editWidgetComponent.widgetFormGroup.markAsPristine(); |
642 | const widget = deepClone(this.editingWidget); | 640 | const widget = deepClone(this.editingWidget); |
643 | const widgetLayout = deepClone(this.editingWidgetLayout); | 641 | const widgetLayout = deepClone(this.editingWidgetLayout); |
644 | const id = this.editingWidgetOriginal.id; | 642 | const id = this.editingWidgetOriginal.id; |
@@ -15,14 +15,9 @@ | @@ -15,14 +15,9 @@ | ||
15 | limitations under the License. | 15 | limitations under the License. |
16 | 16 | ||
17 | --> | 17 | --> |
18 | -<form [formGroup]="widgetFormGroup"> | 18 | +<form #widgetForm="ngForm" [formGroup]="widgetFormGroup"> |
19 | <fieldset [disabled]="isLoading$ | async"> | 19 | <fieldset [disabled]="isLoading$ | async"> |
20 | <tb-widget-config | 20 | <tb-widget-config |
21 | - [typeParameters]="typeParameters" | ||
22 | - [actionSources]="actionSources" | ||
23 | - [isDataEnabled]="isDataEnabled" | ||
24 | - [widgetSettingsSchema]="settingsSchema" | ||
25 | - [dataKeySettingsSchema]="dataKeySettingsSchema" | ||
26 | [aliasController]="aliasController" | 21 | [aliasController]="aliasController" |
27 | [functionsOnly]="widgetEditMode" | 22 | [functionsOnly]="widgetEditMode" |
28 | [entityAliases]="dashboard.configuration.entityAliases" | 23 | [entityAliases]="dashboard.configuration.entityAliases" |
@@ -14,7 +14,7 @@ | @@ -14,7 +14,7 @@ | ||
14 | /// limitations under the License. | 14 | /// limitations under the License. |
15 | /// | 15 | /// |
16 | 16 | ||
17 | -import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core'; | 17 | +import { Component, OnInit, Input, OnChanges, SimpleChanges, ViewChild } from '@angular/core'; |
18 | import { PageComponent } from '@shared/components/page.component'; | 18 | import { PageComponent } from '@shared/components/page.component'; |
19 | import { Store } from '@ngrx/store'; | 19 | import { Store } from '@ngrx/store'; |
20 | import { AppState } from '@core/core.state'; | 20 | import { AppState } from '@core/core.state'; |
@@ -29,7 +29,7 @@ import { Widget, WidgetActionSource, WidgetTypeParameters } from '@shared/models | @@ -29,7 +29,7 @@ import { Widget, WidgetActionSource, WidgetTypeParameters } from '@shared/models | ||
29 | import { WidgetComponentService } from '@home/components/widget/widget-component.service'; | 29 | import { WidgetComponentService } from '@home/components/widget/widget-component.service'; |
30 | import { WidgetConfigComponentData } from '../../models/widget-component.models'; | 30 | import { WidgetConfigComponentData } from '../../models/widget-component.models'; |
31 | import { deepClone, isDefined, isString } from '@core/utils'; | 31 | import { deepClone, isDefined, isString } from '@core/utils'; |
32 | -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; | 32 | +import { FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms'; |
33 | import { EntityType } from '@shared/models/entity-type.models'; | 33 | import { EntityType } from '@shared/models/entity-type.models'; |
34 | import { Observable, of } from 'rxjs'; | 34 | import { Observable, of } from 'rxjs'; |
35 | import { EntityAlias, EntityAliases } from '@shared/models/alias.models'; | 35 | import { EntityAlias, EntityAliases } from '@shared/models/alias.models'; |
@@ -66,21 +66,20 @@ export class EditWidgetComponent extends PageComponent implements OnInit, OnChan | @@ -66,21 +66,20 @@ export class EditWidgetComponent extends PageComponent implements OnInit, OnChan | ||
66 | @Input() | 66 | @Input() |
67 | widgetLayout: WidgetLayout; | 67 | widgetLayout: WidgetLayout; |
68 | 68 | ||
69 | - @Input() | 69 | + @ViewChild('widgetForm', {static: true}) widgetForm: NgForm; |
70 | + | ||
70 | widgetFormGroup: FormGroup; | 71 | widgetFormGroup: FormGroup; |
71 | 72 | ||
72 | widgetConfig: WidgetConfigComponentData; | 73 | widgetConfig: WidgetConfigComponentData; |
73 | - typeParameters: WidgetTypeParameters; | ||
74 | - actionSources: {[key: string]: WidgetActionSource}; | ||
75 | - isDataEnabled: boolean; | ||
76 | - settingsSchema: any; | ||
77 | - dataKeySettingsSchema: any; | ||
78 | - functionsOnly: boolean; | ||
79 | 74 | ||
80 | constructor(protected store: Store<AppState>, | 75 | constructor(protected store: Store<AppState>, |
81 | private dialog: MatDialog, | 76 | private dialog: MatDialog, |
77 | + private fb: FormBuilder, | ||
82 | private widgetComponentService: WidgetComponentService) { | 78 | private widgetComponentService: WidgetComponentService) { |
83 | super(store); | 79 | super(store); |
80 | + this.widgetFormGroup = this.fb.group({ | ||
81 | + widgetConfig: [null] | ||
82 | + }); | ||
84 | } | 83 | } |
85 | 84 | ||
86 | ngOnInit(): void { | 85 | ngOnInit(): void { |
@@ -104,27 +103,33 @@ export class EditWidgetComponent extends PageComponent implements OnInit, OnChan | @@ -104,27 +103,33 @@ export class EditWidgetComponent extends PageComponent implements OnInit, OnChan | ||
104 | 103 | ||
105 | private loadWidgetConfig() { | 104 | private loadWidgetConfig() { |
106 | const widgetInfo = this.widgetComponentService.getInstantWidgetInfo(this.widget); | 105 | const widgetInfo = this.widgetComponentService.getInstantWidgetInfo(this.widget); |
107 | - this.widgetConfig = { | ||
108 | - config: this.widget.config, | ||
109 | - layout: this.widgetLayout, | ||
110 | - widgetType: this.widget.type | ||
111 | - }; | ||
112 | - const settingsSchema = widgetInfo.typeSettingsSchema || widgetInfo.settingsSchema; | ||
113 | - const dataKeySettingsSchema = widgetInfo.typeDataKeySettingsSchema || widgetInfo.dataKeySettingsSchema; | ||
114 | - this.typeParameters = widgetInfo.typeParameters; | ||
115 | - this.actionSources = widgetInfo.actionSources; | ||
116 | - this.isDataEnabled = isDefined(widgetInfo.typeParameters) ? !widgetInfo.typeParameters.useCustomDatasources : true; | ||
117 | - if (!settingsSchema || settingsSchema === '') { | ||
118 | - this.settingsSchema = {}; | 106 | + const rawSettingsSchema = widgetInfo.typeSettingsSchema || widgetInfo.settingsSchema; |
107 | + const rawDataKeySettingsSchema = widgetInfo.typeDataKeySettingsSchema || widgetInfo.dataKeySettingsSchema; | ||
108 | + const typeParameters = widgetInfo.typeParameters; | ||
109 | + const actionSources = widgetInfo.actionSources; | ||
110 | + const isDataEnabled = isDefined(widgetInfo.typeParameters) ? !widgetInfo.typeParameters.useCustomDatasources : true; | ||
111 | + let settingsSchema; | ||
112 | + if (!rawSettingsSchema || rawSettingsSchema === '') { | ||
113 | + settingsSchema = {}; | ||
119 | } else { | 114 | } else { |
120 | - this.settingsSchema = isString(settingsSchema) ? JSON.parse(settingsSchema) : settingsSchema; | 115 | + settingsSchema = isString(rawSettingsSchema) ? JSON.parse(rawSettingsSchema) : rawSettingsSchema; |
121 | } | 116 | } |
122 | - if (!dataKeySettingsSchema || dataKeySettingsSchema === '') { | ||
123 | - this.dataKeySettingsSchema = {}; | 117 | + let dataKeySettingsSchema; |
118 | + if (!rawDataKeySettingsSchema || rawDataKeySettingsSchema === '') { | ||
119 | + dataKeySettingsSchema = {}; | ||
124 | } else { | 120 | } else { |
125 | - this.dataKeySettingsSchema = isString(dataKeySettingsSchema) ? JSON.parse(dataKeySettingsSchema) : dataKeySettingsSchema; | 121 | + dataKeySettingsSchema = isString(rawDataKeySettingsSchema) ? JSON.parse(rawDataKeySettingsSchema) : rawDataKeySettingsSchema; |
126 | } | 122 | } |
127 | - this.functionsOnly = this.dashboard ? false : true; | 123 | + this.widgetConfig = { |
124 | + config: this.widget.config, | ||
125 | + layout: this.widgetLayout, | ||
126 | + widgetType: this.widget.type, | ||
127 | + typeParameters, | ||
128 | + actionSources, | ||
129 | + isDataEnabled, | ||
130 | + settingsSchema, | ||
131 | + dataKeySettingsSchema | ||
132 | + }; | ||
128 | this.widgetFormGroup.reset({widgetConfig: this.widgetConfig}); | 133 | this.widgetFormGroup.reset({widgetConfig: this.widgetConfig}); |
129 | } | 134 | } |
130 | 135 |
@@ -13,37 +13,9 @@ | @@ -13,37 +13,9 @@ | ||
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 | -@mixin tb-checkered-bg() { | ||
17 | - background-color: #fff; | ||
18 | - background-image: | ||
19 | - linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd), | ||
20 | - linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd); | ||
21 | - background-position: 0 0, 4px 4px; | ||
22 | - background-size: 8px 8px; | ||
23 | -} | ||
24 | 16 | ||
25 | :host { | 17 | :host { |
26 | .mat-form-field { | 18 | .mat-form-field { |
27 | width: 100%; | 19 | width: 100%; |
28 | } | 20 | } |
29 | - .tb-color-preview { | ||
30 | - cursor: pointer; | ||
31 | - box-sizing: border-box; | ||
32 | - position: relative; | ||
33 | - width: 24px; | ||
34 | - min-width: 24px; | ||
35 | - height: 24px; | ||
36 | - overflow: hidden; | ||
37 | - content: ""; | ||
38 | - border: 2px solid #fff; | ||
39 | - border-radius: 50%; | ||
40 | - box-shadow: 0 3px 1px -2px rgba(0, 0, 0, .14), 0 2px 2px 0 rgba(0, 0, 0, .098), 0 1px 5px 0 rgba(0, 0, 0, .084); | ||
41 | - | ||
42 | - @include tb-checkered-bg(); | ||
43 | - | ||
44 | - .tb-color-result { | ||
45 | - width: 100%; | ||
46 | - height: 100%; | ||
47 | - } | ||
48 | - } | ||
49 | } | 21 | } |
@@ -18,8 +18,8 @@ import { | @@ -18,8 +18,8 @@ import { | ||
18 | Directive, | 18 | Directive, |
19 | ElementRef, | 19 | ElementRef, |
20 | EventEmitter, | 20 | EventEmitter, |
21 | - Input, | ||
22 | - Output, | 21 | + Input, OnChanges, |
22 | + Output, SimpleChanges, | ||
23 | ViewContainerRef | 23 | ViewContainerRef |
24 | } from '@angular/core'; | 24 | } from '@angular/core'; |
25 | import { Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; | 25 | import { Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; |
@@ -29,7 +29,7 @@ import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; | @@ -29,7 +29,7 @@ import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; | ||
29 | @Directive({ | 29 | @Directive({ |
30 | selector: '[tb-fullscreen]' | 30 | selector: '[tb-fullscreen]' |
31 | }) | 31 | }) |
32 | -export class FullscreenDirective { | 32 | +export class FullscreenDirective implements OnChanges { |
33 | 33 | ||
34 | fullscreenValue = false; | 34 | fullscreenValue = false; |
35 | 35 | ||
@@ -37,16 +37,10 @@ export class FullscreenDirective { | @@ -37,16 +37,10 @@ export class FullscreenDirective { | ||
37 | private parentElement: HTMLElement; | 37 | private parentElement: HTMLElement; |
38 | 38 | ||
39 | @Input() | 39 | @Input() |
40 | - set fullscreen(fullscreen: boolean) { | ||
41 | - if (this.fullscreenValue !== fullscreen) { | ||
42 | - this.fullscreenValue = fullscreen; | ||
43 | - if (this.fullscreenValue) { | ||
44 | - this.enterFullscreen(); | ||
45 | - } else { | ||
46 | - this.exitFullscreen(); | ||
47 | - } | ||
48 | - } | ||
49 | - } | 40 | + fullscreen: boolean; |
41 | + | ||
42 | + @Input() | ||
43 | + fullscreenElement: HTMLElement; | ||
50 | 44 | ||
51 | @Output() | 45 | @Output() |
52 | fullscreenChanged = new EventEmitter<boolean>(); | 46 | fullscreenChanged = new EventEmitter<boolean>(); |
@@ -56,11 +50,30 @@ export class FullscreenDirective { | @@ -56,11 +50,30 @@ export class FullscreenDirective { | ||
56 | private overlay: Overlay) { | 50 | private overlay: Overlay) { |
57 | } | 51 | } |
58 | 52 | ||
53 | + ngOnChanges(changes: SimpleChanges): void { | ||
54 | + let updateFullscreen = false; | ||
55 | + for (const propName of Object.keys(changes)) { | ||
56 | + const change = changes[propName]; | ||
57 | + if (!change.firstChange && change.currentValue !== change.previousValue) { | ||
58 | + if (propName === 'fullscreen') { | ||
59 | + updateFullscreen = true; | ||
60 | + } | ||
61 | + } | ||
62 | + } | ||
63 | + if (updateFullscreen) { | ||
64 | + if (this.fullscreen) { | ||
65 | + this.enterFullscreen(); | ||
66 | + } else { | ||
67 | + this.exitFullscreen(); | ||
68 | + } | ||
69 | + } | ||
70 | + } | ||
71 | + | ||
59 | enterFullscreen() { | 72 | enterFullscreen() { |
60 | - const targetElement = this.elementRef; | ||
61 | - this.parentElement = targetElement.nativeElement.parentElement; | ||
62 | - this.parentElement.removeChild(targetElement.nativeElement); | ||
63 | - targetElement.nativeElement.classList.add('tb-fullscreen'); | 73 | + const targetElement: HTMLElement = this.fullscreenElement || this.elementRef.nativeElement; |
74 | + this.parentElement = targetElement.parentElement; | ||
75 | + this.parentElement.removeChild(targetElement); | ||
76 | + targetElement.classList.add('tb-fullscreen'); | ||
64 | const position = this.overlay.position(); | 77 | const position = this.overlay.position(); |
65 | const config = new OverlayConfig({ | 78 | const config = new OverlayConfig({ |
66 | hasBackdrop: false, | 79 | hasBackdrop: false, |
@@ -73,19 +86,19 @@ export class FullscreenDirective { | @@ -73,19 +86,19 @@ export class FullscreenDirective { | ||
73 | 86 | ||
74 | this.overlayRef = this.overlay.create(config); | 87 | this.overlayRef = this.overlay.create(config); |
75 | this.overlayRef.attach(new EmptyPortal()); | 88 | this.overlayRef.attach(new EmptyPortal()); |
76 | - this.overlayRef.overlayElement.append( targetElement.nativeElement ); | 89 | + this.overlayRef.overlayElement.append( targetElement ); |
77 | this.fullscreenChanged.emit(true); | 90 | this.fullscreenChanged.emit(true); |
78 | } | 91 | } |
79 | 92 | ||
80 | exitFullscreen() { | 93 | exitFullscreen() { |
81 | - const targetElement = this.elementRef; | 94 | + const targetElement: HTMLElement = this.fullscreenElement || this.elementRef.nativeElement; |
82 | if (this.parentElement) { | 95 | if (this.parentElement) { |
83 | - this.overlayRef.overlayElement.removeChild( targetElement.nativeElement ); | ||
84 | - this.parentElement.append(targetElement.nativeElement); | 96 | + this.overlayRef.overlayElement.removeChild( targetElement ); |
97 | + this.parentElement.append(targetElement); | ||
85 | this.parentElement = null; | 98 | this.parentElement = null; |
86 | } | 99 | } |
87 | - targetElement.nativeElement.classList.remove('tb-fullscreen'); | ||
88 | - if (this.elementRef !== targetElement) { | 100 | + targetElement.classList.remove('tb-fullscreen'); |
101 | + if (this.elementRef) { | ||
89 | this.elementRef.nativeElement.classList.remove('tb-fullscreen'); | 102 | this.elementRef.nativeElement.classList.remove('tb-fullscreen'); |
90 | } | 103 | } |
91 | this.overlayRef.dispose(); | 104 | this.overlayRef.dispose(); |
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 | + | ||
17 | + | ||
18 | +export interface JsonFormComponentData { | ||
19 | + model?: any; | ||
20 | + schema?: any; | ||
21 | + form?: any; | ||
22 | + groupInfoes?: any[]; | ||
23 | +} |
@@ -15,8 +15,8 @@ | @@ -15,8 +15,8 @@ | ||
15 | limitations under the License. | 15 | limitations under the License. |
16 | 16 | ||
17 | --> | 17 | --> |
18 | -<div class="tb-json-form" style="background: #fff;" tb-fullscreen [fullscreen]="isFullscreen" | ||
19 | - (fullscreenChanged)="onFullscreenChanged()"> | 18 | +<div class="tb-json-form" style="background: #fff;" tb-fullscreen [fullscreenElement]="targetFullscreenElement" |
19 | + [fullscreen]="isFullscreen" | ||
20 | + (fullscreenChanged)="onFullscreenChanged($event)"> | ||
20 | <div #reactRoot></div> | 21 | <div #reactRoot></div> |
21 | - <div>{{ model | json }}</div> | ||
22 | </div> | 22 | </div> |
@@ -40,6 +40,7 @@ import * as React from 'react'; | @@ -40,6 +40,7 @@ import * as React from 'react'; | ||
40 | import * as ReactDOM from 'react-dom'; | 40 | import * as ReactDOM from 'react-dom'; |
41 | import ReactSchemaForm from './react/json-form-react'; | 41 | import ReactSchemaForm from './react/json-form-react'; |
42 | import JsonFormUtils from './react/json-form-utils'; | 42 | import JsonFormUtils from './react/json-form-utils'; |
43 | +import { JsonFormComponentData } from './json-form-component.models'; | ||
43 | 44 | ||
44 | @Component({ | 45 | @Component({ |
45 | selector: 'tb-json-form', | 46 | selector: 'tb-json-form', |
@@ -64,12 +65,6 @@ export class JsonFormComponent implements OnInit, ControlValueAccessor, Validato | @@ -64,12 +65,6 @@ export class JsonFormComponent implements OnInit, ControlValueAccessor, Validato | ||
64 | @ViewChild('reactRoot', {static: true}) | 65 | @ViewChild('reactRoot', {static: true}) |
65 | reactRootElmRef: ElementRef<HTMLElement>; | 66 | reactRootElmRef: ElementRef<HTMLElement>; |
66 | 67 | ||
67 | - @Input() schema: any; | ||
68 | - | ||
69 | - @Input() form: any; | ||
70 | - | ||
71 | - @Input() groupInfoes: any[]; | ||
72 | - | ||
73 | private readonlyValue: boolean; | 68 | private readonlyValue: boolean; |
74 | get readonly(): boolean { | 69 | get readonly(): boolean { |
75 | return this.readonlyValue; | 70 | return this.readonlyValue; |
@@ -91,11 +86,18 @@ export class JsonFormComponent implements OnInit, ControlValueAccessor, Validato | @@ -91,11 +86,18 @@ export class JsonFormComponent implements OnInit, ControlValueAccessor, Validato | ||
91 | onToggleFullscreen: this.onToggleFullscreen.bind(this) | 86 | onToggleFullscreen: this.onToggleFullscreen.bind(this) |
92 | }; | 87 | }; |
93 | 88 | ||
89 | + data: JsonFormComponentData; | ||
90 | + | ||
94 | model: any; | 91 | model: any; |
92 | + schema: any; | ||
93 | + form: any; | ||
94 | + groupInfoes: any[]; | ||
95 | 95 | ||
96 | isModelValid = true; | 96 | isModelValid = true; |
97 | 97 | ||
98 | isFullscreen = false; | 98 | isFullscreen = false; |
99 | + targetFullscreenElement: HTMLElement; | ||
100 | + fullscreenFinishFn: () => void; | ||
99 | 101 | ||
100 | private propagateChange = null; | 102 | private propagateChange = null; |
101 | 103 | ||
@@ -128,77 +130,91 @@ export class JsonFormComponent implements OnInit, ControlValueAccessor, Validato | @@ -128,77 +130,91 @@ export class JsonFormComponent implements OnInit, ControlValueAccessor, Validato | ||
128 | }; | 130 | }; |
129 | } | 131 | } |
130 | 132 | ||
131 | - writeValue(value: any): void { | ||
132 | - this.model = value || {}; | 133 | + writeValue(data: JsonFormComponentData): void { |
134 | + this.data = data; | ||
135 | + this.schema = this.data && this.data.schema ? deepClone(this.data.schema) : { | ||
136 | + type: 'object' | ||
137 | + }; | ||
138 | + this.schema.strict = true; | ||
139 | + this.form = this.data && this.data.form ? deepClone(this.data.form) : [ '*' ]; | ||
140 | + this.groupInfoes = this.data && this.data.groupInfoes ? deepClone(this.data.groupInfoes) : []; | ||
141 | + this.model = this.data && this.data.model || {}; | ||
133 | this.model = inspector.sanitize(this.schema, this.model).data; | 142 | this.model = inspector.sanitize(this.schema, this.model).data; |
134 | this.updateAndRender(); | 143 | this.updateAndRender(); |
135 | this.isModelValid = this.validateModel(); | 144 | this.isModelValid = this.validateModel(); |
136 | if (!this.isModelValid) { | 145 | if (!this.isModelValid) { |
137 | this.updateView(); | 146 | this.updateView(); |
138 | } | 147 | } |
139 | - } | 148 | +} |
140 | 149 | ||
141 | updateView() { | 150 | updateView() { |
142 | - this.propagateChange(this.model); | 151 | + if (this.data) { |
152 | + this.data.model = this.model; | ||
153 | + this.propagateChange(this.data); | ||
154 | + } | ||
143 | } | 155 | } |
144 | 156 | ||
145 | ngOnChanges(changes: SimpleChanges): void { | 157 | ngOnChanges(changes: SimpleChanges): void { |
146 | for (const propName of Object.keys(changes)) { | 158 | for (const propName of Object.keys(changes)) { |
147 | const change = changes[propName]; | 159 | const change = changes[propName]; |
148 | if (!change.firstChange && change.currentValue !== change.previousValue) { | 160 | if (!change.firstChange && change.currentValue !== change.previousValue) { |
149 | - if (propName === 'schema') { | ||
150 | - this.model = inspector.sanitize(this.schema, this.model).data; | ||
151 | - this.isModelValid = this.validateModel(); | ||
152 | - } | ||
153 | - if (['readonly', 'schema', 'form', 'groupInfoes'].includes(propName)) { | 161 | + if (propName === 'readonly') { |
154 | this.updateAndRender(); | 162 | this.updateAndRender(); |
155 | } | 163 | } |
156 | } | 164 | } |
157 | } | 165 | } |
158 | } | 166 | } |
159 | 167 | ||
160 | - onFullscreenChanged() {} | ||
161 | - | ||
162 | private onModelChange(key: (string | number)[], val: any) { | 168 | private onModelChange(key: (string | number)[], val: any) { |
163 | if (isString(val) && val === '') { | 169 | if (isString(val) && val === '') { |
164 | val = undefined; | 170 | val = undefined; |
165 | } | 171 | } |
166 | if (JsonFormUtils.updateValue(key, this.model, val)) { | 172 | if (JsonFormUtils.updateValue(key, this.model, val)) { |
167 | this.formProps.model = this.model; | 173 | this.formProps.model = this.model; |
174 | + this.isModelValid = this.validateModel(); | ||
168 | this.updateView(); | 175 | this.updateView(); |
169 | } | 176 | } |
170 | } | 177 | } |
171 | 178 | ||
172 | - private onColorClick(event: MouseEvent, key: string, val: string) { | 179 | + private onColorClick(key: (string | number)[], |
180 | + val: tinycolor.ColorFormats.RGBA, | ||
181 | + colorSelectedFn: (color: tinycolor.ColorFormats.RGBA) => void) { | ||
173 | this.dialogs.colorPicker(tinycolor(val).toRgbString()).subscribe((color) => { | 182 | this.dialogs.colorPicker(tinycolor(val).toRgbString()).subscribe((color) => { |
174 | - const e = event as any; | ||
175 | - if (e.data && e.data.onValueChanged) { | ||
176 | - e.data.onValueChanged(tinycolor(color).toRgb()); | 183 | + if (colorSelectedFn) { |
184 | + colorSelectedFn(tinycolor(color).toRgb()); | ||
177 | } | 185 | } |
178 | }); | 186 | }); |
179 | } | 187 | } |
180 | 188 | ||
181 | - private onToggleFullscreen() { | 189 | + private onToggleFullscreen(element: HTMLElement, fullscreenFinishFn?: () => void) { |
190 | + this.targetFullscreenElement = element; | ||
182 | this.isFullscreen = !this.isFullscreen; | 191 | this.isFullscreen = !this.isFullscreen; |
183 | - this.formProps.isFullscreen = this.isFullscreen; | 192 | + this.fullscreenFinishFn = fullscreenFinishFn; |
193 | + } | ||
194 | + | ||
195 | + onFullscreenChanged(fullscreen: boolean) { | ||
196 | + this.formProps.isFullscreen = fullscreen; | ||
197 | + this.renderReactSchemaForm(false); | ||
198 | + if (this.fullscreenFinishFn) { | ||
199 | + this.fullscreenFinishFn(); | ||
200 | + this.fullscreenFinishFn = null; | ||
201 | + } | ||
184 | } | 202 | } |
185 | 203 | ||
186 | private updateAndRender() { | 204 | private updateAndRender() { |
187 | - const schema = this.schema ? deepClone(this.schema) : { | ||
188 | - type: 'object' | ||
189 | - }; | ||
190 | - schema.strict = true; | ||
191 | - const form = this.form ? deepClone(this.form) : [ '*' ]; | ||
192 | - const groupInfoes = this.groupInfoes ? deepClone(this.groupInfoes) : []; | 205 | + |
193 | this.formProps.option.formDefaults.readonly = this.readonly; | 206 | this.formProps.option.formDefaults.readonly = this.readonly; |
194 | - this.formProps.schema = schema; | ||
195 | - this.formProps.form = form; | ||
196 | - this.formProps.groupInfoes = groupInfoes; | 207 | + this.formProps.schema = this.schema; |
208 | + this.formProps.form = this.form; | ||
209 | + this.formProps.groupInfoes = this.groupInfoes; | ||
197 | this.formProps.model = deepClone(this.model); | 210 | this.formProps.model = deepClone(this.model); |
198 | this.renderReactSchemaForm(); | 211 | this.renderReactSchemaForm(); |
199 | } | 212 | } |
200 | 213 | ||
201 | - private renderReactSchemaForm() { | 214 | + private renderReactSchemaForm(destroy: boolean = true) { |
215 | + if (destroy) { | ||
216 | + this.destroyReactSchemaForm(); | ||
217 | + } | ||
202 | ReactDOM.render(React.createElement(ReactSchemaForm, this.formProps), this.reactRootElmRef.nativeElement); | 218 | ReactDOM.render(React.createElement(ReactSchemaForm, this.formProps), this.reactRootElmRef.nativeElement); |
203 | } | 219 | } |
204 | 220 |
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 | + | ||
17 | +import * as React from 'react'; | ||
18 | +import ThingsboardBaseComponent from './json-form-base-component'; | ||
19 | +import reactCSS from 'reactcss'; | ||
20 | +import ReactAce from 'react-ace'; | ||
21 | +import Button from '@material-ui/core/Button'; | ||
22 | +import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; | ||
23 | +import * as tinycolor from 'tinycolor2'; | ||
24 | +import { IEditorProps } from 'react-ace/src/types'; | ||
25 | + | ||
26 | +interface ThingsboardAceEditorProps extends JsonFormFieldProps { | ||
27 | + mode: string; | ||
28 | + onTidy: (value: string) => string; | ||
29 | +} | ||
30 | + | ||
31 | +interface ThingsboardAceEditorState extends JsonFormFieldState { | ||
32 | + isFull: boolean; | ||
33 | + focused: boolean; | ||
34 | +} | ||
35 | + | ||
36 | +class ThingsboardAceEditor extends React.Component<ThingsboardAceEditorProps, ThingsboardAceEditorState> { | ||
37 | + | ||
38 | + hostElement: HTMLElement; | ||
39 | + private aceEditor: IEditorProps; | ||
40 | + | ||
41 | + constructor(props) { | ||
42 | + super(props); | ||
43 | + this.onValueChanged = this.onValueChanged.bind(this); | ||
44 | + this.onBlur = this.onBlur.bind(this); | ||
45 | + this.onFocus = this.onFocus.bind(this); | ||
46 | + this.onTidy = this.onTidy.bind(this); | ||
47 | + this.onLoad = this.onLoad.bind(this); | ||
48 | + this.onToggleFull = this.onToggleFull.bind(this); | ||
49 | + const value = props.value ? props.value + '' : ''; | ||
50 | + this.state = { | ||
51 | + isFull: false, | ||
52 | + value, | ||
53 | + focused: false | ||
54 | + }; | ||
55 | + } | ||
56 | + | ||
57 | + onValueChanged(value) { | ||
58 | + this.setState({ | ||
59 | + value | ||
60 | + }); | ||
61 | + this.props.onChangeValidate({ | ||
62 | + target: { | ||
63 | + value | ||
64 | + } | ||
65 | + }); | ||
66 | + } | ||
67 | + | ||
68 | + onBlur() { | ||
69 | + this.setState({ focused: false }); | ||
70 | + } | ||
71 | + | ||
72 | + onFocus() { | ||
73 | + this.setState({ focused: true }); | ||
74 | + } | ||
75 | + | ||
76 | + onTidy() { | ||
77 | + if (!this.props.form.readonly) { | ||
78 | + let value = this.state.value; | ||
79 | + value = this.props.onTidy(value); | ||
80 | + this.setState({ | ||
81 | + value | ||
82 | + }); | ||
83 | + this.props.onChangeValidate({ | ||
84 | + target: { | ||
85 | + value | ||
86 | + } | ||
87 | + }); | ||
88 | + } | ||
89 | + } | ||
90 | + | ||
91 | + onLoad(editor: IEditorProps) { | ||
92 | + this.aceEditor = editor; | ||
93 | + } | ||
94 | + | ||
95 | + onToggleFull() { | ||
96 | + this.setState({ isFull: !this.state.isFull }); | ||
97 | + this.props.onToggleFullscreen(this.hostElement, () => { | ||
98 | + if (this.aceEditor) { | ||
99 | + this.aceEditor.resize(); | ||
100 | + this.aceEditor.renderer.updateFull(); | ||
101 | + } | ||
102 | + }); | ||
103 | + } | ||
104 | + | ||
105 | + componentDidUpdate() { | ||
106 | + } | ||
107 | + | ||
108 | + render() { | ||
109 | + | ||
110 | + const styles = reactCSS({ | ||
111 | + default: { | ||
112 | + tidyButtonStyle: { | ||
113 | + color: '#7B7B7B', | ||
114 | + minWidth: '32px', | ||
115 | + minHeight: '15px', | ||
116 | + lineHeight: '15px', | ||
117 | + fontSize: '0.800rem', | ||
118 | + margin: '0', | ||
119 | + padding: '4px', | ||
120 | + height: '23px', | ||
121 | + borderRadius: '5px', | ||
122 | + marginLeft: '5px' | ||
123 | + } | ||
124 | + } | ||
125 | + }); | ||
126 | + | ||
127 | + let labelClass = 'tb-label'; | ||
128 | + if (this.props.form.required) { | ||
129 | + labelClass += ' tb-required'; | ||
130 | + } | ||
131 | + if (this.props.form.readonly) { | ||
132 | + labelClass += ' tb-readonly'; | ||
133 | + } | ||
134 | + if (this.state.focused) { | ||
135 | + labelClass += ' tb-focused'; | ||
136 | + } | ||
137 | + let containerClass = 'tb-container'; | ||
138 | + const style = this.props.form.style || {width: '100%'}; | ||
139 | + if (this.state.isFull) { | ||
140 | + containerClass += ' fullscreen-form-field'; | ||
141 | + } | ||
142 | + return ( | ||
143 | + <div> | ||
144 | + <div className='tb-json-form' ref={c => (this.hostElement = c)}> | ||
145 | + <div className={containerClass}> | ||
146 | + <label className={labelClass}>{this.props.form.title}</label> | ||
147 | + <div className='json-form-ace-editor'> | ||
148 | + <div className='title-panel'> | ||
149 | + <label>{this.props.mode}</label> | ||
150 | + <Button style={ styles.tidyButtonStyle } | ||
151 | + className='tidy-button' onClick={this.onTidy}>Tidy</Button> | ||
152 | + <Button style={ styles.tidyButtonStyle } | ||
153 | + className='tidy-button' onClick={this.onToggleFull}> | ||
154 | + {this.state.isFull ? | ||
155 | + 'Exit fullscreen' : 'Fullscreen'} | ||
156 | + </Button> | ||
157 | + </div> | ||
158 | + <ReactAce mode={this.props.mode} | ||
159 | + height={this.state.isFull ? '100%' : '150px'} | ||
160 | + width={this.state.isFull ? '100%' : '300px'} | ||
161 | + theme='github' | ||
162 | + onChange={this.onValueChanged} | ||
163 | + onFocus={this.onFocus} | ||
164 | + onBlur={this.onBlur} | ||
165 | + onLoad={this.onLoad} | ||
166 | + name={this.props.form.title} | ||
167 | + value={this.state.value} | ||
168 | + readOnly={this.props.form.readonly} | ||
169 | + editorProps={{$blockScrolling: Infinity}} | ||
170 | + enableBasicAutocompletion={true} | ||
171 | + enableSnippets={true} | ||
172 | + enableLiveAutocompletion={true} | ||
173 | + style={style}/> | ||
174 | + </div> | ||
175 | + <div className='json-form-error' | ||
176 | + style={{opacity: this.props.valid ? '0' : '1'}}>{this.props.error}</div> | ||
177 | + </div> | ||
178 | + </div> | ||
179 | + </div> | ||
180 | + ); | ||
181 | + } | ||
182 | +} | ||
183 | + | ||
184 | +export default ThingsboardBaseComponent(ThingsboardAceEditor); |
@@ -17,7 +17,8 @@ import * as React from 'react'; | @@ -17,7 +17,8 @@ import * as React from 'react'; | ||
17 | import JsonFormUtils from './json-form-utils'; | 17 | import JsonFormUtils from './json-form-utils'; |
18 | import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; | 18 | import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; |
19 | 19 | ||
20 | -export default ThingsboardBaseComponent => class extends React.Component<JsonFormFieldProps, JsonFormFieldState> { | 20 | +export default ThingsboardBaseComponent => class<P extends JsonFormFieldProps> |
21 | + extends React.Component<P, JsonFormFieldState> { | ||
21 | 22 | ||
22 | constructor(props) { | 23 | constructor(props) { |
23 | super(props); | 24 | super(props); |
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 * as ReactDOM from 'react-dom'; | ||
18 | +import ThingsboardBaseComponent from './json-form-base-component'; | ||
19 | +import reactCSS from 'reactcss'; | ||
20 | +import * as tinycolor from 'tinycolor2'; | ||
21 | +import TextField from '@material-ui/core/TextField'; | ||
22 | +import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; | ||
23 | +import IconButton from '@material-ui/core/IconButton'; | ||
24 | +import ClearIcon from '@material-ui/icons/Clear'; | ||
25 | +import Tooltip from '@material-ui/core/Tooltip'; | ||
26 | + | ||
27 | +interface ThingsboardColorState extends JsonFormFieldState { | ||
28 | + color: tinycolor.ColorFormats.RGBA | null; | ||
29 | + focused: boolean; | ||
30 | +} | ||
31 | + | ||
32 | +class ThingsboardColor extends React.Component<JsonFormFieldProps, ThingsboardColorState> { | ||
33 | + | ||
34 | + constructor(props) { | ||
35 | + super(props); | ||
36 | + this.onBlur = this.onBlur.bind(this); | ||
37 | + this.onFocus = this.onFocus.bind(this); | ||
38 | + this.onValueChanged = this.onValueChanged.bind(this); | ||
39 | + this.onSwatchClick = this.onSwatchClick.bind(this); | ||
40 | + this.onClear = this.onClear.bind(this); | ||
41 | + const value = props.value ? props.value + '' : null; | ||
42 | + const color = value != null ? tinycolor(value).toRgb() : null; | ||
43 | + this.state = { | ||
44 | + color, | ||
45 | + focused: false | ||
46 | + }; | ||
47 | + } | ||
48 | + | ||
49 | + onBlur() { | ||
50 | + this.setState({focused: false}); | ||
51 | + } | ||
52 | + | ||
53 | + onFocus() { | ||
54 | + this.setState({focused: true}); | ||
55 | + } | ||
56 | + | ||
57 | + componentDidMount() { | ||
58 | + const node = ReactDOM.findDOMNode(this); | ||
59 | + const colContainer = $(node).children('#color-container'); | ||
60 | + colContainer.click((event) => { | ||
61 | + if (!this.props.form.readonly) { | ||
62 | + this.onSwatchClick(event); | ||
63 | + } | ||
64 | + }); | ||
65 | + } | ||
66 | + | ||
67 | + componentWillUnmount() { | ||
68 | + const node = ReactDOM.findDOMNode(this); | ||
69 | + const colContainer = $(node).children('#color-container'); | ||
70 | + colContainer.off( 'click' ); | ||
71 | + } | ||
72 | + | ||
73 | + onValueChanged(value: tinycolor.ColorFormats.RGBA | null) { | ||
74 | + let color: tinycolor.Instance = null; | ||
75 | + if (value != null) { | ||
76 | + color = tinycolor(value); | ||
77 | + } | ||
78 | + this.setState({ | ||
79 | + color: value | ||
80 | + }); | ||
81 | + let colorValue = ''; | ||
82 | + if (color != null && color.getAlpha() !== 1) { | ||
83 | + colorValue = color.toRgbString(); | ||
84 | + } else if (color != null) { | ||
85 | + colorValue = color.toHexString(); | ||
86 | + } | ||
87 | + this.props.onChangeValidate({ | ||
88 | + target: { | ||
89 | + value: colorValue | ||
90 | + } | ||
91 | + }); | ||
92 | + } | ||
93 | + | ||
94 | + onSwatchClick(event) { | ||
95 | + this.props.onColorClick(this.props.form.key, this.state.color, | ||
96 | + (color) => { | ||
97 | + this.onValueChanged(color); | ||
98 | + } | ||
99 | + ); | ||
100 | + } | ||
101 | + | ||
102 | + onClear(event) { | ||
103 | + if (event) { | ||
104 | + event.stopPropagation(); | ||
105 | + } | ||
106 | + this.onValueChanged(null); | ||
107 | + } | ||
108 | + | ||
109 | + render() { | ||
110 | + | ||
111 | + let background = 'rgba(0,0,0,0)'; | ||
112 | + if (this.state.color != null) { | ||
113 | + background = `rgba(${ this.state.color.r }, ${ this.state.color.g }, ${ this.state.color.b }, ${ this.state.color.a })`; | ||
114 | + } | ||
115 | + | ||
116 | + const styles = reactCSS({ | ||
117 | + default: { | ||
118 | + color: { | ||
119 | + background: `${ background }` | ||
120 | + }, | ||
121 | + swatch: { | ||
122 | + display: 'inline-block', | ||
123 | + marginRight: '10px', | ||
124 | + marginTop: 'auto', | ||
125 | + marginBottom: 'auto', | ||
126 | + cursor: 'pointer', | ||
127 | + opacity: `${ this.props.form.readonly ? '0.6' : '1' }` | ||
128 | + }, | ||
129 | + swatchText: { | ||
130 | + width: '100%' | ||
131 | + }, | ||
132 | + container: { | ||
133 | + display: 'flex', | ||
134 | + flexDirection: 'row', | ||
135 | + alignItems: 'center' | ||
136 | + }, | ||
137 | + colorContainer: { | ||
138 | + display: 'flex', | ||
139 | + width: '100%' | ||
140 | + } | ||
141 | + }, | ||
142 | + }); | ||
143 | + | ||
144 | + let fieldClass = 'tb-field'; | ||
145 | + if (this.props.form.required) { | ||
146 | + fieldClass += ' tb-required'; | ||
147 | + } | ||
148 | + if (this.props.form.readonly) { | ||
149 | + fieldClass += ' tb-readonly'; | ||
150 | + } | ||
151 | + if (this.state.focused) { | ||
152 | + fieldClass += ' tb-focused'; | ||
153 | + } | ||
154 | + | ||
155 | + let stringColor = ''; | ||
156 | + if (this.state.color != null) { | ||
157 | + const color = tinycolor(this.state.color); | ||
158 | + stringColor = color.toRgbString(); | ||
159 | + } | ||
160 | + | ||
161 | + return ( | ||
162 | + <div style={ styles.container }> | ||
163 | + <div id='color-container' style={ styles.colorContainer }> | ||
164 | + <div className='tb-color-preview' style={ styles.swatch }> | ||
165 | + <div className='tb-color-result' style={ styles.color }/> | ||
166 | + </div> | ||
167 | + <TextField | ||
168 | + className={fieldClass} | ||
169 | + label={this.props.form.title} | ||
170 | + error={!this.props.valid} | ||
171 | + helperText={this.props.valid ? this.props.form.placeholder : this.props.error} | ||
172 | + value={stringColor} | ||
173 | + disabled={this.props.form.readonly} | ||
174 | + onFocus={this.onFocus} | ||
175 | + onBlur={this.onBlur} | ||
176 | + style={ styles.swatchText }/> | ||
177 | + </div> | ||
178 | + <Tooltip title='Clear' placement='top'><IconButton onClick={this.onClear}><ClearIcon/></IconButton></Tooltip> | ||
179 | + </div> | ||
180 | + ); | ||
181 | + } | ||
182 | +} | ||
183 | + | ||
184 | +export default ThingsboardBaseComponent(ThingsboardColor); |
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 ThingsboardAceEditor from './json-form-ace-editor'; | ||
18 | +import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; | ||
19 | +import { css_beautify } from 'js-beautify'; | ||
20 | + | ||
21 | +class ThingsboardCss extends React.Component<JsonFormFieldProps, JsonFormFieldState> { | ||
22 | + | ||
23 | + constructor(props) { | ||
24 | + super(props); | ||
25 | + this.onTidyCss = this.onTidyCss.bind(this); | ||
26 | + } | ||
27 | + | ||
28 | + onTidyCss(css: string): string { | ||
29 | + return css_beautify(css, {indent_size: 4}); | ||
30 | + } | ||
31 | + | ||
32 | + render() { | ||
33 | + return ( | ||
34 | + <ThingsboardAceEditor {...this.props} mode='css' onTidy={this.onTidyCss} {...this.state}></ThingsboardAceEditor> | ||
35 | + ); | ||
36 | + } | ||
37 | +} | ||
38 | + | ||
39 | +export default ThingsboardCss; |
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 ThingsboardAceEditor from './json-form-ace-editor'; | ||
18 | +import { html_beautify } from 'js-beautify'; | ||
19 | +import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; | ||
20 | + | ||
21 | +class ThingsboardHtml extends React.Component<JsonFormFieldProps, JsonFormFieldState> { | ||
22 | + | ||
23 | + constructor(props) { | ||
24 | + super(props); | ||
25 | + this.onTidyHtml = this.onTidyHtml.bind(this); | ||
26 | + } | ||
27 | + | ||
28 | + onTidyHtml(html: string): string { | ||
29 | + return html_beautify(html, {indent_size: 4}); | ||
30 | + } | ||
31 | + | ||
32 | + render() { | ||
33 | + return ( | ||
34 | + <ThingsboardAceEditor {...this.props} mode='html' onTidy={this.onTidyHtml} {...this.state}></ThingsboardAceEditor> | ||
35 | + ); | ||
36 | + } | ||
37 | +} | ||
38 | + | ||
39 | +export default ThingsboardHtml; |
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 React, {useCallback} from 'react'; | ||
17 | +import { DropzoneState, useDropzone } from 'react-dropzone'; | ||
18 | +import ThingsboardBaseComponent from './json-form-base-component'; | ||
19 | +import Dropzone from 'react-dropzone'; | ||
20 | +import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; | ||
21 | +import IconButton from '@material-ui/core/IconButton'; | ||
22 | +import ClearIcon from '@material-ui/icons/Clear'; | ||
23 | +import Tooltip from '@material-ui/core/Tooltip'; | ||
24 | + | ||
25 | +interface ThingsboardImageState extends JsonFormFieldState { | ||
26 | + imageUrl: string; | ||
27 | +} | ||
28 | + | ||
29 | +class ThingsboardImage extends React.Component<JsonFormFieldProps, ThingsboardImageState> { | ||
30 | + | ||
31 | + constructor(props) { | ||
32 | + super(props); | ||
33 | + this.onDrop = this.onDrop.bind(this); | ||
34 | + this.onClear = this.onClear.bind(this); | ||
35 | + const value = props.value ? props.value + '' : null; | ||
36 | + this.state = { | ||
37 | + imageUrl: value | ||
38 | + }; | ||
39 | + } | ||
40 | + | ||
41 | + onDrop(acceptedFiles: File[]) { | ||
42 | + const reader = new FileReader(); | ||
43 | + reader.onload = () => { | ||
44 | + this.onValueChanged(reader.result); | ||
45 | + }; | ||
46 | + reader.readAsDataURL(acceptedFiles[0]); | ||
47 | + } | ||
48 | + | ||
49 | + onValueChanged(value) { | ||
50 | + this.setState({ | ||
51 | + imageUrl: value | ||
52 | + }); | ||
53 | + this.props.onChangeValidate({ | ||
54 | + target: { | ||
55 | + value | ||
56 | + } | ||
57 | + }); | ||
58 | + } | ||
59 | + | ||
60 | + onClear(event) { | ||
61 | + if (event) { | ||
62 | + event.stopPropagation(); | ||
63 | + } | ||
64 | + this.onValueChanged(''); | ||
65 | + } | ||
66 | + | ||
67 | + render() { | ||
68 | + | ||
69 | + let labelClass = 'tb-label'; | ||
70 | + if (this.props.form.required) { | ||
71 | + labelClass += ' tb-required'; | ||
72 | + } | ||
73 | + if (this.props.form.readonly) { | ||
74 | + labelClass += ' tb-readonly'; | ||
75 | + } | ||
76 | + | ||
77 | + let previewComponent; | ||
78 | + if (this.state.imageUrl) { | ||
79 | + previewComponent = <img className='tb-image-preview' src={this.state.imageUrl} />; | ||
80 | + } else { | ||
81 | + previewComponent = <div>No image selected</div>; | ||
82 | + } | ||
83 | + | ||
84 | + return ( | ||
85 | + <div className='tb-container'> | ||
86 | + <label className={labelClass}>{this.props.form.title}</label> | ||
87 | + <div className='tb-image-select-container'> | ||
88 | + <div className='tb-image-preview-container'>{previewComponent}</div> | ||
89 | + <div className='tb-image-clear-container'> | ||
90 | + <Tooltip title='Clear' placement='top'> | ||
91 | + <IconButton className='tb-image-clear-btn' onClick={this.onClear}><ClearIcon/></IconButton> | ||
92 | + </Tooltip> | ||
93 | + </div> | ||
94 | + <Dropzone onDrop={this.onDrop} | ||
95 | + accept='image/*' multiple={false}> | ||
96 | + {({getRootProps, getInputProps}) => ( | ||
97 | + <div className='tb-dropzone' {...getRootProps()}> | ||
98 | + <div>Drop an image or click to select a file to upload.</div> | ||
99 | + <input {...getInputProps()} /> | ||
100 | + </div> | ||
101 | + )} | ||
102 | + </Dropzone> | ||
103 | + </div> | ||
104 | + </div> | ||
105 | + ); | ||
106 | + } | ||
107 | +} | ||
108 | + | ||
109 | +export default ThingsboardBaseComponent(ThingsboardImage); |
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 ThingsboardAceEditor from './json-form-ace-editor'; | ||
18 | +import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; | ||
19 | + | ||
20 | +class ThingsboardJavaScript extends React.Component<JsonFormFieldProps, JsonFormFieldState> { | ||
21 | + | ||
22 | + constructor(props) { | ||
23 | + super(props); | ||
24 | + this.onTidyJavascript = this.onTidyJavascript.bind(this); | ||
25 | + } | ||
26 | + | ||
27 | + onTidyJavascript(javascript: string): string { | ||
28 | + return js_beautify(javascript, {indent_size: 4, wrap_line_length: 60}); | ||
29 | + } | ||
30 | + | ||
31 | + render() { | ||
32 | + return ( | ||
33 | + <ThingsboardAceEditor {...this.props} mode='javascript' onTidy={this.onTidyJavascript} {...this.state}></ThingsboardAceEditor> | ||
34 | + ); | ||
35 | + } | ||
36 | +} | ||
37 | + | ||
38 | +export default ThingsboardJavaScript; |
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 ThingsboardAceEditor from './json-form-ace-editor'; | ||
18 | +import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; | ||
19 | + | ||
20 | +class ThingsboardJson extends React.Component<JsonFormFieldProps, JsonFormFieldState> { | ||
21 | + | ||
22 | + constructor(props) { | ||
23 | + super(props); | ||
24 | + this.onTidyJson = this.onTidyJson.bind(this); | ||
25 | + } | ||
26 | + | ||
27 | + onTidyJson(json: string): string { | ||
28 | + return js_beautify(json, {indent_size: 4}); | ||
29 | + } | ||
30 | + | ||
31 | + render() { | ||
32 | + return ( | ||
33 | + <ThingsboardAceEditor {...this.props} mode='json' onTidy={this.onTidyJson} {...this.state}></ThingsboardAceEditor> | ||
34 | + ); | ||
35 | + } | ||
36 | +} | ||
37 | + | ||
38 | +export default ThingsboardJson; |
@@ -17,24 +17,25 @@ import * as React from 'react'; | @@ -17,24 +17,25 @@ 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'; | 19 | import ThingsboardArray from './json-form-array'; |
20 | -/*import ThingsboardJavaScript from './json-form-javascript.jsx'; | ||
21 | -import ThingsboardJson from './json-form-json.jsx'; | ||
22 | -import ThingsboardHtml from './json-form-html.jsx'; | ||
23 | -import ThingsboardCss from './json-form-css.jsx'; | ||
24 | -import ThingsboardColor from './json-form-color.jsx'*/ | 20 | +import ThingsboardJavaScript from './json-form-javascript'; |
21 | +import ThingsboardJson from './json-form-json'; | ||
22 | +import ThingsboardHtml from './json-form-html'; | ||
23 | +import ThingsboardCss from './json-form-css'; | ||
24 | +import ThingsboardColor from './json-form-color'; | ||
25 | import ThingsboardRcSelect from './json-form-rc-select'; | 25 | import ThingsboardRcSelect from './json-form-rc-select'; |
26 | import ThingsboardNumber from './json-form-number'; | 26 | 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'; | 30 | import ThingsboardDate from './json-form-date'; |
31 | -/*import ThingsboardImage from './json-form-image.jsx';*/ | 31 | +import ThingsboardImage from './json-form-image'; |
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'; | 34 | import ThingsboardFieldSet from './json-form-fieldset'; |
35 | -import { JsonFormProps, GroupInfo, JsonFormData } from './json-form.models'; | 35 | +import { JsonFormProps, GroupInfo, JsonFormData, onChangeFn, OnColorClickFn } from './json-form.models'; |
36 | 36 | ||
37 | import _ from 'lodash'; | 37 | import _ from 'lodash'; |
38 | +import * as tinycolor from 'tinycolor2'; | ||
38 | 39 | ||
39 | class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> { | 40 | class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> { |
40 | 41 | ||
@@ -52,15 +53,15 @@ class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> { | @@ -52,15 +53,15 @@ class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> { | ||
52 | select: ThingsboardSelect, | 53 | select: ThingsboardSelect, |
53 | radios: ThingsboardRadios, | 54 | radios: ThingsboardRadios, |
54 | date: ThingsboardDate, | 55 | date: ThingsboardDate, |
55 | - // image: ThingsboardImage, | 56 | + image: ThingsboardImage, |
56 | checkbox: ThingsboardCheckbox, | 57 | checkbox: ThingsboardCheckbox, |
57 | help: ThingsboardHelp, | 58 | help: ThingsboardHelp, |
58 | array: ThingsboardArray, | 59 | array: ThingsboardArray, |
59 | - // javascript: ThingsboardJavaScript, | ||
60 | - // json: ThingsboardJson, | ||
61 | - // html: ThingsboardHtml, | ||
62 | - // css: ThingsboardCss, | ||
63 | - // color: ThingsboardColor, | 60 | + javascript: ThingsboardJavaScript, |
61 | + json: ThingsboardJson, | ||
62 | + html: ThingsboardHtml, | ||
63 | + css: ThingsboardCss, | ||
64 | + color: ThingsboardColor, | ||
64 | 'rc-select': ThingsboardRcSelect, | 65 | 'rc-select': ThingsboardRcSelect, |
65 | fieldset: ThingsboardFieldSet | 66 | fieldset: ThingsboardFieldSet |
66 | }; | 67 | }; |
@@ -78,20 +79,21 @@ class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> { | @@ -78,20 +79,21 @@ class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> { | ||
78 | } | 79 | } |
79 | } | 80 | } |
80 | 81 | ||
81 | - onColorClick(event, key, val) { | ||
82 | - this.props.onColorClick(event, key, val); | 82 | + onColorClick(key: (string | number)[], val: tinycolor.ColorFormats.RGBA, |
83 | + colorSelectedFn: (color: tinycolor.ColorFormats.RGBA) => void) { | ||
84 | + this.props.onColorClick(key, val, colorSelectedFn); | ||
83 | } | 85 | } |
84 | 86 | ||
85 | - onToggleFullscreen() { | ||
86 | - this.props.onToggleFullscreen(); | 87 | + onToggleFullscreen(element: HTMLElement, fullscreenFinishFn?: () => void) { |
88 | + this.props.onToggleFullscreen(element, fullscreenFinishFn); | ||
87 | } | 89 | } |
88 | 90 | ||
89 | 91 | ||
90 | builder(form: JsonFormData, | 92 | builder(form: JsonFormData, |
91 | model: any, | 93 | model: any, |
92 | index: number, | 94 | index: number, |
93 | - onChange: (key: (string | number)[], val: any) => void, | ||
94 | - onColorClick: (event: MouseEvent, key: string, val: string) => void, | 95 | + onChange: onChangeFn, |
96 | + onColorClick: OnColorClickFn, | ||
95 | onToggleFullscreen: () => void, | 97 | onToggleFullscreen: () => void, |
96 | mapper: {[type: string]: any}): JSX.Element { | 98 | mapper: {[type: string]: any}): JSX.Element { |
97 | const type = form.type; | 99 | const type = form.type; |
@@ -173,9 +175,9 @@ class ThingsboardSchemaGroup extends React.Component<ThingsboardSchemaGroupProps | @@ -173,9 +175,9 @@ class ThingsboardSchemaGroup extends React.Component<ThingsboardSchemaGroupProps | ||
173 | } | 175 | } |
174 | 176 | ||
175 | render() { | 177 | render() { |
176 | - const theCla = 'pull-right fa fa-chevron-down md-toggle-icon' + (this.state.showGroup ? '' : ' tb-toggled'); | 178 | + const theCla = 'pull-right fa fa-chevron-down tb-toggle-icon' + (this.state.showGroup ? '' : ' tb-toggled'); |
177 | return (<section className='mat-elevation-z1' style={{marginTop: '10px'}}> | 179 | return (<section className='mat-elevation-z1' style={{marginTop: '10px'}}> |
178 | - <div className='SchemaGroupname md-button-toggle' | 180 | + <div className='SchemaGroupname tb-button-toggle' |
179 | onClick={this.toogleGroup.bind(this)}>{this.props.info.GroupTitle}<span className={theCla}></span></div> | 181 | onClick={this.toogleGroup.bind(this)}>{this.props.info.GroupTitle}<span className={theCla}></span></div> |
180 | <div style={{padding: '20px'}} className={this.state.showGroup ? '' : 'invisible'}>{this.props.forms}</div> | 182 | <div style={{padding: '20px'}} className={this.state.showGroup ? '' : 'invisible'}>{this.props.forms}</div> |
181 | </section>); | 183 | </section>); |
@@ -544,11 +544,13 @@ function traverseSchema(schema: JsonSchemaData, fn: (prop: any, path: string[]) | @@ -544,11 +544,13 @@ function traverseSchema(schema: JsonSchemaData, fn: (prop: any, path: string[]) | ||
544 | 544 | ||
545 | const traverse = ($schema: JsonSchemaData, $fn: (prop: any, path: string[]) => any, $path: string[]) => { | 545 | const traverse = ($schema: JsonSchemaData, $fn: (prop: any, path: string[]) => any, $path: string[]) => { |
546 | $fn($schema, $path); | 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); | 547 | + if ($schema.properties) { |
548 | + for (const k of Object.keys($schema.properties)) { | ||
549 | + if ($schema.properties.hasOwnProperty(k)) { | ||
550 | + const currentPath = $path.slice(); | ||
551 | + currentPath.push(k); | ||
552 | + traverse($schema.properties[k], $fn, currentPath); | ||
553 | + } | ||
552 | } | 554 | } |
553 | } | 555 | } |
554 | if (!ignoreArrays && $schema.items) { | 556 | if (!ignoreArrays && $schema.items) { |
@@ -18,12 +18,13 @@ import { isUndefined, isDefined, isString } from '@app/core/utils'; | @@ -18,12 +18,13 @@ import { isUndefined, isDefined, isString } from '@app/core/utils'; | ||
18 | import * as equal from 'deep-equal'; | 18 | import * as equal from 'deep-equal'; |
19 | import ObjectPath from 'objectpath'; | 19 | import ObjectPath from 'objectpath'; |
20 | import * as React from 'react'; | 20 | import * as React from 'react'; |
21 | +import * as tinycolor from 'tinycolor2'; | ||
21 | 22 | ||
22 | export interface SchemaValidationResult { | 23 | export interface SchemaValidationResult { |
23 | valid: boolean; | 24 | valid: boolean; |
24 | error?: { | 25 | error?: { |
25 | message?: string; | 26 | message?: string; |
26 | - } | 27 | + }; |
27 | } | 28 | } |
28 | 29 | ||
29 | export interface FormOption { | 30 | export interface FormOption { |
@@ -47,6 +48,11 @@ export interface GroupInfo { | @@ -47,6 +48,11 @@ export interface GroupInfo { | ||
47 | GroupTitle: string; | 48 | GroupTitle: string; |
48 | } | 49 | } |
49 | 50 | ||
51 | +export type onChangeFn = (key: (string | number)[], val: any) => void; | ||
52 | +export type OnColorClickFn = (key: (string | number)[], val: tinycolor.ColorFormats.RGBA, | ||
53 | + colorSelectedFn: (color: tinycolor.ColorFormats.RGBA) => void) => void; | ||
54 | +export type onToggleFullscreenFn = (element: HTMLElement, fullscreenFinishFn?: () => void) => void; | ||
55 | + | ||
50 | export interface JsonFormProps { | 56 | export interface JsonFormProps { |
51 | model?: any; | 57 | model?: any; |
52 | schema?: any; | 58 | schema?: any; |
@@ -55,9 +61,9 @@ export interface JsonFormProps { | @@ -55,9 +61,9 @@ export interface JsonFormProps { | ||
55 | isFullscreen: boolean; | 61 | isFullscreen: boolean; |
56 | ignore?: {[key: string]: boolean}; | 62 | ignore?: {[key: string]: boolean}; |
57 | option: FormOption; | 63 | option: FormOption; |
58 | - onModelChange?: (key: (string | number)[], val: any) => void; | ||
59 | - onColorClick?: (event: MouseEvent, key: string, val: string) => void; | ||
60 | - onToggleFullscreen?: () => void; | 64 | + onModelChange?: onChangeFn; |
65 | + onColorClick?: OnColorClickFn; | ||
66 | + onToggleFullscreen?: onToggleFullscreenFn; | ||
61 | mapper?: {[type: string]: any}; | 67 | mapper?: {[type: string]: any}; |
62 | } | 68 | } |
63 | 69 | ||
@@ -98,22 +104,24 @@ export interface JsonFormData { | @@ -98,22 +104,24 @@ export interface JsonFormData { | ||
98 | [key: string]: any; | 104 | [key: string]: any; |
99 | } | 105 | } |
100 | 106 | ||
107 | +export type ComponentBuilderFn = (form: JsonFormData, | ||
108 | + model: any, | ||
109 | + index: number, | ||
110 | + onChange: onChangeFn, | ||
111 | + onColorClick: OnColorClickFn, | ||
112 | + onToggleFullscreen: onToggleFullscreenFn, | ||
113 | + mapper: {[type: string]: any}) => JSX.Element; | ||
114 | + | ||
101 | export interface JsonFormFieldProps { | 115 | export interface JsonFormFieldProps { |
102 | value: any; | 116 | value: any; |
103 | model: any; | 117 | model: any; |
104 | form: JsonFormData; | 118 | form: JsonFormData; |
105 | - builder: (form: JsonFormData, | ||
106 | - model: any, | ||
107 | - index: number, | ||
108 | - onChange: (key: (string | number)[], val: any) => void, | ||
109 | - onColorClick: (event: MouseEvent, key: string, val: string) => void, | ||
110 | - onToggleFullscreen: () => void, | ||
111 | - mapper: {[type: string]: any}) => JSX.Element; | 119 | + builder: ComponentBuilderFn; |
112 | mapper?: {[type: string]: any}; | 120 | mapper?: {[type: string]: any}; |
113 | - onChange?: (key: (string | number)[], val: any) => void; | ||
114 | - onColorClick?: (event: MouseEvent, key: string, val: string) => void; | 121 | + onChange?: onChangeFn; |
122 | + onColorClick?: OnColorClickFn; | ||
115 | onChangeValidate?: (e: any) => void; | 123 | onChangeValidate?: (e: any) => void; |
116 | - onToggleFullscreen?: () => void; | 124 | + onToggleFullscreen?: onToggleFullscreenFn; |
117 | valid?: boolean; | 125 | valid?: boolean; |
118 | error?: string; | 126 | error?: string; |
119 | options?: { | 127 | options?: { |
@@ -21,47 +21,24 @@ $swift-ease-out-timing-function: cubic-bezier(.25, .8, .25, 1) !default; | @@ -21,47 +21,24 @@ $swift-ease-out-timing-function: cubic-bezier(.25, .8, .25, 1) !default; | ||
21 | $input-label-float-offset: 6px !default; | 21 | $input-label-float-offset: 6px !default; |
22 | $input-label-float-scale: .75 !default; | 22 | $input-label-float-scale: .75 !default; |
23 | 23 | ||
24 | -.tb-json-form { | ||
25 | - &.tb-fullscreen { | ||
26 | - [name="ReactSchemaForm"] { | ||
27 | - .SchemaForm { | ||
28 | - &.SchemaFormFullscreen { | ||
29 | - position: absolute; | ||
30 | - top: 0; | ||
31 | - right: 0; | ||
32 | - bottom: 0; | ||
33 | - left: 0; | ||
34 | - | ||
35 | - > div:not(.fullscreen-form-field) { | ||
36 | - display: none !important; | ||
37 | - } | ||
38 | - | ||
39 | - > div.fullscreen-form-field { | ||
40 | - position: relative; | ||
41 | - width: 100%; | ||
42 | - height: 100%; | ||
43 | - } | ||
44 | - } | ||
45 | - } | 24 | +$previewSize: 100px !default; |
46 | 25 | ||
47 | - > div { | ||
48 | - > section { | ||
49 | - margin: 0 !important; | ||
50 | - box-shadow: none !important; | ||
51 | - | ||
52 | - .SchemaGroupname { | ||
53 | - display: none !important; | ||
54 | - } | 26 | +.tb-json-form { |
55 | 27 | ||
56 | - > div { | ||
57 | - padding: 0 !important; | ||
58 | - } | ||
59 | - } | ||
60 | - } | 28 | + &.tb-fullscreen { |
29 | + background: #fff; | ||
30 | + position: absolute; | ||
31 | + top: 0; | ||
32 | + right: 0; | ||
33 | + bottom: 0; | ||
34 | + left: 0; | ||
35 | + > div.fullscreen-form-field { | ||
36 | + position: relative; | ||
37 | + width: 100%; | ||
38 | + height: 100%; | ||
61 | } | 39 | } |
62 | } | 40 | } |
63 | 41 | ||
64 | - | ||
65 | .json-form-error { | 42 | .json-form-error { |
66 | position: relative; | 43 | position: relative; |
67 | bottom: -5px; | 44 | bottom: -5px; |
@@ -74,6 +51,7 @@ $input-label-float-scale: .75 !default; | @@ -74,6 +51,7 @@ $input-label-float-scale: .75 !default; | ||
74 | 51 | ||
75 | .tb-container { | 52 | .tb-container { |
76 | position: relative; | 53 | position: relative; |
54 | + box-sizing: border-box; | ||
77 | padding: 10px 0; | 55 | padding: 10px 0; |
78 | margin-top: 32px; | 56 | margin-top: 32px; |
79 | } | 57 | } |
@@ -144,6 +122,7 @@ $input-label-float-scale: .75 !default; | @@ -144,6 +122,7 @@ $input-label-float-scale: .75 !default; | ||
144 | 122 | ||
145 | .tb-head-label { | 123 | .tb-head-label { |
146 | color: rgba(0, 0, 0, .54); | 124 | color: rgba(0, 0, 0, .54); |
125 | + padding-bottom: 15px; | ||
147 | } | 126 | } |
148 | 127 | ||
149 | .SchemaGroupname { | 128 | .SchemaGroupname { |
@@ -154,4 +133,130 @@ $input-label-float-scale: .75 !default; | @@ -154,4 +133,130 @@ $input-label-float-scale: .75 !default; | ||
154 | .invisible { | 133 | .invisible { |
155 | display: none; | 134 | display: none; |
156 | } | 135 | } |
136 | + | ||
137 | + span.tb-toggle-icon { | ||
138 | + padding-top: 12px; | ||
139 | + padding-bottom: 12px; | ||
140 | + } | ||
141 | + | ||
142 | + .tb-button-toggle .tb-toggle-icon { | ||
143 | + display: inline-block; | ||
144 | + width: 15px; | ||
145 | + margin: auto 0 auto auto; | ||
146 | + background-size: 100% auto; | ||
147 | + | ||
148 | + transition: transform .3s, ease-in-out; | ||
149 | + } | ||
150 | + | ||
151 | + .tb-button-toggle .tb-toggle-icon.tb-toggled { | ||
152 | + transform: rotateZ(180deg); | ||
153 | + } | ||
154 | + | ||
155 | + .fullscreen-form-field { | ||
156 | + .json-form-ace-editor { | ||
157 | + height: calc(100% - 60px); | ||
158 | + } | ||
159 | + } | ||
160 | + | ||
161 | + .json-form-ace-editor { | ||
162 | + position: relative; | ||
163 | + height: 100%; | ||
164 | + border: 1px solid #c0c0c0; | ||
165 | + | ||
166 | + .title-panel { | ||
167 | + position: absolute; | ||
168 | + top: 10px; | ||
169 | + right: 20px; | ||
170 | + z-index: 5; | ||
171 | + font-size: .8rem; | ||
172 | + font-weight: 500; | ||
173 | + | ||
174 | + label { | ||
175 | + padding: 4px; | ||
176 | + color: #00acc1; | ||
177 | + text-transform: uppercase; | ||
178 | + background: rgba(220, 220, 220, .35); | ||
179 | + border-radius: 5px; | ||
180 | + } | ||
181 | + | ||
182 | + button.tidy-button { | ||
183 | + background: rgba(220, 220, 220, .35) !important; | ||
184 | + | ||
185 | + span { | ||
186 | + padding: 0 !important; | ||
187 | + font-size: 12px !important; | ||
188 | + } | ||
189 | + } | ||
190 | + } | ||
191 | + } | ||
192 | + | ||
193 | + .tb-image-select-container { | ||
194 | + position: relative; | ||
195 | + width: 100%; | ||
196 | + height: $previewSize; | ||
197 | + } | ||
198 | + | ||
199 | + .tb-image-preview { | ||
200 | + width: auto; | ||
201 | + max-width: $previewSize; | ||
202 | + height: auto; | ||
203 | + max-height: $previewSize; | ||
204 | + } | ||
205 | + | ||
206 | + .tb-image-preview-container { | ||
207 | + position: relative; | ||
208 | + float: left; | ||
209 | + width: $previewSize; | ||
210 | + height: $previewSize; | ||
211 | + margin-right: 12px; | ||
212 | + vertical-align: top; | ||
213 | + border: solid 1px; | ||
214 | + | ||
215 | + div { | ||
216 | + width: 100%; | ||
217 | + font-size: 18px; | ||
218 | + text-align: center; | ||
219 | + } | ||
220 | + | ||
221 | + div, .tb-image-preview { | ||
222 | + position: absolute; | ||
223 | + top: 50%; | ||
224 | + left: 50%; | ||
225 | + transform: translate(-50%, -50%); | ||
226 | + } | ||
227 | + } | ||
228 | + | ||
229 | + .tb-dropzone { | ||
230 | + outline: none; | ||
231 | + position: relative; | ||
232 | + height: $previewSize; | ||
233 | + padding: 0 8px; | ||
234 | + overflow: hidden; | ||
235 | + vertical-align: top; | ||
236 | + border: dashed 2px; | ||
237 | + | ||
238 | + div { | ||
239 | + position: absolute; | ||
240 | + top: 50%; | ||
241 | + left: 50%; | ||
242 | + width: 100%; | ||
243 | + font-size: 24px; | ||
244 | + text-align: center; | ||
245 | + transform: translate(-50%, -50%); | ||
246 | + } | ||
247 | + } | ||
248 | + | ||
249 | + .tb-image-clear-container { | ||
250 | + position: relative; | ||
251 | + float: right; | ||
252 | + width: 48px; | ||
253 | + height: $previewSize; | ||
254 | + } | ||
255 | + | ||
256 | + .tb-image-clear-btn { | ||
257 | + position: absolute !important; | ||
258 | + top: 50%; | ||
259 | + transform: translate(0%, -50%) !important; | ||
260 | + } | ||
261 | + | ||
157 | } | 262 | } |
@@ -283,11 +283,6 @@ export interface LegendData { | @@ -283,11 +283,6 @@ export interface LegendData { | ||
283 | data: Array<LegendKeyData>; | 283 | data: Array<LegendKeyData>; |
284 | } | 284 | } |
285 | 285 | ||
286 | -export interface WidgetConfigSettings { | ||
287 | - [key: string]: any; | ||
288 | - // TODO: | ||
289 | -} | ||
290 | - | ||
291 | export enum WidgetActionType { | 286 | export enum WidgetActionType { |
292 | openDashboardState = 'openDashboardState', | 287 | openDashboardState = 'openDashboardState', |
293 | updateDashboardState = 'updateDashboardState', | 288 | updateDashboardState = 'updateDashboardState', |
@@ -347,7 +342,7 @@ export interface WidgetConfig { | @@ -347,7 +342,7 @@ export interface WidgetConfig { | ||
347 | units?: string; | 342 | units?: string; |
348 | decimals?: number; | 343 | decimals?: number; |
349 | actions?: {[actionSourceId: string]: Array<WidgetActionDescriptor>}; | 344 | actions?: {[actionSourceId: string]: Array<WidgetActionDescriptor>}; |
350 | - settings?: WidgetConfigSettings; | 345 | + settings?: any; |
351 | alarmSource?: Datasource; | 346 | alarmSource?: Datasource; |
352 | alarmSearchStatus?: AlarmSearchStatus; | 347 | alarmSearchStatus?: AlarmSearchStatus; |
353 | alarmsPollingInterval?: number; | 348 | alarmsPollingInterval?: number; |
@@ -39,3 +39,12 @@ | @@ -39,3 +39,12 @@ | ||
39 | margin: auto; | 39 | margin: auto; |
40 | } | 40 | } |
41 | } | 41 | } |
42 | + | ||
43 | +@mixin tb-checkered-bg() { | ||
44 | + background-color: #fff; | ||
45 | + background-image: | ||
46 | + linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd), | ||
47 | + linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd); | ||
48 | + background-position: 0 0, 4px 4px; | ||
49 | + background-size: 8px 8px; | ||
50 | +} |
@@ -852,4 +852,25 @@ mat-label { | @@ -852,4 +852,25 @@ mat-label { | ||
852 | } | 852 | } |
853 | } | 853 | } |
854 | } | 854 | } |
855 | + | ||
856 | + .tb-color-preview { | ||
857 | + cursor: pointer; | ||
858 | + box-sizing: border-box; | ||
859 | + position: relative; | ||
860 | + width: 24px; | ||
861 | + min-width: 24px; | ||
862 | + height: 24px; | ||
863 | + overflow: hidden; | ||
864 | + content: ""; | ||
865 | + border: 2px solid #fff; | ||
866 | + border-radius: 50%; | ||
867 | + box-shadow: 0 3px 1px -2px rgba(0, 0, 0, .14), 0 2px 2px 0 rgba(0, 0, 0, .098), 0 1px 5px 0 rgba(0, 0, 0, .084); | ||
868 | + | ||
869 | + @include tb-checkered-bg(); | ||
870 | + | ||
871 | + .tb-color-result { | ||
872 | + width: 100%; | ||
873 | + height: 100%; | ||
874 | + } | ||
875 | + } | ||
855 | } | 876 | } |