Files
weflow-new/src/pages/Export/hooks/useExportConfig.ts
T
2026-06-30 00:18:31 +08:00

235 lines
8.0 KiB
TypeScript

/**
* ExportV2 — useExportConfig hook
*
* Manages global export configurations: output path, write layout, and export options.
* Synchronizes state with configService for persistence.
*/
import { useState, useCallback, useEffect, useRef } from 'react'
import type { ExportOptions, TextExportFormat, DisplayNamePreference } from '../types'
import { createDefaultExportOptions, defaultTxtColumns } from '../constants'
import type { ExportWriteLayout } from '../../../services/config'
import * as configService from '../../../services/config'
import {
resolveExportDateRangeConfig,
type ExportDefaultDateRangeConfig
} from '../../../utils/exportDateRange'
export interface ExportConfigResult {
isLoaded: boolean
// Base settings
exportPath: string
setExportPath: (path: string) => void
writeLayout: ExportWriteLayout
setWriteLayout: (layout: ExportWriteLayout) => void
// Export options
options: ExportOptions
updateOptions: (patch: Partial<ExportOptions>) => void
// Full raw date range config (for the date range picker)
rawDateRangeConfig: ExportDefaultDateRangeConfig | string | null
setRawDateRangeConfig: (config: ExportDefaultDateRangeConfig | string | null) => void
}
export function useExportConfig(): ExportConfigResult {
const [isLoaded, setIsLoaded] = useState(false)
const [exportPath, setExportPathState] = useState<string>('')
const [writeLayout, setWriteLayoutState] = useState<ExportWriteLayout>('C')
const [options, setOptions] = useState<ExportOptions>(createDefaultExportOptions())
const [rawDateRangeConfig, setRawDateRangeConfigState] = useState<ExportDefaultDateRangeConfig | string | null>(null)
// Use a ref to avoid infinite loops if we need to check current state in effects
const optionsRef = useRef(options)
optionsRef.current = options
// 1. Initial Load
useEffect(() => {
let isMounted = true
const loadConfigs = async () => {
try {
const [
path,
layout,
format,
avatars,
media,
dateRange,
voiceAsText,
pathStyle,
excelCompact,
txtCols,
concurrency
] = await Promise.all([
configService.getExportPath(),
configService.getExportWriteLayout(),
configService.getExportDefaultFormat(),
configService.getExportDefaultAvatars(),
configService.getExportDefaultMedia(),
configService.getExportDefaultDateRange(),
configService.getExportDefaultVoiceAsText(),
configService.getExportDefaultPathStyle(),
configService.getExportDefaultExcelCompactColumns(),
configService.getExportDefaultTxtColumns(),
configService.getExportDefaultConcurrency()
])
if (!isMounted) return
// Base settings
if (path) {
setExportPathState(path)
} else {
try {
const downloadsPath = await window.electronAPI.app.getDownloadsPath()
setExportPathState(downloadsPath)
} catch (e) {
setExportPathState('')
}
}
setWriteLayoutState(layout || 'C')
// Build ExportOptions from multiple configs
const newOptions = createDefaultExportOptions()
if (format) newOptions.format = format as TextExportFormat
if (typeof avatars === 'boolean') newOptions.exportAvatars = avatars
if (media) {
newOptions.exportImages = media.images !== false
newOptions.exportVideos = media.videos !== false
newOptions.exportVoices = media.voices !== false
newOptions.exportEmojis = media.emojis !== false
newOptions.exportFiles = media.files !== false
// The old page had `exportMedia: true` derived from whether any media was checked
newOptions.exportMedia = (
newOptions.exportImages ||
newOptions.exportVideos ||
newOptions.exportVoices ||
newOptions.exportEmojis ||
newOptions.exportFiles
)
}
if (typeof voiceAsText === 'boolean') newOptions.exportVoiceAsText = voiceAsText
if (pathStyle === 'auto' || pathStyle === 'posix' || pathStyle === 'windows') newOptions.exportPathStyle = pathStyle
if (typeof excelCompact === 'boolean') newOptions.excelCompactColumns = excelCompact
if (Array.isArray(txtCols)) newOptions.txtColumns = txtCols
if (typeof concurrency === 'number' && concurrency > 0) newOptions.exportConcurrency = concurrency
// Date range
setRawDateRangeConfigState(dateRange)
if (dateRange) {
const resolvedDateRange = resolveExportDateRangeConfig(dateRange as any, new Date())
newOptions.useAllTime = resolvedDateRange.useAllTime
newOptions.dateRange = resolvedDateRange.dateRange
}
setOptions(newOptions)
} catch (err) {
console.error('[useExportConfig] Error loading configs:', err)
} finally {
if (isMounted) setIsLoaded(true)
}
}
void loadConfigs()
return () => { isMounted = false }
}, [])
// 2. Setters (State + ConfigService)
const setExportPath = useCallback((path: string) => {
setExportPathState(path)
void configService.setExportPath(path)
}, [])
const setWriteLayout = useCallback((layout: ExportWriteLayout) => {
setWriteLayoutState(layout)
void configService.setExportWriteLayout(layout)
}, [])
const updateOptions = useCallback((patch: Partial<ExportOptions>) => {
setOptions(prev => {
const next = { ...prev, ...patch }
// Persist individual keys based on the patch
if (patch.format !== undefined) {
void configService.setExportDefaultFormat(patch.format)
}
if (patch.exportAvatars !== undefined) {
void configService.setExportDefaultAvatars(patch.exportAvatars)
}
if (
patch.exportImages !== undefined ||
patch.exportVideos !== undefined ||
patch.exportVoices !== undefined ||
patch.exportEmojis !== undefined ||
patch.exportFiles !== undefined ||
patch.maxFileSizeMb !== undefined
) {
void configService.setExportDefaultMedia({
images: next.exportImages,
videos: next.exportVideos,
voices: next.exportVoices,
emojis: next.exportEmojis,
files: next.exportFiles
})
}
if (patch.exportVoiceAsText !== undefined) {
void configService.setExportDefaultVoiceAsText(patch.exportVoiceAsText)
}
if (patch.exportPathStyle !== undefined) {
void configService.setExportDefaultPathStyle(patch.exportPathStyle)
}
if (patch.excelCompactColumns !== undefined) {
void configService.setExportDefaultExcelCompactColumns(patch.excelCompactColumns)
}
if (patch.txtColumns !== undefined) {
void configService.setExportDefaultTxtColumns(patch.txtColumns)
}
if (patch.exportConcurrency !== undefined) {
void configService.setExportDefaultConcurrency(patch.exportConcurrency)
}
// Auto-derive exportMedia
next.exportMedia = (
next.exportImages ||
next.exportVideos ||
next.exportVoices ||
next.exportEmojis ||
next.exportFiles
)
return next
})
}, [])
const setRawDateRangeConfig = useCallback((config: ExportDefaultDateRangeConfig | string | null) => {
setRawDateRangeConfigState(config)
if (config) {
void configService.setExportDefaultDateRange(config as any)
// Update options as well
const resolvedDateRange = resolveExportDateRangeConfig(config as any, new Date())
updateOptions({
useAllTime: resolvedDateRange.useAllTime,
dateRange: resolvedDateRange.dateRange
})
}
}, [updateOptions])
return {
isLoaded,
exportPath,
setExportPath,
writeLayout,
setWriteLayout,
options,
updateOptions,
rawDateRangeConfig,
setRawDateRangeConfig
}
}