返回 presentation-ai
PresentButton.tsx
根目录 / src / components / presentation / buttons / PresentButton.tsx
1 "use client";
2
3 import { Loader2, Play, X } from "lucide-react";
4 import { useEffect, useState } from "react";
5
6 import { Button } from "@/components/ui/button";
7 import { useDebouncedSave } from "@/hooks/presentation/useDebouncedSave";
8 import { cn } from "@/lib/utils";
9 import { usePresentationState } from "@/states/presentation-state";
10
11 export function PresentButton() {
12 const [isPreparingPresentMode, setIsPreparingPresentMode] = useState(false);
13 const [isExiting, setIsExiting] = useState(false);
14 const { saveImmediately } = useDebouncedSave();
15 const isPresenting = usePresentationState((s) => s.isPresenting);
16 const setIsPresenting = usePresentationState((s) => s.setIsPresenting);
17 const setIsPresentingLoading = usePresentationState(
18 (s) => s.setIsPresentingLoading,
19 );
20 const resetPresentingScaleLocks = usePresentationState(
21 (s) => s.resetPresentingScaleLocks,
22 );
23 const isGeneratingPresentation = usePresentationState(
24 (s) => s.isGeneratingPresentation,
25 );
26 const isGeneratingOutline = usePresentationState(
27 (s) => s.isGeneratingOutline,
28 );
29
30 useEffect(() => {
31 if (isPresenting) {
32 setIsPreparingPresentMode(false);
33 setIsExiting(false);
34 } else {
35 setIsExiting(false);
36 }
37 }, [isPresenting]);
38
39 // Check if generation is in progress
40 const isGenerating = isGeneratingPresentation || isGeneratingOutline;
41 const isLoading = isPreparingPresentMode || isExiting;
42 const isDisabled = isGenerating || isLoading;
43
44 return (
45 <Button
46 type="button"
47 size="sm"
48 className={cn(
49 "notranslate h-10 w-10 shrink-0 rounded-lg px-0 sm:h-9 sm:w-auto sm:gap-1.5 sm:rounded-md sm:px-3",
50 isPresenting
51 ? "bg-red-600 text-white hover:bg-red-700"
52 : "bg-purple-600 text-white hover:bg-purple-700",
53 isDisabled && "cursor-not-allowed opacity-70",
54 )}
55 translate="no"
56 onClick={async () => {
57 if (isDisabled) return;
58
59 if (isPresenting) {
60 setIsExiting(true);
61 await new Promise((resolve) => setTimeout(resolve, 50));
62 setIsPresenting(false);
63 setIsPresentingLoading(false);
64 resetPresentingScaleLocks();
65 setIsExiting(false);
66 return;
67 }
68
69 setIsPreparingPresentMode(true);
70 try {
71 await saveImmediately({ includeMetadata: true });
72 resetPresentingScaleLocks();
73 setIsPresentingLoading(true);
74 setIsPresenting(true);
75 } catch (caughtError) {
76 setIsPreparingPresentMode(false);
77 throw caughtError;
78 }
79 setIsPreparingPresentMode(false);
80 }}
81 disabled={isDisabled}
82 >
83 <span className="sr-only" translate="no">
84 {isPresenting ? "Exit" : "Present"}
85 </span>
86 {isLoading ? (
87 <>
88 <Loader2
89 className="h-5 w-5 animate-spin sm:mr-1 sm:h-4 sm:w-4"
90 aria-hidden="true"
91 />
92 <span className="hidden sm:inline" translate="no">
93 {isPresenting ? "Exiting" : "Presenting"}
94 </span>
95 </>
96 ) : isPresenting ? (
97 <>
98 <X className="h-5 w-5 sm:mr-1 sm:h-4 sm:w-4" aria-hidden="true" />
99 <span className="hidden sm:inline" translate="no">
100 Exit
101 </span>
102 </>
103 ) : (
104 <>
105 <Play className="h-5 w-5 sm:mr-1 sm:h-4 sm:w-4" aria-hidden="true" />
106 <span className="hidden sm:inline" translate="no">
107 Present
108 </span>
109 </>
110 )}
111 </Button>
112 );
113 }
114
114 lines Plain Text