mirror of
				https://github.com/SillyTavern/SillyTavern.git
				synced 2025-06-05 21:59:27 +02:00 
			
		
		
		
	- Need to pull my "wider" popup type from the other branch, because 1/1 aspect ratio scaling is just bad - Add a few tailwind-like classes for ul-li styling - Add <kbd> element styling for keyboard buttons
		
			
				
	
	
		
			238 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			238 lines
		
	
	
		
			7.5 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, wider, 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 (wider) dlg.classList.add('wider_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 ?? template.getAttribute('popup_text_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 ?? template.getAttribute('popup_text_yes');
 | 
						|
                this.cancel.textContent = cancelButton ?? template.getAttribute('popup_text_no');
 | 
						|
                break;
 | 
						|
            }
 | 
						|
            case POPUP_TYPE.INPUT: {
 | 
						|
                this.input.style.display = 'block';
 | 
						|
                this.ok.textContent = okButton ?? template.getAttribute('popup_text_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, wider, large, allowHorizontalScrolling, allowVerticalScrolling } = {}) {
 | 
						|
    const popup = new Popup(
 | 
						|
        text,
 | 
						|
        type,
 | 
						|
        inputValue,
 | 
						|
        { okButton, cancelButton, rows, wide, wider, large, allowHorizontalScrolling, allowVerticalScrolling },
 | 
						|
    );
 | 
						|
    return popup.show();
 | 
						|
}
 |