From f8428df247fd4cf96e3551ddd4059b9c88a7deec Mon Sep 17 00:00:00 2001 From: suyao Date: Wed, 3 Dec 2025 16:53:27 +0800 Subject: [PATCH] feat: add pause and resume functionality to file watcher; enhance error handling in useInPlaceEdit hook --- src/main/services/FileStorage.ts | 12 ++++++++++-- src/renderer/src/hooks/useInPlaceEdit.ts | 15 +++++++++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/main/services/FileStorage.ts b/src/main/services/FileStorage.ts index 57ab8c801a..67068201a4 100644 --- a/src/main/services/FileStorage.ts +++ b/src/main/services/FileStorage.ts @@ -151,6 +151,7 @@ class FileStorage { private currentWatchPath?: string private debounceTimer?: NodeJS.Timeout private watcherConfig: Required = DEFAULT_WATCHER_CONFIG + private isPaused = false constructor() { this.initStorageDir() @@ -1448,6 +1449,12 @@ class FileStorage { private createChangeHandler() { return (eventType: string, filePath: string) => { + // Skip processing if watcher is paused + if (this.isPaused) { + logger.debug('File change ignored (watcher paused)', { eventType, filePath }) + return + } + if (!this.shouldWatchFile(filePath, eventType)) { return } @@ -1744,8 +1751,8 @@ class FileStorage { public pauseFileWatcher = async (): Promise => { if (this.watcher) { logger.debug('Pausing file watcher') - // Chokidar doesn't have pause, so we temporarily set a flag - // We'll handle this by clearing the debounce timer + this.isPaused = true + // Clear any pending debounced notifications if (this.debounceTimer) { clearTimeout(this.debounceTimer) this.debounceTimer = undefined @@ -1759,6 +1766,7 @@ class FileStorage { public resumeFileWatcher = async (): Promise => { if (this.watcher && this.currentWatchPath) { logger.debug('Resuming file watcher') + this.isPaused = false // Send a synthetic refresh event to trigger tree reload this.notifyChange('refresh', this.currentWatchPath) } diff --git a/src/renderer/src/hooks/useInPlaceEdit.ts b/src/renderer/src/hooks/useInPlaceEdit.ts index 874c1fbc11..ab614f9528 100644 --- a/src/renderer/src/hooks/useInPlaceEdit.ts +++ b/src/renderer/src/hooks/useInPlaceEdit.ts @@ -1,10 +1,12 @@ import { loggerService } from '@logger' import { useCallback, useLayoutEffect, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' const logger = loggerService.withContext('useInPlaceEdit') export interface UseInPlaceEditOptions { onSave: ((value: string) => void) | ((value: string) => Promise) onCancel?: () => void + onError?: (error: unknown) => void autoSelectOnStart?: boolean trimOnSave?: boolean } @@ -28,7 +30,8 @@ export interface UseInPlaceEditReturn { * @returns An object containing the editing state and handler functions */ export function useInPlaceEdit(options: UseInPlaceEditOptions): UseInPlaceEditReturn { - const { onSave, onCancel, autoSelectOnStart = true, trimOnSave = true } = options + const { onSave, onCancel, onError, autoSelectOnStart = true, trimOnSave = true } = options + const { t } = useTranslation() const [isSaving, setIsSaving] = useState(false) const [isEditing, setIsEditing] = useState(false) @@ -68,9 +71,17 @@ export function useInPlaceEdit(options: UseInPlaceEditOptions): UseInPlaceEditRe setEditValue('') } catch (error) { logger.error('Error saving in-place edit', { error }) + + // Call custom error handler if provided, otherwise show default toast + if (onError) { + onError(error) + } else { + window.toast.error(t('common.save_failed') || 'Failed to save') + } + } finally { setIsSaving(false) } - }, [isSaving, trimOnSave, editValue, onSave]) + }, [isSaving, trimOnSave, editValue, onSave, onError, t]) const cancelEdit = useCallback(() => { setIsEditing(false)