pstream is dead; long live pstream
taciturnaxolotl.github.io/pstream-ng/
1import merge from "lodash.merge";
2import { createTheme } from "./types";
3import { defaultTheme } from "./default";
4import { colorToRgbString } from "../src/utils/color";
5import { allThemes } from "./all";
6
7const availableThemes = [
8 { id: "default", theme: defaultTheme },
9 ...allThemes.map((t) => ({
10 id: t.name,
11 theme: { extend: t.extend },
12 })),
13];
14
15function cssVarName(path: string) {
16 return `--colors-${path}`;
17}
18
19// Generate the custom theme structure with CSS variables
20function generateCustomThemeStructure(theme: any, prefix = ""): any {
21 const result: any = {};
22 for (const key in theme) {
23 if (typeof theme[key] === "object" && theme[key] !== null) {
24 result[key] = generateCustomThemeStructure(
25 theme[key],
26 `${prefix}${key}-`,
27 );
28 } else {
29 result[key] =
30 `rgb(var(${cssVarName(`${prefix}${key}`)}) / <alpha-value>)`;
31 }
32 }
33 return result;
34}
35
36export const customTheme = createTheme({
37 name: "custom",
38 extend: {
39 colors: generateCustomThemeStructure(defaultTheme.extend.colors),
40 },
41});
42
43// Define parts
44const parts = {
45 primary: [
46 "lightBar.light",
47 "type.logo",
48 "buttons.primary",
49 "buttons.primaryText",
50 "buttons.primaryHover",
51 "buttons.toggle",
52 "buttons.toggleDisabled",
53 "buttons.purple",
54 "buttons.purpleHover",
55 "global.accentA",
56 "global.accentB",
57 "pill.highlight",
58 "progress.filled",
59 "video.audio.set",
60 "video.context.type.accent",
61 "video.context.sliderFilled",
62 "video.scraping.loading",
63 "onboarding.good",
64 "onboarding.best",
65 "onboarding.link",
66 "onboarding.barFilled",
67 "settings.sidebar.type.iconActivated",
68 "settings.sidebar.type.activated",
69 "type.link",
70 "type.linkHover",
71 "largeCard.icon",
72 "mediaCard.barFillColor",
73 ],
74 secondary: [
75 "type.text",
76 "type.dimmed",
77 "type.secondary",
78 "type.emphasis",
79 "type.divider",
80 "type.danger",
81 "type.success",
82 "buttons.secondary",
83 "buttons.secondaryText",
84 "buttons.secondaryHover",
85 "buttons.danger",
86 "buttons.dangerHover",
87 "buttons.cancel",
88 "buttons.cancelHover",
89 "utils.divider",
90 "search.text",
91 "search.placeholder",
92 "search.icon",
93 "dropdown.text",
94 "dropdown.secondary",
95 "dropdown.border",
96 "authentication.border",
97 "authentication.inputBg",
98 "authentication.inputBgHover",
99 "authentication.wordBackground",
100 "authentication.copyText",
101 "authentication.copyTextHover",
102 "authentication.errorText",
103 "settings.sidebar.activeLink",
104 "settings.sidebar.badge",
105 "settings.sidebar.type.secondary",
106 "settings.sidebar.type.inactive",
107 "settings.sidebar.type.icon",
108 "settings.card.border",
109 "onboarding.bar",
110 "onboarding.divider",
111 "onboarding.border",
112 "errors.border",
113 "errors.type.secondary",
114 "about.circle",
115 "about.circleText",
116 "editBadge.bg",
117 "editBadge.bgHover",
118 "editBadge.text",
119 "progress.background",
120 "progress.preloaded",
121 "pill.background",
122 "pill.backgroundHover",
123 "pill.activeBackground",
124 "video.buttonBackground",
125 "video.autoPlay.background",
126 "video.autoPlay.hover",
127 "video.scraping.error",
128 "video.scraping.success",
129 "video.scraping.noresult",
130 "video.context.light",
131 "video.context.border",
132 "video.context.hoverColor",
133 "video.context.buttonFocus",
134 "video.context.inputBg",
135 "video.context.buttonOverInputHover",
136 "video.context.inputPlaceholder",
137 "video.context.cardBorder",
138 "video.context.slider",
139 "video.context.error",
140 "video.context.buttons.list",
141 "video.context.buttons.active",
142 "video.context.closeHover",
143 "video.context.type.main",
144 "video.context.type.secondary",
145 "mediaCard.barColor",
146 "mediaCard.badge",
147 "mediaCard.badgeText",
148 ],
149 tertiary: [
150 "background.main",
151 "background.secondary",
152 "background.secondaryHover",
153 "background.accentA",
154 "background.accentB",
155 "modal.background",
156 "mediaCard.shadow",
157 "mediaCard.hoverBackground",
158 "mediaCard.hoverAccent",
159 "mediaCard.hoverShadow",
160 "search.background",
161 "search.hoverBackground",
162 "search.focused",
163 "dropdown.background",
164 "dropdown.altBackground",
165 "dropdown.hoverBackground",
166 "dropdown.contentBackground",
167 "dropdown.highlight",
168 "dropdown.highlightHover",
169 "largeCard.background",
170 "settings.card.background",
171 "settings.card.altBackground",
172 "settings.saveBar.background",
173 "onboarding.card",
174 "onboarding.cardHover",
175 "errors.card",
176 "themePreview.primary",
177 "themePreview.secondary",
178 "themePreview.ghost",
179 "video.scraping.card",
180 "video.context.background",
181 "video.context.flagBg",
182 ],
183};
184
185function getNestedValue(obj: any, path: string) {
186 return path.split(".").reduce((o, i) => (o ? o[i] : undefined), obj);
187}
188
189function extractColors(theme: any, keys: string[]) {
190 const colors: Record<string, string> = {};
191 // We need to flatten the structure to css vars
192 keys.forEach((key) => {
193 const value = getNestedValue(theme.extend.colors, key);
194 if (value) {
195 colors[cssVarName(key.replace(/\./g, "-"))] = colorToRgbString(value);
196 }
197 });
198 return colors;
199}
200
201// Generate options for each part
202export const primaryOptions = availableThemes.map((t) => {
203 const merged = merge({}, defaultTheme, t.theme);
204 return {
205 id: t.id,
206 colors: extractColors(merged, parts.primary),
207 };
208});
209
210export const secondaryOptions = availableThemes.map((t) => {
211 const merged = merge({}, defaultTheme, t.theme);
212 return {
213 id: t.id,
214 colors: extractColors(merged, parts.secondary),
215 };
216});
217
218export const tertiaryOptions = availableThemes.map((t) => {
219 const merged = merge({}, defaultTheme, t.theme);
220 return {
221 id: t.id,
222 colors: extractColors(merged, parts.tertiary),
223 };
224});