返回 oh-my-ppt
AddBlankPageDialog.tsx
根目录 / src / renderer / src / components / session-detail / modal / AddBlankPageDialog.tsx
1 import { useMemo } from 'react'
2 import { useT } from '@renderer/i18n'
3 import { ipc } from '@renderer/lib/ipc'
4 import {
5 useGenerateStore,
6 useSessionDetailUiStore,
7 useSessionStore,
8 useToastStore
9 } from '@renderer/store'
10 import { normalizePagesForSelection } from '../shared/pageUtils'
11
12 interface AddBlankPageDialogProps {
13 sessionId: string
14 }
15
16 export function AddBlankPageDialog({
17 sessionId
18 }: AddBlankPageDialogProps): React.JSX.Element | null {
19 const t = useT()
20 const open = useSessionDetailUiStore((state) => state.blankPageDialogOpen)
21 const sourcePageId = useSessionDetailUiStore((state) => state.blankPageSourceId)
22 const setOpen = useSessionDetailUiStore((state) => state.setBlankPageDialogOpen)
23 const setSourcePageId = useSessionDetailUiStore((state) => state.setBlankPageSourceId)
24 const setIsAddingPage = useSessionDetailUiStore((state) => state.setIsAddingPage)
25 const currentPages = useGenerateStore((state) => state.currentPages)
26 const loadSession = useSessionStore((state) => state.loadSession)
27 const toastError = useToastStore((state) => state.error)
28
29 const pages = useMemo(() => normalizePagesForSelection(currentPages), [currentPages])
30
31 const handleCreateBlankPage = async (): Promise<void> => {
32 if (!sessionId || !sourcePageId) return
33 const sourcePage = pages.find((page) => page.id === sourcePageId)
34 if (!sourcePage) return
35 setOpen(false)
36 setIsAddingPage(true)
37 useGenerateStore.setState({ isGenerating: true, error: null, status: 'running' })
38 let targetSelection: string | null | undefined = undefined
39
40 try {
41 const result = await ipc.createBlankSessionPage({
42 sessionId,
43 sourcePageId: sourcePage.id
44 })
45 useGenerateStore.getState().setPages(result.generatedPages)
46 await loadSession(sessionId)
47 useGenerateStore.getState().setPages(useSessionStore.getState().currentGeneratedPages)
48 targetSelection = result.selectedPageId || null
49 useSessionDetailUiStore.getState().bumpPreviewKey()
50 void ipc
51 .clearSpeechScript(sessionId)
52 .catch((err) => console.warn('[speech] clearSpeechScript failed', err))
53 } catch (err) {
54 const message = err instanceof Error ? err.message : t('sessionDetail.addBlankPageFailed')
55 toastError(message)
56 } finally {
57 useSessionDetailUiStore.getState().finishAddPage(targetSelection)
58 useGenerateStore.getState().finishGeneration()
59 }
60 }
61
62 if (!open) return null
63
64 return (
65 <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
66 <div className="w-[520px] rounded-2xl bg-white p-6 shadow-2xl">
67 <h3 className="mb-2 text-base font-semibold text-[#2f3a2a]">
68 {t('sessionDetail.addBlankPage')}
69 </h3>
70 <p className="mb-4 text-xs leading-5 text-[#8a9a7b]">
71 {t('sessionDetail.addBlankPageHint')}
72 </p>
73 <div className="mb-4 max-h-[320px] space-y-2 overflow-y-auto pr-1">
74 {pages.map((page) => (
75 <button
76 key={page.id}
77 type="button"
78 onClick={() => setSourcePageId(page.id)}
79 className={`flex w-full items-center gap-3 rounded-xl border px-3 py-2 text-left transition-colors ${
80 sourcePageId === page.id
81 ? 'border-[#8eaa70] bg-[#eef6e7] text-[#2f3a2a]'
82 : 'border-[#d4e4c1]/60 bg-[#f8f6f0] text-[#5d6b4d] hover:bg-[#f0ece3]'
83 }`}
84 >
85 <span className="shrink-0 rounded-md bg-[#d4e4c1]/70 px-2 py-1 text-[11px] font-semibold text-[#3e4a32]">
86 P{page.pageNumber}
87 </span>
88 <span className="min-w-0 flex-1 truncate text-sm font-medium">
89 {page.title || t('sessionDetail.untitledPage')}
90 </span>
91 </button>
92 ))}
93 </div>
94 <div className="flex items-center justify-end gap-2">
95 <button
96 type="button"
97 onClick={() => setOpen(false)}
98 className="cursor-pointer rounded-xl px-4 py-2 text-sm font-medium text-[#5d6b4d] transition-colors hover:bg-[#f0ece3]"
99 >
100 {t('sessionDetail.addPageCancel')}
101 </button>
102 <button
103 type="button"
104 disabled={!sourcePageId}
105 onClick={() => void handleCreateBlankPage()}
106 className="cursor-pointer rounded-xl bg-[#5d6b4d] px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-[#3e4a32] disabled:cursor-not-allowed disabled:opacity-40"
107 >
108 {t('sessionDetail.addBlankPageCreate')}
109 </button>
110 </div>
111 </div>
112 </div>
113 )
114 }
115
115 lines Plain Text