Merge pull request #1337 from artisticMink/hotfix/bulk-edit-enhanced

Fixes for bulk editing overlay
This commit is contained in:
Cohee 2023-11-10 00:10:27 +02:00 committed by GitHub
commit bc2b3e9c4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 111 additions and 36 deletions

View File

@ -1,7 +1,3 @@
#rm_print_characters_block .character_select,
#rm_print_characters_block .group_select{
cursor: pointer;
}
#rm_print_characters_block.group_overlay_mode_select .character_select { #rm_print_characters_block.group_overlay_mode_select .character_select {
transition: background-color 0.4s ease; transition: background-color 0.4s ease;
@ -9,6 +5,15 @@
background-color: rgba(170, 170, 170, 0.15); background-color: rgba(170, 170, 170, 0.15);
} }
#rm_print_characters_block.group_overlay_mode_select .group_select {
cursor: auto;
filter: saturate(0.3);
}
#rm_print_characters_block.group_overlay_mode_select .group_select:hover {
background: none;
}
#rm_print_characters_block.group_overlay_mode_select .character_select input.bulk_select_checkbox { #rm_print_characters_block.group_overlay_mode_select .character_select input.bulk_select_checkbox {
display: none !important; display: none !important;
} }
@ -26,7 +31,7 @@
#character_context_menu { #character_context_menu {
position: absolute; position: absolute;
padding: 3px; padding: 3px;
z-index: 10000; z-index: 9998;
background-color: var(--black90a); background-color: var(--black90a);
border: 1px solid var(--black90a); border: 1px solid var(--black90a);
border-radius: 10px; border-radius: 10px;

View File

@ -93,11 +93,11 @@
<div id="bg1"></div> <div id="bg1"></div>
<div id="character_context_menu" class="hidden"> <div id="character_context_menu" class="hidden">
<ul> <ul>
<li><button id="character_context_menu_favorite">Favorite</button></li> <li><button id="character_context_menu_favorite" data-i18n="Favorite">Favorite</button></li>
<li><button id="character_context_menu_tag">Tag</button></li> <li><button id="character_context_menu_tag" data-i18n="Tag">Tag</button></li>
<li><button id="character_context_menu_duplicate">Duplicate</button></li> <li><button id="character_context_menu_duplicate" data-i18n="Duplicate">Duplicate</button></li>
<li><button id="character_context_menu_persona">Persona</button></li> <li><button id="character_context_menu_persona" data-i18n="Persona">Persona</button></li>
<li><button id="character_context_menu_delete">Delete</button></li> <li><button id="character_context_menu_delete" data-i18n="Delete">Delete</button></li>
</ul> </ul>
</div> </div>
<!-- top bar central settings buttons --> <!-- top bar central settings buttons -->

View File

