返回 oh-my-ppt
exportProgressToast.tsx
根目录 / src / renderer / src / components / session-detail / hooks / exportProgressToast.tsx
1 import type { ReactNode } from 'react'
2 import type { ToastId } from '../../../store/toastStore'
3
4 type ExportProgressToastOptions = {
5 id?: ToastId
6 description?: ReactNode
7 duration?: number
8 }
9
10 type ExportProgressToastApi = {
11 loading: (message: ReactNode, options?: ExportProgressToastOptions) => ToastId
12 success: (message: ReactNode, options?: ExportProgressToastOptions) => ToastId
13 info: (message: ReactNode, options?: ExportProgressToastOptions) => ToastId
14 error: (message: ReactNode, options?: ExportProgressToastOptions) => ToastId
15 }
16
17 export const EXPORT_PROGRESS_TOAST_DURATION_MS = 60 * 60 * 1000
18
19 export function clampExportProgress(progress: number): number {
20 return Math.max(0, Math.min(100, Math.round(progress)))
21 }
22
23 function ExportProgressDescription({
24 description,
25 progress
26 }: {
27 description: ReactNode
28 progress: number
29 }): React.JSX.Element {
30 return (
31 <div className="mt-1 flex w-[260px] max-w-full flex-col gap-2">
32 <div className="text-[12px] leading-snug text-muted-foreground">{description}</div>
33 <div className="flex items-center gap-2">
34 <div className="soft-inset h-2 flex-1 overflow-hidden rounded-full">
35 <div
36 className="h-full rounded-full bg-primary transition-all"
37 style={{ width: `${Math.min(100, Math.max(0, progress))}%` }}
38 />
39 </div>
40 <span className="w-9 text-right text-[11px] tabular-nums text-primary">{progress}%</span>
41 </div>
42 </div>
43 )
44 }
45
46 export function startExportProgressToast({
47 toast,
48 title,
49 description,
50 initialProgress = 8
51 }: {
52 toast: ExportProgressToastApi
53 title: string
54 description: ReactNode
55 initialProgress?: number
56 }): {
57 success: (message: string, options?: ExportProgressToastOptions) => void
58 cancel: (message: string) => void
59 error: (message: string) => void
60 update: (payload: { progress: number; description?: ReactNode }) => void
61 dispose: () => void
62 } {
63 let finished = false
64 let progress = clampExportProgress(initialProgress)
65 let currentDescription = description
66
67 const renderDescription = (): React.JSX.Element => (
68 <ExportProgressDescription description={currentDescription} progress={progress} />
69 )
70
71 const toastId = toast.loading(title, {
72 description: renderDescription(),
73 duration: EXPORT_PROGRESS_TOAST_DURATION_MS
74 })
75
76 const stop = (): void => {
77 if (finished) return
78 finished = true
79 }
80
81 const terminalOptions = (options?: ExportProgressToastOptions): ExportProgressToastOptions => ({
82 ...options,
83 id: toastId,
84 description: options?.description ?? null
85 })
86
87 return {
88 success: (message, options) => {
89 stop()
90 toast.success(message, terminalOptions(options))
91 },
92 cancel: (message) => {
93 stop()
94 toast.info(message, terminalOptions({ duration: 3000 }))
95 },
96 error: (message) => {
97 stop()
98 toast.error(message, terminalOptions({ duration: 6000 }))
99 },
100 update: (payload) => {
101 if (finished) return
102 progress = clampExportProgress(payload.progress)
103 currentDescription = payload.description ?? description
104 toast.loading(title, {
105 id: toastId,
106 description: renderDescription(),
107 duration: EXPORT_PROGRESS_TOAST_DURATION_MS
108 })
109 },
110 dispose: stop
111 }
112 }
113
113 lines Plain Text