UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

1,137 lines (1,126 loc) 2.34 MB
import * as i0 from '@angular/core'; import { Input, Directive, Injectable, Injector, InjectionToken, NgModuleRef, createNgModule, Optional, Inject, isDevMode, inject, Pipe, EventEmitter, NgModule, LOCALE_ID, EnvironmentInjector, createEnvironmentInjector, HostListener, Component, HostBinding, Output, forwardRef, DestroyRef, ViewChild, SecurityContext, TemplateRef, provideAppInitializer, Self, SkipSelf, Attribute, ContentChild, ViewContainerRef, ElementRef, ContentChildren, ViewChildren, createComponent, runInInjectionContext, importProvidersFrom, ChangeDetectionStrategy, SimpleChange, reflectComponentType, signal, effect, Type, computed, input, output } from '@angular/core'; import * as i1$3 from 'ngx-bootstrap/dropdown'; import { BsDropdownModule, BsDropdownDirective } from 'ngx-bootstrap/dropdown'; import { CdkTrapFocus, A11yModule } from '@angular/cdk/a11y'; import { castArray, flatten, uniq, sortBy, groupBy, camelCase, isEqual, isUndefined, throttle as throttle$1, keys, get, isNaN as isNaN$1, isFinite, each, mapValues, mapKeys, forEach, assign, min, every, first, map as map$1, find, negate, upperFirst, memoize as memoize$1, property, some, entries, omitBy, isDate, pick, flatMap, orderBy, isEmpty, filter as filter$2, snakeCase, matches, escape, escapeRegExp, assignWith, set, toNumber, isEqualWith, clone, omit, cloneDeep, uniqBy, has, transform, identity, flow, findIndex as findIndex$1, isNil, chunk, values, union, without, indexOf, parseInt as parseInt$1, kebabCase, forOwn } from 'lodash-es'; import { Subject, BehaviorSubject, ReplaySubject, merge, of, defer, combineLatest, race, isObservable, from, NEVER, Observable, firstValueFrom, fromEvent, pipe, throwError, concat, map as map$2, filter as filter$1, tap as tap$1, fromEventPattern, startWith as startWith$1, switchMap as switchMap$1, distinctUntilChanged as distinctUntilChanged$1, takeUntil as takeUntil$1, empty, timer, forkJoin, interval, shareReplay as shareReplay$1, mergeMap as mergeMap$1 } from 'rxjs'; import { shareReplay, map, distinctUntilChanged, filter, startWith, switchMap, take, scan, debounceTime, share, takeUntil, tap, catchError, first as first$1, retryWhen, delay, concatMap, debounce, sample, withLatestFrom, mergeMap, every as every$1, toArray, merge as merge$1, expand, mapTo, skip, reduce, finalize, combineLatestWith } from 'rxjs/operators'; import * as i1 from '@c8y/client'; import { OperationStatus, TenantLoginOptionType, UserManagementSource, GrantType, ApplicationType, BasicAuth, CookieAuth, Realtime, FetchClient, QueriesUtil, Client, PasswordStrength, ApplicationAvailability, InventoryService, AlarmService, TenantService, ApplicationService, UserService, aggregationType, Service, Paging, FeatureService } from '@c8y/client'; import { __decorate, __metadata } from 'tslib'; import * as i1$4 from '@angular/router'; import { NavigationEnd, RouterModule as RouterModule$1, NavigationStart, RouterLink, RouterLinkActive, RouterOutlet, ActivationEnd, PRIMARY_OUTLET, ActivatedRoute, ActivationStart, ChildActivationEnd, ROUTES, Router, NavigationCancel, NavigationError } from '@angular/router'; import * as i4 from '@c8y/ngx-components/api'; import { DataModule } from '@c8y/ngx-components/api'; import { coerceNumberProperty } from '@angular/cdk/coercion'; import { gettext as gettext$1 } from '@c8y/ngx-components/gettext'; export * from '@c8y/ngx-components/gettext'; import * as i1$1 from '@ngx-translate/core'; import { TranslateService as TranslateService$1, TranslateDefaultParser, TranslateDirective, TranslatePipe, TranslateModule, TranslateLoader, TranslateFakeCompiler, TranslateCompiler, TranslateParser, MissingTranslationHandler, TranslateStore, USE_STORE, USE_DEFAULT_LANG, USE_EXTEND, DEFAULT_LANGUAGE } from '@ngx-translate/core'; import { VIRTUAL_SCROLL_STRATEGY, CdkVirtualScrollViewport, CdkVirtualForOf, CdkFixedSizeVirtualScroll, ScrollingModule } from '@angular/cdk/scrolling'; import * as i1$a from '@angular/common'; import { formatDate, registerLocaleData, DOCUMENT, DatePipe as DatePipe$1, DATE_PIPE_DEFAULT_TIMEZONE, DATE_PIPE_DEFAULT_OPTIONS, NgIf, NgClass, NgTemplateOutlet, NgSwitch, NgSwitchCase, AsyncPipe, DecimalPipe, NgStyle, CommonModule as CommonModule$1, NgFor, NgComponentOutlet, NgSwitchDefault, KeyValuePipe, LocationStrategy, UpperCasePipe, NgPlural, NgPluralCase, SlicePipe, NgForOf } from '@angular/common'; import * as i1$5 from 'ngx-bootstrap/tooltip'; import { TooltipModule } from 'ngx-bootstrap/tooltip'; import { setTheme } from 'ngx-bootstrap/utils'; import * as i2 from 'ngx-bootstrap/datepicker'; import { BsDatepickerModule, BsDatepickerDirective } from 'ngx-bootstrap/datepicker'; import { defineLocale, enGbLocale, zhCnLocale, ptBrLocale, plLocale, nlLocale, koLocale, jaLocale, frLocale, esLocale, deLocale } from 'ngx-bootstrap/chronos'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import saveAs$1, { saveAs } from 'file-saver'; import * as i1$2 from '@angular/platform-browser'; import { BlobReader, ZipReader, BlobWriter, ZipWriter } from '@zip.js/zip.js'; import * as i2$2 from 'ngx-bootstrap/collapse'; import { CollapseModule } from 'ngx-bootstrap/collapse'; import { sortBy as sortBy$1, isEmpty as isEmpty$1, findIndex, assign as assign$1, flatten as flatten$1, get as get$1, set as set$1, pick as pick$1 } from 'lodash'; import * as i1$6 from '@angular/cdk/bidi'; import * as i2$1 from '@angular/cdk/stepper'; import { CdkStepper, STEP_STATE, CdkStep, CdkStepperModule, STEPPER_GLOBAL_OPTIONS } from '@angular/cdk/stepper'; import * as i1$7 from 'ngx-bootstrap/modal'; import { ModalModule as ModalModule$1, BsModalRef } from 'ngx-bootstrap/modal'; import * as i1$8 from '@angular/forms'; import { FormsModule as FormsModule$1, Validators, NG_VALUE_ACCESSOR, NG_VALIDATORS, CheckboxControlValueAccessor, FormControl, NgModel, FormControlName, ReactiveFormsModule, NgForm, ControlContainer, FormArray, NgControl, FormGroup, FormBuilder } from '@angular/forms'; import { TOKEN_KEY, TFATOKEN_KEY, getThemePreference, setThemePreference, applyTheme, mergeRemotes, removeRemotes } from '@c8y/bootstrap'; import * as i1$9 from 'ngx-bootstrap/popover'; import { PopoverModule, PopoverDirective } from 'ngx-bootstrap/popover'; import { parsePhoneNumberFromString } from 'libphonenumber-js/max'; import { QRCodeComponent } from 'angularx-qrcode'; import { compare, coerce } from 'semver'; import * as mimeDB from 'mime-db'; import * as i2$3 from '@ngx-formly/core'; import { FormlyModule, FieldWrapper, FieldArrayType, FieldType, FORMLY_CONFIG, ɵdefineHiddenProp as _defineHiddenProp } from '@ngx-formly/core'; import { TextFieldModule } from '@angular/cdk/text-field'; import * as i3 from '@ngx-formly/core/select'; import { FormlySelectModule } from '@ngx-formly/core/select'; import * as i4$1 from 'ngx-bootstrap/timepicker'; import { TimepickerModule } from 'ngx-bootstrap/timepicker'; import { FormlyJsonschema } from '@ngx-formly/core/json-schema'; import { CdkDropList, CdkDrag, CdkDragHandle, CdkDragPlaceholder, DragDropModule } from '@angular/cdk/drag-drop'; import { TimeSpanInMs, INTERVAL_VALUES, INTERVALS, INTERVAL_TITLES, IntervalPickerComponent } from '@c8y/ngx-components/interval-picker'; import { CdkTable, CdkColumnDef, CdkHeaderCellDef, CdkHeaderCell, CdkCellDef, CdkCell, CdkFooterCellDef, CdkFooterCell, CdkHeaderRowDef, CdkHeaderRow, CdkRowDef, CdkRow, CdkFooterRowDef, CdkFooterRow, CdkTableModule } from '@angular/cdk/table'; import * as i11 from 'ngx-bootstrap/pagination'; import { PaginationModule } from 'ngx-bootstrap/pagination'; /** * Allows to set a icon. Switches between c8y default icons * and font awesome icons. * ```html * <i [c8yIcon]="'clock'"> * ``` */ class IconDirective { set c8yIcon(icon) { this.updateIcon(icon); } constructor(el, renderer) { this.el = el; this.renderer = renderer; this.c8yMatch = /^c8y-/; this.dltC8yMatch = /^dlt-c8y-/; this.currentClasses = ''; } isC8y(icon) { return this.c8yMatch.test(icon); } isDltC8y(icon) { return this.dltC8yMatch.test(icon); } getClasses(icon) { let classes = ''; if (typeof icon === 'object' && icon?.class) { icon.class = this.mapFontAwesomeToDelightIcons(icon.class); classes = `${this.isC8y(icon.class) ? `${icon.class.replace(this.c8yMatch, 'c8y-icon c8y-icon-')}` : this.isDltC8y(icon.class) ? '' : `c8y-icon dlt-c8y-icon-${icon.class}`} c8y-icon-duocolor`; return classes; } if (icon && typeof icon === 'string') { const _icon = icon.trim(); const isC8y = this.isC8y(_icon); const iconClass = isC8y ? _icon.replace(this.c8yMatch, 'c8y-icon-') : `c8y-icon dlt-c8y-icon-${_icon}`; classes = `${isC8y ? 'c8y-icon' : ''} ${iconClass}`; } return classes; } updateIcon(icon) { const newClasses = this.getClasses(icon); if (newClasses !== this.currentClasses) { this.currentClasses .split(/\s/) .filter(c => c) .forEach(klass => { this.renderer.removeClass(this.el.nativeElement, klass); }); newClasses .split(/\s/) .filter(c => c) .forEach(klass => { this.renderer.addClass(this.el.nativeElement, klass); }); this.currentClasses = newClasses; } } mapFontAwesomeToDelightIcons(iconClasses) { if (!iconClasses) { return; } const regex = /fa-/gi; return iconClasses.replace(regex, 'dlt-c8y-icon-'); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: IconDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.14", type: IconDirective, isStandalone: true, selector: "[c8yIcon]", inputs: { c8yIcon: "c8yIcon" }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: IconDirective, decorators: [{ type: Directive, args: [{ selector: '[c8yIcon]', standalone: true }] }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }], propDecorators: { c8yIcon: [{ type: Input }] } }); /* * -----------------------------WARNING--------------------------------- * This file implements all properties of the origin AppOptions * **NEVER** change this file directly. Instead do: * 1. open packages/options/src/ApplicationOptions.ts and add your property there * 2. check in this file if all options are implemented. * * TODO: This file should throw when a property is in the AppOptions but not implemented here. * This seems not to work atm. We need to check why. */ class ApplicationOptions { } /** * Tells how a plugin is scoped. */ var PluginsExportScopes; (function (PluginsExportScopes) { /** * Limit the plugin to the current application. It is imported by default. */ PluginsExportScopes["SELF"] = "self"; /** * Allows to add the plugin to a global scope, meaning it is imported to all applications at the same time. * This is not used at the moment but planned to be implemented in the new branding editor. */ PluginsExportScopes["GLOBAL"] = "global"; /** * Limit the plugin to the current application. The plugin is not imported by default. */ PluginsExportScopes["SELF_OPTIONAL"] = "self-optional"; /** * Like undefined the plugin is available for any private application. */ PluginsExportScopes["DEFAULT"] = ""; })(PluginsExportScopes || (PluginsExportScopes = {})); var GroupFragment; (function (GroupFragment) { GroupFragment["groupType"] = "c8y_DeviceGroup"; GroupFragment["subGroupType"] = "c8y_DeviceSubgroup"; GroupFragment["groupFragmentType"] = "c8y_IsDeviceGroup"; GroupFragment["dataBrokerSourceFragment"] = "c8y_BrokerSource"; GroupFragment["dynamicGroupType"] = "c8y_DynamicGroup"; GroupFragment["dynamicGroupFragment"] = "c8y_IsDynamicGroup"; GroupFragment["dynamicGroupColumnConfig"] = "c8y_UIDeviceFilterConfig"; GroupFragment["dynamicGroupQueryString"] = "c8y_DeviceQueryString"; })(GroupFragment || (GroupFragment = {})); class PluginsResolveService { constructor() { this._refresh$ = new Subject(); this._allPluginsLoaded$ = new BehaviorSubject(false); this._contextPathsFromWhereRemotesHaveBeenLoaded$ = new BehaviorSubject([]); this._loadedPluginNames$ = new BehaviorSubject([]); this._injectors$ = new ReplaySubject(); this._pluginDetails$ = new ReplaySubject(); this.injectors$ = this._injectors$; this.refresh$ = this._refresh$.asObservable().pipe(shareReplay(1)); this.pluginDetails$ = this._pluginDetails$; this.allPluginsLoaded$ = this._allPluginsLoaded$.asObservable(); this.contextPathsFromWhereRemotesHaveBeenLoaded$ = this._contextPathsFromWhereRemotesHaveBeenLoaded$.asObservable(); this.loadedPluginNames$ = this._loadedPluginNames$.asObservable(); } /** * Will refresh all current registered hooks. */ refreshHooks() { this._refresh$.next(); } markPluginsAsLoaded() { this._allPluginsLoaded$.next(true); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PluginsResolveService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PluginsResolveService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PluginsResolveService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [] }); class StandalonePluginInjector extends Injector { /** * @deprecated Use `constructor` instead. */ static create(..._args) { throw Error('Not implemented'); } constructor(options) { super(); this.options = options; this.injector = Injector.create(options); } get name() { return this.options.name; } get(token, notFoundValue, options) { return this.injector.get(token, notFoundValue, options); } } /** * @deprecated Only meant for internal usage. * @internal */ const HOOK_PLUGIN = new InjectionToken('HOOK_PLUGIN'); /** * @deprecated Only meant for internal usage. * @internal */ function hookPlugin(plugins) { return hookGeneric(plugins, HOOK_PLUGIN, { multi: false }); } class PluginsLoaderService { constructor(plugins, injector, pluginsResolveService) { this.plugins = plugins; this.injector = injector; this.pluginsResolveService = pluginsResolveService; this.loadModulesDynamically(this.plugins); } /** * Loads modules and handles hooking correctly. * @param remoteNgModules The modules to load. */ loadModulesDynamically(remoteModules) { const modulesWithMetadata = this.loadModules(remoteModules || [], this.injector); const injectors = modulesWithMetadata.map(m => m.injector); const refs = modulesWithMetadata .map(m => { if (m.moduleRef && m.remoteNgModule) { return { moduleRef: m.moduleRef, remoteNgModule: m.remoteNgModule }; } return null; }) .filter(m => !!m); const loadedPluginNames = modulesWithMetadata.map(m => m.name); this.pluginsResolveService._loadedPluginNames$.next(loadedPluginNames); const loadedContextPaths = modulesWithMetadata.map(m => m.fullContextPath); this.pluginsResolveService._contextPathsFromWhereRemotesHaveBeenLoaded$.next(loadedContextPaths); injectors.forEach(injector => this.pluginsResolveService._injectors$.next(injector)); refs.forEach(ref => this.pluginsResolveService._pluginDetails$.next(ref)); this.pluginsResolveService.refreshHooks(); this.pluginsResolveService.markPluginsAsLoaded(); return modulesWithMetadata; } /** * Uses the provided remoteModule factories to load and instantiate the modules. * The provided injector is used as a parent to create the module instances. */ loadModules(remoteModules, injector) { const loadedResults = []; for (const { factory, name, fullContextPath } of remoteModules) { try { const moduleOrProviders = factory[name]; let result; if (Array.isArray(moduleOrProviders)) { result = this.loadProviders(moduleOrProviders, name, injector); } else { result = this.loadModule(moduleOrProviders, injector); } const resultWithName = { ...result, name, fullContextPath }; loadedResults.push(resultWithName); } catch (ex) { console.error(`Failed to load ${name}`, ex); } } return loadedResults; } loadProviders(providers, name, parentInjector) { const injector = new StandalonePluginInjector({ providers: providers, name: `pluginsInjector-${name}`, parent: parentInjector }); return { injector }; } loadModule(remoteNgModule, parentInjector) { let moduleRef; if (remoteNgModule instanceof NgModuleRef) { // AOT moduleRef = remoteNgModule; } else { // JIT moduleRef = createNgModule(remoteNgModule, parentInjector); } return { moduleRef, remoteNgModule, injector: moduleRef.injector }; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PluginsLoaderService, deps: [{ token: HOOK_PLUGIN, optional: true }, { token: i0.EnvironmentInjector }, { token: PluginsResolveService }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PluginsLoaderService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PluginsLoaderService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: Array, decorators: [{ type: Optional }, { type: Inject, args: [HOOK_PLUGIN] }] }, { type: i0.EnvironmentInjector }, { type: PluginsResolveService }] }); /** * A abstract state which should be included in each state. * @abstract */ class StateService { /** * Maps to a property and just returns that property. * @param mappedProperty The property to map to. */ map(mappedProperty) { return this.state$.pipe(map(mappedProperty), distinctUntilChanged()); } /** * Emits a new state. */ emitNewState() { this.state$.next(this.state); } } function fromTrigger(router, refresh, factories) { return merge(router.events.pipe(filter(evt => evt instanceof NavigationEnd)), ...castArray(refresh)).pipe(startWith(1), switchMap(() => fromFactories(factories, router))); } function fromTriggerOnce(router, refresh, factories) { return merge(...castArray(refresh)).pipe(startWith(1), switchMap(() => fromFactories(factories, router))); } var InjectionType; (function (InjectionType) { InjectionType[InjectionType["COMPONENT"] = 0] = "COMPONENT"; InjectionType[InjectionType["ROUTE"] = 1] = "ROUTE"; })(InjectionType || (InjectionType = {})); function getInjectedHooks(token, injectors, type = InjectionType.COMPONENT) { return () => flatten(injectors.map(injector => { const factoryOrFactories = injector.get(token, [], { self: true }); const factories = Array.isArray(factoryOrFactories) ? flatten(factoryOrFactories) : [factoryOrFactories]; if (injector.scopes?.has('root')) { return factories; } if (injector instanceof StandalonePluginInjector) { // No need to set injector for items retrieved from standalone plugins return factories; } factories.forEach((factory) => { if (!factory.get && factory.injector !== null) { if (type === InjectionType.ROUTE) { factory._injector = injector; } else { factory.injector = injector; } } }); return factories; })); } function fromFactories(factories, router, withFirstEmpty = true) { return !Array.isArray(factories) || factories.length < 1 ? of([]) : defer(() => { const factoryObservables = resolveInjectedFactories(factories).map(f => { if (Array.isArray(f)) { return toObservableOfArrays(f, withFirstEmpty); } if (isExtensionFactory(f)) { return toObservableOfArrays(f.get(getActivatedRoute(router)), withFirstEmpty); } return toObservableOfArrays([f], withFirstEmpty); }); return combineLatest(factoryObservables); }).pipe(map(results => sortByPriority([].concat(...results))), map(value => uniq(value))); } function resolveInjectedFactories(factories) { return flatten(factories.map(f => { if (typeof f === 'function') { const func = f; return func(); } return [f]; })); } function stateToFactory(componentsState) { const components$ = componentsState.pipe(map((componentSet) => [...componentSet])); return { get: () => components$ }; } function sortByPriority(items) { return sortBy(items, item => -(item?.priority || 0)); } function removeDuplicatesIds(items) { const grouped = groupBy(items, 'id'); const itemsWithoutDuplicates = new Array(); for (const key of Object.keys(grouped)) { if (key && key !== 'undefined') { const sortedByPrio = sortByPriority(grouped[key]); itemsWithoutDuplicates.push(sortedByPrio[0]); } else { itemsWithoutDuplicates.push(...grouped[key]); } } return sortByPriority(itemsWithoutDuplicates); } function toObservableOfArrays(factoryResult, withFirstEmpty) { let observable; if (!factoryResult) { return of([]); } else { observable = toObservable(factoryResult); if (withFirstEmpty) { const withEmptyFirst = observable.pipe(startWith([])); observable = race(observable, withEmptyFirst); } } return observable.pipe(map(result => (Array.isArray(result) ? result : [result]).filter(item => !!item))); } function isPromise(obj) { return !!obj && typeof obj.then === 'function'; } function isExtensionFactory(obj) { return !!obj && typeof obj.get === 'function'; } /** * Converts any value provided to an Observable that emits this value once and then completes. * A convenience method to represent all the data as Observables rather than * a mixture of Observables and other types. * * @param value The value the resulting Observable will emit. */ function toObservable(value) { if (isObservable(value)) { return value; } if (isPromise(value)) { return from(value); } return of(value); } class ExtensionPointWithoutStateForPlugins { constructor(rootInjector, pluginService) { this.factories = []; this.refreshTrigger = new Subject(); this.injectors = [rootInjector]; pluginService.injectors$.subscribe(injector => { this.injectors.push(injector); }); this.refresh$ = merge(this.refreshTrigger, pluginService.refresh$); } /** * Refresh the extension factories */ refresh() { this.refreshTrigger.next(); } } class ExtensionPointForPlugins extends StateService { constructor(rootInjector, pluginService) { super(); this.factories = []; this.state$ = new BehaviorSubject(new Set()); this.refreshTrigger = new Subject(); this.injectors = [rootInjector]; pluginService.injectors$.subscribe(injector => { this.injectors.push(injector); }); this.refresh$ = merge(this.refreshTrigger, pluginService.refresh$); } /** * Refresh the extension factories */ refresh() { this.refreshTrigger.next(); } } /** * Helper function to get the activated route in * a service (as ActivatedRoute injection only * works in components). Works as long as we only use * a tree and no child is active at the same time. * * @param router The current router */ function getActivatedRoute(router) { if (router && router.routerState && router.routerState.root) { let route = router.routerState.root; while (route.firstChild) { route = route.firstChild; } return route; } } /** * A generic function to be used by specific implementations of the HOOK concept. * @param items The items that should be provided under the `useValue` or `useClass` attribute. * Allows an extension factory to be passed as an argument, which can create instances of type T. * @param token The InjectionToken/HOOK to be provided. * @param options If this is a multi provider or not (defaults to true) and provider type definition (defaults to ClassProvider) - `HookOptions`. * @returns A `Provider` (either `ValueProvider` or `ClassProvider`) to be provided in a module. */ function hookGeneric(items, token, options) { const finalOptions = Object.assign({ multi: true, providerType: HookProviderTypes.ClassProvider }, options); const { multi, providerType } = finalOptions; if (typeof items !== 'function') { return { provide: token, useValue: items, multi }; } if (providerType === HookProviderTypes.ExistingProvider) { return { provide: token, useExisting: items, multi }; } return { provide: token, useClass: items, multi }; } var HookProviderTypes; (function (HookProviderTypes) { HookProviderTypes["ExistingProvider"] = "ExistingProvider"; HookProviderTypes["ClassProvider"] = "ClassProvider"; })(HookProviderTypes || (HookProviderTypes = {})); function allEntriesAreEqual(previous, next) { if (previous === next) return true; if (previous == null || next == null) return false; if (previous.length !== next.length) return false; for (let i = 0; i < previous.length; ++i) { if (previous[i] !== next[i]) return false; } return true; } /** * @deprecated Consider using the `hookOptions` function instead. */ const HOOK_OPTIONS = new InjectionToken('HOOK_OPTIONS'); /** * You can either provide a single `ApplicationOptions` as parameter: * ```typescript * hookOptions(...) * ``` * * Or an array to directly register multiple: * ```typescript * hookOptions([...]) * ``` * * Or you provide an Service that implements `ExtensionFactory<ApplicationOptions>` * ```typescript * export class MyApplicationOptionsFactory implements ExtensionFactory<ApplicationOptions> {...} * ... * hookOptions(MyApplicationOptionsFactory) * ``` * A typed alternative to `HOOK_OPTIONS`. * @param options The `ApplicationOptions`'s or `ExtensionFactory` to be provided. * @returns An `Provider` to be provided in your module. */ function hookOptions(options, hookOptions) { return hookGeneric(options, HOOK_OPTIONS, hookOptions); } /** * A service that allows to set or get application options * which configure the default behavior of the UI. */ class OptionsService extends ApplicationOptions { constructor(options, systemOptionsService, tenantOptionService) { super(); this.systemOptionsService = systemOptionsService; this.tenantOptionService = tenantOptionService; this._optionsUpdated$ = new Subject(); this.optionsUpdated$ = this._optionsUpdated$.asObservable(); this.setupOptions(options); } /** * Returns an application option used to configure the UI. * @param optionKey The application options key. * @param defaultValue A value to return if non is set. * @param attemptParse Indicates whether the value should be parsed with JSON.parse. */ get(optionKey, defaultValue, attemptParse) { let value = this[optionKey]; if (typeof value === 'undefined') { value = this[camelCase(optionKey)]; } if (attemptParse) { return this.parseOptionRawValue(value, defaultValue); } return typeof value !== 'undefined' ? value : defaultValue; } /** * Returns an observable of an application option used to configure the UI. * @param optionKey The application options key. * @param defaultValue A value to return if non is set. * @param attemptParse Indicates whether the value should be parsed with JSON.parse. */ get$(optionKey, defaultValue, attemptParse) { const fn = () => this.get(optionKey, defaultValue, attemptParse); return this.optionsUpdated$.pipe(map(fn), startWith(fn()), distinctUntilChanged()); } /** * Sets an application option. * @param key The key to set. * @param value The value to set. */ set(key, value) { const camelCasedKey = camelCase(key); const previousValue = this[camelCasedKey]; this[camelCasedKey] = value; if (!isEqual(previousValue, value)) { this._optionsUpdated$.next(); } } /** * Deletes an application option. * @param key The key to remove. */ delete(key) { const camelCasedKey = camelCase(key); const previousValue = this[camelCasedKey]; delete this[camelCasedKey]; const newValue = this[camelCasedKey]; if (!isEqual(previousValue, newValue)) { this._optionsUpdated$.next(); } } /** * Gets support URL from: * - application option `supportUrl`, * - or current tenant's option `configuration / system.support.url`, * - or current tenant's inherited option `configuration / system.support.url`, * - or system option `configuration / system.support.url`, * - otherwise defaults to `false`. * * @returns Returns support url or false. */ async getSupportUrl() { let url = this.supportUrl; if (isUndefined(url)) { url = await this.getCurrentTenantOption('configuration', 'system.support.url'); } if (isUndefined(url)) { url = await this.getInheritedTenantOption('configuration', 'system.support.url'); } if (isUndefined(url)) { url = (await this.getSystemOption('support', 'url')) || false; } this.supportUrl = url; return this.supportUrl; } /** * Returns if the tenant allows to show the activate-support user menu entry. * Note: Only if system-level support-user/enabled is false we can activate it at tenant level. */ async getActivateSupportUser() { const option = await this.getSystemOption('support-user', 'enabled', true); return !option; } /** * Gets a value from the system service and parses it. * * @param category The category for this option. * @param key The key for that option. * @param defaultValue The default if the option was not found. */ async getSystemOption(category, key, defaultValue) { return this.getOptionFromService(category, key, null, this.systemOptionsService, defaultValue); } /** * Gets a value from the tenant service and parses it. * * @param category The category for this option. * @param key The key for that option. * @param defaultValue The default if the option was not found. */ async getTenantOption(category, key, defaultValue) { return this.getOptionFromService(category, key, null, this.tenantOptionService, defaultValue); } /** * Gets an inherited from parent value from the tenant service if inheritance supported based on given parameters. * * @param category The category for this option. * @param key The key for that option. * @param defaultValue The default if the option was not found. */ async getInheritedTenantOption(category, key, defaultValue) { return this.getOptionFromService(category, key, { evaluate: 'inherited' }, this.tenantOptionService, defaultValue); } /** * Gets current tenant option value from the tenant service omitting the inheritance supported based on given parameters. * * @param category The category for this option. * @param key The key for that option. * @param defaultValue The default if the option was not found. */ async getCurrentTenantOption(category, key, defaultValue) { return this.getOptionFromService(category, key, { evaluate: 'current' }, this.tenantOptionService, defaultValue); } setupOptions(options) { if (options) { if (!Array.isArray(options)) { options = [options]; } let combinedOptions = {}; if (options.length === 1) { combinedOptions = options[0]; } else if (options.length > 1) { options.forEach(optionMap => { if (optionMap) { this.applyOptions(optionMap, combinedOptions); } }); } else { return; } if (this.applyOptions(combinedOptions, this)) { this._optionsUpdated$.next(); } } } applyOptions(options, applyTo) { let optionWasChanged = false; Object.entries(options).forEach(([key, value]) => { const camelCasedKey = camelCase(key); const previousValue = applyTo[camelCasedKey]; applyTo[camelCasedKey] = value; if (!isEqual(previousValue, value)) { optionWasChanged = true; } }); return optionWasChanged; } async getOptionFromService(category, key, filter, service, defaultValue) { try { const { data } = await service.detail({ category, key }, filter); return this.parseOptionRawValue(data.value, defaultValue); } catch (ex) { return defaultValue; } } parseOptionRawValue(rawValue, defaultValue) { let value; try { value = JSON.parse(rawValue); } catch (e) { value = isUndefined(rawValue) ? defaultValue : rawValue; } return value; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: OptionsService, deps: [{ token: HOOK_OPTIONS, optional: true }, { token: i1.SystemOptionsService }, { token: i1.TenantOptionsService }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: OptionsService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: OptionsService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [HOOK_OPTIONS] }] }, { type: i1.SystemOptionsService }, { type: i1.TenantOptionsService }] }); /** * Decorator to throttle functions call. * @param wait throttle time. * @param options set triggers, eg on trailing edge or falling edge, * see lodash documentation for details */ function throttle(wait, options) { return (target, fnName, descriptor) => { const fn = descriptor.value; descriptor.value = function (...args) { const throttledFnName = `_${fnName}Throttled`; if (!this[throttledFnName]) { this[throttledFnName] = throttle$1(fn, wait, options); } return this[throttledFnName](...args); }; }; } const HOOK_CURRENT_USER = new InjectionToken('HOOK_CURRENT_USER'); const HOOK_CURRENT_APPLICATION = new InjectionToken('HOOK_CURRENT_APPLICATION'); const HOOK_CURRENT_TENANT = new InjectionToken('HOOK_CURRENT_TENANT'); function hookCurrentUser(user) { return hookGeneric(user, HOOK_CURRENT_USER, { multi: false }); } function hookCurrentApplication(application) { return hookGeneric(application, HOOK_CURRENT_APPLICATION, { multi: false }); } function hookCurrentTenant(tenant) { return hookGeneric(tenant, HOOK_CURRENT_TENANT, { multi: false }); } function provideBootstrapMetadata(metadata) { return [ hookOptions(metadata.options), hookPlugin(metadata.remoteModuleFactories), hookCurrentUser(metadata.currentUser), hookCurrentApplication(metadata.currentApp), hookCurrentTenant(metadata.currentTenant) ]; } class AppStateService extends StateService { constructor(applicationService, apiService, options, fetchClient, tenantLoginOptionsService, user, application, tenant) { super(); this.applicationService = applicationService; this.apiService = apiService; this.options = options; this.fetchClient = fetchClient; this.tenantLoginOptionsService = tenantLoginOptionsService; /** * Saves the state. Should not be accessible directly. Use map or the getter to access * the state. Use functions in the implementation to change the state. */ this.state$ = new BehaviorSubject({ supportUrl: this.options.supportUrl, lang: this.options.get('defaultLanguage', 'en'), langs: this.getLangs(), langsDetail: this.options.languages, loginOptions: this.options.loginOptions, activateSupportUserAvailable: undefined, versions: { backend: undefined, ui: this.options.versions || { ngx: undefined } }, hidePowered: this.options.hidePowered, isLoading: false, showRightDrawer: this.options.rightDrawer, loginExtraLink: this.options.get('login_extra_link'), newsletter: this.options.newsletter }); this.currentSupportUserName = new BehaviorSubject(null); this.currentUser = new BehaviorSubject(null); this.currentTenant = new BehaviorSubject(null); this.currentApplication = new BehaviorSubject(null); this.currentApplicationConfig = this.currentApplication.pipe(filter(app => !!app), map(app => app?.config || null)); if (application) { this.currentApplication.next(application); this.state.app = application; } if (user) { this.currentUser.next(user); } if (tenant) { this.fetchClient.tenant = tenant.name; this.currentTenant.next(tenant); } this.apiService.isLoading$.subscribe(isLoading => { this.state.isLoading = isLoading; }); if (this.options.contextPath !== 'login') { this.assignApplicationKeyToDefaultHeaders(); } this.currentAppsOfUser = this.currentAppsOfUser$(); } assignApplicationKeyToDefaultHeaders() { const appIsAvailable = this.state.app?.id && this.state.app.key === this.options.key; if (appIsAvailable || !isDevMode()) { this.fetchClient.defaultHeaders = { ...(this.fetchClient.defaultHeaders || {}), 'X-Cumulocity-Application-Key': this.options.key }; } } /** * Returns the current state. */ get state() { return this.state$.value; } getLangs() { const { languages } = this.options; return languages ? keys(languages).filter(k => languages[k]) : []; } /** * Returns the correct UI version. In hybrid mode for angular and ngx. */ get uiVersion() { const version = this.state.versions.ui; return version.ngx || version.ng1; } /** * Dynamic options are stored on the API in a specific config: {} object. They can * be used to configure the app dynamically. * * Note: To avoids conflicts with the default Config, it is recommended * to use a certain namespace. */ async updateCurrentApplicationConfig(config) { const appWithUpdatedConfig = await this.applicationService.updateApplicationConfig(this.state.app.id, config); this.currentApplication.next(appWithUpdatedConfig); return appWithUpdatedConfig.config; } /** * When this function called, it refreshes the values of loginOptions stored within ui state object. * Function is throttled to execute the refresh once in a time specified by params of @throttled decorator, * it should be called on leading edge of the timeout. */ async refreshLoginOptions() { const loginOptions = (await this.tenantLoginOptionsService.listForCurrentTenant()).data; this.state$.next({ ...this.state, loginOptions }); } /** * Checks current users application list and matches it against given application name. * Returns true if application is in the list. * @param name application name */ async isApplicationAvailable(name) { const apps = await this.currentAppsOfUser.pipe(take(1)).toPromise(); return apps.some(app => app.name === name || app.contextPath === name); } /** * Sets current user (including support user). * @param userInfo Info about current user and support user to be set. */ setUser(userInfo) { this.currentSupportUserName.next(userInfo.supportUserName || null); this.currentUser.next(userInfo.user); } /** * Verifies if the current application is owned by the current tenant. * @param app The application to verify. * @returns true if it belongs to the current tenant. */ isOwnerOfApplication(app) { if (!app) { app = this.currentApplication.value; } const currentTenant = this.currentTenant.value; const appOwner = get(app, 'owner.tenant.id'); return currentTenant?.name === appOwner; } /** * Verifies if the current application is owned by the current tenant. * @param app The application to verify. * @returns true if it belongs to the current tenant. */ isOwnerOfApplication$(app) { const app$ = app ? of(app) : this.currentApplication; return combineLatest([app$, this.currentTenant]).pipe(map(([app, tenant]) => { if (!app || !tenant) { return false; } return tenant.name === get(app, 'owner.tenant.id'); })); } async loadDefaultOptions() { this.state.supportUrl = await this.options.getSupportUrl(); this.state.activateSupportUserAvailable = await this.options.getActivateSupportUser(); this.state.versions.backend = await this.options.getSystemOption('system', 'version'); this.emitNewState(); } currentAppsOfUser$() { const appChanges$ = this.onAppChangesCompletion$().pipe(startWith(undefined)); const userChanges$ = this.currentUser.pipe(map(user => user?.id), distinctUntilChanged()); return combineLatest([userChanges$, appChanges$]).pipe(filter(([userId]) => !!userId), switchMap(([userId]) => this.applicationService.listByUser(userId, { dropOverwrittenApps: true, noPaging: true })), map(({ data }) => data), shareReplay({ bufferSize: 1, refCount: true })); } /** * An Observable emitting once all POST, PUT, DELETE requests to the application API finished */ onAppChangesCompletion$() { const methods = ['POST', 'PUT', 'DELETE']; return this.apiService.calls.pipe(filter(({ method, url }) => methods.includes(method) && url?.includes('application/applications')), map(({ phase }) => (phase === 'start' ? 1 : -1)), scan((count, item) => count + item, 0), map(count => count === 0), distinctUntilChanged(), debounceTime(500), filter(completed => !!completed), map(() => { return; })); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: AppStateService, deps: [{ token: i1.ApplicationService }, { token: i4.ApiService }, { token: OptionsService }, { token: i1.FetchClient }, { token: i1.TenantLoginOptionsService }, { token: HOOK_CURRENT_USER, optional: true }, { token: HOOK_CURRENT_APPLICATION, optional: true }, { token: HOOK_CURRENT_TENANT, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: AppStateService, providedIn: 'root' }); } } __decorate([ throttle(600, { trailing: false }), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], AppStateService.prototype, "refreshLoginOptions", null); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: AppStateService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: i1.ApplicationService }, { type: i4.ApiService }, { type: OptionsService }, { type: i1.FetchClient }, { type: i1.TenantLoginOptionsService }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [HOOK_CURRENT_USER] }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [HOOK_CURRENT_APPLICATION] }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [HOOK_CURRENT_TENANT] }] }], propDecorators: { refreshLoginOptions: [] } }); /** * A wrapper class for handling realtime notifications in RxJS fashion. */ class RealtimeService { /** * A flag displaying if realtime notifications are currently active. */ get active() { return this.isActive.value; } /** * An observable emitting a value in case the realtime connection has been interrupted. * Can be used to reload data of e.g. a datapoint graph that wasn't received while realtime was interrupted. */ get reconnect$() { return this.realtimeSubject.reconnect$; } /** * An observable emitting either `connected` or `disconnected` depending on the state of the realtime connection. * Can be used to e.g. inform the user about the interrupted realtime connection. */ get connectionStatus$() { return this.realtimeSubject.connectionStatus$; } constructor(realtimeSubject) { this.realtimeSubject = realtimeSubject; this.isActive = new BehaviorSubject(true); } /** * Get an Observable of all realtime notifications. * * @param {string | number | IIdentified} entityOrId Entity object or id * * @returns An [[Observable]] of notifications wrapped as [[RealtimeMessage]] */ onAll$(entityOrId) { const subject$ = this.realtimeSubject.getObservableForChannel(this.getChannel(entityOrId)); return this.isActive.pipe(switchMap(active => (active ? subject$ : NEVER))); } /** * Subscribes again all realtime channels with active observers. */ start() { if (!this.active) { this.isActive.next(true); } } /** * Stops realtime notifications and unsubscribes all realtime channels. */ stop() { if (this.active) { this.isActive.next(false); } } /** * Get an Observable of all CREATE realtime notifications. * * @param {string | number | IIdentified} entityOrId Entity object or id * * @returns An [[Observable]] of newly created entity objects. */ onCreate$(entityOrId) { return this.onAll$(entityOrId).pipe(filter(msg => msg.realtimeAction === 'CREATE'), map(msg => msg.data)); } /** * Get an Observable of all UPDATE realtime notifications. * * @param