@tilde-nlp/ngx-common 8.1.43 → 8.1.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,10 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, EventEmitter, Component, Input, ViewChild, Output, NgModule, Optional, Inject, Pipe, inject, Directive, HostListener, HostBinding, ViewChildren, input, effect, ElementRef, ContentChild, ContentChildren, InjectionToken, signal, output, computed, makeEnvironmentProviders } from '@angular/core';
2
+ import { Injectable, EventEmitter, Component, Input, ViewChild, Output, NgModule, Optional, Inject, Pipe, inject, Directive, HostListener, HostBinding, ViewChildren, input, effect, ElementRef, ContentChild, ContentChildren, InjectionToken, signal, output, computed, makeEnvironmentProviders, provideAppInitializer } from '@angular/core';
3
3
  import * as i2 from '@angular/material/icon';
4
4
  import { MatIconModule, MatIcon } from '@angular/material/icon';
5
5
  import * as i2$1 from '@angular/platform-browser';
6
6
  import { Title } from '@angular/platform-browser';
7
- import { Subject, map, Observable, of, take, BehaviorSubject, tap, forkJoin, takeUntil, finalize, catchError, fromEvent, debounceTime } from 'rxjs';
7
+ import { Subject, map, Observable, of, take, BehaviorSubject, tap, forkJoin, takeUntil, finalize, catchError, fromEvent, debounceTime, ReplaySubject, filter, first } from 'rxjs';
8
8
  import * as i1$1 from '@angular/common';
9
9
  import { CommonModule, TitleCasePipe, AsyncPipe, DatePipe } from '@angular/common';
10
10
  import { FlexLayoutModule } from '@ngbracket/ngx-layout';
@@ -4417,7 +4417,7 @@ class MultiFunctionalTableComponent {
4417
4417
  }], tableElementRef: [{
4418
4418
  type: ViewChild,
4419
4419
  args: [MatTable, { read: ElementRef }]
4420
- }] }); })();
4420
+ }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }] }); })();
4421
4421
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MultiFunctionalTableComponent, { className: "MultiFunctionalTableComponent", filePath: "lib/multi-functional-table/multi-functional-table.component.ts", lineNumber: 27 }); })();
4422
4422
 
