Files
trpic/web/scripts/preview/img-viewer.js
Hubert Chen 7270ca7625 allow replace file
src/web:
    api/files_id:
        add PUT method for `/api/files/{fileID}` endpoint to replace file
    filter:
        move Files function as new file
web/scripts:
    support replace file for preview pages
2026-07-09 13:16:35 +08:00

1089 lines
34 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.imageList = [];
this.fromArtwork = false;
this.artworkFileIndex = -1;
this.currentIndex = -1;
this.oldURI = "";
this.SettingSubmitting = false;
this.focusBind = this.focusSidebar.bind(this);
this.focusOptions = { capture: true };
this.preview = document.getElementById("preview");
this.sidebar = document.getElementById("sidebar");
this.imageWrapper = document.getElementById("image-wrapper");
this.previewImg = document.getElementById("preview-img");
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.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.sidebarImageSettingCselect = document.getElementById("collapse-select");
this.sidebarImageSettingButton = document.getElementById("setting-button");
this.sidebarImageSettingPostInput = document.getElementById("post-input");
this.sidebarImageSettingPostButton = document.getElementById("post-button");
this.sidebarImageSettingUploadFile = document.getElementById("upload-file");
this.sidebarImageSettingUploadURLInput = document.getElementById("upload-url-input");
this.sidebarImageSettingUploadButton = document.getElementById("upload-button");
this.sidebarImageSettingDeleteButton = document.getElementById("delete-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.sidebarImageSetting.style.display = "block";
}
this.imageWrapper.addEventListener('wheel', this.wheel, { passive: false })
this.imageWrapper.addEventListener('pointerdown', this.down)
this.imageWrapper.addEventListener('pointermove', this.move)
this.imageWrapper.addEventListener('pointerup', this.up)
this.imageWrapper.addEventListener('pointercancel', this.up)
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.sidebarBtn.addEventListener("click", this.toggleSidebar.bind(this));
this.sidebarImageSettingSselect.addEventListener("change", this.updateSettingButtonBackground.bind(this));
this.sidebarImageSettingPselect.addEventListener("change", this.updateSettingButtonBackground.bind(this));
this.sidebarImageSettingCselect.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));
this.sidebarImageSettingDeleteButton.addEventListener("click", this.deleteImage.bind(this));
this.state = {
x: 0,
y: 0,
scale: 1,
}
this.tap = {
startX: 0,
startY: 0,
moved: false,
isMultiTouch: false,
}
this.input = {
pointers: new Map(),
wheel: null,
dirty: false,
}
this.prevPan = null
this.prevPinch = null
}
down = (e) => {
this.imageWrapper.style.cursor = "grabbing"
this.previewImg.style.willChange = "transform"
this.imageWrapper.setPointerCapture(e.pointerId)
this.input.pointers.set(e.pointerId, {
x: e.clientX,
y: e.clientY,
})
if (this.input.pointers.size === 1) {
this.tap.startX = e.clientX
this.tap.startY = e.clientY
this.tap.moved = false
this.tap.isMultiTouch = false
} else {
this.tap.isMultiTouch = true
}
this.input.dirty = true
}
move = (e) => {
const p = this.input.pointers.get(e.pointerId)
if (!p) return
p.x = e.clientX
p.y = e.clientY
const moveDist = Math.hypot(
e.clientX - this.tap.startX,
e.clientY - this.tap.startY
)
if (moveDist > 5) {
this.tap.moved = true
}
this.input.dirty = true
}
up = (e) => {
this.imageWrapper.style.cursor = ""
this.previewImg.style.willChange = ""
this.input.pointers.delete(e.pointerId)
if (this.input.pointers.size === 0 && !this.tap.moved && !this.tap.isMultiTouch) requestAnimationFrame(() => {this.closePreview()});
this.input.dirty = true
}
wheel = (e) => {
e.preventDefault()
this.input.wheel = {
x: e.clientX,
y: e.clientY,
delta: e.deltaY,
}
this.input.dirty = true
}
loop = () => {
this.update()
if (this.preview.style.display == "flex") requestAnimationFrame(this.loop);
}
update() {
if (!this.input.dirty) return
this.input.dirty = false
const pts = [...this.input.pointers.values()]
if (this.input.wheel) {
const { x, y, delta } = this.input.wheel
const scaleFactor = Math.exp(-delta * 0.001)
this.zoomAt(x, y, scaleFactor)
this.input.wheel = null
}
if (pts.length === 1) {
this.pan(pts[0])
} else if (pts.length >= 2) {
this.pinch(pts[0], pts[1])
}
if (pts.length < 2) this.prevPinch = null
if (pts.length !== 1) this.prevPan = null
this.render()
}
pan(p) {
if (!this.prevPan) {
this.prevPan = { ...p }
return
}
this.state.x += p.x - this.prevPan.x
this.state.y += p.y - this.prevPan.y
this.prevPan = { ...p }
}
pinch(p1, p2) {
const center = {
x: (p1.x + p2.x) / 2,
y: (p1.y + p2.y) / 2,
}
const dist = Math.hypot(p1.x - p2.x, p1.y - p2.y)
if (!this.prevPinch) {
this.prevPinch = { center, dist }
return
}
const scaleFactor = dist / this.prevPinch.dist
this.zoomAt(center.x, center.y, scaleFactor)
this.state.x += center.x - this.prevPinch.center.x
this.state.y += center.y - this.prevPinch.center.y
this.prevPinch = { center, dist }
}
zoomAt(cx, cy, scaleFactor) {
const rect = this.previewImg.getBoundingClientRect()
const centerX = rect.left + rect.width / 2
const centerY = rect.top + rect.height / 2
const dx = cx - centerX
const dy = cy - centerY
const prevScale = this.state.scale
let newScale = prevScale * scaleFactor
newScale = Math.max(.5, Math.min(8, newScale))
const ratio = newScale / prevScale
this.state.x -= dx * (ratio - 1)
this.state.y -= dy * (ratio - 1)
this.state.scale = newScale
}
render() {
const { x, y, scale } = this.state
this.previewImg.style.transform =
`translate(${x}px, ${y}px) scale(${scale})`
}
resetTransform() {
this.state.x = 0
this.state.y = 0
this.state.scale = 1
this.render();
}
updateSettingButtonBackground() {
this.sidebarImageSettingButton.style.backgroundColor = "#99af4c";
}
postArtworkFile(e) {
if (this.SettingSubmitting) return;
const link = this.sidebarImageSettingUploadURLInput.value;
const files = this.sidebarImageSettingUploadFile.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 (e.ctrlKey) {
if (confirm("是否替换文件?")) {
const formData = new FormData();
if (link) {
formData.append("link", link);
} else if (files.length == 1) {
formData.append("file", files[0]);
} else {
alert("替换不支持多个文件")
return;
}
let file;
if (this.fromArtwork) {
file = this.imageList[this.currentIndex].edges.files[this.artworkFileIndex];
} else {
file = this.imageList[this.currentIndex];
}
fetch(`/api/files/${file.id}`, { method: "PUT", body: formData })
.then(res => {
res.json().then(resjson => {
switch (res.status) {
case 200:
if (this.fromArtwork) {
this.imageList[this.currentIndex].edges.files[this.artworkFileIndex] = resjson;
} else {
this.imageList[this.currentIndex] = resjson;
}
this.updateInfoBar();
this.previewImg.src = "";
this.previewImg.src = `/files/${resjson.id}.${resjson.ext}`;
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.sidebarImageSettingUploadFile.value = "";
this.SettingSubmitting = false;
})
.catch(error => {
alert(`请求失败: ${error}`);
});
}
return;
}
if (link) {
const formData = new FormData();
formData.append("artwork_id", artwork.id);
formData.append("link", link);
fetch(`/api/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.sidebarImageSettingUploadFile.value = "";
this.SettingSubmitting = false;
})
.catch(error => {
alert(`请求失败: ${error}`);
});
} else {
for (const file of files) {
const formData = new FormData();
formData.append("artwork_id", artwork.id);
formData.append("file", file);
fetch(`/api/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.sidebarImageSettingUploadFile.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("artwork_id", artwork.id);
formData.append("link", link);
fetch(`/api/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;
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) return;
let Pvalue = this.sidebarImageSettingPselect.value;
let Cvalue = this.sidebarImageSettingCselect.value;
if (Number(Svalue) > 2 && Pvalue == "false") {
if (!confirm(`Set this artwork to public with L${Svalue} sensitivity?`)) return;
}
this.SettingSubmitting = true;
const formData = new FormData();
formData.append("private", Pvalue);
formData.append("sensitive", Svalue);
formData.append("collapse", Cvalue);
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;
artwork.collapse = resjson.collapse ? true : false;
this.updateInfoBar();
break;
case 401:
alert("提交失败:需要验证");
break;
default:
alert(`提交失败:${resjson.msg}: ${resjson.error}`);
break;
}
})
})
.finally(() => {
this.SettingSubmitting = false;
setTimeout(() => {
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(`Delete this ${file.id} image?`)) return;
fetch(`/api/files/${file.id}`, {
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, 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}.${file.ext}`
link.download = `trpic_${filename}`;
link.href = `/files/${filename}`;
link.click();
link.remove();
}
openSidebar() {
localStorage.setItem("sidebar_on", true);
this.sidebar.classList.add("open");
this.preview.classList.add("offset");
this.sidebarBtn.style.backgroundColor = "rgba(105, 105, 105, 0.3)";
}
closeSidebar() {
localStorage.setItem("sidebar_on", false);
this.sidebar.classList.remove("open");
this.preview.classList.remove("offset");
this.sidebarBtn.style.backgroundColor = "";
}
toggleSidebar() {
if (this.sidebar.classList.contains("open")) {
this.closeSidebar();
} else {
this.openSidebar();
}
}
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];
}
if (!(this.fromArtwork && index == prev)) this.resetTransform();
const highResSrc = `/files/${file.id}.${file.ext}`;
const lowResSrc = `/thumbnails/${file.id}.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");
};
if (this.preview.style.display == "none") {
this.preview.style.display = "flex";
this.loop();
gallery.addEventListener('focus', this.focusBind, this.focusOptions);
this.focusSidebar();
}
this.infoBox.style.opacity = 0;
document.body.style.overflow = "hidden";
document.body.style.touchAction = "none";
gallery.style.opacity = 0.1;
}
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);
const link = document.createElement("a");
link.textContent = `[${social.id}]`;
link.href = `/?socials=${social.id}`;
author.appendChild(link);
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}x${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} L${artwork.sensitive ? artwork.sensitive : 0} ${artwork.private ? " (private)" : ""}`;
this.sidebarImageInfo.replaceChildren(fileRes, fileInfo, id);
if (document.cookie.includes("_trpic_user")) {
const c = document.createElement("a");
c.href = `/convert/${file.id}`;
c.target = "_blank";
c.textContent = file.quality ? `${file.quality}%` : "Convert";
this.sidebarImageInfo.appendChild(c);
const u = document.createElement("a");
u.href = `/upscayl/${file.id}`;
u.target = "_blank";
u.textContent = file.upscayl_model ? `[${file.upscayl_model}]` : "Upscayl";
this.sidebarImageInfo.appendChild(u);
if (file.source_url) {
const s = document.createElement("a");
s.href = file.source_url;
u.target = "_blank";
const urlObj = new URL(file.source_url);
s.textContent = urlObj.hostname;
this.sidebarImageInfo.appendChild(s);
}
}
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.sidebarImageSettingCselect.value = Boolean(artwork.collapse);
this.sidebarImageSettingCselect.style.backgroundColor = artwork.collapse ? "rgb(80, 70, 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);
const link = document.createElement("a");
link.textContent = `[${social.id}]`;
link.href = `/?socials=${social.id}`;
author.appendChild(link);
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 = `/files/?tags=${tag.id}`;
this.sidebarImageTag.appendChild(tagLink);
})
}
const fileRes = document.createElement("p");
fileRes.textContent = `${file.width}x${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} L${artwork.sensitive ? artwork.sensitive : 0} ${artwork.private ? " (private)" : ""}`;
this.sidebarImageInfo.replaceChildren(fileRes, fileInfo, id);
if (document.cookie.includes("_trpic_user")) {
const c = document.createElement("a");
c.href = `/convert/${file.id}`;
c.target = "_blank";
c.textContent = file.quality ? `${file.quality}%` : "Convert";
this.sidebarImageInfo.appendChild(c);
const u = document.createElement("a");
u.href = `/upscayl/${file.id}`;
u.target = "_blank";
u.textContent = file.upscayl_model ? `[${file.upscayl_model}]` : "Upscayl";
this.sidebarImageInfo.appendChild(u);
if (file.source_url) {
const s = document.createElement("a");
s.href = file.source_url;
u.target = "_blank";
const urlObj = new URL(file.source_url);
s.textContent = urlObj.hostname;
this.sidebarImageInfo.appendChild(s);
}
}
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.sidebarImageSettingCselect.value = Boolean(artwork.collapse);
this.sidebarImageSettingCselect.style.backgroundColor = artwork.collapse ? "rgb(80, 70, 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.preventDefault();
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 == img.dataset.id);
} else {
index = this.imageList.findIndex((i) => i.id === img.dataset.id);
}
if (index !== -1) {
this.oldURI = window.location.pathname + window.location.search;
if (window.innerWidth <= 640) this.closeSidebar();
this.resetTransform()
this.showImageByIndex(index, 0);
}
}
closePreview() {
let artwork_id;
let file_id;
if (this.fromArtwork) {
artwork_id = this.imageList[this.currentIndex].id;
file_id = this.imageList[this.currentIndex].edges.files[this.artworkFileIndex].id;
} else {
artwork_id = this.imageList[this.currentIndex].edges.artwork.id;
file_id = this.imageList[this.currentIndex].id;
}
gallery.removeEventListener("focus", this.focusBind, this.focusOptions);
let e = document.getElementById(`trpic_artwork_${artwork_id}_${file_id}`)
if (!e) e = document.querySelector(`[id^="trpic_artwork_${artwork_id}"]`);
e.focus({ focusVisible: true });
this.currentIndex = -1;
this.infoBox.style.opacity = 1;
this.preview.style.display = "none";
document.body.style.overflow = "";
document.body.style.touchAction = "";
gallery.style.opacity = 1;
history.replaceState(null, null, this.oldURI);
}
focusSidebar() {
this.sidebarBtn.focus()
}
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;
}
}
bindClickEvents(img) {
img.addEventListener("click", (e) => this.openPreview(e, img));
img.style.cursor = "zoom-in";
}
}
let imgviewer;
document.addEventListener('DOMContentLoaded', () => {
imgviewer = new ImageViewer({})
});