diff --git a/public/css/toggle-dependent.css b/public/css/toggle-dependent.css
index d77e59eac..05113c5f1 100644
--- a/public/css/toggle-dependent.css
+++ b/public/css/toggle-dependent.css
@@ -347,6 +347,7 @@ body.movingUI #sheld,
body.movingUI .drawer-content,
body.movingUI #expression-holder,
body.movingUI .zoomed_avatar,
+body.movingUI .draggable,
body.movingUI #floatingPrompt,
body.movingUI #groupMemberListPopout {
resize: both;
diff --git a/public/index.html b/public/index.html
index a1edce383..2c3e26f20 100644
--- a/public/index.html
+++ b/public/index.html
@@ -4387,6 +4387,16 @@
+
+
+
+
+
diff --git a/public/scripts/RossAscends-mods.js b/public/scripts/RossAscends-mods.js
index acc26e9c4..f4073fa8a 100644
--- a/public/scripts/RossAscends-mods.js
+++ b/public/scripts/RossAscends-mods.js
@@ -17,6 +17,7 @@ import {
getEntitiesList,
getThumbnailUrl,
selectCharacterById,
+ eventSource,
} from "../script.js";
import {
@@ -34,6 +35,7 @@ import { debounce, delay, getStringHash, waitUntilCondition } from "./utils.js";
import { chat_completion_sources, oai_settings } from "./openai.js";
import { getTokenCount } from "./tokenizers.js";
+
var RPanelPin = document.getElementById("rm_button_panel_pin");
var LPanelPin = document.getElementById("lm_button_panel_pin");
var WIPanelPin = document.getElementById("WI_panel_pin");
@@ -448,8 +450,9 @@ export function dragElement(elmnt) {
topbar, topbarWidth, topBarFirstX, topBarLastX, topBarLastY, sheldWidth;
var elmntName = elmnt.attr('id');
-
+ console.log(`dragElement called for ${elmntName}`);
const elmntNameEscaped = $.escapeSelector(elmntName);
+ console.log(`dragElement escaped name: ${elmntNameEscaped}`);
const elmntHeader = $(`#${elmntNameEscaped}header`);
if (elmntHeader.length) {
@@ -556,6 +559,7 @@ export function dragElement(elmnt) {
console.debug(`Saving ${elmntName} Height/Width`)
power_user.movingUIState[elmntName].width = width;
power_user.movingUIState[elmntName].height = height;
+ eventSource.emit('resizeUI', elmntName);
saveSettingsDebounced();
})
}
@@ -950,8 +954,15 @@ export function initRossMods() {
CheckLocal();
}
+ // Helper function to check if nanogallery2's lightbox is active
+ function isNanogallery2LightboxActive() {
+ // Check if the body has the 'nGY2On' class, adjust this based on actual behavior
+ return $('body').hasClass('nGY2_body_scrollbar');
+ }
+
if (event.key == "ArrowLeft") { //swipes left
if (
+ !isNanogallery2LightboxActive() && // Check if lightbox is NOT active
$(".swipe_left:last").css('display') === 'flex' &&
$("#send_textarea").val() === '' &&
$("#character_popup").css("display") === "none" &&
@@ -963,6 +974,7 @@ export function initRossMods() {
}
if (event.key == "ArrowRight") { //swipes right
if (
+ !isNanogallery2LightboxActive() && // Check if lightbox is NOT active
$(".swipe_right:last").css('display') === 'flex' &&
$("#send_textarea").val() === '' &&
$("#character_popup").css("display") === "none" &&
@@ -973,6 +985,7 @@ export function initRossMods() {
}
}
+
if (event.ctrlKey && event.key == "ArrowUp") { //edits last USER message if chatbar is empty and focused
if (
$("#send_textarea").val() === '' &&
@@ -1074,6 +1087,13 @@ export function initRossMods() {
}
}
+ if ($(".draggable").is(":visible")) {
+ // Remove the first matched element
+ $('.draggable:first').remove();
+ return;
+ }
+
+
if (event.ctrlKey && /^[1-9]$/.test(event.key)) {
// This will eventually be to trigger quick replies
event.preventDefault();
diff --git a/public/scripts/extensions/gallery/index.js b/public/scripts/extensions/gallery/index.js
new file mode 100644
index 000000000..39fc982ef
--- /dev/null
+++ b/public/scripts/extensions/gallery/index.js
@@ -0,0 +1,398 @@
+import {
+ eventSource,
+ this_chid,
+ characters,
+ getRequestHeaders,
+} from "../../../script.js";
+import { selected_group } from "../../group-chats.js";
+import { loadFileToDocument } from "../../utils.js";
+import { loadMovingUIState } from '../../power-user.js';
+import { dragElement } from '../../RossAscends-mods.js'
+
+const extensionName = "gallery";
+const extensionFolderPath = `scripts/extensions/${extensionName}/`;
+let firstTime = true;
+
+// Exposed defaults for future tweaking
+let thumbnailHeight = 150;
+let paginationVisiblePages = 10;
+let paginationMaxLinesPerPage = 2;
+let galleryMaxRows = 3;
+
+
+/**
+ * Retrieves a list of gallery items based on a given URL. This function calls an API endpoint
+ * to get the filenames and then constructs the item list.
+ *
+ * @param {string} url - The base URL to retrieve the list of images.
+ * @returns {Promise} - Resolves with an array of gallery item objects, rejects on error.
+ */
+async function getGalleryItems(url) {
+ const response = await fetch(`/listimgfiles/${url}`, {
+ method: 'POST',
+ headers: getRequestHeaders(),
+ });
+
+ const data = await response.json();
+ const items = data.map((file) => ({
+ src: `user/images/${url}/${file}`,
+ srct: `user/images/${url}/${file}`,
+ title: "", // Optional title for each item
+ }));
+
+ return items;
+}
+
+/**
+ * Initializes a gallery using the provided items and sets up the drag-and-drop functionality.
+ * It uses the nanogallery2 library to display the items and also initializes
+ * event listeners to handle drag-and-drop of files onto the gallery.
+ *
+ * @param {Array} items - An array of objects representing the items to display in the gallery.
+ * @param {string} url - The URL to use when a file is dropped onto the gallery for uploading.
+ * @returns {Promise} - Promise representing the completion of the gallery initialization.
+ */
+async function initGallery(items, url) {
+ $("#dragGallery").nanogallery2({
+ "items": items,
+ thumbnailWidth: 'auto',
+ thumbnailHeight: thumbnailHeight,
+ paginationVisiblePages: paginationVisiblePages,
+ paginationMaxLinesPerPage: paginationMaxLinesPerPage,
+ galleryMaxRows: galleryMaxRows,
+ galleryPaginationTopButtons: false,
+ galleryNavigationOverlayButtons: true,
+ galleryTheme: {
+ navigationBar: { background: 'none', borderTop: '', borderBottom: '', borderRight: '', borderLeft: '' },
+ navigationBreadcrumb: { background: '#111', color: '#fff', colorHover: '#ccc', borderRadius: '4px' },
+ navigationFilter: { color: '#ddd', background: '#111', colorSelected: '#fff', backgroundSelected: '#111', borderRadius: '4px' },
+ navigationPagination: { background: '#111', color: '#fff', colorHover: '#ccc', borderRadius: '4px' },
+ thumbnail: { background: '#444', backgroundImage: 'linear-gradient(315deg, #111 0%, #445 90%)', borderColor: '#000', borderRadius: '0px', labelOpacity: 1, labelBackground: 'rgba(34, 34, 34, 0)', titleColor: '#fff', titleBgColor: 'transparent', titleShadow: '', descriptionColor: '#ccc', descriptionBgColor: 'transparent', descriptionShadow: '', stackBackground: '#aaa' },
+ thumbnailIcon: { padding: '5px', color: '#fff', shadow: '' },
+ pagination: { background: '#181818', backgroundSelected: '#666', color: '#fff', borderRadius: '2px', shapeBorder: '3px solid var(--SmartThemeQuoteColor)', shapeColor: '#444', shapeSelectedColor: '#aaa' }
+ },
+ galleryDisplayMode: "pagination",
+ fnThumbnailOpen: viewWithDragbox,
+ });
+
+ $(document).ready(function () {
+ $('.nGY2GThumbnailImage').on('click', function () {
+ let imageUrl = $(this).find('.nGY2GThumbnailImg').attr('src');
+ // Do what you want with the imageUrl, for example:
+ // Display it in a full-size view or replace the gallery grid content with this image
+ console.log(imageUrl);
+ });
+ });
+
+ eventSource.on('resizeUI', function (elmntName) {
+ console.log('resizeUI saw', elmntName);
+ // Your logic here
+
+ // If you want to resize the nanogallery2 instance when this event is triggered:
+ jQuery("#dragGallery").nanogallery2('resize');
+ });
+
+ const dropZone = $('#dragGallery');
+ //remove any existing handlers
+ dropZone.off('dragover');
+ dropZone.off('dragleave');
+ dropZone.off('drop');
+
+
+ // Initialize dropzone handlers
+ dropZone.on('dragover', function (e) {
+ e.stopPropagation(); // Ensure this event doesn't propagate
+ e.preventDefault();
+ $(this).addClass('dragging'); // Add a CSS class to change appearance during drag-over
+ });
+
+ dropZone.on('dragleave', function (e) {
+ e.stopPropagation(); // Ensure this event doesn't propagate
+ $(this).removeClass('dragging');
+ });
+
+ dropZone.on('drop', function (e) {
+ e.stopPropagation(); // Ensure this event doesn't propagate
+ e.preventDefault();
+ $(this).removeClass('dragging');
+ let file = e.originalEvent.dataTransfer.files[0];
+ uploadFile(file, url); // Added url parameter to know where to upload
+ });
+}
+
+/**
+ * Displays a character gallery using the nanogallery2 library.
+ *
+ * This function takes care of:
+ * - Loading necessary resources for the gallery on the first invocation.
+ * - Preparing gallery items based on the character or group selection.
+ * - Handling the drag-and-drop functionality for image upload.
+ * - Displaying the gallery in a popup.
+ * - Cleaning up resources when the gallery popup is closed.
+ *
+ * @returns {Promise} - Promise representing the completion of the gallery display process.
+ */
+async function showCharGallery() {
+ // Load necessary files if it's the first time calling the function
+ if (firstTime) {
+ await loadFileToDocument(
+ `${extensionFolderPath}nanogallery2.woff.min.css`,
+ "css"
+ );
+ await loadFileToDocument(
+ `${extensionFolderPath}jquery.nanogallery2.min.js`,
+ "js"
+ );
+ firstTime = false;
+ toastr.info("Images can also be found in the folder `user/images`", "Drag and drop images onto the gallery to upload them", { timeOut: 6000 });
+ }
+
+ try {
+ let url = selected_group || this_chid;
+ if (!selected_group && this_chid) {
+ const char = characters[this_chid];
+ url = char.avatar.replace(".png", "");
+ }
+
+ const items = await getGalleryItems(url);
+ // if there already is a gallery, destroy it and place this one in its place
+ if ($(`#dragGallery`).length) {
+ $(`#dragGallery`).nanogallery2("destroy");
+ initGallery(items, url);
+ } else {
+ makeMovable();
+ setTimeout(async () => {
+ await initGallery(items, url);
+ }, 100);
+ }
+
+ } catch (err) {
+ console.trace();
+ console.error(err);
+ }
+}
+
+/**
+ * Uploads a given file to a specified URL.
+ * Once the file is uploaded, it provides a success message using toastr,
+ * destroys the existing gallery, fetches the latest items, and reinitializes the gallery.
+ *
+ * @param {File} file - The file object to be uploaded.
+ * @param {string} url - The URL indicating where the file should be uploaded.
+ * @returns {Promise} - Promise representing the completion of the file upload and gallery refresh.
+ */
+async function uploadFile(file, url) {
+ // Convert the file to a base64 string
+ const reader = new FileReader();
+ reader.onloadend = async function () {
+ const base64Data = reader.result;
+
+ // Create the payload
+ const payload = {
+ image: base64Data
+ };
+
+ // Add the ch_name from the provided URL (assuming it's the character name)
+ payload.ch_name = url;
+
+ try {
+ const headers = await getRequestHeaders();
+
+ // Merge headers with content-type for JSON
+ Object.assign(headers, {
+ 'Content-Type': 'application/json'
+ });
+
+ const response = await fetch('/uploadimage', {
+ method: 'POST',
+ headers: headers,
+ body: JSON.stringify(payload)
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! Status: ${response.status}`);
+ }
+
+ const result = await response.json();
+
+ toastr.success('File uploaded successfully. Saved at: ' + result.path);
+
+ // Refresh the gallery
+ $("#dragGallery").nanogallery2("destroy"); // Destroy old gallery
+ const newItems = await getGalleryItems(url); // Fetch the latest items
+ initGallery(newItems, url); // Reinitialize the gallery with new items and pass 'url'
+
+
+ } catch (error) {
+ console.error("There was an issue uploading the file:", error);
+
+ // Replacing alert with toastr error notification
+ toastr.error('Failed to upload the file.');
+ }
+ }
+ reader.readAsDataURL(file);
+}
+
+$(document).ready(function () {
+ // Register an event listener
+ eventSource.on("charManagementDropdown", (selectedOptionId) => {
+ if (selectedOptionId === "show_char_gallery") {
+ showCharGallery();
+ }
+ });
+
+ // Add an option to the dropdown
+ $("#char-management-dropdown").append(
+ $("", {
+ id: "show_char_gallery",
+ text: "Show Gallery",
+ })
+ );
+});
+
+/**
+ * Creates a new draggable container based on a template.
+ * This function takes a template with the ID 'generic_draggable_template' and clones it.
+ * The cloned element has its attributes set, a new child div appended, and is made visible on the body.
+ * Additionally, it sets up the element to prevent dragging on its images.
+ */
+function makeMovable(id="gallery"){
+
+ console.debug('making new container from template')
+ const template = $('#generic_draggable_template').html();
+ const newElement = $(template);
+ newElement.attr('forChar', id);
+ newElement.attr('id', `${id}`);
+ newElement.find('.drag-grabber').attr('id', `${id}header`);
+ //add a div for the gallery
+ newElement.append(`
`);
+ // add no-scrollbar class to this element
+ newElement.addClass('no-scrollbar');
+
+ // get the close button and set its id and data-related-id
+ const closeButton = newElement.find('.dragClose');
+ closeButton.attr('id', `${id}close`);
+ closeButton.attr('data-related-id', `${id}`);
+
+ $(`#dragGallery`).css('display', 'block');
+
+ $('body').append(newElement);
+
+ loadMovingUIState();
+ $(`.draggable[forChar="${id}"]`).css('display', 'block');
+ dragElement(newElement);
+
+ $(`.draggable[forChar="${id}"] img`).on('dragstart', (e) => {
+ console.log('saw drag on avatar!');
+ e.preventDefault();
+ return false;
+ });
+
+ $('body').on('click', '.dragClose', function () {
+ const relatedId = $(this).data('related-id'); // Get the ID of the related draggable
+ $(`#${relatedId}`).remove(); // Remove the associated draggable
+ });
+}
+
+/**
+ * Creates a new draggable image based on a template.
+ *
+ * This function clones a provided template with the ID 'generic_draggable_template',
+ * appends the given image URL, ensures the element has a unique ID,
+ * and attaches the element to the body. After appending, it also prevents
+ * dragging on the appended image.
+ *
+ * @param {string} id - A base identifier for the new draggable element.
+ * @param {string} url - The URL of the image to be added to the draggable element.
+ */
+function makeDragImg(id, url) {
+ // Step 1: Clone the template content
+ const template = document.getElementById('generic_draggable_template');
+
+ if (!(template instanceof HTMLTemplateElement)) {
+ console.error('The element is not a tag');
+ return;
+ }
+
+ const newElement = document.importNode(template.content, true);
+
+ // Step 2: Append the given image
+ const imgElem = document.createElement('img');
+ imgElem.src = url;
+ let uniqueId = `draggable_${id}`;
+ const draggableElem = newElement.querySelector('.draggable');
+ if (draggableElem) {
+ draggableElem.appendChild(imgElem);
+
+ // Find a unique id for the draggable element
+
+ let counter = 1;
+ while (document.getElementById(uniqueId)) {
+ uniqueId = `draggable_${id}_${counter}`;
+ counter++;
+ }
+ draggableElem.id = uniqueId;
+
+ // Ensure that the newly added element is displayed as block
+ draggableElem.style.display = 'block';
+
+ // Add an id to the close button
+ // If the close button exists, set related-id
+ const closeButton = draggableElem.querySelector('.dragClose');
+ if (closeButton) {
+ closeButton.id = `${uniqueId}close`;
+ closeButton.dataset.relatedId = uniqueId;
+ }
+
+ // Find the .drag-grabber and set its matching unique ID
+ const dragGrabber = draggableElem.querySelector('.drag-grabber');
+ if (dragGrabber) {
+ dragGrabber.id = `${uniqueId}header`; // appending _header to make it match the parent's unique ID
+ }
+ }
+
+ // Step 3: Attach it to the body
+ document.body.appendChild(newElement);
+
+ // Step 4: Call dragElement and loadMovingUIState
+ const appendedElement = document.getElementById(uniqueId);
+ if (appendedElement) {
+ var elmntName = $(appendedElement);
+ loadMovingUIState();
+ dragElement(elmntName);
+
+ // Prevent dragging the image
+ $(`#${uniqueId} img`).on('dragstart', (e) => {
+ console.log('saw drag on avatar!');
+ e.preventDefault();
+ return false;
+ });
+ } else {
+ console.error("Failed to append the template content or retrieve the appended content.");
+ }
+
+ $('body').on('click', '.dragClose', function () {
+ const relatedId = $(this).data('related-id'); // Get the ID of the related draggable
+ $(`#${relatedId}`).remove(); // Remove the associated draggable
+ });
+}
+
+
+/**
+ * Processes a list of items (containing URLs) and creates a draggable box for the first item.
+ *
+ * If the provided list of items is non-empty, it takes the URL of the first item,
+ * derives an ID from the URL, and uses the makeDragImg function to create
+ * a draggable image element based on that ID and URL.
+ *
+ * @param {Array} items - A list of items where each item has a responsiveURL method that returns a URL.
+ */
+function viewWithDragbox(items) {
+ if (items && items.length > 0) {
+ var url = items[0].responsiveURL(); // Get the URL of the clicked image/video
+ // ID should just be the last part of the URL, removing the extension
+ var id = url.substring(url.lastIndexOf('/') + 1, url.lastIndexOf('.'));
+ makeDragImg(id, url);
+ }
+}
+
diff --git a/public/scripts/extensions/gallery/jquery.nanogallery2.min.js b/public/scripts/extensions/gallery/jquery.nanogallery2.min.js
new file mode 100644
index 000000000..fb5a9c500
--- /dev/null
+++ b/public/scripts/extensions/gallery/jquery.nanogallery2.min.js
@@ -0,0 +1,80 @@
+/* nanogallery2 - v3.0.5 - 2021-02-26 */
+/*!
+ * @preserve nanogallery2 - javascript photo / video gallery and lightbox
+ * Homepage: http://nanogallery2.nanostudio.org
+ * Sources: https://github.com/nanostudio-org/nanogallery2
+ *
+ * License: GPLv3 and commercial licence
+ *
+ * Requirements:
+ * - jQuery (http://www.jquery.com) - version >= 1.7.1
+ *
+ * Embeded components:
+ * - shifty (https://github.com/jeremyckahn/shifty)
+ * - imagesloaded (https://github.com/desandro/imagesloaded)
+ * - hammer.js (http://hammerjs.github.io/)
+ * - screenfull.js (https://github.com/sindresorhus/screenfull.js)
+ * Tools:
+ * - webfont generated with http://fontello.com - mainly based on Font Awesome Copyright (C) 2012 by Dave Gandy (http://fontawesome.io/)
+ * - ICO online converter: https://iconverticons.com/online/
+ */
+!function(e){"use strict";"function"==typeof define&&define.amd?define(["jquery"],e):"object"==typeof exports&&"function"==typeof require?e(require("jquery")):e(jQuery)}((function(e){"use strict";function t(e){var t=document.getElementById("ngyColorHelperToRGB");return null===t&&((t=document.createElement("div")).id="ngyColorHelperToRGB",t.style.cssText="display: none; color:"+e+";",document.body.appendChild(t)),getComputedStyle(t).color}function n(e,t,n){var i="";if("RGBA("==t.toUpperCase().substring(0,5)&&(i="a",t="rgb("+t.substring(5)),"number"!=typeof e||e<-1||e>1||"string"!=typeof t||"r"!=t[0]&&"#"!=t[0]||"string"!=typeof n&&void 0!==n)return null;function a(e){var t=e.length,n=new Object;if(t>9){if((e=e.split(",")).length<3||e.length>4)return null;n[0]=o(e[0].slice(4)),n[1]=o(e[1]),n[2]=o(e[2]),n[3]=e[3]?parseFloat(e[3]):-1}else{if(8==t||6==t||t<4)return null;t<6&&(e="#"+e[1]+e[1]+e[2]+e[2]+e[3]+e[3]+(t>4?e[4]+""+e[4]:"")),e=o(e.slice(1),16),n[0]=e>>16&255,n[1]=e>>8&255,n[2]=255&e,n[3]=9==t||5==t?r((e>>24&255)/255*1e4)/1e4:-1}return n}var o=parseInt,r=Math.round,l=t.length>9,s=(l="string"==typeof n?n.length>9||"c"==n&&!l:l,e<0),u=(e=s?-1*e:e,n=n&&"c"!=n?n:s?"#000000":"#FFFFFF",a(t)),c=a(n);return u&&c?l?"rgb"+i+"("+r((c[0]-u[0])*e+u[0])+","+r((c[1]-u[1])*e+u[1])+","+r((c[2]-u[2])*e+u[2])+(u[3]<0&&c[3]<0?")":","+(u[3]>-1&&c[3]>-1?r(1e4*((c[3]-u[3])*e+u[3]))/1e4:c[3]<0?u[3]:c[3])+")"):"#"+(4294967296+16777216*(u[3]>-1&&c[3]>-1?r(255*((c[3]-u[3])*e+u[3])):c[3]>-1?r(255*c[3]):u[3]>-1?r(255*u[3]):255)+65536*r((c[0]-u[0])*e+u[0])+256*r((c[1]-u[1])*e+u[1])+r((c[2]-u[2])*e+u[2])).toString(16).slice(u[3]>-1||c[3]>-1?1:3):null}function i(e){if(null===e||"object"!=typeof e)return e;var t=e.constructor();for(var n in e)t[n]=i(e[n]);return t}function a(){var e=jQuery(window);return{l:e.scrollLeft(),t:e.scrollTop(),w:e.width(),h:e.height()}}function o(e,t){var n=0;""==e&&(e="*"),jQuery(e).each((function(){var e=parseInt(jQuery(this).css("z-index"));n=e>n?e:n})),n++,jQuery(t).css("z-index",n)}var r=function(e){return{}.toString.call(e).match(/\s([a-zA-Z]+)/)[1].toLowerCase()};function l(){this.LightboxReOpen=function(){m()},this.ReloadAlbum=function(){if(""===u.O.kind)throw"Not supported for this content source:"+u.O.kind;var e=u.GOM.albumIdx;if(-1==e)throw"Current album not found.";for(var t=u.I[e].GetID(),n=u.I.length,i=0;i1&&e--,u.GOM.pagination.currentPage=e,u.GOM.ScrollToTop(),L(),E(!0),!1},this.PaginationCountPages=function(){return 0==u.GOM.items.length?0:Math.ceil((u.GOM.items[u.GOM.items.length-1].row+1)/u.galleryMaxRows.Get())};var s=function(e,t,n){var i;return function(){var a=this,o=arguments;function r(){n||e.apply(a,o),i=null}i?clearTimeout(i):n&&e.apply(a,o),i=setTimeout(r,t||100)}};window.ng_draf=function(e){return requestAnimationFrame((function(){window.requestAnimationFrame(e)}))},window.requestTimeout=function(e,t){if(!(window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame&&window.mozCancelRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame))return window.setTimeout(e,t);var n=(new Date).getTime(),i=new Object;return i.value=requestAnimFrame((function a(){(new Date).getTime()-n>=t?e.call():i.value=requestAnimFrame(a)})),i},window.requestAnimFrame=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(e,t){window.setTimeout(e,1e3/60)},window.clearRequestTimeout=function(e){window.cancelAnimationFrame?window.cancelAnimationFrame(e.value):window.webkitCancelAnimationFrame?window.webkitCancelAnimationFrame(e.value):window.webkitCancelRequestAnimationFrame?window.webkitCancelRequestAnimationFrame(e.value):window.mozCancelRequestAnimationFrame?window.mozCancelRequestAnimationFrame(e.value):window.oCancelRequestAnimationFrame?window.oCancelRequestAnimationFrame(e.value):window.msCancelRequestAnimationFrame?window.msCancelRequestAnimationFrame(e.value):clearTimeout(e)};var u=this;function c(e){this.$e=null,this.ngy2ItemIdx=e,this.mediaNumber=u.VOM.items.length+1,this.posX=0,this.posY=0}u.I=[],u.Id=[],u.O=null,u.baseEltID=null,u.$E={base:null,conTnParent:null,conLoadingB:null,conConsole:null,conNavigationBar:null,conTnBottom:null,scrollableParent:null},u.shoppingCart=[],u.layout={internal:!0,engine:"",support:{rows:!1},prerequisite:{imageSize:!1},SetEngine:function(){if(u.layout.internal){if("auto"==u.tn.settings.width[u.GOM.curNavLevel][u.GOM.curWidth]||""==u.tn.settings.width[u.GOM.curNavLevel][u.GOM.curWidth])return u.layout.engine="JUSTIFIED",u.layout.support.rows=!0,void(u.layout.prerequisite.imageSize=!0);if("auto"==u.tn.settings.height[u.GOM.curNavLevel][u.GOM.curWidth]||""==u.tn.settings.height[u.GOM.curNavLevel][u.GOM.curWidth])return u.layout.engine="CASCADING",u.layout.support.rows=!1,void(u.layout.prerequisite.imageSize=!0);if(null!=u.tn.settings.getMosaic())return u.layout.engine="MOSAIC",u.layout.support.rows=!0,void(u.layout.prerequisite.imageSize=!1);u.layout.engine="GRID",u.layout.support.rows=!0,u.layout.prerequisite.imageSize=!1}}},u.galleryResizeEventEnabled=!1,u.galleryMaxRows={l1:0,lN:0,Get:function(){return u.galleryMaxRows[u.GOM.curNavLevel]}},u.galleryMaxItems={l1:0,lN:0,Get:function(){return u.galleryMaxItems[u.GOM.curNavLevel]}},u.galleryFilterTags={l1:0,lN:0,Get:function(){return u.galleryFilterTags[u.GOM.curNavLevel]}},u.galleryFilterTagsMode={l1:0,lN:0,Get:function(){return u.galleryFilterTagsMode[u.GOM.curNavLevel]}},u.galleryDisplayMode={l1:"FULLCONTENT",lN:"FULLCONTENT",Get:function(){return u.galleryDisplayMode[u.GOM.curNavLevel]}},u.galleryLastRowFull={l1:!1,lN:!1,Get:function(){return u.galleryLastRowFull[u.GOM.curNavLevel]}},u.gallerySorting={l1:"",lN:"",Get:function(){return u.gallerySorting[u.GOM.curNavLevel]}},u.galleryDisplayTransition={l1:"none",lN:"none",Get:function(){return u.galleryDisplayTransition[u.GOM.curNavLevel]}},u.galleryDisplayTransitionDuration={l1:500,lN:500,Get:function(){return u.galleryDisplayTransitionDuration[u.GOM.curNavLevel]}},u.$currentTouchedThumbnail=null,u.tn={opt:{l1:{crop:!0,stacks:0,stacksTranslateX:0,stacksTranslateY:0,stacksTranslateZ:0,stacksRotateX:0,stacksRotateY:0,stacksRotateZ:0,stacksScale:0,borderHorizontal:0,borderVertical:0,baseGridHeight:0,displayTransition:"FADEIN",displayTransitionStartVal:0,displayTransitionEasing:"easeOutQuart",displayTransitionDuration:240,displayInterval:15},lN:{crop:!0,stacks:0,stacksTranslateX:0,stacksTranslateY:0,stacksTranslateZ:0,stacksRotateX:0,stacksRotateY:0,stacksRotateZ:0,stacksScale:0,borderHorizontal:0,borderVertical:0,baseGridHeight:0,displayTransition:"FADEIN",displayTransitionStartVal:0,displayTransitionEasing:"easeOutQuart",displayTransitionDuration:240,displayInterval:15},Get:function(e){return u.tn.opt[u.GOM.curNavLevel][e]}},scale:1,labelHeight:{l1:0,lN:0,get:function(){return u.tn.labelHeight[u.GOM.curNavLevel]}},defaultSize:{width:{l1:{xs:0,sm:0,me:0,la:0,xl:0},lN:{xs:0,sm:0,me:0,la:0,xl:0}},height:{l1:{xs:0,sm:0,me:0,la:0,xl:0},lN:{xs:0,sm:0,me:0,la:0,xl:0}},getWidth:function(){return u.tn.defaultSize.width[u.GOM.curNavLevel][u.GOM.curWidth]},getOuterWidth:function(){u.tn.borderWidth=u.tn.opt.Get("borderHorizontal"),u.tn.borderHeight=u.tn.opt.Get("borderVertical");var e=u.tn.defaultSize.width[u.GOM.curNavLevel][u.GOM.curWidth]+2*u.tn.opt.Get("borderHorizontal");return"right"!=u.O.thumbnailLabel.get("position")&&"left"!=u.O.thumbnailLabel.get("position")||(e+=u.tn.defaultSize.width[u.GOM.curNavLevel][u.GOM.curWidth]),e},getHeight:function(){return u.tn.defaultSize.height[u.GOM.curNavLevel][u.GOM.curWidth]},getOuterHeight:function(){return u.tn.defaultSize.height[u.GOM.curNavLevel][u.GOM.curWidth]+2*u.tn.opt.Get("borderVertical")}},settings:{width:{l1:{xs:0,sm:0,me:0,la:0,xl:0,xsc:"u",smc:"u",mec:"u",lac:"u",xlc:"u"},lN:{xs:0,sm:0,me:0,la:0,xl:0,xsc:"u",smc:"u",mec:"u",lac:"u",xlc:"u"}},height:{l1:{xs:0,sm:0,me:0,la:0,xl:0,xsc:"u",smc:"u",mec:"u",lac:"u",xlc:"u"},lN:{xs:0,sm:0,me:0,la:0,xl:0,xsc:"u",smc:"u",mec:"u",lac:"u",xlc:"u"}},getH:function(e,t){var n=null==e?u.GOM.curNavLevel:e,i=null==t?u.GOM.curWidth:t;return"MOSAIC"==u.layout.engine?this.height[n][i]*this.mosaic[n+"Factor"].h[i]:this.height[n][i]},getW:function(e,t){var n=null==e?u.GOM.curNavLevel:e,i=null==t?u.GOM.curWidth:t;return"MOSAIC"==u.layout.engine?this.width[n][i]*this.mosaic[n+"Factor"].w[i]:this.width[n][i]},mosaic:{l1:{xs:null,sm:null,me:null,la:null,xl:null},lN:{xs:null,sm:null,me:null,la:null,xl:null},l1Factor:{h:{xs:1,sm:1,me:1,la:1,xl:1},w:{xs:1,sm:1,me:1,la:1,xl:1}},lNFactor:{h:{xs:1,sm:1,me:1,la:1,xl:1},w:{xs:1,sm:1,me:1,la:1,xl:1}}},getMosaic:function(){return this.mosaic[u.GOM.curNavLevel][u.GOM.curWidth]},mosaicCalcFactor:function(e,t){for(var n=1,i=1,a=0;at?(r&&(clearTimeout(r),r=null),l=u,o=e.apply(i,a),r||(i=a=null)):r||!1===n.trailing||(r=setTimeout(s,c)),o}}(x,15,{leading:!1}),u.blockList=null,u.allowList=null,u.albumList=[],u.locationHashLastUsed="",u.custGlobals={},u.touchAutoOpenDelayTimerID=0,u.i18nLang="",u.timeLastTouchStart=0,u.custGlobals={},u.markupOrApiProcessed=!1,u.GOM={albumIdx:-1,clipArea:{top:0,height:0},displayArea:{width:0,height:0},displayAreaLast:{width:0,height:0},displayedMoreSteps:0,items:[],$imgPreloader:[],thumbnails2Display:[],itemsDisplayed:0,firstDisplay:!0,firstDisplayTime:0,navigationBar:{displayed:!1,$newContent:""},cache:{viewport:null,containerOffset:null,areaWidth:100},nbSelected:0,pagination:{currentPage:0},panThreshold:60,panYOnly:!1,lastFullRow:-1,lastDisplayedIdx:-1,displayInterval:{from:0,len:0},hammertime:null,curNavLevel:"l1",curWidth:"me",albumSearch:"",albumSearchTags:"",lastZIndex:0,lastRandomValue:0,slider:{hostIdx:-1,hostItem:null,currentIdx:0,nextIdx:0,timerID:0,tween:null},NGY2Item:function(e){if(null==u.GOM.items[e]||null==u.GOM.items[e])return null;var t=u.GOM.items[e].thumbnailIdx;return u.I[t]},GTn:function(e,t,n){this.thumbnailIdx=e,this.width=0,this.height=0,this.top=0,this.left=0,this.row=0,this.imageWidth=t,this.imageHeight=n,this.resizedContentWidth=0,this.resizedContentHeight=0,this.displayed=!1,this.neverDisplayed=!0,this.inDisplayArea=!1},ScrollToTop:function(){var e,t,n,i;if(!u.GOM.firstDisplay&&(null!==u.$E.scrollableParent||(e=u.$E.base,t=20,n=a(),(i=e.offset()).top>=n.t&&i.top<=n.t+n.h-t)||u.$E.base.get(0).scrollIntoView(),null!==u.$E.scrollableParent)){var o=u.$E.scrollableParent.scrollTop(),r=Math.abs(u.$E.scrollableParent.offset().top-u.$E.base.offset().top-o);o>r&&window.ng_draf((function(){u.$E.scrollableParent.scrollTop(r)}))}}},u.VOM={viewerDisplayed:!1,viewerIsFullscreen:!1,infoDisplayed:!1,toolbarsDisplayed:!0,toolsHide:null,zoom:{posX:0,posY:0,userFactor:1,isZooming:!1},padding:{H:0,V:0},window:{lastWidth:0,lastHeight:0},$viewer:null,$toolbar:null,$toolbarTL:null,$toolbarTR:null,toolbarMode:"std",playSlideshow:!1,playSlideshowTimerID:0,slideshowDelay:3e3,albumID:-1,viewerMediaIsChanged:!1,items:[],panMode:"off",$baseCont:null,$content:null,content:{previous:{vIdx:-1,$media:null,NGY2Item:function(){return u.I[u.VOM.items[u.VOM.content.previous.vIdx].ngy2ItemIdx]}},current:{vIdx:-1,$media:null,NGY2Item:function(){return u.I[u.VOM.items[u.VOM.content.current.vIdx].ngy2ItemIdx]}},next:{vIdx:-1,$media:null,NGY2Item:function(){return u.I[u.VOM.items[u.VOM.content.next.vIdx].ngy2ItemIdx]}}},IdxNext:function(){var e=0;return u.VOM.content.current.vIdx=t){var n=this.oneTmbWidth*u.VOM.content.current.vIdx;n+this.posX=this.vwidth&&(this.posX=this.vwidth-(n+this.oneTmbWidth))}this.PanGallery(0)}},PanGallery:function(e){this.gwidththis.vwidth-this.oneTmbWidth&&(this.posX=this.vwidth-this.oneTmbWidth),this.posX+this.gwidthu.VOM.ImageLoader.maxChecks?(u.VOM.ImageLoader.list[i]=null,e.callback(0,0,e.ngitem,e.checks)):(t++,e.checks++));0==t&&(u.VOM.ImageLoader.list=[],clearInterval(u.VOM.ImageLoader.intervalHandle),delete u.VOM.ImageLoader.intervalHandle)}}},u.popup={isDisplayed:!1,$elt:null,close:function(){null!=this.$elt&&(new NGTweenable).tween({from:{opacity:1},to:{opacity:0},attachment:{t:this},easing:"easeInOutSine",duration:100,step:function(e,t){null!=t.t.$elt&&t.t.$elt.css("opacity",e.opacity)},finish:function(e,t){null!=t.t.$elt&&(t.t.$elt.remove(),t.t.$elt=null),t.t.isDisplayed=!1}})}},u.galleryTheme_dark={navigationBar:{background:"none",borderTop:"",borderBottom:"",borderRight:"",borderLeft:""},navigationBreadcrumb:{background:"#111",color:"#fff",colorHover:"#ccc",borderRadius:"4px"},navigationFilter:{color:"#ddd",background:"#111",colorSelected:"#fff",backgroundSelected:"#111",borderRadius:"4px"},navigationPagination:{background:"#111",color:"#fff",colorHover:"#ccc",borderRadius:"4px"},thumbnail:{background:"#444",backgroundImage:"linear-gradient(315deg, #111 0%, #445 90%)",borderColor:"#000",borderRadius:"0px",labelOpacity:1,labelBackground:"rgba(34, 34, 34, 0)",titleColor:"#fff",titleBgColor:"transparent",titleShadow:"",descriptionColor:"#ccc",descriptionBgColor:"transparent",descriptionShadow:"",stackBackground:"#aaa"},thumbnailIcon:{padding:"5px",color:"#fff",shadow:""},pagination:{background:"#181818",backgroundSelected:"#666",color:"#fff",borderRadius:"2px",shapeBorder:"3px solid #666",shapeColor:"#444",shapeSelectedColor:"#aaa"}},u.galleryTheme_light={navigationBar:{background:"none",borderTop:"",borderBottom:"",borderRight:"",borderLeft:""},navigationBreadcrumb:{background:"#eee",color:"#000",colorHover:"#333",borderRadius:"4px"},navigationFilter:{background:"#eee",color:"#222",colorSelected:"#000",backgroundSelected:"#eee",borderRadius:"4px"},navigationPagination:{background:"#eee",color:"#000",colorHover:"#333",borderRadius:"4px"},thumbnail:{background:"#444",backgroundImage:"linear-gradient(315deg, #111 0%, #445 90%)",borderColor:"#000",labelOpacity:1,labelBackground:"rgba(34, 34, 34, 0)",titleColor:"#fff",titleBgColor:"transparent",titleShadow:"",descriptionColor:"#ccc",descriptionBgColor:"transparent",descriptionShadow:"",stackBackground:"#888"},thumbnailIcon:{padding:"5px",color:"#fff"},pagination:{background:"#eee",backgroundSelected:"#aaa",color:"#000",borderRadius:"2px",shapeBorder:"3px solid #666",shapeColor:"#444",shapeSelectedColor:"#aaa"}},u.viewerTheme_dark={background:"#000",barBackground:"rgba(4, 4, 4, 0.2)",barBorder:"0px solid #111",barColor:"#fff",barDescriptionColor:"#ccc"},u.viewerTheme_light={background:"#f8f8f8",barBackground:"rgba(4, 4, 4, 0.7)",barBorder:"0px solid #111",barColor:"#fff",barDescriptionColor:"#ccc"};var h=NGY2Tools.NanoAlert,d=NGY2Tools.NanoConsoleLog;function m(){u.VOM.items=[],u.VOM.albumID="0",u.GOM.curNavLevel="l1";var e=0,t=u.$E.base[0].attributes,n="";t.hasOwnProperty("src")&&(n=t.src.nodeValue),""==n&&t.hasOwnProperty("data-ngthumb")&&(n=t["data-ngthumb"].nodeValue);for(var i=void 0,a=0;a0?Ne(i):d(u,"No content for Lightbox standalone.")}function p(e){var t={albumID:"0",imageID:"0"},n=e.split("/");return n.length>0&&(t.albumID=n[0],n.length>1&&(t.imageID=n[1])),t}function g(e,t){u.VOM.viewerDisplayed&&nt(null);var n=NGY2Item.GetIdx(u,t);u.GOM.curNavLevel="lN",0==n&&(u.GOM.curNavLevel="l1"),u.layout.SetEngine(),u.galleryResizeEventEnabled=!1,-1==n&&(NGY2Item.New(u,"","",t,"0","album"),n=u.I.length-1),u.I[n].contentIsLoaded?(me(),u.GOM.pagination.currentPage=0,lt(t,""),w(n)):Z(t,g,e,t)}function f(){switch(u.galleryDisplayMode.Get()){case"PAGINATION":u.layout.support.rows&&u.galleryMaxRows.Get()>0&&function(){if(u.$E.conTnBottom.css("opacity",0),u.$E.conTnBottom.children().remove(),0==u.GOM.items.length)return;var e=Math.ceil((u.GOM.items[u.GOM.items.length-1].row+1)/u.galleryMaxRows.Get());if(1==e)return;u.GOM.pagination.currentPage>e-1&&(u.GOM.pagination.currentPage=e-1);if(M(),0==u.GOM.displayInterval.len)return;if("NUMBERS"==u.O.galleryPaginationMode&&u.GOM.pagination.currentPage>0){jQuery('").appendTo(u.$E.conTnBottom).click((function(e){G()}))}var t=0,n=e;if("NUMBERS"!=u.O.galleryPaginationMode)t=0;else{var i=u.O.paginationVisiblePages;if(i>=e)t=0;else{var a=0;a=i%2==1?(i+1)/2:i/2,u.GOM.pagination.currentPagee&&(n=e-1)):(n=(t=u.GOM.pagination.currentPage-a)+i)>e&&(n=e-1),n-t'+l+"").appendTo(u.$E.conTnBottom);s.data("pageNumber",o),s.click((function(e){u.GOM.pagination.currentPage=jQuery(this).data("pageNumber"),ot("pageChanged"),u.GOM.ScrollToTop(),L(),E(!0)}))}if("NUMBERS"==u.O.galleryPaginationMode&&u.GOM.pagination.currentPage+1