Showing
16 changed files
with
242 additions
and
59 deletions
... | ... | @@ -72,6 +72,7 @@ public class EntityKeyMapping { |
72 | 72 | private static final String PHONE = "phone"; |
73 | 73 | |
74 | 74 | public static final List<String> commonEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME); |
75 | + public static final List<String> dashboardEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, TITLE); | |
75 | 76 | public static final List<String> labeledEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL); |
76 | 77 | public static final List<String> contactBasedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, EMAIL, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE); |
77 | 78 | |
... | ... | @@ -89,7 +90,7 @@ public class EntityKeyMapping { |
89 | 90 | |
90 | 91 | allowedEntityFieldMap.put(EntityType.USER, new HashSet<>(Arrays.asList(CREATED_TIME, FIRST_NAME, LAST_NAME, EMAIL))); |
91 | 92 | |
92 | - allowedEntityFieldMap.put(EntityType.DASHBOARD, new HashSet<>(commonEntityFields)); | |
93 | + allowedEntityFieldMap.put(EntityType.DASHBOARD, new HashSet<>(dashboardEntityFields)); | |
93 | 94 | allowedEntityFieldMap.put(EntityType.RULE_CHAIN, new HashSet<>(commonEntityFields)); |
94 | 95 | allowedEntityFieldMap.put(EntityType.RULE_NODE, new HashSet<>(commonEntityFields)); |
95 | 96 | allowedEntityFieldMap.put(EntityType.WIDGET_TYPE, new HashSet<>(commonEntityFields)); |
... | ... | @@ -118,12 +119,12 @@ public class EntityKeyMapping { |
118 | 119 | contactBasedAliases.put(LABEL, TITLE); |
119 | 120 | aliases.put(EntityType.TENANT, contactBasedAliases); |
120 | 121 | aliases.put(EntityType.CUSTOMER, contactBasedAliases); |
122 | + aliases.put(EntityType.DASHBOARD, contactBasedAliases); | |
121 | 123 | Map<String, String> commonEntityAliases = new HashMap<>(); |
122 | 124 | commonEntityAliases.put(TITLE, NAME); |
123 | 125 | aliases.put(EntityType.DEVICE, commonEntityAliases); |
124 | 126 | aliases.put(EntityType.ASSET, commonEntityAliases); |
125 | 127 | aliases.put(EntityType.ENTITY_VIEW, commonEntityAliases); |
126 | - aliases.put(EntityType.DASHBOARD, commonEntityAliases); | |
127 | 128 | aliases.put(EntityType.WIDGETS_BUNDLE, commonEntityAliases); |
128 | 129 | |
129 | 130 | Map<String, String> userEntityAliases = new HashMap<>(); | ... | ... |
... | ... | @@ -8997,10 +8997,10 @@ |
8997 | 8997 | "integrity": "sha512-4O3GWAYJaauMCILm07weko2rHA8a4kjn7+8Lg4s1d7SxwS/3IpkVD/GljbRrIJ1c1W/XGJ3GbuK7RyYZEJChhw==" |
8998 | 8998 | }, |
8999 | 8999 | "ngx-flowchart": { |
9000 | - "version": "git://github.com/thingsboard/ngx-flowchart.git#a4157b0eef2eb3646ef920447c7b06b39d54f87f", | |
9000 | + "version": "git://github.com/thingsboard/ngx-flowchart.git#7a02f4748b5e7821a883c903107af5f20415d026", | |
9001 | 9001 | "from": "git://github.com/thingsboard/ngx-flowchart.git#master", |
9002 | 9002 | "requires": { |
9003 | - "tslib": "^1.10.0" | |
9003 | + "tslib": "^1.13.0" | |
9004 | 9004 | }, |
9005 | 9005 | "dependencies": { |
9006 | 9006 | "tslib": { | ... | ... |
... | ... | @@ -25,6 +25,10 @@ const PROXY_CONFIG = { |
25 | 25 | "target": `http://${ruleNodeUiforwardHost}:${ruleNodeUiforwardPort}`, |
26 | 26 | "secure": false, |
27 | 27 | }, |
28 | + "/static": { | |
29 | + "target": "http://localhost:8080", | |
30 | + "secure": false, | |
31 | + }, | |
28 | 32 | "/api/ws": { |
29 | 33 | "target": "ws://localhost:8080", |
30 | 34 | "ws": true, | ... | ... |
... | ... | @@ -337,6 +337,7 @@ export class WidgetSubscription implements IWidgetSubscription { |
337 | 337 | } |
338 | 338 | |
339 | 339 | private initDataSubscription(): Observable<any> { |
340 | + this.notifyDataLoading(); | |
340 | 341 | const initDataSubscriptionSubject = new ReplaySubject(1); |
341 | 342 | this.loadStDiff().subscribe(() => { |
342 | 343 | if (!this.ctx.aliasController) { |
... | ... | @@ -431,6 +432,7 @@ export class WidgetSubscription implements IWidgetSubscription { |
431 | 432 | }); |
432 | 433 | this.configureLoadedData(); |
433 | 434 | this.hasResolvedData = this.datasources.length > 0; |
435 | + this.updateDataTimewindow(); | |
434 | 436 | this.notifyDataLoaded(); |
435 | 437 | this.onDataUpdated(true); |
436 | 438 | }) |
... | ... | @@ -731,6 +733,7 @@ export class WidgetSubscription implements IWidgetSubscription { |
731 | 733 | if (this.type === widgetType.alarm) { |
732 | 734 | this.updateAlarmDataSubscription(); |
733 | 735 | } else { |
736 | + this.notifyDataLoading(); | |
734 | 737 | this.dataSubscribe(); |
735 | 738 | } |
736 | 739 | } |
... | ... | @@ -831,13 +834,21 @@ export class WidgetSubscription implements IWidgetSubscription { |
831 | 834 | } |
832 | 835 | } |
833 | 836 | |
834 | - private dataSubscribe() { | |
837 | + private updateDataTimewindow() { | |
835 | 838 | if (!this.hasDataPageLink) { |
836 | 839 | if (this.type === widgetType.timeseries && this.timeWindowConfig) { |
837 | 840 | this.updateRealtimeSubscription(); |
838 | 841 | if (this.comparisonEnabled) { |
839 | 842 | this.updateSubscriptionForComparison(); |
840 | 843 | } |
844 | + } | |
845 | + } | |
846 | + } | |
847 | + | |
848 | + private dataSubscribe() { | |
849 | + if (!this.hasDataPageLink) { | |
850 | + if (this.type === widgetType.timeseries && this.timeWindowConfig) { | |
851 | + this.updateDataTimewindow(); | |
841 | 852 | if (this.subscriptionTimewindow.fixedWindow) { |
842 | 853 | this.onDataUpdated(); |
843 | 854 | } |
... | ... | @@ -1025,6 +1036,11 @@ export class WidgetSubscription implements IWidgetSubscription { |
1025 | 1036 | } |
1026 | 1037 | } |
1027 | 1038 | |
1039 | + private notifyDataLoading() { | |
1040 | + this.loadingData = true; | |
1041 | + this.callbacks.dataLoading(this); | |
1042 | + } | |
1043 | + | |
1028 | 1044 | private notifyDataLoaded() { |
1029 | 1045 | this.loadingData = false; |
1030 | 1046 | this.callbacks.dataLoading(this); |
... | ... | @@ -1268,6 +1284,7 @@ export class WidgetSubscription implements IWidgetSubscription { |
1268 | 1284 | if (this.caulculateLegendData) { |
1269 | 1285 | this.updateLegend(index, data.data, detectChanges); |
1270 | 1286 | } |
1287 | + this.notifyDataLoaded(); | |
1271 | 1288 | this.onDataUpdated(detectChanges); |
1272 | 1289 | } |
1273 | 1290 | } | ... | ... |
... | ... | @@ -239,7 +239,7 @@ export class RuleChainService { |
239 | 239 | }); |
240 | 240 | } |
241 | 241 | if (moduleResource) { |
242 | - tasks.push(this.resourcesService.loadModule(moduleResource, ruleNodeConfigResourcesModulesMap).pipe( | |
242 | + tasks.push(this.resourcesService.loadFactories(moduleResource, ruleNodeConfigResourcesModulesMap).pipe( | |
243 | 243 | map((res) => { |
244 | 244 | if (nodeDefinition.configDirective && nodeDefinition.configDirective.length) { |
245 | 245 | const selector = snakeCase(nodeDefinition.configDirective, '-'); | ... | ... |
... | ... | @@ -25,6 +25,8 @@ import { |
25 | 25 | } from '@angular/core'; |
26 | 26 | import { DOCUMENT } from '@angular/common'; |
27 | 27 | import { forkJoin, Observable, ReplaySubject, throwError } from 'rxjs'; |
28 | +import { HttpClient } from '@angular/common/http'; | |
29 | +import { objToBase64 } from '@core/utils'; | |
28 | 30 | |
29 | 31 | declare const SystemJS; |
30 | 32 | |
... | ... | @@ -34,12 +36,14 @@ declare const SystemJS; |
34 | 36 | export class ResourcesService { |
35 | 37 | |
36 | 38 | private loadedResources: { [url: string]: ReplaySubject<any> } = {}; |
37 | - private loadedModules: { [url: string]: ReplaySubject<ComponentFactory<any>[]> } = {}; | |
39 | + private loadedModules: { [url: string]: ReplaySubject<Type<any>[]> } = {}; | |
40 | + private loadedFactories: { [url: string]: ReplaySubject<ComponentFactory<any>[]> } = {}; | |
38 | 41 | |
39 | 42 | private anchor = this.document.getElementsByTagName('head')[0] || this.document.getElementsByTagName('body')[0]; |
40 | 43 | |
41 | 44 | constructor(@Inject(DOCUMENT) private readonly document: any, |
42 | 45 | private compiler: Compiler, |
46 | + private http: HttpClient, | |
43 | 47 | private injector: Injector) {} |
44 | 48 | |
45 | 49 | public loadResource(url: string): Observable<any> { |
... | ... | @@ -60,12 +64,12 @@ export class ResourcesService { |
60 | 64 | return this.loadResourceByType(fileType, url); |
61 | 65 | } |
62 | 66 | |
63 | - public loadModule(url: string, modulesMap: {[key: string]: any}): Observable<ComponentFactory<any>[]> { | |
64 | - if (this.loadedModules[url]) { | |
65 | - return this.loadedModules[url].asObservable(); | |
67 | + public loadFactories(url: string, modulesMap: {[key: string]: any}): Observable<ComponentFactory<any>[]> { | |
68 | + if (this.loadedFactories[url]) { | |
69 | + return this.loadedFactories[url].asObservable(); | |
66 | 70 | } |
67 | 71 | const subject = new ReplaySubject<ComponentFactory<any>[]>(); |
68 | - this.loadedModules[url] = subject; | |
72 | + this.loadedFactories[url] = subject; | |
69 | 73 | if (modulesMap) { |
70 | 74 | for (const moduleId of Object.keys(modulesMap)) { |
71 | 75 | SystemJS.set(moduleId, modulesMap[moduleId]); |
... | ... | @@ -86,19 +90,76 @@ export class ResourcesService { |
86 | 90 | c.ngModuleFactory.create(this.injector); |
87 | 91 | componentFactories.push(...c.componentFactories); |
88 | 92 | } |
89 | - this.loadedModules[url].next(componentFactories); | |
90 | - this.loadedModules[url].complete(); | |
93 | + this.loadedFactories[url].next(componentFactories); | |
94 | + this.loadedFactories[url].complete(); | |
91 | 95 | } catch (e) { |
92 | - this.loadedModules[url].error(new Error(`Unable to init module from url: ${url}`)); | |
93 | - delete this.loadedModules[url]; | |
96 | + this.loadedFactories[url].error(new Error(`Unable to init module from url: ${url}`)); | |
97 | + delete this.loadedFactories[url]; | |
94 | 98 | } |
95 | 99 | }, |
96 | 100 | (e) => { |
97 | - this.loadedModules[url].error(new Error(`Unable to compile module from url: ${url}`)); | |
98 | - delete this.loadedModules[url]; | |
101 | + this.loadedFactories[url].error(new Error(`Unable to compile module from url: ${url}`)); | |
102 | + delete this.loadedFactories[url]; | |
99 | 103 | }); |
100 | 104 | } else { |
101 | - this.loadedModules[url].error(new Error(`Module '${url}' doesn't have default export!`)); | |
105 | + this.loadedFactories[url].error(new Error(`Module '${url}' doesn't have default export!`)); | |
106 | + delete this.loadedFactories[url]; | |
107 | + } | |
108 | + }, | |
109 | + (e) => { | |
110 | + this.loadedFactories[url].error(new Error(`Unable to load module from url: ${url}`)); | |
111 | + delete this.loadedFactories[url]; | |
112 | + } | |
113 | + ); | |
114 | + return subject.asObservable(); | |
115 | + } | |
116 | + | |
117 | + public loadModules(url: string, modulesMap: {[key: string]: any}): Observable<Type<any>[]> { | |
118 | + if (this.loadedModules[url]) { | |
119 | + return this.loadedModules[url].asObservable(); | |
120 | + } | |
121 | + const subject = new ReplaySubject<Type<any>[]>(); | |
122 | + this.loadedModules[url] = subject; | |
123 | + if (modulesMap) { | |
124 | + for (const moduleId of Object.keys(modulesMap)) { | |
125 | + SystemJS.set(moduleId, modulesMap[moduleId]); | |
126 | + } | |
127 | + } | |
128 | + SystemJS.import(url).then( | |
129 | + (module) => { | |
130 | + try { | |
131 | + let modules; | |
132 | + try { | |
133 | + modules = this.extractNgModules(module); | |
134 | + } catch (e) { | |
135 | + } | |
136 | + if (modules && modules.length) { | |
137 | + const tasks: Promise<ModuleWithComponentFactories<any>>[] = []; | |
138 | + for (const m of modules) { | |
139 | + tasks.push(this.compiler.compileModuleAndAllComponentsAsync(m)); | |
140 | + } | |
141 | + forkJoin(tasks).subscribe((compiled) => { | |
142 | + try { | |
143 | + for (const c of compiled) { | |
144 | + c.ngModuleFactory.create(this.injector); | |
145 | + } | |
146 | + this.loadedModules[url].next(modules); | |
147 | + this.loadedModules[url].complete(); | |
148 | + } catch (e) { | |
149 | + this.loadedModules[url].error(new Error(`Unable to init module from url: ${url}`)); | |
150 | + delete this.loadedModules[url]; | |
151 | + } | |
152 | + }, | |
153 | + (e) => { | |
154 | + this.loadedModules[url].error(new Error(`Unable to compile module from url: ${url}`)); | |
155 | + delete this.loadedModules[url]; | |
156 | + }); | |
157 | + } else { | |
158 | + this.loadedModules[url].error(new Error(`Module '${url}' doesn't have default export or not NgModule!`)); | |
159 | + delete this.loadedModules[url]; | |
160 | + } | |
161 | + } catch (e) { | |
162 | + this.loadedModules[url].error(new Error(`Unable to load module from url: ${url}`)); | |
102 | 163 | delete this.loadedModules[url]; |
103 | 164 | } |
104 | 165 | }, | ... | ... |
... | ... | @@ -330,7 +330,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, |
330 | 330 | this.defaultPageSize = pageSize; |
331 | 331 | } |
332 | 332 | this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize * 2, this.defaultPageSize * 3]; |
333 | - this.pageLink.pageSize = this.displayPagination ? this.defaultPageSize : Number.POSITIVE_INFINITY; | |
333 | + this.pageLink.pageSize = this.displayPagination ? this.defaultPageSize : 1024; | |
334 | 334 | |
335 | 335 | this.pageLink.searchPropagatedAlarms = isDefined(this.widgetConfig.searchPropagatedAlarms) |
336 | 336 | ? this.widgetConfig.searchPropagatedAlarms : true; |
... | ... | @@ -535,10 +535,15 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, |
535 | 535 | } else { |
536 | 536 | this.pageLink.page = 0; |
537 | 537 | } |
538 | - this.pageLink.sortOrder = { | |
539 | - key: findEntityKeyByColumnDef(this.sort.active, this.columns), | |
540 | - direction: Direction[this.sort.direction.toUpperCase()] | |
541 | - }; | |
538 | + const key = findEntityKeyByColumnDef(this.sort.active, this.columns); | |
539 | + if (key) { | |
540 | + this.pageLink.sortOrder = { | |
541 | + key, | |
542 | + direction: Direction[this.sort.direction.toUpperCase()] | |
543 | + }; | |
544 | + } else { | |
545 | + this.pageLink.sortOrder = null; | |
546 | + } | |
542 | 547 | const sortOrderLabel = fromEntityColumnDef(this.sort.active, this.columns); |
543 | 548 | const keyFilters: KeyFilter[] = null; // TODO: |
544 | 549 | this.alarmsDatasource.loadAlarms(this.pageLink, sortOrderLabel, keyFilters); | ... | ... |
... | ... | @@ -240,7 +240,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni |
240 | 240 | this.defaultPageSize = pageSize; |
241 | 241 | } |
242 | 242 | this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize * 2, this.defaultPageSize * 3]; |
243 | - this.pageLink.pageSize = this.displayPagination ? this.defaultPageSize : Number.POSITIVE_INFINITY; | |
243 | + this.pageLink.pageSize = this.displayPagination ? this.defaultPageSize : 1024; | |
244 | 244 | |
245 | 245 | const cssString = constructTableCssString(this.widgetConfig); |
246 | 246 | const cssParser = new cssjs(); |
... | ... | @@ -460,10 +460,15 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni |
460 | 460 | } else { |
461 | 461 | this.pageLink.page = 0; |
462 | 462 | } |
463 | - this.pageLink.sortOrder = { | |
464 | - key: findEntityKeyByColumnDef(this.sort.active, this.columns), | |
465 | - direction: Direction[this.sort.direction.toUpperCase()] | |
466 | - }; | |
463 | + const key = findEntityKeyByColumnDef(this.sort.active, this.columns); | |
464 | + if (key) { | |
465 | + this.pageLink.sortOrder = { | |
466 | + key, | |
467 | + direction: Direction[this.sort.direction.toUpperCase()] | |
468 | + }; | |
469 | + } else { | |
470 | + this.pageLink.sortOrder = null; | |
471 | + } | |
467 | 472 | const sortOrderLabel = fromEntityColumnDef(this.sort.active, this.columns); |
468 | 473 | const keyFilters: KeyFilter[] = null; // TODO: |
469 | 474 | this.entityDatasource.loadEntities(this.pageLink, sortOrderLabel, keyFilters); | ... | ... |
... | ... | @@ -159,7 +159,7 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key: |
159 | 159 | } |
160 | 160 | template = createLabelFromDatasource(data.$datasource, template); |
161 | 161 | |
162 | - let match = varsRegex.exec(template); | |
162 | + let match = /\${([^}]*)}/g.exec(template); | |
163 | 163 | while (match !== null) { |
164 | 164 | const variable = match[0]; |
165 | 165 | let label = match[1]; |
... | ... | @@ -186,7 +186,7 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key: |
186 | 186 | textValue = value; |
187 | 187 | } |
188 | 188 | template = template.split(variable).join(textValue); |
189 | - match = varsRegex.exec(template); | |
189 | + match = /\${([^}]*)}/g.exec(template); | |
190 | 190 | } |
191 | 191 | |
192 | 192 | let actionTags: string; | ... | ... |
... | ... | @@ -96,7 +96,10 @@ export function entityDataSortOrderFromString(strSortOrder: string, columns: Ent |
96 | 96 | if (!property && !property.length) { |
97 | 97 | return null; |
98 | 98 | } |
99 | - const column = findColumnByLabel(property, columns); | |
99 | + let column = findColumnByLabel(property, columns); | |
100 | + if (!column) { | |
101 | + column = findColumnByName(property, columns); | |
102 | + } | |
100 | 103 | if (column && column.entityKey) { |
101 | 104 | return {key: column.entityKey, direction}; |
102 | 105 | } |
... | ... | @@ -113,13 +116,22 @@ export function findColumnByEntityKey(key: EntityKey, columns: EntityColumn[]): |
113 | 116 | } |
114 | 117 | |
115 | 118 | export function findEntityKeyByColumnDef(def: string, columns: EntityColumn[]): EntityKey { |
116 | - return findColumnByDef(def, columns).entityKey; | |
119 | + if (def) { | |
120 | + const column = findColumnByDef(def, columns); | |
121 | + return column ? column.entityKey : null; | |
122 | + } else { | |
123 | + return null; | |
124 | + } | |
117 | 125 | } |
118 | 126 | |
119 | 127 | export function findColumn(searchProperty: string, searchValue: string, columns: EntityColumn[]): EntityColumn { |
120 | 128 | return columns.find(theColumn => theColumn[searchProperty] === searchValue); |
121 | 129 | } |
122 | 130 | |
131 | +export function findColumnByName(name: string, columns: EntityColumn[]): EntityColumn { | |
132 | + return findColumn('name', name, columns); | |
133 | +} | |
134 | + | |
123 | 135 | export function findColumnByLabel(label: string, columns: EntityColumn[]): EntityColumn { |
124 | 136 | let column: EntityColumn; |
125 | 137 | const alarmColumns = columns.filter(c => c.type === DataKeyType.alarm); | ... | ... |
... | ... | @@ -41,6 +41,43 @@ import { NULL_UUID } from '@shared/models/id/has-uuid'; |
41 | 41 | import { WidgetTypeId } from '@app/shared/models/id/widget-type-id'; |
42 | 42 | import { TenantId } from '@app/shared/models/id/tenant-id'; |
43 | 43 | import { SharedModule } from '@shared/shared.module'; |
44 | +import * as AngularCore from '@angular/core'; | |
45 | +import * as AngularCommon from '@angular/common'; | |
46 | +import * as AngularForms from '@angular/forms'; | |
47 | +import * as AngularRouter from '@angular/router'; | |
48 | +import * as AngularCdkKeycodes from '@angular/cdk/keycodes'; | |
49 | +import * as AngularCdkCoercion from '@angular/cdk/coercion'; | |
50 | +import * as AngularMaterialChips from '@angular/material/chips'; | |
51 | +import * as AngularMaterialAutocomplete from '@angular/material/autocomplete'; | |
52 | +import * as AngularMaterialDialog from '@angular/material/dialog'; | |
53 | +import * as NgrxStore from '@ngrx/store'; | |
54 | +import * as RxJs from 'rxjs'; | |
55 | +import * as RxJsOperators from 'rxjs/operators'; | |
56 | +import * as TranslateCore from '@ngx-translate/core'; | |
57 | +import * as TbCore from '@core/public-api'; | |
58 | +import * as TbShared from '@shared/public-api'; | |
59 | +import * as _moment from 'moment'; | |
60 | + | |
61 | +declare const SystemJS; | |
62 | + | |
63 | +const widgetResourcesModulesMap = { | |
64 | + '@angular/core': SystemJS.newModule(AngularCore), | |
65 | + '@angular/common': SystemJS.newModule(AngularCommon), | |
66 | + '@angular/forms': SystemJS.newModule(AngularForms), | |
67 | + '@angular/router': SystemJS.newModule(AngularRouter), | |
68 | + '@angular/cdk/keycodes': SystemJS.newModule(AngularCdkKeycodes), | |
69 | + '@angular/cdk/coercion': SystemJS.newModule(AngularCdkCoercion), | |
70 | + '@angular/material/chips': SystemJS.newModule(AngularMaterialChips), | |
71 | + '@angular/material/autocomplete': SystemJS.newModule(AngularMaterialAutocomplete), | |
72 | + '@angular/material/dialog': SystemJS.newModule(AngularMaterialDialog), | |
73 | + '@ngrx/store': SystemJS.newModule(NgrxStore), | |
74 | + rxjs: SystemJS.newModule(RxJs), | |
75 | + 'rxjs/operators': SystemJS.newModule(RxJsOperators), | |
76 | + '@ngx-translate/core': SystemJS.newModule(TranslateCore), | |
77 | + '@core/public-api': SystemJS.newModule(TbCore), | |
78 | + '@shared/public-api': SystemJS.newModule(TbShared), | |
79 | + moment: SystemJS.newModule(_moment) | |
80 | +}; | |
44 | 81 | |
45 | 82 | // @dynamic |
46 | 83 | @Injectable() |
... | ... | @@ -105,8 +142,8 @@ export class WidgetComponentService { |
105 | 142 | const initSubject = new ReplaySubject(); |
106 | 143 | this.init$ = initSubject.asObservable(); |
107 | 144 | const loadDefaultWidgetInfoTasks = [ |
108 | - this.loadWidgetResources(this.missingWidgetType, 'global-widget-missing-type', [SharedModule]), | |
109 | - this.loadWidgetResources(this.errorWidgetType, 'global-widget-error-type', [SharedModule]), | |
145 | + this.loadWidgetResources(this.missingWidgetType, 'global-widget-missing-type', [SharedModule, WidgetComponentsModule]), | |
146 | + this.loadWidgetResources(this.errorWidgetType, 'global-widget-error-type', [SharedModule, WidgetComponentsModule]), | |
110 | 147 | ]; |
111 | 148 | forkJoin(loadDefaultWidgetInfoTasks).subscribe( |
112 | 149 | () => { |
... | ... | @@ -218,31 +255,71 @@ export class WidgetComponentService { |
218 | 255 | this.cssParser.cssPreviewNamespace = widgetNamespace; |
219 | 256 | this.cssParser.createStyleElement(widgetNamespace, widgetInfo.templateCss); |
220 | 257 | const resourceTasks: Observable<string>[] = []; |
258 | + const modulesTasks: Observable<Type<any>[] | string>[] = []; | |
221 | 259 | if (widgetInfo.resources.length > 0) { |
222 | - widgetInfo.resources.forEach((resource) => { | |
260 | + widgetInfo.resources.filter(r => r.isModule).forEach( | |
261 | + (resource) => { | |
262 | + modulesTasks.push( | |
263 | + this.resources.loadModules(resource.url, widgetResourcesModulesMap).pipe( | |
264 | + catchError((e: Error) => of(e?.message ? e.message : `Failed to load widget resource module: '${resource.url}'`)) | |
265 | + ) | |
266 | + ); | |
267 | + } | |
268 | + ); | |
269 | + } | |
270 | + widgetInfo.resources.filter(r => !r.isModule).forEach( | |
271 | + (resource) => { | |
223 | 272 | resourceTasks.push( |
224 | 273 | this.resources.loadResource(resource.url).pipe( |
225 | 274 | catchError(e => of(`Failed to load widget resource: '${resource.url}'`)) |
226 | 275 | ) |
227 | 276 | ); |
228 | - }); | |
277 | + } | |
278 | + ); | |
279 | + | |
280 | + let modulesObservable: Observable<string | Type<any>[]>; | |
281 | + if (modulesTasks.length) { | |
282 | + modulesObservable = forkJoin(modulesTasks).pipe( | |
283 | + map(res => { | |
284 | + const msg = res.find(r => typeof r === 'string'); | |
285 | + if (msg) { | |
286 | + return msg as string; | |
287 | + } else { | |
288 | + let resModules = (res as Type<any>[][]).flat(); | |
289 | + if (modules && modules.length) { | |
290 | + resModules = resModules.concat(modules); | |
291 | + } | |
292 | + return resModules; | |
293 | + } | |
294 | + }) | |
295 | + ); | |
296 | + } else { | |
297 | + modulesObservable = modules && modules.length ? of(modules) : of([]); | |
229 | 298 | } |
299 | + | |
230 | 300 | resourceTasks.push( |
231 | - this.dynamicComponentFactoryService.createDynamicComponentFactory( | |
232 | - class DynamicWidgetComponentInstance extends DynamicWidgetComponent {}, | |
233 | - widgetInfo.templateHtml, | |
234 | - modules | |
235 | - ).pipe( | |
236 | - map((factory) => { | |
237 | - widgetInfo.componentFactory = factory; | |
238 | - return null; | |
239 | - }), | |
240 | - catchError(e => { | |
241 | - const details = this.utils.parseException(e); | |
242 | - const errorMessage = `Failed to compile widget html. \n Error: ${details.message}`; | |
243 | - return of(errorMessage); | |
244 | - }) | |
245 | - ) | |
301 | + modulesObservable.pipe( | |
302 | + mergeMap((resolvedModules) => { | |
303 | + if (typeof resolvedModules === 'string') { | |
304 | + return of(resolvedModules); | |
305 | + } else { | |
306 | + return this.dynamicComponentFactoryService.createDynamicComponentFactory( | |
307 | + class DynamicWidgetComponentInstance extends DynamicWidgetComponent {}, | |
308 | + widgetInfo.templateHtml, | |
309 | + resolvedModules | |
310 | + ).pipe( | |
311 | + map((factory) => { | |
312 | + widgetInfo.componentFactory = factory; | |
313 | + return null; | |
314 | + }), | |
315 | + catchError(e => { | |
316 | + const details = this.utils.parseException(e); | |
317 | + const errorMessage = `Failed to compile widget html. \n Error: ${details.message}`; | |
318 | + return of(errorMessage); | |
319 | + }) | |
320 | + ) | |
321 | + } | |
322 | + })) | |
246 | 323 | ); |
247 | 324 | return forkJoin(resourceTasks).pipe( |
248 | 325 | switchMap(msgs => { | ... | ... |
... | ... | @@ -794,16 +794,10 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI |
794 | 794 | options.useDashboardTimewindow = true; |
795 | 795 | } |
796 | 796 | } |
797 | - let datasource: Datasource; | |
798 | 797 | if (options.type === widgetType.alarm) { |
799 | - datasource = this.entityService.createAlarmSourceFromSubscriptionInfo(subscriptionsInfo[0]); | |
798 | + options.alarmSource = this.entityService.createAlarmSourceFromSubscriptionInfo(subscriptionsInfo[0]); | |
800 | 799 | } else { |
801 | - datasource = this.entityService.createDatasourcesFromSubscriptionsInfo(subscriptionsInfo); | |
802 | - } | |
803 | - if (options.type === widgetType.alarm) { | |
804 | - options.alarmSource = datasource; | |
805 | - } else { | |
806 | - options.datasources = [datasource]; | |
800 | + options.datasources = this.entityService.createDatasourcesFromSubscriptionsInfo(subscriptionsInfo); | |
807 | 801 | } |
808 | 802 | this.createSubscription(options, subscribe).subscribe( |
809 | 803 | (subscription) => { | ... | ... |
... | ... | @@ -129,6 +129,10 @@ |
129 | 129 | (ngModelChange)="isDirty = true" |
130 | 130 | placeholder="{{ 'widget.resource-url' | translate }}"/> |
131 | 131 | </mat-form-field> |
132 | + <mat-checkbox [(ngModel)]="resource.isModule" | |
133 | + (ngModelChange)="isDirty = true"> | |
134 | + {{ 'widget.resource-is-module' | translate }} | |
135 | + </mat-checkbox> | |
132 | 136 | <button mat-icon-button color="primary" |
133 | 137 | [disabled]="isLoading$ | async" |
134 | 138 | (click)="removeResource(i)" | ... | ... |
... | ... | @@ -1787,6 +1787,7 @@ |
1787 | 1787 | "type": "Widget type", |
1788 | 1788 | "resources": "Resources", |
1789 | 1789 | "resource-url": "JavaScript/CSS URL", |
1790 | + "resource-is-module": "Is module", | |
1790 | 1791 | "remove-resource": "Remove resource", |
1791 | 1792 | "add-resource": "Add resource", |
1792 | 1793 | "html": "HTML", | ... | ... |