diff --git a/ui-ngx/extra-webpack.config.js b/ui-ngx/extra-webpack.config.js index 1be975de4f..f445eccafb 100644 --- a/ui-ngx/extra-webpack.config.js +++ b/ui-ngx/extra-webpack.config.js @@ -60,21 +60,23 @@ module.exports = (config, options) => { ); const index = config.plugins.findIndex(p => p instanceof ngWebpack.ivy.AngularWebpackPlugin || p instanceof ngWebpack.AngularWebpackPlugin); - const angularWebpackPlugin = config.plugins[index]; - - addTransformerToAngularWebpackPlugin(angularWebpackPlugin, keysTransformer); + let angularWebpackPlugin = config.plugins[index]; if (config.mode === 'production') { const angularCompilerOptions = angularWebpackPlugin.pluginOptions; angularCompilerOptions.emitClassMetadata = true; angularCompilerOptions.emitNgModuleScope = true; config.plugins.splice(index, 1); - config.plugins.push(new ngWebpack.ivy.AngularWebpackPlugin(angularCompilerOptions)); + angularWebpackPlugin = new ngWebpack.ivy.AngularWebpackPlugin(angularCompilerOptions); + config.plugins.push(angularWebpackPlugin); const javascriptOptimizerOptions = config.optimization.minimizer[1].options; delete javascriptOptimizerOptions.define.ngJitMode; config.optimization.minimizer.splice(1, 1); config.optimization.minimizer.push(new JavaScriptOptimizerPlugin(javascriptOptimizerOptions)); } + + addTransformerToAngularWebpackPlugin(angularWebpackPlugin, keysTransformer); + return config; }; diff --git a/ui-ngx/src/app/core/http/rule-chain.service.ts b/ui-ngx/src/app/core/http/rule-chain.service.ts index 1b77bf4ead..6b0f3c3f22 100644 --- a/ui-ngx/src/app/core/http/rule-chain.service.ts +++ b/ui-ngx/src/app/core/http/rule-chain.service.ts @@ -219,7 +219,7 @@ export class RuleChainService { map((res) => { if (nodeDefinition.configDirective && nodeDefinition.configDirective.length) { const selector = snakeCase(nodeDefinition.configDirective, '-'); - const componentFactory = res.find((factory) => + const componentFactory = res.factories.find((factory) => factory.selector === selector); if (componentFactory) { this.ruleNodeConfigFactories[nodeDefinition.configDirective] = componentFactory; diff --git a/ui-ngx/src/app/core/services/resources.service.ts b/ui-ngx/src/app/core/services/resources.service.ts index 763617eda9..688c0da31f 100644 --- a/ui-ngx/src/app/core/services/resources.service.ts +++ b/ui-ngx/src/app/core/services/resources.service.ts @@ -30,6 +30,11 @@ import { IModulesMap } from '@modules/common/modules-map.models'; declare const System; +export interface ModulesWithFactories { + modules: Type[]; + factories: ComponentFactory[]; +} + @Injectable({ providedIn: 'root' }) @@ -37,7 +42,7 @@ export class ResourcesService { private loadedResources: { [url: string]: ReplaySubject } = {}; private loadedModules: { [url: string]: ReplaySubject[]> } = {}; - private loadedFactories: { [url: string]: ReplaySubject[]> } = {}; + private loadedModulesAndFactories: { [url: string]: ReplaySubject } = {}; private anchor = this.document.getElementsByTagName('head')[0] || this.document.getElementsByTagName('body')[0]; @@ -64,13 +69,13 @@ export class ResourcesService { return this.loadResourceByType(fileType, url); } - public loadFactories(url: string, modulesMap: IModulesMap): Observable[]> { - if (this.loadedFactories[url]) { - return this.loadedFactories[url].asObservable(); + public loadFactories(url: string, modulesMap: IModulesMap): Observable { + if (this.loadedModulesAndFactories[url]) { + return this.loadedModulesAndFactories[url].asObservable(); } modulesMap.init(); - const subject = new ReplaySubject[]>(); - this.loadedFactories[url] = subject; + const subject = new ReplaySubject(); + this.loadedModulesAndFactories[url] = subject; import('@angular/compiler').then( () => { System.import(url).then( @@ -88,25 +93,29 @@ export class ResourcesService { c.ngModuleFactory.create(this.injector); componentFactories.push(...c.componentFactories); } - this.loadedFactories[url].next(componentFactories); - this.loadedFactories[url].complete(); + const modulesWithFactories: ModulesWithFactories = { + modules, + factories: componentFactories + }; + this.loadedModulesAndFactories[url].next(modulesWithFactories); + this.loadedModulesAndFactories[url].complete(); } catch (e) { - this.loadedFactories[url].error(new Error(`Unable to init module from url: ${url}`)); - delete this.loadedFactories[url]; + this.loadedModulesAndFactories[url].error(new Error(`Unable to init module from url: ${url}`)); + delete this.loadedModulesAndFactories[url]; } }, (e) => { - this.loadedFactories[url].error(new Error(`Unable to compile module from url: ${url}`)); - delete this.loadedFactories[url]; + this.loadedModulesAndFactories[url].error(new Error(`Unable to compile module from url: ${url}`)); + delete this.loadedModulesAndFactories[url]; }); } else { - this.loadedFactories[url].error(new Error(`Module '${url}' doesn't have default export!`)); - delete this.loadedFactories[url]; + this.loadedModulesAndFactories[url].error(new Error(`Module '${url}' doesn't have default export!`)); + delete this.loadedModulesAndFactories[url]; } }, (e) => { - this.loadedFactories[url].error(new Error(`Unable to load module from url: ${url}`)); - delete this.loadedFactories[url]; + this.loadedModulesAndFactories[url].error(new Error(`Unable to load module from url: ${url}`)); + delete this.loadedModulesAndFactories[url]; } ); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/led-indicator-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/led-indicator-widget-settings.component.ts index d940e0f64a..8ee8ebe127 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/led-indicator-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/led-indicator-widget-settings.component.ts @@ -32,7 +32,7 @@ export class LedIndicatorWidgetSettingsComponent extends WidgetSettingsComponent functionScopeVariables = this.widgetService.getWidgetScopeVariables(); get targetDeviceAliasId(): string { - const aliasIds = this.widget.config.targetDeviceAliasIds; + const aliasIds = this.widget?.config?.targetDeviceAliasIds; if (aliasIds && aliasIds.length) { return aliasIds[0]; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/round-switch-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/round-switch-widget-settings.component.ts index cfbdd757f7..cf7ee3bbb9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/round-switch-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/round-switch-widget-settings.component.ts @@ -37,7 +37,7 @@ export class RoundSwitchWidgetSettingsComponent extends WidgetSettingsComponent } get targetDeviceAliasId(): string { - const aliasIds = this.widget.config.targetDeviceAliasIds; + const aliasIds = this.widget?.config?.targetDeviceAliasIds; if (aliasIds && aliasIds.length) { return aliasIds[0]; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slide-toggle-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slide-toggle-widget-settings.component.ts index 47cf13da53..f95473dbb9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slide-toggle-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/slide-toggle-widget-settings.component.ts @@ -37,7 +37,7 @@ export class SlideToggleWidgetSettingsComponent extends WidgetSettingsComponent } get targetDeviceAliasId(): string { - const aliasIds = this.widget.config.targetDeviceAliasIds; + const aliasIds = this.widget?.config?.targetDeviceAliasIds; if (aliasIds && aliasIds.length) { return aliasIds[0]; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/switch-control-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/switch-control-widget-settings.component.ts index 33e928f2c5..65f09f2dd8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/switch-control-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/switch-control-widget-settings.component.ts @@ -37,7 +37,7 @@ export class SwitchControlWidgetSettingsComponent extends WidgetSettingsComponen } get targetDeviceAliasId(): string { - const aliasIds = this.widget.config.targetDeviceAliasIds; + const aliasIds = this.widget?.config?.targetDeviceAliasIds; if (aliasIds && aliasIds.length) { return aliasIds[0]; } diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts b/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts index e4760c9185..67541e82cd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Inject, Injectable, Optional, Type } from '@angular/core'; +import { ComponentFactory, Inject, Injectable, Optional, Type } from '@angular/core'; import { DynamicComponentFactoryService } from '@core/services/dynamic-component-factory.service'; import { WidgetService } from '@core/http/widget.service'; import { forkJoin, from, Observable, of, ReplaySubject, Subject, throwError } from 'rxjs'; @@ -28,7 +28,7 @@ import { } 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 { ModulesWithFactories, ResourcesService } from '@core/services/resources.service'; import { Widget, widgetActionSources, WidgetControllerDescriptor, WidgetType } from '@shared/models/widget.models'; import { catchError, map, mergeMap, switchMap, tap } from 'rxjs/operators'; import { isFunction, isUndefined } from '@core/utils'; @@ -46,6 +46,7 @@ import * as tinycolor_ from 'tinycolor2'; import moment from 'moment'; import { IModulesMap } from '@modules/common/modules-map.models'; import { HOME_COMPONENTS_MODULE_TOKEN } from '@home/components/tokens'; +import { widgetSettingsComponentsMap } from '@home/components/widget/lib/settings/widget-settings.module'; const tinycolor = tinycolor_; @@ -314,12 +315,12 @@ export class WidgetComponentService { this.cssParser.cssPreviewNamespace = widgetNamespace; this.cssParser.createStyleElement(widgetNamespace, widgetInfo.templateCss); const resourceTasks: Observable[] = []; - const modulesTasks: Observable[] | string>[] = []; + const modulesTasks: Observable[] = []; if (widgetInfo.resources.length > 0) { widgetInfo.resources.filter(r => r.isModule).forEach( (resource) => { modulesTasks.push( - this.resources.loadModules(resource.url, this.modulesMap).pipe( + this.resources.loadFactories(resource.url, this.modulesMap).pipe( catchError((e: Error) => of(e?.message ? e.message : `Failed to load widget resource module: '${resource.url}'`)) ) ); @@ -336,7 +337,7 @@ export class WidgetComponentService { } ); - let modulesObservable: Observable[]>; + let modulesObservable: Observable; if (modulesTasks.length) { modulesObservable = forkJoin(modulesTasks).pipe( map(res => { @@ -344,16 +345,20 @@ export class WidgetComponentService { if (msg) { return msg as string; } else { - let resModules = (res as Type[][]).flat(); + const modulesWithFactoriesList = res as ModulesWithFactories[]; + const resModulesWithFactories: ModulesWithFactories = { + modules: modulesWithFactoriesList.map(mf => mf.modules).flat(), + factories: modulesWithFactoriesList.map(mf => mf.factories).flat() + }; if (modules && modules.length) { - resModules = resModules.concat(modules); + resModulesWithFactories.modules.concat(modules); } - return resModules; + return resModulesWithFactories; } }) ); } else { - modulesObservable = modules && modules.length ? of(modules) : of([]); + modulesObservable = modules && modules.length ? of({modules, factories: []}) : of({modules: [], factories: []}); } resourceTasks.push( @@ -362,10 +367,11 @@ export class WidgetComponentService { if (typeof resolvedModules === 'string') { return of(resolvedModules); } else { + this.registerWidgetSettingsForms(widgetInfo, resolvedModules.factories); return this.dynamicComponentFactoryService.createDynamicComponentFactory( class DynamicWidgetComponentInstance extends DynamicWidgetComponent {}, widgetInfo.templateHtml, - resolvedModules + resolvedModules.modules ).pipe( map((factory) => { widgetInfo.componentFactory = factory; @@ -395,6 +401,25 @@ export class WidgetComponentService { )); } + private registerWidgetSettingsForms(widgetInfo: WidgetInfo, factories: ComponentFactory[]) { + const directives: string[] = []; + if (widgetInfo.settingsDirective && widgetInfo.settingsDirective.length) { + directives.push(widgetInfo.settingsDirective); + } + if (widgetInfo.dataKeySettingsDirective && widgetInfo.dataKeySettingsDirective.length) { + directives.push(widgetInfo.dataKeySettingsDirective); + } + if (widgetInfo.latestDataKeySettingsDirective && widgetInfo.latestDataKeySettingsDirective.length) { + directives.push(widgetInfo.latestDataKeySettingsDirective); + } + if (directives.length) { + factories.filter((factory) => directives.includes(factory.selector)) + .forEach((foundFactory) => { + widgetSettingsComponentsMap[foundFactory.selector] = foundFactory.componentType; + }); + } + } + private createWidgetControllerDescriptor(widgetInfo: WidgetInfo, name: string): WidgetControllerDescriptor { let widgetTypeFunctionBody = `return function _${name} (ctx) {\n` + ' var self = this;\n' +