diff --git a/src/components/dialog/content/manager/ManagerDialogContent.vue b/src/components/dialog/content/manager/ManagerDialogContent.vue index 8345a003d..f255a771c 100644 --- a/src/components/dialog/content/manager/ManagerDialogContent.vue +++ b/src/components/dialog/content/manager/ManagerDialogContent.vue @@ -93,7 +93,14 @@ import { whenever } from '@vueuse/core' import { merge } from 'lodash' import Button from 'primevue/button' -import { computed, onMounted, onUnmounted, ref, watch } from 'vue' +import { + computed, + onBeforeUnmount, + onMounted, + onUnmounted, + ref, + watch +} from 'vue' import { useI18n } from 'vue-i18n' import ContentDivider from '@/components/common/ContentDivider.vue' @@ -106,6 +113,7 @@ import PackCard from '@/components/dialog/content/manager/packCard/PackCard.vue' import RegistrySearchBar from '@/components/dialog/content/manager/registrySearchBar/RegistrySearchBar.vue' import GridSkeleton from '@/components/dialog/content/manager/skeleton/GridSkeleton.vue' import { useResponsiveCollapse } from '@/composables/element/useResponsiveCollapse' +import { useManagerStatePersistence } from '@/composables/manager/useManagerStatePersistence' import { useInstalledPacks } from '@/composables/nodePack/useInstalledPacks' import { usePackUpdateStatus } from '@/composables/nodePack/usePackUpdateStatus' import { useWorkflowPacks } from '@/composables/nodePack/useWorkflowPacks' @@ -116,13 +124,15 @@ import type { TabItem } from '@/types/comfyManagerTypes' import { ManagerTab } from '@/types/comfyManagerTypes' import { components } from '@/types/comfyRegistryTypes' -const { initialTab = ManagerTab.All } = defineProps<{ - initialTab: ManagerTab +const { initialTab } = defineProps<{ + initialTab?: ManagerTab }>() const { t } = useI18n() const comfyManagerStore = useComfyManagerStore() const { getPackById } = useComfyRegistryStore() +const persistedState = useManagerStatePersistence() +const initialState = persistedState.loadStoredState() const GRID_STYLE = { display: 'grid', @@ -156,8 +166,10 @@ const tabs = ref([ icon: 'pi-sync' } ]) + +const initialTabId = initialTab ?? initialState.selectedTabId const selectedTab = ref( - tabs.value.find((tab) => tab.id === initialTab) || tabs.value[0] + tabs.value.find((tab) => tab.id === initialTabId) || tabs.value[0] ) const { @@ -168,7 +180,11 @@ const { searchMode, sortField, suggestions -} = useRegistrySearch() +} = useRegistrySearch({ + initialSortField: initialState.sortField, + initialSearchMode: initialState.searchMode, + initialSearchQuery: initialState.searchQuery +}) pageNumber.value = 0 const onApproachEnd = () => { pageNumber.value++ @@ -456,6 +472,15 @@ watch(searchQuery, () => { } }) +onBeforeUnmount(() => { + persistedState.persistState({ + selectedTabId: selectedTab.value?.id, + searchQuery: searchQuery.value, + searchMode: searchMode.value, + sortField: sortField.value + }) +}) + onUnmounted(() => { getPackById.cancel() }) diff --git a/src/composables/manager/useManagerStatePersistence.ts b/src/composables/manager/useManagerStatePersistence.ts new file mode 100644 index 000000000..1f2aad120 --- /dev/null +++ b/src/composables/manager/useManagerStatePersistence.ts @@ -0,0 +1,54 @@ +import { + ManagerState, + ManagerTab, + SortableAlgoliaField +} from '@/types/comfyManagerTypes' + +const STORAGE_KEY = 'Comfy.Manager.UI.State' + +export const useManagerStatePersistence = () => { + /** + * Load the UI state from localStorage. + */ + const loadStoredState = (): ManagerState => { + try { + const stored = localStorage.getItem(STORAGE_KEY) + if (stored) { + return JSON.parse(stored) + } + } catch (e) { + console.error('Failed to load manager UI state:', e) + } + return { + selectedTabId: ManagerTab.All, + searchQuery: '', + searchMode: 'packs', + sortField: SortableAlgoliaField.Downloads + } + } + + /** + * Persist the UI state to localStorage. + */ + const persistState = (state: ManagerState) => { + localStorage.setItem(STORAGE_KEY, JSON.stringify(state)) + } + + /** + * Reset the UI state to the default values. + */ + const reset = () => { + persistState({ + selectedTabId: ManagerTab.All, + searchQuery: '', + searchMode: 'packs', + sortField: SortableAlgoliaField.Downloads + }) + } + + return { + loadStoredState, + persistState, + reset + } +} diff --git a/src/composables/useRegistrySearch.ts b/src/composables/useRegistrySearch.ts index ab30121b3..6e943d88a 100644 --- a/src/composables/useRegistrySearch.ts +++ b/src/composables/useRegistrySearch.ts @@ -1,7 +1,7 @@ import { watchDebounced } from '@vueuse/core' import type { Hit } from 'algoliasearch/dist/lite/browser' import { memoize, orderBy } from 'lodash' -import { computed, onUnmounted, ref, watch } from 'vue' +import { computed, ref, watch } from 'vue' import { useAlgoliaSearchService } from '@/services/algoliaSearchService' import type { @@ -14,7 +14,6 @@ import { SortableAlgoliaField } from '@/types/comfyManagerTypes' const SEARCH_DEBOUNCE_TIME = 320 const DEFAULT_PAGE_SIZE = 64 const DEFAULT_SORT_FIELD = SortableAlgoliaField.Downloads // Set in the index configuration -const DEFAULT_MAX_CACHE_SIZE = 64 const SORT_DIRECTIONS: Record = { [SortableAlgoliaField.Downloads]: 'desc', [SortableAlgoliaField.Created]: 'desc', @@ -30,18 +29,25 @@ const isDateField = (field: SortableAlgoliaField): boolean => /** * Composable for managing UI state of Comfy Node Registry search. */ -export function useRegistrySearch( - options: { - maxCacheSize?: number - } = {} -) { - const { maxCacheSize = DEFAULT_MAX_CACHE_SIZE } = options +export function useRegistrySearch(options: { + initialSortField?: SortableAlgoliaField + initialSearchMode?: 'nodes' | 'packs' + initialSearchQuery?: string + initialPageNumber?: number +}) { + const { + initialSortField = SortableAlgoliaField.Downloads, + initialSearchMode = 'packs', + initialSearchQuery = '', + initialPageNumber = 0 + } = options + const isLoading = ref(false) - const sortField = ref(SortableAlgoliaField.Downloads) - const searchMode = ref<'nodes' | 'packs'>('packs') + const sortField = ref(initialSortField) + const searchMode = ref<'nodes' | 'packs'>(initialSearchMode) const pageSize = ref(DEFAULT_PAGE_SIZE) - const pageNumber = ref(0) - const searchQuery = ref('') + const pageNumber = ref(initialPageNumber) + const searchQuery = ref(initialSearchQuery) const results = ref([]) const suggestions = ref([]) @@ -62,9 +68,7 @@ export function useRegistrySearch( ) const { searchPacksCached, toRegistryPack, clearSearchPacksCache } = - useAlgoliaSearchService({ - maxCacheSize - }) + useAlgoliaSearchService() const algoliaToRegistry = memoize( toRegistryPack, @@ -124,8 +128,6 @@ export function useRegistrySearch( immediate: true }) - onUnmounted(clearSearchPacksCache) - return { isLoading, pageNumber, @@ -135,6 +137,7 @@ export function useRegistrySearch( searchQuery, suggestions, searchResults: resultsAsRegistryPacks, - nodeSearchResults: resultsAsNodes + nodeSearchResults: resultsAsNodes, + clearCache: clearSearchPacksCache } } diff --git a/src/services/algoliaSearchService.ts b/src/services/algoliaSearchService.ts index f1e7825a9..9dd57d9df 100644 --- a/src/services/algoliaSearchService.ts +++ b/src/services/algoliaSearchService.ts @@ -40,12 +40,6 @@ const RETRIEVE_ATTRIBUTES: SearchAttribute[] = [ ] interface AlgoliaSearchServiceOptions { - /** - * Maximum number of search results to store in the cache. - * The cache is automatically cleared when the component is unmounted. - * @default 64 - */ - maxCacheSize?: number /** * Minimum number of characters for suggestions. An additional query * will be made to the suggestions/completions index for queries that @@ -55,17 +49,15 @@ interface AlgoliaSearchServiceOptions { minCharsForSuggestions?: number } +const searchPacksCache = new QuickLRU({ + maxSize: DEFAULT_MAX_CACHE_SIZE +}) + export const useAlgoliaSearchService = ( options: AlgoliaSearchServiceOptions = {} ) => { - const { - maxCacheSize = DEFAULT_MAX_CACHE_SIZE, - minCharsForSuggestions = DEFAULT_MIN_CHARS_FOR_SUGGESTIONS - } = options + const { minCharsForSuggestions = DEFAULT_MIN_CHARS_FOR_SUGGESTIONS } = options const searchClient = algoliasearch(__ALGOLIA_APP_ID__, __ALGOLIA_API_KEY__) - const searchPacksCache = new QuickLRU({ - maxSize: maxCacheSize - }) const toRegistryLatestVersion = ( algoliaNode: AlgoliaNodePack diff --git a/src/services/dialogService.ts b/src/services/dialogService.ts index 224f83f93..5361689f4 100644 --- a/src/services/dialogService.ts +++ b/src/services/dialogService.ts @@ -21,7 +21,6 @@ import TemplateWorkflowsDialogHeader from '@/components/templates/TemplateWorkfl import { t } from '@/i18n' import type { ExecutionErrorWsMessage } from '@/schemas/apiSchema' import { type ShowDialogOptions, useDialogStore } from '@/stores/dialogStore' -import { ManagerTab } from '@/types/comfyManagerTypes' export type ConfirmationDialogType = | 'default' @@ -129,9 +128,7 @@ export const useDialogService = () => { } function showManagerDialog( - props: InstanceType['$props'] = { - initialTab: ManagerTab.All - } + props: InstanceType['$props'] = {} ) { dialogStore.showDialog({ key: 'global-manager', diff --git a/src/types/comfyManagerTypes.ts b/src/types/comfyManagerTypes.ts index dbb8bc169..ab79343b7 100644 --- a/src/types/comfyManagerTypes.ts +++ b/src/types/comfyManagerTypes.ts @@ -39,7 +39,7 @@ export enum SortableAlgoliaField { } export interface TabItem { - id: string + id: ManagerTab label: string icon: string } @@ -234,3 +234,10 @@ export interface InstallPackParams extends ManagerPackInfo { export interface UpdateAllPacksParams { mode?: ManagerDatabaseSource } + +export interface ManagerState { + selectedTabId: ManagerTab + searchQuery: string + searchMode: 'nodes' | 'packs' + sortField: SortableAlgoliaField +}