diff --git a/public/scripts/BulkEditOverlay.js b/public/scripts/BulkEditOverlay.js
index 997a78477..bcbb2b8ce 100644
--- a/public/scripts/BulkEditOverlay.js
+++ b/public/scripts/BulkEditOverlay.js
@@ -10,10 +10,12 @@ import {
getRequestHeaders,
this_chid
} from "../script.js";
+
import {favsToHotswap} from "./RossAscends-mods.js";
import {convertCharacterToPersona} from "./personas.js";
import {createTagInput, getTagKeyForCharacter, tag_map} from "./tags.js";
+// Utility object for popup messages.
const popupMessage = {
deleteChat(characterCount) {
return `
Delete ${characterCount} characters?
@@ -25,29 +27,29 @@ const popupMessage = {
},
}
-const toggleFavoriteHighlight = (characterId) => {
- const element = document.getElementById(`CharID${characterId}`);
- element.classList.toggle('is_fav');
-}
-
-class CharacterGroupOverlayState {
- static browse = 0;
- static select = 1;
-}
-
+/**
+ * Static object representing the actions of the
+ * character context menu override.
+ */
class CharacterContextMenu {
+ /**
+ * Tag one or more characters,
+ * opens a popup.
+ *
+ * @param selectedCharacters
+ */
static tag = (selectedCharacters) => {
BulkTagPopupHandler.show(selectedCharacters);
}
/**
- * Duplicate a character
+ * Duplicate one or more characters
*
* @param characterId
* @returns {Promise}
*/
static duplicate = async (characterId) => {
- const character = CharacterContextMenu.getCharacter(characterId);
+ const character = CharacterContextMenu.#getCharacter(characterId);
return fetch('/dupecharacter', {
method: 'POST',
@@ -58,13 +60,15 @@ class CharacterContextMenu {
/**
* Favorite a character
- * and toggle its ui element.
+ * and highlight it.
*
* @param characterId
* @returns {Promise}
*/
static favorite = async (characterId) => {
- const character = CharacterContextMenu.getCharacter(characterId);
+ const character = CharacterContextMenu.#getCharacter(characterId);
+
+ // Only set fav for V2 spec
const data = {
name: character.name,
avatar: character.avatar,
@@ -80,22 +84,34 @@ class CharacterContextMenu {
headers: getRequestHeaders(),
body: JSON.stringify(data),
}).then((response) => {
- if (response.ok) toggleFavoriteHighlight(characterId)
- else response.json().then(json => toastr.error('Character not saved. Error: ' + json.message + '. Field: ' + json.error));
+ if (response.ok) {
+ const element = document.getElementById(`CharID${characterId}`);
+ element.classList.toggle('is_fav');
+ } else {
+ response.json().then(json => toastr.error('Character not saved. Error: ' + json.message + '. Field: ' + json.error));
+ }
});
}
/**
- * Convert the given characters to personas.
- * Shows popup for each.
+ * Convert one or more characters to persona,
+ * may open a popup for one or more characters.
*
* @param characterId
* @returns {Promise}
*/
static persona = async (characterId) => await convertCharacterToPersona(characterId);
+ /**
+ * Delete one or more characters,
+ * opens a popup.
+ *
+ * @param characterId
+ * @param deleteChats
+ * @returns {Promise}
+ */
static delete = async (characterId, deleteChats = false) => {
- const character = CharacterContextMenu.getCharacter(characterId);
+ const character = CharacterContextMenu.#getCharacter(characterId);
return fetch('/deletecharacter', {
method: 'POST',
@@ -123,12 +139,19 @@ class CharacterContextMenu {
}
})
}
+
eventSource.emit('characterDeleted', { id: this_chid, character: characters[this_chid] });
});
}
- static getCharacter = (characterId) => characters[characterId] ?? null;
+ static #getCharacter = (characterId) => characters[characterId] ?? null;
+ /**
+ * Show the context menu at the given position
+ *
+ * @param positionX
+ * @param positionY
+ */
static show = (positionX, positionY) => {
let contextMenu = document.getElementById(BulkEditOverlay.contextMenuId);
contextMenu.style.left = `${positionX}px`;
@@ -137,8 +160,16 @@ class CharacterContextMenu {
document.getElementById(BulkEditOverlay.contextMenuId).classList.remove('hidden');
}
+ /**
+ * Hide the context menu
+ */
static hide = () => document.getElementById(BulkEditOverlay.contextMenuId).classList.add('hidden');
+ /**
+ * Sets up the context menu for the given overlay
+ *
+ * @param characterGroupOverlay
+ */
constructor(characterGroupOverlay) {
const contextMenuItems = [
{id: 'character_context_menu_favorite', callback: characterGroupOverlay.handleContextMenuFavorite},
@@ -153,7 +184,7 @@ class CharacterContextMenu {
}
/**
- * Appends/Removes the bulk tag popup
+ * Represents a tag control not bound to a single character
*/
class BulkTagPopupHandler {
static #getHtml = (characterIds) => {
@@ -180,6 +211,11 @@ class BulkTagPopupHandler {
`
};
+ /**
+ * Append and show the tag control
+ *
+ * @param characters - The characters assigned to this control
+ */
static show(characters) {
document.body.insertAdjacentHTML('beforeend', this.#getHtml(characters));
createTagInput('#bulkTagInput', '#bulkTagList');
@@ -187,6 +223,9 @@ class BulkTagPopupHandler {
document.querySelector('#bulk_tag_popup_reset').addEventListener('click', this.resetTags.bind(this, characters));
}
+ /**
+ * Hide and remove the tag control
+ */
static hide() {
let popupElement = document.querySelector('#bulk_tag_shadow_popup');
if (popupElement) {
@@ -194,6 +233,11 @@ class BulkTagPopupHandler {
}
}
+ /**
+ * Empty the tag map for the given characters
+ *
+ * @param characterIds
+ */
static resetTags(characterIds) {
characterIds.forEach((characterId) => {
const key = getTagKeyForCharacter(characterId);
@@ -202,6 +246,20 @@ class BulkTagPopupHandler {
}
}
+class CharacterGroupOverlayState {
+ /**
+ *
+ * @type {number}
+ */
+ static browse = 0;
+
+ /**
+ *
+ * @type {number}
+ */
+ static select = 1;
+}
+
/**
* Implement a SingletonPattern, allowing access to the group overlay instance
* from everywhere via (new CharacterGroupOverlay())
@@ -273,7 +331,14 @@ class BulkEditOverlay {
bulkEditOverlayInstance = Object.freeze(this);
}
+ /**
+ * Set the overlay to browse mode
+ */
browseState = () => this.state = CharacterGroupOverlayState.browse;
+
+ /**
+ * Set the overlay to select mode
+ */
selectState = () => this.state = CharacterGroupOverlayState.select;
/**
@@ -294,6 +359,32 @@ class BulkEditOverlay {
grid.addEventListener('click', this.handleCancelClick);
}
+ /**
+ * Handle state changes
+ *
+ *
+ */
+ handleStateChange = () => {
+ switch (this.state) {
+ case CharacterGroupOverlayState.browse:
+ this.container.classList.remove(BulkEditOverlay.selectModeClass);
+ this.#enableClickEventsForCharacters();
+ this.clearSelectedCharacters();
+ this.disableContextMenu();
+ this.#disableBulkEditButtonHighlight();
+ CharacterContextMenu.hide();
+ break;
+ case CharacterGroupOverlayState.select:
+ this.container.classList.add(BulkEditOverlay.selectModeClass);
+ this.#disableClickEventsForCharacters();
+ this.enableContextMenu();
+ this.#enableBulkEditButtonHighlight();
+ break;
+ }
+
+ this.stateChangeCallbacks.forEach(callback => callback(this.state));
+ }
+
/**
* Block the browsers native context menu and
* set a click event to hide the custom context menu.
@@ -331,27 +422,6 @@ class BulkEditOverlay {
this.state = CharacterGroupOverlayState.browse;
}
- handleStateChange = () => {
- switch (this.state) {
- case CharacterGroupOverlayState.browse:
- this.container.classList.remove(BulkEditOverlay.selectModeClass);
- this.#enableClickEventsForCharacters();
- this.clearSelectedCharacters();
- this.disableContextMenu();
- this.#disableBulkEditButtonHighlight();
- CharacterContextMenu.hide();
- break;
- case CharacterGroupOverlayState.select:
- this.container.classList.add(BulkEditOverlay.selectModeClass);
- this.#disableClickEventsForCharacters();
- this.enableContextMenu();
- this.#enableBulkEditButtonHighlight();
- break;
- }
-
- this.stateChangeCallbacks.forEach(callback => callback(this.state));
- }
-
#enableClickEventsForCharacters = () => [...this.container.getElementsByClassName(BulkEditOverlay.characterClass)]
.forEach(element => element.removeEventListener('click', this.toggleCharacterSelected));