@lenne.tech/ng-base 0.3.3 → 0.3.7

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.
Files changed (28) hide show
  1. package/bundles/lenne.tech-ng-base-src-lib-base.umd.js +711 -736
  2. package/bundles/lenne.tech-ng-base-src-lib-base.umd.js.map +1 -1
  3. package/bundles/lenne.tech-ng-base.umd.js +711 -736
  4. package/bundles/lenne.tech-ng-base.umd.js.map +1 -1
  5. package/esm2015/lib/base/classes/helper.class.js +19 -1
  6. package/esm2015/lib/base/directives/match-height.directive.js +19 -14
  7. package/esm2015/lib/base/services/graphql.service.js +2 -2
  8. package/esm2015/lib/base/services/image.service.js +24 -38
  9. package/esm2015/lib/base/services/scroll.service.js +9 -30
  10. package/esm2015/src/lib/base/classes/helper.class.js +19 -1
  11. package/esm2015/src/lib/base/directives/match-height.directive.js +19 -14
  12. package/esm2015/src/lib/base/services/graphql.service.js +2 -2
  13. package/esm2015/src/lib/base/services/image.service.js +24 -38
  14. package/esm2015/src/lib/base/services/scroll.service.js +9 -30
  15. package/fesm2015/lenne.tech-ng-base-src-lib-base.js +542 -555
  16. package/fesm2015/lenne.tech-ng-base-src-lib-base.js.map +1 -1
  17. package/fesm2015/lenne.tech-ng-base.js +542 -555
  18. package/fesm2015/lenne.tech-ng-base.js.map +1 -1
  19. package/lib/base/classes/helper.class.d.ts +7 -0
  20. package/lib/base/directives/match-height.directive.d.ts +7 -7
  21. package/lib/base/services/image.service.d.ts +1 -2
  22. package/lib/base/services/scroll.service.d.ts +1 -2
  23. package/package.json +1 -1
  24. package/src/lib/base/classes/helper.class.d.ts +7 -0
  25. package/src/lib/base/directives/match-height.directive.d.ts +7 -7
  26. package/src/lib/base/lenne.tech-ng-base-src-lib-base.metadata.json +1 -1
  27. package/src/lib/base/services/image.service.d.ts +1 -2
  28. package/src/lib/base/services/scroll.service.d.ts +1 -2
@@ -2,13 +2,14 @@ import * as i1 from 'apollo-angular';
2
2
  import { APOLLO_OPTIONS, gql, Apollo } from 'apollo-angular';
3
3
  import { HttpLink } from 'apollo-angular/http';
4
4
  import * as i0 from '@angular/core';
5
- import { InjectionToken, Injectable, Inject, Directive, ElementRef, Input, Output, HostBinding, NgZone, HostListener, Pipe, NgModule } from '@angular/core';
5
+ import { InjectionToken, Injectable, Inject, Directive, ElementRef, Input, Output, HostBinding, PLATFORM_ID, HostListener, Pipe, NgModule } from '@angular/core';
6
6
  import { BehaviorSubject, Observable, of, throwError } from 'rxjs';
7
7
  import { ApolloLink, split, concat } from '@apollo/client/core';
8
8
  import { InMemoryCache } from '@apollo/client/cache';
9
9
  import { WebSocketLink } from '@apollo/client/link/ws';
10
10
  import { getMainDefinition } from '@apollo/client/utilities';
11
11
  import { __awaiter } from 'tslib';
12
+ import { isPlatformBrowser } from '@angular/common';
12
13
  import * as i1$2 from '@angular/platform-browser';
13
14
  import { DomSanitizer, Title, Meta } from '@angular/platform-browser';
14
15
  import * as i1$1 from '@angular/router';
@@ -419,362 +420,6 @@ LazyLoadDirective.propDecorators = {
419
420
  src: [{ type: Input }]
420
421
  };
421
422
 
