UI: Widget settings forms support for extensions
This commit is contained in:
		
							parent
							
								
									c44727851d
								
							
						
					
					
						commit
						33e5b033ec
					
				@ -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;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
@ -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];
 | 
			
		||||
          }
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@ -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];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -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];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -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];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -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];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -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' +
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user