Frontend: Add thumb model (photo viewer refactoring)

Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
Michael Mayer 2020-04-21 16:34:43 +02:00
parent 9344a52760
commit 3a257684bd
8 changed files with 184 additions and 80 deletions

View file

@ -1,41 +1,14 @@
import PhotoSwipe from "photoswipe"; import PhotoSwipe from "photoswipe";
import PhotoSwipeUI_Default from "photoswipe/dist/photoswipe-ui-default.js"; import PhotoSwipeUI_Default from "photoswipe/dist/photoswipe-ui-default.js";
const thumbs = window.clientConfig.thumbnails;
class Viewer { class Viewer {
constructor() { constructor() {
this.photos = [];
this.el = null; this.el = null;
this.gallery = null; this.gallery = null;
} }
photosWithSizes() {
return this.photos.map(this.createPhotoSizes);
}
createPhotoSizes(photo) {
const result = {
title: photo.PhotoTitle,
download_url: photo.getDownloadUrl(),
original_w: photo.FileWidth,
original_h: photo.FileHeight,
uuid: photo.PhotoUUID,
};
const thumbs = window.clientConfig.thumbnails;
for (let i = 0; i < thumbs.length; i++) {
let size = photo.calculateSize(thumbs[i].Width, thumbs[i].Height);
result[thumbs[i].Name] = {
src: photo.getThumbnailUrl(thumbs[i].Name),
w: size.width,
h: size.height,
};
}
return result;
}
getEl() { getEl() {
if (!this.el) { if (!this.el) {
this.el = document.getElementById("p-photo-viewer"); this.el = document.getElementById("p-photo-viewer");
@ -50,15 +23,12 @@ class Viewer {
return this.el; return this.el;
} }
show(photos, index = 0) { show(items, index = 0) {
if (!Array.isArray(photos) || photos.length === 0 || index >= photos.length) { if (!Array.isArray(items) || items.length === 0 || index >= items.length) {
console.log("Array passed to gallery was empty:", photos); console.log("Array passed to gallery was empty:", items);
return; return;
} }
this.photos = photos;
const shareButtons = [ const shareButtons = [
{id: "fit_720", template: "Tiny (size)", label: "Tiny", url: "{{raw_image_url}}", download: true}, {id: "fit_720", template: "Tiny (size)", label: "Tiny", url: "{{raw_image_url}}", download: true},
{id: "fit_1280", template: "Small (size)", label: "Small", url: "{{raw_image_url}}", download: true}, {id: "fit_1280", template: "Small (size)", label: "Small", url: "{{raw_image_url}}", download: true},
@ -83,21 +53,19 @@ class Viewer {
arrowEl: true, arrowEl: true,
preloaderEl: true, preloaderEl: true,
getImageURLForShare: function (button) { getImageURLForShare: function (button) {
const photo = gallery.currItem; const item = gallery.currItem;
if(button.id === "original") { if(button.id === "original") {
button.label = button.template.replace("size", photo.original_w + " × " + photo.original_h); button.label = button.template.replace("size", item.original_w + " × " + item.original_h);
return photo.download_url; return item.download_url;
} else { } else {
button.label = button.template.replace("size", photo[button.id].w + " × " + photo[button.id].h); button.label = button.template.replace("size", item[button.id].w + " × " + item[button.id].h);
return photo[button.id].src + "?download=1"; return item[button.id].src + "?download=1";
} }
}, },
}; };
let photosWithSizes = this.photosWithSizes(); let gallery = new PhotoSwipe(this.getEl(), PhotoSwipeUI_Default, items, options);
let gallery = new PhotoSwipe(this.getEl(), PhotoSwipeUI_Default, photosWithSizes, options);
let realViewportWidth; let realViewportWidth;
let realViewportHeight; let realViewportHeight;
let previousSize; let previousSize;
@ -143,8 +111,6 @@ class Viewer {
} }
static mapViewportToImageSize(viewportWidth, viewportHeight) { static mapViewportToImageSize(viewportWidth, viewportHeight) {
const thumbs = window.clientConfig.thumbnails;
for (let i = 0; i < thumbs.length; i++) { for (let i = 0; i < thumbs.length; i++) {
if (thumbs[i].Width >= viewportWidth || thumbs[i].Height >= viewportHeight) { if (thumbs[i].Width >= viewportWidth || thumbs[i].Height >= viewportHeight) {
return thumbs[i].Name; return thumbs[i].Name;

View file

@ -9,16 +9,17 @@ class Model {
} }
} }
setValues(values) { setValues(values, scalarOnly) {
if (!values) return; if (!values) return;
for (let key in values) { for (let key in values) {
if (values.hasOwnProperty(key) && key !== "__originalValues") { if (values.hasOwnProperty(key) && key !== "__originalValues") {
this[key] = values[key]; this[key] = values[key];
if (typeof values[key] === "object") {
this.__originalValues[key] = JSON.parse(JSON.stringify(values[key])); if (typeof values[key] !== "object") {
} else {
this.__originalValues[key] = values[key]; this.__originalValues[key] = values[key];
} else if (!scalarOnly) {
this.__originalValues[key] = JSON.parse(JSON.stringify(values[key]));
} }
} }
@ -36,15 +37,15 @@ class Model {
let val; let val;
if (defaults.hasOwnProperty(key)) { if (defaults.hasOwnProperty(key)) {
switch (typeof defaults[key]) { switch (typeof defaults[key]) {
case "bigint": case "bigint":
case "number": case "number":
val = parseFloat(this[key]); val = parseFloat(this[key]);
break; break;
case "boolean": case "boolean":
val = !!this[key]; val = !!this[key];
break; break;
default: default:
val = this[key]; val = this[key];
} }
} else { } else {
val = this[key]; val = this[key];

View file

@ -104,7 +104,7 @@ class Photo extends RestModel {
return; return;
} }
const primary = this.Files.find(f => f.FilePrimary === true); const primary = this.Files.find(f => !!f.FilePrimary);
if (!primary) { if (!primary) {
return; return;
@ -115,19 +115,32 @@ class Photo extends RestModel {
this.FileHeight = primary.FileHeight; this.FileHeight = primary.FileHeight;
} }
getThumbnailUrl(type) { primaryFileHash() {
if (this.Files && this.Files.length) { if (this.Files) {
const primary = this.Files.find(f => !!f.FilePrimary); const primary = this.Files.find(f => !!f.FilePrimary);
return "/api/v1/thumbnails/" + primary.FileHash + "/" + type;
if (primary && primary.FileHash) {
return primary.FileHash;
}
} else if (this.FileHash) { } else if (this.FileHash) {
return "/api/v1/thumbnails/" + this.FileHash + "/" + type; return this.FileHash;
} }
return "/api/v1/svg/photo"; return ""
}
getThumbnailUrl(type) {
let hash = this.primaryFileHash();
if (!hash) {
return "/api/v1/svg/photo";
}
return "/api/v1/thumbnails/" + hash + "/" + type;
} }
getDownloadUrl() { getDownloadUrl() {
return "/api/v1/download/" + this.FileHash; return "/api/v1/download/" + this.primaryFileHash();
} }
getThumbnailSrcset() { getThumbnailSrcset() {
@ -283,9 +296,9 @@ class Photo extends RestModel {
let files = this.Files; let files = this.Files;
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
let photo = new this.constructor(this.getValues()) let photo = new this.constructor(this.getValues());
photo.setValues(files[i]) photo.setValues(files[i], true);
photos.push(photo) photos.push(photo);
} }
console.log("PHOTOS", photos); console.log("PHOTOS", photos);

View file

@ -90,16 +90,16 @@ class Rest extends Model {
let offset = 0; let offset = 0;
if (response.headers) { if (response.headers) {
if (response.headers['x-count']) { if (response.headers["x-count"]) {
count = response.headers['x-count']; count = response.headers["x-count"];
} }
if (response.headers['x-limit']) { if (response.headers["x-limit"]) {
limit = response.headers['x-limit']; limit = response.headers["x-limit"];
} }
if (response.headers['x-offset']) { if (response.headers["x-offset"]) {
offset = response.headers['x-offset']; offset = response.headers["x-offset"];
} }
} }

123
frontend/src/model/thumb.js Normal file
View file

@ -0,0 +1,123 @@
import Model from "./model";
const thumbs = window.clientConfig.thumbnails;
class Thumb extends Model {
getDefaults() {
return {
uuid: "",
title: "",
original_w: "",
original_h: "",
download_url: "",
};
}
static fromPhotos(photos) {
let result = [];
photos.forEach((p) => {
result.push(this.fromPhoto(p));
});
return result;
}
static fromPhoto(photo) {
if (photo.Files) {
return this.fromFile(photo, photo.Files.find(f => !!f.FilePrimary));
}
const result = {
uuid: photo.PhotoUUID,
title: photo.PhotoTitle,
download_url: "/api/v1/download/" + photo.FileHash,
original_w: photo.FileWidth,
original_h: photo.FileHeight,
};
for (let i = 0; i < thumbs.length; i++) {
let size = photo.calculateSize(thumbs[i].Width, thumbs[i].Height);
result[thumbs[i].Name] = {
src: photo.getThumbnailUrl(thumbs[i].Name),
w: size.width,
h: size.height,
};
}
return new this(result);
}
static fromFile(photo, file) {
const result = {
uuid: photo.PhotoUUID,
title: photo.PhotoTitle,
download_url: "/api/v1/download/" + file.FileHash,
original_w: file.FileWidth,
original_h: file.FileHeight,
};
thumbs.forEach((t) => {
let size = this.calculateSize(file, t.Width, t.Height);
result[t.Name] = {
src: this.thumbnailUrl(file, t.Name),
w: size.width,
h: size.height,
};
});
return new this(result);
}
static fromFiles(photos) {
let result = [];
photos.forEach((p) => {
if (!p.Files) return;
p.Files.forEach((f) => {
if (f.FileType === 'jpg') {
result.push(this.fromFile(p, f));
}
}
);
});
return result;
}
static calculateSize(file, width, height) {
if (width >= file.FileWidth && height >= file.FileHeight) { // Smaller
return {width: file.FileWidth, height: file.FileHeight};
}
const srcAspectRatio = file.FileWidth / file.FileHeight;
const maxAspectRatio = width / height;
let newW, newH;
if (srcAspectRatio > maxAspectRatio) {
newW = width;
newH = Math.round(newW / srcAspectRatio);
} else {
newH = height;
newW = Math.round(newH * srcAspectRatio);
}
return {width: newW, height: newH};
}
static thumbnailUrl(file, type) {
if (!file.FileHash) {
return "/api/v1/svg/photo";
}
return "/api/v1/thumbnails/" + file.FileHash + "/" + type;
}
}
export default Thumb;

View file

@ -47,6 +47,7 @@
import Photo from "model/photo"; import Photo from "model/photo";
import Album from "model/album"; import Album from "model/album";
import Event from "pubsub-js"; import Event from "pubsub-js";
import Thumb from "../../model/thumb";
export default { export default {
name: 'p-page-album-photos', name: 'p-page-album-photos',
@ -143,9 +144,9 @@
}, },
openPhoto(index, showMerged) { openPhoto(index, showMerged) {
if (showMerged) { if (showMerged) {
this.$viewer.show(this.results[index].expand(), 0) this.$viewer.show(Thumb.fromFiles([this.results[index]]), 0)
} else { } else {
this.$viewer.show(this.results, index); this.$viewer.show(Thumb.fromPhotos(this.results), index);
} }
}, },
loadMore() { loadMore() {

View file

@ -36,6 +36,7 @@
<script> <script>
import Photo from "model/photo"; import Photo from "model/photo";
import Thumb from "model/thumb";
import Event from "pubsub-js"; import Event from "pubsub-js";
export default { export default {
@ -151,9 +152,9 @@
}, },
openPhoto(index, showMerged) { openPhoto(index, showMerged) {
if (showMerged) { if (showMerged) {
this.$viewer.show(this.results[index].expand(), 0) this.$viewer.show(Thumb.fromFiles([this.results[index]]), 0)
} else { } else {
this.$viewer.show(this.results, index); this.$viewer.show(Thumb.fromPhotos(this.results), index);
} }
}, },
loadMore() { loadMore() {

View file

@ -6,7 +6,6 @@ let assert = chai.assert;
describe("common/viewer", () => { describe("common/viewer", () => {
it("should construct viewer", () => { it("should construct viewer", () => {
const viewer = new Viewer(); const viewer = new Viewer();
assert.equal(viewer.photos, "");
assert.equal(viewer.el, null); assert.equal(viewer.el, null);
}); });
}); });