diff --git a/public/scripts/extensions/quick-reply/html/qrEditor.html b/public/scripts/extensions/quick-reply/html/qrEditor.html
index 8ed195520..034a5194c 100644
--- a/public/scripts/extensions/quick-reply/html/qrEditor.html
+++ b/public/scripts/extensions/quick-reply/html/qrEditor.html
@@ -94,14 +94,27 @@
Testing
-
diff --git a/public/scripts/extensions/quick-reply/src/QuickReply.js b/public/scripts/extensions/quick-reply/src/QuickReply.js
index d7a8bc251..c70aa374e 100644
--- a/public/scripts/extensions/quick-reply/src/QuickReply.js
+++ b/public/scripts/extensions/quick-reply/src/QuickReply.js
@@ -1,5 +1,6 @@
import { POPUP_TYPE, Popup } from '../../../popup.js';
import { setSlashCommandAutoComplete } from '../../../slash-commands.js';
+import { SlashCommandAbortController } from '../../../slash-commands/SlashCommandAbortController.js';
import { SlashCommandParserError } from '../../../slash-commands/SlashCommandParserError.js';
import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js';
import { getSortableDelay } from '../../../utils.js';
@@ -50,9 +51,14 @@ export class QuickReply {
/**@type {Popup}*/ editorPopup;
/**@type {HTMLElement}*/ editorExecuteBtn;
+ /**@type {HTMLElement}*/ editorExecuteBtnPause;
+ /**@type {HTMLElement}*/ editorExecuteBtnStop;
+ /**@type {HTMLElement}*/ editorExecuteProgress;
/**@type {HTMLElement}*/ editorExecuteErrors;
+ /**@type {HTMLElement}*/ editorExecuteResult;
/**@type {HTMLInputElement}*/ editorExecuteHide;
/**@type {Promise}*/ editorExecutePromise;
+ /**@type {SlashCommandAbortController}*/ abortController;
get hasContext() {
@@ -426,9 +432,15 @@ export class QuickReply {
this.updateContext();
});
+ /**@type {HTMLElement}*/
+ const executeProgress = dom.querySelector('#qr--modal-executeProgress');
+ this.editorExecuteProgress = executeProgress;
/**@type {HTMLElement}*/
const executeErrors = dom.querySelector('#qr--modal-executeErrors');
this.editorExecuteErrors = executeErrors;
+ /**@type {HTMLElement}*/
+ const executeResult = dom.querySelector('#qr--modal-executeResult');
+ this.editorExecuteResult = executeResult;
/**@type {HTMLInputElement}*/
const executeHide = dom.querySelector('#qr--modal-executeHide');
this.editorExecuteHide = executeHide;
@@ -438,6 +450,26 @@ export class QuickReply {
executeBtn.addEventListener('click', async()=>{
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;
} else {
@@ -448,17 +480,33 @@ export class QuickReply {
async executeFromEditor() {
if (this.editorExecutePromise) return;
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.editorExecuteResult.innerHTML = '';
if (this.editorExecuteHide.checked) {
this.editorPopup.dom.classList.add('qr--hide');
}
try {
this.editorExecutePromise = this.execute({}, true);
- await this.editorExecutePromise;
- this.editorExecuteErrors.classList.remove('qr--hasErrors');
- this.editorExecuteErrors.innerHTML = '';
+ const result = await this.editorExecutePromise;
+ if (this.abortController?.signal?.aborted) {
+ 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) {
this.editorExecuteErrors.classList.add('qr--hasErrors');
+ this.editorExecuteProgress.classList.add('qr--error');
+ this.editorExecuteProgress.classList.remove('qr--paused');
if (ex instanceof SlashCommandParserError) {
this.editorExecuteErrors.innerHTML = `
${ex.message}
@@ -476,6 +524,10 @@ export class QuickReply {
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)) {
scope.setMacro(`arg::${key}`, args[key]);
}
+ if (isEditor) {
+ this.abortController = new SlashCommandAbortController();
+ }
return await this.onExecute(this, {
message:this.message,
isAutoExecute: args.isAutoExecute ?? false,
diff --git a/public/scripts/extensions/quick-reply/src/QuickReplySet.js b/public/scripts/extensions/quick-reply/src/QuickReplySet.js
index 8b147fa60..106084081 100644
--- a/public/scripts/extensions/quick-reply/src/QuickReplySet.js
+++ b/public/scripts/extensions/quick-reply/src/QuickReplySet.js
@@ -144,6 +144,8 @@ export class QuickReplySet {
result = await executeSlashCommandsWithOptions(input, {
handleParserErrors: false,
scope: options.scope,
+ abortController: qr.abortController,
+ onProgress: (done, total) => qr.updateEditorProgress(done, total),
});
} else {
result = await executeSlashCommandsOnChatInput(input, {
diff --git a/public/scripts/extensions/quick-reply/style.css b/public/scripts/extensions/quick-reply/style.css
index dbb0e6d18..e911d403a 100644
--- a/public/scripts/extensions/quick-reply/style.css
+++ b/public/scripts/extensions/quick-reply/style.css
@@ -286,14 +286,87 @@
.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-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;
flex-direction: row;
gap: 0.5em;
+ padding: 0.5em 0.75em;
}
-.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-execute.qr--busy {
- opacity: 0.5;
+.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons .qr--modal-executeButton .qr--modal-executeComboIcon {
+ 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;
+ 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 {
display: none;
@@ -309,6 +382,32 @@
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeErrors.qr--hasErrors {
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 {
opacity: 0 !important;
}
diff --git a/public/scripts/extensions/quick-reply/style.less b/public/scripts/extensions/quick-reply/style.less
index 0774e9e09..0c9da0af9 100644
--- a/public/scripts/extensions/quick-reply/style.less
+++ b/public/scripts/extensions/quick-reply/style.less
@@ -313,13 +313,86 @@
}
}
- #qr--modal-execute {
+ #qr--modal-executeButtons {
display: flex;
- flex-direction: row;
- gap: 0.5em;
- &.qr--busy {
+ gap: 1em;
+ .qr--modal-executeButton {
+ 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;
- 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 {
@@ -336,9 +409,32 @@
min-width: 100%;
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 {
opacity: 0 !important;