| | 'use strict'; |
| |
|
| | import { |
| | characterGroupOverlay, |
| | 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 { callGenericPopup, POPUP_TYPE } from './popup.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) => void(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.toString()); |
| | } |
| | } |
| |
|
| | $('#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; |
| | const [x, y] = this.#getContextMenuPosition(event); |
| | CharacterContextMenu.show(x, y); |
| | } |
| | } |
| |
|
| | 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(); |
| | const [x,y] = this.#getContextMenuPosition(event); |
| | CharacterContextMenu.show(x, y); |
| | this.#contextMenuOpen = true; |
| | }; |
| |
|
| | handleContextMenuHide = (event) => { |
| | let contextMenu = document.getElementById(BulkEditOverlay.contextMenuId); |
| | if (false === contextMenu.contains(event.target)) { |
| | CharacterContextMenu.hide(); |
| | this.#contextMenuOpen = false; |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | 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 checkbox = popupContent.find('#del_char_checkbox'); |
| | const promise = callGenericPopup(popupContent, POPUP_TYPE.CONFIRM) |
| | .then((accept) => { |
| | if (!accept) return; |
| |
|
| | const deleteChats = checkbox.prop('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 }; |
| |
|