4423
4423
  class StatusDisplayModule {
@@ -8520,7 +8520,7 @@ class SubscriptionComponent {
8520
8520
  CommonModule,
8521
8521
  MatButtonModule
8522
8522
  ], template: "@if(token()){\r\n <!-- narrow type to non null property -->\r\n @let userToken = token()!;\r\n\r\n <h3 class=\"subscription-section-title text-l-semi-bold\">{{ \"SUBSCRIPTION.USER_INFO\" | translate }}</h3>\r\n\r\n <div class=\"subscription-data-row\">\r\n <div class=\"subscription-label\">\r\n <span class=\"label\">{{ \"SUBSCRIPTION.LABEL_NAME\" | translate }}</span>\r\n </div>\r\n\r\n <div class=\"subscription-data\">\r\n <span>{{ userToken.name }}</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"subscription-data-row\">\r\n <div class=\"subscription-label\">\r\n <span class=\"label\">{{ \"SUBSCRIPTION.LABEL_EMAIL\" | translate }}</span>\r\n </div>\r\n\r\n <div class=\"subscription-data\">\r\n <span>{{ userToken.email }}</span>\r\n </div>\r\n </div>\r\n}\r\n\r\n@if(subscription()){\r\n @let currentSubscription = subscription()!;\r\n\r\n <div fxLayout=\"row\" class=\"subscription-data-row\">\r\n <div class=\"subscription-label\">\r\n <span class=\"label\">{{ \"SUBSCRIPTION.LABEL_SUBSCRIPTION_PLAN_ID\" | translate }}</span>\r\n </div>\r\n\r\n <div class=\"subscription-data\">\r\n <ng-container>\r\n <span>\r\n {{ currentSubscription.Label ? currentSubscription.Label : currentSubscription.PlanId }}\r\n\r\n @switch (subscriptionState()) {\r\n @case(subscriptionStates.TRIAL){\r\n <span> ({{ \"SUBSCRIPTION.TRIAL_END_DATE\" | translate: { date: currentSubscription.EndDate | date: \"dd.MM.yyyy.\" } }}) </span>\r\n }\r\n @case (subscriptionStates.ACTIVE) {\r\n @if(currentSubscription.EndDate){\r\n <span> ({{ \"SUBSCRIPTION.END_DATE\" | translate: { date: currentSubscription.EndDate | date: \"dd.MM.yyyy.\" } }}) </span>\r\n }\r\n }\r\n @case (subscriptionStates.ENDED) {\r\n ({{ \"SUBSCRIPTION.END_DATE\" | translate: { date: currentSubscription.EndDate | date: \"dd.MM.yyyy.\" } }})\r\n }\r\n }\r\n </span>\r\n </ng-container>\r\n </div>\r\n </div>\r\n\r\n @if(currentSubscription.EndDate){\r\n <div class=\"subscription-data-row\">\r\n <div class=\"subscription-label\">\r\n <span class=\"label\">{{ \"SUBSCRIPTION.LABEL_SUBSCRIPTION_END_DATE\" | translate }}</span>\r\n </div>\r\n\r\n <div class=\"subscription-data\">\r\n <ng-container>\r\n <span>{{currentSubscription.EndDate | date: \"dd.MM.yyyy\"}}</span>\r\n </ng-container>\r\n </div>\r\n </div>\r\n }\r\n\r\n @if(currentSubscription.Addons?.length){\r\n <div fxLayout=\"row\" class=\"subscription-data-row\">\r\n <div class=\"subscription-label\">\r\n <span class=\"label\">{{ \"SUBSCRIPTION.ADDITIONS_PLUGINS\" | translate }}</span>\r\n </div>\r\n\r\n <div class=\"subscription-data\">\r\n @for (item of currentSubscription.Addons; track $index; let i = $index) {\r\n <span>{{ item.id }}</span>\r\n @if(item.qt>1){\r\n <span> ({{item.qt}})</span>\r\n }\r\n <span> {{ currentSubscription.Addons!.length - 1 > i ? \",\" : \"\" }} </span>\r\n }\r\n </div>\r\n </div>\r\n }\r\n}\r\n\r\n@if(visibleQuotas().length){\r\n <h3 class=\"subscription-section-title text-l-semi-bold\">{{ \"SUBSCRIPTION.LIMITS\" | translate }}</h3>\r\n\r\n @for (currentQuota of visibleQuotas(); track $index) {\r\n <div class=\"subscription-limit-wrapper\">\r\n <mat-progress-bar [value]=\"currentQuota.percentUsed\"></mat-progress-bar>\r\n <div class=\"subscription-limit\">\r\n <span class=\"text-l-semi-bold\">\r\n {{ (\"QUOTA.\" + (currentQuota.quotaType.toString() | uppercase) + (currentQuota.isUnlimited? \"_UNLIMITED\": \"\")) | translate: { used: currentQuota.convertedUsed, limit: currentQuota.convertedLimit, percent: currentQuota.percentUsed} }}\r\n </span>\r\n </div>\r\n </div>\r\n }\r\n}\r\n\r\n<!-- Add some extra info, such as disclaimer etc -->\r\n <ng-content></ng-content>\r\n\r\n<div class=\"subscription-actions-wrapper\">\r\n @if(chargebeeSettings()){\r\n <a mat-flat-button color=\"accent\" class=\"manage-sub-btn button-row-element\" href=\"javascript:void(0)\" data-cb-type=\"portal\" (click)=\"manageClicked()\">\r\n {{ \"SUBSCRIPTION.MANAGE\" | translate }}\r\n </a>\r\n }\r\n @if(contactUsUrl()){\r\n <a mat-stroked-button [attr.href]=\"contactUsUrl()\" target=\"_blank\" color=\"accent\" (click)=\"contactUsClicked()\">{{\"SUBSCRIPTION.CONTACT\" | translate}}</a>\r\n }\r\n</div>\r\n", styles: [":host{display:block}mat-card{margin:1.5rem}.subscription-container:not(.mobile){margin:16px 32px}.subscription-section-title{margin:32px 0 18px}.subscription-data-row{border-bottom:2px solid var(--base-70);margin-bottom:18px;flex-direction:row;display:flex}.subscription-data{color:var(--base-30);word-wrap:break-word;margin-left:.5rem}.subscription-limit-wrapper,.subscription-limit{margin-top:16px}.subscription-actions-wrapper{padding:32px 0 18px;display:flex;flex-direction:row;gap:24px}.subscription-manage-link{text-decoration:none}.subscription-container:not(.mobile,.empty){width:75%}.subscription-label:not(.mobile,.empty){width:35%}.subscription-data:not(.mobile,.empty){width:65%}.untranslated-files-disclaimer{margin-top:16px;margin-bottom:10px;color:var(--base-40)}.no-subscription-message{margin-top:32px;color:var(--base-40)}.contact-sales-btn{text-align:center}.contact-sales-btn:hover{text-decoration:none}\n"] }]
8523
- }], null, null); })();
8523
+ }], null, { token: [{ type: i0.Input, args: [{ isSignal: true, alias: "token", required: true }] }], subscription: [{ type: i0.Input, args: [{ isSignal: true, alias: "subscription", required: true }] }], quota: [{ type: i0.Input, args: [{ isSignal: true, alias: "quota", required: true }] }], visibleQuotaTypes: [{ type: i0.Input, args: [{ isSignal: true, alias: "visibleQuotaTypes", required: false }] }], contactUsUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "contactUsUrl", required: false }] }], chargebeeSettings: [{ type: i0.Input, args: [{ isSignal: true, alias: "chargebeeSettings", required: false }] }], manageClick: [{ type: i0.Output, args: ["manageClick"] }], contactUsClick: [{ type: i0.Output, args: ["contactUsClick"] }] }); })();
8524
8524
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(SubscriptionComponent, { className: "SubscriptionComponent", filePath: "lib/subscription/subscription.component.ts", lineNumber: 26 }); })();
8525
8525
 
8526
8526
  var AccessibilityFontSizes;
@@ -9460,22 +9460,14 @@ class CookieConsentComponent {
9460
9460
  this.destroy.complete();
9461
9461
  }
9462
9462
  resolveInitialConsentState() {
9463
- const configured = this.isCookieConsentConfigured();
9464
- if (this.isPromise(configured)) {
9465
- configured.then((isConfigured) => {
9466
- if (isConfigured) {
9467
- this.isClosed = true;
9468
- return;
9469
- }
9470
- this.setupTranslations();
9471
- });
9472
- return;
9473
- }
9474
- if (configured) {
9475
- this.isClosed = true;
9476
- return;
9477
- }
9478
- this.setupTranslations();
9463
+ this.isCookieConsentConfigured()
9464
+ .pipe(tap((isConfigured) => {
9465
+ if (isConfigured) {
9466
+ this.isClosed = true;
9467
+ return;
9468
+ }
9469
+ this.setupTranslations();
9470
+ })).subscribe();
9479
9471
  }
