improving lightbox

bug fixes with new image loading and fullscreen mode
This commit is contained in:
Braun Patrik 2017-03-25 21:59:30 +01:00
parent 52544ae956
commit f1d8364a2e
9 changed files with 218 additions and 84 deletions

View File

@ -30,6 +30,7 @@ import {Config} from "./config/Config";
import {GalleryMapComponent} from "./gallery/map/map.gallery.component";
import {GalleryMapLightboxComponent} from "./gallery/map/lightbox/lightbox.map.gallery.component";
import {ThumbnailManagerService} from "./gallery/thumnailManager.service";
import {OverlayService} from "./gallery/overlay.service";
@NgModule({
imports: [
@ -68,7 +69,8 @@ import {ThumbnailManagerService} from "./gallery/thumnailManager.service";
AuthenticationService,
ThumbnailLoaderService,
ThumbnailManagerService,
FullScreenService],
FullScreenService,
OverlayService],
bootstrap: [AppComponent]
})

View File

@ -1,9 +1,12 @@
import {Injectable} from "@angular/core";
import {Event} from "../../../common/event/Event";
@Injectable()
export class FullScreenService {
OnFullScreenChange = new Event<boolean>();
public isFullScreenEnabled(): boolean {
return !!(document.fullscreenElement || document['mozFullScreenElement'] || document.webkitFullscreenElement);
}
@ -22,6 +25,7 @@ export class FullScreenService {
} else if (element.msRequestFullscreen) {
element.msRequestFullscreen();
}
this.OnFullScreenChange.trigger(true);
}
public exitFullScreen() {
@ -36,6 +40,7 @@ export class FullScreenService {
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
}
this.OnFullScreenChange.trigger(false);
}
}

View File

