Frontend: Add thumb model (photo viewer refactoring)
Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
parent
9344a52760
commit
3a257684bd
|
@ -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;
|
||||||
|
|
|
@ -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];
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
123
frontend/src/model/thumb.js
Normal 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;
|
|
@ -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() {
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue