diff --git a/src/common/QueryParams.ts b/src/common/QueryParams.ts index 80a9413c..9ad32c4d 100644 --- a/src/common/QueryParams.ts +++ b/src/common/QueryParams.ts @@ -14,6 +14,7 @@ export const QueryParams = { query: 'qs', }, photo: 'p', + playback: 'play', sharingKey_query: 'sk', sharingKey_params: 'sharingKey', directory: 'directory', diff --git a/src/frontend/app/model/query.service.ts b/src/frontend/app/model/query.service.ts index 9a71f1e8..8035723b 100644 --- a/src/frontend/app/model/query.service.ts +++ b/src/frontend/app/model/query.service.ts @@ -10,32 +10,35 @@ import {ContentLoaderService} from '../ui/gallery/contentLoader.service'; @Injectable() export class QueryService { constructor( - private shareService: ShareService, - private galleryService: ContentLoaderService + private shareService: ShareService, + private galleryService: ContentLoaderService ) { } getMediaStringId(media: MediaDTO): string { if (this.galleryService.isSearchResult()) { return Utils.concatUrls( - media.directory.path, - media.directory.name, - media.name + media.directory.path, + media.directory.name, + media.name ); } else { return media.name; } } - getParams(media?: MediaDTO): { [key: string]: string } { + getParams(lightbox?: { media?: MediaDTO, playing?: boolean }): { [key: string]: string } { const query: { [key: string]: string } = {}; - if (media) { - query[QueryParams.gallery.photo] = this.getMediaStringId(media); + if (lightbox?.media) { + query[QueryParams.gallery.photo] = this.getMediaStringId(lightbox?.media); + } + if (lightbox?.playing) { + query[QueryParams.gallery.playback] = 'true'; } if (Config.Sharing.enabled === true) { if (this.shareService.isSharing()) { query[QueryParams.gallery.sharingKey_query] = - this.shareService.getSharingKey(); + this.shareService.getSharingKey(); } } return query; @@ -48,14 +51,14 @@ export class QueryService { if (Config.Sharing.enabled === true) { if (this.shareService.isSharing()) { params[QueryParams.gallery.sharingKey_query] = - this.shareService.getSharingKey(); + this.shareService.getSharingKey(); } } if ( - directory && - directory.lastModified && - directory.lastScanned && - !directory.isPartial + directory && + directory.lastModified && + directory.lastScanned && + !directory.isPartial ) { params[QueryParams.gallery.knownLastModified] = directory.lastModified; params[QueryParams.gallery.knownLastScanned] = directory.lastScanned; diff --git a/src/frontend/app/ui/gallery/grid/grid.gallery.component.ts b/src/frontend/app/ui/gallery/grid/grid.gallery.component.ts index fa93b692..972d3db5 100644 --- a/src/frontend/app/ui/gallery/grid/grid.gallery.component.ts +++ b/src/frontend/app/ui/gallery/grid/grid.gallery.component.ts @@ -192,7 +192,7 @@ export class GalleryGridComponent photoClicked(media: MediaDTO): void { this.router.navigate([], { - queryParams: this.queryService.getParams(media), + queryParams: this.queryService.getParams({media}), }); } diff --git a/src/frontend/app/ui/gallery/lightbox/controls/controls.lightbox.gallery.component.html b/src/frontend/app/ui/gallery/lightbox/controls/controls.lightbox.gallery.component.html index b93f119f..2f45201d 100644 --- a/src/frontend/app/ui/gallery/lightbox/controls/controls.lightbox.gallery.component.html +++ b/src/frontend/app/ui/gallery/lightbox/controls/controls.lightbox.gallery.component.html @@ -180,7 +180,7 @@ (click)="nextMediaManuallyTriggered()"> @@ -211,17 +211,17 @@ *ngIf="zoom == 1 && activePhoto && activePhoto.gridMedia.isPhoto()">
diff --git a/src/frontend/app/ui/gallery/lightbox/controls/controls.lightbox.gallery.component.ts b/src/frontend/app/ui/gallery/lightbox/controls/controls.lightbox.gallery.component.ts index 171b48c3..1fe5309e 100644 --- a/src/frontend/app/ui/gallery/lightbox/controls/controls.lightbox.gallery.component.ts +++ b/src/frontend/app/ui/gallery/lightbox/controls/controls.lightbox.gallery.component.ts @@ -16,11 +16,6 @@ import {FileSizePipe} from '../../../../pipes/FileSizePipe'; import {DatePipe} from '@angular/common'; import {LightBoxTitleTexts} from '../../../../../../common/config/public/ClientConfig'; -export enum PlayBackStates { - Paused = 1, - Play = 2, -} - @Component({ selector: 'app-lightbox-controls', @@ -39,17 +34,17 @@ export class ControlsLightboxComponent implements OnDestroy, OnInit, OnChanges { @Output() toggleFullScreen = new EventEmitter(); @Output() nextPhoto = new EventEmitter(); @Output() previousPhoto = new EventEmitter(); + @Output() togglePlayback = new EventEmitter(); @Input() navigation = {hasPrev: true, hasNext: true}; @Input() activePhoto: GalleryPhotoComponent; @Input() mediaElement: GalleryLightboxMediaComponent; @Input() photoFrameDim = {width: 1, height: 1, aspect: 1}; + @Input() slideShowRunning: boolean; public readonly facesEnabled = Config.Faces.enabled; public zoom = 1; - public playBackState: PlayBackStates = PlayBackStates.Paused; - public PlayBackStates = PlayBackStates; public playBackDurations = [1, 2, 5, 10, 15, 20, 30, 60]; public selectedSlideshowSpeed: number = null; public controllersDimmed = false; @@ -94,7 +89,7 @@ export class ControlsLightboxComponent implements OnDestroy, OnInit, OnChanges { if (this.zoom === zoom) { return; } - this.pause(); + this.stopSlideShow(); this.drag.x = (this.drag.x / this.zoom) * zoom; this.drag.y = (this.drag.y / this.zoom) * zoom; this.prevDrag.x = this.drag.x; @@ -117,15 +112,19 @@ export class ControlsLightboxComponent implements OnDestroy, OnInit, OnChanges { } ngOnDestroy(): void { - this.pause(); + this.stopSlideShow(); if (this.visibilityTimer != null) { clearTimeout(this.visibilityTimer); + this.visibilityTimer = null; } } ngOnChanges(): void { this.updateFaceContainerDim(); + if (this.slideShowRunning) { + this.runSlideShow(); + } } pan($event: { deltaY: number; deltaX: number; isFinal: boolean }): void { @@ -312,14 +311,19 @@ export class ControlsLightboxComponent implements OnDestroy, OnInit, OnChanges { this.ctx.stroke(); } - resetSlideshowTimer(): void { - if (this.playBackState == PlayBackStates.Play) { - this.play(); + private resetSlideshowTimer(): void { + if (this.slideShowRunning === true) { + this.stopSlideShow(); + this.runSlideShow(); } } - public play(): void { - this.pause(); + public runSlideShow(): void { + //timer already running, do not reset it. + if (this.timerSub) { + return; + } + this.stopSlideShow(); this.drawSliderProgress(0); this.timerSub = interval(100) .pipe(filter((t) => { @@ -328,7 +332,6 @@ export class ControlsLightboxComponent implements OnDestroy, OnInit, OnChanges { })) .pipe(skip(1)) // do not skip to next photo right away .subscribe(this.showNextMedia); - this.playBackState = PlayBackStates.Play; } public slideshowSpeedChanged() { @@ -340,12 +343,20 @@ export class ControlsLightboxComponent implements OnDestroy, OnInit, OnChanges { this.showControls(); } - public pause(): void { + public stopSlideShow(): void { if (this.timerSub != null) { this.timerSub.unsubscribe(); + this.timerSub = null; } this.ctx = null; - this.playBackState = PlayBackStates.Paused; + } + + playClicked() { + this.togglePlayback.emit(true); + } + + pauseClicked() { + this.togglePlayback.emit(false); } resetZoom(): void { @@ -520,6 +531,7 @@ export class ControlsLightboxComponent implements OnDestroy, OnInit, OnChanges { get BottomLeftTitle(): string { return this.getText(Config.Gallery.Lightbox.Titles.bottomLeftTitle); } + get BottomLeftSubtitle(): string { return this.getText(Config.Gallery.Lightbox.Titles.bottomLeftSubtitle); } diff --git a/src/frontend/app/ui/gallery/lightbox/lightbox.gallery.component.html b/src/frontend/app/ui/gallery/lightbox/lightbox.gallery.component.html index 251cee36..fc0dadae 100644 --- a/src/frontend/app/ui/gallery/lightbox/lightbox.gallery.component.html +++ b/src/frontend/app/ui/gallery/lightbox/lightbox.gallery.component.html @@ -37,12 +37,14 @@ *ngIf="isOpen()" #controls [activePhoto]="activePhoto" + [slideShowRunning]="slideShowRunning" (closed)="hide()" [navigation]="navigation" (nextPhoto)="nextImage()" (previousPhoto)="prevImage()" (toggleInfoPanel)="toggleInfoPanel()" (toggleFullScreen)="toggleFullscreen()" + (togglePlayback)="togglePlayback($event)" [photoFrameDim]="photoFrameDim" [mediaElement]="mediaElement"> diff --git a/src/frontend/app/ui/gallery/lightbox/lightbox.gallery.component.ts b/src/frontend/app/ui/gallery/lightbox/lightbox.gallery.component.ts index 261c1e9c..a3623b96 100644 --- a/src/frontend/app/ui/gallery/lightbox/lightbox.gallery.component.ts +++ b/src/frontend/app/ui/gallery/lightbox/lightbox.gallery.component.ts @@ -65,6 +65,7 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit { photosChange: null, route: null, }; + slideShowRunning: boolean; constructor( public fullScreenService: FullScreenService, @@ -105,10 +106,18 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit { this.updatePhotoFrameDim(); this.subscription.route = this.route.queryParams.subscribe( (params: Params) => { + const validPhoto = params[QueryParams.gallery.photo] && + params[QueryParams.gallery.photo] !== ''; + + + if (params[QueryParams.gallery.playback]) { + this.runSlideShow(); + } else { + this.stopSlideShow(); + } + this.delayedMediaShow = null; - if ( - params[QueryParams.gallery.photo] && - params[QueryParams.gallery.photo] !== '' + if (validPhoto ) { this.delayedMediaShow = params[QueryParams.gallery.photo]; // photos are not yet available to show @@ -120,13 +129,28 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit { this.delayedMediaShow = null; this.hideLightbox(); } + + } ); } + private runSlideShow() { + if (!this.activePhoto && this.gridPhotoQL?.length > 0) { + this.navigateToPhoto(0); + } + this.slideShowRunning = true; + this.controls?.runSlideShow(); + } + + private stopSlideShow() { + this.slideShowRunning = false; + this.controls?.stopSlideShow(); + } + ngOnDestroy(): void { if (this.controls) { - this.controls.pause(); + this.controls.stopSlideShow(); } if (this.subscription.photosChange != null) { this.subscription.photosChange.unsubscribe(); @@ -186,12 +210,18 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit { if (this.delayedMediaShow) { this.onNavigateTo(this.delayedMediaShow); } + if (this.slideShowRunning) { + this.runSlideShow(); + } } ); if (this.delayedMediaShow) { this.onNavigateTo(this.delayedMediaShow); } + if (this.slideShowRunning) { + this.runSlideShow(); + } } @HostListener('window:resize', ['$event']) @@ -213,7 +243,7 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit { public prevImage(): void { if (this.controls) { - this.controls.pause(); + this.controls.stopSlideShow(); } if (this.activePhotoId > 0) { this.navigateToPhoto(this.activePhotoId - 1); @@ -410,14 +440,13 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit { this.router .navigate([], { queryParams: this.queryService.getParams( - this.gridPhotoQL.get(photoIndex).gridMedia.media + {media: this.gridPhotoQL.get(photoIndex).gridMedia.media, playing: this.slideShowRunning} ), }) .then(() => { this.piTitleService.setMediaTitle(this.gridPhotoQL.get(photoIndex).gridMedia); }) .catch(console.error); - } private showPhoto(photoIndex: number, resize = true): void { @@ -434,7 +463,7 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit { this.fullScreenService.exitFullScreen(); if (this.controls) { - this.controls.pause(); + this.controls.stopSlideShow(); } this.animating = true; @@ -531,5 +560,14 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit { } return null; } + + togglePlayback(value: boolean): void { + if (this.slideShowRunning === value) { + return; + } + this.slideShowRunning = value; + // resets query. This side effect is to assign playback = true to the url + this.navigateToPhoto(this.activePhotoId); + } }