Commit ed56f041f8aaa464b9288ac4982c9d917a9fd908

Authored by Igor Kulikov
1 parent bcfc7cc7

UI optimizations

@@ -86,25 +86,9 @@ @@ -86,25 +86,9 @@
86 ] 86 ]
87 }, 87 },
88 "scripts": [ 88 "scripts": [
89 - "node_modules/jquery/dist/jquery.min.js",  
90 - "node_modules/jquery.terminal/js/jquery.terminal.min.js",  
91 - "node_modules/flot/lib/jquery.colorhelpers.js",  
92 - "node_modules/flot/src/jquery.flot.js",  
93 - "node_modules/flot/src/plugins/jquery.flot.time.js",  
94 - "node_modules/flot/src/plugins/jquery.flot.selection.js",  
95 - "node_modules/flot/src/plugins/jquery.flot.pie.js",  
96 - "node_modules/flot/src/plugins/jquery.flot.crosshair.js",  
97 - "node_modules/flot/src/plugins/jquery.flot.stack.js",  
98 - "node_modules/flot/src/plugins/jquery.flot.symbol.js",  
99 - "node_modules/flot.curvedlines/curvedLines.js",  
100 "node_modules/tinycolor2/dist/tinycolor-min.js", 89 "node_modules/tinycolor2/dist/tinycolor-min.js",
101 - "node_modules/tooltipster/dist/js/tooltipster.bundle.min.js",  
102 - "node_modules/split.js/dist/split.js",  
103 - "node_modules/js-beautify/js/lib/beautify.js",  
104 - "node_modules/js-beautify/js/lib/beautify-css.js",  
105 - "node_modules/js-beautify/js/lib/beautify-html.js",  
106 - "node_modules/systemjs/dist/system.js",  
107 - "node_modules/jstree/dist/jstree.min.js" 90 + "node_modules/split.js/dist/split.min.js",
  91 + "node_modules/systemjs/dist/system.js"
108 ], 92 ],
109 "customWebpackConfig": { 93 "customWebpackConfig": {
110 "path": "./extra-webpack.config.js" 94 "path": "./extra-webpack.config.js"
@@ -130,7 +114,11 @@ @@ -130,7 +114,11 @@
130 "tinycolor2", 114 "tinycolor2",
131 "json-schema-defaults", 115 "json-schema-defaults",
132 "leaflet-providers", 116 "leaflet-providers",
133 - "lodash" 117 + "lodash",
  118 + "jquery",
  119 + "jquery.terminal",
  120 + "tooltipster",
  121 + "jstree"
134 ] 122 ]
135 }, 123 },
136 "configurations": { 124 "configurations": {
@@ -35,6 +35,13 @@ module.exports = (config, options) => { @@ -35,6 +35,13 @@ module.exports = (config, options) => {
35 }) 35 })
36 ); 36 );
37 config.plugins.push( 37 config.plugins.push(
  38 + new webpack.ProvidePlugin(
  39 + {
  40 + $: "jquery"
  41 + }
  42 + )
  43 + );
  44 + config.plugins.push(
38 new CompressionPlugin({ 45 new CompressionPlugin({
39 filename: "[path][base].gz[query]", 46 filename: "[path][base].gz[query]",
40 algorithm: "gzip", 47 algorithm: "gzip",
@@ -44,6 +51,9 @@ module.exports = (config, options) => { @@ -44,6 +51,9 @@ module.exports = (config, options) => {
44 deleteOriginalAssets: false, 51 deleteOriginalAssets: false,
45 }) 52 })
46 ); 53 );
  54 + config.plugins.push(
  55 + new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
  56 + );
47 57
48 if (config.mode === 'production') { 58 if (config.mode === 'production') {
49 const index = config.plugins.findIndex(p => p instanceof AngularCompilerPlugin.AngularCompilerPlugin); 59 const index = config.plugins.findIndex(p => p instanceof AngularCompilerPlugin.AngularCompilerPlugin);
@@ -24,6 +24,9 @@ import { DialogComponent } from '@shared/components/dialog.component'; @@ -24,6 +24,9 @@ import { DialogComponent } from '@shared/components/dialog.component';
24 import { Router } from '@angular/router'; 24 import { Router } from '@angular/router';
25 import { ContentType, contentTypesMap } from '@shared/models/constants'; 25 import { ContentType, contentTypesMap } from '@shared/models/constants';
26 import { getAce } from '@shared/models/ace/ace.models'; 26 import { getAce } from '@shared/models/ace/ace.models';
  27 +import { Observable } from 'rxjs/internal/Observable';
  28 +import { beautifyJs } from '@shared/models/beautify.models';
  29 +import { of } from 'rxjs';
27 30
28 export interface EventContentDialogData { 31 export interface EventContentDialogData {
29 content: string; 32 content: string;
@@ -64,33 +67,42 @@ export class EventContentDialogComponent extends DialogComponent<EventContentDia @@ -64,33 +67,42 @@ export class EventContentDialogComponent extends DialogComponent<EventContentDia
64 createEditor(editorElementRef: ElementRef, content: string) { 67 createEditor(editorElementRef: ElementRef, content: string) {
65 const editorElement = editorElementRef.nativeElement; 68 const editorElement = editorElementRef.nativeElement;
66 let mode = 'java'; 69 let mode = 'java';
  70 + let content$: Observable<string> = null;
67 if (this.contentType) { 71 if (this.contentType) {
68 mode = contentTypesMap.get(this.contentType).code; 72 mode = contentTypesMap.get(this.contentType).code;
69 if (this.contentType === ContentType.JSON && content) { 73 if (this.contentType === ContentType.JSON && content) {
70 - content = js_beautify(content, {indent_size: 4}); 74 + content$ = beautifyJs(content, {indent_size: 4});
71 } 75 }
72 } 76 }
73 - let editorOptions: Partial<Ace.EditorOptions> = {  
74 - mode: `ace/mode/${mode}`,  
75 - theme: 'ace/theme/github',  
76 - showGutter: false,  
77 - showPrintMargin: false,  
78 - readOnly: true  
79 - }; 77 + if (!content$) {
  78 + content$ = of(content);
  79 + }
  80 +
  81 + content$.subscribe(
  82 + (processedContent) => {
  83 + let editorOptions: Partial<Ace.EditorOptions> = {
  84 + mode: `ace/mode/${mode}`,
  85 + theme: 'ace/theme/github',
  86 + showGutter: false,
  87 + showPrintMargin: false,
  88 + readOnly: true
  89 + };
80 90
81 - const advancedOptions = {  
82 - enableSnippets: false,  
83 - enableBasicAutocompletion: false,  
84 - enableLiveAutocompletion: false  
85 - }; 91 + const advancedOptions = {
  92 + enableSnippets: false,
  93 + enableBasicAutocompletion: false,
  94 + enableLiveAutocompletion: false
  95 + };
86 96
87 - editorOptions = {...editorOptions, ...advancedOptions};  
88 - getAce().subscribe(  
89 - (ace) => {  
90 - const editor = ace.edit(editorElement, editorOptions);  
91 - editor.session.setUseWrapMode(false);  
92 - editor.setValue(content, -1);  
93 - this.updateEditorSize(editorElement, content, editor); 97 + editorOptions = {...editorOptions, ...advancedOptions};
  98 + getAce().subscribe(
  99 + (ace) => {
  100 + const editor = ace.edit(editorElement, editorOptions);
  101 + editor.session.setUseWrapMode(false);
  102 + editor.setValue(processedContent, -1);
  103 + this.updateEditorSize(editorElement, processedContent, editor);
  104 + }
  105 + );
94 } 106 }
95 ); 107 );
96 } 108 }
@@ -29,7 +29,7 @@ @@ -29,7 +29,7 @@
29 </mat-form-field> 29 </mat-form-field>
30 <div *ngIf="alarmScheduleForm.get('type').value !== alarmScheduleType.ANY_TIME"> 30 <div *ngIf="alarmScheduleForm.get('type').value !== alarmScheduleType.ANY_TIME">
31 <tb-timezone-select 31 <tb-timezone-select
32 - [defaultTimezone]="defaultTimezone" 32 + [defaultTimezone]="defaultTimezone$ | async"
33 required 33 required
34 formControlName="timezone"> 34 formControlName="timezone">
35 </tb-timezone-select> 35 </tb-timezone-select>
@@ -32,11 +32,15 @@ import { @@ -32,11 +32,15 @@ import {
32 AlarmSchedule, 32 AlarmSchedule,
33 AlarmScheduleType, 33 AlarmScheduleType,
34 AlarmScheduleTypeTranslationMap, 34 AlarmScheduleTypeTranslationMap,
35 - dayOfWeekTranslations, getAlarmScheduleRangeText, timeOfDayToUTCTimestamp, utcTimestampToTimeOfDay 35 + dayOfWeekTranslations,
  36 + getAlarmScheduleRangeText,
  37 + timeOfDayToUTCTimestamp,
  38 + utcTimestampToTimeOfDay
36 } from '@shared/models/device.models'; 39 } from '@shared/models/device.models';
37 import { isDefined, isDefinedAndNotNull } from '@core/utils'; 40 import { isDefined, isDefinedAndNotNull } from '@core/utils';
38 -import * as _moment from 'moment-timezone';  
39 import { MatCheckboxChange } from '@angular/material/checkbox'; 41 import { MatCheckboxChange } from '@angular/material/checkbox';
  42 +import { getDefaultTimezone } from '@shared/models/time/time.models';
  43 +import { share } from 'rxjs/operators';
40 44
41 @Component({ 45 @Component({
42 selector: 'tb-alarm-schedule', 46 selector: 'tb-alarm-schedule',
@@ -58,7 +62,9 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator, @@ -58,7 +62,9 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator,
58 62
59 alarmScheduleForm: FormGroup; 63 alarmScheduleForm: FormGroup;
60 64
61 - defaultTimezone = _moment.tz.guess(); 65 + defaultTimezone$ = getDefaultTimezone().pipe(
  66 + share()
  67 + );
62 68
63 alarmScheduleTypes = Object.keys(AlarmScheduleType); 69 alarmScheduleTypes = Object.keys(AlarmScheduleType);
64 alarmScheduleType = AlarmScheduleType; 70 alarmScheduleType = AlarmScheduleType;
@@ -93,9 +99,11 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator, @@ -93,9 +99,11 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator,
93 items: this.fb.array(Array.from({length: 7}, (value, i) => this.defaultItemsScheduler(i)), this.validateItems) 99 items: this.fb.array(Array.from({length: 7}, (value, i) => this.defaultItemsScheduler(i)), this.validateItems)
94 }); 100 });
95 this.alarmScheduleForm.get('type').valueChanges.subscribe((type) => { 101 this.alarmScheduleForm.get('type').valueChanges.subscribe((type) => {
96 - this.alarmScheduleForm.reset({type, items: this.defaultItems, timezone: this.defaultTimezone}, {emitEvent: false});  
97 - this.updateValidators(type, true);  
98 - this.alarmScheduleForm.updateValueAndValidity(); 102 + getDefaultTimezone().subscribe((defaultTimezone) => {
  103 + this.alarmScheduleForm.reset({type, items: this.defaultItems, timezone: defaultTimezone}, {emitEvent: false});
  104 + this.updateValidators(type, true);
  105 + this.alarmScheduleForm.updateValueAndValidity();
  106 + });
99 }); 107 });
100 this.alarmScheduleForm.valueChanges.subscribe(() => { 108 this.alarmScheduleForm.valueChanges.subscribe(() => {
101 this.updateModel(); 109 this.updateModel();
@@ -34,13 +34,13 @@ import { AppState } from '@core/core.state'; @@ -34,13 +34,13 @@ import { AppState } from '@core/core.state';
34 import { CustomActionDescriptor } from '@shared/models/widget.models'; 34 import { CustomActionDescriptor } from '@shared/models/widget.models';
35 import { Ace } from 'ace-builds'; 35 import { Ace } from 'ace-builds';
36 import { CancelAnimationFrame, RafService } from '@core/services/raf.service'; 36 import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
37 -import { css_beautify, html_beautify } from 'js-beautify';  
38 import { ResizeObserver } from '@juggle/resize-observer'; 37 import { ResizeObserver } from '@juggle/resize-observer';
39 import { CustomPrettyActionEditorCompleter } from '@home/components/widget/action/custom-action.models'; 38 import { CustomPrettyActionEditorCompleter } from '@home/components/widget/action/custom-action.models';
40 import { Observable } from 'rxjs/internal/Observable'; 39 import { Observable } from 'rxjs/internal/Observable';
41 import { forkJoin, from } from 'rxjs'; 40 import { forkJoin, from } from 'rxjs';
42 import { map, tap } from 'rxjs/operators'; 41 import { map, tap } from 'rxjs/operators';
43 import { getAce } from '@shared/models/ace/ace.models'; 42 import { getAce } from '@shared/models/ace/ace.models';
  43 +import { beautifyCss, beautifyHtml } from '@shared/models/beautify.models';
44 44
45 @Component({ 45 @Component({
46 selector: 'tb-custom-action-pretty-resources-tabs', 46 selector: 'tb-custom-action-pretty-resources-tabs',
@@ -140,21 +140,27 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl @@ -140,21 +140,27 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl
140 } 140 }
141 141
142 public beautifyCss(): void { 142 public beautifyCss(): void {
143 - const res = css_beautify(this.action.customCss, {indent_size: 4});  
144 - if (this.action.customCss !== res) {  
145 - this.action.customCss = res;  
146 - this.cssEditor.setValue(this.action.customCss ? this.action.customCss : '', -1);  
147 - this.notifyActionUpdated();  
148 - } 143 + beautifyCss(this.action.customCss, {indent_size: 4}).subscribe(
  144 + (res) => {
  145 + if (this.action.customCss !== res) {
  146 + this.action.customCss = res;
  147 + this.cssEditor.setValue(this.action.customCss ? this.action.customCss : '', -1);
  148 + this.notifyActionUpdated();
  149 + }
  150 + }
  151 + );
149 } 152 }
150 153
151 public beautifyHtml(): void { 154 public beautifyHtml(): void {
152 - const res = html_beautify(this.action.customHtml, {indent_size: 4, wrap_line_length: 60});  
153 - if (this.action.customHtml !== res) {  
154 - this.action.customHtml = res;  
155 - this.htmlEditor.setValue(this.action.customHtml ? this.action.customHtml : '', -1);  
156 - this.notifyActionUpdated();  
157 - } 155 + beautifyHtml(this.action.customHtml, {indent_size: 4, wrap_line_length: 60}).subscribe(
  156 + (res) => {
  157 + if (this.action.customHtml !== res) {
  158 + this.action.customHtml = res;
  159 + this.htmlEditor.setValue(this.action.customHtml ? this.action.customHtml : '', -1);
  160 + this.notifyActionUpdated();
  161 + }
  162 + }
  163 + );
158 } 164 }
159 165
160 private initAceEditors(): Observable<any> { 166 private initAceEditors(): Observable<any> {
@@ -111,11 +111,32 @@ export class WidgetComponentService { @@ -111,11 +111,32 @@ export class WidgetComponentService {
111 const initSubject = new ReplaySubject(); 111 const initSubject = new ReplaySubject();
112 this.init$ = initSubject.asObservable(); 112 this.init$ = initSubject.asObservable();
113 113
114 - (window as any).tinycolor = tinycolor;  
115 - (window as any).cssjs = cssjs;  
116 - (window as any).moment = moment; 114 + const w = (this.window as any);
  115 +
  116 + w.tinycolor = tinycolor;
  117 + w.cssjs = cssjs;
  118 + w.moment = moment;
  119 + w.$ = $;
  120 + w.jQuery = $;
117 121
118 const widgetModulesTasks: Observable<any>[] = []; 122 const widgetModulesTasks: Observable<any>[] = [];
  123 + widgetModulesTasks.push(from(import('jquery.terminal')));
  124 +
  125 + widgetModulesTasks.push(from(import('flot/src/jquery.flot.js')).pipe(
  126 + mergeMap(() => {
  127 + const flotJsPluginsTasks: Observable<any>[] = [];
  128 + flotJsPluginsTasks.push(from(import('flot/lib/jquery.colorhelpers.js')));
  129 + flotJsPluginsTasks.push(from(import('flot/src/plugins/jquery.flot.time.js')));
  130 + flotJsPluginsTasks.push(from(import('flot/src/plugins/jquery.flot.selection.js')));
  131 + flotJsPluginsTasks.push(from(import('flot/src/plugins/jquery.flot.pie.js')));
  132 + flotJsPluginsTasks.push(from(import('flot/src/plugins/jquery.flot.crosshair.js')));
  133 + flotJsPluginsTasks.push(from(import('flot/src/plugins/jquery.flot.stack.js')));
  134 + flotJsPluginsTasks.push(from(import('flot/src/plugins/jquery.flot.symbol.js')));
  135 + flotJsPluginsTasks.push(from(import('flot.curvedlines/curvedLines.js')));
  136 + return forkJoin(flotJsPluginsTasks);
  137 + })
  138 + ));
  139 +
119 widgetModulesTasks.push(from(import('@home/components/widget/lib/flot-widget')).pipe( 140 widgetModulesTasks.push(from(import('@home/components/widget/lib/flot-widget')).pipe(
120 tap((mod) => { 141 tap((mod) => {
121 (window as any).TbFlot = mod.TbFlot; 142 (window as any).TbFlot = mod.TbFlot;
@@ -29,7 +29,7 @@ import { @@ -29,7 +29,7 @@ import {
29 import { EntitiesTableComponent } from '../../components/entity/entities-table.component'; 29 import { EntitiesTableComponent } from '../../components/entity/entities-table.component';
30 import { Authority } from '@shared/models/authority.enum'; 30 import { Authority } from '@shared/models/authority.enum';
31 import { RuleChainsTableConfigResolver } from '@modules/home/pages/rulechain/rulechains-table-config.resolver'; 31 import { RuleChainsTableConfigResolver } from '@modules/home/pages/rulechain/rulechains-table-config.resolver';
32 -import { Observable } from 'rxjs'; 32 +import { from, Observable } from 'rxjs';
33 import { BreadCrumbConfig, BreadCrumbLabelFunction } from '@shared/components/breadcrumb'; 33 import { BreadCrumbConfig, BreadCrumbLabelFunction } from '@shared/components/breadcrumb';
34 import { ResolvedRuleChainMetaData, RuleChain } from '@shared/models/rule-chain.models'; 34 import { ResolvedRuleChainMetaData, RuleChain } from '@shared/models/rule-chain.models';
35 import { RuleChainService } from '@core/http/rule-chain.service'; 35 import { RuleChainService } from '@core/http/rule-chain.service';
@@ -76,6 +76,17 @@ export class RuleNodeComponentsResolver implements Resolve<Array<RuleNodeCompone @@ -76,6 +76,17 @@ export class RuleNodeComponentsResolver implements Resolve<Array<RuleNodeCompone
76 } 76 }
77 77
78 @Injectable() 78 @Injectable()
  79 +export class TooltipsterResolver implements Resolve<any> {
  80 +
  81 + constructor() {
  82 + }
  83 +
  84 + resolve(route: ActivatedRouteSnapshot): Observable<any> {
  85 + return from(import('tooltipster'));
  86 + }
  87 +}
  88 +
  89 +@Injectable()
79 export class RuleChainImportGuard implements CanActivate { 90 export class RuleChainImportGuard implements CanActivate {
80 91
81 constructor(private itembuffer: ItemBufferService, 92 constructor(private itembuffer: ItemBufferService,
@@ -144,7 +155,8 @@ const routes: Routes = [ @@ -144,7 +155,8 @@ const routes: Routes = [
144 resolve: { 155 resolve: {
145 ruleChain: RuleChainResolver, 156 ruleChain: RuleChainResolver,
146 ruleChainMetaData: ResolvedRuleChainMetaDataResolver, 157 ruleChainMetaData: ResolvedRuleChainMetaDataResolver,
147 - ruleNodeComponents: RuleNodeComponentsResolver 158 + ruleNodeComponents: RuleNodeComponentsResolver,
  159 + tooltipster: TooltipsterResolver
148 } 160 }
149 }, 161 },
150 { 162 {
@@ -162,7 +174,8 @@ const routes: Routes = [ @@ -162,7 +174,8 @@ const routes: Routes = [
162 import: true 174 import: true
163 }, 175 },
164 resolve: { 176 resolve: {
165 - ruleNodeComponents: RuleNodeComponentsResolver 177 + ruleNodeComponents: RuleNodeComponentsResolver,
  178 + tooltipster: TooltipsterResolver
166 } 179 }
167 } 180 }
168 ] 181 ]
@@ -178,6 +191,7 @@ const routes: Routes = [ @@ -178,6 +191,7 @@ const routes: Routes = [
178 RuleChainResolver, 191 RuleChainResolver,
179 ResolvedRuleChainMetaDataResolver, 192 ResolvedRuleChainMetaDataResolver,
180 RuleNodeComponentsResolver, 193 RuleNodeComponentsResolver,
  194 + TooltipsterResolver,
181 RuleChainImportGuard 195 RuleChainImportGuard
182 ] 196 ]
183 }) 197 })
@@ -34,7 +34,6 @@ import { TranslateService } from '@ngx-translate/core'; @@ -34,7 +34,6 @@ import { TranslateService } from '@ngx-translate/core';
34 import { getCurrentIsLoading } from '@app/core/interceptors/load.selectors'; 34 import { getCurrentIsLoading } from '@app/core/interceptors/load.selectors';
35 import { Ace } from 'ace-builds'; 35 import { Ace } from 'ace-builds';
36 import { getAce, Range } from '@shared/models/ace/ace.models'; 36 import { getAce, Range } from '@shared/models/ace/ace.models';
37 -import { css_beautify, html_beautify } from 'js-beautify';  
38 import { CancelAnimationFrame, RafService } from '@core/services/raf.service'; 37 import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
39 import { WINDOW } from '@core/services/window.service'; 38 import { WINDOW } from '@core/services/window.service';
40 import { WindowMessage } from '@shared/models/window-message.model'; 39 import { WindowMessage } from '@shared/models/window-message.model';
@@ -51,6 +50,7 @@ import Timeout = NodeJS.Timeout; @@ -51,6 +50,7 @@ import Timeout = NodeJS.Timeout;
51 import { widgetEditorCompleter } from '@home/pages/widget/widget-editor.models'; 50 import { widgetEditorCompleter } from '@home/pages/widget/widget-editor.models';
52 import { Observable } from 'rxjs/internal/Observable'; 51 import { Observable } from 'rxjs/internal/Observable';
53 import { map, tap } from 'rxjs/operators'; 52 import { map, tap } from 'rxjs/operators';
  53 +import { beautifyCss, beautifyHtml, beautifyJs } from '@shared/models/beautify.models';
54 54
55 // @dynamic 55 // @dynamic
56 @Component({ 56 @Component({
@@ -618,48 +618,63 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe @@ -618,48 +618,63 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
618 } 618 }
619 619
620 beautifyCss(): void { 620 beautifyCss(): void {
621 - const res = css_beautify(this.widget.templateCss, {indent_size: 4});  
622 - if (this.widget.templateCss !== res) {  
623 - this.isDirty = true;  
624 - this.widget.templateCss = res;  
625 - this.cssEditor.setValue(this.widget.templateCss ? this.widget.templateCss : '', -1);  
626 - } 621 + beautifyCss(this.widget.templateCss, {indent_size: 4}).subscribe(
  622 + (res) => {
  623 + if (this.widget.templateCss !== res) {
  624 + this.isDirty = true;
  625 + this.widget.templateCss = res;
  626 + this.cssEditor.setValue(this.widget.templateCss ? this.widget.templateCss : '', -1);
  627 + }
  628 + }
  629 + );
627 } 630 }
628 631
629 beautifyHtml(): void { 632 beautifyHtml(): void {
630 - const res = html_beautify(this.widget.templateHtml, {indent_size: 4, wrap_line_length: 60});  
631 - if (this.widget.templateHtml !== res) {  
632 - this.isDirty = true;  
633 - this.widget.templateHtml = res;  
634 - this.htmlEditor.setValue(this.widget.templateHtml ? this.widget.templateHtml : '', -1);  
635 - } 633 + beautifyHtml(this.widget.templateHtml, {indent_size: 4, wrap_line_length: 60}).subscribe(
  634 + (res) => {
  635 + if (this.widget.templateHtml !== res) {
  636 + this.isDirty = true;
  637 + this.widget.templateHtml = res;
  638 + this.htmlEditor.setValue(this.widget.templateHtml ? this.widget.templateHtml : '', -1);
  639 + }
  640 + }
  641 + );
636 } 642 }
637 643
638 beautifyJson(): void { 644 beautifyJson(): void {
639 - const res = js_beautify(this.widget.settingsSchema, {indent_size: 4});  
640 - if (this.widget.settingsSchema !== res) {  
641 - this.isDirty = true;  
642 - this.widget.settingsSchema = res;  
643 - this.jsonSettingsEditor.setValue(this.widget.settingsSchema ? this.widget.settingsSchema : '', -1);  
644 - } 645 + beautifyJs(this.widget.settingsSchema, {indent_size: 4}).subscribe(
  646 + (res) => {
  647 + if (this.widget.settingsSchema !== res) {
  648 + this.isDirty = true;
  649 + this.widget.settingsSchema = res;
  650 + this.jsonSettingsEditor.setValue(this.widget.settingsSchema ? this.widget.settingsSchema : '', -1);
  651 + }
  652 + }
  653 + );
645 } 654 }
646 655
647 beautifyDataKeyJson(): void { 656 beautifyDataKeyJson(): void {
648 - const res = js_beautify(this.widget.dataKeySettingsSchema, {indent_size: 4});  
649 - if (this.widget.dataKeySettingsSchema !== res) {  
650 - this.isDirty = true;  
651 - this.widget.dataKeySettingsSchema = res;  
652 - this.dataKeyJsonSettingsEditor.setValue(this.widget.dataKeySettingsSchema ? this.widget.dataKeySettingsSchema : '', -1);  
653 - } 657 + beautifyJs(this.widget.dataKeySettingsSchema, {indent_size: 4}).subscribe(
  658 + (res) => {
  659 + if (this.widget.dataKeySettingsSchema !== res) {
  660 + this.isDirty = true;
  661 + this.widget.dataKeySettingsSchema = res;
  662 + this.dataKeyJsonSettingsEditor.setValue(this.widget.dataKeySettingsSchema ? this.widget.dataKeySettingsSchema : '', -1);
  663 + }
  664 + }
  665 + );
654 } 666 }
655 667
656 beautifyJs(): void { 668 beautifyJs(): void {
657 - const res = js_beautify(this.widget.controllerScript, {indent_size: 4, wrap_line_length: 60});  
658 - if (this.widget.controllerScript !== res) {  
659 - this.isDirty = true;  
660 - this.widget.controllerScript = res;  
661 - this.jsEditor.setValue(this.widget.controllerScript ? this.widget.controllerScript : '', -1);  
662 - } 669 + beautifyJs(this.widget.controllerScript, {indent_size: 4, wrap_line_length: 60}).subscribe(
  670 + (res) => {
  671 + if (this.widget.controllerScript !== res) {
  672 + this.isDirty = true;
  673 + this.widget.controllerScript = res;
  674 + this.jsEditor.setValue(this.widget.controllerScript ? this.widget.controllerScript : '', -1);
  675 + }
  676 + }
  677 + );
663 } 678 }
664 679
665 removeResource(index: number) { 680 removeResource(index: number) {
@@ -41,6 +41,7 @@ import { TestScriptInputParams } from '@shared/models/rule-node.models'; @@ -41,6 +41,7 @@ import { TestScriptInputParams } from '@shared/models/rule-node.models';
41 import { RuleChainService } from '@core/http/rule-chain.service'; 41 import { RuleChainService } from '@core/http/rule-chain.service';
42 import { mergeMap } from 'rxjs/operators'; 42 import { mergeMap } from 'rxjs/operators';
43 import { ActionNotificationShow } from '@core/notification/notification.actions'; 43 import { ActionNotificationShow } from '@core/notification/notification.actions';
  44 +import { beautifyJs } from '@shared/models/beautify.models';
44 45
45 export interface NodeScriptTestDialogData { 46 export interface NodeScriptTestDialogData {
46 script: string; 47 script: string;
@@ -110,12 +111,17 @@ export class NodeScriptTestDialogComponent extends DialogComponent<NodeScriptTes @@ -110,12 +111,17 @@ export class NodeScriptTestDialogComponent extends DialogComponent<NodeScriptTes
110 this.nodeScriptTestFormGroup = this.fb.group({ 111 this.nodeScriptTestFormGroup = this.fb.group({
111 payload: this.fb.group({ 112 payload: this.fb.group({
112 msgType: [this.data.msgType, [Validators.required]], 113 msgType: [this.data.msgType, [Validators.required]],
113 - msg: [js_beautify(JSON.stringify(this.data.msg), {indent_size: 4}), []], 114 + msg: [null, []],
114 }), 115 }),
115 metadata: [this.data.metadata, [Validators.required]], 116 metadata: [this.data.metadata, [Validators.required]],
116 script: [this.data.script, []], 117 script: [this.data.script, []],
117 output: ['', []] 118 output: ['', []]
118 }); 119 });
  120 + beautifyJs(JSON.stringify(this.data.msg), {indent_size: 4}).subscribe(
  121 + (res) => {
  122 + this.nodeScriptTestFormGroup.get('payload').get('msg').patchValue(res, {emitEvent: false});
  123 + }
  124 + );
119 } 125 }
120 126
121 ngAfterViewInit(): void { 127 ngAfterViewInit(): void {
@@ -166,7 +172,11 @@ export class NodeScriptTestDialogComponent extends DialogComponent<NodeScriptTes @@ -166,7 +172,11 @@ export class NodeScriptTestDialogComponent extends DialogComponent<NodeScriptTes
166 172
167 test(): void { 173 test(): void {
168 this.testNodeScript().subscribe((output) => { 174 this.testNodeScript().subscribe((output) => {
169 - this.nodeScriptTestFormGroup.get('output').setValue(js_beautify(output, {indent_size: 4})); 175 + beautifyJs(output, {indent_size: 4}).subscribe(
  176 + (res) => {
  177 + this.nodeScriptTestFormGroup.get('output').setValue(res);
  178 + }
  179 + );
170 }); 180 });
171 } 181 }
172 182
@@ -37,6 +37,7 @@ import { TranslateService } from '@ngx-translate/core'; @@ -37,6 +37,7 @@ import { TranslateService } from '@ngx-translate/core';
37 import { CancelAnimationFrame, RafService } from '@core/services/raf.service'; 37 import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
38 import { ResizeObserver } from '@juggle/resize-observer'; 38 import { ResizeObserver } from '@juggle/resize-observer';
39 import { TbEditorCompleter } from '@shared/models/ace/completion.models'; 39 import { TbEditorCompleter } from '@shared/models/ace/completion.models';
  40 +import { beautifyJs } from '@shared/models/beautify.models';
40 41
41 @Component({ 42 @Component({
42 selector: 'tb-js-func', 43 selector: 'tb-js-func',
@@ -214,9 +215,12 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor, @@ -214,9 +215,12 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor,
214 } 215 }
215 216
216 beautifyJs() { 217 beautifyJs() {
217 - const res = js_beautify(this.modelValue, {indent_size: 4, wrap_line_length: 60});  
218 - this.jsEditor.setValue(res ? res : '', -1);  
219 - this.updateView(); 218 + beautifyJs(this.modelValue, {indent_size: 4, wrap_line_length: 60}).subscribe(
  219 + (res) => {
  220 + this.jsEditor.setValue(res ? res : '', -1);
  221 + this.updateView();
  222 + }
  223 + );
220 } 224 }
221 225
222 validateOnSubmit(): void { 226 validateOnSubmit(): void {
@@ -36,6 +36,7 @@ import { CancelAnimationFrame, RafService } from '@core/services/raf.service'; @@ -36,6 +36,7 @@ import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
36 import { guid } from '@core/utils'; 36 import { guid } from '@core/utils';
37 import { ResizeObserver } from '@juggle/resize-observer'; 37 import { ResizeObserver } from '@juggle/resize-observer';
38 import { getAce } from '@shared/models/ace/ace.models'; 38 import { getAce } from '@shared/models/ace/ace.models';
  39 +import { beautifyJs } from '@shared/models/beautify.models';
39 40
40 @Component({ 41 @Component({
41 selector: 'tb-json-content', 42 selector: 'tb-json-content',
@@ -286,9 +287,12 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid @@ -286,9 +287,12 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid
286 } 287 }
287 288
288 beautifyJSON() { 289 beautifyJSON() {
289 - const res = js_beautify(this.contentBody, {indent_size: 4, wrap_line_length: 60});  
290 - this.jsonEditor.setValue(res ? res : '', -1);  
291 - this.updateView(); 290 + beautifyJs(this.contentBody, {indent_size: 4, wrap_line_length: 60}).subscribe(
  291 + (res) => {
  292 + this.jsonEditor.setValue(res ? res : '', -1);
  293 + this.updateView();
  294 + }
  295 + );
292 } 296 }
293 297
294 minifyJSON() { 298 minifyJSON() {
@@ -22,6 +22,7 @@ import { IEditorProps } from 'react-ace/src/types'; @@ -22,6 +22,7 @@ import { IEditorProps } from 'react-ace/src/types';
22 import { map, mergeMap } from 'rxjs/operators'; 22 import { map, mergeMap } from 'rxjs/operators';
23 import { loadAceDependencies } from '@shared/models/ace/ace.models'; 23 import { loadAceDependencies } from '@shared/models/ace/ace.models';
24 import { from } from 'rxjs'; 24 import { from } from 'rxjs';
  25 +import { Observable } from 'rxjs/internal/Observable';
25 26
26 const ReactAce = React.lazy(() => { 27 const ReactAce = React.lazy(() => {
27 return loadAceDependencies().pipe( 28 return loadAceDependencies().pipe(
@@ -33,7 +34,7 @@ const ReactAce = React.lazy(() => { @@ -33,7 +34,7 @@ const ReactAce = React.lazy(() => {
33 34
34 interface ThingsboardAceEditorProps extends JsonFormFieldProps { 35 interface ThingsboardAceEditorProps extends JsonFormFieldProps {
35 mode: string; 36 mode: string;
36 - onTidy: (value: string) => string; 37 + onTidy: (value: string) => Observable<string>;
37 } 38 }
38 39
39 interface ThingsboardAceEditorState extends JsonFormFieldState { 40 interface ThingsboardAceEditorState extends JsonFormFieldState {
@@ -84,15 +85,18 @@ class ThingsboardAceEditor extends React.Component<ThingsboardAceEditorProps, Th @@ -84,15 +85,18 @@ class ThingsboardAceEditor extends React.Component<ThingsboardAceEditorProps, Th
84 onTidy() { 85 onTidy() {
85 if (!this.props.form.readonly) { 86 if (!this.props.form.readonly) {
86 let value = this.state.value; 87 let value = this.state.value;
87 - value = this.props.onTidy(value);  
88 - this.setState({  
89 - value  
90 - });  
91 - this.props.onChangeValidate({  
92 - target: {  
93 - value  
94 - }  
95 - }); 88 + this.props.onTidy(value).subscribe(
  89 + (processedValue) => {
  90 + this.setState({
  91 + value: processedValue
  92 + });
  93 + this.props.onChangeValidate({
  94 + target: {
  95 + value: processedValue
  96 + }
  97 + });
  98 + }
  99 + );
96 } 100 }
97 } 101 }
98 102
@@ -16,7 +16,8 @@ @@ -16,7 +16,8 @@
16 import * as React from 'react'; 16 import * as React from 'react';
17 import ThingsboardAceEditor from './json-form-ace-editor'; 17 import ThingsboardAceEditor from './json-form-ace-editor';
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 -import { css_beautify } from 'js-beautify'; 19 +import { Observable } from 'rxjs/internal/Observable';
  20 +import { beautifyCss } from '@shared/models/beautify.models';
20 21
21 class ThingsboardCss extends React.Component<JsonFormFieldProps, JsonFormFieldState> { 22 class ThingsboardCss extends React.Component<JsonFormFieldProps, JsonFormFieldState> {
22 23
@@ -25,8 +26,8 @@ class ThingsboardCss extends React.Component<JsonFormFieldProps, JsonFormFieldSt @@ -25,8 +26,8 @@ class ThingsboardCss extends React.Component<JsonFormFieldProps, JsonFormFieldSt
25 this.onTidyCss = this.onTidyCss.bind(this); 26 this.onTidyCss = this.onTidyCss.bind(this);
26 } 27 }
27 28
28 - onTidyCss(css: string): string {  
29 - return css_beautify(css, {indent_size: 4}); 29 + onTidyCss(css: string): Observable<string> {
  30 + return beautifyCss(css, {indent_size: 4});
30 } 31 }
31 32
32 render() { 33 render() {
@@ -15,8 +15,9 @@ @@ -15,8 +15,9 @@
15 */ 15 */
16 import * as React from 'react'; 16 import * as React from 'react';
17 import ThingsboardAceEditor from './json-form-ace-editor'; 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'; 18 import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models';
  19 +import { Observable } from 'rxjs/internal/Observable';
  20 +import { beautifyHtml } from '@shared/models/beautify.models';
20 21
21 class ThingsboardHtml extends React.Component<JsonFormFieldProps, JsonFormFieldState> { 22 class ThingsboardHtml extends React.Component<JsonFormFieldProps, JsonFormFieldState> {
22 23
@@ -25,8 +26,8 @@ class ThingsboardHtml extends React.Component<JsonFormFieldProps, JsonFormFieldS @@ -25,8 +26,8 @@ class ThingsboardHtml extends React.Component<JsonFormFieldProps, JsonFormFieldS
25 this.onTidyHtml = this.onTidyHtml.bind(this); 26 this.onTidyHtml = this.onTidyHtml.bind(this);
26 } 27 }
27 28
28 - onTidyHtml(html: string): string {  
29 - return html_beautify(html, {indent_size: 4}); 29 + onTidyHtml(html: string): Observable<string> {
  30 + return beautifyHtml(html, {indent_size: 4});
30 } 31 }
31 32
32 render() { 33 render() {
@@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
16 import * as React from 'react'; 16 import * as React from 'react';
17 import ThingsboardAceEditor from './json-form-ace-editor'; 17 import ThingsboardAceEditor from './json-form-ace-editor';
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 +import { Observable } from 'rxjs/internal/Observable';
  20 +import { beautifyJs } from '@shared/models/beautify.models';
19 21
20 class ThingsboardJavaScript extends React.Component<JsonFormFieldProps, JsonFormFieldState> { 22 class ThingsboardJavaScript extends React.Component<JsonFormFieldProps, JsonFormFieldState> {
21 23
@@ -24,8 +26,8 @@ class ThingsboardJavaScript extends React.Component<JsonFormFieldProps, JsonForm @@ -24,8 +26,8 @@ class ThingsboardJavaScript extends React.Component<JsonFormFieldProps, JsonForm
24 this.onTidyJavascript = this.onTidyJavascript.bind(this); 26 this.onTidyJavascript = this.onTidyJavascript.bind(this);
25 } 27 }
26 28
27 - onTidyJavascript(javascript: string): string {  
28 - return js_beautify(javascript, {indent_size: 4, wrap_line_length: 60}); 29 + onTidyJavascript(javascript: string): Observable<string> {
  30 + return beautifyJs(javascript, {indent_size: 4, wrap_line_length: 60});
29 } 31 }
30 32
31 render() { 33 render() {
@@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
16 import * as React from 'react'; 16 import * as React from 'react';
17 import ThingsboardAceEditor from './json-form-ace-editor'; 17 import ThingsboardAceEditor from './json-form-ace-editor';
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 +import { Observable } from 'rxjs/internal/Observable';
  20 +import { beautifyJs } from '@shared/models/beautify.models';
19 21
20 class ThingsboardJson extends React.Component<JsonFormFieldProps, JsonFormFieldState> { 22 class ThingsboardJson extends React.Component<JsonFormFieldProps, JsonFormFieldState> {
21 23
@@ -24,8 +26,8 @@ class ThingsboardJson extends React.Component<JsonFormFieldProps, JsonFormFieldS @@ -24,8 +26,8 @@ class ThingsboardJson extends React.Component<JsonFormFieldProps, JsonFormFieldS
24 this.onTidyJson = this.onTidyJson.bind(this); 26 this.onTidyJson = this.onTidyJson.bind(this);
25 } 27 }
26 28
27 - onTidyJson(json: string): string {  
28 - return js_beautify(json, {indent_size: 4}); 29 + onTidyJson(json: string): Observable<string> {
  30 + return beautifyJs(json, {indent_size: 4});
29 } 31 }
30 32
31 render() { 33 render() {
@@ -145,128 +145,131 @@ export class NavTreeComponent implements OnInit { @@ -145,128 +145,131 @@ export class NavTreeComponent implements OnInit {
145 }; 145 };
146 } 146 }
147 147
148 - this.treeElement = $('.tb-nav-tree-container', this.elementRef.nativeElement).jstree(config); 148 + import('jstree').then(() => {
149 149
150 - this.treeElement.on('changed.jstree', (e: any, data) => {  
151 - const node: NavTreeNode = data.instance.get_selected(true)[0];  
152 - if (this.onNodeSelected) {  
153 - this.ngZone.run(() => this.onNodeSelected(node, e as Event));  
154 - }  
155 - });  
156 -  
157 - this.treeElement.on('model.jstree', (e: any, data) => {  
158 - if (this.onNodesInserted) {  
159 - this.ngZone.run(() => this.onNodesInserted(data.nodes, data.parent));  
160 - }  
161 - }); 150 + this.treeElement = $('.tb-nav-tree-container', this.elementRef.nativeElement).jstree(config);
162 151
163 - if (this.editCallbacks) {  
164 - this.editCallbacks.selectNode = id => {  
165 - const node: NavTreeNode = this.treeElement.jstree('get_node', id);  
166 - if (node) {  
167 - this.treeElement.jstree('deselect_all', true);  
168 - this.treeElement.jstree('select_node', node);  
169 - }  
170 - };  
171 - this.editCallbacks.deselectAll = () => {  
172 - this.treeElement.jstree('deselect_all');  
173 - };  
174 - this.editCallbacks.getNode = (id) => {  
175 - const node: NavTreeNode = this.treeElement.jstree('get_node', id);  
176 - return node;  
177 - };  
178 - this.editCallbacks.getParentNodeId = (id) => {  
179 - const node: NavTreeNode = this.treeElement.jstree('get_node', id);  
180 - if (node) {  
181 - return this.treeElement.jstree('get_parent', node); 152 + this.treeElement.on('changed.jstree', (e: any, data) => {
  153 + const node: NavTreeNode = data.instance.get_selected(true)[0];
  154 + if (this.onNodeSelected) {
  155 + this.ngZone.run(() => this.onNodeSelected(node, e as Event));
182 } 156 }
183 - };  
184 - this.editCallbacks.openNode = (id, cb) => {  
185 - const node: NavTreeNode = this.treeElement.jstree('get_node', id);  
186 - if (node) {  
187 - this.treeElement.jstree('open_node', node, cb);  
188 - }  
189 - };  
190 - this.editCallbacks.nodeIsOpen = (id) => {  
191 - const node: NavTreeNode = this.treeElement.jstree('get_node', id);  
192 - if (node) {  
193 - return this.treeElement.jstree('is_open', node);  
194 - } else {  
195 - return true;  
196 - }  
197 - };  
198 - this.editCallbacks.nodeIsLoaded = (id) => {  
199 - const node: NavTreeNode = this.treeElement.jstree('get_node', id);  
200 - if (node) {  
201 - return this.treeElement.jstree('is_loaded', node);  
202 - } else {  
203 - return true; 157 + });
  158 +
  159 + this.treeElement.on('model.jstree', (e: any, data) => {
  160 + if (this.onNodesInserted) {
  161 + this.ngZone.run(() => this.onNodesInserted(data.nodes, data.parent));
204 } 162 }
205 - };  
206 - this.editCallbacks.refreshNode = (id) => {  
207 - if (id === '#') {  
208 - this.treeElement.jstree('refresh');  
209 - this.treeElement.jstree('redraw');  
210 - } else { 163 + });
  164 +
  165 + if (this.editCallbacks) {
  166 + this.editCallbacks.selectNode = id => {
  167 + const node: NavTreeNode = this.treeElement.jstree('get_node', id);
  168 + if (node) {
  169 + this.treeElement.jstree('deselect_all', true);
  170 + this.treeElement.jstree('select_node', node);
  171 + }
  172 + };
  173 + this.editCallbacks.deselectAll = () => {
  174 + this.treeElement.jstree('deselect_all');
  175 + };
  176 + this.editCallbacks.getNode = (id) => {
  177 + const node: NavTreeNode = this.treeElement.jstree('get_node', id);
  178 + return node;
  179 + };
  180 + this.editCallbacks.getParentNodeId = (id) => {
211 const node: NavTreeNode = this.treeElement.jstree('get_node', id); 181 const node: NavTreeNode = this.treeElement.jstree('get_node', id);
212 if (node) { 182 if (node) {
213 - const opened = this.treeElement.jstree('is_open', node);  
214 - this.treeElement.jstree('refresh_node', node); 183 + return this.treeElement.jstree('get_parent', node);
  184 + }
  185 + };
  186 + this.editCallbacks.openNode = (id, cb) => {
  187 + const node: NavTreeNode = this.treeElement.jstree('get_node', id);
  188 + if (node) {
  189 + this.treeElement.jstree('open_node', node, cb);
  190 + }
  191 + };
  192 + this.editCallbacks.nodeIsOpen = (id) => {
  193 + const node: NavTreeNode = this.treeElement.jstree('get_node', id);
  194 + if (node) {
  195 + return this.treeElement.jstree('is_open', node);
  196 + } else {
  197 + return true;
  198 + }
  199 + };
  200 + this.editCallbacks.nodeIsLoaded = (id) => {
  201 + const node: NavTreeNode = this.treeElement.jstree('get_node', id);
  202 + if (node) {
  203 + return this.treeElement.jstree('is_loaded', node);
  204 + } else {
  205 + return true;
  206 + }
  207 + };
  208 + this.editCallbacks.refreshNode = (id) => {
  209 + if (id === '#') {
  210 + this.treeElement.jstree('refresh');
215 this.treeElement.jstree('redraw'); 211 this.treeElement.jstree('redraw');
216 - if (node.children && opened/* && !node.children.length*/) {  
217 - this.treeElement.jstree('open_node', node); 212 + } else {
  213 + const node: NavTreeNode = this.treeElement.jstree('get_node', id);
  214 + if (node) {
  215 + const opened = this.treeElement.jstree('is_open', node);
  216 + this.treeElement.jstree('refresh_node', node);
  217 + this.treeElement.jstree('redraw');
  218 + if (node.children && opened/* && !node.children.length*/) {
  219 + this.treeElement.jstree('open_node', node);
  220 + }
218 } 221 }
219 } 222 }
220 - }  
221 - };  
222 - this.editCallbacks.updateNode = (id, newName) => {  
223 - const node: NavTreeNode = this.treeElement.jstree('get_node', id);  
224 - if (node) {  
225 - this.treeElement.jstree('rename_node', node, newName);  
226 - }  
227 - };  
228 - this.editCallbacks.createNode = (parentId, node, pos) => {  
229 - const parentNode: NavTreeNode = this.treeElement.jstree('get_node', parentId);  
230 - if (parentNode) {  
231 - this.treeElement.jstree('create_node', parentNode, node, pos);  
232 - }  
233 - };  
234 - this.editCallbacks.deleteNode = (id) => {  
235 - const node: NavTreeNode = this.treeElement.jstree('get_node', id);  
236 - if (node) {  
237 - this.treeElement.jstree('delete_node', node);  
238 - }  
239 - };  
240 - this.editCallbacks.disableNode = (id) => {  
241 - const node: NavTreeNode = this.treeElement.jstree('get_node', id);  
242 - if (node) {  
243 - this.treeElement.jstree('disable_node', node);  
244 - }  
245 - };  
246 - this.editCallbacks.enableNode = (id) => {  
247 - const node: NavTreeNode = this.treeElement.jstree('get_node', id);  
248 - if (node) {  
249 - this.treeElement.jstree('enable_node', node);  
250 - }  
251 - };  
252 - this.editCallbacks.setNodeHasChildren = (id, hasChildren) => {  
253 - const node: NavTreeNode = this.treeElement.jstree('get_node', id);  
254 - if (node) {  
255 - if (!node.children || (Array.isArray(node.children) && !node.children.length)) {  
256 - node.children = hasChildren;  
257 - node.state.loaded = !hasChildren;  
258 - node.state.opened = false;  
259 - this.treeElement.jstree('_node_changed', node.id);  
260 - this.treeElement.jstree('redraw'); 223 + };
  224 + this.editCallbacks.updateNode = (id, newName) => {
  225 + const node: NavTreeNode = this.treeElement.jstree('get_node', id);
  226 + if (node) {
  227 + this.treeElement.jstree('rename_node', node, newName);
261 } 228 }
262 - }  
263 - };  
264 - this.editCallbacks.search = (searchText) => {  
265 - this.treeElement.jstree('search', searchText);  
266 - };  
267 - this.editCallbacks.clearSearch = () => {  
268 - this.treeElement.jstree('clear_search');  
269 - };  
270 - } 229 + };
  230 + this.editCallbacks.createNode = (parentId, node, pos) => {
  231 + const parentNode: NavTreeNode = this.treeElement.jstree('get_node', parentId);
  232 + if (parentNode) {
  233 + this.treeElement.jstree('create_node', parentNode, node, pos);
  234 + }
  235 + };
  236 + this.editCallbacks.deleteNode = (id) => {
  237 + const node: NavTreeNode = this.treeElement.jstree('get_node', id);
  238 + if (node) {
  239 + this.treeElement.jstree('delete_node', node);
  240 + }
  241 + };
  242 + this.editCallbacks.disableNode = (id) => {
  243 + const node: NavTreeNode = this.treeElement.jstree('get_node', id);
  244 + if (node) {
  245 + this.treeElement.jstree('disable_node', node);
  246 + }
  247 + };
  248 + this.editCallbacks.enableNode = (id) => {
  249 + const node: NavTreeNode = this.treeElement.jstree('get_node', id);
  250 + if (node) {
  251 + this.treeElement.jstree('enable_node', node);
  252 + }
  253 + };
  254 + this.editCallbacks.setNodeHasChildren = (id, hasChildren) => {
  255 + const node: NavTreeNode = this.treeElement.jstree('get_node', id);
  256 + if (node) {
  257 + if (!node.children || (Array.isArray(node.children) && !node.children.length)) {
  258 + node.children = hasChildren;
  259 + node.state.loaded = !hasChildren;
  260 + node.state.opened = false;
  261 + this.treeElement.jstree('_node_changed', node.id);
  262 + this.treeElement.jstree('redraw');
  263 + }
  264 + }
  265 + };
  266 + this.editCallbacks.search = (searchText) => {
  267 + this.treeElement.jstree('search', searchText);
  268 + };
  269 + this.editCallbacks.clearSearch = () => {
  270 + this.treeElement.jstree('clear_search');
  271 + };
  272 + }
  273 + });
271 } 274 }
272 } 275 }
@@ -16,21 +16,14 @@ @@ -16,21 +16,14 @@
16 16
17 import { AfterViewInit, Component, forwardRef, Input, NgZone, OnInit, ViewChild } from '@angular/core'; 17 import { AfterViewInit, Component, forwardRef, Input, NgZone, OnInit, ViewChild } from '@angular/core';
18 import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; 18 import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
19 -import { Observable, of } from 'rxjs'; 19 +import { Observable } from 'rxjs';
20 import { map, mergeMap, share, tap } from 'rxjs/operators'; 20 import { map, mergeMap, share, tap } from 'rxjs/operators';
21 import { Store } from '@ngrx/store'; 21 import { Store } from '@ngrx/store';
22 import { AppState } from '@app/core/core.state'; 22 import { AppState } from '@app/core/core.state';
23 import { TranslateService } from '@ngx-translate/core'; 23 import { TranslateService } from '@ngx-translate/core';
24 import { coerceBooleanProperty } from '@angular/cdk/coercion'; 24 import { coerceBooleanProperty } from '@angular/cdk/coercion';
25 -import * as _moment from 'moment-timezone';  
26 import { MatAutocompleteTrigger } from '@angular/material/autocomplete'; 25 import { MatAutocompleteTrigger } from '@angular/material/autocomplete';
27 -  
28 -interface TimezoneInfo {  
29 - id: string;  
30 - name: string;  
31 - offset: string;  
32 - nOffset: number;  
33 -} 26 +import { getTimezoneInfo, getTimezones, TimezoneInfo } from '@shared/models/time/time.models';
34 27
35 @Component({ 28 @Component({
36 selector: 'tb-timezone-select', 29 selector: 'tb-timezone-select',
@@ -50,28 +43,14 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af @@ -50,28 +43,14 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
50 43
51 defaultTimezoneId: string = null; 44 defaultTimezoneId: string = null;
52 45
53 - defaultTimezoneInfo: TimezoneInfo = null;  
54 -  
55 - timezones: TimezoneInfo[] = _moment.tz.names().map((zoneName) => {  
56 - const tz = _moment.tz(zoneName);  
57 - return {  
58 - id: zoneName,  
59 - name: zoneName.replace(/_/g, ' '),  
60 - offset: `UTC${tz.format('Z')}`,  
61 - nOffset: tz.utcOffset()  
62 - }  
63 - }); 46 + timezones$ = getTimezones().pipe(
  47 + share()
  48 + );
64 49
65 @Input() 50 @Input()
66 set defaultTimezone(timezone: string) { 51 set defaultTimezone(timezone: string) {
67 if (this.defaultTimezoneId !== timezone) { 52 if (this.defaultTimezoneId !== timezone) {
68 this.defaultTimezoneId = timezone; 53 this.defaultTimezoneId = timezone;
69 - if (this.defaultTimezoneId) {  
70 - this.defaultTimezoneInfo =  
71 - this.timezones.find((timezoneInfo) => timezoneInfo.id === this.defaultTimezoneId);  
72 - } else {  
73 - this.defaultTimezoneInfo = null;  
74 - }  
75 } 54 }
76 } 55 }
77 56
@@ -150,23 +129,27 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af @@ -150,23 +129,27 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
150 129
151 writeValue(value: string | null): void { 130 writeValue(value: string | null): void {
152 this.searchText = ''; 131 this.searchText = '';
153 - let foundTimezone: TimezoneInfo = null;  
154 if (value !== null) { 132 if (value !== null) {
155 - foundTimezone = this.timezones.find(timezoneInfo => timezoneInfo.id === value);  
156 - }  
157 - if (foundTimezone !== null) {  
158 - this.modelValue = value;  
159 - this.selectTimezoneFormGroup.get('timezone').patchValue(foundTimezone, {emitEvent: false}); 133 + getTimezoneInfo(value, this.defaultTimezoneId).subscribe(
  134 + (foundTimezone) => {
  135 + if (foundTimezone !== null) {
  136 + this.selectTimezoneFormGroup.get('timezone').patchValue(foundTimezone, {emitEvent: false});
  137 + if (foundTimezone.id !== value) {
  138 + setTimeout(() => {
  139 + this.updateView(foundTimezone.id);
  140 + }, 0);
  141 + } else {
  142 + this.modelValue = value;
  143 + }
  144 + } else {
  145 + this.modelValue = null;
  146 + this.selectTimezoneFormGroup.get('timezone').patchValue('', {emitEvent: false});
  147 + }
  148 + }
  149 + );
160 } else { 150 } else {
161 - if (this.defaultTimezoneInfo) {  
162 - this.selectTimezoneFormGroup.get('timezone').patchValue(this.defaultTimezoneInfo, {emitEvent: false});  
163 - setTimeout(() => {  
164 - this.updateView(this.defaultTimezoneInfo.id);  
165 - }, 0);  
166 - } else {  
167 - this.modelValue = null;  
168 - this.selectTimezoneFormGroup.get('timezone').patchValue('', {emitEvent: false});  
169 - } 151 + this.modelValue = null;
  152 + this.selectTimezoneFormGroup.get('timezone').patchValue('', {emitEvent: false});
170 } 153 }
171 this.dirty = true; 154 this.dirty = true;
172 } 155 }
@@ -182,10 +165,15 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af @@ -182,10 +165,15 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
182 if (this.ignoreClosePanel) { 165 if (this.ignoreClosePanel) {
183 this.ignoreClosePanel = false; 166 this.ignoreClosePanel = false;
184 } else { 167 } else {
185 - if (!this.modelValue && this.defaultTimezoneInfo) {  
186 - this.ngZone.run(() => {  
187 - this.selectTimezoneFormGroup.get('timezone').reset(this.defaultTimezoneInfo, {emitEvent: true});  
188 - }); 168 + if (!this.modelValue && this.defaultTimezoneId) {
  169 + getTimezoneInfo(this.defaultTimezoneId).subscribe(
  170 + (defaultTimezoneInfo) => {
  171 + if (defaultTimezoneInfo !== null) {
  172 + this.ngZone.run(() => {
  173 + this.selectTimezoneFormGroup.get('timezone').reset(defaultTimezoneInfo, {emitEvent: true});
  174 + });
  175 + }
  176 + });
189 } 177 }
190 } 178 }
191 } 179 }
@@ -203,12 +191,13 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af @@ -203,12 +191,13 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
203 191
204 fetchTimezones(searchText?: string): Observable<Array<TimezoneInfo>> { 192 fetchTimezones(searchText?: string): Observable<Array<TimezoneInfo>> {
205 this.searchText = searchText; 193 this.searchText = searchText;
206 - let result = this.timezones;  
207 if (searchText && searchText.length) { 194 if (searchText && searchText.length) {
208 - result = this.timezones.filter((timezoneInfo) =>  
209 - timezoneInfo.name.toLowerCase().includes(searchText.toLowerCase())); 195 + return getTimezones().pipe(
  196 + map((timezones) => timezones.filter((timezoneInfo) =>
  197 + timezoneInfo.name.toLowerCase().includes(searchText.toLowerCase())))
  198 + );
210 } 199 }
211 - return of(result); 200 + return getTimezones();
212 } 201 }
213 202
214 clear() { 203 clear() {
  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 { Observable } from 'rxjs/internal/Observable';
  18 +import { from, of } from 'rxjs';
  19 +import { map, tap } from 'rxjs/operators';
  20 +
  21 +let jsBeautifyModule: any;
  22 +let htmlBeautifyModule: any;
  23 +let cssBeautifyModule: any;
  24 +
  25 +function loadJsBeautify(): Observable<any> {
  26 + if (jsBeautifyModule) {
  27 + return of(jsBeautifyModule);
  28 + } else {
  29 + return from(import('js-beautify/js/lib/beautify.js')).pipe(
  30 + tap((module) => {
  31 + jsBeautifyModule = module;
  32 + })
  33 + );
  34 + }
  35 +}
  36 +
  37 +function loadHtmlBeautify(): Observable<any> {
  38 + if (htmlBeautifyModule) {
  39 + return of(htmlBeautifyModule);
  40 + } else {
  41 + return from(import('js-beautify/js/lib/beautify-html.js')).pipe(
  42 + tap((module) => {
  43 + htmlBeautifyModule = module;
  44 + })
  45 + );
  46 + }
  47 +}
  48 +
  49 +function loadCssBeautify(): Observable<any> {
  50 + if (cssBeautifyModule) {
  51 + return of(cssBeautifyModule);
  52 + } else {
  53 + return from(import('js-beautify/js/lib/beautify-css.js')).pipe(
  54 + tap((module) => {
  55 + cssBeautifyModule = module;
  56 + })
  57 + );
  58 + }
  59 +}
  60 +
  61 +export function beautifyJs(source: string, options?: JSBeautifyOptions): Observable<string> {
  62 + return loadJsBeautify().pipe(
  63 + map((mod) => {
  64 + return mod.js_beautify(source, options);
  65 + })
  66 + );
  67 +}
  68 +
  69 +export function beautifyCss(source: string, options?: CSSBeautifyOptions): Observable<string> {
  70 + return loadCssBeautify().pipe(
  71 + map((mod) => mod.css_beautify(source, options))
  72 + );
  73 +}
  74 +
  75 +export function beautifyHtml(source: string, options?: HTMLBeautifyOptions): Observable<string> {
  76 + return loadHtmlBeautify().pipe(
  77 + map((mod) => mod.html_beautify(source, options))
  78 + );
  79 +}
@@ -25,7 +25,7 @@ import { RuleChainId } from '@shared/models/id/rule-chain-id'; @@ -25,7 +25,7 @@ import { RuleChainId } from '@shared/models/id/rule-chain-id';
25 import { EntityInfoData } from '@shared/models/entity.models'; 25 import { EntityInfoData } from '@shared/models/entity.models';
26 import { KeyFilter } from '@shared/models/query/query.models'; 26 import { KeyFilter } from '@shared/models/query/query.models';
27 import { TimeUnit } from '@shared/models/time/time.models'; 27 import { TimeUnit } from '@shared/models/time/time.models';
28 -import * as _moment from 'moment-timezone'; 28 +import * as _moment from 'moment';
29 import { AbstractControl, ValidationErrors } from '@angular/forms'; 29 import { AbstractControl, ValidationErrors } from '@angular/forms';
30 30
31 export enum DeviceProfileType { 31 export enum DeviceProfileType {
@@ -17,6 +17,9 @@ @@ -17,6 +17,9 @@
17 import { TimeService } from '@core/services/time.service'; 17 import { TimeService } from '@core/services/time.service';
18 import { deepClone, isDefined, isUndefined } from '@app/core/utils'; 18 import { deepClone, isDefined, isUndefined } from '@app/core/utils';
19 import * as moment_ from 'moment'; 19 import * as moment_ from 'moment';
  20 +import { Observable } from 'rxjs/internal/Observable';
  21 +import { from, of } from 'rxjs';
  22 +import { map, tap } from 'rxjs/operators';
20 23
21 const moment = moment_; 24 const moment = moment_;
22 25
@@ -481,3 +484,63 @@ export const timeUnitTranslationMap = new Map<TimeUnit, string>( @@ -481,3 +484,63 @@ export const timeUnitTranslationMap = new Map<TimeUnit, string>(
481 [TimeUnit.DAYS, 'timeunit.days'] 484 [TimeUnit.DAYS, 'timeunit.days']
482 ] 485 ]
483 ); 486 );
  487 +
  488 +export interface TimezoneInfo {
  489 + id: string;
  490 + name: string;
  491 + offset: string;
  492 + nOffset: number;
  493 +}
  494 +
  495 +let timezones: TimezoneInfo[] = null;
  496 +let defaultTimezone: string = null;
  497 +
  498 +export function getTimezones(): Observable<TimezoneInfo[]> {
  499 + if (timezones) {
  500 + return of(timezones);
  501 + } else {
  502 + return from(import('moment-timezone')).pipe(
  503 + map((monentTz) => {
  504 + return monentTz.tz.names().map((zoneName) => {
  505 + const tz = monentTz.tz(zoneName);
  506 + return {
  507 + id: zoneName,
  508 + name: zoneName.replace(/_/g, ' '),
  509 + offset: `UTC${tz.format('Z')}`,
  510 + nOffset: tz.utcOffset()
  511 + };
  512 + });
  513 + }),
  514 + tap((zones) => {
  515 + timezones = zones;
  516 + })
  517 + );
  518 + }
  519 +}
  520 +
  521 +export function getTimezoneInfo(timezoneId: string, defaultTimezoneId?: string): Observable<TimezoneInfo> {
  522 + return getTimezones().pipe(
  523 + map((timezoneList) => {
  524 + let foundTimezone = timezoneList.find(timezoneInfo => timezoneInfo.id === timezoneId);
  525 + if (!foundTimezone && defaultTimezoneId) {
  526 + foundTimezone = timezoneList.find(timezoneInfo => timezoneInfo.id === defaultTimezoneId);
  527 + }
  528 + return foundTimezone;
  529 + })
  530 + );
  531 +}
  532 +
  533 +export function getDefaultTimezone(): Observable<string> {
  534 + if (defaultTimezone) {
  535 + return of(defaultTimezone);
  536 + } else {
  537 + return from(import('moment-timezone')).pipe(
  538 + map((monentTz) => {
  539 + return monentTz.tz.guess();
  540 + }),
  541 + tap((zone) => {
  542 + defaultTimezone = zone;
  543 + })
  544 + );
  545 + }
  546 +}
@@ -37,6 +37,18 @@ @@ -37,6 +37,18 @@
37 ], 37 ],
38 "ace": [ 38 "ace": [
39 "node_modules/ace-builds/src-noconflict/ace.js" 39 "node_modules/ace-builds/src-noconflict/ace.js"
  40 + ],
  41 + "jquery": [
  42 + "node_modules/jquery/dist/jquery.min.js"
  43 + ],
  44 + "jquery.terminal": [
  45 + "node_modules/jquery.terminal/js/jquery.terminal.js"
  46 + ],
  47 + "tooltipster": [
  48 + "node_modules/tooltipster/dist/js/tooltipster.bundle.min.js"
  49 + ],
  50 + "jstree": [
  51 + "node_modules/jstree/dist/jstree.min.js"
40 ] 52 ]
41 }, 53 },
42 "lib": [ 54 "lib": [