Add spinner to indicate profile application
| @@ -49,6 +49,48 @@ const FANCY_NAMES = { | |||
| 49 | 'tokenizer': 'Tokenizer', | 49 | 'tokenizer': 'Tokenizer', |
| 50 | }; | 50 | }; |
| 51 | 51 | ||
| 52 | /** | ||
| 53 | * A wrapper for the connection manager spinner. | ||
| 54 | */ | ||
| 55 | class ConnectionManagerSpinner { | ||
| 56 | /** | ||
| 57 | * @type {AbortController[]} | ||
| 58 | */ | ||
| 59 | static abortControllers = []; | ||
| 60 | |||
| 61 | /** @type {HTMLElement} */ | ||
| 62 | spinnerElement; | ||
| 63 | |||
| 64 | /** @type {AbortController} */ | ||
| 65 | abortController = new AbortController(); | ||
| 66 | |||
| 67 | constructor() { | ||
| 68 | // @ts-ignore | ||
| 69 | this.spinnerElement = document.getElementById('connection_profile_spinner'); | ||
| 70 | this.abortController = new AbortController(); | ||
| 71 | } | ||
| 72 | |||
| 73 | start() { | ||
| 74 | ConnectionManagerSpinner.abortControllers.push(this.abortController); | ||
| 75 | this.spinnerElement.classList.remove('hidden'); | ||
| 76 | } | ||
| 77 | |||
| 78 | stop() { | ||
| 79 | this.spinnerElement.classList.add('hidden'); | ||
| 80 | } | ||
| 81 | |||
| 82 | isAborted() { | ||
| 83 | return this.abortController.signal.aborted; | ||
| 84 | } | ||
| 85 | |||
| 86 | static abort() { | ||
| 87 | for (const controller of ConnectionManagerSpinner.abortControllers) { | ||
| 88 | controller.abort(); | ||
| 89 | } | ||
| 90 | ConnectionManagerSpinner.abortControllers = []; | ||
| 91 | } | ||
| 92 | } | ||
| 93 | |||
| 52 | /** @type {() => SlashCommandEnumValue[]} */ | 94 | /** @type {() => SlashCommandEnumValue[]} */ |
| 53 | const profilesProvider = () => [ | 95 | const profilesProvider = () => [ |
| 54 | new SlashCommandEnumValue(NONE), | 96 | new SlashCommandEnumValue(NONE), |
| @@ -214,10 +256,19 @@ async function applyConnectionProfile(profile) { | |||
| 214 | return; | 256 | return; |
| 215 | } | 257 | } |
| 216 | 258 | ||
| 259 | // Abort any ongoing profile application | ||
| 260 | ConnectionManagerSpinner.abort(); | ||
| 261 | |||
| 217 | const mode = profile.mode; | 262 | const mode = profile.mode; |
| 218 | const commands = mode === 'cc' ? CC_COMMANDS : TC_COMMANDS; | 263 | const commands = mode === 'cc' ? CC_COMMANDS : TC_COMMANDS; |
| 264 | const spinner = new ConnectionManagerSpinner(); | ||
| 265 | spinner.start(); | ||
| 219 | 266 | ||
| 220 | for (const command of commands) { | 267 | for (const command of commands) { |
| 268 | if (spinner.isAborted()) { | ||
| 269 | throw new Error('Profile application aborted'); | ||
| 270 | } | ||
| 271 | |||
| 221 | const argument = profile[command]; | 272 | const argument = profile[command]; |
| 222 | if (!argument) { | 273 | if (!argument) { |
| 223 | continue; | 274 | continue; |
| @@ -226,9 +277,11 @@ async function applyConnectionProfile(profile) { | |||
| 226 | try { | 277 | try { |
| 227 | await executeSlashCommandsWithOptions(commandText, { handleParserErrors: false, handleExecutionErrors: false }); | 278 | await executeSlashCommandsWithOptions(commandText, { handleParserErrors: false, handleExecutionErrors: false }); |
| 228 | } catch (error) { | 279 | } catch (error) { |
| 229 | console.warn(`Failed to execute command: ${commandText}`, error); | 280 | console.error(`Failed to execute command: ${commandText}`, error); |
| 230 | } | 281 | } |
| 231 | } | 282 | } |
| 283 | |||
| 284 | spinner.stop(); | ||
| 232 | } | 285 | } |
| 233 | 286 | ||
| 234 | /** | 287 | /** |
| @@ -13,8 +13,9 @@ | |||
| 13 | <i id="delete_connection_profile" class="menu_button fa-solid fa-trash-can" title="Delete a connection profile" data-i18n="[title]Delete a connection profile"></i> | 13 | <i id="delete_connection_profile" class="menu_button fa-solid fa-trash-can" title="Delete a connection profile" data-i18n="[title]Delete a connection profile"></i> |
| 14 | </div> | 14 | </div> |
| 15 | <details id="connection_profile_details" class="marginBot10"> | 15 | <details id="connection_profile_details" class="marginBot10"> |
| 16 | <summary data-i18n="Profile Details"> | 16 | <summary> |
| 17 | Profile Details | 17 | <span data-i18n="Profile Details">Profile Details</span> |
| 18 | <i id="connection_profile_spinner" class="fa-solid fa-spinner fa-spin hidden"></i> | ||
| 18 | </summary> | 19 | </summary> |
| 19 | <div id="connection_profile_details_content" class="marginTop5"></div> | 20 | <div id="connection_profile_details_content" class="marginTop5"></div> |
| 20 | </details> | 21 | </details> |
| @@ -7,3 +7,7 @@ | |||
| 7 | #connection_profile_details ul { | 7 | #connection_profile_details ul { |
| 8 | margin: 5px 0; | 8 | margin: 5px 0; |
| 9 | } | 9 | } |
| 10 | |||
| 11 | #connection_profile_spinner { | ||
| 12 | margin-lefT: 5px; | ||
| 13 | } | ||