mirror of
https://github.com/stashapp/CommunityScripts.git
synced 2026-02-04 20:04:27 -06:00
339 lines
11 KiB
JavaScript
339 lines
11 KiB
JavaScript
(async () => {
|
|
const localStorageGalleryKey = "imageGalleryNavigation-GalleryID";
|
|
const localStorageGalleryParams = "imageGalleryNavigation-GalleryParams";
|
|
|
|
const defaultSearchParams = new URLSearchParams({
|
|
sortby: "title",
|
|
sortdir: "asc",
|
|
});
|
|
|
|
let pluginSettings = {};
|
|
const defaultPluginSettings = {
|
|
enableTransform: true
|
|
};
|
|
|
|
// In order to handle scenarios where an image is in multiple galleries, capture ID of gallery the user is navigating from.
|
|
// If user navigates directly to an image URL and image is in multiple galleries, we will just use the first gallery in list.
|
|
// This may break if user jumps around in browser history, in which case we will fall back to basic scenario of assuming first gallery in list.
|
|
async function setupGalleryImageLinks() {
|
|
document.querySelectorAll("a[href*='/images/']").forEach(function (link) {
|
|
link.addEventListener("click", () => {
|
|
// Parse Gallery URL.
|
|
var galleryID = window.location.pathname.split("/")[2];
|
|
|
|
// Save Gallery Info.
|
|
localStorage.setItem(localStorageGalleryKey, galleryID);
|
|
localStorage.setItem(localStorageGalleryParams, window.location.search);
|
|
});
|
|
});
|
|
}
|
|
|
|
// On image page, get data about gallery (image's position within gallery, next/prev image IDs),
|
|
// add arrow buttons to page, and register arrow keypress handlers,
|
|
async function setupImageContainer() {
|
|
const configSettings = await csLib.getConfiguration("imageGalleryNavigation", {}); // getConfiguration is from cs-ui-lib.js
|
|
pluginSettings = {
|
|
...defaultPluginSettings,
|
|
...configSettings,
|
|
};
|
|
|
|
var imageID = window.location.pathname.split("/")[2];
|
|
var imageGalleries = await findImage(imageID);
|
|
|
|
if (imageGalleries != null && imageGalleries.length > 0) {
|
|
// Get first entry in galleries list.
|
|
var galleryID = imageGalleries[0];
|
|
var galleryParams = defaultSearchParams;
|
|
|
|
// Check if there is a saved gallery ID and it is in gallery list. If true, use saved ID.
|
|
var savedGalleryId = localStorage.getItem(localStorageGalleryKey);
|
|
var savedGalleryParamsStr = localStorage.getItem(
|
|
localStorageGalleryParams
|
|
);
|
|
var savedGalleryParams = savedGalleryParamsStr
|
|
? new URLSearchParams(savedGalleryParamsStr)
|
|
: defaultSearchParams;
|
|
if (savedGalleryId != null && imageGalleries.includes(savedGalleryId)) {
|
|
galleryID = savedGalleryId;
|
|
|
|
if (savedGalleryParams != null) {
|
|
galleryParams = savedGalleryParams;
|
|
}
|
|
} else {
|
|
localStorage.setItem(localStorageGalleryKey, galleryID);
|
|
localStorage.setItem(localStorageGalleryParams, null);
|
|
}
|
|
|
|
// Get gallery image list.
|
|
var galleryImages = await findGalleryImages(galleryID, galleryParams);
|
|
var totalImageCount = galleryImages.length;
|
|
var currentImageIndex = galleryImages.indexOf(imageID);
|
|
var nextImageID =
|
|
galleryImages[wrapIndex(currentImageIndex + 1, totalImageCount)];
|
|
var prevImageID =
|
|
galleryImages[wrapIndex(currentImageIndex - 1, totalImageCount)];
|
|
|
|
// Add UI elements.
|
|
insertGalleryToolbar(currentImageIndex, totalImageCount, galleryImages);
|
|
insertArrowButtons(nextImageID, prevImageID);
|
|
insertArrowKeyHandlers(nextImageID, prevImageID);
|
|
|
|
// Add translate/scale controls.
|
|
if (pluginSettings.enableTransform) {
|
|
setupMouseEventHandlers();
|
|
}
|
|
}
|
|
}
|
|
|
|
function insertGalleryToolbar(
|
|
currentImageIndex,
|
|
totalImageCount,
|
|
galleryImages
|
|
) {
|
|
var galleryToolbar = document.createElement("div");
|
|
galleryToolbar.innerHTML = `<span class="imageGalleryNav-NavTitle">Gallery Image: </span><input type="number" class="text-input imageGalleryNav-NavInput" value="${
|
|
currentImageIndex + 1
|
|
}"> <span class="imageGalleryNav-NavTotal">/ ${totalImageCount}</span>`;
|
|
|
|
var toolbar = document.querySelector("div.image-toolbar");
|
|
toolbar.parentNode.insertBefore(galleryToolbar, toolbar.nextSibling);
|
|
|
|
galleryToolbar.querySelector("input").addEventListener("change", (e) => {
|
|
var imageIndex = e.target.value - 1;
|
|
if (imageIndex < 0 || imageIndex > totalImageCount - 1) {
|
|
e.target.value = currentImageIndex + 1;
|
|
e.target.select();
|
|
} else {
|
|
var imageID = galleryImages[imageIndex];
|
|
redirectToImage(imageID);
|
|
}
|
|
});
|
|
|
|
galleryToolbar.querySelector("input").addEventListener("focus", (e) => {
|
|
e.target.select();
|
|
});
|
|
}
|
|
|
|
function insertArrowButtons(nextImageID, prevImageID) {
|
|
var leftButton = document.createElement("button");
|
|
leftButton.className = "imageGalleryNav-leftButton btn btn-primary";
|
|
leftButton.innerText = "<";
|
|
leftButton.addEventListener("click", () => {
|
|
redirectToImage(prevImageID);
|
|
});
|
|
|
|
var rightButton = document.createElement("button");
|
|
rightButton.className = "imageGalleryNav-rightButton btn btn-primary";
|
|
rightButton.innerText = ">";
|
|
rightButton.addEventListener("click", () => {
|
|
redirectToImage(nextImageID);
|
|
});
|
|
|
|
document.querySelector("div.image-container").prepend(leftButton);
|
|
document.querySelector("div.image-container").prepend(rightButton);
|
|
}
|
|
|
|
function insertArrowKeyHandlers(nextImageID, prevImageID) {
|
|
// TODO: Determine how to cleanup this listener properly, so that we can handle left/right arrow key.
|
|
// // Add keypress handlers for arrow keys.
|
|
// document.addEventListener('keydown', function onKeydownHandler(e) {
|
|
// if (!isTextboxFocused()) {
|
|
// if (e.key === "ArrowRight") {
|
|
// redirectToImage(nextImageID);
|
|
// } else if (e.key === "ArrowLeft") {
|
|
// redirectToImage(prevImageID);
|
|
// }
|
|
// }
|
|
// });
|
|
}
|
|
|
|
function setupMouseEventHandlers() {
|
|
var img = document.querySelector("div.image-container img");
|
|
|
|
// Init transform values.
|
|
imgScale = 1.0;
|
|
imgTranslateX = 0.0;
|
|
imgTranslateY = 0.0;
|
|
|
|
// Prevent listeners from being attached twice.
|
|
img.removeEventListener('mousedown', onStartDrag);
|
|
img.removeEventListener('mousemove', onDrag);
|
|
document.removeEventListener('mouseup', onStopDrag);
|
|
img.removeEventListener('wheel', onMouseWheel);
|
|
|
|
// Add event listeners.
|
|
img.addEventListener('mousedown', onStartDrag);
|
|
img.addEventListener('mousemove', onDrag);
|
|
document.addEventListener('mouseup', onStopDrag);
|
|
img.addEventListener('wheel', onMouseWheel);
|
|
}
|
|
|
|
// *** Mouse Event Handlers *** //
|
|
var imgScale = 1.0;
|
|
var imgTranslateX = 0.0;
|
|
var imgTranslateY = 0.0;
|
|
var dragStartX = 0.0;
|
|
var dragStartY = 0.0;
|
|
var dragStartTranslateX = 0.0;
|
|
var dragStartTranslateY = 0.0;
|
|
var dragActive = false;
|
|
|
|
function onStartDrag(event) {
|
|
if (event.button === 0) {
|
|
event.preventDefault(); // Needed to prevent drag from being blocked.
|
|
dragActive = true;
|
|
dragStartX = event.clientX;
|
|
dragStartY = event.clientY;
|
|
dragStartTranslateX = imgTranslateX;
|
|
dragStartTranslateY = imgTranslateY;
|
|
}
|
|
}
|
|
|
|
function onStopDrag(event) {
|
|
if (event.button === 0) {
|
|
dragActive = false;
|
|
}
|
|
}
|
|
|
|
function onDrag(event) {
|
|
if (dragActive) {
|
|
imgTranslateX = dragStartTranslateX + (event.clientX - dragStartX);
|
|
imgTranslateY = dragStartTranslateY + (event.clientY - dragStartY);
|
|
applyImageTransform();
|
|
}
|
|
}
|
|
|
|
function onMouseWheel(event) {
|
|
event.preventDefault();
|
|
|
|
// TODO: Zoom to cursor.
|
|
// var img = document.querySelector("div.image-container img");
|
|
// img.style.transformOrigin = `${event.offsetX}px ${event.offsetY}px`
|
|
imgScale += (event.deltaY * -0.001);
|
|
imgScale = Math.max(imgScale, 1.0);
|
|
|
|
if (imgScale == 1.0) {
|
|
imgTranslateX = 0.0;
|
|
imgTranslateY = 0.0;
|
|
}
|
|
|
|
applyImageTransform();
|
|
}
|
|
|
|
function applyImageTransform() {
|
|
var img = document.querySelector("div.image-container img");
|
|
img.style.transform = `translate(${imgTranslateX}px,${imgTranslateY}px) scale(${imgScale})`
|
|
}
|
|
|
|
// *** Utility Functions ***
|
|
|
|
function redirectToImage(imageID) {
|
|
const baseImagesPath = "/images/";
|
|
window.location.replace(`${baseImagesPath}${imageID}`);
|
|
}
|
|
|
|
function isTextboxFocused() {
|
|
if (
|
|
document.activeElement.nodeName == "TEXTAREA" ||
|
|
document.activeElement.nodeName == "INPUT" ||
|
|
(document.activeElement.nodeName == "DIV" &&
|
|
document.activeElement.isContentEditable)
|
|
) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function wrapIndex(index, arrayLength) {
|
|
return ((index % arrayLength) + arrayLength) % arrayLength;
|
|
}
|
|
|
|
function getFindFilter(searchParams) {
|
|
var findFilter = {
|
|
per_page: -1,
|
|
sort: searchParams.has("sortby") ? searchParams.get("sortby") : "title",
|
|
direction: searchParams.has("sortdir")
|
|
? searchParams.get("sortdir").toUpperCase()
|
|
: "ASC",
|
|
};
|
|
|
|
return findFilter;
|
|
}
|
|
|
|
function getImageFilter(galleryID, searchParams) {
|
|
var imageFilter = {
|
|
galleries: { value: galleryID, modifier: "INCLUDES_ALL" },
|
|
};
|
|
|
|
if (searchParams.has("c")) {
|
|
searchParams.getAll("c").forEach((cStr) => {
|
|
// Parse filter condition string.
|
|
cStr = cStr.replaceAll("(", "{").replaceAll(")", "}");
|
|
cObj = JSON.parse(cStr);
|
|
|
|
// Init filter type field.
|
|
imageFilter[cObj.type] = {};
|
|
|
|
// Get all keys (except for "type").
|
|
var keys = Object.keys(cObj);
|
|
keys.splice(keys.indexOf("type"), 1);
|
|
|
|
// Add all filter data.
|
|
keys.forEach((keyName) => {
|
|
if (typeof cObj[keyName] === "object") {
|
|
// Special parsing for object type "value" fields (used where there's possibly a value and value2)
|
|
var keys2 = Object.keys(cObj[keyName]);
|
|
keys2.forEach((keyName2) => {
|
|
imageFilter[cObj.type][keyName2] = cObj[keyName][keyName2];
|
|
});
|
|
} else {
|
|
imageFilter[cObj.type][keyName] = cObj[keyName];
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
console.log(imageFilter);
|
|
return imageFilter;
|
|
}
|
|
|
|
// *** GQL Calls ***
|
|
|
|
// Find Image by ID
|
|
// Return Galleries list (id)
|
|
async function findImage(imageID) {
|
|
const variables = { id: imageID };
|
|
const query = `query ($id: ID!) { findImage(id: $id) { galleries { id } } }`;
|
|
return await csLib
|
|
.callGQL({ query, variables })
|
|
.then((data) => data.findImage.galleries.map((item) => item.id));
|
|
}
|
|
|
|
// Find Images by Gallery ID
|
|
// Return Images list (id)
|
|
async function findGalleryImages(galleryID, galleryParams) {
|
|
const imageFilter = getImageFilter(galleryID, galleryParams);
|
|
const findFilter = getFindFilter(galleryParams);
|
|
const variables = { image_filter: imageFilter, filter: findFilter };
|
|
const query = `query ($image_filter: ImageFilterType!, $filter: FindFilterType!) { findImages(image_filter: $image_filter, filter: $filter) { images { id } } }`;
|
|
return await csLib
|
|
.callGQL({ query, variables })
|
|
.then((data) => data.findImages.images.map((item) => item.id));
|
|
}
|
|
|
|
// Wait for galleries page to load.
|
|
csLib.PathElementListener(
|
|
"/galleries/",
|
|
".image-card",
|
|
setupGalleryImageLinks
|
|
); // PathElementListener is from cs-ui-lib.js
|
|
|
|
// Wait for images page to load.
|
|
csLib.PathElementListener(
|
|
"/images/",
|
|
".image-container",
|
|
setupImageContainer
|
|
); // PathElementListener is from cs-ui-lib.js
|
|
})();
|