UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

1,124 lines (1,107 loc) 2.74 MB
import * as i0 from '@angular/core'; import { Input, Directive, Pipe, createEnvironmentInjector, InjectionToken, Optional, Inject, Injectable, EventEmitter, NgModule, LOCALE_ID, NgModuleRef, createNgModule, isDevMode, inject, DOCUMENT, EnvironmentInjector, Injector, HostListener, Component, HostBinding, Output, forwardRef, DestroyRef, ViewChild, SecurityContext, TemplateRef, provideAppInitializer, ElementRef, Self, SkipSelf, Attribute, ContentChild, ContentChildren, ViewContainerRef, input, output, ChangeDetectorRef, effect, ChangeDetectionStrategy, ViewChildren, viewChild, createComponent, runInInjectionContext, importProvidersFrom, SimpleChange, reflectComponentType, signal, computed, Type } from '@angular/core'; import * as i1$3 from 'ngx-bootstrap/dropdown'; import { BsDropdownDirective, BsDropdownModule } from 'ngx-bootstrap/dropdown'; import { ConfigurableFocusTrapFactory, CdkTrapFocus, A11yModule } from '@angular/cdk/a11y'; import { castArray, flatten, uniq, sortBy, groupBy, camelCase, isEqual, isUndefined, throttle as throttle$1, each, mapValues, mapKeys, keys, get, isNaN as isNaN$1, isFinite, forEach, assign, min, every, first, map as map$1, find, negate, upperFirst, memoize as memoize$1, isEmpty, property, some, entries, omitBy, isDate, pick, flatMap, orderBy, filter as filter$2, snakeCase, matches, escape, escapeRegExp, assignWith, set, toNumber, isEqualWith, clone, omit, cloneDeep, uniqBy, has, transform, identity, unset, flow, findIndex as findIndex$1, isNil, chunk, values, union, without, indexOf, kebabCase, forOwn } from 'lodash-es'; import { merge, of, defer, combineLatest, race, isObservable, from, Subject, BehaviorSubject, ReplaySubject, firstValueFrom, lastValueFrom, NEVER, Observable, startWith as startWith$1, filter as filter$1, tap as tap$1, mergeMap, fromEvent, pipe, throwError, concat, map as map$2, fromEventPattern, withLatestFrom, distinctUntilChanged as distinctUntilChanged$1, takeUntil as takeUntil$1, catchError as catchError$1, empty, timer, forkJoin, interval, shareReplay as shareReplay$1, switchMap as switchMap$1, skip as skip$1, debounceTime as debounceTime$1 } from 'rxjs'; import { map, distinctUntilChanged, filter, startWith, switchMap, shareReplay, take, scan, takeUntil, tap, catchError, debounceTime, share, first as first$1, retryWhen, delay, concatMap, debounce, sample, withLatestFrom as withLatestFrom$1, mergeMap as mergeMap$1, 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 { InventoryService, OperationStatus, TenantLoginOptionType, UserManagementSource, GrantType, ApplicationType, BasicAuth, CookieAuth, Realtime, FetchClient, BearerAuthFromSessionStorage, QueriesUtil, Client, PasswordStrength, ApplicationAvailability, 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 { provideClientLibServices } from '@c8y/ngx-components/api'; import * as i1$1 from '@ngx-translate/core'; import { TranslateDirective, TranslatePipe, TranslateStore, TranslateService as TranslateService$1, provideTranslateService, provideTranslateLoader, provideMissingTranslationHandler, TranslateParser } from '@ngx-translate/core'; import * as i2$3 from '@angular/common'; import { formatDate, registerLocaleData, DatePipe as DatePipe$1, DATE_PIPE_DEFAULT_TIMEZONE, DATE_PIPE_DEFAULT_OPTIONS, NgIf, NgClass, NgTemplateOutlet, NgSwitch, NgSwitchCase, AsyncPipe, DecimalPipe, NgStyle, CommonModule as CommonModule$1, DOCUMENT as DOCUMENT$1, NgFor, NgComponentOutlet, NgSwitchDefault, KeyValuePipe, LocationStrategy, UpperCasePipe, NgPlural, NgPluralCase, SlicePipe, NgForOf } from '@angular/common'; import { gettext as gettext$1 } from '@c8y/ngx-components/gettext'; import { coerceNumberProperty } from '@angular/cdk/coercion'; import { VIRTUAL_SCROLL_STRATEGY, CdkVirtualScrollViewport, CdkVirtualForOf, CdkFixedSizeVirtualScroll, ScrollingModule, ScrollDispatcher } from '@angular/cdk/scrolling'; import * as i1$5 from 'ngx-bootstrap/tooltip'; import { TooltipModule } from 'ngx-bootstrap/tooltip'; import { setTheme } from 'ngx-bootstrap/utils'; import { defineLocale, enGbLocale, zhCnLocale, ptBrLocale, plLocale, nlLocale, koLocale, jaLocale, frLocale, esLocale, deLocale } from 'ngx-bootstrap/chronos'; import * as i2 from 'ngx-bootstrap/datepicker'; import { BsDatepickerModule, BsDatepickerDirective } from 'ngx-bootstrap/datepicker'; 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, get as get$1, set as set$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, NgControl, ReactiveFormsModule, NgForm, ControlContainer, FormArray, 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$4 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 { /** * Sets the icon to be displayed. This directive handles the correct CSS classes * for Cumulocity IoT's dual-color icon sets. * * There are two main icon sets: * 1. **Cumulocity Icons**: These are specific to the platform. To use them, provide the icon name prefixed with **c8y-**. * 2. **Delight Icons**: This is the default icon set. To use them, provide just the icon name without any prefix. * * ```html * <!-- To display a Cumulocity IoT icon (e.g., cockpit) --> * <i [c8yIcon]="'c8y-cockpit'"></i> * * <!-- To display a default Delight icon (e.g., download) --> * <i [c8yIcon]="'download'"></i> * * <!-- You can also use it without property binding for static icons --> * <i c8yIcon="building"></i> * ``` */ 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: "20.3.19", 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: "20.3.19", type: IconDirective, isStandalone: true, selector: "[c8yIcon]", inputs: { c8yIcon: "c8yIcon" }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", 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 = {})); /** * Applies CSS classes based on the value's range. */ class ApplyRangeClassPipe { /** * Transforms the input value based on the specified ranges. * * @param value - Initial value used to determine the CSS class. * @param ranges - An object containing the min and max range values for yellow and red colors. * @returns The CSS class to be applied. */ transform(value, ranges) { if (value == null) { return; } if (value >= ranges.yellowRangeMin && value < ranges.yellowRangeMax) { return 'text-warning'; } else if (value >= ranges.redRangeMin && value <= ranges.redRangeMax) { return 'text-danger'; } return 'default'; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ApplyRangeClassPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); } static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.19", ngImport: i0, type: ApplyRangeClassPipe, isStandalone: true, name: "applyRangeClass" }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ApplyRangeClassPipe, decorators: [{ type: Pipe, args: [{ name: 'applyRangeClass', standalone: true }] }] }); 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 = {})); /** * 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, withFirstEmpty = true) { return merge(...castArray(refresh)).pipe(startWith(1), switchMap(() => fromFactories(factories, router, withFirstEmpty))); } 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; } factories.forEach((factory) => { if (!factory.get && factory.injector !== null) { if (type === InjectionType.ROUTE) { const route = factory; if (!route._injector) { const envInjector = createEnvironmentInjector(route.providers || [], injector, `Route: ${route.path}`); factory._injector = envInjector; } } 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: "20.3.19", 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: "20.3.19", ngImport: i0, type: OptionsService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", 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); }; }; } 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$.asObservable(); 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(); } addInjector(injector) { this._injectors$.next(injector); } async waitForPluginsToLoad() { await firstValueFrom(this.allPluginsLoaded$.pipe(filter(loaded => !!loaded), take(1))); } getAllInjectors() { return lastValueFrom(this.getAllInjectors$().pipe(scan((acc, curr) => [...acc, curr], []))); } getAllInjectors$() { return this._injectors$.pipe(takeUntil(this.waitForPluginsToLoad())); } getFromAllInjectors(token, notFoundValue = null) { return this.getAllInjectors$() .pipe(map(injector => injector.get(token, notFoundValue))) .pipe(filter(i => !!i)); } markPluginsAsLoaded() { this._allPluginsLoaded$.next(true); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: PluginsResolveService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: PluginsResolveService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: PluginsResolveService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [] }); const LANGUAGES = new InjectionToken('Languages'); const LOCALE_PATH = new InjectionToken('Locale path'); /** * A service to translate messages by using regexp patterns. */ class PatternMessagesService { constructor(patterns, translateService, translateStore) { this.translateService = translateService; this.translateStore = translateStore; this.patterns = {}; this.pipes = { absoluteDate: (date) => formatDate(date, 'medium', this.translateService.currentLang), translate: key => this.translateService.instant(key) }; each(patterns, pattern => { Object.assign(this.patterns, pattern); }); } translate(message) { const translation = this.translateWithPatterns(message); return translation !== message ? translation : ''; } translateWithPatterns(message, patterns = this.patterns) { let translatedMessage = message; each(patterns, (patternCfg, pattern) => { const globalRegExp = new RegExp(pattern, 'g'); let globalMatch; if (!globalRegExp.test(translatedMessage)) { return; } globalRegExp.test(''); // reset the regexp globalMatch = globalRegExp.exec(translatedMessage); while (globalMatch !== null) { const [localMatch] = globalMatch; const placeholderValues = mapValues(patternCfg.placeholders, placeholder => { const expr = placeholder.capture || placeholder; let replacement = localMatch.replace(new RegExp(pattern, 'g'), expr); if (placeholder.translate) { replacement = this.translateWithPatterns(replacement, placeholder.translate); } return replacement; }); translatedMessage = translatedMessage.replace(localMatch, this.translateWithParams(patternCfg, placeholderValues)); globalMatch = globalRegExp.exec(translatedMessage); } }); return translatedMessage; } translateWithParams(patternCfg, params = {}) { const { defaultLang, currentLang, compiler } = this.translateService; const translations = this.translateStore.getTranslations(currentLang); const defaultTranslations = this.translateStore.getTranslations(defaultLang); const originalKey = patternCfg.gettext; let originalValue = originalKey; if (translations) { if (translations[originalKey]) { originalValue = translations[originalKey]; } else if (defaultTranslations) { if (defaultTranslations[originalKey]) { originalValue = defaultTranslations[originalKey]; } } } let key = originalKey; let value = originalValue; const interpolateParams = { ...params, noPatternMessages: true }; let match; const pipeRegex = RegExp('{{\\s*([^\\s]+)\\s*\\|\\s*([^\\s]+)\\s*}}', 'g'); // tslint:disable-next-line:no-conditional-assignment while ((match = pipeRegex.exec(originalKey)) !== null) { const [placeholder, paramName, pipeName] = match; if (this.pipes[pipeName]) { key = key.replace(placeholder, `{{${paramName}}}`); value = value.replace(placeholder, `{{${paramName}}}`); interpolateParams[paramName] = this.pipes[pipeName](params[paramName]); } } if (translations) { const compiledValue = compiler.compile(value, currentLang); this.translateStore.setTranslations(currentLang, { [key]: compiledValue }, true); } return this.translateService.instant(key, interpolateParams); } } /** * @deprecated Consider using the `hookPatternMessages` function instead. */ const HOOK_PATTERN_MESSAGES = new InjectionToken('HOOK_PATTERN_MESSAGES'); /** * You can either provide a single `PatternMessages` as parameter: * ```typescript * hookPatternMessages(...) * ``` * * Or an array to directly register multiple: * ```typescript * hookPatternMessages([...]) * ``` * * Or you provide an Service that implements `ExtensionFactory<PatternMessages>` * ```typescript * export class MyPatternMessagesFactory implements ExtensionFactory<PatternMessages> {...} * ... * hookPatternMessages(MyPatternMessagesFactory) * ``` * A typed alternative to `HOOK_PATTERN_MESSAGES`. * @param patterns The `PatternMessages`'s or `ExtensionFactory` to be provided. * @returns An `Provider` to be provided in your module. */ function hookPatternMessages(patterns, options) { return hookGeneric(patterns, HOOK_PATTERN_MESSAGES, options); } /** * Returns a trimmed translation key. * If the key contains HTML, it also removes all whitespaces. * The reason behind it is that by default Angular compiler removes * whitespaces from adjacent inline elements, * which prevents ngx-translate from finding a matching entry in the dictionary. */ function trimTranslationKey(key) { key = key .replace(/(\r\n|\n|\r)/gm, '') .replace(/\s{2,}/g, ' ') .trim(); const containsHTML = /(<([^>]+)>)/i.test(key); return containsHTML ? key.replace(/\s/g, '') : key; } /** * We want to have translation keys unified, so they don't contain unnecessary spaces and line breaks. * This way we can dynamically build keys from HTML, and match them to extracted string, that might be HTML as well. */ function getDictionaryWithTrimmedKeys(dictionary) { return mapKeys(dictionary, (value, key) => trimTranslationKey(key)); } /** * Removes inline context indicators enclosed in backticks from a translation string. * * @param translation - The original translation string that may contain backtick-enclosed context indicators. * * @example * ```ts * removeContextIndicators("Hello World`context`"); * // Returns: "Hello World" * ``` */ function removeContextIndicators(translation) { return translation.replace(/`[^`]*`/g, ''); } class MissingTranslationCustomHandler { constructor(parser, store, patterns) { this.parser = parser; this.store = store; this.patterns = patterns; this.cache = new Map(); // only in case handle might ever be called with different TranslateService instances this.patternMessagesServices = new Map(); if (!this.patterns) { this.patterns = []; } } handle(params) { const { key: messageKey, interpolateParams, translateService } = params; let translation = this.getFromCache(translateService, messageKey, interpolateParams); if (!translation) { const patternMessagesService = this.getPatternMessagesService(translateService); const patternMessageTranslation = this.getPatternMessageTranslation(patternMessagesService, messageKey, interpolateParams); if (patternMessageTranslation) { translation = patternMessageTranslation; } else { translation = this.parser.interpolate(messageKey, interpolateParams); } translation = removeContextIndicators(translation); this.addToCache(translateService, messageKey, interpolateParams, translation); } return translation; } getPatternMessagesService(translateService) { let service = this.patternMessagesServices.get(translateService); if (!service) { service = new PatternMessagesService(this.patterns, translateService, this.store); this.patternMessagesServices.set(translateService, service); } return service; } getFromCache(translateService, messageKey, interpolateParams) { const { currentLang } = translateService; const currentCache = this.cache.get(currentLang) || new Map(); const cacheKey = this.getCacheKey(messageKey, interpolateParams); return currentCache.get(cacheKey); } addToCache(translateService, messageKey, interpolateParams, translation) { const { currentLang } = translateService; const currentCache = this.cache.get(currentLang) || new Map(); const cacheKey = this.getCacheKey(messageKey, interpolateParams); currentCache.set(cacheKey, translation); this.cache.set(currentLang, currentCache); } getCacheKey(messageKey, interpolateParams) { return interpolateParams ? `${messageKey} ${JSON.stringify(interpolateParams)}` : messageKey; } getPatternMessageTranslation(patternMessagesService, messageKey, interpolateParams) { const shouldTryPatternMessages = !interpolateParams || !interpolateParams.noPatternMessages; if (shouldTryPatternMessages) { return patternMessagesService.translate(messageKey); } return undefined; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: MissingTranslationCustomHandler, deps: [{ token: i1$1.TranslateParser }, { token: i1$1.TranslateStore }, { token: HOOK_PATTERN_MESSAGES, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: MissingTranslationCustomHandler }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: MissingTranslationCustomHandler, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: i1$1.TranslateParser }, { type: i1$1.TranslateStore }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [HOOK_PATTERN_MESSAGES] }] }] }); /* tslint:disable:max-line-length */ const PATTERN_MESSAGES_ALARMS = { '^Alarm created$': { gettext: gettext$1('Alarm created') }, '^Alarm updated$': { gettext: gettext$1('Alarm updated') }, "^Device name: '(.+?)', alarm text: '(.+?)'$": { gettext: gettext$1('Device name: "{{deviceName}}", alarm text: "{{alarmText | translate}}"'), placeholders: { deviceName: '$1', alarmText: '$2' } } }; /* tslint:disable:max-line-length */ const PATTERN_MESSAGES_APPLICATIONS = { '^Application with given name already exist$': { gettext: gettext$1('Application with given name already exists.') }, '^Application with id (.+?) is already assigned to the tenant (.+?)$': { gettext: gettext$1('This application is already assigned to tenant "{{tenant}}".'), placeholders: { tenant: '$2' } }, '^Cannot update/delete application binary via this endpoint$': { gettext: gettext$1('Cannot update/delete application binary via this endpoint') }, '^Error occurred when trying to find an Application for id ID (.+) : Could not find application by ID (\\d+)$': { gettext: gettext$1('Could not find application by ID {{applicationId}}.'), placeholders: { applicationId: '$2' } }, '^Failed to delete application. : Cannot remove application assigned to other tenants.$': { gettext: gettext$1('Could not delete application assigned to other tenants.') }, "^name of Application cannot start with '(.+)' prefix.$": { gettext: gettext$1('Application name must not start with "{{ prefix }}".'), placeholders: { prefix: '$1' } }, '^Microservice application name incorrect. Please use only lower-case letters, digits and dashes. Maximum length is (\\d+) characters.$': { gettext: gettext$1('Microservice application name is incorrect: only lower case letters, digits and dashes allowed. Maximum length: {{maxLength}}.'), placeholders: { maxLength: '$1' } }, '^Platform application cannot be added to, nor removed from any tenant.$': { gettext: gettext$1('Platform application cannot be added to, nor removed from any tenant.') }, '^Failed to refresh application. : Cannot refresh non local-hosted application.$': { gettext: gettext$1('Could not reactivate the application as it is not hosted locally.') }, '^Failed to refresh application. : Cannot refresh application without active version id.$': { gettext: gettext$1('Could not reactivate the application as it has no active version.') }, '^Application deleted$': { gettext: gettext$1('Application deleted') }, '^Microservice application "(.+?)" deleted for tenant "(.+?)"$': { gettext: gettext$1('Microservice application "{{appName}}" deleted for tenant "{{tenant}}"'), placeholders: { appName: '$1', tenant: '$2' } }, '^Hosted application "(.+?)" deleted for tenant "(.+?)"$': { gettext: gettext$1('Hosted application "{{appName}}" deleted for tenant "{{tenant}}"'), placeholders: { appName: '$1', tenant: '$2' } }, '^External application "(.+?)" deleted for tenant "(.+?)"$': { gettext: gettext$1('External application "{{appName}}" deleted for tenant "{{tenant}}"'), placeholders: { appName: '$1', tenant: '$2' } }, '^Application activated$': { gettext: gettext$1('Application activated') }, '^Microservice application "(.+?)" activated: version \\[(.+?)\\] added, activeVersionId \\[(.+?)\\] added$': { gettext: gettext$1('Microservice application "{{appName}}" activated: version "{{version}}" added, activeVersionId "{{activeVersionId}}" added'), placeholders: { appName: '$1', version: '$2', activeVersionId: '$3' } }, '^Hosted application "(.+?)" activated: version \\[(.+?)\\] added, activeVersionId \\[(.+?)\\] added$': { gettext: gettext$1('Hosted application "{{appName}}" activated: version "{{version}}" added, activeVersionId "{{activeVersionId}}" added'), placeholders: { appName: '$1', version: '$2', activeVersionId: '$3