9480
9472
  injectConsentScript() {
9481
9473
  if (!this.script || typeof document === 'undefined') {
@@ -9502,24 +9494,19 @@ class CookieConsentComponent {
9502
9494
  return translations[key] ? key : 'en';
9503
9495
  }
9504
9496
  isCookieConsentConfigured() {
9505
- const snapshot = this.#storage?.snapshot();
9506
- if (snapshot) {
9507
- return this.applyStoredConsent(snapshot);
9497
+ if (!this.#storage) {
9498
+ return of(false);
9508
9499
  }
9509
- const stored = this.#storage?.read();
9510
- if (this.isPromise(stored)) {
9511
- return stored.then((config) => this.applyStoredConsent(config));
9512
- }
9513
- return this.applyStoredConsent(stored ?? null);
9500
+ return this.#storage?.read().pipe(map((config) => {
9501
+ return this.hasUserHandledConsent(config ?? null);
9502
+ }));
9514
9503
  }
9515
- applyStoredConsent(config) {
9504
+ hasUserHandledConsent(config) {
9516
9505
  const consent = config?.cookieConsent;
9517
9506
  if (!consent?.updatedAt) {
9518
9507
  return false;
9519
9508
  }
9520
- if (consent.accepted) {
9521
- this.#analytics.cookieConsentGiven(true);
9522
- }
9509
+ this.#analytics.cookieConsentGiven(true);
9523
9510
  return true;
9524
9511
  }
9525
9512
  persistConsent(accepted) {
@@ -9532,10 +9519,7 @@ class CookieConsentComponent {
9532
9519
  accepted,
9533
9520
  updatedAt: now,
9534
9521
  },
9535
- });
9536
- }
9537
- isPromise(value) {
9538
- return !!value && typeof value.then === 'function';
9522
+ }).subscribe();
9539
9523
  }
9540
9524
  static { this.ɵfac = function CookieConsentComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || CookieConsentComponent)(); }; }
