@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
1,124 lines (1,107 loc) • 2.74 MB
JavaScript
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