mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-10 23:59:45 +08:00
chore: update configuration and improve cache management
- Added "packages/ui/scripts/**" to .oxlintrc.json for linting. - Excluded ".claude/**" from biome.jsonc. - Refactored API path types in apiPaths.ts for better clarity. - Updated error handling in errorCodes.ts to ensure stack trace is always available. - Modified preferenceSchemas.ts to include new features and updated generated timestamp. - Cleaned up tsconfig.json for better organization. - Adjusted CustomTag component to improve rendering logic. - Enhanced CodeEditor utility functions for better type safety. - Improved Scrollbar story for better readability. - Refactored CacheService to streamline comments and improve documentation. - Updated useCache and useSharedCache hooks for better clarity and functionality. - Cleaned up selectionStore and settings.ts by commenting out deprecated actions. - Updated DataApiHookTests for better optimistic update handling.
This commit is contained in:
parent
8981d0a09d
commit
c0cca4ae44
@ -27,7 +27,7 @@
|
|||||||
"env": {
|
"env": {
|
||||||
"node": true
|
"node": true
|
||||||
},
|
},
|
||||||
"files": ["src/main/**", "resources/scripts/**", "scripts/**", "playwright.config.ts", "electron.vite.config.ts"]
|
"files": ["src/main/**", "resources/scripts/**", "scripts/**", "playwright.config.ts", "electron.vite.config.ts", "packages/ui/scripts/**"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"env": {
|
"env": {
|
||||||
|
|||||||
@ -38,6 +38,7 @@
|
|||||||
"!.github/**",
|
"!.github/**",
|
||||||
"!.husky/**",
|
"!.husky/**",
|
||||||
"!.vscode/**",
|
"!.vscode/**",
|
||||||
|
"!.claude/**",
|
||||||
"!*.yaml",
|
"!*.yaml",
|
||||||
"!*.yml",
|
"!*.yml",
|
||||||
"!*.mjs",
|
"!*.mjs",
|
||||||
|
|||||||
@ -35,29 +35,26 @@ export type MatchApiPath<Path extends string> = {
|
|||||||
/**
|
/**
|
||||||
* Extract query parameters type for a given concrete path
|
* Extract query parameters type for a given concrete path
|
||||||
*/
|
*/
|
||||||
export type QueryParamsForPath<Path extends string> =
|
export type QueryParamsForPath<Path extends string> = MatchApiPath<Path> extends keyof ApiSchemas
|
||||||
MatchApiPath<Path> extends keyof ApiSchemas
|
? ApiSchemas[MatchApiPath<Path>] extends { GET: { query?: infer Q } }
|
||||||
? ApiSchemas[MatchApiPath<Path>] extends { GET: { query?: infer Q } }
|
? Q
|
||||||
? Q
|
|
||||||
: Record<string, any>
|
|
||||||
: Record<string, any>
|
: Record<string, any>
|
||||||
|
: Record<string, any>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract request body type for a given concrete path and HTTP method
|
* Extract request body type for a given concrete path and HTTP method
|
||||||
*/
|
*/
|
||||||
export type BodyForPath<Path extends string, Method extends string> =
|
export type BodyForPath<Path extends string, Method extends string> = MatchApiPath<Path> extends keyof ApiSchemas
|
||||||
MatchApiPath<Path> extends keyof ApiSchemas
|
? ApiSchemas[MatchApiPath<Path>] extends { [M in Method]: { body: infer B } }
|
||||||
? ApiSchemas[MatchApiPath<Path>] extends { [M in Method]: { body: infer B } }
|
? B
|
||||||
? B
|
|
||||||
: any
|
|
||||||
: any
|
: any
|
||||||
|
: any
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract response type for a given concrete path and HTTP method
|
* Extract response type for a given concrete path and HTTP method
|
||||||
*/
|
*/
|
||||||
export type ResponseForPath<Path extends string, Method extends string> =
|
export type ResponseForPath<Path extends string, Method extends string> = MatchApiPath<Path> extends keyof ApiSchemas
|
||||||
MatchApiPath<Path> extends keyof ApiSchemas
|
? ApiSchemas[MatchApiPath<Path>] extends { [M in Method]: { response: infer R } }
|
||||||
? ApiSchemas[MatchApiPath<Path>] extends { [M in Method]: { response: infer R } }
|
? R
|
||||||
? R
|
|
||||||
: any
|
|
||||||
: any
|
: any
|
||||||
|
: any
|
||||||
|
|||||||
@ -68,7 +68,7 @@ export class DataApiErrorFactory {
|
|||||||
message: customMessage || ERROR_MESSAGES[code],
|
message: customMessage || ERROR_MESSAGES[code],
|
||||||
status: ERROR_STATUS_MAP[code],
|
status: ERROR_STATUS_MAP[code],
|
||||||
details,
|
details,
|
||||||
stack: process.env.NODE_ENV === 'development' ? stack : undefined
|
stack: stack || undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Auto-generated preferences configuration
|
* Auto-generated preferences configuration
|
||||||
* Generated at: 2025-09-14T09:02:01.333Z
|
* Generated at: 2025-09-16T03:17:03.354Z
|
||||||
*
|
*
|
||||||
* This file is automatically generated from classification.json
|
* This file is automatically generated from classification.json
|
||||||
* To update this file, modify classification.json and run:
|
* To update this file, modify classification.json and run:
|
||||||
@ -309,12 +309,16 @@ export interface PreferenceSchemas {
|
|||||||
'feature.notes.font_size': number
|
'feature.notes.font_size': number
|
||||||
// redux/note/settings.isFullWidth
|
// redux/note/settings.isFullWidth
|
||||||
'feature.notes.full_width': boolean
|
'feature.notes.full_width': boolean
|
||||||
|
// redux/note/notesPath
|
||||||
|
'feature.notes.path': string
|
||||||
// redux/note/settings.showTabStatus
|
// redux/note/settings.showTabStatus
|
||||||
'feature.notes.show_tab_status': boolean
|
'feature.notes.show_tab_status': boolean
|
||||||
// redux/note/settings.showTableOfContents
|
// redux/note/settings.showTableOfContents
|
||||||
'feature.notes.show_table_of_contents': boolean
|
'feature.notes.show_table_of_contents': boolean
|
||||||
// redux/note/settings.showWorkspace
|
// redux/note/settings.showWorkspace
|
||||||
'feature.notes.show_workspace': boolean
|
'feature.notes.show_workspace': boolean
|
||||||
|
// redux/note/sortType
|
||||||
|
'feature.notes.sort_type': string
|
||||||
// redux/settings/clickTrayToShowQuickAssistant
|
// redux/settings/clickTrayToShowQuickAssistant
|
||||||
'feature.quick_assistant.click_tray_to_show': boolean
|
'feature.quick_assistant.click_tray_to_show': boolean
|
||||||
// redux/settings/enableQuickAssistant
|
// redux/settings/enableQuickAssistant
|
||||||
@ -560,9 +564,11 @@ export const DefaultPreferences: PreferenceSchemas = {
|
|||||||
'feature.notes.font_family': 'default',
|
'feature.notes.font_family': 'default',
|
||||||
'feature.notes.font_size': 16,
|
'feature.notes.font_size': 16,
|
||||||
'feature.notes.full_width': true,
|
'feature.notes.full_width': true,
|
||||||
|
'feature.notes.path': '',
|
||||||
'feature.notes.show_tab_status': true,
|
'feature.notes.show_tab_status': true,
|
||||||
'feature.notes.show_table_of_contents': true,
|
'feature.notes.show_table_of_contents': true,
|
||||||
'feature.notes.show_workspace': true,
|
'feature.notes.show_workspace': true,
|
||||||
|
'feature.notes.sort_type': 'sort_a2z',
|
||||||
'feature.quick_assistant.click_tray_to_show': false,
|
'feature.quick_assistant.click_tray_to_show': false,
|
||||||
'feature.quick_assistant.enabled': false,
|
'feature.quick_assistant.enabled': false,
|
||||||
'feature.quick_assistant.read_clipboard_at_startup': true,
|
'feature.quick_assistant.read_clipboard_at_startup': true,
|
||||||
@ -674,8 +680,8 @@ export const DefaultPreferences: PreferenceSchemas = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成统计:
|
* 生成统计:
|
||||||
* - 总配置项: 195
|
* - 总配置项: 197
|
||||||
* - electronStore项: 1
|
* - electronStore项: 1
|
||||||
* - redux项: 194
|
* - redux项: 196
|
||||||
* - localStorage项: 0
|
* - localStorage项: 0
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -44,7 +44,7 @@ const CustomTag: FC<CustomTagProps> = ({
|
|||||||
...(disabled && { cursor: 'not-allowed' }),
|
...(disabled && { cursor: 'not-allowed' }),
|
||||||
...style
|
...style
|
||||||
}}>
|
}}>
|
||||||
{icon && icon} {children}
|
{icon} {children}
|
||||||
{closable && (
|
{closable && (
|
||||||
<CloseIcon
|
<CloseIcon
|
||||||
$size={size}
|
$size={size}
|
||||||
|
|||||||
@ -95,7 +95,7 @@ export async function getNormalizedExtension(language: string) {
|
|||||||
export function getCmThemeNames(): string[] {
|
export function getCmThemeNames(): string[] {
|
||||||
return ['auto', 'light', 'dark']
|
return ['auto', 'light', 'dark']
|
||||||
.concat(Object.keys(cmThemes))
|
.concat(Object.keys(cmThemes))
|
||||||
.filter((item) => typeof cmThemes[item as keyof typeof cmThemes] !== 'function')
|
.filter((item) => typeof (cmThemes as any)[item] !== 'function')
|
||||||
.filter((item) => !/^(defaultSettings)/.test(item as string) && !/(Style)$/.test(item as string))
|
.filter((item) => !/^(defaultSettings)/.test(item as string) && !/(Style)$/.test(item as string))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -192,14 +192,15 @@ export const LongArticle: Story = {
|
|||||||
|
|
||||||
<p className="mb-4">
|
<p className="mb-4">
|
||||||
Scrolling is a fundamental interaction pattern in user interfaces. It allows users to navigate through content
|
Scrolling is a fundamental interaction pattern in user interfaces. It allows users to navigate through content
|
||||||
that exceeds the visible viewport, making it possible to present large amounts of information in a limited space.
|
that exceeds the visible viewport, making it possible to present large amounts of information in a limited
|
||||||
|
space.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2 className="mb-3 text-xl font-semibold">History of Scrolling</h2>
|
<h2 className="mb-3 text-xl font-semibold">History of Scrolling</h2>
|
||||||
<p className="mb-4">
|
<p className="mb-4">
|
||||||
The concept of scrolling dates back to the early days of computing, when terminal displays could only show a
|
The concept of scrolling dates back to the early days of computing, when terminal displays could only show a
|
||||||
limited number of lines. As content grew beyond what could fit on a single screen, the need for scrolling became
|
limited number of lines. As content grew beyond what could fit on a single screen, the need for scrolling
|
||||||
apparent.
|
became apparent.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2 className="mb-3 text-xl font-semibold">Types of Scrolling</h2>
|
<h2 className="mb-3 text-xl font-semibold">Types of Scrolling</h2>
|
||||||
@ -211,9 +212,7 @@ export const LongArticle: Story = {
|
|||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2 className="mb-3 text-xl font-semibold">Best Practices</h2>
|
<h2 className="mb-3 text-xl font-semibold">Best Practices</h2>
|
||||||
<p className="mb-4">
|
<p className="mb-4">When implementing scrolling in your applications, consider the following best practices:</p>
|
||||||
When implementing scrolling in your applications, consider the following best practices:
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<ol className="mb-4 ml-6 list-decimal">
|
<ol className="mb-4 ml-6 list-decimal">
|
||||||
<li className="mb-2">Always provide visual feedback for scrollable areas</li>
|
<li className="mb-2">Always provide visual feedback for scrollable areas</li>
|
||||||
@ -243,8 +242,8 @@ export const LongArticle: Story = {
|
|||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p className="mb-4">
|
<p className="mb-4">
|
||||||
To optimize scrolling performance, consider using techniques like virtual scrolling for large lists, debouncing
|
To optimize scrolling performance, consider using techniques like virtual scrolling for large lists,
|
||||||
scroll event handlers, and leveraging CSS transforms for animations.
|
debouncing scroll event handlers, and leveraging CSS transforms for animations.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
)
|
)
|
||||||
@ -256,4 +255,4 @@ export const LongArticle: Story = {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,13 +17,6 @@
|
|||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src/**/*"],
|
||||||
"src/**/*"
|
"exclude": ["node_modules", "dist", "**/*.test.*", "**/__tests__/**"]
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"node_modules",
|
|
||||||
"dist",
|
|
||||||
"**/*.test.*",
|
|
||||||
"**/__tests__/**"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -105,33 +105,7 @@ export class CacheService {
|
|||||||
|
|
||||||
// ============ Persist Cache Interface (Reserved) ============
|
// ============ Persist Cache Interface (Reserved) ============
|
||||||
|
|
||||||
/**
|
// TODO: Implement persist cache in future
|
||||||
* Get persist cache value (interface reserved for future)
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
getPersist<T>(_key: string): T | undefined {
|
|
||||||
// TODO: Implement persist cache in future
|
|
||||||
logger.warn('getPersist not implemented yet')
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set persist cache value (interface reserved for future)
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
setPersist<T>(_key: string, _value: T): void {
|
|
||||||
// TODO: Implement persist cache in future
|
|
||||||
logger.warn('setPersist not implemented yet')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check persist cache key (interface reserved for future)
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
hasPersist(_key: string): boolean {
|
|
||||||
// TODO: Implement persist cache in future
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============ IPC Handlers for Cache Synchronization ============
|
// ============ IPC Handlers for Cache Synchronization ============
|
||||||
|
|
||||||
|
|||||||
@ -26,12 +26,7 @@ class PreferenceNotifier {
|
|||||||
* @param metadata - Optional metadata for debugging (unused but kept for API compatibility)
|
* @param metadata - Optional metadata for debugging (unused but kept for API compatibility)
|
||||||
* @returns Unsubscribe function
|
* @returns Unsubscribe function
|
||||||
*/
|
*/
|
||||||
subscribe = (
|
subscribe = (key: string, callback: (key: string, newValue: any, oldValue?: any) => void): (() => void) => {
|
||||||
key: string,
|
|
||||||
callback: (key: string, newValue: any, oldValue?: any) => void,
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
_metadata?: string
|
|
||||||
): (() => void) => {
|
|
||||||
if (!this.subscriptions.has(key)) {
|
if (!this.subscriptions.has(key)) {
|
||||||
this.subscriptions.set(key, new Set())
|
this.subscriptions.set(key, new Set())
|
||||||
}
|
}
|
||||||
@ -383,7 +378,7 @@ export class PreferenceService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.notifier.subscribe(key, listener, `subscribeChange-${key}`)
|
return this.notifier.subscribe(key, listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -401,9 +396,7 @@ export class PreferenceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Subscribe to all keys and collect unsubscribe functions
|
// Subscribe to all keys and collect unsubscribe functions
|
||||||
const unsubscribeFunctions = keys.map((key) =>
|
const unsubscribeFunctions = keys.map((key) => this.notifier.subscribe(key, listener))
|
||||||
this.notifier.subscribe(key, listener, `subscribeMultipleChanges-${key}`)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Return a function that unsubscribes from all keys
|
// Return a function that unsubscribes from all keys
|
||||||
return () => {
|
return () => {
|
||||||
|
|||||||
@ -76,13 +76,7 @@ export class TestService {
|
|||||||
* Get paginated list of test items
|
* Get paginated list of test items
|
||||||
*/
|
*/
|
||||||
async getItems(
|
async getItems(
|
||||||
params: {
|
params: { page?: number; limit?: number; type?: string; status?: string; search?: string } = {}
|
||||||
page?: number
|
|
||||||
limit?: number
|
|
||||||
type?: string
|
|
||||||
status?: string
|
|
||||||
search?: string
|
|
||||||
} = {}
|
|
||||||
): Promise<{
|
): Promise<{
|
||||||
items: any[]
|
items: any[]
|
||||||
total: number
|
total: number
|
||||||
|
|||||||
@ -8,7 +8,6 @@ if (process.contextIsolated) {
|
|||||||
try {
|
try {
|
||||||
contextBridge.exposeInMainWorld('electron', electronAPI)
|
contextBridge.exposeInMainWorld('electron', electronAPI)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
|
||||||
console.error('[Preload]Failed to expose APIs:', error as Error)
|
console.error('[Preload]Failed to expose APIs:', error as Error)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -61,6 +61,9 @@ export class CacheService {
|
|||||||
return CacheService.instance
|
return CacheService.instance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the cache service with persist cache loading and IPC listeners
|
||||||
|
*/
|
||||||
public initialize(): void {
|
public initialize(): void {
|
||||||
this.loadPersistCache()
|
this.loadPersistCache()
|
||||||
this.setupIpcListeners()
|
this.setupIpcListeners()
|
||||||
@ -71,7 +74,9 @@ export class CacheService {
|
|||||||
// ============ Memory Cache (Cross-component) ============
|
// ============ Memory Cache (Cross-component) ============
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get value from memory cache
|
* Get value from memory cache with TTL validation
|
||||||
|
* @param key - Cache key to retrieve
|
||||||
|
* @returns Cached value or undefined if not found or expired
|
||||||
*/
|
*/
|
||||||
get<K extends UseCacheKey>(key: K): UseCacheSchema[K]
|
get<K extends UseCacheKey>(key: K): UseCacheSchema[K]
|
||||||
get<T>(key: Exclude<string, UseCacheKey>): T | undefined
|
get<T>(key: Exclude<string, UseCacheKey>): T | undefined
|
||||||
@ -91,7 +96,10 @@ export class CacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set value in memory cache
|
* Set value in memory cache with optional TTL
|
||||||
|
* @param key - Cache key to store
|
||||||
|
* @param value - Value to cache
|
||||||
|
* @param ttl - Time to live in milliseconds (optional)
|
||||||
*/
|
*/
|
||||||
set<K extends UseCacheKey>(key: K, value: UseCacheSchema[K]): void
|
set<K extends UseCacheKey>(key: K, value: UseCacheSchema[K]): void
|
||||||
set<T>(key: Exclude<string, UseCacheKey>, value: T, ttl?: number): void
|
set<T>(key: Exclude<string, UseCacheKey>, value: T, ttl?: number): void
|
||||||
@ -122,7 +130,9 @@ export class CacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if key exists in memory cache
|
* Check if key exists in memory cache and is not expired
|
||||||
|
* @param key - Cache key to check
|
||||||
|
* @returns True if key exists and is valid, false otherwise
|
||||||
*/
|
*/
|
||||||
|
|
||||||
has<K extends UseCacheKey>(key: K): boolean
|
has<K extends UseCacheKey>(key: K): boolean
|
||||||
@ -144,7 +154,9 @@ export class CacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete from memory cache
|
* Delete from memory cache with hook protection
|
||||||
|
* @param key - Cache key to delete
|
||||||
|
* @returns True if deletion succeeded, false if key is protected by active hooks
|
||||||
*/
|
*/
|
||||||
delete<K extends UseCacheKey>(key: K): boolean
|
delete<K extends UseCacheKey>(key: K): boolean
|
||||||
delete(key: Exclude<string, UseCacheKey>): boolean
|
delete(key: Exclude<string, UseCacheKey>): boolean
|
||||||
@ -168,7 +180,9 @@ export class CacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a key has TTL set (for warning purposes)
|
* Check if a key has TTL set in memory cache
|
||||||
|
* @param key - Cache key to check
|
||||||
|
* @returns True if key has TTL configured
|
||||||
*/
|
*/
|
||||||
hasTTL<K extends UseCacheKey>(key: K): boolean
|
hasTTL<K extends UseCacheKey>(key: K): boolean
|
||||||
hasTTL(key: Exclude<string, UseCacheKey>): boolean
|
hasTTL(key: Exclude<string, UseCacheKey>): boolean
|
||||||
@ -178,7 +192,9 @@ export class CacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a shared cache key has TTL set (for warning purposes)
|
* Check if a shared cache key has TTL set
|
||||||
|
* @param key - Shared cache key to check
|
||||||
|
* @returns True if key has TTL configured
|
||||||
*/
|
*/
|
||||||
hasSharedTTL<K extends UseSharedCacheKey>(key: K): boolean
|
hasSharedTTL<K extends UseSharedCacheKey>(key: K): boolean
|
||||||
hasSharedTTL(key: Exclude<string, UseSharedCacheKey>): boolean
|
hasSharedTTL(key: Exclude<string, UseSharedCacheKey>): boolean
|
||||||
@ -190,7 +206,9 @@ export class CacheService {
|
|||||||
// ============ Shared Cache (Cross-window) ============
|
// ============ Shared Cache (Cross-window) ============
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get value from shared cache
|
* Get value from shared cache with TTL validation
|
||||||
|
* @param key - Shared cache key to retrieve
|
||||||
|
* @returns Cached value or undefined if not found or expired
|
||||||
*/
|
*/
|
||||||
getShared<K extends UseSharedCacheKey>(key: K): UseSharedCacheSchema[K]
|
getShared<K extends UseSharedCacheKey>(key: K): UseSharedCacheSchema[K]
|
||||||
getShared<T>(key: Exclude<string, UseSharedCacheKey>): T | undefined
|
getShared<T>(key: Exclude<string, UseSharedCacheKey>): T | undefined
|
||||||
@ -209,7 +227,10 @@ export class CacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set value in shared cache
|
* Set value in shared cache with cross-window synchronization
|
||||||
|
* @param key - Shared cache key to store
|
||||||
|
* @param value - Value to cache
|
||||||
|
* @param ttl - Time to live in milliseconds (optional)
|
||||||
*/
|
*/
|
||||||
setShared<K extends UseSharedCacheKey>(key: K, value: UseSharedCacheSchema[K]): void
|
setShared<K extends UseSharedCacheKey>(key: K, value: UseSharedCacheSchema[K]): void
|
||||||
setShared<T>(key: Exclude<string, UseSharedCacheKey>, value: T, ttl?: number): void
|
setShared<T>(key: Exclude<string, UseSharedCacheKey>, value: T, ttl?: number): void
|
||||||
@ -256,7 +277,9 @@ export class CacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if key exists in shared cache
|
* Check if key exists in shared cache and is not expired
|
||||||
|
* @param key - Shared cache key to check
|
||||||
|
* @returns True if key exists and is valid, false otherwise
|
||||||
*/
|
*/
|
||||||
hasShared<K extends UseSharedCacheKey>(key: K): boolean
|
hasShared<K extends UseSharedCacheKey>(key: K): boolean
|
||||||
hasShared(key: Exclude<string, UseSharedCacheKey>): boolean
|
hasShared(key: Exclude<string, UseSharedCacheKey>): boolean
|
||||||
@ -275,7 +298,9 @@ export class CacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete from shared cache
|
* Delete from shared cache with cross-window synchronization and hook protection
|
||||||
|
* @param key - Shared cache key to delete
|
||||||
|
* @returns True if deletion succeeded, false if key is protected by active hooks
|
||||||
*/
|
*/
|
||||||
deleteShared<K extends UseSharedCacheKey>(key: K): boolean
|
deleteShared<K extends UseSharedCacheKey>(key: K): boolean
|
||||||
deleteShared(key: Exclude<string, UseSharedCacheKey>): boolean
|
deleteShared(key: Exclude<string, UseSharedCacheKey>): boolean
|
||||||
@ -308,7 +333,9 @@ export class CacheService {
|
|||||||
// ============ Persist Cache (Cross-window + localStorage) ============
|
// ============ Persist Cache (Cross-window + localStorage) ============
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get value from persist cache
|
* Get value from persist cache with automatic default value fallback
|
||||||
|
* @param key - Persist cache key to retrieve
|
||||||
|
* @returns Cached value or default value if not found
|
||||||
*/
|
*/
|
||||||
getPersist<K extends RendererPersistCacheKey>(key: K): RendererPersistCacheSchema[K] {
|
getPersist<K extends RendererPersistCacheKey>(key: K): RendererPersistCacheSchema[K] {
|
||||||
const value = this.persistCache.get(key)
|
const value = this.persistCache.get(key)
|
||||||
@ -325,7 +352,9 @@ export class CacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set value in persist cache
|
* Set value in persist cache with cross-window sync and localStorage persistence
|
||||||
|
* @param key - Persist cache key to store
|
||||||
|
* @param value - Value to cache (must match schema type)
|
||||||
*/
|
*/
|
||||||
setPersist<K extends RendererPersistCacheKey>(key: K, value: RendererPersistCacheSchema[K]): void {
|
setPersist<K extends RendererPersistCacheKey>(key: K, value: RendererPersistCacheSchema[K]): void {
|
||||||
const existingValue = this.persistCache.get(key)
|
const existingValue = this.persistCache.get(key)
|
||||||
@ -353,6 +382,8 @@ export class CacheService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if key exists in persist cache
|
* Check if key exists in persist cache
|
||||||
|
* @param key - Persist cache key to check
|
||||||
|
* @returns True if key exists in cache
|
||||||
*/
|
*/
|
||||||
hasPersist(key: RendererPersistCacheKey): boolean {
|
hasPersist(key: RendererPersistCacheKey): boolean {
|
||||||
return this.persistCache.has(key)
|
return this.persistCache.has(key)
|
||||||
@ -363,14 +394,16 @@ export class CacheService {
|
|||||||
// ============ Hook Reference Management ============
|
// ============ Hook Reference Management ============
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a hook as using a specific key
|
* Register a hook as using a specific cache key to prevent deletion
|
||||||
|
* @param key - Cache key being used by the hook
|
||||||
*/
|
*/
|
||||||
registerHook(key: string): void {
|
registerHook(key: string): void {
|
||||||
this.activeHooks.add(key)
|
this.activeHooks.add(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unregister a hook from using a specific key
|
* Unregister a hook from using a specific cache key
|
||||||
|
* @param key - Cache key no longer being used by the hook
|
||||||
*/
|
*/
|
||||||
unregisterHook(key: string): void {
|
unregisterHook(key: string): void {
|
||||||
this.activeHooks.delete(key)
|
this.activeHooks.delete(key)
|
||||||
@ -379,7 +412,10 @@ export class CacheService {
|
|||||||
// ============ Subscription Management ============
|
// ============ Subscription Management ============
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscribe to cache changes for specific key
|
* Subscribe to cache changes for a specific key
|
||||||
|
* @param key - Cache key to watch for changes
|
||||||
|
* @param callback - Function to call when key changes
|
||||||
|
* @returns Unsubscribe function
|
||||||
*/
|
*/
|
||||||
subscribe(key: string, callback: CacheSubscriber): () => void {
|
subscribe(key: string, callback: CacheSubscriber): () => void {
|
||||||
if (!this.subscribers.has(key)) {
|
if (!this.subscribers.has(key)) {
|
||||||
@ -398,7 +434,8 @@ export class CacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify subscribers for specific key
|
* Notify all subscribers when a cache key changes
|
||||||
|
* @param key - Cache key that changed
|
||||||
*/
|
*/
|
||||||
notifySubscribers(key: string): void {
|
notifySubscribers(key: string): void {
|
||||||
const keySubscribers = this.subscribers.get(key)
|
const keySubscribers = this.subscribers.get(key)
|
||||||
@ -416,7 +453,10 @@ export class CacheService {
|
|||||||
// ============ Private Methods ============
|
// ============ Private Methods ============
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deep equality comparison for cache values
|
* Perform deep equality comparison for cache values
|
||||||
|
* @param a - First value to compare
|
||||||
|
* @param b - Second value to compare
|
||||||
|
* @returns True if values are deeply equal
|
||||||
*/
|
*/
|
||||||
private deepEqual(a: any, b: any): boolean {
|
private deepEqual(a: any, b: any): boolean {
|
||||||
// Use Object.is for primitive values and same reference
|
// Use Object.is for primitive values and same reference
|
||||||
@ -451,7 +491,7 @@ export class CacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load persist cache from localStorage
|
* Load persist cache from localStorage with default value initialization
|
||||||
*/
|
*/
|
||||||
private loadPersistCache(): void {
|
private loadPersistCache(): void {
|
||||||
// First, initialize with default values
|
// First, initialize with default values
|
||||||
@ -490,7 +530,7 @@ export class CacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save persist cache to localStorage
|
* Save persist cache to localStorage with size validation
|
||||||
*/
|
*/
|
||||||
private savePersistCache(): void {
|
private savePersistCache(): void {
|
||||||
try {
|
try {
|
||||||
@ -517,7 +557,7 @@ export class CacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Schedule persist cache save with debounce
|
* Schedule persist cache save with 200ms debounce to avoid excessive writes
|
||||||
*/
|
*/
|
||||||
private schedulePersistSave(): void {
|
private schedulePersistSave(): void {
|
||||||
this.persistDirty = true
|
this.persistDirty = true
|
||||||
@ -533,7 +573,8 @@ export class CacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Broadcast cache sync message to other windows
|
* Broadcast cache sync message to other windows via IPC
|
||||||
|
* @param message - Cache sync message to broadcast
|
||||||
*/
|
*/
|
||||||
private broadcastSync(message: CacheSyncMessage): void {
|
private broadcastSync(message: CacheSyncMessage): void {
|
||||||
if (window.api?.cache?.broadcastSync) {
|
if (window.api?.cache?.broadcastSync) {
|
||||||
@ -542,7 +583,7 @@ export class CacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup IPC listeners for cache synchronization
|
* Setup IPC listeners for receiving cache sync messages from other windows
|
||||||
*/
|
*/
|
||||||
private setupIpcListeners(): void {
|
private setupIpcListeners(): void {
|
||||||
if (!window.api?.cache?.onSync) {
|
if (!window.api?.cache?.onSync) {
|
||||||
@ -574,7 +615,7 @@ export class CacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup window unload handler to force save persist cache
|
* Setup window unload handler to ensure persist cache is saved before exit
|
||||||
*/
|
*/
|
||||||
private setupWindowUnloadHandler(): void {
|
private setupWindowUnloadHandler(): void {
|
||||||
window.addEventListener('beforeunload', () => {
|
window.addEventListener('beforeunload', () => {
|
||||||
@ -585,7 +626,7 @@ export class CacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cleanup service resources
|
* Cleanup service resources including timers, caches, and event listeners
|
||||||
*/
|
*/
|
||||||
public cleanup(): void {
|
public cleanup(): void {
|
||||||
// Force save persist cache if dirty
|
// Force save persist cache if dirty
|
||||||
|
|||||||
@ -13,30 +13,45 @@ import { useCallback, useEffect, useSyncExternalStore } from 'react'
|
|||||||
const logger = loggerService.withContext('useCache')
|
const logger = loggerService.withContext('useCache')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* React hook for cross-component memory cache
|
* React hook for component-level memory cache
|
||||||
*
|
*
|
||||||
* Features:
|
* Use this for data that needs to be shared between components in the same window.
|
||||||
* - Synchronous API with useSyncExternalStore
|
* Data is lost when the app restarts.
|
||||||
* - Automatic default value setting
|
|
||||||
* - Hook lifecycle management
|
|
||||||
* - TTL support with warning when used
|
|
||||||
*
|
*
|
||||||
* @param key - Cache key
|
* @param key - Cache key from the predefined schema
|
||||||
* @param initValue - Default value (set automatically if not exists)
|
* @param initValue - Initial value (optional, uses schema default if not provided)
|
||||||
* @returns [value, setValue]
|
* @returns [value, setValue] - Similar to useState but shared across components
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* // Basic usage
|
||||||
|
* const [theme, setTheme] = useCache('ui.theme')
|
||||||
|
*
|
||||||
|
* // With custom initial value
|
||||||
|
* const [count, setCount] = useCache('counter', 0)
|
||||||
|
*
|
||||||
|
* // Update the value
|
||||||
|
* setTheme('dark')
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
export function useCache<K extends UseCacheKey>(
|
export function useCache<K extends UseCacheKey>(
|
||||||
key: K,
|
key: K,
|
||||||
initValue?: UseCacheSchema[K]
|
initValue?: UseCacheSchema[K]
|
||||||
): [UseCacheSchema[K], (value: UseCacheSchema[K]) => void] {
|
): [UseCacheSchema[K], (value: UseCacheSchema[K]) => void] {
|
||||||
// Subscribe to cache changes
|
/**
|
||||||
|
* Subscribe to cache changes using React's useSyncExternalStore
|
||||||
|
* This ensures the component re-renders when the cache value changes
|
||||||
|
*/
|
||||||
const value = useSyncExternalStore(
|
const value = useSyncExternalStore(
|
||||||
useCallback((callback) => cacheService.subscribe(key, callback), [key]),
|
useCallback((callback) => cacheService.subscribe(key, callback), [key]),
|
||||||
useCallback(() => cacheService.get<UseCacheSchema[K]>(key), [key]),
|
useCallback(() => cacheService.get<UseCacheSchema[K]>(key), [key]),
|
||||||
useCallback(() => cacheService.get<UseCacheSchema[K]>(key), [key]) // SSR snapshot
|
useCallback(() => cacheService.get<UseCacheSchema[K]>(key), [key]) // SSR snapshot
|
||||||
)
|
)
|
||||||
|
|
||||||
// Set default value if not exists
|
/**
|
||||||
|
* Initialize cache with default value if it doesn't exist
|
||||||
|
* Priority: existing cache value > custom initValue > schema default
|
||||||
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (cacheService.has(key)) {
|
if (cacheService.has(key)) {
|
||||||
return
|
return
|
||||||
@ -49,13 +64,19 @@ export function useCache<K extends UseCacheKey>(
|
|||||||
}
|
}
|
||||||
}, [key, initValue])
|
}, [key, initValue])
|
||||||
|
|
||||||
// Register hook lifecycle
|
/**
|
||||||
|
* Register this hook as actively using the cache key
|
||||||
|
* This prevents the cache service from deleting the key while the hook is active
|
||||||
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
cacheService.registerHook(key)
|
cacheService.registerHook(key)
|
||||||
return () => cacheService.unregisterHook(key)
|
return () => cacheService.unregisterHook(key)
|
||||||
}, [key])
|
}, [key])
|
||||||
|
|
||||||
// Check for TTL warning
|
/**
|
||||||
|
* Warn developers when using TTL with hooks
|
||||||
|
* TTL can cause values to expire between renders, leading to unstable behavior
|
||||||
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (cacheService.hasTTL(key)) {
|
if (cacheService.hasTTL(key)) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
@ -64,6 +85,10 @@ export function useCache<K extends UseCacheKey>(
|
|||||||
}
|
}
|
||||||
}, [key])
|
}, [key])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Memoized setter function for updating the cache value
|
||||||
|
* @param newValue - New value to store in cache
|
||||||
|
*/
|
||||||
const setValue = useCallback(
|
const setValue = useCallback(
|
||||||
(newValue: UseCacheSchema[K]) => {
|
(newValue: UseCacheSchema[K]) => {
|
||||||
cacheService.set(key, newValue)
|
cacheService.set(key, newValue)
|
||||||
@ -77,28 +102,43 @@ export function useCache<K extends UseCacheKey>(
|
|||||||
/**
|
/**
|
||||||
* React hook for cross-window shared cache
|
* React hook for cross-window shared cache
|
||||||
*
|
*
|
||||||
* Features:
|
* Use this for data that needs to be shared between all app windows.
|
||||||
* - Synchronous API (uses local copy)
|
* Data is lost when the app restarts.
|
||||||
* - Cross-window synchronization via IPC
|
|
||||||
* - Automatic default value setting
|
|
||||||
* - Hook lifecycle management
|
|
||||||
*
|
*
|
||||||
* @param key - Cache key
|
* @param key - Cache key from the predefined schema
|
||||||
* @param initValue - Default value (set automatically if not exists)
|
* @param initValue - Initial value (optional, uses schema default if not provided)
|
||||||
* @returns [value, setValue]
|
* @returns [value, setValue] - Similar to useState but shared across all windows
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* // Shared across all windows
|
||||||
|
* const [windowCount, setWindowCount] = useSharedCache('app.windowCount')
|
||||||
|
*
|
||||||
|
* // With custom initial value
|
||||||
|
* const [sharedState, setSharedState] = useSharedCache('app.state', { loaded: false })
|
||||||
|
*
|
||||||
|
* // Changes automatically sync to all open windows
|
||||||
|
* setWindowCount(3)
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
export function useSharedCache<K extends UseSharedCacheKey>(
|
export function useSharedCache<K extends UseSharedCacheKey>(
|
||||||
key: K,
|
key: K,
|
||||||
initValue?: UseSharedCacheSchema[K]
|
initValue?: UseSharedCacheSchema[K]
|
||||||
): [UseSharedCacheSchema[K], (value: UseSharedCacheSchema[K]) => void] {
|
): [UseSharedCacheSchema[K], (value: UseSharedCacheSchema[K]) => void] {
|
||||||
// Subscribe to cache changes
|
/**
|
||||||
|
* Subscribe to shared cache changes using React's useSyncExternalStore
|
||||||
|
* This ensures the component re-renders when the shared cache value changes
|
||||||
|
*/
|
||||||
const value = useSyncExternalStore(
|
const value = useSyncExternalStore(
|
||||||
useCallback((callback) => cacheService.subscribe(key, callback), [key]),
|
useCallback((callback) => cacheService.subscribe(key, callback), [key]),
|
||||||
useCallback(() => cacheService.getShared<UseSharedCacheSchema[K]>(key), [key]),
|
useCallback(() => cacheService.getShared<UseSharedCacheSchema[K]>(key), [key]),
|
||||||
useCallback(() => cacheService.getShared<UseSharedCacheSchema[K]>(key), [key]) // SSR snapshot
|
useCallback(() => cacheService.getShared<UseSharedCacheSchema[K]>(key), [key]) // SSR snapshot
|
||||||
)
|
)
|
||||||
|
|
||||||
// Set default value if not exists
|
/**
|
||||||
|
* Initialize shared cache with default value if it doesn't exist
|
||||||
|
* Priority: existing shared cache value > custom initValue > schema default
|
||||||
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (cacheService.hasShared(key)) {
|
if (cacheService.hasShared(key)) {
|
||||||
return
|
return
|
||||||
@ -111,13 +151,19 @@ export function useSharedCache<K extends UseSharedCacheKey>(
|
|||||||
}
|
}
|
||||||
}, [key, initValue])
|
}, [key, initValue])
|
||||||
|
|
||||||
// Register hook lifecycle
|
/**
|
||||||
|
* Register this hook as actively using the shared cache key
|
||||||
|
* This prevents the cache service from deleting the key while the hook is active
|
||||||
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
cacheService.registerHook(key)
|
cacheService.registerHook(key)
|
||||||
return () => cacheService.unregisterHook(key)
|
return () => cacheService.unregisterHook(key)
|
||||||
}, [key])
|
}, [key])
|
||||||
|
|
||||||
// Check for TTL warning
|
/**
|
||||||
|
* Warn developers when using TTL with shared cache hooks
|
||||||
|
* TTL can cause values to expire between renders, leading to unstable behavior
|
||||||
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (cacheService.hasSharedTTL(key)) {
|
if (cacheService.hasSharedTTL(key)) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
@ -126,6 +172,11 @@ export function useSharedCache<K extends UseSharedCacheKey>(
|
|||||||
}
|
}
|
||||||
}, [key])
|
}, [key])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Memoized setter function for updating the shared cache value
|
||||||
|
* Changes will be synchronized across all renderer windows
|
||||||
|
* @param newValue - New value to store in shared cache
|
||||||
|
*/
|
||||||
const setValue = useCallback(
|
const setValue = useCallback(
|
||||||
(newValue: UseSharedCacheSchema[K]) => {
|
(newValue: UseSharedCacheSchema[K]) => {
|
||||||
cacheService.setShared(key, newValue)
|
cacheService.setShared(key, newValue)
|
||||||
@ -139,31 +190,52 @@ export function useSharedCache<K extends UseSharedCacheKey>(
|
|||||||
/**
|
/**
|
||||||
* React hook for persistent cache with localStorage
|
* React hook for persistent cache with localStorage
|
||||||
*
|
*
|
||||||
* Features:
|
* Use this for data that needs to persist across app restarts and be shared between all windows.
|
||||||
* - Type-safe with predefined schema
|
* Data is automatically saved to localStorage.
|
||||||
* - Cross-window synchronization
|
|
||||||
* - Automatic default value setting
|
|
||||||
* - No TTL support (as discussed)
|
|
||||||
*
|
*
|
||||||
* @param key - Predefined persist cache key
|
* @param key - Cache key from the predefined schema
|
||||||
* @returns [value, setValue]
|
* @returns [value, setValue] - Similar to useState but persisted and shared across all windows
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* // Persisted across app restarts
|
||||||
|
* const [userPrefs, setUserPrefs] = usePersistCache('user.preferences')
|
||||||
|
*
|
||||||
|
* // Automatically saved and synced across all windows
|
||||||
|
* const [appSettings, setAppSettings] = usePersistCache('app.settings')
|
||||||
|
*
|
||||||
|
* // Changes are automatically saved
|
||||||
|
* setUserPrefs({ theme: 'dark', language: 'en' })
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
export function usePersistCache<K extends RendererPersistCacheKey>(
|
export function usePersistCache<K extends RendererPersistCacheKey>(
|
||||||
key: K
|
key: K
|
||||||
): [RendererPersistCacheSchema[K], (value: RendererPersistCacheSchema[K]) => void] {
|
): [RendererPersistCacheSchema[K], (value: RendererPersistCacheSchema[K]) => void] {
|
||||||
// Subscribe to cache changes
|
/**
|
||||||
|
* Subscribe to persist cache changes using React's useSyncExternalStore
|
||||||
|
* This ensures the component re-renders when the persist cache value changes
|
||||||
|
*/
|
||||||
const value = useSyncExternalStore(
|
const value = useSyncExternalStore(
|
||||||
useCallback((callback) => cacheService.subscribe(key, callback), [key]),
|
useCallback((callback) => cacheService.subscribe(key, callback), [key]),
|
||||||
useCallback(() => cacheService.getPersist(key), [key]),
|
useCallback(() => cacheService.getPersist(key), [key]),
|
||||||
useCallback(() => cacheService.getPersist(key), [key]) // SSR snapshot
|
useCallback(() => cacheService.getPersist(key), [key]) // SSR snapshot
|
||||||
)
|
)
|
||||||
|
|
||||||
// Register hook lifecycle (using string key for tracking)
|
/**
|
||||||
|
* Register this hook as actively using the persist cache key
|
||||||
|
* This prevents the cache service from deleting the key while the hook is active
|
||||||
|
* Note: Persist cache keys are predefined and generally not deleted
|
||||||
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
cacheService.registerHook(key)
|
cacheService.registerHook(key)
|
||||||
return () => cacheService.unregisterHook(key)
|
return () => cacheService.unregisterHook(key)
|
||||||
}, [key])
|
}, [key])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Memoized setter function for updating the persist cache value
|
||||||
|
* Changes will be synchronized across all windows and persisted to localStorage
|
||||||
|
* @param newValue - New value to store in persist cache (must match schema type)
|
||||||
|
*/
|
||||||
const setValue = useCallback(
|
const setValue = useCallback(
|
||||||
(newValue: RendererPersistCacheSchema[K]) => {
|
(newValue: RendererPersistCacheSchema[K]) => {
|
||||||
cacheService.setPersist(key, newValue)
|
cacheService.setPersist(key, newValue)
|
||||||
|
|||||||
@ -20,7 +20,7 @@ import llm from './llm'
|
|||||||
import mcp from './mcp'
|
import mcp from './mcp'
|
||||||
import memory from './memory'
|
import memory from './memory'
|
||||||
import messageBlocksReducer from './messageBlock'
|
import messageBlocksReducer from './messageBlock'
|
||||||
// import migrate from './migrate'
|
import migrate from './migrate'
|
||||||
import minapps from './minapps'
|
import minapps from './minapps'
|
||||||
import newMessagesReducer from './newMessage'
|
import newMessagesReducer from './newMessage'
|
||||||
import { setNotesPath } from './note'
|
import { setNotesPath } from './note'
|
||||||
@ -72,8 +72,8 @@ const persistedReducer = persistReducer(
|
|||||||
key: 'cherry-studio',
|
key: 'cherry-studio',
|
||||||
storage,
|
storage,
|
||||||
version: 155,
|
version: 155,
|
||||||
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs']
|
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs'],
|
||||||
// migrate
|
migrate
|
||||||
},
|
},
|
||||||
rootReducer
|
rootReducer
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @deprecated this file will be removed after data refactor
|
||||||
|
*/
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import { nanoid } from '@reduxjs/toolkit'
|
import { nanoid } from '@reduxjs/toolkit'
|
||||||
import { DEFAULT_CONTEXTCOUNT, DEFAULT_TEMPERATURE, isMac } from '@renderer/config/constant'
|
import { DEFAULT_CONTEXTCOUNT, DEFAULT_TEMPERATURE, isMac } from '@renderer/config/constant'
|
||||||
|
|||||||
@ -49,7 +49,7 @@ export interface RuntimeState {
|
|||||||
// update: UpdateState
|
// update: UpdateState
|
||||||
// export: ExportState
|
// export: ExportState
|
||||||
// chat: ChatState
|
// chat: ChatState
|
||||||
websearch: WebSearchState
|
// websearch: WebSearchState
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExportState {
|
export interface ExportState {
|
||||||
@ -85,9 +85,9 @@ const initialState: RuntimeState = {
|
|||||||
// renamingTopics: [],
|
// renamingTopics: [],
|
||||||
// newlyRenamedTopics: []
|
// newlyRenamedTopics: []
|
||||||
// },
|
// },
|
||||||
websearch: {
|
// websearch: {
|
||||||
activeSearches: {}
|
// activeSearches: {}
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
const runtimeSlice = createSlice({
|
const runtimeSlice = createSlice({
|
||||||
|
|||||||
@ -54,54 +54,58 @@ const selectionSlice = createSlice({
|
|||||||
name: 'selectionStore',
|
name: 'selectionStore',
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
setSelectionEnabled: (state, action: PayloadAction<boolean>) => {
|
// setSelectionEnabled: (state, action: PayloadAction<boolean>) => {
|
||||||
state.selectionEnabled = action.payload
|
// state.selectionEnabled = action.payload
|
||||||
},
|
// },
|
||||||
setTriggerMode: (state, action: PayloadAction<SelectionTriggerMode>) => {
|
// setTriggerMode: (state, action: PayloadAction<SelectionTriggerMode>) => {
|
||||||
state.triggerMode = action.payload
|
// state.triggerMode = action.payload
|
||||||
},
|
// },
|
||||||
setIsCompact: (state, action: PayloadAction<boolean>) => {
|
// setIsCompact: (state, action: PayloadAction<boolean>) => {
|
||||||
state.isCompact = action.payload
|
// state.isCompact = action.payload
|
||||||
},
|
// },
|
||||||
setIsAutoClose: (state, action: PayloadAction<boolean>) => {
|
// setIsAutoClose: (state, action: PayloadAction<boolean>) => {
|
||||||
state.isAutoClose = action.payload
|
// state.isAutoClose = action.payload
|
||||||
},
|
// },
|
||||||
setIsAutoPin: (state, action: PayloadAction<boolean>) => {
|
// setIsAutoPin: (state, action: PayloadAction<boolean>) => {
|
||||||
state.isAutoPin = action.payload
|
// state.isAutoPin = action.payload
|
||||||
},
|
// },
|
||||||
setIsFollowToolbar: (state, action: PayloadAction<boolean>) => {
|
// setIsFollowToolbar: (state, action: PayloadAction<boolean>) => {
|
||||||
state.isFollowToolbar = action.payload
|
// state.isFollowToolbar = action.payload
|
||||||
},
|
// },
|
||||||
setIsRemeberWinSize: (state, action: PayloadAction<boolean>) => {
|
// setIsRemeberWinSize: (state, action: PayloadAction<boolean>) => {
|
||||||
state.isRemeberWinSize = action.payload
|
// state.isRemeberWinSize = action.payload
|
||||||
},
|
// },
|
||||||
setFilterMode: (state, action: PayloadAction<SelectionFilterMode>) => {
|
// setFilterMode: (state, action: PayloadAction<SelectionFilterMode>) => {
|
||||||
state.filterMode = action.payload
|
// state.filterMode = action.payload
|
||||||
},
|
// },
|
||||||
setFilterList: (state, action: PayloadAction<string[]>) => {
|
// setFilterList: (state, action: PayloadAction<string[]>) => {
|
||||||
state.filterList = action.payload
|
// state.filterList = action.payload
|
||||||
},
|
// },
|
||||||
setActionWindowOpacity: (state, action: PayloadAction<number>) => {
|
// setActionWindowOpacity: (state, action: PayloadAction<number>) => {
|
||||||
state.actionWindowOpacity = action.payload
|
// state.actionWindowOpacity = action.payload
|
||||||
},
|
// },
|
||||||
setActionItems: (state, action: PayloadAction<SelectionActionItem[]>) => {
|
// setActionItems: (state, action: PayloadAction<SelectionActionItem[]>) => {
|
||||||
state.actionItems = action.payload
|
// state.actionItems = action.payload
|
||||||
|
// },
|
||||||
|
setPlaceholder: (state, action: PayloadAction<Partial<SelectionState>>) => {
|
||||||
|
state = { ...state, ...action.payload }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export const {
|
export const {
|
||||||
setSelectionEnabled,
|
// setSelectionEnabled,
|
||||||
setTriggerMode,
|
// setTriggerMode,
|
||||||
setIsCompact,
|
// setIsCompact,
|
||||||
setIsAutoClose,
|
// setIsAutoClose,
|
||||||
setIsAutoPin,
|
// setIsAutoPin,
|
||||||
setIsFollowToolbar,
|
// setIsFollowToolbar,
|
||||||
setIsRemeberWinSize,
|
// setIsRemeberWinSize,
|
||||||
setFilterMode,
|
// setFilterMode,
|
||||||
setFilterList,
|
// setFilterList,
|
||||||
setActionWindowOpacity,
|
// setActionWindowOpacity,
|
||||||
setActionItems
|
// setActionItems,
|
||||||
|
setPlaceholder
|
||||||
} = selectionSlice.actions
|
} = selectionSlice.actions
|
||||||
|
|
||||||
export default selectionSlice.reducer
|
export default selectionSlice.reducer
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* //TODO @deprecated this file will be removed after data refactor
|
||||||
|
*/
|
||||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||||
import { isMac } from '@renderer/config/constant'
|
import { isMac } from '@renderer/config/constant'
|
||||||
import {
|
import {
|
||||||
@ -621,9 +624,9 @@ const settingsSlice = createSlice({
|
|||||||
// setCodeImageTools: (state, action: PayloadAction<boolean>) => {
|
// setCodeImageTools: (state, action: PayloadAction<boolean>) => {
|
||||||
// state.codeImageTools = action.payload
|
// state.codeImageTools = action.payload
|
||||||
// },
|
// },
|
||||||
setCodeFancyBlock: (state, action: PayloadAction<boolean>) => {
|
// setCodeFancyBlock: (state, action: PayloadAction<boolean>) => {
|
||||||
state.codeFancyBlock = action.payload
|
// state.codeFancyBlock = action.payload
|
||||||
},
|
// },
|
||||||
// setMathEngine: (state, action: PayloadAction<MathEngine>) => {
|
// setMathEngine: (state, action: PayloadAction<MathEngine>) => {
|
||||||
// state.mathEngine = action.payload
|
// state.mathEngine = action.payload
|
||||||
// },
|
// },
|
||||||
@ -822,18 +825,18 @@ const settingsSlice = createSlice({
|
|||||||
setDefaultPaintingProvider: (state, action: PayloadAction<PaintingProvider>) => {
|
setDefaultPaintingProvider: (state, action: PayloadAction<PaintingProvider>) => {
|
||||||
state.defaultPaintingProvider = action.payload
|
state.defaultPaintingProvider = action.payload
|
||||||
},
|
},
|
||||||
setS3: (state, action: PayloadAction<S3Config>) => {
|
// setS3: (state, action: PayloadAction<S3Config>) => {
|
||||||
state.s3 = action.payload
|
// state.s3 = action.payload
|
||||||
},
|
// },
|
||||||
setS3Partial: (state, action: PayloadAction<Partial<S3Config>>) => {
|
// setS3Partial: (state, action: PayloadAction<Partial<S3Config>>) => {
|
||||||
state.s3 = { ...state.s3, ...action.payload }
|
// state.s3 = { ...state.s3, ...action.payload }
|
||||||
},
|
// },
|
||||||
setEnableDeveloperMode: (state, action: PayloadAction<boolean>) => {
|
// setEnableDeveloperMode: (state, action: PayloadAction<boolean>) => {
|
||||||
state.enableDeveloperMode = action.payload
|
// state.enableDeveloperMode = action.payload
|
||||||
},
|
// },
|
||||||
setNavbarPosition: (state, action: PayloadAction<'left' | 'top'>) => {
|
// setNavbarPosition: (state, action: PayloadAction<'left' | 'top'>) => {
|
||||||
state.navbarPosition = action.payload
|
// state.navbarPosition = action.payload
|
||||||
},
|
// },
|
||||||
// API Server actions
|
// API Server actions
|
||||||
setApiServerEnabled: (state, action: PayloadAction<boolean>) => {
|
setApiServerEnabled: (state, action: PayloadAction<boolean>) => {
|
||||||
state.apiServer = {
|
state.apiServer = {
|
||||||
@ -913,7 +916,7 @@ export const {
|
|||||||
// setCodeCollapsible,
|
// setCodeCollapsible,
|
||||||
// setCodeWrappable,
|
// setCodeWrappable,
|
||||||
// setCodeImageTools,
|
// setCodeImageTools,
|
||||||
setCodeFancyBlock,
|
// setCodeFancyBlock,
|
||||||
// setMathEngine,
|
// setMathEngine,
|
||||||
// setMathEnableSingleDollar,
|
// setMathEnableSingleDollar,
|
||||||
// setFoldDisplayMode,
|
// setFoldDisplayMode,
|
||||||
|
|||||||
@ -218,7 +218,7 @@ const DataApiHookTests: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const optimisticData = {
|
const optimisticData = {
|
||||||
...(singleItem || {}),
|
...singleItem,
|
||||||
title: `${(singleItem as any)?.title || 'Unknown'} (Optimistic Update)`,
|
title: `${(singleItem as any)?.title || 'Unknown'} (Optimistic Update)`,
|
||||||
updatedAt: new Date().toISOString()
|
updatedAt: new Date().toISOString()
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user