mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-02-25 16:37:50 +01:00
Merge pull request #2449 from SillyTavern/loader-improvements
Loader overlay improvements & Popup.complete async
This commit is contained in:
commit
847f471ed2
@ -1,4 +1,4 @@
|
|||||||
#loader, #preloader {
|
#preloader {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -27,8 +27,17 @@ dialog {
|
|||||||
backface-visibility: hidden;
|
backface-visibility: hidden;
|
||||||
transform: translateZ(0);
|
transform: translateZ(0);
|
||||||
-webkit-font-smoothing: subpixel-antialiased;
|
-webkit-font-smoothing: subpixel-antialiased;
|
||||||
|
|
||||||
|
/* Variables setup */
|
||||||
|
--popup-animation-speed: var(--animation-duration-slow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Popup styles applied to the main popup */
|
||||||
|
.popup--animation-fast { --popup-animation-speed: var(--animation-duration); }
|
||||||
|
.popup--animation-slow { --popup-animation-speed: var(--animation-duration-slow); }
|
||||||
|
.popup--animation-none { --popup-animation-speed: 0ms; }
|
||||||
|
|
||||||
|
/* Styling of main popup elements */
|
||||||
.popup .popup-body {
|
.popup .popup-body {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -60,11 +69,11 @@ dialog {
|
|||||||
|
|
||||||
/* Opening animation */
|
/* Opening animation */
|
||||||
.popup[opening] {
|
.popup[opening] {
|
||||||
animation: pop-in var(--animation-duration-slow) ease-in-out;
|
animation: pop-in var(--popup-animation-speed) ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popup[opening]::backdrop {
|
.popup[opening]::backdrop {
|
||||||
animation: fade-in var(--animation-duration-slow) ease-in-out;
|
animation: fade-in var(--popup-animation-speed) ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Open state of the dialog */
|
/* Open state of the dialog */
|
||||||
@ -85,11 +94,11 @@ body.no-blur .popup[open]::backdrop {
|
|||||||
|
|
||||||
/* Closing animation */
|
/* Closing animation */
|
||||||
.popup[closing] {
|
.popup[closing] {
|
||||||
animation: pop-out var(--animation-duration-slow) ease-in-out;
|
animation: pop-out var(--popup-animation-speed) ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popup[closing]::backdrop {
|
.popup[closing]::backdrop {
|
||||||
animation: fade-out var(--animation-duration-slow) ease-in-out;
|
animation: fade-out var(--popup-animation-speed) ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popup #toast-container {
|
.popup #toast-container {
|
||||||
|
@ -43,7 +43,6 @@ import {
|
|||||||
saveGroupChat,
|
saveGroupChat,
|
||||||
getGroups,
|
getGroups,
|
||||||
generateGroupWrapper,
|
generateGroupWrapper,
|
||||||
deleteGroup,
|
|
||||||
is_group_generating,
|
is_group_generating,
|
||||||
resetSelectedGroup,
|
resetSelectedGroup,
|
||||||
select_group_chats,
|
select_group_chats,
|
||||||
@ -228,7 +227,7 @@ import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, de
|
|||||||
import { initPresetManager } from './scripts/preset-manager.js';
|
import { initPresetManager } from './scripts/preset-manager.js';
|
||||||
import { MacrosParser, evaluateMacros } from './scripts/macros.js';
|
import { MacrosParser, evaluateMacros } from './scripts/macros.js';
|
||||||
import { currentUser, setUserControls } from './scripts/user.js';
|
import { currentUser, setUserControls } from './scripts/user.js';
|
||||||
import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup, fixToastrForDialogs } from './scripts/popup.js';
|
import { POPUP_TYPE, Popup, callGenericPopup, fixToastrForDialogs } from './scripts/popup.js';
|
||||||
import { renderTemplate, renderTemplateAsync } from './scripts/templates.js';
|
import { renderTemplate, renderTemplateAsync } from './scripts/templates.js';
|
||||||
import { ScraperManager } from './scripts/scrapers.js';
|
import { ScraperManager } from './scripts/scrapers.js';
|
||||||
import { SlashCommandParser } from './scripts/slash-commands/SlashCommandParser.js';
|
import { SlashCommandParser } from './scripts/slash-commands/SlashCommandParser.js';
|
||||||
@ -266,8 +265,6 @@ await new Promise((resolve) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
showLoader();
|
showLoader();
|
||||||
// Yoink preloader entirely; it only exists to cover up unstyled content while loading JS
|
|
||||||
document.getElementById('preloader').remove();
|
|
||||||
|
|
||||||
// Configure toast library:
|
// Configure toast library:
|
||||||
toastr.options.escapeHtml = true; // Prevent raw HTML inserts
|
toastr.options.escapeHtml = true; // Prevent raw HTML inserts
|
||||||
@ -7829,6 +7826,8 @@ window['SillyTavern'].getContext = function () {
|
|||||||
registerDataBankScraper: ScraperManager.registerDataBankScraper,
|
registerDataBankScraper: ScraperManager.registerDataBankScraper,
|
||||||
callPopup: callPopup,
|
callPopup: callPopup,
|
||||||
callGenericPopup: callGenericPopup,
|
callGenericPopup: callGenericPopup,
|
||||||
|
showLoader: showLoader,
|
||||||
|
hideLoader: hideLoader,
|
||||||
mainApi: main_api,
|
mainApi: main_api,
|
||||||
extensionSettings: extension_settings,
|
extensionSettings: extension_settings,
|
||||||
ModuleWorkerWrapper: ModuleWorkerWrapper,
|
ModuleWorkerWrapper: ModuleWorkerWrapper,
|
||||||
|
@ -641,9 +641,16 @@ async function showExtensionsDetails() {
|
|||||||
action: async () => {
|
action: async () => {
|
||||||
requiresReload = true;
|
requiresReload = true;
|
||||||
await autoUpdateExtensions(true);
|
await autoUpdateExtensions(true);
|
||||||
popup.complete(POPUP_RESULT.AFFIRMATIVE);
|
await popup.complete(POPUP_RESULT.AFFIRMATIVE);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// If we are updating an extension, the "old" popup is still active. We should close that.
|
||||||
|
const oldPopup = Popup.util.popups.find(popup => popup.content.querySelector('.extensions_info'));
|
||||||
|
if (oldPopup) {
|
||||||
|
await oldPopup.complete(POPUP_RESULT.CANCELLED);
|
||||||
|
}
|
||||||
|
|
||||||
const popup = new Popup(`<div class="extensions_info">${html}</div>`, POPUP_TYPE.TEXT, '', { okButton: 'Close', wide: true, large: true, customButtons: [updateAllButton], allowVerticalScrolling: true });
|
const popup = new Popup(`<div class="extensions_info">${html}</div>`, POPUP_TYPE.TEXT, '', { okButton: 'Close', wide: true, large: true, customButtons: [updateAllButton], allowVerticalScrolling: true });
|
||||||
popupPromise = popup.show();
|
popupPromise = popup.show();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -1,19 +1,44 @@
|
|||||||
|
import { POPUP_RESULT, POPUP_TYPE, Popup } from './popup.js';
|
||||||
|
|
||||||
const ELEMENT_ID = 'loader';
|
const ELEMENT_ID = 'loader';
|
||||||
|
|
||||||
|
/** @type {Popup} */
|
||||||
|
let loaderPopup;
|
||||||
|
|
||||||
|
let preloaderYoinked = false;
|
||||||
|
|
||||||
export function showLoader() {
|
export function showLoader() {
|
||||||
const container = $('<div></div>').attr('id', ELEMENT_ID);
|
// Two loaders don't make sense. Don't await, we can overlay the old loader while it closes
|
||||||
const loader = $('<div></div>').attr('id', 'load-spinner').addClass('fa-solid fa-gear fa-spin fa-3x');
|
if (loaderPopup) loaderPopup.complete(POPUP_RESULT.CANCELLED);
|
||||||
container.append(loader);
|
|
||||||
$('body').append(container);
|
loaderPopup = new Popup(`
|
||||||
|
<div id="loader">
|
||||||
|
<div id="load-spinner" class="fa-solid fa-gear fa-spin fa-3x"></div>
|
||||||
|
</div>`, POPUP_TYPE.DISPLAY, null, { transparent: true, animation: 'none' });
|
||||||
|
|
||||||
|
// No close button, loaders are not closable
|
||||||
|
loaderPopup.closeButton.style.display = 'none';
|
||||||
|
|
||||||
|
loaderPopup.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function hideLoader() {
|
export async function hideLoader() {
|
||||||
//Sets up a 2-step animation. Spinner blurs/fades out, and then the loader shadow does the same.
|
if (!loaderPopup) {
|
||||||
|
console.warn('There is no loader showing to hide');
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
// Spinner blurs/fades out
|
||||||
$('#load-spinner').on('transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd', function () {
|
$('#load-spinner').on('transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd', function () {
|
||||||
$(`#${ELEMENT_ID}`)
|
|
||||||
//only fade out the spinner and replace with login screen
|
|
||||||
.animate({ opacity: 0 }, 300, function () {
|
|
||||||
$(`#${ELEMENT_ID}`).remove();
|
$(`#${ELEMENT_ID}`).remove();
|
||||||
|
// Yoink preloader entirely; it only exists to cover up unstyled content while loading JS
|
||||||
|
// If it's present, we remove it once and then it's gone.
|
||||||
|
yoinkPreloader();
|
||||||
|
|
||||||
|
loaderPopup.complete(POPUP_RESULT.AFFIRMATIVE).then(() => {
|
||||||
|
loaderPopup = null;
|
||||||
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -22,4 +47,11 @@ export async function hideLoader() {
|
|||||||
'filter': 'blur(15px)',
|
'filter': 'blur(15px)',
|
||||||
'opacity': '0',
|
'opacity': '0',
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function yoinkPreloader() {
|
||||||
|
if (preloaderYoinked) return;
|
||||||
|
document.getElementById('preloader').remove();
|
||||||
|
preloaderYoinked = true;
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ export const POPUP_RESULT = {
|
|||||||
* @property {boolean?} [transparent=false] - Whether to display the popup in transparent mode (no background, border, shadow or anything, only its content)
|
* @property {boolean?} [transparent=false] - Whether to display the popup in transparent mode (no background, border, shadow or anything, only its content)
|
||||||
* @property {boolean?} [allowHorizontalScrolling=false] - Whether to allow horizontal scrolling in the popup
|
* @property {boolean?} [allowHorizontalScrolling=false] - Whether to allow horizontal scrolling in the popup
|
||||||
* @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 {'slow'|'fast'|'none'?} [animation='slow'] - Animation speed for the popup (opening, closing, ...)
|
||||||
* @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 {CustomPopupInput[]?} [customInputs=null] - Custom inputs to add to the popup. The display below the content and the input box, one by one.
|
||||||
@ -98,7 +99,7 @@ const showPopupHelper = {
|
|||||||
const result = await popup.show();
|
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}`);
|
if (typeof result === 'string' || typeof result === 'boolean') throw new Error(`Invalid popup result. CONFIRM popups only support numbers, or null. Result: ${result}`);
|
||||||
return result;
|
return result;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Popup {
|
export class Popup {
|
||||||
@ -142,7 +143,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, customInputs = 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, animation = 'slow', 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
|
||||||
@ -175,6 +176,7 @@ export class Popup {
|
|||||||
if (transparent) this.dlg.classList.add('transparent_dialogue_popup');
|
if (transparent) this.dlg.classList.add('transparent_dialogue_popup');
|
||||||
if (allowHorizontalScrolling) this.dlg.classList.add('horizontal_scrolling_dialogue_popup');
|
if (allowHorizontalScrolling) this.dlg.classList.add('horizontal_scrolling_dialogue_popup');
|
||||||
if (allowVerticalScrolling) this.dlg.classList.add('vertical_scrolling_dialogue_popup');
|
if (allowVerticalScrolling) this.dlg.classList.add('vertical_scrolling_dialogue_popup');
|
||||||
|
if (animation) this.dlg.classList.add('popup--animation-' + animation);
|
||||||
|
|
||||||
// If custom button captions are provided, we set them beforehand
|
// If custom button captions are provided, we set them beforehand
|
||||||
this.okButton.textContent = typeof okButton === 'string' ? okButton : 'OK';
|
this.okButton.textContent = typeof okButton === 'string' ? okButton : 'OK';
|
||||||
@ -210,7 +212,7 @@ export class Popup {
|
|||||||
this.customInputs = customInputs;
|
this.customInputs = customInputs;
|
||||||
this.customInputs?.forEach(input => {
|
this.customInputs?.forEach(input => {
|
||||||
if (!input.id || !(typeof input.id === 'string')) {
|
if (!input.id || !(typeof input.id === 'string')) {
|
||||||
console.warn('Given custom input does not have a valid id set')
|
console.warn('Given custom input does not have a valid id set');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,20 +320,20 @@ export class Popup {
|
|||||||
if (String(undefined) === String(resultControl.dataset.result)) return;
|
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);
|
if (isNaN(result)) throw new Error('Invalid result control. Result must be a number. ' + resultControl.dataset.result);
|
||||||
const type = resultControl.dataset.resultEvent || 'click';
|
const type = resultControl.dataset.resultEvent || 'click';
|
||||||
resultControl.addEventListener(type, () => this.complete(result));
|
resultControl.addEventListener(type, async () => await this.complete(result));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Bind dialog listeners manually, so we can be sure context is preserved
|
// Bind dialog listeners manually, so we can be sure context is preserved
|
||||||
const cancelListener = (evt) => {
|
const cancelListener = async (evt) => {
|
||||||
this.complete(POPUP_RESULT.CANCELLED);
|
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
|
await this.complete(POPUP_RESULT.CANCELLED);
|
||||||
window.removeEventListener('cancel', cancelListenerBound);
|
window.removeEventListener('cancel', cancelListenerBound);
|
||||||
};
|
};
|
||||||
const cancelListenerBound = cancelListener.bind(this);
|
const cancelListenerBound = cancelListener.bind(this);
|
||||||
this.dlg.addEventListener('cancel', cancelListenerBound);
|
this.dlg.addEventListener('cancel', cancelListenerBound);
|
||||||
|
|
||||||
const keyListener = (evt) => {
|
const keyListener = async (evt) => {
|
||||||
switch (evt.key) {
|
switch (evt.key) {
|
||||||
case 'Enter': {
|
case 'Enter': {
|
||||||
// CTRL+Enter counts as a closing action, but all other modifiers (ALT, SHIFT) should not trigger this
|
// CTRL+Enter counts as a closing action, but all other modifiers (ALT, SHIFT) should not trigger this
|
||||||
@ -347,10 +349,10 @@ export class Popup {
|
|||||||
if (!resultControl)
|
if (!resultControl)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const result = Number(document.activeElement.getAttribute('data-result') ?? this.defaultResult);
|
|
||||||
this.complete(result);
|
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
|
const result = Number(document.activeElement.getAttribute('data-result') ?? this.defaultResult);
|
||||||
|
await this.complete(result);
|
||||||
window.removeEventListener('keydown', keyListenerBound);
|
window.removeEventListener('keydown', keyListenerBound);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -430,8 +432,10 @@ export class Popup {
|
|||||||
* - All other will return the result value as provided as `POPUP_RESULT` or a custom number value
|
* - All other will return the result value as provided as `POPUP_RESULT` or a custom number value
|
||||||
*
|
*
|
||||||
* @param {POPUP_RESULT|number} result - The result of the popup (either an existing `POPUP_RESULT` or a custom result value)
|
* @param {POPUP_RESULT|number} result - The result of the popup (either an existing `POPUP_RESULT` or a custom result value)
|
||||||
|
*
|
||||||
|
* @returns {Promise<string|number|boolean?>} A promise that resolves with the value of the popup when it is completed.
|
||||||
*/
|
*/
|
||||||
complete(result) {
|
async complete(result) {
|
||||||
// In all cases besides INPUT the popup value should be the result
|
// In all cases besides INPUT the popup value should be the result
|
||||||
/** @type {POPUP_RESULT|number|boolean|string?} */
|
/** @type {POPUP_RESULT|number|boolean|string?} */
|
||||||
let value = result;
|
let value = result;
|
||||||
@ -468,6 +472,8 @@ export class Popup {
|
|||||||
|
|
||||||
Popup.util.lastResult = { value, result, inputResults: this.inputResults };
|
Popup.util.lastResult = { value, result, inputResults: this.inputResults };
|
||||||
this.#hide();
|
this.#hide();
|
||||||
|
|
||||||
|
return this.#promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1653,8 +1653,8 @@ async function buttonsCallback(args, text) {
|
|||||||
const buttonElement = document.createElement('div');
|
const buttonElement = document.createElement('div');
|
||||||
buttonElement.classList.add('menu_button', 'result-control', 'wide100p');
|
buttonElement.classList.add('menu_button', 'result-control', 'wide100p');
|
||||||
buttonElement.dataset.result = String(result);
|
buttonElement.dataset.result = String(result);
|
||||||
buttonElement.addEventListener('click', () => {
|
buttonElement.addEventListener('click', async () => {
|
||||||
popup?.complete(result);
|
await popup.complete(result);
|
||||||
});
|
});
|
||||||
buttonElement.innerText = button;
|
buttonElement.innerText = button;
|
||||||
buttonContainer.appendChild(buttonElement);
|
buttonContainer.appendChild(buttonElement);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user