返回 presentation-ai
theme-resolution.ts
根目录 / src / lib / presentation / theme-resolution.ts
1 import {
2 themes,
3 type ThemeProperties,
4 type Themes,
5 } from "@/lib/presentation/themes";
6 import {
7 presentationAiThemePropertiesSchema,
8 presentationThemePropertiesSchema,
9 presentationThemeStyleDataSchema,
10 } from "./theme-schema";
11
12 export const PRESENTATION_AUTO_THEME_ID = "auto";
13
14 export type PresentationThemeSelectionSource = "auto" | "selected";
15
16 export function isPresentationAutoTheme(theme: string | null | undefined) {
17 return theme === PRESENTATION_AUTO_THEME_ID;
18 }
19
20 export function isBuiltInPresentationTheme(
21 theme: string | null | undefined,
22 ): theme is Themes {
23 return typeof theme === "string" && theme in themes;
24 }
25
26 function resolveCompleteCustomThemeData(
27 customThemeData: unknown,
28 ): ThemeProperties | null {
29 const parsedTheme =
30 presentationThemePropertiesSchema.safeParse(customThemeData);
31
32 if (parsedTheme.success) {
33 return parsedTheme.data;
34 }
35
36 const parsedThemeStyleData =
37 presentationThemeStyleDataSchema.safeParse(customThemeData);
38
39 if (parsedThemeStyleData.success) {
40 const fallbackTheme = themes.mystique;
41
42 return {
43 ...parsedThemeStyleData.data,
44 name: parsedThemeStyleData.data.name ?? `Custom ${fallbackTheme.name}`,
45 description:
46 parsedThemeStyleData.data.description ??
47 `Custom theme based on ${fallbackTheme.name}`,
48 };
49 }
50
51 const parsedPartialTheme =
52 presentationAiThemePropertiesSchema.safeParse(customThemeData);
53
54 if (!parsedPartialTheme.success) {
55 return null;
56 }
57
58 const fallbackTheme = themes.mystique;
59 const completedTheme: ThemeProperties = {
60 ...fallbackTheme,
61 name: parsedPartialTheme.data.name ?? `Custom ${fallbackTheme.name}`,
62 description:
63 parsedPartialTheme.data.description ??
64 `Custom theme based on ${fallbackTheme.name}`,
65 colors: {
66 ...fallbackTheme.colors,
67 ...parsedPartialTheme.data.colors,
68 },
69 fonts: {
70 ...fallbackTheme.fonts,
71 ...parsedPartialTheme.data.fonts,
72 },
73 background: parsedPartialTheme.data.background ?? fallbackTheme.background,
74 };
75 const parsedCompletedTheme =
76 presentationThemePropertiesSchema.safeParse(completedTheme);
77
78 return parsedCompletedTheme.success ? parsedCompletedTheme.data : null;
79 }
80
81 export function resolvePresentationThemeData({
82 customThemeData,
83 theme,
84 }: {
85 customThemeData: unknown;
86 theme: string | null | undefined;
87 }): ThemeProperties | null {
88 if (customThemeData) {
89 const resolved = resolveCompleteCustomThemeData(customThemeData);
90 if (resolved) {
91 return resolved;
92 }
93 // Custom data failed validation — fall through to built-in lookup
94 }
95
96 if (isBuiltInPresentationTheme(theme)) {
97 return themes[theme];
98 }
99
100 return null;
101 }
102
103 export function getPersistablePresentationTheme({
104 fallbackTheme,
105 theme,
106 }: {
107 fallbackTheme: Themes;
108 theme: string | null | undefined;
109 }): Themes | string {
110 return isPresentationAutoTheme(theme)
111 ? fallbackTheme
112 : (theme ?? fallbackTheme);
113 }
114
114 lines TYPESCRIPT