mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-25 03:10:08 +08:00
refactor: update preferences management and enhance PreferenceService documentation
- Updated preferences types in preferences.ts to use PreferenceTypes for better type safety. - Added new preference keys related to notes features in preferences.ts. - Enhanced documentation in PreferenceService.ts and usePreference.ts to clarify usage and update strategies. - Improved caching and subscription mechanisms in PreferenceService for better performance and reliability.
This commit is contained in:
parent
7f114ade4d
commit
c2a1178dff
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated preferences configuration
|
||||
* Generated at: 2025-09-03T04:46:03.708Z
|
||||
* Generated at: 2025-09-03T13:39:01.110Z
|
||||
*
|
||||
* This file is automatically generated from classification.json
|
||||
* To update this file, modify classification.json and run:
|
||||
@ -10,24 +10,7 @@
|
||||
*/
|
||||
|
||||
import { TRANSLATE_PROMPT } from '@shared/config/prompts'
|
||||
import type {
|
||||
AssistantIconType,
|
||||
AssistantTabSortType,
|
||||
ChatMessageNavigationMode,
|
||||
ChatMessageStyle,
|
||||
LanguageVarious,
|
||||
MultiModelFoldDisplayMode,
|
||||
MultiModelGridPopoverTrigger,
|
||||
MultiModelMessageStyle,
|
||||
ProxyMode,
|
||||
SelectionActionItem,
|
||||
SelectionFilterMode,
|
||||
SelectionTriggerMode,
|
||||
SendMessageShortcut,
|
||||
SidebarIcon,
|
||||
WindowStyle
|
||||
} from '@shared/data/preferenceTypes'
|
||||
import { ThemeMode, UpgradeChannel } from '@shared/data/preferenceTypes'
|
||||
import * as PreferenceTypes from '@shared/data/preferenceTypes'
|
||||
|
||||
/* eslint @typescript-eslint/member-ordering: ["error", {
|
||||
"interfaces": { "order": "alphabetically" },
|
||||
@ -43,11 +26,11 @@ export interface PreferencesType {
|
||||
// redux/settings/autoCheckUpdate
|
||||
'app.dist.auto_update.enabled': boolean
|
||||
// redux/settings/testChannel
|
||||
'app.dist.test_plan.channel': UpgradeChannel
|
||||
'app.dist.test_plan.channel': PreferenceTypes.UpgradeChannel
|
||||
// redux/settings/testPlan
|
||||
'app.dist.test_plan.enabled': boolean
|
||||
// redux/settings/language
|
||||
'app.language': LanguageVarious | null
|
||||
'app.language': PreferenceTypes.LanguageVarious | null
|
||||
// redux/settings/launchOnBoot
|
||||
'app.launch_on_boot': boolean
|
||||
// redux/settings/notification.assistant
|
||||
@ -61,7 +44,7 @@ export interface PreferencesType {
|
||||
// redux/settings/proxyBypassRules
|
||||
'app.proxy.bypass_rules': string
|
||||
// redux/settings/proxyMode
|
||||
'app.proxy.mode': ProxyMode
|
||||
'app.proxy.mode': PreferenceTypes.ProxyMode
|
||||
// redux/settings/proxyUrl
|
||||
'app.proxy.url': string
|
||||
// redux/settings/enableSpellCheck
|
||||
@ -83,11 +66,11 @@ export interface PreferencesType {
|
||||
// redux/settings/clickAssistantToShowTopic
|
||||
'assistant.click_to_show_topic': boolean
|
||||
// redux/settings/assistantIconType
|
||||
'assistant.icon_type': AssistantIconType
|
||||
'assistant.icon_type': PreferenceTypes.AssistantIconType
|
||||
// redux/settings/showAssistants
|
||||
'assistant.tab.show': boolean
|
||||
// redux/settings/assistantsTabSortType
|
||||
'assistant.tab.sort_type': AssistantTabSortType
|
||||
'assistant.tab.sort_type': PreferenceTypes.AssistantTabSortType
|
||||
// redux/settings/codeCollapsible
|
||||
'chat.code.collapsible': boolean
|
||||
// redux/settings/codeEditor.autocompletion
|
||||
@ -129,7 +112,7 @@ export interface PreferencesType {
|
||||
// redux/settings/enableQuickPanelTriggers
|
||||
'chat.input.quick_panel.triggers_enabled': boolean
|
||||
// redux/settings/sendMessageShortcut
|
||||
'chat.input.send_message_shortcut': SendMessageShortcut
|
||||
'chat.input.send_message_shortcut': PreferenceTypes.SendMessageShortcut
|
||||
// redux/settings/showInputEstimatedTokens
|
||||
'chat.input.show_estimated_tokens': boolean
|
||||
// redux/settings/autoTranslateWithSpace
|
||||
@ -149,15 +132,15 @@ export interface PreferencesType {
|
||||
// redux/settings/mathEnableSingleDollar
|
||||
'chat.message.math.single_dollar': boolean
|
||||
// redux/settings/foldDisplayMode
|
||||
'chat.message.multi_model.fold_display_mode': MultiModelFoldDisplayMode
|
||||
'chat.message.multi_model.fold_display_mode': PreferenceTypes.MultiModelFoldDisplayMode
|
||||
// redux/settings/gridColumns
|
||||
'chat.message.multi_model.grid_columns': number
|
||||
// redux/settings/gridPopoverTrigger
|
||||
'chat.message.multi_model.grid_popover_trigger': MultiModelGridPopoverTrigger
|
||||
'chat.message.multi_model.grid_popover_trigger': PreferenceTypes.MultiModelGridPopoverTrigger
|
||||
// redux/settings/multiModelMessageStyle
|
||||
'chat.message.multi_model.style': MultiModelMessageStyle
|
||||
'chat.message.multi_model.style': PreferenceTypes.MultiModelMessageStyle
|
||||
// redux/settings/messageNavigation
|
||||
'chat.message.navigation_mode': ChatMessageNavigationMode
|
||||
'chat.message.navigation_mode': PreferenceTypes.ChatMessageNavigationMode
|
||||
// redux/settings/renderInputMessageAsMarkdown
|
||||
'chat.message.render_as_markdown': boolean
|
||||
// redux/settings/showMessageDivider
|
||||
@ -167,7 +150,7 @@ export interface PreferencesType {
|
||||
// redux/settings/showPrompt
|
||||
'chat.message.show_prompt': boolean
|
||||
// redux/settings/messageStyle
|
||||
'chat.message.style': ChatMessageStyle
|
||||
'chat.message.style': PreferenceTypes.ChatMessageStyle
|
||||
// redux/settings/thoughtAutoCollapse
|
||||
'chat.message.thought.auto_collapse': boolean
|
||||
// redux/settings/narrowMode
|
||||
@ -312,7 +295,17 @@ export interface PreferencesType {
|
||||
'feature.minapp.open_link_external': boolean
|
||||
// redux/settings/showOpenedMinappsInSidebar
|
||||
'feature.minapp.show_opened_in_sidebar': boolean
|
||||
// redux/settings/showWorkspace
|
||||
// redux/note/settings.defaultEditMode
|
||||
'feature.notes.default_edit_mode': string
|
||||
// redux/note/settings.fontFamily
|
||||
'feature.notes.default_font_family': string
|
||||
// redux/note/settings.defaultViewMode
|
||||
'feature.notes.default_view_mode': string
|
||||
// redux/note/settings.isFullWidth
|
||||
'feature.notes.full_width': boolean
|
||||
// redux/note/settings.showTabStatus
|
||||
'feature.notes.show_tab_status': boolean
|
||||
// redux/note/settings.showWorkspace
|
||||
'feature.notes.show_workspace': boolean
|
||||
// redux/settings/clickTrayToShowQuickAssistant
|
||||
'feature.quick_assistant.click_tray_to_show': boolean
|
||||
@ -321,7 +314,7 @@ export interface PreferencesType {
|
||||
// redux/settings/readClipboardAtStartup
|
||||
'feature.quick_assistant.read_clipboard_at_startup': boolean
|
||||
// redux/selectionStore/actionItems
|
||||
'feature.selection.action_items': SelectionActionItem[]
|
||||
'feature.selection.action_items': PreferenceTypes.SelectionActionItem[]
|
||||
// redux/selectionStore/actionWindowOpacity
|
||||
'feature.selection.action_window_opacity': number
|
||||
// redux/selectionStore/isAutoClose
|
||||
@ -335,13 +328,13 @@ export interface PreferencesType {
|
||||
// redux/selectionStore/filterList
|
||||
'feature.selection.filter_list': string[]
|
||||
// redux/selectionStore/filterMode
|
||||
'feature.selection.filter_mode': SelectionFilterMode
|
||||
'feature.selection.filter_mode': PreferenceTypes.SelectionFilterMode
|
||||
// redux/selectionStore/isFollowToolbar
|
||||
'feature.selection.follow_toolbar': boolean
|
||||
// redux/selectionStore/isRemeberWinSize
|
||||
'feature.selection.remember_win_size': boolean
|
||||
// redux/selectionStore/triggerMode
|
||||
'feature.selection.trigger_mode': SelectionTriggerMode
|
||||
'feature.selection.trigger_mode': PreferenceTypes.SelectionTriggerMode
|
||||
// redux/settings/translateModelPrompt
|
||||
'feature.translate.model_prompt': string
|
||||
// redux/settings/targetLanguage
|
||||
@ -395,15 +388,15 @@ export interface PreferencesType {
|
||||
// redux/settings/navbarPosition
|
||||
'ui.navbar.position': 'left' | 'top'
|
||||
// redux/settings/sidebarIcons.disabled
|
||||
'ui.sidebar.icons.invisible': SidebarIcon[]
|
||||
'ui.sidebar.icons.invisible': PreferenceTypes.SidebarIcon[]
|
||||
// redux/settings/sidebarIcons.visible
|
||||
'ui.sidebar.icons.visible': SidebarIcon[]
|
||||
'ui.sidebar.icons.visible': PreferenceTypes.SidebarIcon[]
|
||||
// redux/settings/theme
|
||||
'ui.theme_mode': ThemeMode
|
||||
'ui.theme_mode': PreferenceTypes.ThemeMode
|
||||
// redux/settings/userTheme.colorPrimary
|
||||
'ui.theme_user.color_primary': string
|
||||
// redux/settings/windowStyle
|
||||
'ui.window_style': WindowStyle
|
||||
'ui.window_style': PreferenceTypes.WindowStyle
|
||||
}
|
||||
}
|
||||
|
||||
@ -413,7 +406,7 @@ export const DefaultPreferences: PreferencesType = {
|
||||
'app.developer_mode.enabled': false,
|
||||
'app.disable_hardware_acceleration': false,
|
||||
'app.dist.auto_update.enabled': true,
|
||||
'app.dist.test_plan.channel': UpgradeChannel.LATEST,
|
||||
'app.dist.test_plan.channel': PreferenceTypes.UpgradeChannel.LATEST,
|
||||
'app.dist.test_plan.enabled': false,
|
||||
'app.language': null,
|
||||
'app.launch_on_boot': false,
|
||||
@ -548,6 +541,11 @@ export const DefaultPreferences: PreferencesType = {
|
||||
'feature.minapp.max_keep_alive': 3,
|
||||
'feature.minapp.open_link_external': false,
|
||||
'feature.minapp.show_opened_in_sidebar': true,
|
||||
'feature.notes.default_edit_mode': 'preview',
|
||||
'feature.notes.default_font_family': 'default',
|
||||
'feature.notes.default_view_mode': 'edit',
|
||||
'feature.notes.full_width': true,
|
||||
'feature.notes.show_tab_status': true,
|
||||
'feature.notes.show_workspace': true,
|
||||
'feature.quick_assistant.click_tray_to_show': false,
|
||||
'feature.quick_assistant.enabled': false,
|
||||
@ -648,7 +646,7 @@ export const DefaultPreferences: PreferencesType = {
|
||||
'code_tools',
|
||||
'notes'
|
||||
],
|
||||
'ui.theme_mode': ThemeMode.system,
|
||||
'ui.theme_mode': PreferenceTypes.ThemeMode.system,
|
||||
'ui.theme_user.color_primary': '#00b96b',
|
||||
'ui.window_style': 'opaque'
|
||||
}
|
||||
@ -658,8 +656,8 @@ export const DefaultPreferences: PreferencesType = {
|
||||
|
||||
/**
|
||||
* 生成统计:
|
||||
* - 总配置项: 184
|
||||
* - 总配置项: 189
|
||||
* - electronStore项: 1
|
||||
* - redux项: 183
|
||||
* - redux项: 188
|
||||
* - localStorage项: 0
|
||||
*/
|
||||
|
||||
@ -7,9 +7,17 @@ import type {
|
||||
} from '@shared/data/preferenceTypes'
|
||||
|
||||
const logger = loggerService.withContext('PreferenceService')
|
||||
|
||||
/**
|
||||
* Renderer-side PreferenceService providing cached access to preferences
|
||||
* with real-time synchronization across windows using useSyncExternalStore
|
||||
* Renderer-side PreferenceService providing cached access to preferences with real-time synchronization
|
||||
*
|
||||
* Features:
|
||||
* - Caching system for fast access to frequently used preferences
|
||||
* - Optimistic and pessimistic update strategies
|
||||
* - Real-time synchronization across windows via IPC
|
||||
* - Race condition handling for concurrent updates
|
||||
* - Batch operations for multiple preferences
|
||||
* - Integration with React's useSyncExternalStore
|
||||
*/
|
||||
export class PreferenceService {
|
||||
private static instance: PreferenceService
|
||||
@ -53,6 +61,7 @@ export class PreferenceService {
|
||||
|
||||
/**
|
||||
* Get the singleton instance of PreferenceService
|
||||
* @returns The singleton PreferenceService instance
|
||||
*/
|
||||
public static getInstance(): PreferenceService {
|
||||
if (!PreferenceService.instance) {
|
||||
@ -63,6 +72,7 @@ export class PreferenceService {
|
||||
|
||||
/**
|
||||
* Setup IPC change listener for preference updates from main process
|
||||
* Establishes communication channel for real-time preference synchronization
|
||||
*/
|
||||
private setupChangeListeners() {
|
||||
if (!window.api?.preference?.onChanged) {
|
||||
@ -83,6 +93,7 @@ export class PreferenceService {
|
||||
|
||||
/**
|
||||
* Notify all relevant listeners about preference changes
|
||||
* @param key The preference key that changed
|
||||
*/
|
||||
private notifyChangeListeners(key: string) {
|
||||
// Notify global listeners
|
||||
@ -96,7 +107,9 @@ export class PreferenceService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single preference value with caching
|
||||
* Get a single preference value with caching and auto-subscription
|
||||
* @param key The preference key to retrieve
|
||||
* @returns Promise resolving to the preference value with defaults applied
|
||||
*/
|
||||
public async get<K extends PreferenceKeyType>(key: K): Promise<PreferenceDefaultScopeType[K]> {
|
||||
// Check cache first
|
||||
@ -127,6 +140,10 @@ export class PreferenceService {
|
||||
|
||||
/**
|
||||
* Set a single preference value with configurable update strategy
|
||||
* @param key The preference key to update
|
||||
* @param value The new value to set
|
||||
* @param options Update strategy options (optimistic by default)
|
||||
* @returns Promise that resolves when update completes
|
||||
*/
|
||||
public async set<K extends PreferenceKeyType>(
|
||||
key: K,
|
||||
@ -142,6 +159,10 @@ export class PreferenceService {
|
||||
|
||||
/**
|
||||
* Optimistic update: Queue request to prevent race conditions
|
||||
* Updates UI immediately, then syncs to database with rollback on failure
|
||||
* @param key The preference key to update
|
||||
* @param value The new value to set
|
||||
* @returns Promise that resolves when update completes
|
||||
*/
|
||||
private async setOptimistic<K extends PreferenceKeyType>(
|
||||
key: K,
|
||||
@ -153,6 +174,10 @@ export class PreferenceService {
|
||||
|
||||
/**
|
||||
* Execute optimistic update with proper race condition handling
|
||||
* @param key The preference key to update
|
||||
* @param value The new value to set
|
||||
* @param requestId Unique identifier for this update request
|
||||
* @returns Promise that resolves when update completes
|
||||
*/
|
||||
private async executeOptimisticUpdate(key: PreferenceKeyType, value: any, requestId: string): Promise<void> {
|
||||
const existingState = this.optimisticValues.get(key)
|
||||
@ -190,6 +215,10 @@ export class PreferenceService {
|
||||
|
||||
/**
|
||||
* Pessimistic update: Wait for database confirmation before updating UI
|
||||
* Updates database first, then UI on success
|
||||
* @param key The preference key to update
|
||||
* @param value The new value to set
|
||||
* @returns Promise that resolves when update completes
|
||||
*/
|
||||
private async setPessimistic<K extends PreferenceKeyType>(
|
||||
key: K,
|
||||
@ -210,7 +239,9 @@ export class PreferenceService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get multiple preferences at once, return is Partial<PreferenceDefaultScopeType>
|
||||
* Get multiple preferences at once with caching and auto-subscription
|
||||
* @param keys Array of preference keys to retrieve
|
||||
* @returns Promise resolving to partial object with preference values
|
||||
*/
|
||||
public async getMultipleRaw(keys: PreferenceKeyType[]): Promise<Partial<PreferenceDefaultScopeType>> {
|
||||
// Check which keys are already cached
|
||||
@ -261,6 +292,8 @@ export class PreferenceService {
|
||||
|
||||
/**
|
||||
* Get multiple preferences at once and return them as a record of key-value pairs
|
||||
* @param keys Object mapping local names to preference keys
|
||||
* @returns Promise resolving to object with mapped preference values
|
||||
*/
|
||||
public async getMultiple<T extends Record<string, PreferenceKeyType>>(
|
||||
keys: T
|
||||
@ -277,6 +310,9 @@ export class PreferenceService {
|
||||
|
||||
/**
|
||||
* Set multiple preferences at once with configurable update strategy
|
||||
* @param updates Object containing preference key-value pairs to update
|
||||
* @param options Update strategy options (optimistic by default)
|
||||
* @returns Promise that resolves when all updates complete
|
||||
*/
|
||||
public async setMultiple(
|
||||
updates: Partial<PreferenceDefaultScopeType>,
|
||||
@ -291,6 +327,8 @@ export class PreferenceService {
|
||||
|
||||
/**
|
||||
* Optimistic batch update: Update UI immediately, then sync to database
|
||||
* @param updates Object containing preference key-value pairs to update
|
||||
* @returns Promise that resolves when batch update completes
|
||||
*/
|
||||
private async setMultipleOptimistic(updates: Partial<PreferenceDefaultScopeType>): Promise<void> {
|
||||
const batchRequestId = this.generateRequestId()
|
||||
@ -346,6 +384,8 @@ export class PreferenceService {
|
||||
|
||||
/**
|
||||
* Pessimistic batch update: Wait for database confirmation before updating UI
|
||||
* @param updates Object containing preference key-value pairs to update
|
||||
* @returns Promise that resolves when batch update completes
|
||||
*/
|
||||
private async setMultiplePessimistic(updates: Partial<PreferenceDefaultScopeType>): Promise<void> {
|
||||
try {
|
||||
@ -365,7 +405,9 @@ export class PreferenceService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to a specific key for change notifications
|
||||
* Subscribe to specific keys for change notifications from main process
|
||||
* @param keys Array of preference keys to subscribe to
|
||||
* @returns Promise that resolves when subscription is established
|
||||
*/
|
||||
private async subscribeToKeyInternal(keys: PreferenceKeyType[]): Promise<void> {
|
||||
const keysToSubscribe = keys.filter((key) => !this.subscribedKeys.has(key))
|
||||
@ -382,6 +424,8 @@ export class PreferenceService {
|
||||
|
||||
/**
|
||||
* Subscribe to global preference changes (for useSyncExternalStore)
|
||||
* @param callback Function to call when any preference changes
|
||||
* @returns Unsubscribe function
|
||||
*/
|
||||
public subscribeAllChanges = (callback: () => void): (() => void) => {
|
||||
this.allChangesListeners.add(callback)
|
||||
@ -392,6 +436,8 @@ export class PreferenceService {
|
||||
|
||||
/**
|
||||
* Subscribe to specific key changes (for useSyncExternalStore)
|
||||
* @param key The preference key to watch for changes
|
||||
* @returns Function that takes a callback and returns an unsubscribe function
|
||||
*/
|
||||
public subscribeChange =
|
||||
(key: PreferenceKeyType) =>
|
||||
@ -416,6 +462,8 @@ export class PreferenceService {
|
||||
|
||||
/**
|
||||
* Get cached value without async fetch
|
||||
* @param key The preference key to retrieve from cache
|
||||
* @returns The cached value or undefined if not cached
|
||||
*/
|
||||
public getCachedValue<K extends PreferenceKeyType>(key: K): PreferenceDefaultScopeType[K] | undefined {
|
||||
return this.cache[key]
|
||||
@ -423,14 +471,16 @@ export class PreferenceService {
|
||||
|
||||
/**
|
||||
* Check if a preference is cached
|
||||
* @param key The preference key to check
|
||||
* @returns True if the key is cached, false otherwise
|
||||
*/
|
||||
public isCached(key: PreferenceKeyType): boolean {
|
||||
return key in this.cache && this.cache[key] !== undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all preferences from main process at once
|
||||
* Provides optimal performance by loading complete preference set into memory
|
||||
* Load all preferences from main process at once for optimal performance
|
||||
* @returns Promise resolving to all preference values
|
||||
*/
|
||||
public async preloadAll(): Promise<PreferenceDefaultScopeType> {
|
||||
try {
|
||||
@ -458,6 +508,7 @@ export class PreferenceService {
|
||||
|
||||
/**
|
||||
* Check if all preferences are loaded in cache
|
||||
* @returns True if full cache has been loaded, false otherwise
|
||||
*/
|
||||
public isFullyCached(): boolean {
|
||||
return this.fullCacheLoaded
|
||||
@ -465,6 +516,8 @@ export class PreferenceService {
|
||||
|
||||
/**
|
||||
* Preload specific preferences into cache
|
||||
* @param keys Array of preference keys to preload
|
||||
* @returns Promise that resolves when preloading completes
|
||||
*/
|
||||
public async preload(keys: PreferenceKeyType[]): Promise<void> {
|
||||
const uncachedKeys = keys.filter((key) => !this.isCached(key))
|
||||
@ -480,7 +533,9 @@ export class PreferenceService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm an optimistic update (called when main process confirms the update)
|
||||
* Confirm an optimistic update when main process confirms the update
|
||||
* @param key The preference key that was updated
|
||||
* @param requestId The unique identifier for the update request
|
||||
*/
|
||||
private confirmOptimistic(key: PreferenceKeyType, requestId: string): void {
|
||||
const optimisticState = this.optimisticValues.get(key)
|
||||
@ -498,7 +553,9 @@ export class PreferenceService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback an optimistic update (called on failure)
|
||||
* Rollback an optimistic update when main process update fails
|
||||
* @param key The preference key to rollback
|
||||
* @param requestId The unique identifier for the failed update request
|
||||
*/
|
||||
private rollbackOptimistic(key: PreferenceKeyType, requestId: string): void {
|
||||
const optimisticState = this.optimisticValues.get(key)
|
||||
@ -523,7 +580,8 @@ export class PreferenceService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all pending optimistic updates (for debugging)
|
||||
* Get all pending optimistic updates for debugging purposes
|
||||
* @returns Array of pending optimistic update information
|
||||
*/
|
||||
public getPendingOptimisticUpdates(): Array<{
|
||||
key: string
|
||||
@ -545,13 +603,18 @@ export class PreferenceService {
|
||||
|
||||
/**
|
||||
* Generate unique request ID for tracking concurrent requests
|
||||
* @returns Unique request identifier string
|
||||
*/
|
||||
private generateRequestId(): string {
|
||||
return `req_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Add request to queue for a specific key
|
||||
* Add request to queue for a specific key to prevent race conditions
|
||||
* @param key The preference key to update
|
||||
* @param requestId Unique identifier for this request
|
||||
* @param value The value to set
|
||||
* @returns Promise that resolves when the request is processed
|
||||
*/
|
||||
private enqueueRequest(key: PreferenceKeyType, requestId: string, value: any): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
@ -571,6 +634,8 @@ export class PreferenceService {
|
||||
|
||||
/**
|
||||
* Process the next queued request for a key
|
||||
* @param key The preference key to process requests for
|
||||
* @returns Promise that resolves when processing completes
|
||||
*/
|
||||
private async processNextQueuedRequest(key: PreferenceKeyType): Promise<void> {
|
||||
const queue = this.requestQueues.get(key)
|
||||
@ -589,6 +654,7 @@ export class PreferenceService {
|
||||
|
||||
/**
|
||||
* Complete current request and process next in queue
|
||||
* @param key The preference key to complete processing for
|
||||
*/
|
||||
private completeQueuedRequest(key: PreferenceKeyType): void {
|
||||
const queue = this.requestQueues.get(key)
|
||||
@ -606,7 +672,7 @@ export class PreferenceService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all cached preferences (for testing/debugging)
|
||||
* Clear all cached preferences for testing/debugging
|
||||
*/
|
||||
public clearCache(): void {
|
||||
this.cache = {}
|
||||
@ -615,7 +681,7 @@ export class PreferenceService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup service (call when shutting down)
|
||||
* Cleanup service resources - call when shutting down
|
||||
*/
|
||||
public cleanup(): void {
|
||||
if (this.changeListenerCleanup) {
|
||||
|
||||
@ -17,9 +17,9 @@ const logger = loggerService.withContext('usePreference')
|
||||
*
|
||||
* @param key - The preference key to manage (must be a valid PreferenceKeyType)
|
||||
* @param options - Optional configuration for update behavior:
|
||||
* - strategy: 'optimistic' (default) for immediate UI updates, 'pessimistic' for database-first updates
|
||||
* - optimistic: true (default) for immediate UI updates, false for database-first updates
|
||||
* @returns A tuple [value, setValue] where:
|
||||
* - value: Current preference value or undefined if not loaded/cached
|
||||
* - value: Current preference value with defaults applied (never undefined)
|
||||
* - setValue: Async function to update the preference value
|
||||
*
|
||||
* @example
|
||||
@ -28,19 +28,14 @@ const logger = loggerService.withContext('usePreference')
|
||||
* const [theme, setTheme] = usePreference('app.theme.mode')
|
||||
*
|
||||
* // Pessimistic updates for critical settings
|
||||
* const [apiKey, setApiKey] = usePreference('api.key', { strategy: 'pessimistic' })
|
||||
* const [apiKey, setApiKey] = usePreference('api.key', { optimistic: false })
|
||||
*
|
||||
* // Simple optimistic updates
|
||||
* const [fontSize, setFontSize] = usePreference('chat.message.font_size', {
|
||||
* strategy: 'optimistic'
|
||||
* optimistic: true
|
||||
* })
|
||||
*
|
||||
* // Conditional rendering based on preference value
|
||||
* if (theme === undefined) {
|
||||
* return <LoadingSpinner />
|
||||
* }
|
||||
*
|
||||
* // Updating preference value
|
||||
* // Value is never undefined - defaults are applied automatically
|
||||
* const handleThemeChange = async (newTheme: string) => {
|
||||
* try {
|
||||
* await setTheme(newTheme) // UI updates immediately with optimistic strategy
|
||||
@ -62,7 +57,7 @@ const logger = loggerService.withContext('usePreference')
|
||||
* ```typescript
|
||||
* // Advanced usage with form handling for message font size
|
||||
* const [fontSize, setFontSize] = usePreference('chat.message.font_size', {
|
||||
* strategy: 'optimistic' // Immediate feedback for UI preferences
|
||||
* optimistic: true // Immediate feedback for UI preferences
|
||||
* })
|
||||
*
|
||||
* const handleFontSizeChange = useCallback(async (size: number) => {
|
||||
@ -75,7 +70,7 @@ const logger = loggerService.withContext('usePreference')
|
||||
* return (
|
||||
* <input
|
||||
* type="number"
|
||||
* value={fontSize ?? 14}
|
||||
* value={fontSize}
|
||||
* onChange={(e) => handleFontSizeChange(Number(e.target.value))}
|
||||
* min={8}
|
||||
* max={72}
|
||||
@ -131,9 +126,9 @@ export function usePreference<K extends PreferenceKeyType>(
|
||||
* @param keys - Object mapping local names to preference keys. Keys are your custom names,
|
||||
* values must be valid PreferenceKeyType identifiers
|
||||
* @param options - Optional configuration for update behavior:
|
||||
* - strategy: 'optimistic' (default) for immediate UI updates, 'pessimistic' for database-first updates
|
||||
* - optimistic: true (default) for immediate UI updates, false for database-first updates
|
||||
* @returns A tuple [values, updateValues] where:
|
||||
* - values: Object with your local keys mapped to current preference values (undefined if not loaded)
|
||||
* - values: Object with your local keys mapped to current preference values with defaults applied
|
||||
* - updateValues: Async function to batch update multiple preferences at once
|
||||
*
|
||||
* @example
|
||||
@ -149,12 +144,12 @@ export function usePreference<K extends PreferenceKeyType>(
|
||||
* const [apiSettings, setApiSettings] = useMultiplePreferences({
|
||||
* apiKey: 'api.key',
|
||||
* endpoint: 'api.endpoint'
|
||||
* }, { strategy: 'pessimistic' })
|
||||
* }, { optimistic: false })
|
||||
*
|
||||
* // Accessing individual values with type safety
|
||||
* const currentTheme = uiSettings.theme // string | undefined
|
||||
* const currentFontSize = uiSettings.fontSize // number | undefined
|
||||
* const showLines = uiSettings.showLineNumbers // boolean | undefined
|
||||
* // Accessing individual values with type safety (defaults applied automatically)
|
||||
* const currentTheme = uiSettings.theme // string (never undefined)
|
||||
* const currentFontSize = uiSettings.fontSize // number (never undefined)
|
||||
* const showLines = uiSettings.showLineNumbers // boolean (never undefined)
|
||||
*
|
||||
* // Batch updating multiple preferences
|
||||
* const resetToDefaults = async () => {
|
||||
@ -198,11 +193,7 @@ export function usePreference<K extends PreferenceKeyType>(
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // Conditional rendering based on loading state
|
||||
* if (Object.values(settings).every(val => val === undefined)) {
|
||||
* return <SettingsSkeletonLoader />
|
||||
* }
|
||||
*
|
||||
* // No need to check for undefined - defaults are applied automatically
|
||||
* return (
|
||||
* <form onSubmit={(e) => {
|
||||
* e.preventDefault()
|
||||
@ -214,13 +205,13 @@ export function usePreference<K extends PreferenceKeyType>(
|
||||
* <input
|
||||
* name="maxBackups"
|
||||
* type="number"
|
||||
* defaultValue={settings.maxBackups ?? 10}
|
||||
* defaultValue={settings.maxBackups}
|
||||
* min="0"
|
||||
* />
|
||||
* <input
|
||||
* name="syncInterval"
|
||||
* type="number"
|
||||
* defaultValue={settings.syncInterval ?? 3600}
|
||||
* defaultValue={settings.syncInterval}
|
||||
* min="60"
|
||||
* />
|
||||
* <button type="submit">Save Backup Settings</button>
|
||||
@ -241,12 +232,13 @@ export function usePreference<K extends PreferenceKeyType>(
|
||||
*
|
||||
* // Single subscription handles all code preferences
|
||||
* // More efficient than 5 separate usePreference calls
|
||||
* // No need for null checks - defaults are already applied
|
||||
* const codeConfig = useMemo(() => ({
|
||||
* showLineNumbers: codePrefs.showLineNumbers ?? false,
|
||||
* wrappable: codePrefs.wrappable ?? false,
|
||||
* collapsible: codePrefs.collapsible ?? false,
|
||||
* autocompletion: codePrefs.autocompletion ?? true,
|
||||
* foldGutter: codePrefs.foldGutter ?? false
|
||||
* showLineNumbers: codePrefs.showLineNumbers,
|
||||
* wrappable: codePrefs.wrappable,
|
||||
* collapsible: codePrefs.collapsible,
|
||||
* autocompletion: codePrefs.autocompletion,
|
||||
* foldGutter: codePrefs.foldGutter
|
||||
* }), [codePrefs])
|
||||
*
|
||||
* return <CodeBlock config={codeConfig} />
|
||||
|
||||
Loading…
Reference in New Issue
Block a user