返回 presentation-ai
PresentationHeader.tsx
根目录 / src / components / presentation / core / PresentationHeader.tsx
1 "use client";
2
3 import { Palette } from "lucide-react";
4 import * as motion from "motion/react-client";
5 import { useSession } from "next-auth/react";
6 import Link from "next/link";
7 import { usePathname } from "next/navigation";
8 import { useEffect, useState } from "react";
9
10 // Import our new components
11 import { updatePresentationTitle } from "@/app/_actions/notebook/presentation/presentationActions";
12 import AllweoneText from "@/components/globals/allweone-logo";
13 import { ExportButton } from "@/components/presentation/buttons/ExportButton";
14 import { PresentButton } from "@/components/presentation/buttons/PresentButton";
15 import { PresentationMenu } from "@/components/presentation/controls/PresentationMenu";
16 import { PresentationSavingIndicator } from "@/components/presentation/core/PresentationSavingIndicator";
17 import { Button } from "@/components/ui/button";
18 import { Brain } from "@/components/ui/icons";
19 import { Input } from "@/components/ui/input";
20 import { usePresentationState } from "@/states/presentation-state";
21
22 interface PresentationHeaderProps {
23 title?: string;
24 }
25
26 export default function PresentationHeader({ title }: PresentationHeaderProps) {
27 const currentPresentationTitle = usePresentationState(
28 (s) => s.currentPresentationTitle,
29 );
30 const isPresenting = usePresentationState((s) => s.isPresenting);
31 const currentPresentationId = usePresentationState(
32 (s) => s.currentPresentationId,
33 );
34 const isReadOnly = usePresentationState((s) => s.isReadOnly);
35 const setActiveRightPanel = usePresentationState(
36 (s) => s.setActiveRightPanel,
37 );
38
39 const { status } = useSession();
40
41 const [presentationTitle, setPresentationTitle] = useState<string>(
42 "Presentation",
43 );
44 const pathname = usePathname();
45
46 const isPresentationPage =
47 (pathname.startsWith("/presentation/") ||
48 pathname.startsWith("/share/presentation/")) &&
49 !pathname.includes("generate");
50 const showPresentationTitle = pathname !== "/presentation";
51
52 const isLoggedOut = status === "unauthenticated";
53 const showBrand = isLoggedOut;
54
55 // Update title when it changes in the state
56 useEffect(() => {
57 if (currentPresentationTitle) {
58 setPresentationTitle(currentPresentationTitle);
59 } else if (title) {
60 setPresentationTitle(title);
61 }
62 }, [currentPresentationTitle, title]);
63
64 if (pathname === "/presentation/create")
65 return (
66 <header
67 className="notranslate flex min-h-12 w-full max-w-screen items-center justify-between gap-2 overflow-clip border-accent px-2 py-2"
68 translate="no"
69 >
70 <div className="flex min-w-0 items-center gap-2">
71 {/* This component is suppose to be logo but for now its is actually hamburger menu */}
72
73 <Link href="/presentation">
74 <Button size={"icon"} className="rounded-full" variant={"ghost"}>
75 <Brain></Brain>
76 </Button>
77 </Link>
78
79 <motion.div
80 initial={false}
81 layout="position"
82 transition={{ duration: 1 }}
83 >
84 <Link href="/" className="h-max">
85 <AllweoneText className="h-10 w-30 cursor-pointer transition-transform duration-100 active:scale-95"></AllweoneText>
86 </Link>
87 </motion.div>
88 </div>
89
90 {/* <SideBarDropdown /> */}
91 </header>
92 );
93
94 return (
95 <header
96 className="notranslate flex min-h-12 w-full items-center justify-between gap-3 overflow-hidden border-b border-accent bg-background px-3 py-2 sm:px-4"
97 translate="no"
98 >
99 {/* Left section with breadcrumb navigation */}
100 <div className="flex min-w-0 flex-1 items-center gap-2">
101 {showBrand ? (
102 <Link href="/">
103 <AllweoneText className="h-8 w-28" />
104 </Link>
105 ) : (
106 <Link
107 href="/presentation"
108 className="text-muted-foreground hover:text-foreground"
109 >
110 <Brain className="h-5 w-5"></Brain>
111 </Link>
112 )}
113 {isPresentationPage && !isLoggedOut && (
114 <PresentationMenu readOnly={isReadOnly} />
115 )}
116 {isLoggedOut && showPresentationTitle ? (
117 <span className="truncate text-sm font-medium text-foreground sm:hidden">
118 {presentationTitle}
119 </span>
120 ) : !isLoggedOut && showPresentationTitle ? (
121 <Input
122 type="text"
123 id="presentation-title-input"
124 value={presentationTitle}
125 onChange={(e) => setPresentationTitle(e.target.value)}
126 disabled={isReadOnly}
127 onBlur={async () => {
128 if (isReadOnly) {
129 return;
130 }
131 if (
132 presentationTitle &&
133 currentPresentationTitle !== presentationTitle &&
134 currentPresentationId
135 ) {
136 try {
137 await updatePresentationTitle(
138 currentPresentationId,
139 presentationTitle,
140 );
141 } catch {
142 setPresentationTitle(currentPresentationTitle || "");
143 }
144 }
145 }}
146 className="line-clamp-1 h-auto min-w-0 flex-1 cursor-text rounded-xs border-none bg-transparent p-0 font-medium text-ellipsis shadow-none outline-none sm:max-w-96"
147 style={{
148 appearance: "none",
149 }}
150 />
151 ) : null}
152 </div>
153
154 {isLoggedOut && showPresentationTitle ? (
155 <div className="pointer-events-none absolute top-1/2 left-1/2 w-[min(60vw,40rem)] -translate-x-1/2 -translate-y-1/2 px-3 text-center">
156 <span className="line-clamp-1 text-lg font-medium text-foreground">
157 {presentationTitle}
158 </span>
159 </div>
160 ) : null}
161
162 {/* Right section with actions */}
163 <div className="scrollbar-hide flex max-w-[56vw] shrink-0 items-center gap-2 overflow-x-auto md:max-w-none md:overflow-visible">
164 {/* Saving indicator - Placed right before Theme button */}
165 {isPresentationPage && !isPresenting && !isReadOnly && (
166 <PresentationSavingIndicator />
167 )}
168
169 {/* Theme button - Only in presentation page, not outline or present mode */}
170 {isPresentationPage && !isPresenting && !isReadOnly && (
171 <Button
172 variant="ghost"
173 className="h-9 gap-1.5"
174 onClick={() => setActiveRightPanel("theme")}
175 >
176 <Palette className="size-4" />
177 <span className="sr-only">
178 Theme
179 </span>
180 <span className="hidden sm:inline">
181 Theme
182 </span>
183 </Button>
184 )}
185
186 {/* Export button - Only in presentation page, not outline or present mode */}
187 {isPresentationPage && !isPresenting && !isReadOnly && <ExportButton />}
188
189 {/* Present button - Only in presentation page, not outline */}
190 {isPresentationPage && <PresentButton />}
191
192 </div>
193 </header>
194 );
195 }
196
196 lines Plain Text