1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2025-02-01 13:17:55 +02:00

improve race condition for opening lightbox

This commit is contained in:
Patrik J. Braun 2023-09-20 22:03:25 +02:00
parent 1bf1306237
commit f4cdb5a83a
3 changed files with 250 additions and 245 deletions

View File

@ -2,7 +2,7 @@ import {Injectable} from '@angular/core';
import {BehaviorSubject, Observable} from 'rxjs';
import {PhotoDTO} from '../../../../../common/entities/PhotoDTO';
import {DirectoryContent} from '../contentLoader.service';
import {map, switchMap} from 'rxjs/operators';
import {debounceTime, map, switchMap} from 'rxjs/operators';
export enum FilterRenderType {
enum = 1,
@ -39,19 +39,19 @@ export class FilterService {
{
name: $localize`Faces`,
mapFn: (m: PhotoDTO): string[] =>
m.metadata.faces
? m.metadata.faces.map((f) => f.name)
: ['<' + $localize`no face` + '>'],
m.metadata.faces
? m.metadata.faces.map((f) => f.name)
: ['<' + $localize`no face` + '>'],
renderType: FilterRenderType.enum,
isArrayValue: true,
},
{
name: $localize`Faces groups`,
mapFn: (m: PhotoDTO): string =>
m.metadata.faces
?.map((f) => f.name)
.sort()
.join(', '),
m.metadata.faces
?.map((f) => f.name)
.sort()
.join(', '),
renderType: FilterRenderType.enum,
isArrayValue: false,
},
@ -124,18 +124,18 @@ export class FilterService {
private getStatistic(prefiltered: DirectoryContent): { date: Date, endDate: Date, dateStr: string, count: number, max: number }[] {
if (!prefiltered ||
!prefiltered.media ||
prefiltered.media.length === 0) {
!prefiltered.media ||
prefiltered.media.length === 0) {
return [];
}
const ret: { date: Date, endDate: Date, dateStr: string, count: number, max: number }[] = [];
const minDate = prefiltered.media.reduce(
(p, curr) => Math.min(p, curr.metadata.creationDate),
Number.MAX_VALUE - 1
(p, curr) => Math.min(p, curr.metadata.creationDate),
Number.MAX_VALUE - 1
);
const maxDate = prefiltered.media.reduce(
(p, curr) => Math.max(p, curr.metadata.creationDate),
Number.MIN_VALUE + 1
(p, curr) => Math.max(p, curr.metadata.creationDate),
Number.MIN_VALUE + 1
);
const diff = (maxDate - minDate) / 1000;
const H = 60 * 60;
@ -221,124 +221,125 @@ export class FilterService {
}
public applyFilters(
directoryContent: Observable<DirectoryContent>
directoryContent: Observable<DirectoryContent>
): Observable<DirectoryContent> {
return directoryContent.pipe(
switchMap((dirContent: DirectoryContent) => {
this.statistic = this.getStatistic(dirContent);
this.resetFilters(false);
return this.activeFilters.pipe(
map((afilters) => {
if (!dirContent || !dirContent.media || (!afilters.filtersVisible && !afilters.areFiltersActive)) {
return dirContent;
debounceTime(1),
switchMap((dirContent: DirectoryContent) => {
this.statistic = this.getStatistic(dirContent);
this.resetFilters(false);
return this.activeFilters.pipe(
map((afilters) => {
if (!dirContent || !dirContent.media || (!afilters.filtersVisible && !afilters.areFiltersActive)) {
return dirContent;
}
// clone, so the original won't get overwritten
const c = {
media: dirContent.media,
directories: dirContent.directories,
metaFile: dirContent.metaFile,
};
/* Date Selector */
if (c.media.length > 0) {
// Update date filter range
afilters.dateFilter.minDate = c.media.reduce(
(p, curr) => Math.min(p, curr.metadata.creationDate),
Number.MAX_VALUE - 1
);
afilters.dateFilter.maxDate = c.media.reduce(
(p, curr) => Math.max(p, curr.metadata.creationDate),
Number.MIN_VALUE + 1
);
// Add a few sec padding
afilters.dateFilter.minDate -= (afilters.dateFilter.minDate % 1000) + 1000;
afilters.dateFilter.maxDate += (afilters.dateFilter.maxDate % 1000) + 1000;
if (afilters.dateFilter.minFilter === Number.MIN_VALUE) {
afilters.dateFilter.minFilter = afilters.dateFilter.minDate;
}
if (afilters.dateFilter.maxFilter === Number.MAX_VALUE) {
afilters.dateFilter.maxFilter = afilters.dateFilter.maxDate;
}
// Apply Date filter
c.media = c.media.filter(
(m) =>
m.metadata.creationDate >= afilters.dateFilter.minFilter &&
m.metadata.creationDate <= afilters.dateFilter.maxFilter
);
} else {
afilters.dateFilter.minDate = Number.MIN_VALUE;
afilters.dateFilter.maxDate = Number.MAX_VALUE;
afilters.dateFilter.minFilter = Number.MIN_VALUE;
afilters.dateFilter.maxFilter = Number.MAX_VALUE;
}
// filters
for (const f of afilters.selectedFilters) {
/* Update filter options */
const valueMap: { [key: string]: any } = {};
f.options.forEach((o) => {
valueMap[o.name] = o;
o.count = 0; // reset count so unknown option can be removed at the end
});
if (f.filter.isArrayValue) {
c.media.forEach((m) => {
(f.filter.mapFn(m as PhotoDTO) as string[])?.forEach((v) => {
valueMap[v] = valueMap[v] || {
name: v,
count: 0,
selected: true,
};
valueMap[v].count++;
});
});
} else {
c.media.forEach((m) => {
const key = f.filter.mapFn(m as PhotoDTO) as string;
valueMap[key] = valueMap[key] || {
name: key,
count: 0,
selected: true,
};
valueMap[key].count++;
});
}
f.options = Object.values(valueMap)
.filter((o) => o.count > 0)
.sort((a, b) => b.count - a.count);
/* Apply filters */
f.options.forEach((opt) => {
if (opt.selected) {
return;
}
// clone, so the original won't get overwritten
const c = {
media: dirContent.media,
directories: dirContent.directories,
metaFile: dirContent.metaFile,
};
/* Date Selector */
if (c.media.length > 0) {
// Update date filter range
afilters.dateFilter.minDate = c.media.reduce(
(p, curr) => Math.min(p, curr.metadata.creationDate),
Number.MAX_VALUE - 1
);
afilters.dateFilter.maxDate = c.media.reduce(
(p, curr) => Math.max(p, curr.metadata.creationDate),
Number.MIN_VALUE + 1
);
// Add a few sec padding
afilters.dateFilter.minDate -= (afilters.dateFilter.minDate % 1000) + 1000;
afilters.dateFilter.maxDate += (afilters.dateFilter.maxDate % 1000) + 1000;
if (afilters.dateFilter.minFilter === Number.MIN_VALUE) {
afilters.dateFilter.minFilter = afilters.dateFilter.minDate;
}
if (afilters.dateFilter.maxFilter === Number.MAX_VALUE) {
afilters.dateFilter.maxFilter = afilters.dateFilter.maxDate;
}
// Apply Date filter
c.media = c.media.filter(
(m) =>
m.metadata.creationDate >= afilters.dateFilter.minFilter &&
m.metadata.creationDate <= afilters.dateFilter.maxFilter
);
if (f.filter.isArrayValue) {
c.media = c.media.filter((m) => {
const mapped = f.filter.mapFn(m as PhotoDTO) as string[];
if (!mapped) {
return true;
}
return mapped.indexOf(opt.name) === -1;
});
} else {
afilters.dateFilter.minDate = Number.MIN_VALUE;
afilters.dateFilter.maxDate = Number.MAX_VALUE;
afilters.dateFilter.minFilter = Number.MIN_VALUE;
afilters.dateFilter.maxFilter = Number.MAX_VALUE;
c.media = c.media.filter(
(m) =>
(f.filter.mapFn(m as PhotoDTO) as string) !== opt.name
);
}
// filters
for (const f of afilters.selectedFilters) {
/* Update filter options */
const valueMap: { [key: string]: any } = {};
f.options.forEach((o) => {
valueMap[o.name] = o;
o.count = 0; // reset count so unknown option can be removed at the end
});
if (f.filter.isArrayValue) {
c.media.forEach((m) => {
(f.filter.mapFn(m as PhotoDTO) as string[])?.forEach((v) => {
valueMap[v] = valueMap[v] || {
name: v,
count: 0,
selected: true,
};
valueMap[v].count++;
});
});
} else {
c.media.forEach((m) => {
const key = f.filter.mapFn(m as PhotoDTO) as string;
valueMap[key] = valueMap[key] || {
name: key,
count: 0,
selected: true,
};
valueMap[key].count++;
});
}
f.options = Object.values(valueMap)
.filter((o) => o.count > 0)
.sort((a, b) => b.count - a.count);
/* Apply filters */
f.options.forEach((opt) => {
if (opt.selected) {
return;
}
if (f.filter.isArrayValue) {
c.media = c.media.filter((m) => {
const mapped = f.filter.mapFn(m as PhotoDTO) as string[];
if (!mapped) {
return true;
}
return mapped.indexOf(opt.name) === -1;
});
} else {
c.media = c.media.filter(
(m) =>
(f.filter.mapFn(m as PhotoDTO) as string) !== opt.name
);
}
});
}
// If the number of photos did not change, the filters are not active
afilters.areFiltersActive = c.media.length !== dirContent.media.length;
return c;
})
);
})
});
}
// If the number of photos did not change, the filters are not active
afilters.areFiltersActive = c.media.length !== dirContent.media.length;
return c;
})
);
})
);
}

View File

@ -385,6 +385,9 @@ export class GalleryGridComponent
* Makes sure that the photo with the given mediaString is visible on the screen
*/
private renderUpToMedia(mediaStringId: string): void {
if (!this.mediaGroups) {
return;
}
let groupIndex = -1;
let mediaIndex = -1;
for (let i = 0; i < this.mediaGroups.length; ++i) {

View File

@ -67,14 +67,14 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
};
constructor(
public fullScreenService: FullScreenService,
private changeDetector: ChangeDetectorRef,
private overlayService: OverlayService,
private builder: AnimationBuilder,
private router: Router,
private queryService: QueryService,
private route: ActivatedRoute,
private piTitleService: PiTitleService
public fullScreenService: FullScreenService,
private changeDetector: ChangeDetectorRef,
private overlayService: OverlayService,
private builder: AnimationBuilder,
private router: Router,
private queryService: QueryService,
private route: ActivatedRoute,
private piTitleService: PiTitleService
) {
}
@ -104,22 +104,23 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
this.infoPanelMaxWidth = 1000;
this.updatePhotoFrameDim();
this.subscription.route = this.route.queryParams.subscribe(
(params: Params) => {
if (
params[QueryParams.gallery.photo] &&
params[QueryParams.gallery.photo] !== ''
) {
this.delayedMediaShow = params[QueryParams.gallery.photo]
// photos are not yet available to show
if (!this.gridPhotoQL) {
return;
}
this.onNavigateTo(params[QueryParams.gallery.photo]);
} else if (this.status === LightboxStates.Open) {
this.delayedMediaShow = null;
this.hideLightbox();
(params: Params) => {
this.delayedMediaShow = null;
if (
params[QueryParams.gallery.photo] &&
params[QueryParams.gallery.photo] !== ''
) {
this.delayedMediaShow = params[QueryParams.gallery.photo];
// photos are not yet available to show
if (!this.gridPhotoQL) {
return;
}
this.onNavigateTo(params[QueryParams.gallery.photo]);
} else if (this.status === LightboxStates.Open) {
this.delayedMediaShow = null;
this.hideLightbox();
}
}
);
}
@ -144,9 +145,9 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
onNavigateTo(photoStringId: string): string {
if (
this.activePhoto &&
this.queryService.getMediaStringId(this.activePhoto.gridMedia.media) ===
photoStringId
this.activePhoto &&
this.queryService.getMediaStringId(this.activePhoto.gridMedia.media) ===
photoStringId
) {
return;
}
@ -155,8 +156,8 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
this.controls.resetZoom();
}
const photo = this.gridPhotoQL.find(
(i): boolean =>
this.queryService.getMediaStringId(i.gridMedia.media) === photoStringId
(i): boolean =>
this.queryService.getMediaStringId(i.gridMedia.media) === photoStringId
);
if (!photo) {
return (this.delayedMediaShow = photoStringId);
@ -175,17 +176,17 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
}
this.gridPhotoQL = value;
this.subscription.photosChange = this.gridPhotoQL.changes.subscribe(
(): void => {
if (
this.activePhotoId != null &&
this.gridPhotoQL.length > this.activePhotoId
) {
this.updateActivePhoto(this.activePhotoId);
}
if (this.delayedMediaShow) {
this.onNavigateTo(this.delayedMediaShow);
}
(): void => {
if (
this.activePhotoId != null &&
this.gridPhotoQL.length > this.activePhotoId
) {
this.updateActivePhoto(this.activePhotoId);
}
if (this.delayedMediaShow) {
this.onNavigateTo(this.delayedMediaShow);
}
}
);
if (this.delayedMediaShow) {
@ -233,8 +234,8 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
lightboxDimension.top -= PageHelper.ScrollY;
this.animating = true;
this.animatePhoto(
selectedPhoto.getDimension(),
this.calcLightBoxPhotoDimension(selectedPhoto.gridMedia.media)
selectedPhoto.getDimension(),
this.calcLightBoxPhotoDimension(selectedPhoto.gridMedia.media)
).onDone((): void => {
this.animating = false;
this.status = LightboxStates.Open;
@ -257,41 +258,41 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
public hide(): void {
this.router
.navigate([], {queryParams: this.queryService.getParams()})
.then(() => {
this.piTitleService.setLastNonMedia();
})
.catch(console.error);
.navigate([], {queryParams: this.queryService.getParams()})
.then(() => {
this.piTitleService.setLastNonMedia();
})
.catch(console.error);
}
animatePhoto(from: Dimension, to: Dimension = from): AnimationPlayer {
const elem = this.builder
.build([
style(DimensionUtils.toString(from)),
animate('0.2s ease-in-out', style(DimensionUtils.toString(to))),
])
.create(this.mediaElement.elementRef.nativeElement);
.build([
style(DimensionUtils.toString(from)),
animate('0.2s ease-in-out', style(DimensionUtils.toString(to))),
])
.create(this.mediaElement.elementRef.nativeElement);
elem.play();
return elem;
}
animateLightbox(
from: Dimension = {
top: 0,
left: 0,
width: this.photoFrameDim.width,
height: this.photoFrameDim.height,
} as Dimension,
to: Dimension = from
from: Dimension = {
top: 0,
left: 0,
width: this.photoFrameDim.width,
height: this.photoFrameDim.height,
} as Dimension,
to: Dimension = from
): AnimationPlayer {
const elem = this.builder
.build([
style(DimensionUtils.toString(from)),
animate('0.2s ease-in-out', style(DimensionUtils.toString(to))),
])
.create(this.lightboxElement.nativeElement);
.build([
style(DimensionUtils.toString(from)),
animate('0.2s ease-in-out', style(DimensionUtils.toString(to))),
])
.create(this.lightboxElement.nativeElement);
elem.play();
return elem;
}
@ -311,30 +312,30 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
}, 1000);
const starPhotoPos = this.calcLightBoxPhotoDimension(
this.activePhoto.gridMedia.media
this.activePhoto.gridMedia.media
);
this.infoPanelWidth = 0;
this.updatePhotoFrameDim();
const endPhotoPos = this.calcLightBoxPhotoDimension(
this.activePhoto.gridMedia.media
this.activePhoto.gridMedia.media
);
if (enableAnimate) {
this.animatePhoto(starPhotoPos, endPhotoPos);
}
if (enableAnimate) {
this.animateLightbox(
{
top: 0,
left: 0,
width: Math.max(this.photoFrameDim.width - this.infoPanelMaxWidth, 0),
height: this.photoFrameDim.height,
} as Dimension,
{
top: 0,
left: 0,
width: this.photoFrameDim.width,
height: this.photoFrameDim.height,
} as Dimension
{
top: 0,
left: 0,
width: Math.max(this.photoFrameDim.width - this.infoPanelMaxWidth, 0),
height: this.photoFrameDim.height,
} as Dimension,
{
top: 0,
left: 0,
width: this.photoFrameDim.width,
height: this.photoFrameDim.height,
} as Dimension
);
}
}
@ -348,27 +349,27 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
this.infoPanelVisible = true;
const starPhotoPos = this.calcLightBoxPhotoDimension(
this.activePhoto.gridMedia.media
this.activePhoto.gridMedia.media
);
this.infoPanelWidth = this.infoPanelMaxWidth;
this.updatePhotoFrameDim();
const endPhotoPos = this.calcLightBoxPhotoDimension(
this.activePhoto.gridMedia.media
this.activePhoto.gridMedia.media
);
this.animatePhoto(starPhotoPos, endPhotoPos);
this.animateLightbox(
{
top: 0,
left: 0,
width: this.photoFrameDim.width + this.infoPanelMaxWidth,
height: this.photoFrameDim.height,
} as Dimension,
{
top: 0,
left: 0,
width: this.photoFrameDim.width,
height: this.photoFrameDim.height,
} as Dimension
{
top: 0,
left: 0,
width: this.photoFrameDim.width + this.infoPanelMaxWidth,
height: this.photoFrameDim.height,
} as Dimension,
{
top: 0,
left: 0,
width: this.photoFrameDim.width,
height: this.photoFrameDim.height,
} as Dimension
);
if (this.iPvisibilityTimer != null) {
clearTimeout(this.iPvisibilityTimer);
@ -394,28 +395,28 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
private updatePhotoFrameDim = (): void => {
this.photoFrameDim = {
width: Math.max(
window.innerWidth - this.infoPanelWidth,
0
window.innerWidth - this.infoPanelWidth,
0
),
height: window.innerHeight,
aspect: 0
};
this.photoFrameDim.aspect =
Math.round((this.photoFrameDim.width / this.photoFrameDim.height) * 100) /
100;
Math.round((this.photoFrameDim.width / this.photoFrameDim.height) * 100) /
100;
};
private navigateToPhoto(photoIndex: number): void {
this.router
.navigate([], {
queryParams: this.queryService.getParams(
this.gridPhotoQL.get(photoIndex).gridMedia.media
),
})
.then(() => {
this.piTitleService.setMediaTitle(this.gridPhotoQL.get(photoIndex).gridMedia);
})
.catch(console.error);
.navigate([], {
queryParams: this.queryService.getParams(
this.gridPhotoQL.get(photoIndex).gridMedia.media
),
})
.then(() => {
this.piTitleService.setMediaTitle(this.gridPhotoQL.get(photoIndex).gridMedia);
})
.catch(console.error);
}
@ -442,17 +443,17 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
this.blackCanvasOpacity = 0;
this.animatePhoto(
this.calcLightBoxPhotoDimension(this.activePhoto.gridMedia.media),
this.activePhoto.getDimension()
this.calcLightBoxPhotoDimension(this.activePhoto.gridMedia.media),
this.activePhoto.getDimension()
);
this.animateLightbox(
{
top: 0,
left: 0,
width: this.photoFrameDim.width,
height: this.photoFrameDim.height,
} as Dimension,
lightboxDimension
{
top: 0,
left: 0,
width: this.photoFrameDim.width,
height: this.photoFrameDim.height,
} as Dimension,
lightboxDimension
).onDone((): void => {
this.status = LightboxStates.Closed;
this.activePhoto = null;
@ -473,7 +474,7 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
if (resize) {
this.animatePhoto(
this.calcLightBoxPhotoDimension(this.activePhoto.gridMedia.media)
this.calcLightBoxPhotoDimension(this.activePhoto.gridMedia.media)
);
}
this.navigation.hasPrev = photoIndex > 0;
@ -483,8 +484,8 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
// if target image out of screen -> scroll to there
if (
PageHelper.ScrollY > to.top ||
PageHelper.ScrollY + this.photoFrameDim.height < to.top
PageHelper.ScrollY > to.top ||
PageHelper.ScrollY + this.photoFrameDim.height < to.top
) {
PageHelper.ScrollY = to.top;
}
@ -507,15 +508,15 @@ export class GalleryLightboxComponent implements OnDestroy, OnInit {
const windowAspect = this.photoFrameDim.aspect;
if (photoAspect < windowAspect) {
width = Math.round(
photo.metadata.size.width *
(this.photoFrameDim.height / photo.metadata.size.height)
photo.metadata.size.width *
(this.photoFrameDim.height / photo.metadata.size.height)
);
height = this.photoFrameDim.height;
} else {
width = this.photoFrameDim.width;
height = Math.round(
photo.metadata.size.height *
(this.photoFrameDim.width / photo.metadata.size.width)
photo.metadata.size.height *
(this.photoFrameDim.width / photo.metadata.size.width)
);
}
const top = this.photoFrameDim.height / 2 - height / 2;