148 lines
5.7 KiB
TypeScript
148 lines
5.7 KiB
TypeScript
/**
|
|
* Custom hook for monitoring sync progress
|
|
* Centralizes the pattern of polling syncProgressManager and updating state
|
|
*/
|
|
|
|
import { useState, useEffect, useRef, useCallback, type MutableRefObject } from 'react'
|
|
import type { SyncProgress } from '../helpers/syncProgressHelper'
|
|
|
|
export interface UseSyncProgressOptions {
|
|
onComplete?: () => void | Promise<void>
|
|
pollInterval?: number
|
|
maxDuration?: number
|
|
}
|
|
|
|
export interface UseSyncProgressResult {
|
|
syncProgress: SyncProgress | null
|
|
isSyncing: boolean
|
|
startMonitoring: () => void
|
|
stopMonitoring: () => void
|
|
}
|
|
|
|
/**
|
|
* Hook to monitor sync progress from syncProgressManager
|
|
*/
|
|
export function useSyncProgress(options: UseSyncProgressOptions = {}): UseSyncProgressResult {
|
|
const config = normalizeSyncProgressOptions(options)
|
|
const state = useSyncProgressState({ onComplete: config.onComplete })
|
|
const checkProgress = useCheckProgress({ onCompleteRef: state.onCompleteRef, setSyncProgress: state.setSyncProgress, setIsSyncing: state.setIsSyncing, stopMonitoring: state.stopMonitoring })
|
|
const startMonitoring = useStartMonitoring({ pollInterval: config.pollInterval, maxDuration: config.maxDuration, checkProgress, stopMonitoring: state.stopMonitoring, setIsSyncing: state.setIsSyncing, setSyncProgress: state.setSyncProgress, isMonitoringRef: state.isMonitoringRef, intervalRef: state.intervalRef, timeoutRef: state.timeoutRef })
|
|
useUnmountCleanup({ stopMonitoring: state.stopMonitoring })
|
|
return { syncProgress: state.syncProgress, isSyncing: state.isSyncing, startMonitoring, stopMonitoring: state.stopMonitoring }
|
|
}
|
|
|
|
function normalizeSyncProgressOptions(options: UseSyncProgressOptions): { onComplete: UseSyncProgressOptions['onComplete']; pollInterval: number; maxDuration: number } {
|
|
return { onComplete: options.onComplete, pollInterval: options.pollInterval ?? 500, maxDuration: options.maxDuration ?? 60000 }
|
|
}
|
|
|
|
function useSyncProgressState(params: { onComplete: UseSyncProgressOptions['onComplete'] }): {
|
|
syncProgress: SyncProgress | null
|
|
setSyncProgress: (value: SyncProgress | null) => void
|
|
isSyncing: boolean
|
|
setIsSyncing: (value: boolean) => void
|
|
intervalRef: MutableRefObject<ReturnType<typeof setInterval> | null>
|
|
timeoutRef: MutableRefObject<ReturnType<typeof setTimeout> | null>
|
|
isMonitoringRef: MutableRefObject<boolean>
|
|
onCompleteRef: MutableRefObject<UseSyncProgressOptions['onComplete']>
|
|
stopMonitoring: () => void
|
|
} {
|
|
const [syncProgress, setSyncProgress] = useState<SyncProgress | null>(null)
|
|
const [isSyncing, setIsSyncing] = useState(false)
|
|
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
|
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
const isMonitoringRef = useRef(false)
|
|
const onCompleteRef = useRef(params.onComplete)
|
|
|
|
useEffect(() => {
|
|
onCompleteRef.current = params.onComplete
|
|
}, [params.onComplete])
|
|
|
|
const stopMonitoring = useCallback((): void => {
|
|
if (!isMonitoringRef.current) {
|
|
return
|
|
}
|
|
isMonitoringRef.current = false
|
|
setIsSyncing(false)
|
|
intervalRef.current = clearIntervalAndReturnNull(intervalRef.current)
|
|
timeoutRef.current = clearTimeoutAndReturnNull(timeoutRef.current)
|
|
}, [])
|
|
|
|
return { syncProgress, setSyncProgress, isSyncing, setIsSyncing, intervalRef, timeoutRef, isMonitoringRef, onCompleteRef, stopMonitoring }
|
|
}
|
|
|
|
function clearIntervalAndReturnNull(current: ReturnType<typeof setInterval> | null): null {
|
|
if (current) {
|
|
clearInterval(current)
|
|
}
|
|
return null
|
|
}
|
|
|
|
function clearTimeoutAndReturnNull(current: ReturnType<typeof setTimeout> | null): null {
|
|
if (current) {
|
|
clearTimeout(current)
|
|
}
|
|
return null
|
|
}
|
|
|
|
function useCheckProgress(params: {
|
|
onCompleteRef: MutableRefObject<UseSyncProgressOptions['onComplete']>
|
|
setSyncProgress: (value: SyncProgress | null) => void
|
|
setIsSyncing: (value: boolean) => void
|
|
stopMonitoring: () => void
|
|
}): () => Promise<void> {
|
|
const { onCompleteRef, setSyncProgress, setIsSyncing, stopMonitoring } = params
|
|
return useCallback(async (): Promise<void> => {
|
|
const { syncProgressManager } = await import('../syncProgressManager')
|
|
const current = syncProgressManager.getProgress()
|
|
if (!current) {
|
|
return
|
|
}
|
|
setSyncProgress(current)
|
|
if (!current.completed) {
|
|
return
|
|
}
|
|
setIsSyncing(false)
|
|
await onCompleteRef.current?.()
|
|
stopMonitoring()
|
|
}, [onCompleteRef, setIsSyncing, setSyncProgress, stopMonitoring])
|
|
}
|
|
|
|
function useStartMonitoring(params: {
|
|
pollInterval: number
|
|
maxDuration: number
|
|
checkProgress: () => Promise<void>
|
|
stopMonitoring: () => void
|
|
setIsSyncing: (value: boolean) => void
|
|
setSyncProgress: (value: SyncProgress | null) => void
|
|
isMonitoringRef: MutableRefObject<boolean>
|
|
intervalRef: MutableRefObject<ReturnType<typeof setInterval> | null>
|
|
timeoutRef: MutableRefObject<ReturnType<typeof setTimeout> | null>
|
|
}): () => void {
|
|
const {
|
|
pollInterval,
|
|
maxDuration,
|
|
checkProgress,
|
|
stopMonitoring,
|
|
setIsSyncing,
|
|
setSyncProgress,
|
|
isMonitoringRef,
|
|
intervalRef,
|
|
timeoutRef,
|
|
} = params
|
|
return useCallback((): void => {
|
|
if (isMonitoringRef.current) {
|
|
return
|
|
}
|
|
isMonitoringRef.current = true
|
|
setIsSyncing(true)
|
|
setSyncProgress({ currentStep: 0, totalSteps: 7, completed: false })
|
|
intervalRef.current = setInterval(() => void checkProgress(), pollInterval)
|
|
timeoutRef.current = setTimeout(() => stopMonitoring(), maxDuration)
|
|
}, [checkProgress, intervalRef, isMonitoringRef, maxDuration, pollInterval, setIsSyncing, setSyncProgress, stopMonitoring, timeoutRef])
|
|
}
|
|
|
|
function useUnmountCleanup(params: { stopMonitoring: () => void }): void {
|
|
const { stopMonitoring } = params
|
|
useEffect(() => () => stopMonitoring(), [stopMonitoring])
|
|
}
|