mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Update Popup to support custom input checkboxes
This commit is contained in:
@ -92,6 +92,24 @@ dialog {
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.popup-crop-wrap {
|
||||||
|
margin: 10px auto;
|
||||||
|
max-height: 75vh;
|
||||||
|
max-height: 75svh;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-crop-wrap img {
|
||||||
|
max-width: 100%;
|
||||||
|
/* This rule is very important, please do not ignore this! */
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-inputs {
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: smaller;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
.popup-input {
|
.popup-input {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
@ -4883,6 +4883,7 @@
|
|||||||
<img class="popup-crop-image" src="">
|
<img class="popup-crop-image" src="">
|
||||||
</div>
|
</div>
|
||||||
<textarea class="popup-input text_pole result-control" rows="1" data-result="1" data-result-event="submit"></textarea>
|
<textarea class="popup-input text_pole result-control" rows="1" data-result="1" data-result-event="submit"></textarea>
|
||||||
|
<div class="popup-inputs"></div>
|
||||||
<div class="popup-controls">
|
<div class="popup-controls">
|
||||||
<div class="popup-button-ok menu_button result-control" data-result="1" data-i18n="Delete">Delete</div>
|
<div class="popup-button-ok menu_button result-control" data-result="1" data-i18n="Delete">Delete</div>
|
||||||
<div class="popup-button-cancel menu_button result-control" data-result="0" data-i18n="Cancel">Cancel</div>
|
<div class="popup-button-cancel menu_button result-control" data-result="0" data-i18n="Cancel">Cancel</div>
|
||||||
|
@ -37,6 +37,7 @@ export const POPUP_RESULT = {
|
|||||||
* @property {boolean?} [allowVerticalScrolling=false] - Whether to allow vertical scrolling in the popup
|
* @property {boolean?} [allowVerticalScrolling=false] - Whether to allow vertical scrolling in the popup
|
||||||
* @property {POPUP_RESULT|number?} [defaultResult=POPUP_RESULT.AFFIRMATIVE] - The default result of this popup when Enter is pressed. Can be changed from `POPUP_RESULT.AFFIRMATIVE`.
|
* @property {POPUP_RESULT|number?} [defaultResult=POPUP_RESULT.AFFIRMATIVE] - The default result of this popup when Enter is pressed. Can be changed from `POPUP_RESULT.AFFIRMATIVE`.
|
||||||
* @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 {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 {CustomPopupInput[]?} [customInputs=null] - Custom inputs to add to the popup. The display below the content and the input box, one by one.
|
||||||
* @property {(popup: Popup) => boolean?} [onClosing=null] - Handler called before the popup closes, return `false` to cancel the close
|
* @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 {(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 {number?} [cropAspect=null] - Aspect ratio for the crop popup
|
||||||
@ -52,6 +53,14 @@ export const POPUP_RESULT = {
|
|||||||
* @property {boolean?} [appendAtEnd] - Whether to append the button to the end of the popup - by default it will be prepended
|
* @property {boolean?} [appendAtEnd] - Whether to append the button to the end of the popup - by default it will be prepended
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} CustomPopupInput
|
||||||
|
* @property {string} id - The id for the html element
|
||||||
|
* @property {string} label - The label text for the input
|
||||||
|
* @property {string?} [tooltip=null] - Optional tooltip icon displayed behind the label
|
||||||
|
* @property {boolean?} [defaultState=false] - The default state when opening the popup (false if not set)
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {object} ShowPopupHelper
|
* @typedef {object} ShowPopupHelper
|
||||||
* Local implementation of the helper functionality to show several popups.
|
* Local implementation of the helper functionality to show several popups.
|
||||||
@ -98,23 +107,26 @@ export class Popup {
|
|||||||
/** @type {string} */ id;
|
/** @type {string} */ id;
|
||||||
|
|
||||||
/** @type {HTMLDialogElement} */ dlg;
|
/** @type {HTMLDialogElement} */ dlg;
|
||||||
/** @type {HTMLElement} */ body;
|
/** @type {HTMLDivElement} */ body;
|
||||||
/** @type {HTMLElement} */ content;
|
/** @type {HTMLDivElement} */ content;
|
||||||
/** @type {HTMLTextAreaElement} */ input;
|
/** @type {HTMLTextAreaElement} */ mainInput;
|
||||||
/** @type {HTMLElement} */ controls;
|
/** @type {HTMLDivElement} */ inputControls;
|
||||||
/** @type {HTMLElement} */ okButton;
|
/** @type {HTMLDivElement} */ buttonControls;
|
||||||
/** @type {HTMLElement} */ cancelButton;
|
/** @type {HTMLDivElement} */ okButton;
|
||||||
/** @type {HTMLElement} */ closeButton;
|
/** @type {HTMLDivElement} */ cancelButton;
|
||||||
/** @type {HTMLElement} */ cropWrap;
|
/** @type {HTMLDivElement} */ closeButton;
|
||||||
|
/** @type {HTMLDivElement} */ cropWrap;
|
||||||
/** @type {HTMLImageElement} */ cropImage;
|
/** @type {HTMLImageElement} */ cropImage;
|
||||||
/** @type {POPUP_RESULT|number?} */ defaultResult;
|
/** @type {POPUP_RESULT|number?} */ defaultResult;
|
||||||
/** @type {CustomPopupButton[]|string[]?} */ customButtons;
|
/** @type {CustomPopupButton[]|string[]?} */ customButtons;
|
||||||
|
/** @type {CustomPopupInput[]} */ customInputs;
|
||||||
|
|
||||||
/** @type {(popup: Popup) => boolean?} */ onClosing;
|
/** @type {(popup: Popup) => boolean?} */ onClosing;
|
||||||
/** @type {(popup: Popup) => void?} */ onClose;
|
/** @type {(popup: Popup) => void?} */ onClose;
|
||||||
|
|
||||||
/** @type {POPUP_RESULT|number} */ result;
|
/** @type {POPUP_RESULT|number} */ result;
|
||||||
/** @type {any} */ value;
|
/** @type {any} */ value;
|
||||||
|
/** @type {Map<string,boolean>?} */ inputResults;
|
||||||
/** @type {any} */ cropData;
|
/** @type {any} */ cropData;
|
||||||
|
|
||||||
/** @type {HTMLElement} */ lastFocus;
|
/** @type {HTMLElement} */ lastFocus;
|
||||||
@ -130,7 +142,7 @@ export class Popup {
|
|||||||
* @param {string} [inputValue=''] - The initial value of the input field
|
* @param {string} [inputValue=''] - The initial value of the input field
|
||||||
* @param {PopupOptions} [options={}] - Additional options for the popup
|
* @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, cropAspect = null, cropImage = 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, customInputs = null, onClosing = null, onClose = null, cropAspect = null, cropImage = null } = {}) {
|
||||||
Popup.util.popups.push(this);
|
Popup.util.popups.push(this);
|
||||||
|
|
||||||
// Make this popup uniquely identifiable
|
// Make this popup uniquely identifiable
|
||||||
@ -147,8 +159,9 @@ export class Popup {
|
|||||||
this.dlg = template.content.cloneNode(true).querySelector('.popup');
|
this.dlg = template.content.cloneNode(true).querySelector('.popup');
|
||||||
this.body = this.dlg.querySelector('.popup-body');
|
this.body = this.dlg.querySelector('.popup-body');
|
||||||
this.content = this.dlg.querySelector('.popup-content');
|
this.content = this.dlg.querySelector('.popup-content');
|
||||||
this.input = this.dlg.querySelector('.popup-input');
|
this.mainInput = this.dlg.querySelector('.popup-input');
|
||||||
this.controls = this.dlg.querySelector('.popup-controls');
|
this.inputControls = this.dlg.querySelector('.popup-inputs');
|
||||||
|
this.buttonControls = this.dlg.querySelector('.popup-controls');
|
||||||
this.okButton = this.dlg.querySelector('.popup-button-ok');
|
this.okButton = this.dlg.querySelector('.popup-button-ok');
|
||||||
this.cancelButton = this.dlg.querySelector('.popup-button-cancel');
|
this.cancelButton = this.dlg.querySelector('.popup-button-cancel');
|
||||||
this.closeButton = this.dlg.querySelector('.popup-button-close');
|
this.closeButton = this.dlg.querySelector('.popup-button-close');
|
||||||
@ -181,9 +194,9 @@ export class Popup {
|
|||||||
buttonElement.tabIndex = 0;
|
buttonElement.tabIndex = 0;
|
||||||
|
|
||||||
if (button.appendAtEnd) {
|
if (button.appendAtEnd) {
|
||||||
this.controls.appendChild(buttonElement);
|
this.buttonControls.appendChild(buttonElement);
|
||||||
} else {
|
} else {
|
||||||
this.controls.insertBefore(buttonElement, this.okButton);
|
this.buttonControls.insertBefore(buttonElement, this.okButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof button.action === 'function') {
|
if (typeof button.action === 'function') {
|
||||||
@ -191,13 +204,43 @@ export class Popup {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.customInputs = customInputs;
|
||||||
|
this.customInputs?.forEach(input => {
|
||||||
|
if (!input.id || !(typeof input.id === 'string')) {
|
||||||
|
console.warn('Given custom input does not have a valid id set')
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const label = document.createElement('label');
|
||||||
|
label.classList.add('checkbox_label', 'justifyCenter');
|
||||||
|
label.setAttribute('for', input.id);
|
||||||
|
const inputElement = document.createElement('input');
|
||||||
|
inputElement.type = 'checkbox';
|
||||||
|
inputElement.id = input.id;
|
||||||
|
inputElement.checked = input.defaultState ?? false;
|
||||||
|
label.appendChild(inputElement);
|
||||||
|
const labelText = document.createElement('span');
|
||||||
|
labelText.innerText = input.label;
|
||||||
|
label.appendChild(labelText);
|
||||||
|
|
||||||
|
if (input.tooltip) {
|
||||||
|
const tooltip = document.createElement('div');
|
||||||
|
tooltip.title = input.tooltip;
|
||||||
|
tooltip.classList.add('fa-solid', 'fa-circle-info', 'opacity50p');
|
||||||
|
label.appendChild(tooltip);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.inputControls.appendChild(label);
|
||||||
|
});
|
||||||
|
|
||||||
// Set the default button class
|
// Set the default button class
|
||||||
const defaultButton = this.controls.querySelector(`[data-result="${this.defaultResult}"]`);
|
const defaultButton = this.buttonControls.querySelector(`[data-result="${this.defaultResult}"]`);
|
||||||
if (defaultButton) defaultButton.classList.add('menu_button_default');
|
if (defaultButton) defaultButton.classList.add('menu_button_default');
|
||||||
|
|
||||||
// Styling differences depending on the popup type
|
// Styling differences depending on the popup type
|
||||||
// General styling for all types first, that might be overriden for specific types below
|
// General styling for all types first, that might be overriden for specific types below
|
||||||
this.input.style.display = 'none';
|
this.mainInput.style.display = 'none';
|
||||||
|
this.inputControls.style.display = customInputs ? 'block' : 'none';
|
||||||
this.closeButton.style.display = 'none';
|
this.closeButton.style.display = 'none';
|
||||||
this.cropWrap.style.display = 'none';
|
this.cropWrap.style.display = 'none';
|
||||||
|
|
||||||
@ -212,12 +255,12 @@ export class Popup {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case POPUP_TYPE.INPUT: {
|
case POPUP_TYPE.INPUT: {
|
||||||
this.input.style.display = 'block';
|
this.mainInput.style.display = 'block';
|
||||||
if (!okButton) this.okButton.textContent = template.getAttribute('popup-button-save');
|
if (!okButton) this.okButton.textContent = template.getAttribute('popup-button-save');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case POPUP_TYPE.DISPLAY: {
|
case POPUP_TYPE.DISPLAY: {
|
||||||
this.controls.style.display = 'none';
|
this.buttonControls.style.display = 'none';
|
||||||
this.closeButton.style.display = 'block';
|
this.closeButton.style.display = 'block';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -243,8 +286,8 @@ export class Popup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.input.value = inputValue;
|
this.mainInput.value = inputValue;
|
||||||
this.input.rows = rows ?? 1;
|
this.mainInput.rows = rows ?? 1;
|
||||||
|
|
||||||
this.content.innerHTML = '';
|
this.content.innerHTML = '';
|
||||||
if (content instanceof jQuery) {
|
if (content instanceof jQuery) {
|
||||||
@ -352,12 +395,12 @@ export class Popup {
|
|||||||
if (!control) {
|
if (!control) {
|
||||||
switch (this.type) {
|
switch (this.type) {
|
||||||
case POPUP_TYPE.INPUT: {
|
case POPUP_TYPE.INPUT: {
|
||||||
control = this.input;
|
control = this.mainInput;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
// Select default button
|
// Select default button
|
||||||
control = this.controls.querySelector(`[data-result="${this.defaultResult}"]`);
|
control = this.buttonControls.querySelector(`[data-result="${this.defaultResult}"]`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -389,7 +432,7 @@ export class Popup {
|
|||||||
let value = result;
|
let value = result;
|
||||||
// Input type have special results, so the input can be accessed directly without the need to save the popup and access both result and value
|
// Input type have special results, so the input can be accessed directly without the need to save the popup and access both result and value
|
||||||
if (this.type === POPUP_TYPE.INPUT) {
|
if (this.type === POPUP_TYPE.INPUT) {
|
||||||
if (result >= POPUP_RESULT.AFFIRMATIVE) value = this.input.value;
|
if (result >= POPUP_RESULT.AFFIRMATIVE) value = this.mainInput.value;
|
||||||
else if (result === POPUP_RESULT.NEGATIVE) value = false;
|
else if (result === POPUP_RESULT.NEGATIVE) value = false;
|
||||||
else if (result === POPUP_RESULT.CANCELLED) value = null;
|
else if (result === POPUP_RESULT.CANCELLED) value = null;
|
||||||
else value = false; // Might a custom negative value?
|
else value = false; // Might a custom negative value?
|
||||||
@ -402,6 +445,14 @@ export class Popup {
|
|||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.customInputs?.length) {
|
||||||
|
this.inputResults = new Map(this.customInputs.map(input => {
|
||||||
|
/** @type {HTMLInputElement} */
|
||||||
|
const inputControl = this.dlg.querySelector(`#${input.id}`);
|
||||||
|
return [inputControl.id, inputControl.checked];
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
this.value = value;
|
this.value = value;
|
||||||
this.result = result;
|
this.result = result;
|
||||||
|
|
||||||
|
@ -4150,18 +4150,6 @@ h5 {
|
|||||||
grid-template-columns: 340px auto;
|
grid-template-columns: 340px auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popup-crop-wrap {
|
|
||||||
margin: 10px auto;
|
|
||||||
max-height: 75vh;
|
|
||||||
max-height: 75svh;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popup-crop-wrap img {
|
|
||||||
max-width: 100%;
|
|
||||||
/* This rule is very important, please do not ignore this! */
|
|
||||||
}
|
|
||||||
|
|
||||||
body .ui-autocomplete {
|
body .ui-autocomplete {
|
||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
Reference in New Issue
Block a user