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
1089 lines
34 KiB
JavaScript
1089 lines
34 KiB
JavaScript
// 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({})
|
||
});
|