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>
This commit is contained in:
@@ -717,6 +717,7 @@ async function firstLoadInit() {
|
||||
initLoaderOverlay.appendChild(splashMessage);
|
||||
|
||||
const initLoaderHandle = loader.show({
|
||||
slug: 'app-init',
|
||||
toastMode: loader.ToastMode.NONE,
|
||||
overlayContent: initLoaderOverlay,
|
||||
});
|
||||
@@ -10592,6 +10593,7 @@ export async function renameGroupOrCharacterChat({ characterId, groupId, oldFile
|
||||
}
|
||||
|
||||
const loaderHandle = showLoader ? loader.show({
|
||||
slug: 'chat-rename',
|
||||
title: t`Rename Chat`,
|
||||
message: t`Renaming chat…`,
|
||||
toastMode: loader.ToastMode.STATIC,
|
||||
@@ -11196,6 +11198,7 @@ jQuery(async function () {
|
||||
$('#select_chat_cross').trigger('click');
|
||||
|
||||
const loaderHandle = loader.show({
|
||||
slug: 'chat-delete',
|
||||
title: t`Delete Chat`,
|
||||
message: t`Deleting chat…`,
|
||||
toastMode: loader.ToastMode.STATIC,
|
||||
|
||||
@@ -848,6 +848,7 @@ class BulkEditOverlay {
|
||||
const deleteChats = checkbox.prop('checked') ?? false;
|
||||
|
||||
const loaderHandle = loader.show({
|
||||
slug: 'bulk-delete',
|
||||
title: t`Bulk Delete`,
|
||||
message: t`Deleting ${characterIds.length} character(s)…`,
|
||||
toastMode: loader.ToastMode.STATIC,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ActionLoaderToastMode, getActiveLoaderHandles, getLoaderHandleById, hideActionLoader, showActionLoader } from './action-loader.js';
|
||||
import { ActionLoaderToastMode, getActiveLoaderHandles, getLoaderHandleById, loader } from './action-loader.js';
|
||||
import { t } from './i18n.js';
|
||||
import { SlashCommand } from './slash-commands/SlashCommand.js';
|
||||
import { SlashCommandNamedArgument, ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';
|
||||
@@ -115,6 +115,12 @@ export function registerActionLoaderSlashCommands() {
|
||||
description: 'Optional title for the toast notification',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'slug',
|
||||
description: 'Unique slug for the loader (to identify it easily via code or CSS)',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
defaultValue: 'slash-wrap',
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'stopTooltip',
|
||||
description: 'Tooltip text for the stop button (only used when toast=stoppable)',
|
||||
@@ -148,7 +154,8 @@ export function registerActionLoaderSlashCommands() {
|
||||
const title = args.title ? String(args.title) : '';
|
||||
const stopTooltip = String(args.stopTooltip ?? t`Stop`);
|
||||
|
||||
const loader = showActionLoader({
|
||||
const actionLoader = loader.show({
|
||||
slug: typeof args.slug === 'string' ? String(args.slug) : 'slash-wrap',
|
||||
blocking,
|
||||
toastMode,
|
||||
message,
|
||||
@@ -162,7 +169,7 @@ export function registerActionLoaderSlashCommands() {
|
||||
const result = await closureCopy.execute();
|
||||
return result.pipe;
|
||||
} finally {
|
||||
await loader.hide();
|
||||
await actionLoader.hide();
|
||||
}
|
||||
},
|
||||
}));
|
||||
@@ -231,6 +238,12 @@ export function registerActionLoaderSlashCommands() {
|
||||
description: 'Optional title for the toast notification',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'slug',
|
||||
description: 'Unique slug for the loader (to identify it easily via code or CSS)',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
defaultValue: 'slash-show',
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'stopTooltip',
|
||||
description: 'Tooltip text for the stop button (only used when toast=stoppable)',
|
||||
@@ -258,7 +271,8 @@ export function registerActionLoaderSlashCommands() {
|
||||
const title = args.title ? String(args.title) : '';
|
||||
const stopTooltip = String(args.stopTooltip ?? t`Stop`);
|
||||
|
||||
const handle = showActionLoader({
|
||||
const handle = loader.show({
|
||||
slug: typeof args.slug === 'string' ? String(args.slug) : 'slash-show',
|
||||
blocking,
|
||||
toastMode,
|
||||
message,
|
||||
@@ -307,7 +321,7 @@ export function registerActionLoaderSlashCommands() {
|
||||
}
|
||||
|
||||
// No handle provided - hide all active loaders
|
||||
const result = await hideActionLoader();
|
||||
const result = await loader.hide();
|
||||
return result ? 'true' : 'false';
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -33,6 +33,7 @@ export const ActionLoaderToastMode = {
|
||||
* @typedef {object} ActionLoaderOptions
|
||||
* @property {boolean} [blocking=true] - Whether to show the blocking overlay. Set to false for non-blocking toast-only loaders.
|
||||
* @property {ActionLoaderToastMode} [toastMode='stoppable'] - Toast display mode
|
||||
* @property {string} [slug=null] - Unique slug for the loader to identify it easily via code or CSS
|
||||
* @property {string} [message='Generating...'] - The message to display in the toast
|
||||
* @property {string} [title] - Optional title for the toast notification
|
||||
* @property {string} [stopTooltip='Stop'] - Tooltip text for the stop button
|
||||
@@ -83,7 +84,10 @@ export class ActionLoaderHandle {
|
||||
}
|
||||
|
||||
/** @type {string} Unique identifier for this handle */
|
||||
id;
|
||||
#id;
|
||||
|
||||
/** @type {string|null} Unique slug for the loader */
|
||||
#slug = null;
|
||||
|
||||
/** @type {JQuery<HTMLElement>|null} The toast element for this loader */
|
||||
#toast = null;
|
||||
@@ -105,6 +109,7 @@ export class ActionLoaderHandle {
|
||||
* @param {object} options - Configuration options
|
||||
* @param {boolean} [options.blocking=true] - Whether to show blocking overlay
|
||||
* @param {ActionLoaderToastMode} [options.toastMode] - Toast display mode
|
||||
* @param {string|null} [options.slug] - Unique slug for the loader (to identify it easily via code or CSS)
|
||||
* @param {string} [options.message='Generating...'] - Message to display in the toast
|
||||
* @param {string} [options.title] - Title for the toast notification
|
||||
* @param {string} [options.stopTooltip='Stop'] - Tooltip for the stop button
|
||||
@@ -116,6 +121,7 @@ export class ActionLoaderHandle {
|
||||
constructor({
|
||||
blocking = true,
|
||||
toastMode = ActionLoaderToastMode.STOPPABLE,
|
||||
slug = null,
|
||||
message = t`Generating...`,
|
||||
title = '',
|
||||
stopTooltip = t`Stop`,
|
||||
@@ -129,7 +135,8 @@ export class ActionLoaderHandle {
|
||||
return;
|
||||
}
|
||||
|
||||
this.id = generateLoaderId();
|
||||
this.#id = generateLoaderId();
|
||||
this.#slug = slug;
|
||||
this.#blocking = blocking;
|
||||
this.#onStop = onStop;
|
||||
this.#onHide = onHide;
|
||||
@@ -164,6 +171,12 @@ export class ActionLoaderHandle {
|
||||
const toastContent = document.createElement('div');
|
||||
toastContent.className = 'action-loader-toast';
|
||||
|
||||
if (this.#slug) {
|
||||
toastContent.dataset.slug = this.#slug;
|
||||
}
|
||||
toastContent.dataset.loaderId = this.#id;
|
||||
toastContent.dataset.blocking = this.#blocking.toString();
|
||||
|
||||
const messageSpan = document.createElement('span');
|
||||
messageSpan.className = 'action-loader-message';
|
||||
messageSpan.textContent = message;
|
||||
@@ -217,6 +230,22 @@ export class ActionLoaderHandle {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The unique identifier for this loader handle.
|
||||
* @returns {string}
|
||||
*/
|
||||
get id() {
|
||||
return this.#id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The unique slug for this loader handle, used to identify it easily via code or CSS.
|
||||
* @returns {string|null}
|
||||
*/
|
||||
get slug() {
|
||||
return this.#slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this handle is still active (not disposed).
|
||||
* @returns {boolean}
|
||||
|
||||
@@ -700,6 +700,7 @@ export function initBookmarks() {
|
||||
}
|
||||
|
||||
const loaderHandle = loader.show({
|
||||
slug: 'chat-load',
|
||||
title: t`Chat History`,
|
||||
message: t`Loading chat…`,
|
||||
toastMode: loader.ToastMode.STATIC,
|
||||
|
||||
@@ -3061,6 +3061,7 @@ async function generatePicture(initiator, args, trigger, message, callback) {
|
||||
// Show non-blocking stoppable toast for this generation
|
||||
loaderHandle = loader.show({
|
||||
blocking: false,
|
||||
slug: `${MODULE_NAME}-image-generation`,
|
||||
title: t`Image Generation`,
|
||||
message: t`Generating an image...`,
|
||||
onStop: stopListener,
|
||||
@@ -5300,6 +5301,7 @@ async function generateMediaSwipe(mediaAttachment, message, onStart, onComplete,
|
||||
// Show non-blocking stoppable toast for this generation
|
||||
loaderHandle = loader.show({
|
||||
blocking: false,
|
||||
slug: `${MODULE_NAME}-image-generation`,
|
||||
title: t`Image Generation`,
|
||||
message: t`Generating an image...`,
|
||||
onStop: stopListener,
|
||||
|
||||
@@ -28,6 +28,7 @@ export function showLoader() {
|
||||
|
||||
// Create a blocking loader with no toast (matches old behavior)
|
||||
legacyLoaderHandle = loader.show({
|
||||
slug: 'legacy-loader',
|
||||
blocking: true,
|
||||
toastMode: loader.ToastMode.NONE,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user