mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-08-16 02:33:21 -03:00
refactor(sort): extract seeded random sort helpers into SortDropdown
This commit is contained in:
@@ -4,7 +4,7 @@ import { getStorageItem, setStorageItem, removeStorageItem, getSessionItem, setS
|
|||||||
import { showToast, openCivitaiByMetadata } from '../../utils/uiHelpers.js';
|
import { showToast, openCivitaiByMetadata } from '../../utils/uiHelpers.js';
|
||||||
import { performModelUpdateCheck } from '../../utils/updateCheckHelpers.js';
|
import { performModelUpdateCheck } from '../../utils/updateCheckHelpers.js';
|
||||||
import { sidebarManager } from '../SidebarManager.js';
|
import { sidebarManager } from '../SidebarManager.js';
|
||||||
import { initSortDropdown } from './SortDropdown.js';
|
import { initSortDropdown, applySortToSelect, randomizeSortValue } from './SortDropdown.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PageControls class - Unified control management for model pages
|
* PageControls class - Unified control management for model pages
|
||||||
@@ -108,20 +108,20 @@ export class PageControls {
|
|||||||
const sortSelect = document.getElementById('sortSelect');
|
const sortSelect = document.getElementById('sortSelect');
|
||||||
if (sortSelect) {
|
if (sortSelect) {
|
||||||
initSortDropdown(sortSelect);
|
initSortDropdown(sortSelect);
|
||||||
this.applySortToSelect(this.pageState.sortBy);
|
applySortToSelect(this.pageState.sortBy);
|
||||||
sortSelect.addEventListener('change', async (e) => {
|
sortSelect.addEventListener('change', async (e) => {
|
||||||
let value = e.target.value;
|
let value = e.target.value;
|
||||||
if (value.startsWith('random')) {
|
if (value.startsWith('random')) {
|
||||||
// Every pick of Random reshuffles the list: generate a
|
// Every pick of Random reshuffles the list: generate a
|
||||||
// fresh seed so the backend keeps a stable order across
|
// fresh seed so the backend keeps a stable order across
|
||||||
// paginated requests.
|
// paginated requests.
|
||||||
value = this._randomizeSortValue();
|
value = randomizeSortValue();
|
||||||
}
|
}
|
||||||
this.pageState.sortBy = value;
|
this.pageState.sortBy = value;
|
||||||
this.saveSortPreference(value);
|
this.saveSortPreference(value);
|
||||||
// Reset the seeded Random option when switching away from
|
// Reset the seeded Random option when switching away from
|
||||||
// Random, or re-apply the fresh seed when picking it again.
|
// Random, or re-apply the fresh seed when picking it again.
|
||||||
this.applySortToSelect(value);
|
applySortToSelect(value);
|
||||||
await this.resetAndReload();
|
await this.resetAndReload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -322,44 +322,6 @@ export class PageControls {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply a sort value to the native sort <select>, keeping the Random
|
|
||||||
* option's value in sync when the persisted value carries a seed
|
|
||||||
* (e.g. "random:abc123"). Must be used instead of assigning
|
|
||||||
* sortSelect.value directly whenever the value may be a seeded random
|
|
||||||
* sort, otherwise the native select has no matching option.
|
|
||||||
* @param {string} sortValue - Sort value like "name:asc" or "random:<seed>"
|
|
||||||
*/
|
|
||||||
applySortToSelect(sortValue) {
|
|
||||||
const sortSelect = document.getElementById('sortSelect');
|
|
||||||
if (!sortSelect) return;
|
|
||||||
const randomOpt = sortSelect.querySelector('option[value="random"], option[value^="random:"]');
|
|
||||||
if (randomOpt) {
|
|
||||||
randomOpt.value = String(sortValue).startsWith('random') ? sortValue : 'random';
|
|
||||||
}
|
|
||||||
sortSelect.value = sortValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a fresh seeded random sort value ("random:<seed>") and keep
|
|
||||||
* the native <select> in sync so its value matches the persisted sort
|
|
||||||
* string and the dropdown shows the selected label.
|
|
||||||
* @returns {string} The new sort value, e.g. "random:abc123xyz"
|
|
||||||
*/
|
|
||||||
_randomizeSortValue() {
|
|
||||||
const seed = Math.random().toString(36).slice(2, 12);
|
|
||||||
const value = `random:${seed}`;
|
|
||||||
const sortSelect = document.getElementById('sortSelect');
|
|
||||||
if (sortSelect) {
|
|
||||||
const randomOpt = sortSelect.querySelector('option[value="random"], option[value^="random:"]');
|
|
||||||
if (randomOpt) {
|
|
||||||
randomOpt.value = value;
|
|
||||||
}
|
|
||||||
sortSelect.value = value;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load sort preference from storage
|
* Load sort preference from storage
|
||||||
*/
|
*/
|
||||||
@@ -374,7 +336,7 @@ export class PageControls {
|
|||||||
// Handle legacy format conversion
|
// Handle legacy format conversion
|
||||||
const convertedSort = this.convertLegacySortFormat(savedSort);
|
const convertedSort = this.convertLegacySortFormat(savedSort);
|
||||||
this.pageState.sortBy = convertedSort;
|
this.pageState.sortBy = convertedSort;
|
||||||
this.applySortToSelect(convertedSort);
|
applySortToSelect(convertedSort);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -568,7 +530,7 @@ export class PageControls {
|
|||||||
this.pageState.sortBy = restoredSort;
|
this.pageState.sortBy = restoredSort;
|
||||||
this.saveSortPreference(restoredSort);
|
this.saveSortPreference(restoredSort);
|
||||||
this._removeVlmSortOption();
|
this._removeVlmSortOption();
|
||||||
this.applySortToSelect(restoredSort);
|
applySortToSelect(restoredSort);
|
||||||
const sortSelect = document.getElementById('sortSelect');
|
const sortSelect = document.getElementById('sortSelect');
|
||||||
if (sortSelect) {
|
if (sortSelect) {
|
||||||
sortSelect.disabled = false;
|
sortSelect.disabled = false;
|
||||||
@@ -620,7 +582,7 @@ export class PageControls {
|
|||||||
const savedGroupedSort = getStorageItem(groupedKey);
|
const savedGroupedSort = getStorageItem(groupedKey);
|
||||||
if (savedGroupedSort) {
|
if (savedGroupedSort) {
|
||||||
this.pageState.sortBy = savedGroupedSort;
|
this.pageState.sortBy = savedGroupedSort;
|
||||||
this.applySortToSelect(savedGroupedSort);
|
applySortToSelect(savedGroupedSort);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Leaving group mode: persist current sort for next time, restore non-group sort
|
// Leaving group mode: persist current sort for next time, restore non-group sort
|
||||||
@@ -628,7 +590,7 @@ export class PageControls {
|
|||||||
const savedNormalSort = getStorageItem(`${this.pageType}_sort`);
|
const savedNormalSort = getStorageItem(`${this.pageType}_sort`);
|
||||||
if (savedNormalSort) {
|
if (savedNormalSort) {
|
||||||
this.pageState.sortBy = savedNormalSort;
|
this.pageState.sortBy = savedNormalSort;
|
||||||
this.applySortToSelect(savedNormalSort);
|
applySortToSelect(savedNormalSort);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -913,7 +875,7 @@ export class PageControls {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sortSelect) {
|
if (sortSelect) {
|
||||||
this.applySortToSelect(this.pageState.sortBy);
|
applySortToSelect(this.pageState.sortBy);
|
||||||
}
|
}
|
||||||
if (searchInput) {
|
if (searchInput) {
|
||||||
searchInput.value = this.pageState.filters?.search || '';
|
searchInput.value = this.pageState.filters?.search || '';
|
||||||
|
|||||||
@@ -18,6 +18,44 @@
|
|||||||
const SORT_GROUP_SELECTOR = '.sort-dropdown-group';
|
const SORT_GROUP_SELECTOR = '.sort-dropdown-group';
|
||||||
const ACTIVE_GROUP_SELECTOR = '.sort-dropdown-group.active, .dropdown-group.active';
|
const ACTIVE_GROUP_SELECTOR = '.sort-dropdown-group.active, .dropdown-group.active';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a sort value to the page's native sort <select>, keeping the Random
|
||||||
|
* option's value in sync when the persisted value carries a seed
|
||||||
|
* (e.g. "random:abc123"). Must be used instead of assigning
|
||||||
|
* sortSelect.value directly whenever the value may be a seeded random
|
||||||
|
* sort, otherwise the native select has no matching option.
|
||||||
|
* @param {string} sortValue - Sort value like "name:asc" or "random:<seed>"
|
||||||
|
*/
|
||||||
|
export function applySortToSelect(sortValue) {
|
||||||
|
const sortSelect = document.getElementById('sortSelect');
|
||||||
|
if (!sortSelect) return;
|
||||||
|
const randomOpt = sortSelect.querySelector('option[value="random"], option[value^="random:"]');
|
||||||
|
if (randomOpt) {
|
||||||
|
randomOpt.value = String(sortValue).startsWith('random') ? sortValue : 'random';
|
||||||
|
}
|
||||||
|
sortSelect.value = sortValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a fresh seeded random sort value ("random:<seed>") and keep the
|
||||||
|
* native <select> in sync so its value matches the persisted sort string and
|
||||||
|
* the dropdown shows the selected label.
|
||||||
|
* @returns {string} The new sort value, e.g. "random:abc123xyz"
|
||||||
|
*/
|
||||||
|
export function randomizeSortValue() {
|
||||||
|
const seed = Math.random().toString(36).slice(2, 12);
|
||||||
|
const value = `random:${seed}`;
|
||||||
|
const sortSelect = document.getElementById('sortSelect');
|
||||||
|
if (sortSelect) {
|
||||||
|
const randomOpt = sortSelect.querySelector('option[value="random"], option[value^="random:"]');
|
||||||
|
if (randomOpt) {
|
||||||
|
randomOpt.value = value;
|
||||||
|
}
|
||||||
|
sortSelect.value = value;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize a decoupled sort dropdown around a native <select>.
|
* Initialize a decoupled sort dropdown around a native <select>.
|
||||||
* Idempotent: safe to call more than once on the same element.
|
* Idempotent: safe to call more than once on the same element.
|
||||||
|
|||||||
+4
-42
@@ -10,7 +10,7 @@ import { DuplicatesManager } from './components/DuplicatesManager.js';
|
|||||||
import { refreshVirtualScroll, recreateVirtualScroll } from './utils/infiniteScroll.js';
|
import { refreshVirtualScroll, recreateVirtualScroll } from './utils/infiniteScroll.js';
|
||||||
import { refreshRecipes, RecipeSidebarApiClient } from './api/recipeApi.js';
|
import { refreshRecipes, RecipeSidebarApiClient } from './api/recipeApi.js';
|
||||||
import { sidebarManager } from './components/SidebarManager.js';
|
import { sidebarManager } from './components/SidebarManager.js';
|
||||||
import { initSortDropdown } from './components/controls/SortDropdown.js';
|
import { initSortDropdown, applySortToSelect, randomizeSortValue } from './components/controls/SortDropdown.js';
|
||||||
|
|
||||||
class RecipePageControls {
|
class RecipePageControls {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -245,20 +245,20 @@ class RecipeManager {
|
|||||||
this.pageState.sortBy = savedSort;
|
this.pageState.sortBy = savedSort;
|
||||||
}
|
}
|
||||||
initSortDropdown(sortSelect);
|
initSortDropdown(sortSelect);
|
||||||
this.applySortToSelect(this.pageState.sortBy || 'date:desc');
|
applySortToSelect(this.pageState.sortBy || 'date:desc');
|
||||||
sortSelect.addEventListener('change', () => {
|
sortSelect.addEventListener('change', () => {
|
||||||
let value = sortSelect.value;
|
let value = sortSelect.value;
|
||||||
if (value.startsWith('random')) {
|
if (value.startsWith('random')) {
|
||||||
// Every pick of Random reshuffles the list: generate a
|
// Every pick of Random reshuffles the list: generate a
|
||||||
// fresh seed so the backend keeps a stable order across
|
// fresh seed so the backend keeps a stable order across
|
||||||
// paginated requests.
|
// paginated requests.
|
||||||
value = this._randomizeSortValue();
|
value = randomizeSortValue();
|
||||||
}
|
}
|
||||||
this.pageState.sortBy = value;
|
this.pageState.sortBy = value;
|
||||||
setStorageItem('recipes_sort', value);
|
setStorageItem('recipes_sort', value);
|
||||||
// Reset the seeded Random option when switching away from
|
// Reset the seeded Random option when switching away from
|
||||||
// Random, or re-apply the fresh seed when picking it again.
|
// Random, or re-apply the fresh seed when picking it again.
|
||||||
this.applySortToSelect(value);
|
applySortToSelect(value);
|
||||||
refreshVirtualScroll();
|
refreshVirtualScroll();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -351,44 +351,6 @@ class RecipeManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply a sort value to the native sort <select>, keeping the Random
|
|
||||||
* option's value in sync when the persisted value carries a seed
|
|
||||||
* (e.g. "random:abc123"). Must be used instead of assigning
|
|
||||||
* sortSelect.value directly whenever the value may be a seeded random
|
|
||||||
* sort, otherwise the native select has no matching option.
|
|
||||||
* @param {string} sortValue - Sort value like "date:desc" or "random:<seed>"
|
|
||||||
*/
|
|
||||||
applySortToSelect(sortValue) {
|
|
||||||
const sortSelect = document.getElementById('sortSelect');
|
|
||||||
if (!sortSelect) return;
|
|
||||||
const randomOpt = sortSelect.querySelector('option[value="random"], option[value^="random:"]');
|
|
||||||
if (randomOpt) {
|
|
||||||
randomOpt.value = String(sortValue).startsWith('random') ? sortValue : 'random';
|
|
||||||
}
|
|
||||||
sortSelect.value = sortValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a fresh seeded random sort value ("random:<seed>") and keep
|
|
||||||
* the native <select> in sync so its value matches the persisted sort
|
|
||||||
* string and the dropdown shows the selected label.
|
|
||||||
* @returns {string} The new sort value, e.g. "random:abc123xyz"
|
|
||||||
*/
|
|
||||||
_randomizeSortValue() {
|
|
||||||
const seed = Math.random().toString(36).slice(2, 12);
|
|
||||||
const value = `random:${seed}`;
|
|
||||||
const sortSelect = document.getElementById('sortSelect');
|
|
||||||
if (sortSelect) {
|
|
||||||
const randomOpt = sortSelect.querySelector('option[value="random"], option[value^="random:"]');
|
|
||||||
if (randomOpt) {
|
|
||||||
randomOpt.value = value;
|
|
||||||
}
|
|
||||||
sortSelect.value = value;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
normalizeLoadRecipesOptions(options = true) {
|
normalizeLoadRecipesOptions(options = true) {
|
||||||
if (typeof options === 'boolean') {
|
if (typeof options === 'boolean') {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { describe, it, beforeEach, afterEach, expect, vi } from 'vitest';
|
import { describe, it, beforeEach, afterEach, expect, vi } from 'vitest';
|
||||||
|
import { applySortToSelect } from '../../../static/js/components/controls/SortDropdown.js';
|
||||||
|
|
||||||
const resetAndReloadMock = vi.fn();
|
const resetAndReloadMock = vi.fn();
|
||||||
const getModelApiClientMock = vi.fn();
|
const getModelApiClientMock = vi.fn();
|
||||||
@@ -190,7 +191,7 @@ describe('Random sort option', () => {
|
|||||||
sortSelect.value = 'random';
|
sortSelect.value = 'random';
|
||||||
sortSelect.dispatchEvent(new Event('change', { bubbles: true }));
|
sortSelect.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
controls.applySortToSelect('name:desc');
|
applySortToSelect('name:desc');
|
||||||
|
|
||||||
expect(sortSelect.value).toBe('name:desc');
|
expect(sortSelect.value).toBe('name:desc');
|
||||||
expect(randomOpt.value).toBe('random');
|
expect(randomOpt.value).toBe('random');
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||||
import { renderRecipesPage } from '../utils/pageFixtures.js';
|
import { renderRecipesPage } from '../utils/pageFixtures.js';
|
||||||
|
import { applySortToSelect } from '../../../static/js/components/controls/SortDropdown.js';
|
||||||
|
|
||||||
const initializeAppMock = vi.fn();
|
const initializeAppMock = vi.fn();
|
||||||
const initializePageFeaturesMock = vi.fn();
|
const initializePageFeaturesMock = vi.fn();
|
||||||
@@ -203,12 +204,12 @@ describe('RecipeManager Random sort', () => {
|
|||||||
it('applies a non-random sort back to the plain random option', async () => {
|
it('applies a non-random sort back to the plain random option', async () => {
|
||||||
const sortSelect = renderSortSelect();
|
const sortSelect = renderSortSelect();
|
||||||
const randomOpt = sortSelect.querySelector('option[value="random"]');
|
const randomOpt = sortSelect.querySelector('option[value="random"]');
|
||||||
const manager = await createManager();
|
await createManager();
|
||||||
|
|
||||||
sortSelect.value = 'random';
|
sortSelect.value = 'random';
|
||||||
sortSelect.dispatchEvent(new Event('change', { bubbles: true }));
|
sortSelect.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
manager.applySortToSelect('name:asc');
|
applySortToSelect('name:asc');
|
||||||
|
|
||||||
expect(sortSelect.value).toBe('name:asc');
|
expect(sortSelect.value).toBe('name:asc');
|
||||||
expect(randomOpt.value).toBe('random');
|
expect(randomOpt.value).toBe('random');
|
||||||
|
|||||||
Reference in New Issue
Block a user