import {FaunerieApp} from "./FaunerieApp";
import {FaunerieImageType} from "libfaunerie";
import * as fs from "node:fs";
import {FaunerieUtilities} from "./FaunerieUtilities";
import {shell} from "@electron/remote";
export class FaunerieAppDisplay {
private instance: FaunerieApp;
constructor(app: FaunerieApp) {
this.instance = app;
}
runUserSort(a: any, b: any) {
if ((document.getElementById("sorting") as HTMLInputElement).value === "new") {
return new Date(b.first_seen_at).getTime() - new Date(a.first_seen_at).getTime();
} else if ((document.getElementById("sorting") as HTMLInputElement).value === "id") {
return b.id - a.id;
} else if ((document.getElementById("sorting") as HTMLInputElement).value === "popular") {
return b.score - a.score;
} else if ((document.getElementById("sorting") as HTMLInputElement).value === "size") {
return b.size - a.size;
} else if ((document.getElementById("sorting") as HTMLInputElement).value === "duration") {
return b.duration - a.duration;
} else if ((document.getElementById("sorting") as HTMLInputElement).value === "resolution") {
return (b.height * b.width) - (a.height * a.width);
} else if ((document.getElementById("sorting") as HTMLInputElement).value === "relevant") {
return b.wilson_score - a.wilson_score;
}
}
displayNoResults() {
document.getElementById("search-warning").style.display = "";
document.getElementById("pages").style.display = "none";
document.getElementById("search-warning").innerHTML = "There are no images matching your request. Make sure the selected rating, filters and search query are correct and try again.";
}
getThumbnailHTML(i: any) {
if (i['representations']['thumb'].endsWith(".webm")) i['representations']['thumb'] = i['representations']['thumb'].substring(0, i['representations']['thumb'].length - 5) + ".gif";
let thumbnail = this.instance.dataStore.database.frontend.getImageFile(i, FaunerieImageType.ThumbnailURL);
// noinspection CssUnknownTarget
return `
`;
}
filterEnabledRatings(i: any) {
return (((document.getElementById("rating-safe") as HTMLInputElement).checked && i.tags.includes("safe")) ||
((document.getElementById("rating-suggestive") as HTMLInputElement).checked && i.tags.includes("suggestive")) ||
((document.getElementById("rating-questionable") as HTMLInputElement).checked && i.tags.includes("questionable")) ||
((document.getElementById("rating-explicit") as HTMLInputElement).checked && i.tags.includes("explicit")) ||
((document.getElementById("rating-grimdark") as HTMLInputElement).checked && i.tags.includes("grimdark")))
}
getPageNumbers() {
let _dataStore = this.instance.dataStore;
let items = _dataStore.currentViewItems;
let totalPages = Math.ceil(items.length / 50) + 1;
let pageNumbers = [];
if (_dataStore.page - 4 > 0) {
pageNumbers.push(_dataStore.page - 4);
pageNumbers.push(_dataStore.page - 3);
pageNumbers.push(_dataStore.page - 2);
pageNumbers.push(_dataStore.page - 1);
} else if (_dataStore.page - 3 > 0) {
pageNumbers.push(_dataStore.page - 3);
pageNumbers.push(_dataStore.page - 2);
pageNumbers.push(_dataStore.page - 1);
} else if (_dataStore.page - 2 > 0) {
pageNumbers.push(_dataStore.page - 2);
pageNumbers.push(_dataStore.page - 1);
} else if (_dataStore.page - 1 > 0) {
pageNumbers.push(_dataStore.page - 1);
}
pageNumbers.push(_dataStore.page);
let i = 0;
while (pageNumbers.length < 9) {
if (_dataStore.page + i + 1 <= totalPages) {
pageNumbers.push(_dataStore.page + i + 1);
} else {
break;
}
i++;
}
return pageNumbers;
}
getPageHTML(page: number) {
let length = (Math.ceil(this.instance.dataStore.currentViewItems.length / 50) + 1).toString().length;
return "" + ("0".repeat(length).substring(0, length - page.toString().length) + page.toString()) + "
";
}
getPageListHTML(pageNumbers: number[]) {
let _dataStore = this.instance.dataStore;
let items = _dataStore.currentViewItems;
let totalPages = Math.ceil(items.length / 50) + 1;
return `
«
‹
${totalPages > 5 ? pageNumbers.map(i => `
${this.getPageHTML(i)}
`).join("") : Array(totalPages).fill(null).map((_, i) => i + 1).map(i => `
${this.getPageHTML(i)}
`).join("")}
›
»
`;
}
updateDisplay() {
let _dataStore = this.instance.dataStore;
document.getElementById("search-warning").style.display = "none";
if (document.getElementById("search-error").style.display === "none") {
document.getElementById("pages").style.display = "";
} else {
document.getElementById("pages").style.display = "none";
}
let items = _dataStore.currentViewItems = [..._dataStore.currentView]
.sort(this.runUserSort)
.filter(this.filterEnabledRatings)
.map((i) => this.getThumbnailHTML(i));
if (items.length === 0 && document.getElementById("search-error").style.display === "none") this.displayNoResults();
if ((document.getElementById("order") as HTMLInputElement).value === "up") {
items = items.reverse();
}
document.getElementById("images").innerHTML = items.splice((_dataStore.page - 1) * 50, 50).join("");
document.getElementById("pages").innerHTML = this.getPageListHTML(this.getPageNumbers());
this.updateTitle();
this.instance.bootstrapTooltips();
}
updateTitle() {
let _dataStore = this.instance.dataStore;
let totalPages = Math.ceil(_dataStore.currentViewItems.length / 50) + 1;
if (_dataStore.searching) {
if (_dataStore.loadedFromCache) {
document.title = "Searching for " + (document.getElementById("search") as HTMLInputElement).value.trim() + " (page " + _dataStore.page + "/" + totalPages + ") — Faunerie (Cached)";
} else {
document.title = "Searching for " + (document.getElementById("search") as HTMLInputElement).value.trim() + " (page " + _dataStore.page + "/" + totalPages + ") — Faunerie";
}
} else {
if (_dataStore.loadedFromCache) {
document.title = "All images (page " + _dataStore.page + "/" + totalPages + ") — Faunerie (Cached)";
} else {
document.title = "All images (page " + _dataStore.page + "/" + totalPages + ") — Faunerie";
}
}
}
setRating(rating: string) {
Array.from(document.querySelectorAll(".tooltip.bs-tooltip-auto")).map(i => i.outerHTML = "");
(document.getElementById("rating-safe") as HTMLInputElement).checked = false;
(document.getElementById("rating-questionable") as HTMLInputElement).checked = false;
(document.getElementById("rating-suggestive") as HTMLInputElement).checked = false;
(document.getElementById("rating-explicit") as HTMLInputElement).checked = false;
(document.getElementById("rating-grimdark") as HTMLInputElement).checked = false;
let code = 0;
if (rating === "safe") code = 1;
if (rating === "questionable") code = 2;
if (rating === "suggestive") code = 3;
if (rating === "explicit") code = 4;
if (rating === "grimdark") code = 5;
if (code >= 1) (document.getElementById("rating-safe") as HTMLInputElement).checked = true;
if (code >= 2) (document.getElementById("rating-questionable") as HTMLInputElement).checked = true;
if (code >= 3) (document.getElementById("rating-suggestive") as HTMLInputElement).checked = true;
if (code >= 4) (document.getElementById("rating-explicit") as HTMLInputElement).checked = true;
if (code >= 5) (document.getElementById("rating-grimdark") as HTMLInputElement).checked = true;
}
highlightZone(id: string, state: boolean) {
if (state) {
document.getElementById("preview-zone-" + id).classList.add("hover");
document.getElementById("preview-tag-zone-" + id).classList.add("hover");
} else {
document.getElementById("preview-zone-" + id).classList.remove("hover");
document.getElementById("preview-tag-zone-" + id).classList.remove("hover");
}
}
buildImageTitle() {
let _dataStore = this.instance.dataStore;
return "Image #" +
(_dataStore.currentImage.source_id ?? _dataStore.currentImage.id) +
(_dataStore.currentImage.source_name
? " (" + _dataStore.currentImage.source_name + ")"
: "") +
"" +
FaunerieUtilities.getMimeBadge(_dataStore.currentImage.mime_type)
}
displayImageSize() {
let _dataStore = this.instance.dataStore;
let size = 0;
let sizeExplanation = [];
let file = _dataStore.database.frontend.getImageFile(_dataStore.currentImage, FaunerieImageType.ViewFile);
let thumb = _dataStore.database.frontend.getImageFile(_dataStore.currentImage, FaunerieImageType.ThumbnailFile);
if (file) {
let cSize = fs.lstatSync(file).size;
size += cSize;
sizeExplanation.push("Image: " + FaunerieUtilities.formatSize(cSize));
}
if (thumb) {
let cSize = fs.lstatSync(thumb).size;
size += cSize;
sizeExplanation.push("Thumbnail: " + FaunerieUtilities.formatSize(cSize));
}
let cSize = JSON.stringify(_dataStore.currentImage).length;
size += cSize;
sizeExplanation.push("Metadata: " + FaunerieUtilities.formatSize(cSize));
document.getElementById("preview-size").innerHTML =
"" +
FaunerieUtilities.formatSize(size) + "";
}
categorySortingNumber(cat: string) {
if (cat === "error") return 0;
if (cat === "rating") return 1;
if (cat === "origin") return 2;
if (cat === "character") return 3;
if (cat === "oc") return 4;
if (cat === "species") return 5;
if (cat === "body-type") return 6;
if (cat === "content-official") return 7;
if (cat === "content-fanmade") return 8;
if (cat === "spoiler") return 9;
return 999;
}
async getImpliedTags() {
let _dataStore = this.instance.dataStore;
let categories = {};
let implied = [];
let impliedNames = [];
for (let id of _dataStore.currentImage.tag_ids) {
implied.push(...(await _dataStore.database.frontend.getImpliedTagIdsFromId(id)).filter(i => !_dataStore.currentImage.tag_ids.includes(i)));
impliedNames.push(...(await _dataStore.database.frontend.getImpliedTagNamesFromId(id)).filter(i => !_dataStore.currentImage.tags.includes(i)));
}
for (let id of implied) {
categories[id] = (await _dataStore.database._sql("SELECT category FROM tags WHERE id=" + id))[0]['category'];
}
implied = [...new Set(implied)];
impliedNames = [...new Set(impliedNames)];
let impliedTags = impliedNames.map((i: string, j: number) => [i, implied[j], categories[implied[j]]]);
return impliedTags.sort((a, b) => {
return a[0].localeCompare(b[0]);
}).sort((a, b) => {
return this.categorySortingNumber(a[2]) - this.categorySortingNumber(b[2]);
});
}
async getTags() {
let _dataStore = this.instance.dataStore;
let categories = {};
for (let id of _dataStore.currentImage.tag_ids) {
categories[id] = (await _dataStore.database._sql("SELECT category FROM tags WHERE id=" + id))[0]['category'];
}
let tags = _dataStore.currentImage.tags.map((i: string, j: number) => [i, _dataStore.currentImage.tag_ids[j], categories[_dataStore.currentImage.tag_ids[j]]]);
return tags.sort((a: string, b: string) => {
return a[0].localeCompare(b[0]);
}).sort((a: string, b: string) => {
return this.categorySortingNumber(a[2]) - this.categorySortingNumber(b[2]);
});
}
async displayTags() {
let tags = await this.getTags();
let impliedTags = await this.getImpliedTags();
document.getElementById("preview-tags").innerHTML = tags.map((i: any[]) =>
"" +
i[0] + ""
).join("") +
impliedTags.map(i =>
"" +
i[0] + ""
).join("");
}
async initializeImageUI(id: string) {
let _dataStore = this.instance.dataStore;
_dataStore.modal.show();
_dataStore.currentImage = await _dataStore.database.frontend.getImage(id);
_dataStore.currentImageClasses = [];
document.getElementById("preview-zones").innerHTML = "";
document.getElementById("preview-parts-loader").style.display = "";
document.getElementById("preview-parts-none").style.display = "none";
document.getElementById("preview-parts-unsupported").style.display = "none";
document.getElementById("preview-parts-list").style.display = "none";
document.title = "Viewing image #" + (_dataStore.currentImage.source_id ?? id) + " — " + document.title;
document.getElementById("preview-title").innerHTML = this.buildImageTitle();
document.getElementById("preview-date").innerHTML = "Uploaded " + FaunerieUtilities.timeAgo(_dataStore.currentImage.created_at * 1000) + "";
document.getElementById("preview-resolution").innerText = _dataStore.currentImage.width + " × " + _dataStore.currentImage.height;
document.getElementById("preview-source-cta").innerText = "View on " + _dataStore.currentImage.source_name ?? "Derpibooru";
}
getCreditDescriptors() {
let descriptors = {
artists: [], editors: [], generators: [], photographers: [], prompters: [], colorists: []
};
let finalDescriptors = [];
for (let tag of this.instance.dataStore.currentImage.tags) {
if (tag.startsWith("artist:")) descriptors.artists.push(tag.substring(7));
if (tag.startsWith("editor:")) descriptors.editors.push(tag.substring(7));
if (tag.startsWith("generator:")) descriptors.generators.push(tag.substring(10));
if (tag.startsWith("photographer:")) descriptors.photographers.push(tag.substring(13));
if (tag.startsWith("prompter:")) descriptors.prompters.push(tag.substring(9));
if (tag.startsWith("colorist:")) descriptors.colorists.push(tag.substring(9));
}
if (descriptors.artists.length > 0) finalDescriptors.push("artist" + (descriptors.artists.length > 1 ? "s" : "") + ": " + descriptors.artists.join(""));
if (descriptors.editors.length > 0) finalDescriptors.push("editor" + (descriptors.editors.length > 1 ? "s" : "") + ": " + descriptors.editors.join(""));
if (descriptors.generators.length > 0) finalDescriptors.push("generator" + (descriptors.generators.length > 1 ? "s" : "") + ": " + descriptors.generators.join(""));
if (descriptors.photographers.length > 0) finalDescriptors.push("photographer" + (descriptors.photographers.length > 1 ? "s" : "") + ": " + descriptors.photographers.join(""));
if (descriptors.prompters.length > 0) finalDescriptors.push("prompter" + (descriptors.prompters.length > 1 ? "s" : "") + ": " + descriptors.prompters.join(""));
if (descriptors.colorists.length > 0) finalDescriptors.push("colorist" + (descriptors.colorists.length > 1 ? "s" : "") + ": " + descriptors.colorists.join(""));
if (finalDescriptors[0]) finalDescriptors[0] = finalDescriptors[0].substring(0, 1).toUpperCase() + finalDescriptors[0].substring(1);
return finalDescriptors;
}
displayCredits() {
let _dataStore = this.instance.dataStore;
let artist = "Anonymous artist";
let finalDescriptors = this.getCreditDescriptors();
if (finalDescriptors.length > 0) artist = finalDescriptors.join("; ");
if (_dataStore.currentImage.tags.includes("official")) {
if (finalDescriptors.length > 0) {
artist = finalDescriptors.join("; ") + " (official content)";
} else {
artist = "Official Hasbro content";
}
}
if (_dataStore.currentImage.tags.includes("artist needed")) {
artist = "Unknown artist";
}
document.getElementById("preview-artist").innerText = artist;
}
displayScore() {
let _dataStore = this.instance.dataStore;
if (_dataStore.currentImage.score !== 0) {
try { document.getElementById("preview-score").outerHTML = ""; } catch (e) {}
document.getElementById("preview-statistics").insertAdjacentHTML("afterbegin", ``);
if (_dataStore.currentImage.faves > 0) {
document.getElementById("preview-score").innerText = _dataStore.currentImage.faves + " favorites · " + _dataStore.currentImage.score + " points (" + _dataStore.currentImage.upvotes + " up, " + _dataStore.currentImage.downvotes + " down)";
} else {
document.getElementById("preview-score").innerText = _dataStore.currentImage.score + " points (" + _dataStore.currentImage.upvotes + " up, " + _dataStore.currentImage.downvotes + " down)";
}
} else {
try { document.getElementById("preview-score").outerHTML = ""; } catch (e) {}
}
}
displayViewer() {
let _dataStore = this.instance.dataStore;
let url = _dataStore.database.frontend.getImageFile(_dataStore.currentImage, FaunerieImageType.ViewURL)
if (_dataStore.currentImage.mime_type.startsWith("video/")) {
document.getElementById("preview-content").innerHTML = `
`;
} else {
document.getElementById("preview-content").innerHTML = `
`;
}
}
async openImage(id: string) {
await this.initializeImageUI(id);
this.displayImageSize();
await this.displayTags();
this.displayCredits();
this.displayScore();
this.displayViewer();
this.instance.ai.displayClasses(id);
this.instance.bootstrapTooltips();
}
openImageOnSource() {
let image = this.instance.dataStore.currentImage;
shell.openExternal(image['source'].replace("%s", image['source_id']));
}
}