返回 presentation-ai
useThemeModalState.ts
根目录 / src / components / notebook / presentation / components / theme / useThemeModalState.ts
1 import { useQuery } from "@tanstack/react-query";
2 import { useEffect, useState } from "react";
3
4 import { getUserCustomThemes } from "@/app/_actions/presentation/theme-actions";
5 import {
6 themes as builtInThemes,
7 type ThemeProperties,
8 } from "@/lib/presentation/themes";
9 import { usePresentationState } from "@/states/presentation-state";
10
11 interface CustomTheme {
12 id: string;
13 name: string;
14 description?: string;
15 themeData: ThemeProperties;
16 isPublic: boolean;
17 logoUrl?: string;
18 userId: string;
19 user?: {
20 name: string;
21 };
22 isFavorite?: boolean;
23 likeCount?: number;
24 isLiked?: boolean;
25 }
26
27 /**
28 * Custom hook containing theme modal state and logic
29 */
30 export function useThemeModalState(
31 isOpen: boolean,
32 initialPreviewTheme?: {
33 id: string;
34 data: ThemeProperties;
35 },
36 ) {
37 const {
38 theme: currentThemeId,
39 customThemeData,
40 setTheme,
41 } = usePresentationState();
42
43 const [activeTab, setActiveTab] = useState("allweone-themes");
44 const [selectedThemeId, setSelectedThemeId] = useState<string | null>(null);
45 const [selectedThemeData, setSelectedThemeData] =
46 useState<ThemeProperties | null>(null);
47 const [hasInitialized, setHasInitialized] = useState(false);
48
49 // Initialize selected theme from current theme when opening
50 useEffect(() => {
51 if (!isOpen) {
52 setHasInitialized(false);
53 return;
54 }
55
56 if (hasInitialized) return;
57
58 if (initialPreviewTheme) {
59 setSelectedThemeId(initialPreviewTheme.id);
60 setSelectedThemeData(initialPreviewTheme.data);
61 setHasInitialized(true);
62 return;
63 }
64
65 // Try to find the current theme in built-ins first
66 if (
67 typeof currentThemeId === "string" &&
68 builtInThemes[currentThemeId as keyof typeof builtInThemes]
69 ) {
70 setSelectedThemeId(currentThemeId);
71 setSelectedThemeData(
72 builtInThemes[currentThemeId as keyof typeof builtInThemes],
73 );
74 } else if (currentThemeId === "auto" && customThemeData) {
75 setSelectedThemeId(currentThemeId);
76 setSelectedThemeData(customThemeData);
77 } else {
78 const keys = Object.keys(builtInThemes);
79 if (keys.length > 0) {
80 const firstKey = keys[0]!;
81 setSelectedThemeId(firstKey);
82 setSelectedThemeData(
83 builtInThemes[firstKey as keyof typeof builtInThemes],
84 );
85 }
86 }
87 setHasInitialized(true);
88 }, [
89 isOpen,
90 hasInitialized,
91 currentThemeId,
92 customThemeData,
93 initialPreviewTheme,
94 ]);
95
96 // Fetch user themes with React Query
97 const { data: userThemes = [], isLoading: isLoadingUserThemes } = useQuery({
98 queryKey: ["userThemes"],
99 queryFn: async () => {
100 const result = await getUserCustomThemes();
101 return result.success ? (result.themes as CustomTheme[]) : [];
102 },
103 enabled: isOpen,
104 });
105
106 const handlePreviewTheme = (id: string, theme: ThemeProperties) => {
107 setSelectedThemeId(id);
108 setSelectedThemeData(theme);
109 };
110
111 const handleApplyTheme = () => {
112 if (selectedThemeId && selectedThemeData) {
113 setTheme(
114 selectedThemeId,
115 builtInThemes[selectedThemeId as keyof typeof builtInThemes]
116 ? undefined
117 : selectedThemeData,
118 );
119 }
120 };
121
122 return {
123 activeTab,
124 setActiveTab,
125 selectedThemeId,
126 selectedThemeData,
127 userThemes,
128 isLoadingUserThemes,
129 handlePreviewTheme,
130 handleApplyTheme,
131 };
132 }
133
133 lines TYPESCRIPT