1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2025-02-07 13:41:44 +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 {BehaviorSubject, Observable} from 'rxjs';
import {PhotoDTO} from '../../../../../common/entities/PhotoDTO'; import {PhotoDTO} from '../../../../../common/entities/PhotoDTO';
import {DirectoryContent} from '../contentLoader.service'; import {DirectoryContent} from '../contentLoader.service';
import {map, switchMap} from 'rxjs/operators'; import {debounceTime, map, switchMap} from 'rxjs/operators';
export enum FilterRenderType { export enum FilterRenderType {
enum = 1, enum = 1,
@ -39,19 +39,19 @@ export class FilterService {
{ {
name: $localize`Faces`, name: $localize`Faces`,
mapFn: (m: PhotoDTO): string[] => mapFn: (m: PhotoDTO): string[] =>
m.metadata.faces m.metadata.faces
? m.metadata.faces.map((f) => f.name) ? m.metadata.faces.map((f) => f.name)
: ['<' + $localize`no face` + '>'], : ['<' + $localize`no face` + '>'],
renderType: FilterRenderType.enum, renderType: FilterRenderType.enum,
isArrayValue: true, isArrayValue: true,
}, },
{ {
name: $localize`Faces groups`, name: $localize`Faces groups`,
mapFn: (m: PhotoDTO): string => mapFn: (m: PhotoDTO): string =>
m.metadata.faces m.metadata.faces
?.map((f) => f.name) ?.map((f) => f.name)
.sort() .sort()
.join(', '), .join(', '),
renderType: FilterRenderType.enum, renderType: FilterRenderType.enum,
isArrayValue: false, isArrayValue: false,
}, },
@ -124,18 +124,18 @@ export class FilterService {
private getStatistic(prefiltered: DirectoryContent): { date: Date, endDate: Date, dateStr: string, count: number, max: number }[] { private getStatistic(prefiltered: DirectoryContent): { date: Date, endDate: Date, dateStr: string, count: number, max: number }[] {
if (!prefiltered || if (!prefiltered ||
!prefiltered.media || !prefiltered.media ||
prefiltered.media.length === 0) { prefiltered.media.length === 0) {
return []; return [];
} }
const ret: { date: Date, endDate: Date, dateStr: string, count: number, max: number }[] = []; const ret: { date: Date, endDate: Date, dateStr: string, count: number, max: number }[] = [];
const minDate = prefiltered.media.reduce( const minDate = prefiltered.media.reduce(
(p, curr) => Math.min(p, curr.metadata.creationDate), (p, curr) => Math.min(p, curr.metadata.creationDate),
Number.MAX_VALUE - 1 Number.MAX_VALUE - 1
); );
const maxDate = prefiltered.media.reduce( const maxDate = prefiltered.media.reduce(
(p, curr) => Math.max(p, curr.metadata.creationDate), (p, curr) => Math.max(p, curr.metadata.creationDate),
Number.MIN_VALUE + 1 Number.MIN_VALUE + 1
); );
const diff = (maxDate - minDate) / 1000; const diff = (maxDate - minDate) / 1000;
const H = 60 * 60; const H = 60 * 60;
@ -221,124 +221,125 @@ export class FilterService {
} }
public applyFilters( public applyFilters(
directoryContent: Observable<DirectoryContent> directoryContent: Observable<DirectoryContent>
): Observable<DirectoryContent> { ): Observable<DirectoryContent> {
return directoryContent.pipe( return directoryContent.pipe(
switchMap((dirContent: DirectoryContent) => { debounceTime(1),
this.statistic = this.getStatistic(dirContent); switchMap((dirContent: DirectoryContent) => {
this.resetFilters(false); this.statistic = this.getStatistic(dirContent);
return this.activeFilters.pipe( this.resetFilters(false);
map((afilters) => { return this.activeFilters.pipe(
if (!dirContent || !dirContent.media || (!afilters.filtersVisible && !afilters.areFiltersActive)) { map((afilters) => {
return dirContent; 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;
} }
if (f.filter.isArrayValue) {
// clone, so the original won't get overwritten c.media = c.media.filter((m) => {
const c = { const mapped = f.filter.mapFn(m as PhotoDTO) as string[];
media: dirContent.media, if (!mapped) {
directories: dirContent.directories, return true;
metaFile: dirContent.metaFile, }
}; return mapped.indexOf(opt.name) === -1;
});
/* 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 { } else {
afilters.dateFilter.minDate = Number.MIN_VALUE; c.media = c.media.filter(
afilters.dateFilter.maxDate = Number.MAX_VALUE; (m) =>
afilters.dateFilter.minFilter = Number.MIN_VALUE; (f.filter.mapFn(m as PhotoDTO) as string) !== opt.name
afilters.dateFilter.maxFilter = Number.MAX_VALUE; );
} }
});
// filters }
for (const f of afilters.selectedFilters) { // If the number of photos did not change, the filters are not active
afilters.areFiltersActive = c.media.length !== dirContent.media.length;
/* Update filter options */ return c;
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;
})
);
})
); );
} }

View File

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

View File

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