Commit ab0a1d1ea7b491b144bf37b4324686c6200be015

Authored by Igor Kulikov
1 parent 9ec843cb

Improve dynamic widgets loading.

@@ -24,7 +24,11 @@ @@ -24,7 +24,11 @@
24 "tsConfig": "src/tsconfig.app.json", 24 "tsConfig": "src/tsconfig.app.json",
25 "assets": [ 25 "assets": [
26 "src/thingsboard.ico", 26 "src/thingsboard.ico",
27 - "src/assets" 27 + "src/assets",
  28 + { "glob": "worker-html.js", "input": "./node_modules/ace-builds/src-min/", "output": "/" },
  29 + { "glob": "worker-css.js", "input": "./node_modules/ace-builds/src-min/", "output": "/" },
  30 + { "glob": "worker-json.js", "input": "./node_modules/ace-builds/src-min/", "output": "/" },
  31 + { "glob": "worker-javascript.js", "input": "./node_modules/ace-builds/src-min/", "output": "/" }
28 ], 32 ],
29 "styles": [ 33 "styles": [
30 "src/styles.scss" 34 "src/styles.scss"
@@ -49,11 +53,7 @@ @@ -49,11 +53,7 @@
49 "node_modules/ace-builds/src-min/snippets/css.js", 53 "node_modules/ace-builds/src-min/snippets/css.js",
50 "node_modules/ace-builds/src-min/snippets/json.js", 54 "node_modules/ace-builds/src-min/snippets/json.js",
51 "node_modules/ace-builds/src-min/snippets/java.js", 55 "node_modules/ace-builds/src-min/snippets/java.js",
52 - "node_modules/ace-builds/src-min/snippets/javascript.js",  
53 - { "bundleName": "worker-html", "input": "node_modules/ace-builds/src-min/worker-html.js" },  
54 - { "bundleName": "worker-css", "input": "node_modules/ace-builds/src-min/worker-css.js" },  
55 - { "bundleName": "worker-json", "input": "node_modules/ace-builds/src-min/worker-json.js" },  
56 - { "bundleName": "worker-javascript", "input": "node_modules/ace-builds/src-min/worker-javascript.js" } 56 + "node_modules/ace-builds/src-min/snippets/javascript.js"
57 ], 57 ],
58 "es5BrowserSupport": true, 58 "es5BrowserSupport": true,
59 "customWebpackConfig": { 59 "customWebpackConfig": {
@@ -34,7 +34,7 @@ module.exports = { @@ -34,7 +34,7 @@ module.exports = {
34 new CompressionPlugin({ 34 new CompressionPlugin({
35 filename: "[path].gz[query]", 35 filename: "[path].gz[query]",
36 algorithm: "gzip", 36 algorithm: "gzip",
37 - test: /\.js$|\.css$|\.svg$|\.ttf$|\.woff$|\.woff2$|\.eot$|\.json$/, 37 + test: /\.js$|\.css$|\.html$|\.svg?.+$|\.jpg$|\.ttf?.+$|\.woff?.+$|\.eot?.+$|\.json$/,
38 threshold: 10240, 38 threshold: 10240,
39 minRatio: 0.8, 39 minRatio: 0.8,
40 deleteOriginalAssets: false 40 deleteOriginalAssets: false
@@ -14,47 +14,29 @@ @@ -14,47 +14,29 @@
14 /// limitations under the License. 14 /// limitations under the License.
15 /// 15 ///
16 16
17 -import {Injectable} from '@angular/core';  
18 -import {defaultHttpOptions} from './http-utils';  
19 -import { Observable, ReplaySubject, Subject, of, forkJoin, throwError } from 'rxjs/index';  
20 -import {HttpClient} from '@angular/common/http';  
21 -import {PageLink} from '@shared/models/page/page-link';  
22 -import {PageData} from '@shared/models/page/page-data';  
23 -import {WidgetsBundle} from '@shared/models/widgets-bundle.model';  
24 -import {  
25 - WidgetControllerDescriptor,  
26 - WidgetInfo,  
27 - WidgetType,  
28 - WidgetTypeInstance,  
29 - widgetActionSources,  
30 - MissingWidgetType, toWidgetInfo, ErrorWidgetType  
31 -} from '@shared/models/widget.models'; 17 +import { Injectable } from '@angular/core';
  18 +import { defaultHttpOptions } from './http-utils';
  19 +import { Observable } from 'rxjs/index';
  20 +import { HttpClient } from '@angular/common/http';
  21 +import { PageLink } from '@shared/models/page/page-link';
  22 +import { PageData } from '@shared/models/page/page-data';
  23 +import { WidgetsBundle } from '@shared/models/widgets-bundle.model';
  24 +import { WidgetType } from '@shared/models/widget.models';
32 import { UtilsService } from '@core/services/utils.service'; 25 import { UtilsService } from '@core/services/utils.service';
33 -import { isFunction, isUndefined } from '@core/utils';  
34 import { TranslateService } from '@ngx-translate/core'; 26 import { TranslateService } from '@ngx-translate/core';
35 -import { AuthPayload } from '@core/auth/auth.models';  
36 -import cssjs from '@core/css/css';  
37 import { ResourcesService } from '../services/resources.service'; 27 import { ResourcesService } from '../services/resources.service';
38 -import { catchError, map, switchMap } from 'rxjs/operators';  
39 28
40 @Injectable({ 29 @Injectable({
41 providedIn: 'root' 30 providedIn: 'root'
42 }) 31 })
43 export class WidgetService { 32 export class WidgetService {
44 33
45 - private cssParser = new cssjs();  
46 -  
47 - private widgetsInfoInMemoryCache = new Map<string, WidgetInfo>();  
48 -  
49 - private widgetsInfoFetchQueue = new Map<string, Array<Subject<WidgetInfo>>>();  
50 -  
51 constructor( 34 constructor(
52 private http: HttpClient, 35 private http: HttpClient,
53 private utils: UtilsService, 36 private utils: UtilsService,
54 private resources: ResourcesService, 37 private resources: ResourcesService,
55 private translate: TranslateService 38 private translate: TranslateService
56 ) { 39 ) {
57 - this.cssParser.testMode = false;  
58 } 40 }
59 41
60 public getWidgetBundles(pageLink: PageLink, ignoreErrors: boolean = false, 42 public getWidgetBundles(pageLink: PageLink, ignoreErrors: boolean = false,
@@ -88,261 +70,4 @@ export class WidgetService { @@ -88,261 +70,4 @@ export class WidgetService {
88 return this.http.get<WidgetType>(`/api/widgetType?isSystem=${isSystem}&bundleAlias=${bundleAlias}&alias=${widgetTypeAlias}`, 70 return this.http.get<WidgetType>(`/api/widgetType?isSystem=${isSystem}&bundleAlias=${bundleAlias}&alias=${widgetTypeAlias}`,
89 defaultHttpOptions(ignoreLoading, ignoreErrors)); 71 defaultHttpOptions(ignoreLoading, ignoreErrors));
90 } 72 }
91 -  
92 - public getWidgetInfo(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): Observable<WidgetInfo> {  
93 - const widgetInfoSubject = new ReplaySubject<WidgetInfo>();  
94 - const widgetInfo = this.getWidgetInfoFromCache(bundleAlias, widgetTypeAlias, isSystem);  
95 - if (widgetInfo) {  
96 - widgetInfoSubject.next(widgetInfo);  
97 - widgetInfoSubject.complete();  
98 - } else {  
99 - if (this.utils.widgetEditMode) {  
100 - // TODO:  
101 - } else {  
102 - const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem);  
103 - let fetchQueue = this.widgetsInfoFetchQueue.get(key);  
104 - if (fetchQueue) {  
105 - fetchQueue.push(widgetInfoSubject);  
106 - } else {  
107 - fetchQueue = new Array<Subject<WidgetInfo>>();  
108 - this.widgetsInfoFetchQueue.set(key, fetchQueue);  
109 - this.getWidgetType(bundleAlias, widgetTypeAlias, isSystem).subscribe(  
110 - (widgetType) => {  
111 - this.loadWidget(widgetType, bundleAlias, isSystem, widgetInfoSubject);  
112 - },  
113 - () => {  
114 - widgetInfoSubject.next(MissingWidgetType);  
115 - widgetInfoSubject.complete();  
116 - this.resolveWidgetsInfoFetchQueue(key, MissingWidgetType);  
117 - }  
118 - );  
119 - }  
120 - }  
121 - }  
122 - return widgetInfoSubject.asObservable();  
123 - }  
124 -  
125 - private loadWidget(widgetType: WidgetType, bundleAlias: string, isSystem: boolean, widgetInfoSubject: Subject<WidgetInfo>) {  
126 - const widgetInfo = toWidgetInfo(widgetType);  
127 - const key = this.createWidgetInfoCacheKey(bundleAlias, widgetInfo.alias, isSystem);  
128 - this.loadWidgetResources(widgetInfo, bundleAlias, isSystem).subscribe(  
129 - () => {  
130 - let widgetControllerDescriptor: WidgetControllerDescriptor = null;  
131 - try {  
132 - widgetControllerDescriptor = this.createWidgetControllerDescriptor(widgetInfo, key);  
133 - } catch (e) {  
134 - const details = this.utils.parseException(e);  
135 - const errorMessage = `Failed to compile widget script. \n Error: ${details.message}`;  
136 - this.processWidgetLoadError([errorMessage], key, widgetInfoSubject);  
137 - }  
138 - if (widgetControllerDescriptor) {  
139 - if (widgetControllerDescriptor.settingsSchema) {  
140 - widgetInfo.typeSettingsSchema = widgetControllerDescriptor.settingsSchema;  
141 - }  
142 - if (widgetControllerDescriptor.dataKeySettingsSchema) {  
143 - widgetInfo.typeDataKeySettingsSchema = widgetControllerDescriptor.dataKeySettingsSchema;  
144 - }  
145 - widgetInfo.typeParameters = widgetControllerDescriptor.typeParameters;  
146 - widgetInfo.actionSources = widgetControllerDescriptor.actionSources;  
147 - widgetInfo.widgetTypeFunction = widgetControllerDescriptor.widgetTypeFunction;  
148 - this.putWidgetInfoToCache(widgetInfo, bundleAlias, widgetInfo.alias, isSystem);  
149 - if (widgetInfoSubject) {  
150 - widgetInfoSubject.next(widgetInfo);  
151 - widgetInfoSubject.complete();  
152 - }  
153 - this.resolveWidgetsInfoFetchQueue(key, widgetInfo);  
154 - }  
155 - },  
156 - (errorMessages: string[]) => {  
157 - this.processWidgetLoadError(errorMessages, key, widgetInfoSubject);  
158 - }  
159 - );  
160 - }  
161 -  
162 - private loadWidgetResources(widgetInfo: WidgetInfo, bundleAlias: string, isSystem: boolean): Observable<any> {  
163 - const widgetNamespace = `widget-type-${(isSystem ? 'sys-' : '')}${bundleAlias}-${widgetInfo.alias}`;  
164 - this.cssParser.cssPreviewNamespace = widgetNamespace;  
165 - this.cssParser.createStyleElement(widgetNamespace, widgetInfo.templateCss);  
166 - const resourceTasks: Observable<string>[] = [];  
167 - if (widgetInfo.resources.length > 0) {  
168 - widgetInfo.resources.forEach((resource) => {  
169 - resourceTasks.push(  
170 - this.resources.loadResource(resource.url).pipe(  
171 - catchError(e => of(`Failed to load widget resource: '${resource.url}'`))  
172 - )  
173 - );  
174 - });  
175 - return forkJoin(resourceTasks).pipe(  
176 - switchMap(msgs => {  
177 - let errors: string[];  
178 - if (msgs && msgs.length) {  
179 - errors = msgs.filter(msg => msg && msg.length > 0);  
180 - }  
181 - if (errors && errors.length) {  
182 - return throwError(errors);  
183 - } else {  
184 - return of(null);  
185 - }  
186 - }  
187 - ));  
188 - } else {  
189 - return of(null);  
190 - }  
191 - }  
192 -  
193 - private createWidgetControllerDescriptor(widgetInfo: WidgetInfo, name: string): WidgetControllerDescriptor {  
194 - let widgetTypeFunctionBody = `return function ${name} (ctx) {\n` +  
195 - ' var self = this;\n' +  
196 - ' self.ctx = ctx;\n\n'; /*+  
197 -  
198 - ' self.onInit = function() {\n\n' +  
199 -  
200 - ' }\n\n' +  
201 -  
202 - ' self.onDataUpdated = function() {\n\n' +  
203 -  
204 - ' }\n\n' +  
205 -  
206 - ' self.useCustomDatasources = function() {\n\n' +  
207 -  
208 - ' }\n\n' +  
209 -  
210 - ' self.typeParameters = function() {\n\n' +  
211 - return {  
212 - useCustomDatasources: false,  
213 - maxDatasources: -1, //unlimited  
214 - maxDataKeys: -1, //unlimited  
215 - dataKeysOptional: false,  
216 - stateData: false  
217 - };  
218 - ' }\n\n' +  
219 -  
220 - ' self.actionSources = function() {\n\n' +  
221 - return {  
222 - 'headerButton': {  
223 - name: 'Header button',  
224 - multiple: true  
225 - }  
226 - };  
227 - }\n\n' +  
228 - ' self.onResize = function() {\n\n' +  
229 -  
230 - ' }\n\n' +  
231 -  
232 - ' self.onEditModeChanged = function() {\n\n' +  
233 -  
234 - ' }\n\n' +  
235 -  
236 - ' self.onMobileModeChanged = function() {\n\n' +  
237 -  
238 - ' }\n\n' +  
239 -  
240 - ' self.getSettingsSchema = function() {\n\n' +  
241 -  
242 - ' }\n\n' +  
243 -  
244 - ' self.getDataKeySettingsSchema = function() {\n\n' +  
245 -  
246 - ' }\n\n' +  
247 -  
248 - ' self.onDestroy = function() {\n\n' +  
249 -  
250 - ' }\n\n' +  
251 - '}';*/  
252 -  
253 - widgetTypeFunctionBody += widgetInfo.controllerScript;  
254 - widgetTypeFunctionBody += '\n};\n';  
255 -  
256 - try {  
257 -  
258 - const widgetTypeFunction = new Function(widgetTypeFunctionBody);  
259 - const widgetType = widgetTypeFunction.apply(this);  
260 - const widgetTypeInstance: WidgetTypeInstance = new widgetType();  
261 - const result: WidgetControllerDescriptor = {  
262 - widgetTypeFunction: widgetType  
263 - };  
264 - if (isFunction(widgetTypeInstance.getSettingsSchema)) {  
265 - result.settingsSchema = widgetTypeInstance.getSettingsSchema();  
266 - }  
267 - if (isFunction(widgetTypeInstance.getDataKeySettingsSchema)) {  
268 - result.dataKeySettingsSchema = widgetTypeInstance.getDataKeySettingsSchema();  
269 - }  
270 - if (isFunction(widgetTypeInstance.typeParameters)) {  
271 - result.typeParameters = widgetTypeInstance.typeParameters();  
272 - } else {  
273 - result.typeParameters = {};  
274 - }  
275 - if (isFunction(widgetTypeInstance.useCustomDatasources)) {  
276 - result.typeParameters.useCustomDatasources = widgetTypeInstance.useCustomDatasources();  
277 - } else {  
278 - result.typeParameters.useCustomDatasources = false;  
279 - }  
280 - if (isUndefined(result.typeParameters.maxDatasources)) {  
281 - result.typeParameters.maxDatasources = -1;  
282 - }  
283 - if (isUndefined(result.typeParameters.maxDataKeys)) {  
284 - result.typeParameters.maxDataKeys = -1;  
285 - }  
286 - if (isUndefined(result.typeParameters.dataKeysOptional)) {  
287 - result.typeParameters.dataKeysOptional = false;  
288 - }  
289 - if (isUndefined(result.typeParameters.stateData)) {  
290 - result.typeParameters.stateData = false;  
291 - }  
292 - if (isFunction(widgetTypeInstance.actionSources)) {  
293 - result.actionSources = widgetTypeInstance.actionSources();  
294 - } else {  
295 - result.actionSources = {};  
296 - }  
297 - for (const actionSourceId of Object.keys(widgetActionSources)) {  
298 - result.actionSources[actionSourceId] = {...widgetActionSources[actionSourceId]};  
299 - result.actionSources[actionSourceId].name = this.translate.instant(result.actionSources[actionSourceId].name);  
300 - }  
301 - return result;  
302 - } catch (e) {  
303 - this.utils.processWidgetException(e);  
304 - throw e;  
305 - }  
306 - }  
307 -  
308 - private processWidgetLoadError(errorMessages: string[], cacheKey: string, widgetInfoSubject: Subject<WidgetInfo>) {  
309 - const widgetInfo = {...ErrorWidgetType};  
310 - errorMessages.forEach(error => {  
311 - widgetInfo.templateHtml += `<div class="tb-widget-error-msg">${error}</div>`;  
312 - });  
313 - widgetInfo.templateHtml += '</div>';  
314 - if (widgetInfoSubject) {  
315 - widgetInfoSubject.next(widgetInfo);  
316 - widgetInfoSubject.complete();  
317 - }  
318 - this.resolveWidgetsInfoFetchQueue(cacheKey, widgetInfo);  
319 - }  
320 -  
321 - private resolveWidgetsInfoFetchQueue(key: string, widgetInfo: WidgetInfo) {  
322 - const fetchQueue = this.widgetsInfoFetchQueue.get(key);  
323 - if (fetchQueue) {  
324 - fetchQueue.forEach(subject => {  
325 - subject.next(widgetInfo);  
326 - subject.complete();  
327 - });  
328 - this.widgetsInfoFetchQueue.delete(key);  
329 - }  
330 - }  
331 -  
332 - // Cache functions  
333 -  
334 - private createWidgetInfoCacheKey(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): string {  
335 - return `${isSystem ? 'sys_' : ''}${bundleAlias}_${widgetTypeAlias}`;  
336 - }  
337 -  
338 - private getWidgetInfoFromCache(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): WidgetInfo | undefined {  
339 - const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem);  
340 - return this.widgetsInfoInMemoryCache.get(key);  
341 - }  
342 -  
343 - private putWidgetInfoToCache(widgetInfo: WidgetInfo, bundleAlias: string, widgetTypeAlias: string, isSystem: boolean) {  
344 - const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem);  
345 - this.widgetsInfoInMemoryCache.set(key, widgetInfo);  
346 - }  
347 -  
348 } 73 }
ui-ngx/src/app/core/services/dynamic-component-factory.service.ts renamed from ui-ngx/src/app/modules/home/components/widget/dynamic-widget-component-factory.service.ts
@@ -22,63 +22,79 @@ import { @@ -22,63 +22,79 @@ import {
22 Injector, 22 Injector,
23 NgModule, 23 NgModule,
24 NgModuleRef, 24 NgModuleRef,
25 - Type,  
26 - ViewEncapsulation 25 + OnDestroy,
  26 + Type
27 } from '@angular/core'; 27 } from '@angular/core';
28 -import {  
29 - DynamicWidgetComponent,  
30 - DynamicWidgetComponentModule  
31 -} from '@home/components/widget/dynamic-widget.component';  
32 -import { CommonModule } from '@angular/common';  
33 -import { SharedModule } from '@shared/shared.module';  
34 import { Observable, ReplaySubject } from 'rxjs'; 28 import { Observable, ReplaySubject } from 'rxjs';
35 -import { HomeComponentsModule } from '../home-components.module';  
36 -import { WidgetComponentsModule } from './widget-components.module'; 29 +import { CommonModule } from '@angular/common';
  30 +
  31 +export abstract class DynamicComponentModule implements OnDestroy {
  32 +
  33 + ngOnDestroy(): void {
  34 + console.log('Module destroyed!');
  35 + }
37 36
38 -interface DynamicWidgetComponentModuleData {  
39 - moduleRef: NgModuleRef<DynamicWidgetComponentModule>;  
40 - moduleType: Type<DynamicWidgetComponentModule>;  
41 } 37 }
42 38
43 -@Injectable()  
44 -export class DynamicWidgetComponentFactoryService { 39 +interface DynamicComponentModuleData {
  40 + moduleRef: NgModuleRef<DynamicComponentModule>;
  41 + moduleType: Type<DynamicComponentModule>;
  42 +}
45 43
46 - private dynamicComponentModulesMap = new Map<ComponentFactory<DynamicWidgetComponent>, DynamicWidgetComponentModuleData>(); 44 +@Injectable(
  45 + {
  46 + providedIn: 'root'
  47 + }
  48 +)
  49 +export class DynamicComponentFactoryService {
  50 +
  51 + private dynamicComponentModulesMap = new Map<ComponentFactory<any>, DynamicComponentModuleData>();
47 52
48 constructor(private compiler: Compiler, 53 constructor(private compiler: Compiler,
49 private injector: Injector) { 54 private injector: Injector) {
50 } 55 }
51 56
52 - public createDynamicWidgetComponentFactory(template: string): Observable<ComponentFactory<DynamicWidgetComponent>> {  
53 - const dymamicWidgetComponentFactorySubject = new ReplaySubject<ComponentFactory<DynamicWidgetComponent>>();  
54 - const comp = this.createDynamicWidgetComponent(template); 57 + public createDynamicComponentFactory<T>(
  58 + componentType: Type<T>,
  59 + template: string,
  60 + modules?: Type<any>[]): Observable<ComponentFactory<T>> {
  61 + const dymamicComponentFactorySubject = new ReplaySubject<ComponentFactory<T>>();
  62 + const comp = this.createDynamicComponent(componentType, template);
  63 + let moduleImports = [CommonModule];
  64 + if (modules) {
  65 + moduleImports = [...moduleImports, ...modules];
  66 + }
55 // noinspection AngularInvalidImportedOrDeclaredSymbol,AngularInvalidEntryComponent 67 // noinspection AngularInvalidImportedOrDeclaredSymbol,AngularInvalidEntryComponent
56 @NgModule({ 68 @NgModule({
57 declarations: [comp], 69 declarations: [comp],
58 entryComponents: [comp], 70 entryComponents: [comp],
59 - imports: [CommonModule, SharedModule, WidgetComponentsModule], 71 + imports: moduleImports
60 }) 72 })
61 - class DynamicWidgetComponentInstanceModule extends DynamicWidgetComponentModule {}  
62 - this.compiler.compileModuleAsync(DynamicWidgetComponentInstanceModule).then(  
63 - (module) => {  
64 - const moduleRef = module.create(this.injector);  
65 - const factory = moduleRef.componentFactoryResolver.resolveComponentFactory(comp);  
66 - this.dynamicComponentModulesMap.set(factory, {  
67 - moduleRef,  
68 - moduleType: module.moduleType  
69 - });  
70 - dymamicWidgetComponentFactorySubject.next(factory);  
71 - dymamicWidgetComponentFactorySubject.complete();  
72 - }  
73 - ).catch(  
74 - (e) => {  
75 - dymamicWidgetComponentFactorySubject.error(`Failed to create dynamic widget component factory: ${e}`);  
76 - }  
77 - );  
78 - return dymamicWidgetComponentFactorySubject.asObservable(); 73 + class DynamicComponentInstanceModule extends DynamicComponentModule {}
  74 + try {
  75 + this.compiler.compileModuleAsync(DynamicComponentInstanceModule).then(
  76 + (module) => {
  77 + const moduleRef = module.create(this.injector);
  78 + const factory = moduleRef.componentFactoryResolver.resolveComponentFactory(comp);
  79 + this.dynamicComponentModulesMap.set(factory, {
  80 + moduleRef,
  81 + moduleType: module.moduleType
  82 + });
  83 + dymamicComponentFactorySubject.next(factory);
  84 + dymamicComponentFactorySubject.complete();
  85 + }
  86 + ).catch(
  87 + (e) => {
  88 + dymamicComponentFactorySubject.error(e);
  89 + }
  90 + );
  91 + } catch (e) {
  92 + dymamicComponentFactorySubject.error(e);
  93 + }
  94 + return dymamicComponentFactorySubject.asObservable();
79 } 95 }
80 96
81 - public destroyDynamicWidgetComponentFactory(factory: ComponentFactory<DynamicWidgetComponent>) { 97 + public destroyDynamicComponentFactory<T>(factory: ComponentFactory<T>) {
82 const moduleData = this.dynamicComponentModulesMap.get(factory); 98 const moduleData = this.dynamicComponentModulesMap.get(factory);
83 if (moduleData) { 99 if (moduleData) {
84 moduleData.moduleRef.destroy(); 100 moduleData.moduleRef.destroy();
@@ -87,14 +103,11 @@ export class DynamicWidgetComponentFactoryService { @@ -87,14 +103,11 @@ export class DynamicWidgetComponentFactoryService {
87 } 103 }
88 } 104 }
89 105
90 - private createDynamicWidgetComponent(template: string): Type<DynamicWidgetComponent> { 106 + private createDynamicComponent<T>(componentType: Type<T>, template: string): Type<T> {
91 // noinspection AngularMissingOrInvalidDeclarationInModule 107 // noinspection AngularMissingOrInvalidDeclarationInModule
92 - @Component({ 108 + return Component({
93 template 109 template
94 - })  
95 - class DynamicWidgetInstanceComponent extends DynamicWidgetComponent { }  
96 -  
97 - return DynamicWidgetInstanceComponent; 110 + })(componentType);
98 } 111 }
99 112
100 } 113 }
@@ -16,7 +16,6 @@ @@ -16,7 +16,6 @@
16 16
17 import { Inject, Injectable } from '@angular/core'; 17 import { Inject, Injectable } from '@angular/core';
18 import { WINDOW } from '@core/services/window.service'; 18 import { WINDOW } from '@core/services/window.service';
19 -import { WidgetInfo } from '@shared/models/widget.models';  
20 import { ExceptionData } from '@app/shared/models/error.models'; 19 import { ExceptionData } from '@app/shared/models/error.models';
21 import { isUndefined } from '@core/utils'; 20 import { isUndefined } from '@core/utils';
22 import { WindowMessage } from '@shared/models/window-message.model'; 21 import { WindowMessage } from '@shared/models/window-message.model';
@@ -30,7 +29,7 @@ export class UtilsService { @@ -30,7 +29,7 @@ export class UtilsService {
30 29
31 iframeMode = false; 30 iframeMode = false;
32 widgetEditMode = false; 31 widgetEditMode = false;
33 - editWidgetInfo: WidgetInfo = null; 32 + editWidgetInfo: any = null;
34 33
35 constructor(@Inject(WINDOW) private window: Window, 34 constructor(@Inject(WINDOW) private window: Window,
36 private translate: TranslateService) { 35 private translate: TranslateService) {
@@ -36,7 +36,8 @@ import { AddAttributeDialogComponent } from './attribute/add-attribute-dialog.co @@ -36,7 +36,8 @@ import { AddAttributeDialogComponent } from './attribute/add-attribute-dialog.co
36 import { EditAttributeValuePanelComponent } from './attribute/edit-attribute-value-panel.component'; 36 import { EditAttributeValuePanelComponent } from './attribute/edit-attribute-value-panel.component';
37 import { DashboardComponent } from '@home/components/dashboard/dashboard.component'; 37 import { DashboardComponent } from '@home/components/dashboard/dashboard.component';
38 import { WidgetComponent } from '@home/components/widget/widget.component'; 38 import { WidgetComponent } from '@home/components/widget/widget.component';
39 -import { DynamicWidgetComponentFactoryService } from './widget/dynamic-widget-component-factory.service'; 39 +import { WidgetComponentService } from './widget/widget-component.service';
  40 +import { LegendComponent } from '@home/components/widget/legend.component';
40 41
41 @NgModule({ 42 @NgModule({
42 entryComponents: [ 43 entryComponents: [
@@ -69,7 +70,8 @@ import { DynamicWidgetComponentFactoryService } from './widget/dynamic-widget-co @@ -69,7 +70,8 @@ import { DynamicWidgetComponentFactoryService } from './widget/dynamic-widget-co
69 AddAttributeDialogComponent, 70 AddAttributeDialogComponent,
70 EditAttributeValuePanelComponent, 71 EditAttributeValuePanelComponent,
71 DashboardComponent, 72 DashboardComponent,
72 - WidgetComponent 73 + WidgetComponent,
  74 + LegendComponent
73 ], 75 ],
74 imports: [ 76 imports: [
75 CommonModule, 77 CommonModule,
@@ -88,10 +90,11 @@ import { DynamicWidgetComponentFactoryService } from './widget/dynamic-widget-co @@ -88,10 +90,11 @@ import { DynamicWidgetComponentFactoryService } from './widget/dynamic-widget-co
88 AlarmDetailsDialogComponent, 90 AlarmDetailsDialogComponent,
89 AttributeTableComponent, 91 AttributeTableComponent,
90 DashboardComponent, 92 DashboardComponent,
91 - WidgetComponent 93 + WidgetComponent,
  94 + LegendComponent
92 ], 95 ],
93 providers: [ 96 providers: [
94 - DynamicWidgetComponentFactoryService 97 + WidgetComponentService
95 ] 98 ]
96 }) 99 })
97 export class HomeComponentsModule { } 100 export class HomeComponentsModule { }
@@ -21,24 +21,13 @@ import { AppState } from '@core/core.state'; @@ -21,24 +21,13 @@ import { AppState } from '@core/core.state';
21 import { WidgetContext, IDynamicWidgetComponent } from '@home/models/widget-component.models'; 21 import { WidgetContext, IDynamicWidgetComponent } from '@home/models/widget-component.models';
22 import { ExceptionData } from '@shared/models/error.models'; 22 import { ExceptionData } from '@shared/models/error.models';
23 23
24 -export abstract class DynamicWidgetComponentModule implements OnDestroy {  
25 -  
26 - ngOnDestroy(): void {  
27 - console.log('Module destroyed!');  
28 - }  
29 -  
30 -}  
31 -  
32 -export abstract class DynamicWidgetComponent extends PageComponent implements IDynamicWidgetComponent, OnInit, OnDestroy { 24 +export class DynamicWidgetComponent extends PageComponent implements IDynamicWidgetComponent, OnInit, OnDestroy {
33 25
34 @Input() 26 @Input()
35 widgetContext: WidgetContext; 27 widgetContext: WidgetContext;
36 28
37 @Input() 29 @Input()
38 - widgetErrorData: ExceptionData;  
39 -  
40 - @Input()  
41 - loadingData: boolean; 30 + errorMessages: string[];
42 31
43 [key: string]: any; 32 [key: string]: any;
44 33
@@ -51,7 +40,7 @@ export abstract class DynamicWidgetComponent extends PageComponent implements ID @@ -51,7 +40,7 @@ export abstract class DynamicWidgetComponent extends PageComponent implements ID
51 } 40 }
52 41
53 ngOnDestroy(): void { 42 ngOnDestroy(): void {
54 - console.log('Component destroyed!'); 43 + console.log('Widget component destroyed!');
55 } 44 }
56 45
57 clearRpcError() { 46 clearRpcError() {
  1 +///
  2 +/// Copyright © 2016-2019 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Injectable } from '@angular/core';
  18 +import { DynamicComponentFactoryService } from '@core/services/dynamic-component-factory.service';
  19 +import { WidgetService } from '@core/http/widget.service';
  20 +import { forkJoin, Observable, of, ReplaySubject, Subject, throwError } from 'rxjs';
  21 +import { WidgetInfo, MissingWidgetType, toWidgetInfo, WidgetTypeInstance, ErrorWidgetType } from '@home/models/widget-component.models';
  22 +import cssjs from '@core/css/css';
  23 +import { UtilsService } from '@core/services/utils.service';
  24 +import { ResourcesService } from '@core/services/resources.service';
  25 +import {
  26 + widgetActionSources,
  27 + WidgetControllerDescriptor,
  28 + WidgetType
  29 +} from '@shared/models/widget.models';
  30 +import { catchError, switchMap, map, mergeMap } from 'rxjs/operators';
  31 +import { isFunction, isUndefined } from '@core/utils';
  32 +import { TranslateService } from '@ngx-translate/core';
  33 +import { DynamicWidgetComponent } from '@home/components/widget/dynamic-widget.component';
  34 +import { SharedModule } from '@shared/shared.module';
  35 +import { WidgetComponentsModule } from '@home/components/widget/widget-components.module';
  36 +
  37 +@Injectable()
  38 +export class WidgetComponentService {
  39 +
  40 + private cssParser = new cssjs();
  41 +
  42 + private widgetsInfoInMemoryCache = new Map<string, WidgetInfo>();
  43 +
  44 + private widgetsInfoFetchQueue = new Map<string, Array<Subject<WidgetInfo>>>();
  45 +
  46 + private init$: Observable<any>;
  47 +
  48 + private missingWidgetType: WidgetInfo;
  49 + private errorWidgetType: WidgetInfo;
  50 +
  51 + constructor(private dynamicComponentFactoryService: DynamicComponentFactoryService,
  52 + private widgetService: WidgetService,
  53 + private utils: UtilsService,
  54 + private resources: ResourcesService,
  55 + private translate: TranslateService) {
  56 + this.cssParser.testMode = false;
  57 + this.init();
  58 + }
  59 +
  60 + private init(): Observable<any> {
  61 + if (this.init$) {
  62 + return this.init$;
  63 + } else {
  64 + this.missingWidgetType = {...MissingWidgetType};
  65 + this.errorWidgetType = {...ErrorWidgetType};
  66 + const initSubject = new ReplaySubject();
  67 + this.init$ = initSubject.asObservable();
  68 + const loadDefaultWidgetInfoTasks = [
  69 + this.loadWidgetResources(this.missingWidgetType, 'global-widget-missing-type'),
  70 + this.loadWidgetResources(this.errorWidgetType, 'global-widget-error-type'),
  71 + ];
  72 + forkJoin(loadDefaultWidgetInfoTasks).subscribe(
  73 + () => {
  74 + initSubject.next();
  75 + },
  76 + () => {
  77 + console.error('Failed to load default widget types!');
  78 + initSubject.error('Failed to load default widget types!');
  79 + }
  80 + );
  81 + return this.init$;
  82 + }
  83 + }
  84 +
  85 + public getWidgetInfo(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): Observable<WidgetInfo> {
  86 + return this.init().pipe(
  87 + mergeMap(() => this.getWidgetInfoInternal(bundleAlias, widgetTypeAlias, isSystem))
  88 + );
  89 + }
  90 +
  91 + private getWidgetInfoInternal(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): Observable<WidgetInfo> {
  92 + const widgetInfoSubject = new ReplaySubject<WidgetInfo>();
  93 + const widgetInfo = this.getWidgetInfoFromCache(bundleAlias, widgetTypeAlias, isSystem);
  94 + if (widgetInfo) {
  95 + widgetInfoSubject.next(widgetInfo);
  96 + widgetInfoSubject.complete();
  97 + } else {
  98 + if (this.utils.widgetEditMode) {
  99 + // TODO:
  100 + } else {
  101 + const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem);
  102 + let fetchQueue = this.widgetsInfoFetchQueue.get(key);
  103 + if (fetchQueue) {
  104 + fetchQueue.push(widgetInfoSubject);
  105 + } else {
  106 + fetchQueue = new Array<Subject<WidgetInfo>>();
  107 + this.widgetsInfoFetchQueue.set(key, fetchQueue);
  108 + this.widgetService.getWidgetType(bundleAlias, widgetTypeAlias, isSystem).subscribe(
  109 + (widgetType) => {
  110 + this.loadWidget(widgetType, bundleAlias, isSystem, widgetInfoSubject);
  111 + },
  112 + () => {
  113 + widgetInfoSubject.next(this.missingWidgetType);
  114 + widgetInfoSubject.complete();
  115 + this.resolveWidgetsInfoFetchQueue(key, this.missingWidgetType);
  116 + }
  117 + );
  118 + }
  119 + }
  120 + }
  121 + return widgetInfoSubject.asObservable();
  122 + }
  123 +
  124 + private loadWidget(widgetType: WidgetType, bundleAlias: string, isSystem: boolean, widgetInfoSubject: Subject<WidgetInfo>) {
  125 + const widgetInfo = toWidgetInfo(widgetType);
  126 + const key = this.createWidgetInfoCacheKey(bundleAlias, widgetInfo.alias, isSystem);
  127 + let widgetControllerDescriptor: WidgetControllerDescriptor = null;
  128 + try {
  129 + widgetControllerDescriptor = this.createWidgetControllerDescriptor(widgetInfo, key);
  130 + } catch (e) {
  131 + const details = this.utils.parseException(e);
  132 + const errorMessage = `Failed to compile widget script. \n Error: ${details.message}`;
  133 + this.processWidgetLoadError([errorMessage], key, widgetInfoSubject);
  134 + }
  135 + if (widgetControllerDescriptor) {
  136 + const widgetNamespace = `widget-type-${(isSystem ? 'sys-' : '')}${bundleAlias}-${widgetInfo.alias}`;
  137 + this.loadWidgetResources(widgetInfo, widgetNamespace).subscribe(
  138 + () => {
  139 + if (widgetControllerDescriptor.settingsSchema) {
  140 + widgetInfo.typeSettingsSchema = widgetControllerDescriptor.settingsSchema;
  141 + }
  142 + if (widgetControllerDescriptor.dataKeySettingsSchema) {
  143 + widgetInfo.typeDataKeySettingsSchema = widgetControllerDescriptor.dataKeySettingsSchema;
  144 + }
  145 + widgetInfo.typeParameters = widgetControllerDescriptor.typeParameters;
  146 + widgetInfo.actionSources = widgetControllerDescriptor.actionSources;
  147 + widgetInfo.widgetTypeFunction = widgetControllerDescriptor.widgetTypeFunction;
  148 + this.putWidgetInfoToCache(widgetInfo, bundleAlias, widgetInfo.alias, isSystem);
  149 + if (widgetInfoSubject) {
  150 + widgetInfoSubject.next(widgetInfo);
  151 + widgetInfoSubject.complete();
  152 + }
  153 + this.resolveWidgetsInfoFetchQueue(key, widgetInfo);
  154 + },
  155 + (errorMessages: string[]) => {
  156 + this.processWidgetLoadError(errorMessages, key, widgetInfoSubject);
  157 + }
  158 + );
  159 + }
  160 + }
  161 +
  162 + private loadWidgetResources(widgetInfo: WidgetInfo, widgetNamespace: string): Observable<any> {
  163 + this.cssParser.cssPreviewNamespace = widgetNamespace;
  164 + this.cssParser.createStyleElement(widgetNamespace, widgetInfo.templateCss);
  165 + const resourceTasks: Observable<string>[] = [];
  166 + if (widgetInfo.resources.length > 0) {
  167 + widgetInfo.resources.forEach((resource) => {
  168 + resourceTasks.push(
  169 + this.resources.loadResource(resource.url).pipe(
  170 + catchError(e => of(`Failed to load widget resource: '${resource.url}'`))
  171 + )
  172 + );
  173 + });
  174 + }
  175 + resourceTasks.push(
  176 + this.dynamicComponentFactoryService.createDynamicComponentFactory(
  177 + class DynamicWidgetComponentInstance extends DynamicWidgetComponent {},
  178 + widgetInfo.templateHtml,
  179 + [SharedModule, WidgetComponentsModule]
  180 + ).pipe(
  181 + map((factory) => {
  182 + widgetInfo.componentFactory = factory;
  183 + return null;
  184 + }),
  185 + catchError(e => {
  186 + const details = this.utils.parseException(e);
  187 + const errorMessage = `Failed to compile widget html. \n Error: ${details.message}`;
  188 + return of(errorMessage);
  189 + })
  190 + )
  191 + );
  192 + return forkJoin(resourceTasks).pipe(
  193 + switchMap(msgs => {
  194 + let errors: string[];
  195 + if (msgs && msgs.length) {
  196 + errors = msgs.filter(msg => msg && msg.length > 0);
  197 + }
  198 + if (errors && errors.length) {
  199 + return throwError(errors);
  200 + } else {
  201 + return of(null);
  202 + }
  203 + }
  204 + ));
  205 + }
  206 +
  207 + private createWidgetControllerDescriptor(widgetInfo: WidgetInfo, name: string): WidgetControllerDescriptor {
  208 + let widgetTypeFunctionBody = `return function ${name} (ctx) {\n` +
  209 + ' var self = this;\n' +
  210 + ' self.ctx = ctx;\n\n'; /*+
  211 +
  212 + ' self.onInit = function() {\n\n' +
  213 +
  214 + ' }\n\n' +
  215 +
  216 + ' self.onDataUpdated = function() {\n\n' +
  217 +
  218 + ' }\n\n' +
  219 +
  220 + ' self.useCustomDatasources = function() {\n\n' +
  221 +
  222 + ' }\n\n' +
  223 +
  224 + ' self.typeParameters = function() {\n\n' +
  225 + return {
  226 + useCustomDatasources: false,
  227 + maxDatasources: -1, //unlimited
  228 + maxDataKeys: -1, //unlimited
  229 + dataKeysOptional: false,
  230 + stateData: false
  231 + };
  232 + ' }\n\n' +
  233 +
  234 + ' self.actionSources = function() {\n\n' +
  235 + return {
  236 + 'headerButton': {
  237 + name: 'Header button',
  238 + multiple: true
  239 + }
  240 + };
  241 + }\n\n' +
  242 + ' self.onResize = function() {\n\n' +
  243 +
  244 + ' }\n\n' +
  245 +
  246 + ' self.onEditModeChanged = function() {\n\n' +
  247 +
  248 + ' }\n\n' +
  249 +
  250 + ' self.onMobileModeChanged = function() {\n\n' +
  251 +
  252 + ' }\n\n' +
  253 +
  254 + ' self.getSettingsSchema = function() {\n\n' +
  255 +
  256 + ' }\n\n' +
  257 +
  258 + ' self.getDataKeySettingsSchema = function() {\n\n' +
  259 +
  260 + ' }\n\n' +
  261 +
  262 + ' self.onDestroy = function() {\n\n' +
  263 +
  264 + ' }\n\n' +
  265 + '}';*/
  266 +
  267 + widgetTypeFunctionBody += widgetInfo.controllerScript;
  268 + widgetTypeFunctionBody += '\n};\n';
  269 +
  270 + try {
  271 +
  272 + const widgetTypeFunction = new Function(widgetTypeFunctionBody);
  273 + const widgetType = widgetTypeFunction.apply(this);
  274 + const widgetTypeInstance: WidgetTypeInstance = new widgetType();
  275 + const result: WidgetControllerDescriptor = {
  276 + widgetTypeFunction: widgetType
  277 + };
  278 + if (isFunction(widgetTypeInstance.getSettingsSchema)) {
  279 + result.settingsSchema = widgetTypeInstance.getSettingsSchema();
  280 + }
  281 + if (isFunction(widgetTypeInstance.getDataKeySettingsSchema)) {
  282 + result.dataKeySettingsSchema = widgetTypeInstance.getDataKeySettingsSchema();
  283 + }
  284 + if (isFunction(widgetTypeInstance.typeParameters)) {
  285 + result.typeParameters = widgetTypeInstance.typeParameters();
  286 + } else {
  287 + result.typeParameters = {};
  288 + }
  289 + if (isFunction(widgetTypeInstance.useCustomDatasources)) {
  290 + result.typeParameters.useCustomDatasources = widgetTypeInstance.useCustomDatasources();
  291 + } else {
  292 + result.typeParameters.useCustomDatasources = false;
  293 + }
  294 + if (isUndefined(result.typeParameters.maxDatasources)) {
  295 + result.typeParameters.maxDatasources = -1;
  296 + }
  297 + if (isUndefined(result.typeParameters.maxDataKeys)) {
  298 + result.typeParameters.maxDataKeys = -1;
  299 + }
  300 + if (isUndefined(result.typeParameters.dataKeysOptional)) {
  301 + result.typeParameters.dataKeysOptional = false;
  302 + }
  303 + if (isUndefined(result.typeParameters.stateData)) {
  304 + result.typeParameters.stateData = false;
  305 + }
  306 + if (isFunction(widgetTypeInstance.actionSources)) {
  307 + result.actionSources = widgetTypeInstance.actionSources();
  308 + } else {
  309 + result.actionSources = {};
  310 + }
  311 + for (const actionSourceId of Object.keys(widgetActionSources)) {
  312 + result.actionSources[actionSourceId] = {...widgetActionSources[actionSourceId]};
  313 + result.actionSources[actionSourceId].name = this.translate.instant(result.actionSources[actionSourceId].name);
  314 + }
  315 + return result;
  316 + } catch (e) {
  317 + this.utils.processWidgetException(e);
  318 + throw e;
  319 + }
  320 + }
  321 +
  322 + private processWidgetLoadError(errorMessages: string[], cacheKey: string, widgetInfoSubject: Subject<WidgetInfo>) {
  323 + if (widgetInfoSubject) {
  324 + widgetInfoSubject.error({
  325 + widgetInfo: this.errorWidgetType,
  326 + errorMessages
  327 + });
  328 + }
  329 + this.resolveWidgetsInfoFetchQueue(cacheKey, this.errorWidgetType, errorMessages);
  330 + }
  331 +
  332 + private resolveWidgetsInfoFetchQueue(key: string, widgetInfo: WidgetInfo, errorMessages?: string[]) {
  333 + const fetchQueue = this.widgetsInfoFetchQueue.get(key);
  334 + if (fetchQueue) {
  335 + fetchQueue.forEach(subject => {
  336 + if (!errorMessages) {
  337 + subject.next(widgetInfo);
  338 + subject.complete();
  339 + } else {
  340 + subject.error({
  341 + widgetInfo,
  342 + errorMessages
  343 + });
  344 + }
  345 + });
  346 + this.widgetsInfoFetchQueue.delete(key);
  347 + }
  348 + }
  349 +
  350 + // Cache functions
  351 +
  352 + private createWidgetInfoCacheKey(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): string {
  353 + return `${isSystem ? 'sys_' : ''}${bundleAlias}_${widgetTypeAlias}`;
  354 + }
  355 +
  356 + private getWidgetInfoFromCache(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): WidgetInfo | undefined {
  357 + const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem);
  358 + return this.widgetsInfoInMemoryCache.get(key);
  359 + }
  360 +
  361 + private putWidgetInfoToCache(widgetInfo: WidgetInfo, bundleAlias: string, widgetTypeAlias: string, isSystem: boolean) {
  362 + const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem);
  363 + this.widgetsInfoInMemoryCache.set(key, widgetInfo);
  364 + }
  365 +
  366 +}
@@ -25,14 +25,12 @@ import { LegendComponent } from '@home/components/widget/legend.component'; @@ -25,14 +25,12 @@ import { LegendComponent } from '@home/components/widget/legend.component';
25 ], 25 ],
26 declarations: 26 declarations:
27 [ 27 [
28 - LegendComponent  
29 ], 28 ],
30 imports: [ 29 imports: [
31 CommonModule, 30 CommonModule,
32 SharedModule 31 SharedModule
33 ], 32 ],
34 exports: [ 33 exports: [
35 - LegendComponent  
36 ] 34 ]
37 }) 35 })
38 export class WidgetComponentsModule { } 36 export class WidgetComponentsModule { }
@@ -15,4 +15,16 @@ @@ -15,4 +15,16 @@
15 limitations under the License. 15 limitations under the License.
16 16
17 --> 17 -->
18 -<ng-container #widgetContent></ng-container> 18 +<div class="tb-absolute-fill tb-widget-error" *ngIf="widgetErrorData">
  19 + <span>Widget Error: {{ widgetErrorData.name + ": " + widgetErrorData.message}}</span>
  20 +</div>
  21 +<div class="tb-absolute-fill tb-widget-loading" [fxShow]="loadingData" fxLayout="column" fxLayoutAlign="center center">
  22 + <mat-spinner color="accent" md-mode="indeterminate" diameter="40"></mat-spinner>
  23 +</div>
  24 +<div class="tb-absolute-fill" [fxLayout]="legendContainerLayoutType">
  25 + <tb-legend *ngIf="displayLegend && isLegendFirst" [ngStyle]="legendStyle" [legendConfig]="legendConfig" [legendData]="legendData"></tb-legend>
  26 + <div fxFlex id="widget-container">
  27 + <ng-container #widgetContent></ng-container>
  28 + </div>
  29 + <tb-legend *ngIf="displayLegend && !isLegendFirst" [ngStyle]="legendStyle" [legendConfig]="legendConfig" [legendData]="legendData"></tb-legend>
  30 +</div>
@@ -17,7 +17,6 @@ @@ -17,7 +17,6 @@
17 import { 17 import {
18 AfterViewInit, 18 AfterViewInit,
19 Component, 19 Component,
20 - ComponentFactory,  
21 ComponentFactoryResolver, 20 ComponentFactoryResolver,
22 ComponentRef, 21 ComponentRef,
23 ElementRef, 22 ElementRef,
@@ -38,34 +37,37 @@ import { @@ -38,34 +37,37 @@ import {
38 LegendPosition, 37 LegendPosition,
39 Widget, 38 Widget,
40 WidgetActionDescriptor, 39 WidgetActionDescriptor,
  40 + widgetActionSources,
41 WidgetActionType, 41 WidgetActionType,
42 - WidgetInfo, WidgetResource,  
43 - widgetType,  
44 - WidgetTypeInstance,  
45 - widgetActionSources 42 + WidgetResource,
  43 + widgetType
46 } from '@shared/models/widget.models'; 44 } from '@shared/models/widget.models';
47 import { PageComponent } from '@shared/components/page.component'; 45 import { PageComponent } from '@shared/components/page.component';
48 import { Store } from '@ngrx/store'; 46 import { Store } from '@ngrx/store';
49 import { AppState } from '@core/core.state'; 47 import { AppState } from '@core/core.state';
50 import { WidgetService } from '@core/http/widget.service'; 48 import { WidgetService } from '@core/http/widget.service';
51 import { UtilsService } from '@core/services/utils.service'; 49 import { UtilsService } from '@core/services/utils.service';
52 -import { DynamicWidgetComponent } from '@home/components/widget/dynamic-widget.component';  
53 -import { forkJoin, Observable, of, ReplaySubject, throwError } from 'rxjs';  
54 -import { DynamicWidgetComponentFactoryService } from '@home/components/widget/dynamic-widget-component-factory.service'; 50 +import { forkJoin, Observable, of, throwError } from 'rxjs';
55 import { isDefined, objToBase64 } from '@core/utils'; 51 import { isDefined, objToBase64 } from '@core/utils';
56 import * as $ from 'jquery'; 52 import * as $ from 'jquery';
57 -import { WidgetContext, WidgetHeaderAction } from '@home/models/widget-component.models'; 53 +import {
  54 + IDynamicWidgetComponent,
  55 + WidgetContext,
  56 + WidgetHeaderAction,
  57 + WidgetInfo,
  58 + WidgetTypeInstance
  59 +} from '@home/models/widget-component.models';
58 import { 60 import {
59 EntityInfo, 61 EntityInfo,
60 IWidgetSubscription, 62 IWidgetSubscription,
61 - SubscriptionInfo,  
62 - WidgetSubscriptionOptions,  
63 StateObject, 63 StateObject,
64 StateParams, 64 StateParams,
65 - WidgetSubscriptionContext 65 + SubscriptionInfo,
  66 + WidgetSubscriptionContext,
  67 + WidgetSubscriptionOptions
66 } from '@core/api/widget-api.models'; 68 } from '@core/api/widget-api.models';
67 import { EntityId } from '@shared/models/id/entity-id'; 69 import { EntityId } from '@shared/models/id/entity-id';
68 -import { ActivatedRoute, Router, UrlSegment } from '@angular/router'; 70 +import { ActivatedRoute, Router } from '@angular/router';
69 import cssjs from '@core/css/css'; 71 import cssjs from '@core/css/css';
70 import { ResourcesService } from '@core/services/resources.service'; 72 import { ResourcesService } from '@core/services/resources.service';
71 import { catchError, switchMap } from 'rxjs/operators'; 73 import { catchError, switchMap } from 'rxjs/operators';
@@ -74,6 +76,7 @@ import { TimeService } from '@core/services/time.service'; @@ -74,6 +76,7 @@ import { TimeService } from '@core/services/time.service';
74 import { DeviceService } from '@app/core/http/device.service'; 76 import { DeviceService } from '@app/core/http/device.service';
75 import { AlarmService } from '@app/core/http/alarm.service'; 77 import { AlarmService } from '@app/core/http/alarm.service';
76 import { ExceptionData } from '@shared/models/error.models'; 78 import { ExceptionData } from '@shared/models/error.models';
  79 +import { WidgetComponentService } from './widget-component.service';
77 80
78 @Component({ 81 @Component({
79 selector: 'tb-widget', 82 selector: 'tb-widget',
@@ -99,14 +102,22 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI @@ -99,14 +102,22 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
99 102
100 widget: Widget; 103 widget: Widget;
101 widgetInfo: WidgetInfo; 104 widgetInfo: WidgetInfo;
  105 + errorMessages: string[];
102 widgetContext: WidgetContext; 106 widgetContext: WidgetContext;
103 widgetType: any; 107 widgetType: any;
104 widgetTypeInstance: WidgetTypeInstance; 108 widgetTypeInstance: WidgetTypeInstance;
105 widgetErrorData: ExceptionData; 109 widgetErrorData: ExceptionData;
  110 + loadingData: boolean;
106 111
107 - dynamicWidgetComponentFactory: ComponentFactory<DynamicWidgetComponent>;  
108 - dynamicWidgetComponentRef: ComponentRef<DynamicWidgetComponent>;  
109 - dynamicWidgetComponent: DynamicWidgetComponent; 112 + displayLegend: boolean;
  113 + legendConfig: LegendConfig;
  114 + legendData: LegendData;
  115 + isLegendFirst: boolean;
  116 + legendContainerLayoutType: string;
  117 + legendStyle: {[klass: string]: any};
  118 +
  119 + dynamicWidgetComponentRef: ComponentRef<IDynamicWidgetComponent>;
  120 + dynamicWidgetComponent: IDynamicWidgetComponent;
110 121
111 subscriptionContext: WidgetSubscriptionContext; 122 subscriptionContext: WidgetSubscriptionContext;
112 123
@@ -120,7 +131,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI @@ -120,7 +131,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
120 constructor(protected store: Store<AppState>, 131 constructor(protected store: Store<AppState>,
121 private route: ActivatedRoute, 132 private route: ActivatedRoute,
122 private router: Router, 133 private router: Router,
123 - private dynamicWidgetComponentFactoryService: DynamicWidgetComponentFactoryService, 134 + private widgetComponentService: WidgetComponentService,
124 private componentFactoryResolver: ComponentFactoryResolver, 135 private componentFactoryResolver: ComponentFactoryResolver,
125 private elementRef: ElementRef, 136 private elementRef: ElementRef,
126 private injector: Injector, 137 private injector: Injector,
@@ -134,8 +145,67 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI @@ -134,8 +145,67 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
134 } 145 }
135 146
136 ngOnInit(): void { 147 ngOnInit(): void {
  148 +
  149 + this.loadingData = true;
  150 +
137 this.widget = this.dashboardWidget.widget; 151 this.widget = this.dashboardWidget.widget;
138 152
  153 + this.displayLegend = isDefined(this.widget.config.showLegend) ? this.widget.config.showLegend
  154 + : this.widget.type === widgetType.timeseries;
  155 +
  156 + this.legendContainerLayoutType = 'column';
  157 +
  158 + if (this.displayLegend) {
  159 + this.legendConfig = this.widget.config.legendConfig ||
  160 + {
  161 + position: LegendPosition.bottom,
  162 + showMin: false,
  163 + showMax: false,
  164 + showAvg: this.widget.type === widgetType.timeseries,
  165 + showTotal: false
  166 + };
  167 + this.legendData = {
  168 + keys: [],
  169 + data: []
  170 + };
  171 + if (this.legendConfig.position === LegendPosition.top ||
  172 + this.legendConfig.position === LegendPosition.bottom) {
  173 + this.legendContainerLayoutType = 'column';
  174 + } else {
  175 + this.legendContainerLayoutType = 'row';
  176 + }
  177 + switch (this.legendConfig.position) {
  178 + case LegendPosition.top:
  179 + this.legendStyle = {
  180 + paddingBottom: '8px',
  181 + maxHeight: '50%',
  182 + overflowY: 'auto'
  183 + };
  184 + break;
  185 + case LegendPosition.bottom:
  186 + this.legendStyle = {
  187 + paddingTop: '8px',
  188 + maxHeight: '50%',
  189 + overflowY: 'auto'
  190 + };
  191 + break;
  192 + case LegendPosition.left:
  193 + this.legendStyle = {
  194 + paddingRight: '0px',
  195 + maxWidth: '50%',
  196 + overflowY: 'auto'
  197 + };
  198 + break;
  199 + case LegendPosition.right:
  200 + this.legendStyle = {
  201 + paddingLeft: '0px',
  202 + maxWidth: '50%',
  203 + overflowY: 'auto'
  204 + };
  205 + break;
  206 + }
  207 + }
  208 +
139 const actionDescriptorsBySourceId: {[actionSourceId: string]: Array<WidgetActionDescriptor>} = {}; 209 const actionDescriptorsBySourceId: {[actionSourceId: string]: Array<WidgetActionDescriptor>} = {};
140 if (this.widget.config.actions) { 210 if (this.widget.config.actions) {
141 for (const actionSourceId of Object.keys(this.widget.config.actions)) { 211 for (const actionSourceId of Object.keys(this.widget.config.actions)) {
@@ -244,10 +314,15 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI @@ -244,10 +314,15 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
244 aliasController: this.dashboard.aliasController 314 aliasController: this.dashboard.aliasController
245 }; 315 };
246 316
247 - this.widgetService.getWidgetInfo(this.widget.bundleAlias, this.widget.typeAlias, this.widget.isSystemType).subscribe( 317 + this.widgetComponentService.getWidgetInfo(this.widget.bundleAlias, this.widget.typeAlias, this.widget.isSystemType).subscribe(
248 (widgetInfo) => { 318 (widgetInfo) => {
249 this.widgetInfo = widgetInfo; 319 this.widgetInfo = widgetInfo;
250 this.loadFromWidgetInfo(); 320 this.loadFromWidgetInfo();
  321 + },
  322 + (errorData) => {
  323 + this.widgetInfo = errorData.widgetInfo;
  324 + this.errorMessages = errorData.errorMessages;
  325 + this.loadFromWidgetInfo();
251 } 326 }
252 ); 327 );
253 328
@@ -363,14 +438,9 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI @@ -363,14 +438,9 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
363 } 438 }
364 439
365 private initialize() { 440 private initialize() {
366 - this.configureDynamicWidgetComponent().subscribe(  
367 - () => {  
368 - this.dynamicWidgetComponent.loadingData = false;  
369 - },  
370 - (error) => {  
371 - // TODO:  
372 - }  
373 - ); 441 + this.configureDynamicWidgetComponent();
  442 + // TODO:
  443 + this.loadingData = false;
374 } 444 }
375 445
376 private destroyDynamicWidgetComponent() { 446 private destroyDynamicWidgetComponent() {
@@ -381,127 +451,37 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI @@ -381,127 +451,37 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
381 if (this.dynamicWidgetComponentRef) { 451 if (this.dynamicWidgetComponentRef) {
382 this.dynamicWidgetComponentRef.destroy(); 452 this.dynamicWidgetComponentRef.destroy();
383 } 453 }
384 - if (this.dynamicWidgetComponentFactory) {  
385 - this.dynamicWidgetComponentFactoryService.destroyDynamicWidgetComponentFactory(this.dynamicWidgetComponentFactory);  
386 - }  
387 } 454 }
388 455
389 private handleWidgetException(e) { 456 private handleWidgetException(e) {
390 console.error(e); 457 console.error(e);
391 this.widgetErrorData = this.utils.processWidgetException(e); 458 this.widgetErrorData = this.utils.processWidgetException(e);
392 - if (this.dynamicWidgetComponent) {  
393 - this.dynamicWidgetComponent.widgetErrorData = this.widgetErrorData;  
394 - }  
395 } 459 }
396 460
397 - private configureDynamicWidgetComponent(): Observable<any> { 461 + private configureDynamicWidgetComponent() {
  462 + this.widgetContentContainer.clear();
  463 + this.dynamicWidgetComponentRef = this.widgetContentContainer.createComponent(this.widgetInfo.componentFactory);
  464 + this.dynamicWidgetComponent = this.dynamicWidgetComponentRef.instance;
398 465
399 - const dynamicWidgetComponentSubject = new ReplaySubject(); 466 + this.dynamicWidgetComponent.widgetContext = this.widgetContext;
  467 + this.dynamicWidgetComponent.errorMessages = this.errorMessages;
400 468
401 - let html = '<div class="tb-absolute-fill tb-widget-error" *ngIf="widgetErrorData">' +  
402 - '<span>Widget Error: {{ widgetErrorData.name + ": " + widgetErrorData.message}}</span>' +  
403 - '</div>' +  
404 - '<div class="tb-absolute-fill tb-widget-loading" [fxShow]="loadingData" fxLayout="column" fxLayoutAlign="center center">' +  
405 - '<mat-spinner color="accent" md-mode="indeterminate" diameter="40"></mat-spinner>' +  
406 - '</div>'; 469 + this.widgetContext.$scope = this.dynamicWidgetComponent;
407 470
408 - let containerHtml = `<div id="container">${this.widgetInfo.templateHtml}</div>`; 471 + const containerElement = $(this.elementRef.nativeElement.querySelector('#widget-container'));
409 472
410 - const displayLegend = isDefined(this.widget.config.showLegend) ? this.widget.config.showLegend  
411 - : this.widget.type === widgetType.timeseries; 473 + this.widgetContext.$container = $('> ng-component', containerElement);
  474 + this.widgetContext.$container.css('display', 'block');
  475 + this.widgetContext.$container.attr('id', 'container');
  476 + this.widgetContext.$containerParent = $(containerElement);
412 477
413 - let legendConfig: LegendConfig;  
414 - let legendData: LegendData;  
415 - if (displayLegend) {  
416 - legendConfig = this.widget.config.legendConfig ||  
417 - {  
418 - position: LegendPosition.bottom,  
419 - showMin: false,  
420 - showMax: false,  
421 - showAvg: this.widget.type === widgetType.timeseries,  
422 - showTotal: false  
423 - };  
424 - legendData = {  
425 - keys: [],  
426 - data: []  
427 - };  
428 - let layoutType;  
429 - if (legendConfig.position === LegendPosition.top ||  
430 - legendConfig.position === LegendPosition.bottom) {  
431 - layoutType = 'column';  
432 - } else {  
433 - layoutType = 'row'; 478 + if (this.widgetSizeDetected) {
  479 + this.widgetContext.$container.css('height', this.widgetContext.height + 'px');
  480 + this.widgetContext.$container.css('width', this.widgetContext.width + 'px');
434 } 481 }
435 - let legendStyle;  
436 - switch (legendConfig.position) {  
437 - case LegendPosition.top:  
438 - legendStyle = 'padding-bottom: 8px; max-height: 50%; overflow-y: auto;';  
439 - break;  
440 - case LegendPosition.bottom:  
441 - legendStyle = 'padding-top: 8px; max-height: 50%; overflow-y: auto;';  
442 - break;  
443 - case LegendPosition.left:  
444 - legendStyle = 'padding-right: 0px; max-width: 50%; overflow-y: auto;';  
445 - break;  
446 - case LegendPosition.right:  
447 - legendStyle = 'padding-left: 0px; max-width: 50%; overflow-y: auto;';  
448 - break;  
449 - }  
450 -  
451 - const legendHtml = `<tb-legend style="${legendStyle}" [legendConfig]="legendConfig" [legendData]="legendData"></tb-legend>`;  
452 - containerHtml = `<div fxFlex id="widget-container">${containerHtml}</div>`;  
453 - html += `<div class="tb-absolute-fill" fxLayout="${layoutType}">`;  
454 - if (legendConfig.position === LegendPosition.top ||  
455 - legendConfig.position === LegendPosition.left) {  
456 - html += legendHtml;  
457 - html += containerHtml;  
458 - } else {  
459 - html += containerHtml;  
460 - html += legendHtml;  
461 - }  
462 - html += '</div>';  
463 - } else {  
464 - html += containerHtml;  
465 - }  
466 -  
467 - this.dynamicWidgetComponentFactoryService.createDynamicWidgetComponentFactory(html).subscribe(  
468 - (componentFactory) => {  
469 - this.dynamicWidgetComponentFactory = componentFactory;  
470 - this.widgetContentContainer.clear();  
471 - this.dynamicWidgetComponentRef = this.widgetContentContainer.createComponent(this.dynamicWidgetComponentFactory);  
472 - this.dynamicWidgetComponent = this.dynamicWidgetComponentRef.instance;  
473 -  
474 - this.dynamicWidgetComponent.loadingData = true;  
475 - this.dynamicWidgetComponent.widgetContext = this.widgetContext;  
476 - this.dynamicWidgetComponent.widgetErrorData = this.widgetErrorData;  
477 - this.dynamicWidgetComponent.displayLegend = displayLegend;  
478 - this.dynamicWidgetComponent.legendConfig = legendConfig;  
479 - this.dynamicWidgetComponent.legendData = legendData;  
480 -  
481 - this.widgetContext.$scope = this.dynamicWidgetComponent;  
482 482
483 - const containerElement = displayLegend ? $(this.elementRef.nativeElement.querySelector('#widget-container'))  
484 - : $(this.elementRef.nativeElement);  
485 -  
486 - this.widgetContext.$container = $('#container', containerElement);  
487 - this.widgetContext.$containerParent = $(containerElement);  
488 -  
489 - if (this.widgetSizeDetected) {  
490 - this.widgetContext.$container.css('height', this.widgetContext.height + 'px');  
491 - this.widgetContext.$container.css('width', this.widgetContext.width + 'px');  
492 - }  
493 -  
494 - // @ts-ignore  
495 - addResizeListener(this.widgetContext.$containerParent[0], this.onResizeListener);  
496 -  
497 - dynamicWidgetComponentSubject.next();  
498 - dynamicWidgetComponentSubject.complete();  
499 - },  
500 - (e) => {  
501 - dynamicWidgetComponentSubject.error(e);  
502 - }  
503 - );  
504 - return dynamicWidgetComponentSubject.asObservable(); 483 + // @ts-ignore
  484 + addResizeListener(this.widgetContext.$containerParent[0], this.onResizeListener);
505 } 485 }
506 486
507 private createSubscription(options: WidgetSubscriptionOptions, subscribe: boolean): Observable<IWidgetSubscription> { 487 private createSubscription(options: WidgetSubscriptionOptions, subscribe: boolean): Observable<IWidgetSubscription> {
@@ -16,23 +16,30 @@ @@ -16,23 +16,30 @@
16 16
17 import { ExceptionData } from '@shared/models/error.models'; 17 import { ExceptionData } from '@shared/models/error.models';
18 import { IDashboardComponent } from '@home/models/dashboard-component.models'; 18 import { IDashboardComponent } from '@home/models/dashboard-component.models';
19 -import { WidgetActionDescriptor, WidgetConfig, WidgetConfigSettings, widgetType } from '@shared/models/widget.models'; 19 +import {
  20 + WidgetActionDescriptor,
  21 + WidgetActionSource,
  22 + WidgetConfig,
  23 + WidgetConfigSettings,
  24 + WidgetControllerDescriptor,
  25 + WidgetType,
  26 + widgetType,
  27 + WidgetTypeDescriptor,
  28 + WidgetTypeParameters
  29 +} from '@shared/models/widget.models';
20 import { Timewindow } from '@shared/models/time/time.models'; 30 import { Timewindow } from '@shared/models/time/time.models';
21 import { 31 import {
22 EntityInfo, 32 EntityInfo,
23 - IWidgetSubscription,  
24 - SubscriptionInfo,  
25 - WidgetSubscriptionOptions,  
26 - IStateController,  
27 IAliasController, 33 IAliasController,
28 - TimewindowFunctions,  
29 - WidgetSubscriptionApi, 34 + IStateController,
  35 + IWidgetSubscription,
  36 + IWidgetUtils,
30 RpcApi, 37 RpcApi,
  38 + TimewindowFunctions,
31 WidgetActionsApi, 39 WidgetActionsApi,
32 - IWidgetUtils 40 + WidgetSubscriptionApi
33 } from '@core/api/widget-api.models'; 41 } from '@core/api/widget-api.models';
34 -import { Observable } from 'rxjs';  
35 -import { EntityId } from '@shared/models/id/entity-id'; 42 +import { ComponentFactory } from '@angular/core';
36 43
37 export interface IWidgetAction { 44 export interface IWidgetAction {
38 name: string; 45 name: string;
@@ -49,13 +56,6 @@ export interface WidgetAction extends IWidgetAction { @@ -49,13 +56,6 @@ export interface WidgetAction extends IWidgetAction {
49 show: boolean; 56 show: boolean;
50 } 57 }
51 58
52 -export interface IDynamicWidgetComponent {  
53 - widgetContext: WidgetContext;  
54 - widgetErrorData: ExceptionData;  
55 - loadingData: boolean;  
56 - [key: string]: any;  
57 -}  
58 -  
59 export interface WidgetContext { 59 export interface WidgetContext {
60 inited?: boolean; 60 inited?: boolean;
61 $container?: any; 61 $container?: any;
@@ -87,3 +87,93 @@ export interface WidgetContext { @@ -87,3 +87,93 @@ export interface WidgetContext {
87 customHeaderActions?: Array<WidgetHeaderAction>; 87 customHeaderActions?: Array<WidgetHeaderAction>;
88 widgetActions?: Array<WidgetAction>; 88 widgetActions?: Array<WidgetAction>;
89 } 89 }
  90 +
  91 +export interface IDynamicWidgetComponent {
  92 + widgetContext: WidgetContext;
  93 + errorMessages: string[];
  94 + [key: string]: any;
  95 +}
  96 +
  97 +export interface WidgetInfo extends WidgetTypeDescriptor, WidgetControllerDescriptor {
  98 + widgetName: string;
  99 + alias: string;
  100 + typeSettingsSchema?: string;
  101 + typeDataKeySettingsSchema?: string;
  102 + componentFactory?: ComponentFactory<IDynamicWidgetComponent>;
  103 +}
  104 +
  105 +export const MissingWidgetType: WidgetInfo = {
  106 + type: widgetType.latest,
  107 + widgetName: 'Widget type not found',
  108 + alias: 'undefined',
  109 + sizeX: 8,
  110 + sizeY: 6,
  111 + resources: [],
  112 + templateHtml: '<div class="tb-widget-error-container">' +
  113 + '<div translate class="tb-widget-error-msg">widget.widget-type-not-found</div>' +
  114 + '</div>',
  115 + templateCss: '',
  116 + controllerScript: 'self.onInit = function() {}',
  117 + settingsSchema: '{}\n',
  118 + dataKeySettingsSchema: '{}\n',
  119 + defaultConfig: '{\n' +
  120 + '"title": "Widget type not found",\n' +
  121 + '"datasources": [],\n' +
  122 + '"settings": {}\n' +
  123 + '}\n'
  124 +};
  125 +
  126 +export const ErrorWidgetType: WidgetInfo = {
  127 + type: widgetType.latest,
  128 + widgetName: 'Error loading widget',
  129 + alias: 'error',
  130 + sizeX: 8,
  131 + sizeY: 6,
  132 + resources: [],
  133 + templateHtml: '<div class="tb-widget-error-container">' +
  134 + '<div translate class="tb-widget-error-msg">widget.widget-type-load-error</div>' +
  135 + '<div *ngFor="let error of errorMessages" class="tb-widget-error-msg">{{ error }}</div>' +
  136 + '</div>',
  137 + templateCss: '',
  138 + controllerScript: 'self.onInit = function() {}',
  139 + settingsSchema: '{}\n',
  140 + dataKeySettingsSchema: '{}\n',
  141 + defaultConfig: '{\n' +
  142 + '"title": "Widget failed to load",\n' +
  143 + '"datasources": [],\n' +
  144 + '"settings": {}\n' +
  145 + '}\n'
  146 +};
  147 +
  148 +export interface WidgetTypeInstance {
  149 + getSettingsSchema?: () => string;
  150 + getDataKeySettingsSchema?: () => string;
  151 + typeParameters?: () => WidgetTypeParameters;
  152 + useCustomDatasources?: () => boolean;
  153 + actionSources?: () => {[key: string]: WidgetActionSource};
  154 +
  155 + onInit?: () => void;
  156 + onDataUpdated?: () => void;
  157 + onResize?: () => void;
  158 + onEditModeChanged?: () => void;
  159 + onMobileModeChanged?: () => void;
  160 + onDestroy?: () => void;
  161 +}
  162 +
  163 +export function toWidgetInfo(widgetTypeEntity: WidgetType): WidgetInfo {
  164 + return {
  165 + widgetName: widgetTypeEntity.name,
  166 + alias: widgetTypeEntity.alias,
  167 + type: widgetTypeEntity.descriptor.type,
  168 + sizeX: widgetTypeEntity.descriptor.sizeX,
  169 + sizeY: widgetTypeEntity.descriptor.sizeY,
  170 + resources: widgetTypeEntity.descriptor.resources,
  171 + templateHtml: widgetTypeEntity.descriptor.templateHtml,
  172 + templateCss: widgetTypeEntity.descriptor.templateCss,
  173 + controllerScript: widgetTypeEntity.descriptor.controllerScript,
  174 + settingsSchema: widgetTypeEntity.descriptor.settingsSchema,
  175 + dataKeySettingsSchema: widgetTypeEntity.descriptor.dataKeySettingsSchema,
  176 + defaultConfig: widgetTypeEntity.descriptor.defaultConfig
  177 + };
  178 +}
  179 +
@@ -25,13 +25,14 @@ import { ActivatedRoute } from '@angular/router'; @@ -25,13 +25,14 @@ import { ActivatedRoute } from '@angular/router';
25 import { Authority } from '@shared/models/authority.enum'; 25 import { Authority } from '@shared/models/authority.enum';
26 import { NULL_UUID } from '@shared/models/id/has-uuid'; 26 import { NULL_UUID } from '@shared/models/id/has-uuid';
27 import { Observable } from 'rxjs'; 27 import { Observable } from 'rxjs';
28 -import { toWidgetInfo, Widget, widgetType } from '@app/shared/models/widget.models'; 28 +import { Widget, widgetType } from '@app/shared/models/widget.models';
29 import { WidgetService } from '@core/http/widget.service'; 29 import { WidgetService } from '@core/http/widget.service';
30 import { map, share } from 'rxjs/operators'; 30 import { map, share } from 'rxjs/operators';
31 import { DialogService } from '@core/services/dialog.service'; 31 import { DialogService } from '@core/services/dialog.service';
32 import { FooterFabButtons } from '@app/shared/components/footer-fab-buttons.component'; 32 import { FooterFabButtons } from '@app/shared/components/footer-fab-buttons.component';
33 import { DashboardCallbacks, WidgetsData } from '@home/models/dashboard-component.models'; 33 import { DashboardCallbacks, WidgetsData } from '@home/models/dashboard-component.models';
34 import { IAliasController } from '@app/core/api/widget-api.models'; 34 import { IAliasController } from '@app/core/api/widget-api.models';
  35 +import { toWidgetInfo } from '@home/models/widget-component.models';
35 36
36 @Component({ 37 @Component({
37 selector: 'tb-widget-library', 38 selector: 'tb-widget-library',
@@ -14,11 +14,9 @@ @@ -14,11 +14,9 @@
14 /// limitations under the License. 14 /// limitations under the License.
15 /// 15 ///
16 16
17 -import {BaseData} from '@shared/models/base-data';  
18 -import {TenantId} from '@shared/models/id/tenant-id';  
19 -import {WidgetsBundleId} from '@shared/models/id/widgets-bundle-id';  
20 -import {WidgetTypeId} from '@shared/models/id/widget-type-id';  
21 -import { AliasEntityType, EntityType, EntityTypeTranslation } from '@shared/models/entity-type.models'; 17 +import { BaseData } from '@shared/models/base-data';
  18 +import { TenantId } from '@shared/models/id/tenant-id';
  19 +import { WidgetTypeId } from '@shared/models/id/widget-type-id';
22 import { Timewindow } from '@shared/models/time/time.models'; 20 import { Timewindow } from '@shared/models/time/time.models';
23 21
24 export enum widgetType { 22 export enum widgetType {
@@ -139,86 +137,6 @@ export interface WidgetType extends BaseData<WidgetTypeId> { @@ -139,86 +137,6 @@ export interface WidgetType extends BaseData<WidgetTypeId> {
139 descriptor: WidgetTypeDescriptor; 137 descriptor: WidgetTypeDescriptor;
140 } 138 }
141 139
142 -export interface WidgetInfo extends WidgetTypeDescriptor, WidgetControllerDescriptor {  
143 - widgetName: string;  
144 - alias: string;  
145 - typeSettingsSchema?: string;  
146 - typeDataKeySettingsSchema?: string;  
147 -}  
148 -  
149 -export const MissingWidgetType: WidgetInfo = {  
150 - type: widgetType.latest,  
151 - widgetName: 'Widget type not found',  
152 - alias: 'undefined',  
153 - sizeX: 8,  
154 - sizeY: 6,  
155 - resources: [],  
156 - templateHtml: '<div class="tb-widget-error-container">' +  
157 - '<div translate class="tb-widget-error-msg">widget.widget-type-not-found</div>' +  
158 - '</div>',  
159 - templateCss: '',  
160 - controllerScript: 'self.onInit = function() {}',  
161 - settingsSchema: '{}\n',  
162 - dataKeySettingsSchema: '{}\n',  
163 - defaultConfig: '{\n' +  
164 - '"title": "Widget type not found",\n' +  
165 - '"datasources": [],\n' +  
166 - '"settings": {}\n' +  
167 - '}\n'  
168 -};  
169 -  
170 -export const ErrorWidgetType: WidgetInfo = {  
171 - type: widgetType.latest,  
172 - widgetName: 'Error loading widget',  
173 - alias: 'error',  
174 - sizeX: 8,  
175 - sizeY: 6,  
176 - resources: [],  
177 - templateHtml: '<div class="tb-widget-error-container">' +  
178 - '<div translate class="tb-widget-error-msg">widget.widget-type-load-error</div>',  
179 - templateCss: '',  
180 - controllerScript: 'self.onInit = function() {}',  
181 - settingsSchema: '{}\n',  
182 - dataKeySettingsSchema: '{}\n',  
183 - defaultConfig: '{\n' +  
184 - '"title": "Widget failed to load",\n' +  
185 - '"datasources": [],\n' +  
186 - '"settings": {}\n' +  
187 - '}\n'  
188 -};  
189 -  
190 -export interface WidgetTypeInstance {  
191 - getSettingsSchema?: () => string;  
192 - getDataKeySettingsSchema?: () => string;  
193 - typeParameters?: () => WidgetTypeParameters;  
194 - useCustomDatasources?: () => boolean;  
195 - actionSources?: () => {[key: string]: WidgetActionSource};  
196 -  
197 - onInit?: () => void;  
198 - onDataUpdated?: () => void;  
199 - onResize?: () => void;  
200 - onEditModeChanged?: () => void;  
201 - onMobileModeChanged?: () => void;  
202 - onDestroy?: () => void;  
203 -}  
204 -  
205 -export function toWidgetInfo(widgetTypeEntity: WidgetType): WidgetInfo {  
206 - return {  
207 - widgetName: widgetTypeEntity.name,  
208 - alias: widgetTypeEntity.alias,  
209 - type: widgetTypeEntity.descriptor.type,  
210 - sizeX: widgetTypeEntity.descriptor.sizeX,  
211 - sizeY: widgetTypeEntity.descriptor.sizeY,  
212 - resources: widgetTypeEntity.descriptor.resources,  
213 - templateHtml: widgetTypeEntity.descriptor.templateHtml,  
214 - templateCss: widgetTypeEntity.descriptor.templateCss,  
215 - controllerScript: widgetTypeEntity.descriptor.controllerScript,  
216 - settingsSchema: widgetTypeEntity.descriptor.settingsSchema,  
217 - dataKeySettingsSchema: widgetTypeEntity.descriptor.dataKeySettingsSchema,  
218 - defaultConfig: widgetTypeEntity.descriptor.defaultConfig  
219 - };  
220 -}  
221 -  
222 export enum LegendDirection { 140 export enum LegendDirection {
223 column = 'column', 141 column = 'column',
224 row = 'row' 142 row = 'row'