1import strudelTheme, { settings as strudelThemeSettings } from './themes/strudel-theme.mjs';
2import bluescreen, { settings as bluescreenSettings } from './themes/bluescreen.mjs';
3import blackscreen, { settings as blackscreenSettings } from './themes/blackscreen.mjs';
4import whitescreen, { settings as whitescreenSettings } from './themes/whitescreen.mjs';
5import teletext, { settings as teletextSettings } from './themes/teletext.mjs';
6import algoboy, { settings as algoboySettings } from './themes/algoboy.mjs';
7import CutiePi, { settings as CutiePiSettings } from './themes/CutiePi.mjs';
8import sonicPink, { settings as sonicPinkSettings } from './themes/sonic-pink.mjs';
9import redText, { settings as redTextSettings } from './themes/red-text.mjs';
10import greenText, { settings as greenTextSettings } from './themes/green-text.mjs';
11import archBtw, { settings as archBtwSettings } from './themes/archBtw.mjs';
12import fruitDaw, { settings as fruitDawSettings } from './themes/fruitDaw.mjs';
13
14import bluescreenlight, { settings as bluescreenlightsettings } from './themes/bluescreenlight.mjs';
15
16import androidstudio, { settings as androidstudioSettings } from './themes/androidstudio.mjs';
17import atomone, { settings as atomOneSettings } from './themes/atomone.mjs';
18import aura, { settings as auraSettings } from './themes/aura.mjs';
19import darcula, { settings as darculaSettings } from './themes/darcula.mjs';
20import dracula, { settings as draculaSettings } from './themes/dracula.mjs';
21import duotoneDark, { settings as duotoneDarkSettings } from './themes/duotoneDark.mjs';
22import eclipse, { settings as eclipseSettings } from './themes/eclipse.mjs';
23import githubDark, { settings as githubDarkSettings } from './themes/githubDark.mjs';
24import githubLight, { settings as githubLightSettings } from './themes/githubLight.mjs';
25import gruvboxDark, { settings as gruvboxDarkSettings } from './themes/gruvboxDark.mjs';
26import gruvboxLight, { settings as gruvboxLightSettings } from './themes/gruvboxLight.mjs';
27import materialDark, { settings as materialDarkSettings } from './themes/materialDark.mjs';
28import materialLight, { settings as materialLightSettings } from './themes/materialLight.mjs';
29import nord, { settings as nordSettings } from './themes/nord.mjs';
30import monokai, { settings as monokaiSettings } from './themes/monokai.mjs';
31import solarizedDark, { settings as solarizedDarkSettings } from './themes/solarizedDark.mjs';
32import solarizedLight, { settings as solarizedLightSettings } from './themes/solarizedLight.mjs';
33import sublime, { settings as sublimeSettings } from './themes/sublime.mjs';
34import tokyoNight, { settings as tokyoNightSettings } from './themes/tokyoNight.mjs';
35import tokyoNightStorm, { settings as tokyoNightStormSettings } from './themes/tokioNightStorm.mjs';
36import tokyoNightDay, { settings as tokyoNightDaySettings } from './themes/tokyoNightDay.mjs';
37import vscodeDark, { settings as vscodeDarkSettings } from './themes/vscodeDark.mjs';
38import vscodeLight, { settings as vscodeLightSettings } from './themes/vscodeLight.mjs';
39import xcodeLight, { settings as xcodeLightSettings } from './themes/xcodeLight.mjs';
40import bbedit, { settings as bbeditSettings } from './themes/bbedit.mjs';
41import noctisLilac, { settings as noctisLilacSettings } from './themes/noctisLilac.mjs';
42
43import { setTheme } from '@strudel/draw';
44export const themes = {
45  strudelTheme,
46  algoboy,
47  archBtw,
48  androidstudio,
49  atomone,
50  aura,
51  bbedit,
52  blackscreen,
53  bluescreen,
54  bluescreenlight,
55  CutiePi,
56  darcula,
57  dracula,
58  duotoneDark,
59  eclipse,
60  fruitDaw,
61  githubDark,
62  githubLight,
63  greenText,
64  gruvboxDark,
65  gruvboxLight,
66  sonicPink,
67  materialDark,
68  materialLight,
69  monokai,
70  noctisLilac,
71  nord,
72  redText,
73  solarizedDark,
74  solarizedLight,
75  sublime,
76  teletext,
77  tokyoNight,
78  tokyoNightDay,
79  tokyoNightStorm,
80  vscodeDark,
81  vscodeLight,
82  whitescreen,
83  xcodeLight,
84};
85
86export const settings = {
87  strudelTheme: strudelThemeSettings,
88  bluescreen: bluescreenSettings,
89  bluescreenlight: bluescreenlightsettings,
90  blackscreen: blackscreenSettings,
91  whitescreen: whitescreenSettings,
92  teletext: teletextSettings,
93  algoboy: algoboySettings,
94  archBtw: archBtwSettings,
95  androidstudio: androidstudioSettings,
96  atomone: atomOneSettings,
97  aura: auraSettings,
98  bbedit: bbeditSettings,
99  darcula: darculaSettings,
100  dracula: draculaSettings,
101  duotoneDark: duotoneDarkSettings,
102  eclipse: eclipseSettings,
103  CutiePi: CutiePiSettings,
104  sonicPink: sonicPinkSettings,
105  fruitDaw: fruitDawSettings,
106  githubLight: githubLightSettings,
107  githubDark: githubDarkSettings,
108  greenText: greenTextSettings,
109
110  gruvboxDark: gruvboxDarkSettings,
111  gruvboxLight: gruvboxLightSettings,
112  materialDark: materialDarkSettings,
113  materialLight: materialLightSettings,
114  noctisLilac: noctisLilacSettings,
115  nord: nordSettings,
116  monokai: monokaiSettings,
117  redText: redTextSettings,
118  solarizedLight: solarizedLightSettings,
119  solarizedDark: solarizedDarkSettings,
120  sublime: sublimeSettings,
121  tokyoNight: tokyoNightSettings,
122  tokyoNightStorm: tokyoNightStormSettings,
123  vscodeDark: vscodeDarkSettings,
124  vscodeLight: vscodeLightSettings,
125  xcodeLight: xcodeLightSettings,
126  tokyoNightDay: tokyoNightDaySettings,
127};
128
129function getColors(str) {
130  const colorRegex = /#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})/g;
131  const colors = [];
132
133  let match;
134  while ((match = colorRegex.exec(str)) !== null) {
135    const color = match[0];
136    if (!colors.includes(color)) {
137      colors.push(color);
138    }
139  }
140
141  return colors;
142}
143
144// TODO: remove
145export function themeColors(theme) {
146  return getColors(stringifySafe(theme));
147}
148
149function getCircularReplacer() {
150  const seen = new WeakSet();
151  return (key, value) => {
152    if (typeof value === 'object' && value !== null) {
153      if (seen.has(value)) {
154        return;
155      }
156      seen.add(value);
157    }
158    return value;
159  };
160}
161
162function stringifySafe(json) {
163  return JSON.stringify(json, getCircularReplacer());
164}
165
166export const theme = (theme) => themes[theme] || themes.strudelTheme;
167
168// css style injection helpers
169export function injectStyle(rule) {
170  const newStyle = document.createElement('style');
171  document.head.appendChild(newStyle);
172  const styleSheet = newStyle.sheet;
173  const ruleIndex = styleSheet.insertRule(rule, 0);
174  return () => styleSheet.deleteRule(ruleIndex);
175}
176
177let currentTheme,
178  resetThemeStyle,
179  themeStyle,
180  styleID = 'strudel-theme-vars';
181export function initTheme(theme) {
182  if (!document.getElementById(styleID)) {
183    themeStyle = document.createElement('style');
184    themeStyle.id = styleID;
185    document.head.append(themeStyle);
186  }
187  activateTheme(theme);
188}
189
190export function activateTheme(name) {
191  if (currentTheme === name) {
192    return;
193  }
194  currentTheme = name;
195  if (!settings[name]) {
196    console.warn('theme', name, 'has no settings.. defaulting to strudelTheme settings');
197  }
198  const themeSettings = settings[name] || settings.strudelTheme;
199  // set css variables
200  themeStyle.innerHTML = `:root {
201      color-scheme: ${themeSettings.light ? 'light' : 'dark'};
202      ${Object.entries(themeSettings)
203        // important to override fallback
204        .map(([key, value]) => `--${key}: ${value} !important;`)
205        .join('\n')}
206    }`;
207  setTheme(themeSettings);
208  // tailwind dark mode
209  if (themeSettings.light) {
210    document.documentElement.classList.remove('dark');
211  } else {
212    document.documentElement.classList.add('dark');
213  }
214  resetThemeStyle?.();
215  resetThemeStyle = undefined;
216  if (themeSettings.customStyle) {
217    resetThemeStyle = injectStyle(themeSettings.customStyle);
218  }
219}