UNPKG

ngx-masonry-gallery

Version:
289 lines (282 loc) 12.2 kB
import { CommonModule } from '@angular/common'; import * as i0 from '@angular/core'; import { EventEmitter, Component, ChangeDetectionStrategy, Input, Output, NgModule } from '@angular/core'; import * as imagesLoaded from 'imagesloaded'; import * as Masonry from 'masonry-layout'; class Utilities { newGuid() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { // tslint:disable-next-line:no-bitwise triple-equals const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } } let utilities = new Utilities(); class MasonryGalleryComponent { constructor(renderer) { this.renderer = renderer; this.images = []; this.width = 330; this.gutter = 5; this.verticalGutter = 5; this.imageClasses = []; this.clickImage = new EventEmitter(); this.removeComplete = new EventEmitter(); this.layoutComplete = new EventEmitter(); this.galleryGuid = utilities.newGuid(); this.mansonryItemSelectorClass = `grid-item-${this.galleryGuid}`; this.activeImages = []; this.viewReady = false; } ngOnChanges(changes) { if (changes.images && changes.images.currentValue) { if (!this.viewReady) { // process images once we can this.changesToProcess = changes; } else { this.processImages(changes); } } } ngOnDestroy() { this.msnry?.destroy?.(); } handleClick(image) { this.clickImage.next(image); } ngAfterViewInit() { this.viewReady = true; this.initMasonry(); // process images now if (this.changesToProcess) { this.processImages(this.changesToProcess); this.changesToProcess = undefined; } } addImages(images) { if (images && images.length > 0) { this.addImagesToGallery(images); } } removeImages(images) { if (images && images.length > 0) { images.forEach((image) => { this.removeImageFromGallery(image); }); } } processImages(changes) { const imagesToProcess = this.getAddedAndRemovesImages(changes); // add images to mansonry layout this.addImages(imagesToProcess.addedImages); // removes images from layout this.removeImages(imagesToProcess.removedImages); } getAddedAndRemovesImages(changes) { let addedImages = []; const removedImages = []; const newImagesValue = changes.images .currentValue; const oldImagesValue = changes.images .previousValue; if (!oldImagesValue) { // all images are new ones addedImages = changes.images.currentValue; } else { // process added images newImagesValue.forEach((newImage) => { const existingImage = oldImagesValue.find((m) => m.imageUrl.toLowerCase() === newImage.imageUrl.toLowerCase()); if (existingImage) { // image was in previous value && is in new, do nothing } else { // image is new addedImages.push(newImage); } }); // process removed images oldImagesValue.forEach((oldImage) => { const existingImage = newImagesValue.find((m) => m.imageUrl.toLowerCase() === oldImage.imageUrl.toLowerCase()); if (existingImage) { // image was in previous value && is in new, do nothing } else { // image is removed removedImages.push(oldImage); } }); } return { addedImages: addedImages, removedImages: removedImages, }; } initMasonry() { const grid = document.getElementById(this.galleryGuid); if (!grid) { console.warn(`Invalid grid element with id '${this.galleryGuid}'`); return; } // remove all existing data from grid grid.innerHTML = ""; const msnry = new Masonry(grid, { // options... itemSelector: "." + this.mansonryItemSelectorClass, columnWidth: this.width, gutter: this.gutter, }); const that = this; msnry.on?.("layoutComplete", (items) => { that.layoutComplete.next(items); }); msnry.on?.("removeComplete", (items) => { that.removeComplete.next(items); }); this.grid = grid; this.msnry = msnry; } removeImageFromGallery(image) { // get image guid const imageIdResult = this.activeImages.find((m) => m.image.imageUrl.toLowerCase() === image.imageUrl.toLowerCase()); if (!imageIdResult) { // image was not found, this is probably an error console.warn(`Image with url '${image.imageUrl}' was not found. If you are adding images, make sure to 'replace' the images array with a new one so that detection change can be executed instead of just adding an image to array (which doesn't fire change detection on array property)`); return; } // find image based on its id const imageElem = document.getElementById(imageIdResult.id); if (!imageElem) { // image was not found in DOM console.warn(`Image with id '{${imageIdResult.id}}' was not found in DOM. Have you manipulated the DOM in some way?`); return; } // remove image from gallery this.msnry?.remove?.([imageElem]); // refresh layout this.msnry?.layout?.(); // remove image from array for (let i = 0; i < this.activeImages.length; i++) { const idWithImage = this.activeImages[i]; if (idWithImage.image.imageUrl.toLowerCase() === imageIdResult.image.imageUrl.toLowerCase()) { this.activeImages.splice(i, 1); } } } addImagesToGallery(images) { if (!this.grid) { throw Error("Grid element is not yet ready, are you trying to add image too soon?"); } const imagesWrapper = this.renderer.createElement("span"); images.forEach((image) => { // generate unique image id const imageId = this.getImageId(); // create element const imageElem = this.renderer.createElement("img"); imageElem.setAttribute("id", imageId); imageElem.setAttribute("alt", image.alt ? image.alt : "no description"); imageElem.setAttribute("src", image.imageUrl); // note - images are hidden by default and should be shown only after they are loaded imageElem.setAttribute("style", `display: block; width: ${this.width}px; margin-bottom: ${this.verticalGutter}px`); imageElem.className = this.getImageClass(); imageElem.addEventListener("click", () => { this.handleClick(image); }); // store guid with this image this.activeImages.push({ id: imageId, image: image, }); // add to dom and mansory & refresh layout this.renderer.appendChild(imagesWrapper, imageElem); }); // add html to dom this.renderer.appendChild(this.grid, imagesWrapper); // add images once they are loaded const imgLoad = imagesLoaded(imagesWrapper); imgLoad.on("progress", (instance, image) => { if (image && image.isLoaded && this.msnry && this.msnry.appended && this.msnry.reloadItems) { // this.renderer.appendChild(this.grid, image.img); // unhide image // this.renderer.setStyle(image.img, "display", "block"); // remove image from gallery this.msnry?.addItems?.([image.img]); // refresh layout this.msnry?.layout?.(); } }); } getImageClass() { let className = this.mansonryItemSelectorClass; if (this.imageClasses && this.imageClasses.length > 0) { const customClass = this.imageClasses.join(" "); className += " " + customClass; } return className; } getImageId() { return this.galleryGuid + "_" + utilities.newGuid(); } } /** @nocollapse */ MasonryGalleryComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.0", ngImport: i0, type: MasonryGalleryComponent, deps: [{ token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component }); /** @nocollapse */ MasonryGalleryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.2.0", type: MasonryGalleryComponent, selector: "ngx-masonry-gallery", inputs: { images: "images", width: "width", gutter: "gutter", verticalGutter: "verticalGutter", imageClasses: "imageClasses" }, outputs: { clickImage: "clickImage", removeComplete: "removeComplete", layoutComplete: "layoutComplete" }, usesOnChanges: true, ngImport: i0, template: '<div [id]="galleryGuid"></div>', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.0", ngImport: i0, type: MasonryGalleryComponent, decorators: [{ type: Component, args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: "ngx-masonry-gallery", template: '<div [id]="galleryGuid"></div>', }] }], ctorParameters: function () { return [{ type: i0.Renderer2 }]; }, propDecorators: { images: [{ type: Input }], width: [{ type: Input }], gutter: [{ type: Input }], verticalGutter: [{ type: Input }], imageClasses: [{ type: Input }], clickImage: [{ type: Output }], removeComplete: [{ type: Output }], layoutComplete: [{ type: Output }] } }); class MasonryGalleryModule { } /** @nocollapse */ MasonryGalleryModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.0", ngImport: i0, type: MasonryGalleryModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); /** @nocollapse */ MasonryGalleryModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.2.0", ngImport: i0, type: MasonryGalleryModule, declarations: [MasonryGalleryComponent], imports: [CommonModule], exports: [MasonryGalleryComponent] }); /** @nocollapse */ MasonryGalleryModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.2.0", ngImport: i0, type: MasonryGalleryModule, imports: [CommonModule] }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.0", ngImport: i0, type: MasonryGalleryModule, decorators: [{ type: NgModule, args: [{ imports: [ CommonModule ], declarations: [ MasonryGalleryComponent, ], exports: [ MasonryGalleryComponent, ], }] }] }); /* * Public API */ /** * Generated bundle index. Do not edit. */ export { MasonryGalleryComponent, MasonryGalleryModule }; //# sourceMappingURL=ngx-masonry-gallery.mjs.map