15 changed files with 743 additions and 649 deletions
@ -0,0 +1,113 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { |
|||
Compiler, |
|||
Component, |
|||
ComponentFactory, |
|||
Injectable, |
|||
Injector, |
|||
NgModule, |
|||
NgModuleRef, |
|||
OnDestroy, |
|||
Type |
|||
} from '@angular/core'; |
|||
import { Observable, ReplaySubject } from 'rxjs'; |
|||
import { CommonModule } from '@angular/common'; |
|||
|
|||
export abstract class DynamicComponentModule implements OnDestroy { |
|||
|
|||
ngOnDestroy(): void { |
|||
console.log('Module destroyed!'); |
|||
} |
|||
|
|||
} |
|||
|
|||
interface DynamicComponentModuleData { |
|||
moduleRef: NgModuleRef<DynamicComponentModule>; |
|||
moduleType: Type<DynamicComponentModule>; |
|||
} |
|||
|
|||
@Injectable( |
|||
{ |
|||
providedIn: 'root' |
|||
} |
|||
) |
|||
export class DynamicComponentFactoryService { |
|||
|
|||
private dynamicComponentModulesMap = new Map<ComponentFactory<any>, DynamicComponentModuleData>(); |
|||
|
|||
constructor(private compiler: Compiler, |
|||
private injector: Injector) { |
|||
} |
|||
|
|||
public createDynamicComponentFactory<T>( |
|||
componentType: Type<T>, |
|||
template: string, |
|||
modules?: Type<any>[]): Observable<ComponentFactory<T>> { |
|||
const dymamicComponentFactorySubject = new ReplaySubject<ComponentFactory<T>>(); |
|||
const comp = this.createDynamicComponent(componentType, template); |
|||
let moduleImports = [CommonModule]; |
|||
if (modules) { |
|||
moduleImports = [...moduleImports, ...modules]; |
|||
} |
|||
// noinspection AngularInvalidImportedOrDeclaredSymbol,AngularInvalidEntryComponent
|
|||
@NgModule({ |
|||
declarations: [comp], |
|||
entryComponents: [comp], |
|||
imports: moduleImports |
|||
}) |
|||
class DynamicComponentInstanceModule extends DynamicComponentModule {} |
|||
try { |
|||
this.compiler.compileModuleAsync(DynamicComponentInstanceModule).then( |
|||
(module) => { |
|||
const moduleRef = module.create(this.injector); |
|||
const factory = moduleRef.componentFactoryResolver.resolveComponentFactory(comp); |
|||
this.dynamicComponentModulesMap.set(factory, { |
|||
moduleRef, |
|||
moduleType: module.moduleType |
|||
}); |
|||
dymamicComponentFactorySubject.next(factory); |
|||
dymamicComponentFactorySubject.complete(); |
|||
} |
|||
).catch( |
|||
(e) => { |
|||
dymamicComponentFactorySubject.error(e); |
|||
} |
|||
); |
|||
} catch (e) { |
|||
dymamicComponentFactorySubject.error(e); |
|||
} |
|||
return dymamicComponentFactorySubject.asObservable(); |
|||
} |
|||
|
|||
public destroyDynamicComponentFactory<T>(factory: ComponentFactory<T>) { |
|||
const moduleData = this.dynamicComponentModulesMap.get(factory); |
|||
if (moduleData) { |
|||
moduleData.moduleRef.destroy(); |
|||
this.compiler.clearCacheFor(moduleData.moduleType); |
|||
this.dynamicComponentModulesMap.delete(factory); |
|||
} |
|||
} |
|||
|
|||
private createDynamicComponent<T>(componentType: Type<T>, template: string): Type<T> { |
|||
// noinspection AngularMissingOrInvalidDeclarationInModule
|
|||
return Component({ |
|||
template |
|||
})(componentType); |
|||
} |
|||
|
|||
} |
|||
@ -1,100 +0,0 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { |
|||
Compiler, |
|||
Component, |
|||
ComponentFactory, |
|||
Injectable, |
|||
Injector, |
|||
NgModule, |
|||
NgModuleRef, |
|||
Type, |
|||
ViewEncapsulation |
|||
} from '@angular/core'; |
|||
import { |
|||
DynamicWidgetComponent, |
|||
DynamicWidgetComponentModule |
|||
} from '@home/components/widget/dynamic-widget.component'; |
|||
import { CommonModule } from '@angular/common'; |
|||
import { SharedModule } from '@shared/shared.module'; |
|||
import { Observable, ReplaySubject } from 'rxjs'; |
|||
import { HomeComponentsModule } from '../home-components.module'; |
|||
import { WidgetComponentsModule } from './widget-components.module'; |
|||
|
|||
interface DynamicWidgetComponentModuleData { |
|||
moduleRef: NgModuleRef<DynamicWidgetComponentModule>; |
|||
moduleType: Type<DynamicWidgetComponentModule>; |
|||
} |
|||
|
|||
@Injectable() |
|||
export class DynamicWidgetComponentFactoryService { |
|||
|
|||
private dynamicComponentModulesMap = new Map<ComponentFactory<DynamicWidgetComponent>, DynamicWidgetComponentModuleData>(); |
|||
|
|||
constructor(private compiler: Compiler, |
|||
private injector: Injector) { |
|||
} |
|||
|
|||
public createDynamicWidgetComponentFactory(template: string): Observable<ComponentFactory<DynamicWidgetComponent>> { |
|||
const dymamicWidgetComponentFactorySubject = new ReplaySubject<ComponentFactory<DynamicWidgetComponent>>(); |
|||
const comp = this.createDynamicWidgetComponent(template); |
|||
// noinspection AngularInvalidImportedOrDeclaredSymbol,AngularInvalidEntryComponent
|
|||
@NgModule({ |
|||
declarations: [comp], |
|||
entryComponents: [comp], |
|||
imports: [CommonModule, SharedModule, WidgetComponentsModule], |
|||
}) |
|||
class DynamicWidgetComponentInstanceModule extends DynamicWidgetComponentModule {} |
|||
this.compiler.compileModuleAsync(DynamicWidgetComponentInstanceModule).then( |
|||
(module) => { |
|||
const moduleRef = module.create(this.injector); |
|||
const factory = moduleRef.componentFactoryResolver.resolveComponentFactory(comp); |
|||
this.dynamicComponentModulesMap.set(factory, { |
|||
moduleRef, |
|||
moduleType: module.moduleType |
|||
}); |
|||
dymamicWidgetComponentFactorySubject.next(factory); |
|||
dymamicWidgetComponentFactorySubject.complete(); |
|||
} |
|||
).catch( |
|||
(e) => { |
|||
dymamicWidgetComponentFactorySubject.error(`Failed to create dynamic widget component factory: ${e}`); |
|||
} |
|||
); |
|||
return dymamicWidgetComponentFactorySubject.asObservable(); |
|||
} |
|||
|
|||
public destroyDynamicWidgetComponentFactory(factory: ComponentFactory<DynamicWidgetComponent>) { |
|||
const moduleData = this.dynamicComponentModulesMap.get(factory); |
|||
if (moduleData) { |
|||
moduleData.moduleRef.destroy(); |
|||
this.compiler.clearCacheFor(moduleData.moduleType); |
|||
this.dynamicComponentModulesMap.delete(factory); |
|||
} |
|||
} |
|||
|
|||
private createDynamicWidgetComponent(template: string): Type<DynamicWidgetComponent> { |
|||
// noinspection AngularMissingOrInvalidDeclarationInModule
|
|||
@Component({ |
|||
template |
|||
}) |
|||
class DynamicWidgetInstanceComponent extends DynamicWidgetComponent { } |
|||
|
|||
return DynamicWidgetInstanceComponent; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,366 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Injectable } from '@angular/core'; |
|||
import { DynamicComponentFactoryService } from '@core/services/dynamic-component-factory.service'; |
|||
import { WidgetService } from '@core/http/widget.service'; |
|||
import { forkJoin, Observable, of, ReplaySubject, Subject, throwError } from 'rxjs'; |
|||
import { WidgetInfo, MissingWidgetType, toWidgetInfo, WidgetTypeInstance, ErrorWidgetType } from '@home/models/widget-component.models'; |
|||
import cssjs from '@core/css/css'; |
|||
import { UtilsService } from '@core/services/utils.service'; |
|||
import { ResourcesService } from '@core/services/resources.service'; |
|||
import { |
|||
widgetActionSources, |
|||
WidgetControllerDescriptor, |
|||
WidgetType |
|||
} from '@shared/models/widget.models'; |
|||
import { catchError, switchMap, map, mergeMap } from 'rxjs/operators'; |
|||
import { isFunction, isUndefined } from '@core/utils'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
import { DynamicWidgetComponent } from '@home/components/widget/dynamic-widget.component'; |
|||
import { SharedModule } from '@shared/shared.module'; |
|||
import { WidgetComponentsModule } from '@home/components/widget/widget-components.module'; |
|||
|
|||
@Injectable() |
|||
export class WidgetComponentService { |
|||
|
|||
private cssParser = new cssjs(); |
|||
|
|||
private widgetsInfoInMemoryCache = new Map<string, WidgetInfo>(); |
|||
|
|||
private widgetsInfoFetchQueue = new Map<string, Array<Subject<WidgetInfo>>>(); |
|||
|
|||
private init$: Observable<any>; |
|||
|
|||
private missingWidgetType: WidgetInfo; |
|||
private errorWidgetType: WidgetInfo; |
|||
|
|||
constructor(private dynamicComponentFactoryService: DynamicComponentFactoryService, |
|||
private widgetService: WidgetService, |
|||
private utils: UtilsService, |
|||
private resources: ResourcesService, |
|||
private translate: TranslateService) { |
|||
this.cssParser.testMode = false; |
|||
this.init(); |
|||
} |
|||
|
|||
private init(): Observable<any> { |
|||
if (this.init$) { |
|||
return this.init$; |
|||
} else { |
|||
this.missingWidgetType = {...MissingWidgetType}; |
|||
this.errorWidgetType = {...ErrorWidgetType}; |
|||
const initSubject = new ReplaySubject(); |
|||
this.init$ = initSubject.asObservable(); |
|||
const loadDefaultWidgetInfoTasks = [ |
|||
this.loadWidgetResources(this.missingWidgetType, 'global-widget-missing-type'), |
|||
this.loadWidgetResources(this.errorWidgetType, 'global-widget-error-type'), |
|||
]; |
|||
forkJoin(loadDefaultWidgetInfoTasks).subscribe( |
|||
() => { |
|||
initSubject.next(); |
|||
}, |
|||
() => { |
|||
console.error('Failed to load default widget types!'); |
|||
initSubject.error('Failed to load default widget types!'); |
|||
} |
|||
); |
|||
return this.init$; |
|||
} |
|||
} |
|||
|
|||
public getWidgetInfo(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): Observable<WidgetInfo> { |
|||
return this.init().pipe( |
|||
mergeMap(() => this.getWidgetInfoInternal(bundleAlias, widgetTypeAlias, isSystem)) |
|||
); |
|||
} |
|||
|
|||
private getWidgetInfoInternal(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): Observable<WidgetInfo> { |
|||
const widgetInfoSubject = new ReplaySubject<WidgetInfo>(); |
|||
const widgetInfo = this.getWidgetInfoFromCache(bundleAlias, widgetTypeAlias, isSystem); |
|||
if (widgetInfo) { |
|||
widgetInfoSubject.next(widgetInfo); |
|||
widgetInfoSubject.complete(); |
|||
} else { |
|||
if (this.utils.widgetEditMode) { |
|||
// TODO:
|
|||
} else { |
|||
const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem); |
|||
let fetchQueue = this.widgetsInfoFetchQueue.get(key); |
|||
if (fetchQueue) { |
|||
fetchQueue.push(widgetInfoSubject); |
|||
} else { |
|||
fetchQueue = new Array<Subject<WidgetInfo>>(); |
|||
this.widgetsInfoFetchQueue.set(key, fetchQueue); |
|||
this.widgetService.getWidgetType(bundleAlias, widgetTypeAlias, isSystem).subscribe( |
|||
(widgetType) => { |
|||
this.loadWidget(widgetType, bundleAlias, isSystem, widgetInfoSubject); |
|||
}, |
|||
() => { |
|||
widgetInfoSubject.next(this.missingWidgetType); |
|||
widgetInfoSubject.complete(); |
|||
this.resolveWidgetsInfoFetchQueue(key, this.missingWidgetType); |
|||
} |
|||
); |
|||
} |
|||
} |
|||
} |
|||
return widgetInfoSubject.asObservable(); |
|||
} |
|||
|
|||
private loadWidget(widgetType: WidgetType, bundleAlias: string, isSystem: boolean, widgetInfoSubject: Subject<WidgetInfo>) { |
|||
const widgetInfo = toWidgetInfo(widgetType); |
|||
const key = this.createWidgetInfoCacheKey(bundleAlias, widgetInfo.alias, isSystem); |
|||
let widgetControllerDescriptor: WidgetControllerDescriptor = null; |
|||
try { |
|||
widgetControllerDescriptor = this.createWidgetControllerDescriptor(widgetInfo, key); |
|||
} catch (e) { |
|||
const details = this.utils.parseException(e); |
|||
const errorMessage = `Failed to compile widget script. \n Error: ${details.message}`; |
|||
this.processWidgetLoadError([errorMessage], key, widgetInfoSubject); |
|||
} |
|||
if (widgetControllerDescriptor) { |
|||
const widgetNamespace = `widget-type-${(isSystem ? 'sys-' : '')}${bundleAlias}-${widgetInfo.alias}`; |
|||
this.loadWidgetResources(widgetInfo, widgetNamespace).subscribe( |
|||
() => { |
|||
if (widgetControllerDescriptor.settingsSchema) { |
|||
widgetInfo.typeSettingsSchema = widgetControllerDescriptor.settingsSchema; |
|||
} |
|||
if (widgetControllerDescriptor.dataKeySettingsSchema) { |
|||
widgetInfo.typeDataKeySettingsSchema = widgetControllerDescriptor.dataKeySettingsSchema; |
|||
} |
|||
widgetInfo.typeParameters = widgetControllerDescriptor.typeParameters; |
|||
widgetInfo.actionSources = widgetControllerDescriptor.actionSources; |
|||
widgetInfo.widgetTypeFunction = widgetControllerDescriptor.widgetTypeFunction; |
|||
this.putWidgetInfoToCache(widgetInfo, bundleAlias, widgetInfo.alias, isSystem); |
|||
if (widgetInfoSubject) { |
|||
widgetInfoSubject.next(widgetInfo); |
|||
widgetInfoSubject.complete(); |
|||
} |
|||
this.resolveWidgetsInfoFetchQueue(key, widgetInfo); |
|||
}, |
|||
(errorMessages: string[]) => { |
|||
this.processWidgetLoadError(errorMessages, key, widgetInfoSubject); |
|||
} |
|||
); |
|||
} |
|||
} |
|||
|
|||
private loadWidgetResources(widgetInfo: WidgetInfo, widgetNamespace: string): Observable<any> { |
|||
this.cssParser.cssPreviewNamespace = widgetNamespace; |
|||
this.cssParser.createStyleElement(widgetNamespace, widgetInfo.templateCss); |
|||
const resourceTasks: Observable<string>[] = []; |
|||
if (widgetInfo.resources.length > 0) { |
|||
widgetInfo.resources.forEach((resource) => { |
|||
resourceTasks.push( |
|||
this.resources.loadResource(resource.url).pipe( |
|||
catchError(e => of(`Failed to load widget resource: '${resource.url}'`)) |
|||
) |
|||
); |
|||
}); |
|||
} |
|||
resourceTasks.push( |
|||
this.dynamicComponentFactoryService.createDynamicComponentFactory( |
|||
class DynamicWidgetComponentInstance extends DynamicWidgetComponent {}, |
|||
widgetInfo.templateHtml, |
|||
[SharedModule, WidgetComponentsModule] |
|||
).pipe( |
|||
map((factory) => { |
|||
widgetInfo.componentFactory = factory; |
|||
return null; |
|||
}), |
|||
catchError(e => { |
|||
const details = this.utils.parseException(e); |
|||
const errorMessage = `Failed to compile widget html. \n Error: ${details.message}`; |
|||
return of(errorMessage); |
|||
}) |
|||
) |
|||
); |
|||
return forkJoin(resourceTasks).pipe( |
|||
switchMap(msgs => { |
|||
let errors: string[]; |
|||
if (msgs && msgs.length) { |
|||
errors = msgs.filter(msg => msg && msg.length > 0); |
|||
} |
|||
if (errors && errors.length) { |
|||
return throwError(errors); |
|||
} else { |
|||
return of(null); |
|||
} |
|||
} |
|||
)); |
|||
} |
|||
|
|||
private createWidgetControllerDescriptor(widgetInfo: WidgetInfo, name: string): WidgetControllerDescriptor { |
|||
let widgetTypeFunctionBody = `return function ${name} (ctx) {\n` + |
|||
' var self = this;\n' + |
|||
' self.ctx = ctx;\n\n'; /*+ |
|||
|
|||
' self.onInit = function() {\n\n' + |
|||
|
|||
' }\n\n' + |
|||
|
|||
' self.onDataUpdated = function() {\n\n' + |
|||
|
|||
' }\n\n' + |
|||
|
|||
' self.useCustomDatasources = function() {\n\n' + |
|||
|
|||
' }\n\n' + |
|||
|
|||
' self.typeParameters = function() {\n\n' + |
|||
return { |
|||
useCustomDatasources: false, |
|||
maxDatasources: -1, //unlimited
|
|||
maxDataKeys: -1, //unlimited
|
|||
dataKeysOptional: false, |
|||
stateData: false |
|||
}; |
|||
' }\n\n' + |
|||
|
|||
' self.actionSources = function() {\n\n' + |
|||
return { |
|||
'headerButton': { |
|||
name: 'Header button', |
|||
multiple: true |
|||
} |
|||
}; |
|||
}\n\n' + |
|||
' self.onResize = function() {\n\n' + |
|||
|
|||
' }\n\n' + |
|||
|
|||
' self.onEditModeChanged = function() {\n\n' + |
|||
|
|||
' }\n\n' + |
|||
|
|||
' self.onMobileModeChanged = function() {\n\n' + |
|||
|
|||
' }\n\n' + |
|||
|
|||
' self.getSettingsSchema = function() {\n\n' + |
|||
|
|||
' }\n\n' + |
|||
|
|||
' self.getDataKeySettingsSchema = function() {\n\n' + |
|||
|
|||
' }\n\n' + |
|||
|
|||
' self.onDestroy = function() {\n\n' + |
|||
|
|||
' }\n\n' + |
|||
'}';*/ |
|||
|
|||
widgetTypeFunctionBody += widgetInfo.controllerScript; |
|||
widgetTypeFunctionBody += '\n};\n'; |
|||
|
|||
try { |
|||
|
|||
const widgetTypeFunction = new Function(widgetTypeFunctionBody); |
|||
const widgetType = widgetTypeFunction.apply(this); |
|||
const widgetTypeInstance: WidgetTypeInstance = new widgetType(); |
|||
const result: WidgetControllerDescriptor = { |
|||
widgetTypeFunction: widgetType |
|||
}; |
|||
if (isFunction(widgetTypeInstance.getSettingsSchema)) { |
|||
result.settingsSchema = widgetTypeInstance.getSettingsSchema(); |
|||
} |
|||
if (isFunction(widgetTypeInstance.getDataKeySettingsSchema)) { |
|||
result.dataKeySettingsSchema = widgetTypeInstance.getDataKeySettingsSchema(); |
|||
} |
|||
if (isFunction(widgetTypeInstance.typeParameters)) { |
|||
result.typeParameters = widgetTypeInstance.typeParameters(); |
|||
} else { |
|||
result.typeParameters = {}; |
|||
} |
|||
if (isFunction(widgetTypeInstance.useCustomDatasources)) { |
|||
result.typeParameters.useCustomDatasources = widgetTypeInstance.useCustomDatasources(); |
|||
} else { |
|||
result.typeParameters.useCustomDatasources = false; |
|||
} |
|||
if (isUndefined(result.typeParameters.maxDatasources)) { |
|||
result.typeParameters.maxDatasources = -1; |
|||
} |
|||
if (isUndefined(result.typeParameters.maxDataKeys)) { |
|||
result.typeParameters.maxDataKeys = -1; |
|||
} |
|||
if (isUndefined(result.typeParameters.dataKeysOptional)) { |
|||
result.typeParameters.dataKeysOptional = false; |
|||
} |
|||
if (isUndefined(result.typeParameters.stateData)) { |
|||
result.typeParameters.stateData = false; |
|||
} |
|||
if (isFunction(widgetTypeInstance.actionSources)) { |
|||
result.actionSources = widgetTypeInstance.actionSources(); |
|||
} else { |
|||
result.actionSources = {}; |
|||
} |
|||
for (const actionSourceId of Object.keys(widgetActionSources)) { |
|||
result.actionSources[actionSourceId] = {...widgetActionSources[actionSourceId]}; |
|||
result.actionSources[actionSourceId].name = this.translate.instant(result.actionSources[actionSourceId].name); |
|||
} |
|||
return result; |
|||
} catch (e) { |
|||
this.utils.processWidgetException(e); |
|||
throw e; |
|||
} |
|||
} |
|||
|
|||
private processWidgetLoadError(errorMessages: string[], cacheKey: string, widgetInfoSubject: Subject<WidgetInfo>) { |
|||
if (widgetInfoSubject) { |
|||
widgetInfoSubject.error({ |
|||
widgetInfo: this.errorWidgetType, |
|||
errorMessages |
|||
}); |
|||
} |
|||
this.resolveWidgetsInfoFetchQueue(cacheKey, this.errorWidgetType, errorMessages); |
|||
} |
|||
|
|||
private resolveWidgetsInfoFetchQueue(key: string, widgetInfo: WidgetInfo, errorMessages?: string[]) { |
|||
const fetchQueue = this.widgetsInfoFetchQueue.get(key); |
|||
if (fetchQueue) { |
|||
fetchQueue.forEach(subject => { |
|||
if (!errorMessages) { |
|||
subject.next(widgetInfo); |
|||
subject.complete(); |
|||
} else { |
|||
subject.error({ |
|||
widgetInfo, |
|||
errorMessages |
|||
}); |
|||
} |
|||
}); |
|||
this.widgetsInfoFetchQueue.delete(key); |
|||
} |
|||
} |
|||
|
|||
// Cache functions
|
|||
|
|||
private createWidgetInfoCacheKey(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): string { |
|||
return `${isSystem ? 'sys_' : ''}${bundleAlias}_${widgetTypeAlias}`; |
|||
} |
|||
|
|||
private getWidgetInfoFromCache(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): WidgetInfo | undefined { |
|||
const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem); |
|||
return this.widgetsInfoInMemoryCache.get(key); |
|||
} |
|||
|
|||
private putWidgetInfoToCache(widgetInfo: WidgetInfo, bundleAlias: string, widgetTypeAlias: string, isSystem: boolean) { |
|||
const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem); |
|||
this.widgetsInfoInMemoryCache.set(key, widgetInfo); |
|||
} |
|||
|
|||
} |
|||
Loading…
Reference in new issue