Commit ed56f041f8aaa464b9288ac4982c9d917a9fd908

Authored by Igor Kulikov
1 parent bcfc7cc7

UI optimizations

... ... @@ -86,25 +86,9 @@
86 86 ]
87 87 },
88 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 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 93 "customWebpackConfig": {
110 94 "path": "./extra-webpack.config.js"
... ... @@ -130,7 +114,11 @@
130 114 "tinycolor2",
131 115 "json-schema-defaults",
132 116 "leaflet-providers",
133   - "lodash"
  117 + "lodash",
  118 + "jquery",
  119 + "jquery.terminal",
  120 + "tooltipster",
  121 + "jstree"
134 122 ]
135 123 },
136 124 "configurations": {
... ...
... ... @@ -35,6 +35,13 @@ module.exports = (config, options) => {
35 35 })
36 36 );
37 37 config.plugins.push(
  38 + new webpack.ProvidePlugin(
  39 + {
  40 + $: "jquery"
  41 + }
  42 + )
  43 + );
  44 + config.plugins.push(
38 45 new CompressionPlugin({
39 46 filename: "[path][base].gz[query]",
40 47 algorithm: "gzip",
... ... @@ -44,6 +51,9 @@ module.exports = (config, options) => {
44 51 deleteOriginalAssets: false,
45 52 })
46 53 );
  54 + config.plugins.push(
  55 + new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
  56 + );
47 57
48 58 if (config.mode === 'production') {
49 59 const index = config.plugins.findIndex(p => p instanceof AngularCompilerPlugin.AngularCompilerPlugin);
... ...
... ... @@ -24,6 +24,9 @@ import { DialogComponent } from '@shared/components/dialog.component';
24 24 import { Router } from '@angular/router';
25 25 import { ContentType, contentTypesMap } from '@shared/models/constants';
26 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 31 export interface EventContentDialogData {
29 32 content: string;
... ... @@ -64,33 +67,42 @@ export class EventContentDialogComponent extends DialogComponent<EventContentDia
64 67 createEditor(editorElementRef: ElementRef, content: string) {
65 68 const editorElement = editorElementRef.nativeElement;
66 69 let mode = 'java';
  70 + let content$: Observable<string> = null;
67 71 if (this.contentType) {
68 72 mode = contentTypesMap.get(this.contentType).code;
69 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 29 </mat-form-field>
30 30 <div *ngIf="alarmScheduleForm.get('type').value !== alarmScheduleType.ANY_TIME">
31 31 <tb-timezone-select
32   - [defaultTimezone]="defaultTimezone"
  32 + [defaultTimezone]="defaultTimezone$ | async"
33 33 required
34 34 formControlName="timezone">
35 35 </tb-timezone-select>
... ...
... ... @@ -32,11 +32,15 @@ import {
32 32 AlarmSchedule,
33 33 AlarmScheduleType,
34 34 AlarmScheduleTypeTranslationMap,
35   - dayOfWeekTranslations, getAlarmScheduleRangeText, timeOfDayToUTCTimestamp, utcTimestampToTimeOfDay
  35 + dayOfWeekTranslations,
  36 + getAlarmScheduleRangeText,
  37 + timeOfDayToUTCTimestamp,
  38 + utcTimestampToTimeOfDay
36 39 } from '@shared/models/device.models';
37 40 import { isDefined, isDefinedAndNotNull } from '@core/utils';
38   -import * as _moment from 'moment-timezone';
39 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 45 @Component({
42 46 selector: 'tb-alarm-schedule',
... ... @@ -58,7 +62,9 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator,
58 62
59 63 alarmScheduleForm: FormGroup;
60 64
61   - defaultTimezone = _moment.tz.guess();
  65 + defaultTimezone$ = getDefaultTimezone().pipe(
  66 + share()
  67 + );
62 68
63 69 alarmScheduleTypes = Object.keys(AlarmScheduleType);
64 70 alarmScheduleType = AlarmScheduleType;
... ... @@ -93,9 +99,11 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator,
93 99 items: this.fb.array(Array.from({length: 7}, (value, i) => this.defaultItemsScheduler(i)), this.validateItems)
94 100 });
95 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 108 this.alarmScheduleForm.valueChanges.subscribe(() => {
101 109 this.updateModel();
... ...
... ... @@ -34,13 +34,13 @@ import { AppState } from '@core/core.state';
34 34 import { CustomActionDescriptor } from '@shared/models/widget.models';
35 35 import { Ace } from 'ace-builds';
36 36 import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
37   -import { css_beautify, html_beautify } from 'js-beautify';
38 37 import { ResizeObserver } from '@juggle/resize-observer';
39 38 import { CustomPrettyActionEditorCompleter } from '@home/components/widget/action/custom-action.models';
40 39 import { Observable } from 'rxjs/internal/Observable';
41 40 import { forkJoin, from } from 'rxjs';
42 41 import { map, tap } from 'rxjs/operators';
43 42 import { getAce } from '@shared/models/ace/ace.models';
  43 +import { beautifyCss, beautifyHtml } from '@shared/models/beautify.models';
44 44
45 45 @Component({
46 46 selector: 'tb-custom-action-pretty-resources-tabs',
... ... @@ -140,21 +140,27 @@ export class CustomActionPrettyResourcesTabsComponent extends PageComponent impl
140 140 }
141 141
142 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 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 166 private initAceEditors(): Observable<any> {
... ...
... ... @@ -111,11 +111,32 @@ export class WidgetComponentService {
111 111 const initSubject = new ReplaySubject();
112 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 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 140 widgetModulesTasks.push(from(import('@home/components/widget/lib/flot-widget')).pipe(
120 141 tap((mod) => {
121 142 (window as any).TbFlot = mod.TbFlot;
... ...
... ... @@ -29,7 +29,7 @@ import {
29 29 import { EntitiesTableComponent } from '../../components/entity/entities-table.component';
30 30 import { Authority } from '@shared/models/authority.enum';
31 31 import { RuleChainsTableConfigResolver } from '@modules/home/pages/rulechain/rulechains-table-config.resolver';
32   -import { Observable } from 'rxjs';
  32 +import { from, Observable } from 'rxjs';
33 33 import { BreadCrumbConfig, BreadCrumbLabelFunction } from '@shared/components/breadcrumb';
34 34 import { ResolvedRuleChainMetaData, RuleChain } from '@shared/models/rule-chain.models';
35 35 import { RuleChainService } from '@core/http/rule-chain.service';
... ... @@ -76,6 +76,17 @@ export class RuleNodeComponentsResolver implements Resolve<Array<RuleNodeCompone
76 76 }
77 77
78 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 90 export class RuleChainImportGuard implements CanActivate {
80 91
81 92 constructor(private itembuffer: ItemBufferService,
... ... @@ -144,7 +155,8 @@ const routes: Routes = [
144 155 resolve: {
145 156 ruleChain: RuleChainResolver,
146 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 174 import: true
163 175 },
164 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 191 RuleChainResolver,
179 192 ResolvedRuleChainMetaDataResolver,
180 193 RuleNodeComponentsResolver,
  194 + TooltipsterResolver,
181 195 RuleChainImportGuard
182 196 ]
183 197 })
... ...
... ... @@ -34,7 +34,6 @@ import { TranslateService } from '@ngx-translate/core';
34 34 import { getCurrentIsLoading } from '@app/core/interceptors/load.selectors';
35 35 import { Ace } from 'ace-builds';
36 36 import { getAce, Range } from '@shared/models/ace/ace.models';
37   -import { css_beautify, html_beautify } from 'js-beautify';
38 37 import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
39 38 import { WINDOW } from '@core/services/window.service';
40 39 import { WindowMessage } from '@shared/models/window-message.model';
... ... @@ -51,6 +50,7 @@ import Timeout = NodeJS.Timeout;
51 50 import { widgetEditorCompleter } from '@home/pages/widget/widget-editor.models';
52 51 import { Observable } from 'rxjs/internal/Observable';
53 52 import { map, tap } from 'rxjs/operators';
  53 +import { beautifyCss, beautifyHtml, beautifyJs } from '@shared/models/beautify.models';
54 54
55 55 // @dynamic
56 56 @Component({
... ... @@ -618,48 +618,63 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
618 618 }
619 619
620 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 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 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 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 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 680 removeResource(index: number) {
... ...
... ... @@ -41,6 +41,7 @@ import { TestScriptInputParams } from '@shared/models/rule-node.models';
41 41 import { RuleChainService } from '@core/http/rule-chain.service';
42 42 import { mergeMap } from 'rxjs/operators';
43 43 import { ActionNotificationShow } from '@core/notification/notification.actions';
  44 +import { beautifyJs } from '@shared/models/beautify.models';
44 45
45 46 export interface NodeScriptTestDialogData {
46 47 script: string;
... ... @@ -110,12 +111,17 @@ export class NodeScriptTestDialogComponent extends DialogComponent<NodeScriptTes
110 111 this.nodeScriptTestFormGroup = this.fb.group({
111 112 payload: this.fb.group({
112 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 116 metadata: [this.data.metadata, [Validators.required]],
116 117 script: [this.data.script, []],
117 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 127 ngAfterViewInit(): void {
... ... @@ -166,7 +172,11 @@ export class NodeScriptTestDialogComponent extends DialogComponent<NodeScriptTes
166 172
167 173 test(): void {
168 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 37 import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
38 38 import { ResizeObserver } from '@juggle/resize-observer';
39 39 import { TbEditorCompleter } from '@shared/models/ace/completion.models';
  40 +import { beautifyJs } from '@shared/models/beautify.models';
40 41
41 42 @Component({
42 43 selector: 'tb-js-func',
... ... @@ -214,9 +215,12 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor,
214 215 }
215 216
216 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 226 validateOnSubmit(): void {
... ...
... ... @@ -36,6 +36,7 @@ import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
36 36 import { guid } from '@core/utils';
37 37 import { ResizeObserver } from '@juggle/resize-observer';
38 38 import { getAce } from '@shared/models/ace/ace.models';
  39 +import { beautifyJs } from '@shared/models/beautify.models';
39 40
40 41 @Component({
41 42 selector: 'tb-json-content',
... ... @@ -286,9 +287,12 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid
286 287 }
287 288
288 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 298 minifyJSON() {
... ...
... ... @@ -22,6 +22,7 @@ import { IEditorProps } from 'react-ace/src/types';
22 22 import { map, mergeMap } from 'rxjs/operators';
23 23 import { loadAceDependencies } from '@shared/models/ace/ace.models';
24 24 import { from } from 'rxjs';
  25 +import { Observable } from 'rxjs/internal/Observable';
25 26
26 27 const ReactAce = React.lazy(() => {
27 28 return loadAceDependencies().pipe(
... ... @@ -33,7 +34,7 @@ const ReactAce = React.lazy(() => {
33 34
34 35 interface ThingsboardAceEditorProps extends JsonFormFieldProps {
35 36 mode: string;
36   - onTidy: (value: string) => string;
  37 + onTidy: (value: string) => Observable<string>;
37 38 }
38 39
39 40 interface ThingsboardAceEditorState extends JsonFormFieldState {
... ... @@ -84,15 +85,18 @@ class ThingsboardAceEditor extends React.Component<ThingsboardAceEditorProps, Th
84 85 onTidy() {
85 86 if (!this.props.form.readonly) {
86 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 16 import * as React from 'react';
17 17 import ThingsboardAceEditor from './json-form-ace-editor';
18 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 22 class ThingsboardCss extends React.Component<JsonFormFieldProps, JsonFormFieldState> {
22 23
... ... @@ -25,8 +26,8 @@ class ThingsboardCss extends React.Component<JsonFormFieldProps, JsonFormFieldSt
25 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 33 render() {
... ...
... ... @@ -15,8 +15,9 @@
15 15 */
16 16 import * as React from 'react';
17 17 import ThingsboardAceEditor from './json-form-ace-editor';
18   -import { html_beautify } from 'js-beautify';
19 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 22 class ThingsboardHtml extends React.Component<JsonFormFieldProps, JsonFormFieldState> {
22 23
... ... @@ -25,8 +26,8 @@ class ThingsboardHtml extends React.Component<JsonFormFieldProps, JsonFormFieldS
25 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 33 render() {
... ...
... ... @@ -16,6 +16,8 @@
16 16 import * as React from 'react';
17 17 import ThingsboardAceEditor from './json-form-ace-editor';
18 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 22 class ThingsboardJavaScript extends React.Component<JsonFormFieldProps, JsonFormFieldState> {
21 23
... ... @@ -24,8 +26,8 @@ class ThingsboardJavaScript extends React.Component<JsonFormFieldProps, JsonForm
24 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 33 render() {
... ...
... ... @@ -16,6 +16,8 @@
16 16 import * as React from 'react';
17 17 import ThingsboardAceEditor from './json-form-ace-editor';
18 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 22 class ThingsboardJson extends React.Component<JsonFormFieldProps, JsonFormFieldState> {
21 23
... ... @@ -24,8 +26,8 @@ class ThingsboardJson extends React.Component<JsonFormFieldProps, JsonFormFieldS
24 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 33 render() {
... ...
... ... @@ -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 181 const node: NavTreeNode = this.treeElement.jstree('get_node', id);
212 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 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 16
17 17 import { AfterViewInit, Component, forwardRef, Input, NgZone, OnInit, ViewChild } from '@angular/core';
18 18 import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
19   -import { Observable, of } from 'rxjs';
  19 +import { Observable } from 'rxjs';
20 20 import { map, mergeMap, share, tap } from 'rxjs/operators';
21 21 import { Store } from '@ngrx/store';
22 22 import { AppState } from '@app/core/core.state';
23 23 import { TranslateService } from '@ngx-translate/core';
24 24 import { coerceBooleanProperty } from '@angular/cdk/coercion';
25   -import * as _moment from 'moment-timezone';
26 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 28 @Component({
36 29 selector: 'tb-timezone-select',
... ... @@ -50,28 +43,14 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
50 43
51 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 50 @Input()
66 51 set defaultTimezone(timezone: string) {
67 52 if (this.defaultTimezoneId !== timezone) {
68 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 129
151 130 writeValue(value: string | null): void {
152 131 this.searchText = '';
153   - let foundTimezone: TimezoneInfo = null;
154 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 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 154 this.dirty = true;
172 155 }
... ... @@ -182,10 +165,15 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
182 165 if (this.ignoreClosePanel) {
183 166 this.ignoreClosePanel = false;
184 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 191
204 192 fetchTimezones(searchText?: string): Observable<Array<TimezoneInfo>> {
205 193 this.searchText = searchText;
206   - let result = this.timezones;
207 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 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 25 import { EntityInfoData } from '@shared/models/entity.models';
26 26 import { KeyFilter } from '@shared/models/query/query.models';
27 27 import { TimeUnit } from '@shared/models/time/time.models';
28   -import * as _moment from 'moment-timezone';
  28 +import * as _moment from 'moment';
29 29 import { AbstractControl, ValidationErrors } from '@angular/forms';
30 30
31 31 export enum DeviceProfileType {
... ...
... ... @@ -17,6 +17,9 @@
17 17 import { TimeService } from '@core/services/time.service';
18 18 import { deepClone, isDefined, isUndefined } from '@app/core/utils';
19 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 24 const moment = moment_;
22 25
... ... @@ -481,3 +484,63 @@ export const timeUnitTranslationMap = new Map<TimeUnit, string>(
481 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 37 ],
38 38 "ace": [
39 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 54 "lib": [
... ...