1+ import type { ReactNode } from 'react'
12import { useCallback , useEffect , useMemo , useState } from 'react'
23import { useSearchParams } from 'react-router'
34import { toast } from 'sonner'
@@ -24,14 +25,13 @@ import type {
2425 PhotoSyncResolution ,
2526 PhotoSyncResult ,
2627} from '../types'
27- import { PhotoLibraryActionBar } from './library/PhotoLibraryActionBar'
2828import { PhotoLibraryGrid } from './library/PhotoLibraryGrid'
29- import { PhotoSyncActions } from './sync/PhotoSyncActions '
29+ import { PhotoPageActions } from './PhotoPageActions '
3030import { PhotoSyncConflictsPanel } from './sync/PhotoSyncConflictsPanel'
3131import { PhotoSyncProgressPanel } from './sync/PhotoSyncProgressPanel'
3232import { PhotoSyncResultPanel } from './sync/PhotoSyncResultPanel'
3333
34- type PhotoPageTab = 'sync' | 'library' | 'storage'
34+ export type PhotoPageTab = 'sync' | 'library' | 'storage'
3535
3636const BATCH_RESOLVING_ID = '__batch__'
3737
@@ -207,31 +207,59 @@ export function PhotoPage() {
207207 )
208208 } , [ ] )
209209
210- const handleDeleteAssets = async ( ids : string [ ] ) => {
211- if ( ids . length === 0 ) return
212- try {
213- await deleteMutation . mutateAsync ( ids )
214- toast . success ( `已删除 ${ ids . length } 个资源` )
215- setSelectedIds ( ( prev ) => prev . filter ( ( item ) => ! ids . includes ( item ) ) )
216- void listQuery . refetch ( )
217- } catch ( error ) {
218- const message = error instanceof Error ? error . message : '删除失败,请稍后重试。'
219- toast . error ( '删除失败' , { description : message } )
220- }
221- }
210+ const handleDeleteAssets = useCallback (
211+ async ( ids : string [ ] ) => {
212+ if ( ids . length === 0 ) return
213+ try {
214+ await deleteMutation . mutateAsync ( ids )
215+ toast . success ( `已删除 ${ ids . length } 个资源` )
216+ setSelectedIds ( ( prev ) => prev . filter ( ( item ) => ! ids . includes ( item ) ) )
217+ void listQuery . refetch ( )
218+ } catch ( error ) {
219+ const message = error instanceof Error ? error . message : '删除失败,请稍后重试。'
220+ toast . error ( '删除失败' , { description : message } )
221+ }
222+ } ,
223+ [ deleteMutation , listQuery , setSelectedIds ] ,
224+ )
222225
223- const handleUploadAssets = async ( files : FileList ) => {
224- const fileArray = Array . from ( files )
225- if ( fileArray . length === 0 ) return
226- try {
227- await uploadMutation . mutateAsync ( fileArray )
228- toast . success ( `成功上传 ${ fileArray . length } 张图片` )
226+ const handleUploadAssets = useCallback (
227+ async ( files : FileList ) => {
228+ const fileArray = Array . from ( files )
229+ if ( fileArray . length === 0 ) return
230+ try {
231+ await uploadMutation . mutateAsync ( fileArray )
232+ toast . success ( `成功上传 ${ fileArray . length } 张图片` )
233+ void listQuery . refetch ( )
234+ } catch ( error ) {
235+ const message = error instanceof Error ? error . message : '上传失败,请稍后重试。'
236+ toast . error ( '上传失败' , { description : message } )
237+ }
238+ } ,
239+ [ listQuery , uploadMutation ] ,
240+ )
241+
242+ const handleSyncCompleted = useCallback (
243+ ( data : PhotoSyncResult , context : { dryRun : boolean } ) => {
244+ setResult ( data )
245+ setLastWasDryRun ( context . dryRun )
246+ setSyncProgress ( null )
247+ void summaryQuery . refetch ( )
229248 void listQuery . refetch ( )
230- } catch ( error ) {
231- const message = error instanceof Error ? error . message : '上传失败,请稍后重试。'
232- toast . error ( '上传失败' , { description : message } )
233- }
234- }
249+ } ,
250+ [ listQuery , summaryQuery ] ,
251+ )
252+
253+ const handleDeleteSelected = useCallback ( ( ) => {
254+ void handleDeleteAssets ( selectedIds )
255+ } , [ handleDeleteAssets , selectedIds ] )
256+
257+ const handleDeleteSingle = useCallback (
258+ ( asset : PhotoAssetListItem ) => {
259+ void handleDeleteAssets ( [ asset . id ] )
260+ } ,
261+ [ handleDeleteAssets ] ,
262+ )
235263
236264 const handleResolveConflict = useCallback (
237265 async ( conflict : PhotoSyncConflict , strategy : PhotoSyncResolution ) => {
@@ -325,10 +353,6 @@ export function PhotoPage() {
325353 }
326354 }
327355
328- const handleDeleteSingle = ( asset : PhotoAssetListItem ) => {
329- void handleDeleteAssets ( [ asset . id ] )
330- }
331-
332356 const handleTabChange = ( tab : PhotoPageTab ) => {
333357 setActiveTab ( tab )
334358 const next = new URLSearchParams ( searchParams . toString ( ) )
@@ -347,36 +371,84 @@ export function PhotoPage() {
347371 const showConflictsPanel =
348372 conflictsQuery . isLoading || conflictsQuery . isFetching || ( conflictsQuery . data ?. length ?? 0 ) > 0
349373
374+ let tabContent : ReactNode | null = null
375+
376+ switch ( activeTab ) {
377+ case 'storage' : {
378+ tabContent = < StorageProvidersManager />
379+ break
380+ }
381+ case 'sync' : {
382+ let progressPanel : ReactNode | null = null
383+ if ( syncProgress ) {
384+ progressPanel = < PhotoSyncProgressPanel progress = { syncProgress } />
385+ }
386+
387+ let conflictsPanel : ReactNode | null = null
388+ if ( showConflictsPanel ) {
389+ conflictsPanel = (
390+ < PhotoSyncConflictsPanel
391+ conflicts = { conflictsQuery . data }
392+ isLoading = { conflictsQuery . isLoading || conflictsQuery . isFetching }
393+ resolvingId = { resolvingConflictId }
394+ isBatchResolving = { resolvingConflictId === BATCH_RESOLVING_ID }
395+ onResolve = { handleResolveConflict }
396+ onResolveBatch = { handleResolveConflictsBatch }
397+ onRequestStorageUrl = { getPhotoStorageUrl }
398+ />
399+ )
400+ }
401+
402+ tabContent = (
403+ < >
404+ { progressPanel }
405+ < div className = "space-y-6" >
406+ { conflictsPanel }
407+ < PhotoSyncResultPanel
408+ result = { result }
409+ lastWasDryRun = { lastWasDryRun }
410+ baselineSummary = { summaryQuery . data }
411+ isSummaryLoading = { summaryQuery . isLoading }
412+ onRequestStorageUrl = { getPhotoStorageUrl }
413+ />
414+ </ div >
415+ </ >
416+ )
417+ break
418+ }
419+ case 'library' : {
420+ tabContent = (
421+ < PhotoLibraryGrid
422+ assets = { listQuery . data }
423+ isLoading = { isListLoading }
424+ selectedIds = { selectedSet }
425+ onToggleSelect = { handleToggleSelect }
426+ onOpenAsset = { handleOpenAsset }
427+ onDeleteAsset = { handleDeleteSingle }
428+ isDeleting = { deleteMutation . isPending }
429+ />
430+ )
431+ break
432+ }
433+ default : {
434+ tabContent = null
435+ }
436+ }
437+
350438 return (
351439 < MainPageLayout title = "照片库" description = "在此同步和管理服务器中的照片资产。" >
352- { activeTab !== 'storage' ? (
353- < MainPageLayout . Actions >
354- { activeTab === 'sync' ? (
355- < PhotoSyncActions
356- onCompleted = { ( data , context ) => {
357- setResult ( data )
358- setLastWasDryRun ( context . dryRun )
359- setSyncProgress ( null )
360- void summaryQuery . refetch ( )
361- void listQuery . refetch ( )
362- } }
363- onProgress = { handleProgressEvent }
364- onError = { handleSyncError }
365- />
366- ) : (
367- < PhotoLibraryActionBar
368- selectionCount = { selectedIds . length }
369- isUploading = { uploadMutation . isPending }
370- isDeleting = { deleteMutation . isPending }
371- onUpload = { handleUploadAssets }
372- onDeleteSelected = { ( ) => {
373- void handleDeleteAssets ( selectedIds )
374- } }
375- onClearSelection = { handleClearSelection }
376- />
377- ) }
378- </ MainPageLayout . Actions >
379- ) : null }
440+ < PhotoPageActions
441+ activeTab = { activeTab }
442+ selectionCount = { selectedIds . length }
443+ isUploading = { uploadMutation . isPending }
444+ isDeleting = { deleteMutation . isPending }
445+ onUpload = { handleUploadAssets }
446+ onDeleteSelected = { handleDeleteSelected }
447+ onClearSelection = { handleClearSelection }
448+ onSyncCompleted = { handleSyncCompleted }
449+ onSyncProgress = { handleProgressEvent }
450+ onSyncError = { handleSyncError }
451+ />
380452
381453 < div className = "space-y-6" >
382454 < PageTabs
@@ -389,46 +461,7 @@ export function PhotoPage() {
389461 ] }
390462 />
391463
392- { activeTab === 'storage' ? (
393- < StorageProvidersManager />
394- ) : (
395- < >
396- { activeTab === 'sync' && syncProgress ? < PhotoSyncProgressPanel progress = { syncProgress } /> : null }
397-
398- { activeTab === 'sync' ? (
399- < div className = "space-y-6" >
400- { showConflictsPanel ? (
401- < PhotoSyncConflictsPanel
402- conflicts = { conflictsQuery . data }
403- isLoading = { conflictsQuery . isLoading || conflictsQuery . isFetching }
404- resolvingId = { resolvingConflictId }
405- isBatchResolving = { resolvingConflictId === BATCH_RESOLVING_ID }
406- onResolve = { handleResolveConflict }
407- onResolveBatch = { handleResolveConflictsBatch }
408- onRequestStorageUrl = { getPhotoStorageUrl }
409- />
410- ) : null }
411- < PhotoSyncResultPanel
412- result = { result }
413- lastWasDryRun = { lastWasDryRun }
414- baselineSummary = { summaryQuery . data }
415- isSummaryLoading = { summaryQuery . isLoading }
416- onRequestStorageUrl = { getPhotoStorageUrl }
417- />
418- </ div >
419- ) : (
420- < PhotoLibraryGrid
421- assets = { listQuery . data }
422- isLoading = { isListLoading }
423- selectedIds = { selectedSet }
424- onToggleSelect = { handleToggleSelect }
425- onOpenAsset = { handleOpenAsset }
426- onDeleteAsset = { handleDeleteSingle }
427- isDeleting = { deleteMutation . isPending }
428- />
429- ) }
430- </ >
431- ) }
464+ { tabContent }
432465 </ div >
433466 </ MainPageLayout >
434467 )
0 commit comments