422
- /**
423
- * Image service
424
- */
425
- class ImageService {
426
- /**
427
- * Preaload images (for lazy handling)
428
- *
429
- * @param options
430
- * @protected
431
- */
432
- preLoadImages(options) {
433
- // Init configuration
434
- const config = Object.assign({ doneFunnction: undefined, elements: document, regExp: undefined, timeout: undefined }, options);
435
- return new Promise((resolve) => {
436
- var _a;
437
- // Init variables
438
- let resolved = false;
439
- let timeoutId;
440
- // HTMLCollection to array
441
- if (config.elements instanceof HTMLCollection) {
442
- config.elements = Array.from(config.elements);
443
- }
444
- // Single element to array
445
- if (!Array.isArray(config.elements)) {
446
- config.elements = [config.elements];
447
- }
448
- // Init loading
449
- const promises = [];
450
- // Get all images
451
- let images = [];
452
- config.elements.forEach((element) => {
453
- images = images.concat(Array.from(element.getElementsByTagName('img')));
454
- });
455
- // Prepare images
456
- for (const image of images) {
457
- const src = image.getAttribute('src');
458
- // Check if changes are necessary and possible
459
- if ((image.complete && image.naturalHeight !== 0) || // already laoded
460
- image.getAttribute('loading') !== 'lazy' || // loading attribute !== lazy
461
- !src || // Image has no src
462
- ((_a = config.regExp) === null || _a === void 0 ? void 0 : _a.test(src)) // Test if src matches regular expression
463
- ) {
464
- continue;
465
- }
466
- // Replace lazy loading image with immediately loading image
467
- const newImage = document.createElement('img');
468
- Array.from(image.attributes).forEach((attr) => {
469
- if (attr.name !== 'loading' && attr.name !== 'src') {
470
- newImage.setAttribute(attr.name, attr.value);
471
- }
472
- });
473
- image.replaceWith(newImage);
474
- // Wait for laoding
475
- promises.push(this.loadImage(newImage, src));
476
- }
477
- if (config.timeout) {
478
- timeoutId = setTimeout(() => __awaiter(this, void 0, void 0, function* () {
479
- resolved = true;
480
- if (config.doneFunnction) {
481
- yield config.doneFunction();
482
- }
483
- resolve(undefined);
484
- }));
485
- }
486
- // Wait until all images loaded
487
- Promise.all(promises)
488
- .catch((e) => console.error('Error during image loading', e))
489
- .finally(() => __awaiter(this, void 0, void 0, function* () {
490
- // Check if already resolved
491
- if (resolved) {
492
- return;
493
- }
494
- // Stop timeout
495
- if (timeoutId) {
496
- clearTimeout(timeoutId);
497
- }
498
- // Return result
499
- let result;
500
- if (config.doneFunnction) {
501
- result = yield config.doneFunction();
502
- }
503
- resolve(result);
504
- }));
505
- });
506
- }
507
- /**
508
- * Load image for image element
509
- *
510
- * @param imageElement
511
- * @param src
512
- * @protected
513
- */
514
- loadImage(imageElement, src) {
515
- return __awaiter(this, void 0, void 0, function* () {
516
- src = src || imageElement.src;
517
- return new Promise((resolve, reject) => {
518
- imageElement.onload = () => resolve(imageElement);
519
- imageElement.onerror = reject;
520
- imageElement.src = src;
521
- });
522
- });
523
- }
524
- }
525
- ImageService.ɵprov = i0.ɵɵdefineInjectable({ factory: function ImageService_Factory() { return new ImageService(); }, token: ImageService, providedIn: "root" });
526
- ImageService.decorators = [
527
- { type: Injectable, args: [{
528
- providedIn: 'root',
529
- },] }
530
- ];
531
-
532
- /**
533
- * Set same height (maximum) to different elements
534
- */
535
- class MatchHeightDirective {
536
- constructor(el, imageService, zone) {
537
- this.el = el;
538
- this.imageService = imageService;
539
- this.zone = zone;
540
- this.loadLazyImages = true;
541
- }
542
- onResize() {
543
- this.matchHeight(this.el.nativeElement, this.ltMatchHeight);
544
- }
545
- ngAfterViewChecked() {
546
- this.matchHeight(this.el.nativeElement, this.ltMatchHeight);
547
- }
548
- matchHeight(parent, target) {
549
- // Timeout is set to force Angular to wait for all actions and only continue once the process has run through.
550
- // Otherwise, an infinite loop will occur.
551
- setTimeout(() => __awaiter(this, void 0, void 0, function* () {
552
- if (!parent) {
553
- return;
554
- }
555
- let classNames;
556
- if (!Array.isArray(target)) {
557
- classNames = [target];
558
- }
559
- else {
560
- classNames = target;
561
- }
562
- for (const className of classNames) {
563
- // find all child structureElements with the selected class name
564
- const children = parent.getElementsByClassName(className);
565
- if (!children || children.length < 2) {
566
- continue;
567
- }
568
- // Load images of children
569
- if (this.loadLazyImages) {
570
- yield this.imageService.preLoadImages({ elements: children });
571
- }
572
- // reset all children height
573
- Array.from(children).forEach((element) => {
574
- element.style.height = 'initial';
575
- });
576
- // get all the child structureElements heights
577
- const itemHeights = Array.from(children).map((x) => x.getBoundingClientRect().height);
578
- // find out the tallest one
579
- const maxHeight = itemHeights.reduce((prev, curr) => (curr > prev ? curr : prev), 0);
580
- // update the child structureElements height to the tallest one
581
- Array.from(children).forEach((element) => (element.style.height = `${maxHeight}px`));
582
- }
583
- }), 1);
584
- }
585
- }
586
- MatchHeightDirective.decorators = [
587
- { type: Directive, args: [{
588
- selector: '[ltMatchHeight]',
589
- },] }
590
- ];
591
- MatchHeightDirective.ctorParameters = () => [
592
- { type: ElementRef },
593
- { type: ImageService },
594
- { type: NgZone }
595
- ];
596
- MatchHeightDirective.propDecorators = {
597
- ltMatchHeight: [{ type: Input }],
598
- loadLazyImages: [{ type: Input }],
599
- onResize: [{ type: HostListener, args: ['window:resize',] }]
600
- };
601
-
602
- /**
603
- * Reszise detection
604
- * (inpsired by: https://stackblitz.com/edit/angular-resizable)
605
- */
606
- class ResizableDirective {
607
- constructor(el) {
608
- this.el = el;
609
- this.resizableGrabWidth = 8;
610
- this.resizableMinWidth = 10;
611
- this.dragging = false;
612
- }
613
- ngOnInit() {
614
- this.addListenerToDocument();
615
- this.addListenerToElement();
616
- }
617
- mouseMoveG(evt) {
618
- if (!this.dragging) {
619
- return;
620
- }
621
- this.newWidth(evt.clientX - this.el.nativeElement.offsetLeft);
622
- evt.stopPropagation();
623
- }
624
- newWidth(wid) {
625
- const result = Math.max(this.resizableMinWidth, wid);
626
- this.el.nativeElement.style.width = result + 'px';
627
- }
628
- mouseDown(evt) {
629
- if (this.inDragRegion(evt)) {
630
- this.dragging = true;
631
- this.preventGlobalMouseEvents();
632
- evt.stopPropagation();
633
- }
634
- }
635
- restoreGlobalMouseEvents() {
636
- document.body.style['pointer-events'] = 'auto';
637
- }
638
- mouseUpG(evt) {
639
- if (!this.dragging) {
640
- return;
641
- }
642
- this.restoreGlobalMouseEvents();
643
- this.dragging = false;
644
- evt.stopPropagation();
645
- }
646
- mouseMove(evt) {
647
- if (this.inDragRegion(evt) || this.dragging) {
648
- this.el.nativeElement.style.cursor = 'col-resize';
649
- }
650
- else {
651
- this.el.nativeElement.style.cursor = 'default';
652
- }
653
- }
654
- preventGlobalMouseEvents() {
655
- document.body.style['pointer-events'] = 'none';
656
- }
657
- inDragRegion(evt) {
658
- return this.el.nativeElement.clientWidth - evt.clientX + this.el.nativeElement.offsetLeft < this.resizableGrabWidth;
659
- }
660
- addListenerToDocument() {
661
- document.addEventListener('mousemove', (e) => {
662
- this.mouseMoveG(e);
663
- }, true);
664
- document.addEventListener('mouseup', (e) => {
665
- this.mouseUpG(e);
666
- }, true);
667
- }
668
- addListenerToElement() {
669
- this.el.nativeElement.addEventListener('mousedown', (e) => {
670
- this.mouseDown(e);
671
- }, true);
672
- this.el.nativeElement.addEventListener('mousemove', (e) => {
673
- this.mouseMove(e);
674
- }, true);
675
- }
676
- }
677
- ResizableDirective.decorators = [
678
- { type: Directive, args: [{
679
- selector: '[ltResizable]',
680
- },] }
681
- ];
682
- ResizableDirective.ctorParameters = () => [
683
- { type: ElementRef }
684
- ];
685
- ResizableDirective.propDecorators = {
686
- resizableGrabWidth: [{ type: Input }],
687
- resizableMinWidth: [{ type: Input }]
688
- };
689
-
690
- /**
691
- * Truncate string with ellipses
692
- */
693
- class EllipsesPipe {
694
- transform(value, limit) {
695
- if (!value) {
696
- return null;
697
- }
698
- return value.length > limit ? value.substring(0, limit) + '...' : value;
699
- }
700
- }
701
- EllipsesPipe.decorators = [
702
- { type: Pipe, args: [{
703
- name: 'ltEllipses',
704
- },] }
705
- ];
706
-
707
- /**
708
- * Declarate html as safe
709
- */
710
- class SafeHtmlPipe {
711
- constructor(sanitized) {
712
- this.sanitized = sanitized;
713
- }
714
- transform(value) {
715
- return this.sanitized.bypassSecurityTrustHtml(value);
716
- }
717
- }
718
- SafeHtmlPipe.decorators = [
719
- { type: Pipe, args: [{ name: 'ltSafeHtml' },] }
720
- ];
721
- SafeHtmlPipe.ctorParameters = () => [
722
- { type: DomSanitizer }
723
- ];
724
-
725
- // Imported and exported elements
726
- const elements = [
727
- // Directives
728
- ContextMenuDirective,
729
- LazyLoadDirective,
730
- MatchHeightDirective,
731
- ResizableDirective,
732
- // Pipes
733
- EllipsesPipe,
734
- SafeHtmlPipe,
735
- ];
736
- /**
737
- * Base module
738
- */
739
- class BaseModule {
740
- /**
741
- * Set configuration of base module in root module of the app
742
- */
743
- static forRoot(baseModuleConfig = {}) {
744
- // Default config
745
- const config = Object.assign({ apiUrl: 'localhost:3000', authGuardRedirectUrl: '/auth', logging: false, version: null, prefix: null, scrollDetectionOffset: 200, scrollOffset: 100, storageType: 'local' }, baseModuleConfig);
746
- // Prepare providers
747
- const providers = [
748
- {
749
- provide: BASE_MODULE_CONFIG,
750
- useValue: baseModuleConfig,
751
- },
752
- ];
753
- // Add apollo features if API URL is set
754
- if (baseModuleConfig.apiUrl) {
755
- providers.push({
756
- provide: APOLLO_OPTIONS,
757
- useFactory: apolloOptionsFactory,
758
- deps: [BASE_MODULE_CONFIG, HttpLink, AuthService],
759
- });
760
- }
761
- else if (config.logging) {
762
- console.log('apiUrl is missing, ApolloLink is not initialized');
763
- }
764
- // Return root module
765
- return {
766
- ngModule: BaseModule,
767
- providers,
768
- };
769
- }
770
- }
771
- BaseModule.decorators = [
772
- { type: NgModule, args: [{
773
- declarations: elements,
774
- exports: elements,
775
- },] }
776
- ];
777
-
778
423
  /**
779
424
  * Helper class
780
425
  */
