add progress and pause/abort to QR editor

This commit is contained in:
LenAnderson
2024-04-30 12:16:17 -04:00
parent 4691f73387
commit 3ca4a1d00c
5 changed files with 279 additions and 14 deletions

View File

@@ -94,14 +94,27 @@
<h3>Testing</h3> <h3>Testing</h3>
<div id="qr--modal-execute" class="menu_button" title="Execute the quick reply now"> <div id="qr--modal-executeButtons">
<i class="fa-solid fa-play"></i> <div id="qr--modal-execute" class="qr--modal-executeButton menu_button" title="Execute the quick reply now">
Execute <i class="fa-solid fa-play"></i>
Execute
</div>
<div id="qr--modal-pause" class="qr--modal-executeButton menu_button" title="Pause / continue execution">
<span class="qr--modal-executeComboIcon">
<i class="fa-solid fa-play"></i>
<i class="fa-solid fa-pause"></i>
</span>
</div>
<div id="qr--modal-stop" class="qr--modal-executeButton menu_button" title="Abort execution">
<i class="fa-solid fa-stop"></i>
</div>
</div> </div>
<div id="qr--modal-executeProgress"></div>
<label class="checkbox_label"> <label class="checkbox_label">
<input type="checkbox" id="qr--modal-executeHide"> <input type="checkbox" id="qr--modal-executeHide">
<span> Hide editor while executing</span> <span> Hide editor while executing</span>
</label> </label>
<div id="qr--modal-executeErrors"></div> <div id="qr--modal-executeErrors"></div>
<div id="qr--modal-executeResult"></div>
</div> </div>
</div> </div>

View File

