From 4e283ecc93d616d978564181a431d1f8a08e55a9 Mon Sep 17 00:00:00 2001 From: Varun Patil Date: Sun, 4 Jun 2023 10:44:35 -0700 Subject: [PATCH] refactor: use consistent preview sizes (fix #679) Signed-off-by: Varun Patil --- lib/Db/TimelineQueryMap.php | 4 +- src/bootstrap.ts | 2 +- src/components/frame/Cluster.vue | 2 +- src/components/frame/Folder.vue | 14 +++-- src/components/frame/Photo.vue | 13 ++--- src/components/modal/AlbumPicker.vue | 9 ++-- src/components/modal/ShareModal.vue | 5 +- src/components/top-matter/MapSplitMatter.vue | 5 +- src/components/top-matter/OnThisDay.vue | 5 +- src/components/viewer/ImageEditor.vue | 2 +- src/components/viewer/Viewer.vue | 9 ++-- src/services/utils/helpers.ts | 56 ++++++++++++++++++-- 12 files changed, 95 insertions(+), 31 deletions(-) diff --git a/lib/Db/TimelineQueryMap.php b/lib/Db/TimelineQueryMap.php index 870d9687..0b37c422 100644 --- a/lib/Db/TimelineQueryMap.php +++ b/lib/Db/TimelineQueryMap.php @@ -107,7 +107,7 @@ trait TimelineQueryMap // SELECT these files from the filecache $query = $this->connection->getQueryBuilder(); - $query->select('m.fileid', 'm.dayid', 'm.mapcluster', 'f.etag') + $query->select('m.fileid', 'm.dayid', 'm.mapcluster', 'm.h', 'm.w', 'f.etag') ->from('memories', 'm') ->innerJoin('m', 'filecache', 'f', $query->expr()->eq('m.fileid', 'f.fileid')) ->where($query->expr()->in('m.fileid', $query->createNamedParameter($fileIds, IQueryBuilder::PARAM_INT_ARRAY))) @@ -119,6 +119,8 @@ trait TimelineQueryMap $row['fileid'] = (int) $row['fileid']; $row['mapcluster'] = (int) $row['mapcluster']; $row['dayid'] = (int) $row['dayid']; + $row['h'] = (int) $row['h']; + $row['w'] = (int) $row['w']; } return $files; diff --git a/src/bootstrap.ts b/src/bootstrap.ts index 46ef2ff0..6086338d 100644 --- a/src/bootstrap.ts +++ b/src/bootstrap.ts @@ -48,7 +48,7 @@ declare global { var mViewer: { open: (anchorPhoto: IPhoto, rows: IRow[]) => Promise; - openStatic(photo: IPhoto, list: IPhoto[], thumbSize?: number): Promise; + openStatic(photo: IPhoto, list: IPhoto[], thumbSize?: 256 | 512): Promise; close: () => void; isOpen: () => boolean; }; diff --git a/src/components/frame/Cluster.vue b/src/components/frame/Cluster.vue index 78f98b00..f0e6dce3 100644 --- a/src/components/frame/Cluster.vue +++ b/src/components/frame/Cluster.vue @@ -68,7 +68,7 @@ export default defineComponent({ etag: this.album.album_id, flag: 0, } as unknown as IPhoto; - return getPreviewUrl(mock, true, 512); + return getPreviewUrl({ photo: mock, sqsize: 512 }); } return API.CLUSTER_PREVIEW(this.data.cluster_type, this.data.cluster_id); diff --git a/src/components/frame/Folder.vue b/src/components/frame/Folder.vue index 169c4400..c0af221e 100644 --- a/src/components/frame/Folder.vue +++ b/src/components/frame/Folder.vue @@ -17,7 +17,7 @@
- +
@@ -28,7 +28,7 @@ import { defineComponent, PropType } from 'vue'; import { IFolder, IPhoto } from '../../types'; -import { getPreviewUrl } from '../../services/utils/helpers'; +import * as utils from '../../services/utils/helpers'; import UserConfig from '../../mixins/UserConfig'; import FolderIcon from 'vue-material-design-icons/Folder.vue'; @@ -53,8 +53,6 @@ export default defineComponent({ previews: [] as IPhoto[], // Error occured fetching thumbs error: false, - // Passthrough - getPreviewUrl, }), computed: { @@ -107,6 +105,14 @@ export default defineComponent({ } } }, + + /** Get preview url */ + previewUrl(info: IPhoto) { + return utils.getPreviewUrl({ + photo: info, + sqsize: 256, + }); + }, }, }); diff --git a/src/components/frame/Photo.vue b/src/components/frame/Photo.vue index 0c7107ac..ab7ee66b 100644 --- a/src/components/frame/Photo.vue +++ b/src/components/frame/Photo.vue @@ -160,7 +160,7 @@ export default defineComponent({ /** Get url of the photo */ url() { - let base = 256; + let base: 256 | 512 = 256; // Check if displayed size is larger than the image if (this.data.dispH! > base * 0.9 && this.data.dispW! > base * 0.9) { @@ -172,13 +172,10 @@ export default defineComponent({ base = 512; } - // Make the shorter dimension equal to base - let size = base; - if (this.data.w && this.data.h) { - size = Math.floor((base * Math.max(this.data.w, this.data.h)) / Math.min(this.data.w, this.data.h)) - 1; - } - - return utils.getPreviewUrl(this.data, false, size); + return utils.getPreviewUrl({ + photo: this.data, + msize: base, + }); }, /** Set src with overlay face rect */ diff --git a/src/components/modal/AlbumPicker.vue b/src/components/modal/AlbumPicker.vue index 75608a76..c505df9b 100644 --- a/src/components/modal/AlbumPicker.vue +++ b/src/components/modal/AlbumPicker.vue @@ -91,13 +91,12 @@ export default defineComponent({ methods: { toCoverUrl(fileId: string | number) { - return getPreviewUrl( - { + return getPreviewUrl({ + photo: { fileid: Number(fileId), } as IPhoto, - true, - 256 - ); + sqsize: 256, + }); }, albumCreatedHandler() { diff --git a/src/components/modal/ShareModal.vue b/src/components/modal/ShareModal.vue index 8d9c5416..302200ec 100644 --- a/src/components/modal/ShareModal.vue +++ b/src/components/modal/ShareModal.vue @@ -154,7 +154,10 @@ export default defineComponent({ }, async sharePreview() { - const src = utils.getPreviewUrl(this.photo!, false, 2048); + const src = utils.getPreviewUrl({ + photo: this.photo!, + size: 2048, + }); this.shareWithHref(src, true); }, diff --git a/src/components/top-matter/MapSplitMatter.vue b/src/components/top-matter/MapSplitMatter.vue index 02a7621d..0952e0fc 100644 --- a/src/components/top-matter/MapSplitMatter.vue +++ b/src/components/top-matter/MapSplitMatter.vue @@ -281,7 +281,10 @@ export default defineComponent({ }, clusterPreviewUrl(cluster: IMarkerCluster) { - return utils.getPreviewUrl(cluster.preview, false, 256); + return utils.getPreviewUrl({ + photo: cluster.preview, + msize: 256, + }); }, clusterIconClass(cluster: IMarkerCluster) { diff --git a/src/components/top-matter/OnThisDay.vue b/src/components/top-matter/OnThisDay.vue index 27918c22..bb092e62 100644 --- a/src/components/top-matter/OnThisDay.vue +++ b/src/components/top-matter/OnThisDay.vue @@ -151,7 +151,10 @@ export default defineComponent({ // Get random photo year.preview ||= utils.randomChoice(year.photos); - year.url = utils.getPreviewUrl(year.preview, false, 512); + year.url = utils.getPreviewUrl({ + photo: year.preview, + msize: 512, + }); } await this.$nextTick(); diff --git a/src/components/viewer/ImageEditor.vue b/src/components/viewer/ImageEditor.vue index e090dcea..f39899e5 100644 --- a/src/components/viewer/ImageEditor.vue +++ b/src/components/viewer/ImageEditor.vue @@ -49,7 +49,7 @@ export default defineComponent({ return { source: this.photo.h && this.photo.w - ? utils.getPreviewUrl(this.photo, false, 'screen') + ? utils.getPreviewUrl({ photo: this.photo, size: 'screen' }) : API.IMAGE_DECODABLE(this.photo.fileid, this.photo.etag), defaultSavedImageName: this.defaultSavedImageName, diff --git a/src/components/viewer/Viewer.vue b/src/components/viewer/Viewer.vue index dd48a7dd..de62d3f2 100644 --- a/src/components/viewer/Viewer.vue +++ b/src/components/viewer/Viewer.vue @@ -694,7 +694,8 @@ export default defineComponent({ preload(dayIdx + 3); // Get thumb image - const thumbSrc: string = this.thumbElem(photo)?.getAttribute('src') || utils.getPreviewUrl(photo, false, 256); + const thumbSrc: string = + this.thumbElem(photo)?.getAttribute('src') || utils.getPreviewUrl({ photo, msize: 256 }); // Get full image return { @@ -739,7 +740,7 @@ export default defineComponent({ }, /** Open with a static list of photos */ - async openStatic(photo: IPhoto, list: IPhoto[], thumbSize?: number) { + async openStatic(photo: IPhoto, list: IPhoto[], thumbSize?: 256 | 512) { this.list = list; const photoswipe = await this.createBase({ index: list.findIndex((p) => p.fileid === photo.fileid), @@ -750,7 +751,7 @@ export default defineComponent({ photoswipe.addFilter('itemData', (itemData, index) => ({ ...this.getItemData(this.list[index]), - msrc: thumbSize ? utils.getPreviewUrl(photo, false, thumbSize) : undefined, + msrc: thumbSize ? utils.getPreviewUrl({ photo, msize: thumbSize }) : undefined, })); this.isOpen = true; @@ -759,7 +760,7 @@ export default defineComponent({ /** Get base data object */ getItemData(photo: IPhoto) { - let previewUrl = utils.getPreviewUrl(photo, false, 'screen'); + let previewUrl = utils.getPreviewUrl({ photo, size: 'screen' }); const isvideo = photo.flag & this.c.FLAG_IS_VIDEO; // Preview aren't animated diff --git a/src/services/utils/helpers.ts b/src/services/utils/helpers.ts index 830c5c7c..6eab58f6 100644 --- a/src/services/utils/helpers.ts +++ b/src/services/utils/helpers.ts @@ -11,8 +11,48 @@ export function isMobile() { return globalThis.windowInnerWidth <= 768; } -/** Get preview URL from photo object */ -export function getPreviewUrl(photo: IPhoto, square: boolean, size: number | [number, number] | 'screen') { +/** Preview generation options */ +type PreviewOpts = { + /** Photo object to create preview for */ + photo: IPhoto; +}; +type PreviewOptsSize = PreviewOpts & { + /** + * Directly specify the size of the preview. + * If you already know the size of the photo, use msize instead, + * so that caching can be utilized best. A size of 256 is not allowed + * here size the thumbnails are not pre-generated. + */ + size: 512 | 1024 | 2048 | [number, number] | 'screen'; +}; +type PreviewOptsMsize = PreviewOpts & { + /** + * Size of minimum edge of the preview (recommended). + * This can only be used if the photo object has width and height. + */ + msize: 256 | 512 | 1024 | 2048; +}; +type PreviewOptsSquare = PreviewOpts & { + /** + * Size of the square preview. + * Note that these will still be cached and requested with XImg multipreview. + */ + sqsize: 256 | 512 | 1024; +}; + +/** + * Get preview URL from photo object + * + * @param opts Preview options + */ +export function getPreviewUrl(opts: PreviewOptsSize | PreviewOptsMsize | PreviewOptsSquare) { + // Destructure does not work with union types + let { photo, size, msize, sqsize } = opts as PreviewOptsSize & PreviewOptsMsize & PreviewOptsSquare; + + // Square size is just size + const square = sqsize !== undefined; + if (square) size = sqsize as any; + // Native preview if (photo.flag & constants.c.FLAG_IS_LOCAL) { return API.Q(nativex.API.IMAGE_PREVIEW(photo.fileid), { c: photo.etag }); @@ -25,8 +65,18 @@ export function getPreviewUrl(photo: IPhoto, square: boolean, size: number | [nu size = [sw, sh]; } + // Base size conversion + if (msize !== undefined) { + if (photo.w && photo.h) { + size = (Math.floor((msize * Math.max(photo.w, photo.h)) / Math.min(photo.w, photo.h)) - 1) as any; + } else { + console.warn('Photo has no width or height but using msize'); + size = msize === 256 ? 512 : msize; + } + } + // Convert to array - const [x, y] = typeof size === 'number' ? [size, size] : size; + const [x, y] = typeof size === 'number' ? [size, size] : size!; return API.Q(API.IMAGE_PREVIEW(photo.fileid), { c: photo.etag,