Fix reprint loop on tag filters

- Fix endless loop if a tag was selected
- Tag selection is now saved, both 'selected' and 'excluded' (old state is lost though)
- Streamlined reprinting even more by refactoring bogus drilldown
This commit is contained in:
Wolfsblvt 2024-03-30 20:33:08 +01:00
parent 1ff40f0af4
commit 6fe7c1fdaf
2 changed files with 29 additions and 35 deletions

View File

@ -24,6 +24,7 @@ export const FILTER_STATES = {
EXCLUDED: { key: 'EXCLUDED', class: 'excluded' },
UNDEFINED: { key: 'UNDEFINED', class: 'undefined' },
};
export const DEFAULT_FILTER_STATE = FILTER_STATES.UNDEFINED.key;
/**
* Robust check if one state equals the other. It does not care whether it's the state key or the state value object.
@ -203,7 +204,7 @@ export class FilterHelper {
return this.filterDataByState(data, state, isFolder);
}
filterDataByState(data, state, filterFunc, { includeFolders } = {}) {
filterDataByState(data, state, filterFunc, { includeFolders = false } = {}) {
if (isFilterState(state, FILTER_STATES.SELECTED)) {
return data.filter(entity => filterFunc(entity) || (includeFolders && entity.type == 'tag'));
}

View File

@ -13,7 +13,7 @@ import {
event_types,
} from '../script.js';
// eslint-disable-next-line no-unused-vars
import { FILTER_TYPES, FILTER_STATES, isFilterState, FilterHelper } from './filters.js';
import { FILTER_TYPES, FILTER_STATES, DEFAULT_FILTER_STATE, isFilterState, FilterHelper } from './filters.js';
import { groupCandidatesFilter, groups, selected_group } from './group-chats.js';
import { download, onlyUnique, parseJsonFile, uuidv4, getSortableDelay, debounce } from './utils.js';
@ -180,6 +180,7 @@ function isBogusFolderOpen() {
/**
* Function to be called when a specific tag/folder is chosen to "drill down".
*
* @param {*} source The jQuery element clicked when choosing the folder
* @param {string} tagId The tag id that is behind the chosen folder
* @param {boolean} remove Whether the given tag should be removed (otherwise it is added/chosen)
@ -197,12 +198,9 @@ function chooseBogusFolder(source, tagId, remove = false) {
// Instead of manually updating the filter conditions, we just "click" on the filter tag
// We search inside which filter block we are located in and use that one
const FILTER_SELECTOR = ($(source).closest('#rm_characters_block') ?? $(source).closest('#rm_group_chats_block')).find('.rm_tag_filter');
if (remove) {
// Click twice to skip over the 'excluded' state
$(FILTER_SELECTOR).find(`.tag[id=${tagId}]`).trigger('click').trigger('click');
} else {
$(FILTER_SELECTOR).find(`.tag[id=${tagId}]`).trigger('click');
}
const tagElement = $(FILTER_SELECTOR).find(`.tag[id=${tagId}]`);
toggleTagThreeState(tagElement, { stateOverride: DEFAULT_FILTER_STATE, simulateClick: true });
}
/**
@ -603,8 +601,8 @@ function appendTagToList(listElement, tag, { removable = false, selectable = fal
tagElement.find('.tag_name').text('').attr('title', tag.name).addClass(tag.icon);
}
if (tag.excluded && isGeneralList) {
toggleTagThreeState(tagElement, { stateOverride: FILTER_STATES.EXCLUDED });
if (selectable && isGeneralList) {
toggleTagThreeState(tagElement, { stateOverride: tag.filterState ?? DEFAULT_FILTER_STATE });
}
if (selectable) {
@ -629,34 +627,28 @@ function onTagFilterClick(listElement) {
let state = toggleTagThreeState($(this));
// Manual undefined check required for three-state boolean
if (existingTag) {
existingTag.excluded = isFilterState(state, FILTER_STATES.EXCLUDED);
existingTag.filterState = state;
saveSettingsDebounced();
}
// Update bogus folder if applicable
if (isBogusFolder(existingTag)) {
// Update bogus drilldown
if ($(this).hasClass('selected')) {
appendTagToList($('.rm_tag_controls .rm_tag_bogus_drilldown'), existingTag, { removable: true });
} else {
$(listElement).closest('.rm_tag_controls').find(`.rm_tag_bogus_drilldown .tag[id=${tagId}]`).remove();
}
}
// We don't print anything manually, updating the filter will automatically trigger a redraw of all relevant stuff
runTagFilters(listElement);
updateTagFilterIndicator();
}
function toggleTagThreeState(element, { stateOverride = undefined, simulateClick = false } = {}) {
const states = Object.keys(FILTER_STATES);
// Make it clear we're getting indexes and handling the 'not found' case in one place
function getStateIndex(key, fallback) {
const index = states.indexOf(key);
return index !== -1 ? index : states.indexOf(fallback);
}
const overrideKey = states.includes(stateOverride) ? stateOverride : Object.keys(FILTER_STATES).find(key => FILTER_STATES[key] === stateOverride);
const currentStateIndex = states.indexOf(element.attr('data-toggle-state')) ?? states.length - 1;
const targetStateIndex = overrideKey !== undefined ? states.indexOf(overrideKey) : (currentStateIndex + 1) % states.length;
const currentStateIndex = getStateIndex(element.attr('data-toggle-state'), DEFAULT_FILTER_STATE);
const targetStateIndex = overrideKey !== undefined ? getStateIndex(overrideKey, DEFAULT_FILTER_STATE) : (currentStateIndex + 1) % states.length;
if (simulateClick) {
// Calculate how many clicks are needed to go from the current state to the target state
@ -695,10 +687,8 @@ function runTagFilters(listElement) {
}
function printTagFilters(type = tag_filter_types.character) {
const filterData = structuredClone(entitiesFilter.getFilterData(FILTER_TYPES.TAG));
const FILTER_SELECTOR = type === tag_filter_types.character ? CHARACTER_FILTER_SELECTOR : GROUP_FILTER_SELECTOR;
$(FILTER_SELECTOR).empty();
$(FILTER_SELECTOR).siblings('.rm_tag_bogus_drilldown').empty();
// Print all action tags. (Exclude folder if that setting isn't chosen)
const actionTags = Object.values(ACTIONABLE_TAGS).filter(tag => power_user.bogus_folders || tag.id != ACTIONABLE_TAGS.FOLDER.id);
@ -708,18 +698,21 @@ function printTagFilters(type = tag_filter_types.character) {
printTagList($(FILTER_SELECTOR), { empty: false, tags: inListActionTags, tagActionSelector: tag => tag.action, tagOptions: { isGeneralList: true } });
const characterTagIds = Object.values(tag_map).flat();
const tagsToDisplay = tags
.filter(x => characterTagIds.includes(x.id))
.sort(compareTagsForSort);
const tagsToDisplay = tags.filter(x => characterTagIds.includes(x.id)).sort(compareTagsForSort);
printTagList($(FILTER_SELECTOR), { empty: false, tags: tagsToDisplay, tagOptions: { selectable: true, isGeneralList: true } });
runTagFilters(FILTER_SELECTOR);
// Print bogus folder navigation
const bogusDrilldown = $(FILTER_SELECTOR).siblings('.rm_tag_bogus_drilldown');
bogusDrilldown.empty();
if (power_user.bogus_folders && bogusDrilldown.length > 0) {
const filterData = structuredClone(entitiesFilter.getFilterData(FILTER_TYPES.TAG));
const navigatedTags = filterData.selected.map(x => tags.find(t => t.id == x)).filter(x => isBogusFolder(x));
// Simulate clicks on all "selected" tags when we reprint, otherwise their filter gets lost. "excluded" is persisted.
for (const tagId of filterData.selected) {
toggleTagThreeState($(`${FILTER_SELECTOR} .tag[id="${tagId}"]`), { stateOverride: FILTER_STATES.SELECTED, simulateClick: true });
printTagList(bogusDrilldown, { tags: navigatedTags, tagOptions: { removable: true } });
}
runTagFilters(FILTER_SELECTOR);
if (power_user.show_tag_filters) {
$('.rm_tag_controls .showTagList').addClass('selected');
$('.rm_tag_controls').find('.tag:not(.actionable)').show();