@@ -1,5 +1,6 @@
import { POPUP_TYPE, Popup } from '../../../popup.js'; import { POPUP_TYPE, Popup } from '../../../popup.js';
import { setSlashCommandAutoComplete } from '../../../slash-commands.js'; import { setSlashCommandAutoComplete } from '../../../slash-commands.js';
import { SlashCommandAbortController } from '../../../slash-commands/SlashCommandAbortController.js';
import { SlashCommandParserError } from '../../../slash-commands/SlashCommandParserError.js'; import { SlashCommandParserError } from '../../../slash-commands/SlashCommandParserError.js';
import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js'; import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js';
import { getSortableDelay } from '../../../utils.js'; import { getSortableDelay } from '../../../utils.js';
@@ -50,9 +51,14 @@ export class QuickReply {
/**@type {Popup}*/ editorPopup; /**@type {Popup}*/ editorPopup;
/**@type {HTMLElement}*/ editorExecuteBtn; /**@type {HTMLElement}*/ editorExecuteBtn;
/**@type {HTMLElement}*/ editorExecuteBtnPause;
/**@type {HTMLElement}*/ editorExecuteBtnStop;
/**@type {HTMLElement}*/ editorExecuteProgress;
/**@type {HTMLElement}*/ editorExecuteErrors; /**@type {HTMLElement}*/ editorExecuteErrors;
/**@type {HTMLElement}*/ editorExecuteResult;
/**@type {HTMLInputElement}*/ editorExecuteHide; /**@type {HTMLInputElement}*/ editorExecuteHide;
/**@type {Promise}*/ editorExecutePromise; /**@type {Promise}*/ editorExecutePromise;
/**@type {SlashCommandAbortController}*/ abortController;
get hasContext() { get hasContext() {
@@ -426,9 +432,15 @@ export class QuickReply {
this.updateContext(); this.updateContext();
}); });
/**@type {HTMLElement}*/
const executeProgress = dom.querySelector('#qr--modal-executeProgress');
this.editorExecuteProgress = executeProgress;
/**@type {HTMLElement}*/ /**@type {HTMLElement}*/
const executeErrors = dom.querySelector('#qr--modal-executeErrors'); const executeErrors = dom.querySelector('#qr--modal-executeErrors');
this.editorExecuteErrors = executeErrors; this.editorExecuteErrors = executeErrors;
/**@type {HTMLElement}*/
const executeResult = dom.querySelector('#qr--modal-executeResult');
this.editorExecuteResult = executeResult;
/**@type {HTMLInputElement}*/ /**@type {HTMLInputElement}*/
const executeHide = dom.querySelector('#qr--modal-executeHide'); const executeHide = dom.querySelector('#qr--modal-executeHide');
this.editorExecuteHide = executeHide; this.editorExecuteHide = executeHide;
@@ -438,6 +450,26 @@ export class QuickReply {
executeBtn.addEventListener('click', async()=>{ executeBtn.addEventListener('click', async()=>{
await this.executeFromEditor(); await this.executeFromEditor();
}); });
/**@type {HTMLElement}*/
const executeBtnPause = dom.querySelector('#qr--modal-pause');
this.editorExecuteBtnPause = executeBtnPause;
executeBtnPause.addEventListener('click', async()=>{
if (this.abortController) {
if (this.abortController.signal.paused) {
this.abortController.continue('Continue button clicked');
this.editorExecuteProgress.classList.remove('qr--paused');
} else {
this.abortController.pause('Pause button clicked');
this.editorExecuteProgress.classList.add('qr--paused');
}
}
});
/**@type {HTMLElement}*/
const executeBtnStop = dom.querySelector('#qr--modal-stop');
this.editorExecuteBtnStop = executeBtnStop;
executeBtnStop.addEventListener('click', async()=>{
this.abortController?.abort('Stop button clicked');
});
await popupResult; await popupResult;
} else { } else {
@@ -448,17 +480,33 @@ export class QuickReply {
async executeFromEditor() { async executeFromEditor() {
if (this.editorExecutePromise) return; if (this.editorExecutePromise) return;
this.editorExecuteBtn.classList.add('qr--busy'); this.editorExecuteBtn.classList.add('qr--busy');
this.editorExecuteProgress.style.setProperty('--prog', '0');
this.editorExecuteErrors.classList.remove('qr--hasErrors');
this.editorExecuteResult.classList.remove('qr--hasResult');
this.editorExecuteProgress.classList.remove('qr--error');
this.editorExecuteProgress.classList.remove('qr--success');
this.editorExecuteProgress.classList.remove('qr--paused');
this.editorExecuteProgress.classList.remove('qr--aborted');
this.editorExecuteErrors.innerHTML = ''; this.editorExecuteErrors.innerHTML = '';
this.editorExecuteResult.innerHTML = '';
if (this.editorExecuteHide.checked) { if (this.editorExecuteHide.checked) {
this.editorPopup.dom.classList.add('qr--hide'); this.editorPopup.dom.classList.add('qr--hide');
} }
try { try {
this.editorExecutePromise = this.execute({}, true); this.editorExecutePromise = this.execute({}, true);
await this.editorExecutePromise; const result = await this.editorExecutePromise;
this.editorExecuteErrors.classList.remove('qr--hasErrors'); if (this.abortController?.signal?.aborted) {
this.editorExecuteErrors.innerHTML = ''; this.editorExecuteProgress.classList.add('qr--aborted');
} else {
this.editorExecuteResult.textContent = result?.toString();
this.editorExecuteResult.classList.add('qr--hasResult');
this.editorExecuteProgress.classList.add('qr--success');
}
this.editorExecuteProgress.classList.remove('qr--paused');
} catch (ex) { } catch (ex) {
this.editorExecuteErrors.classList.add('qr--hasErrors'); this.editorExecuteErrors.classList.add('qr--hasErrors');
this.editorExecuteProgress.classList.add('qr--error');
this.editorExecuteProgress.classList.remove('qr--paused');
if (ex instanceof SlashCommandParserError) { if (ex instanceof SlashCommandParserError) {
this.editorExecuteErrors.innerHTML = ` this.editorExecuteErrors.innerHTML = `
<div>${ex.message}</div> <div>${ex.message}</div>
@@ -476,6 +524,10 @@ export class QuickReply {
this.editorPopup.dom.classList.remove('qr--hide'); this.editorPopup.dom.classList.remove('qr--hide');
} }
updateEditorProgress(done, total) {
this.editorExecuteProgress.style.setProperty('--prog', `${done / total * 100}`);
}
@@ -557,6 +609,9 @@ export class QuickReply {
for (const key of Object.keys(args)) { for (const key of Object.keys(args)) {
scope.setMacro(`arg::${key}`, args[key]); scope.setMacro(`arg::${key}`, args[key]);
} }
if (isEditor) {
this.abortController = new SlashCommandAbortController();
}
return await this.onExecute(this, { return await this.onExecute(this, {
message:this.message, message:this.message,
isAutoExecute: args.isAutoExecute ?? false, isAutoExecute: args.isAutoExecute ?? false,

View File

@@ -144,6 +144,8 @@ export class QuickReplySet {
result = await executeSlashCommandsWithOptions(input, { result = await executeSlashCommandsWithOptions(input, {
handleParserErrors: false, handleParserErrors: false,
scope: options.scope, scope: options.scope,
abortController: qr.abortController,
onProgress: (done, total) => qr.updateEditorProgress(done, total),
}); });
} else { } else {
result = await executeSlashCommandsOnChatInput(input, { result = await executeSlashCommandsOnChatInput(input, {

View File

@@ -286,14 +286,87 @@
.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; 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-executeButtons {
display: flex;
gap: 1em;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons .qr--modal-executeButton {
border-width: 2px;
border-style: solid;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
gap: 0.5em; gap: 0.5em;
padding: 0.5em 0.75em;
} }
.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-executeButtons .qr--modal-executeButton .qr--modal-executeComboIcon {
opacity: 0.5; display: flex;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons #qr--modal-execute {
transition: 200ms;
filter: grayscale(0);
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons #qr--modal-execute.qr--busy {
cursor: wait; cursor: wait;
opacity: 0.5;
filter: grayscale(1);
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons #qr--modal-execute {
border-color: #51a351;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons #qr--modal-pause,
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons #qr--modal-stop {
cursor: default;
opacity: 0.5;
filter: grayscale(1);
pointer-events: none;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons .qr--busy ~ #qr--modal-pause,
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons .qr--busy ~ #qr--modal-stop {
cursor: pointer;
opacity: 1;
filter: grayscale(0);
pointer-events: all;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons #qr--modal-pause {
border-color: #92befc;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons #qr--modal-stop {
border-color: #d78872;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeProgress {
--prog: 0;
--progColor: #92befc;
--progFlashColor: #d78872;
--progSuccessColor: #51a351;
--progErrorColor: #bd362f;
--progAbortedColor: #d78872;
height: 0.5em;
background-color: var(--black50a);
position: relative;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeProgress:after {
content: '';
background-color: var(--progColor);
position: absolute;
inset: 0;
right: calc(100% - var(--prog) * 1%);
transition: 200ms;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeProgress.qr--paused:after {
animation-name: qr--progressPulse;
animation-duration: 1500ms;
animation-timing-function: ease-in-out;
animation-delay: 0s;
animation-iteration-count: infinite;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeProgress.qr--aborted:after {
background-color: var(--progAbortedColor);
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeProgress.qr--success:after {
background-color: var(--progSuccessColor);
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeProgress.qr--error:after {
background-color: var(--progErrorColor);
} }
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeErrors { .dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeErrors {
display: none; display: none;
@@ -309,6 +382,32 @@
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeErrors.qr--hasErrors { .dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeErrors.qr--hasErrors {
display: block; display: block;
} }
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeResult {
display: none;
text-align: left;
font-size: smaller;
background-color: #51a351;
color: white;
padding: 0.5em;
overflow: auto;
min-width: 100%;
width: 0;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeResult.qr--hasResult {
display: block;
}
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeResult:before {
content: 'Result: ';
}
@keyframes qr--progressPulse {
0%,
100% {
background-color: var(--progColor);
}
50% {
background-color: var(--progFlashColor);
}
}
.shadow_popup.qr--hide { .shadow_popup.qr--hide {
opacity: 0 !important; opacity: 0 !important;
} }

View File

@@ -313,13 +313,86 @@
} }
} }
#qr--modal-execute { #qr--modal-executeButtons {
display: flex; display: flex;
flex-direction: row; gap: 1em;
gap: 0.5em; .qr--modal-executeButton {
&.qr--busy { border-width: 2px;
border-style: solid;
display: flex;
flex-direction: row;
gap: 0.5em;
padding: 0.5em 0.75em;
.qr--modal-executeComboIcon {
display: flex;
}
}
#qr--modal-execute {
transition: 200ms;
filter: grayscale(0);
&.qr--busy {
cursor: wait;
opacity: 0.5;
filter: grayscale(1);
}
}
#qr--modal-execute {
border-color: rgb(81, 163, 81);
}
#qr--modal-pause, #qr--modal-stop {
cursor: default;
opacity: 0.5; opacity: 0.5;
cursor: wait; filter: grayscale(1);
pointer-events: none;
}
.qr--busy {
~ #qr--modal-pause, ~ #qr--modal-stop {
cursor: pointer;
opacity: 1;
filter: grayscale(0);
pointer-events: all;
}
}
#qr--modal-pause {
border-color: rgb(146, 190, 252);
}
#qr--modal-stop {
border-color: rgb(215, 136, 114);
}
}
#qr--modal-executeProgress {
--prog: 0;
--progColor: rgb(146, 190, 252);
--progFlashColor: rgb(215, 136, 114);
--progSuccessColor: rgb(81, 163, 81);
--progErrorColor: rgb(189, 54, 47);
--progAbortedColor: rgb(215, 136, 114);
height: 0.5em;
background-color: var(--black50a);
position: relative;
&:after {
content: '';
background-color: var(--progColor);
position: absolute;
inset: 0;
right: calc(100% - var(--prog) * 1%);
transition: 200ms;
}
&.qr--paused:after {
animation-name: qr--progressPulse;
animation-duration: 1500ms;
animation-timing-function: ease-in-out;
animation-delay: 0s;
animation-iteration-count: infinite;
}
&.qr--aborted:after {
background-color: var(--progAbortedColor);
}
&.qr--success:after {
background-color: var(--progSuccessColor);
}
&.qr--error:after {
background-color: var(--progErrorColor);
} }
} }
#qr--modal-executeErrors { #qr--modal-executeErrors {
@@ -336,9 +409,32 @@
min-width: 100%; min-width: 100%;
width: 0; width: 0;
} }
#qr--modal-executeResult {
display: none;
&.qr--hasResult {
display: block;
}
&:before { content: 'Result: '; }
text-align: left;
font-size: smaller;
background-color: rgb(81, 163, 81);
color: white;
padding: 0.5em;
overflow: auto;
min-width: 100%;
width: 0;
}
} }
} }
} }
@keyframes qr--progressPulse {
0%, 100% {
background-color: var(--progColor);
}
50% {
background-color: var(--progFlashColor);
}
}
.shadow_popup.qr--hide { .shadow_popup.qr--hide {
opacity: 0 !important; opacity: 0 !important;