mirror of
				https://github.com/SillyTavern/SillyTavern.git
				synced 2025-06-05 21:59:27 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			237 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			237 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import { animation_duration, animation_easing } from '../script.js';
 | |
| import { delay } from './utils.js';
 | |
| 
 | |
| 
 | |
| 
 | |
| /**@readonly*/
 | |
| /**@enum {Number}*/
 | |
| export const POPUP_TYPE = {
 | |
|     'TEXT': 1,
 | |
|     'CONFIRM': 2,
 | |
|     'INPUT': 3,
 | |
| };
 | |
| 
 | |
| /**@readonly*/
 | |
| /**@enum {Boolean}*/
 | |
| export const POPUP_RESULT = {
 | |
|     'AFFIRMATIVE': true,
 | |
|     'NEGATIVE': false,
 | |
|     'CANCELLED': undefined,
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| export class Popup {
 | |
|     /**@type {POPUP_TYPE}*/ type;
 | |
| 
 | |
|     /**@type {HTMLElement}*/ dom;
 | |
|     /**@type {HTMLElement}*/ dlg;
 | |
|     /**@type {HTMLElement}*/ text;
 | |
|     /**@type {HTMLTextAreaElement}*/ input;
 | |
|     /**@type {HTMLElement}*/ ok;
 | |
|     /**@type {HTMLElement}*/ cancel;
 | |
| 
 | |
|     /**@type {POPUP_RESULT}*/ result;
 | |
|     /**@type {any}*/ value;
 | |
| 
 | |
|     /**@type {Promise}*/ promise;
 | |
|     /**@type {Function}*/ resolver;
 | |
| 
 | |
|     /**@type {Function}*/ keyListenerBound;
 | |
| 
 | |
| 
 | |
| 
 | |
|     /**
 | |
|      * @typedef {{okButton?: string, cancelButton?: string, rows?: number, wide?: boolean, large?: boolean, allowHorizontalScrolling?: boolean, allowVerticalScrolling?: boolean }} PopupOptions - Options for the popup.
 | |
|      * @param {JQuery<HTMLElement>|string|Element} text - Text to display in the popup.
 | |
|      * @param {POPUP_TYPE} type - One of Popup.TYPE
 | |
|      * @param {string} inputValue - Value to set the input to.
 | |
|      * @param {PopupOptions} options - Options for the popup.
 | |
|      */
 | |
|     constructor(text, type, inputValue = '', { okButton, cancelButton, rows, wide, large, allowHorizontalScrolling, allowVerticalScrolling } = {}) {
 | |
|         this.type = type;
 | |
| 
 | |
|         /**@type {HTMLTemplateElement}*/
 | |
|         const template = document.querySelector('#shadow_popup_template');
 | |
|         // @ts-ignore
 | |
|         this.dom = template.content.cloneNode(true).querySelector('.shadow_popup');
 | |
|         const dlg = this.dom.querySelector('.dialogue_popup');
 | |
|         // @ts-ignore
 | |
|         this.dlg = dlg;
 | |
|         this.text = this.dom.querySelector('.dialogue_popup_text');
 | |
|         this.input = this.dom.querySelector('.dialogue_popup_input');
 | |
|         this.ok = this.dom.querySelector('.dialogue_popup_ok');
 | |
|         this.cancel = this.dom.querySelector('.dialogue_popup_cancel');
 | |
| 
 | |
|         if (wide) dlg.classList.add('wide_dialogue_popup');
 | |
|         if (large) dlg.classList.add('large_dialogue_popup');
 | |
|         if (allowHorizontalScrolling) dlg.classList.add('horizontal_scrolling_dialogue_popup');
 | |
|         if (allowVerticalScrolling) dlg.classList.add('vertical_scrolling_dialogue_popup');
 | |
| 
 | |
|         this.ok.textContent = okButton ?? 'OK';
 | |
|         this.cancel.textContent = cancelButton ?? 'Cancel';
 | |
| 
 | |
|         switch (type) {
 | |
|             case POPUP_TYPE.TEXT: {
 | |
|                 this.input.style.display = 'none';
 | |
|                 this.cancel.style.display = 'none';
 | |
|                 break;
 | |
|             }
 | |
|             case POPUP_TYPE.CONFIRM: {
 | |
|                 this.input.style.display = 'none';
 | |
|                 this.ok.textContent = okButton ?? 'Yes';
 | |
|                 this.cancel.textContent = cancelButton ?? 'No';
 | |
|                 break;
 | |
|             }
 | |
|             case POPUP_TYPE.INPUT: {
 | |
|                 this.input.style.display = 'block';
 | |
|                 this.ok.textContent = okButton ?? 'Save';
 | |
|                 break;
 | |
|             }
 | |
|             default: {
 | |
|                 // illegal argument
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         this.input.value = inputValue;
 | |
|         this.input.rows = rows ?? 1;
 | |
| 
 | |
|         this.text.innerHTML = '';
 | |
|         if (text instanceof jQuery) {
 | |
|             $(this.text).append(text);
 | |
|         } else if (text instanceof HTMLElement) {
 | |
|             this.text.append(text);
 | |
|         } else if (typeof text == 'string') {
 | |
|             this.text.innerHTML = text;
 | |
|         } else {
 | |
|             // illegal argument
 | |
|         }
 | |
| 
 | |
|         this.input.addEventListener('keydown', (evt) => {
 | |
|             if (evt.key != 'Enter' || evt.altKey || evt.ctrlKey || evt.shiftKey) return;
 | |
|             evt.preventDefault();
 | |
|             evt.stopPropagation();
 | |
|             this.completeAffirmative();
 | |
|         });
 | |
| 
 | |
|         this.ok.addEventListener('click', () => this.completeAffirmative());
 | |
|         this.cancel.addEventListener('click', () => this.completeNegative());
 | |
|         const keyListener = (evt) => {
 | |
|             switch (evt.key) {
 | |
|                 case 'Escape': {
 | |
|                     // does it really matter where we check?
 | |
|                     const topModal = document.elementFromPoint(window.innerWidth / 2, window.innerHeight / 2)?.closest('.shadow_popup');
 | |
|                     if (topModal == this.dom) {
 | |
|                         evt.preventDefault();
 | |
|                         evt.stopPropagation();
 | |
|                         this.completeCancelled();
 | |
|                         window.removeEventListener('keydown', keyListenerBound);
 | |
|                         break;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         };
 | |
|         const keyListenerBound = keyListener.bind(this);
 | |
|         window.addEventListener('keydown', keyListenerBound);
 | |
|     }
 | |
| 
 | |
|     async show() {
 | |
|         document.body.append(this.dom);
 | |
|         this.dom.style.display = 'block';
 | |
|         switch (this.type) {
 | |
|             case POPUP_TYPE.INPUT: {
 | |
|                 this.input.focus();
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         $(this.dom).transition({
 | |
|             opacity: 1,
 | |
|             duration: animation_duration,
 | |
|             easing: animation_easing,
 | |
|         });
 | |
| 
 | |
|         this.promise = new Promise((resolve) => {
 | |
|             this.resolver = resolve;
 | |
|         });
 | |
|         return this.promise;
 | |
|     }
 | |
| 
 | |
|     completeAffirmative() {
 | |
|         switch (this.type) {
 | |
|             case POPUP_TYPE.TEXT:
 | |
|             case POPUP_TYPE.CONFIRM: {
 | |
|                 this.value = true;
 | |
|                 break;
 | |
|             }
 | |
|             case POPUP_TYPE.INPUT: {
 | |
|                 this.value = this.input.value;
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|         this.result = POPUP_RESULT.AFFIRMATIVE;
 | |
|         this.hide();
 | |
|     }
 | |
| 
 | |
|     completeNegative() {
 | |
|         switch (this.type) {
 | |
|             case POPUP_TYPE.TEXT:
 | |
|             case POPUP_TYPE.CONFIRM:
 | |
|             case POPUP_TYPE.INPUT: {
 | |
|                 this.value = false;
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|         this.result = POPUP_RESULT.NEGATIVE;
 | |
|         this.hide();
 | |
|     }
 | |
| 
 | |
|     completeCancelled() {
 | |
|         switch (this.type) {
 | |
|             case POPUP_TYPE.TEXT:
 | |
|             case POPUP_TYPE.CONFIRM:
 | |
|             case POPUP_TYPE.INPUT: {
 | |
|                 this.value = null;
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|         this.result = POPUP_RESULT.CANCELLED;
 | |
|         this.hide();
 | |
|     }
 | |
| 
 | |
| 
 | |
| 
 | |
|     hide() {
 | |
|         $(this.dom).transition({
 | |
|             opacity: 0,
 | |
|             duration: animation_duration,
 | |
|             easing: animation_easing,
 | |
|         });
 | |
|         delay(animation_duration).then(() => {
 | |
|             this.dom.remove();
 | |
|         });
 | |
| 
 | |
|         this.resolver(this.value);
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Displays a blocking popup with a given text and type.
 | |
|  * @param {JQuery<HTMLElement>|string|Element} text - Text to display in the popup.
 | |
|  * @param {POPUP_TYPE} type
 | |
|  * @param {string} inputValue - Value to set the input to.
 | |
|  * @param {PopupOptions} options - Options for the popup.
 | |
|  * @returns
 | |
|  */
 | |
| export function callGenericPopup(text, type, inputValue = '', { okButton, cancelButton, rows, wide, large, allowHorizontalScrolling, allowVerticalScrolling } = {}) {
 | |
|     const popup = new Popup(
 | |
|         text,
 | |
|         type,
 | |
|         inputValue,
 | |
|         { okButton, cancelButton, rows, wide, large, allowHorizontalScrolling, allowVerticalScrolling },
 | |
|     );
 | |
|     return popup.show();
 | |
| }
 |