refactor: adopt createSingleton across the remaining stores
Replace the hand-rolled let _x / getX / __resetX boilerplate with the createSingleton helper in all nine remaining singleton stores. Exposed accessor names (getX, __resetX) are unchanged, so consumers and specs are unaffected. Teardown wired to each stores destroy() where it has one (fontCatalog, fontLifecycle, typography, availableFilter, theme, layout, scrollBreadcrumbs); sort and appliedFilter have no teardown. Also merges layoutStores duplicate $shared/lib imports.
This commit is contained in:
@@ -3,6 +3,7 @@ import {
|
|||||||
DEFAULT_QUERY_STALE_TIME_MS,
|
DEFAULT_QUERY_STALE_TIME_MS,
|
||||||
getQueryClient,
|
getQueryClient,
|
||||||
} from '$shared/api/queryClient';
|
} from '$shared/api/queryClient';
|
||||||
|
import { createSingleton } from '$shared/lib/helpers/createSingleton/createSingleton';
|
||||||
import {
|
import {
|
||||||
type InfiniteData,
|
type InfiniteData,
|
||||||
InfiniteQueryObserver,
|
InfiniteQueryObserver,
|
||||||
@@ -483,14 +484,12 @@ export class FontCatalogStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let _catalog: FontCatalogStore | undefined;
|
const catalog = createSingleton(
|
||||||
|
() => new FontCatalogStore({ limit: 50 }),
|
||||||
|
instance => instance.destroy(),
|
||||||
|
);
|
||||||
|
|
||||||
export function getFontCatalog(): FontCatalogStore {
|
export const getFontCatalog = catalog.get;
|
||||||
return (_catalog ??= new FontCatalogStore({ limit: 50 }));
|
|
||||||
}
|
|
||||||
|
|
||||||
// test-only reset, so specs don't share a live observer
|
// test-only reset, so specs don't share a live observer
|
||||||
export function __resetFontCatalog() {
|
export const __resetFontCatalog = catalog.reset;
|
||||||
_catalog?.destroy();
|
|
||||||
_catalog = undefined;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { createSingleton } from '$shared/lib/helpers/createSingleton/createSingleton';
|
||||||
import { SvelteMap } from 'svelte/reactivity';
|
import { SvelteMap } from 'svelte/reactivity';
|
||||||
import {
|
import {
|
||||||
type FontLoadRequestConfig,
|
type FontLoadRequestConfig,
|
||||||
@@ -419,18 +420,16 @@ export class FontLifecycleManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let _fontLifecycleManager: FontLifecycleManager | undefined;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* App-wide font lifecycle manager, created on first access. Lazy so its
|
* App-wide font lifecycle manager, created on first access. Lazy so its
|
||||||
* AbortController / FontFace bookkeeping isn't set up at module load.
|
* AbortController / FontFace bookkeeping isn't set up at module load.
|
||||||
*/
|
*/
|
||||||
export function getFontLifecycleManager(): FontLifecycleManager {
|
const fontLifecycleManager = createSingleton(
|
||||||
return (_fontLifecycleManager ??= new FontLifecycleManager());
|
() => new FontLifecycleManager(),
|
||||||
}
|
instance => instance.destroy(),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getFontLifecycleManager = fontLifecycleManager.get;
|
||||||
|
|
||||||
// test-only reset, so specs don't share loaded-font/eviction state
|
// test-only reset, so specs don't share loaded-font/eviction state
|
||||||
export function __resetFontLifecycleManager() {
|
export const __resetFontLifecycleManager = fontLifecycleManager.reset;
|
||||||
_fontLifecycleManager?.destroy();
|
|
||||||
_fontLifecycleManager = undefined;
|
|
||||||
}
|
|
||||||
|
|||||||
+8
-12
@@ -19,6 +19,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
type PersistentStore,
|
type PersistentStore,
|
||||||
createPersistentStore,
|
createPersistentStore,
|
||||||
|
createSingleton,
|
||||||
} from '$shared/lib';
|
} from '$shared/lib';
|
||||||
import type { NumericControl } from '$shared/ui';
|
import type { NumericControl } from '$shared/ui';
|
||||||
import { SvelteMap } from 'svelte/reactivity';
|
import { SvelteMap } from 'svelte/reactivity';
|
||||||
@@ -349,22 +350,17 @@ export function createTypographySettingsStore(
|
|||||||
|
|
||||||
export type TypographySettingsStoreInstance = ReturnType<typeof createTypographySettingsStore>;
|
export type TypographySettingsStoreInstance = ReturnType<typeof createTypographySettingsStore>;
|
||||||
|
|
||||||
let _typographySettingsStore: TypographySettingsStoreInstance | undefined;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* App-wide typography settings store, keyed for the comparison view.
|
* App-wide typography settings store, keyed for the comparison view.
|
||||||
* Created on first access so its persistent-store sync effects aren't set up
|
* Created on first access so its persistent-store sync effects aren't set up
|
||||||
* at module load.
|
* at module load.
|
||||||
*/
|
*/
|
||||||
export function getTypographySettingsStore(): TypographySettingsStoreInstance {
|
const typographySettingsStore = createSingleton(
|
||||||
return (_typographySettingsStore ??= createTypographySettingsStore(
|
() => createTypographySettingsStore(DEFAULT_TYPOGRAPHY_CONTROLS_DATA, COMPARISON_STORAGE_KEY),
|
||||||
DEFAULT_TYPOGRAPHY_CONTROLS_DATA,
|
instance => instance.destroy(),
|
||||||
COMPARISON_STORAGE_KEY,
|
);
|
||||||
));
|
|
||||||
}
|
export const getTypographySettingsStore = typographySettingsStore.get;
|
||||||
|
|
||||||
// test-only reset, so specs don't share persisted typography state or leak effects
|
// test-only reset, so specs don't share persisted typography state or leak effects
|
||||||
export function __resetTypographySettingsStore() {
|
export const __resetTypographySettingsStore = typographySettingsStore.reset;
|
||||||
_typographySettingsStore?.destroy();
|
|
||||||
_typographySettingsStore = undefined;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { createSingleton } from '$shared/lib/helpers/createSingleton/createSingleton';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scroll-based breadcrumb tracking store
|
* Scroll-based breadcrumb tracking store
|
||||||
*
|
*
|
||||||
@@ -279,17 +281,15 @@ export function createScrollBreadcrumbsStore(): ScrollBreadcrumbsStore {
|
|||||||
return new ScrollBreadcrumbsStore();
|
return new ScrollBreadcrumbsStore();
|
||||||
}
|
}
|
||||||
|
|
||||||
let _scrollBreadcrumbsStore: ScrollBreadcrumbsStore | undefined;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* App-wide scroll breadcrumbs store, created on first access.
|
* App-wide scroll breadcrumbs store, created on first access.
|
||||||
*/
|
*/
|
||||||
export function getScrollBreadcrumbsStore(): ScrollBreadcrumbsStore {
|
const scrollBreadcrumbsStore = createSingleton(
|
||||||
return (_scrollBreadcrumbsStore ??= createScrollBreadcrumbsStore());
|
() => createScrollBreadcrumbsStore(),
|
||||||
}
|
instance => instance.destroy(),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getScrollBreadcrumbsStore = scrollBreadcrumbsStore.get;
|
||||||
|
|
||||||
// test-only reset, so specs don't share observer/scroll state
|
// test-only reset, so specs don't share observer/scroll state
|
||||||
export function __resetScrollBreadcrumbsStore() {
|
export const __resetScrollBreadcrumbsStore = scrollBreadcrumbsStore.reset;
|
||||||
_scrollBreadcrumbsStore?.destroy();
|
|
||||||
_scrollBreadcrumbsStore = undefined;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -28,7 +28,10 @@
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createPersistentStore } from '$shared/lib';
|
import {
|
||||||
|
createPersistentStore,
|
||||||
|
createSingleton,
|
||||||
|
} from '$shared/lib';
|
||||||
|
|
||||||
export const STORAGE_KEY = 'glyphdiff:theme';
|
export const STORAGE_KEY = 'glyphdiff:theme';
|
||||||
|
|
||||||
@@ -195,23 +198,18 @@ class ThemeManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let _themeManager: ThemeManager | undefined;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* App-wide theme manager, created on first access.
|
* App-wide theme manager, created on first access.
|
||||||
*
|
*
|
||||||
* Lazy so its persistent-store subscription isn't set up at module load.
|
* Lazy so its persistent-store subscription isn't set up at module load.
|
||||||
* Call init() on mount and destroy() on unmount (see Layout).
|
* Call init() on mount and destroy() on unmount (see Layout).
|
||||||
*/
|
*/
|
||||||
export function getThemeManager(): ThemeManager {
|
const themeManager = createSingleton(() => new ThemeManager(), instance => instance.destroy());
|
||||||
return (_themeManager ??= new ThemeManager());
|
|
||||||
}
|
export const getThemeManager = themeManager.get;
|
||||||
|
|
||||||
// test-only reset, so specs don't share persisted theme state
|
// test-only reset, so specs don't share persisted theme state
|
||||||
export function __resetThemeManager() {
|
export const __resetThemeManager = themeManager.reset;
|
||||||
_themeManager?.destroy();
|
|
||||||
_themeManager = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ThemeManager class exported for testing purposes
|
* ThemeManager class exported for testing purposes
|
||||||
|
|||||||
+11
-10
@@ -23,7 +23,10 @@
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createFilter } from '$shared/lib';
|
import {
|
||||||
|
createFilter,
|
||||||
|
createSingleton,
|
||||||
|
} from '$shared/lib';
|
||||||
import { createDebouncedState } from '$shared/lib/helpers';
|
import { createDebouncedState } from '$shared/lib/helpers';
|
||||||
import type {
|
import type {
|
||||||
FilterConfig,
|
FilterConfig,
|
||||||
@@ -129,8 +132,6 @@ export function createAppliedFilterStore<TValue extends string>(config: FilterCo
|
|||||||
|
|
||||||
export type AppliedFilterStore = ReturnType<typeof createAppliedFilterStore>;
|
export type AppliedFilterStore = ReturnType<typeof createAppliedFilterStore>;
|
||||||
|
|
||||||
let _appliedFilterStore: AppliedFilterStore | undefined;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* App-wide filter manager, created on first access.
|
* App-wide filter manager, created on first access.
|
||||||
*
|
*
|
||||||
@@ -138,14 +139,14 @@ let _appliedFilterStore: AppliedFilterStore | undefined;
|
|||||||
* lives in `./bindings.svelte` and populates groups once backend filter
|
* lives in `./bindings.svelte` and populates groups once backend filter
|
||||||
* metadata arrives.
|
* metadata arrives.
|
||||||
*/
|
*/
|
||||||
export function getAppliedFilterStore(): AppliedFilterStore {
|
const appliedFilterStore = createSingleton(() =>
|
||||||
return (_appliedFilterStore ??= createAppliedFilterStore<string>({
|
createAppliedFilterStore<string>({
|
||||||
queryValue: '',
|
queryValue: '',
|
||||||
groups: [],
|
groups: [],
|
||||||
}));
|
})
|
||||||
}
|
);
|
||||||
|
|
||||||
|
export const getAppliedFilterStore = appliedFilterStore.get;
|
||||||
|
|
||||||
// test-only reset, so specs don't share filter/selection state
|
// test-only reset, so specs don't share filter/selection state
|
||||||
export function __resetAppliedFilterStore() {
|
export const __resetAppliedFilterStore = appliedFilterStore.reset;
|
||||||
_appliedFilterStore = undefined;
|
|
||||||
}
|
|
||||||
|
|||||||
+8
-9
@@ -22,6 +22,7 @@ import {
|
|||||||
DEFAULT_QUERY_STALE_TIME_MS,
|
DEFAULT_QUERY_STALE_TIME_MS,
|
||||||
getQueryClient,
|
getQueryClient,
|
||||||
} from '$shared/api/queryClient';
|
} from '$shared/api/queryClient';
|
||||||
|
import { createSingleton } from '$shared/lib/helpers/createSingleton/createSingleton';
|
||||||
import {
|
import {
|
||||||
type QueryKey,
|
type QueryKey,
|
||||||
QueryObserver,
|
QueryObserver,
|
||||||
@@ -126,18 +127,16 @@ export class AvailableFilterStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let _availableFilterStore: AvailableFilterStore | undefined;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* App-wide filter-metadata store, created on first access. Lazy so the
|
* App-wide filter-metadata store, created on first access. Lazy so the
|
||||||
* QueryObserver isn't constructed at module load.
|
* QueryObserver isn't constructed at module load.
|
||||||
*/
|
*/
|
||||||
export function getAvailableFilterStore(): AvailableFilterStore {
|
const availableFilterStore = createSingleton(
|
||||||
return (_availableFilterStore ??= new AvailableFilterStore());
|
() => new AvailableFilterStore(),
|
||||||
}
|
instance => instance.destroy(),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getAvailableFilterStore = availableFilterStore.get;
|
||||||
|
|
||||||
// test-only reset, so specs don't share a live observer
|
// test-only reset, so specs don't share a live observer
|
||||||
export function __resetAvailableFilterStore() {
|
export const __resetAvailableFilterStore = availableFilterStore.reset;
|
||||||
_availableFilterStore?.destroy();
|
|
||||||
_availableFilterStore = undefined;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { createSingleton } from '$shared/lib/helpers/createSingleton/createSingleton';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sort store — manages the current sort option for font listings.
|
* Sort store — manages the current sort option for font listings.
|
||||||
*
|
*
|
||||||
@@ -46,16 +48,12 @@ export function createSortStore(initial: SortOption = 'Popularity') {
|
|||||||
|
|
||||||
export type SortStore = ReturnType<typeof createSortStore>;
|
export type SortStore = ReturnType<typeof createSortStore>;
|
||||||
|
|
||||||
let _sortStore: SortStore | undefined;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* App-wide sort store, created on first access.
|
* App-wide sort store, created on first access.
|
||||||
*/
|
*/
|
||||||
export function getSortStore(): SortStore {
|
const sortStore = createSingleton(() => createSortStore());
|
||||||
return (_sortStore ??= createSortStore());
|
|
||||||
}
|
export const getSortStore = sortStore.get;
|
||||||
|
|
||||||
// test-only reset, so specs don't share selection state
|
// test-only reset, so specs don't share selection state
|
||||||
export function __resetSortStore() {
|
export const __resetSortStore = sortStore.reset;
|
||||||
_sortStore = undefined;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -16,8 +16,11 @@
|
|||||||
* - Desktop Large (>= 1280px): 4 columns
|
* - Desktop Large (>= 1280px): 4 columns
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createPersistentStore } from '$shared/lib';
|
import {
|
||||||
import { responsiveManager } from '$shared/lib';
|
createPersistentStore,
|
||||||
|
createSingleton,
|
||||||
|
responsiveManager,
|
||||||
|
} from '$shared/lib';
|
||||||
|
|
||||||
export type LayoutMode = 'list' | 'grid';
|
export type LayoutMode = 'list' | 'grid';
|
||||||
|
|
||||||
@@ -153,20 +156,16 @@ class LayoutManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let _layoutManager: LayoutManager | undefined;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* App-wide layout manager, created on first access. Lazy so its persisted
|
* App-wide layout manager, created on first access. Lazy so its persisted
|
||||||
* layout preference isn't read at module load.
|
* layout preference isn't read at module load.
|
||||||
*/
|
*/
|
||||||
export function getLayoutManager(): LayoutManager {
|
const layoutManager = createSingleton(() => new LayoutManager(), instance => instance.destroy());
|
||||||
return (_layoutManager ??= new LayoutManager());
|
|
||||||
}
|
export const getLayoutManager = layoutManager.get;
|
||||||
|
|
||||||
// test-only reset, so specs don't share persisted layout state
|
// test-only reset, so specs don't share persisted layout state
|
||||||
export function __resetLayoutManager() {
|
export const __resetLayoutManager = layoutManager.reset;
|
||||||
_layoutManager = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Export class for testing purposes
|
// Export class for testing purposes
|
||||||
export { LayoutManager };
|
export { LayoutManager };
|
||||||
|
|||||||
Reference in New Issue
Block a user