Add Slug Parameter to Action Loader for Programmatic Identification (#5490) * feat: add slug parameter to action-loader for programmatic identification Add optional `slug` parameter to ActionLoaderHandle for easier identification via code or CSS. Update all loader.show() calls across the codebase to include descriptive slugs ('app-init', 'chat-rename', 'chat-delete', 'bulk-delete', 'chat-load', 'image-generation', 'legacy-loader'). Add data attributes (data-slug, data-loader-id, data-blocking) to toast content div. Expose slug via getter and make id private with getter. * Apply suggestions from code review Fix slug jsdoc wording Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix: Add identifier to second loader in img gen --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>

4df18ccb0bb4ee2a0cf28c6060cb2fdad58f6add

Wolfsblvt <wolfsblvt@gmail.com>

Signed
7 files changed, +58 -7Ignore whitespace
public/script.js+3 -0
@@ -717,6 +717,7 @@ async function firstLoadInit() {
717 initLoaderOverlay.appendChild(splashMessage);717 initLoaderOverlay.appendChild(splashMessage);
718718
719 const initLoaderHandle = loader.show({719 const initLoaderHandle = loader.show({
720 slug: 'app-init',
720 toastMode: loader.ToastMode.NONE,721 toastMode: loader.ToastMode.NONE,
721 overlayContent: initLoaderOverlay,722 overlayContent: initLoaderOverlay,
722 });723 });
@@ -10592,6 +10593,7 @@ export async function renameGroupOrCharacterChat({ characterId, groupId, oldFile
10592 }10593 }
1059310594
10594 const loaderHandle = showLoader ? loader.show({10595 const loaderHandle = showLoader ? loader.show({
10596 slug: 'chat-rename',
10595 title: t`Rename Chat`,10597 title: t`Rename Chat`,
10596 message: t`Renaming chat…`,10598 message: t`Renaming chat…`,
10597 toastMode: loader.ToastMode.STATIC,10599 toastMode: loader.ToastMode.STATIC,
@@ -11196,6 +11198,7 @@ jQuery(async function () {
11196 $('#select_chat_cross').trigger('click');11198 $('#select_chat_cross').trigger('click');
1119711199
11198 const loaderHandle = loader.show({11200 const loaderHandle = loader.show({
11201 slug: 'chat-delete',
11199 title: t`Delete Chat`,11202 title: t`Delete Chat`,
11200 message: t`Deleting chat…`,11203 message: t`Deleting chat…`,
11201 toastMode: loader.ToastMode.STATIC,11204 toastMode: loader.ToastMode.STATIC,
public/scripts/BulkEditOverlay.js+1 -0
@@ -848,6 +848,7 @@ class BulkEditOverlay {
848 const deleteChats = checkbox.prop('checked') ?? false;848 const deleteChats = checkbox.prop('checked') ?? false;
849849
850 const loaderHandle = loader.show({850 const loaderHandle = loader.show({
851 slug: 'bulk-delete',
851 title: t`Bulk Delete`,852 title: t`Bulk Delete`,
852 message: t`Deleting ${characterIds.length} character(s)…`,853 message: t`Deleting ${characterIds.length} character(s)…`,
853 toastMode: loader.ToastMode.STATIC,854 toastMode: loader.ToastMode.STATIC,
public/scripts/action-loader-slashcommands.js+19 -5
@@ -1,4 +1,4 @@
1import { ActionLoaderToastMode, getActiveLoaderHandles, getLoaderHandleById, hideActionLoader, showActionLoader } from './action-loader.js';1import { ActionLoaderToastMode, getActiveLoaderHandles, getLoaderHandleById, loader } from './action-loader.js';
2import { t } from './i18n.js';2import { t } from './i18n.js';
3import { SlashCommand } from './slash-commands/SlashCommand.js';3import { SlashCommand } from './slash-commands/SlashCommand.js';
4import { SlashCommandNamedArgument, ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';4import { SlashCommandNamedArgument, ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';
@@ -116,6 +116,12 @@ export function registerActionLoaderSlashCommands() {
116 typeList: [ARGUMENT_TYPE.STRING],116 typeList: [ARGUMENT_TYPE.STRING],
117 }),117 }),
118 SlashCommandNamedArgument.fromProps({118 SlashCommandNamedArgument.fromProps({
119 name: 'slug',
120 description: 'Unique slug for the loader (to identify it easily via code or CSS)',
121 typeList: [ARGUMENT_TYPE.STRING],
122 defaultValue: 'slash-wrap',
123 }),
124 SlashCommandNamedArgument.fromProps({
119 name: 'stopTooltip',125 name: 'stopTooltip',
120 description: 'Tooltip text for the stop button (only used when toast=stoppable)',126 description: 'Tooltip text for the stop button (only used when toast=stoppable)',
121 typeList: [ARGUMENT_TYPE.STRING],127 typeList: [ARGUMENT_TYPE.STRING],
@@ -148,7 +154,8 @@ export function registerActionLoaderSlashCommands() {
148 const title = args.title ? String(args.title) : '';154 const title = args.title ? String(args.title) : '';
149 const stopTooltip = String(args.stopTooltip ?? t`Stop`);155 const stopTooltip = String(args.stopTooltip ?? t`Stop`);
150156
151 const loader = showActionLoader({157 const actionLoader = loader.show({
158 slug: typeof args.slug === 'string' ? String(args.slug) : 'slash-wrap',
152 blocking,159 blocking,
153 toastMode,160 toastMode,
154 message,161 message,
@@ -162,7 +169,7 @@ export function registerActionLoaderSlashCommands() {
162 const result = await closureCopy.execute();169 const result = await closureCopy.execute();
163 return result.pipe;170 return result.pipe;
164 } finally {171 } finally {
165 await loader.hide();172 await actionLoader.hide();
166 }173 }
167 },174 },
168 }));175 }));
@@ -232,6 +239,12 @@ export function registerActionLoaderSlashCommands() {
232 typeList: [ARGUMENT_TYPE.STRING],239 typeList: [ARGUMENT_TYPE.STRING],
233 }),240 }),
234 SlashCommandNamedArgument.fromProps({241 SlashCommandNamedArgument.fromProps({
242 name: 'slug',
243 description: 'Unique slug for the loader (to identify it easily via code or CSS)',
244 typeList: [ARGUMENT_TYPE.STRING],
245 defaultValue: 'slash-show',
246 }),
247 SlashCommandNamedArgument.fromProps({
235 name: 'stopTooltip',248 name: 'stopTooltip',
236 description: 'Tooltip text for the stop button (only used when toast=stoppable)',249 description: 'Tooltip text for the stop button (only used when toast=stoppable)',
237 typeList: [ARGUMENT_TYPE.STRING],250 typeList: [ARGUMENT_TYPE.STRING],
@@ -258,7 +271,8 @@ export function registerActionLoaderSlashCommands() {
258 const title = args.title ? String(args.title) : '';271 const title = args.title ? String(args.title) : '';
259 const stopTooltip = String(args.stopTooltip ?? t`Stop`);272 const stopTooltip = String(args.stopTooltip ?? t`Stop`);
260273
261 const handle = showActionLoader({274 const handle = loader.show({
275 slug: typeof args.slug === 'string' ? String(args.slug) : 'slash-show',
262 blocking,276 blocking,
263 toastMode,277 toastMode,
264 message,278 message,
@@ -307,7 +321,7 @@ export function registerActionLoaderSlashCommands() {
307 }321 }
308322
309 // No handle provided - hide all active loaders323 // No handle provided - hide all active loaders
310 const result = await hideActionLoader();324 const result = await loader.hide();
311 return result ? 'true' : 'false';325 return result ? 'true' : 'false';
312 },326 },
313 }));327 }));
public/scripts/action-loader.js+31 -2
@@ -33,6 +33,7 @@ export const ActionLoaderToastMode = {
33 * @typedef {object} ActionLoaderOptions33 * @typedef {object} ActionLoaderOptions
34 * @property {boolean} [blocking=true] - Whether to show the blocking overlay. Set to false for non-blocking toast-only loaders.34 * @property {boolean} [blocking=true] - Whether to show the blocking overlay. Set to false for non-blocking toast-only loaders.
35 * @property {ActionLoaderToastMode} [toastMode='stoppable'] - Toast display mode35 * @property {ActionLoaderToastMode} [toastMode='stoppable'] - Toast display mode
36 * @property {string} [slug=null] - Unique slug for the loader to identify it easily via code or CSS
36 * @property {string} [message='Generating...'] - The message to display in the toast37 * @property {string} [message='Generating...'] - The message to display in the toast
37 * @property {string} [title] - Optional title for the toast notification38 * @property {string} [title] - Optional title for the toast notification
38 * @property {string} [stopTooltip='Stop'] - Tooltip text for the stop button39 * @property {string} [stopTooltip='Stop'] - Tooltip text for the stop button
@@ -83,7 +84,10 @@ export class ActionLoaderHandle {
83 }84 }
8485
85 /** @type {string} Unique identifier for this handle */86 /** @type {string} Unique identifier for this handle */
86 id;87 #id;
88
89 /** @type {string|null} Unique slug for the loader */
90 #slug = null;
8791
88 /** @type {JQuery<HTMLElement>|null} The toast element for this loader */92 /** @type {JQuery<HTMLElement>|null} The toast element for this loader */
89 #toast = null;93 #toast = null;
@@ -105,6 +109,7 @@ export class ActionLoaderHandle {
105 * @param {object} options - Configuration options109 * @param {object} options - Configuration options
106 * @param {boolean} [options.blocking=true] - Whether to show blocking overlay110 * @param {boolean} [options.blocking=true] - Whether to show blocking overlay
107 * @param {ActionLoaderToastMode} [options.toastMode] - Toast display mode111 * @param {ActionLoaderToastMode} [options.toastMode] - Toast display mode
112 * @param {string|null} [options.slug] - Unique slug for the loader (to identify it easily via code or CSS)
108 * @param {string} [options.message='Generating...'] - Message to display in the toast113 * @param {string} [options.message='Generating...'] - Message to display in the toast
109 * @param {string} [options.title] - Title for the toast notification114 * @param {string} [options.title] - Title for the toast notification
110 * @param {string} [options.stopTooltip='Stop'] - Tooltip for the stop button115 * @param {string} [options.stopTooltip='Stop'] - Tooltip for the stop button
@@ -116,6 +121,7 @@ export class ActionLoaderHandle {
116 constructor({121 constructor({
117 blocking = true,122 blocking = true,
118 toastMode = ActionLoaderToastMode.STOPPABLE,123 toastMode = ActionLoaderToastMode.STOPPABLE,
124 slug = null,
119 message = t`Generating...`,125 message = t`Generating...`,
120 title = '',126 title = '',
121 stopTooltip = t`Stop`,127 stopTooltip = t`Stop`,
@@ -129,7 +135,8 @@ export class ActionLoaderHandle {
129 return;135 return;
130 }136 }
131137
132 this.id = generateLoaderId();138 this.#id = generateLoaderId();
139 this.#slug = slug;
133 this.#blocking = blocking;140 this.#blocking = blocking;
134 this.#onStop = onStop;141 this.#onStop = onStop;
135 this.#onHide = onHide;142 this.#onHide = onHide;
@@ -164,6 +171,12 @@ export class ActionLoaderHandle {
164 const toastContent = document.createElement('div');171 const toastContent = document.createElement('div');
165 toastContent.className = 'action-loader-toast';172 toastContent.className = 'action-loader-toast';
166173
174 if (this.#slug) {
175 toastContent.dataset.slug = this.#slug;
176 }
177 toastContent.dataset.loaderId = this.#id;
178 toastContent.dataset.blocking = this.#blocking.toString();
179
167 const messageSpan = document.createElement('span');180 const messageSpan = document.createElement('span');
168 messageSpan.className = 'action-loader-message';181 messageSpan.className = 'action-loader-message';
169 messageSpan.textContent = message;182 messageSpan.textContent = message;
@@ -218,6 +231,22 @@ export class ActionLoaderHandle {
218 }231 }
219232
220 /**233 /**
234 * The unique identifier for this loader handle.
235 * @returns {string}
236 */
237 get id() {
238 return this.#id;
239 }
240
241 /**
242 * The unique slug for this loader handle, used to identify it easily via code or CSS.
243 * @returns {string|null}
244 */
245 get slug() {
246 return this.#slug;
247 }
248
249 /**
221 * Whether this handle is still active (not disposed).250 * Whether this handle is still active (not disposed).
222 * @returns {boolean}251 * @returns {boolean}
223 */252 */
public/scripts/bookmarks.js+1 -0
@@ -700,6 +700,7 @@ export function initBookmarks() {
700 }700 }
701701
702 const loaderHandle = loader.show({702 const loaderHandle = loader.show({
703 slug: 'chat-load',
703 title: t`Chat History`,704 title: t`Chat History`,
704 message: t`Loading chat…`,705 message: t`Loading chat…`,
705 toastMode: loader.ToastMode.STATIC,706 toastMode: loader.ToastMode.STATIC,
public/scripts/extensions/stable-diffusion/index.js+2 -0
@@ -3061,6 +3061,7 @@ async function generatePicture(initiator, args, trigger, message, callback) {
3061 // Show non-blocking stoppable toast for this generation3061 // Show non-blocking stoppable toast for this generation
3062 loaderHandle = loader.show({3062 loaderHandle = loader.show({
3063 blocking: false,3063 blocking: false,
3064 slug: `${MODULE_NAME}-image-generation`,
3064 title: t`Image Generation`,3065 title: t`Image Generation`,
3065 message: t`Generating an image...`,3066 message: t`Generating an image...`,
3066 onStop: stopListener,3067 onStop: stopListener,
@@ -5300,6 +5301,7 @@ async function generateMediaSwipe(mediaAttachment, message, onStart, onComplete,
5300 // Show non-blocking stoppable toast for this generation5301 // Show non-blocking stoppable toast for this generation
5301 loaderHandle = loader.show({5302 loaderHandle = loader.show({
5302 blocking: false,5303 blocking: false,
5304 slug: `${MODULE_NAME}-image-generation`,
5303 title: t`Image Generation`,5305 title: t`Image Generation`,
5304 message: t`Generating an image...`,5306 message: t`Generating an image...`,
5305 onStop: stopListener,5307 onStop: stopListener,
public/scripts/loader.js+1 -0
@@ -28,6 +28,7 @@ export function showLoader() {
2828
29 // Create a blocking loader with no toast (matches old behavior)29 // Create a blocking loader with no toast (matches old behavior)
30 legacyLoaderHandle = loader.show({30 legacyLoaderHandle = loader.show({
31 slug: 'legacy-loader',
31 blocking: true,32 blocking: true,
32 toastMode: loader.ToastMode.NONE,33 toastMode: loader.ToastMode.NONE,
33 });34 });