@@ -838,214 +483,577 @@ class Helper {
838
483
  if (!source || typeof source !== 'object' || Array.isArray(source)) {
839
484
  return target;
840
485
  }
841
- // Update properties
842
- for (const key of Object.keys(target)) {
843
- if (source[key] !== undefined && (config.funcAllowed || typeof (source[key] !== 'function'))) {
844
- if (typeof source[key] === 'string' && config.dateStringToDate) {
845
- if (Array.isArray(config.dateStringToDate)) {
846
- if (config.dateStringToDate.includes(key)) {
847
- target[key] = Helper.parseDate(source[key]);
848
- }
849
- }
850
- else {
851
- target[key] = Helper.parseDate(source[key]);
852
- }
486
+ // Update properties
487
+ for (const key of Object.keys(target)) {
488
+ if (source[key] !== undefined && (config.funcAllowed || typeof (source[key] !== 'function'))) {
489
+ if (typeof source[key] === 'string' && config.dateStringToDate) {
490
+ if (Array.isArray(config.dateStringToDate)) {
491
+ if (config.dateStringToDate.includes(key)) {
492
+ target[key] = Helper.parseDate(source[key]);
493
+ }
494
+ }
495
+ else {
496
+ target[key] = Helper.parseDate(source[key]);
497
+ }
498
+ }
499
+ else {
500
+ target[key] = source[key];
501
+ }
502
+ }
503
+ }
504
+ // Return target
505
+ return target;
506
+ }
507
+ /**
508
+ * Create Object or Objects of specified type with specified data
509
+ */
510
+ static maps(data, targetClass) {
511
+ // Check data
512
+ if (!data || typeof data !== 'object') {
513
+ return undefined;
514
+ }
515
+ // Check array
516
+ if (!Array.isArray(data)) {
517
+ data = [data];
518
+ }
519
+ // Map
520
+ return data.map((item) => {
521
+ return targetClass.map(item);
522
+ });
523
+ }
524
+ /**
525
+ * Get plain parameter
526
+ */
527
+ static getPlainParameter(parameter) {
528
+ var _a, _b, _c;
529
+ if (!parameter) {
530
+ return parameter;
531
+ }
532
+ return (_c = (_b = (_a = parameter === null || parameter === void 0 ? void 0 : parameter.split('?')) === null || _a === void 0 ? void 0 : _a.shift()) === null || _b === void 0 ? void 0 : _b.split('#')) === null || _c === void 0 ? void 0 : _c.shift();
533
+ }
534
+ /**
535
+ * Get ID of an url parameter
536
+ */
537
+ static getParameterId(parameter) {
538
+ var _a;
539
+ if (!parameter) {
540
+ return parameter;
541
+ }
542
+ return (_a = Helper.getPlainParameter(parameter)) === null || _a === void 0 ? void 0 : _a.split('-').pop();
543
+ }
544
+ /**
545
+ * Delay
546
+ */
547
+ static delay(ms) {
548
+ const promise = new Promise((resolve) => setTimeout(resolve, ms));
549
+ return promise;
550
+ }
551
+ /**
552
+ * Deep freeze
553
+ * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze#What_is_shallow_freeze
554
+ */
555
+ static deepFreeze(object, preparedObjects = []) {
556
+ // Prevent infinit regress
557
+ if (preparedObjects.includes(object)) {
558
+ return object;
559
+ }
560
+ preparedObjects.push(object);
561
+ // Retrieve the property names defined on object
562
+ const propNames = Object.getOwnPropertyNames(object);
563
+ // Freeze properties before freezing self
564
+ for (const name of propNames) {
565
+ const value = object[name];
566
+ if (value && typeof value === 'object') {
567
+ Helper.deepFreeze(value, preparedObjects);
568
+ }
569
+ }
570
+ return Object.freeze(object);
571
+ }
572
+ /**
573
+ * Generates an unique identifier with 24 characters
574
+ */
575
+ static getUID() {
576
+ let dt = new Date().getTime();
577
+ const uuid = 'xxxxxxxxxxxxxxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
578
+ // eslint-disable-next-line no-bitwise
579
+ const r = (dt + Math.random() * 16) % 16 | 0;
580
+ dt = Math.floor(dt / 16);
581
+ // eslint-disable-next-line no-bitwise
582
+ return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
583
+ });
584
+ return uuid;
585
+ }
586
+ /**
587
+ * Parse date string to date object
588
+ */
589
+ static parseDate(dateString) {
590
+ const reISO = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*))(?:Z|(\+|-)([\d|:]*))?$/;
591
+ const reMsAjax = /^\/Date\((d|-|.*)\)[\/|\\]$/;
592
+ if (typeof dateString === 'string') {
593
+ const isoExec = reISO.exec(dateString);
594
+ if (isoExec) {
595
+ return new Date(dateString);
596
+ }
597
+ const msAjaxExec = reMsAjax.exec(dateString);
598
+ if (msAjaxExec) {
599
+ const cleaned = msAjaxExec[1].split(/[-+,.]/);
600
+ return new Date(cleaned[0] ? +cleaned[0] : 0 - +cleaned[1]);
601
+ }
602
+ }
603
+ return dateString;
604
+ }
605
+ /**
606
+ * Get browser name and version
607
+ */
608
+ static getBrowser() {
609
+ const userAgent = navigator.userAgent;
610
+ let data = userAgent.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
611
+ let version;
612
+ if (/trident/i.test(data[1])) {
613
+ version = /\brv[ :]+(\d+)/g.exec(userAgent) || [];
614
+ return { name: 'IE', version: version[1] || '' };
615
+ }
616
+ if (data[1] === 'Chrome') {
617
+ version = userAgent.match(/\bOPR|Edge\/(\d+)/);
618
+ if (version != null) {
619
+ return { name: 'Opera', version: version[1] };
620
+ }
621
+ }
622
+ data = data[2] ? [data[1], data[2]] : [navigator.appName, navigator.appVersion, '-?'];
623
+ version = userAgent.match(/version\/(\d+)/i);
624
+ if (version !== null) {
625
+ data.splice(1, 1, version[1]);
626
+ }
627
+ return {
628
+ name: data[0],
629
+ version: data[1],
630
+ };
631
+ }
632
+ /**
633
+ * Whether first parameter is a deep subset of second parameter
634
+ * Hint: functions will be ignored
635
+ */
636
+ static isSubset(a, b) {
637
+ // Equal
638
+ if (a === b) {
639
+ return true;
640
+ }
641
+ // Different type
642
+ if (typeof a !== typeof b) {
643
+ return false;
644
+ }
645
+ // Process array
646
+ if (Array.isArray(a)) {
647
+ for (const [key, value] of Object.entries(a)) {
648
+ if (Helper.isSubset(value, b[key])) {
649
+ return false;
853
650
  }
854
- else {
855
- target[key] = source[key];
651
+ }
652
+ return true;
653
+ }
654
+ // Process object
655
+ if (typeof a === 'object') {
656
+ const plain = JSON.parse(JSON.stringify(a));
657
+ for (const [key, value] of Object.entries(plain)) {
658
+ if (Helper.isSubset(value, b[key])) {
659
+ return false;
856
660
  }
857
661
  }
662
+ return true;
858
663
  }
859
- // Return target
860
- return target;
664
+ // Else
665
+ return true;
861
666
  }
862
667
  /**
863
- * Create Object or Objects of specified type with specified data
668
+ * Whether parameters are equal (have equal properties)
669
+ * Hint: functions will be ignored
864
670
  */
865
- static maps(data, targetClass) {
866
- // Check data
867
- if (!data || typeof data !== 'object') {
868
- return undefined;
869
- }
870
- // Check array
871
- if (!Array.isArray(data)) {
872
- data = [data];
873
- }
874
- // Map
875
- return data.map((item) => {
876
- return targetClass.map(item);
877
- });
671
+ static isEqual(a, b) {
672
+ return Helper.isSubset(a, b) && Helper.isSubset(b, a);
878
673
  }
879
674
  /**
880
- * Get plain parameter
675
+ * First level difference of two objects
676
+ * Hint: functions will be ignored and when an array or an object is different the hole array or object will used as property value
677
+ *
678
+ * @returns Record<string, any> Object with properties from first object
881
679
  */
882
- static getPlainParameter(parameter) {
883
- var _a, _b, _c;
884
- if (!parameter) {
885
- return parameter;
680
+ static getDiff(a, b, clone = true) {
681
+ const result = {};
682
+ let firstObject = a;
683
+ if (clone) {
684
+ firstObject = JSON.parse(JSON.stringify(a));
886
685
  }
887
- return (_c = (_b = (_a = parameter === null || parameter === void 0 ? void 0 : parameter.split('?')) === null || _a === void 0 ? void 0 : _a.shift()) === null || _b === void 0 ? void 0 : _b.split('#')) === null || _c === void 0 ? void 0 : _c.shift();
686
+ for (const [key, value] of Object.entries(firstObject)) {
687
+ if (!Helper.isSubset(value, b[key])) {
688
+ result[key] = value;
689
+ }
690
+ }
691
+ return result;
888
692
  }
889
693
  /**
890
- * Get ID of an url parameter
694
+ * Check if propertie is type of HTMLCollection or NodeList
695
+ * See https://stackoverflow.com/a/7238344
696
+ *
697
+ * @param nodes
891
698
  */
892
- static getParameterId(parameter) {
893
- var _a;
894
- if (!parameter) {
895
- return parameter;
699
+ static isNodeList(nodes) {
700
+ try {
701
+ const stringRepr = Object.prototype.toString.call(nodes);
702
+ return (typeof nodes === 'object' &&
703
+ /^\[object (HTMLCollection|NodeList|Object)\]$/.test(stringRepr) &&
704
+ typeof nodes.length === 'number' &&
705
+ (nodes.length === 0 || (typeof nodes[0] === 'object' && nodes[0].nodeType > 0)));
706
+ }
707
+ catch (e) {
708
+ return false;
896
709
  }
897
- return (_a = Helper.getPlainParameter(parameter)) === null || _a === void 0 ? void 0 : _a.split('-').pop();
898
710
  }
711
+ }
712
+
713
+ /**
714
+ * Image service
715
+ */
716
+ class ImageService {
899
717
  /**
900
- * Delay
718
+ * Preaload images (for lazy handling)
719
+ *
720
+ * @param options
721
+ * @protected
901
722
  */
902
- static delay(ms) {
903
- const promise = new Promise((resolve) => setTimeout(resolve, ms));
904
- return promise;
723
+ preLoadImages(options) {
724
+ // Init configuration
725
+ const config = Object.assign({ doneFunnction: undefined, elements: document, regExp: undefined }, options);
726
+ return new Promise((resolve) => {
727
+ var _a;
728
+ // Convert element(s) into array
729
+ if (!Array.isArray(config.elements)) {
730
+ // NodeList or HTMLCollection
731
+ if (Helper.isNodeList(config.elements)) {
732
+ config.elements = Array.from(config.elements);
733
+ }
734
+ // Single element
735
+ else {
736
+ config.elements = [config.elements];
737
+ }
738
+ }
739
+ // Init loading
740
+ const promises = [];
741
+ // Get all images
742
+ let images = [];
743
+ config.elements.forEach((element) => {
744
+ images = images.concat(Array.from(element.getElementsByTagName('img')));
745
+ });
746
+ // Prepare images
747
+ for (let image of images) {
748
+ const src = image.getAttribute('src');
749
+ // Check if changes are necessary and possible
750
+ if ((image.complete && image.naturalHeight !== 0) || // already laoded
751
+ !src || // Image has no src
752
+ ((_a = config.regExp) === null || _a === void 0 ? void 0 : _a.test(src)) // Test if src matches regular expression
753
+ ) {
754
+ continue;
755
+ }
756
+ // Replace lazy loading image with immediately loading image
757
+ if (image.getAttribute('loading') === 'lazy') {
758
+ const newImage = document.createElement('img');
759
+ Array.from(image.attributes).forEach((attr) => {
760
+ if (attr.name !== 'loading' && attr.name !== 'src') {
761
+ newImage.setAttribute(attr.name, attr.value);
762
+ }
763
+ });
764
+ image.replaceWith(newImage);
765
+ image = newImage;
766
+ }
767
+ // Wait for laoding
768
+ promises.push(this.loadImage(image, src));
769
+ }
770
+ // Wait until all images loaded
771
+ Promise.all(promises)
772
+ .catch((e) => console.error('Error during image loading', e))
773
+ .finally(() => __awaiter(this, void 0, void 0, function* () {
774
+ // Return result
775
+ let result;
776
+ if (config.doneFunnction) {
777
+ result = yield config.doneFunction();
778
+ }
779
+ resolve(result);
780
+ }));
781
+ });
905
782
  }
906
783
  /**
907
- * Deep freeze
908
- * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze#What_is_shallow_freeze
784
+ * Load image for image element
785
+ *
786
+ * @param imageElement
787
+ * @param src
788
+ * @protected
909
789
  */
910
- static deepFreeze(object, preparedObjects = []) {
911
- // Prevent infinit regress
912
- if (preparedObjects.includes(object)) {
913
- return object;
914
- }
915
- preparedObjects.push(object);
916
- // Retrieve the property names defined on object
917
- const propNames = Object.getOwnPropertyNames(object);
918
- // Freeze properties before freezing self
919
- for (const name of propNames) {
920
- const value = object[name];
921
- if (value && typeof value === 'object') {
922
- Helper.deepFreeze(value, preparedObjects);
790
+ loadImage(imageElement, src) {
791
+ return __awaiter(this, void 0, void 0, function* () {
792
+ src = src || imageElement.src;
793
+ return new Promise((resolve, reject) => {
794
+ imageElement.onload = () => resolve(imageElement);
795
+ imageElement.onerror = reject;
796
+ imageElement.src = src;
797
+ });
798
+ });
799
+ }
800
+ }
801
+ ImageService.ɵprov = i0.ɵɵdefineInjectable({ factory: function ImageService_Factory() { return new ImageService(); }, token: ImageService, providedIn: "root" });
802
+ ImageService.decorators = [
803
+ { type: Injectable, args: [{
804
+ providedIn: 'root',
805
+ },] }
806
+ ];
807
+
808
+ /**
809
+ * Set same height (maximum) to different elements
810
+ */
811
+ class MatchHeightDirective {
812
+ constructor(el, imageService, platformId) {
813
+ this.el = el;
814
+ this.imageService = imageService;
815
+ // Replace lazy loading images with immediate loading images
816
+ this.loadImages = true;
817
+ this.isBrowser = isPlatformBrowser(platformId);
818
+ }
819
+ onResize() {
820
+ this.matchHeight(this.el.nativeElement, this.ltMatchHeight);
821
+ }
822
+ ngOnInit() {
823
+ this.matchHeight(this.el.nativeElement, this.ltMatchHeight);
824
+ }
825
+ matchHeight(parent, target) {
826
+ return __awaiter(this, void 0, void 0, function* () {
827
+ if (!parent) {
828
+ return;
829
+ }
830
+ let classNames;
831
+ if (!Array.isArray(target)) {
832
+ classNames = [target];
833
+ }
834
+ else {
835
+ classNames = target;
836
+ }
837
+ for (const className of classNames) {
838
+ // find all child structureElements with the selected class name
839
+ const children = parent.getElementsByClassName(className);
840
+ if (!children || children.length < 2) {
841
+ continue;
842
+ }
843
+ // Load images of children
844
+ if (this.loadImages) {
845
+ yield this.imageService.preLoadImages({ elements: children });
846
+ }
847
+ // reset all children height
848
+ Array.from(children).forEach((element) => {
849
+ element.style.height = 'initial';
850
+ });
851
+ // get all the child structureElements heights
852
+ const itemHeights = Array.from(children).map((element) => {
853
+ if (element.getBoundingClientRect) {
854
+ return element.getBoundingClientRect().height;
855
+ }
856
+ return 0;
857
+ });
858
+ // find out the tallest one
859
+ const maxHeight = itemHeights.reduce((prev, curr) => (curr > prev ? curr : prev), 0);
860
+ // update the child structureElements height to the tallest one
861
+ Array.from(children).forEach((element) => (element.style.height = `${maxHeight}px`));
923
862
  }
863
+ });
864
+ }
865
+ }
866
+ MatchHeightDirective.decorators = [
867
+ { type: Directive, args: [{
868
+ selector: '[ltMatchHeight]',
869
+ },] }
870
+ ];
871
+ MatchHeightDirective.ctorParameters = () => [
872
+ { type: ElementRef },
873
+ { type: ImageService },
874
+ { type: String, decorators: [{ type: Inject, args: [PLATFORM_ID,] }] }
875
+ ];
876
+ MatchHeightDirective.propDecorators = {
877
+ ltMatchHeight: [{ type: Input }],
878
+ loadImages: [{ type: Input }],
879
+ onResize: [{ type: HostListener, args: ['window:resize',] }]
880
+ };
881
+
882
+ /**
883
+ * Reszise detection
884
+ * (inpsired by: https://stackblitz.com/edit/angular-resizable)
885
+ */
886
+ class ResizableDirective {
887
+ constructor(el) {
888
+ this.el = el;
889
+ this.resizableGrabWidth = 8;
890
+ this.resizableMinWidth = 10;
891
+ this.dragging = false;
892
+ }
893
+ ngOnInit() {
894
+ this.addListenerToDocument();
895
+ this.addListenerToElement();
896
+ }
897
+ mouseMoveG(evt) {
898
+ if (!this.dragging) {
899
+ return;
924
900
  }
925
- return Object.freeze(object);
901
+ this.newWidth(evt.clientX - this.el.nativeElement.offsetLeft);
902
+ evt.stopPropagation();
926
903
  }
927
- /**
928
- * Generates an unique identifier with 24 characters
929
- */
930
- static getUID() {
931
- let dt = new Date().getTime();
932
- const uuid = 'xxxxxxxxxxxxxxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
933
- // eslint-disable-next-line no-bitwise
934
- const r = (dt + Math.random() * 16) % 16 | 0;
935
- dt = Math.floor(dt / 16);
936
- // eslint-disable-next-line no-bitwise
937
- return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
938
- });
939
- return uuid;
904
+ newWidth(wid) {
905
+ const result = Math.max(this.resizableMinWidth, wid);
906
+ this.el.nativeElement.style.width = result + 'px';
940
907
  }
941
- /**
942
- * Parse date string to date object
943
- */
944
- static parseDate(dateString) {
945
- const reISO = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*))(?:Z|(\+|-)([\d|:]*))?$/;
946
- const reMsAjax = /^\/Date\((d|-|.*)\)[\/|\\]$/;
947
- if (typeof dateString === 'string') {
948
- const isoExec = reISO.exec(dateString);
949
- if (isoExec) {
950
- return new Date(dateString);
951
- }
952
- const msAjaxExec = reMsAjax.exec(dateString);
953
- if (msAjaxExec) {
954
- const cleaned = msAjaxExec[1].split(/[-+,.]/);
955
- return new Date(cleaned[0] ? +cleaned[0] : 0 - +cleaned[1]);
956
- }
908
+ mouseDown(evt) {
909
+ if (this.inDragRegion(evt)) {
910
+ this.dragging = true;
911
+ this.preventGlobalMouseEvents();
912
+ evt.stopPropagation();
957
913
  }
958
- return dateString;
959
914
  }
960
- /**
961
- * Get browser name and version
962
- */
963
- static getBrowser() {
964
- const userAgent = navigator.userAgent;
965
- let data = userAgent.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
966
- let version;
967
- if (/trident/i.test(data[1])) {
968
- version = /\brv[ :]+(\d+)/g.exec(userAgent) || [];
969
- return { name: 'IE', version: version[1] || '' };
970
- }
971
- if (data[1] === 'Chrome') {
972
- version = userAgent.match(/\bOPR|Edge\/(\d+)/);
973
- if (version != null) {
974
- return { name: 'Opera', version: version[1] };
975
- }
976
- }
977
- data = data[2] ? [data[1], data[2]] : [navigator.appName, navigator.appVersion, '-?'];
978
- version = userAgent.match(/version\/(\d+)/i);
979
- if (version !== null) {
980
- data.splice(1, 1, version[1]);
981
- }
982
- return {
983
- name: data[0],
984
- version: data[1],
985
- };
915
+ restoreGlobalMouseEvents() {
916
+ document.body.style['pointer-events'] = 'auto';
986
917
  }
987
- /**
988
- * Whether first parameter is a deep subset of second parameter
989
- * Hint: functions will be ignored
990
- */
991
- static isSubset(a, b) {
992
- // Equal
993
- if (a === b) {
994
- return true;
918
+ mouseUpG(evt) {
919
+ if (!this.dragging) {
920
+ return;
995
921
  }
996
- // Different type
997
- if (typeof a !== typeof b) {
998
- return false;
922
+ this.restoreGlobalMouseEvents();
923
+ this.dragging = false;
924
+ evt.stopPropagation();
925
+ }
926
+ mouseMove(evt) {
927
+ if (this.inDragRegion(evt) || this.dragging) {
928
+ this.el.nativeElement.style.cursor = 'col-resize';
999
929
  }
1000
- // Process array
1001
- if (Array.isArray(a)) {
1002
- for (const [key, value] of Object.entries(a)) {
1003
- if (Helper.isSubset(value, b[key])) {
1004
- return false;
1005
- }
1006
- }
1007
- return true;
930
+ else {
931
+ this.el.nativeElement.style.cursor = 'default';
1008
932
  }
1009
- // Process object
1010
- if (typeof a === 'object') {
1011
- const plain = JSON.parse(JSON.stringify(a));
1012
- for (const [key, value] of Object.entries(plain)) {
1013
- if (Helper.isSubset(value, b[key])) {
1014
- return false;
1015
- }
1016
- }
1017
- return true;
933
+ }
934
+ preventGlobalMouseEvents() {
935
+ document.body.style['pointer-events'] = 'none';
936
+ }
937
+ inDragRegion(evt) {
938
+ return this.el.nativeElement.clientWidth - evt.clientX + this.el.nativeElement.offsetLeft < this.resizableGrabWidth;
939
+ }
940
+ addListenerToDocument() {
941
+ document.addEventListener('mousemove', (e) => {
942
+ this.mouseMoveG(e);
943
+ }, true);
944
+ document.addEventListener('mouseup', (e) => {
945
+ this.mouseUpG(e);
946
+ }, true);
947
+ }
948
+ addListenerToElement() {
949
+ this.el.nativeElement.addEventListener('mousedown', (e) => {
950
+ this.mouseDown(e);
951
+ }, true);
952
+ this.el.nativeElement.addEventListener('mousemove', (e) => {
953
+ this.mouseMove(e);
954
+ }, true);
955
+ }
956
+ }
957
+ ResizableDirective.decorators = [
958
+ { type: Directive, args: [{
959
+ selector: '[ltResizable]',
960
+ },] }
961
+ ];
962
+ ResizableDirective.ctorParameters = () => [
963
+ { type: ElementRef }
964
+ ];
965
+ ResizableDirective.propDecorators = {
966
+ resizableGrabWidth: [{ type: Input }],
967
+ resizableMinWidth: [{ type: Input }]
968
+ };
969
+
970
+ /**
971
+ * Truncate string with ellipses
972
+ */
973
+ class EllipsesPipe {
974
+ transform(value, limit) {
975
+ if (!value) {
976
+ return null;
1018
977
  }
1019
- // Else
1020
- return true;
978
+ return value.length > limit ? value.substring(0, limit) + '...' : value;
1021
979
  }
1022
- /**
1023
- * Whether parameters are equal (have equal properties)
1024
- * Hint: functions will be ignored
1025
- */
1026
- static isEqual(a, b) {
1027
- return Helper.isSubset(a, b) && Helper.isSubset(b, a);
980
+ }
981
+ EllipsesPipe.decorators = [
982
+ { type: Pipe, args: [{
983
+ name: 'ltEllipses',
984
+ },] }
985
+ ];
986
+
987
+ /**
988
+ * Declarate html as safe
989
+ */
990
+ class SafeHtmlPipe {
991
+ constructor(sanitized) {
992
+ this.sanitized = sanitized;
993
+ }
994
+ transform(value) {
995
+ return this.sanitized.bypassSecurityTrustHtml(value);
1028
996
  }
997
+ }
998
+ SafeHtmlPipe.decorators = [
999
+ { type: Pipe, args: [{ name: 'ltSafeHtml' },] }
1000
+ ];
1001
+ SafeHtmlPipe.ctorParameters = () => [
1002
+ { type: DomSanitizer }
1003
+ ];
1004
+
1005
+ // Imported and exported elements
1006
+ const elements = [
1007
+ // Directives
1008
+ ContextMenuDirective,
1009
+ LazyLoadDirective,
1010
+ MatchHeightDirective,
1011
+ ResizableDirective,
1012
+ // Pipes
1013
+ EllipsesPipe,
1014
+ SafeHtmlPipe,
1015
+ ];
1016
+ /**
1017
+ * Base module
1018
+ */
1019
+ class BaseModule {
1029
1020
  /**
1030
- * First level difference of two objects
1031
- * Hint: functions will be ignored and when an array or an object is different the hole array or object will used as property value
1032
- *
1033
- * @returns Record<string, any> Object with properties from first object
1021
+ * Set configuration of base module in root module of the app
1034
1022
  */
1035
- static getDiff(a, b, clone = true) {
1036
- const result = {};
1037
- let firstObject = a;
1038
- if (clone) {
1039
- firstObject = JSON.parse(JSON.stringify(a));
1023
+ static forRoot(baseModuleConfig = {}) {
1024
+ // Default config
1025
+ const config = Object.assign({ apiUrl: 'localhost:3000', authGuardRedirectUrl: '/auth', logging: false, version: null, prefix: null, scrollDetectionOffset: 200, scrollOffset: 100, storageType: 'local' }, baseModuleConfig);
1026
+ // Prepare providers
1027
+ const providers = [
1028
+ {
1029
+ provide: BASE_MODULE_CONFIG,
1030
+ useValue: baseModuleConfig,
1031
+ },
1032
+ ];
1033
+ // Add apollo features if API URL is set
1034
+ if (baseModuleConfig.apiUrl) {
1035
+ providers.push({
1036
+ provide: APOLLO_OPTIONS,
1037
+ useFactory: apolloOptionsFactory,
1038
+ deps: [BASE_MODULE_CONFIG, HttpLink, AuthService],
1039
+ });
1040
1040
  }
1041
- for (const [key, value] of Object.entries(firstObject)) {
1042
- if (!Helper.isSubset(value, b[key])) {
1043
- result[key] = value;
1044
- }
1041
+ else if (config.logging) {
1042
+ console.log('apiUrl is missing, ApolloLink is not initialized');
1045
1043
  }
1046
- return result;
1044
+ // Return root module
1045
+ return {
1046
+ ngModule: BaseModule,
1047
+ providers,
1048
+ };
1047
1049
  }
1048
1050
  }
1051
+ BaseModule.decorators = [
1052
+ { type: NgModule, args: [{
1053
+ declarations: elements,
1054
+ exports: elements,
1055
+ },] }
1056
+ ];
1049
1057
 
1050
1058
  /**
1051
1059
  * Standard class
@@ -1838,7 +1846,7 @@ class GraphQLService {
1838
1846
  continue;
1839
1847
  }
1840
1848
  // Skip value if not exists
1841
- if (value === undefined && value === null) {
1849
+ if (value === undefined || value === null) {
1842
1850
  continue;
1843
1851
  }
1844
1852
  // Add current key to metadata
@@ -2153,7 +2161,7 @@ class ScrollService {
2153
2161
  scrollTo(id, options = {}) {
2154
2162
  return __awaiter(this, void 0, void 0, function* () {
2155
2163
  // Prepare config
2156
- const config = Object.assign({ checkUrl: true, loadImagesBefore: true, resetStyles: false, setOverflow: true, stopPropagation: true }, options);
2164
+ const config = Object.assign({ checkUrl: false, loadImagesBefore: true, resetStyles: false, setOverflow: true, stopPropagation: true }, options);
2157
2165
  // Check current URL
2158
2166
  if (config.checkUrl && this.router.url !== '/' && !this.router.url.startsWith('/#')) {
2159
2167
  yield this.router.navigate(['/'], { fragment: id });
@@ -2231,37 +2239,16 @@ class ScrollService {
2231
2239
  * Scroll done promise
2232
2240
  */
2233
2241
  scrollDone(options = {}) {
2234
- // Init configuration
2235
- const config = Object.assign({ doneFunction: undefined, timeout: undefined }, options);
2236
- // Return promise for scroll done
2237
- return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
2238
- // Init variables
2239
- let rejected = false;
2240
- let timeoutId;
2241
- // Set timeout handling if necessary
2242
- if (config.timeout) {
2243
- timeoutId = window.setTimeout(() => {
2244
- rejected = true;
2245
- reject(new Error('Timeout for scrollDone after:' + config.timeout));
2246
- }, config.timeout);
2247
- }
2242
+ return __awaiter(this, void 0, void 0, function* () {
2243
+ // Init configuration
2244
+ const config = Object.assign({ doneFunction: undefined }, options);
2248
2245
  // Wait for scroll done
2249
2246
  yield this.scrollDoneDetection();
2250
- // Resolve promise if not already rejected
2251
- if (!rejected) {
2252
- // Stop timeout if set
2253
- if (timeoutId) {
2254
- window.clearTimeout(timeoutId);
2255
- }
2256
- // Get result (if doneFunction is set)
2257
- let result;
2258
- if (config.doneFunction) {
2259
- result = yield config.doneFunction();
2260
- }
2261
- // Scroll done
2262
- resolve(result);
2247
+ // Get result
2248
+ if (config.doneFunction) {
2249
+ return config.doneFunction();
2263
2250
  }
2264
- }));
2251
+ });
2265
2252
  }
2266
2253
  /**
2267
2254
  * Scroll done detecton