1
0
mirror of https://github.com/xuthus83/pigallery2.git synced 2025-01-14 14:43:17 +08:00

adding browser history support for lightbox

This commit is contained in:
Patrik J. Braun 2018-05-26 20:49:55 -04:00
parent 4afff63415
commit 62ccedcc2f
17 changed files with 221 additions and 70 deletions

View File

@ -7,6 +7,7 @@ import {Title} from '@angular/platform-browser';
import {NotificationService} from './model/notification.service';
import {ShareService} from './gallery/share.service';
import 'hammerjs';
import {QueryService} from './model/query.service';
@Component({
selector: 'app-pi-gallery2',

View File

@ -66,6 +66,7 @@ import {DefaultUrlSerializer, UrlSerializer, UrlTree} from '@angular/router';
import {IndexingSettingsComponent} from './settings/indexing/indexing.settings.component';
import {LanguageComponent} from './language/language.component';
import {I18n, MISSING_TRANSLATION_STRATEGY} from '@ngx-translate/i18n-polyfill';
import {QueryService} from './model/query.service';
@Injectable()
export class GoogleMapsConfig {
@ -173,18 +174,19 @@ export function translationsFactory(locale: string) {
NavigationService,
SettingsService,
OverlayService,
QueryService,
{
provide: TRANSLATIONS,
useFactory: translationsFactory,
deps: [LOCALE_ID]
},
I18n,
/*
{provide: TRANSLATIONS, useValue: translationsFactory('en')},
{provide: TRANSLATIONS_FORMAT, useValue: 'xlf'},
{provide: LOCALE_ID, useValue: 'en'},
{provide: MISSING_TRANSLATION_STRATEGY, useValue: MissingTranslationStrategy.Ignore},
*/
],
bootstrap: [AppComponent]
})

View File

@ -1,7 +1,7 @@
<ng2-slim-loading-bar [color]="'#337ab7'" [height]="'3px'"></ng2-slim-loading-bar>
<nav class="navbar navbar-dark bg-dark navbar-expand-md">
<a class="navbar-brand" [routerLink]="['/gallery','/']"
[queryParams]="_shareService.isSharing() ? { sk: _shareService.getSharingKey() }:{}">
[queryParams]="queryService.getParams()">
<img src="assets/icon_inv.png" width="30" height="30" class="d-inline-block align-top" alt="">
<strong>{{title}}</strong>
</a>

View File

@ -6,6 +6,7 @@ import {Config} from '../../../common/config/public/Config';
import {BehaviorSubject} from 'rxjs';
import {NotificationService} from '../model/notification.service';
import {ShareService} from '../gallery/share.service';
import {QueryService} from '../model/query.service';
@Component({
selector: 'app-frame',
@ -23,7 +24,7 @@ export class FrameComponent {
constructor(private _authService: AuthenticationService,
public notificationService: NotificationService,
public _shareService: ShareService) {
public queryService: QueryService) {
this.user = this._authService.user;
this.authenticationRequired = Config.Client.authenticationRequired;
this.title = Config.Client.applicationTitle;

View File

@ -1,5 +1,6 @@
<a #dirContainer class="button btn btn-default" [routerLink]="['/gallery', getDirectoryPath()]"
[queryParams]="_shareService.isSharing() ? { sk: _shareService.getSharingKey() }:{}"
<a #dirContainer class="button btn btn-default"
[routerLink]="['/gallery', getDirectoryPath()]"
[queryParams]="queryService.getParams()"
style="display: inline-block;">

View File

@ -7,6 +7,7 @@ import {Photo} from '../Photo';
import {Thumbnail, ThumbnailManagerService} from '../thumnailManager.service';
import {ShareService} from '../share.service';
import {PageHelper} from '../../model/page.helper';
import {QueryService} from '../../model/query.service';
@Component({
selector: 'app-gallery-directory',
@ -21,7 +22,7 @@ export class GalleryDirectoryComponent implements OnInit, OnDestroy {
constructor(private thumbnailService: ThumbnailManagerService,
private _sanitizer: DomSanitizer,
public _shareService: ShareService) {
public queryService: QueryService) {
}

View File

@ -1,4 +1,4 @@
<app-gallery-lightbox #lightbox (onLastElement)="onLightboxLastElement()"></app-gallery-lightbox>
<app-gallery-lightbox #lightbox></app-gallery-lightbox>
<app-frame>
<ng-container navbar>

View File

@ -14,6 +14,8 @@ import {UserRoles} from '../../../common/entities/UserDTO';
import {interval} from 'rxjs';
import {ContentWrapper} from '../../../common/entities/ConentWrapper';
import {PageHelper} from '../model/page.helper';
import {QueryService} from '../model/query.service';
import {LightboxStates} from './lightbox/lightbox.gallery.component';
@Component({
selector: 'app-gallery',
@ -92,6 +94,7 @@ export class GalleryComponent implements OnInit, OnDestroy {
this._galleryService.getDirectory(directoryName);
};
ngOnDestroy() {
@ -150,11 +153,5 @@ export class GalleryComponent implements OnInit, OnDestroy {
}
onLightboxLastElement() {
this.grid.renderARow();
}
}

View File

@ -12,6 +12,9 @@ export class GridRowBuilder {
private photoMargin: number,
private containerWidth: number) {
this.photoIndex = startIndex;
if (this.containerWidth <= 0) {
throw new Error('container width cant be <=0, got:' + this.containerWidth);
}
}
public addPhotos(number: number) {

View File

@ -1,7 +1,9 @@
<div #gridContainer>
<app-gallery-grid-photo
*ngFor="let gridPhoto of photosToRender"
(click)="lightbox.show(gridPhoto.photo)"
[routerLink]="[]"
[queryParams]="queryService.getParams(gridPhoto.photo)"
[gridPhoto]="gridPhoto"
[style.width.px]="gridPhoto.renderWidth"
[style.height.px]="gridPhoto.renderHeight"

View File

@ -9,23 +9,28 @@ import {
OnDestroy,
QueryList,
ViewChild,
ViewChildren
ViewChildren,
OnInit
} from '@angular/core';
import {PhotoDTO} from '../../../../common/entities/PhotoDTO';
import {GridRowBuilder} from './GridRowBuilder';
import {GalleryLightboxComponent} from '../lightbox/lightbox.gallery.component';
import {GalleryLightboxComponent, LightboxStates} from '../lightbox/lightbox.gallery.component';
import {GridPhoto} from './GridPhoto';
import {GalleryPhotoComponent} from './photo/photo.grid.gallery.component';
import {OverlayService} from '../overlay.service';
import {Config} from '../../../../common/config/public/Config';
import {PageHelper} from '../../model/page.helper';
import {Subscription} from 'rxjs';
import {ActivatedRoute, Params, Router} from '@angular/router';
import {QueryService} from '../../model/query.service';
import {SimpleChanges} from '@angular/core';
@Component({
selector: 'app-gallery-grid',
templateUrl: './grid.gallery.component.html',
styleUrls: ['./grid.gallery.component.css'],
})
export class GalleryGridComponent implements OnChanges, AfterViewInit, OnDestroy {
export class GalleryGridComponent implements OnChanges, OnInit, AfterViewInit, OnDestroy {
@ViewChild('gridContainer') gridContainer: ElementRef;
@ViewChildren(GalleryPhotoComponent) gridPhotoQL: QueryList<GalleryPhotoComponent>;
@ -47,9 +52,27 @@ export class GalleryGridComponent implements OnChanges, AfterViewInit, OnDestroy
private helperTime = null;
isAfterViewInit = false;
private renderedPhotoIndex = 0;
routeSubscription: Subscription = null;
delayedRenderUpToPhoto: string = null;
constructor(private overlayService: OverlayService,
private changeDetector: ChangeDetectorRef) {
private changeDetector: ChangeDetectorRef,
public queryService: QueryService,
private router: Router,
private route: ActivatedRoute) {
}
ngOnInit() {
this.routeSubscription = this.route.queryParams.subscribe((params: Params) => {
if (params[QueryService.PHOTO_PARAM] && params[QueryService.PHOTO_PARAM] !== '') {
this.delayedRenderUpToPhoto = params[QueryService.PHOTO_PARAM];
if (!this.photos || this.photos.length === 0) {
return;
}
this.renderUpToPhoto(params[QueryService.PHOTO_PARAM]);
}
});
}
ngOnChanges() {
@ -61,6 +84,9 @@ export class GalleryGridComponent implements OnChanges, AfterViewInit, OnDestroy
this.mergeNewPhotos();
this.helperTime = setTimeout(() => {
this.renderPhotos();
if (this.delayedRenderUpToPhoto) {
this.renderUpToPhoto(this.delayedRenderUpToPhoto);
}
}, 0);
}
@ -105,12 +131,23 @@ export class GalleryGridComponent implements OnChanges, AfterViewInit, OnDestroy
this.isAfterViewInit = true;
}
private renderUpToPhoto(photoName: string) {
const index = this.photos.findIndex(p => p.name === photoName);
if (index === -1) {
this.router.navigate([], {queryParams: this.queryService.getParams()});
return;
}
while (this.renderedPhotoIndex < index && this.renderARow()) {
}
}
public renderARow(): number {
if (this.renderedPhotoIndex >= this.photos.length) {
if (this.renderedPhotoIndex >= this.photos.length
|| this.containerWidth === 0) {
return null;
}
let maxRowHeight = this.screenHeight / this.MIN_ROW_COUNT;
const minRowHeight = this.screenHeight / this.MAX_ROW_COUNT;
@ -146,7 +183,7 @@ export class GalleryGridComponent implements OnChanges, AfterViewInit, OnDestroy
}
private sortPhotos() {
// sort pohots by date
// sort photos by date
this.photos.sort((a: PhotoDTO, b: PhotoDTO) => {
return a.metadata.creationDate - b.metadata.creationDate;
});
@ -168,7 +205,6 @@ export class GalleryGridComponent implements OnChanges, AfterViewInit, OnDestroy
break;
}
}
if (lastSameIndex > 0) {
this.photosToRender.splice(lastSameIndex, this.photosToRender.length - lastSameIndex);
this.renderedPhotoIndex = lastSameIndex;
@ -239,6 +275,8 @@ export class GalleryGridComponent implements OnChanges, AfterViewInit, OnDestroy
return false;
}
const pre = PageHelper.isScrollYVisible();
PageHelper.showScrollY();
// if the width changed a bit or the height changed a lot
if (this.containerWidth !== this.gridContainer.nativeElement.clientWidth
|| this.screenHeight < window.innerHeight * 0.75
@ -246,9 +284,15 @@ export class GalleryGridComponent implements OnChanges, AfterViewInit, OnDestroy
this.screenHeight = window.innerHeight;
this.containerWidth = this.gridContainer.nativeElement.clientWidth;
this.clearRenderedPhotos();
if (!pre) {
PageHelper.hideScrollY();
}
return true;
}
if (!pre) {
PageHelper.hideScrollY();
}
return false;
}

View File

@ -1,4 +1,4 @@
<div [hidden]="!visible" #root>
<div [hidden]="!isVisible()" #root>
<div class="blackCanvas"
[style.opacity]="blackCanvasOpacity">

View File

@ -19,6 +19,15 @@ import {animate, AnimationBuilder, AnimationPlayer, style} from '@angular/animat
import {GalleryLightboxPhotoComponent} from './photo/photo.lightbox.gallery.component';
import {Observable, Subscription, timer} from 'rxjs';
import {filter} from 'rxjs/operators';
import {ActivatedRoute, Params, Router} from '@angular/router';
import {PageHelper} from '../../model/page.helper';
import {QueryService} from '../../model/query.service';
export enum LightboxStates {
Open,
Closing,
Closed
}
@Component({
selector: 'app-gallery-lightbox',
@ -27,8 +36,6 @@ import {filter} from 'rxjs/operators';
})
export class GalleryLightboxComponent implements OnDestroy, OnInit {
@Output('onLastElement') onLastElement = new EventEmitter();
@ViewChild('photo') photoElement: GalleryLightboxPhotoComponent;
@ViewChild('lightbox') lightboxElement: ElementRef;
@ -39,8 +46,14 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
public activePhoto: GalleryPhotoComponent;
private gridPhotoQL: QueryList<GalleryPhotoComponent>;
public visible = false;
private changeSubscription: Subscription = null;
public status: LightboxStates = LightboxStates.Closed;
private subscription: {
photosChange: Subscription,
route: Subscription
} = {
photosChange: null,
route: null
};
private timer: Observable<number>;
private timerSub: Subscription;
public playBackState = 0;
@ -53,21 +66,41 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
startPhotoDimension: Dimension = <Dimension>{top: 0, left: 0, width: 0, height: 0};
iPvisibilityTimer = null;
visibilityTimer = null;
delayedPhotoShow: string = null;
constructor(public fullScreenService: FullScreenService,
private changeDetector: ChangeDetectorRef, private overlayService: OverlayService,
private _builder: AnimationBuilder) {
private changeDetector: ChangeDetectorRef,
private overlayService: OverlayService,
private _builder: AnimationBuilder,
private router: Router,
private queryService: QueryService,
private route: ActivatedRoute) {
}
ngOnInit(): void {
this.timer = timer(1000, 2000);
this.subscription.route = this.route.queryParams.subscribe((params: Params) => {
if (params[QueryService.PHOTO_PARAM] && params[QueryService.PHOTO_PARAM] !== '') {
if (!this.gridPhotoQL) {
return this.delayedPhotoShow = params[QueryService.PHOTO_PARAM];
}
this.onNavigateTo(params[QueryService.PHOTO_PARAM]);
} else if (this.status === LightboxStates.Open) {
this.delayedPhotoShow = null;
this.hideLigthbox();
}
});
}
ngOnDestroy(): void {
console.log('destroy');
this.pause();
if (this.changeSubscription != null) {
this.changeSubscription.unsubscribe();
if (this.subscription.photosChange != null) {
this.subscription.photosChange.unsubscribe();
}
if (this.subscription.route != null) {
this.subscription.route.unsubscribe();
}
if (this.visibilityTimer != null) {
@ -78,6 +111,41 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
}
}
onNavigateTo(photoName: string) {
if (this.activePhoto && this.activePhoto.gridPhoto.photo.name === photoName) {
return;
}
const photo = this.gridPhotoQL.find(i => i.gridPhoto.photo.name === photoName);
if (!photo) {
return this.delayedPhotoShow = photoName;
}
if (this.status === LightboxStates.Closed) {
this.showLigthbox(photo.gridPhoto.photo);
} else {
this.showPhoto(this.gridPhotoQL.toArray().indexOf(photo));
}
this.delayedPhotoShow = null;
}
setGridPhotoQL(value: QueryList<GalleryPhotoComponent>) {
if (this.subscription.photosChange != null) {
this.subscription.photosChange.unsubscribe();
}
this.gridPhotoQL = value;
this.subscription.photosChange = this.gridPhotoQL.changes.subscribe(() => {
if (this.activePhotoId != null && this.gridPhotoQL.length > this.activePhotoId) {
this.updateActivePhoto(this.activePhotoId);
}
if (this.delayedPhotoShow) {
this.onNavigateTo(this.delayedPhotoShow);
}
});
if (this.delayedPhotoShow) {
this.onNavigateTo(this.delayedPhotoShow);
}
}
//noinspection JSUnusedGlobalSymbols
@HostListener('window:resize', ['$event'])
onResize() {
@ -89,10 +157,10 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
public nextImage() {
if (this.activePhotoId + 1 < this.gridPhotoQL.length) {
this.showPhoto(this.activePhotoId + 1);
if (this.activePhotoId + 3 >= this.gridPhotoQL.length) {
this.navigateToPhoto(this.activePhotoId + 1);
/*if (this.activePhotoId + 3 >= this.gridPhotoQL.length) {
this.onLastElement.emit({}); // trigger to render more photos if there are
}
}*/
return;
}
}
@ -100,29 +168,38 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
public prevImage() {
this.pause();
if (this.activePhotoId > 0) {
this.showPhoto(this.activePhotoId - 1);
this.navigateToPhoto(this.activePhotoId - 1);
return;
}
}
private navigateToPhoto(photoIndex: number) {
this.router.navigate([],
{queryParams: this.queryService.getParams(this.gridPhotoQL.toArray()[photoIndex].gridPhoto.photo)});
/*
this.activePhoto = null;
this.changeDetector.detectChanges();
this.updateActivePhoto(photoIndex, resize);*/
}
private showPhoto(photoIndex: number, resize: boolean = true) {
this.activePhoto = null;
this.changeDetector.detectChanges();
this.updateActivePhoto(photoIndex, resize);
}
public show(photo: PhotoDTO) {
public showLigthbox(photo: PhotoDTO) {
this.controllersVisible = true;
this.showControls();
this.visible = true;
this.status = LightboxStates.Open;
const selectedPhoto = this.findPhotoComponent(photo);
if (selectedPhoto === null) {
throw new Error('Can\'t find Photo');
}
const lightboxDimension = selectedPhoto.getDimension();
lightboxDimension.top -= this.getBodyScrollTop();
lightboxDimension.top -= PageHelper.ScrollY;
this.animating = true;
this.animatePhoto(selectedPhoto.getDimension(), this.calcLightBoxPhotoDimension(selectedPhoto.gridPhoto.photo)).onDone(() => {
this.animating = false;
@ -148,7 +225,7 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
//noinspection JSUnusedGlobalSymbols
@HostListener('window:keydown', ['$event'])
onKeyPress(e: KeyboardEvent) {
if (this.visible !== true) {
if (this.status !== LightboxStates.Open) {
return;
}
const event: KeyboardEvent = window.event ? <any>window.event : e;
@ -170,13 +247,19 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
}
public hide() {
this.router.navigate([],
{queryParams: this.queryService.getParams()});
}
private hideLigthbox() {
this.controllersVisible = false;
this.status = LightboxStates.Closing;
this.fullScreenService.exitFullScreen();
this.pause();
this.animating = true;
const lightboxDimension = this.activePhoto.getDimension();
lightboxDimension.top -= this.getBodyScrollTop();
lightboxDimension.top -= PageHelper.ScrollY;
this.blackCanvasOpacity = 0;
this.animatePhoto(this.calcLightBoxPhotoDimension(this.activePhoto.gridPhoto.photo), this.activePhoto.getDimension());
@ -186,8 +269,7 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
width: this.getPhotoFrameWidth(),
height: this.getPhotoFrameHeight()
}, lightboxDimension).onDone(() => {
this.visible = false;
this.status = LightboxStates.Closed;
this.activePhoto = null;
this.activePhotoId = null;
this.overlayService.hideOverlay();
@ -227,18 +309,6 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
}
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);
}
});
}
public toggleInfoPanel() {
@ -287,7 +357,7 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
if (this.navigation.hasNext) {
this.nextImage();
} else {
this.showPhoto(0);
this.navigateToPhoto(0);
}
});
this.playBackState = 1;
@ -326,21 +396,13 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
if (this.navigation.hasNext) {
this.nextImage();
} else {
this.showPhoto(0);
this.navigateToPhoto(0);
}
});
this.playBackState = 2;
}
private getBodyScrollTop(): number {
return window.scrollY;
}
private setBodyScrollTop(value: number) {
window.scrollTo(window.scrollX, value);
}
public getPhotoFrameWidth(): number {
return Math.max(window.innerWidth - this.infoPanelWidth, 0);
}
@ -372,15 +434,15 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
const to = this.activePhoto.getDimension();
// if target image out of screen -> scroll to there
if (this.getBodyScrollTop() > to.top || this.getBodyScrollTop() + this.getPhotoFrameHeight() < to.top) {
this.setBodyScrollTop(to.top);
if (PageHelper.ScrollY > to.top || PageHelper.ScrollY + this.getPhotoFrameHeight() < to.top) {
PageHelper.ScrollY = to.top;
}
}
@HostListener('mousemove')
onMousemove() {
onMouseMove() {
this.showControls();
}
@ -432,5 +494,9 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
return <Dimension>{top: top, left: left, width: width, height: height};
}
public isVisible(): boolean {
return this.status !== LightboxStates.Closed;
}
}

View File

@ -2,7 +2,7 @@
<ol id="directory-path" class="breadcrumb">
<li *ngFor="let path of routes" class="breadcrumb-item">
<a *ngIf="path.route" [routerLink]="['/gallery',path.route]"
[queryParams]="_shareService.isSharing() ? { sk: _shareService.getSharingKey() }:{}">{{path.name}}</a>
[queryParams]="queryService.getParams()">{{path.name}}</a>
<ng-container *ngIf="!path.route">{{path.name}}</ng-container>
</li>
</ol>

View File

@ -5,6 +5,7 @@ import {UserDTO} from '../../../../common/entities/UserDTO';
import {AuthenticationService} from '../../model/network/authentication.service';
import {ShareService} from '../share.service';
import {I18n} from '@ngx-translate/i18n-polyfill';
import {QueryService} from '../../model/query.service';
@Component({
selector: 'app-gallery-navbar',
@ -17,7 +18,7 @@ export class GalleryNavigatorComponent implements OnChanges {
routes: Array<NavigatorPath> = [];
constructor(private _authService: AuthenticationService,
public _shareService: ShareService,
public queryService: QueryService,
private i18n: I18n) {
}

View File

@ -11,6 +11,14 @@ export class PageHelper {
return this.supportPageOffset ? window.pageYOffset : this.isCSS1Compat ? document.documentElement.scrollTop : document.body.scrollTop;
}
public static get ScrollX(): number {
return this.supportPageOffset ? window.pageXOffset : this.isCSS1Compat ? document.documentElement.scrollLeft : document.body.scrollLeft;
}
public static set ScrollY(value: number) {
window.scrollTo(this.ScrollX, value);
}
public static showScrollY() {
PageHelper.body.style.overflowY = 'scroll';
}

View File

@ -0,0 +1,24 @@
import {Injectable} from '@angular/core';
import {ShareService} from '../gallery/share.service';
import {PhotoDTO} from '../../../common/entities/PhotoDTO';
@Injectable()
export class QueryService {
public static readonly PHOTO_PARAM = 'p';
constructor(private shareService: ShareService) {
}
getParams(photo?: PhotoDTO): { [key: string]: string } {
const query = {};
if (photo) {
query[QueryService.PHOTO_PARAM] = photo.name;
}
if (this.shareService.isSharing()) {
query['sk'] = this.shareService.getSharingKey();
}
return query;
}
}