Compare commits

...

8 Commits

Author SHA1 Message Date
Cohee
e08d1b522c Don't destroy sortable manually 2025-06-05 08:45:31 +00:00
Cohee
62fdce8cad Replace select2 detection 2025-06-05 08:44:55 +00:00
Cohee
bbe62527d2 Remove pointless DOM call 2025-06-05 08:40:01 +00:00
Cohee
b7323715b2 Prevent saveWorldInfo calls while rendering the list 2025-06-05 08:33:38 +00:00
Cohee
0f9cd5f48d Remove debug logs with DOM queries 2025-06-05 08:25:50 +00:00
RossAscends
e1a1fdb0da dont reload editor list on world list tag click if that world is already displayed and open in the editor 2025-06-05 16:13:27 +09:00
RossAscends
42b2637707 await the fade in.. 2025-06-05 15:51:11 +09:00
RossAscends
b55de85243 Removes memory heap bloat by improving DOM and JQUI datacache clearing when swapping WI in the editor, and all other 'reloadEditor' calls via slashcommands etc.
Results in a slight delay depending on entries visible via pagination (about 2s for 50 visible entries), but I added a pleasant fade transition to make it feel organic.
2025-06-05 15:32:33 +09:00

View File

@@ -1,7 +1,7 @@
import { Fuse } from '../lib.js';
import { saveSettings, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles } from '../script.js';
import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath, flashHighlight, select2ModifyOptions, getSelect2OptionId, dynamicSelect2DataViaAjax, highlightRegex, select2ChoiceClickSubscribe, isFalseBoolean, getSanitizedFilename, checkOverwriteExistingData, getStringHash, parseStringArray, cancelDebounce, findChar, onlyUnique, equalsIgnoreCaseAndAccents } from './utils.js';
import { download, debounce, delay, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath, flashHighlight, select2ModifyOptions, getSelect2OptionId, dynamicSelect2DataViaAjax, highlightRegex, select2ChoiceClickSubscribe, isFalseBoolean, getSanitizedFilename, checkOverwriteExistingData, getStringHash, parseStringArray, cancelDebounce, findChar, onlyUnique, equalsIgnoreCaseAndAccents } from './utils.js';
import { extension_settings, getContext } from './extensions.js';
import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js';
import { isMobile } from './RossAscends-mods.js';
@@ -85,6 +85,7 @@ const saveSettingsDebounced = debounce(() => {
}, debounce_timeout.relaxed);
const sortFn = (a, b) => b.order - a.order;
let updateEditor = (navigation, flashOnNav = true) => { console.debug('Triggered WI navigation', navigation, flashOnNav); };
let isSaveWorldInfoDisabled = false;
// Do not optimize. updateEditor is a function that is updated by the displayWorldEntries with new data.
export const worldInfoFilter = new FilterHelper(() => updateEditor());
@@ -1754,12 +1755,12 @@ function registerWorldInfoSlashCommands() {
*/
export async function showWorldEditor(name) {
if (!name) {
hideWorldEditor();
await hideWorldEditor();
return;
}
const wiData = await loadWorldInfo(name);
displayWorldEntries(name, wiData);
await displayWorldEntries(name, wiData);
}
/**
@@ -1815,8 +1816,8 @@ export async function updateWorldInfoList() {
}
}
function hideWorldEditor() {
displayWorldEntries(null, null);
async function hideWorldEditor() {
await displayWorldEntries(null, null);
}
function getWIElement(name) {
@@ -1937,15 +1938,75 @@ function updateWorldEntryKeyOptionsCache(keyOptions, { remove = false, reset = f
worldEntryKeyOptionsCache.sort((a, b) => b.count - a.count || a.text.localeCompare(b.text));
}
function displayWorldEntries(name, data, navigation = navigation_option.none, flashOnNav = true) {
updateEditor = (navigation, flashOnNav = true) => displayWorldEntries(name, data, navigation, flashOnNav);
function clearEntryList() {
console.time('clearEntryList');
const $list = $('#world_popup_entries_list');
if (!$list.children().length) {
console.debug('List already empty, skipping cleanup.');
console.timeEnd('clearEntryList');
return;
}
// Step 1: Clean all <option> elements within <select>
$list.find('option').each(function () {
const $option = $(this);
$option.off();
$.cleanData([$option[0]]);
$option.remove();
});
// Step 2: Clean all <select> elements
$list.find('select').each(function () {
const $select = $(this);
// Remove Select2-related data and container if present
if ($select.data('select2')) {
try {
$select.select2('destroy');
} catch (e) {
console.debug('Select2 destroy failed:', e);
}
}
const $container = $select.parent();
if ($container.length) {
$container.find('*').off();
$.cleanData($container.find('*').get());
$container.remove();
}
$select.off();
$.cleanData([$select[0]]);
});
// Step 3: Clean <div>, <span>, <input>
$list.find('div, span, input').each(function () {
const $elem = $(this);
$elem.off();
$.cleanData([$elem[0]]);
$elem.remove();
});
const totalElementsOfAnyKindLeftInList = $list.children().length;
// Final cleanup
if (totalElementsOfAnyKindLeftInList) {
console.time('empty');
$list.empty();
console.timeEnd('empty');
}
console.timeEnd('clearEntryList');
}
async function displayWorldEntries(name, data, navigation = navigation_option.none, flashOnNav = true) {
updateEditor = async (navigation, flashOnNav = true) => await displayWorldEntries(name, data, navigation, flashOnNav);
const worldEntriesList = $('#world_popup_entries_list');
// We save costly performance by removing all events before emptying. Because we know there are no relevant event handlers reacting on removing elements
// This prevents jQuery from actually going through all registered events on the controls for each entry when removing it
worldEntriesList.find('*').off();
worldEntriesList.empty().show();
worldEntriesList.css({ 'opacity': 0, 'transition': 'opacity 250ms ease-in-out' });
await delay(250);
clearEntryList();
worldEntriesList.show();
if (!data || !('entries' in data)) {
$('#world_popup_new').off('click').on('click', nullWorldInfo);
@@ -2038,22 +2099,39 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
formatNavigator: PAGINATION_TEMPLATE,
showNavigator: true,
callback: async function (/** @type {object[]} */ page) {
// We save costly performance by removing all events before emptying. Because we know there are no relevant event handlers reacting on removing elements
// This prevents jQuery from actually going through all registered events on the controls for each entry when removing it
worldEntriesList.find('*').off();
worldEntriesList.empty();
try {
// Prevent saveWorldInfo from firing timeouts while rendering the list
isSaveWorldInfoDisabled = true;
clearEntryList();
const keywordHeaders = await renderTemplateAsync('worldInfoKeywordHeaders');
const blocksPromises = page.map(async (entry) => await getWorldEntry(name, data, entry)).filter(x => x);
const blocks = await Promise.all(blocksPromises);
const isCustomOrder = $('#world_info_sort_order').find(':selected').data('rule') === 'custom';
if (!isCustomOrder) {
blocks.forEach(block => {
block.find('.drag-handle').remove();
});
const keywordHeaders = await renderTemplateAsync('worldInfoKeywordHeaders');
const blocks = [];
for (const entry of page) {
try {
const block = await getWorldEntry(name, data, entry);
if (block) {
blocks.push(block);
}
} catch (error) {
console.error(`Error while processing entry ${entry.uid}:`, error);
}
}
const isCustomOrder = $('#world_info_sort_order').find(':selected').data('rule') === 'custom';
if (!isCustomOrder) {
blocks.forEach(block => {
block.find('.drag-handle').remove();
});
}
worldEntriesList.append(keywordHeaders);
worldEntriesList.append(blocks);
} catch (error) {
console.error('Error while rendering WI entries:', error);
} finally {
isSaveWorldInfoDisabled = false;
}
worldEntriesList.append(keywordHeaders);
worldEntriesList.append(blocks);
},
afterSizeSelectorChange: function (e) {
accountStorage.setItem(storageKey, e.target.value);
@@ -2173,7 +2251,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
if (selectedIndex !== -1) {
$('#world_editor_select').val(selectedIndex).trigger('change');
} else {
hideWorldEditor();
await hideWorldEditor();
}
}
});
@@ -2211,6 +2289,12 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
await saveWorldInfo(name, data);
},
});
worldEntriesList.css('opacity', '1');
await delay(250);
worldEntriesList.removeAttr('style');
//$("#world_popup_entries_list").disableSelection();
}
@@ -3266,7 +3350,7 @@ export async function getWorldEntry(name, data, entry) {
container.appendChild(select);
let selectedWorldIndex = -1;
select.addEventListener('change', function() {
select.addEventListener('change', function () {
selectedWorldIndex = this.value === '' ? -1 : Number(this.value);
});
@@ -3661,6 +3745,11 @@ async function _save(name, data) {
* @return {Promise<void>} A promise that resolves when the world info is saved
*/
export async function saveWorldInfo(name, data, immediately = false) {
// Saving is temporarily disabled
if (isSaveWorldInfoDisabled) {
return;
}
if (!name || !data) {
return;
}
@@ -3819,7 +3908,7 @@ export async function createNewWorldInfo(worldName, { interactive = false } = {}
if (selectedIndex !== -1) {
$('#world_editor_select').val(selectedIndex).trigger('change');
} else {
hideWorldEditor();
await hideWorldEditor();
}
return true;
@@ -5385,7 +5474,7 @@ export function initWorldInfo() {
const selectedIndex = String($('#world_editor_select').find(':selected').val());
if (selectedIndex === '') {
hideWorldEditor();
await hideWorldEditor();
} else {
const worldName = world_names[selectedIndex];
showWorldEditor(worldName);
@@ -5537,9 +5626,12 @@ export function initWorldInfo() {
select2ChoiceClickSubscribe($('#world_info'), target => {
const name = $(target).text();
const selectedIndex = world_names.indexOf(name);
if (selectedIndex !== -1) {
const alreadySelectedInEditor = $('#world_editor_select option:selected').text() === name;
if (selectedIndex !== -1 && !alreadySelectedInEditor) {
$('#world_editor_select').val(selectedIndex).trigger('change');
console.log('Quick selection of world', name);
} else {
console.warn('lets not reload an already loaded list yes?');
}
}, { buttonStyle: true, closeDrawer: true });
}