9541
9525
  static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: CookieConsentComponent, selectors: [["lib-cookie-consent"]], inputs: { script: "script", privacyPolicyUrl: "privacyPolicyUrl" }, decls: 1, vars: 1, consts: [[1, "cookie-consent-container"], [1, "cookie-consent-content"], [1, "cookie-consent-description", 3, "innerHTML"], [1, "cookie-consent-actions"], ["mat-flat-button", "", 3, "click"], ["mat-stroked-button", "", 3, "click"], [1, "cookie-consent-info"], [1, "cookie-consent-info--privacy", 3, "href"], [1, "cookie-consent-info--details", 3, "click"], [1, "spin"], [1, "cookie-consent--details"]], template: function CookieConsentComponent_Template(rf, ctx) { if (rf & 1) {
@@ -9599,134 +9583,132 @@ const USER_CONFIG_MESSAGE_UPDATE = 'ngx-user-config:update';
9599
9583
  const DEFAULT_USER_CONFIG_OPTIONS = {
9600
9584
  strategy: UserConfigStrategy.Local,
9601
9585
  iframeUrl: DEFAULT_USER_CONFIG_IFRAME_URL,
9586
+ defaultConfig: DEFAULT_USER_CONFIG
9602
9587
  };
9603
9588
 
9604
- const USER_CONFIG_STORAGE_KEY = 'ngx_common_user_config';
9605
-
9606
9589
  const USER_CONFIG_OPTIONS = new InjectionToken('USER_CONFIG_OPTIONS');
9607
9590
 
9608
- class IframeStorageStrategy {
9609
- #dom;
9610
- #options;
9611
- #iframe;
9612
- #messageHandler;
9613
- constructor() {
9614
- this.#dom = inject(DOMService);
9615
- this.#options = inject(USER_CONFIG_OPTIONS);
9591
+ const mergeSection = (defaults, override) => ({
9592
+ ...defaults,
9593
+ ...(override ?? {}),
9594
+ });
9595
+ const mergeConfigSections = (base, override = {}) => ({
9596
+ accessibility: mergeSection(base.accessibility, override.accessibility),
9597
+ cookieConsent: mergeSection(base.cookieConsent, override.cookieConsent),
9598
+ language: mergeSection(base.language, override.language),
9599
+ metadata: mergeSection(base.metadata, override.metadata),
9600
+ });
9601
+ function createDefaultUserConfig(options) {
9602
+ const override = options.defaultConfig ?? {};
9603
+ return mergeConfigSections(DEFAULT_USER_CONFIG, override);
9604
+ }
9605
+ function mergeUserConfig(current, update, options) {
9606
+ const base = current ?? createDefaultUserConfig(options);
9607
+ const merged = mergeConfigSections(base, update);
9608
+ return {
9609
+ ...merged,
9610
+ metadata: {
9611
+ ...merged.metadata,
9612
+ updatedAt: new Date().toISOString(),
9613
+ },
9614
+ };
9615
+ }
9616
+
9617
+ class BaseStorageStrategy {
9618
+ constructor(options) {
9619
+ this.options = options;
9616
9620
  this.snapshot = signal(null, ...(ngDevMode ? [{ debugName: "snapshot" }] : []));
9617
- this.#ensureIframe();
9618
- const win = this.#dom.window;
9619
- if (!win) {
9620
- return;
9621
- }
9622
- this.#messageHandler = (event) => this.#handleMessage(event);
9623
- win.addEventListener('message', this.#messageHandler);
9624
- const cached = this.#readCache();
9625
- if (cached) {
9626
- this.snapshot.set(cached);
9627
- }
9621
+ /** Tracks when the initial data has been fetched from the source */
9622
+ this.isReady$ = new ReplaySubject(1);
9628
9623
  }
9624
+ /**
9625
+ * Returns an Observable that waits for the source to be ready,
9626
+ * then emits the current snapshot.
9627
+ */
9629
9628
  read() {
9630
- return this.#readCache();
9629
+ return this.isReady$.pipe(filter((ready) => ready), first(), map(() => {
9630
+ return this.snapshot();
9631
+ }));
9631
9632
  }
9633
+ /**
9634
+ * Waits for readiness, performs a deep merge, updates the local signal,
9635
+ * and triggers the implementation-specific persistence.
9636
+ */
9632
9637
  write(config) {
9633
- const merged = this.#mergeWithBase(config);
9634
- this.#writeCache(merged);
9635
- this.#postToBridge({ type: USER_CONFIG_MESSAGE_UPDATE, payload: merged });
9636
- this.snapshot.set(merged);
9638
+ return this.isReady$.pipe(filter((ready) => ready), first(), map(() => {
9639
+ const merged = this.mergeWithBase(config);
9640
+ this.snapshot.set(merged);
9641
+ this.saveToSource(merged);
9642
+ return merged;
9643
+ }));
9644
+ }
9645
+ initialize() {
9646
+ return this.isReady$.pipe(filter((ready) => ready), first(), map(() => null));
9647
+ }
9648
+ mergeWithBase(update) {
9649
+ return mergeUserConfig(this.snapshot(), update, this.options);
9650
+ }
9651
+ createDefaultConfig() {
9652
+ return createDefaultUserConfig(this.options);
9653
+ }
9654
+ static { this.ɵfac = function BaseStorageStrategy_Factory(__ngFactoryType__) { i0.ɵɵinvalidFactory(); }; }
9655
+ static { this.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: BaseStorageStrategy, factory: BaseStorageStrategy.ɵfac }); }
9656
+ }
9657
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(BaseStorageStrategy, [{
9658
+ type: Injectable
9659
+ }], () => [{ type: undefined }], null); })();
9660
+
9661
+ class IframeStorageStrategy extends BaseStorageStrategy {
9662
+ #dom = inject(DOMService);
9663
+ #options = inject(USER_CONFIG_OPTIONS);
9664
+ #iframe;
9665
+ #messageHandler;
9666
+ constructor() {
9667
+ super(inject(USER_CONFIG_OPTIONS));
9668
+ this.#init();
9669
+ }
9670
+ saveToSource(config) {
9671
+ this.#postToBridge({ type: USER_CONFIG_MESSAGE_UPDATE, payload: config });
9637
9672
  }
9638
9673
  ngOnDestroy() {
9639
9674
  if (this.#messageHandler && this.#dom.window) {
9640
9675
  this.#dom.window.removeEventListener('message', this.#messageHandler);
9641
9676
  }
9642
9677
  }
9643
- #ensureIframe() {
9644
- if (this.#iframe) {
9645
- return;
9646
- }
9647
- const win = this.#dom.window;
9648
- const doc = win?.document;
9649
- if (!doc) {
9678
+ #init() {
9679
+ const window = this.#dom.window;
9680
+ if (!window) {
9650
9681
  return;
9651
9682
  }
9683
+ ;
9684
+ this.#messageHandler = (event) => this.#handleMessage(event);
9685
+ window.addEventListener('message', this.#messageHandler);
9686
+ const doc = window.document;
9652
9687
  const iframe = doc.createElement('iframe');
9653
9688
  iframe.style.display = 'none';
9654
9689
  iframe.name = USER_CONFIG_IFRAME_NAME;
9655
9690
  iframe.src = this.#options?.iframeUrl ?? DEFAULT_USER_CONFIG_IFRAME_URL;
9656
9691
  iframe.setAttribute('aria-hidden', 'true');
9657
- iframe.addEventListener('load', () => this.#postToBridge({ type: USER_CONFIG_MESSAGE_REQUEST }));
9692
+ iframe.addEventListener('load', () => {
9693
+ this.#postToBridge({ type: USER_CONFIG_MESSAGE_REQUEST });
9694
+ });
9658
9695
  doc.body?.appendChild(iframe);
9659
9696
  this.#iframe = iframe;
9660
9697
  }
9661
9698
  #handleMessage(event) {
9662
- if (!this.#iframe || event.source !== this.#iframe.contentWindow) {
9663
- return;
9664
- }
9665
- const data = event.data;
9666
- if (data?.type === USER_CONFIG_MESSAGE_SNAPSHOT && data.payload) {
9667
- const snapshot = data.payload;
9668
- this.#writeCache(snapshot);
9669
- this.snapshot.set(snapshot);
9670
- }
9671
- }
9672
- #postToBridge(message) {
9673
- if (!this.#iframe?.contentWindow) {
9699
+ if (event.source !== this.#iframe?.contentWindow) {
9674
9700
  return;
9675
9701
  }
9676
- this.#iframe.contentWindow.postMessage(message, USER_CONFIG_IFRAME_TARGET_ORIGIN);
9677
- }
9678
- #readCache() {
9679
- const store = this.#dom.localStorage;
9680
- if (!store) {
9681
- return null;
9682
- }
9683
- return this.#parse(store.getItem(USER_CONFIG_STORAGE_KEY));
9684
- }
9685
- #writeCache(config) {
9686
- const store = this.#dom.localStorage;
9687
- if (!store) {
9702
+ const messageData = event.data;
9703
+ if (messageData?.type !== USER_CONFIG_MESSAGE_SNAPSHOT) {
9688
9704
  return;
9689
9705
  }
9690
- try {
9691
- store.setItem(USER_CONFIG_STORAGE_KEY, JSON.stringify(config));
9692
- }
9693
- catch {
9694
- /* noop */
9695
- }
9706
+ const data = messageData?.payload ?? this.createDefaultConfig();
9707
+ this.snapshot.set(data);
9708
+ this.isReady$.next(true);
9696
9709
  }
9697
- #parse(raw) {
9698
- if (!raw) {
9699
- return null;
9700
- }
9701
- try {
9702
- return JSON.parse(raw);
9703
- }
9704
- catch {
9705
- return DEFAULT_USER_CONFIG;
9706
- }
9707
- }
9708
- #mergeWithBase(update) {
9709
- const base = this.snapshot() ?? this.read() ?? DEFAULT_USER_CONFIG;
9710
- const mergedMetadata = {
9711
- ...base.metadata,
9712
- ...update.metadata,
9713
- updatedAt: new Date().toISOString(),
9714
- };
9715
- return {
9716
- accessibility: {
9717
- ...base.accessibility,
9718
- ...update.accessibility,
9719
- },
9720
- cookieConsent: {
9721
- ...base.cookieConsent,
9722
- ...update.cookieConsent,
9723
- },
9724
- language: {
9725
- ...base.language,
9726
- ...update.language,
9727
- },
9728
- metadata: mergedMetadata,
9729
- };
9710
+ #postToBridge(message) {
9711
+ this.#iframe?.contentWindow?.postMessage(message, USER_CONFIG_IFRAME_TARGET_ORIGIN);
9730
9712
  }
