mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge branch 'staging' into parser-followup-2
This commit is contained in:
@ -1,3 +1,4 @@
|
||||
import { power_user } from './power-user.js';
|
||||
import { removeFromArray, runAfterAnimation, uuidv4 } from './utils.js';
|
||||
|
||||
/** @readonly */
|
||||
@ -11,6 +12,8 @@ export const POPUP_TYPE = {
|
||||
INPUT: 3,
|
||||
/** Popup without any button controls. Used to simply display content, with a small X in the corner. */
|
||||
DISPLAY: 4,
|
||||
/** Popup that displays an image to crop. Returns a cropped image in result. */
|
||||
CROP: 5,
|
||||
};
|
||||
|
||||
/** @readonly */
|
||||
@ -36,12 +39,14 @@ export const POPUP_RESULT = {
|
||||
* @property {CustomPopupButton[]|string[]?} [customButtons=null] - Custom buttons to add to the popup. If only strings are provided, the buttons will be added with default options, and their result will be in order from `2` onward.
|
||||
* @property {(popup: Popup) => boolean?} [onClosing=null] - Handler called before the popup closes, return `false` to cancel the close
|
||||
* @property {(popup: Popup) => void?} [onClose=null] - Handler called after the popup closes, but before the DOM is cleaned up
|
||||
* @property {number?} [cropAspect=null] - Aspect ratio for the crop popup
|
||||
* @property {string?} [cropImage=null] - Image URL to display in the crop popup
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} CustomPopupButton
|
||||
* @property {string} text - The text of the button
|
||||
* @property {POPUP_RESULT|number?} result - The result of the button - can also be a custom result value to make be able to find out that this button was clicked. If no result is specified, this button will **not** close the popup.
|
||||
* @property {POPUP_RESULT|number?} [result] - The result of the button - can also be a custom result value to make be able to find out that this button was clicked. If no result is specified, this button will **not** close the popup.
|
||||
* @property {string[]|string?} [classes] - Optional custom CSS classes applied to the button
|
||||
* @property {()=>void?} [action] - Optional action to perform when the button is clicked
|
||||
* @property {boolean?} [appendAtEnd] - Whether to append the button to the end of the popup - by default it will be prepended
|
||||
@ -69,6 +74,22 @@ const showPopupHelper = {
|
||||
const value = await popup.show();
|
||||
return value ? String(value) : null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Asynchronously displays a confirmation popup with the given header and text, returning the clicked result button value.
|
||||
*
|
||||
* @param {string} header - The header text for the popup.
|
||||
* @param {string} text - The main text for the popup.
|
||||
* @param {PopupOptions} [popupOptions={}] - Options for the popup.
|
||||
* @return {Promise<POPUP_RESULT>} A Promise that resolves with the result of the user's interaction.
|
||||
*/
|
||||
confirm: async (header, text, popupOptions = {}) => {
|
||||
const content = PopupUtils.BuildTextWithHeader(header, text);
|
||||
const popup = new Popup(content, POPUP_TYPE.CONFIRM, null, popupOptions);
|
||||
const result = await popup.show();
|
||||
if (typeof result === 'string' || typeof result === 'boolean') throw new Error(`Invalid popup result. CONFIRM popups only support numbers, or null. Result: ${result}`);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
export class Popup {
|
||||
@ -84,6 +105,8 @@ export class Popup {
|
||||
/** @type {HTMLElement} */ okButton;
|
||||
/** @type {HTMLElement} */ cancelButton;
|
||||
/** @type {HTMLElement} */ closeButton;
|
||||
/** @type {HTMLElement} */ cropWrap;
|
||||
/** @type {HTMLImageElement} */ cropImage;
|
||||
/** @type {POPUP_RESULT|number?} */ defaultResult;
|
||||
/** @type {CustomPopupButton[]|string[]?} */ customButtons;
|
||||
|
||||
@ -92,6 +115,7 @@ export class Popup {
|
||||
|
||||
/** @type {POPUP_RESULT|number} */ result;
|
||||
/** @type {any} */ value;
|
||||
/** @type {any} */ cropData;
|
||||
|
||||
/** @type {HTMLElement} */ lastFocus;
|
||||
|
||||
@ -106,7 +130,7 @@ export class Popup {
|
||||
* @param {string} [inputValue=''] - The initial value of the input field
|
||||
* @param {PopupOptions} [options={}] - Additional options for the popup
|
||||
*/
|
||||
constructor(content, type, inputValue = '', { okButton = null, cancelButton = null, rows = 1, wide = false, wider = false, large = false, transparent = false, allowHorizontalScrolling = false, allowVerticalScrolling = false, defaultResult = POPUP_RESULT.AFFIRMATIVE, customButtons = null, onClosing = null, onClose = null } = {}) {
|
||||
constructor(content, type, inputValue = '', { okButton = null, cancelButton = null, rows = 1, wide = false, wider = false, large = false, transparent = false, allowHorizontalScrolling = false, allowVerticalScrolling = false, defaultResult = POPUP_RESULT.AFFIRMATIVE, customButtons = null, onClosing = null, onClose = null, cropAspect = null, cropImage = null } = {}) {
|
||||
Popup.util.popups.push(this);
|
||||
|
||||
// Make this popup uniquely identifiable
|
||||
@ -128,6 +152,8 @@ export class Popup {
|
||||
this.okButton = this.dlg.querySelector('.popup-button-ok');
|
||||
this.cancelButton = this.dlg.querySelector('.popup-button-cancel');
|
||||
this.closeButton = this.dlg.querySelector('.popup-button-close');
|
||||
this.cropWrap = this.dlg.querySelector('.popup-crop-wrap');
|
||||
this.cropImage = this.dlg.querySelector('.popup-crop-image');
|
||||
|
||||
this.dlg.setAttribute('data-id', this.id);
|
||||
if (wide) this.dlg.classList.add('wide_dialogue_popup');
|
||||
@ -159,6 +185,10 @@ export class Popup {
|
||||
} else {
|
||||
this.controls.insertBefore(buttonElement, this.okButton);
|
||||
}
|
||||
|
||||
if (typeof button.action === 'function') {
|
||||
buttonElement.addEventListener('click', button.action);
|
||||
}
|
||||
});
|
||||
|
||||
// Set the default button class
|
||||
@ -169,6 +199,7 @@ export class Popup {
|
||||
// General styling for all types first, that might be overriden for specific types below
|
||||
this.input.style.display = 'none';
|
||||
this.closeButton.style.display = 'none';
|
||||
this.cropWrap.style.display = 'none';
|
||||
|
||||
switch (type) {
|
||||
case POPUP_TYPE.TEXT: {
|
||||
@ -190,6 +221,22 @@ export class Popup {
|
||||
this.closeButton.style.display = 'block';
|
||||
break;
|
||||
}
|
||||
case POPUP_TYPE.CROP: {
|
||||
this.cropWrap.style.display = 'block';
|
||||
this.cropImage.src = cropImage;
|
||||
if (!okButton) this.okButton.textContent = template.getAttribute('popup-button-crop');
|
||||
$(this.cropImage).cropper({
|
||||
aspectRatio: cropAspect ?? 2 / 3,
|
||||
autoCropArea: 1,
|
||||
viewMode: 2,
|
||||
rotatable: false,
|
||||
crop: (event) => {
|
||||
this.cropData = event.detail;
|
||||
this.cropData.want_resize = !power_user.never_resize_avatars;
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.warn('Unknown popup type.', type);
|
||||
break;
|
||||
@ -220,6 +267,7 @@ export class Popup {
|
||||
this.dlg.querySelectorAll('[data-result]').forEach(resultControl => {
|
||||
if (!(resultControl instanceof HTMLElement)) return;
|
||||
const result = Number(resultControl.dataset.result);
|
||||
if (String(undefined) === String(resultControl.dataset.result)) return;
|
||||
if (isNaN(result)) throw new Error('Invalid result control. Result must be a number. ' + resultControl.dataset.result);
|
||||
const type = resultControl.dataset.resultEvent || 'click';
|
||||
resultControl.addEventListener(type, () => this.complete(result));
|
||||
@ -347,6 +395,13 @@ export class Popup {
|
||||
else value = false; // Might a custom negative value?
|
||||
}
|
||||
|
||||
// Cropped image should be returned as a data URL
|
||||
if (this.type === POPUP_TYPE.CROP) {
|
||||
value = result >= POPUP_RESULT.AFFIRMATIVE
|
||||
? $(this.cropImage).data('cropper').getCroppedCanvas().toDataURL('image/jpeg')
|
||||
: null;
|
||||
}
|
||||
|
||||
this.value = value;
|
||||
this.result = result;
|
||||
|
||||
|
Reference in New Issue
Block a user