Commit 516ae3d8142c4c554cd20499bc6a2774300b48eb
Committed by
GitHub
1 parent
25ba8139
Create ui to support attribute type JSON (#2471)
Showing
17 changed files
with
384 additions
and
14 deletions
@@ -168,7 +168,7 @@ | @@ -168,7 +168,7 @@ | ||
168 | class="tb-value-cell" | 168 | class="tb-value-cell" |
169 | (click)="editAttribute($event, attribute)"> | 169 | (click)="editAttribute($event, attribute)"> |
170 | <div fxLayout="row"> | 170 | <div fxLayout="row"> |
171 | - <span fxFlex>{{attribute.value}}</span> | 171 | + <span fxFlex>{{attribute.value | tbJson}}</span> |
172 | <span [fxShow]="!isClientSideTelemetryTypeMap.get(attributeScope)"> | 172 | <span [fxShow]="!isClientSideTelemetryTypeMap.get(attributeScope)"> |
173 | <mat-icon>edit</mat-icon> | 173 | <mat-icon>edit</mat-icon> |
174 | </span> | 174 | </span> |
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2020 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<form [formGroup]="jsonFormGroup" (ngSubmit)="add()" style="min-width: 400px;"> | ||
19 | + <mat-toolbar fxLayout="row" color="primary"> | ||
20 | + <h2>{{ (this.data.title ? this.data.title : 'details.edit-json') | translate }}</h2> | ||
21 | + <span fxFlex></span> | ||
22 | + <button mat-button mat-icon-button | ||
23 | + (click)="cancel()" | ||
24 | + type="button"> | ||
25 | + <mat-icon class="material-icons">close</mat-icon> | ||
26 | + </button> | ||
27 | + </mat-toolbar> | ||
28 | + <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> | ||
29 | + </mat-progress-bar> | ||
30 | + <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> | ||
31 | + <div mat-dialog-content> | ||
32 | + <fieldset [disabled]="isLoading$ | async"> | ||
33 | + <tb-json-object-edit | ||
34 | + formControlName="json" | ||
35 | + label="{{ 'value.json-value' | translate }}" | ||
36 | + validateContent="true" | ||
37 | + [required]="true" | ||
38 | + [fillHeight]="false"> | ||
39 | + </tb-json-object-edit> | ||
40 | + </fieldset> | ||
41 | + </div> | ||
42 | + <div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center"> | ||
43 | + <span fxFlex></span> | ||
44 | + <button mat-button mat-raised-button color="primary" | ||
45 | + type="submit" | ||
46 | + [disabled]="(isLoading$ | async) || jsonFormGroup.invalid || !jsonFormGroup.dirty"> | ||
47 | + {{ 'action.save' | translate }} | ||
48 | + </button> | ||
49 | + <button mat-button color="primary" | ||
50 | + style="margin-right: 20px;" | ||
51 | + type="button" | ||
52 | + [disabled]="(isLoading$ | async)" | ||
53 | + (click)="cancel()" cdkFocusInitial> | ||
54 | + {{ 'action.cancel' | translate }} | ||
55 | + </button> | ||
56 | + </div> | ||
57 | +</form> |
1 | +/// | ||
2 | +/// Copyright © 2016-2020 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 {Component, Inject, OnInit} from "@angular/core"; | ||
18 | +import {DialogComponent} from "@shared/components/dialog.component"; | ||
19 | +import {Store} from "@ngrx/store"; | ||
20 | +import {AppState} from "@core/core.state"; | ||
21 | +import {Router} from "@angular/router"; | ||
22 | +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; | ||
23 | +import {FormBuilder, FormGroup} from "@angular/forms"; | ||
24 | + | ||
25 | +export interface JsonObjectEdittDialogData { | ||
26 | + jsonValue: Object; | ||
27 | + title?: string; | ||
28 | +} | ||
29 | + | ||
30 | +@Component({ | ||
31 | + selector: 'tb-object-edit-dialog', | ||
32 | + templateUrl: './json-object-edit-dialog.component.html', | ||
33 | + styleUrls: [] | ||
34 | +}) | ||
35 | +export class JsonObjectEditDialogComponent extends DialogComponent<JsonObjectEditDialogComponent, Object> | ||
36 | + implements OnInit { | ||
37 | + | ||
38 | + jsonFormGroup: FormGroup; | ||
39 | + | ||
40 | + submitted = false; | ||
41 | + | ||
42 | + constructor(protected store: Store<AppState>, | ||
43 | + protected router: Router, | ||
44 | + @Inject(MAT_DIALOG_DATA) public data: JsonObjectEdittDialogData, | ||
45 | + public dialogRef: MatDialogRef<JsonObjectEditDialogComponent, Object>, | ||
46 | + public fb: FormBuilder) { | ||
47 | + super(store, router, dialogRef); | ||
48 | + } | ||
49 | + | ||
50 | + ngOnInit(): void { | ||
51 | + this.jsonFormGroup = this.fb.group({ | ||
52 | + json: [this.data.jsonValue, []] | ||
53 | + }); | ||
54 | + } | ||
55 | + | ||
56 | + cancel(): void { | ||
57 | + this.dialogRef.close(undefined); | ||
58 | + } | ||
59 | + | ||
60 | + add(): void { | ||
61 | + this.dialogRef.close(this.jsonFormGroup.get('json').value); | ||
62 | + } | ||
63 | +} |
1 | +/// | ||
2 | +/// Copyright © 2016-2020 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 {Directive, ElementRef, forwardRef, HostListener, Renderer2, SkipSelf} from "@angular/core"; | ||
18 | +import { | ||
19 | + ControlValueAccessor, | ||
20 | + FormControl, FormGroupDirective, | ||
21 | + NG_VALIDATORS, | ||
22 | + NG_VALUE_ACCESSOR, NgForm, | ||
23 | + ValidationErrors, | ||
24 | + Validator | ||
25 | +} from "@angular/forms"; | ||
26 | +import {ErrorStateMatcher} from "@angular/material/core"; | ||
27 | + | ||
28 | +@Directive({ | ||
29 | + selector: '[tb-json-to-string]', | ||
30 | + providers: [{ | ||
31 | + provide: NG_VALUE_ACCESSOR, | ||
32 | + useExisting: forwardRef(() => TbJsonToStringDirective), | ||
33 | + multi: true | ||
34 | + }, | ||
35 | + { | ||
36 | + provide: NG_VALIDATORS, | ||
37 | + useExisting: forwardRef(() => TbJsonToStringDirective), | ||
38 | + multi: true, | ||
39 | + }, | ||
40 | + { | ||
41 | + provide: ErrorStateMatcher, | ||
42 | + useExisting: TbJsonToStringDirective | ||
43 | + }] | ||
44 | +}) | ||
45 | + | ||
46 | +export class TbJsonToStringDirective implements ControlValueAccessor, Validator, ErrorStateMatcher { | ||
47 | + private propagateChange = null; | ||
48 | + private parseError: boolean; | ||
49 | + private data: any; | ||
50 | + | ||
51 | + @HostListener('input', ['$event.target.value']) input(newValue: any): void { | ||
52 | + try { | ||
53 | + this.data = JSON.parse(newValue); | ||
54 | + this.parseError = false; | ||
55 | + } catch (e) { | ||
56 | + this.parseError = true; | ||
57 | + } | ||
58 | + | ||
59 | + this.propagateChange(this.data); | ||
60 | + } | ||
61 | + | ||
62 | + constructor(private render: Renderer2, | ||
63 | + private element: ElementRef, | ||
64 | + @SkipSelf() private errorStateMatcher: ErrorStateMatcher) { | ||
65 | + | ||
66 | + } | ||
67 | + | ||
68 | + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { | ||
69 | + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); | ||
70 | + const customErrorState = !!(control && control.invalid && this.parseError); | ||
71 | + return originalErrorState || customErrorState; | ||
72 | + } | ||
73 | + | ||
74 | + validate(c: FormControl): ValidationErrors { | ||
75 | + return (!this.parseError) ? null : { | ||
76 | + invalidJSON: { | ||
77 | + valid: false | ||
78 | + } | ||
79 | + }; | ||
80 | + } | ||
81 | + | ||
82 | + writeValue(obj: any): void { | ||
83 | + if (obj) { | ||
84 | + this.data = obj; | ||
85 | + this.parseError = false; | ||
86 | + this.render.setProperty(this.element.nativeElement, 'value', JSON.stringify(obj)); | ||
87 | + } | ||
88 | + } | ||
89 | + | ||
90 | + registerOnChange(fn: any): void { | ||
91 | + this.propagateChange = fn; | ||
92 | + } | ||
93 | + | ||
94 | + registerOnTouched(fn: any): void { | ||
95 | + } | ||
96 | +} |
@@ -22,9 +22,13 @@ | @@ -22,9 +22,13 @@ | ||
22 | <label class="tb-title no-padding">{{ label }}</label> | 22 | <label class="tb-title no-padding">{{ label }}</label> |
23 | <span fxFlex></span> | 23 | <span fxFlex></span> |
24 | <button type="button" | 24 | <button type="button" |
25 | - mat-button *ngIf="!readonly" class="tidy" (click)="beautifyJson()"> | 25 | + mat-button *ngIf="!readonly" class="tidy" (click)="beautifyJSON()"> |
26 | {{'js-func.tidy' | translate }} | 26 | {{'js-func.tidy' | translate }} |
27 | </button> | 27 | </button> |
28 | + <button type="button" | ||
29 | + mat-button *ngIf="!readonly" class="tidy" (click)="minifyJSON()"> | ||
30 | + {{'js-func.mini' | translate }} | ||
31 | + </button> | ||
28 | <button type='button' mat-button mat-icon-button (click)="fullscreen = !fullscreen" | 32 | <button type='button' mat-button mat-icon-button (click)="fullscreen = !fullscreen" |
29 | class="tb-mat-32" | 33 | class="tb-mat-32" |
30 | matTooltip="{{(fullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}" | 34 | matTooltip="{{(fullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}" |
@@ -257,12 +257,18 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid | @@ -257,12 +257,18 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid | ||
257 | } | 257 | } |
258 | } | 258 | } |
259 | 259 | ||
260 | - beautifyJson() { | 260 | + beautifyJSON() { |
261 | const res = js_beautify(this.contentBody, {indent_size: 4, wrap_line_length: 60}); | 261 | const res = js_beautify(this.contentBody, {indent_size: 4, wrap_line_length: 60}); |
262 | this.jsonEditor.setValue(res ? res : '', -1); | 262 | this.jsonEditor.setValue(res ? res : '', -1); |
263 | this.updateView(); | 263 | this.updateView(); |
264 | } | 264 | } |
265 | 265 | ||
266 | + minifyJSON() { | ||
267 | + const res = JSON.stringify(this.contentBody); | ||
268 | + this.jsonEditor.setValue(res ? res : '', -1); | ||
269 | + this.updateView(); | ||
270 | + } | ||
271 | + | ||
266 | onFullscreen() { | 272 | onFullscreen() { |
267 | if (this.jsonEditor) { | 273 | if (this.jsonEditor) { |
268 | setTimeout(() => { | 274 | setTimeout(() => { |
@@ -18,12 +18,20 @@ | @@ -18,12 +18,20 @@ | ||
18 | <div style="background: #fff;" [ngClass]="{'fill-height': fillHeight}" | 18 | <div style="background: #fff;" [ngClass]="{'fill-height': fillHeight}" |
19 | tb-fullscreen | 19 | tb-fullscreen |
20 | [fullscreen]="fullscreen" (fullscreenChanged)="onFullscreen()" fxLayout="column"> | 20 | [fullscreen]="fullscreen" (fullscreenChanged)="onFullscreen()" fxLayout="column"> |
21 | - <div fxLayout="row" fxLayoutAlign="start center"> | 21 | + <div fxLayout="row" fxLayoutAlign="start center" class="tb-json-object-toolbar"> |
22 | <label class="tb-title no-padding" | 22 | <label class="tb-title no-padding" |
23 | ng-class="{'tb-required': required, | 23 | ng-class="{'tb-required': required, |
24 | 'tb-readonly': readonly, | 24 | 'tb-readonly': readonly, |
25 | 'tb-error': !objectValid}">{{ label }}</label> | 25 | 'tb-error': !objectValid}">{{ label }}</label> |
26 | <span fxFlex></span> | 26 | <span fxFlex></span> |
27 | + <button type="button" | ||
28 | + mat-button *ngIf="!readonly" class="tidy" (click)="beautifyJSON()"> | ||
29 | + {{'js-func.tidy' | translate }} | ||
30 | + </button> | ||
31 | + <button type="button" | ||
32 | + mat-button *ngIf="!readonly" class="tidy" (click)="minifyJSON()"> | ||
33 | + {{'js-func.mini' | translate }} | ||
34 | + </button> | ||
27 | <button mat-button mat-icon-button (click)="fullscreen = !fullscreen" | 35 | <button mat-button mat-icon-button (click)="fullscreen = !fullscreen" |
28 | matTooltip="{{(fullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}" | 36 | matTooltip="{{(fullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}" |
29 | matTooltipPosition="above"> | 37 | matTooltipPosition="above"> |
@@ -21,6 +21,25 @@ | @@ -21,6 +21,25 @@ | ||
21 | } | 21 | } |
22 | } | 22 | } |
23 | 23 | ||
24 | +.tb-json-object-toolbar { | ||
25 | + button.mat-button, button.mat-icon-button, button.mat-icon-button.tb-mat-32 { | ||
26 | + align-items: center; | ||
27 | + vertical-align: middle; | ||
28 | + min-width: 32px; | ||
29 | + min-height: 15px; | ||
30 | + padding: 4px; | ||
31 | + margin: 0; | ||
32 | + font-size: .8rem; | ||
33 | + line-height: 15px; | ||
34 | + color: #7b7b7b; | ||
35 | + background: rgba(220, 220, 220, .35); | ||
36 | + | ||
37 | + &:not(:last-child) { | ||
38 | + margin-right: 4px; | ||
39 | + } | ||
40 | + } | ||
41 | +} | ||
42 | + | ||
24 | .tb-json-object-panel { | 43 | .tb-json-object-panel { |
25 | height: 100%; | 44 | height: 100%; |
26 | margin-left: 15px; | 45 | margin-left: 15px; |
@@ -195,6 +195,22 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va | @@ -195,6 +195,22 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va | ||
195 | } | 195 | } |
196 | } | 196 | } |
197 | 197 | ||
198 | + beautifyJSON() { | ||
199 | + const res = JSON.stringify(this.modelValue, null, 2); | ||
200 | + if (this.jsonEditor) { | ||
201 | + this.jsonEditor.setValue(res ? res : '', -1); | ||
202 | + } | ||
203 | + this.updateView(); | ||
204 | + } | ||
205 | + | ||
206 | + minifyJSON() { | ||
207 | + const res = JSON.stringify(this.modelValue); | ||
208 | + if (this.jsonEditor) { | ||
209 | + this.jsonEditor.setValue(res ? res : '', -1); | ||
210 | + } | ||
211 | + this.updateView(); | ||
212 | + } | ||
213 | + | ||
198 | writeValue(value: any): void { | 214 | writeValue(value: any): void { |
199 | this.modelValue = value; | 215 | this.modelValue = value; |
200 | this.contentValue = ''; | 216 | this.contentValue = ''; |
@@ -59,5 +59,21 @@ | @@ -59,5 +59,21 @@ | ||
59 | {{ (modelValue ? 'value.true' : 'value.false') | translate }} | 59 | {{ (modelValue ? 'value.true' : 'value.false') | translate }} |
60 | </mat-checkbox> | 60 | </mat-checkbox> |
61 | </div> | 61 | </div> |
62 | + <div fxLayout="row" fxLayoutAlign="center" fxFlex="60" *ngIf="valueType === valueTypeEnum.JSON" class="mat-block"> | ||
63 | + <mat-form-field fxFlex class="mat-block"> | ||
64 | + <mat-label translate>value.json-value</mat-label> | ||
65 | + <input [disabled]="disabled" matInput tb-json-to-string required name="value" #value="ngModel" | ||
66 | + [(ngModel)]="modelValue" (ngModelChange)="onValueChanged()"/> | ||
67 | + <button matSuffix mat-button mat-icon-button (click)="openEditJSONDialog($event)"> | ||
68 | + <mat-icon>open_in_new</mat-icon> | ||
69 | + </button> | ||
70 | + <mat-error *ngIf="value.hasError('required')"> | ||
71 | + {{ (requiredText ? requiredText : 'value.json-value-required') | translate }} | ||
72 | + </mat-error> | ||
73 | + <mat-error *ngIf="value.hasError('invalidJSON')"> | ||
74 | + {{ 'value.json-value-invalid' | translate }} | ||
75 | + </mat-error> | ||
76 | + </mat-form-field> | ||
77 | + </div> | ||
62 | </section> | 78 | </section> |
63 | </form> | 79 | </form> |
@@ -17,6 +17,12 @@ | @@ -17,6 +17,12 @@ | ||
17 | import { Component, forwardRef, Input, OnInit, ViewChild } from '@angular/core'; | 17 | import { Component, forwardRef, Input, OnInit, ViewChild } from '@angular/core'; |
18 | import { ControlValueAccessor, NG_VALUE_ACCESSOR, NgForm } from '@angular/forms'; | 18 | import { ControlValueAccessor, NG_VALUE_ACCESSOR, NgForm } from '@angular/forms'; |
19 | import { ValueType, valueTypesMap } from '@shared/models/constants'; | 19 | import { ValueType, valueTypesMap } from '@shared/models/constants'; |
20 | +import { isObject } from "@core/utils"; | ||
21 | +import { MatDialog } from "@angular/material/dialog"; | ||
22 | +import { | ||
23 | + JsonObjectEditDialogComponent, | ||
24 | + JsonObjectEdittDialogData | ||
25 | +} from "@shared/components/dialog/json-object-edit-dialog.component"; | ||
20 | 26 | ||
21 | @Component({ | 27 | @Component({ |
22 | selector: 'tb-value-input', | 28 | selector: 'tb-value-input', |
@@ -50,13 +56,35 @@ export class ValueInputComponent implements OnInit, ControlValueAccessor { | @@ -50,13 +56,35 @@ export class ValueInputComponent implements OnInit, ControlValueAccessor { | ||
50 | 56 | ||
51 | private propagateChange = null; | 57 | private propagateChange = null; |
52 | 58 | ||
53 | - constructor() { | 59 | + constructor( |
60 | + public dialog: MatDialog, | ||
61 | + ) { | ||
54 | 62 | ||
55 | } | 63 | } |
56 | 64 | ||
57 | ngOnInit(): void { | 65 | ngOnInit(): void { |
58 | } | 66 | } |
59 | 67 | ||
68 | + openEditJSONDialog($event: Event) { | ||
69 | + if ($event) { | ||
70 | + $event.stopPropagation(); | ||
71 | + } | ||
72 | + this.dialog.open<JsonObjectEditDialogComponent, JsonObjectEdittDialogData, Object>(JsonObjectEditDialogComponent, { | ||
73 | + disableClose: true, | ||
74 | + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], | ||
75 | + data: { | ||
76 | + jsonValue: this.modelValue | ||
77 | + } | ||
78 | + }).afterClosed().subscribe( | ||
79 | + (res) => { | ||
80 | + if (res) { | ||
81 | + this.modelValue = res; | ||
82 | + this.inputForm.control.patchValue({'value': this.modelValue}); | ||
83 | + } | ||
84 | + } | ||
85 | + ); | ||
86 | + } | ||
87 | + | ||
60 | registerOnChange(fn: any): void { | 88 | registerOnChange(fn: any): void { |
61 | this.propagateChange = fn; | 89 | this.propagateChange = fn; |
62 | } | 90 | } |
@@ -78,6 +106,8 @@ export class ValueInputComponent implements OnInit, ControlValueAccessor { | @@ -78,6 +106,8 @@ export class ValueInputComponent implements OnInit, ControlValueAccessor { | ||
78 | } else { | 106 | } else { |
79 | this.valueType = ValueType.DOUBLE; | 107 | this.valueType = ValueType.DOUBLE; |
80 | } | 108 | } |
109 | + } else if (isObject(this.modelValue)) { | ||
110 | + this.valueType = ValueType.JSON; | ||
81 | } else { | 111 | } else { |
82 | this.valueType = ValueType.STRING; | 112 | this.valueType = ValueType.STRING; |
83 | } | 113 | } |
@@ -94,6 +124,8 @@ export class ValueInputComponent implements OnInit, ControlValueAccessor { | @@ -94,6 +124,8 @@ export class ValueInputComponent implements OnInit, ControlValueAccessor { | ||
94 | onValueTypeChanged() { | 124 | onValueTypeChanged() { |
95 | if (this.valueType === ValueType.BOOLEAN) { | 125 | if (this.valueType === ValueType.BOOLEAN) { |
96 | this.modelValue = false; | 126 | this.modelValue = false; |
127 | + } if (this.valueType === ValueType.JSON) { | ||
128 | + this.modelValue = {}; | ||
97 | } else { | 129 | } else { |
98 | this.modelValue = null; | 130 | this.modelValue = null; |
99 | } | 131 | } |
@@ -121,7 +121,8 @@ export enum ValueType { | @@ -121,7 +121,8 @@ export enum ValueType { | ||
121 | STRING = 'STRING', | 121 | STRING = 'STRING', |
122 | INTEGER = 'INTEGER', | 122 | INTEGER = 'INTEGER', |
123 | DOUBLE = 'DOUBLE', | 123 | DOUBLE = 'DOUBLE', |
124 | - BOOLEAN = 'BOOLEAN' | 124 | + BOOLEAN = 'BOOLEAN', |
125 | + JSON = 'JSON' | ||
125 | } | 126 | } |
126 | 127 | ||
127 | export const valueTypesMap = new Map<ValueType, ValueTypeData>( | 128 | export const valueTypesMap = new Map<ValueType, ValueTypeData>( |
@@ -153,6 +154,13 @@ export const valueTypesMap = new Map<ValueType, ValueTypeData>( | @@ -153,6 +154,13 @@ export const valueTypesMap = new Map<ValueType, ValueTypeData>( | ||
153 | name: 'value.boolean', | 154 | name: 'value.boolean', |
154 | icon: 'mdi:checkbox-marked-outline' | 155 | icon: 'mdi:checkbox-marked-outline' |
155 | } | 156 | } |
157 | + ], | ||
158 | + [ | ||
159 | + ValueType.JSON, | ||
160 | + { | ||
161 | + name: 'value.json', | ||
162 | + icon: 'mdi:json' | ||
163 | + } | ||
156 | ] | 164 | ] |
157 | ] | 165 | ] |
158 | ); | 166 | ); |
ui-ngx/src/app/shared/pipe/tbJson.pipe.ts
0 → 100644
1 | +/// | ||
2 | +/// Copyright © 2016-2020 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 {Pipe, PipeTransform} from '@angular/core'; | ||
18 | +import {isObject, isNumber} from "@core/utils"; | ||
19 | + | ||
20 | +@Pipe({name: 'tbJson'}) | ||
21 | +export class TbJsonPipe implements PipeTransform { | ||
22 | + transform(value: any): string { | ||
23 | + if (isObject(value)) { | ||
24 | + return JSON.stringify(value); | ||
25 | + } else if (isNumber(value)) { | ||
26 | + return value.toString(); | ||
27 | + } | ||
28 | + return value; | ||
29 | + } | ||
30 | +} |
@@ -104,6 +104,7 @@ import { TbErrorComponent } from '@shared/components/tb-error.component'; | @@ -104,6 +104,7 @@ import { TbErrorComponent } from '@shared/components/tb-error.component'; | ||
104 | import { EntityTypeListComponent } from '@shared/components/entity/entity-type-list.component'; | 104 | import { EntityTypeListComponent } from '@shared/components/entity/entity-type-list.component'; |
105 | import { EntitySubTypeListComponent } from '@shared/components/entity/entity-subtype-list.component'; | 105 | import { EntitySubTypeListComponent } from '@shared/components/entity/entity-subtype-list.component'; |
106 | import { TruncatePipe } from '@shared/pipe/truncate.pipe'; | 106 | import { TruncatePipe } from '@shared/pipe/truncate.pipe'; |
107 | +import { TbJsonPipe } from "@shared/pipe/tbJson.pipe"; | ||
107 | import { ColorPickerDialogComponent } from '@shared/components/dialog/color-picker-dialog.component'; | 108 | import { ColorPickerDialogComponent } from '@shared/components/dialog/color-picker-dialog.component'; |
108 | import { MatChipDraggableDirective } from '@shared/components/mat-chip-draggable.directive'; | 109 | import { MatChipDraggableDirective } from '@shared/components/mat-chip-draggable.directive'; |
109 | import { ColorInputComponent } from '@shared/components/color-input.component'; | 110 | import { ColorInputComponent } from '@shared/components/color-input.component'; |
@@ -124,6 +125,8 @@ import { TbCheatSheetComponent } from '@shared/components/cheatsheet.component'; | @@ -124,6 +125,8 @@ import { TbCheatSheetComponent } from '@shared/components/cheatsheet.component'; | ||
124 | import { TbHotkeysDirective } from '@shared/components/hotkeys.directive'; | 125 | import { TbHotkeysDirective } from '@shared/components/hotkeys.directive'; |
125 | import { NavTreeComponent } from '@shared/components/nav-tree.component'; | 126 | import { NavTreeComponent } from '@shared/components/nav-tree.component'; |
126 | import { LedLightComponent } from '@shared/components/led-light.component'; | 127 | import { LedLightComponent } from '@shared/components/led-light.component'; |
128 | +import { TbJsonToStringDirective } from "@shared/components/directives/tb-json-to-string.directive"; | ||
129 | +import { JsonObjectEditDialogComponent } from "@shared/components/dialog/json-object-edit-dialog.component"; | ||
127 | 130 | ||
128 | @NgModule({ | 131 | @NgModule({ |
129 | providers: [ | 132 | providers: [ |
@@ -132,6 +135,7 @@ import { LedLightComponent } from '@shared/components/led-light.component'; | @@ -132,6 +135,7 @@ import { LedLightComponent } from '@shared/components/led-light.component'; | ||
132 | EnumToArrayPipe, | 135 | EnumToArrayPipe, |
133 | HighlightPipe, | 136 | HighlightPipe, |
134 | TruncatePipe, | 137 | TruncatePipe, |
138 | + TbJsonPipe, | ||
135 | { | 139 | { |
136 | provide: FlowInjectionToken, | 140 | provide: FlowInjectionToken, |
137 | useValue: Flow | 141 | useValue: Flow |
@@ -202,7 +206,10 @@ import { LedLightComponent } from '@shared/components/led-light.component'; | @@ -202,7 +206,10 @@ import { LedLightComponent } from '@shared/components/led-light.component'; | ||
202 | EnumToArrayPipe, | 206 | EnumToArrayPipe, |
203 | HighlightPipe, | 207 | HighlightPipe, |
204 | TruncatePipe, | 208 | TruncatePipe, |
205 | - KeyboardShortcutPipe | 209 | + TbJsonPipe, |
210 | + KeyboardShortcutPipe, | ||
211 | + TbJsonToStringDirective, | ||
212 | + JsonObjectEditDialogComponent | ||
206 | ], | 213 | ], |
207 | imports: [ | 214 | imports: [ |
208 | CommonModule, | 215 | CommonModule, |
@@ -357,8 +364,10 @@ import { LedLightComponent } from '@shared/components/led-light.component'; | @@ -357,8 +364,10 @@ import { LedLightComponent } from '@shared/components/led-light.component'; | ||
357 | EnumToArrayPipe, | 364 | EnumToArrayPipe, |
358 | HighlightPipe, | 365 | HighlightPipe, |
359 | TruncatePipe, | 366 | TruncatePipe, |
367 | + TbJsonPipe, | ||
360 | KeyboardShortcutPipe, | 368 | KeyboardShortcutPipe, |
361 | - TranslateModule | 369 | + TranslateModule, |
370 | + JsonObjectEditDialogComponent | ||
362 | ] | 371 | ] |
363 | }) | 372 | }) |
364 | export class SharedModule { } | 373 | export class SharedModule { } |
@@ -626,6 +626,7 @@ | @@ -626,6 +626,7 @@ | ||
626 | "details": { | 626 | "details": { |
627 | "details": "Details", | 627 | "details": "Details", |
628 | "edit-mode": "Edit mode", | 628 | "edit-mode": "Edit mode", |
629 | + "edit-json": "Edit JSON", | ||
629 | "toggle-edit-mode": "Toggle edit mode" | 630 | "toggle-edit-mode": "Toggle edit mode" |
630 | }, | 631 | }, |
631 | "device": { | 632 | "device": { |
@@ -1298,7 +1299,8 @@ | @@ -1298,7 +1299,8 @@ | ||
1298 | "js-func": { | 1299 | "js-func": { |
1299 | "no-return-error": "Function must return value!", | 1300 | "no-return-error": "Function must return value!", |
1300 | "return-type-mismatch": "Function must return value of '{{type}}' type!", | 1301 | "return-type-mismatch": "Function must return value of '{{type}}' type!", |
1301 | - "tidy": "Tidy" | 1302 | + "tidy": "Tidy", |
1303 | + "mini": "Mini" | ||
1302 | }, | 1304 | }, |
1303 | "key-val": { | 1305 | "key-val": { |
1304 | "key": "Key", | 1306 | "key": "Key", |
@@ -1635,7 +1637,11 @@ | @@ -1635,7 +1637,11 @@ | ||
1635 | "boolean-value": "Boolean value", | 1637 | "boolean-value": "Boolean value", |
1636 | "false": "False", | 1638 | "false": "False", |
1637 | "true": "True", | 1639 | "true": "True", |
1638 | - "long": "Long" | 1640 | + "long": "Long", |
1641 | + "json": "JSON", | ||
1642 | + "json-value": "JSON value", | ||
1643 | + "json-value-invalid": "JSON value has an invalid format", | ||
1644 | + "json-value-required": "JSON value is required." | ||
1639 | }, | 1645 | }, |
1640 | "widget": { | 1646 | "widget": { |
1641 | "widget-library": "Widgets Library", | 1647 | "widget-library": "Widgets Library", |
@@ -607,6 +607,7 @@ | @@ -607,6 +607,7 @@ | ||
607 | }, | 607 | }, |
608 | "details": { | 608 | "details": { |
609 | "edit-mode": "Режим редактирования", | 609 | "edit-mode": "Режим редактирования", |
610 | + "edit-json": "Редактировать JSON", | ||
610 | "toggle-edit-mode": "Режим редактирования" | 611 | "toggle-edit-mode": "Режим редактирования" |
611 | }, | 612 | }, |
612 | "device": { | 613 | "device": { |
@@ -1191,8 +1192,7 @@ | @@ -1191,8 +1192,7 @@ | ||
1191 | }, | 1192 | }, |
1192 | "js-func": { | 1193 | "js-func": { |
1193 | "no-return-error": "Функция должна возвращать значение!", | 1194 | "no-return-error": "Функция должна возвращать значение!", |
1194 | - "return-type-mismatch": "Функция должна возвращать значение типа '{{type}}'!", | ||
1195 | - "tidy": "Tidy" | 1195 | + "return-type-mismatch": "Функция должна возвращать значение типа '{{type}}'!" |
1196 | }, | 1196 | }, |
1197 | "key-val": { | 1197 | "key-val": { |
1198 | "key": "Ключ", | 1198 | "key": "Ключ", |
@@ -724,6 +724,7 @@ | @@ -724,6 +724,7 @@ | ||
724 | "details": { | 724 | "details": { |
725 | "details": "Деталі", | 725 | "details": "Деталі", |
726 | "edit-mode": "Режим редагування", | 726 | "edit-mode": "Режим редагування", |
727 | + "edit-json": "Редагувати JSON", | ||
727 | "toggle-edit-mode": "Перемкнути режим редагування" | 728 | "toggle-edit-mode": "Перемкнути режим редагування" |
728 | }, | 729 | }, |
729 | "device": { | 730 | "device": { |
@@ -1606,8 +1607,7 @@ | @@ -1606,8 +1607,7 @@ | ||
1606 | }, | 1607 | }, |
1607 | "js-func": { | 1608 | "js-func": { |
1608 | "no-return-error": "Функція повинна повертати значення!", | 1609 | "no-return-error": "Функція повинна повертати значення!", |
1609 | - "return-type-mismatch": "Функція повинна повернути значення типу '{{type}}'!", | ||
1610 | - "tidy": "Tidy" | 1610 | + "return-type-mismatch": "Функція повинна повернути значення типу '{{type}}'!" |
1611 | }, | 1611 | }, |
1612 | "key-val": { | 1612 | "key-val": { |
1613 | "key": "Ключ", | 1613 | "key": "Ключ", |