@ -1,13 +1,14 @@
import {
Component,
Input,
ElementRef,
OnChanges,
ViewChild,
ViewChildren,
QueryList,
AfterViewInit,
HostListener
ChangeDetectorRef,
Component,
ElementRef,
HostListener,
Input,
OnChanges,
QueryList,
ViewChild,
ViewChildren
} from "@angular/core";
import {PhotoDTO} from "../../../../common/entities/PhotoDTO";
import {GridRowBuilder} from "./GridRowBuilder";
@ -15,31 +16,33 @@ import {GalleryLightboxComponent} from "../lightbox/lightbox.gallery.component";
import {GridPhoto} from "./GridPhoto";
import {GalleryPhotoComponent} from "./photo/photo.grid.gallery.component";
import {Config} from "../../config/Config";
import {OverlayService} from "../overlay.service";
@Component({
selector: 'gallery-grid',
templateUrl: 'app/gallery/grid/grid.gallery.component.html',
styleUrls: ['app/gallery/grid/grid.gallery.component.css'],
})
export class GalleryGridComponent implements OnChanges,AfterViewInit {
export class GalleryGridComponent implements OnChanges, AfterViewInit {
@ViewChild('gridContainer') gridContainer:ElementRef;
@ViewChildren(GalleryPhotoComponent) gridPhotoQL:QueryList<GalleryPhotoComponent>;
@ViewChild('gridContainer') gridContainer: ElementRef;
@ViewChildren(GalleryPhotoComponent) gridPhotoQL: QueryList<GalleryPhotoComponent>;
@Input() photos: Array<PhotoDTO>;
@Input() lightbox:GalleryLightboxComponent;
@Input() lightbox: GalleryLightboxComponent;
photosToRender:Array<GridPhoto> = [];
containerWidth:number = 0;
photosToRender: Array<GridPhoto> = [];
containerWidth: number = 0;
private IMAGE_MARGIN = 2;
private TARGET_COL_COUNT = 5;
private MIN_ROW_COUNT = 2;
private MAX_ROW_COUNT = 5;
onScrollFired = false;
private onScrollFired = false;
private scrollbarWidth = 0;
constructor() {
constructor(private overlayService: OverlayService, private changeDetector: ChangeDetectorRef) {
}
ngOnChanges() {
@ -61,16 +64,17 @@ export class GalleryGridComponent implements OnChanges,AfterViewInit {
}
this.updateContainerWidth();
this.sortPhotos();
//render the same amount of images on resize
let renderedIndex = this.renderedPhotoIndex;
this.clearRenderedPhotos();
setImmediate(() => {
this.renderPhotos();
});
this.renderPhotos(renderedIndex);
}
isAfterViewInit:boolean = false;
isAfterViewInit: boolean = false;
ngAfterViewInit() {
this.lightbox.gridPhotoQL = this.gridPhotoQL;
this.lightbox.setGridPhotoQL(this.gridPhotoQL);
//TODO: implement scroll detection
@ -96,6 +100,7 @@ export class GalleryGridComponent implements OnChanges,AfterViewInit {
private clearRenderedPhotos() {
this.photosToRender = [];
this.renderedPhotoIndex = 0;
this.changeDetector.detectChanges();
}
private mergeNewPhotos() {
@ -123,9 +128,9 @@ export class GalleryGridComponent implements OnChanges,AfterViewInit {
}
private renderedPhotoIndex:number = 0;
private renderedPhotoIndex: number = 0;
private renderPhotos() {
private renderPhotos(numberOfPhotos: number = 0) {
if (this.containerWidth == 0 || this.renderedPhotoIndex >= this.photos.length || !this.shouldRenderMore()) {
return;
}
@ -133,7 +138,7 @@ export class GalleryGridComponent implements OnChanges,AfterViewInit {
let renderedContentHeight = 0;
while (this.renderedPhotoIndex < this.photos.length && this.shouldRenderMore(renderedContentHeight) === true) {
while (this.renderedPhotoIndex < this.photos.length && (this.shouldRenderMore(renderedContentHeight) === true || this.renderedPhotoIndex < numberOfPhotos)) {
let ret = this.renderARow();
if (ret === null) {
throw new Error("Gridphotos rendering failed");
@ -149,7 +154,7 @@ export class GalleryGridComponent implements OnChanges,AfterViewInit {
* @param offset Add height to the client height (conent is not yet added to the dom, but calculate with it)
* @returns {boolean}
*/
private shouldRenderMore(offset:number = 0):boolean {
private shouldRenderMore(offset: number = 0): boolean {
return Config.Client.enableOnScrollRendering === false ||
window.scrollY >= (document.body.clientHeight + offset - window.innerHeight) * 0.7
|| (document.body.clientHeight + offset) * 0.85 < window.innerHeight;
@ -164,7 +169,7 @@ export class GalleryGridComponent implements OnChanges,AfterViewInit {
this.renderPhotos();
if (Config.Client.enableOnScrollThumbnailPrioritising === true) {
this.gridPhotoQL.toArray().forEach((pc:GalleryPhotoComponent) => {
this.gridPhotoQL.toArray().forEach((pc: GalleryPhotoComponent) => {
pc.onScroll();
});
}
@ -174,15 +179,16 @@ export class GalleryGridComponent implements OnChanges,AfterViewInit {
}
}
public renderARow():number {
public renderARow(): number {
if (this.renderedPhotoIndex >= this.photos.length) {
return null;
}
let maxRowHeight = window.innerHeight / this.MIN_ROW_COUNT;
let minRowHeight = window.innerHeight / this.MAX_ROW_COUNT;
let photoRowBuilder = new GridRowBuilder(this.photos, this.renderedPhotoIndex, this.IMAGE_MARGIN, this.containerWidth);
let photoRowBuilder = new GridRowBuilder(this.photos, this.renderedPhotoIndex, this.IMAGE_MARGIN, this.containerWidth - this.overlayService.getPhantomScrollbarWidth());
photoRowBuilder.addPhotos(this.TARGET_COL_COUNT);
photoRowBuilder.adjustRowHeightBetween(minRowHeight, maxRowHeight);
@ -198,7 +204,7 @@ export class GalleryGridComponent implements OnChanges,AfterViewInit {
return rowHeight;
}
private updateContainerWidth():number {
private updateContainerWidth(): number {
if (!this.gridContainer) {
return;
}

View File

@ -37,9 +37,11 @@
<span class="glyphicon glyphicon-remove highlight" (click)="hide()" title="close"></span>
</div>
<div class="navigation-arrow highlight" *ngIf="navigation.hasPrev" id="leftArrow" (click)="prevImage()"><span
<div class="navigation-arrow highlight" *ngIf="navigation.hasPrev" title="key: left arrow" id="leftArrow"
(click)="prevImage()"><span
class="glyphicon glyphicon-chevron-left"></span></div>
<div class="navigation-arrow highlight" *ngIf="navigation.hasNext" id="rightArrow" (click)="nextImage()"><span
<div class="navigation-arrow highlight" *ngIf="navigation.hasNext" title="key: right arrow" id="rightArrow"
(click)="nextImage()"><span
class="glyphicon glyphicon-chevron-right"></span></div>
</div>
</div>

View File

@ -1,8 +1,19 @@
import {Component, QueryList, Output, EventEmitter, HostListener, ElementRef, ViewChild} from "@angular/core";
import {
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
HostListener,
Output,
QueryList,
ViewChild
} from "@angular/core";
import {PhotoDTO} from "../../../../common/entities/PhotoDTO";
import {GalleryPhotoComponent} from "../grid/photo/photo.grid.gallery.component";
import {Dimension} from "../../model/IRenderable";
import {FullScreenService} from "../fullscreen.service";
import {OverlayService} from "../overlay.service";
import {Subscription} from "rxjs";
@Component({
selector: 'gallery-lightbox',
@ -19,62 +30,80 @@ export class GalleryLightboxComponent {
public blackCanvasOpacity: any = 0;
private activePhoto: GalleryPhotoComponent;
public gridPhotoQL: QueryList<GalleryPhotoComponent>;
private gridPhotoQL: QueryList<GalleryPhotoComponent>;
private visible = false;
private changeSubscription: Subscription = null;
@ViewChild("root") elementRef: ElementRef;
constructor(private fullScreenService: FullScreenService) {
constructor(private fullScreenService: FullScreenService, private changeDetector: ChangeDetectorRef, private overlayService: OverlayService) {
}
//noinspection JSUnusedGlobalSymbols
@HostListener('window:resize', ['$event'])
onResize() {
if (this.activePhoto) {
this.disableAnimation();
this.lightboxDimension.width = this.getScreenWidth();
this.lightboxDimension.height = this.getScreenHeight();
this.updateActivePhoto(this.activePhotoId);
}
}
public nextImage() {
this.disableAnimation();
let pcList = this.gridPhotoQL.toArray();
for (let i = 0; i < pcList.length; i++) {
if (pcList[i] === this.activePhoto) {
if (i + 1 < pcList.length) {
this.showPhoto(pcList[i + 1]);
if (i + 3 === pcList.length) {
if (this.activePhotoId + 1 < this.gridPhotoQL.length) {
this.showPhoto(this.activePhotoId + 1);
if (this.activePhotoId + 3 >= this.gridPhotoQL.length) {
this.onLastElement.emit({}); //trigger to render more photos if there are
}
}
return;
}
}
console.warn("can't find photo to show next");
}
public prevImage() {
this.disableAnimation();
let pcList = this.gridPhotoQL.toArray();
for (let i = 0; i < pcList.length; i++) {
if (pcList[i] === this.activePhoto) {
if (i > 0) {
this.showPhoto(pcList[i - 1]);
}
if (this.activePhotoId > 0) {
this.showPhoto(this.activePhotoId - 1);
return;
}
}
console.warn("can't find photo to show prev");
}
private showPhoto(photoComponent: GalleryPhotoComponent) {
activePhotoId: number = null;
private showPhoto(photoIndex: number) {
this.activePhoto = null;
this.changeDetector.detectChanges();
this.updateActivePhoto(photoIndex);
}
private updateActivePhoto(photoIndex: number) {
let pcList = this.gridPhotoQL.toArray();
let index = pcList.indexOf(photoComponent);
if (index == -1) {
if (photoIndex < 0 || photoIndex > this.gridPhotoQL.length) {
throw new Error("Can't find the photo");
}
this.activePhotoId = photoIndex;
this.activePhoto = pcList[photoIndex];
this.photoDimension = this.calcLightBoxPhotoDimension(this.activePhoto.gridPhoto.photo);
this.navigation.hasPrev = photoIndex > 0;
this.navigation.hasNext = photoIndex + 1 < pcList.length;
let to = this.activePhoto.getDimension();
//if target image out of screen -> scroll to there
if (this.getBodyScrollTop() > to.top || this.getBodyScrollTop() + this.getScreenHeight() < to.top) {
this.setBodyScrollTop(to.top);
}
this.photoDimension = this.calcLightBoxPhotoDimension(photoComponent.gridPhoto.photo);
this.navigation.hasPrev = index > 0;
this.navigation.hasNext = index + 1 < pcList.length;
this.activePhoto = photoComponent;
}
public show(photo: PhotoDTO) {
@ -90,8 +119,8 @@ export class GalleryLightboxComponent {
this.blackCanvasOpacity = 0;
this.photoDimension = selectedPhoto.getDimension();
document.getElementsByTagName('body')[0].style.overflow = 'hidden';
//disable scroll
this.overlayService.showOverlay();
setImmediate(() => {
this.lightboxDimension = <Dimension>{
top: 0,
@ -100,19 +129,13 @@ export class GalleryLightboxComponent {
height: this.getScreenHeight()
};
this.blackCanvasOpacity = 1.0;
this.showPhoto(selectedPhoto);
this.showPhoto(this.gridPhotoQL.toArray().indexOf(selectedPhoto));
});
}
public hide() {
this.enableAnimation();
this.fullScreenService.exitFullScreen();
let to = this.activePhoto.getDimension();
//iff target image out of screen -> scroll to there
if (this.getBodyScrollTop() > to.top || this.getBodyScrollTop() + this.getScreenHeight() < to.top) {
this.setBodyScrollTop(to.top);
}
this.lightboxDimension = this.activePhoto.getDimension();
this.lightboxDimension.top -= this.getBodyScrollTop();
@ -121,13 +144,24 @@ export class GalleryLightboxComponent {
setTimeout(() => {
this.visible = false;
this.activePhoto = null;
document.getElementsByTagName('body')[0].style.overflow = 'scroll';
this.overlayService.hideOverlay();
}, 500);
}
setGridPhotoQL(value: QueryList<GalleryPhotoComponent>) {
if (this.changeSubscription != null) {
this.changeSubscription.unsubscribe();
}
this.gridPhotoQL = value;
this.changeSubscription = this.gridPhotoQL.changes.subscribe(() => {
if (this.activePhotoId != null && this.gridPhotoQL.length > this.activePhotoId) {
this.updateActivePhoto(this.activePhotoId);
}
});
}
private findPhotoComponent(photo: any) {
let galleryPhotoComponents = this.gridPhotoQL.toArray();
for (let i = 0; i < galleryPhotoComponents.length; i++) {
@ -138,15 +172,26 @@ export class GalleryLightboxComponent {
return null;
}
//noinspection JSUnusedGlobalSymbols
@HostListener('window:keydown', ['$event'])
onKeyPress(e: KeyboardEvent) {
if (this.visible != true) {
return;
}
let event: KeyboardEvent = window.event ? <any>window.event : e;
switch (event.keyCode) {
case 37:
if (this.activePhotoId > 0) {
this.prevImage();
}
break;
case 39:
if (this.activePhotoId < this.gridPhotoQL.length - 1) {
this.nextImage();
}
break;
case 27: //escape
this.hide();
break;
}
}

View File

@ -4,12 +4,13 @@
[style.height.%]="imageSize.height"
[src]="thumbnailPath()"/>
<img *ngIf="gridPhoto"
<img *ngIf="gridPhoto !== null"
[style.width.%]="imageSize.width"
[style.height.%]="imageSize.height"
[src]="gridPhoto.getPhotoPath()"
(load)="onImageLoad()"
(error)="onImageError()"/>
</div>

View File

@ -1,4 +1,4 @@
import {Component, OnChanges, Input, ViewChild, ElementRef} from "@angular/core";
import {Component, Input, OnChanges} from "@angular/core";
import {GridPhoto} from "../../grid/GridPhoto";
@Component({
@ -8,12 +8,11 @@ import {GridPhoto} from "../../grid/GridPhoto";
})
export class GalleryLightboxPhotoComponent implements OnChanges {
@Input() gridPhoto:GridPhoto;
@Input() gridPhoto: GridPhoto;
public imageSize = {width: "auto", height: "100"};
@ViewChild('imgContainer') nativeElement:ElementRef;
imageLoaded:boolean = false;
imageLoaded: boolean = false;
constructor() {
}
@ -48,12 +47,12 @@ export class GalleryLightboxPhotoComponent implements OnChanges {
console.error("cant load image");
}
public showThumbnail():boolean {
public showThumbnail(): boolean {
return this.gridPhoto && !this.imageLoaded &&
(this.gridPhoto.isThumbnailAvailable() || this.gridPhoto.isReplacementThumbnailAvailable());
}
public thumbnailPath():string {
public thumbnailPath(): string {
if (this.gridPhoto.isThumbnailAvailable() === true)
return this.gridPhoto.getThumbnailPath();

View File

@ -1,9 +1,9 @@
import {Component, Input, OnChanges, ElementRef, ViewChild} from "@angular/core";
import {Component, ElementRef, HostListener, Input, OnChanges, ViewChild} from "@angular/core";
import {PhotoDTO} from "../../../../../common/entities/PhotoDTO";
import {Dimension} from "../../../model/IRenderable";
import {FullScreenService} from "../../fullscreen.service";
import {SebmGoogleMap} from "angular2-google-maps/core";
import {ThumbnailManagerService, IconThumbnail} from "../../thumnailManager.service";
import {IconThumbnail, ThumbnailManagerService} from "../../thumnailManager.service";
import {IconPhoto} from "../../IconPhoto";
@Component({
@ -138,6 +138,20 @@ export class GalleryMapLightboxComponent implements OnChanges {
return window.innerHeight;
}
//noinspection JSUnusedGlobalSymbols
@HostListener('window:keydown', ['$event'])
onKeyPress(e: KeyboardEvent) {
if (this.visible != true) {
return;
}
let event: KeyboardEvent = window.event ? <any>window.event : e;
switch (event.keyCode) {
case 27: //escape
this.hide();
break;
}
}
}

View File

@ -0,0 +1,60 @@
import {Injectable} from "@angular/core";
import {Event} from "../../../common/event/Event";
@Injectable()
export class OverlayService {
OnOverlayChange = new Event<boolean>();
private scrollWidth: number = null;
public showOverlay() {
//disable scrolling
document.getElementsByTagName('body')[0].style.overflow = 'hidden';
this.OnOverlayChange.trigger(true);
}
public hideOverlay() {
document.getElementsByTagName('body')[0].style.overflowY = 'scroll';
this.OnOverlayChange.trigger(false);
}
getScrollbarWidth() {
if (this.scrollWidth == null) {
let outer = document.createElement("div");
outer.style.visibility = "hidden";
outer.style.width = "100px";
outer.style.msOverflowStyle = "scrollbar"; // needed for WinJS apps
document.body.appendChild(outer);
let widthNoScroll = outer.offsetWidth;
// force scrollbars
outer.style.overflow = "scroll";
// add innerdiv
let inner = document.createElement("div");
inner.style.width = "100%";
outer.appendChild(inner);
let widthWithScroll = inner.offsetWidth;
// remove divs
outer.parentNode.removeChild(outer);
this.scrollWidth = widthNoScroll - widthWithScroll;
}
return this.scrollWidth;
}
getPhantomScrollbarWidth() {
if (document.getElementsByTagName('body')[0].style.overflow == 'hidden') {
return this.getScrollbarWidth();
}
return 0;
}
}