Showing
14 changed files
with
688 additions
and
594 deletions
... | ... | @@ -24,7 +24,11 @@ |
24 | 24 | "tsConfig": "src/tsconfig.app.json", |
25 | 25 | "assets": [ |
26 | 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 | 33 | "styles": [ |
30 | 34 | "src/styles.scss" |
... | ... | @@ -49,11 +53,7 @@ |
49 | 53 | "node_modules/ace-builds/src-min/snippets/css.js", |
50 | 54 | "node_modules/ace-builds/src-min/snippets/json.js", |
51 | 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 | 58 | "es5BrowserSupport": true, |
59 | 59 | "customWebpackConfig": { | ... | ... |
... | ... | @@ -34,7 +34,7 @@ module.exports = { |
34 | 34 | new CompressionPlugin({ |
35 | 35 | filename: "[path].gz[query]", |
36 | 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 | 38 | threshold: 10240, |
39 | 39 | minRatio: 0.8, |
40 | 40 | deleteOriginalAssets: false | ... | ... |
... | ... | @@ -14,47 +14,29 @@ |
14 | 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 | 25 | import { UtilsService } from '@core/services/utils.service'; |
33 | -import { isFunction, isUndefined } from '@core/utils'; | |
34 | 26 | import { TranslateService } from '@ngx-translate/core'; |
35 | -import { AuthPayload } from '@core/auth/auth.models'; | |
36 | -import cssjs from '@core/css/css'; | |
37 | 27 | import { ResourcesService } from '../services/resources.service'; |
38 | -import { catchError, map, switchMap } from 'rxjs/operators'; | |
39 | 28 | |
40 | 29 | @Injectable({ |
41 | 30 | providedIn: 'root' |
42 | 31 | }) |
43 | 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 | 34 | constructor( |
52 | 35 | private http: HttpClient, |
53 | 36 | private utils: UtilsService, |
54 | 37 | private resources: ResourcesService, |
55 | 38 | private translate: TranslateService |
56 | 39 | ) { |
57 | - this.cssParser.testMode = false; | |
58 | 40 | } |
59 | 41 | |
60 | 42 | public getWidgetBundles(pageLink: PageLink, ignoreErrors: boolean = false, |
... | ... | @@ -88,261 +70,4 @@ export class WidgetService { |
88 | 70 | return this.http.get<WidgetType>(`/api/widgetType?isSystem=${isSystem}&bundleAlias=${bundleAlias}&alias=${widgetTypeAlias}`, |
89 | 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 | 22 | Injector, |
23 | 23 | NgModule, |
24 | 24 | NgModuleRef, |
25 | - Type, | |
26 | - ViewEncapsulation | |
25 | + OnDestroy, | |
26 | + Type | |
27 | 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 | 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 | 53 | constructor(private compiler: Compiler, |
49 | 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 | 67 | // noinspection AngularInvalidImportedOrDeclaredSymbol,AngularInvalidEntryComponent |
56 | 68 | @NgModule({ |
57 | 69 | declarations: [comp], |
58 | 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 | 98 | const moduleData = this.dynamicComponentModulesMap.get(factory); |
83 | 99 | if (moduleData) { |
84 | 100 | moduleData.moduleRef.destroy(); |
... | ... | @@ -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 | 107 | // noinspection AngularMissingOrInvalidDeclarationInModule |
92 | - @Component({ | |
108 | + return Component({ | |
93 | 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 | 16 | |
17 | 17 | import { Inject, Injectable } from '@angular/core'; |
18 | 18 | import { WINDOW } from '@core/services/window.service'; |
19 | -import { WidgetInfo } from '@shared/models/widget.models'; | |
20 | 19 | import { ExceptionData } from '@app/shared/models/error.models'; |
21 | 20 | import { isUndefined } from '@core/utils'; |
22 | 21 | import { WindowMessage } from '@shared/models/window-message.model'; |
... | ... | @@ -30,7 +29,7 @@ export class UtilsService { |
30 | 29 | |
31 | 30 | iframeMode = false; |
32 | 31 | widgetEditMode = false; |
33 | - editWidgetInfo: WidgetInfo = null; | |
32 | + editWidgetInfo: any = null; | |
34 | 33 | |
35 | 34 | constructor(@Inject(WINDOW) private window: Window, |
36 | 35 | private translate: TranslateService) { | ... | ... |
... | ... | @@ -36,7 +36,8 @@ import { AddAttributeDialogComponent } from './attribute/add-attribute-dialog.co |
36 | 36 | import { EditAttributeValuePanelComponent } from './attribute/edit-attribute-value-panel.component'; |
37 | 37 | import { DashboardComponent } from '@home/components/dashboard/dashboard.component'; |
38 | 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 | 42 | @NgModule({ |
42 | 43 | entryComponents: [ |
... | ... | @@ -69,7 +70,8 @@ import { DynamicWidgetComponentFactoryService } from './widget/dynamic-widget-co |
69 | 70 | AddAttributeDialogComponent, |
70 | 71 | EditAttributeValuePanelComponent, |
71 | 72 | DashboardComponent, |
72 | - WidgetComponent | |
73 | + WidgetComponent, | |
74 | + LegendComponent | |
73 | 75 | ], |
74 | 76 | imports: [ |
75 | 77 | CommonModule, |
... | ... | @@ -88,10 +90,11 @@ import { DynamicWidgetComponentFactoryService } from './widget/dynamic-widget-co |
88 | 90 | AlarmDetailsDialogComponent, |
89 | 91 | AttributeTableComponent, |
90 | 92 | DashboardComponent, |
91 | - WidgetComponent | |
93 | + WidgetComponent, | |
94 | + LegendComponent | |
92 | 95 | ], |
93 | 96 | providers: [ |
94 | - DynamicWidgetComponentFactoryService | |
97 | + WidgetComponentService | |
95 | 98 | ] |
96 | 99 | }) |
97 | 100 | export class HomeComponentsModule { } | ... | ... |
... | ... | @@ -21,24 +21,13 @@ import { AppState } from '@core/core.state'; |
21 | 21 | import { WidgetContext, IDynamicWidgetComponent } from '@home/models/widget-component.models'; |
22 | 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 | 26 | @Input() |
35 | 27 | widgetContext: WidgetContext; |
36 | 28 | |
37 | 29 | @Input() |
38 | - widgetErrorData: ExceptionData; | |
39 | - | |
40 | - @Input() | |
41 | - loadingData: boolean; | |
30 | + errorMessages: string[]; | |
42 | 31 | |
43 | 32 | [key: string]: any; |
44 | 33 | |
... | ... | @@ -51,7 +40,7 @@ export abstract class DynamicWidgetComponent extends PageComponent implements ID |
51 | 40 | } |
52 | 41 | |
53 | 42 | ngOnDestroy(): void { |
54 | - console.log('Component destroyed!'); | |
43 | + console.log('Widget component destroyed!'); | |
55 | 44 | } |
56 | 45 | |
57 | 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 | 25 | ], |
26 | 26 | declarations: |
27 | 27 | [ |
28 | - LegendComponent | |
29 | 28 | ], |
30 | 29 | imports: [ |
31 | 30 | CommonModule, |
32 | 31 | SharedModule |
33 | 32 | ], |
34 | 33 | exports: [ |
35 | - LegendComponent | |
36 | 34 | ] |
37 | 35 | }) |
38 | 36 | export class WidgetComponentsModule { } | ... | ... |
... | ... | @@ -15,4 +15,16 @@ |
15 | 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 | 17 | import { |
18 | 18 | AfterViewInit, |
19 | 19 | Component, |
20 | - ComponentFactory, | |
21 | 20 | ComponentFactoryResolver, |
22 | 21 | ComponentRef, |
23 | 22 | ElementRef, |
... | ... | @@ -38,34 +37,37 @@ import { |
38 | 37 | LegendPosition, |
39 | 38 | Widget, |
40 | 39 | WidgetActionDescriptor, |
40 | + widgetActionSources, | |
41 | 41 | WidgetActionType, |
42 | - WidgetInfo, WidgetResource, | |
43 | - widgetType, | |
44 | - WidgetTypeInstance, | |
45 | - widgetActionSources | |
42 | + WidgetResource, | |
43 | + widgetType | |
46 | 44 | } from '@shared/models/widget.models'; |
47 | 45 | import { PageComponent } from '@shared/components/page.component'; |
48 | 46 | import { Store } from '@ngrx/store'; |
49 | 47 | import { AppState } from '@core/core.state'; |
50 | 48 | import { WidgetService } from '@core/http/widget.service'; |
51 | 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 | 51 | import { isDefined, objToBase64 } from '@core/utils'; |
56 | 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 | 60 | import { |
59 | 61 | EntityInfo, |
60 | 62 | IWidgetSubscription, |
61 | - SubscriptionInfo, | |
62 | - WidgetSubscriptionOptions, | |
63 | 63 | StateObject, |
64 | 64 | StateParams, |
65 | - WidgetSubscriptionContext | |
65 | + SubscriptionInfo, | |
66 | + WidgetSubscriptionContext, | |
67 | + WidgetSubscriptionOptions | |
66 | 68 | } from '@core/api/widget-api.models'; |
67 | 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 | 71 | import cssjs from '@core/css/css'; |
70 | 72 | import { ResourcesService } from '@core/services/resources.service'; |
71 | 73 | import { catchError, switchMap } from 'rxjs/operators'; |
... | ... | @@ -74,6 +76,7 @@ import { TimeService } from '@core/services/time.service'; |
74 | 76 | import { DeviceService } from '@app/core/http/device.service'; |
75 | 77 | import { AlarmService } from '@app/core/http/alarm.service'; |
76 | 78 | import { ExceptionData } from '@shared/models/error.models'; |
79 | +import { WidgetComponentService } from './widget-component.service'; | |
77 | 80 | |
78 | 81 | @Component({ |
79 | 82 | selector: 'tb-widget', |
... | ... | @@ -99,14 +102,22 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI |
99 | 102 | |
100 | 103 | widget: Widget; |
101 | 104 | widgetInfo: WidgetInfo; |
105 | + errorMessages: string[]; | |
102 | 106 | widgetContext: WidgetContext; |
103 | 107 | widgetType: any; |
104 | 108 | widgetTypeInstance: WidgetTypeInstance; |
105 | 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 | 122 | subscriptionContext: WidgetSubscriptionContext; |
112 | 123 | |
... | ... | @@ -120,7 +131,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI |
120 | 131 | constructor(protected store: Store<AppState>, |
121 | 132 | private route: ActivatedRoute, |
122 | 133 | private router: Router, |
123 | - private dynamicWidgetComponentFactoryService: DynamicWidgetComponentFactoryService, | |
134 | + private widgetComponentService: WidgetComponentService, | |
124 | 135 | private componentFactoryResolver: ComponentFactoryResolver, |
125 | 136 | private elementRef: ElementRef, |
126 | 137 | private injector: Injector, |
... | ... | @@ -134,8 +145,67 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI |
134 | 145 | } |
135 | 146 | |
136 | 147 | ngOnInit(): void { |
148 | + | |
149 | + this.loadingData = true; | |
150 | + | |
137 | 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 | 209 | const actionDescriptorsBySourceId: {[actionSourceId: string]: Array<WidgetActionDescriptor>} = {}; |
140 | 210 | if (this.widget.config.actions) { |
141 | 211 | for (const actionSourceId of Object.keys(this.widget.config.actions)) { |
... | ... | @@ -244,10 +314,15 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI |
244 | 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 | 318 | (widgetInfo) => { |
249 | 319 | this.widgetInfo = widgetInfo; |
250 | 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 | 438 | } |
364 | 439 | |
365 | 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 | 446 | private destroyDynamicWidgetComponent() { |
... | ... | @@ -381,127 +451,37 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI |
381 | 451 | if (this.dynamicWidgetComponentRef) { |
382 | 452 | this.dynamicWidgetComponentRef.destroy(); |
383 | 453 | } |
384 | - if (this.dynamicWidgetComponentFactory) { | |
385 | - this.dynamicWidgetComponentFactoryService.destroyDynamicWidgetComponentFactory(this.dynamicWidgetComponentFactory); | |
386 | - } | |
387 | 454 | } |
388 | 455 | |
389 | 456 | private handleWidgetException(e) { |
390 | 457 | console.error(e); |
391 | 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 | 487 | private createSubscription(options: WidgetSubscriptionOptions, subscribe: boolean): Observable<IWidgetSubscription> { | ... | ... |
... | ... | @@ -16,23 +16,30 @@ |
16 | 16 | |
17 | 17 | import { ExceptionData } from '@shared/models/error.models'; |
18 | 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 | 30 | import { Timewindow } from '@shared/models/time/time.models'; |
21 | 31 | import { |
22 | 32 | EntityInfo, |
23 | - IWidgetSubscription, | |
24 | - SubscriptionInfo, | |
25 | - WidgetSubscriptionOptions, | |
26 | - IStateController, | |
27 | 33 | IAliasController, |
28 | - TimewindowFunctions, | |
29 | - WidgetSubscriptionApi, | |
34 | + IStateController, | |
35 | + IWidgetSubscription, | |
36 | + IWidgetUtils, | |
30 | 37 | RpcApi, |
38 | + TimewindowFunctions, | |
31 | 39 | WidgetActionsApi, |
32 | - IWidgetUtils | |
40 | + WidgetSubscriptionApi | |
33 | 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 | 44 | export interface IWidgetAction { |
38 | 45 | name: string; |
... | ... | @@ -49,13 +56,6 @@ export interface WidgetAction extends IWidgetAction { |
49 | 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 | 59 | export interface WidgetContext { |
60 | 60 | inited?: boolean; |
61 | 61 | $container?: any; |
... | ... | @@ -87,3 +87,93 @@ export interface WidgetContext { |
87 | 87 | customHeaderActions?: Array<WidgetHeaderAction>; |
88 | 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 | 25 | import { Authority } from '@shared/models/authority.enum'; |
26 | 26 | import { NULL_UUID } from '@shared/models/id/has-uuid'; |
27 | 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 | 29 | import { WidgetService } from '@core/http/widget.service'; |
30 | 30 | import { map, share } from 'rxjs/operators'; |
31 | 31 | import { DialogService } from '@core/services/dialog.service'; |
32 | 32 | import { FooterFabButtons } from '@app/shared/components/footer-fab-buttons.component'; |
33 | 33 | import { DashboardCallbacks, WidgetsData } from '@home/models/dashboard-component.models'; |
34 | 34 | import { IAliasController } from '@app/core/api/widget-api.models'; |
35 | +import { toWidgetInfo } from '@home/models/widget-component.models'; | |
35 | 36 | |
36 | 37 | @Component({ |
37 | 38 | selector: 'tb-widget-library', | ... | ... |
... | ... | @@ -14,11 +14,9 @@ |
14 | 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 | 20 | import { Timewindow } from '@shared/models/time/time.models'; |
23 | 21 | |
24 | 22 | export enum widgetType { |
... | ... | @@ -139,86 +137,6 @@ export interface WidgetType extends BaseData<WidgetTypeId> { |
139 | 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 | 140 | export enum LegendDirection { |
223 | 141 | column = 'column', |
224 | 142 | row = 'row' | ... | ... |