at position ${index}`, () => {
+ expect(des[index].nativeElement.style.width).toBe(val.width);
+ expect(des[index].nativeElement.style['flex-wrap']).toBe(val.wrapStyle);
+ });
+ });
+
+ it('should check expected results for bare
without this directive', () => {
+ expect(bareElement.properties.ksWrap).toBeUndefined();
+ });
+ });
+});
diff --git a/projects/ks89/angular-modal-gallery/src/lib/directives/wrap.directive.ts b/projects/ks89/angular-modal-gallery/src/lib/directives/wrap.directive.ts
new file mode 100644
index 00000000..d7f967da
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/directives/wrap.directive.ts
@@ -0,0 +1,77 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { Directive, ElementRef, Input, OnChanges, OnInit, Renderer2 } from '@angular/core';
+
+/**
+ * Directive to change the flex-wrap css property of an element.
+ */
+@Directive({
+ selector: '[ksWrap]',
+ standalone: false
+})
+export class WrapDirective implements OnInit, OnChanges {
+ /**
+ * Boolean input that it's true to add 'flex-wrap: wrap', 'flex-wrap: nowrap' otherwise.
+ */
+ @Input()
+ wrap: boolean | undefined;
+ /**
+ * String input to force the width of the element to be able to see wrapping.
+ */
+ @Input()
+ width: string | undefined;
+
+ constructor(private renderer: Renderer2, private el: ElementRef) {}
+
+ /**
+ * Method ´ngOnInit´ to apply the style of this directive.
+ * This is an angular lifecycle hook, so its called automatically by Angular itself.
+ * In particular, it's called only one time!!!
+ */
+ ngOnInit(): void {
+ this.applyStyle();
+ }
+
+ /**
+ * Method ´ngOnChanges´ to apply the style of this directive.
+ * This is an angular lifecycle hook, so its called automatically by Angular itself.
+ * In particular, it's called when any data-bound property of a directive changes!!!
+ */
+ ngOnChanges(): void {
+ this.applyStyle();
+ }
+
+ /**
+ * Private method to change both width and flex-wrap css properties.
+ */
+ private applyStyle(): void {
+ // TODO is this right???? If wrap is false I cannot apply width and flex-wrap
+ if (!this.wrap) {
+ return;
+ }
+ this.renderer.setStyle(this.el.nativeElement, 'width', this.width);
+ this.renderer.setStyle(this.el.nativeElement, 'flex-wrap', this.wrap ? 'wrap' : 'nowrap');
+ }
+}
diff --git a/projects/ks89/angular-modal-gallery/src/lib/modal-gallery.module.ts b/projects/ks89/angular-modal-gallery/src/lib/modal-gallery.module.ts
new file mode 100644
index 00000000..c94dc5f1
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/modal-gallery.module.ts
@@ -0,0 +1,52 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { APP_INITIALIZER, NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { OverlayModule } from '@angular/cdk/overlay';
+
+import { COMPONENTS, CarouselComponent } from './components/components';
+import { PlainGalleryComponent } from './components/plain-gallery/plain-gallery.component';
+import { DIRECTIVES } from './directives/directives';
+import { AttachToOverlayService } from './components/modal-gallery/attach-to-overlay.service';
+
+/**
+ * Module to import it in the root module of your application.
+ */
+@NgModule({
+ imports: [CommonModule, OverlayModule],
+ declarations: [COMPONENTS, DIRECTIVES],
+ providers: [
+ {
+ provide: APP_INITIALIZER,
+ multi: true,
+ deps: [AttachToOverlayService],
+ useFactory: (service: AttachToOverlayService): (() => void) => {
+ return () => service.initialize();
+ }
+ }
+ ],
+ exports: [PlainGalleryComponent, CarouselComponent]
+})
+export class GalleryModule {}
diff --git a/projects/ks89/angular-modal-gallery/src/lib/model/accessibility.interface.ts b/projects/ks89/angular-modal-gallery/src/lib/model/accessibility.interface.ts
new file mode 100644
index 00000000..5193b3af
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/model/accessibility.interface.ts
@@ -0,0 +1,71 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+/**
+ * Interface `Accessibility` to config titles, alt texts, aria labels and so on
+ */
+export interface AccessibilityConfig {
+ backgroundAriaLabel: string;
+ backgroundTitle: string;
+
+ plainGalleryContentAriaLabel: string;
+ plainGalleryContentTitle: string;
+
+ modalGalleryContentAriaLabel: string;
+ modalGalleryContentTitle: string;
+
+ loadingSpinnerAriaLabel: string;
+ loadingSpinnerTitle: string;
+
+ mainContainerAriaLabel: string;
+ mainContainerTitle: string;
+ mainPrevImageAriaLabel: string;
+ mainPrevImageTitle: string;
+ mainNextImageAriaLabel: string;
+ mainNextImageTitle: string;
+
+ dotsContainerAriaLabel: string;
+ dotsContainerTitle: string;
+ dotAriaLabel: string;
+
+ previewsContainerAriaLabel: string;
+ previewsContainerTitle: string;
+ previewScrollPrevAriaLabel: string;
+ previewScrollPrevTitle: string;
+ previewScrollNextAriaLabel: string;
+ previewScrollNextTitle: string;
+
+ carouselContainerAriaLabel: string;
+ carouselContainerTitle: string;
+ carouselPrevImageAriaLabel: string;
+ carouselPrevImageTitle: string;
+ carouselNextImageAriaLabel: string;
+ carouselNextImageTitle: string;
+ carouselPreviewsContainerAriaLabel: string;
+ carouselPreviewsContainerTitle: string;
+ carouselPreviewScrollPrevAriaLabel: string;
+ carouselPreviewScrollPrevTitle: string;
+ carouselPreviewScrollNextAriaLabel: string;
+ carouselPreviewScrollNextTitle: string;
+}
diff --git a/projects/ks89/angular-modal-gallery/src/lib/model/action.enum.ts b/projects/ks89/angular-modal-gallery/src/lib/model/action.enum.ts
new file mode 100644
index 00000000..513df1a1
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/model/action.enum.ts
@@ -0,0 +1,35 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+/**
+ * Enum `Action` with a list of possible actions, based on the source of the action.
+ */
+export enum Action {
+ NORMAL, // default value
+ CLICK, // mouse click
+ KEYBOARD,
+ SWIPE,
+ LOAD,
+ AUTOPLAY
+}
diff --git a/projects/ks89/angular-modal-gallery/src/lib/model/buttons-config.interface.ts b/projects/ks89/angular-modal-gallery/src/lib/model/buttons-config.interface.ts
new file mode 100644
index 00000000..b102ab25
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/model/buttons-config.interface.ts
@@ -0,0 +1,98 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { Action } from './action.enum';
+import { InternalLibImage } from './image-internal.class';
+import { Size } from './size.interface';
+
+/**
+ * Interface `ButtonsConfig` to add buttons, show/hide their, and to add the strategy.
+ */
+export interface ButtonsConfig {
+ visible: boolean;
+ strategy: ButtonsStrategy;
+ buttons?: ButtonConfig[];
+}
+
+/**
+ * Interface `ButtonConfig` to configure a single button.
+ */
+export interface ButtonConfig {
+ className?: string;
+ size?: Size;
+ fontSize?: string;
+ type: ButtonType;
+ title?: string;
+ ariaLabel?: string;
+ extUrlInNewTab?: boolean; // to open the external url in a new tab, instead of the current one
+}
+
+/**
+ * Interface `ButtonEvent` to represent the event payload when a button is clicked.
+ */
+export interface ButtonEvent {
+ button: ButtonConfig;
+ image: InternalLibImage | null;
+ action: Action;
+ galleryId: number;
+}
+
+/**
+ * Enum `ButtonsStrategy` to configure the logic of a button.
+ */
+export enum ButtonsStrategy {
+ // don't use 0 here
+ // the first index is 1 and all of the following members are auto-incremented from that point on
+ DEFAULT = 1,
+ SIMPLE,
+ ADVANCED,
+ FULL,
+ CUSTOM
+}
+
+/**
+ * Enum `ButtonType` is the type of a button.
+ */
+export enum ButtonType {
+ // don't use 0 here
+ // the first index is 1 and all of the following members are auto-incremented from that point on
+ DELETE = 1,
+ EXTURL,
+ DOWNLOAD,
+ CLOSE,
+ CUSTOM,
+ FULLSCREEN
+}
+
+/**
+ * Array of admitted types of buttons.
+ */
+export const WHITELIST_BUTTON_TYPES: ButtonType[] = [
+ ButtonType.FULLSCREEN,
+ ButtonType.DELETE,
+ ButtonType.EXTURL,
+ ButtonType.DOWNLOAD,
+ ButtonType.CLOSE,
+ ButtonType.CUSTOM
+];
diff --git a/projects/ks89/angular-modal-gallery/src/lib/model/carousel-config.interface.ts b/projects/ks89/angular-modal-gallery/src/lib/model/carousel-config.interface.ts
new file mode 100644
index 00000000..5bf2a59b
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/model/carousel-config.interface.ts
@@ -0,0 +1,35 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+/**
+ * Interface `CarouselConfig` to change the style of the carousel and some additional features.
+ */
+export interface CarouselConfig {
+ maxWidth: string;
+ maxHeight: string;
+ showArrows: boolean;
+ objectFit: string;
+ keyboardEnable: boolean;
+ modalGalleryEnable: boolean;
+}
diff --git a/projects/ks89/angular-modal-gallery/src/lib/model/carousel-image-config.interface.ts b/projects/ks89/angular-modal-gallery/src/lib/model/carousel-image-config.interface.ts
new file mode 100644
index 00000000..1d309e0c
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/model/carousel-image-config.interface.ts
@@ -0,0 +1,33 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { Description } from './description.interface';
+
+/**
+ * Interface `CarouselImageConfig` to change current image behaviour in carousel.
+ */
+export interface CarouselImageConfig {
+ description?: Description;
+ invertSwipe?: boolean;
+}
diff --git a/projects/ks89/angular-modal-gallery/src/lib/model/carousel-preview-config.interface.ts b/projects/ks89/angular-modal-gallery/src/lib/model/carousel-preview-config.interface.ts
new file mode 100644
index 00000000..3590f497
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/model/carousel-preview-config.interface.ts
@@ -0,0 +1,49 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+/**
+ * Interface `BreakpointsConfig` to configure responsive breakpoints as numbers to express pixels
+ */
+export interface BreakpointsConfig {
+ xSmall: number;
+ small: number;
+ medium: number;
+ large: number;
+ xLarge: number;
+}
+
+/**
+ * Interface `CarouselPreviewConfig` to configure carousel's previews
+ */
+export interface CarouselPreviewConfig {
+ visible: boolean;
+ number?: number;
+ arrows?: boolean;
+ clickable?: boolean;
+ width?: string;
+ // maxHeight is called in this way because it represents the maxHeight for users, however inside the code it's the height
+ // I called maxHeight because it's handled in a complex way also with Breakpoints and I'm using Math.min like is it's the maxHeight instead of the height
+ maxHeight?: string;
+ breakpoints?: BreakpointsConfig;
+}
diff --git a/projects/ks89/angular-modal-gallery/src/lib/model/current-image-config.interface.ts b/projects/ks89/angular-modal-gallery/src/lib/model/current-image-config.interface.ts
new file mode 100644
index 00000000..1765a5a5
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/model/current-image-config.interface.ts
@@ -0,0 +1,38 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { LoadingConfig } from './loading-config.interface';
+import { Description } from './description.interface';
+
+/**
+ * Interface `CurrentImageConfig` to change current image behaviour in modal-gallery.
+ */
+export interface CurrentImageConfig {
+ navigateOnClick?: boolean;
+ downloadable?: boolean;
+ loadingConfig?: LoadingConfig;
+ description?: Description;
+ // option to invert swipe direction (because for some users it's more natural)
+ invertSwipe?: boolean;
+}
diff --git a/projects/ks89/angular-modal-gallery/src/lib/model/description.interface.ts b/projects/ks89/angular-modal-gallery/src/lib/model/description.interface.ts
new file mode 100644
index 00000000..9d730b40
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/model/description.interface.ts
@@ -0,0 +1,66 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+/**
+ * Interface `Description` to change the description, either with a full custom
+ * description or with a small and simple customization.
+ * Also, you could change margins, background style and so on.
+ */
+export interface Description {
+ strategy: DescriptionStrategy;
+ customFullDescription?: string;
+ imageText?: string;
+ numberSeparator?: string;
+ beforeTextDescription?: string;
+
+ style?: DescriptionStyle;
+}
+
+/**
+ * Enum `DescriptionStrategy` with keys and their relative key codes.
+ */
+export enum DescriptionStrategy {
+ ALWAYS_HIDDEN = 1,
+ ALWAYS_VISIBLE,
+ HIDE_IF_EMPTY
+}
+
+/**
+ * Interface to change css properties.
+ */
+export interface DescriptionStyle {
+ bgColor?: string;
+ textColor?: string;
+ width?: string;
+ height?: string;
+ position?: string;
+ top?: string;
+ bottom?: string;
+ left?: string;
+ right?: string;
+ marginTop?: string;
+ marginBottom?: string;
+ marginRight?: string;
+ marginLeft?: string;
+}
diff --git a/projects/ks89/angular-modal-gallery/src/lib/model/dots-config.interface.ts b/projects/ks89/angular-modal-gallery/src/lib/model/dots-config.interface.ts
new file mode 100644
index 00000000..6170d1de
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/model/dots-config.interface.ts
@@ -0,0 +1,30 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+/**
+ * Interface `DotsConfig` to show/hide dots.
+ */
+export interface DotsConfig {
+ visible: boolean;
+}
diff --git a/projects/ks89/angular-modal-gallery/src/lib/model/image-internal.class.ts b/projects/ks89/angular-modal-gallery/src/lib/model/image-internal.class.ts
new file mode 100644
index 00000000..ced04a11
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/model/image-internal.class.ts
@@ -0,0 +1,39 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { Image, ModalImage, PlainImage } from './image.class';
+
+/**
+ * Internal representation of an image adding other fields
+ * to the public `Image` class.
+ */
+export class InternalLibImage extends Image {
+ previouslyLoaded: boolean;
+
+ constructor(id: number, modal: ModalImage, plain?: PlainImage, previouslyLoaded: boolean = false) {
+ super(id, modal, plain);
+
+ this.previouslyLoaded = previouslyLoaded;
+ }
+}
diff --git a/projects/ks89/angular-modal-gallery/src/lib/model/image.class.ts b/projects/ks89/angular-modal-gallery/src/lib/model/image.class.ts
new file mode 100644
index 00000000..d7644315
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/model/image.class.ts
@@ -0,0 +1,109 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { Action } from './action.enum';
+import { Size } from './size.interface';
+import { SafeResourceUrl } from '@angular/platform-browser';
+
+/**
+ * Class `Image` that represents an image with both `modal` and `plain` configurations.
+ * Both image `id` and `modal` are mandatory, instead `plain` is optional.
+ */
+export class Image {
+ id: number;
+ loading: 'eager' | 'lazy';
+ fetchpriority: 'high' | 'low' | 'auto';
+ modal: ModalImage;
+ plain?: PlainImage;
+
+ constructor(id: number, modal: ModalImage, plain?: PlainImage, loading: 'eager' | 'lazy' = 'lazy', fetchpriority: 'high' | 'low' | 'auto' = 'auto') {
+ this.id = id;
+ this.modal = modal;
+ this.plain = plain;
+ this.loading = loading;
+ this.fetchpriority = fetchpriority
+ }
+}
+
+/**
+ * Interface `ImageData` to configure an image, but it isn't used directly.
+ * Please, refers to `PlainImage` or `ModalImage`.
+ */
+export interface ImageData {
+ img: string | SafeResourceUrl;
+ description?: string;
+ title?: string;
+ alt?: string;
+ ariaLabel?: string;
+ fallbackImg?: string | SafeResourceUrl;
+}
+
+/**
+ * Interface `ModalImage` to configure the modal image.
+ */
+export interface ModalImage extends ImageData {
+ extUrl?: string;
+ downloadFileName?: string;
+ sources?: Source[];
+}
+
+/**
+ * Interface `PlainImage` to configure the plain image.
+ */
+export interface PlainImage extends ImageData {
+ size?: Size;
+}
+
+/**
+ * Class `ImageEvent` that represents the event payload with the result and the triggered action.
+ * It also contains the source id of the gallery that emitted this event
+ */
+export class ImageEvent {
+ galleryId: number;
+ action: Action;
+ result: number | boolean;
+
+ constructor(galleryId: number, action: Action, result: number | boolean) {
+ this.galleryId = galleryId;
+ this.action = action;
+ this.result = result;
+ }
+}
+
+/**
+ * Class `ImageModalEvent` that represents the event payload with galleryId, result and the triggered action.
+ */
+export class ImageModalEvent extends ImageEvent {
+ constructor(galleryId: number, action: Action, result: number | boolean) {
+ super(galleryId, action, result);
+ }
+}
+
+/**
+ * Interface `Source` to configure sources of picture element.
+ */
+export interface Source {
+ srcset: string;
+ media: string;
+}
diff --git a/projects/ks89/angular-modal-gallery/src/lib/model/interaction-event.interface.ts b/projects/ks89/angular-modal-gallery/src/lib/model/interaction-event.interface.ts
new file mode 100644
index 00000000..e2fbfe7e
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/model/interaction-event.interface.ts
@@ -0,0 +1,8 @@
+import { Action } from './action.enum';
+
+export interface InteractionEvent {
+ source: string;
+ action: Action;
+ // tslint:disable-next-line:no-any
+ payload: any;
+}
diff --git a/projects/ks89/angular-modal-gallery/src/lib/model/keyboard-config.interface.ts b/projects/ks89/angular-modal-gallery/src/lib/model/keyboard-config.interface.ts
new file mode 100644
index 00000000..02cc616c
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/model/keyboard-config.interface.ts
@@ -0,0 +1,32 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+/**
+ * Interface `KeyboardConfig` to assign custom codes to ESC, RIGHT and LEFT keyboard's actions.
+ */
+export interface KeyboardConfig {
+ esc?: string;
+ right?: string;
+ left?: string;
+}
diff --git a/projects/ks89/angular-modal-gallery/src/lib/model/keyboard.enum.ts b/projects/ks89/angular-modal-gallery/src/lib/model/keyboard.enum.ts
new file mode 100644
index 00000000..8bc54142
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/model/keyboard.enum.ts
@@ -0,0 +1,44 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+/**
+ * Enum `Keyboard` with keys and their relative codes.
+ */
+import { DOWN_ARROW_CODE, ESC_CODE, LEFT_ARROW_CODE, RIGHT_ARROW_CODE, UP_ARROW_CODE } from '../utils/user-input.util';
+
+type Keyboard = Readonly<{
+ ESC: typeof ESC_CODE,
+ LEFT_ARROW: typeof LEFT_ARROW_CODE,
+ RIGHT_ARROW: typeof RIGHT_ARROW_CODE,
+ UP_ARROW: typeof UP_ARROW_CODE,
+ DOWN_ARROW: typeof DOWN_ARROW_CODE
+}>;
+
+export const Keyboard: Keyboard = {
+ ESC: ESC_CODE,
+ LEFT_ARROW: LEFT_ARROW_CODE,
+ RIGHT_ARROW: RIGHT_ARROW_CODE,
+ UP_ARROW: UP_ARROW_CODE,
+ DOWN_ARROW: DOWN_ARROW_CODE
+};
diff --git a/projects/ks89/angular-modal-gallery/src/lib/model/lib-config.interface.ts b/projects/ks89/angular-modal-gallery/src/lib/model/lib-config.interface.ts
new file mode 100644
index 00000000..0313cf41
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/model/lib-config.interface.ts
@@ -0,0 +1,68 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { SlideConfig } from './slide-config.interface';
+import { AccessibilityConfig } from './accessibility.interface';
+import { PreviewConfig } from './preview-config.interface';
+import { ButtonsConfig } from './buttons-config.interface';
+import { DotsConfig } from './dots-config.interface';
+import { PlainGalleryConfig } from './plain-gallery-config.interface';
+import { CurrentImageConfig } from './current-image-config.interface';
+import { KeyboardConfig } from './keyboard-config.interface';
+import { CarouselConfig } from './carousel-config.interface';
+import { CarouselImageConfig } from './carousel-image-config.interface';
+import { CarouselPreviewConfig } from './carousel-preview-config.interface';
+import { PlayConfig } from './play-config.interface';
+
+export interface AccessibleLibConfig {
+ accessibilityConfig?: AccessibilityConfig;
+}
+
+export interface CommonLibConfig {
+ previewConfig?: PreviewConfig;
+ dotsConfig?: DotsConfig;
+ slideConfig?: SlideConfig;
+}
+
+export interface CarouselLibConfig extends CommonLibConfig, AccessibleLibConfig {
+ carouselConfig?: CarouselConfig;
+ carouselImageConfig?: CarouselImageConfig;
+ carouselPreviewsConfig?: CarouselPreviewConfig;
+ carouselPlayConfig?: PlayConfig;
+ carouselDotsConfig?: DotsConfig;
+ carouselSlideInfinite?: boolean;
+}
+
+export interface ModalLibConfig extends CommonLibConfig, AccessibleLibConfig {
+ enableCloseOutside?: boolean;
+ keyboardConfig?: KeyboardConfig;
+ currentImageConfig?: CurrentImageConfig;
+ buttonsConfig?: ButtonsConfig;
+}
+
+export interface PlainLibConfig extends AccessibleLibConfig {
+ plainGalleryConfig?: PlainGalleryConfig;
+}
+
+export interface LibConfig extends ModalLibConfig, PlainLibConfig, CarouselLibConfig {}
diff --git a/projects/ks89/angular-modal-gallery/src/lib/model/loading-config.interface.ts b/projects/ks89/angular-modal-gallery/src/lib/model/loading-config.interface.ts
new file mode 100644
index 00000000..7c5dfe0c
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/model/loading-config.interface.ts
@@ -0,0 +1,44 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+/**
+ * Interface `LoadingConfig` to configure loading icon.
+ */
+export interface LoadingConfig {
+ enable: boolean;
+ type: LoadingType;
+}
+
+/**
+ * Enum `LoadingType` with a list of possible types.
+ */
+export enum LoadingType {
+ STANDARD = 1,
+ CIRCULAR,
+ BARS,
+ DOTS,
+ CUBE_FLIPPING,
+ CIRCLES,
+ EXPLOSING_SQUARES
+}
diff --git a/projects/ks89/angular-modal-gallery/src/lib/model/max-size.interface.ts b/projects/ks89/angular-modal-gallery/src/lib/model/max-size.interface.ts
new file mode 100644
index 00000000..ea546697
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/model/max-size.interface.ts
@@ -0,0 +1,32 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+/**
+ * Interface `MaxSize` to configure the size based on max-width and max-height.
+ * They can be pixels, percentages.
+ */
+export interface MaxSize {
+ maxWidth?: string;
+ maxHeight?: string;
+}
diff --git a/projects/ks89/angular-modal-gallery/src/lib/model/modal-gallery-config.interface.ts b/projects/ks89/angular-modal-gallery/src/lib/model/modal-gallery-config.interface.ts
new file mode 100644
index 00000000..960b93eb
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/model/modal-gallery-config.interface.ts
@@ -0,0 +1,41 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { TemplateRef } from '@angular/core';
+import { Image } from './image.class';
+import { ModalLibConfig } from './lib-config.interface';
+
+export interface ModalGalleryConfig {
+ id: number;
+ images: Image[];
+ currentImage: Image;
+ libConfig?: ModalLibConfig;
+ /**
+ * Optional template reference for the rendering of previews.
+ * Template may access following context variables:
+ * - "preview": the `Image` object of the preview
+ * - "defaultTemplate": the template used by default to render the preview (in case the need is to augment it)
+ */
+ previewsTemplate?: TemplateRef
;
+}
diff --git a/projects/ks89/angular-modal-gallery/src/lib/model/plain-gallery-config.interface.ts b/projects/ks89/angular-modal-gallery/src/lib/model/plain-gallery-config.interface.ts
new file mode 100644
index 00000000..2a004bbd
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/model/plain-gallery-config.interface.ts
@@ -0,0 +1,99 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { Size } from './size.interface';
+
+/**
+ * Interface `PlainGalleryConfig` to configure plain-gallery features.
+ */
+export interface PlainGalleryConfig {
+ strategy: PlainGalleryStrategy;
+ layout: PlainGalleryLayout;
+ advanced?: AdvancedConfig;
+}
+
+/**
+ * Interface `PlainGalleryLayout` to configure the layout. This interface isn't used directly, instead
+ * refers to either `LineLayout`, `GridLayout`.
+ */
+// tslint:disable-next-line no-empty-interface
+export interface PlainGalleryLayout {}
+
+/**
+ * Class `LineLayout` to configure a linear plain gallery.
+ */
+export class LineLayout implements PlainGalleryLayout {
+ breakConfig: BreakConfig;
+ justify: string;
+ size: Size;
+
+ constructor(size: Size, breakConfig: BreakConfig, justify: string) {
+ this.size = size;
+ this.breakConfig = breakConfig;
+ this.justify = justify;
+ }
+}
+
+/**
+ * Class `GridLayout` to configure a grid plain gallery.
+ */
+export class GridLayout implements PlainGalleryLayout {
+ breakConfig: BreakConfig;
+ size: Size;
+
+ constructor(size: Size, breakConfig: BreakConfig) {
+ this.size = size;
+ this.breakConfig = breakConfig;
+ }
+}
+
+/**
+ * Enum `PlainGalleryStrategy` to choose the behaviour of the plain gallery.
+ */
+export enum PlainGalleryStrategy {
+ // don't use 0 here
+ // the first index is 1 and all of the following members are auto-incremented from that point on
+ ROW = 1,
+ COLUMN,
+ GRID,
+ CUSTOM // full custom strategy
+}
+
+/**
+ * Interface `BreakConfig` to limit the number of items of the plain gallery or to force it to fill other lines.
+ */
+export interface BreakConfig {
+ length: number;
+ wrap: boolean;
+}
+
+/**
+ * Interface `AdvancedConfig` to use `` tags instead of ` `.
+ * It also contains a string property to customize the css background property.
+ * For more info check here https://www.w3schools.com/cssref/css3_pr_background.asp
+ */
+export interface AdvancedConfig {
+ aTags: boolean;
+ additionalBackground: string;
+}
diff --git a/projects/ks89/angular-modal-gallery/src/lib/model/play-config.interface.ts b/projects/ks89/angular-modal-gallery/src/lib/model/play-config.interface.ts
new file mode 100644
index 00000000..060efa23
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/model/play-config.interface.ts
@@ -0,0 +1,32 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+/**
+ * Interface `PlayConfig` to change the behaviour about auto-navigation for both modal gallery and carousel.
+ */
+export interface PlayConfig {
+ autoPlay: boolean;
+ interval: number;
+ pauseOnHover: boolean;
+}
diff --git a/projects/ks89/angular-modal-gallery/src/lib/model/preview-config.interface.ts b/projects/ks89/angular-modal-gallery/src/lib/model/preview-config.interface.ts
new file mode 100644
index 00000000..2a21d9e6
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/model/preview-config.interface.ts
@@ -0,0 +1,38 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { Size } from './size.interface';
+
+/**
+ * Interface `PreviewConfig` to configure previews in modal gallery.
+ */
+export interface PreviewConfig {
+ visible: boolean;
+ // by default previews are hidden on mobile
+ mobileVisible?: boolean;
+ number?: number;
+ arrows?: boolean;
+ clickable?: boolean;
+ size?: Size;
+}
diff --git a/projects/ks89/angular-modal-gallery/src/lib/model/size.interface.ts b/projects/ks89/angular-modal-gallery/src/lib/model/size.interface.ts
new file mode 100644
index 00000000..9fea6b44
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/model/size.interface.ts
@@ -0,0 +1,32 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+/**
+ * Interface `Size` to configure the size based on width and height.
+ * They can be pixels, percentages or also 'auto'.
+ */
+export interface Size {
+ width: string;
+ height: string;
+}
diff --git a/projects/ks89/angular-modal-gallery/src/lib/model/slide-config.interface.ts b/projects/ks89/angular-modal-gallery/src/lib/model/slide-config.interface.ts
new file mode 100644
index 00000000..d29edef2
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/model/slide-config.interface.ts
@@ -0,0 +1,43 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { Size } from './size.interface';
+import { PlayConfig } from './play-config.interface';
+
+/**
+ * Interface `SlideConfig` to configure sliding features of modal gallery.
+ */
+export interface SlideConfig {
+ infinite?: boolean;
+ playConfig?: PlayConfig;
+ sidePreviews?: SidePreviewsConfig;
+}
+
+/**
+ * Interface `SidePreviewsConfig` to configure sliding features of previews in modal gallery.
+ */
+export interface SidePreviewsConfig {
+ show: boolean;
+ size: Size;
+}
diff --git a/projects/ks89/angular-modal-gallery/src/lib/services/config.service.spec.ts b/projects/ks89/angular-modal-gallery/src/lib/services/config.service.spec.ts
new file mode 100644
index 00000000..09ed1cae
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/services/config.service.spec.ts
@@ -0,0 +1,838 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { inject, TestBed, waitForAsync } from '@angular/core/testing';
+
+import {
+ ConfigService,
+ DEFAULT_CAROUSEL_BREAKPOINTS,
+ DEFAULT_CAROUSEL_DESCRIPTION,
+ DEFAULT_CAROUSEL_IMAGE_CONFIG,
+ DEFAULT_CAROUSEL_PREVIEWS_CONFIG,
+ DEFAULT_CURRENT_CAROUSEL_CONFIG,
+ DEFAULT_CURRENT_CAROUSEL_PLAY,
+ DEFAULT_CURRENT_IMAGE_CONFIG,
+ DEFAULT_DESCRIPTION,
+ DEFAULT_DESCRIPTION_STYLE,
+ DEFAULT_LAYOUT,
+ DEFAULT_LOADING,
+ DEFAULT_PLAIN_CONFIG,
+ DEFAULT_PREVIEW_CONFIG,
+ DEFAULT_PREVIEW_SIZE,
+ DEFAULT_SLIDE_CONFIG
+} from './config.service';
+import { LibConfig } from '../model/lib-config.interface';
+import { ButtonsConfig, ButtonsStrategy, ButtonType } from '../model/buttons-config.interface';
+import { AccessibilityConfig } from '../model/accessibility.interface';
+import { KS_DEFAULT_ACCESSIBILITY_CONFIG } from '../components/accessibility-default';
+import { Size } from '../model/size.interface';
+import { DotsConfig } from '../model/dots-config.interface';
+import { KeyboardConfig } from '../model/keyboard-config.interface';
+import { CarouselConfig } from '../model/carousel-config.interface';
+import { PlayConfig } from '../model/play-config.interface';
+import { CarouselPreviewConfig } from '../model/carousel-preview-config.interface';
+import { CarouselImageConfig } from '../model/carousel-image-config.interface';
+import { Description, DescriptionStrategy, DescriptionStyle } from '../model/description.interface';
+import { AdvancedConfig, GridLayout, LineLayout, PlainGalleryConfig, PlainGalleryStrategy } from '../model/plain-gallery-config.interface';
+import { CurrentImageConfig } from '../model/current-image-config.interface';
+import { LoadingConfig, LoadingType } from '../model/loading-config.interface';
+import { SidePreviewsConfig, SlideConfig } from '../model/slide-config.interface';
+import { PreviewConfig } from '../model/preview-config.interface';
+
+// tslint:disable:no-shadowed-variable
+
+describe('ConfigService', () => {
+
+ beforeEach(
+ waitForAsync(() => {
+ TestBed.configureTestingModule({
+ providers: [ConfigService]
+ });
+ })
+ );
+
+ it('should instantiate service when inject service',
+ inject([ConfigService], (service: ConfigService) => {
+ expect(service instanceof ConfigService).toEqual(true);
+ })
+ );
+
+ describe('#setConfig()', () => {
+
+ describe('---YES---', () => {
+
+ describe('slideConfig', () => {
+
+ it(`should call setConfig to update the library configuration with an undefined slideConfig`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ slideConfig: undefined
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.slideConfig).toEqual(DEFAULT_SLIDE_CONFIG);
+ })
+ );
+
+ it(`should call setConfig to update the library configuration with a custom slideConfig (playConfig and sidePreviews are undefined)`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ slideConfig: {
+ infinite: true,
+ playConfig: undefined,
+ sidePreviews: undefined
+ } as SlideConfig
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.slideConfig?.infinite).toEqual(inputConfig.slideConfig?.infinite);
+ expect(result?.slideConfig?.playConfig).toEqual({autoPlay: false, interval: 5000, pauseOnHover: true} as PlayConfig);
+ expect(result?.slideConfig?.sidePreviews).toEqual({show: true, size: {width: '100px', height: 'auto'}} as SidePreviewsConfig);
+ })
+ );
+
+ it(`should call setConfig to update the library configuration with a custom slideConfig`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ slideConfig: {
+ infinite: true,
+ playConfig: {autoPlay: true, interval: 10000, pauseOnHover: false} as PlayConfig,
+ sidePreviews: {show: false, size: {width: '200px', height: '100px'}} as SidePreviewsConfig
+ } as SlideConfig
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.slideConfig?.infinite).toEqual(inputConfig.slideConfig?.infinite);
+ expect(result?.slideConfig?.playConfig?.autoPlay).toEqual(inputConfig.slideConfig?.playConfig?.autoPlay);
+ expect(result?.slideConfig?.playConfig?.interval).toEqual(inputConfig.slideConfig?.playConfig?.interval);
+ expect(result?.slideConfig?.playConfig?.pauseOnHover).toEqual(inputConfig.slideConfig?.playConfig?.pauseOnHover);
+ expect(result?.slideConfig?.sidePreviews?.show).toEqual(inputConfig.slideConfig?.sidePreviews?.show);
+ expect(result?.slideConfig?.sidePreviews?.size?.width).toEqual(inputConfig.slideConfig?.sidePreviews?.size?.width);
+ expect(result?.slideConfig?.sidePreviews?.size?.height).toEqual(inputConfig.slideConfig?.sidePreviews?.size?.height);
+ })
+ );
+ });
+
+ describe('accessibilityConfig', () => {
+ it(`should call setConfig to update the library configuration with an undefined accessibilityConfig`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ accessibilityConfig: undefined
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.accessibilityConfig).toEqual(KS_DEFAULT_ACCESSIBILITY_CONFIG);
+ })
+ );
+
+ it(`should call setConfig to update the library configuration with a custom accessibilityConfig`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ accessibilityConfig: {
+ backgroundAriaLabel: 'example-test'
+ } as AccessibilityConfig
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ if (!result || !result.accessibilityConfig || !inputConfig || !inputConfig.accessibilityConfig) {
+ throw new Error('Interrupted test, because of some undefined values');
+ }
+ expect(result.accessibilityConfig.backgroundAriaLabel).toEqual(inputConfig.accessibilityConfig.backgroundAriaLabel);
+ })
+ );
+ });
+
+ describe('previewConfig', () => {
+
+ it(`should call setConfig to update the library configuration with an undefined previewConfig`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ previewConfig: undefined
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.previewConfig).toEqual(DEFAULT_PREVIEW_CONFIG);
+ })
+ );
+
+ it(`should call setConfig to update the library configuration with a custom previewConfig`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ previewConfig: {
+ visible: false,
+ number: 2,
+ arrows: false,
+ clickable: false,
+ size: DEFAULT_PREVIEW_SIZE
+ } as PreviewConfig
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.previewConfig?.visible).toEqual(inputConfig.previewConfig?.visible);
+ expect(result?.previewConfig?.number).toEqual(inputConfig.previewConfig?.number);
+ expect(result?.previewConfig?.arrows).toEqual(inputConfig.previewConfig?.arrows);
+ expect(result?.previewConfig?.clickable).toEqual(inputConfig.previewConfig?.clickable);
+ expect(result?.previewConfig?.size).toEqual(inputConfig.previewConfig?.size);
+ })
+ );
+
+ it(`should call setConfig to update the library configuration with a custom previewConfig (number < 0)`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ previewConfig: {
+ visible: false,
+ number: -1,
+ arrows: false,
+ clickable: false,
+ size: DEFAULT_PREVIEW_SIZE
+ } as PreviewConfig
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.previewConfig?.visible).toEqual(inputConfig.previewConfig?.visible);
+ expect(result?.previewConfig?.number).toEqual(DEFAULT_PREVIEW_CONFIG.number);
+ expect(result?.previewConfig?.arrows).toEqual(inputConfig.previewConfig?.arrows);
+ expect(result?.previewConfig?.clickable).toEqual(inputConfig.previewConfig?.clickable);
+ expect(result?.previewConfig?.size).toEqual(inputConfig.previewConfig?.size);
+ })
+ );
+
+ it(`should call setConfig to update the library configuration with a custom previewConfig (number = 0)`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ previewConfig: {
+ visible: false,
+ number: 0,
+ arrows: false,
+ clickable: false,
+ size: DEFAULT_PREVIEW_SIZE
+ } as PreviewConfig
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.previewConfig?.visible).toEqual(inputConfig.previewConfig?.visible);
+ expect(result?.previewConfig?.number).toEqual(DEFAULT_PREVIEW_CONFIG.number);
+ expect(result?.previewConfig?.arrows).toEqual(inputConfig.previewConfig?.arrows);
+ expect(result?.previewConfig?.clickable).toEqual(inputConfig.previewConfig?.clickable);
+ expect(result?.previewConfig?.size).toEqual(inputConfig.previewConfig?.size);
+ })
+ );
+ });
+
+ describe('buttonsConfig', () => {
+ it(`should call setConfig to update the library configuration with an undefined buttonsConfig`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ buttonsConfig: undefined
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.buttonsConfig).toEqual({visible: true, strategy: ButtonsStrategy.DEFAULT});
+ })
+ );
+
+ it(`should call setConfig to update the library configuration with a custom buttonsConfig`,
+ inject([ConfigService], (service: ConfigService) => {
+ const customConfig: ButtonsConfig = {
+ visible: false,
+ strategy: ButtonsStrategy.CUSTOM,
+ buttons: [{
+ className: 'fake',
+ size: {height: '100px', width: '200px'} as Size,
+ fontSize: '12px',
+ type: ButtonType.CUSTOM,
+ title: 'fake title',
+ ariaLabel: 'fake aria label',
+ extUrlInNewTab: false
+ }]
+ };
+ const inputConfig: LibConfig = {
+ buttonsConfig: customConfig
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.buttonsConfig?.visible).toEqual(inputConfig.buttonsConfig?.visible);
+ expect(result?.buttonsConfig?.strategy).toEqual(inputConfig.buttonsConfig?.strategy);
+ expect(result?.buttonsConfig?.buttons).toEqual(inputConfig.buttonsConfig?.buttons);
+ })
+ );
+ });
+
+ describe('dotsConfig', () => {
+
+ it(`should call setConfig to update the library configuration with an undefined dotsConfig`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ dotsConfig: undefined
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.dotsConfig).toEqual({visible: true} as DotsConfig);
+ })
+ );
+
+ it(`should call setConfig to update the library configuration with a custom dotsConfig`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ dotsConfig: {visible: false} as DotsConfig
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.dotsConfig?.visible).toEqual(inputConfig.dotsConfig?.visible);
+ })
+ );
+ });
+
+ describe('plainGalleryConfig', () => {
+
+ it(`should call setConfig to update the library configuration with an undefined plainGalleryConfig`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ plainGalleryConfig: undefined
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.plainGalleryConfig).toEqual(DEFAULT_PLAIN_CONFIG);
+ })
+ );
+
+ it(`should call setConfig to update the library configuration with a custom plainGalleryConfig`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ plainGalleryConfig: {
+ strategy: PlainGalleryStrategy.ROW,
+ layout: DEFAULT_LAYOUT,
+ advanced: undefined
+ } as PlainGalleryConfig
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.plainGalleryConfig?.strategy).toEqual(inputConfig.plainGalleryConfig?.strategy);
+ expect(result?.plainGalleryConfig?.layout).toEqual(inputConfig.plainGalleryConfig?.layout);
+ expect(result?.plainGalleryConfig?.advanced).toEqual({aTags: false, additionalBackground: '50% 50%/cover'} as AdvancedConfig);
+ })
+ );
+
+ it(`should call setConfig to update the library configuration with a custom plainGalleryConfig with custom advanced`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ plainGalleryConfig: {
+ strategy: PlainGalleryStrategy.ROW,
+ layout: DEFAULT_LAYOUT,
+ advanced: {aTags: true, additionalBackground: '10% 10%/cover'} as AdvancedConfig
+ } as PlainGalleryConfig
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.plainGalleryConfig?.strategy).toEqual(inputConfig.plainGalleryConfig?.strategy);
+ expect(result?.plainGalleryConfig?.layout).toEqual(inputConfig.plainGalleryConfig?.layout);
+ expect(result?.plainGalleryConfig?.advanced?.aTags).toEqual(inputConfig.plainGalleryConfig?.advanced?.aTags);
+ expect(result?.plainGalleryConfig?.advanced?.additionalBackground).toEqual(inputConfig.plainGalleryConfig?.advanced?.additionalBackground);
+ })
+ );
+
+ it(`should call setConfig to update the library configuration with a custom plainGalleryConfig`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ plainGalleryConfig: {
+ strategy: PlainGalleryStrategy.GRID,
+ layout: new GridLayout(DEFAULT_PREVIEW_SIZE, {length: -1, wrap: false}),
+ advanced: {aTags: true, additionalBackground: '10% 10%/cover'} as AdvancedConfig
+ } as PlainGalleryConfig
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ // wrap is forced to true in case of GridLayout
+ expect((result?.plainGalleryConfig?.layout as GridLayout).breakConfig?.wrap).toEqual(true);
+ })
+ );
+ });
+
+ describe('currentImageConfig', () => {
+ it(`should call setConfig to update the library configuration with an undefined currentImageConfig`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ currentImageConfig: undefined
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.currentImageConfig).toEqual(DEFAULT_CURRENT_IMAGE_CONFIG);
+ })
+ );
+
+ it(`should call setConfig to update the library configuration with a custom currentImageConfig (loadingConfig and description are undefined)`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ currentImageConfig: {
+ navigateOnClick: false,
+ loadingConfig: undefined,
+ description: undefined,
+ downloadable: true,
+ invertSwipe: true
+ } as CurrentImageConfig
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.currentImageConfig?.navigateOnClick).toEqual(inputConfig.currentImageConfig?.navigateOnClick);
+ expect(result?.currentImageConfig?.downloadable).toEqual(inputConfig.currentImageConfig?.downloadable);
+ expect(result?.currentImageConfig?.invertSwipe).toEqual(inputConfig.currentImageConfig?.invertSwipe);
+ expect(result?.currentImageConfig?.loadingConfig).toEqual(DEFAULT_LOADING);
+ expect(result?.currentImageConfig?.description).toEqual(DEFAULT_DESCRIPTION);
+ })
+ );
+
+ it(`should call setConfig to update the library configuration with a custom currentImageConfig (custom loadingConfig)`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ currentImageConfig: {
+ navigateOnClick: false,
+ loadingConfig: {enable: false, type: LoadingType.CIRCLES} as LoadingConfig,
+ description: undefined,
+ downloadable: true,
+ invertSwipe: true
+ } as CurrentImageConfig
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.currentImageConfig?.navigateOnClick).toEqual(inputConfig.currentImageConfig?.navigateOnClick);
+ expect(result?.currentImageConfig?.downloadable).toEqual(inputConfig.currentImageConfig?.downloadable);
+ expect(result?.currentImageConfig?.invertSwipe).toEqual(inputConfig.currentImageConfig?.invertSwipe);
+ expect(result?.currentImageConfig?.loadingConfig?.enable).toEqual(inputConfig.currentImageConfig?.loadingConfig?.enable);
+ expect(result?.currentImageConfig?.loadingConfig?.type).toEqual(inputConfig.currentImageConfig?.loadingConfig?.type);
+ expect(result?.currentImageConfig?.description).toEqual(DEFAULT_DESCRIPTION);
+ })
+ );
+
+ it(`should call setConfig to update the library configuration with a custom currentImageConfig (custom description)`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ currentImageConfig: {
+ navigateOnClick: false,
+ loadingConfig: undefined,
+ description: {
+ strategy: DescriptionStrategy.HIDE_IF_EMPTY,
+ imageText: 'Img ',
+ numberSeparator: ' of ',
+ beforeTextDescription: ' * ',
+ style: DEFAULT_DESCRIPTION_STYLE
+ } as Description,
+ downloadable: true,
+ invertSwipe: true
+ } as CurrentImageConfig
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.currentImageConfig?.navigateOnClick).toEqual(inputConfig.currentImageConfig?.navigateOnClick);
+ expect(result?.currentImageConfig?.downloadable).toEqual(inputConfig.currentImageConfig?.downloadable);
+ expect(result?.currentImageConfig?.invertSwipe).toEqual(inputConfig.currentImageConfig?.invertSwipe);
+ expect(result?.currentImageConfig?.loadingConfig).toEqual(DEFAULT_LOADING);
+ expect(result?.currentImageConfig?.description?.strategy).toEqual(inputConfig.currentImageConfig?.description?.strategy);
+ expect(result?.currentImageConfig?.description?.imageText).toEqual(inputConfig.currentImageConfig?.description?.imageText);
+ expect(result?.currentImageConfig?.description?.numberSeparator).toEqual(inputConfig.currentImageConfig?.description?.numberSeparator);
+ expect(result?.currentImageConfig?.description?.beforeTextDescription).toEqual(inputConfig.currentImageConfig?.description?.beforeTextDescription);
+ expect(result?.currentImageConfig?.description?.style).toEqual(inputConfig.currentImageConfig?.description?.style);
+ })
+ );
+
+ it(`should call setConfig to update the library configuration with a custom currentImageConfig (custom description with style undefined)`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ currentImageConfig: {
+ navigateOnClick: false,
+ loadingConfig: undefined,
+ description: {
+ strategy: DescriptionStrategy.ALWAYS_VISIBLE,
+ imageText: 'Img ',
+ numberSeparator: ' of ',
+ beforeTextDescription: ' * ',
+ style: undefined
+ } as Description,
+ downloadable: true,
+ invertSwipe: true
+ } as CurrentImageConfig
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.currentImageConfig?.navigateOnClick).toEqual(inputConfig.currentImageConfig?.navigateOnClick);
+ expect(result?.currentImageConfig?.downloadable).toEqual(inputConfig.currentImageConfig?.downloadable);
+ expect(result?.currentImageConfig?.invertSwipe).toEqual(inputConfig.currentImageConfig?.invertSwipe);
+ expect(result?.currentImageConfig?.loadingConfig).toEqual(DEFAULT_LOADING);
+ expect(result?.currentImageConfig?.description?.strategy).toEqual(inputConfig.currentImageConfig?.description?.strategy);
+ expect(result?.currentImageConfig?.description?.imageText).toEqual(inputConfig.currentImageConfig?.description?.imageText);
+ expect(result?.currentImageConfig?.description?.numberSeparator).toEqual(inputConfig.currentImageConfig?.description?.numberSeparator);
+ expect(result?.currentImageConfig?.description?.beforeTextDescription).toEqual(inputConfig.currentImageConfig?.description?.beforeTextDescription);
+ expect(result?.currentImageConfig?.description?.style).toEqual(DEFAULT_DESCRIPTION_STYLE);
+ })
+ );
+ });
+
+ describe('keyboardConfig', () => {
+
+ it(`should call setConfig to update the library configuration with an undefined keyboardConfig`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ keyboardConfig: undefined
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.keyboardConfig).toEqual(undefined);
+ })
+ );
+
+ it(`should call setConfig to update the library configuration with a custom keyboardConfig`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ keyboardConfig: {
+ esc: 'KeyQ',
+ right: 'KeyW',
+ left: 'KeyE'
+ } as KeyboardConfig
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.keyboardConfig).toEqual(inputConfig.keyboardConfig);
+ })
+ );
+ });
+
+ describe('carouselImageConfig', () => {
+
+ it(`should call setConfig to update the library configuration with an undefined carouselImageConfig`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ carouselImageConfig: undefined
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.carouselImageConfig).toEqual(DEFAULT_CAROUSEL_IMAGE_CONFIG);
+ })
+ );
+
+ it(`should call setConfig to update the library configuration with a custom carouselImageConfig (description undefined)`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ carouselImageConfig: {
+ description: undefined,
+ invertSwipe: true
+ } as CarouselImageConfig
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.carouselImageConfig?.invertSwipe).toEqual(inputConfig.carouselImageConfig?.invertSwipe);
+ expect(result?.carouselImageConfig?.description).toEqual(DEFAULT_CAROUSEL_DESCRIPTION);
+ })
+ );
+
+ it(`should call setConfig to update the library configuration with a custom carouselImageConfig (description.style undefined)`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ carouselImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.ALWAYS_HIDDEN,
+ imageText: 'Img ',
+ numberSeparator: ' of ',
+ beforeTextDescription: ' * ',
+ style: undefined
+ },
+ invertSwipe: true
+ } as CarouselImageConfig
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.carouselImageConfig?.invertSwipe).toEqual(inputConfig.carouselImageConfig?.invertSwipe);
+ expect(result?.carouselImageConfig?.description?.strategy).toEqual(inputConfig.carouselImageConfig?.description?.strategy);
+ expect(result?.carouselImageConfig?.description?.imageText).toEqual(inputConfig.carouselImageConfig?.description?.imageText);
+ expect(result?.carouselImageConfig?.description?.numberSeparator).toEqual(inputConfig.carouselImageConfig?.description?.numberSeparator);
+ expect(result?.carouselImageConfig?.description?.beforeTextDescription).toEqual(inputConfig.carouselImageConfig?.description?.beforeTextDescription);
+ expect(result?.carouselImageConfig?.description?.style).toEqual(DEFAULT_DESCRIPTION_STYLE);
+ })
+ );
+
+ it(`should call setConfig to update the library configuration with a custom carouselImageConfig (custom description.style)`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ carouselImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.ALWAYS_HIDDEN,
+ imageText: 'Img ',
+ numberSeparator: ' of ',
+ beforeTextDescription: ' * ',
+ style: {
+ bgColor: 'rgba(200, 0, 120, .5)',
+ textColor: 'black',
+ marginTop: '5px',
+ marginBottom: '10px',
+ marginLeft: '10px',
+ marginRight: '10px'
+ } as DescriptionStyle
+ },
+ invertSwipe: true
+ } as CarouselImageConfig
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.carouselImageConfig?.invertSwipe).toEqual(inputConfig.carouselImageConfig?.invertSwipe);
+ expect(result?.carouselImageConfig?.description?.strategy).toEqual(inputConfig.carouselImageConfig?.description?.strategy);
+ expect(result?.carouselImageConfig?.description?.imageText).toEqual(inputConfig.carouselImageConfig?.description?.imageText);
+ expect(result?.carouselImageConfig?.description?.numberSeparator).toEqual(inputConfig.carouselImageConfig?.description?.numberSeparator);
+ expect(result?.carouselImageConfig?.description?.beforeTextDescription).toEqual(inputConfig.carouselImageConfig?.description?.beforeTextDescription);
+ expect(result?.carouselImageConfig?.description?.style?.bgColor).toEqual(inputConfig.carouselImageConfig?.description?.style?.bgColor);
+ expect(result?.carouselImageConfig?.description?.style?.textColor).toEqual(inputConfig.carouselImageConfig?.description?.style?.textColor);
+ expect(result?.carouselImageConfig?.description?.style?.marginTop).toEqual(inputConfig.carouselImageConfig?.description?.style?.marginTop);
+ expect(result?.carouselImageConfig?.description?.style?.marginBottom).toEqual(inputConfig.carouselImageConfig?.description?.style?.marginBottom);
+ expect(result?.carouselImageConfig?.description?.style?.marginLeft).toEqual(inputConfig.carouselImageConfig?.description?.style?.marginLeft);
+ expect(result?.carouselImageConfig?.description?.style?.marginRight).toEqual(inputConfig.carouselImageConfig?.description?.style?.marginRight);
+ })
+ );
+ });
+
+ describe('carouselConfig', () => {
+
+ it(`should call setConfig to update the library configuration with an undefined carouselConfig`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ carouselConfig: undefined
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.carouselConfig).toEqual(DEFAULT_CURRENT_CAROUSEL_CONFIG);
+ })
+ );
+
+ it(`should call setConfig to update the library configuration with a custom carouselConfig`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ carouselConfig: {
+ maxWidth: '80%',
+ maxHeight: '200px',
+ showArrows: false,
+ objectFit: 'cover',
+ keyboardEnable: false,
+ modalGalleryEnable: false
+ } as CarouselConfig
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.carouselConfig).toEqual(inputConfig.carouselConfig);
+ })
+ );
+ });
+
+ describe('carouselPlayConfig', () => {
+
+ it(`should call setConfig to update the library configuration with an undefined carouselPlayConfig`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ carouselPlayConfig: undefined
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.carouselPlayConfig).toEqual(DEFAULT_CURRENT_CAROUSEL_PLAY);
+ })
+ );
+
+ it(`should call setConfig to update the library configuration with a custom carouselPlayConfig`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ carouselPlayConfig: {
+ autoPlay: false,
+ interval: 1000,
+ pauseOnHover: false
+ } as PlayConfig
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.carouselPlayConfig).toEqual(inputConfig.carouselPlayConfig);
+ })
+ );
+ });
+
+ describe('carouselPreviewsConfig', () => {
+
+ it(`should call setConfig to update the library configuration with an undefined carouselPreviewsConfig`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ carouselPreviewsConfig: undefined
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.carouselPreviewsConfig).toEqual(DEFAULT_CAROUSEL_PREVIEWS_CONFIG);
+ })
+ );
+
+ it(`should call setConfig to update the library configuration with an undefined carouselPreviewsConfig without 'breakpoints'`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 4,
+ arrows: true,
+ clickable: true,
+ width: 100 / 4 + '%',
+ maxHeight: '200px'
+ } as CarouselPreviewConfig
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.carouselPreviewsConfig).toEqual(DEFAULT_CAROUSEL_PREVIEWS_CONFIG);
+ })
+ );
+
+ [0, -1].forEach((item: number, index: number) => {
+ it(`should call setConfig to update the library configuration with a custom carouselPreviewsConfig with invalid 'number'. Test i = ${index}`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ carouselPreviewsConfig: {
+ visible: false,
+ number: item,
+ arrows: false,
+ clickable: false,
+ width: 100 / 2 + '%',
+ maxHeight: '100px',
+ breakpoints: DEFAULT_CAROUSEL_BREAKPOINTS
+ } as CarouselPreviewConfig
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ const numberVal: number = DEFAULT_CAROUSEL_PREVIEWS_CONFIG.number as number;
+ const widthVal: number = 100 / numberVal;
+ expect(result?.carouselPreviewsConfig?.visible).toEqual(inputConfig.carouselPreviewsConfig?.visible);
+ expect(result?.carouselPreviewsConfig?.number).toEqual(numberVal);
+ expect(result?.carouselPreviewsConfig?.arrows).toEqual(inputConfig.carouselPreviewsConfig?.arrows);
+ expect(result?.carouselPreviewsConfig?.clickable).toEqual(inputConfig.carouselPreviewsConfig?.clickable);
+ expect(result?.carouselPreviewsConfig?.width).toEqual(`${widthVal}%`);
+ expect(result?.carouselPreviewsConfig?.maxHeight).toEqual(inputConfig.carouselPreviewsConfig?.maxHeight);
+ expect(result?.carouselPreviewsConfig?.breakpoints).toEqual(inputConfig.carouselPreviewsConfig?.breakpoints);
+ })
+ );
+ });
+ });
+
+ describe('carouselDotsConfig', () => {
+
+ it(`should call setConfig to update the library configuration with an undefined carouselDotsConfig`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ carouselDotsConfig: undefined
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.carouselDotsConfig).toEqual({visible: true} as DotsConfig);
+ })
+ );
+
+ it(`should call setConfig to update the library configuration with a custom carouselDotsConfig`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ carouselDotsConfig: {visible: false} as DotsConfig
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.carouselDotsConfig).toEqual(inputConfig.carouselDotsConfig);
+ })
+ );
+ });
+
+ describe('enableCloseOutside', () => {
+
+ it(`should call setConfig to update the library configuration with an undefined enableCloseOutside`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ enableCloseOutside: undefined
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.enableCloseOutside).toEqual(true);
+ })
+ );
+
+ it(`should call setConfig to update the library configuration with a custom enableCloseOutside`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ enableCloseOutside: false
+ };
+ service.setConfig(1, inputConfig);
+ const result: LibConfig | undefined = service.getConfig(1);
+ expect(result?.enableCloseOutside).toEqual(inputConfig.enableCloseOutside);
+ })
+ );
+ });
+
+ it(`should call setConfig with an undefined configuration object.`,
+ inject([ConfigService], (service: ConfigService) => {
+ service.setConfig(1, undefined);
+ const result: LibConfig | undefined = service.getConfig(1);
+ // console.log('result', result);
+ })
+ );
+ });
+
+ describe('---NO---', () => {
+ describe('carouselPlayConfig', () => {
+ it(`should throw an error if carouselPlayConfig.interval is < 0`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ carouselPlayConfig: {
+ autoPlay: false,
+ interval: -1,
+ pauseOnHover: false
+ } as PlayConfig
+ };
+ expect(() => service.setConfig(1, inputConfig)).toThrowError(`Carousel's interval must be a number >= 0`);
+ })
+ );
+ });
+
+ describe('plainGalleryConfig', () => {
+ [PlainGalleryStrategy.GRID, PlainGalleryStrategy.CUSTOM].forEach((item: PlainGalleryStrategy) => {
+ it(`should throw an error because 'LineLayout requires either ROW or COLUMN strategy'`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ plainGalleryConfig: {
+ strategy: item,
+ layout: new LineLayout(DEFAULT_PREVIEW_SIZE, {length: -1, wrap: false}, 'flex-start'),
+ advanced: {aTags: true, additionalBackground: '10% 10%/cover'} as AdvancedConfig
+ } as PlainGalleryConfig
+ };
+ expect(() => service.setConfig(1, inputConfig)).toThrowError('LineLayout requires either ROW or COLUMN strategy');
+ })
+ );
+ });
+
+ it(`should throw an error because 'GridLayout requires GRID strategy'`,
+ inject([ConfigService], (service: ConfigService) => {
+ const inputConfig: LibConfig = {
+ plainGalleryConfig: {
+ strategy: PlainGalleryStrategy.ROW,
+ layout: new GridLayout(DEFAULT_PREVIEW_SIZE, {length: -1, wrap: false}),
+ advanced: {aTags: true, additionalBackground: '10% 10%/cover'} as AdvancedConfig
+ } as PlainGalleryConfig
+ };
+ expect(() => service.setConfig(1, inputConfig)).toThrowError('GridLayout requires GRID strategy');
+ })
+ );
+ });
+ });
+ });
+});
diff --git a/projects/ks89/angular-modal-gallery/src/lib/services/config.service.ts b/projects/ks89/angular-modal-gallery/src/lib/services/config.service.ts
new file mode 100644
index 00000000..6f6ec7ed
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/services/config.service.ts
@@ -0,0 +1,388 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { Injectable } from '@angular/core';
+import { SidePreviewsConfig, SlideConfig } from '../model/slide-config.interface';
+import { PlayConfig } from '../model/play-config.interface';
+import { KS_DEFAULT_ACCESSIBILITY_CONFIG } from '../components/accessibility-default';
+import { PreviewConfig } from '../model/preview-config.interface';
+import { Size } from '../model/size.interface';
+import { ButtonsConfig, ButtonsStrategy } from '../model/buttons-config.interface';
+import { DotsConfig } from '../model/dots-config.interface';
+import { GridLayout, LineLayout, PlainGalleryConfig, PlainGalleryStrategy } from '../model/plain-gallery-config.interface';
+import { CurrentImageConfig } from '../model/current-image-config.interface';
+import { LoadingConfig, LoadingType } from '../model/loading-config.interface';
+import { Description, DescriptionStrategy, DescriptionStyle } from '../model/description.interface';
+import { CarouselConfig } from '../model/carousel-config.interface';
+import { CarouselImageConfig } from '../model/carousel-image-config.interface';
+import { BreakpointsConfig, CarouselPreviewConfig } from '../model/carousel-preview-config.interface';
+import { LibConfig } from '../model/lib-config.interface';
+
+export const DEFAULT_PREVIEW_SIZE: Size = { height: '50px', width: 'auto' };
+export const DEFAULT_LAYOUT: LineLayout = new LineLayout(DEFAULT_PREVIEW_SIZE, { length: -1, wrap: false }, 'flex-start');
+export const DEFAULT_PLAIN_CONFIG: PlainGalleryConfig = {
+ strategy: PlainGalleryStrategy.ROW,
+ layout: DEFAULT_LAYOUT,
+ advanced: { aTags: false, additionalBackground: '50% 50%/cover' }
+};
+export const DEFAULT_LOADING: LoadingConfig = { enable: true, type: LoadingType.STANDARD };
+export const DEFAULT_DESCRIPTION_STYLE: DescriptionStyle = {
+ bgColor: 'rgba(0, 0, 0, .5)',
+ textColor: 'white',
+ marginTop: '0px',
+ marginBottom: '0px',
+ marginLeft: '0px',
+ marginRight: '0px'
+};
+export const DEFAULT_DESCRIPTION: Description = {
+ strategy: DescriptionStrategy.ALWAYS_VISIBLE,
+ imageText: 'Image ',
+ numberSeparator: '/',
+ beforeTextDescription: ' - ',
+ style: DEFAULT_DESCRIPTION_STYLE
+};
+export const DEFAULT_CAROUSEL_DESCRIPTION: Description = {
+ strategy: DescriptionStrategy.ALWAYS_HIDDEN,
+ imageText: 'Image ',
+ numberSeparator: '/',
+ beforeTextDescription: ' - ',
+ style: DEFAULT_DESCRIPTION_STYLE
+};
+export const DEFAULT_CURRENT_IMAGE_CONFIG: CurrentImageConfig = {
+ navigateOnClick: true,
+ loadingConfig: DEFAULT_LOADING,
+ description: DEFAULT_DESCRIPTION,
+ downloadable: false,
+ invertSwipe: false
+};
+export const DEFAULT_CAROUSEL_IMAGE_CONFIG: CarouselImageConfig = {
+ description: DEFAULT_CAROUSEL_DESCRIPTION,
+ invertSwipe: false
+};
+export const DEFAULT_CURRENT_CAROUSEL_CONFIG: CarouselConfig = {
+ maxWidth: '100%',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: false
+};
+export const DEFAULT_CURRENT_CAROUSEL_PLAY: PlayConfig = {
+ autoPlay: true,
+ interval: 5000,
+ pauseOnHover: true
+};
+
+export const DEFAULT_CAROUSEL_BREAKPOINTS: BreakpointsConfig = { xSmall: 100, small: 100, medium: 150, large: 200, xLarge: 200 };
+export const DEFAULT_CAROUSEL_PREVIEWS_CONFIG: CarouselPreviewConfig = {
+ visible: true,
+ number: 4,
+ arrows: true,
+ clickable: true,
+ width: 100 / 4 + '%',
+ maxHeight: '200px',
+ breakpoints: DEFAULT_CAROUSEL_BREAKPOINTS
+};
+export const DEFAULT_SLIDE_CONFIG: SlideConfig = {
+ infinite: false,
+ playConfig: { autoPlay: false, interval: 5000, pauseOnHover: true } as PlayConfig,
+ sidePreviews: { show: true, size: { width: '100px', height: 'auto' } } as SidePreviewsConfig
+};
+export const DEFAULT_PREVIEW_CONFIG: PreviewConfig = {
+ visible: true,
+ number: 3,
+ arrows: true,
+ clickable: true,
+ size: DEFAULT_PREVIEW_SIZE
+};
+
+const DEFAULT_CONFIG: LibConfig = Object.freeze({
+ slideConfig: DEFAULT_SLIDE_CONFIG,
+ accessibilityConfig: KS_DEFAULT_ACCESSIBILITY_CONFIG,
+ previewConfig: DEFAULT_PREVIEW_CONFIG,
+ buttonsConfig: { visible: true, strategy: ButtonsStrategy.DEFAULT } as ButtonsConfig,
+ dotsConfig: { visible: true } as DotsConfig,
+ plainGalleryConfig: DEFAULT_PLAIN_CONFIG,
+ currentImageConfig: DEFAULT_CURRENT_IMAGE_CONFIG,
+ keyboardConfig: undefined, // by default nothing, because the library uses default buttons automatically
+ carouselConfig: DEFAULT_CURRENT_CAROUSEL_CONFIG,
+ carouselImageConfig: DEFAULT_CAROUSEL_IMAGE_CONFIG,
+ carouselPreviewsConfig: DEFAULT_CAROUSEL_PREVIEWS_CONFIG,
+ carouselPlayConfig: DEFAULT_CURRENT_CAROUSEL_PLAY,
+ carouselDotsConfig: { visible: true } as DotsConfig,
+ carouselSlideInfinite: true,
+ enableCloseOutside: true
+});
+
+/**
+ * Service to handle library configuration in a unique place
+ */
+@Injectable({ providedIn: 'root' })
+export class ConfigService {
+ configMap: Map = new Map();
+
+ getConfig(id: number): LibConfig | undefined {
+ this.initIfNotExists(id);
+ return this.configMap.get(id);
+ }
+
+ setConfig(id: number, obj: LibConfig | undefined): void {
+ this.initIfNotExists(id);
+ if (!obj) {
+ return;
+ }
+
+ if (
+ !DEFAULT_CONFIG ||
+ !DEFAULT_CONFIG.slideConfig ||
+ !DEFAULT_CONFIG.slideConfig.sidePreviews ||
+ !DEFAULT_CONFIG.previewConfig ||
+ !DEFAULT_CONFIG.previewConfig.size ||
+ !DEFAULT_CONFIG.previewConfig.number ||
+ !DEFAULT_CONFIG.plainGalleryConfig ||
+ !DEFAULT_CONFIG.currentImageConfig ||
+ !DEFAULT_CONFIG.currentImageConfig ||
+ !DEFAULT_CONFIG.currentImageConfig.description ||
+ !DEFAULT_CONFIG.carouselImageConfig ||
+ !DEFAULT_CONFIG.carouselImageConfig.description ||
+ !DEFAULT_CONFIG.carouselPreviewsConfig ||
+ !DEFAULT_CONFIG.carouselPreviewsConfig.breakpoints ||
+ !DEFAULT_CAROUSEL_PREVIEWS_CONFIG.number
+ ) {
+ throw new Error('Internal library error - DEFAULT_CONFIG must be fully initialized!!!');
+ }
+
+ const newConfig: LibConfig = Object.assign({}, this.configMap.get(id));
+ if (obj.slideConfig) {
+ let playConfig;
+ let sidePreviews;
+ let size;
+ if (obj.slideConfig.playConfig) {
+ playConfig = Object.assign({}, DEFAULT_CONFIG.slideConfig.playConfig, obj.slideConfig.playConfig);
+ } else {
+ playConfig = DEFAULT_CONFIG.slideConfig.playConfig;
+ }
+ if (obj.slideConfig.sidePreviews) {
+ if (obj.slideConfig.sidePreviews.size) {
+ size = Object.assign({}, DEFAULT_CONFIG.slideConfig.sidePreviews.size, obj.slideConfig.sidePreviews.size);
+ } else {
+ size = DEFAULT_CONFIG.slideConfig.sidePreviews.size;
+ }
+ sidePreviews = Object.assign({}, DEFAULT_CONFIG.slideConfig.sidePreviews, obj.slideConfig.sidePreviews);
+ } else {
+ sidePreviews = DEFAULT_CONFIG.slideConfig.sidePreviews;
+ size = DEFAULT_CONFIG.slideConfig.sidePreviews.size;
+ }
+ const newSlideConfig: SlideConfig = Object.assign({}, DEFAULT_CONFIG.slideConfig, obj.slideConfig);
+ newSlideConfig.playConfig = playConfig;
+ newSlideConfig.sidePreviews = sidePreviews;
+ newSlideConfig.sidePreviews.size = size;
+ newConfig.slideConfig = newSlideConfig;
+ }
+ if (obj.accessibilityConfig) {
+ newConfig.accessibilityConfig = Object.assign({}, DEFAULT_CONFIG.accessibilityConfig, obj.accessibilityConfig);
+ }
+ if (obj.previewConfig) {
+ let size: Size;
+ let num: number;
+ if (obj.previewConfig.size) {
+ size = Object.assign({}, DEFAULT_CONFIG.previewConfig.size, obj.previewConfig.size);
+ } else {
+ size = DEFAULT_CONFIG.previewConfig.size;
+ }
+ if (obj.previewConfig.number) {
+ if (obj.previewConfig.number <= 0) {
+ // if number is <= 0 reset to default
+ num = DEFAULT_CONFIG.previewConfig.number;
+ } else {
+ num = obj.previewConfig.number;
+ }
+ } else {
+ num = DEFAULT_CONFIG.previewConfig.number;
+ }
+ const newPreviewConfig: PreviewConfig = Object.assign({}, DEFAULT_CONFIG.previewConfig, obj.previewConfig);
+ newPreviewConfig.size = size;
+ newPreviewConfig.number = num;
+ newConfig.previewConfig = newPreviewConfig;
+ }
+ if (obj.buttonsConfig) {
+ newConfig.buttonsConfig = Object.assign({}, DEFAULT_CONFIG.buttonsConfig, obj.buttonsConfig);
+ }
+ if (obj.dotsConfig) {
+ newConfig.dotsConfig = Object.assign({}, DEFAULT_CONFIG.dotsConfig, obj.dotsConfig);
+ }
+ if (obj.plainGalleryConfig) {
+ let advanced;
+ let layout;
+ if (obj.plainGalleryConfig.advanced) {
+ advanced = Object.assign({}, DEFAULT_CONFIG.plainGalleryConfig.advanced, obj.plainGalleryConfig.advanced);
+ } else {
+ advanced = DEFAULT_CONFIG.plainGalleryConfig.advanced;
+ }
+ if (obj.plainGalleryConfig.layout) {
+ // it isn't mandatory to use assign, because obj.plainGalleryConfig.layout is an instance of class (LineaLayout, GridLayout)
+ layout = obj.plainGalleryConfig.layout;
+ } else {
+ layout = DEFAULT_CONFIG.plainGalleryConfig.layout;
+ }
+ const newPlainGalleryConfig: PlainGalleryConfig = Object.assign({}, DEFAULT_CONFIG.plainGalleryConfig, obj.plainGalleryConfig);
+ newPlainGalleryConfig.layout = layout;
+ newPlainGalleryConfig.advanced = advanced;
+ newConfig.plainGalleryConfig = initPlainGalleryConfig(newPlainGalleryConfig);
+ }
+ if (obj.currentImageConfig) {
+ let loading;
+ let description;
+ let descriptionStyle;
+ if (obj.currentImageConfig.loadingConfig) {
+ loading = Object.assign({}, DEFAULT_CONFIG.currentImageConfig.loadingConfig, obj.currentImageConfig.loadingConfig);
+ } else {
+ loading = DEFAULT_CONFIG.currentImageConfig.loadingConfig;
+ }
+ if (obj.currentImageConfig.description) {
+ description = Object.assign({}, DEFAULT_CONFIG.currentImageConfig.description, obj.currentImageConfig.description);
+ if (obj.currentImageConfig.description.style) {
+ descriptionStyle = Object.assign({}, DEFAULT_CONFIG.currentImageConfig.description.style, obj.currentImageConfig.description.style);
+ } else {
+ descriptionStyle = DEFAULT_CONFIG.currentImageConfig.description.style;
+ }
+ } else {
+ description = DEFAULT_CONFIG.currentImageConfig.description;
+ descriptionStyle = DEFAULT_CONFIG.currentImageConfig.description.style;
+ }
+ const newCurrentImageConfig: CurrentImageConfig = Object.assign({}, DEFAULT_CONFIG.currentImageConfig, obj.currentImageConfig);
+ newCurrentImageConfig.loadingConfig = loading;
+ newCurrentImageConfig.description = description;
+ newCurrentImageConfig.description.style = descriptionStyle;
+ newConfig.currentImageConfig = newCurrentImageConfig;
+ }
+ if (obj.keyboardConfig) {
+ newConfig.keyboardConfig = Object.assign({}, DEFAULT_CONFIG.keyboardConfig, obj.keyboardConfig);
+ }
+
+ // carousel
+ if (obj.carouselConfig) {
+ newConfig.carouselConfig = Object.assign({}, DEFAULT_CONFIG.carouselConfig, obj.carouselConfig);
+ }
+ if (obj.carouselImageConfig) {
+ let description;
+ let descriptionStyle;
+ if (obj.carouselImageConfig.description) {
+ description = Object.assign({}, DEFAULT_CONFIG.carouselImageConfig.description, obj.carouselImageConfig.description);
+ if (obj.carouselImageConfig.description.style) {
+ descriptionStyle = Object.assign({}, DEFAULT_CONFIG.carouselImageConfig.description.style, obj.carouselImageConfig.description.style);
+ } else {
+ descriptionStyle = DEFAULT_CONFIG.carouselImageConfig.description.style;
+ }
+ } else {
+ description = DEFAULT_CONFIG.carouselImageConfig.description;
+ descriptionStyle = DEFAULT_CONFIG.carouselImageConfig.description.style;
+ }
+ const newCarouselImageConfig: CarouselImageConfig = Object.assign({}, DEFAULT_CONFIG.carouselImageConfig, obj.carouselImageConfig);
+ newCarouselImageConfig.description = description;
+ newCarouselImageConfig.description.style = descriptionStyle;
+ newConfig.carouselImageConfig = newCarouselImageConfig;
+ }
+ if (obj.carouselPlayConfig) {
+ // check values
+ if (obj.carouselPlayConfig.interval <= 0) {
+ throw new Error(`Carousel's interval must be a number >= 0`);
+ }
+ newConfig.carouselPlayConfig = Object.assign({}, DEFAULT_CONFIG.carouselPlayConfig, obj.carouselPlayConfig);
+ }
+ if (obj.carouselPreviewsConfig) {
+ // check values
+ let num: number;
+ let breakpoints: BreakpointsConfig;
+ if (!obj.carouselPreviewsConfig.number || obj.carouselPreviewsConfig.number <= 0) {
+ num = DEFAULT_CAROUSEL_PREVIEWS_CONFIG.number;
+ } else {
+ num = obj.carouselPreviewsConfig.number;
+ }
+ if (obj.carouselPreviewsConfig.breakpoints) {
+ breakpoints = Object.assign({}, DEFAULT_CONFIG.carouselPreviewsConfig.breakpoints, obj.carouselPreviewsConfig.breakpoints);
+ } else {
+ breakpoints = DEFAULT_CONFIG.carouselPreviewsConfig.breakpoints;
+ }
+ newConfig.carouselPreviewsConfig = Object.assign({}, DEFAULT_CONFIG.carouselPreviewsConfig, obj.carouselPreviewsConfig);
+ newConfig.carouselPreviewsConfig.number = num;
+ newConfig.carouselPreviewsConfig.breakpoints = breakpoints;
+ // Init preview image width based on the number of previews in PreviewConfig
+ // Don't move this line above, because I need to be sure that both configPreview.number
+ // and configPreview.size are initialized
+ newConfig.carouselPreviewsConfig.width = 100 / newConfig.carouselPreviewsConfig.number + '%';
+ }
+ if (obj.carouselDotsConfig) {
+ newConfig.carouselDotsConfig = Object.assign({}, DEFAULT_CONFIG.carouselDotsConfig, obj.carouselDotsConfig);
+ }
+ if (obj.carouselSlideInfinite === undefined) {
+ newConfig.carouselSlideInfinite = DEFAULT_CONFIG.carouselSlideInfinite;
+ } else {
+ newConfig.carouselSlideInfinite = obj.carouselSlideInfinite;
+ }
+ if (obj.enableCloseOutside === undefined) {
+ newConfig.enableCloseOutside = DEFAULT_CONFIG.enableCloseOutside;
+ } else {
+ newConfig.enableCloseOutside = obj.enableCloseOutside;
+ }
+ this.configMap.set(id, newConfig);
+ }
+
+ private initIfNotExists(id: number): void {
+ if (!this.configMap.has(id)) {
+ this.configMap.set(id, DEFAULT_CONFIG);
+ }
+ }
+}
+
+/**
+ * Function to build and return a `PlainGalleryConfig` object, proving also default values and validating the input object.
+ * @param plainGalleryConfig object with the config requested by user
+ * @returns PlainGalleryConfig the plain gallery configuration
+ * @throws an Error if layout and strategy aren't compatible
+ */
+function initPlainGalleryConfig(plainGalleryConfig: PlainGalleryConfig): PlainGalleryConfig {
+ const newPlayGalleryConfig: PlainGalleryConfig = Object.assign({}, DEFAULT_CONFIG.plainGalleryConfig, plainGalleryConfig);
+
+ if (newPlayGalleryConfig.layout instanceof LineLayout) {
+ if (newPlayGalleryConfig.strategy !== PlainGalleryStrategy.ROW && newPlayGalleryConfig.strategy !== PlainGalleryStrategy.COLUMN) {
+ throw new Error('LineLayout requires either ROW or COLUMN strategy');
+ }
+ if (!newPlayGalleryConfig.layout || !newPlayGalleryConfig.layout.breakConfig) {
+ throw new Error('Both layout and breakConfig must be valid');
+ }
+ }
+
+ if (newPlayGalleryConfig.layout instanceof GridLayout) {
+ if (newPlayGalleryConfig.strategy !== PlainGalleryStrategy.GRID) {
+ throw new Error('GridLayout requires GRID strategy');
+ }
+ if (!newPlayGalleryConfig.layout || !newPlayGalleryConfig.layout.breakConfig) {
+ throw new Error('Both layout and breakConfig must be valid');
+ }
+ // force wrap for grid layout
+ newPlayGalleryConfig.layout.breakConfig.wrap = true;
+ }
+ return newPlayGalleryConfig;
+}
diff --git a/projects/ks89/angular-modal-gallery/src/lib/services/id-validator.service.spec.ts b/projects/ks89/angular-modal-gallery/src/lib/services/id-validator.service.spec.ts
new file mode 100644
index 00000000..eee0016e
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/services/id-validator.service.spec.ts
@@ -0,0 +1,133 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { inject, TestBed, waitForAsync } from '@angular/core/testing';
+
+import { IdValidatorService } from './id-validator.service';
+
+const validIds: number[] = [0, 1, 2, 3, 500, 900, 1000];
+const notValidIds: number[] = [-12, -50, -1, NaN];
+
+const errorMessage = 'You must provide a valid [id]="unique integer > 0 here" to the gallery/carousel in your template';
+
+function getErrorNotUnique(galleryId: number): string {
+ return `Cannot create gallery with id=${galleryId} because already used in your application. This must be a unique integer >= 0`;
+}
+
+describe('IdValidatorService', () => {
+ beforeEach(
+ waitForAsync(() => {
+ TestBed.configureTestingModule({
+ providers: [IdValidatorService]
+ });
+ })
+ );
+
+ it('should instantiate service when inject service',
+ inject([IdValidatorService], (service: IdValidatorService) => {
+ expect(service instanceof IdValidatorService).toEqual(true);
+ })
+ );
+
+ describe('#checkAndAdd()', () => {
+ validIds.forEach((val: number, index: number) => {
+ it(`should call checkAndAdd with valid inputs expecting true as result. Test i=${index}`,
+ inject([IdValidatorService], (service: IdValidatorService) => {
+ expect(service.checkAndAdd(val)).toBeTruthy();
+ })
+ );
+ });
+
+ notValidIds.forEach((val: number, index: number) => {
+ it(`should call checkAndAdd with bad inputs expecting an error as result. Test i=${index}`,
+ inject([IdValidatorService], (service: IdValidatorService) => {
+ expect(() => service.checkAndAdd(val)).toThrow(new Error(errorMessage));
+ })
+ );
+ });
+
+ it(`should call checkAndAdd multiple times with the same input expecting an error as result.`,
+ inject([IdValidatorService], (service: IdValidatorService) => {
+ const multipleValue = 5;
+ service.checkAndAdd(0);
+ service.checkAndAdd(1);
+ service.checkAndAdd(multipleValue);
+ service.checkAndAdd(300);
+ expect(() => service.checkAndAdd(multipleValue)).toThrow(new Error(getErrorNotUnique(multipleValue)));
+ })
+ );
+ });
+
+ describe('#remove()', () => {
+ validIds.forEach((val: number, index: number) => {
+ it(`should call remove with valid inputs expecting true as result. Test i=${index}`,
+ inject([IdValidatorService], (service: IdValidatorService) => {
+ expect(service.remove(val)).toBeTruthy();
+ })
+ );
+ });
+
+ it(`should call remove multiple times with the same input expecting true as result.`,
+ inject([IdValidatorService], (service: IdValidatorService) => {
+ const multipleValue = 5;
+ service.checkAndAdd(0);
+ service.checkAndAdd(1);
+ service.checkAndAdd(multipleValue);
+ expect(service.remove(multipleValue)).toBeTruthy();
+ expect(service.remove(multipleValue)).toBeTruthy();
+ })
+ );
+
+ it(`should call remove for a non existing id expecting true as result.`,
+ inject([IdValidatorService], (service: IdValidatorService) => {
+ const notExisting = 5;
+ service.checkAndAdd(0);
+ service.checkAndAdd(1);
+ expect(service.remove(notExisting)).toBeTruthy();
+ expect(service.remove(notExisting)).toBeTruthy();
+ })
+ );
+
+ it(`should call remove for an id and add it again without errors.`,
+ inject([IdValidatorService], (service: IdValidatorService) => {
+ const validId = 5;
+ service.checkAndAdd(0);
+ service.checkAndAdd(1);
+ service.checkAndAdd(validId);
+ expect(service.remove(validId)).toBeTruthy();
+ expect(service.checkAndAdd(validId)).toBeTruthy();
+ expect(service.remove(validId)).toBeTruthy();
+ expect(service.checkAndAdd(validId)).toBeTruthy();
+ })
+ );
+
+ notValidIds.forEach((val: number, index: number) => {
+ it(`should call remove with bad inputs expecting an error as result. Test i=${index}`,
+ inject([IdValidatorService], (service: IdValidatorService) => {
+ expect(() => service.remove(val)).toThrow(new Error(errorMessage));
+ })
+ );
+ });
+ });
+});
diff --git a/projects/ks89/angular-modal-gallery/src/lib/services/id-validator.service.ts b/projects/ks89/angular-modal-gallery/src/lib/services/id-validator.service.ts
new file mode 100644
index 00000000..29b92f95
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/services/id-validator.service.ts
@@ -0,0 +1,66 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { Injectable } from '@angular/core';
+
+/**
+ * Service to check if the provided id is unique
+ */
+@Injectable({ providedIn: 'root' })
+export class IdValidatorService {
+ ids = new Map();
+
+ /**
+ * Method to check and reserve an id for the current instance of the library.
+ * In this way, no other instances can use the same id.
+ * @param galleryId number or undefined that represents the unique id of the gallery.
+ * @return boolean true if success. false is never returned, instead an exception is thrown
+ * @throws a error with a message if galleryId is neither unique, < 0 or an integer
+ */
+ checkAndAdd(galleryId: number | undefined): boolean {
+ if (galleryId === undefined || !Number.isInteger(galleryId) || galleryId < 0) {
+ throw new Error('You must provide a valid [id]="unique integer > 0 here" to the gallery/carousel in your template');
+ }
+ if (this.ids.get(galleryId)) {
+ throw new Error(`Cannot create gallery with id=${galleryId} because already used in your application. This must be a unique integer >= 0`);
+ }
+ this.ids.set(galleryId, galleryId);
+ return true;
+ }
+
+ /**
+ * Method to remove a reserved id. In this way you are able to use the id again for another instance of the library.
+ * @param galleryId number or undefined that represents the unique id of the gallery.
+ * @return boolean true if success. false is never returned, instead an exception is thrown*
+ * @throws a error with a message if galleryId is neither integer or < 0
+ * * this should be improved without return true, because it doesn't make sense! :(
+ */
+ remove(galleryId: number | undefined): boolean {
+ if (galleryId === undefined || !Number.isInteger(galleryId) || galleryId < 0) {
+ throw new Error('You must provide a valid [id]="unique integer > 0 here" to the gallery/carousel in your template');
+ }
+ this.ids.delete(galleryId);
+ return true;
+ }
+}
diff --git a/projects/ks89/angular-modal-gallery/src/lib/utils/breakpoint-observer-mock.spec.ts b/projects/ks89/angular-modal-gallery/src/lib/utils/breakpoint-observer-mock.spec.ts
new file mode 100644
index 00000000..66e77a41
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/utils/breakpoint-observer-mock.spec.ts
@@ -0,0 +1,23 @@
+import { Injectable } from '@angular/core';
+import { Observable, of } from 'rxjs';
+import { Breakpoints, BreakpointState } from '@angular/cdk/layout';
+
+/**
+ * Mocked BreakpointObserver service from @angular/cdk used only in testing
+ */
+@Injectable({providedIn: 'root'})
+export class MediumMockedBreakpointObserver {
+ isMatched(value: string | string[]): boolean {
+ return value === Breakpoints.Medium;
+ }
+
+ observe(value: string | string[]): Observable {
+ const response: BreakpointState = {
+ matches: false,
+ breakpoints: {
+ Medium: true
+ }
+ };
+ return of(response);
+ }
+}
diff --git a/projects/ks89/angular-modal-gallery/src/lib/utils/image.util.spec.ts b/projects/ks89/angular-modal-gallery/src/lib/utils/image.util.spec.ts
new file mode 100644
index 00000000..a00f5f22
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/utils/image.util.spec.ts
@@ -0,0 +1,109 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { Image } from '../model/image.class';
+import { getIndex } from './image.util';
+
+const imagesMock: Image[] = [
+ new Image(0, {
+ img: '../assets/images/gallery/img1.jpg'
+ }),
+ new Image(2, {
+ img: '../assets/images/gallery/img2.jpg'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/img3.jpg'
+ }),
+ new Image(3, {
+ img: '../assets/images/gallery/img4.jpg'
+ })
+];
+
+const imagesMockEqualIds: Image[] = [
+ new Image(1, {
+ img: '../assets/images/gallery/img1.jpg'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/img2.jpg'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/img3.jpg'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/img4.jpg'
+ })
+];
+
+const imgToSearchFirst: Image = new Image(0, {img: 'source'});
+const imgToSearchLast: Image = new Image(3, {img: 'source'});
+const imgToSearch: Image = new Image(1, {img: 'source'});
+const imgToSearchHigh: Image = new Image(2000, {img: 'source'});
+
+const imgToSearchNegative: Image = new Image(-2, {img: 'source'});
+
+const NOT_FOUND = -1;
+
+describe('image.util', () => {
+
+ describe('#getIndex()', () => {
+
+ it('should find the image obtaining its index', () => {
+ const firstIndex: number = getIndex(imgToSearchFirst, imagesMock);
+ const lastIndex: number = getIndex(imgToSearchLast, imagesMock);
+ const index: number = getIndex(imgToSearch, imagesMock);
+ expect(firstIndex).toBe(0);
+ expect(lastIndex).toBe(imagesMock.length - 1);
+ expect(index).toBe(2);
+ });
+
+ it('should find the first image obtaining its index from an array with all the same ids', () => {
+ const index: number = getIndex(imgToSearch, imagesMockEqualIds);
+ expect(index).toBe(0);
+ });
+
+ it(`shouldn't find the image, so the result index will be -1`, () => {
+ const indexHigh: number = getIndex(imgToSearchHigh, imagesMock);
+ expect(indexHigh).toBe(NOT_FOUND);
+ });
+
+ it(`should throw an error, because the input image is not valid`, () => {
+ // @ts-ignore
+ expect(() => getIndex(null, imagesMock)).toThrowError(`image must be a valid Image object`);
+ });
+
+ it(`should throw an error, because the input array of images is not valid`, () => {
+ // @ts-ignore
+ expect(() => getIndex(imgToSearch, null)).toThrowError(`arrayOfImages must be a valid Image[]`);
+ });
+
+ it(`should throw an error, because the input image hasn't an id`, () => {
+ expect(() => getIndex({modal: {img: ''}} as Image, imagesMock)).toThrowError(`A numeric Image 'id' is mandatory`);
+ });
+
+ it(`should throw an error, because the Image id must be >= 0`, () => {
+ expect(() => getIndex(imgToSearchNegative, imagesMock)).toThrowError(`Image 'id' must be >= 0`);
+ });
+
+ });
+});
diff --git a/projects/ks89/angular-modal-gallery/src/lib/utils/image.util.ts b/projects/ks89/angular-modal-gallery/src/lib/utils/image.util.ts
new file mode 100644
index 00000000..a3419fcf
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/utils/image.util.ts
@@ -0,0 +1,54 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { Image } from '../model/image.class';
+
+/**
+ * Utility function to get the index of the input `image` from `arrayOfImages`
+ * @param image Image to get the index. The image 'id' must be a number >= 0
+ * @param arrayOfImages Image[] to search the image within it
+ * @returns number the index of the image. -1 if not found.
+ * @throws an Error if either image or arrayOfImages are not valid,
+ * or if the input image doesn't contain an 'id', or the 'id' is < 0
+ */
+export function getIndex(image: Image, arrayOfImages: Image[]): number {
+ if (!image) {
+ throw new Error('image must be a valid Image object');
+ }
+
+ if (!arrayOfImages) {
+ throw new Error('arrayOfImages must be a valid Image[]');
+ }
+
+ if (!image.id && image.id !== 0) {
+ // id = 0 is admitted
+ throw new Error(`A numeric Image 'id' is mandatory`);
+ }
+
+ if (image.id < 0) {
+ throw new Error(`Image 'id' must be >= 0`);
+ }
+
+ return arrayOfImages.findIndex((val: Image) => val.id === image.id);
+}
diff --git a/projects/ks89/angular-modal-gallery/src/lib/utils/user-input.util.ts b/projects/ks89/angular-modal-gallery/src/lib/utils/user-input.util.ts
new file mode 100644
index 00000000..5950dbdd
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/lib/utils/user-input.util.ts
@@ -0,0 +1,109 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+/**
+ * Key of the keyboard's key `enter`
+ */
+export const ENTER_KEY: string = 'Enter';
+/**
+ * Code of the keyboard's key `enter`
+ */
+export const ENTER_CODE: string = 'Enter';
+
+/**
+ * Key of the keyboard's key `esc`
+ */
+export const ESC_KEY: string = 'Escape';
+/**
+ * Code of the keyboard's key `esc`
+ */
+export const ESC_CODE: string = 'Escape';
+/**
+ * Key of the keyboard's key 'right arrow'
+ */
+export const RIGHT_ARROW_KEY: string = 'ArrowRight';
+/**
+ * Code of the keyboard's key 'right arrow'
+ */
+export const RIGHT_ARROW_CODE: string = 'ArrowRight';
+/**
+ * Key of the keyboard's key 'left arrow'
+ */
+export const LEFT_ARROW_KEY: string = 'ArrowLeft';
+/**
+ * Code of the keyboard's key 'left arrow'
+ */
+export const LEFT_ARROW_CODE: string = 'ArrowLeft';
+/**
+ * Key of the keyboard's key 'left arrow'
+ */
+export const UP_ARROW_KEY: string = 'ArrowUp';
+/**
+ * Code of the keyboard's key 'left arrow'
+ */
+export const UP_ARROW_CODE: string = 'ArrowUp';
+/**
+ * Key of the keyboard's key 'left arrow'
+ */
+export const DOWN_ARROW_KEY: string = 'ArrowDown';
+/**
+ * Code of the keyboard's key 'left arrow'
+ */
+export const DOWN_ARROW_CODE: string = 'ArrowDown';
+/**
+ * Key of the keyboard's key `space`
+ */
+export const SPACE_KEY : string= '';
+/**
+ * Code of the keyboard's key `space`
+ */
+export const SPACE_CODE: string = 'Space';
+
+/**
+ * Const to represent the right direction
+ */
+export const DIRECTION_RIGHT: string = 'right';
+/**
+ * Const to represent the left direction
+ */
+export const DIRECTION_LEFT: string = 'left';
+
+
+/**
+ * Keycode of the main mouse button
+ */
+export const MOUSE_MAIN_BUTTON_CLICK = 0;
+
+/**
+ * Const NEXT
+ */
+export const NEXT = 1;
+/**
+ * Const PREV
+ */
+export const PREV = -1;
+/**
+ * Const NOTHING to represents a situation when it isn't both NEXT and PREV
+ */
+export const NOTHING = 0;
diff --git a/projects/ks89/angular-modal-gallery/src/public-api.ts b/projects/ks89/angular-modal-gallery/src/public-api.ts
new file mode 100644
index 00000000..10fef9f1
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/src/public-api.ts
@@ -0,0 +1,88 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+/**
+ * Index file to export all interfaces, enums, classes and so on.
+ * This file represents the public apis.
+ */
+
+export { GalleryModule } from './lib/modal-gallery.module';
+
+export { Action } from './lib/model/action.enum';
+export { Image, ImageEvent, ImageModalEvent } from './lib/model/image.class';
+export type { PlainImage, ModalImage } from './lib/model/image.class';
+export { DescriptionStrategy } from './lib/model/description.interface';
+export type { Description } from './lib/model/description.interface';
+export type { KeyboardConfig } from './lib/model/keyboard-config.interface';
+export type { DotsConfig } from './lib/model/dots-config.interface';
+export type { PreviewConfig } from './lib/model/preview-config.interface';
+export type { AccessibilityConfig } from './lib/model/accessibility.interface';
+
+export type { BreakpointsConfig, CarouselPreviewConfig } from './lib/model/carousel-preview-config.interface';
+export type { CarouselConfig } from './lib/model/carousel-config.interface';
+export type { PlayConfig } from './lib/model/play-config.interface';
+export type { CarouselImageConfig } from './lib/model/carousel-image-config.interface';
+
+export type { Size } from './lib/model/size.interface';
+
+export { ButtonsStrategy, ButtonType } from './lib/model/buttons-config.interface';
+export type { ButtonsConfig, ButtonEvent } from './lib/model/buttons-config.interface';
+
+export type { ModalLibConfig, PlainLibConfig, CarouselLibConfig } from './lib/model/lib-config.interface';
+
+export type { ModalGalleryConfig } from './lib/model/modal-gallery-config.interface';
+
+export type { CurrentImageConfig } from './lib/model/current-image-config.interface';
+
+export { LoadingType } from './lib/model/loading-config.interface';
+export type { LoadingConfig } from './lib/model/loading-config.interface';
+
+export type { InteractionEvent } from './lib/model/interaction-event.interface';
+
+export { KS_DEFAULT_ACCESSIBILITY_CONFIG } from './lib/components/accessibility-default';
+export {
+ KS_DEFAULT_BTN_FULL_SCREEN,
+ KS_DEFAULT_BTN_CLOSE,
+ KS_DEFAULT_BTN_DELETE,
+ KS_DEFAULT_BTN_DOWNLOAD,
+ KS_DEFAULT_BTN_EXTURL,
+ KS_DEFAULT_SIZE
+} from './lib/components/upper-buttons/upper-buttons-default';
+
+export {
+ LineLayout,
+ GridLayout,
+ PlainGalleryStrategy
+} from './lib/model/plain-gallery-config.interface';
+export type {
+ PlainGalleryConfig,
+ PlainGalleryLayout, BreakConfig
+} from './lib/model/plain-gallery-config.interface';
+
+export { ModalGalleryComponent } from './lib/components/modal-gallery/modal-gallery.component';
+export { PlainGalleryComponent } from './lib/components/plain-gallery/plain-gallery.component';
+export { CarouselComponent } from './lib/components/carousel/carousel.component';
+
+export { ModalGalleryService } from './lib/components/modal-gallery/modal-gallery.service';
+export { ModalGalleryRef } from './lib/components/modal-gallery/modal-gallery-ref';
diff --git a/projects/ks89/angular-modal-gallery/tsconfig.lib.json b/projects/ks89/angular-modal-gallery/tsconfig.lib.json
new file mode 100644
index 00000000..fdcd9fe2
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/tsconfig.lib.json
@@ -0,0 +1,15 @@
+/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
+/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
+{
+ "extends": "../../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../../out-tsc/lib",
+ "declaration": true,
+ "declarationMap": true,
+ "inlineSources": true,
+ "types": []
+ },
+ "exclude": [
+ "**/*.spec.ts"
+ ]
+}
diff --git a/projects/ks89/angular-modal-gallery/tsconfig.lib.prod.json b/projects/ks89/angular-modal-gallery/tsconfig.lib.prod.json
new file mode 100644
index 00000000..9215caac
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/tsconfig.lib.prod.json
@@ -0,0 +1,11 @@
+/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
+/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
+{
+ "extends": "./tsconfig.lib.json",
+ "compilerOptions": {
+ "declarationMap": false
+ },
+ "angularCompilerOptions": {
+ "compilationMode": "partial"
+ }
+}
diff --git a/projects/ks89/angular-modal-gallery/tsconfig.spec.json b/projects/ks89/angular-modal-gallery/tsconfig.spec.json
new file mode 100644
index 00000000..69ea5088
--- /dev/null
+++ b/projects/ks89/angular-modal-gallery/tsconfig.spec.json
@@ -0,0 +1,16 @@
+/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
+/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
+{
+ "extends": "../../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../../out-tsc/spec",
+ "types": [
+ "jasmine",
+ "node"
+ ]
+ },
+ "include": [
+ "**/*.spec.ts",
+ "**/*.d.ts"
+ ]
+}
diff --git a/readme-images/amg-old.png b/readme-images/amg-old.png
new file mode 100644
index 00000000..11635c97
Binary files /dev/null and b/readme-images/amg-old.png differ
diff --git a/readme-images/amg-svgomg-min.svg b/readme-images/amg-svgomg-min.svg
new file mode 100644
index 00000000..15f14cae
--- /dev/null
+++ b/readme-images/amg-svgomg-min.svg
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/readme-images/amg.png b/readme-images/amg.png
new file mode 100644
index 00000000..35e60fae
Binary files /dev/null and b/readme-images/amg.png differ
diff --git a/readme-images/amg.svg b/readme-images/amg.svg
new file mode 100644
index 00000000..f6f42c31
--- /dev/null
+++ b/readme-images/amg.svg
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/readme-images/carousel-fix.png b/readme-images/carousel-fix.png
new file mode 100644
index 00000000..0ceb3958
Binary files /dev/null and b/readme-images/carousel-fix.png differ
diff --git a/readme-images/carousel-full.png b/readme-images/carousel-full.png
new file mode 100644
index 00000000..2baf25fb
Binary files /dev/null and b/readme-images/carousel-full.png differ
diff --git a/readme-images/favicon-128x128.png b/readme-images/favicon-128x128.png
new file mode 100644
index 00000000..36405395
Binary files /dev/null and b/readme-images/favicon-128x128.png differ
diff --git a/readme-images/favicon-144x144.png b/readme-images/favicon-144x144.png
new file mode 100644
index 00000000..ff416433
Binary files /dev/null and b/readme-images/favicon-144x144.png differ
diff --git a/readme-images/favicon-152x152.png b/readme-images/favicon-152x152.png
new file mode 100644
index 00000000..639f854d
Binary files /dev/null and b/readme-images/favicon-152x152.png differ
diff --git a/readme-images/favicon-192x192.png b/readme-images/favicon-192x192.png
new file mode 100644
index 00000000..4e279875
Binary files /dev/null and b/readme-images/favicon-192x192.png differ
diff --git a/readme-images/favicon-32x32.png b/readme-images/favicon-32x32.png
new file mode 100644
index 00000000..1b2d0ead
Binary files /dev/null and b/readme-images/favicon-32x32.png differ
diff --git a/readme-images/favicon-384x384.png b/readme-images/favicon-384x384.png
new file mode 100644
index 00000000..a2ca07fa
Binary files /dev/null and b/readme-images/favicon-384x384.png differ
diff --git a/readme-images/favicon-512x512.png b/readme-images/favicon-512x512.png
new file mode 100644
index 00000000..a759eb9a
Binary files /dev/null and b/readme-images/favicon-512x512.png differ
diff --git a/readme-images/favicon-72x72.png b/readme-images/favicon-72x72.png
new file mode 100644
index 00000000..e4a2ad31
Binary files /dev/null and b/readme-images/favicon-72x72.png differ
diff --git a/readme-images/favicon-96x96.png b/readme-images/favicon-96x96.png
new file mode 100644
index 00000000..f3f24e25
Binary files /dev/null and b/readme-images/favicon-96x96.png differ
diff --git a/readme-images/modal1.png b/readme-images/modal1.png
new file mode 100644
index 00000000..2f14e1c4
Binary files /dev/null and b/readme-images/modal1.png differ
diff --git a/readme-images/modal2.png b/readme-images/modal2.png
new file mode 100644
index 00000000..78255a4c
Binary files /dev/null and b/readme-images/modal2.png differ
diff --git a/readme-images/plain-col.png b/readme-images/plain-col.png
new file mode 100644
index 00000000..f9eb12d8
Binary files /dev/null and b/readme-images/plain-col.png differ
diff --git a/readme-images/plain-grid.png b/readme-images/plain-grid.png
new file mode 100644
index 00000000..a2cb3416
Binary files /dev/null and b/readme-images/plain-grid.png differ
diff --git a/readme-images/plain-row.png b/readme-images/plain-row.png
new file mode 100644
index 00000000..645d0ab0
Binary files /dev/null and b/readme-images/plain-row.png differ
diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
new file mode 100644
index 00000000..20be26e4
--- /dev/null
+++ b/src/app/app-routing.module.ts
@@ -0,0 +1,20 @@
+import { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+
+import { ModalGalleryExampleComponent } from './modal-gallery/modal-gallery.component';
+import { PlainGalleryExampleComponent } from './plain-gallery/plain-gallery.component';
+import { CarouselExampleComponent } from './carousel/carousel.component';
+import { HomeComponent } from './home/home.component';
+
+const routes: Routes = [
+ { path: '', component: HomeComponent },
+ { path: 'carousel', component: CarouselExampleComponent },
+ { path: 'modal', component: ModalGalleryExampleComponent },
+ { path: 'plain', component: PlainGalleryExampleComponent }
+];
+
+@NgModule({
+ imports: [RouterModule.forRoot(routes)],
+ exports: [RouterModule]
+})
+export class AppRoutingModule {}
diff --git a/src/app/app.component.html b/src/app/app.component.html
new file mode 100644
index 00000000..27bca28a
--- /dev/null
+++ b/src/app/app.component.html
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+This library and these examples are created by Stefano Cappa (@Ks89)
+
+
+ A special thank to all authors of icons used in this library:
+
+
+
+
+ A special thanks to all authors of spinners used in this library:
+
+
diff --git a/src/app/app.component.scss b/src/app/app.component.scss
new file mode 100644
index 00000000..e06277ac
--- /dev/null
+++ b/src/app/app.component.scss
@@ -0,0 +1,47 @@
+// The MIT License (MIT)
+//
+// Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+
+#main-title {
+ text-align: center;
+ width: 100%;
+}
+
+#menu {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+
+ > .menu-item {
+ margin-left: 10px;
+ }
+}
+
+.copyright {
+ text-align: center;
+}
+
+.margin {
+ margin-left: 15px;
+ margin-right: 15px;
+}
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
new file mode 100644
index 00000000..17a818a5
--- /dev/null
+++ b/src/app/app.component.ts
@@ -0,0 +1,33 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'ks-root',
+ templateUrl: './app.component.html',
+ styleUrls: ['./app.component.scss'],
+ standalone: false
+})
+export class AppComponent {}
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
new file mode 100644
index 00000000..5f336573
--- /dev/null
+++ b/src/app/app.module.ts
@@ -0,0 +1,71 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { BrowserModule } from '@angular/platform-browser';
+import { NgModule } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+
+import { AppRoutingModule } from './app-routing.module';
+import { AppComponent } from './app.component';
+import { CarouselExampleComponent } from './carousel/carousel.component';
+import { PlainGalleryExampleComponent } from './plain-gallery/plain-gallery.component';
+import { ModalGalleryExampleComponent } from './modal-gallery/modal-gallery.component';
+import { NavbarComponent } from './navbar/navbar.component';
+import { HomeComponent } from './home/home.component';
+import { IntroHeaderComponent } from './intro-header/intro-header.component';
+
+// ********************** angular-modal-gallery *****************************
+import { GalleryModule } from '@ks89/angular-modal-gallery'; // <----------------- angular-modal-gallery library import
+// **************************************************************************
+
+// ************************ optional font-awesome 5 ************************
+// to install use both `npm i --save @fortawesome/fontawesome-svg-core` and `npm i --save @fortawesome/free-solid-svg-icons`
+import { library, dom } from '@fortawesome/fontawesome-svg-core';
+import { faExternalLinkAlt, faPlus, faTimes, faDownload } from '@fortawesome/free-solid-svg-icons';
+library.add(faExternalLinkAlt, faPlus, faTimes, faDownload);
+dom.watch(); // Kicks off the process of finding tags and replacing with
+// *************************************************************************
+
+@NgModule({
+ declarations: [
+ AppComponent,
+ CarouselExampleComponent,
+ PlainGalleryExampleComponent,
+ ModalGalleryExampleComponent,
+ NavbarComponent,
+ HomeComponent,
+ IntroHeaderComponent
+ ],
+ imports: [
+ BrowserModule,
+ FormsModule,
+
+ AppRoutingModule,
+
+ GalleryModule // <-------------------------------------------- @ks89/angular-modal-gallery module import
+ ],
+ providers: [],
+ bootstrap: [AppComponent]
+})
+export class AppModule {}
diff --git a/src/app/carousel/carousel.component.ts b/src/app/carousel/carousel.component.ts
new file mode 100644
index 00000000..a7114bd8
--- /dev/null
+++ b/src/app/carousel/carousel.component.ts
@@ -0,0 +1,576 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { Component } from '@angular/core';
+
+import {
+ AccessibilityConfig,
+ CarouselLibConfig,
+ Image,
+ ImageEvent,
+ ModalGalleryConfig,
+ ModalGalleryRef,
+ ModalGalleryService,
+ ModalLibConfig
+} from '@ks89/angular-modal-gallery';
+
+@Component({
+ selector: 'ks-carousel-page',
+ templateUrl: './carousel.html',
+ styleUrls: ['./carousel.scss'],
+ standalone: false
+})
+export class CarouselExampleComponent {
+ imageIndex = 1;
+ galleryId = 1;
+ autoPlay = true;
+ showArrows = true;
+ showDots = true;
+
+ LIBCONFIG102: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: false
+ }
+ };
+ LIBCONFIG103: CarouselLibConfig = {
+ carouselSlideInfinite: false
+ };
+ LIBCONFIG104: CarouselLibConfig = {
+ carouselDotsConfig: {
+ visible: false
+ }
+ };
+ LIBCONFIG105: CarouselLibConfig = {
+ carouselPlayConfig: {
+ autoPlay: false,
+ interval: 5000,
+ pauseOnHover: true
+ }
+ };
+ LIBCONFIG106: CarouselLibConfig = {
+ carouselPlayConfig: {
+ autoPlay: true,
+ interval: 10000,
+ pauseOnHover: false
+ }
+ };
+ LIBCONFIG107: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 7,
+ width: 'auto',
+ maxHeight: '100px'
+ }
+ };
+ LIBCONFIG113: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 5
+ },
+ carouselConfig: {
+ maxWidth: '766px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: false
+ }
+ };
+ LIBCONFIG114: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 5,
+ width: 'auto',
+ maxHeight: '100px'
+ },
+ carouselConfig: {
+ maxWidth: '766px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: true
+ }
+ };
+ LIBCONFIG115: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 5,
+ width: 'auto',
+ maxHeight: '100px'
+ },
+ carouselConfig: {
+ maxWidth: '766px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: false,
+ modalGalleryEnable: false
+ }
+ };
+ LIBCONFIG116: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 7,
+ clickable: false
+ }
+ };
+ LIBCONFIG117: CarouselLibConfig = {
+ carouselImageConfig: {
+ invertSwipe: true
+ }
+ };
+ LIBCONFIG118: CarouselLibConfig = {
+ carouselImageConfig: {
+ description: {
+ strategy: 2
+ }
+ }
+ };
+ LIBCONFIG119: CarouselLibConfig = {
+ carouselConfig: {
+ maxWidth: '766px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: false
+ },
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 5,
+ width: 'auto',
+ maxHeight: '200px'
+ }
+ };
+ LIBCONFIG120: CarouselLibConfig = {
+ carouselConfig: {
+ maxWidth: '766px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: false
+ },
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 5,
+ width: 'auto',
+ maxHeight: '150px'
+ }
+ };
+ LIBCONFIG121: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ breakpoints: {
+ xSmall: 50,
+ small: 60,
+ medium: 80,
+ large: 150,
+ xLarge: 180
+ }
+ }
+ };
+ LIBCONFIG122: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ breakpoints: {
+ xSmall: 50,
+ small: 60,
+ medium: 70,
+ large: 80,
+ xLarge: 100
+ }
+ },
+ carouselConfig: {
+ maxWidth: '500px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: false
+ }
+ };
+ LIBCONFIG124: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ breakpoints: {
+ xSmall: 50,
+ small: 60,
+ medium: 70,
+ large: 80,
+ xLarge: 100
+ }
+ },
+ carouselConfig: {
+ maxWidth: '500px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: false
+ },
+ carouselPlayConfig: {
+ autoPlay: false,
+ interval: 1,
+ pauseOnHover: true
+ }
+ };
+
+ imagesRect: Image[] = [
+ new Image(
+ 0,
+ {
+ img: '/assets/images/gallery/milan-pegasus-gallery-statue.jpg',
+ description: 'Description 1'
+ },
+ {
+ img: '/assets/images/gallery/thumbs/t-milan-pegasus-gallery-statue.jpg',
+ title: 'First image title',
+ alt: 'First image alt',
+ ariaLabel: 'First image aria-label'
+ }
+ ),
+ new Image(1, { img: '/assets/images/gallery/pexels-photo-47223.jpeg' }, { img: '/assets/images/gallery/thumbs/t-pexels-photo-47223.jpg' }),
+ new Image(
+ 2,
+ {
+ img: '/assets/images/gallery/pexels-photo-52062.jpeg',
+ description: 'Description 3',
+ title: 'Third image title',
+ alt: 'Third image alt',
+ ariaLabel: 'Third image aria-label'
+ },
+ {
+ img: '/assets/images/gallery/thumbs/t-pexels-photo-52062.jpg',
+ description: 'Description 3'
+ }
+ ),
+ new Image(
+ 3,
+ {
+ img: '/assets/images/gallery/pexels-photo-66943.jpeg',
+ description: 'Description 4',
+ title: 'Fourth image title (modal obj)',
+ alt: 'Fourth image alt (modal obj)',
+ ariaLabel: 'Fourth image aria-label (modal obj)'
+ },
+ {
+ img: '/assets/images/gallery/thumbs/t-pexels-photo-66943.jpg',
+ title: 'Fourth image title (plain obj)',
+ alt: 'Fourth image alt (plain obj)',
+ ariaLabel: 'Fourth image aria-label (plain obj)'
+ }
+ ),
+ new Image(4, { img: '/assets/images/gallery/pexels-photo-93750.jpeg' }, { img: '/assets/images/gallery/thumbs/t-pexels-photo-93750.jpg' }),
+ new Image(
+ 5,
+ {
+ img: '/assets/images/gallery/pexels-photo-94420.jpeg',
+ description: 'Description 6'
+ },
+ { img: '/assets/images/gallery/thumbs/t-pexels-photo-94420.jpg' }
+ ),
+ new Image(6, { img: '/assets/images/gallery/pexels-photo-96947.jpeg' }, { img: '/assets/images/gallery/thumbs/t-pexels-photo-96947.jpg' })
+ ];
+
+ imagesRectWithSources: Image[] = [
+ new Image(
+ 0,
+ {
+ img: '/assets/images/gallery/milan-pegasus-gallery-statue.jpg',
+ description: 'Description 1',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/milan-pegasus-gallery-statue-480w.jpg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/milan-pegasus-gallery-statue-768w.jpg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/milan-pegasus-gallery-statue-1024w.jpg' }
+ ]
+ },
+ {
+ img: '/assets/images/gallery/thumbs/t-milan-pegasus-gallery-statue.jpg',
+ title: 'First image title',
+ alt: 'First image alt',
+ ariaLabel: 'First image aria-label'
+ }
+ ),
+ new Image(1, { img: '/assets/images/gallery/pexels-photo-47223.jpeg',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/pexels-photo-47223-480w.jpeg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/pexels-photo-47223-768w.jpeg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/pexels-photo-47223-1024w.jpeg' }
+ ] }, { img: '/assets/images/gallery/thumbs/t-pexels-photo-47223.jpg' }),
+ new Image(
+ 2,
+ {
+ img: '/assets/images/gallery/pexels-photo-52062.jpeg',
+ description: 'Description 3',
+ title: 'Third image title',
+ alt: 'Third image alt',
+ ariaLabel: 'Third image aria-label',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/pexels-photo-52062-480w.jpeg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/pexels-photo-52062-768w.jpeg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/pexels-photo-52062-1024w.jpeg' }
+ ]
+ },
+ {
+ img: '/assets/images/gallery/thumbs/t-pexels-photo-52062.jpg',
+ description: 'Description 3'
+ }
+ ),
+ new Image(
+ 3,
+ {
+ img: '/assets/images/gallery/pexels-photo-66943.jpeg',
+ description: 'Description 4',
+ title: 'Fourth image title (modal obj)',
+ alt: 'Fourth image alt (modal obj)',
+ ariaLabel: 'Fourth image aria-label (modal obj)',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/pexels-photo-66943-480w.jpeg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/pexels-photo-66943-768w.jpeg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/pexels-photo-66943-1024w.jpeg' }
+ ]
+ },
+ {
+ img: '/assets/images/gallery/thumbs/t-pexels-photo-66943.jpg',
+ title: 'Fourth image title (plain obj)',
+ alt: 'Fourth image alt (plain obj)',
+ ariaLabel: 'Fourth image aria-label (plain obj)'
+ }
+ ),
+ new Image(4, { img: '/assets/images/gallery/pexels-photo-93750.jpeg',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/pexels-photo-93750-480w.jpeg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/pexels-photo-93750-768w.jpeg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/pexels-photo-93750-1024w.jpeg' }
+ ] }, { img: '/assets/images/gallery/thumbs/t-pexels-photo-93750.jpg' }),
+ new Image(
+ 5,
+ {
+ img: '/assets/images/gallery/pexels-photo-94420.jpeg',
+ description: 'Description 6',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/pexels-photo-94420-480w.jpeg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/pexels-photo-94420-768w.jpeg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/pexels-photo-94420-1024w.jpeg' }
+ ]
+ },
+ { img: '/assets/images/gallery/thumbs/t-pexels-photo-94420.jpg' }
+ ),
+ new Image(6, { img: '/assets/images/gallery/pexels-photo-96947.jpeg',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/pexels-photo-96947-480w.jpeg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/pexels-photo-96947-768w.jpeg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/pexels-photo-96947-1024w.jpeg' }
+ ] }, { img: '/assets/images/gallery/thumbs/t-pexels-photo-96947.jpg' })
+ ];
+
+ emptyImagesArray: Image[] = [];
+
+ imagesRectNoTitles: Image[] = [
+ new Image(
+ 0,
+ { img: '../assets/images/gallery/milan-pegasus-gallery-statue.jpg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-milan-pegasus-gallery-statue.jpg', title: '' }
+ ),
+ new Image(
+ 1,
+ { img: '../assets/images/gallery/pexels-photo-47223.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-47223.jpg', title: '' }
+ ),
+ new Image(
+ 2,
+ { img: '../assets/images/gallery/pexels-photo-52062.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-52062.jpg', title: '' }
+ ),
+ new Image(
+ 3,
+ { img: '../assets/images/gallery/pexels-photo-66943.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-66943.jpg', title: '' }
+ ),
+ new Image(
+ 4,
+ { img: '../assets/images/gallery/pexels-photo-93750.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-93750.jpg', title: '' }
+ ),
+ new Image(
+ 5,
+ { img: '../assets/images/gallery/pexels-photo-94420.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-94420.jpg', title: '' }
+ ),
+ new Image(
+ 6,
+ { img: '../assets/images/gallery/pexels-photo-96947.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-96947.jpg', title: '' }
+ )
+ ];
+
+ fallbackRectImages: Image[] = [
+ new Image(0, {
+ // this file is not available so the browser returns an error
+ img: '../assets/images/gallery/UNEXISTING_IMG1.jpg',
+ // because the img above doesn't exists, the library will use this file
+ fallbackImg: '../assets/images/gallery/fallback-carousel1.jpg'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/UNEXISTING_IMG2.jpg',
+ fallbackImg: '../assets/images/gallery/fallback-carousel2.jpg'
+ }),
+ new Image(
+ 2,
+ {
+ img: '../assets/images/gallery/UNEXISTING_IMG3.jpg',
+ fallbackImg: '../assets/images/gallery/fallback-carousel3.jpg'
+ },
+ {
+ img: '../assets/images/gallery/thumbs/UNEXISTING_IMG3.png',
+ fallbackImg: '../assets/images/gallery/fallback-carousel3.jpg'
+ }
+ ),
+ new Image(3, {
+ img: '../assets/images/gallery/UNEXISTING_IMG4.jpg',
+ fallbackImg: '../assets/images/gallery/fallback-carousel4.jpg'
+ }),
+ new Image(
+ 4,
+ {
+ img: '../assets/images/gallery/UNEXISTING_IMG5.jpg',
+ fallbackImg: '../assets/images/gallery/fallback-carousel5.jpg'
+ },
+ {
+ img: '../assets/images/gallery/thumbs/UNEXISTING_IMG5.jpg',
+ fallbackImg: '../assets/images/gallery/fallback-carousel5.jpg'
+ }
+ )
+ ];
+
+ accessibilityConfig: AccessibilityConfig = {
+ backgroundAriaLabel: 'CUSTOM Modal gallery full screen background',
+ backgroundTitle: 'CUSTOM background title',
+
+ plainGalleryContentAriaLabel: 'CUSTOM Plain gallery content',
+ plainGalleryContentTitle: 'CUSTOM plain gallery content title',
+
+ modalGalleryContentAriaLabel: 'CUSTOM Modal gallery content',
+ modalGalleryContentTitle: 'CUSTOM modal gallery content title',
+
+ loadingSpinnerAriaLabel: 'CUSTOM The current image is loading. Please be patient.',
+ loadingSpinnerTitle: 'CUSTOM The current image is loading. Please be patient.',
+
+ mainContainerAriaLabel: 'CUSTOM Current image and navigation',
+ mainContainerTitle: 'CUSTOM main container title',
+ mainPrevImageAriaLabel: 'CUSTOM Previous image',
+ mainPrevImageTitle: 'CUSTOM Previous image',
+ mainNextImageAriaLabel: 'CUSTOM Next image',
+ mainNextImageTitle: 'CUSTOM Next image',
+
+ dotsContainerAriaLabel: 'CUSTOM Image navigation dots',
+ dotsContainerTitle: 'CUSTOM dots container title',
+ dotAriaLabel: 'CUSTOM Navigate to image number',
+
+ previewsContainerAriaLabel: 'CUSTOM Image previews',
+ previewsContainerTitle: 'CUSTOM previews title',
+ previewScrollPrevAriaLabel: 'CUSTOM Scroll previous previews',
+ previewScrollPrevTitle: 'CUSTOM Scroll previous previews',
+ previewScrollNextAriaLabel: 'CUSTOM Scroll next previews',
+ previewScrollNextTitle: 'CUSTOM Scroll next previews',
+
+ carouselContainerAriaLabel: 'Current image and navigation',
+ carouselContainerTitle: '',
+ carouselPrevImageAriaLabel: 'Previous image',
+ carouselPrevImageTitle: 'Previous image',
+ carouselNextImageAriaLabel: 'Next image',
+ carouselNextImageTitle: 'Next image',
+ carouselPreviewsContainerAriaLabel: 'Image previews',
+ carouselPreviewsContainerTitle: '',
+ carouselPreviewScrollPrevAriaLabel: 'Scroll previous previews',
+ carouselPreviewScrollPrevTitle: 'Scroll previous previews',
+ carouselPreviewScrollNextAriaLabel: 'Scroll next previews',
+ carouselPreviewScrollNextTitle: 'Scroll next previews'
+ };
+
+ constructor(private modalGalleryService: ModalGalleryService) {}
+
+ addRandomImage(): void {
+ const imageToCopy: Image = this.imagesRect[Math.floor(Math.random() * this.imagesRect.length)];
+ const newImage: Image = new Image(this.imagesRect.length - 1 + 1, imageToCopy.modal, imageToCopy.plain);
+ this.imagesRect = [...this.imagesRect, newImage];
+ }
+
+ onChangeAutoPlay(): void {
+ this.autoPlay = !this.autoPlay;
+ }
+
+ onChangeShowArrows(): void {
+ this.showArrows = !this.showArrows;
+ }
+
+ onChangeShowDots(): void {
+ this.showDots = !this.showDots;
+ }
+
+ // output evets
+ onShow(event: ImageEvent): void {
+ console.log('show', event);
+ }
+
+ onFirstImage(event: ImageEvent): void {
+ console.log('firstImage', event);
+ }
+
+ onLastImage(event: ImageEvent): void {
+ console.log('lastImage', event);
+ }
+
+ getLibConfig108(autoPlay: boolean, showArrows: boolean, showDots: boolean): CarouselLibConfig {
+ return {
+ carouselDotsConfig: {
+ visible: showDots
+ },
+ carouselPlayConfig: {
+ autoPlay: autoPlay,
+ interval: 3000,
+ pauseOnHover: true
+ },
+ carouselConfig: {
+ maxWidth: '100%',
+ maxHeight: '400px',
+ showArrows: showArrows,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: false
+ }
+ } as CarouselLibConfig;
+ }
+
+ openModal(imageIndex: number, id: number): void {
+ const imageToShow: Image = this.imagesRect[imageIndex];
+ const dialogRef: ModalGalleryRef = this.modalGalleryService.open({
+ id,
+ images: this.imagesRect,
+ currentImage: imageToShow
+ } as ModalGalleryConfig) as ModalGalleryRef;
+ }
+}
diff --git a/src/app/carousel/carousel.html b/src/app/carousel/carousel.html
new file mode 100644
index 00000000..ee04ac8c
--- /dev/null
+++ b/src/app/carousel/carousel.html
@@ -0,0 +1,215 @@
+Carousel
+
+
+Basic examples
+
+
+ A1 - (id=100) - carousel example (minimal with all defaults) without content projection
+
+
+
+
+
+ A2 - (id=101) - carousel example (minimal with all defaults)
+
+
+ This is my projected content!
+
+
+
+
+ A3 - (id=102) - carousel example without previews
+
+
+ This is my projected content!
+
+
+
+
+ A4 - (id=103) - carousel example without infinite sliding
+
+
+ This is my projected content!
+
+
+
+
+ A5 - (id=104) - carousel example without dots
+
+
+ This is my projected content!
+
+
+
+
+ A6 - (id=105) - carousel example without auto-play (but all other playConfig properties have default values)
+
+
+ This is my projected content!
+
+
+
+
+ A7 - (id=106) - carousel example with a custom playConfig (10s of interval and pauseOnHover disabled)
+
+
+ This is my projected content!
+
+
+
+
+ A8 - (id=107) - carousel example with a custom previewConfig (7 previews with 'auto' width and 100px of height)
+
+
+ This is my projected content!
+
+
+
+
+ A9 - (id=108) - carousel example with buttons to enable/disable autoplay, arrows and other properties
+ Autoplay: {{autoPlay ? 'disable autoplay' : 'enable autoplay'}}
+ Show Arrows: {{showArrows ? 'Hide Arrows' : 'Show Arrows'}}
+ Show Dots: {{showDots ? 'Hide Dots' : 'Show Dots'}}
+
+
+ This is my projected content!
+
+
+
+
+ A10 - (id=109) - carousel example (minimal with all defaults) with outputs
+
+
+
+
+
+ A11 - (id=110) - carousel example with fallback images when it's not possible to load normal images (to fix issue #194)
+
+
+
+
+
+ A12 - (id=111) - carousel example without title attribute on images
+
+
+
+
+
+ A13 - (id=112) - carousel example with an empty images array.
+ Carousel component doesn't support empty images arrays! To prevent runtime errors, you should use an *ngIf to remove the carousel when there are no images.
+
+
+
+
+
+
+Examples with custom style
+
+ B1 - (id=113) - carousel example with fixed maxWidth (766px) and custom previews
+ By default, on bigger screen, previews will have height = 200px. If you want you can customize it or also change all breakpoint widths.
+
+
+ This is my projected content!
+
+
+
+
+ B2 - (id=114) - carousel example with fixed maxWidth (766px), custom previews and open modal on click
+
+
+ This is my projected content!
+
+
+
+
+ B3 - (id=115) - carousel example with fixed maxWidth (766px), custom previews and keyboard navigation disabled (for example left/right arrows)
+
+
+ This is my projected content!
+
+
+
+
+ B4 - (id=116) - carousel example with 7 images and unclickable previews
+
+
+ This is my projected content!
+
+
+
+
+
+Examples with custom current image
+
+ C1 - (id=117) - carousel example with invert swipe on touchscreen devices
+
+
+ This is my projected content!
+
+
+
+
+ C2 - (id=118) - carousel example with description
+
+
+ This is my projected content!
+
+
+
+
+
+Examples with custom previews height
+I know that these examples look bad, but the purpose is to show only how the library handles heights.
+ In both examples I didn't set breakpoints, so it will be used default values, but with the maxHeight specified
+If F1, the maxHeight is 200px, but also the default breakpoints has 200px as maximum size, so the result will be very bad on bigger screen.
+ In F2, I'm using a smaller maxHeight, so previews won't be taller than 150px, despite the default breakpoints on bigger screens (200px).
+
+ F1 - (id=119) - carousel example with fixed maxWidth (766px) and custom previews (maxHeight 200px)
+
+
+ This is my projected content!
+
+
+
+
+ F2 - (id=120) - carousel example with fixed maxWidth (766px) and custom previews (maxHeight 150px)
+
+
+ This is my projected content!
+
+
+
+
+
+Examples with custom heights for previews (to try responsiveness)
+
+
+ G1 - (id=121) - carousel example (previews with different heights based on the window's width: xSmall: 50, small: 60, medium: 80, large: 150, xLarge: 180)
+
+
+
+
+
+ G2 - (id=122) - carousel example with fixed maxWidth (500px) (previews with different heights based on the window's width: xSmall: 50, small: 60, medium: 70, large: 80, xLarge: 100)
+
+
+
+
+Examples using sources (to improve LCP)
+
+
+ H1 - (id=123) - carousel example (minimal with all defaults) without content projection - using sources
+
+
+
+
+ H2 - (id=124) - carousel example with fixed maxWidth (500px) (previews with different heights based on the window's width: xSmall: 50, small: 60, medium: 70, large: 80, xLarge: 100) - using sources
+
+
+
+
+
diff --git a/src/app/carousel/carousel.scss b/src/app/carousel/carousel.scss
new file mode 100644
index 00000000..547c14f9
--- /dev/null
+++ b/src/app/carousel/carousel.scss
@@ -0,0 +1,157 @@
+// The MIT License (MIT)
+//
+// Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+:host {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: flex-start;
+}
+
+section {
+ width: 100%;
+}
+
+h2, h3, p {
+ margin-left: 20px;
+}
+
+$text-color: #fff;
+$background: rgba(0, 0, 0, .7);
+
+.my-app-custom-plain-container-row {
+ align-items: center;
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+
+ .my-app-custom-image-row {
+ cursor: pointer;
+ height: auto;
+ margin-right: 2px;
+ width: 50px;
+
+ &.after {
+ border-top: 2px;
+ cursor: pointer;
+ display: none;
+ height: 100%;
+ left: 0;
+ position: absolute;
+ top: 0;
+ width: 100%;
+ }
+ }
+}
+
+.my-app-custom-plain-container-column {
+ align-items: center;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+
+ .my-app-custom-image-column {
+ cursor: pointer;
+ height: auto;
+ margin-right: 2px;
+ width: 50px;
+
+ &.after {
+ border-top: 2px;
+ cursor: pointer;
+ display: none;
+ height: 100%;
+ left: 0;
+ position: absolute;
+ top: 0;
+ width: 100%;
+ }
+ }
+}
+
+.my-app-custom-plain-container-with-desc {
+ align-items: center;
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+
+ figure {
+ margin: 0;
+ position: relative;
+
+ img {
+ max-width: 100%;
+ height: auto;
+ cursor: pointer;
+ }
+
+ figcaption {
+ background: rgba(0, 0, 0, .5);
+ color: #fff;
+ font-size: 85%;
+ padding: 5px;
+ position: absolute;
+ bottom: 3px;
+ left: 0;
+ right: 0;
+ }
+ }
+
+ .description {
+ font-weight: bold;
+ text-align: center;
+ }
+
+ .my-app-custom-image-with-desc {
+ height: auto;
+ margin-right: 2px;
+ width: 200px;
+ align-self: start;
+ }
+}
+
+.more {
+ background: $background;
+ cursor: pointer;
+ color: $text-color;
+ padding-top: 4px;
+ height: 46px;
+ position: absolute;
+ text-align: center;
+ width: 50px;
+}
+
+
+.projected {
+ color: white;
+ font-weight: 600;
+ font-size: 24px;
+ text-align: center;
+ position: absolute;
+ top: 50%;
+ width: 100%;
+ pointer-events: none;
+}
+
+.title {
+ margin-top: 40px;
+}
diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts
new file mode 100644
index 00000000..82d07f20
--- /dev/null
+++ b/src/app/home/home.component.ts
@@ -0,0 +1,33 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'ks-home-page',
+ templateUrl: './home.html',
+ styleUrls: ['./home.scss'],
+ standalone: false
+})
+export class HomeComponent {}
diff --git a/src/app/home/home.html b/src/app/home/home.html
new file mode 100644
index 00000000..f5ec1261
--- /dev/null
+++ b/src/app/home/home.html
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/src/app/home/home.scss b/src/app/home/home.scss
new file mode 100644
index 00000000..8c5e86a7
--- /dev/null
+++ b/src/app/home/home.scss
@@ -0,0 +1,26 @@
+// The MIT License (MIT)
+//
+// Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+.container {
+ margin-left: 15px;
+ margin-right: 15px;
+}
diff --git a/src/app/intro-header/intro-header.component.ts b/src/app/intro-header/intro-header.component.ts
new file mode 100644
index 00000000..b6c7ba73
--- /dev/null
+++ b/src/app/intro-header/intro-header.component.ts
@@ -0,0 +1,33 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'ks-intro-header',
+ templateUrl: 'intro-header.html',
+ styleUrls: ['intro-header.scss'],
+ standalone: false
+})
+export class IntroHeaderComponent {}
diff --git a/src/app/intro-header/intro-header.html b/src/app/intro-header/intro-header.html
new file mode 100644
index 00000000..94cdd372
--- /dev/null
+++ b/src/app/intro-header/intro-header.html
@@ -0,0 +1,8 @@
+
diff --git a/src/app/intro-header/intro-header.scss b/src/app/intro-header/intro-header.scss
new file mode 100644
index 00000000..d0f1cbc3
--- /dev/null
+++ b/src/app/intro-header/intro-header.scss
@@ -0,0 +1,73 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+// search all occurrences in all code, also html, because it's everywhere
+$color-primary: #101010;
+$color-secondary: #9e9e9e;
+$color-white: #FFF;
+$color-black: #000;
+$color-light-black: #343A40;
+$color-code: #c100e0;
+$color-url: #0060b7;
+$color-warning: #880012;
+
+//$font-huge: 24px;
+$font-big: 18px;
+$font-middle: 14px;
+$font-small: 12px;
+$font-very-small: 10px;
+
+.intro-header {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: center;
+ background-color: $color-primary;
+ background: linear-gradient(135deg, $color-primary, $color-secondary);
+ margin-top: 10px;
+ padding-bottom: 1px;
+ color: $color-white;
+
+ h1 {
+ color: $color-white;
+ font-size: 3rem;
+ }
+}
+
+.project-title {
+ margin-top: 20px;
+ font-size: 2.8rem;
+ text-align: center;
+}
+
+img {
+ margin-top: 25px;
+ border-radius: 10px
+}
+
+.lead {
+ font-size: 1rem;
+ text-align: center;
+ color: #d4d4d4;
+}
diff --git a/src/app/modal-gallery/libconfigs.ts b/src/app/modal-gallery/libconfigs.ts
new file mode 100644
index 00000000..fdf8c729
--- /dev/null
+++ b/src/app/modal-gallery/libconfigs.ts
@@ -0,0 +1,583 @@
+import {
+ ButtonsStrategy,
+ ButtonType,
+ Description,
+ DescriptionStrategy,
+ KS_DEFAULT_BTN_CLOSE,
+ KS_DEFAULT_BTN_DELETE,
+ KS_DEFAULT_BTN_DOWNLOAD,
+ KS_DEFAULT_BTN_EXTURL,
+ KS_DEFAULT_BTN_FULL_SCREEN,
+ LoadingConfig,
+ LoadingType,
+ ModalLibConfig,
+ Size
+} from '@ks89/angular-modal-gallery';
+
+const DEFAULT_SIZE_PREVIEWS: Size = {
+ width: '100px',
+ height: 'auto'
+};
+
+// Examples A
+export const LIBCONFIG_406: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ sidePreviews: {
+ show: true,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ }
+};
+
+export const LIBCONFIG_407: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ sidePreviews: {
+ show: false,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ }
+};
+
+export const LIBCONFIG_408: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ sidePreviews: {
+ show: false,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ }
+};
+
+// Examples B
+export const LIBCONFIG_500: ModalLibConfig = {
+ previewConfig: {
+ visible: false
+ },
+ dotsConfig: {
+ visible: false
+ }
+};
+
+export const LIBCONFIG_501: ModalLibConfig = {
+ buttonsConfig: {
+ visible: false,
+ strategy: ButtonsStrategy.DEFAULT
+ },
+ previewConfig: {
+ visible: false
+ },
+ dotsConfig: {
+ visible: false
+ }
+};
+
+export const LIBCONFIG_502: ModalLibConfig = {
+ buttonsConfig: {
+ visible: false,
+ strategy: ButtonsStrategy.DEFAULT
+ },
+ slideConfig: {
+ infinite: false,
+ sidePreviews: {
+ show: false,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ },
+ previewConfig: {
+ visible: false
+ },
+ dotsConfig: {
+ visible: false
+ }
+};
+
+export const LIBCONFIG_503: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ size: {
+ width: '90px',
+ height: 'auto'
+ }
+ }
+};
+
+export const LIBCONFIG_504: ModalLibConfig = {
+ enableCloseOutside: false
+};
+
+export const LIBCONFIG_505: ModalLibConfig = {
+ currentImageConfig: { downloadable: false },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.DEFAULT
+ }
+};
+
+export const LIBCONFIG_506: ModalLibConfig = {
+ currentImageConfig: { downloadable: true },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.DEFAULT
+ }
+};
+
+export const LIBCONFIG_507: ModalLibConfig = {
+ currentImageConfig: { downloadable: true },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.SIMPLE
+ }
+};
+
+export const LIBCONFIG_508: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ sidePreviews: {
+ show: false,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ }
+};
+
+export const LIBCONFIG_509: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ sidePreviews: {
+ show: true,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ }
+};
+
+export const LIBCONFIG_510: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: false,
+ type: LoadingType.STANDARD
+ }
+ }
+};
+export const LIBCONFIG_511: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.STANDARD
+ }
+ }
+};
+export const LIBCONFIG_512: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.CIRCULAR
+ }
+ }
+};
+export const LIBCONFIG_513: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.BARS
+ }
+ }
+};
+export const LIBCONFIG_514: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.DOTS
+ }
+ }
+};
+export const LIBCONFIG_515: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.CUBE_FLIPPING
+ }
+ }
+};
+export const LIBCONFIG_516: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.CIRCLES
+ }
+ }
+};
+export const LIBCONFIG_517: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.EXPLOSING_SQUARES
+ }
+ }
+};
+
+export const LIBCONFIG_518: ModalLibConfig = {
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.DEFAULT
+ }
+};
+export const LIBCONFIG_519: ModalLibConfig = {
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.SIMPLE
+ }
+};
+export const LIBCONFIG_520: ModalLibConfig = {
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.ADVANCED
+ }
+};
+export const LIBCONFIG_521: ModalLibConfig = {
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.FULL
+ }
+};
+
+// default buttons but extUrl will open the link in a new tab instead of the current one
+// this requires to specify all buttons manually (also if they are not really custom)
+export const LIBCONFIG_522: ModalLibConfig = {
+ currentImageConfig: { downloadable: true },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.CUSTOM,
+ buttons: [
+ {
+ className: 'ext-url-image',
+ type: ButtonType.EXTURL,
+ extUrlInNewTab: true // <--- this is the important thing to understand this example
+ },
+ {
+ className: 'download-image',
+ type: ButtonType.DOWNLOAD
+ },
+ {
+ className: 'close-image',
+ type: ButtonType.CLOSE
+ }
+ ]
+ }
+};
+
+export const LIBCONFIG_523: ModalLibConfig = {
+ currentImageConfig: {
+ downloadable: true
+ },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.CUSTOM,
+ buttons: [
+ // KS_DEFAULT_BTN_ROTATE,
+ KS_DEFAULT_BTN_FULL_SCREEN,
+ KS_DEFAULT_BTN_DELETE,
+ KS_DEFAULT_BTN_EXTURL,
+ KS_DEFAULT_BTN_DOWNLOAD,
+ KS_DEFAULT_BTN_CLOSE
+ ]
+ }
+};
+
+export const LIBCONFIG_524: ModalLibConfig = {
+ currentImageConfig: {
+ downloadable: true
+ },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.CUSTOM,
+ buttons: [
+ {
+ className: 'fas fa-plus white',
+ type: ButtonType.CUSTOM,
+ ariaLabel: 'custom plus aria label',
+ title: 'custom plus title',
+ fontSize: '20px'
+ },
+ {
+ className: 'fas fa-times white',
+ type: ButtonType.CLOSE,
+ ariaLabel: 'custom close aria label',
+ title: 'custom close title',
+ fontSize: '20px'
+ },
+ {
+ className: 'fas fa-download white',
+ type: ButtonType.DOWNLOAD,
+ ariaLabel: 'custom download aria label',
+ title: 'custom download title',
+ fontSize: '20px'
+ },
+ {
+ className: 'fas fa-external-link-alt white',
+ type: ButtonType.EXTURL,
+ ariaLabel: 'custom exturl aria label',
+ title: 'custom exturl title',
+ fontSize: '20px'
+ }
+ ]
+ }
+};
+
+export const LIBCONFIG_525: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ mobileVisible: true
+ }
+};
+
+// Examples C
+export const LIBCONFIG_600: ModalLibConfig = {
+ keyboardConfig: {
+ esc: 'KeyQ',
+ left: 'ArrowDown',
+ right: 'ArrowUp'
+ }
+};
+
+export const LIBCONFIG_601: ModalLibConfig = {
+ currentImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.ALWAYS_VISIBLE,
+ imageText: 'Look this image ',
+ numberSeparator: ' of ',
+ beforeTextDescription: ' => '
+ }
+ }
+};
+
+export const LIBCONFIG_602: ModalLibConfig = {
+ currentImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.HIDE_IF_EMPTY,
+ imageText: 'Look this image ',
+ numberSeparator: ' of ',
+ beforeTextDescription: ' => '
+ }
+ }
+};
+
+export const LIBCONFIG_603: ModalLibConfig = {
+ currentImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.ALWAYS_HIDDEN,
+ // you should build this value programmatically with the result of (show)="..()" event
+ customFullDescription: 'Custom description of the current visible image'
+ // if customFullDescription !== undefined, all other fields will be ignored
+ // imageText: '',
+ // numberSeparator: '',
+ // beforeTextDescription: '',
+ }
+ }
+};
+
+export const LIBCONFIG_604: ModalLibConfig = {
+ currentImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.ALWAYS_VISIBLE,
+ // you should build this value programmatically with the result of (show)="..()" event
+ customFullDescription: 'Custom description of the current visible image'
+ // if customFullDescription !== undefined, all other fields will be ignored
+ // imageText: '',
+ // numberSeparator: '',
+ // beforeTextDescription: '',
+ }
+ }
+};
+
+export const LIBCONFIG_605: ModalLibConfig = {
+ currentImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.ALWAYS_VISIBLE,
+ imageText: 'Look this image ',
+ numberSeparator: ' of ',
+ beforeTextDescription: ' => '
+ }
+ }
+};
+
+export const LIBCONFIG_606: ModalLibConfig = {
+ currentImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.ALWAYS_VISIBLE,
+ imageText: 'Look this image ',
+ numberSeparator: ' of ',
+ beforeTextDescription: ' => ',
+ style: {
+ bgColor: 'rgba(255,0,0,.5)',
+ textColor: 'blue',
+ marginTop: '3px',
+ marginBottom: '0px',
+ marginLeft: '5px',
+ marginRight: '5px',
+ position: 'absolute',
+ top: '0px',
+ height: '25px'
+ // be careful to use width, in particular with % values
+ }
+ }
+ }
+};
+
+export const LIBCONFIG_607: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ number: 1
+ }
+};
+
+export const LIBCONFIG_608: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ number: 5
+ }
+};
+
+export const LIBCONFIG_609: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ arrows: false
+ }
+};
+
+export const LIBCONFIG_610: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ clickable: false
+ }
+};
+
+export const LIBCONFIG_611: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ size: { width: '30px', height: '30px' }
+ }
+};
+
+export const LIBCONFIG_612: ModalLibConfig = {
+ accessibilityConfig: {
+ backgroundAriaLabel: 'CUSTOM Modal gallery full screen background',
+ backgroundTitle: 'CUSTOM background title',
+
+ plainGalleryContentAriaLabel: 'CUSTOM Plain gallery content',
+ plainGalleryContentTitle: 'CUSTOM plain gallery content title',
+
+ modalGalleryContentAriaLabel: 'CUSTOM Modal gallery content',
+ modalGalleryContentTitle: 'CUSTOM modal gallery content title',
+
+ loadingSpinnerAriaLabel: 'CUSTOM The current image is loading. Please be patient.',
+ loadingSpinnerTitle: 'CUSTOM The current image is loading. Please be patient.',
+
+ mainContainerAriaLabel: 'CUSTOM Current image and navigation',
+ mainContainerTitle: 'CUSTOM main container title',
+ mainPrevImageAriaLabel: 'CUSTOM Previous image',
+ mainPrevImageTitle: 'CUSTOM Previous image',
+ mainNextImageAriaLabel: 'CUSTOM Next image',
+ mainNextImageTitle: 'CUSTOM Next image',
+
+ dotsContainerAriaLabel: 'CUSTOM Image navigation dots',
+ dotsContainerTitle: 'CUSTOM dots container title',
+ dotAriaLabel: 'CUSTOM Navigate to image number',
+
+ previewsContainerAriaLabel: 'CUSTOM Image previews',
+ previewsContainerTitle: 'CUSTOM previews title',
+ previewScrollPrevAriaLabel: 'CUSTOM Scroll previous previews',
+ previewScrollPrevTitle: 'CUSTOM Scroll previous previews',
+ previewScrollNextAriaLabel: 'CUSTOM Scroll next previews',
+ previewScrollNextTitle: 'CUSTOM Scroll next previews',
+
+ carouselContainerAriaLabel: 'Current image and navigation',
+ carouselContainerTitle: '',
+ carouselPrevImageAriaLabel: 'Previous image',
+ carouselPrevImageTitle: 'Previous image',
+ carouselNextImageAriaLabel: 'Next image',
+ carouselNextImageTitle: 'Next image',
+ carouselPreviewsContainerAriaLabel: 'Image previews',
+ carouselPreviewsContainerTitle: '',
+ carouselPreviewScrollPrevAriaLabel: 'Scroll previous previews',
+ carouselPreviewScrollPrevTitle: 'Scroll previous previews',
+ carouselPreviewScrollNextAriaLabel: 'Scroll next previews',
+ carouselPreviewScrollNextTitle: 'Scroll next previews'
+ }
+};
+
+export const LIBCONFIG_613: ModalLibConfig = {
+ currentImageConfig: {
+ navigateOnClick: false
+ }
+};
+
+// Examples D
+export const LIBCONFIG_701: ModalLibConfig = {
+ currentImageConfig: {
+ downloadable: true
+ },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.SIMPLE
+ }
+};
+
+export const LIBCONFIG_702: ModalLibConfig = {
+ currentImageConfig: {
+ invertSwipe: true
+ }
+};
+
+export const LIBCONFIG_703: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ sidePreviews: {
+ show: true,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ }
+};
+
+// Examples E
+export const LIBCONFIG_800: ModalLibConfig = {
+ slideConfig: {
+ playConfig: {
+ autoPlay: true,
+ interval: 5000,
+ pauseOnHover: true
+ }
+ }
+};
+
+export const LIBCONFIG_801: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ playConfig: {
+ autoPlay: true,
+ interval: 5000,
+ pauseOnHover: false
+ }
+ }
+};
+
+export const LIBCONFIG_802: ModalLibConfig = {
+ slideConfig: {
+ playConfig: {
+ autoPlay: true,
+ interval: 1000,
+ pauseOnHover: false
+ }
+ }
+};
+
+// Examples F
+export const LIBCONFIG_900: ModalLibConfig = {
+ slideConfig: {
+ infinite: false
+ },
+ currentImageConfig: {
+ loadingConfig: { enable: true, type: LoadingType.STANDARD } as LoadingConfig,
+ description: { strategy: DescriptionStrategy.ALWAYS_VISIBLE } as Description
+ }
+};
diff --git a/src/app/modal-gallery/modal-gallery.component.ts b/src/app/modal-gallery/modal-gallery.component.ts
new file mode 100644
index 00000000..1672e87b
--- /dev/null
+++ b/src/app/modal-gallery/modal-gallery.component.ts
@@ -0,0 +1,763 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { TemplateRef } from '@angular/core';
+import { ViewChild } from '@angular/core';
+import { Component, OnDestroy } from '@angular/core';
+import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
+
+import {
+ Action,
+ ButtonEvent,
+ ButtonType,
+ Image,
+ ImageModalEvent,
+ ModalGalleryService,
+ ModalGalleryRef,
+ ModalGalleryConfig,
+ ModalLibConfig
+} from '@ks89/angular-modal-gallery';
+import { Subscription } from 'rxjs';
+
+import * as libConfigs from './libconfigs';
+
+@Component({
+ selector: 'ks-modal-gallery-page',
+ templateUrl: './modal-gallery.html',
+ styleUrls: ['./modal-gallery.scss'],
+ standalone: false
+})
+export class ModalGalleryExampleComponent implements OnDestroy {
+ /**
+ * A custom template to illustrate the customization of previews rendering.
+ */
+ @ViewChild('previewsTemplate')
+ previewsTemplate?: TemplateRef;
+
+ imageIndex = 0;
+ galleryId = 1;
+ isPlaying = true;
+ // Examples A
+ CONFIG406: ModalLibConfig = libConfigs.LIBCONFIG_406;
+ CONFIG407: ModalLibConfig = libConfigs.LIBCONFIG_407;
+ CONFIG408: ModalLibConfig = libConfigs.LIBCONFIG_408;
+ // Examples B
+ CONFIG500: ModalLibConfig = libConfigs.LIBCONFIG_500;
+ CONFIG501: ModalLibConfig = libConfigs.LIBCONFIG_501;
+ CONFIG502: ModalLibConfig = libConfigs.LIBCONFIG_502;
+ CONFIG503: ModalLibConfig = libConfigs.LIBCONFIG_503;
+ CONFIG504: ModalLibConfig = libConfigs.LIBCONFIG_504;
+ CONFIG505: ModalLibConfig = libConfigs.LIBCONFIG_505;
+ CONFIG506: ModalLibConfig = libConfigs.LIBCONFIG_506;
+ CONFIG507: ModalLibConfig = libConfigs.LIBCONFIG_507;
+ CONFIG508: ModalLibConfig = libConfigs.LIBCONFIG_508;
+ CONFIG509: ModalLibConfig = libConfigs.LIBCONFIG_509;
+ CONFIG510: ModalLibConfig = libConfigs.LIBCONFIG_510;
+ CONFIG511: ModalLibConfig = libConfigs.LIBCONFIG_511;
+ CONFIG512: ModalLibConfig = libConfigs.LIBCONFIG_512;
+ CONFIG513: ModalLibConfig = libConfigs.LIBCONFIG_513;
+ CONFIG514: ModalLibConfig = libConfigs.LIBCONFIG_514;
+ CONFIG515: ModalLibConfig = libConfigs.LIBCONFIG_515;
+ CONFIG516: ModalLibConfig = libConfigs.LIBCONFIG_516;
+ CONFIG517: ModalLibConfig = libConfigs.LIBCONFIG_517;
+ CONFIG518: ModalLibConfig = libConfigs.LIBCONFIG_518;
+ CONFIG519: ModalLibConfig = libConfigs.LIBCONFIG_519;
+ CONFIG520: ModalLibConfig = libConfigs.LIBCONFIG_520;
+ CONFIG521: ModalLibConfig = libConfigs.LIBCONFIG_521;
+ CONFIG522: ModalLibConfig = libConfigs.LIBCONFIG_522;
+ CONFIG523: ModalLibConfig = libConfigs.LIBCONFIG_523;
+ CONFIG524: ModalLibConfig = libConfigs.LIBCONFIG_524;
+ CONFIG525: ModalLibConfig = libConfigs.LIBCONFIG_525;
+ // Examples C
+ CONFIG600: ModalLibConfig = libConfigs.LIBCONFIG_600;
+ CONFIG601: ModalLibConfig = libConfigs.LIBCONFIG_601;
+ CONFIG602: ModalLibConfig = libConfigs.LIBCONFIG_602;
+ CONFIG603: ModalLibConfig = libConfigs.LIBCONFIG_603;
+ CONFIG604: ModalLibConfig = libConfigs.LIBCONFIG_604;
+ CONFIG605: ModalLibConfig = libConfigs.LIBCONFIG_605;
+ CONFIG606: ModalLibConfig = libConfigs.LIBCONFIG_606;
+ CONFIG607: ModalLibConfig = libConfigs.LIBCONFIG_607;
+ CONFIG608: ModalLibConfig = libConfigs.LIBCONFIG_608;
+ CONFIG609: ModalLibConfig = libConfigs.LIBCONFIG_609;
+ CONFIG610: ModalLibConfig = libConfigs.LIBCONFIG_610;
+ CONFIG611: ModalLibConfig = libConfigs.LIBCONFIG_611;
+ CONFIG612: ModalLibConfig = libConfigs.LIBCONFIG_612;
+ CONFIG613: ModalLibConfig = libConfigs.LIBCONFIG_613;
+ // Examples D
+ CONFIG701: ModalLibConfig = libConfigs.LIBCONFIG_701;
+ CONFIG702: ModalLibConfig = libConfigs.LIBCONFIG_702;
+ CONFIG703: ModalLibConfig = libConfigs.LIBCONFIG_703;
+ // Examples E
+ CONFIG800: ModalLibConfig = libConfigs.LIBCONFIG_800;
+ CONFIG801: ModalLibConfig = libConfigs.LIBCONFIG_801;
+ CONFIG802: ModalLibConfig = libConfigs.LIBCONFIG_802;
+ // Example F
+ CONFIG900: ModalLibConfig = libConfigs.LIBCONFIG_900;
+
+ images: Image[] = [
+ new Image(0, {
+ img: '../assets/images/gallery/img1.jpg',
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/img2.jpg',
+ description: 'Description 2'
+ }),
+ new Image(
+ 2,
+ {
+ img: '../assets/images/gallery/img3.jpg',
+ description: 'Description 3',
+ extUrl: 'http://www.google.com'
+ },
+ {
+ img: '../assets/images/gallery/thumbs/img3.png',
+ title: 'custom title 2',
+ alt: 'custom alt 2',
+ ariaLabel: 'arial label 2'
+ }
+ ),
+ new Image(3, {
+ img: '../assets/images/gallery/img4.jpg',
+ description: 'Description 4',
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(4, { img: '../assets/images/gallery/img5.jpg' }, { img: '../assets/images/gallery/thumbs/img5.jpg' })
+ ];
+
+ emptyImagesArray: Image[] = [];
+
+ imagesRectNoTitles: Image[] = [
+ new Image(
+ 0,
+ { img: '../assets/images/gallery/milan-pegasus-gallery-statue.jpg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-milan-pegasus-gallery-statue.jpg', title: '' }
+ ),
+ new Image(
+ 1,
+ { img: '../assets/images/gallery/pexels-photo-47223.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-47223.jpg', title: '' }
+ ),
+ new Image(
+ 2,
+ { img: '../assets/images/gallery/pexels-photo-52062.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-52062.jpg', title: '' }
+ ),
+ new Image(
+ 3,
+ { img: '../assets/images/gallery/pexels-photo-66943.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-66943.jpg', title: '' }
+ ),
+ new Image(
+ 4,
+ { img: '../assets/images/gallery/pexels-photo-93750.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-93750.jpg', title: '' }
+ ),
+ new Image(
+ 5,
+ { img: '../assets/images/gallery/pexels-photo-94420.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-94420.jpg', title: '' }
+ ),
+ new Image(
+ 6,
+ { img: '../assets/images/gallery/pexels-photo-96947.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-96947.jpg', title: '' }
+ )
+ ];
+
+ fallbackImages: Image[] = [
+ new Image(0, {
+ // this file is not available so the browser returns an error
+ img: '../assets/images/gallery/UNEXISTING_IMG1.jpg',
+ // because the img above doesn't exists, the library will use this file
+ fallbackImg: '../assets/images/gallery/fallback1.jpg'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/UNEXISTING_IMG2.jpg',
+ fallbackImg: '../assets/images/gallery/fallback2.jpg'
+ }),
+ new Image(
+ 2,
+ {
+ img: '../assets/images/gallery/UNEXISTING_IMG3.jpg',
+ fallbackImg: '../assets/images/gallery/fallback3.jpg'
+ },
+ {
+ img: '../assets/images/gallery/thumbs/UNEXISTING_IMG3.png',
+ fallbackImg: '../assets/images/gallery/fallback3.jpg'
+ }
+ ),
+ new Image(3, {
+ img: '../assets/images/gallery/UNEXISTING_IMG4.jpg',
+ fallbackImg: '../assets/images/gallery/fallback4.jpg'
+ }),
+ new Image(
+ 4,
+ {
+ img: '../assets/images/gallery/UNEXISTING_IMG5.jpg',
+ fallbackImg: '../assets/images/gallery/fallback5.jpg'
+ },
+ {
+ img: '../assets/images/gallery/thumbs/UNEXISTING_IMG5.jpg',
+ fallbackImg: '../assets/images/gallery/fallback5.jpg'
+ }
+ )
+ ];
+
+ // array of images (obviously with different id) where paths are the same.
+ // to prevent caching issues I have to append '?index'.
+ sameImages: Image[] = [
+ new Image(0, {
+ img: '../assets/images/gallery/img1.jpg?1',
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/img1.jpg?2',
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(2, {
+ img: '../assets/images/gallery/img1.jpg?3',
+ extUrl: 'http://www.google.com'
+ })
+ ];
+
+ // example of a png converted into base64 using https://www.base64-image.de/ or other similar websites
+ base64String =
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABN0lEQV' +
+ 'R4nO3SQQ2AQBDAwAVlaMEhCkAV' +
+ 'b2RcQmcU9NEZAAAAAOD/tvN675k5VoewxLOvLmAtA8QZIM4AcQaIM0CcAeIMEGeAOAPEGSDOAHEGiDNAnAHiDBBngDgDxBkgzgBxBogzQJwB4gwQZ4A4A8QZIM4AcQaIM0C' +
+ 'cAeIMEGeAOAPEGSDOAHEGiDNAnAHiDBBngDgDxBkgzgBxBogzQJwB4gwQZ4A4A8QZIM4AcQaIM0CcAeIMEGeAOAPEGSDOAHEGiDNAnAHiDBBngDgDxBkgzgBxBogzQJwB4g' +
+ 'wQZ4A4A8QZIM4AcQaIM0CcAeIMEGeAOAPEGSDOAHEGiDNAnAHiDBBngDgDxBkgzgBxBogzQJwB4gwQZ4A4A8QZIM4AcQaIM0CcAeIMEGeAOAPEGQAAAAAA4Pc+8asEoPPGq' +
+ 'xUAAAAASUVORK5CYII';
+
+ base64RedString =
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAY1BMVEX/AAD/////WVn/+vr/qan/Nzf/ERH/2tr/s7P/KSn/' +
+ '7+//vr7/0ND/W1v/6+v/m5v/4+P/U1P/HR3/o6P/rq7/g4P/k5P/t7f/dXX/SEj/zMz/ZWX/h4f/bm7/amr/np7/yMhDG/2oAAAC8ElEQVR4nO3dC3KqQBCF4WkHERHFRyKIL/' +
+ 'a/ymDuVYMMFipTbbfnW8H5S4lQVGUMaWe4B3iHQvlQKB8K5UOhfCiUD4XyoVA+FJ7Myijd5dvBO9nmuzQqZ68X2mI9NO9suC7s84VxNuAO6GSQxU8VJvuQe3pn4T55uLDYcK9+' +
+ '0KZ4qDB574vPbej+HF2Fcc499km563p0FAbcQ18QdCi0B+6VLzk0fjtuC0dj7o0vGo/uF064B/agvFcYca/rRdReeOTe1pNjW6HkP6J1gbtQwzV4NnEVJtyrepU0C2M599ldhH' +
+ 'GjcMq9qWfT28KUe1Hv0nrhnHuPB/Na4YJ7jgeLv4UZ9xovsmuhXXKP8WJpL4Ur7i2erC6Fun4Kr8Jz4Rf3Em++/hdKf+htN/5XqOuGtC75LfzmnuHR96nQ6v2SVl9TWxVq/pKevq' +
+ 'aG1twjvFpXhTLeLz1rQMZyb/DMmhH3BM9GRudjxVVmtN51n62M1DdpXeVG2rveR22MxLe9jxgazfdsJ2Oj9en3THsfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
+ 'AAAAAAAAAAAAAAgHba/+98+AFnI+g/30L/GSX6z5nRf1aQ/vOe9J/Zpf/cNf1n533A+Yf6z7DUfw6p/rNkVX9Nkw850/kDzuXWf7Y6ab37Xl0K7ZJ7ixdLeykknQ8YGV0LacG9xo' +
+ 'MF/S2cc8/xYF4rpJR7T+9SqhfSlHtRz6Z0Wxjr+lEM40ahstvThJqFNOFe1aMJuQop4N7Vm4DchXTkXtaTI7UVUsS9rRcRtRequBZLuldII+mPw+MR3S8ke+De+JKDvQ1qFMr+kx' +
+ 'o0cxyFFEt945bHjhpXYXV/I/HN8DBxtrgLiQpp74Y3RUtJW2H1Oe7l3IuHe/fnd7+wuh4zGe+lBpnr+utSWLHF+r0vyeG6aPw+PFT4a1ZG6S7fDt7JNt+lUTnrsL5LoWwolA+F8q' +
+ 'FQPhTKh0L5UCgfCuVDoXw/lnQz7dm7GjoAAAAASUVORK5CYII=';
+ base64GreenString =
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADIAgMAAADQNkYNAAAADFBMVEUAAAAy/ysy/ysy/ysyTcibAAAAA3RSTlMA2r/af0d' +
+ 'WAAAAQUlEQVRo3u3YMREAMAzEsJAMyZJsMXy3XORdBFySJK3qxFXH1Y1DEARBEARBEARBEARBEARBkNmk436mvSRJ0o4eOKL2P81eyn8AAAAASUVORK5CYII=';
+
+ base64Image: SafeResourceUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.base64String);
+ base64RedImage: SafeResourceUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.base64RedString);
+ base64GreenImage: SafeResourceUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.base64GreenString);
+
+ imagesBase64: Image[] = [
+ new Image(0, {
+ img: this.base64Image,
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(1, {
+ img: this.base64GreenImage,
+ description: 'Description 2'
+ }),
+ new Image(
+ 2,
+ {
+ img: this.base64RedImage,
+ description: 'Description 3',
+ extUrl: 'http://www.google.com'
+ },
+ {
+ img: this.base64RedImage,
+ title: 'custom title 2',
+ alt: 'custom alt 2',
+ ariaLabel: 'arial label 2'
+ }
+ )
+ ];
+
+ imagesCustomDownloadFileName: Image[] = [
+ new Image(0, {
+ img: '../assets/images/gallery/img1.jpg',
+ downloadFileName: 'first-img.jpg'
+ }),
+ new Image(1, {
+ img: this.base64Image,
+ downloadFileName: 'second-img-base64.jpg'
+ })
+ ];
+
+ imagesHtmlDescriptions: Image[] = [
+ new Image(0, {
+ img: '../assets/images/gallery/img1.jpg',
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/img2.jpg',
+ description: 'This is the description number 2 '
+ }),
+ new Image(
+ 2,
+ {
+ img: '../assets/images/gallery/img3.jpg',
+ description: '',
+ extUrl: 'http://www.google.com'
+ },
+ {
+ img: '../assets/images/gallery/thumbs/img3.png',
+ title: 'custom title 2',
+ alt: 'custom alt 2',
+ ariaLabel: 'arial label 2'
+ }
+ ),
+ new Image(3, {
+ img: '../assets/images/gallery/img4.jpg',
+ description: 'Description 4',
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(4, { img: '../assets/images/gallery/img5.jpg' }, { img: '../assets/images/gallery/thumbs/img5.jpg' })
+ ];
+
+ imagesRect: Image[] = [
+ new Image(
+ 0,
+ {
+ img: '../assets/images/gallery/milan-pegasus-gallery-statue.jpg',
+ description: 'Description 1'
+ },
+ { img: '../assets/images/gallery/thumbs/t-milan-pegasus-gallery-statue.jpg' }
+ ),
+ new Image(1, { img: '../assets/images/gallery/pexels-photo-47223.jpeg' }, { img: '../assets/images/gallery/thumbs/t-pexels-photo-47223.jpg' }),
+ new Image(
+ 2,
+ {
+ img: '../assets/images/gallery/pexels-photo-52062.jpeg',
+ description: 'Description 3'
+ },
+ {
+ img: '../assets/images/gallery/thumbs/t-pexels-photo-52062.jpg',
+ description: 'Description 3'
+ }
+ ),
+ new Image(
+ 3,
+ {
+ img: '../assets/images/gallery/pexels-photo-66943.jpeg',
+ description: 'Description 4'
+ },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-66943.jpg' }
+ ),
+ new Image(4, { img: '../assets/images/gallery/pexels-photo-93750.jpeg' }, { img: '../assets/images/gallery/thumbs/t-pexels-photo-93750.jpg' }),
+ new Image(
+ 5,
+ {
+ img: '../assets/images/gallery/pexels-photo-94420.jpeg',
+ description: 'Description 6'
+ },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-94420.jpg' }
+ ),
+ new Image(6, { img: '../assets/images/gallery/pexels-photo-96947.jpeg' }, { img: '../assets/images/gallery/thumbs/t-pexels-photo-96947.jpg' })
+ ];
+
+ imagesMixedSizes: Image[] = [
+ new Image(0, {
+ img: '../assets/images/gallery/pexels-photo-135230.png',
+ description: 'Description 1'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/pexels-photo-547115.jpeg'
+ }),
+ new Image(2, {
+ img: '../assets/images/gallery/pexels-photo-556664.jpeg',
+ description: 'Description 3'
+ }),
+ new Image(3, {
+ img: '../assets/images/gallery/pexels-photo-787594.jpeg',
+ description: 'Description 4'
+ }),
+ new Image(4, {
+ img: '../assets/images/gallery/pexels-photo-803105.jpeg'
+ })
+ ];
+
+ // example of images with small previews (they are different files) to show
+ // loading spinners
+ imagesForLoadingSpinner: Image[] = [
+ new Image(
+ 0,
+ {
+ img: '../assets/images/loading-spinner-samples/pexels-photo-74506.jpg'
+ },
+ { img: '../assets/images/loading-spinner-samples/pexels-photo-74506-thumb.jpg' }
+ ),
+ new Image(
+ 1,
+ {
+ img: '../assets/images/loading-spinner-samples/pexels-photo-106006.jpg'
+ },
+ { img: '../assets/images/loading-spinner-samples/pexels-photo-106006-thumb.jpg' }
+ ),
+ new Image(
+ 2,
+ {
+ img: '../assets/images/loading-spinner-samples/pexels-photo-464336.jpg'
+ },
+ { img: '../assets/images/loading-spinner-samples/pexels-photo-464336-thumb.jpg' }
+ ),
+ new Image(
+ 3,
+ {
+ img: '../assets/images/loading-spinner-samples/pexels-photo.jpg'
+ },
+ { img: '../assets/images/loading-spinner-samples/pexels-photo-thumb.jpg' }
+ ),
+ new Image(
+ 4,
+ {
+ img: '../assets/images/loading-spinner-samples/traffic-highway-lights-night-56891.jpg'
+ },
+ { img: '../assets/images/loading-spinner-samples/traffic-highway-lights-night-56891-thumb.jpg' }
+ )
+ ];
+
+ // array with a single image inside (the first one)
+ singleImage: Image[] = [this.images[0]];
+
+ imagesInfiniteAutoAdd: Image[] = [
+ new Image(0, {
+ img: '../assets/images/gallery/img1.jpg?1',
+ extUrl: 'http://www.google.com'
+ })
+ ];
+
+ private count = 0;
+
+ // subscriptions to receive events from the gallery
+ // REMEMBER TO call unsubscribe(); in ngOnDestroy (see below)
+ private closeSubscription: Subscription | undefined;
+ private showSubscription: Subscription | undefined;
+ private firstImageSubscription: Subscription | undefined;
+ private lastImageSubscription: Subscription | undefined;
+ private hasDataSubscription: Subscription | undefined;
+ private buttonBeforeHookSubscription: Subscription | undefined;
+ private buttonAfterHookSubscription: Subscription | undefined;
+
+ // this variable is used only for example of auto navigation
+ // tslint:disable-next-line:no-any
+ private timeout: any;
+
+ constructor(private modalGalleryService: ModalGalleryService, private sanitizer: DomSanitizer) {}
+
+ openModalWithAutoClose(id: number, imagesArrayToUse: Image[], imageIndex: number, libConfig?: ModalLibConfig): void {
+ const imageToShow: Image = imagesArrayToUse[imageIndex];
+ const dialogRef: ModalGalleryRef = this.modalGalleryService.open({
+ id,
+ images: imagesArrayToUse,
+ currentImage: imageToShow,
+ libConfig
+ } as ModalGalleryConfig) as ModalGalleryRef;
+
+ this.showSubscription = dialogRef.show$.subscribe((event: ImageModalEvent) => {
+ console.log('OUTPUT - show$: ', event);
+ const galleryId: number = event.galleryId;
+ console.log(`onShowAutoCloseExample with id=${galleryId} action: ` + Action[event.action]);
+ console.log('onShowAutoCloseExample result:' + event.result);
+ console.log('Starting timeout of 3 seconds to close modal gallery automatically');
+ // clear previous timeout
+ clearTimeout(this.timeout);
+ this.timeout = setTimeout(() => {
+ console.log('setTimeout end - closing gallery with id=' + galleryId);
+ this.modalGalleryService.close(galleryId, false);
+ }, 3000);
+ });
+ }
+
+ addRandomImage(): void {
+ // add to images array
+ const imageToCopy: Image = this.images[Math.floor(Math.random() * this.images.length)];
+ const newImage: Image = new Image(this.images.length - 1 + 1, imageToCopy.modal, imageToCopy.plain);
+ this.images = [...this.images, newImage];
+ // add also to imagesRect
+ const imageRectToCopy: Image = this.imagesRect[Math.floor(Math.random() * this.imagesRect.length)];
+ const newImageRect: Image = new Image(this.imagesRect.length - 1 + 1, imageRectToCopy.modal, imageRectToCopy.plain);
+ this.imagesRect = [...this.imagesRect, newImageRect];
+ // add also to imagesMixedSizes
+ const imageMixToCopy: Image = this.imagesMixedSizes[Math.floor(Math.random() * this.imagesMixedSizes.length)];
+ const newImageMix: Image = new Image(this.imagesMixedSizes.length - 1 + 1, imageMixToCopy.modal, imageMixToCopy.plain);
+ this.imagesMixedSizes = [...this.imagesMixedSizes, newImageMix];
+ }
+
+ openModal(id: number, imagesArrayToUse: Image[], imageIndex: number, libConfig?: ModalLibConfig): void {
+ if(imagesArrayToUse.length === 0) {
+ console.error('Cannot open modal-gallery because images array cannot be empty');
+ return;
+ }
+ if(imageIndex > imagesArrayToUse.length - 1) {
+ console.error('Cannot open modal-gallery because imageIndex must be valid');
+ return;
+ }
+ const imageToShow: Image = imagesArrayToUse[imageIndex];
+ const dialogRef: ModalGalleryRef = this.modalGalleryService.open({
+ id,
+ images: imagesArrayToUse,
+ currentImage: imageToShow,
+ libConfig,
+ } as ModalGalleryConfig) as ModalGalleryRef;
+ }
+
+ openModalWithOutputs(id: number, imagesArrayToUse: Image[], imageIndex: number, libConfig?: ModalLibConfig): void {
+ const imageToShow: Image = imagesArrayToUse[imageIndex];
+ const dialogRef: ModalGalleryRef = this.modalGalleryService.open({
+ id,
+ images: imagesArrayToUse,
+ currentImage: imageToShow,
+ libConfig
+ } as ModalGalleryConfig) as ModalGalleryRef;
+ this.closeSubscription = dialogRef.close$.subscribe((event: ImageModalEvent) => {
+ console.log('OUTPUT - close$: ', event);
+ });
+ this.showSubscription = dialogRef.show$.subscribe((event: ImageModalEvent) => {
+ console.log('OUTPUT - show$: ', event);
+ });
+ this.firstImageSubscription = dialogRef.firstImage$.subscribe((event: ImageModalEvent) => {
+ console.log('OUTPUT - firstImage$: ', event);
+ });
+ this.lastImageSubscription = dialogRef.lastImage$.subscribe((event: ImageModalEvent) => {
+ console.log('OUTPUT - lastImage$: ', event);
+ });
+ this.hasDataSubscription = dialogRef.hasData$.subscribe((event: ImageModalEvent) => {
+ // angular-modal-gallery will emit this event if it will load successfully input images
+ console.log('OUTPUT - hasData$: ', event);
+ });
+ this.buttonBeforeHookSubscription = dialogRef.buttonBeforeHook$.subscribe((event: ButtonEvent) => {
+ console.log('OUTPUT - buttonBeforeHook$: ', event);
+ if (!event || !event.button) {
+ return;
+ }
+ // Invoked after a click on a button, but before that the related
+ // action is applied.
+ // For instance: this method will be invoked after a click
+ // of 'close' button, but before that the modal gallery
+ // will be really closed.
+ if (event.button.type === ButtonType.DELETE) {
+ // remove the current image and reassign all other to the array of images
+ console.log('delete in app with images count ' + this.images.length);
+ this.images = this.images.filter((val: Image) => event.image && val.id !== event.image.id);
+ }
+ });
+ this.buttonAfterHookSubscription = dialogRef.buttonAfterHook$.subscribe((event: ButtonEvent) => {
+ if (!event || !event.button) {
+ return;
+ }
+ // Invoked after both a click on a button and its related action.
+ // For instance: this method will be invoked after a click
+ // of 'close' button, but before that the modal gallery
+ // will be really closed.
+ });
+ }
+
+ openModalWithDeleteButton(id: number, imagesArrayToUse: Image[], imageIndex: number, libConfig?: ModalLibConfig): void {
+ const imageToShow: Image = imagesArrayToUse[imageIndex];
+ const dialogRef: ModalGalleryRef = this.modalGalleryService.open({
+ id,
+ images: [...imagesArrayToUse],
+ currentImage: Object.assign({}, imageToShow),
+ libConfig
+ } as ModalGalleryConfig) as ModalGalleryRef;
+ this.buttonBeforeHookSubscription = dialogRef.buttonBeforeHook$.subscribe((event: ButtonEvent) => {
+ console.log('OUTPUT - buttonBeforeHook$:', event);
+ if (!event || !event.button) {
+ return;
+ }
+ // Invoked after a click on a button, but before that the related
+ // action is applied.
+ // For instance: this method will be invoked after a click
+ // of 'close' button, but before that the modal gallery
+ // will be really closed.
+ });
+ this.buttonAfterHookSubscription = dialogRef.buttonAfterHook$.subscribe((event: ButtonEvent) => {
+ console.log('OUTPUT - buttonAfterHook$:', event);
+ if (!event || !event.button) {
+ return;
+ }
+ if (event.button.type === ButtonType.DELETE) {
+ // remove the current image and reassign all other to the array of images
+ this.images = this.images.filter((val: Image) => event.image && val.id !== event.image.id);
+ this.modalGalleryService.updateModalImages(this.images);
+ }
+ // Invoked after both a click on a button and its related action.
+ // For instance: this method will be invoked after a click
+ // of 'close' button, but before that the modal gallery
+ // will be really closed.
+ });
+ }
+
+ openModalWithAddButton(id: number, imagesArrayToUse: Image[], imageIndex: number, libConfig?: ModalLibConfig): void {
+ const imageToShow: Image = imagesArrayToUse[imageIndex];
+ const dialogRef: ModalGalleryRef = this.modalGalleryService.open({
+ id,
+ images: imagesArrayToUse,
+ currentImage: imageToShow,
+ libConfig
+ } as ModalGalleryConfig) as ModalGalleryRef;
+ this.buttonBeforeHookSubscription = dialogRef.buttonBeforeHook$.subscribe((event: ButtonEvent) => {
+ if (!event || !event.button) {
+ return;
+ }
+ // Invoked after a click on a button, but before that the related
+ // action is applied.
+
+ if (event.button.type === ButtonType.CUSTOM) {
+ console.log('adding a new random image at the end');
+ this.addRandomImage();
+ setTimeout(() => {
+ this.modalGalleryService.updateModalImages(this.images);
+ }, 0);
+ }
+ });
+ this.buttonAfterHookSubscription = dialogRef.buttonAfterHook$.subscribe((event: ButtonEvent) => {
+ console.log('OUTPUT - buttonAfterHook$:', event);
+ if (!event || !event.button) {
+ return;
+ }
+ // Invoked after both a click on a button and its related action.
+ // For instance: this method will be invoked after a click
+ // of 'close' button, but before that the modal gallery
+ // will be really closed.
+ });
+ }
+
+ openModalWithAutoAdd(id: number, imagesArrayToUse: Image[], imageIndex: number, libConfig?: ModalLibConfig): void {
+ const imageToShow: Image = imagesArrayToUse[imageIndex];
+ const dialogRef: ModalGalleryRef = this.modalGalleryService.open({
+ id,
+ images: imagesArrayToUse,
+ currentImage: imageToShow,
+ libConfig
+ } as ModalGalleryConfig) as ModalGalleryRef;
+ this.showSubscription = dialogRef.show$.subscribe((event: ImageModalEvent) => {
+ console.log('OUTPUT - show$: ', event);
+ if (this.count !== 0) {
+ return;
+ }
+ const interval = setInterval(() => {
+ const imageToCopy: Image = this.images[Math.floor(Math.random() * this.images.length)];
+ const newImage: Image = new Image(this.imagesInfiniteAutoAdd.length - 1 + 1, imageToCopy.modal, imageToCopy.plain);
+ newImage.modal.img += `?${this.imagesInfiniteAutoAdd.length + 1}`;
+ this.imagesInfiniteAutoAdd = [...this.imagesInfiniteAutoAdd, newImage];
+ this.modalGalleryService.updateModalImages(this.imagesInfiniteAutoAdd);
+ this.count++;
+ if (this.count === 4) {
+ clearInterval(interval);
+ }
+ }, 2000);
+ });
+ }
+
+ openModalWithAutoUpdate(id: number, imagesArrayToUse: Image[], imageIndex: number, libConfig?: ModalLibConfig): void {
+ const imageToShow: Image = imagesArrayToUse[imageIndex];
+ const dialogRef: ModalGalleryRef = this.modalGalleryService.open({
+ id,
+ images: imagesArrayToUse,
+ currentImage: imageToShow,
+ libConfig
+ } as ModalGalleryConfig) as ModalGalleryRef;
+ this.showSubscription = dialogRef.show$.subscribe((event: ImageModalEvent) => {
+ console.log('OUTPUT - show$: ', event);
+ if (this.count !== 0) {
+ return;
+ }
+ const indexToRefresh = 1;
+ const image: Image = new Image(1, {
+ img: '../assets/images/gallery/img5.jpg',
+ description: 'Description 2 updated with imag5.jpg'
+ });
+
+ console.log('updating image at index ' + indexToRefresh + ', after 4 seconds');
+
+ // create the new array of images with the updated image inside
+ const newImages: Image[] = [...this.images];
+ newImages[indexToRefresh] = image;
+
+ setTimeout(() => {
+ this.modalGalleryService.updateModalImages(newImages);
+ console.log('image updated successfully!');
+ }, 4000);
+ });
+ }
+
+ autoPlayButton(config: ModalLibConfig): boolean {
+ this.isPlaying = !this.isPlaying;
+ if (config && config.slideConfig && config.slideConfig.playConfig) {
+ config.slideConfig.playConfig.autoPlay = this.isPlaying;
+ }
+ return this.isPlaying;
+ }
+
+ trackById(index: number, item: Image): number {
+ return item.id;
+ }
+
+ openModalWithPreviewsTemplate(id: number, imagesArrayToUse: Image[], imageIndex: number, libConfig?: ModalLibConfig): void {
+ if(imagesArrayToUse.length === 0) {
+ console.error('Cannot open modal-gallery because images array cannot be empty');
+ return;
+ }
+ if(imageIndex > imagesArrayToUse.length - 1) {
+ console.error('Cannot open modal-gallery because imageIndex must be valid');
+ return;
+ }
+ const imageToShow: Image = imagesArrayToUse[imageIndex];
+ const dialogRef: ModalGalleryRef = this.modalGalleryService.open({
+ id,
+ images: imagesArrayToUse,
+ currentImage: imageToShow,
+ libConfig,
+ previewsTemplate: this.previewsTemplate,
+ } as ModalGalleryConfig) as ModalGalleryRef;
+ }
+
+ ngOnDestroy(): void {
+ // release resources to prevent memory leaks and unexpected behaviours
+ if (this.closeSubscription) {
+ this.closeSubscription.unsubscribe();
+ }
+ if (this.showSubscription) {
+ this.showSubscription.unsubscribe();
+ }
+ if (this.firstImageSubscription) {
+ this.firstImageSubscription.unsubscribe();
+ }
+ if (this.lastImageSubscription) {
+ this.lastImageSubscription.unsubscribe();
+ }
+ if (this.hasDataSubscription) {
+ this.hasDataSubscription.unsubscribe();
+ }
+ if (this.buttonBeforeHookSubscription) {
+ this.buttonBeforeHookSubscription.unsubscribe();
+ }
+ if (this.buttonAfterHookSubscription) {
+ this.buttonAfterHookSubscription.unsubscribe();
+ }
+ }
+}
diff --git a/src/app/modal-gallery/modal-gallery.html b/src/app/modal-gallery/modal-gallery.html
new file mode 100644
index 00000000..2a264c8b
--- /dev/null
+++ b/src/app/modal-gallery/modal-gallery.html
@@ -0,0 +1,325 @@
+Modal Gallery
+
+
+If you want, you can add a random image to every example
+ Add image
+
+
+
+
WARNING: remember to use always different ids across your application!!!
+
+
+
+Minimal examples
+
+ A1 - (id=400) - Minimal demo - all defaults (images array)
+ Open modal gallery at index={{imageIndex}}
+
+
+ A1bis - (id=401) - Minimal demo - all defaults with rect images (imagesRect array)
+ Open modal gallery at index={{imageIndex}}
+
+
+ A1tris - (id=402) - Minimal demo - all defaults with images of different sizes (imagesMixedSizes array)
+ Open modal gallery at index={{imageIndex}}
+
+
+ A2 - (id=403) - Minimal demo - listen for events
+ I added 'openModalWithOutputs()' public method to listen for events
+ Open modal gallery at index={{imageIndex}}
+
+
+ A3 - (id=405) - Minimal demo - single image (singleImage array)
+ Open modal gallery
+
+
+ A4 - (id=406) - Minimal demo - single image with infinite sliding enabled (to fix issue #156)
+ Open modal gallery
+
+
+ A5 - (id=407) - Minimal demo - two images with infinite sliding enabled (to fix issue #156)
+ Open modal gallery at index={{imageIndex}}
+
+
+ A6 - (id=408) - Minimal demo - three image with infinite sliding enabled (to fix issue #156)
+ Open modal gallery at index={{imageIndex}}
+
+
+ A7 - (id=409) - Minimal demo - use fallback images when it's not possible to load normal images (to fix issue #194)
+ Open modal gallery
+
+
+
+
+Simple examples
+
+ B1 - (id=500) - Simple demo - only current image and buttons (previews and dots are hidden)
+ Open modal gallery at index={{imageIndex}}
+
+
+ B2 - (id=501) - Simple demo - only current image (buttons, previews and dots are hidden)
+ Open modal gallery at index={{imageIndex}}
+
+
+ B3 - (id=502) - Simple demo - only current image (side previews, buttons, previews and dots are hidden)
+ Open modalgallery at index={{imageIndex}}
+
+
+ B3bis - (id=503) - Simple demo - custom preview size
+ Open modalgallery at index={{imageIndex}}
+
+
+ B4 - (id=504) - Simple demo - disable closeOutside
+ Open modalgallery at index={{imageIndex}}
+
+
+ B5 - (id=505) - Simple demo - no downloadable at all
+ Open modalgallery at index={{imageIndex}}
+
+
+ B6 - (id=506) - Simple demo - no download button (only with keyboard)
+ Open modalgallery at index={{imageIndex}}
+
+
+ B7 - (id=507) - Simple demo - download with both button and keyboard
+ Open modalgallery at index={{imageIndex}}
+
+
+ B8 - (id=508) - Simple demo - infinite sliding but NO side previews
+ Open modalgallery at index={{imageIndex}}
+
+
+ B9 - (id=509) - Simple demo - infinite sliding and side previews
+ Open modalgallery at index={{imageIndex}}
+
+
+ B10 - (id=510) - Simple demo - disable loading spinner
+ Open modalgallery at index={{imageIndex}}
+
+
+ B11 - (id=511) - Simple demo - loading spinner of type Standard
+ Open modalgallery at index={{imageIndex}}
+
+
+ B12 - (id=512) - Simple demo - loading spinner of type Circular
+ Open modalgallery at index={{imageIndex}}
+
+
+ B13 - (id=513) - Simple demo - loading spinner of type Bars
+ Open modalgallery at index={{imageIndex}}
+
+
+ B14 - (id=514) - Simple demo - loading spinner of type Dots
+ Open modalgallery at index={{imageIndex}}
+
+
+ B15 - (id=515) - Simple demo - loading spinner of type Cube Flipping
+ Open modalgallery at index={{imageIndex}}
+
+
+ B16 - (id=516) - Simple demo - loading spinner of type Circles
+ Open modalgallery at index={{imageIndex}}
+
+
+ B17 - (id=517) - Simple demo - loading spinner of type Explosing Squares
+ Open modalgallery at index={{imageIndex}}
+
+
+
+ B18 - (id=518) - Simple demo - buttons config DEFAULT strategy (only close)
+ Open modalgallery at index={{imageIndex}}
+
+
+ B19 - (id=519) - Simple demo - buttons config SIMPLE strategy (close and download)
+ Open modalgallery at index={{imageIndex}}
+
+
+ B20 - (id=520) - Simple demo - buttons config ADVANCED strategy (close, download and exturl in the current
+ tab)
+ Open modalgallery at index={{imageIndex}}
+
+
+ B21 - (id=521) - Simple demo - buttons config FULL strategy (all buttons)
+ I added also 'openModalWithDeleteButton()' public method to support delete button
+ Open modalgallery at index={{imageIndex}}
+
+
+ B22 - (id=522) - Simple demo - buttons config CUSTOM strategy with exturl in ANOTHER TAB ('_blank')
+ Open modalgallery at index={{imageIndex}}
+
+
+ B23 - (id=523) - Simple demo - buttons config CUSTOM but with all default buttons already included in the library
+ I added also 'openModalWithDeleteButton()' public method to support delete button
+ Open modalgallery at index={{imageIndex}}
+
+
+ B24 - (id=524) - Simple demo - buttons config CUSTOM strategy with Font Awesome 5
+ I added also 'openModalWithAddButton()' public method to support delete button
+
+ Open modalgallery at index={{imageIndex}}
+
+
+ B25 - (id=525) - Previews visible on mobile screen
+ Added PreviewConfig.mobileVisible configuration param
+
+ Open modalgallery at index={{imageIndex}}
+
+
+
+
+Advanced examples
+
+ C1 - (id=600) - Advanced demo - custom keyboard ('up'/'down' arrows and 'q' to close)
+ Open modalgallery at index={{imageIndex}}
+
+
+ C2 - (id=601) - Advanced demo - custom description always visible
+ Open modalgallery at index={{imageIndex}}
+
+
+ C3 - (id=602) - Advanced demo - custom description hide if empty
+ Open modalgallery at index={{imageIndex}}
+
+
+ C4 - (id=603) - Advanced demo - custom description always hidden
+ Open modalgallery at index={{imageIndex}}
+
+
+ C5 - (id=604) - Advanced demo - custom FULL description always visible
+ Open modalgallery at index={{imageIndex}}
+
+
+ C6 - (id=605) - Advanced demo - custom HTML description always visible
+ Open modalgallery at index={{imageIndex}}
+
+
+ C7 - (id=606) - Advanced demo - custom description style (always visible)
+ Open modalgallery at index={{imageIndex}}
+
+
+
+ C8 - (id=607) - Advanced demo - preview custom configuration with 1 image (clickable)
+ Open modalgallery at index={{imageIndex}}
+
+
+
+ C9 - (id=608) - Advanced demo - preview custom configuration with 5 images (clickable)
+ Open modalgallery at index={{imageIndex}}
+
+
+
+ C10 - (id=609) - Advanced demo - preview custom configuration without arrows (clickable)
+ Open modalgallery at index={{imageIndex}}
+
+
+ C11 - (id=610) - Advanced demo - preview custom configuration not clickable
+ Open modalgallery at index={{imageIndex}}
+
+
+ C12 - (id=611) - Advanced demo - preview custom configuration with custom size
+ Open modalgallery at index={{imageIndex}}
+
+
+
+ C13 - (id=612) - Advanced demo - accessibility config
+ Open modalgallery at index={{imageIndex}}
+
+
+
+ C14 - (id=613) - Advanced demo - disable clicks on current image in modal-image
+ Open modalgallery at index={{imageIndex}}
+
+
+
+ C15 - (id=614) - Advanced demo - after 3 seconds it closes modal gallery automatically with galleryService close method
+ Attention: please check the console to understand what it's happening!
+ Timer will clear and restart every time you show another image!
+ Open modalgallery at index={{imageIndex}}
+
+
+
+
+
+Other examples
+
+ D1 - (id=700) - Other demo - base64 images
+ Open modalgallery at index={{imageIndex}}
+
+
+
+ D2 - (id=701) - Other demo - custom file name, used when downloaded
+ Open modalgallery at index={{imageIndex}}
+
+
+
+ D3 - (id=702) - Other demo - invert touchscreen swipe direction
+ Open modalgallery at index={{imageIndex}}
+
+
+
+ D4 - (id=703) - Other demo - infinite sliding and automatic add of images when the gallery is visible
+ Open modalgallery at index={{imageIndex}}
+
+
+
+ D5 - (id=704) - Other demo - automatic update of the second image (index=1) via gallery service (open the second image and wait some seconds to see the result)
+ Open modalgallery at index={{imageIndex}}
+
+
+
+ D6 - (id=705) - Other demo - remove title attribute on images
+ Open modalgallery at index={{imageIndex}}
+
+
+
+ D7 - (id=706) - Other demo - modal gallery with an empty images array
+ Open modalgallery at index={{imageIndex}}
+
+
+
+
+
+AutoPlay examples
+
+ E1 - (id=800) - AutoPlay demo - with interval=5000 and pauseOnHover enabled
+ Open modalgallery at index={{imageIndex}}
+
+
+ E2 - (id=801) - AutoPlay demo - with interval=5000, pauseOnHover disabled and infinite is enabled
+ Open modalgallery at index={{imageIndex}}
+
+
+ E3 - (id=802) - AutoPlay demo - with interval=1000 and pauseOnHover disabled
+ {{isPlaying ? 'Pause' : 'Play'}}
+ Open modalgallery at index={{imageIndex}}
+
+
+
+
+
+
+Experimental examples*
+* these will be either improved or removed in next versions
+
+ F1 - (id=900) - Experimental demo - infinite sliding with only one image to see if side arrows are hidden
+ Open modalgallery
+
+
+ F2 - (id=901) - Experimental demo - an array of Images with the same source file (different classes/ids and paths with appended '?imageIndex' to prevent caching issues)
+ Open modalgallery
+
+
+ F3 - (id=902) - Experimental demo - 'previews' rendering customization (via Angular templates)
+
+
+
{{preview?.modal?.description ?? ' '}}
+
+
+
+
+
+ Open modalgallery
+
+
diff --git a/src/app/modal-gallery/modal-gallery.scss b/src/app/modal-gallery/modal-gallery.scss
new file mode 100644
index 00000000..994466cd
--- /dev/null
+++ b/src/app/modal-gallery/modal-gallery.scss
@@ -0,0 +1,177 @@
+// The MIT License (MIT)
+//
+// Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+:host {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: flex-start;
+ margin-left: 20px;
+ margin-right: 20px;
+}
+
+$text-color: #fff;
+$background: rgba(0, 0, 0, .7);
+
+button {
+ background-color: #0060b7;
+ color: white;
+ height: 25px;
+ width: auto;
+ border: 0;
+ border-radius: 5px;
+ cursor: pointer;
+
+ &:hover {
+ background-color: #0086ff;
+ }
+}
+
+.my-app-custom-plain-container-row {
+ align-items: center;
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+
+ .my-app-custom-image-row {
+ cursor: pointer;
+ height: auto;
+ margin-right: 2px;
+ width: 50px;
+
+ &.after {
+ border-top: 2px;
+ cursor: pointer;
+ display: none;
+ height: 100%;
+ left: 0;
+ position: absolute;
+ top: 0;
+ width: 100%;
+ }
+ }
+}
+
+.my-app-custom-plain-container-column {
+ align-items: center;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+
+ .my-app-custom-image-column {
+ cursor: pointer;
+ height: auto;
+ margin-right: 2px;
+ width: 50px;
+
+ &.after {
+ border-top: 2px;
+ cursor: pointer;
+ display: none;
+ height: 100%;
+ left: 0;
+ position: absolute;
+ top: 0;
+ width: 100%;
+ }
+ }
+}
+
+.my-app-custom-plain-container-with-desc {
+ align-items: center;
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+
+ figure {
+ margin: 0;
+ position: relative;
+
+ img {
+ max-width: 100%;
+ height: auto;
+ cursor: pointer;
+ }
+
+ figcaption {
+ background: rgba(0, 0, 0, .5);
+ color: #fff;
+ font-size: 85%;
+ padding: 5px;
+ position: absolute;
+ bottom: 3px;
+ left: 0;
+ right: 0;
+ }
+ }
+
+ .description {
+ font-weight: bold;
+ text-align: center;
+ }
+
+ .my-app-custom-image-with-desc {
+ height: auto;
+ margin-right: 2px;
+ width: 200px;
+ align-self: start;
+ }
+}
+
+.more {
+ background: $background;
+ cursor: pointer;
+ color: $text-color;
+ padding-top: 4px;
+ height: 46px;
+ position: absolute;
+ text-align: center;
+ width: 50px;
+}
+
+
+.transcluded {
+ color: white;
+ font-weight: 600;
+ font-size: 24px;
+ text-align: center;
+ position: absolute;
+ top: 50%;
+ width: 100%;
+ pointer-events: none;
+}
+
+.title {
+ margin-top: 40px;
+}
+
+.preview-block {
+ margin-right: 10px;
+}
+.preview-description {
+ color: #fff;
+ margin-bottom: 3px;
+}
+.preview-description,
+.preview-default {
+ text-align: center;
+}
diff --git a/src/app/navbar/navbar.component.ts b/src/app/navbar/navbar.component.ts
new file mode 100644
index 00000000..5d9df903
--- /dev/null
+++ b/src/app/navbar/navbar.component.ts
@@ -0,0 +1,63 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import { Component } from '@angular/core';
+import { Router } from '@angular/router';
+import { BreakpointObserver } from '@angular/cdk/layout';
+
+@Component({
+ selector: 'ks-navbar',
+ templateUrl: 'navbar.html',
+ styleUrls: ['navbar.scss'],
+ standalone: false
+})
+export class NavbarComponent {
+ navbarHeight = '56px';
+ // path: string = PATH + '/assets/amg.svg';
+
+ collapsed = false;
+
+ constructor(private router: Router, breakpointObserver: BreakpointObserver) {
+ breakpointObserver.observe(['(min-width: 990px)']).subscribe(result => {
+ if (result.matches) {
+ console.log('min width 990px');
+ this.collapsed = false;
+ }
+ });
+ }
+
+ isNavItemActive(location: string): string {
+ return this.router.url.includes(location) ? 'active' : '';
+ }
+
+ onNavigateTo(path: string): void {
+ this.collapsed = false;
+ this.router.navigate([path]);
+ }
+
+ onToggle(): void {
+ this.collapsed = !this.collapsed;
+ this.navbarHeight = this.collapsed ? '56px' : '150px';
+ }
+}
diff --git a/src/app/navbar/navbar.html b/src/app/navbar/navbar.html
new file mode 100644
index 00000000..f95de0c4
--- /dev/null
+++ b/src/app/navbar/navbar.html
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/navbar/navbar.scss b/src/app/navbar/navbar.scss
new file mode 100644
index 00000000..d7e45bc7
--- /dev/null
+++ b/src/app/navbar/navbar.scss
@@ -0,0 +1,233 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+nav {
+ padding-top: 8px;
+ padding-bottom: 8px;
+ padding-left: 16px;
+ padding-right: 16px;
+}
+
+ul {
+ margin-top: 0;
+ margin-bottom: 0;
+ padding-left: 0;
+}
+
+nav.nav-expanded {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ flex-wrap: wrap;
+ background-color: #343A40;
+ padding-top: 8px;
+ padding-left: 16px;
+ padding-bottom: 8px;
+ position: fixed;
+ top: 0;
+ right: 0;
+ left: 0;
+ z-index: 10;
+
+ > .navbar-hamburger {
+ display: none;
+ background-color: #343A40;
+ border-color: rgba(255, 255, 255, .1);
+
+ @media screen and (max-width: 990px) {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: flex-start;
+ cursor: pointer;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ padding-left: 12px;
+ padding-right: 12px;
+ }
+ }
+
+ > .navbar-wrapper {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+ align-items: center;
+
+ @media screen and (max-width: 990px) {
+ display: none;
+ }
+
+ > .navbar-brand {
+ font-size: 16px;
+ color: #ffffff;
+ font-weight: 400;
+ margin-left: 8px;
+ margin-right: 8px;
+ cursor: pointer;
+
+ > img {
+ border-radius: 3px
+ }
+
+ &:hover {
+ color: #bdbdbd;
+ }
+ }
+
+ > .navbar-nav {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+ align-items: center;
+ list-style: none;
+
+ > .nav-item {
+ cursor: pointer;
+ color: #e2e2e2;
+
+ > a.nav-link {
+ color: #b9b9b9;
+ font-size: 14px;
+ font-weight: 400;
+ margin-left: 8px;
+ margin-right: 8px;
+
+ &:hover {
+ color: #E2E2E2;
+ }
+ }
+ }
+ }
+ }
+
+ > #githubButtons {
+ margin-right: 30px;
+ @media only screen and (max-width: 1059px) {
+ display: none;
+ }
+
+ > .github-badge {
+ margin-left: 8px;
+ }
+ }
+}
+
+
+// cexpanded on mobile devices after pressing the hamburger button
+nav.nav-collapsed {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: flex-start;
+ flex-wrap: wrap;
+ background-color: #343A40;
+ padding-top: 8px;
+ padding-left: 16px;
+ padding-bottom: 8px;
+ position: fixed;
+ top: 0;
+ right: 0;
+ left: 0;
+ z-index: 10;
+
+ > .navbar-hamburger {
+ display: none;
+ background-color: #343A40;
+ border-color: rgba(255, 255, 255, .1);
+
+ @media screen and (max-width: 990px) {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: flex-start;
+ cursor: pointer;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ padding-left: 12px;
+ padding-right: 12px;
+ }
+ }
+
+ > .navbar-wrapper {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: flex-start;
+ margin-top: 8px;
+ width: 100%;
+
+ > .navbar-brand {
+ font-size: 16px;
+ color: #ffffff;
+ font-weight: 400;
+ margin-left: 8px;
+ margin-right: 8px;
+ margin-top: 8px;
+ margin-bottom: 8px;
+ cursor: pointer;
+ width: 100%;
+
+ > img {
+ border-radius: 3px
+ }
+
+ &:hover {
+ color: #bdbdbd;
+ }
+ }
+
+ > .navbar-nav {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: flex-start;
+ width: 100%;
+ list-style: none;
+
+ > .nav-item {
+ cursor: pointer;
+ color: #e2e2e2;
+ width: 100%;
+ margin-top: 8px;
+ margin-bottom: 8px;
+
+ > a.nav-link {
+ color: #b9b9b9;
+ font-size: 14px;
+ font-weight: 400;
+ margin-left: 8px;
+ margin-right: 8px;
+
+ &:hover {
+ color: #E2E2E2;
+ }
+ }
+ }
+ }
+ }
+}
+
+
+
diff --git a/src/app/plain-gallery/plain-gallery.component.ts b/src/app/plain-gallery/plain-gallery.component.ts
new file mode 100644
index 00000000..bc54d635
--- /dev/null
+++ b/src/app/plain-gallery/plain-gallery.component.ts
@@ -0,0 +1,296 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { Component } from '@angular/core';
+
+import {
+ GridLayout,
+ Image,
+ LineLayout,
+ PlainGalleryConfig,
+ PlainGalleryStrategy,
+ ModalGalleryService,
+ ModalGalleryRef,
+ PlainLibConfig
+} from '@ks89/angular-modal-gallery';
+
+@Component({
+ selector: 'ks-plain-gallery-page',
+ templateUrl: './plain-gallery.html',
+ styleUrls: ['./plain-gallery.scss'],
+ standalone: false
+})
+export class PlainGalleryExampleComponent {
+ constructor(private modalGalleryService: ModalGalleryService) {}
+
+ plainGalleryRow: PlainGalleryConfig = {
+ strategy: PlainGalleryStrategy.ROW,
+ layout: new LineLayout({ width: '80px', height: '80px' }, { length: 2, wrap: true }, 'flex-start')
+ };
+ plainGalleryRowSpaceAround: PlainGalleryConfig = {
+ strategy: PlainGalleryStrategy.ROW,
+ layout: new LineLayout({ width: '50px', height: '50px' }, { length: 2, wrap: true }, 'space-around')
+ };
+ plainGalleryRowATags: PlainGalleryConfig = {
+ strategy: PlainGalleryStrategy.ROW,
+ layout: new LineLayout({ width: '95px', height: '63px' }, { length: 4, wrap: true }, 'flex-start'),
+ // when advanced is defined, additionalBackground: '50% 50%/cover' will be used by default.
+ // I added this here, to be more explicit.
+ advanced: { aTags: true, additionalBackground: '50% 50%/cover' }
+ };
+
+ plainGalleryColumn: PlainGalleryConfig = {
+ strategy: PlainGalleryStrategy.COLUMN,
+ layout: new LineLayout({ width: '50px', height: '50px' }, { length: 3, wrap: true }, 'flex-start')
+ };
+
+ plainGalleryGrid: PlainGalleryConfig = {
+ strategy: PlainGalleryStrategy.GRID,
+ layout: new GridLayout({ width: '80px', height: '80px' }, { length: 3, wrap: true })
+ };
+
+ images: Image[] = [
+ new Image(0, {
+ img: '../assets/images/gallery/img1.jpg',
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/img2.jpg',
+ description: 'Description 2'
+ }),
+ new Image(
+ 2,
+ {
+ img: '../assets/images/gallery/img3.jpg',
+ description: 'Description 3',
+ extUrl: 'http://www.google.com'
+ },
+ {
+ img: '../assets/images/gallery/thumbs/img3.png',
+ title: 'custom title 2',
+ alt: 'custom alt 2',
+ ariaLabel: 'arial label 2'
+ }
+ ),
+ new Image(3, {
+ img: '../assets/images/gallery/img4.jpg',
+ description: 'Description 4',
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(4, { img: '../assets/images/gallery/img5.jpg' }, { img: '../assets/images/gallery/thumbs/img5.jpg' })
+ ];
+
+ emptyImagesArray: Image[] = [];
+
+ fallbackImages: Image[] = [
+ new Image(0, {
+ // this file is not available so the browser returns an error
+ img: '../assets/images/gallery/UNEXISTING_IMG1.jpg',
+ // because the img above doesn't exists, the library will use this file
+ fallbackImg: '../assets/images/gallery/fallback1.jpg'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/UNEXISTING_IMG2.jpg',
+ fallbackImg: '../assets/images/gallery/fallback2.jpg'
+ }),
+ new Image(
+ 2,
+ {
+ img: '../assets/images/gallery/UNEXISTING_IMG3.jpg',
+ fallbackImg: '../assets/images/gallery/fallback3.jpg'
+ },
+ {
+ img: '../assets/images/gallery/thumbs/UNEXISTING_IMG3.png',
+ fallbackImg: '../assets/images/gallery/fallback3.jpg'
+ }
+ ),
+ new Image(3, {
+ img: '../assets/images/gallery/UNEXISTING_IMG4.jpg',
+ fallbackImg: '../assets/images/gallery/fallback4.jpg'
+ }),
+ new Image(
+ 4,
+ {
+ img: '../assets/images/gallery/UNEXISTING_IMG5.jpg',
+ fallbackImg: '../assets/images/gallery/fallback5.jpg'
+ },
+ {
+ img: '../assets/images/gallery/thumbs/UNEXISTING_IMG5.jpg',
+ fallbackImg: '../assets/images/gallery/fallback5.jpg'
+ }
+ )
+ ];
+
+ imagesRect: Image[] = [
+ new Image(
+ 0,
+ {
+ img: '../assets/images/gallery/milan-pegasus-gallery-statue.jpg',
+ description: 'Description 1'
+ },
+ { img: '../assets/images/gallery/thumbs/t-milan-pegasus-gallery-statue.jpg' }
+ ),
+ new Image(1, { img: '../assets/images/gallery/pexels-photo-47223.jpeg' }, { img: '../assets/images/gallery/thumbs/t-pexels-photo-47223.jpg' }),
+ new Image(
+ 2,
+ {
+ img: '../assets/images/gallery/pexels-photo-52062.jpeg',
+ description: 'Description 3'
+ },
+ {
+ img: '../assets/images/gallery/thumbs/t-pexels-photo-52062.jpg',
+ description: 'Description 3'
+ }
+ ),
+ new Image(
+ 3,
+ {
+ img: '../assets/images/gallery/pexels-photo-66943.jpeg',
+ description: 'Description 4'
+ },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-66943.jpg' }
+ ),
+ new Image(4, { img: '../assets/images/gallery/pexels-photo-93750.jpeg' }, { img: '../assets/images/gallery/thumbs/t-pexels-photo-93750.jpg' }),
+ new Image(
+ 5,
+ {
+ img: '../assets/images/gallery/pexels-photo-94420.jpeg',
+ description: 'Description 6'
+ },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-94420.jpg' }
+ ),
+ new Image(6, { img: '../assets/images/gallery/pexels-photo-96947.jpeg' }, { img: '../assets/images/gallery/thumbs/t-pexels-photo-96947.jpg' })
+ ];
+
+ imagesRectNoTitles: Image[] = [
+ new Image(
+ 0,
+ { img: '../assets/images/gallery/milan-pegasus-gallery-statue.jpg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-milan-pegasus-gallery-statue.jpg', title: '' }
+ ),
+ new Image(
+ 1,
+ { img: '../assets/images/gallery/pexels-photo-47223.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-47223.jpg', title: '' }
+ ),
+ new Image(
+ 2,
+ { img: '../assets/images/gallery/pexels-photo-52062.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-52062.jpg', title: '' }
+ ),
+ new Image(
+ 3,
+ { img: '../assets/images/gallery/pexels-photo-66943.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-66943.jpg', title: '' }
+ ),
+ new Image(
+ 4,
+ { img: '../assets/images/gallery/pexels-photo-93750.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-93750.jpg', title: '' }
+ ),
+ new Image(
+ 5,
+ { img: '../assets/images/gallery/pexels-photo-94420.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-94420.jpg', title: '' }
+ ),
+ new Image(
+ 6,
+ { img: '../assets/images/gallery/pexels-photo-96947.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-96947.jpg', title: '' }
+ )
+ ];
+
+ libConfigPlainGalleryRow: PlainLibConfig = {
+ plainGalleryConfig: this.plainGalleryRow
+ };
+ libConfigPlainGalleryRowSpaceAround: PlainLibConfig = {
+ plainGalleryConfig: this.plainGalleryRowSpaceAround
+ };
+ libConfigPlainGalleryRowATags: PlainLibConfig = {
+ plainGalleryConfig: this.plainGalleryRowATags
+ };
+ libConfigPlainGalleryColumn: PlainLibConfig = {
+ plainGalleryConfig: this.plainGalleryColumn
+ };
+ libConfigPlainGalleryGrid: PlainLibConfig = {
+ plainGalleryConfig: this.plainGalleryGrid
+ };
+
+ openImageModalRow(id: number, image: Image): void {
+ console.log('Opening modal gallery from custom plain gallery row, with image: ', image);
+ const index: number = this.getCurrentIndexCustomLayout(image, this.images);
+ const dialogRef: ModalGalleryRef = this.modalGalleryService.open({
+ id,
+ images: this.images,
+ currentImage: this.images[index]
+ }) as ModalGalleryRef;
+ }
+
+ openImageModalColumn(id: number, image: Image): void {
+ console.log('Opening modal gallery from custom plain gallery column, with image: ', image);
+ const index: number = this.getCurrentIndexCustomLayout(image, this.images);
+ const dialogRef: ModalGalleryRef = this.modalGalleryService.open({
+ id,
+ images: this.images,
+ currentImage: this.images[index]
+ }) as ModalGalleryRef;
+ }
+
+ openImageModalRowDescription(id: number, image: Image): void {
+ console.log('Opening modal gallery from custom plain gallery row and description, with image: ', image);
+ const index: number = this.getCurrentIndexCustomLayout(image, this.imagesRect);
+ const dialogRef: ModalGalleryRef = this.modalGalleryService.open({
+ id,
+ images: this.imagesRect,
+ currentImage: this.imagesRect[index]
+ }) as ModalGalleryRef;
+ }
+
+ addRandomImage(): void {
+ // add to images array
+ const imageToCopy: Image = this.images[Math.floor(Math.random() * this.images.length)];
+ const newImage: Image = new Image(this.images.length - 1 + 1, imageToCopy.modal, imageToCopy.plain);
+ this.images = [...this.images, newImage];
+ // add also to imagesRect
+ const imageRectToCopy: Image = this.imagesRect[Math.floor(Math.random() * this.imagesRect.length)];
+ const newImageRect: Image = new Image(this.imagesRect.length - 1 + 1, imageRectToCopy.modal, imageRectToCopy.plain);
+ this.imagesRect = [...this.imagesRect, newImageRect];
+ }
+
+ onShow(id: number, index: number, images: Image[] = this.images): void {
+ const dialogRef: ModalGalleryRef = this.modalGalleryService.open({
+ id,
+ images,
+ currentImage: images[index]
+ }) as ModalGalleryRef;
+ }
+
+ trackById(index: number, item: Image): number {
+ return item.id;
+ }
+
+ private getCurrentIndexCustomLayout(image: Image, images: Image[]): number {
+ return image ? images.indexOf(image) : -1;
+ }
+}
diff --git a/src/app/plain-gallery/plain-gallery.html b/src/app/plain-gallery/plain-gallery.html
new file mode 100644
index 00000000..73f06879
--- /dev/null
+++ b/src/app/plain-gallery/plain-gallery.html
@@ -0,0 +1,152 @@
+Plain Gallery
+
+
+If you want, you can add a random image to every example
+ Add image
+
+
+
+Layout examples
+
+ P1 - (id=200) - row plain gallery layout (limit 2) and custom size
+
+
+
+
+
+ P2 - (id=201) - row plain gallery layout space around (limit 2)
+
+
+
+
+
+ P3 - (id=202) - row plain gallery layout with a tags and custom rectangular sizes (limit 4)
+
+
+
+
+
+ P4 - (id=203) - column plain gallery layout (limit 3)
+
+
+
+
+
+ P5 - (id=204) - grid plain gallery layout and custom size
+
+
+
+
+
+ P6 - (id=205) - full custom plain gallery (row) with image pointer
+
+
+
+
+
+ P7 - (id=206) - full custom plain gallery (column) with image pointer
+
+
+
+
+
+ P8 - (id=207) - full custom plain gallery (row) with descriptions
+
+
+
+
+
+ {{img.modal.description ? img.modal.description : 'No description available'}}
+
+
+
+
+
+
+
+ P9 - (id=208) - row plain gallery layout with fallback images when it's not possible to load normal images (to fix issue #194)
+
+
+
+
+
+ P10 - (id=209) - row plain gallery layout with a tags and custom rectangular sizes (limit 4) + fallback images when it's not possible to load normal images (to fix issue #194)
+
+
+
+
+
+ P11 - (id=210) - row plain gallery layout (limit 2) and custom size + remove title attribute on images
+
+
+
+
+
+ P12 - (id=211) - row plain gallery layout with an empty images array
+
+
+
+
+
diff --git a/src/app/plain-gallery/plain-gallery.scss b/src/app/plain-gallery/plain-gallery.scss
new file mode 100644
index 00000000..b498df8d
--- /dev/null
+++ b/src/app/plain-gallery/plain-gallery.scss
@@ -0,0 +1,169 @@
+// The MIT License (MIT)
+//
+// Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+:host {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: flex-start;
+ margin-left: 20px;
+ margin-right: 20px;
+}
+
+section {
+ width: 100%;
+}
+
+$text-color: #fff;
+$background: rgba(0, 0, 0, .7);
+
+button {
+ background-color: #0060b7;
+ color: white;
+ height: 25px;
+ width: auto;
+ border: 0;
+ border-radius: 5px;
+ cursor: pointer;
+
+ &:hover {
+ background-color: #0086ff;
+ }
+}
+
+.my-app-custom-plain-container-row {
+ align-items: center;
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+
+ .my-app-custom-image-row {
+ cursor: pointer;
+ height: auto;
+ margin-right: 2px;
+ width: 50px;
+
+ &.after {
+ border-top: 2px;
+ cursor: pointer;
+ display: none;
+ height: 100%;
+ left: 0;
+ position: absolute;
+ top: 0;
+ width: 100%;
+ }
+ }
+}
+
+.my-app-custom-plain-container-column {
+ align-items: center;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+
+ .my-app-custom-image-column {
+ cursor: pointer;
+ height: auto;
+ margin-right: 2px;
+ width: 50px;
+
+ &.after {
+ border-top: 2px;
+ cursor: pointer;
+ display: none;
+ height: 100%;
+ left: 0;
+ position: absolute;
+ top: 0;
+ width: 100%;
+ }
+ }
+}
+
+.my-app-custom-plain-container-with-desc {
+ align-items: center;
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+
+ figure {
+ margin: 0;
+ position: relative;
+
+ img {
+ max-width: 100%;
+ height: auto;
+ cursor: pointer;
+ }
+
+ figcaption {
+ background: rgba(0, 0, 0, .5);
+ color: #fff;
+ font-size: 85%;
+ padding: 5px;
+ position: absolute;
+ bottom: 3px;
+ left: 0;
+ right: 0;
+ }
+ }
+
+ .description {
+ font-weight: bold;
+ text-align: center;
+ }
+
+ .my-app-custom-image-with-desc {
+ height: auto;
+ margin-right: 2px;
+ width: 200px;
+ align-self: start;
+ }
+}
+
+.more {
+ background: $background;
+ cursor: pointer;
+ color: $text-color;
+ padding-top: 4px;
+ height: 46px;
+ position: absolute;
+ text-align: center;
+ width: 50px;
+}
+
+
+.transcluded {
+ color: white;
+ font-weight: 600;
+ font-size: 24px;
+ text-align: center;
+ position: absolute;
+ top: 50%;
+ width: 100%;
+ pointer-events: none;
+}
+
+.title {
+ margin-top: 40px;
+}
diff --git a/src/assets/images/gallery/fallback-carousel1.jpg b/src/assets/images/gallery/fallback-carousel1.jpg
new file mode 100644
index 00000000..5d49263f
Binary files /dev/null and b/src/assets/images/gallery/fallback-carousel1.jpg differ
diff --git a/src/assets/images/gallery/fallback-carousel2.jpg b/src/assets/images/gallery/fallback-carousel2.jpg
new file mode 100644
index 00000000..2c2ce26a
Binary files /dev/null and b/src/assets/images/gallery/fallback-carousel2.jpg differ
diff --git a/src/assets/images/gallery/fallback-carousel3.jpg b/src/assets/images/gallery/fallback-carousel3.jpg
new file mode 100644
index 00000000..a4ea6b40
Binary files /dev/null and b/src/assets/images/gallery/fallback-carousel3.jpg differ
diff --git a/src/assets/images/gallery/fallback-carousel4.jpg b/src/assets/images/gallery/fallback-carousel4.jpg
new file mode 100644
index 00000000..0d59507d
Binary files /dev/null and b/src/assets/images/gallery/fallback-carousel4.jpg differ
diff --git a/src/assets/images/gallery/fallback-carousel5.jpg b/src/assets/images/gallery/fallback-carousel5.jpg
new file mode 100644
index 00000000..5511feb8
Binary files /dev/null and b/src/assets/images/gallery/fallback-carousel5.jpg differ
diff --git a/src/assets/images/gallery/fallback1.jpg b/src/assets/images/gallery/fallback1.jpg
new file mode 100644
index 00000000..0827c782
Binary files /dev/null and b/src/assets/images/gallery/fallback1.jpg differ
diff --git a/src/assets/images/gallery/fallback2.jpg b/src/assets/images/gallery/fallback2.jpg
new file mode 100644
index 00000000..6562c517
Binary files /dev/null and b/src/assets/images/gallery/fallback2.jpg differ
diff --git a/src/assets/images/gallery/fallback3.jpg b/src/assets/images/gallery/fallback3.jpg
new file mode 100644
index 00000000..3a3d63f4
Binary files /dev/null and b/src/assets/images/gallery/fallback3.jpg differ
diff --git a/src/assets/images/gallery/fallback4.jpg b/src/assets/images/gallery/fallback4.jpg
new file mode 100644
index 00000000..0dfe8551
Binary files /dev/null and b/src/assets/images/gallery/fallback4.jpg differ
diff --git a/src/assets/images/gallery/fallback5.jpg b/src/assets/images/gallery/fallback5.jpg
new file mode 100644
index 00000000..38da58e4
Binary files /dev/null and b/src/assets/images/gallery/fallback5.jpg differ
diff --git a/src/assets/images/gallery/img1.jpg b/src/assets/images/gallery/img1.jpg
new file mode 100644
index 00000000..7d358899
Binary files /dev/null and b/src/assets/images/gallery/img1.jpg differ
diff --git a/src/assets/images/gallery/img2.jpg b/src/assets/images/gallery/img2.jpg
new file mode 100644
index 00000000..4c101c12
Binary files /dev/null and b/src/assets/images/gallery/img2.jpg differ
diff --git a/src/assets/images/gallery/img2.png b/src/assets/images/gallery/img2.png
new file mode 100644
index 00000000..295cb1f4
Binary files /dev/null and b/src/assets/images/gallery/img2.png differ
diff --git a/src/assets/images/gallery/img3.jpg b/src/assets/images/gallery/img3.jpg
new file mode 100644
index 00000000..c6b1c22d
Binary files /dev/null and b/src/assets/images/gallery/img3.jpg differ
diff --git a/src/assets/images/gallery/img4.jpg b/src/assets/images/gallery/img4.jpg
new file mode 100644
index 00000000..403482ae
Binary files /dev/null and b/src/assets/images/gallery/img4.jpg differ
diff --git a/src/assets/images/gallery/img5.jpg b/src/assets/images/gallery/img5.jpg
new file mode 100644
index 00000000..bf97ab6c
Binary files /dev/null and b/src/assets/images/gallery/img5.jpg differ
diff --git a/src/assets/images/gallery/milan-pegasus-gallery-statue-1024w.jpg b/src/assets/images/gallery/milan-pegasus-gallery-statue-1024w.jpg
new file mode 100644
index 00000000..c0a922d1
Binary files /dev/null and b/src/assets/images/gallery/milan-pegasus-gallery-statue-1024w.jpg differ
diff --git a/src/assets/images/gallery/milan-pegasus-gallery-statue-480w.jpg b/src/assets/images/gallery/milan-pegasus-gallery-statue-480w.jpg
new file mode 100644
index 00000000..ac54d7d7
Binary files /dev/null and b/src/assets/images/gallery/milan-pegasus-gallery-statue-480w.jpg differ
diff --git a/src/assets/images/gallery/milan-pegasus-gallery-statue-768w.jpg b/src/assets/images/gallery/milan-pegasus-gallery-statue-768w.jpg
new file mode 100644
index 00000000..dba50163
Binary files /dev/null and b/src/assets/images/gallery/milan-pegasus-gallery-statue-768w.jpg differ
diff --git a/src/assets/images/gallery/milan-pegasus-gallery-statue.jpg b/src/assets/images/gallery/milan-pegasus-gallery-statue.jpg
new file mode 100644
index 00000000..f2c02760
Binary files /dev/null and b/src/assets/images/gallery/milan-pegasus-gallery-statue.jpg differ
diff --git a/src/assets/images/gallery/pexels-photo-135230-1024w.png b/src/assets/images/gallery/pexels-photo-135230-1024w.png
new file mode 100644
index 00000000..a543d4db
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-135230-1024w.png differ
diff --git a/src/assets/images/gallery/pexels-photo-135230-480w.png b/src/assets/images/gallery/pexels-photo-135230-480w.png
new file mode 100644
index 00000000..dc29041e
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-135230-480w.png differ
diff --git a/src/assets/images/gallery/pexels-photo-135230-768w.png b/src/assets/images/gallery/pexels-photo-135230-768w.png
new file mode 100644
index 00000000..b04917ed
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-135230-768w.png differ
diff --git a/src/assets/images/gallery/pexels-photo-135230.png b/src/assets/images/gallery/pexels-photo-135230.png
new file mode 100644
index 00000000..8aa887fd
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-135230.png differ
diff --git a/src/assets/images/gallery/pexels-photo-47223-1024w.jpeg b/src/assets/images/gallery/pexels-photo-47223-1024w.jpeg
new file mode 100644
index 00000000..f7931c21
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-47223-1024w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-47223-480w.jpeg b/src/assets/images/gallery/pexels-photo-47223-480w.jpeg
new file mode 100644
index 00000000..14897ecf
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-47223-480w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-47223-768w.jpeg b/src/assets/images/gallery/pexels-photo-47223-768w.jpeg
new file mode 100644
index 00000000..73f72c9a
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-47223-768w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-47223.jpeg b/src/assets/images/gallery/pexels-photo-47223.jpeg
new file mode 100644
index 00000000..46ea1f75
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-47223.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-52062-1024w.jpeg b/src/assets/images/gallery/pexels-photo-52062-1024w.jpeg
new file mode 100644
index 00000000..a7052fd3
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-52062-1024w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-52062-480w.jpeg b/src/assets/images/gallery/pexels-photo-52062-480w.jpeg
new file mode 100644
index 00000000..1ac7ce8c
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-52062-480w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-52062-768w.jpeg b/src/assets/images/gallery/pexels-photo-52062-768w.jpeg
new file mode 100644
index 00000000..e1f3256c
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-52062-768w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-52062.jpeg b/src/assets/images/gallery/pexels-photo-52062.jpeg
new file mode 100644
index 00000000..0c9502e4
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-52062.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-547115-1024w.jpeg b/src/assets/images/gallery/pexels-photo-547115-1024w.jpeg
new file mode 100644
index 00000000..592a1516
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-547115-1024w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-547115-480w.jpeg b/src/assets/images/gallery/pexels-photo-547115-480w.jpeg
new file mode 100644
index 00000000..15c404dd
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-547115-480w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-547115-768w.jpeg b/src/assets/images/gallery/pexels-photo-547115-768w.jpeg
new file mode 100644
index 00000000..68fa15da
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-547115-768w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-547115.jpeg b/src/assets/images/gallery/pexels-photo-547115.jpeg
new file mode 100644
index 00000000..4056b197
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-547115.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-556664-1024w.jpeg b/src/assets/images/gallery/pexels-photo-556664-1024w.jpeg
new file mode 100644
index 00000000..b56cf006
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-556664-1024w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-556664-480w.jpeg b/src/assets/images/gallery/pexels-photo-556664-480w.jpeg
new file mode 100644
index 00000000..f7990e34
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-556664-480w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-556664-768w.jpeg b/src/assets/images/gallery/pexels-photo-556664-768w.jpeg
new file mode 100644
index 00000000..cbb74582
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-556664-768w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-556664.jpeg b/src/assets/images/gallery/pexels-photo-556664.jpeg
new file mode 100644
index 00000000..33383d36
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-556664.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-66943-1024w.jpeg b/src/assets/images/gallery/pexels-photo-66943-1024w.jpeg
new file mode 100644
index 00000000..18988bf3
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-66943-1024w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-66943-480w.jpeg b/src/assets/images/gallery/pexels-photo-66943-480w.jpeg
new file mode 100644
index 00000000..c40829b9
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-66943-480w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-66943-768w.jpeg b/src/assets/images/gallery/pexels-photo-66943-768w.jpeg
new file mode 100644
index 00000000..953ef6f3
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-66943-768w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-66943.jpeg b/src/assets/images/gallery/pexels-photo-66943.jpeg
new file mode 100644
index 00000000..8dd20157
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-66943.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-787594-1024w.jpeg b/src/assets/images/gallery/pexels-photo-787594-1024w.jpeg
new file mode 100644
index 00000000..52113fef
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-787594-1024w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-787594-480w.jpeg b/src/assets/images/gallery/pexels-photo-787594-480w.jpeg
new file mode 100644
index 00000000..3b97fb45
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-787594-480w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-787594-768w.jpeg b/src/assets/images/gallery/pexels-photo-787594-768w.jpeg
new file mode 100644
index 00000000..b08cc133
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-787594-768w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-787594.jpeg b/src/assets/images/gallery/pexels-photo-787594.jpeg
new file mode 100644
index 00000000..e2986131
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-787594.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-803105-1024w.jpeg b/src/assets/images/gallery/pexels-photo-803105-1024w.jpeg
new file mode 100644
index 00000000..99bb60cd
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-803105-1024w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-803105-480w.jpeg b/src/assets/images/gallery/pexels-photo-803105-480w.jpeg
new file mode 100644
index 00000000..815624dc
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-803105-480w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-803105-768w.jpeg b/src/assets/images/gallery/pexels-photo-803105-768w.jpeg
new file mode 100644
index 00000000..1118cde8
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-803105-768w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-803105.jpeg b/src/assets/images/gallery/pexels-photo-803105.jpeg
new file mode 100644
index 00000000..8b7c44a4
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-803105.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-93750-1024w.jpeg b/src/assets/images/gallery/pexels-photo-93750-1024w.jpeg
new file mode 100644
index 00000000..bf1e8eaf
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-93750-1024w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-93750-480w.jpeg b/src/assets/images/gallery/pexels-photo-93750-480w.jpeg
new file mode 100644
index 00000000..08020dbe
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-93750-480w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-93750-768w.jpeg b/src/assets/images/gallery/pexels-photo-93750-768w.jpeg
new file mode 100644
index 00000000..8fe37d40
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-93750-768w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-93750.jpeg b/src/assets/images/gallery/pexels-photo-93750.jpeg
new file mode 100644
index 00000000..ab537b02
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-93750.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-94420-1024w.jpeg b/src/assets/images/gallery/pexels-photo-94420-1024w.jpeg
new file mode 100644
index 00000000..c8a13148
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-94420-1024w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-94420-480w.jpeg b/src/assets/images/gallery/pexels-photo-94420-480w.jpeg
new file mode 100644
index 00000000..acc790e7
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-94420-480w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-94420-768w.jpeg b/src/assets/images/gallery/pexels-photo-94420-768w.jpeg
new file mode 100644
index 00000000..11fbaad9
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-94420-768w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-94420.jpeg b/src/assets/images/gallery/pexels-photo-94420.jpeg
new file mode 100644
index 00000000..d3ebb012
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-94420.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-96947-1024w.jpeg b/src/assets/images/gallery/pexels-photo-96947-1024w.jpeg
new file mode 100644
index 00000000..3e4a701f
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-96947-1024w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-96947-480w.jpeg b/src/assets/images/gallery/pexels-photo-96947-480w.jpeg
new file mode 100644
index 00000000..0248070f
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-96947-480w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-96947-768w.jpeg b/src/assets/images/gallery/pexels-photo-96947-768w.jpeg
new file mode 100644
index 00000000..c74f523e
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-96947-768w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-96947.jpeg b/src/assets/images/gallery/pexels-photo-96947.jpeg
new file mode 100644
index 00000000..c6965748
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-96947.jpeg differ
diff --git a/src/assets/images/gallery/thumbs/fallback-carousel1.jpg b/src/assets/images/gallery/thumbs/fallback-carousel1.jpg
new file mode 100644
index 00000000..5ce2a4ed
Binary files /dev/null and b/src/assets/images/gallery/thumbs/fallback-carousel1.jpg differ
diff --git a/src/assets/images/gallery/thumbs/fallback-carousel2.jpg b/src/assets/images/gallery/thumbs/fallback-carousel2.jpg
new file mode 100644
index 00000000..6417a30b
Binary files /dev/null and b/src/assets/images/gallery/thumbs/fallback-carousel2.jpg differ
diff --git a/src/assets/images/gallery/thumbs/fallback-carousel3.jpg b/src/assets/images/gallery/thumbs/fallback-carousel3.jpg
new file mode 100644
index 00000000..a0939a7a
Binary files /dev/null and b/src/assets/images/gallery/thumbs/fallback-carousel3.jpg differ
diff --git a/src/assets/images/gallery/thumbs/fallback-carousel4.jpg b/src/assets/images/gallery/thumbs/fallback-carousel4.jpg
new file mode 100644
index 00000000..5be118b7
Binary files /dev/null and b/src/assets/images/gallery/thumbs/fallback-carousel4.jpg differ
diff --git a/src/assets/images/gallery/thumbs/fallback-carousel5.jpg b/src/assets/images/gallery/thumbs/fallback-carousel5.jpg
new file mode 100644
index 00000000..c61eb0ee
Binary files /dev/null and b/src/assets/images/gallery/thumbs/fallback-carousel5.jpg differ
diff --git a/src/assets/images/gallery/thumbs/fallback1.jpg b/src/assets/images/gallery/thumbs/fallback1.jpg
new file mode 100644
index 00000000..2afb5530
Binary files /dev/null and b/src/assets/images/gallery/thumbs/fallback1.jpg differ
diff --git a/src/assets/images/gallery/thumbs/fallback2.jpg b/src/assets/images/gallery/thumbs/fallback2.jpg
new file mode 100644
index 00000000..140e9718
Binary files /dev/null and b/src/assets/images/gallery/thumbs/fallback2.jpg differ
diff --git a/src/assets/images/gallery/thumbs/fallback3.jpg b/src/assets/images/gallery/thumbs/fallback3.jpg
new file mode 100644
index 00000000..11115387
Binary files /dev/null and b/src/assets/images/gallery/thumbs/fallback3.jpg differ
diff --git a/src/assets/images/gallery/thumbs/fallback4.jpg b/src/assets/images/gallery/thumbs/fallback4.jpg
new file mode 100644
index 00000000..9737793b
Binary files /dev/null and b/src/assets/images/gallery/thumbs/fallback4.jpg differ
diff --git a/src/assets/images/gallery/thumbs/fallback5.jpg b/src/assets/images/gallery/thumbs/fallback5.jpg
new file mode 100644
index 00000000..88915773
Binary files /dev/null and b/src/assets/images/gallery/thumbs/fallback5.jpg differ
diff --git a/src/assets/images/gallery/thumbs/img1.jpg b/src/assets/images/gallery/thumbs/img1.jpg
new file mode 100644
index 00000000..68e10d14
Binary files /dev/null and b/src/assets/images/gallery/thumbs/img1.jpg differ
diff --git a/src/assets/images/gallery/thumbs/img2.jpg b/src/assets/images/gallery/thumbs/img2.jpg
new file mode 100644
index 00000000..3b69746a
Binary files /dev/null and b/src/assets/images/gallery/thumbs/img2.jpg differ
diff --git a/src/assets/images/gallery/thumbs/img3.png b/src/assets/images/gallery/thumbs/img3.png
new file mode 100644
index 00000000..3f0bf060
Binary files /dev/null and b/src/assets/images/gallery/thumbs/img3.png differ
diff --git a/src/assets/images/gallery/thumbs/img4.jpg b/src/assets/images/gallery/thumbs/img4.jpg
new file mode 100644
index 00000000..d08ebe7f
Binary files /dev/null and b/src/assets/images/gallery/thumbs/img4.jpg differ
diff --git a/src/assets/images/gallery/thumbs/img5.jpg b/src/assets/images/gallery/thumbs/img5.jpg
new file mode 100644
index 00000000..b932ee9f
Binary files /dev/null and b/src/assets/images/gallery/thumbs/img5.jpg differ
diff --git a/src/assets/images/gallery/thumbs/t-milan-pegasus-gallery-statue.jpg b/src/assets/images/gallery/thumbs/t-milan-pegasus-gallery-statue.jpg
new file mode 100644
index 00000000..6881e08c
Binary files /dev/null and b/src/assets/images/gallery/thumbs/t-milan-pegasus-gallery-statue.jpg differ
diff --git a/src/assets/images/gallery/thumbs/t-pexels-photo-47223.jpg b/src/assets/images/gallery/thumbs/t-pexels-photo-47223.jpg
new file mode 100644
index 00000000..be93d546
Binary files /dev/null and b/src/assets/images/gallery/thumbs/t-pexels-photo-47223.jpg differ
diff --git a/src/assets/images/gallery/thumbs/t-pexels-photo-52062.jpg b/src/assets/images/gallery/thumbs/t-pexels-photo-52062.jpg
new file mode 100644
index 00000000..eb99fe42
Binary files /dev/null and b/src/assets/images/gallery/thumbs/t-pexels-photo-52062.jpg differ
diff --git a/src/assets/images/gallery/thumbs/t-pexels-photo-66943.jpg b/src/assets/images/gallery/thumbs/t-pexels-photo-66943.jpg
new file mode 100644
index 00000000..e33dc4be
Binary files /dev/null and b/src/assets/images/gallery/thumbs/t-pexels-photo-66943.jpg differ
diff --git a/src/assets/images/gallery/thumbs/t-pexels-photo-93750.jpg b/src/assets/images/gallery/thumbs/t-pexels-photo-93750.jpg
new file mode 100644
index 00000000..feae47af
Binary files /dev/null and b/src/assets/images/gallery/thumbs/t-pexels-photo-93750.jpg differ
diff --git a/src/assets/images/gallery/thumbs/t-pexels-photo-94420.jpg b/src/assets/images/gallery/thumbs/t-pexels-photo-94420.jpg
new file mode 100644
index 00000000..075cdd51
Binary files /dev/null and b/src/assets/images/gallery/thumbs/t-pexels-photo-94420.jpg differ
diff --git a/src/assets/images/gallery/thumbs/t-pexels-photo-96947.jpg b/src/assets/images/gallery/thumbs/t-pexels-photo-96947.jpg
new file mode 100644
index 00000000..ab777557
Binary files /dev/null and b/src/assets/images/gallery/thumbs/t-pexels-photo-96947.jpg differ
diff --git a/src/assets/images/loading-spinner-samples/pexels-photo-106006-thumb.jpg b/src/assets/images/loading-spinner-samples/pexels-photo-106006-thumb.jpg
new file mode 100644
index 00000000..e80e1a53
Binary files /dev/null and b/src/assets/images/loading-spinner-samples/pexels-photo-106006-thumb.jpg differ
diff --git a/src/assets/images/loading-spinner-samples/pexels-photo-106006.jpg b/src/assets/images/loading-spinner-samples/pexels-photo-106006.jpg
new file mode 100644
index 00000000..6baa8325
Binary files /dev/null and b/src/assets/images/loading-spinner-samples/pexels-photo-106006.jpg differ
diff --git a/src/assets/images/loading-spinner-samples/pexels-photo-464336-thumb.jpg b/src/assets/images/loading-spinner-samples/pexels-photo-464336-thumb.jpg
new file mode 100644
index 00000000..7bd64447
Binary files /dev/null and b/src/assets/images/loading-spinner-samples/pexels-photo-464336-thumb.jpg differ
diff --git a/src/assets/images/loading-spinner-samples/pexels-photo-464336.jpg b/src/assets/images/loading-spinner-samples/pexels-photo-464336.jpg
new file mode 100644
index 00000000..c7253d57
Binary files /dev/null and b/src/assets/images/loading-spinner-samples/pexels-photo-464336.jpg differ
diff --git a/src/assets/images/loading-spinner-samples/pexels-photo-74506-thumb.jpg b/src/assets/images/loading-spinner-samples/pexels-photo-74506-thumb.jpg
new file mode 100644
index 00000000..24028aa0
Binary files /dev/null and b/src/assets/images/loading-spinner-samples/pexels-photo-74506-thumb.jpg differ
diff --git a/src/assets/images/loading-spinner-samples/pexels-photo-74506.jpg b/src/assets/images/loading-spinner-samples/pexels-photo-74506.jpg
new file mode 100644
index 00000000..c4973505
Binary files /dev/null and b/src/assets/images/loading-spinner-samples/pexels-photo-74506.jpg differ
diff --git a/src/assets/images/loading-spinner-samples/pexels-photo-thumb.jpg b/src/assets/images/loading-spinner-samples/pexels-photo-thumb.jpg
new file mode 100644
index 00000000..34615206
Binary files /dev/null and b/src/assets/images/loading-spinner-samples/pexels-photo-thumb.jpg differ
diff --git a/src/assets/images/loading-spinner-samples/pexels-photo.jpg b/src/assets/images/loading-spinner-samples/pexels-photo.jpg
new file mode 100644
index 00000000..3ceffcbe
Binary files /dev/null and b/src/assets/images/loading-spinner-samples/pexels-photo.jpg differ
diff --git a/src/assets/images/loading-spinner-samples/traffic-highway-lights-night-56891-thumb.jpg b/src/assets/images/loading-spinner-samples/traffic-highway-lights-night-56891-thumb.jpg
new file mode 100644
index 00000000..8cfda5fa
Binary files /dev/null and b/src/assets/images/loading-spinner-samples/traffic-highway-lights-night-56891-thumb.jpg differ
diff --git a/src/assets/images/loading-spinner-samples/traffic-highway-lights-night-56891.jpg b/src/assets/images/loading-spinner-samples/traffic-highway-lights-night-56891.jpg
new file mode 100644
index 00000000..9457c1c6
Binary files /dev/null and b/src/assets/images/loading-spinner-samples/traffic-highway-lights-night-56891.jpg differ
diff --git a/src/assets/menu.svg b/src/assets/menu.svg
new file mode 100644
index 00000000..65fc9003
--- /dev/null
+++ b/src/assets/menu.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/favicon.png b/src/favicon.png
new file mode 100644
index 00000000..4e279875
Binary files /dev/null and b/src/favicon.png differ
diff --git a/src/index.html b/src/index.html
new file mode 100644
index 00000000..475fb312
--- /dev/null
+++ b/src/index.html
@@ -0,0 +1,18 @@
+
+
+
+
+ @ks89/angular-modal-gallery demo
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main.ts b/src/main.ts
new file mode 100644
index 00000000..1d335a67
--- /dev/null
+++ b/src/main.ts
@@ -0,0 +1,8 @@
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+
+import { AppModule } from './app/app.module';
+
+platformBrowserDynamic().bootstrapModule(AppModule, {
+ ngZoneEventCoalescing: true
+})
+ .catch(err => console.error(err));
diff --git a/src/styles.scss b/src/styles.scss
new file mode 100644
index 00000000..47d84e5a
--- /dev/null
+++ b/src/styles.scss
@@ -0,0 +1,61 @@
+// The MIT License (MIT)
+//
+// Copyright (c) 2017-2024 Stefano Cappa (Ks89)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+// You can add global styles to this file, and also import other style files
+
+// *****************************************************************
+// *********** required by @ks89/angular-modal-gallery *************
+// *****************************************************************
+// ATTENTION: You have to install @angular/cdk to be able to use @ks89/angular-modal-gallery properly
+@import "@angular/cdk/overlay-prebuilt.css";
+
+.ks-modal-gallery-backdrop {
+ background: #000 !important;;
+ opacity: 0.85 !important;;
+}
+
+.ks-modal-gallery-panel {
+ z-index: 90000 !important;
+}
+// *****************************************************************
+// *****************************************************************
+// *****************************************************************
+
+body {
+ font-family: "Montserrat", sans-serif;
+ margin: 0;
+ padding: 0;
+}
+
+// Not required by angular-modal-gallery. Used only in this demo
+.red-text {
+ color: red;
+}
+
+.title {
+ text-align: center;
+ color: red;
+}
+
+.center-text {
+ text-align: center;
+}
diff --git a/src/test.ts b/src/test.ts
new file mode 100644
index 00000000..c04c8760
--- /dev/null
+++ b/src/test.ts
@@ -0,0 +1,26 @@
+// This file is required by karma.conf.js and loads recursively all the .spec and framework files
+
+import 'zone.js/testing';
+import { getTestBed } from '@angular/core/testing';
+import {
+ BrowserDynamicTestingModule,
+ platformBrowserDynamicTesting
+} from '@angular/platform-browser-dynamic/testing';
+
+declare const require: {
+ context(path: string, deep?: boolean, filter?: RegExp): {
+ (id: string): T;
+ keys(): string[];
+ };
+};
+
+// First, initialize the Angular testing environment.
+getTestBed().initTestEnvironment(
+ BrowserDynamicTestingModule,
+ platformBrowserDynamicTesting(),
+);
+
+// Then we find all the tests.
+const context = require.context('./', true, /\.spec\.ts$/);
+// And load the modules.
+context.keys().forEach(context);
diff --git a/systemjs.config.js b/systemjs.config.js
deleted file mode 100644
index 2e12bf6a..00000000
--- a/systemjs.config.js
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * System configuration for Angular 2 samples
- * Adjust as necessary for your application needs.
- */
-(function(global) {
- // map tells the System loader where to look for things
- var map = {
- 'app': '/app', // 'dist',
-// 'httpresource': 'node_modules/httpresource',
- '@angular': 'node_modules/@angular',
- 'rxjs': 'node_modules/rxjs',
- 'directives' : 'directives/'
- };
- // packages tells the System loader how to load when no filename and/or no extension
- var packages = {
- 'app': { main: 'main.js', defaultExtension: 'js' },
- 'rxjs': { defaultExtension: 'js' },
- 'directives':{ defaultExtension: 'js' }
- };
- var ngPackageNames = [
- 'common',
- 'compiler',
- 'core',
- 'platform-browser',
- 'platform-browser-dynamic',
- 'upgrade'
- ];
- // Individual files (~300 requests):
- function packIndex(pkgName) {
- packages['@angular/'+pkgName] = { main: 'index.js', defaultExtension: 'js' };
- }
- // Bundled (~40 requests):
- function packUmd(pkgName) {
- packages['@angular/'+pkgName] = { main: '/bundles/' + pkgName + '.umd.js', defaultExtension: 'js' };
- }
- // Most environments should use UMD; some (Karma) need the individual index files
- var setPackageConfig = System.packageWithIndex ? packIndex : packUmd;
-// packages['httpresource']={ default: 'resource.js',defaultExtension: 'js' };
- // Add package entries for angular packages
- ngPackageNames.forEach(setPackageConfig);
- var config = {
- map: map,
- packages: packages
- };
- System.config(config);
-})(this);
diff --git a/tsconfig.app.json b/tsconfig.app.json
new file mode 100644
index 00000000..3775b37e
--- /dev/null
+++ b/tsconfig.app.json
@@ -0,0 +1,15 @@
+/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
+/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./out-tsc/app",
+ "types": []
+ },
+ "files": [
+ "src/main.ts"
+ ],
+ "include": [
+ "src/**/*.d.ts"
+ ]
+}
diff --git a/tsconfig.json b/tsconfig.json
index 9be71e4c..0b8ea91d 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,17 +1,42 @@
+/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
+/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{
+ "compileOnSave": false,
"compilerOptions": {
- "target": "es5",
- "module": "system",
- "moduleResolution": "node",
- "sourceMap": true,
- "emitDecoratorMetadata": true,
+ "paths": {
+ "@ks89/angular-modal-gallery": [
+ "./dist/ks89/angular-modal-gallery"
+ ]
+ },
+ "outDir": "./dist/out-tsc",
+ "strict": true,
+ "noImplicitOverride": true,
+ "noPropertyAccessFromIndexSignature": false, // FIXME should become tre
+ "noImplicitReturns": true,
+ "noFallthroughCasesInSwitch": true,
+ "skipLibCheck": true,
+ "isolatedModules": true,
+ "esModuleInterop": true,
"experimentalDecorators": true,
- "removeComments": false,
- "noImplicitAny": false
+ "moduleResolution": "bundler",
+ "importHelpers": true,
+ "target": "ES2022",
+ "module": "ES2022",
+ "useDefineForClassFields": false, // to inject sanitizer without errors
+ "lib": [
+ "ES2022",
+ "dom"
+ ],
+ "typeRoots": [
+ "node_modules/@types"
+ ]
},
- "exclude": [
- "node_modules",
- "typings/main",
- "typings/main.d.ts"
- ]
+ "angularCompilerOptions": {
+ "enableI18nLegacyMessageIdFormat": false,
+ "strictInjectionParameters": true,
+ "strictInputAccessModifiers": true,
+
+ // FIXME THIS SHOULD BE FORCED TO TRUE IN THE FUTURE VERSIONS OF THE LIBRARY
+ "strictTemplates": false
+ }
}
diff --git a/tsconfig.spec.json b/tsconfig.spec.json
new file mode 100644
index 00000000..5fb748d9
--- /dev/null
+++ b/tsconfig.spec.json
@@ -0,0 +1,15 @@
+/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
+/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./out-tsc/spec",
+ "types": [
+ "jasmine"
+ ]
+ },
+ "include": [
+ "src/**/*.spec.ts",
+ "src/**/*.d.ts"
+ ]
+}
diff --git a/typings.json b/typings.json
deleted file mode 100644
index c06bb4d7..00000000
--- a/typings.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "ambientDependencies": {
- "es6-shim": "github:DefinitelyTyped/DefinitelyTyped/es6-shim/es6-shim.d.ts#7de6c3dd94feaeb21f20054b9f30d5dabc5efabd",
- "jasmine": "github:DefinitelyTyped/DefinitelyTyped/jasmine/jasmine.d.ts#7de6c3dd94feaeb21f20054b9f30d5dabc5efabd"
- }
-}