ng-flyweight-gallery
Version:
`ng-flyweight-gallery` is a lightweight and customizable Angular component library for creating responsive media galleries. It supports images and videos, with features like autoplay, loop for videos and a responsive layout.
194 lines (182 loc) • 13.6 kB
JavaScript
import * as i0 from '@angular/core';
import { SecurityContext, Component, Input, NgModule } from '@angular/core';
import * as i2 from '@angular/common';
import { CommonModule } from '@angular/common';
import * as i1 from '@angular/platform-browser';
class NgFlyweightGalleryComponent {
sanitizer;
mediaItems = [];
selectedItem;
lightboxOpen = false;
selectedIndex = 0;
constructor(sanitizer) {
this.sanitizer = sanitizer;
}
ngOnInit() {
this.mediaItems.forEach((item, index) => {
item.source = this.sanitizeUrl(item.source);
if (!item.thumbnail) {
if (item.type === 'image') {
item.thumbnail = this.sanitizeUrl(item.source);
}
else if (item.type === 'video') {
this.generateVideoThumbnail(item, index);
}
}
else {
item.thumbnail = this.sanitizeUrl(item.thumbnail);
}
});
}
sanitizeUrl(url) {
return this.sanitizer.sanitize(SecurityContext.RESOURCE_URL, url);
}
generateVideoThumbnail(item, index) {
const video = document.createElement('video');
video.src = item.source;
video.crossOrigin = 'anonymous';
video.load();
video.addEventListener('loadeddata', () => {
video.currentTime = 2;
});
video.addEventListener('seeked', () => {
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth / 4;
canvas.height = video.videoHeight / 4;
const ctx = canvas.getContext('2d');
if (ctx) {
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
item.thumbnail = this.sanitizeUrl(canvas.toDataURL('image/jpeg'));
}
});
}
captureVideoThumbnail(event, index) {
const videoElement = event.target;
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
if (context) {
// Set canvas dimensions
canvas.width = videoElement.videoWidth;
canvas.height = videoElement.videoHeight;
videoElement.currentTime = 0;
videoElement.addEventListener('loadeddata', () => {
context.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
const thumbnailDataUrl = canvas.toDataURL('image/jpeg');
this.mediaItems[index].thumbnail = thumbnailDataUrl;
});
}
}
openLightbox(item, index) {
this.selectedItem = item;
this.selectedIndex = index;
this.lightboxOpen = true;
}
closeLightbox(event) {
if (event?.target === event?.currentTarget) {
this.lightboxOpen = false;
this.selectedItem = undefined;
}
}
// Navigation functions
prev() {
if (this.selectedIndex > 0) {
this.selectedIndex--;
this.selectedItem = this.mediaItems[this.selectedIndex];
}
}
next() {
if (this.selectedIndex < this.mediaItems.length - 1) {
this.selectedIndex++;
this.selectedItem = this.mediaItems[this.selectedIndex];
}
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: NgFlyweightGalleryComponent, deps: [{ token: i1.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.6", type: NgFlyweightGalleryComponent, isStandalone: false, selector: "ng-flyweight-gallery", inputs: { mediaItems: "mediaItems" }, ngImport: i0, template: `
<div class="fwt-gallery-grid">
<div class="fwt-gallery-item" *ngFor="let item of mediaItems; index as i" (click)="openLightbox(item, i)">
<!-- Image thumbnail -->
<img (click)="openLightbox(item, i)" *ngIf="item.type === 'image'" [src]="item?.thumbnail || item.source" [alt]="item.title">
<!-- Video thumbnail -->
<video
(click)="openLightbox(item, i)"
*ngIf="item.type === 'video' && !item?.thumbnail"
[src]="item.source"
(loadedmetadata)="captureVideoThumbnail($event, i)"
muted
></video>
<img (click)="openLightbox(item, i)" *ngIf="item.type === 'video' && item?.thumbnail" [src]="item.thumbnail" [alt]="item.title">
</div>
</div>
<div class="fwt-lightbox" *ngIf="lightboxOpen">
<div class="fwt-lightbox-backdrop"></div>
<button class="fwt-nav-btn fwt-prev" (click)="prev()">❮</button>
<button class="fwt-nav-btn fwt-next" (click)="next()">❯</button>
<div class="fwt-lightbox-content">
<div class="fwt-media-container" *ngIf="selectedItem">
<img *ngIf="selectedItem.type === 'image'" [src]="selectedItem.source" [alt]="selectedItem.title">
<video *ngIf="selectedItem.type === 'video'" [src]="selectedItem.source" [autoplay]="selectedItem.autoplay"
[loop]="selectedItem.loop" controls>
</video>
</div>
</div>
<button class="fwt-close-btn" (click)="closeLightbox()">×</button>
</div>
`, isInline: true, styles: [".fwt-gallery-grid{background-color:transparent;display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:10px;max-height:450px;overflow-y:auto;padding-right:10px;scrollbar-width:thin;scrollbar-color:#888 #f1f1f1}.fwt-gallery-grid ::-webkit-scrollbar{width:8px}.fwt-gallery-grid ::-webkit-scrollbar-thumb{background-color:#888;border-radius:4px;border:2px solid transparent;background-clip:content-box}.fwt-gallery-grid ::-webkit-scrollbar-thumb:hover{background-color:#555}.fwt-gallery-grid ::-webkit-scrollbar-track{background-color:#f1f1f1;border-radius:4px}@media (max-width: 768px){.fwt-gallery-grid{grid-template-columns:repeat(auto-fill,minmax(120px,1fr))}}@media (max-width: 480px){.fwt-gallery-grid{grid-template-columns:1fr}}.fwt-gallery-item{cursor:pointer;transition:transform .3s;border:2px solid lightsteelblue;border-radius:5px;object-fit:cover;-webkit-object-fit:cover;-moz-object-fit:cover}.fwt-gallery-item:hover{transform:scale(1.05);-webkit-transform:scale(1.05);-ms-transform:scale(1.05)}.fwt-gallery-item :is(img,video){width:100%;height:100%}.fwt-lightbox{position:fixed;top:0;left:0;width:100%;height:100%;display:flex;justify-content:center;align-items:center;z-index:1000}.fwt-lightbox .fwt-lightbox-backdrop{position:absolute;top:0;left:0;width:100%;height:100%;background:#000c;z-index:999}.fwt-lightbox .fwt-lightbox-content{position:relative;max-width:70%;max-height:70%;display:flex;justify-content:center;align-items:center;text-align:center;z-index:1001}.fwt-lightbox .fwt-lightbox-content .fwt-media-container :is(img,video){width:100%;height:auto}@media (max-width: 768px){.fwt-lightbox .fwt-lightbox-content{max-width:90%;max-height:90%}}@media (max-width: 480px){.fwt-lightbox .fwt-lightbox-content{max-width:95%;max-height:95%}}.fwt-lightbox :is(.fwt-nav-btn,.fwt-close-btn){position:absolute;top:50%;transform:translateY(-50%);-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);background:transparent;border:none;color:#fff;font-size:2rem;cursor:pointer;z-index:1002}.fwt-lightbox :is(.fwt-nav-btn,.fwt-close-btn).fwt-prev{left:10px}.fwt-lightbox :is(.fwt-nav-btn,.fwt-close-btn).fwt-next{right:10px}.fwt-lightbox :is(.fwt-nav-btn,.fwt-close-btn).fwt-close-btn{top:10px;right:10px;transform:none}@media (max-width: 480px){.fwt-lightbox :is(.fwt-nav-btn,.fwt-close-btn){font-size:1.5rem}}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: NgFlyweightGalleryComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-flyweight-gallery', standalone: false, template: `
<div class="fwt-gallery-grid">
<div class="fwt-gallery-item" *ngFor="let item of mediaItems; index as i" (click)="openLightbox(item, i)">
<!-- Image thumbnail -->
<img (click)="openLightbox(item, i)" *ngIf="item.type === 'image'" [src]="item?.thumbnail || item.source" [alt]="item.title">
<!-- Video thumbnail -->
<video
(click)="openLightbox(item, i)"
*ngIf="item.type === 'video' && !item?.thumbnail"
[src]="item.source"
(loadedmetadata)="captureVideoThumbnail($event, i)"
muted
></video>
<img (click)="openLightbox(item, i)" *ngIf="item.type === 'video' && item?.thumbnail" [src]="item.thumbnail" [alt]="item.title">
</div>
</div>
<div class="fwt-lightbox" *ngIf="lightboxOpen">
<div class="fwt-lightbox-backdrop"></div>
<button class="fwt-nav-btn fwt-prev" (click)="prev()">❮</button>
<button class="fwt-nav-btn fwt-next" (click)="next()">❯</button>
<div class="fwt-lightbox-content">
<div class="fwt-media-container" *ngIf="selectedItem">
<img *ngIf="selectedItem.type === 'image'" [src]="selectedItem.source" [alt]="selectedItem.title">
<video *ngIf="selectedItem.type === 'video'" [src]="selectedItem.source" [autoplay]="selectedItem.autoplay"
[loop]="selectedItem.loop" controls>
</video>
</div>
</div>
<button class="fwt-close-btn" (click)="closeLightbox()">×</button>
</div>
`, styles: [".fwt-gallery-grid{background-color:transparent;display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:10px;max-height:450px;overflow-y:auto;padding-right:10px;scrollbar-width:thin;scrollbar-color:#888 #f1f1f1}.fwt-gallery-grid ::-webkit-scrollbar{width:8px}.fwt-gallery-grid ::-webkit-scrollbar-thumb{background-color:#888;border-radius:4px;border:2px solid transparent;background-clip:content-box}.fwt-gallery-grid ::-webkit-scrollbar-thumb:hover{background-color:#555}.fwt-gallery-grid ::-webkit-scrollbar-track{background-color:#f1f1f1;border-radius:4px}@media (max-width: 768px){.fwt-gallery-grid{grid-template-columns:repeat(auto-fill,minmax(120px,1fr))}}@media (max-width: 480px){.fwt-gallery-grid{grid-template-columns:1fr}}.fwt-gallery-item{cursor:pointer;transition:transform .3s;border:2px solid lightsteelblue;border-radius:5px;object-fit:cover;-webkit-object-fit:cover;-moz-object-fit:cover}.fwt-gallery-item:hover{transform:scale(1.05);-webkit-transform:scale(1.05);-ms-transform:scale(1.05)}.fwt-gallery-item :is(img,video){width:100%;height:100%}.fwt-lightbox{position:fixed;top:0;left:0;width:100%;height:100%;display:flex;justify-content:center;align-items:center;z-index:1000}.fwt-lightbox .fwt-lightbox-backdrop{position:absolute;top:0;left:0;width:100%;height:100%;background:#000c;z-index:999}.fwt-lightbox .fwt-lightbox-content{position:relative;max-width:70%;max-height:70%;display:flex;justify-content:center;align-items:center;text-align:center;z-index:1001}.fwt-lightbox .fwt-lightbox-content .fwt-media-container :is(img,video){width:100%;height:auto}@media (max-width: 768px){.fwt-lightbox .fwt-lightbox-content{max-width:90%;max-height:90%}}@media (max-width: 480px){.fwt-lightbox .fwt-lightbox-content{max-width:95%;max-height:95%}}.fwt-lightbox :is(.fwt-nav-btn,.fwt-close-btn){position:absolute;top:50%;transform:translateY(-50%);-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);background:transparent;border:none;color:#fff;font-size:2rem;cursor:pointer;z-index:1002}.fwt-lightbox :is(.fwt-nav-btn,.fwt-close-btn).fwt-prev{left:10px}.fwt-lightbox :is(.fwt-nav-btn,.fwt-close-btn).fwt-next{right:10px}.fwt-lightbox :is(.fwt-nav-btn,.fwt-close-btn).fwt-close-btn{top:10px;right:10px;transform:none}@media (max-width: 480px){.fwt-lightbox :is(.fwt-nav-btn,.fwt-close-btn){font-size:1.5rem}}\n"] }]
}], ctorParameters: () => [{ type: i1.DomSanitizer }], propDecorators: { mediaItems: [{
type: Input
}] } });
class NgFlyweightGalleryModule {
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: NgFlyweightGalleryModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.1.6", ngImport: i0, type: NgFlyweightGalleryModule, declarations: [NgFlyweightGalleryComponent], imports: [CommonModule], exports: [NgFlyweightGalleryComponent] });
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: NgFlyweightGalleryModule, imports: [CommonModule] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: NgFlyweightGalleryModule, decorators: [{
type: NgModule,
args: [{
declarations: [NgFlyweightGalleryComponent],
imports: [CommonModule],
exports: [NgFlyweightGalleryComponent],
}]
}] });
/*
* Public API Surface of ng-flyweight-gallery
*/
/**
* Generated bundle index. Do not edit.
*/
export { NgFlyweightGalleryComponent, NgFlyweightGalleryModule };
//# sourceMappingURL=ng-flyweight-gallery.mjs.map