|
'use strict'; |
|
|
|
import { |
|
characterGroupOverlay, |
|
callPopup, |
|
characters, |
|
event_types, |
|
eventSource, |
|
getCharacters, |
|
getRequestHeaders, |
|
buildAvatarList, |
|
characterToEntity, |
|
printCharactersDebounced, |
|
deleteCharacter, |
|
} from '../script.js'; |
|
|
|
import { favsToHotswap } from './RossAscends-mods.js'; |
|
import { hideLoader, showLoader } from './loader.js'; |
|
import { convertCharacterToPersona } from './personas.js'; |
|
import { createTagInput, getTagKeyForEntity, getTagsList, printTagList, tag_map, compareTagsForSort, removeTagFromMap, importTags, tag_import_setting } from './tags.js'; |
|
|
|
|
|
|
|
|
|
|
|
class CharacterContextMenu { |
|
|
|
|
|
|
|
|
|
|
|
|
|
static tag = (selectedCharacters) => { |
|
characterGroupOverlay.bulkTagPopupHandler.show(selectedCharacters); |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static duplicate = async (characterId) => { |
|
const character = CharacterContextMenu.#getCharacter(characterId); |
|
const body = { avatar_url: character.avatar }; |
|
|
|
const result = await fetch('/api/characters/duplicate', { |
|
method: 'POST', |
|
headers: getRequestHeaders(), |
|
body: JSON.stringify(body), |
|
}); |
|
|
|
if (!result.ok) { |
|
throw new Error('Character not duplicated'); |
|
} |
|
|
|
const data = await result.json(); |
|
await eventSource.emit(event_types.CHARACTER_DUPLICATED, { oldAvatar: body.avatar_url, newAvatar: data.path }); |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static favorite = async (characterId) => { |
|
const character = CharacterContextMenu.#getCharacter(characterId); |
|
const newFavState = !character.data.extensions.fav; |
|
|
|
const data = { |
|
name: character.name, |
|
avatar: character.avatar, |
|
data: { |
|
extensions: { |
|
fav: newFavState, |
|
}, |
|
}, |
|
fav: newFavState, |
|
}; |
|
|
|
const mergeResponse = await fetch('/api/characters/merge-attributes', { |
|
method: 'POST', |
|
headers: getRequestHeaders(), |
|
body: JSON.stringify(data), |
|
}); |
|
|
|
if (!mergeResponse.ok) { |
|
mergeResponse.json().then(json => toastr.error(`Character not saved. Error: ${json.message}. Field: ${json.error}`)); |
|
} |
|
|
|
const element = document.getElementById(`CharID${characterId}`); |
|
element.classList.toggle('is_fav'); |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static persona = async (characterId) => await convertCharacterToPersona(characterId); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static delete = async (characterKey, deleteChats = false) => { |
|
await deleteCharacter(characterKey, { deleteChats: deleteChats }); |
|
}; |
|
|
|
static #getCharacter = (characterId) => characters[characterId] ?? null; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static show = (positionX, positionY) => { |
|
let contextMenu = document.getElementById(BulkEditOverlay.contextMenuId); |
|
contextMenu.style.left = `${positionX}px`; |
|
contextMenu.style.top = `${positionY}px`; |
|
|
|
document.getElementById(BulkEditOverlay.contextMenuId).classList.remove('hidden'); |
|
|
|
|
|
const boundingRect = contextMenu.getBoundingClientRect(); |
|
if (boundingRect.right > window.innerWidth) { |
|
contextMenu.style.left = `${positionX - (boundingRect.right - window.innerWidth)}px`; |
|
} |
|
if (boundingRect.bottom > window.innerHeight) { |
|
contextMenu.style.top = `${positionY - (boundingRect.bottom - window.innerHeight)}px`; |
|
} |
|
}; |
|
|
|
|
|
|
|
|
|
static hide = () => document.getElementById(BulkEditOverlay.contextMenuId).classList.add('hidden'); |
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor(characterGroupOverlay) { |
|
const contextMenuItems = [ |
|
{ id: 'character_context_menu_favorite', callback: characterGroupOverlay.handleContextMenuFavorite }, |
|
{ id: 'character_context_menu_duplicate', callback: characterGroupOverlay.handleContextMenuDuplicate }, |
|
{ id: 'character_context_menu_delete', callback: characterGroupOverlay.handleContextMenuDelete }, |
|
{ id: 'character_context_menu_persona', callback: characterGroupOverlay.handleContextMenuPersona }, |
|
{ id: 'character_context_menu_tag', callback: characterGroupOverlay.handleContextMenuTag }, |
|
]; |
|
|
|
contextMenuItems.forEach(contextMenuItem => document.getElementById(contextMenuItem.id).addEventListener('click', contextMenuItem.callback)); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
class BulkTagPopupHandler { |
|
|
|
|
|
|
|
|
|
characterIds; |
|
|
|
|
|
|
|
|
|
|
|
currentMutualTags; |
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor() { } |
|
|
|
|
|
|
|
|
|
|
|
|
|
#getHtml = () => { |
|
const characterData = JSON.stringify({ characterIds: this.characterIds }); |
|
return `<div id="bulk_tag_shadow_popup"> |
|
<div id="bulk_tag_popup" class="wider_dialogue_popup"> |
|
<div id="bulk_tag_popup_holder"> |
|
<h3 class="marginBot5">Modify tags of ${this.characterIds.length} characters</h3> |
|
<small class="bulk_tags_desc m-b-1">Add or remove the mutual tags of all selected characters. Import all or existing tags for all selected characters.</small> |
|
<div id="bulk_tags_avatars_block" class="avatars_inline avatars_inline_small tags tags_inline"></div> |
|
<br> |
|
<div id="bulk_tags_div" class="marginBot5" data-characters='${characterData}'> |
|
<div class="tag_controls"> |
|
<input id="bulkTagInput" class="text_pole tag_input wide100p margin0" data-i18n="[placeholder]Search / Create Tags" placeholder="Search / Create tags" maxlength="25" /> |
|
<div class="tags_view menu_button fa-solid fa-tags" title="View all tags" data-i18n="[title]View all tags"></div> |
|
</div> |
|
<div id="bulkTagList" class="m-t-1 tags"></div> |
|
</div> |
|
<div id="dialogue_popup_controls" class="m-t-1"> |
|
<div id="bulk_tag_popup_reset" class="menu_button" title="Remove all tags from the selected characters" data-i18n="[title]Remove all tags from the selected characters"> |
|
<i class="fa-solid fa-trash-can margin-right-10px"></i> |
|
All |
|
</div> |
|
<div id="bulk_tag_popup_remove_mutual" class="menu_button" title="Remove all mutual tags from the selected characters" data-i18n="[title]Remove all mutual tags from the selected characters"> |
|
<i class="fa-solid fa-trash-can margin-right-10px"></i> |
|
Mutual |
|
</div> |
|
<div id="bulk_tag_popup_import_all_tags" class="menu_button" title="Import all tags from selected characters" data-i18n="[title]Import all tags from selected characters"> |
|
Import All |
|
</div> |
|
<div id="bulk_tag_popup_import_existing_tags" class="menu_button" title="Import existing tags from selected characters" data-i18n="[title]Import existing tags from selected characters"> |
|
Import Existing |
|
</div> |
|
<div id="bulk_tag_popup_cancel" class="menu_button" data-i18n="Cancel">Close</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div>`; |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
show(characterIds) { |
|
|
|
this.characterIds = characterIds.slice(); |
|
|
|
if (this.characterIds.length == 0) { |
|
console.log('No characters selected for bulk edit tags.'); |
|
return; |
|
} |
|
|
|
document.body.insertAdjacentHTML('beforeend', this.#getHtml()); |
|
|
|
const entities = this.characterIds.map(id => characterToEntity(characters[id], id)).filter(entity => entity.item !== undefined); |
|
buildAvatarList($('#bulk_tags_avatars_block'), entities); |
|
|
|
|
|
printTagList($('#bulkTagList'), { tags: () => this.getMutualTags(), tagOptions: { removable: true } }); |
|
|
|
|
|
createTagInput('#bulkTagInput', '#bulkTagList', { tags: () => this.getMutualTags(), tagOptions: { removable: true } }); |
|
|
|
document.querySelector('#bulk_tag_popup_reset').addEventListener('click', this.resetTags.bind(this)); |
|
document.querySelector('#bulk_tag_popup_remove_mutual').addEventListener('click', this.removeMutual.bind(this)); |
|
document.querySelector('#bulk_tag_popup_cancel').addEventListener('click', this.hide.bind(this)); |
|
document.querySelector('#bulk_tag_popup_import_all_tags').addEventListener('click', this.importAllTags.bind(this)); |
|
document.querySelector('#bulk_tag_popup_import_existing_tags').addEventListener('click', this.importExistingTags.bind(this)); |
|
} |
|
|
|
|
|
|
|
|
|
async importExistingTags() { |
|
for (const characterId of this.characterIds) { |
|
await importTags(characters[characterId], { importSetting: tag_import_setting.ONLY_EXISTING }); |
|
} |
|
|
|
$('#bulkTagList').empty(); |
|
} |
|
|
|
|
|
|
|
|
|
async importAllTags() { |
|
for (const characterId of this.characterIds) { |
|
await importTags(characters[characterId], { importSetting: tag_import_setting.ALL }); |
|
} |
|
|
|
$('#bulkTagList').empty(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
getMutualTags() { |
|
if (this.characterIds.length == 0) { |
|
return []; |
|
} |
|
|
|
if (this.characterIds.length === 1) { |
|
|
|
return getTagsList(getTagKeyForEntity(this.characterIds[0])); |
|
} |
|
|
|
|
|
const allTags = this.characterIds.map(cid => getTagsList(getTagKeyForEntity(cid))); |
|
const mutualTags = allTags.reduce((mutual, characterTags) => |
|
mutual.filter(tag => characterTags.some(cTag => cTag.id === tag.id)), |
|
); |
|
|
|
this.currentMutualTags = mutualTags.sort(compareTagsForSort); |
|
return this.currentMutualTags; |
|
} |
|
|
|
|
|
|
|
|
|
hide() { |
|
let popupElement = document.querySelector('#bulk_tag_shadow_popup'); |
|
if (popupElement) { |
|
document.body.removeChild(popupElement); |
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
resetTags() { |
|
for (const characterId of this.characterIds) { |
|
const key = getTagKeyForEntity(characterId); |
|
if (key) tag_map[key] = []; |
|
} |
|
|
|
$('#bulkTagList').empty(); |
|
|
|
printCharactersDebounced(); |
|
} |
|
|
|
|
|
|
|
|
|
removeMutual() { |
|
const mutualTags = this.getMutualTags(); |
|
|
|
for (const characterId of this.characterIds) { |
|
for (const tag of mutualTags) { |
|
removeTagFromMap(tag.id, characterId); |
|
} |
|
} |
|
|
|
$('#bulkTagList').empty(); |
|
|
|
printCharactersDebounced(); |
|
} |
|
} |
|
|
|
class BulkEditOverlayState { |
|
|
|
|
|
|
|
|
|
static browse = 0; |
|
|
|
|
|
|
|
|
|
|
|
static select = 1; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let bulkEditOverlayInstance = null; |
|
|
|
class BulkEditOverlay { |
|
static containerId = 'rm_print_characters_block'; |
|
static contextMenuId = 'character_context_menu'; |
|
static characterClass = 'character_select'; |
|
static groupClass = 'group_select'; |
|
static bogusFolderClass = 'bogus_folder_select'; |
|
static selectModeClass = 'group_overlay_mode_select'; |
|
static selectedClass = 'character_selected'; |
|
static legacySelectedClass = 'bulk_select_checkbox'; |
|
static bulkSelectedCountId = 'bulkSelectedCount'; |
|
|
|
static longPressDelay = 2500; |
|
|
|
#state = BulkEditOverlayState.browse; |
|
#longPress = false; |
|
#stateChangeCallbacks = []; |
|
#selectedCharacters = []; |
|
#bulkTagPopupHandler = new BulkTagPopupHandler(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lastSelected = { characterId: undefined, select: undefined }; |
|
|
|
|
|
|
|
|
|
|
|
|
|
#contextMenuOpen = false; |
|
|
|
|
|
|
|
|
|
|
|
|
|
#cancelNextToggle = false; |
|
|
|
|
|
|
|
|
|
container = null; |
|
|
|
get state() { |
|
return this.#state; |
|
} |
|
|
|
set state(newState) { |
|
if (this.#state === newState) return; |
|
|
|
eventSource.emit(event_types.CHARACTER_GROUP_OVERLAY_STATE_CHANGE_BEFORE, newState) |
|
.then(() => { |
|
this.#state = newState; |
|
eventSource.emit(event_types.CHARACTER_GROUP_OVERLAY_STATE_CHANGE_AFTER, this.state); |
|
}); |
|
} |
|
|
|
get isLongPress() { |
|
return this.#longPress; |
|
} |
|
|
|
set isLongPress(longPress) { |
|
this.#longPress = longPress; |
|
} |
|
|
|
get stateChangeCallbacks() { |
|
return this.#stateChangeCallbacks; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
get selectedCharacters() { |
|
return this.#selectedCharacters; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
get bulkTagPopupHandler() { |
|
return this.#bulkTagPopupHandler; |
|
} |
|
|
|
constructor() { |
|
if (bulkEditOverlayInstance instanceof BulkEditOverlay) |
|
return bulkEditOverlayInstance; |
|
|
|
this.container = document.getElementById(BulkEditOverlay.containerId); |
|
|
|
eventSource.on(event_types.CHARACTER_GROUP_OVERLAY_STATE_CHANGE_AFTER, this.handleStateChange); |
|
bulkEditOverlayInstance = Object.freeze(this); |
|
} |
|
|
|
|
|
|
|
|
|
browseState = () => this.state = BulkEditOverlayState.browse; |
|
|
|
|
|
|
|
|
|
selectState = () => this.state = BulkEditOverlayState.select; |
|
|
|
|
|
|
|
|
|
onPageLoad = () => { |
|
this.browseState(); |
|
|
|
const elements = this.#getEnabledElements(); |
|
elements.forEach(element => element.addEventListener('touchstart', this.handleHold)); |
|
elements.forEach(element => element.addEventListener('mousedown', this.handleHold)); |
|
elements.forEach(element => element.addEventListener('contextmenu', this.handleDefaultContextMenu)); |
|
|
|
elements.forEach(element => element.addEventListener('touchend', this.handleLongPressEnd)); |
|
elements.forEach(element => element.addEventListener('mouseup', this.handleLongPressEnd)); |
|
elements.forEach(element => element.addEventListener('dragend', this.handleLongPressEnd)); |
|
elements.forEach(element => element.addEventListener('touchmove', this.handleLongPressEnd)); |
|
|
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
handleStateChange = () => { |
|
switch (this.state) { |
|
case BulkEditOverlayState.browse: |
|
this.container.classList.remove(BulkEditOverlay.selectModeClass); |
|
this.#contextMenuOpen = false; |
|
this.#enableClickEventsForCharacters(); |
|
this.#enableClickEventsForGroups(); |
|
this.clearSelectedCharacters(); |
|
this.disableContextMenu(); |
|
this.#disableBulkEditButtonHighlight(); |
|
CharacterContextMenu.hide(); |
|
break; |
|
case BulkEditOverlayState.select: |
|
this.container.classList.add(BulkEditOverlay.selectModeClass); |
|
this.#disableClickEventsForCharacters(); |
|
this.#disableClickEventsForGroups(); |
|
this.enableContextMenu(); |
|
this.#enableBulkEditButtonHighlight(); |
|
break; |
|
} |
|
|
|
this.stateChangeCallbacks.forEach(callback => callback(this.state)); |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
enableContextMenu = () => { |
|
this.container.addEventListener('contextmenu', this.handleContextMenuShow); |
|
document.addEventListener('click', this.handleContextMenuHide); |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
disableContextMenu = () => { |
|
this.container.removeEventListener('contextmenu', this.handleContextMenuShow); |
|
document.removeEventListener('click', this.handleContextMenuHide); |
|
}; |
|
|
|
handleDefaultContextMenu = (event) => { |
|
if (this.isLongPress) { |
|
event.preventDefault(); |
|
event.stopPropagation(); |
|
return false; |
|
} |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
handleHold = (event) => { |
|
if (0 !== event.button && event.type !== 'touchstart') return; |
|
if (this.#contextMenuOpen) { |
|
this.#contextMenuOpen = false; |
|
this.#cancelNextToggle = true; |
|
CharacterContextMenu.hide(); |
|
return; |
|
} |
|
|
|
let cancel = false; |
|
|
|
const cancelHold = (event) => cancel = true; |
|
this.container.addEventListener('mouseup', cancelHold); |
|
this.container.addEventListener('touchend', cancelHold); |
|
|
|
this.isLongPress = true; |
|
|
|
setTimeout(() => { |
|
if (this.isLongPress && !cancel) { |
|
if (this.state === BulkEditOverlayState.browse) { |
|
this.selectState(); |
|
} else if (this.state === BulkEditOverlayState.select) { |
|
this.#contextMenuOpen = true; |
|
CharacterContextMenu.show(...this.#getContextMenuPosition(event)); |
|
} |
|
} |
|
|
|
this.container.removeEventListener('mouseup', cancelHold); |
|
this.container.removeEventListener('touchend', cancelHold); |
|
}, BulkEditOverlay.longPressDelay); |
|
}; |
|
|
|
handleLongPressEnd = (event) => { |
|
this.isLongPress = false; |
|
if (this.#contextMenuOpen) event.stopPropagation(); |
|
}; |
|
|
|
handleCancelClick = () => { |
|
if (false === this.#contextMenuOpen) this.state = BulkEditOverlayState.browse; |
|
this.#contextMenuOpen = false; |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#getContextMenuPosition = (event) => [ |
|
event.clientX || event.touches[0].clientX, |
|
event.clientY || event.touches[0].clientY, |
|
]; |
|
|
|
#stopEventPropagation = (event) => { |
|
if (this.#contextMenuOpen) { |
|
this.handleContextMenuHide(event); |
|
} |
|
event.stopPropagation(); |
|
}; |
|
|
|
#enableClickEventsForGroups = () => this.#getDisabledElements().forEach((element) => element.removeEventListener('click', this.#stopEventPropagation)); |
|
|
|
#disableClickEventsForGroups = () => this.#getDisabledElements().forEach((element) => element.addEventListener('click', this.#stopEventPropagation)); |
|
|
|
#enableClickEventsForCharacters = () => this.#getEnabledElements().forEach(element => element.removeEventListener('click', this.toggleCharacterSelected)); |
|
|
|
#disableClickEventsForCharacters = () => this.#getEnabledElements().forEach(element => element.addEventListener('click', this.toggleCharacterSelected)); |
|
|
|
#enableBulkEditButtonHighlight = () => document.getElementById('bulkEditButton').classList.add('bulk_edit_overlay_active'); |
|
|
|
#disableBulkEditButtonHighlight = () => document.getElementById('bulkEditButton').classList.remove('bulk_edit_overlay_active'); |
|
|
|
#getEnabledElements = () => [...this.container.getElementsByClassName(BulkEditOverlay.characterClass)]; |
|
|
|
#getDisabledElements = () => [...this.container.getElementsByClassName(BulkEditOverlay.groupClass), ...this.container.getElementsByClassName(BulkEditOverlay.bogusFolderClass)]; |
|
|
|
toggleCharacterSelected = event => { |
|
event.stopPropagation(); |
|
|
|
const character = event.currentTarget; |
|
|
|
if (!this.#contextMenuOpen && !this.#cancelNextToggle) { |
|
if (event.shiftKey) { |
|
|
|
document.getSelection().removeAllRanges(); |
|
|
|
this.handleShiftClick(character); |
|
} else { |
|
this.toggleSingleCharacter(character); |
|
} |
|
} |
|
|
|
this.#cancelNextToggle = false; |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
handleShiftClick = (currentCharacter) => { |
|
const characterId = Number(currentCharacter.getAttribute('data-chid')); |
|
const select = !this.selectedCharacters.includes(characterId); |
|
|
|
if (this.lastSelected.characterId >= 0 && this.lastSelected.select !== undefined) { |
|
|
|
if (select === this.lastSelected.select) { |
|
this.toggleCharactersInRange(currentCharacter, select); |
|
} |
|
} |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
toggleSingleCharacter = (character, { markState = true } = {}) => { |
|
const characterId = Number(character.getAttribute('data-chid')); |
|
|
|
const select = !this.selectedCharacters.includes(characterId); |
|
const legacyBulkEditCheckbox = character.querySelector('.' + BulkEditOverlay.legacySelectedClass); |
|
|
|
if (select) { |
|
character.classList.add(BulkEditOverlay.selectedClass); |
|
if (legacyBulkEditCheckbox) legacyBulkEditCheckbox.checked = true; |
|
this.#selectedCharacters.push(characterId); |
|
} else { |
|
character.classList.remove(BulkEditOverlay.selectedClass); |
|
if (legacyBulkEditCheckbox) legacyBulkEditCheckbox.checked = false; |
|
this.#selectedCharacters = this.#selectedCharacters.filter(item => characterId !== item); |
|
} |
|
|
|
this.updateSelectedCount(); |
|
|
|
if (markState) { |
|
this.lastSelected.characterId = characterId; |
|
this.lastSelected.select = select; |
|
} |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
updateSelectedCount = (countOverride = undefined) => { |
|
const count = countOverride ?? this.selectedCharacters.length; |
|
$(`#${BulkEditOverlay.bulkSelectedCountId}`).text(count).attr('title', `${count} characters selected`); |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
toggleCharactersInRange = (currentCharacter, select) => { |
|
const currentCharacterId = Number(currentCharacter.getAttribute('data-chid')); |
|
const characters = Array.from(document.querySelectorAll('#' + BulkEditOverlay.containerId + ' .' + BulkEditOverlay.characterClass)); |
|
|
|
const startIndex = characters.findIndex(c => Number(c.getAttribute('data-chid')) === Number(this.lastSelected.characterId)); |
|
const endIndex = characters.findIndex(c => Number(c.getAttribute('data-chid')) === currentCharacterId); |
|
|
|
for (let i = Math.min(startIndex, endIndex); i <= Math.max(startIndex, endIndex); i++) { |
|
const character = characters[i]; |
|
const characterId = Number(character.getAttribute('data-chid')); |
|
const isCharacterSelected = this.selectedCharacters.includes(characterId); |
|
|
|
|
|
|
|
if ((select && !isCharacterSelected || !select && isCharacterSelected) && character instanceof HTMLElement) { |
|
this.toggleSingleCharacter(character, { markState: currentCharacterId == characterId }); |
|
} |
|
} |
|
}; |
|
|
|
handleContextMenuShow = (event) => { |
|
event.preventDefault(); |
|
CharacterContextMenu.show(...this.#getContextMenuPosition(event)); |
|
this.#contextMenuOpen = true; |
|
}; |
|
|
|
handleContextMenuHide = (event) => { |
|
let contextMenu = document.getElementById(BulkEditOverlay.contextMenuId); |
|
if (false === contextMenu.contains(event.target)) { |
|
CharacterContextMenu.hide(); |
|
} |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
handleContextMenuFavorite = async () => { |
|
const promises = []; |
|
|
|
for (const characterId of this.selectedCharacters) { |
|
promises.push(CharacterContextMenu.favorite(characterId)); |
|
} |
|
|
|
await Promise.allSettled(promises); |
|
await getCharacters(); |
|
await favsToHotswap(); |
|
this.browseState(); |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
handleContextMenuDuplicate = () => Promise.all(this.selectedCharacters.map(async characterId => CharacterContextMenu.duplicate(characterId))) |
|
.then(() => getCharacters()) |
|
.then(() => this.browseState()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
handleContextMenuPersona = async () => { |
|
for (const characterId of this.selectedCharacters) { |
|
await CharacterContextMenu.persona(characterId); |
|
} |
|
|
|
this.browseState(); |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static #getDeletePopupContentHtml = (characterIds) => { |
|
return ` |
|
<h3 class="marginBot5">Delete ${characterIds.length} characters?</h3> |
|
<span class="bulk_delete_note"> |
|
<i class="fa-solid fa-triangle-exclamation warning margin-r5"></i> |
|
<b>THIS IS PERMANENT!</b> |
|
</span> |
|
<div id="bulk_delete_avatars_block" class="avatars_inline avatars_inline_small tags tags_inline m-t-1"></div> |
|
<br> |
|
<div id="bulk_delete_options" class="m-b-1"> |
|
<label for="del_char_checkbox" class="checkbox_label justifyCenter"> |
|
<input type="checkbox" id="del_char_checkbox" /> |
|
<span>Also delete the chat files</span> |
|
</label> |
|
</div>`; |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
handleContextMenuDelete = () => { |
|
const characterIds = this.selectedCharacters; |
|
const popupContent = BulkEditOverlay.#getDeletePopupContentHtml(characterIds); |
|
const promise = callPopup(popupContent, null) |
|
.then((accept) => { |
|
if (true !== accept) return; |
|
|
|
const deleteChats = document.getElementById('del_char_checkbox').checked ?? false; |
|
|
|
showLoader(); |
|
const toast = toastr.info('We\'re deleting your characters, please wait...', 'Working on it'); |
|
const avatarList = characterIds.map(id => characters[id]?.avatar).filter(a => a); |
|
return CharacterContextMenu.delete(avatarList, deleteChats) |
|
.then(() => this.browseState()) |
|
.finally(() => { |
|
toastr.clear(toast); |
|
hideLoader(); |
|
}); |
|
}); |
|
|
|
|
|
const entities = characterIds.map(id => characterToEntity(characters[id], id)).filter(entity => entity.item !== undefined); |
|
buildAvatarList($('#bulk_delete_avatars_block'), entities); |
|
|
|
return promise; |
|
}; |
|
|
|
|
|
|
|
|
|
handleContextMenuTag = () => { |
|
CharacterContextMenu.tag(this.selectedCharacters); |
|
this.browseState(); |
|
}; |
|
|
|
addStateChangeCallback = callback => this.stateChangeCallbacks.push(callback); |
|
|
|
|
|
|
|
|
|
|
|
clearSelectedCharacters = () => { |
|
document.querySelectorAll('#' + BulkEditOverlay.containerId + ' .' + BulkEditOverlay.selectedClass) |
|
.forEach(element => element.classList.remove(BulkEditOverlay.selectedClass)); |
|
this.selectedCharacters.length = 0; |
|
}; |
|
} |
|
|
|
export { BulkEditOverlayState, CharacterContextMenu, BulkEditOverlay }; |
|
|