wacom
Version:
Module which has common services and components which can be used on all projects.
962 lines (951 loc) • 181 kB
JavaScript
import * as i0 from '@angular/core';
import { InjectionToken, Inject, Optional, Injectable, ViewChild, Component, inject, ApplicationRef, EnvironmentInjector, createComponent, signal, PLATFORM_ID, ChangeDetectorRef, output, ElementRef, DestroyRef, Directive, Pipe, isSignal, provideEnvironmentInitializer, DOCUMENT, makeEnvironmentProviders, NgModule } from '@angular/core';
import * as i1 from '@angular/router';
import * as i2 from '@angular/platform-browser';
import { DomSanitizer } from '@angular/platform-browser';
import * as i1$1 from '@angular/common';
import { CommonModule } from '@angular/common';
import { Subject, firstValueFrom, Observable, ReplaySubject, EMPTY } from 'rxjs';
import * as i1$2 from '@angular/common/http';
import { HttpHeaders, HttpErrorResponse, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { first, catchError } from 'rxjs/operators';
import { FormsModule } from '@angular/forms';
/**
* Possible alert variants that control styling and default icons.
*/
const ALERT_TYPES = ['info', 'error', 'success', 'warning', 'question'];
/**
* Possible screen positions where alerts can be placed.
*/
const ALERT_POSITIONS = [
'topLeft',
'top',
'topRight',
'left',
'center',
'right',
'bottomLeft',
'bottom',
'bottomRight',
];
/**
* Default values applied when an alert is shown without specific options.
*/
const DEFAULT_ALERT_CONFIG = {
text: '',
type: 'info',
class: '',
progress: true,
position: 'bottom',
timeout: 3000,
closable: true,
buttons: [],
};
const CONFIG_TOKEN = new InjectionToken('config');
const DEFAULT_CONFIG = {
store: {
prefix: 'waStore',
},
meta: {
useTitleSuffix: false,
warnMissingGuard: true,
defaults: { links: {} },
},
socket: false,
http: {
url: '',
headers: {},
},
theme: {
primary: '#fff',
secondary: '#000',
info: '#9ddeff',
error: '#ffafb4',
success: '#a6efb8',
warning: '#ffcfa5',
question: '#fff9b2',
},
};
const MODAL_SIZES = ['small', 'mid', 'big', 'full'];
const DEFAULT_MODAL_CONFIG = {
size: 'mid',
timeout: 0,
class: '',
closable: true,
};
const isDefined = (val) => typeof val !== 'undefined';
class MetaService {
constructor(config, router, meta, titleService) {
this.config = config;
this.router = router;
this.meta = meta;
this.titleService = titleService;
this.config = this.config || DEFAULT_CONFIG;
this._meta = this.config.meta || {};
this._warnMissingGuard();
}
/**
* Sets the default meta tags.
*
* @param defaults - The default meta tags.
*/
setDefaults(defaults) {
this._meta.defaults = {
...this._meta.defaults,
...defaults,
};
}
/**
* Sets the title and optional title suffix.
*
* @param title - The title to set.
* @param titleSuffix - The title suffix to append.
* @returns The MetaService instance.
*/
setTitle(title, titleSuffix) {
let titleContent = isDefined(title) ? title || '' : this._meta.defaults?.['title'] || '';
if (this._meta.useTitleSuffix) {
titleContent += isDefined(titleSuffix) ? titleSuffix : this._meta.defaults?.['titleSuffix'] || '';
}
this._updateMetaTag('title', titleContent);
this._updateMetaTag('og:title', titleContent);
this._updateMetaTag('twitter:title', titleContent);
this.titleService.setTitle(titleContent);
return this;
}
/**
* Sets link tags.
*
* @param links - The links to set.
* @returns The MetaService instance.
*/
setLink(links) {
Object.keys(links).forEach((rel) => {
let link = document.createElement('link');
link.setAttribute('rel', rel);
link.setAttribute('href', links[rel]);
document.head.appendChild(link);
});
return this;
}
/**
* Sets a meta tag.
*
* @param tag - The meta tag name.
* @param value - The meta tag value.
* @param prop - The meta tag property.
*/
setTag(tag, value, prop) {
if (tag === 'title' || tag === 'titleSuffix') {
throw new Error(`Attempt to set ${tag} through 'setTag': 'title' and 'titleSuffix' are reserved. Use 'MetaService.setTitle' instead.`);
}
const content = (isDefined(value) ? value || '' : this._meta.defaults?.[tag] || '') + '';
this._updateMetaTag(tag, content, prop);
if (tag === 'description') {
this._updateMetaTag('og:description', content, prop);
this._updateMetaTag('twitter:description', content, prop);
}
}
/**
* Updates a meta tag.
*
* @param tag - The meta tag name.
* @param value - The meta tag value.
* @param prop - The meta tag property.
*/
_updateMetaTag(tag, value, prop) {
prop = prop || (tag.startsWith('og:') || tag.startsWith('twitter:') ? 'property' : 'name');
this.meta.updateTag({ [prop]: tag, content: value });
}
/**
* Removes a meta tag.
*
* @param tag - The meta tag name.
* @param prop - The meta tag property.
*/
removeTag(tag, prop) {
prop = prop || (tag.startsWith('og:') || tag.startsWith('twitter:') ? 'property' : 'name');
this.meta.removeTag(`${prop}="${tag}"`);
}
/**
* Warns about missing meta guards in routes.
*/
_warnMissingGuard() {
if (isDefined(this._meta.warnMissingGuard) && !this._meta.warnMissingGuard) {
return;
}
const hasDefaultMeta = !!Object.keys(this._meta.defaults ?? {}).length;
const hasMetaGuardInArr = (it) => it && it.IDENTIFIER === 'MetaGuard';
let hasShownWarnings = false;
const checkRoute = (route) => {
const hasRouteMeta = route.data && route.data['meta'];
const showWarning = !isDefined(route.redirectTo) && (hasDefaultMeta || hasRouteMeta) && !(route.canActivate || []).some(hasMetaGuardInArr);
if (showWarning) {
console.warn(`Route with path "${route.path}" has ${hasRouteMeta ? '' : 'default '}meta tags, but does not use MetaGuard. Please add MetaGuard to the canActivate array in your route configuration`);
hasShownWarnings = true;
}
(route.children || []).forEach(checkRoute);
};
this.router.config.forEach(checkRoute);
if (hasShownWarnings) {
console.warn(`To disable these warnings, set metaConfig.warnMissingGuard: false in your MetaConfig passed to MetaModule.forRoot()`);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: MetaService, deps: [{ token: CONFIG_TOKEN, optional: true }, { token: i1.Router }, { token: i2.Meta }, { token: i2.Title }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: MetaService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: MetaService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: () => [{ type: undefined, decorators: [{
type: Inject,
args: [CONFIG_TOKEN]
}, {
type: Optional
}] }, { type: i1.Router }, { type: i2.Meta }, { type: i2.Title }] });
class MetaGuard {
static { this.IDENTIFIER = 'MetaGuard'; }
constructor(metaService, config) {
this.metaService = metaService;
this.config = config;
if (!this.config)
this.config = DEFAULT_CONFIG;
this._meta = this.config.meta || {};
this._meta.defaults = this._meta.defaults || {};
}
canActivate(route, state) {
this._processRouteMetaTags(route.data && route.data['meta']);
return true;
}
_processRouteMetaTags(meta = {}) {
if (meta.disableUpdate) {
return;
}
if (meta.title) {
this.metaService.setTitle(meta.title, meta.titleSuffix);
}
if (meta.links && Object.keys(meta.links).length) {
this.metaService.setLink(meta.links);
}
if (this._meta.defaults?.links && Object.keys(this._meta.defaults.links).length) {
this.metaService.setLink(this._meta.defaults.links);
}
Object.keys(meta).forEach((prop) => {
if (prop === 'title' || prop === 'titleSuffix' || prop === 'links') {
return;
}
Object.keys(meta[prop]).forEach((key) => {
this.metaService.setTag(key, meta[prop][key], prop);
});
});
Object.keys(this._meta.defaults).forEach((key) => {
if (key in meta || key === 'title' || key === 'titleSuffix' || key === 'links') {
return;
}
this.metaService.setTag(key, this._meta.defaults[key]);
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: MetaGuard, deps: [{ token: MetaService }, { token: CONFIG_TOKEN, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: MetaGuard, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: MetaGuard, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: () => [{ type: MetaService }, { type: undefined, decorators: [{
type: Inject,
args: [CONFIG_TOKEN]
}, {
type: Optional
}] }] });
/**
* Displays an individual alert message with optional icon, actions and
* auto‑dismiss behaviour. All inputs are configured by the service when the
* component is created dynamically.
*/
class AlertComponent {
constructor() {
/** Type of alert which determines styling and icon. */
this.type = 'info';
/** Position on the screen where the alert appears. */
this.position = 'bottom';
/** Flag used to trigger the deletion animation. */
this.delete_animation = false;
/** Optional action buttons rendered within the alert. */
this.buttons = [];
this._removed = false;
}
/**
* Starts the auto‑dismiss timer and pauses it while the alert is
* hovered, resuming when the mouse leaves.
*/
ngAfterViewInit() {
if (this.timeout) {
let remaining = JSON.parse(JSON.stringify(this.timeout));
let timer = window.setTimeout(() => {
this.remove();
}, remaining);
let start = new Date();
this.alertRef.nativeElement.addEventListener('mouseenter', () => {
clearTimeout(timer);
remaining -= new Date().getTime() - start.getTime();
}, false);
this.alertRef.nativeElement.addEventListener('mouseleave', () => {
start = new Date();
clearTimeout(timer);
timer = window.setTimeout(() => {
this.remove();
}, remaining);
}, false);
}
}
/**
* Triggers the closing animation and invokes the provided close
* callback once finished.
*/
remove(callback) {
if (this._removed)
return;
this._removed = true;
callback?.();
this.delete_animation = true;
setTimeout(() => {
this.close();
this.delete_animation = false;
}, 350);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: AlertComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.2.1", type: AlertComponent, isStandalone: true, selector: "alert", viewQueries: [{ propertyName: "alertRef", first: true, predicate: ["alertRef"], descendants: true }], ngImport: i0, template: "@if (text) {\r\n\t<div class=\"wacom-alert wacom-alert--auto-height\" [class.wacom-alert--closing]=\"delete_animation\" [ngClass]=\"class\">\r\n\t\t<div [ngClass]=\"'wacom-alert__content--color-' + type\" class=\"wacom-alert__content wacom-alert__content--bounce-in-up\" #alertRef>\r\n\t\t\t@if (progress) {\r\n\t\t\t\t<div class=\"wacom-alert__progress\">\r\n\t\t\t\t\t<span\r\n\t\t\t\t\t\tclass=\"wacom-alert__progress-bar\"\r\n\t\t\t\t\t\t[ngClass]=\"'wacom-alert__progress-bar--' + type\"\r\n\t\t\t\t\t\t[ngStyle]=\"{\r\n\t\t\t\t\t\t\t'animation-duration': (timeout + 350) / 1000 + 's',\r\n\t\t\t\t\t\t}\"\r\n\t\t\t\t\t></span>\r\n\t\t\t\t</div>\r\n\t\t\t}\r\n\t\t\t<div class=\"wacom-alert__body\">\r\n\t\t\t\t<div class=\"wacom-alert__texts\">\r\n\t\t\t\t\t@if (icon) {\r\n\t\t\t\t\t\t<div class=\"{{ icon }}\"></div>\r\n\t\t\t\t\t}\r\n\t\t\t\t\t<div class=\"wacom-alert__message wacom-alert__message--slide-in\">\r\n\t\t\t\t\t\t{{ text }}\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t\t@if (type === \"question\") {\r\n\t\t\t\t\t<div>\r\n\t\t\t\t\t\t@for (button of buttons; track button.text) {\r\n\t\t\t\t\t\t\t<button (click)=\"remove(button.callback)\" class=\"wacom-alert__button\">\r\n\t\t\t\t\t\t\t\t{{ button.text }}\r\n\t\t\t\t\t\t\t</button>\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t</div>\r\n\t\t\t\t}\r\n\t\t\t\t@if (closable) {\r\n\t\t\t\t\t<div class=\"wacom-alert__close\" (click)=\"remove()\"></div>\r\n\t\t\t\t}\r\n\t\t\t</div>\r\n\t\t</div>\r\n\t</div>\r\n}\r\n", styles: ["@keyframes iziT-bounceInUp{0%{opacity:0;transform:translateY(200px)}50%{opacity:1;transform:translateY(-10px)}70%{transform:translateY(5px)}to{transform:translateY(0)}}@keyframes wacom-alert-progress{0%{width:100%}to{width:0%}}.wacom-alert{font-size:0;height:100px;width:100%;transform:translateZ(0);backface-visibility:hidden;transition:.3s all ease-in-out;opacity:1}.wacom-alert--closing{opacity:0;transition:.3s all ease-in-out}.wacom-alert--auto-height{height:auto!important}.wacom-alert__content{display:inline-block;clear:both;position:relative;font-family:Lato,Tahoma,Arial;font-size:14px;padding:8px 0 9px;background:#eeeeeee6;border-color:#eeeeeee6;width:100%;pointer-events:all;cursor:default;transform:translate(0);-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;min-height:54px}.wacom-alert__content:hover .wacom-alert__progress-bar{animation-play-state:paused}.wacom-alert__content--bounce-in-up{-webkit-animation:iziT-bounceInUp .7s ease-in-out both;animation:iziT-bounceInUp .7s ease-in-out both}.wacom-alert__content--theme-dark{background:#565c70;border-color:#565c70}.wacom-alert__content--theme-dark .wacom-alert__message{color:#ffffffb3;font-weight:300}.wacom-alert__content--color-info{background-color:var(--wacom-info)}.wacom-alert__content--color-error{background-color:var(--wacom-error)}.wacom-alert__content--color-success{background-color:var(--wacom-success)}.wacom-alert__content--color-warning{background-color:var(--wacom-warning)}.wacom-alert__content--color-question{background-color:var(--wacom-question)}.wacom-alert__close{width:15px;height:15px;right:17px;top:-5px;opacity:.3;position:relative;order:2}.wacom-alert__close:hover{opacity:1}.wacom-alert__close:before,.wacom-alert__close:after{cursor:pointer;position:absolute;left:15px;content:\" \";height:12px;width:2px;background-color:var(--wacom-secondary)}.wacom-alert__close:before{transform:rotate(45deg)}.wacom-alert__close:after{transform:rotate(-45deg)}.wacom-alert__progress{bottom:0;position:absolute;width:100%;margin-bottom:0;border-radius:50px}.wacom-alert__progress:hover .wacom-alert__progress-bar{animation-play-state:paused}.wacom-alert__progress-bar{display:block;width:100%;height:2px;background-color:#a5a5a5ed;animation-name:wacom-alert-progress;animation-duration:10s;border-radius:50px}.wacom-alert__progress-bar--info{background-color:var(--wacom-info)}.wacom-alert__progress-bar--error{background-color:var(--wacom-error)}.wacom-alert__progress-bar--success{background-color:var(--wacom-success)}.wacom-alert__progress-bar--warning{background-color:var(--wacom-warning)}.wacom-alert__progress-bar--question{background-color:var(--wacom-question)}.wacom-alert__body{position:relative;padding:0 0 0 10px;min-height:36px;margin:0 0 0 15px;text-align:left;display:flex;justify-content:space-between;align-items:center}.wacom-alert__texts{margin:10px 0 0;padding-right:2px;display:flex;justify-content:space-between;align-items:center}.wacom-alert__message{padding:0;font-size:14px;line-height:16px;text-align:left;color:#0009;white-space:normal}.wacom-alert__message--slide-in{-webkit-animation:iziT-slideIn 1s cubic-bezier(.16,.81,.32,1) both;-moz-animation:iziT-slideIn 1s cubic-bezier(.16,.81,.32,1) both;animation:iziT-slideIn 1s cubic-bezier(.16,.81,.32,1) both}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: AlertComponent, decorators: [{
type: Component,
args: [{ selector: 'alert', imports: [CommonModule], template: "@if (text) {\r\n\t<div class=\"wacom-alert wacom-alert--auto-height\" [class.wacom-alert--closing]=\"delete_animation\" [ngClass]=\"class\">\r\n\t\t<div [ngClass]=\"'wacom-alert__content--color-' + type\" class=\"wacom-alert__content wacom-alert__content--bounce-in-up\" #alertRef>\r\n\t\t\t@if (progress) {\r\n\t\t\t\t<div class=\"wacom-alert__progress\">\r\n\t\t\t\t\t<span\r\n\t\t\t\t\t\tclass=\"wacom-alert__progress-bar\"\r\n\t\t\t\t\t\t[ngClass]=\"'wacom-alert__progress-bar--' + type\"\r\n\t\t\t\t\t\t[ngStyle]=\"{\r\n\t\t\t\t\t\t\t'animation-duration': (timeout + 350) / 1000 + 's',\r\n\t\t\t\t\t\t}\"\r\n\t\t\t\t\t></span>\r\n\t\t\t\t</div>\r\n\t\t\t}\r\n\t\t\t<div class=\"wacom-alert__body\">\r\n\t\t\t\t<div class=\"wacom-alert__texts\">\r\n\t\t\t\t\t@if (icon) {\r\n\t\t\t\t\t\t<div class=\"{{ icon }}\"></div>\r\n\t\t\t\t\t}\r\n\t\t\t\t\t<div class=\"wacom-alert__message wacom-alert__message--slide-in\">\r\n\t\t\t\t\t\t{{ text }}\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t\t@if (type === \"question\") {\r\n\t\t\t\t\t<div>\r\n\t\t\t\t\t\t@for (button of buttons; track button.text) {\r\n\t\t\t\t\t\t\t<button (click)=\"remove(button.callback)\" class=\"wacom-alert__button\">\r\n\t\t\t\t\t\t\t\t{{ button.text }}\r\n\t\t\t\t\t\t\t</button>\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t</div>\r\n\t\t\t\t}\r\n\t\t\t\t@if (closable) {\r\n\t\t\t\t\t<div class=\"wacom-alert__close\" (click)=\"remove()\"></div>\r\n\t\t\t\t}\r\n\t\t\t</div>\r\n\t\t</div>\r\n\t</div>\r\n}\r\n", styles: ["@keyframes iziT-bounceInUp{0%{opacity:0;transform:translateY(200px)}50%{opacity:1;transform:translateY(-10px)}70%{transform:translateY(5px)}to{transform:translateY(0)}}@keyframes wacom-alert-progress{0%{width:100%}to{width:0%}}.wacom-alert{font-size:0;height:100px;width:100%;transform:translateZ(0);backface-visibility:hidden;transition:.3s all ease-in-out;opacity:1}.wacom-alert--closing{opacity:0;transition:.3s all ease-in-out}.wacom-alert--auto-height{height:auto!important}.wacom-alert__content{display:inline-block;clear:both;position:relative;font-family:Lato,Tahoma,Arial;font-size:14px;padding:8px 0 9px;background:#eeeeeee6;border-color:#eeeeeee6;width:100%;pointer-events:all;cursor:default;transform:translate(0);-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;min-height:54px}.wacom-alert__content:hover .wacom-alert__progress-bar{animation-play-state:paused}.wacom-alert__content--bounce-in-up{-webkit-animation:iziT-bounceInUp .7s ease-in-out both;animation:iziT-bounceInUp .7s ease-in-out both}.wacom-alert__content--theme-dark{background:#565c70;border-color:#565c70}.wacom-alert__content--theme-dark .wacom-alert__message{color:#ffffffb3;font-weight:300}.wacom-alert__content--color-info{background-color:var(--wacom-info)}.wacom-alert__content--color-error{background-color:var(--wacom-error)}.wacom-alert__content--color-success{background-color:var(--wacom-success)}.wacom-alert__content--color-warning{background-color:var(--wacom-warning)}.wacom-alert__content--color-question{background-color:var(--wacom-question)}.wacom-alert__close{width:15px;height:15px;right:17px;top:-5px;opacity:.3;position:relative;order:2}.wacom-alert__close:hover{opacity:1}.wacom-alert__close:before,.wacom-alert__close:after{cursor:pointer;position:absolute;left:15px;content:\" \";height:12px;width:2px;background-color:var(--wacom-secondary)}.wacom-alert__close:before{transform:rotate(45deg)}.wacom-alert__close:after{transform:rotate(-45deg)}.wacom-alert__progress{bottom:0;position:absolute;width:100%;margin-bottom:0;border-radius:50px}.wacom-alert__progress:hover .wacom-alert__progress-bar{animation-play-state:paused}.wacom-alert__progress-bar{display:block;width:100%;height:2px;background-color:#a5a5a5ed;animation-name:wacom-alert-progress;animation-duration:10s;border-radius:50px}.wacom-alert__progress-bar--info{background-color:var(--wacom-info)}.wacom-alert__progress-bar--error{background-color:var(--wacom-error)}.wacom-alert__progress-bar--success{background-color:var(--wacom-success)}.wacom-alert__progress-bar--warning{background-color:var(--wacom-warning)}.wacom-alert__progress-bar--question{background-color:var(--wacom-question)}.wacom-alert__body{position:relative;padding:0 0 0 10px;min-height:36px;margin:0 0 0 15px;text-align:left;display:flex;justify-content:space-between;align-items:center}.wacom-alert__texts{margin:10px 0 0;padding-right:2px;display:flex;justify-content:space-between;align-items:center}.wacom-alert__message{padding:0;font-size:14px;line-height:16px;text-align:left;color:#0009;white-space:normal}.wacom-alert__message--slide-in{-webkit-animation:iziT-slideIn 1s cubic-bezier(.16,.81,.32,1) both;-moz-animation:iziT-slideIn 1s cubic-bezier(.16,.81,.32,1) both;animation:iziT-slideIn 1s cubic-bezier(.16,.81,.32,1) both}\n"] }]
}], propDecorators: { alertRef: [{
type: ViewChild,
args: ['alertRef']
}] } });
/**
* BaseComponent is an abstract class that provides basic functionality for managing the current timestamp.
*/
class BaseComponent {
constructor() {
/**
* The current timestamp in milliseconds since the Unix epoch.
*/
this.now = new Date().getTime();
}
/**
* Refreshes the `now` property with the current timestamp.
*/
refreshNow() {
this.now = new Date().getTime();
}
}
/**
* Container component that provides placeholder elements for alert instances
* rendered in different screen positions.
*/
class WrapperComponent {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: WrapperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.2.1", type: WrapperComponent, isStandalone: true, selector: "lib-wrapper", ngImport: i0, template: "<div class=\"wacom-wrapper\">\r\n\t<div class=\"wacom-wrapper__alert wacom-wrapper__alert--top-left\" id=\"topLeft\"></div>\r\n\t<div class=\"wacom-wrapper__alert wacom-wrapper__alert--top\" id=\"top\"></div>\r\n\t<div class=\"wacom-wrapper__alert wacom-wrapper__alert--top-right\" id=\"topRight\"></div>\r\n\r\n\t<div class=\"wacom-wrapper__alert wacom-wrapper__alert--left\" id=\"left\"></div>\r\n\t<div class=\"wacom-wrapper__alert wacom-wrapper__alert--center\" id=\"center\"></div>\r\n\t<div class=\"wacom-wrapper__alert wacom-wrapper__alert--right\" id=\"right\"></div>\r\n\r\n\t<div class=\"wacom-wrapper__alert wacom-wrapper__alert--bottom-left\" id=\"bottomLeft\"></div>\r\n\t<div class=\"wacom-wrapper__alert wacom-wrapper__alert--bottom\" id=\"bottom\"></div>\r\n\t<div class=\"wacom-wrapper__alert wacom-wrapper__alert--bottom-right\" id=\"bottomRight\"></div>\r\n</div>\r\n", styles: [".wacom-wrapper__alert{z-index:99999;position:fixed;width:100%;pointer-events:none;display:flex;flex-direction:column}.wacom-wrapper__alert--top-left{top:0;left:0;text-align:left}.wacom-wrapper__alert--top{top:0;left:0;right:0;text-align:center}.wacom-wrapper__alert--top-right{top:0;right:0;text-align:right}.wacom-wrapper__alert--left{top:0;bottom:0;left:0;justify-content:center;text-align:left}.wacom-wrapper__alert--center{inset:0;text-align:center;justify-content:center;flex-flow:column;align-items:center}.wacom-wrapper__alert--right{top:0;bottom:0;right:0;justify-content:center;text-align:right}.wacom-wrapper__alert--bottom-left{bottom:0;left:0;text-align:left}.wacom-wrapper__alert--bottom{bottom:0;left:0;right:0;text-align:center}.wacom-wrapper__alert--bottom-right{bottom:0;right:0;text-align:right}@media only screen and (max-width: 567px){.wacom-wrapper__alert--top-left,.wacom-wrapper__alert--top-right,.wacom-wrapper__alert--left,.wacom-wrapper__alert--right,.wacom-wrapper__alert--bottom-left,.wacom-wrapper__alert--bottom-right,.wacom-wrapper__alert--center{left:0;right:0;text-align:center}.wacom-wrapper__alert--center{align-items:stretch}.wacom-wrapper__alert--left,.wacom-wrapper__alert--right{flex-flow:column;align-items:center}}\n"] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: WrapperComponent, decorators: [{
type: Component,
args: [{ selector: 'lib-wrapper', imports: [], template: "<div class=\"wacom-wrapper\">\r\n\t<div class=\"wacom-wrapper__alert wacom-wrapper__alert--top-left\" id=\"topLeft\"></div>\r\n\t<div class=\"wacom-wrapper__alert wacom-wrapper__alert--top\" id=\"top\"></div>\r\n\t<div class=\"wacom-wrapper__alert wacom-wrapper__alert--top-right\" id=\"topRight\"></div>\r\n\r\n\t<div class=\"wacom-wrapper__alert wacom-wrapper__alert--left\" id=\"left\"></div>\r\n\t<div class=\"wacom-wrapper__alert wacom-wrapper__alert--center\" id=\"center\"></div>\r\n\t<div class=\"wacom-wrapper__alert wacom-wrapper__alert--right\" id=\"right\"></div>\r\n\r\n\t<div class=\"wacom-wrapper__alert wacom-wrapper__alert--bottom-left\" id=\"bottomLeft\"></div>\r\n\t<div class=\"wacom-wrapper__alert wacom-wrapper__alert--bottom\" id=\"bottom\"></div>\r\n\t<div class=\"wacom-wrapper__alert wacom-wrapper__alert--bottom-right\" id=\"bottomRight\"></div>\r\n</div>\r\n", styles: [".wacom-wrapper__alert{z-index:99999;position:fixed;width:100%;pointer-events:none;display:flex;flex-direction:column}.wacom-wrapper__alert--top-left{top:0;left:0;text-align:left}.wacom-wrapper__alert--top{top:0;left:0;right:0;text-align:center}.wacom-wrapper__alert--top-right{top:0;right:0;text-align:right}.wacom-wrapper__alert--left{top:0;bottom:0;left:0;justify-content:center;text-align:left}.wacom-wrapper__alert--center{inset:0;text-align:center;justify-content:center;flex-flow:column;align-items:center}.wacom-wrapper__alert--right{top:0;bottom:0;right:0;justify-content:center;text-align:right}.wacom-wrapper__alert--bottom-left{bottom:0;left:0;text-align:left}.wacom-wrapper__alert--bottom{bottom:0;left:0;right:0;text-align:center}.wacom-wrapper__alert--bottom-right{bottom:0;right:0;text-align:right}@media only screen and (max-width: 567px){.wacom-wrapper__alert--top-left,.wacom-wrapper__alert--top-right,.wacom-wrapper__alert--left,.wacom-wrapper__alert--right,.wacom-wrapper__alert--bottom-left,.wacom-wrapper__alert--bottom-right,.wacom-wrapper__alert--center{left:0;right:0;text-align:center}.wacom-wrapper__alert--center{align-items:stretch}.wacom-wrapper__alert--left,.wacom-wrapper__alert--right{flex-flow:column;align-items:center}}\n"] }]
}] });
/**
* Utility service for programmatically creating and interacting with Angular
* components within the DOM.
*/
class DomService {
constructor() {
/** Reference to the root application used for view attachment. */
this._appRef = inject(ApplicationRef);
/** Injector utilized when creating dynamic components. */
this._injector = inject(EnvironmentInjector);
/**
* Flags to ensure components with a specific `providedIn` key are only
* instantiated once at a time.
*/
this._providedIn = {};
}
/**
* Appends a component to a specified element by ID.
*
* @param component - The component to append.
* @param options - The options to project into the component.
* @param id - The ID of the element to append the component to.
* @returns An object containing the native element and the component reference.
*/
appendById(component, options = {}, id) {
const componentRef = createComponent(component, {
environmentInjector: this._injector,
});
this.projectComponentInputs(componentRef, options);
this._appRef.attachView(componentRef.hostView);
const domElem = componentRef.hostView.rootNodes[0];
const element = document.getElementById(id);
if (element && typeof element.appendChild === 'function') {
element.appendChild(domElem);
}
return {
nativeElement: domElem,
componentRef: componentRef,
remove: () => this.removeComponent(componentRef),
};
}
/**
* Appends a component to a specified element or to the body.
*
* @param component - The component to append.
* @param options - The options to project into the component.
* @param element - The element to append the component to. Defaults to body.
* @returns An object containing the native element and the component reference.
*/
appendComponent(component, options = {}, element = document.body) {
if (options.providedIn) {
if (this._providedIn[options.providedIn]) {
return;
}
this._providedIn[options.providedIn] = true;
}
const componentRef = createComponent(component, {
environmentInjector: this._injector,
});
this.projectComponentInputs(componentRef, options);
this._appRef.attachView(componentRef.hostView);
const domElem = componentRef.hostView.rootNodes[0];
if (element && typeof element.appendChild === 'function') {
element.appendChild(domElem);
}
return {
nativeElement: domElem,
componentRef: componentRef,
remove: () => this.removeComponent(componentRef, options.providedIn),
};
}
/**
* Gets a reference to a dynamically created component.
*
* @param component - The component to create.
* @param options - The options to project into the component.
* @returns The component reference.
*/
getComponentRef(component, options = {}) {
const componentRef = createComponent(component, {
environmentInjector: this._injector,
});
this.projectComponentInputs(componentRef, options);
this._appRef.attachView(componentRef.hostView);
return componentRef;
}
/**
* Projects the inputs onto the component.
*
* @param component - The component reference.
* @param options - The options to project into the component.
* @returns The component reference with the projected inputs.
*/
projectComponentInputs(component, options) {
if (options) {
const props = Object.getOwnPropertyNames(options);
for (const prop of props) {
component.instance[prop] = options[prop];
}
}
return component;
}
/**
* Removes a previously attached component and optionally clears its
* unique `providedIn` flag.
*
* @param componentRef - Reference to the component to be removed.
* @param providedIn - Optional key used to track unique instances.
*/
removeComponent(componentRef, providedIn) {
this._appRef.detachView(componentRef.hostView);
componentRef.destroy();
if (providedIn) {
delete this._providedIn[providedIn];
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: DomService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: DomService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: DomService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}] });
class AlertService {
/**
* Creates a new alert service.
*
* @param config Optional global configuration provided via the
* `CONFIG_TOKEN` injection token.
* @param _dom Service responsible for DOM manipulation and dynamic
* component creation.
*/
constructor(config, _dom) {
this._dom = _dom;
this._alerts = [];
/** Mapping of alert positions to wrapper child indexes. */
this._positionNumber = {
topLeft: 0,
top: 1,
topRight: 2,
left: 3,
center: 4,
right: 5,
bottomLeft: 6,
bottom: 7,
bottomRight: 8,
};
this._config = {
...DEFAULT_ALERT_CONFIG,
...(config?.alert || {}),
};
this._container = this._dom.appendComponent(WrapperComponent);
}
/**
* Displays an alert. Accepts either an options object or a simple string
* which will be used as the alert text.
*
* @returns Reference to the created alert or embedded component
* element.
*/
show(opts) {
opts = this._opts(opts);
if (opts.unique && this._alerts.find((m) => m.unique === opts.unique)) {
return this._alerts.find((m) => m.unique === opts.unique);
}
this._alerts.push(opts);
opts.id ||= Math.floor(Math.random() * Date.now()) + Date.now();
if (!opts.type)
opts.type = 'info';
if (!opts.position)
opts.position = 'bottomRight';
let alertComponent;
let content;
opts.close = () => {
content?.remove();
alertComponent?.remove();
content = undefined;
alertComponent = undefined;
if (typeof opts.onClose == 'function') {
opts.onClose?.();
}
this._alerts.splice(this._alerts.findIndex((m) => m.id === opts.id), 1);
};
alertComponent = this._dom.appendById(AlertComponent, opts, opts.position);
if (typeof opts.component === 'function') {
content = this._dom.appendComponent(opts.component, opts, this._container.nativeElement.children[0].children[this._positionNumber[opts.position] || 0]);
}
if (typeof opts.timeout !== 'number') {
opts.timeout = 3000;
}
if (opts.timeout) {
setTimeout(() => {
opts.close?.();
}, opts.timeout);
}
return opts;
}
/**
* Convenience alias for `show`.
*/
open(opts) {
this.show(opts);
}
/**
* Displays an informational alert.
*/
info(opts) {
opts = this._opts(opts);
opts.type = 'info';
this.show(opts);
}
/**
* Displays a success alert.
*/
success(opts) {
opts = this._opts(opts);
opts.type = 'success';
this.show(opts);
}
/**
* Displays a warning alert.
*/
warning(opts) {
opts = this._opts(opts);
opts.type = 'warning';
this.show(opts);
}
/**
* Displays an error alert.
*/
error(opts) {
opts = this._opts(opts);
opts.type = 'error';
this.show(opts);
}
/**
* Displays a question alert.
*/
question(opts) {
opts = this._opts(opts);
opts.type = 'question';
this.show(opts);
}
/**
* Removes all alert elements from the document.
*/
destroy() {
for (let i = this._alerts.length - 1; i >= 0; i--) {
this._alerts[i].close?.();
}
}
_opts(opts) {
return typeof opts === 'string'
? {
...this._config,
text: opts,
}
: {
...this._config,
...opts,
};
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: AlertService, deps: [{ token: CONFIG_TOKEN, optional: true }, { token: DomService }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: AlertService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: AlertService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: () => [{ type: undefined, decorators: [{
type: Inject,
args: [CONFIG_TOKEN]
}, {
type: Optional
}] }, { type: DomService }] });
// Add capitalize method to String prototype if it doesn't already exist
if (!String.prototype.capitalize) {
String.prototype.capitalize = function () {
if (this.length > 0) {
return this.charAt(0).toUpperCase() + this.slice(1).toLowerCase();
}
return '';
};
}
class CoreService {
constructor(platformId) {
this.platformId = platformId;
this.deviceID = localStorage.getItem('deviceID') || (typeof crypto?.randomUUID === 'function' ? crypto.randomUUID() : this.UUID());
// After While
this._afterWhile = {};
// Device management
this.device = '';
// Version management
this.version = '1.0.0';
this.appVersion = '';
this.dateVersion = '';
// Signal management
this._signals = {};
// Await management
this._completed = {};
this._completeResolvers = {};
// Locking management
this._locked = {};
this._unlockResolvers = {};
// Linking management
this.linkCollections = [];
this.linkRealCollectionName = {};
this.linkIds = {};
localStorage.setItem('deviceID', this.deviceID);
this.detectDevice();
}
/**
* Generates a UUID (Universally Unique Identifier) version 4.
*
* This implementation uses `Math.random()` to generate random values,
* making it suitable for general-purpose identifiers, but **not** for
* cryptographic or security-sensitive use cases.
*
* The format follows the UUID v4 standard: `xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`
* where:
* - `x` is a random hexadecimal digit (0–f)
* - `4` indicates UUID version 4
* - `y` is one of 8, 9, A, or B
*
* Example: `f47ac10b-58cc-4372-a567-0e02b2c3d479`
*
* @returns A string containing a UUID v4.
*/
UUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0;
const v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
/**
* Converts an object to an array. Optionally holds keys instead of values.
*
* @param {any} obj - The object to be converted.
* @param {boolean} [holder=false] - If true, the keys will be held in the array; otherwise, the values will be held.
* @returns {any[]} The resulting array.
*/
ota(obj, holder = false) {
if (Array.isArray(obj))
return obj;
if (typeof obj !== 'object' || obj === null)
return [];
const arr = [];
for (const each in obj) {
if (obj.hasOwnProperty(each) && (obj[each] || typeof obj[each] === 'number' || typeof obj[each] === 'boolean')) {
if (holder) {
arr.push(each);
}
else {
arr.push(obj[each]);
}
}
}
return arr;
}
/**
* Removes elements from `fromArray` that are present in `removeArray` based on a comparison field.
*
* @param {any[]} removeArray - The array of elements to remove.
* @param {any[]} fromArray - The array from which to remove elements.
* @param {string} [compareField='_id'] - The field to use for comparison.
* @returns {any[]} The modified `fromArray` with elements removed.
*/
splice(removeArray, fromArray, compareField = '_id') {
if (!Array.isArray(removeArray) || !Array.isArray(fromArray)) {
return fromArray;
}
const removeSet = new Set(removeArray.map((item) => item[compareField]));
return fromArray.filter((item) => !removeSet.has(item[compareField]));
}
/**
* Unites multiple _id values into a single unique _id.
* The resulting _id is unique regardless of the order of the input _id values.
*
* @param {...string[]} args - The _id values to be united.
* @returns {string} The unique combined _id.
*/
ids2id(...args) {
args.sort((a, b) => {
if (Number(a.toString().substring(0, 8)) > Number(b.toString().substring(0, 8))) {
return 1;
}
return -1;
});
return args.join();
}
/**
* Delays the execution of a callback function for a specified amount of time.
* If called again within that time, the timer resets.
*
* @param {string | object | (() => void)} doc - A unique identifier for the timer, an object to host the timer, or the callback function.
* @param {() => void} [cb] - The callback function to execute after the delay.
* @param {number} [time=1000] - The delay time in milliseconds.
*/
afterWhile(doc, cb, time = 1000) {
if (typeof doc === 'function') {
cb = doc;
doc = 'common';
}
if (typeof cb === 'function' && typeof time === 'number') {
if (typeof doc === 'string') {
clearTimeout(this._afterWhile[doc]);
this._afterWhile[doc] = window.setTimeout(cb, time);
}
else if (typeof doc === 'object') {
clearTimeout(doc.__afterWhile);
doc.__afterWhile = window.setTimeout(cb, time);
}
else {
console.warn('badly configured after while');
}
}
}
/**
* Recursively copies properties from one object to another.
* Handles nested objects, arrays, and Date instances appropriately.
*
* @param from - The source object from which properties are copied.
* @param to - The target object to which properties are copied.
*/
copy(from, to) {
for (const each in from) {
if (typeof from[each] !== 'object' || from[each] instanceof Date || Array.isArray(from[each]) || from[each] === null) {
to[each] = from[each];
}
else {
if (typeof to[each] !== 'object' || to[each] instanceof Date || Array.isArray(to[each]) || to[each] === null) {
to[each] = {};
}
this.copy(from[each], to[each]);
}
}
}
/**
* Detects the device type based on the user agent.
*/
detectDevice() {
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
if (/windows phone/i.test(userAgent)) {
this.device = 'Windows Phone';
}
else if (/android/i.test(userAgent)) {
this.device = 'Android';
}
else if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) {
this.device = 'iOS';
}
else {
this.device = 'Web';
}
}
/**
* Checks if the device is a mobile device.
* @returns {boolean} - Returns true if the device is a mobile device.
*/
isMobile() {
return this.device === 'Windows Phone' || this.device === 'Android' || this.device === 'iOS';
}
/**
* Checks if the device is a tablet.
* @returns {boolean} - Returns true if the device is a tablet.
*/
isTablet() {
return this.device === 'iOS' && /iPad/.test(navigator.userAgent);
}
/**
* Checks if the device is a web browser.
* @returns {boolean} - Returns true if the device is a web browser.
*/
isWeb() {
return this.device === 'Web';
}
/**
* Checks if the device is an Android device.
* @returns {boolean} - Returns true if the device is an Android device.
*/
isAndroid() {
return this.device === 'Android';
}
/**
* Checks if the device is an iOS device.
* @returns {boolean} - Returns true if the device is an iOS device.
*/
isIos() {
return this.device === 'iOS';
}
/**
* Sets the combined version string based on appVersion and dateVersion.
*/
setVersion() {
this.version = this.appVersion || '';
this.version += this.version && this.dateVersion ? ' ' : '';
this.version += this.dateVersion || '';
}
/**
* Sets the app version and updates the combined version string.
*
* @param {string} appVersion - The application version to set.
*/
setAppVersion(appVersion) {
this.appVersion = appVersion;
this.setVersion();
}
/**
* Sets the date version and updates the combined version string.
*
* @param {string} dateVersion - The date version to set.
*/
setDateVersion(dateVersion) {
this.dateVersion = dateVersion;
this.setVersion();
}
/**
* Emits a signal, optionally passing data to the listeners.
* @param signal - The name of the signal to emit.
* @param data - Optional data to pass to the listeners.
*/
emit(signal, data) {
if (!this._signals[signal]) {
this._signals[signal] = new Subject();
}
this._signals[signal].next(data);
}
/**
* Returns an Observable that emits values when the specified signal is emitted.
* Multiple components or services can subscribe to this Observable to be notified of the signal.
* @param signal - The name of the signal to listen for.
* @returns An Observable that emits when the signal is emitted.
*/
on(signal) {
if (!this._signals[signal]) {
this._signals[signal] = new Subject();
}
return this._signals[signal].asObservable();
}
/**
* Completes the Subject for a specific signal, effectively stopping any future emissions.
* This also unsubscribes all listeners for the signal.
* @param signal - The name of the signal to stop.
*/
off(signal) {
if (!this._signals[signal])
return;
this._signals[signal].complete();
delete this._signals[signal];
}
/**
* Marks a task as complete.
* @param task - The task to mark as complete, identified by a string.
*/
complete(task, document = true) {
this._completed[task] = document;
if (this._completeResolvers[task]) {
this._completeResolvers[task].forEach((resolve) => resolve(document));
this._completeResolvers[task] = [];
}
}
/**
* Waits for one or more tasks to be marked as complete.
*
* @param {string | string[]} tasks - The task or array of tasks to wait for.
* @returns {Promise<unknown>} A promise that resolves when all specified tasks are complete.
* - If a single task is provided, resolves with its completion result.
* - If multiple tasks are provided, resolves with an array of results in the same order.
*
* @remarks
* If any task is not yet completed, a resolver is attached. The developer is responsible for managing
* resolver cleanup if needed. Resolvers remain after resolution and are not removed automatically.
*/
onComplete(tasks) {
if (typeof tasks === 'string') {
tasks = [tasks];
}
if (this._isCompleted(tasks)) {
return Promise.resolve(tasks.length > 1 ? tasks.map((task) => this._completed[task]) : this._completed[tasks[0]]);
}
return new Promise((resolve) => {
for (const task of tasks) {
if (!this._completeResolvers[task]) {
this._completeResolvers[task] = [];
}
this._completeResolvers[task].push(this._allCompleted(tasks, resolve));
}
});
}
/**
* Returns a resolver function that checks if all given tasks ar