Files
trpic/web/scripts/preview/img-viewer.js
Hubert Chen 2b8e238e72 add thumbhash
add thumbhash use `github.com/galdor/go-thumbhash` and `github.com/evanw/thumbhash`

database:
    add `thumbhash` column for `files` table
image:
    add `EncodeThumbHash()` function use `go.n16f.net/thumbhash`
router:
    add `/api/task/files/thumbhash` endpoint to check thumbhask
task:
    add `CheckThumbHash()` function
web/scripts:
    add thumbhash logic for `load_*` scripts
    add `thumbhash.js` from `https://github.com/evanw/thumbhash/blob/main/js/thumbhash.js`, edited
web/styles/gallery:
    add thumbhash transition when thumbnail loaded
    adjust style for wrapped imgs
web/templates:
    add `role="main"` for gallery and artwork id pages
    render thumbhash script for gallery pages
2026-04-21 23:26:29 +08:00

887 lines
29 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Modified from https://github.com/mrhuo/image-viewer
class ImageViewer {
constructor() {
this.targetSelector = "#gallery img";
this.currentScale = 1.0;
this.maxScale = 8;
this.minScale = 0.5;
this.isDragging = false;
this.hasMoved = false;
this.downX = 0;
this.downY = 0;
this.startX = 0;
this.startY = 0;
this.currentX = 0;
this.currentY = 0;
this.imageList = [];
this.fromArtwork = false;
this.artworkFileIndex = -1;
this.currentIndex = -1;
this.oldURI = "";
this.SettingSubmitting = false;
this.preview = document.getElementById("preview");
this.previewView = document.getElementById("preview-view");
this.sidebar = document.getElementById("sidebar");
this.imageWrapper = document.getElementById("image-wrapper");
this.previewImg = document.getElementById("preview-img");
this.navControls = document.getElementById("nav-controls");
this.prevBtn = document.getElementById("prev-btn");
this.nextBtn = document.getElementById("next-btn");
this.infoBox = document.getElementById("info-box");
this.imageCaption = document.getElementById("image-caption");
this.imageIndex = document.getElementById("image-index");
this.imageInfo = document.getElementById("image-info");
this.downloadBtn = document.getElementById("download-btn");
this.deleteBtn = document.getElementById("delete-btn");
this.sidebarBtn = document.getElementById("sidebar-btn");
this.sidebarContent = document.getElementById("sidebar-content");
this.sidebarImageArtworkLink = document.getElementById("img-artwork-a");
this.sidebarImageAuthor = document.getElementById("img-author");
this.sidebarImageSource = document.getElementById("img-source");
this.sidebarImageTag = document.getElementById("img-tag");
this.sidebarImageInfo = document.getElementById("img-info");
this.sidebarImageSetting = document.getElementById("img-setting");
this.sidebarImageSettingSselect = document.getElementById("sensitive-select");
this.sidebarImageSettingPselect = document.getElementById("private-select");
this.sidebarImageSettingButton = document.getElementById("setting-button");
this.sidebarImageSettingPostInput = document.getElementById("post-input");
this.sidebarImageSettingPostButton = document.getElementById("post-button");
this.sidebarImageSettingUploadFileButton = document.getElementById("upload-file-button");
this.sidebarImageSettingUploadURLInput = document.getElementById("upload-url-input");
this.sidebarImageSettingUploadButton = document.getElementById("upload-button");
let on = false;
let status = localStorage.getItem("sidebar_on")
if (status) {
if (status === "true" && window.innerWidth > 640) {
on = true;
}
} else if (window.innerWidth > 1600) {
on = true;
}
if (on) this.openSidebar();
if (document.cookie.includes("_trpic_user")) {
this.deleteBtn.style.display = "flex";
this.sidebarImageSetting.style.display = "block";
}
this.imageWrapper.addEventListener("wheel", this.handleWheel.bind(this));
this.imageWrapper.addEventListener("mousedown", this.startDrag.bind(this));
this.prevBtn.addEventListener("click", this.showPrevImage.bind(this));
this.nextBtn.addEventListener("click", this.showNextImage.bind(this));
document.addEventListener("keydown", this.handleKeyDown.bind(this));
this.downloadBtn.addEventListener("click", this.downloadImage.bind(this));
this.deleteBtn.addEventListener("click", this.deleteImage.bind(this));
this.sidebarBtn.addEventListener("click", this.toggleSidebar.bind(this));
this.sidebarImageSettingSselect.addEventListener("change", this.updateSettingButtonBackground.bind(this));
this.sidebarImageSettingPselect.addEventListener("change", this.updateSettingButtonBackground.bind(this));
this.sidebarImageSettingButton.addEventListener("click", this.patchArtwork.bind(this));
this.sidebarImageSettingPostButton.addEventListener("click", this.postSource.bind(this));
this.sidebarImageSettingUploadButton.addEventListener("click", this.postArtworkFile.bind(this));
}
updateSettingButtonBackground() {
this.sidebarImageSettingButton.style.backgroundColor = "#99af4c";
}
postArtworkFile() {
const link = this.sidebarImageSettingUploadURLInput.value;
const files = this.sidebarImageSettingUploadFileButton.files;
if (link == "" && files.length == 0) {
alert("请填入链接或文件");
return;
}
this.SettingSubmitting = true;
this.sidebarImageSettingUploadButton.style.backgroundColor = "rgb(50, 50, 50)";
let artwork;
if (this.fromArtwork) {
artwork = this.imageList[this.currentIndex];
} else {
artwork = this.imageList[this.currentIndex].edges.artwork;
}
if (link) {
const formData = new FormData();
formData.append("link", link);
fetch(`/api/artworks/${artwork.id}/files`, {
method: "POST",
body: formData,
})
.then(res => {
res.json().then(resjson => {
switch (res.status) {
case 201:
break;
case 401:
alert("提交失败:需要验证");
break;
default:
alert(`提交失败:${resjson.msg}: ${resjson.error}`);
break;
}
})
})
.finally(() => {
this.sidebarImageSettingUploadButton.style.backgroundColor = "rgb(50, 50, 50)";
this.sidebarImageSettingUploadURLInput.value = "";
this.sidebarImageSettingUploadFileButton.value = "";
this.SettingSubmitting = false;
})
.catch(error => {
alert(`请求失败: ${error}`);
});
} else {
for (const file of files) {
const formData = new FormData();
formData.append("file", file);
fetch(`/api/artworks/${artwork.id}/files`, {
method: "POST",
body: formData,
})
.then(res => {
res.json().then(resjson => {
switch (res.status) {
case 201:
break;
case 401:
alert("提交失败:需要验证");
break;
default:
alert(`提交失败:${resjson.msg}: ${resjson.error}`);
break;
}
})
})
.finally(() => {
this.sidebarImageSettingUploadButton.style.backgroundColor = "rgb(50, 50, 50)";
this.sidebarImageSettingUploadURLInput.value = "";
this.sidebarImageSettingUploadFileButton.value = "";
this.SettingSubmitting = false;
})
.catch(error => {
alert(`请求失败: ${error}`);
});
}
}
}
postSource() {
const link = this.sidebarImageSettingPostInput.value;
if (link == "" || this.SettingSubmitting == true) return;
this.SettingSubmitting = true;
this.sidebarImageSettingPostButton.style.backgroundColor = "rgb(50, 80, 80)";
let artwork;
if (this.fromArtwork) {
artwork = this.imageList[this.currentIndex];
} else {
artwork = this.imageList[this.currentIndex].edges.artwork;
}
const formData = new FormData();
formData.append("link", link);
fetch(`/api/artworks/${artwork.id}/posts`, {
method: "POST",
body: formData,
})
.then(res => {
res.json().then(resjson => {
switch (res.status) {
case 200:
artwork.edges.posts = resjson.edges.posts;
artwork.edges.socials = resjson.edges.socials;
artwork.edges.tags = resjson.edges.tags;
this.updateInfoBar();
break;
case 401:
alert("提交失败:需要验证");
break;
case 409:
if (confirm("此来源已链接到另一个作品,前往查看?")) {
window.location.href = `/artworks/${resjson.artwork_id}`;
}
break;
default:
alert(`提交失败:${resjson.msg}: ${resjson.error}`);
break;
}
})
})
.finally(() => {
this.sidebarImageSettingPostButton.style.backgroundColor = "rgb(50, 50, 50)";
this.sidebarImageSettingPostInput.value = "";
this.SettingSubmitting = false;
})
.catch(error => {
alert(`请求失败: ${error}`);
});
}
patchArtwork() {
if (this.SettingSubmitting) return;
this.SettingSubmitting = true;
let artwork;
if (this.fromArtwork) {
artwork = this.imageList[this.currentIndex];
} else {
artwork = this.imageList[this.currentIndex].edges.artwork;
}
let Svalue = this.sidebarImageSettingSselect.value;
if (Svalue < 0 || Svalue > 5) {
this.SettingSubmitting = false;
return;
}
let Pvalue = this.sidebarImageSettingPselect.value;
const formData = new FormData();
formData.append("private", Pvalue);
formData.append("sensitive", Svalue);
this.sidebarImageSettingButton.style.backgroundColor = "rgb(50, 80, 80)";
fetch(`/api/artworks/${artwork.id}`, {
method: "PATCH",
body: formData,
})
.then(res => {
res.json().then(resjson => {
switch (res.status) {
case 200:
artwork.sensitive = resjson.sensitive ? resjson.sensitive : 0;
artwork.private = resjson.private ? true : false;
this.updateInfoBar();
break;
case 401:
alert("提交失败:需要验证");
break;
case 409:
if (confirm("此来源已链接到另一个作品,前往查看?")) {
window.location.href = `/artworks/${resjson.artwork_id}`;
}
break;
default:
alert(`提交失败:${resjson.msg}: ${resjson.error}`);
break;
}
})
})
.finally(() => {
this.SettingSubmitting = false;
setInterval(() => {
this.sidebarImageSettingButton.style.backgroundColor = "rgb(50, 50, 50)";
}, 1000)
})
.catch(error => {
alert(`请求失败: ${error}`);
});
}
appendFiles(files) {
if (files.length === 0) return;
if (this.fromArtwork) {
this.imageList = [];
this.artworkFileIndex = 0;
this.currentIndex = 0;
}
this.imageList.push(...files);
this.fromArtwork = false;
}
appendArtworks(artworks) {
if (artworks.length === 0) return;
if (!this.fromArtwork) {
this.imageList = [];
this.currentIndex = 0;
}
this.imageList.push(...artworks);
this.fromArtwork = true;
}
resetList() {
this.imageList = [];
this.currentIndex = 0;
}
deleteArtwork() {
let artwork_id;
if (this.fromArtwork) {
artwork_id = this.imageList[this.currentIndex].id;
} else {
artwork_id = this.imageList[this.currentIndex].edges.artwork.id;
}
fetch(`/api/artworks/${artwork_id}`, {
method: "DELETE",
})
.then((response) => {
if (response.ok) {
setTimeout(() => {
this.closePreview();
resetGallery();
}, 1000);
} else {
response.json().then((resjson) => {
alert(`Failed to delete artwork: ${resjson.msg}`);
});
}
})
.catch((err) => {
console.error(err);
alert(`Failed to delete artwork: ${err}`);
});
}
deleteImage() {
let file;
if (this.fromArtwork) {
file = this.imageList[this.currentIndex].edges.files[this.artworkFileIndex];
} else {
file = this.imageList[this.currentIndex];
}
if (!confirm(`Are you sure you want to delete this ${file.id_str} image?`)) return;
fetch(`/api/files/${file.id_str}`, {
method: "DELETE",
})
.then((response) => {
if (response.ok) {
setTimeout(() => {
this.closePreview();
resetGallery();
}, 1000);
} else {
response.json().then((resjson) => {
if (resjson.code == 403) {
if (confirm(`This is last image of this artwork, are you want to delete this artwork?`)) this.deleteArtwork();
return
}
alert(`Failed to delete image: ${resjson.msg}`);
});
}
})
.catch((err) => {
console.error(err);
alert(`Failed to delete image: ${err}`);
});
}
downloadImage() {
let file;
if (this.fromArtwork) {
file = this.imageList[this.currentIndex].edges.files[this.artworkFileIndex];
} else {
file = this.imageList[this.currentIndex];
}
const link = document.createElement("a");
const filename = `${ file.id_str }.${ file.ext }`
link.download = `trpic_${filename}`;
link.href = `/files/${filename}`;
link.click();
link.remove();
}
openSidebar() {
localStorage.setItem("sidebar_on", true);
this.sidebar.style.width = "30vw";
this.sidebar.classList.add("open");
this.navControls.classList.add("min-hide");
this.sidebarBtn.style.backgroundColor = "rgba(105, 105, 105, 0.3)";
}
closeSidebar() {
localStorage.setItem("sidebar_on", false);
this.sidebar.style.width = "0px";
this.sidebar.classList.remove("open");
this.navControls.classList.remove("min-hide");
this.sidebarBtn.style.backgroundColor = "";
}
toggleSidebar() {
if (this.sidebar.classList.contains("open")) {
this.closeSidebar();
} else {
this.openSidebar();
}
}
bindDragEvents() {
this.imageWrapper.addEventListener("mousemove", this.doDrag.bind(this));
this.imageWrapper.addEventListener("mouseup", this.endDrag.bind(this));
this.imageWrapper.addEventListener("mouseleave", this.endDrag.bind(this));
}
unbindDragEvents() {
this.imageWrapper.removeEventListener("mousemove", this.doDrag.bind(this));
this.imageWrapper.removeEventListener("mouseup", this.endDrag.bind(this));
this.imageWrapper.removeEventListener("mouseleave",this.endDrag.bind(this));
}
resetTransform() {
this.currentScale = 1.0;
this.currentX = 0;
this.currentY = 0;
this.updateImageTransform();
}
updateImageTransform() {
this.previewImg.style.transform = `
translate(${this.currentX}px, ${this.currentY}px)
scale(${this.currentScale})
`;
}
showImageByIndex(index, prev) {
if (index < 0 || index >= this.imageList.length) {
return;
}
if (index === this.imageList.length - 7 && index > prev) {
loadMore();
}
this.currentIndex = index;
let file;
if (this.fromArtwork) {
file = this.imageList[this.currentIndex].edges.files[this.artworkFileIndex];
} else {
file = this.imageList[this.currentIndex];
}
this.resetTransform();
const highResSrc = `/files/${file.id_str}.${file.ext}`;
const lowResSrc = `/thumbnails/${file.id_str}.jpg`;
this.previewImg.src = (file.thumbed && !file.highres_loaded) ? lowResSrc : highResSrc;
this.updateInfoBar();
this.updateNavButtons();
this.previewImg.onload = () => {
// 如果有高分辨率图片,异步加载并在完成后替换
if (file.thumbed) {
this.loadHighResImage(file, highResSrc);
}
};
this.previewImg.onerror = () => {
this.updateInfoBar("Failed to load image");
};
this.infoBox.style.opacity = 0;
this.preview.style.display = "flex";
document.body.style.overflow = "hidden";
gallery.style.opacity = 0.3;
}
loadHighResImage(file, highResSrc) {
this.imageIndex.textContent += " loading...";
const listIndex = this.currentIndex;
const fileIndex = this.artworkFileIndex;
const highResImg = new Image();
highResImg.onload = () => {
this.previewImg.onload = null;
// Check if the image index has changed
if (this.currentIndex != listIndex || this.artworkFileIndex != fileIndex) return;
// High-resolution image loaded, replace current image
this.previewImg.src = highResSrc;
file.highres_loaded = "true";
this.updateInfoBar();
};
highResImg.onerror = (err) => {
if (this.currentIndex != listIndex || this.artworkFileIndex != fileIndex) return;
this.updateInfoBar();
alert(`Failed to load high res image: ${err}`)
};
highResImg.src = highResSrc;
}
updateInfoBar() {
if (this.fromArtwork) {
this.updateInfoBarArtwork();
} else {
this.updateInfoBarFile();
}
}
updateInfoBarArtwork() {
const artwork = this.imageList[this.currentIndex];
this.imageIndex.textContent = `${this.currentIndex + 1}${artwork.edges.files.length > 1 ? ` - ${this.artworkFileIndex + 1}` : ""} / ${this.imageList.length}`;
this.sidebarImageAuthor.replaceChildren();
if (artwork.edges.socials) {
artwork.edges.socials.forEach((social, index) => {
const author = document.createElement("p");
if (index > 0) {
author.style.borderTop = "1px solid #777";
}
const authorLink = document.createElement("a");
authorLink.textContent = `${social.platform}/${social.display_name}`;
authorLink.href = social.url;
authorLink.target = "_blank";
author.appendChild(authorLink);
this.sidebarImageAuthor.appendChild(author);
})
}
this.sidebarImageSource.replaceChildren();
if (artwork.edges.posts) {
artwork.edges.posts.forEach((post, index) => {
const source = document.createElement("div");
if (index > 0) {
source.style.borderTop = "1px solid #777";
}
const link = document.createElement("a");
link.textContent = `${post.platform}/${post.post_id}`;
link.href = post.url;
link.target = "_blank";
source.appendChild(link);
if (post.time) {
const time = document.createElement("p");
time.classList.add("time");
time.textContent = post.time.substring(0, 19);
source.appendChild(time);
}
if (post.title) {
const title = document.createElement("p");
title.classList.add("title");
title.textContent = post.title;
source.appendChild(title);
}
if (post.text) {
const text = document.createElement("p");
text.classList.add("desc");
text.textContent = post.text;
// text.innerHTML = post.text;
source.appendChild(text);
}
this.sidebarImageSource.appendChild(source);
})
}
this.sidebarImageTag.replaceChildren();
if (artwork.edges.tags) {
artwork.edges.tags.forEach(tag => {
const tagLink = document.createElement("a");
tagLink.textContent = `#${tag.text}`;
tagLink.href = `/artworks/?tags=${tag.id}`;
this.sidebarImageTag.appendChild(tagLink);
})
}
const file = artwork.edges.files[this.artworkFileIndex];
const fileRes = document.createElement("p");
fileRes.textContent = `${file.width}*${file.height}px`;
const fileInfo = document.createElement("p");
fileInfo.textContent = `${file.size_str} · ${file.ext}`;
const id = document.createElement("p");
id.id = "img-id";
id.textContent = `${file.id_str} L${artwork.sensitive ? artwork.sensitive : 0} ${artwork.private ? " (private)" : ""}`;
this.sidebarImageInfo.replaceChildren(fileRes, fileInfo, id);
if (document.cookie.includes("_trpic_user")) {
const a = document.createElement("a");
a.href = `/convert/${file.id_str}`;
a.target = "_blank";
a.textContent = `${file.quality ? `${file.quality}% ` : ""}Convert`;
this.sidebarImageInfo.appendChild(a);
}
this.sidebarImageSettingSselect.value = artwork.sensitive ? artwork.sensitive : 0;
this.sidebarImageSettingPselect.value = Boolean(artwork.private);
this.sidebarImageSettingPselect.style.backgroundColor = artwork.private ? "rgb(80, 70, 50)" : "rgb(50, 80, 50)";
this.sidebarImageArtworkLink.textContent = this.sidebarImageArtworkLink.href = `/artworks/${artwork.id}`;
history.replaceState(null, null, `/artworks/${artwork.id}${this.artworkFileIndex > 0 ? `/photo/${this.artworkFileIndex}` : ""}`);
}
updateInfoBarFile() {
const file = this.imageList[this.currentIndex];
const artwork = file.edges.artwork
this.imageIndex.textContent = `${this.currentIndex + 1} / ${this.imageList.length}`;
this.sidebarImageAuthor.replaceChildren();
if (artwork.edges.socials) {
artwork.edges.socials.forEach((social, index) => {
const author = document.createElement("p");
if (index > 0) {
author.style.borderTop = "1px solid #777";
}
const authorLink = document.createElement("a");
authorLink.textContent = `${social.platform}/${social.display_name}`;
authorLink.href = social.url;
authorLink.target = "_blank";
author.appendChild(authorLink);
this.sidebarImageAuthor.appendChild(author);
})
}
this.sidebarImageSource.replaceChildren();
if (artwork.edges.posts) {
artwork.edges.posts.forEach((post, index) => {
const source = document.createElement("div");
if (index > 0) {
source.style.borderTop = "1px solid #777";
}
const link = document.createElement("a");
link.textContent = `${post.platform}/${post.post_id}`;
link.href = post.url;
link.target = "_blank";
source.appendChild(link);
if (post.time) {
const time = document.createElement("p");
time.classList.add("time");
time.textContent = post.time.substring(0, 19);
source.appendChild(time);
}
if (post.title) {
const title = document.createElement("p");
title.textContent = post.title;
source.appendChild(title);
}
if (post.text) {
const text = document.createElement("p");
text.classList.add("desc");
text.textContent = post.text;
// text.innerHTML = post.text;
source.appendChild(text);
}
this.sidebarImageSource.appendChild(source);
})
}
this.sidebarImageTag.replaceChildren();
if (artwork.edges.tags) {
artwork.edges.tags.forEach(tag => {
const tagLink = document.createElement("a");
tagLink.textContent = `#${tag.text}`;
tagLink.href = `/artworks/?tags=${tag.id}`;
this.sidebarImageTag.appendChild(tagLink);
})
}
const fileRes = document.createElement("p");
fileRes.textContent = `${file.width}*${file.height}px`;
const fileInfo = document.createElement("p");
fileInfo.textContent = `${file.size_str} · ${file.ext}`;
const id = document.createElement("p");
id.id = "img-id";
id.textContent = `${file.id_str} L${artwork.sensitive ? artwork.sensitive : 0 } ${artwork.private ? " (private)" : ""}`;
this.sidebarImageInfo.replaceChildren(fileRes, fileInfo, id);
if (document.cookie.includes("_trpic_user")) {
const a = document.createElement("a");
a.href = `/convert/${file.id_str}`;
a.target = "_blank";
a.textContent = `${file.quality ? `${file.quality}% ` : ""}Convert`;
this.sidebarImageInfo.appendChild(a);
}
this.sidebarImageSettingSselect.value = artwork.sensitive ? artwork.sensitive : 0;
this.sidebarImageSettingPselect.value = Boolean(artwork.private);
this.sidebarImageSettingPselect.style.backgroundColor = artwork.private ? "rgb(80, 70, 50)" : "rgb(50, 80, 50)";
this.sidebarImageArtworkLink.textContent = this.sidebarImageArtworkLink.href = `/artworks/${artwork.id}`;
history.replaceState(null, null, `/artworks/${artwork.id}`);
}
updateNavButtons() {
let hasPrev = false;
let hasNext = false;
if (this.fromArtwork) {
hasPrev = this.artworkFileIndex > 0 || this.currentIndex > 0;
hasNext = this.artworkFileIndex < this.imageList[this.currentIndex].edges.files.length - 1 || this.currentIndex < this.imageList.length - 1;
} else {
hasPrev = this.currentIndex > 0;
hasNext = this.currentIndex < this.imageList.length - 1;
}
this.prevBtn.style.opacity = hasPrev ? "1" : "0";
this.prevBtn.style.pointerEvents = hasPrev ? "auto" : "none";
this.nextBtn.style.opacity = hasNext ? "1" : "0";
this.nextBtn.style.pointerEvents = hasNext ? "auto" : "none";
}
openPreview(e, img) {
if (img.tagName != "IMG") return;
e.stopPropagation();
let index;
if (this.fromArtwork) {
index = this.imageList.findIndex((i) => i.id == img.dataset.artwork_id);
this.artworkFileIndex = this.imageList[index].edges.files.findIndex((i) => i.id_str == img.dataset.id_str);
} else {
index = this.imageList.findIndex((i) => i.id_str === img.dataset.id_str);
}
if (index !== -1) {
this.oldURI = window.location.pathname + window.location.search;
if (window.innerWidth <= 640) this.closeSidebar();
this.showImageByIndex(index, 0);
}
}
closePreview() {
this.currentIndex = -1;
this.infoBox.style.opacity = 1;
this.preview.style.display = "none";
document.body.style.overflow = "";
gallery.style.opacity = 1;
history.replaceState(null, null, this.oldURI);
}
showNextImage() {
if (this.fromArtwork) {
if (this.artworkFileIndex < this.imageList[this.currentIndex].edges.files.length - 1) {
this.artworkFileIndex++;
this.showImageByIndex(this.currentIndex, this.currentIndex);
return;
}
if (this.currentIndex < this.imageList.length - 1) {
this.artworkFileIndex = 0;
this.showImageByIndex(this.currentIndex + 1, this.currentIndex);
}
} else {
if (this.currentIndex < this.imageList.length - 1) {
this.showImageByIndex(this.currentIndex + 1, this.currentIndex);
}
}
}
showPrevImage() {
if (this.fromArtwork) {
if (this.artworkFileIndex > 0) {
this.artworkFileIndex--;
this.showImageByIndex(this.currentIndex, this.currentIndex);
return;
}
if (this.currentIndex > 0) {
this.artworkFileIndex = this.imageList[this.currentIndex - 1].edges.files.length - 1;
this.showImageByIndex(this.currentIndex - 1, this.currentIndex);
}
} else {
if (this.currentIndex > 0) {
this.showImageByIndex(this.currentIndex - 1, this.currentIndex);
}
}
}
handleKeyDown(e) {
if (e.key == "Enter") {
this.openPreview(e, document.activeElement);
return;
}
if (this.preview.style.display !== "flex") return;
if (document.activeElement === this.sidebarImageSettingPostInput || document.activeElement === this.sidebarImageSettingUploadURLInput) return;
switch (e.key) {
case "Escape":
this.closePreview();
break;
case "ArrowRight":
this.showNextImage();
break;
case "ArrowLeft":
this.showPrevImage();
break;
case "b":
if (e.ctrlKey) {
this.toggleSidebar();
}
break;
}
}
handleWheel(e) {
e.preventDefault();
const delta = e.deltaY > 0 ? this.currentScale < 2 ? -0.1 : -0.2 : this.currentScale < 2 ? 0.1 : 0.2;
const newScale = parseFloat((this.currentScale + delta).toFixed(10));
if (newScale >= this.minScale && newScale <= this.maxScale) {
const ratio = newScale / this.currentScale;
const rect = this.imageWrapper.getBoundingClientRect();
const offsetX = e.clientX - (rect.left + rect.width / 2);
const offsetY = e.clientY - (rect.top + rect.height / 2);
this.currentX = offsetX * (1 - ratio) + this.currentX * ratio;
this.currentY = offsetY * (1 - ratio) + this.currentY * ratio;
this.currentScale = newScale;
this.updateImageTransform();
}
}
startDrag(e) {
if (e.button !== 0) return; // Only left button
e.preventDefault();
e.stopPropagation();
this.isDragging = true;
this.hasMoved = false;
this.startX = e.clientX - this.currentX;
this.startY = e.clientY - this.currentY;
this.downX = e.clientX;
this.downY = e.clientY;
this.imageWrapper.classList.add("dragging");
this.bindDragEvents();
}
doDrag(e) {
if (!this.isDragging) return;
e.preventDefault();
e.stopPropagation();
const dx = e.clientX - this.downX;
const dy = e.clientY - this.downY;
if (Math.abs(dx) > 2 || Math.abs(dy) > 2) {
this.hasMoved = true;
}
this.currentX = e.clientX - this.startX;
this.currentY = e.clientY - this.startY;
this.updateImageTransform();
}
endDrag(e) {
if (!this.isDragging) return;
if (e) {
e.stopPropagation();
}
this.isDragging = false;
this.imageWrapper.classList.remove("dragging");
this.unbindDragEvents();
if (!this.hasMoved) {
this.closePreview();
}
}
bindClickEvents(img) {
img.addEventListener("click", (e) => this.openPreview(e, img));
img.style.cursor = "zoom-in";
}
}
let imgviewer;
document.addEventListener('DOMContentLoaded', () => {
imgviewer = new ImageViewer({})
});