@o3r/configuration
Version:
This module contains configuration-related features such as CMS compatibility, Configuration override, store and debugging. It enables your application runtime configuration and comes with an integrated ng builder to help you generate configurations suppo
803 lines (775 loc) • 40.3 kB
JavaScript
import { deepFill, computeItemIdentifier, sendOtterMessage, filterMessageContent, otterComponentInfoPropertyName } from '@o3r/core';
import { map, distinctUntilChanged, shareReplay, filter, take, switchMap } from 'rxjs/operators';
import * as i0 from '@angular/core';
import { InjectionToken, NgModule, Optional, Inject, Injectable, inject, DestroyRef } from '@angular/core';
import { firstValueFrom, fromEvent, of, combineLatest, BehaviorSubject, shareReplay as shareReplay$1 } from 'rxjs';
import * as i1 from '@ngrx/store';
import { createAction, props, on, createReducer, StoreModule, createFeatureSelector, createSelector, select } from '@ngrx/store';
import { createEntityAdapter } from '@ngrx/entity';
import { takeUntilDestroyed, toSignal, toObservable } from '@angular/core/rxjs-interop';
import * as i2 from '@o3r/logger';
import { LoggerModule } from '@o3r/logger';
/**
* Get the component and library's names based on the configuration name
* @param configurationName Name of the component as found in the store
* @returns Object containing library and component name.
*/
function parseConfigurationName(configurationName) {
const parsedConfigurationName = configurationName.match(/^([^#]*)#(.*)$/i);
if (parsedConfigurationName) {
return {
library: parsedConfigurationName.length > 2 ? parsedConfigurationName[1] : undefined,
componentName: parsedConfigurationName.length > 2 ? parsedConfigurationName[2] : parsedConfigurationName[0]
};
}
}
/**
* Operator to get the configuration from store for a given component and merge it with the global config
* @param defaultValue Default value of the configuration
*/
function getConfiguration(defaultValue) {
return (source) => source.pipe(map((configOverride) => {
return deepFill(defaultValue, configOverride);
}), distinctUntilChanged((prev, current) => JSON.stringify(prev) === JSON.stringify(current)), shareReplay({ refCount: true, bufferSize: 1 }));
}
/** Actions */
const ACTION_SET = '[ConfigOverride] set';
/**
* Clear all overrides and fill the store with the payload
*/
const setConfigOverride = createAction(ACTION_SET, props());
/**
* ConfigOverride Store initial value
*/
const configOverrideInitialState = { configOverrides: {} };
/**
* List of basic actions for ConfigOverride Store
*/
const configOverrideReducerFeatures = [
on(setConfigOverride, (_state, payload) => ({ ...payload.state }))
];
/**
* ConfigOverride Store reducer
*/
const configOverrideReducer = createReducer(configOverrideInitialState, ...configOverrideReducerFeatures);
/**
* Name of the ConfigOverride Store
*/
const CONFIG_OVERRIDE_STORE_NAME = 'configOverride';
/** Token of the ConfigOverride reducer */
const CONFIG_OVERRIDE_REDUCER_TOKEN = new InjectionToken('Feature ConfigOverride Reducer');
/** Provide default reducer for ConfigOverride store */
function getDefaultConfigOverrideReducer() {
return configOverrideReducer;
}
class ConfigOverrideStoreModule {
static forRoot(reducerFactory) {
return {
ngModule: ConfigOverrideStoreModule,
providers: [
{ provide: CONFIG_OVERRIDE_REDUCER_TOKEN, useFactory: reducerFactory }
]
};
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: ConfigOverrideStoreModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
/** @nocollapse */ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.1", ngImport: i0, type: ConfigOverrideStoreModule, imports: [i1.StoreFeatureModule] }); }
/** @nocollapse */ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: ConfigOverrideStoreModule, providers: [
{ provide: CONFIG_OVERRIDE_REDUCER_TOKEN, useFactory: getDefaultConfigOverrideReducer }
], imports: [StoreModule.forFeature(CONFIG_OVERRIDE_STORE_NAME, CONFIG_OVERRIDE_REDUCER_TOKEN)] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: ConfigOverrideStoreModule, decorators: [{
type: NgModule,
args: [{
imports: [
StoreModule.forFeature(CONFIG_OVERRIDE_STORE_NAME, CONFIG_OVERRIDE_REDUCER_TOKEN)
],
providers: [
{ provide: CONFIG_OVERRIDE_REDUCER_TOKEN, useFactory: getDefaultConfigOverrideReducer }
]
}]
}] });
/** Select ConfigOverride State */
const selectConfigOverrideState = createFeatureSelector(CONFIG_OVERRIDE_STORE_NAME);
/** Select the array of ConfigOverride */
const selectConfigOverride = createSelector(selectConfigOverrideState, (state) => state?.configOverrides || {});
/**
* Get the override for given component identifier
* @param componentId
*/
const selectComponentOverrideConfig = (componentId) => createSelector(selectConfigOverride, (configOverrides) => configOverrides[componentId] || {});
/**
* Deserializer
* @param rawObject
*/
const configOverrideStorageDeserializer = (rawObject) => {
if (!rawObject) {
return configOverrideInitialState;
}
return rawObject;
};
const configOverrideStorageSync = {
deserialize: configOverrideStorageDeserializer
};
/** Entity Actions */
const ACTION_CLEAR_ENTITIES = '[Configuration] clear entities';
const ACTION_UPDATE_ENTITIES = '[Configuration] update entities';
const ACTION_UPSERT_ENTITIES = '[Configuration] upsert entities';
const ACTION_SET_ENTITIES = '[Configuration] set entities';
const ACTION_UPDATE_CONFIGURATION_ENTITY = '[Configuration] update configuration entity';
const ACTION_UPSERT_CONFIGURATION_ENTITY = '[Configuration] set configuration entity';
/**
* Upsert one configuration entity
*/
const upsertConfigurationEntity = createAction(ACTION_UPSERT_CONFIGURATION_ENTITY, props());
/**
* Update one configuration entity
*/
const updateConfigurationEntity = createAction(ACTION_UPDATE_CONFIGURATION_ENTITY, props());
/**
* Clear all configuration and fill the store with the payload
*/
const setConfigurationEntities = createAction(ACTION_SET_ENTITIES, props());
/**
* Update configuration with known IDs, ignore the new ones
*/
const updateConfigurationEntities = createAction(ACTION_UPDATE_ENTITIES, props());
/**
* Update configuration with known IDs, insert the new ones
*/
const upsertConfigurationEntities = createAction(ACTION_UPSERT_ENTITIES, props());
/**
* Clear only the entities, keeps the other attributes in the state
*/
const clearConfigurationEntities = createAction(ACTION_CLEAR_ENTITIES);
/**
* compute the configuration into SetEntitiesPayload
* @param customConfigObject array of configurations
*/
function computeConfiguration(customConfigObject) {
const entities = customConfigObject.reduce((acc, conf) => {
const id = computeItemIdentifier(conf.name, conf.library);
acc[id] = { ...conf.config, id };
return acc;
}, {});
return { entities };
}
/**
* Configuration Store adapter
*/
const configurationAdapter = createEntityAdapter({
selectId: (model) => model.id
});
/**
* Configuration Store initial value
*/
const configurationInitialState = configurationAdapter.getInitialState({});
/**
* List of basic actions for Configuration Store
*/
const configurationReducerFeatures = [
on(setConfigurationEntities, (state, payload) => configurationAdapter.addMany(Object.keys(payload).map((id) => ({ ...payload[id], id })), configurationAdapter.removeAll(state))),
on(updateConfigurationEntities, (state, payload) => configurationAdapter.updateMany(Object.keys(payload.entities).map((id) => ({ id: id, changes: payload.entities[id] })), state)),
on(upsertConfigurationEntities, (state, payload) => configurationAdapter.upsertMany(Object.keys(payload.entities).map((id) => ({ ...payload.entities[id], id })), state)),
on(clearConfigurationEntities, (state) => configurationAdapter.removeAll(state)),
on(updateConfigurationEntity, (state, payload) => configurationAdapter.updateOne({ id: payload.id, changes: payload.configuration }, state)),
on(upsertConfigurationEntity, (state, payload) => configurationAdapter.upsertOne({ id: payload.id, ...payload.configuration }, state))
];
/**
* Configuration Store reducer
*/
const configurationReducer = createReducer(configurationInitialState, ...configurationReducerFeatures);
/**
* Name of the Configuration Store
*/
const CONFIGURATION_STORE_NAME = 'configuration';
/**
* ID of the global configuration
*/
const globalConfigurationId = 'global';
/** Token of the Configuration reducer */
const CONFIGURATION_REDUCER_TOKEN = new InjectionToken('Feature Configuration Reducer');
/** Provide default reducer for Configuration store */
function getDefaultConfigurationReducer() {
return configurationReducer;
}
class ConfigurationStoreModule {
static forRoot(reducerFactory) {
return {
ngModule: ConfigurationStoreModule,
providers: [
{ provide: CONFIGURATION_REDUCER_TOKEN, useFactory: reducerFactory }
]
};
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: ConfigurationStoreModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
/** @nocollapse */ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.1", ngImport: i0, type: ConfigurationStoreModule, imports: [i1.StoreFeatureModule] }); }
/** @nocollapse */ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: ConfigurationStoreModule, providers: [
{ provide: CONFIGURATION_REDUCER_TOKEN, useFactory: getDefaultConfigurationReducer }
], imports: [StoreModule.forFeature(CONFIGURATION_STORE_NAME, CONFIGURATION_REDUCER_TOKEN)] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: ConfigurationStoreModule, decorators: [{
type: NgModule,
args: [{
imports: [
StoreModule.forFeature(CONFIGURATION_STORE_NAME, CONFIGURATION_REDUCER_TOKEN)
],
providers: [
{ provide: CONFIGURATION_REDUCER_TOKEN, useFactory: getDefaultConfigurationReducer }
]
}]
}] });
const { selectIds, selectEntities, selectAll, selectTotal } = configurationAdapter.getSelectors();
/**
* Select Configuration State
* Note: the usage of createSelector is to avoid warning printing because of potentially undefined feature store
*/
const selectConfigurationState = createSelector((state) => state[CONFIGURATION_STORE_NAME], (state) => state);
/** Select the array of Configuration ids */
const selectConfigurationIds = createSelector(selectConfigurationState, (state) => state ? selectIds(state) : []);
/** Select the array of Configuration */
const selectAllConfiguration = createSelector(selectConfigurationState, (state) => state ? selectAll(state) : []);
/** Select the dictionary of Configuration entities */
const selectConfigurationEntities = createSelector(selectConfigurationState, (state) => state ? selectEntities(state) : {});
/** Select the total Configuration count */
const selectConfigurationTotal = createSelector(selectConfigurationState, (state) => state ? selectTotal(state) : 0);
/**
* Select the configuration for component with id
* @param props property of the selector
* @param props.id id of the component
*/
const selectConfigurationForComponent = (props) => createSelector(selectConfigurationEntities, (entities) => (entities?.[props.id] || {}));
/**
* Select the global configuration
*/
const selectGlobalConfiguration = createSelector(selectConfigurationForComponent({ id: globalConfigurationId }), (config) => config);
const configurationStorageSync = {
deserialize: (rawObject) => {
if (!rawObject || !rawObject.ids) {
return configurationInitialState;
}
const storeObject = configurationAdapter.getInitialState(rawObject);
return storeObject;
}
};
const OTTER_CONFIGURATION_DEVTOOLS_DEFAULT_OPTIONS = {
defaultLibraryName: '@o3r/components',
defaultJsonFilename: 'partial-static-config.json',
isActivatedOnBootstrap: false,
isActivatedOnBootstrapWhenCMSContext: true
};
const OTTER_CONFIGURATION_DEVTOOLS_OPTIONS = new InjectionToken('Otter Configuration Devtools options');
class OtterConfigurationDevtools {
constructor(store, appRef, options) {
this.store = store;
this.appRef = appRef;
this.options = options;
this.options = { ...OTTER_CONFIGURATION_DEVTOOLS_DEFAULT_OPTIONS, ...options };
/** Full configuration store */
this.configurationEntities$ = this.store.pipe(select(selectConfigurationEntities), map((entities) => Object.values(entities)
.filter((entity) => !!entity)
.map((entity) => {
const { id, ...config } = entity;
const { library = this.options.defaultLibraryName, componentName: name = id } = parseConfigurationName(id) || {};
return { name, config, library };
})), shareReplay(1));
}
/**
* Get configuration name based on input information
* @param selector Selector for a component configuration. It can be a string in the form library#componentName (i.e: @my-lib/shared-components#HeaderContComponent)
* or an object with the component and library names (i.e: {library:"@my-lib/shared-components", componentName:'HeaderContComponent'})
* @param isFallbackName Determine if the name requested is a fallback name
* @returns string in the format library#componentName (i.e: "@my-lib/shared-components#HeaderContComponent")
*/
getComponentConfigName(selector, isFallbackName = false) {
if (!isFallbackName) {
return typeof selector === 'string' ? selector : computeItemIdentifier(selector.componentName, selector.library || this.options.defaultLibraryName);
}
return typeof selector === 'string'
? computeItemIdentifier(selector, this.options.defaultLibraryName || 'global')
: computeItemIdentifier(selector.componentName, this.options.defaultLibraryName || 'global');
}
/**
* Get the list of components which have a configuration loaded in the store
*/
getComponentsWithConfiguration() {
return firstValueFrom(this.store
.pipe(select(selectConfigurationIds), map((ids) => ids
.map((configName) => parseConfigurationName(configName.toString()))
.filter((parsedName) => !!parsedName)
.map((parsedName) => parsedName.componentName + (parsedName.library ? ' from ' + parsedName.library : '')))));
}
/**
* Set a specified value of a component configuration
* @param selector Selector for a component configuration
* @param configProperty Name of the configuration property to set
* @param configValue Value of the configuration property to set
*/
setDynamicConfig(selector, configProperty, configValue) {
this.store.dispatch(upsertConfigurationEntity({
id: this.getComponentConfigName(selector),
configuration: { [configProperty]: configValue }
}));
this.appRef.tick();
}
/**
* Get the configuration for a specific component
* @param selector Selector for a component configuration. It can be a string in the form library#configurationName (i.e: @my-lib/shared-components#HeaderPresConfig)
* or an object with the configuration and library names (i.e: {library:"@my-lib/shared-components", componentName:'HeaderPresConfig'})
*/
getCurrentConfigurationFor(selector) {
return firstValueFrom(this.store.pipe(select(selectConfigurationEntities), map((entities) => entities[this.getComponentConfigName(selector)] || entities[this.getComponentConfigName(selector, true)]), filter((entity) => !!entity), map((entity) => {
const { id, ...configuration } = entity;
return configuration;
})));
}
/**
* Get the whole configuration of the application
*/
getConfiguration() {
return firstValueFrom(this.configurationEntities$);
}
/**
* Load a json configuration
* @param configurations configurations to load
*/
loadConfiguration(configurations) {
this.store.dispatch(upsertConfigurationEntities(computeConfiguration(typeof configurations === 'string' ? JSON.parse(configurations) : configurations)));
this.appRef.tick();
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: OtterConfigurationDevtools, deps: [{ token: i1.Store }, { token: i0.ApplicationRef }, { token: OTTER_CONFIGURATION_DEVTOOLS_OPTIONS, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
/** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: OtterConfigurationDevtools, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: OtterConfigurationDevtools, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: () => [{ type: i1.Store }, { type: i0.ApplicationRef }, { type: undefined, decorators: [{
type: Optional
}, {
type: Inject,
args: [OTTER_CONFIGURATION_DEVTOOLS_OPTIONS]
}] }] });
/* eslint-disable no-console -- service to log message in the console */
class ConfigurationDevtoolsConsoleService {
/** Name of the Window property to access to the devtools */
static { this.windowModuleName = 'configuration'; }
constructor(configurationDevtools, options) {
this.configurationDevtools = configurationDevtools;
this.options = options;
this.options = { ...OTTER_CONFIGURATION_DEVTOOLS_DEFAULT_OPTIONS, ...options };
if (this.options.isActivatedOnBootstrap
|| (this.options.isActivatedOnBootstrapWhenCMSContext
&& document.body.dataset.cmscontext === 'true')) {
this.activate();
}
}
downloadJSON(content, fileName = this.options.defaultJsonFilename) {
const a = document.createElement('a');
const file = new Blob([content], { type: 'text/plain' });
a.href = URL.createObjectURL(file);
a.download = fileName;
a.click();
}
copyElementToClipboard(content) {
const input = document.createElement('textarea');
input.value = content;
document.body.append(input);
input.select();
document.execCommand('copy');
input.remove();
}
/**
* Set a specified value of a component configuration
* @param selector Selector for a component configuration
* @param configProperty Name of the configuration property to set
* @param configValue Value of the configuration property to set
*/
setDynamicConfig(selector, configProperty, configValue) {
this.configurationDevtools.setDynamicConfig(selector, configProperty, configValue);
}
/** @inheritDoc */
activate() {
const windowWithDevtools = window;
windowWithDevtools._OTTER_DEVTOOLS_ ||= {};
windowWithDevtools._OTTER_DEVTOOLS_[ConfigurationDevtoolsConsoleService.windowModuleName] = this;
console.info(`Otter Configuration Devtools is now accessible via the _OTTER_DEVTOOLS_.${ConfigurationDevtoolsConsoleService.windowModuleName} variable`);
}
/**
* Display the list of configurations loaded in the store and the library they originate from
* @returns array with the configurations and libraries for example: ["LibComponentsCommonRuntimeConfig from @my-lib/shared-common"]
*/
async displayComponentsWithConfiguration() {
const selectors = await this.configurationDevtools.getComponentsWithConfiguration();
console.log(selectors);
}
/**
* Display the configuration for a specific component
* @param selector Selector for a component configuration. It can be a string in the form library#configurationName (i.e: '@my-lib/shared-components#HeaderContConfig')
* or an object with the configuration and library names (i.e: {library:"@my-lib/shared-components", componentName:'HeaderContConfig'}).
* Note the object input componentName expects a configuration name not a component name.
* @returns Configuration object (i.e: {airlineLogoPath: "img/airlines/icon-BH.svg", displayLanguageSelector: false})
*/
async displayCurrentConfigurationFor(selector) {
const configuration = await this.configurationDevtools.getCurrentConfigurationFor(selector);
console.log(configuration);
}
/**
* Download the JSON file of the whole configuration
* @param fileName Name of the file to download
*/
async saveConfiguration(fileName = this.options.defaultJsonFilename) {
const configs = await firstValueFrom(this.configurationDevtools.configurationEntities$);
this.downloadJSON(JSON.stringify(configs), fileName);
}
/**
* Display the whole configuration of the application
*/
async displayConfiguration() {
const configs = await this.configurationDevtools.getConfiguration();
console.log(configs);
}
/**
* Display a bookmark to generate the current configuration
*/
async displayConfigurationBookmark() {
const content = await this.configurationDevtools.getConfiguration();
console.log('BOOKMARK');
console.log(`javascript:window._OTTER_DEVTOOLS_.updateConfigurations('${JSON.stringify(content).replace(/'/g, '\\\'')}')`);
}
/**
* Copy the whole configuration to the clipboard
*/
async copyConfigurationToClipboard() {
const configs = await firstValueFrom(this.configurationDevtools.configurationEntities$);
this.copyElementToClipboard(JSON.stringify(configs));
}
/**
* Replace N configurations in one shot
* @param configurations array of configurations to update
*/
updateConfigurations(configurations) {
this.configurationDevtools.loadConfiguration(configurations);
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: ConfigurationDevtoolsConsoleService, deps: [{ token: OtterConfigurationDevtools }, { token: OTTER_CONFIGURATION_DEVTOOLS_OPTIONS, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
/** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: ConfigurationDevtoolsConsoleService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: ConfigurationDevtoolsConsoleService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: () => [{ type: OtterConfigurationDevtools }, { type: undefined, decorators: [{
type: Optional
}, {
type: Inject,
args: [OTTER_CONFIGURATION_DEVTOOLS_OPTIONS]
}] }] });
/**
* Determine if the given message is a Configuration message
* @param message message to check
*/
const isConfigurationMessage = (message) => {
return message && (message.dataType === 'configurations'
|| message.dataType === 'updateConfig'
|| message.dataType === 'requestMessages'
|| message.dataType === 'connect');
};
class ConfigurationDevtoolsMessageService {
constructor(store, logger, configurationDevtools, options) {
this.store = store;
this.logger = logger;
this.configurationDevtools = configurationDevtools;
this.options = options;
this.sendMessage = (sendOtterMessage);
this.destroyRef = inject(DestroyRef);
this.options = { ...OTTER_CONFIGURATION_DEVTOOLS_DEFAULT_OPTIONS, ...options };
if (this.options.isActivatedOnBootstrap) {
this.activate();
}
}
async sendCurrentConfigurationState() {
const configurations = await firstValueFrom(this.store.pipe(select(selectConfigurationEntities)));
this.sendMessage('configurations', { configurations });
}
/**
* Function to trigger a re-send a requested messages to the Otter Chrome DevTools extension
* @param only restricted list of messages to re-send
*/
handleReEmitRequest(only) {
if (!only || only.includes('configurations')) {
return this.sendCurrentConfigurationState();
}
}
/**
* Function to handle the incoming messages from Otter Chrome DevTools extension
* @param message message coming from the Otter Chrome DevTools extension
*/
async handleEvents(message) {
this.logger.debug('Message handling by the configuration service', message);
switch (message.dataType) {
case 'connect': {
await this.connectPlugin();
break;
}
case 'requestMessages': {
await this.handleReEmitRequest(message.only);
break;
}
case 'updateConfig': {
this.configurationDevtools.loadConfiguration([{
name: message.id,
config: message.configValue
}]);
break;
}
default: {
this.logger.warn('Message ignored by the configuration service', message);
}
}
}
/**
* Function to connect the plugin to the Otter DevTools extension
*/
connectPlugin() {
this.logger.debug('Otter DevTools is plugged to configuration service of the application');
return this.sendCurrentConfigurationState();
}
/** @inheritDoc */
activate() {
fromEvent(window, 'message').pipe(takeUntilDestroyed(this.destroyRef), filterMessageContent(isConfigurationMessage)).subscribe((e) => this.handleEvents(e));
this.store.pipe(select(selectConfigurationEntities), takeUntilDestroyed(this.destroyRef)).subscribe((configurations) => this.sendMessage('configurations', { configurations }));
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: ConfigurationDevtoolsMessageService, deps: [{ token: i1.Store }, { token: i2.LoggerService }, { token: OtterConfigurationDevtools }, { token: OTTER_CONFIGURATION_DEVTOOLS_OPTIONS, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
/** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: ConfigurationDevtoolsMessageService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: ConfigurationDevtoolsMessageService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: () => [{ type: i1.Store }, { type: i2.LoggerService }, { type: OtterConfigurationDevtools }, { type: undefined, decorators: [{
type: Optional
}, {
type: Inject,
args: [OTTER_CONFIGURATION_DEVTOOLS_OPTIONS]
}] }] });
class ConfigurationDevtoolsModule {
/**
* Initialize Otter Devtools
* @param options
*/
static instrument(options) {
return {
ngModule: ConfigurationDevtoolsModule,
providers: [
{ provide: OTTER_CONFIGURATION_DEVTOOLS_OPTIONS, useValue: { ...OTTER_CONFIGURATION_DEVTOOLS_DEFAULT_OPTIONS, ...options }, multi: false },
ConfigurationDevtoolsMessageService,
ConfigurationDevtoolsConsoleService,
OtterConfigurationDevtools
]
};
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: ConfigurationDevtoolsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
/** @nocollapse */ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.1", ngImport: i0, type: ConfigurationDevtoolsModule, imports: [StoreModule,
ConfigurationStoreModule] }); }
/** @nocollapse */ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: ConfigurationDevtoolsModule, providers: [
{ provide: OTTER_CONFIGURATION_DEVTOOLS_OPTIONS, useValue: OTTER_CONFIGURATION_DEVTOOLS_DEFAULT_OPTIONS },
ConfigurationDevtoolsMessageService,
ConfigurationDevtoolsConsoleService,
OtterConfigurationDevtools
], imports: [StoreModule,
ConfigurationStoreModule] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: ConfigurationDevtoolsModule, decorators: [{
type: NgModule,
args: [{
imports: [
StoreModule,
ConfigurationStoreModule
],
providers: [
{ provide: OTTER_CONFIGURATION_DEVTOOLS_OPTIONS, useValue: OTTER_CONFIGURATION_DEVTOOLS_DEFAULT_OPTIONS },
ConfigurationDevtoolsMessageService,
ConfigurationDevtoolsConsoleService,
OtterConfigurationDevtools
]
}]
}] });
class ConfigurationBaseServiceModule {
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: ConfigurationBaseServiceModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
/** @nocollapse */ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.1", ngImport: i0, type: ConfigurationBaseServiceModule, imports: [ConfigurationStoreModule, LoggerModule] }); }
/** @nocollapse */ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: ConfigurationBaseServiceModule, imports: [ConfigurationStoreModule, LoggerModule] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: ConfigurationBaseServiceModule, decorators: [{
type: NgModule,
args: [{
imports: [ConfigurationStoreModule, LoggerModule]
}]
}] });
const jsonStringifyDiff = (obj1, obj2) => JSON.stringify(obj1) === JSON.stringify(obj2);
/**
* Configuration service
*/
class ConfigurationBaseService {
constructor(store) {
this.store = store;
this.extendedConfiguration = {};
}
/**
* Update a specific component config or add it to the store if does not exist
* @param configuration to edit/add
* @param configurationId Configuration ID
*/
upsertConfiguration(configuration, configurationId = globalConfigurationId) {
this.store.dispatch(upsertConfigurationEntity({ id: configurationId, configuration }));
}
/**
* Update a specific component config
* @param configuration Partial config to edit
* @param configurationId Configuration ID
*/
updateConfiguration(configuration, configurationId = globalConfigurationId) {
this.store.dispatch(updateConfigurationEntity({ id: configurationId, configuration }));
}
/**
* This function will get the configuration stored in the data attribute of the html's body tag
* @param configTagName Value used to identify the data attribute where the config is pushed in the index.html
*/
getConfigFromBodyTag(configTagName = 'staticconfig') {
const bootstrapConfigString = document.body.dataset[configTagName];
const customConfigObject = bootstrapConfigString ? JSON.parse(bootstrapConfigString) : [];
if (customConfigObject.length > 0) {
this.computeConfiguration(customConfigObject);
}
}
/**
* Transform the custom configuration in store configuration model
* @param customConfigObject Configuration object (extracted from body tag for static config or downloaded in case of dynamic config)
*/
computeConfiguration(customConfigObject) {
this.store.dispatch(upsertConfigurationEntities(computeConfiguration(customConfigObject)));
}
/**
* Complete a stored configuration by adding the missing fields
* @param extension Configuration extension to be included in the store
* @param configurationId Configuration ID to extend
* @param forceUpdate Force update the configuration in the store
*/
extendConfiguration(extension, configurationId = globalConfigurationId, forceUpdate = false) {
if (this.extendedConfiguration[configurationId] && !forceUpdate) {
return;
}
this.extendedConfiguration[configurationId] = true;
this.store.pipe(select(selectConfigurationEntities), take(1), map((storedConfigs) => configurationId in storedConfigs ? deepFill(extension, storedConfigs[configurationId]) : extension)).subscribe((extendedConfig) => this.upsertConfiguration(extendedConfig, configurationId));
}
/**
* Operator to get the configuration from store for a given component and merge it with the global config
* @param id Id of the component
* @param defaultValue Default value of the configuration
*/
getComponentConfig(id, defaultValue) {
return (source) => {
const componentConfigurationFromStore$ = this.getConfig(id);
return source.pipe(switchMap((overrideConfig) => componentConfigurationFromStore$.pipe(map((componentConfigurationFromStore) => {
const config = componentConfigurationFromStore ? deepFill(defaultValue, componentConfigurationFromStore) : defaultValue;
return overrideConfig ? deepFill(config, overrideConfig) : config;
}))));
};
}
/**
* Get an observable of the configuration from store for a given component and merge it with the global config + the config overrides from the rules engine
* @param id Id of the component
*/
getConfig(id) {
const globalConfig$ = this.store.pipe(select(selectGlobalConfiguration));
const componentConfig$ = id === globalConfigurationId ? of({}) : this.store.pipe(select(selectConfigurationForComponent({ id })));
const overrideConfig$ = this.store.pipe(select(selectComponentOverrideConfig(id)));
return combineLatest([globalConfig$, componentConfig$, overrideConfig$]).pipe(map(([globalConfig, componentConfig, overrideConfig]) => ({ ...globalConfig, ...componentConfig, ...overrideConfig })), distinctUntilChanged(jsonStringifyDiff));
}
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: ConfigurationBaseService, deps: [{ token: i1.Store }], target: i0.ɵɵFactoryTarget.Injectable }); }
/** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: ConfigurationBaseService, providedIn: ConfigurationBaseServiceModule }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: ConfigurationBaseService, decorators: [{
type: Injectable,
args: [{
providedIn: ConfigurationBaseServiceModule
}]
}], ctorParameters: () => [{ type: i1.Store }] });
const decorator = (target, key) => {
const privateField = `_${key}`;
if (delete target[key]) {
Object.defineProperty(target, key, {
get: function () {
return this[privateField];
},
set: function (value) {
this[privateField] = value;
if (this[otterComponentInfoPropertyName]) {
this[otterComponentInfoPropertyName].configId = this[privateField].configId;
}
},
enumerable: true,
configurable: true
});
}
};
/**
* Decorator to identify the component's configuration
*/
// eslint-disable-next-line @typescript-eslint/naming-convention -- decorator should be PascalCase
function O3rConfig() {
return decorator;
}
class ConfigurationObserver {
constructor(
/** Configuration ID */
configId, defaultConfig, configurationService) {
this.configId = configId;
/** Inner subscriber */
this.subscriber = new BehaviorSubject({});
if (configurationService) {
configurationService.extendConfiguration(defaultConfig, configId);
}
this.observable = this.subscriber
.pipe(configurationService ? configurationService.getComponentConfig(configId, defaultConfig) : getConfiguration(defaultConfig), shareReplay({ refCount: true, bufferSize: 1 }));
}
/** @inheritdoc */
next(value) {
this.subscriber.next(value || {});
}
/** @inheritdoc */
error(err) {
this.subscriber.error(err);
this.closed = true;
}
/** @inheritdoc */
complete() {
this.subscriber.complete();
this.closed = true;
}
/**
* @inheritdoc
*/
asObservable() {
return this.observable;
}
}
/**
* Get a configuration signal
* @param configInput
* @param configId
* @param defaultConfig
*/
function configSignal(configInput, configId, defaultConfig) {
const configurationService = inject(ConfigurationBaseService, { optional: true });
if (configurationService) {
configurationService.extendConfiguration(defaultConfig, configId);
}
const signal = toSignal(toObservable(configInput).pipe(configurationService ? configurationService.getComponentConfig(configId, defaultConfig) : getConfiguration(defaultConfig), shareReplay$1({ bufferSize: 1, refCount: true })), { initialValue: defaultConfig });
signal.configId = configId;
return signal;
}
/**
* Generated bundle index. Do not edit.
*/
export { CONFIGURATION_REDUCER_TOKEN, CONFIGURATION_STORE_NAME, CONFIG_OVERRIDE_REDUCER_TOKEN, CONFIG_OVERRIDE_STORE_NAME, ConfigOverrideStoreModule, ConfigurationBaseService, ConfigurationBaseServiceModule, ConfigurationDevtoolsConsoleService, ConfigurationDevtoolsMessageService, ConfigurationDevtoolsModule, ConfigurationObserver, ConfigurationStoreModule, O3rConfig, OTTER_CONFIGURATION_DEVTOOLS_DEFAULT_OPTIONS, OTTER_CONFIGURATION_DEVTOOLS_OPTIONS, OtterConfigurationDevtools, clearConfigurationEntities, computeConfiguration, configOverrideInitialState, configOverrideReducer, configOverrideReducerFeatures, configOverrideStorageDeserializer, configOverrideStorageSync, configSignal, configurationAdapter, configurationInitialState, configurationReducer, configurationReducerFeatures, configurationStorageSync, getConfiguration, getDefaultConfigOverrideReducer, getDefaultConfigurationReducer, globalConfigurationId, isConfigurationMessage, parseConfigurationName, selectAllConfiguration, selectComponentOverrideConfig, selectConfigOverride, selectConfigOverrideState, selectConfigurationEntities, selectConfigurationForComponent, selectConfigurationIds, selectConfigurationState, selectConfigurationTotal, selectGlobalConfiguration, setConfigOverride, setConfigurationEntities, updateConfigurationEntities, updateConfigurationEntity, upsertConfigurationEntities, upsertConfigurationEntity };
//# sourceMappingURL=o3r-configuration.mjs.map