Merge pull request #987 from city-unit/feature/exorcism
Internal Gallery Extension
This commit is contained in:
commit
eb815b906c
|
@ -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;
|
||||
|
|
|
@ -4387,6 +4387,16 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<template id="generic_draggable_template">
|
||||
<div class="draggable">
|
||||
<div class="panelControlBar flex-container">
|
||||
<div class="fa-solid fa-grip drag-grabber"></div>
|
||||
<div class="fa-solid fa-circle-xmark dragClose"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<div id="rawPromptPopup" class="list-group">
|
||||
<div id="rawPromptWrapper" class="tokenItemizingSubclass"></div>
|
||||
</div>
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<Array>} - 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<Object>} 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<void>} - 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<void>} - 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<void>} - 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(
|
||||
$("<option>", {
|
||||
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(`<div id="dragGallery"></div>`);
|
||||
// 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 <template> 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);
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"display_name": "Gallery",
|
||||
"loading_order": 6,
|
||||
"requires": [],
|
||||
"optional": [
|
||||
],
|
||||
"js": "index.js",
|
||||
"css": "",
|
||||
"author": "City-Unit",
|
||||
"version": "1.5.0",
|
||||
"homePage": "https://github.com/SillyTavern/SillyTavern"
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -873,6 +873,7 @@ async function sdMessageButton(e) {
|
|||
const message_id = $mes.attr('mesid');
|
||||
const message = context.chat[message_id];
|
||||
const characterName = message?.name || context.name2;
|
||||
const characterFileName = context.characterId ? context.characters[context.characterId].name : context.groups[Object.keys(context.groups).filter(x => context.groups[x].id === context.groupId)[0]].id.toString();
|
||||
const messageText = message?.mes;
|
||||
const hasSavedImage = message?.extra?.image && message?.extra?.title;
|
||||
|
||||
|
@ -888,7 +889,7 @@ async function sdMessageButton(e) {
|
|||
message.extra.title = prompt;
|
||||
|
||||
console.log('Regenerating an image, using existing prompt:', prompt);
|
||||
await sendGenerationRequest(generationMode.FREE, prompt, characterName, saveGeneratedImage);
|
||||
await sendGenerationRequest(generationMode.FREE, prompt, characterFileName, saveGeneratedImage);
|
||||
}
|
||||
else {
|
||||
console.log("doing /sd raw last");
|
||||
|
|
|
@ -848,6 +848,38 @@ export async function saveBase64AsFile(base64Data, characterName, filename = "",
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads either a CSS or JS file and appends it to the appropriate document section.
|
||||
*
|
||||
* @param {string} url - The URL of the file to be loaded.
|
||||
* @param {string} type - The type of file to load: "css" or "js".
|
||||
* @returns {Promise} - Resolves when the file has loaded, rejects if there's an error or invalid type.
|
||||
*/
|
||||
export function loadFileToDocument(url, type) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let element;
|
||||
|
||||
if (type === "css") {
|
||||
element = document.createElement("link");
|
||||
element.rel = "stylesheet";
|
||||
element.href = url;
|
||||
} else if (type === "js") {
|
||||
element = document.createElement("script");
|
||||
element.src = url;
|
||||
} else {
|
||||
reject("Invalid type specified");
|
||||
return;
|
||||
}
|
||||
|
||||
element.onload = resolve;
|
||||
element.onerror = reject;
|
||||
|
||||
type === "css"
|
||||
? document.head.appendChild(element)
|
||||
: document.body.appendChild(element);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a thumbnail from a data URL.
|
||||
* @param {string} dataUrl The data URL encoded data of the image.
|
||||
|
|
|
@ -1239,6 +1239,19 @@ input[type="file"] {
|
|||
opacity: 1;
|
||||
}
|
||||
|
||||
.dragClose {
|
||||
height: 15px;
|
||||
aspect-ratio: 1 / 1;
|
||||
font-size: 20px;
|
||||
opacity: 0.5;
|
||||
transition: all 250ms;
|
||||
}
|
||||
|
||||
.dragClose:hover {
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#floatingPrompt .drag-grabber {
|
||||
position: unset;
|
||||
}
|
||||
|
@ -3399,6 +3412,34 @@ a {
|
|||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.draggable {
|
||||
min-width: 100px;
|
||||
min-height: 100px;
|
||||
max-height: 90vh;
|
||||
max-width: 90vh;
|
||||
width: calc((100vw - var(--sheldWidth)) /2);
|
||||
position: absolute;
|
||||
padding: 0;
|
||||
filter: drop-shadow(2px 2px 2px var(--grey7070a));
|
||||
z-index: 29;
|
||||
overflow: hidden;
|
||||
display: none;
|
||||
bottom: 0;
|
||||
aspect-ratio: 2 / 3;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||
.no-scrollbar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Hide scrollbar for IE, Edge, and Firefox */
|
||||
.no-scrollbar {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
#groupMemberListPopoutClose {
|
||||
height: 15px;
|
||||
aspect-ratio: 1 / 1;
|
||||
|
@ -3538,3 +3579,9 @@ a {
|
|||
margin-left: 10px;
|
||||
/* Give some space between the button and search box */
|
||||
}
|
||||
|
||||
.draggable img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
18
server.js
18
server.js
|
@ -2673,7 +2673,7 @@ app.post('/uploadimage', jsonParser, async (request, response) => {
|
|||
}
|
||||
|
||||
// Extracting the base64 data and the image format
|
||||
const match = request.body.image.match(/^data:image\/(png|jpg|webp);base64,(.+)$/);
|
||||
const match = request.body.image.match(/^data:image\/(png|jpg|webp|jpeg|gif);base64,(.+)$/);
|
||||
if (!match) {
|
||||
return response.status(400).send({ error: "Invalid image format" });
|
||||
}
|
||||
|
@ -2705,6 +2705,22 @@ app.post('/uploadimage', jsonParser, async (request, response) => {
|
|||
}
|
||||
});
|
||||
|
||||
app.post('/listimgfiles/:folder', (req, res) => {
|
||||
const directoryPath = path.join(process.cwd(), 'public/user/images/', sanitize(req.params.folder));
|
||||
|
||||
if (!fs.existsSync(directoryPath)) {
|
||||
fs.mkdirSync(directoryPath, { recursive: true });
|
||||
}
|
||||
|
||||
try {
|
||||
const images = getImages(directoryPath);
|
||||
return res.send(images);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return res.status(500).send({ error: "Unable to retrieve files" });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
app.post('/getgroups', jsonParser, (_, response) => {
|
||||
const groups = [];
|
||||
|
|
Loading…
Reference in New Issue