9731
9713
  static { this.ɵfac = function IframeStorageStrategy_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || IframeStorageStrategy)(); }; }
9732
9714
  static { this.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: IframeStorageStrategy, factory: IframeStorageStrategy.ɵfac, providedIn: 'root' }); }
@@ -9736,91 +9718,60 @@ class IframeStorageStrategy {
9736
9718
  args: [{ providedIn: 'root' }]
9737
9719
  }], () => [], null); })();
9738
9720
 
9739
- class LocalStorageStrategy {
9740
- #dom;
9721
+ const USER_CONFIG_STORAGE_KEY = 'ngx_common_user_config';
9722
+
9723
+ class LocalStorageStrategy extends BaseStorageStrategy {
9724
+ #dom = inject(DOMService);
9741
9725
  #storageListener;
9742
9726
  constructor() {
9743
- this.#dom = inject(DOMService);
9744
- this.snapshot = signal(null, ...(ngDevMode ? [{ debugName: "snapshot" }] : []));
9745
- const win = this.#dom.window;
9746
- if (!win) {
9747
- return;
9748
- }
9749
- this.#storageListener = (event) => {
9750
- if (event.key !== USER_CONFIG_STORAGE_KEY || !event.newValue) {
9751
- return;
9727
+ super(inject(USER_CONFIG_OPTIONS));
9728
+ this.#init();
9729
+ }
9730
+ saveToSource(config) {
9731
+ const store = this.#dom.localStorage;
9732
+ if (store) {
9733
+ try {
9734
+ store.setItem(USER_CONFIG_STORAGE_KEY, JSON.stringify(config));
9752
9735
  }
9753
- const parsed = this.#parse(event.newValue);
9754
- if (parsed) {
9755
- this.snapshot.set(parsed);
9736
+ catch (exception) {
9737
+ console.error('Failed to save user config to localStorage', exception);
9756
9738
  }
9757
- };
9758
- win.addEventListener('storage', this.#storageListener);
9759
- const current = this.read();
9760
- if (current) {
9761
- this.snapshot.set(current);
9762
9739
  }
9763
9740
  }
9764
- read() {
9765
- const store = this.#dom.localStorage;
9766
- if (!store) {
9767
- return null;
9741
+ ngOnDestroy() {
9742
+ if (this.#storageListener && this.#dom.window) {
9743
+ this.#dom.window.removeEventListener('storage', this.#storageListener);
9768
9744
  }
9769
- return this.#parse(store.getItem(USER_CONFIG_STORAGE_KEY));
9770
9745
  }
9771
- write(config) {
9746
+ #init() {
9747
+ const win = this.#dom.window;
9772
9748
  const store = this.#dom.localStorage;
9773
- if (!store) {
9774
- return;
9775
- }
9776
- const merged = this.#mergeWithBase(config);
9777
- try {
9778
- store.setItem(USER_CONFIG_STORAGE_KEY, JSON.stringify(merged));
9779
- this.snapshot.set(merged);
9780
- }
9781
- catch {
9782
- /* noop */
9749
+ if (win) {
9750
+ this.#storageListener = (event) => {
9751
+ if (event.key === USER_CONFIG_STORAGE_KEY && event.newValue) {
9752
+ this.snapshot.set(this.#parse(event.newValue));
9753
+ }
9754
+ };
9755
+ win.addEventListener('storage', this.#storageListener);
9783
9756
  }
9784
- }
9785
- ngOnDestroy() {
9786
- if (this.#storageListener && this.#dom.window) {
9787
- this.#dom.window.removeEventListener('storage', this.#storageListener);
9757
+ if (store) {
9758
+ const raw = store.getItem(USER_CONFIG_STORAGE_KEY);
9759
+ this.snapshot.set(this.#parse(raw));
9788
9760
  }
9761
+ // LocalStorage is available immediately
9762
+ this.isReady$.next(true);
9789
9763
  }
9790
9764
  #parse(raw) {
9791
9765
  if (!raw) {
9792
- return null;
9766
+ return this.createDefaultConfig();
9793
9767
  }
9794
9768
  try {
9795
9769
  return JSON.parse(raw);
9796
9770
  }
9797
9771
  catch {
9798
- return DEFAULT_USER_CONFIG;
9772
+ return this.createDefaultConfig();
9799
9773
  }
9800
9774
  }
9801
- #mergeWithBase(update) {
9802
- const base = this.snapshot() ?? this.read() ?? DEFAULT_USER_CONFIG;
9803
- const mergedMetadata = {
9804
- ...base.metadata,
9805
- ...update.metadata,
9806
- updatedAt: new Date().toISOString(),
9807
- };
9808
- return {
9809
- accessibility: {
9810
- ...base.accessibility,
9811
- ...update.accessibility,
9812
- },
9813
- cookieConsent: {
9814
- ...base.cookieConsent,
9815
- ...update.cookieConsent,
9816
- },
9817
- language: {
9818
- ...base.language,
9819
- ...update.language,
9820
- },
9821
- metadata: mergedMetadata,
9822
- };
9823
- }
9824
9775
  static { this.ɵfac = function LocalStorageStrategy_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || LocalStorageStrategy)(); }; }
9825
9776
  static { this.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: LocalStorageStrategy, factory: LocalStorageStrategy.ɵfac, providedIn: 'root' }); }
9826
9777
  }
@@ -9863,9 +9814,103 @@ function provideUserConfig(options = {}) {
9863
9814
  useFactory: userConfigStorageFactory,
9864
9815
  deps: [USER_CONFIG_OPTIONS],
9865
9816
  },
9817
+ provideAppInitializer(() => {
9818
+ const storage = inject(USER_CONFIG_STORAGE);
9819
+ return storage.initialize();
9820
+ })
9866
9821
  ]);
9867
9822
  }
9868
9823
 
9824
+ const LOCALIZATION_CONFIGURATION_TOKEN = new InjectionToken("Localization configuration");
9825
+
9826
+ class UILanguageService {
9827
+ #translateService = inject(TranslateService);
9828
+ #configuration = inject(LOCALIZATION_CONFIGURATION_TOKEN);
9829
+ #storage = inject(USER_CONFIG_STORAGE, { optional: true });
9830
+ get currentLanguage() {
9831
+ return this.#getFromStore();
9832
+ }
9833
+ get languages() {
9834
+ return this.#configuration.supportedLanguages ?? [];
9835
+ }
9836
+ /**
9837
+ * Initializes the language service by setting the default language and loading the user's preferred language from storage.
9838
+ *
9839
+ * - Sets the default language using `ngx-translate#setFallbackLang`.
9840
+ * - Attempts to retrieve the user's preferred language from storage (implementation details omitted for brevity).
9841
+ * - If a preferred language is found, calls `useLanguage` to set the current language.
9842
+ */
9843
+ initialize() {
9844
+ this.#translateService.setFallbackLang(this.#getDefaultLanguage());
9845
+ this.#getFromStore()
9846
+ .pipe(tap(language => this.useLanguage(language))).subscribe();
9847
+ }
9848
+ /**
9849
+ * Sets the current language for the application.
9850
+ * @param languageCode - The desired language code.
9851
+ *
9852
+ * This method attempts to set the language using the provided `languageCode`.
9853
+ * If the language code is not supported (not present in the `languages` array),
9854
+ * it:
9855
+ * - Logs an error message.
9856
+ * - Falls back to the `defaultLanguage`.
9857
+ *
9858
+ * It updates UI language and stores the value in store.
9859
+ */
9860
+ useLanguage(languageCode) {
9861
+ if (!this.languages.includes(languageCode)) {
9862
+ console.error(`Language with code '${languageCode}' not supported`);
9863
+ languageCode = this.#getDefaultLanguage();
9864
+ }
9865
+ this.#translateService.use(languageCode);
9866
+ this.#updateDocumentLangAttribute(languageCode);
9867
+ this.updateStore(languageCode);
9868
+ }
9869
+ updateStore(language) {
9870
+ if (!this.#storage) {
9871
+ return;
9872
+ }
9873
+ this.#storage.write({ language: { locale: language } }).subscribe();
9874
+ }
9875
+ #getFromStore() {
9876
+ const defaultLanguage = this.#getDefaultLanguage();
9877
+ if (!this.#storage) {
9878
+ return of(defaultLanguage);
9879
+ }
9880
+ return this.#storage.read()
9881
+ .pipe(map(config => config?.language.locale ?? defaultLanguage));
9882
+ }
9883
+ #getDefaultLanguage() {
9884
+ return this.#configuration.defaultLanguage;
9885
+ }
9886
+ #updateDocumentLangAttribute(lang) {
9887
+ const doc = document.documentElement;
9888
+ doc.lang = lang.split('-')[0];
9889
+ }
9890
+ static { this.ɵfac = function UILanguageService_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || UILanguageService)(); }; }
9891
+ static { this.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: UILanguageService, factory: UILanguageService.ɵfac, providedIn: 'root' }); }
9892
+ }
9893
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(UILanguageService, [{
9894
+ type: Injectable,
9895
+ args: [{
9896
+ providedIn: 'root',
9897
+ }]
9898
+ }], null, null); })();
9899
+
9900
+ const provideLanguageService = (config) => {
9901
+ return makeEnvironmentProviders([
9902
+ {
9903
+ provide: LOCALIZATION_CONFIGURATION_TOKEN,
9904
+ useFactory: (...deps) => config.localizationConfiguration.useFactory(...deps),
9905
+ deps: config.localizationConfiguration.deps
9906
+ },
9907
+ provideAppInitializer(() => {
9908
+ const languageService = inject(UILanguageService);
9909
+ languageService.initialize();
9910
+ })
9911
+ ]);
9912
+ };
9913
+
9869
9914
  /*
9870
9915
  * Public API Surface of ngx-common
9871
9916
  */
