feat: allow user close the tab to sync the draft (#30034)

This commit is contained in:
wangxiaolei 2025-12-23 19:01:29 +08:00 committed by GitHub
parent aea3a6f80c
commit 870a6427c9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 44 additions and 19 deletions

View File

@ -224,23 +224,31 @@ export const Workflow: FC<WorkflowProps> = memo(({
return () => {
handleSyncWorkflowDraft(true, true)
}
}, [])
}, [handleSyncWorkflowDraft])
const { handleRefreshWorkflowDraft } = useWorkflowRefreshDraft()
const handleSyncWorkflowDraftWhenPageClose = useCallback(() => {
if (document.visibilityState === 'hidden')
syncWorkflowDraftWhenPageClose()
else if (document.visibilityState === 'visible')
setTimeout(() => handleRefreshWorkflowDraft(), 500)
}, [syncWorkflowDraftWhenPageClose, handleRefreshWorkflowDraft])
}, [syncWorkflowDraftWhenPageClose, handleRefreshWorkflowDraft, workflowStore])
// Also add beforeunload handler as additional safety net for tab close
const handleBeforeUnload = useCallback(() => {
syncWorkflowDraftWhenPageClose()
}, [syncWorkflowDraftWhenPageClose])
useEffect(() => {
document.addEventListener('visibilitychange', handleSyncWorkflowDraftWhenPageClose)
window.addEventListener('beforeunload', handleBeforeUnload)
return () => {
document.removeEventListener('visibilitychange', handleSyncWorkflowDraftWhenPageClose)
window.removeEventListener('beforeunload', handleBeforeUnload)
}
}, [handleSyncWorkflowDraftWhenPageClose])
}, [handleSyncWorkflowDraftWhenPageClose, handleBeforeUnload])
useEventListener('keydown', (e) => {
if ((e.key === 'd' || e.key === 'D') && (e.ctrlKey || e.metaKey))
@ -419,7 +427,7 @@ export const Workflow: FC<WorkflowProps> = memo(({
onPaneContextMenu={handlePaneContextMenu}
onSelectionContextMenu={handleSelectionContextMenu}
connectionLineComponent={CustomConnectionLine}
// TODO: For LOOP node, how to distinguish between ITERATION and LOOP here? Maybe both are the same?
// NOTE: For LOOP node, how to distinguish between ITERATION and LOOP here? Maybe both are the same?
connectionLineContainerStyle={{ zIndex: ITERATION_CHILDREN_Z_INDEX }}
defaultViewport={viewport}
multiSelectionKeyCode={null}

View File

@ -7,6 +7,12 @@ import type {
} from '@/app/components/workflow/types'
import { debounce } from 'lodash-es'
type DebouncedFunc = {
(fn: () => void): void
cancel?: () => void
flush?: () => void
}
export type WorkflowDraftSliceShape = {
backupDraft?: {
nodes: Node[]
@ -16,7 +22,7 @@ export type WorkflowDraftSliceShape = {
environmentVariables: EnvironmentVariable[]
}
setBackupDraft: (backupDraft?: WorkflowDraftSliceShape['backupDraft']) => void
debouncedSyncWorkflowDraft: (fn: () => void) => void
debouncedSyncWorkflowDraft: DebouncedFunc
syncWorkflowDraftHash: string
setSyncWorkflowDraftHash: (hash: string) => void
isSyncingWorkflowDraft: boolean
@ -25,20 +31,31 @@ export type WorkflowDraftSliceShape = {
setIsWorkflowDataLoaded: (loaded: boolean) => void
nodes: Node[]
setNodes: (nodes: Node[]) => void
flushPendingSync: () => void
}
export const createWorkflowDraftSlice: StateCreator<WorkflowDraftSliceShape> = set => ({
backupDraft: undefined,
setBackupDraft: backupDraft => set(() => ({ backupDraft })),
debouncedSyncWorkflowDraft: debounce((syncWorkflowDraft) => {
export const createWorkflowDraftSlice: StateCreator<WorkflowDraftSliceShape> = (set) => {
// Create the debounced function and store it with access to cancel/flush methods
const debouncedFn = debounce((syncWorkflowDraft) => {
syncWorkflowDraft()
}, 5000),
syncWorkflowDraftHash: '',
setSyncWorkflowDraftHash: syncWorkflowDraftHash => set(() => ({ syncWorkflowDraftHash })),
isSyncingWorkflowDraft: false,
setIsSyncingWorkflowDraft: isSyncingWorkflowDraft => set(() => ({ isSyncingWorkflowDraft })),
isWorkflowDataLoaded: false,
setIsWorkflowDataLoaded: loaded => set(() => ({ isWorkflowDataLoaded: loaded })),
nodes: [],
setNodes: nodes => set(() => ({ nodes })),
})
}, 5000)
return {
backupDraft: undefined,
setBackupDraft: backupDraft => set(() => ({ backupDraft })),
debouncedSyncWorkflowDraft: debouncedFn,
syncWorkflowDraftHash: '',
setSyncWorkflowDraftHash: syncWorkflowDraftHash => set(() => ({ syncWorkflowDraftHash })),
isSyncingWorkflowDraft: false,
setIsSyncingWorkflowDraft: isSyncingWorkflowDraft => set(() => ({ isSyncingWorkflowDraft })),
isWorkflowDataLoaded: false,
setIsWorkflowDataLoaded: loaded => set(() => ({ isWorkflowDataLoaded: loaded })),
nodes: [],
setNodes: nodes => set(() => ({ nodes })),
flushPendingSync: () => {
// Flush any pending debounced sync operations
if (debouncedFn.flush)
debouncedFn.flush()
},
}
}