返回 presentation-ai
ThemePanel.tsx
1 "use client";
2
3 import { useQuery } from "@tanstack/react-query";
4 import { useSession } from "next-auth/react";
5 import { useMemo } from "react";
6
7 import {
8 getSystemPresentationThemes,
9 getUserCustomThemes,
10 } from "@/app/_actions/presentation/theme-actions";
11 import { getUserFavoriteThemes } from "@/app/_actions/presentation/theme-favorite-actions";
12 import { type CustomTheme } from "@/components/notebook/presentation/components/theme/types";
13 import { PRESENTATION_AUTO_THEME_ID } from "@/lib/presentation/theme-resolution";
14 import { themes as builtInThemes } from "@/lib/presentation/themes";
15 import { usePresentationState } from "@/states/presentation-state";
16 import { FontsSection } from "../../controls/global-settings/sections/FontsSection";
17 import { ThemeFilter } from "./theme/Filter";
18 import { useThemePanelState } from "./theme/theme-panel-state";
19 import { ThemePanelHeader } from "./theme/ThemeHeader";
20 import { ThemeSelector, type ThemeListItem } from "./theme/ThemeSelector";
21 import { ThemeTabs } from "./theme/ThemeTabs";
22
23 export function ThemePanel() {
24 const { generatedThemeData, theme: activeTheme } = usePresentationState();
25 const { tab, showFavorites, showFont } = useThemePanelState();
26 const { data: session } = useSession();
27 const canEditSystemThemes = session?.user?.isAdmin === true;
28
29 const systemThemesQuery = useQuery({
30 queryKey: ["presentation", "themes", "system"],
31 queryFn: async () => {
32 const result = await getSystemPresentationThemes();
33 return result.success ? (result.themes as CustomTheme[]) : [];
34 },
35 enabled: tab === "standard",
36 });
37
38 const userThemesQuery = useQuery({
39 queryKey: ["presentation", "themes", "user"],
40 queryFn: async () => {
41 const result = await getUserCustomThemes();
42 return result.success ? (result.themes as CustomTheme[]) : [];
43 },
44 enabled: tab === "public",
45 });
46
47 const favoriteThemesQuery = useQuery({
48 queryKey: ["presentation", "themes", "favorites"],
49 queryFn: async () => {
50 const result = await getUserFavoriteThemes();
51 return result.success ? (result.themes as CustomTheme[]) : [];
52 },
53 enabled: showFavorites,
54 });
55
56 const userThemes = userThemesQuery.data ?? [];
57 const favoriteThemes = favoriteThemesQuery.data ?? [];
58 const systemThemes = systemThemesQuery.data ?? [];
59
60 const standardThemeItems = useMemo<ThemeListItem[]>(() => {
61 const systemThemesById = new Map(
62 systemThemes.map((item) => [item.id, item]),
63 );
64
65 const automaticThemeItem: ThemeListItem[] = generatedThemeData
66 ? [
67 {
68 themeId: PRESENTATION_AUTO_THEME_ID,
69 theme: {
70 ...generatedThemeData,
71 name: generatedThemeData.name || "Custom Theme",
72 description:
73 generatedThemeData.description ||
74 "Generated presentation theme",
75 },
76 canLike: false,
77 showFavoriteButton: false,
78 },
79 ]
80 : [];
81
82 const builtInThemeItems = Object.entries(builtInThemes).map(
83 ([key, value]) => {
84 const systemTheme = systemThemesById.get(key);
85 const themeData = systemTheme
86 ? {
87 ...value,
88 ...systemTheme.themeData,
89 name: systemTheme.name,
90 description: systemTheme.description ?? value.description,
91 }
92 : value;
93
94 return {
95 themeId: key,
96 theme: themeData,
97 canLike: false,
98 showFavoriteButton: false,
99 isAdminTheme: true,
100 canEditSystemTheme: canEditSystemThemes,
101 };
102 },
103 );
104
105 return [...automaticThemeItem, ...builtInThemeItems];
106 }, [canEditSystemThemes, generatedThemeData, systemThemes]);
107
108 const userThemeItems = useMemo<ThemeListItem[]>(
109 () =>
110 userThemes.map((item) => ({
111 key: item.id,
112 themeId: item.id,
113 theme: { ...item.themeData, name: item.name },
114 isFavorite: item.isFavorite ?? false,
115 likeCount: item.likeCount ?? 0,
116 isLiked: item.isLiked ?? false,
117 canLike: false,
118 showFavoriteButton: false,
119 isUserTheme: true,
120 })),
121 [userThemes],
122 );
123
124 const favoriteThemeItems = useMemo<ThemeListItem[]>(
125 () =>
126 favoriteThemes.map((item) => ({
127 key: item.id,
128 themeId: item.id,
129 theme: { ...item.themeData, name: item.name },
130 isFavorite: item.isFavorite ?? true,
131 likeCount: item.likeCount ?? 0,
132 isLiked: item.isLiked ?? false,
133 canLike: false,
134 showFavoriteButton: true,
135 isUserTheme: item.userId === session?.user?.id,
136 })),
137 [favoriteThemes, session?.user?.id],
138 );
139
140 let themesForSelector: ThemeListItem[] = [];
141 if (showFavorites) {
142 themesForSelector = favoriteThemeItems;
143 } else if (tab === "public") {
144 themesForSelector = userThemeItems;
145 } else {
146 themesForSelector = standardThemeItems;
147 }
148
149 const isLoadingThemes = showFavorites
150 ? favoriteThemesQuery.isPending
151 : tab === "public" && userThemesQuery.isPending;
152
153 return (
154 <div className="flex h-full flex-col gap-2 bg-background">
155 <ThemePanelHeader />
156 <ThemeTabs />
157 <ThemeFilter />
158
159 {showFont ? (
160 <div className="scrollbar-thin max-h-[calc(100vh-4rem-4rem-4rem)] overflow-y-auto px-4 pt-2 pb-4 scrollbar-thumb-accent">
161 <FontsSection />
162 </div>
163 ) : (
164 <ThemeSelector
165 themes={themesForSelector}
166 activeKey={String(activeTheme)}
167 isLoading={isLoadingThemes && !themesForSelector.length}
168 emptyMessage={
169 showFavorites
170 ? "You have not favorited any themes yet."
171 : tab === "public"
172 ? "You have not created any themes yet."
173 : "No themes available right now."
174 }
175 />
176 )}
177 </div>
178 );
179 }
180
180 lines Plain Text