refactor(GetFonts): restructure filter API and add sort store

This commit is contained in:
Ilia Mashkov
2026-03-02 22:19:59 +03:00
parent 0dd08874bc
commit efe1b4f9df
10 changed files with 1073 additions and 96 deletions
+9
View File
@@ -3,4 +3,13 @@ export type {
FilterGroupConfig,
} from './types/filter';
export { filtersStore } from './state/filters.svelte';
export { filterManager } from './state/manager.svelte';
export {
SORT_MAP,
SORT_OPTIONS,
type SortApiValue,
type SortOption,
sortStore,
} from './store/sortStore.svelte';
@@ -15,8 +15,8 @@
* ```
*/
import { fetchProxyFilters } from '$entities/Font/api/proxy/filters';
import type { FilterMetadata } from '$entities/Font/api/proxy/filters';
import { fetchProxyFilters } from '$features/GetFonts/api/filters/filters';
import type { FilterMetadata } from '$features/GetFonts/api/filters/filters';
import { queryClient } from '$shared/api/queryClient';
import {
type QueryKey,
@@ -32,9 +32,6 @@ import {
* Provides reactive access to filter data
*/
class FiltersStore {
/** Cleanup function for effects */
cleanup: () => void;
/** TanStack Query result state */
protected result = $state<QueryObserverResult<FilterMetadata[], Error>>({} as any);
@@ -54,13 +51,6 @@ class FiltersStore {
this.observer.subscribe(r => {
this.result = r;
});
// Sync Svelte state changes -> TanStack Query options
this.cleanup = $effect.root(() => {
$effect(() => {
this.observer.setOptions(this.getOptions());
});
});
}
/**
@@ -119,10 +109,10 @@ class FiltersStore {
}
/**
* Clean up effects and observers
* Clean up observer subscription
*/
destroy() {
this.cleanup();
this.observer.destroy();
}
}
@@ -1,39 +1,39 @@
/**
* Filter manager singleton
*
* Creates filterManager with empty groups initially, then reactively
* populates groups when filtersStore loads data from backend.
*/
import { createFilterManager } from '../../lib/filterManager/filterManager.svelte';
import { filtersStore } from './filters.svelte';
export const filterManager = createFilterManager({
queryValue: '',
groups: [],
});
/**
* Creates initial filter config
*
* Uses dynamic filters from backend with empty state initially
* Reactively sync backend filter metadata into filterManager groups.
* When filtersStore.filters resolves, setGroups replaces the empty groups.
*/
function createInitialConfig() {
const dynamicFilters = filtersStore.filters;
$effect.root(() => {
$effect(() => {
const dynamicFilters = filtersStore.filters;
// If filters are loaded, use them
if (dynamicFilters.length > 0) {
return {
queryValue: '',
groups: dynamicFilters.map(filter => ({
id: filter.id,
label: filter.name,
properties: filter.options.map(opt => ({
id: opt.id,
name: opt.name,
value: opt.value,
count: opt.count,
selected: false,
if (dynamicFilters.length > 0) {
filterManager.setGroups(
dynamicFilters.map(filter => ({
id: filter.id,
label: filter.name,
properties: filter.options.sort((a, b) => b.count - a.count).map(opt => ({
id: opt.id,
name: opt.name,
value: opt.value,
selected: false,
})),
})),
})),
};
}
// No filters loaded yet - return empty state
return {
queryValue: '',
groups: [],
};
}
const initialConfig = createInitialConfig();
export const filterManager = createFilterManager(initialConfig);
);
}
});
});
@@ -0,0 +1,41 @@
/**
* Sort store — manages the current sort option for font listings.
*
* Display labels are mapped to API values through SORT_MAP so that
* the UI layer never has to know about the wire format.
*/
export type SortOption = 'Name' | 'Popularity' | 'Newest';
export const SORT_OPTIONS: SortOption[] = ['Name', 'Popularity', 'Newest'] as const;
export const SORT_MAP: Record<SortOption, 'name' | 'popularity' | 'lastModified'> = {
Name: 'name',
Popularity: 'popularity',
Newest: 'lastModified',
};
export type SortApiValue = (typeof SORT_MAP)[SortOption];
function createSortStore(initial: SortOption = 'Popularity') {
let current = $state<SortOption>(initial);
return {
/** Current display label (e.g. 'Popularity') */
get value() {
return current;
},
/** Mapped API value (e.g. 'popularity') */
get apiValue(): SortApiValue {
return SORT_MAP[current];
},
/** Set the active sort option by its display label */
set(option: SortOption) {
current = option;
},
};
}
export const sortStore = createSortStore();