UNPKG

wacom

Version:

Module which has common services and components which can be used on all projects.

962 lines (951 loc) 181 kB
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