UI: Widget settings forms support for extensions

This commit is contained in:
Igor Kulikov 2022-05-11 18:16:30 +03:00
parent c44727851d
commit 33e5b033ec
8 changed files with 71 additions and 35 deletions

View File

@ -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;
};

View File

@ -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;

View File

@ -30,6 +30,11 @@ import { IModulesMap } from '@modules/common/modules-map.models';
declare const System;
export interface ModulesWithFactories {
modules: Type<any>[];
factories: ComponentFactory<any>[];
}
@Injectable({
providedIn: 'root'
})
@ -37,7 +42,7 @@ export class ResourcesService {
private loadedResources: { [url: string]: ReplaySubject<any> } = {};
private loadedModules: { [url: string]: ReplaySubject<Type<any>[]> } = {};
private loadedFactories: { [url: string]: ReplaySubject<ComponentFactory<any>[]> } = {};
private loadedModulesAndFactories: { [url: string]: ReplaySubject<ModulesWithFactories> } = {};
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<ComponentFactory<any>[]> {
if (this.loadedFactories[url]) {
return this.loadedFactories[url].asObservable();
public loadFactories(url: string, modulesMap: IModulesMap): Observable<ModulesWithFactories> {
if (this.loadedModulesAndFactories[url]) {
return this.loadedModulesAndFactories[url].asObservable();
}
modulesMap.init();
const subject = new ReplaySubject<ComponentFactory<any>[]>();
this.loadedFactories[url] = subject;
const subject = new ReplaySubject<ModulesWithFactories>();
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];
}
);
}

View File

@ -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];
}

View File

@ -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];
}

View File

@ -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];
}

View File

@ -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];
}

View File

@ -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<string>[] = [];
const modulesTasks: Observable<Type<any>[] | string>[] = [];
const modulesTasks: Observable<ModulesWithFactories | string>[] = [];
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<string | Type<any>[]>;
let modulesObservable: Observable<string | ModulesWithFactories>;
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<any>[][]).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<any>[]) {
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' +