Merge pull request #2045 from LenAnderson/popups-with-own-dom

Popups with own dom
This commit is contained in:
Cohee 2024-04-09 11:11:56 +03:00 committed by GitHub
commit a10cff376b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 295 additions and 38 deletions

View File

@ -4511,6 +4511,22 @@
</div>
</div>
<!-- various fullscreen popups -->
<template id="shadow_popup_template">
<div class="shadow_popup">
<div class="dialogue_popup">
<div class="dialogue_popup_holder">
<div class="dialogue_popup_text">
<h3 class="margin0">text</h3>
</div>
<textarea class="dialogue_popup_input text_pole" rows="1"></textarea>
<div class="dialogue_popup_controls">
<div class="dialogue_popup_ok menu_button" data-i18n="Delete">Delete</div>
<div class="dialogue_popup_cancel menu_button" data-i18n="Cancel">Cancel</div>
</div>
</div>
</div>
</div>
</template>
<div id="shadow_popup">
<div id="dialogue_popup">
<div id="dialogue_popup_holder">

View File

@ -211,6 +211,7 @@ import { loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermati
import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId } from './scripts/chats.js';
import { initPresetManager } from './scripts/preset-manager.js';
import { evaluateMacros } from './scripts/macros.js';
import { callGenericPopup } from './scripts/popup.js';
//exporting functions and vars for mods
export {
@ -821,7 +822,7 @@ let create_save = {
//animation right menu
export const ANIMATION_DURATION_DEFAULT = 125;
export let animation_duration = ANIMATION_DURATION_DEFAULT;
let animation_easing = 'ease-in-out';
export let animation_easing = 'ease-in-out';
let popup_type = '';
let chat_file_for_del = '';
let online_status = 'no_connection';
@ -7808,6 +7809,7 @@ window['SillyTavern'].getContext = function () {
registedDebugFunction: registerDebugFunction,
renderExtensionTemplate: renderExtensionTemplate,
callPopup: callPopup,
callGenericPopup: callGenericPopup,
mainApi: main_api,
extensionSettings: extension_settings,
ModuleWorkerWrapper: ModuleWorkerWrapper,

View File

@ -1,4 +1,4 @@
import { callPopup } from '../../../../script.js';
import { POPUP_TYPE, Popup } from '../../../popup.js';
import { getSortableDelay } from '../../../utils.js';
import { log, warn } from '../index.js';
import { QuickReplyContextLink } from './QuickReplyContextLink.js';
@ -44,6 +44,8 @@ export class QuickReply {
/**@type {HTMLInputElement}*/ settingsDomLabel;
/**@type {HTMLTextAreaElement}*/ settingsDomMessage;
/**@type {Popup}*/ editorPopup;
/**@type {HTMLElement}*/ editorExecuteBtn;
/**@type {HTMLElement}*/ editorExecuteErrors;
/**@type {HTMLInputElement}*/ editorExecuteHide;
@ -197,7 +199,8 @@ export class QuickReply {
/**@type {HTMLElement} */
// @ts-ignore
const dom = this.template.cloneNode(true);
const popupResult = callPopup(dom, 'text', undefined, { okButton: 'OK', wide: true, large: true, rows: 1 });
this.editorPopup = new Popup(dom, POPUP_TYPE.TEXT, undefined, { okButton: 'OK', wide: true, large: true, rows: 1 });
const popupResult = this.editorPopup.show();
// basics
/**@type {HTMLInputElement}*/
@ -435,7 +438,7 @@ export class QuickReply {
this.editorExecuteBtn.classList.add('qr--busy');
this.editorExecuteErrors.innerHTML = '';
if (this.editorExecuteHide.checked) {
document.querySelector('#shadow_popup').classList.add('qr--hide');
this.editorPopup.dom.classList.add('qr--hide');
}
try {
this.editorExecutePromise = this.execute();
@ -445,7 +448,7 @@ export class QuickReply {
}
this.editorExecutePromise = null;
this.editorExecuteBtn.classList.remove('qr--busy');
document.querySelector('#shadow_popup').classList.remove('qr--hide');
this.editorPopup.dom.classList.remove('qr--hide');
}

View File

@ -216,60 +216,60 @@
align-items: baseline;
}
@media screen and (max-width: 750px) {
body #dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor {
body .dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor {
flex-direction: column;
}
body #dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels {
body .dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels {
flex-direction: column;
}
body #dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-message {
body .dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-message {
min-height: 90svh;
}
}
#dialogue_popup:has(#qr--modalEditor) {
.dialogue_popup:has(#qr--modalEditor) {
aspect-ratio: unset;
}
#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text {
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text {
display: flex;
flex-direction: column;
}
#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor {
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor {
flex: 1 1 auto;
display: flex;
flex-direction: row;
gap: 1em;
}
#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main {
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main {
flex: 1 1 auto;
display: flex;
flex-direction: column;
}
#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels {
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels {
flex: 0 0 auto;
display: flex;
flex-direction: row;
gap: 0.5em;
}
#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label {
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label {
flex: 1 1 1px;
display: flex;
flex-direction: column;
}
#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label > .qr--labelText {
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label > .qr--labelText {
flex: 1 1 auto;
}
#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label > .qr--labelHint {
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label > .qr--labelHint {
flex: 1 1 auto;
}
#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label > input {
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label > input {
flex: 0 0 auto;
}
#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer {
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer {
flex: 1 1 auto;
display: flex;
flex-direction: column;
}
#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings {
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings {
display: flex;
flex-direction: row;
gap: 1em;
@ -277,24 +277,24 @@
font-size: smaller;
align-items: baseline;
}
#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings > .checkbox_label {
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings > .checkbox_label {
white-space: nowrap;
}
#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings > .checkbox_label > input {
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings > .checkbox_label > input {
font-size: inherit;
}
#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-message {
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-message {
flex: 1 1 auto;
}
#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor #qr--modal-execute {
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-execute {
display: flex;
flex-direction: row;
gap: 0.5em;
}
#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor #qr--modal-execute.qr--busy {
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-execute.qr--busy {
opacity: 0.5;
cursor: wait;
}
#shadow_popup.qr--hide {
.shadow_popup.qr--hide {
opacity: 0 !important;
}

View File

@ -242,7 +242,7 @@
@media screen and (max-width: 750px) {
body #dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor {
body .dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor {
flex-direction: column;
> #qr--main > .qr--labels {
flex-direction: column;
@ -252,10 +252,10 @@
}
}
}
#dialogue_popup:has(#qr--modalEditor) {
.dialogue_popup:has(#qr--modalEditor) {
aspect-ratio: unset;
#dialogue_popup_text {
.dialogue_popup_text {
display: flex;
flex-direction: column;
@ -326,6 +326,6 @@
}
}
#shadow_popup.qr--hide {
.shadow_popup.qr--hide {
opacity: 0 !important;
}

225
public/scripts/popup.js Normal file
View File

@ -0,0 +1,225 @@
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.ok.addEventListener('click', ()=>this.completeAffirmative());
this.cancel.addEventListener('click', ()=>this.completeNegative());
const keyListener = (evt)=>{
switch (evt.key) {
case 'Escape': {
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, rows, wide, large, allowHorizontalScrolling, allowVerticalScrolling },
);
return popup.show();
}

View File

@ -2059,7 +2059,8 @@ grammarly-extension {
/* Focus */
#bulk_tag_popup,
#dialogue_popup {
#dialogue_popup,
.dialogue_popup {
width: 500px;
max-width: 90vw;
max-width: 90svw;
@ -2112,7 +2113,8 @@ grammarly-extension {
}
#bulk_tag_popup_holder,
#dialogue_popup_holder {
#dialogue_popup_holder,
.dialogue_popup_holder {
display: flex;
flex-direction: column;
height: 100%;
@ -2120,13 +2122,15 @@ grammarly-extension {
padding: 0 10px;
}
#dialogue_popup_text {
#dialogue_popup_text,
.dialogue_popup_text {
flex-grow: 1;
overflow-y: auto;
height: 100%;
}
#dialogue_popup_controls {
#dialogue_popup_controls,
.dialogue_popup_controls {
display: flex;
align-self: center;
gap: 20px;
@ -2134,14 +2138,16 @@ grammarly-extension {
#bulk_tag_popup_reset,
#bulk_tag_popup_remove_mutual,
#dialogue_popup_ok {
#dialogue_popup_ok,
.dialogue_popup_ok {
background-color: var(--crimson70a);
cursor: pointer;
}
#bulk_tag_popup_reset:hover,
#bulk_tag_popup_remove_mutual:hover,
#dialogue_popup_ok:hover {
#dialogue_popup_ok:hover,
.dialogue_popup_ok:hover {
background-color: var(--crimson-hover);
}
@ -2149,13 +2155,15 @@ grammarly-extension {
max-height: 70vh;
}
#dialogue_popup_input {
#dialogue_popup_input,
.dialogue_popup_input {
margin: 10px 0;
width: 100%;
}
#bulk_tag_popup_cancel,
#dialogue_popup_cancel {
#dialogue_popup_cancel,
.dialogue_popup_cancel {
cursor: pointer;
}
@ -2220,7 +2228,7 @@ grammarly-extension {
margin-right: 25px;
}
#shadow_popup {
#shadow_popup, .shadow_popup {
backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2));
-webkit-backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2));
background-color: var(--black30a);
@ -2232,6 +2240,9 @@ grammarly-extension {
height: 100svh;
z-index: 9999;
top: 0;
&.shadow_popup {
z-index: 9998;
}
}
#bgtest {