@@ -9874,5 +9919,5 @@ function provideUserConfig(options = {}) {
9874
9919
  * Generated bundle index. Do not edit.
9875
9920
  */
9876
9921
 
9877
- export { ALERT_CONFIGURATION_TOKEN, AccessibilityContrasts, AccessibilityDialogComponent, AccessibilityFontSizes, AccessibilityService, AccessibilityTextMagnifierService, AlertService, AnalyticsService, AuthHeadersHelper, COLLECTIONS_MENU, CUSTOM_TITLE_CONFIGURATION_TOKEN, ClickOutsideDirective, ClickOutsideModule, CloseButtonComponent, CloseButtonModule, CombinedCollection, CombinedCollectionTooltipKey, CompanyProductComponent, CompanyProductModule, Confirmation, ConfirmationModalComponent, ConfirmationModalModule, ConfirmationService, ConversionHelper, CookieConsentComponent, CustomPaginatorInernationalizationHelper, CustomTitleStrategyService, CustomTranslateLoader, DEFAULT_ACCESSIBILITY_PREFERENCES, DEFAULT_COOKIE_CONSENT_PREFERENCES, DEFAULT_USER_CONFIG, DEFAULT_USER_CONFIG_IFRAME_URL, DEFAULT_USER_CONFIG_OPTIONS, DISABLE_EXPORT_ATTRIBUTE_NAME, DOMService, DateAgoModule, DateAgoPipe, DomainTranslatePipe, DragAndDropDirective, DragAndDropModule, ERROR_CODES, EngineTermApiService, EngineTermCollectionScope, EngineTermCollectionSource, EngineTermCollectionStatus, EngineTermCollectionSubStatus, ExportFormat, ExtensionDialogComponent, ExtensionDialogModule, ExtensionDialogService, FILE_SIZE_UNIT, FileCategories, FileExtensionHelper, FileExtensions, FileSizeLabelPipe, FileTypeIcons, FileTypes, FileUploadComponent, FileUploadErrorTypeEnum, FileUploadModule, FilterBarComponent, FilterBarModule, FilterWithHighlightModule, FilterWithHighlightPipe, FooterComponent, FooterModule, GlobalMessageComponent, HashHelper, HtmlElementParseHelper, HtmlHelper, IconService, InlineMessageComponent, InlineMessageIconPosition, InlineMessageModule, InlineMessageType, LAST_USED_SYSTEM_LOCAL_STORAGE_KEY, LLMActions, LLMComponent, LLMModule, LLM_CONFIGURATION_TOKEN, LanguageTranslateModule, LanguageTranslatePipe, LanguageTranslateService, MatButtonLoadingDirective, MatButtonLoadingModule, MatomoService, MissingTranslationHandlerService, MissingTranslationHelper, MtCollectionStatus, MultiFunctionalTableComponent, MultiFunctionalTableModule, NewFeatureDialogWrapperComponent, NotificationMessageComponent, NotificationMessageModule, NotificationMessageType, NotificationService, OPEN_CLOSE_BTN_ICONS_TOKEN, ObjectLengthModule, ObjectLengthPipe, OpenCloseButtonComponent, OpenCloseButtonModule, OpenExtensionDialogComponent, Operations, PlausibleEventDirective, PlausibleHelper, PlausibleModule, ResolutionHelper, SCREEN_SIZE, SaveFileHelper, SelectLanguageDialogComponent, SidebarComponent, SidebarService, SortAlphabeticallyModule, SortAlphabeticallyPipe, SortByNumberPipe, SortDomainsPipe, SortHelper, SortLanguageListPipe, SortTranslationsByPropertyModule, SortTranslationsByPropertyPipe, SortTranslationsModule, SortTranslationsPipe, StatusDisplayComponent, StatusDisplayModule, SubscriptionComponent, SubscriptionPlan, SystemService, TerminologyApiService, TerminologyCollectionService, TerminologyComponent, TerminologyConfigService, TerminologyCreateCollectionComponent, TerminologyModule, TerminologyService, TextToSpeechComponent, TldLoaderComponent, TldLoaderModule, ToastComponent, ToastService, USER_CONFIG_IFRAME_NAME, USER_CONFIG_IFRAME_TARGET_ORIGIN, USER_CONFIG_MESSAGE_REQUEST, USER_CONFIG_MESSAGE_SNAPSHOT, USER_CONFIG_MESSAGE_UPDATE, USER_CONFIG_OPTIONS, USER_CONFIG_STORAGE, USER_CONFIG_STORAGE_KEY, USER_CONFIG_VERSION, UserConfigStrategy, createMetadata, getFileSizeLabel, isUserConfigOptionsFactoryProvider, provideCustomTitleStrategy, provideUserConfig, userConfigStorageFactory };
9922
+ export { ALERT_CONFIGURATION_TOKEN, AccessibilityContrasts, AccessibilityDialogComponent, AccessibilityFontSizes, AccessibilityService, AccessibilityTextMagnifierService, AlertService, AnalyticsService, AuthHeadersHelper, COLLECTIONS_MENU, CUSTOM_TITLE_CONFIGURATION_TOKEN, ClickOutsideDirective, ClickOutsideModule, CloseButtonComponent, CloseButtonModule, CombinedCollection, CombinedCollectionTooltipKey, CompanyProductComponent, CompanyProductModule, Confirmation, ConfirmationModalComponent, ConfirmationModalModule, ConfirmationService, ConversionHelper, CookieConsentComponent, CustomPaginatorInernationalizationHelper, CustomTitleStrategyService, CustomTranslateLoader, DEFAULT_ACCESSIBILITY_PREFERENCES, DEFAULT_COOKIE_CONSENT_PREFERENCES, DEFAULT_USER_CONFIG, DEFAULT_USER_CONFIG_IFRAME_URL, DEFAULT_USER_CONFIG_OPTIONS, DISABLE_EXPORT_ATTRIBUTE_NAME, DOMService, DateAgoModule, DateAgoPipe, DomainTranslatePipe, DragAndDropDirective, DragAndDropModule, ERROR_CODES, EngineTermApiService, EngineTermCollectionScope, EngineTermCollectionSource, EngineTermCollectionStatus, EngineTermCollectionSubStatus, ExportFormat, ExtensionDialogComponent, ExtensionDialogModule, ExtensionDialogService, FILE_SIZE_UNIT, FileCategories, FileExtensionHelper, FileExtensions, FileSizeLabelPipe, FileTypeIcons, FileTypes, FileUploadComponent, FileUploadErrorTypeEnum, FileUploadModule, FilterBarComponent, FilterBarModule, FilterWithHighlightModule, FilterWithHighlightPipe, FooterComponent, FooterModule, GlobalMessageComponent, HashHelper, HtmlElementParseHelper, HtmlHelper, IconService, InlineMessageComponent, InlineMessageIconPosition, InlineMessageModule, InlineMessageType, LAST_USED_SYSTEM_LOCAL_STORAGE_KEY, LLMActions, LLMComponent, LLMModule, LLM_CONFIGURATION_TOKEN, LOCALIZATION_CONFIGURATION_TOKEN, LanguageTranslateModule, LanguageTranslatePipe, LanguageTranslateService, MatButtonLoadingDirective, MatButtonLoadingModule, MatomoService, MissingTranslationHandlerService, MissingTranslationHelper, MtCollectionStatus, MultiFunctionalTableComponent, MultiFunctionalTableModule, NewFeatureDialogWrapperComponent, NotificationMessageComponent, NotificationMessageModule, NotificationMessageType, NotificationService, OPEN_CLOSE_BTN_ICONS_TOKEN, ObjectLengthModule, ObjectLengthPipe, OpenCloseButtonComponent, OpenCloseButtonModule, OpenExtensionDialogComponent, Operations, PlausibleEventDirective, PlausibleHelper, PlausibleModule, ResolutionHelper, SCREEN_SIZE, SaveFileHelper, SelectLanguageDialogComponent, SidebarComponent, SidebarService, SortAlphabeticallyModule, SortAlphabeticallyPipe, SortByNumberPipe, SortDomainsPipe, SortHelper, SortLanguageListPipe, SortTranslationsByPropertyModule, SortTranslationsByPropertyPipe, SortTranslationsModule, SortTranslationsPipe, StatusDisplayComponent, StatusDisplayModule, SubscriptionComponent, SubscriptionPlan, SystemService, TerminologyApiService, TerminologyCollectionService, TerminologyComponent, TerminologyConfigService, TerminologyCreateCollectionComponent, TerminologyModule, TerminologyService, TextToSpeechComponent, TldLoaderComponent, TldLoaderModule, ToastComponent, ToastService, UILanguageService, USER_CONFIG_IFRAME_NAME, USER_CONFIG_IFRAME_TARGET_ORIGIN, USER_CONFIG_MESSAGE_REQUEST, USER_CONFIG_MESSAGE_SNAPSHOT, USER_CONFIG_MESSAGE_UPDATE, USER_CONFIG_OPTIONS, USER_CONFIG_STORAGE, USER_CONFIG_STORAGE_KEY, USER_CONFIG_VERSION, UserConfigStrategy, createMetadata, getFileSizeLabel, isUserConfigOptionsFactoryProvider, provideCustomTitleStrategy, provideLanguageService, provideUserConfig, userConfigStorageFactory };
9878
9923
  //# sourceMappingURL=tilde-nlp-ngx-common.mjs.map