@ -159,6 +159,15 @@ class CharacterContextMenu {
contextMenu.style.top = `${positionY}px`; contextMenu.style.top = `${positionY}px`;
document.getElementById(BulkEditOverlay.contextMenuId).classList.remove('hidden'); document.getElementById(BulkEditOverlay.contextMenuId).classList.remove('hidden');
// Adjust position if context menu is outside of viewport
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`;
}
} }
/** /**
@ -277,6 +286,7 @@ class BulkEditOverlay {
static containerId = 'rm_print_characters_block'; static containerId = 'rm_print_characters_block';
static contextMenuId = 'character_context_menu'; static contextMenuId = 'character_context_menu';
static characterClass = 'character_select'; static characterClass = 'character_select';
static groupClass = 'group_select';
static selectModeClass = 'group_overlay_mode_select'; static selectModeClass = 'group_overlay_mode_select';
static selectedClass = 'character_selected'; static selectedClass = 'character_selected';
static legacySelectedClass = 'bulk_select_checkbox'; static legacySelectedClass = 'bulk_select_checkbox';
@ -288,6 +298,20 @@ class BulkEditOverlay {
#stateChangeCallbacks = []; #stateChangeCallbacks = [];
#selectedCharacters = []; #selectedCharacters = [];
/**
* Locks other pointer actions when the context menu is open
*
* @type {boolean}
*/
#contextMenuOpen = false;
/**
* Whether the next character select should be skipped
*
* @type {boolean}
*/
#cancelNextToggle = false;
/** /**
* @type HTMLElement * @type HTMLElement
*/ */
@ -332,7 +356,6 @@ class BulkEditOverlay {
return bulkEditOverlayInstance return bulkEditOverlayInstance
this.container = document.getElementById(BulkEditOverlay.containerId); this.container = document.getElementById(BulkEditOverlay.containerId);
this.container.addEventListener('click', this.handleCancelClick);
eventSource.on(event_types.CHARACTER_GROUP_OVERLAY_STATE_CHANGE_AFTER, this.handleStateChange); eventSource.on(event_types.CHARACTER_GROUP_OVERLAY_STATE_CHANGE_AFTER, this.handleStateChange);
bulkEditOverlayInstance = Object.freeze(this); bulkEditOverlayInstance = Object.freeze(this);
@ -364,8 +387,9 @@ class BulkEditOverlay {
elements.forEach(element => element.addEventListener('dragend', this.handleLongPressEnd)); elements.forEach(element => element.addEventListener('dragend', this.handleLongPressEnd));
elements.forEach(element => element.addEventListener('touchmove', this.handleLongPressEnd)); elements.forEach(element => element.addEventListener('touchmove', this.handleLongPressEnd));
const grid = document.getElementById(BulkEditOverlay.containerId); // Cohee: It only triggers when clicking on a margin between the elements?
grid.addEventListener('click', this.handleCancelClick); // Feel free to fix or remove this, I'm not sure how to.
//this.container.addEventListener('click', this.handleCancelClick);
} }
/** /**
@ -377,7 +401,9 @@ class BulkEditOverlay {
switch (this.state) { switch (this.state) {
case BulkEditOverlayState.browse: case BulkEditOverlayState.browse:
this.container.classList.remove(BulkEditOverlay.selectModeClass); this.container.classList.remove(BulkEditOverlay.selectModeClass);
this.#contextMenuOpen = false;
this.#enableClickEventsForCharacters(); this.#enableClickEventsForCharacters();
this.#enableClickEventsForGroups();
this.clearSelectedCharacters(); this.clearSelectedCharacters();
this.disableContextMenu(); this.disableContextMenu();
this.#disableBulkEditButtonHighlight(); this.#disableBulkEditButtonHighlight();
@ -386,6 +412,7 @@ class BulkEditOverlay {
case BulkEditOverlayState.select: case BulkEditOverlayState.select:
this.container.classList.add(BulkEditOverlay.selectModeClass); this.container.classList.add(BulkEditOverlay.selectModeClass);
this.#disableClickEventsForCharacters(); this.#disableClickEventsForCharacters();
this.#disableClickEventsForGroups();
this.enableContextMenu(); this.enableContextMenu();
this.#enableBulkEditButtonHighlight(); this.#enableBulkEditButtonHighlight();
break; break;
@ -399,7 +426,7 @@ class BulkEditOverlay {
* set a click event to hide the custom context menu. * set a click event to hide the custom context menu.
*/ */
enableContextMenu = () => { enableContextMenu = () => {
document.getElementById('rm_print_characters_block').addEventListener('contextmenu', this.handleContextMenuShow); this.container.addEventListener('contextmenu', this.handleContextMenuShow);
document.addEventListener('click', this.handleContextMenuHide); document.addEventListener('click', this.handleContextMenuHide);
} }
@ -408,7 +435,7 @@ class BulkEditOverlay {
* menu to be opened. * menu to be opened.
*/ */
disableContextMenu = () => { disableContextMenu = () => {
document.getElementById('rm_print_characters_block').removeEventListener('contextmenu', this.handleContextMenuShow); this.container.removeEventListener('contextmenu', this.handleContextMenuShow);
document.removeEventListener('click', this.handleContextMenuHide); document.removeEventListener('click', this.handleContextMenuHide);
} }
@ -420,26 +447,52 @@ class BulkEditOverlay {
} }
} }
/**
* Opens menu on long-press.
*
* @param event - Pointer event
*/
handleHold = (event) => { handleHold = (event) => {
if (0 !== event.button && event.type !== 'touchstart') return; 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; this.isLongPress = true;
setTimeout(() => { setTimeout(() => {
if (this.isLongPress) { if (this.isLongPress && !cancel) {
if (this.state === BulkEditOverlayState.browse) if (this.state === BulkEditOverlayState.browse) {
this.selectState(); this.selectState();
else if (this.state === BulkEditOverlayState.select) } else if (this.state === BulkEditOverlayState.select) {
CharacterContextMenu.show(...this.#getContextMenuPosition(event)); this.#contextMenuOpen = true;
} CharacterContextMenu.show(...this.#getContextMenuPosition(event));
}, BulkEditOverlay.longPressDelay); }
}
this.container.removeEventListener('mouseup', cancelHold);
this.container.removeEventListener('touchend', cancelHold);
},
BulkEditOverlay.longPressDelay);
} }
handleLongPressEnd = () => { handleLongPressEnd = (event) => {
this.isLongPress = false; this.isLongPress = false;
if (this.#contextMenuOpen) event.stopPropagation();
} }
handleCancelClick = () => { handleCancelClick = () => {
this.state = BulkEditOverlayState.browse; if (false === this.#contextMenuOpen) this.state = BulkEditOverlayState.browse;
this.#contextMenuOpen = false;
} }
/** /**
@ -453,6 +506,17 @@ class BulkEditOverlay {
event.clientY || event.touches[0].clientY, 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)); #enableClickEventsForCharacters = () => this.#getEnabledElements().forEach(element => element.removeEventListener('click', this.toggleCharacterSelected));
#disableClickEventsForCharacters = () => this.#getEnabledElements().forEach(element => element.addEventListener('click', this.toggleCharacterSelected)); #disableClickEventsForCharacters = () => this.#getEnabledElements().forEach(element => element.addEventListener('click', this.toggleCharacterSelected));
@ -463,6 +527,8 @@ class BulkEditOverlay {
#getEnabledElements = () => [...this.container.getElementsByClassName(BulkEditOverlay.characterClass)]; #getEnabledElements = () => [...this.container.getElementsByClassName(BulkEditOverlay.characterClass)];
#getDisabledElements = () =>[...this.container.getElementsByClassName(BulkEditOverlay.groupClass)];
toggleCharacterSelected = event => { toggleCharacterSelected = event => {
event.stopPropagation(); event.stopPropagation();
@ -473,25 +539,28 @@ class BulkEditOverlay {
const legacyBulkEditCheckbox = character.querySelector('.' + BulkEditOverlay.legacySelectedClass); const legacyBulkEditCheckbox = character.querySelector('.' + BulkEditOverlay.legacySelectedClass);
if (alreadySelected) { // Only toggle when context menu is closed and wasn't just closed.
character.classList.remove(BulkEditOverlay.selectedClass); if (!this.#contextMenuOpen && !this.#cancelNextToggle)
if (legacyBulkEditCheckbox) legacyBulkEditCheckbox.checked = false; if (alreadySelected) {
this.dismissCharacter(characterId); character.classList.remove(BulkEditOverlay.selectedClass);
} else { if (legacyBulkEditCheckbox) legacyBulkEditCheckbox.checked = false;
character.classList.add(BulkEditOverlay.selectedClass) this.dismissCharacter(characterId);
if (legacyBulkEditCheckbox) legacyBulkEditCheckbox.checked = true; } else {
this.selectCharacter(characterId); character.classList.add(BulkEditOverlay.selectedClass)
} if (legacyBulkEditCheckbox) legacyBulkEditCheckbox.checked = true;
this.selectCharacter(characterId);
}
this.#cancelNextToggle = false;
} }
handleContextMenuShow = (event) => { handleContextMenuShow = (event) => {
event.preventDefault(); event.preventDefault();
document.getElementById(BulkEditOverlay.containerId).style.pointerEvents = 'none';
CharacterContextMenu.show(...this.#getContextMenuPosition(event)); CharacterContextMenu.show(...this.#getContextMenuPosition(event));
this.#contextMenuOpen = true;
} }
handleContextMenuHide = (event) => { handleContextMenuHide = (event) => {
document.getElementById(BulkEditOverlay.containerId).style.pointerEvents = '';
let contextMenu = document.getElementById(BulkEditOverlay.contextMenuId); let contextMenu = document.getElementById(BulkEditOverlay.contextMenuId);
if (false === contextMenu.contains(event.target)) { if (false === contextMenu.contains(event.target)) {
CharacterContextMenu.hide(); CharacterContextMenu.hide();

View File

@ -10,7 +10,7 @@ import {
import { FILTER_TYPES, FilterHelper } from "./filters.js"; import { FILTER_TYPES, FilterHelper } from "./filters.js";
import { groupCandidatesFilter, selected_group } from "./group-chats.js"; import { groupCandidatesFilter, selected_group } from "./group-chats.js";
import { uuidv4 } from "./utils.js"; import { onlyUnique, uuidv4 } from "./utils.js";
export { export {
tags, tags,
@ -153,6 +153,7 @@ function addTagToMap(tagId, characterId = null) {
} }
else { else {
tag_map[key].push(tagId); tag_map[key].push(tagId);
tag_map[key] = tag_map[key].filter(onlyUnique);
} }
} }