@phenx-inc/ctlsurf 0.14.2 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD-PARTY-NOTICES.md +82 -0
- package/out/headless/index.mjs +3 -2
- package/out/headless/index.mjs.map +2 -2
- package/out/main/index.js +27 -0
- package/out/preload/index.js +5 -0
- package/out/renderer/assets/{cssMode-bQYoCKra.js → cssMode-CJR4iD-G.js} +3 -3
- package/out/renderer/assets/{freemarker2-BS1ERQl_.js → freemarker2-BRI9ISq9.js} +1 -1
- package/out/renderer/assets/{handlebars-B-ZEHRLr.js → handlebars-3hL6XIZI.js} +1 -1
- package/out/renderer/assets/{html-lk9e1Lkl.js → html-Dd5_vBoP.js} +1 -1
- package/out/renderer/assets/{htmlMode-Ds1eMYgM.js → htmlMode-DgNtlKcG.js} +3 -3
- package/out/renderer/assets/{index-BpCORklm.css → index-3sFJEz7i.css} +787 -399
- package/out/renderer/assets/{index-BJNR4hRK.js → index-5azC4yNn.js} +421 -99
- package/out/renderer/assets/{javascript-D6OeziKX.js → javascript-D0vPLEJg.js} +2 -2
- package/out/renderer/assets/{jsonMode-t5SESE75.js → jsonMode-DBvR0zMu.js} +3 -3
- package/out/renderer/assets/{liquid-B5Wjh0p_.js → liquid-CLoc6Tx7.js} +1 -1
- package/out/renderer/assets/{lspLanguageFeatures-BEgiDCIW.js → lspLanguageFeatures-CiaMaQoY.js} +1 -1
- package/out/renderer/assets/{mdx-ByNAXh8t.js → mdx-DTsq6tlv.js} +1 -1
- package/out/renderer/assets/{python-CIFO0XQW.js → python-D04DpZDr.js} +1 -1
- package/out/renderer/assets/{razor-LXHhGcNO.js → razor-Di6CJ6s8.js} +1 -1
- package/out/renderer/assets/{tsMode-WiO8CDaI.js → tsMode-mfv6mHOv.js} +1 -1
- package/out/renderer/assets/{typescript-ZBQlq256.js → typescript-CDA0Hhef.js} +1 -1
- package/out/renderer/assets/{xml-DBaLJlQU.js → xml-BU0voRuq.js} +1 -1
- package/out/renderer/assets/{yaml-CId9skah.js → yaml-qzFWnaZJ.js} +1 -1
- package/out/renderer/index.html +60 -3
- package/package.json +5 -4
- package/src/main/index.ts +52 -1
- package/src/main/orchestrator.ts +8 -0
- package/src/preload/index.ts +10 -0
- package/src/renderer/App.tsx +2 -0
- package/src/renderer/components/CtlsurfPanel.tsx +1 -1
- package/src/renderer/components/EditorPanel.tsx +29 -1
- package/src/renderer/components/ObserverPanel.tsx +13 -6
- package/src/renderer/components/SettingsDialog.tsx +82 -0
- package/src/renderer/components/TerminalPanel.tsx +12 -22
- package/src/renderer/components/TicketPanel.tsx +17 -10
- package/src/renderer/index.html +58 -1
- package/src/renderer/lib/theme.ts +173 -0
- package/src/renderer/lib/themes.ts +126 -0
- package/src/renderer/styles.css +787 -399
package/src/renderer/index.html
CHANGED
|
@@ -4,10 +4,42 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>ctlsurf-worker</title>
|
|
7
|
+
<!-- Stamp the theme before the first paint. This has to be an inline,
|
|
8
|
+
synchronous script in <head>: reading the pref over IPC (or waiting for
|
|
9
|
+
the bundle) would let one frame of the wrong theme through, which reads
|
|
10
|
+
as a flash every launch. lib/theme.ts owns the same key afterwards. -->
|
|
11
|
+
<script>
|
|
12
|
+
(function () {
|
|
13
|
+
var DARK = 'tokyo-night', LIGHT = 'tokyo-day'
|
|
14
|
+
var a = null
|
|
15
|
+
try {
|
|
16
|
+
var raw = localStorage.getItem('ctlsurf.appearance')
|
|
17
|
+
if (raw) a = JSON.parse(raw)
|
|
18
|
+
else {
|
|
19
|
+
// Migrate the original system/light/dark preference. lib/theme.ts
|
|
20
|
+
// performs the same mapping; both must agree or the first frame
|
|
21
|
+
// disagrees with the second.
|
|
22
|
+
var legacy = localStorage.getItem('ctlsurf.theme')
|
|
23
|
+
if (legacy === 'light') a = { sync: false, theme: LIGHT }
|
|
24
|
+
else if (legacy === 'dark') a = { sync: false, theme: DARK }
|
|
25
|
+
}
|
|
26
|
+
} catch (e) { /* private mode, or corrupt JSON */ }
|
|
27
|
+
var prefersLight = window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches
|
|
28
|
+
var id
|
|
29
|
+
if (a && a.sync === false) id = a.theme
|
|
30
|
+
else if (a) id = prefersLight ? a.light : a.dark
|
|
31
|
+
else id = prefersLight ? LIGHT : DARK
|
|
32
|
+
document.documentElement.setAttribute('data-theme', id || (prefersLight ? LIGHT : DARK))
|
|
33
|
+
})()
|
|
34
|
+
</script>
|
|
7
35
|
<!-- Boot splash styles are inlined here so they paint on the first frame,
|
|
8
36
|
before the JS bundle and styles.css have loaded. Without this the window
|
|
9
|
-
shows an empty dark pane while React mounts and the startup IPC runs.
|
|
37
|
+
shows an empty dark pane while React mounts and the startup IPC runs.
|
|
38
|
+
They cannot use the tokens in styles.css for the same reason, so the two
|
|
39
|
+
palettes are repeated here — keep them in step with :root there. -->
|
|
10
40
|
<style>
|
|
41
|
+
:root { color-scheme: dark }
|
|
42
|
+
:root[data-theme='tokyo-day'], :root[data-theme='catppuccin-latte'] { color-scheme: light }
|
|
11
43
|
#boot-splash {
|
|
12
44
|
position: fixed;
|
|
13
45
|
inset: 0;
|
|
@@ -48,6 +80,31 @@
|
|
|
48
80
|
letter-spacing: 0.04em;
|
|
49
81
|
color: #565f89;
|
|
50
82
|
}
|
|
83
|
+
/* One rule per theme: the splash paints before styles.css exists, so it
|
|
84
|
+
cannot use the tokens. Keep in step with the :root blocks there. */
|
|
85
|
+
:root[data-theme='tokyo-day'] #boot-splash { background: #e9eaef; }
|
|
86
|
+
:root[data-theme='tokyo-day'] .boot-spinner {
|
|
87
|
+
border-color: rgba(46, 125, 233, 0.22);
|
|
88
|
+
border-top-color: #2e7de9;
|
|
89
|
+
}
|
|
90
|
+
:root[data-theme='tokyo-day'] .boot-splash-title { color: #1b1e33; }
|
|
91
|
+
:root[data-theme='tokyo-day'] .boot-splash-label { color: #6b7190; }
|
|
92
|
+
|
|
93
|
+
:root[data-theme='catppuccin-mocha'] #boot-splash { background: #1e1e2e; }
|
|
94
|
+
:root[data-theme='catppuccin-mocha'] .boot-spinner {
|
|
95
|
+
border-color: rgba(137, 180, 250, 0.22);
|
|
96
|
+
border-top-color: #89b4fa;
|
|
97
|
+
}
|
|
98
|
+
:root[data-theme='catppuccin-mocha'] .boot-splash-title { color: #cdd6f4; }
|
|
99
|
+
:root[data-theme='catppuccin-mocha'] .boot-splash-label { color: #7f849c; }
|
|
100
|
+
|
|
101
|
+
:root[data-theme='catppuccin-latte'] #boot-splash { background: #eff1f5; }
|
|
102
|
+
:root[data-theme='catppuccin-latte'] .boot-spinner {
|
|
103
|
+
border-color: rgba(30, 102, 245, 0.22);
|
|
104
|
+
border-top-color: #1e66f5;
|
|
105
|
+
}
|
|
106
|
+
:root[data-theme='catppuccin-latte'] .boot-splash-title { color: #2f3145; }
|
|
107
|
+
:root[data-theme='catppuccin-latte'] .boot-splash-label { color: #7c7f93; }
|
|
51
108
|
</style>
|
|
52
109
|
</head>
|
|
53
110
|
<body>
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_DARK, DEFAULT_LIGHT, getTheme, isThemeId, type ThemeDef,
|
|
4
|
+
} from './themes'
|
|
5
|
+
|
|
6
|
+
// Appearance state.
|
|
7
|
+
//
|
|
8
|
+
// `sync` follows the OS and picks between the `light` / `dark` choices as it
|
|
9
|
+
// flips; with sync off, `theme` is pinned. Keeping the pair around while pinned
|
|
10
|
+
// means turning sync back on restores what you had rather than resetting.
|
|
11
|
+
//
|
|
12
|
+
// Stored in localStorage alongside the other renderer-local UI prefs
|
|
13
|
+
// (ctlsurf.floatingMicVisible, ctlsurf.voice.*) rather than in the main-process
|
|
14
|
+
// settings file — index.html has to read it synchronously before first paint,
|
|
15
|
+
// which IPC cannot do. The main process keeps a mirror for recovery; see below.
|
|
16
|
+
export interface Appearance {
|
|
17
|
+
sync: boolean
|
|
18
|
+
theme: string
|
|
19
|
+
light: string
|
|
20
|
+
dark: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const KEY = 'ctlsurf.appearance'
|
|
24
|
+
const LEGACY_KEY = 'ctlsurf.theme'
|
|
25
|
+
|
|
26
|
+
export const DEFAULT_APPEARANCE: Appearance = {
|
|
27
|
+
sync: true,
|
|
28
|
+
theme: DEFAULT_DARK,
|
|
29
|
+
light: DEFAULT_LIGHT,
|
|
30
|
+
dark: DEFAULT_DARK,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function coerce(raw: unknown): Appearance | null {
|
|
34
|
+
if (!raw || typeof raw !== 'object') return null
|
|
35
|
+
const o = raw as Record<string, unknown>
|
|
36
|
+
// An unknown id (a theme removed in a later version) falls back rather than
|
|
37
|
+
// leaving the app with no palette at all.
|
|
38
|
+
return {
|
|
39
|
+
sync: o.sync !== false,
|
|
40
|
+
theme: isThemeId(o.theme) ? o.theme : DEFAULT_APPEARANCE.theme,
|
|
41
|
+
light: isThemeId(o.light) ? o.light : DEFAULT_APPEARANCE.light,
|
|
42
|
+
dark: isThemeId(o.dark) ? o.dark : DEFAULT_APPEARANCE.dark,
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// null means "nothing stored", distinct from an explicit default — only the
|
|
47
|
+
// former may be replaced by the main-process mirror on boot.
|
|
48
|
+
function stored(): Appearance | null {
|
|
49
|
+
try {
|
|
50
|
+
const raw = localStorage.getItem(KEY)
|
|
51
|
+
if (raw) return coerce(JSON.parse(raw))
|
|
52
|
+
// Migrate the original system/light/dark preference.
|
|
53
|
+
const legacy = localStorage.getItem(LEGACY_KEY)
|
|
54
|
+
if (legacy === 'light') return { ...DEFAULT_APPEARANCE, sync: false, theme: DEFAULT_LIGHT }
|
|
55
|
+
if (legacy === 'dark') return { ...DEFAULT_APPEARANCE, sync: false, theme: DEFAULT_DARK }
|
|
56
|
+
if (legacy === 'system') return { ...DEFAULT_APPEARANCE }
|
|
57
|
+
} catch { /* private mode, or corrupt JSON */ }
|
|
58
|
+
return null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function getAppearance(): Appearance {
|
|
62
|
+
return stored() ?? { ...DEFAULT_APPEARANCE }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const darkQuery = (): MediaQueryList | null =>
|
|
66
|
+
typeof window.matchMedia === 'function'
|
|
67
|
+
? window.matchMedia('(prefers-color-scheme: dark)')
|
|
68
|
+
: null
|
|
69
|
+
|
|
70
|
+
export function prefersDark(): boolean {
|
|
71
|
+
return darkQuery()?.matches !== false
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function resolveThemeId(a: Appearance = getAppearance()): string {
|
|
75
|
+
if (!a.sync) return a.theme
|
|
76
|
+
return prefersDark() ? a.dark : a.light
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Subscribers get the resolved ThemeDef, so none of them has to redo the
|
|
80
|
+
// system-preference dance. Module-level: terminals outlive the components that
|
|
81
|
+
// render them (see the _terminals cache in TerminalPanel), so the xterm palette
|
|
82
|
+
// listener must not be tied to a component lifecycle.
|
|
83
|
+
type Listener = (theme: ThemeDef) => void
|
|
84
|
+
const listeners = new Set<Listener>()
|
|
85
|
+
|
|
86
|
+
export function subscribeTheme(fn: Listener): () => void {
|
|
87
|
+
listeners.add(fn)
|
|
88
|
+
return () => { listeners.delete(fn) }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function currentTheme(): ThemeDef {
|
|
92
|
+
return getTheme(resolveThemeId())
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function applyDom(): ThemeDef {
|
|
96
|
+
const theme = currentTheme()
|
|
97
|
+
document.documentElement.setAttribute('data-theme', theme.id)
|
|
98
|
+
for (const fn of listeners) fn(theme)
|
|
99
|
+
return theme
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Keep the native layer in step: form controls, scrollbars and the background
|
|
103
|
+
// Electron paints before the renderer loads are Chromium's, not our CSS. The
|
|
104
|
+
// main process also persists this, which is what makes the choice recoverable.
|
|
105
|
+
function syncNative(a: Appearance): void {
|
|
106
|
+
const theme = getTheme(resolveThemeId(a))
|
|
107
|
+
void window.worker?.setAppearance?.({
|
|
108
|
+
pref: a,
|
|
109
|
+
source: a.sync ? 'system' : theme.mode,
|
|
110
|
+
background: theme.background,
|
|
111
|
+
}).catch(() => { /* older main process */ })
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function setAppearance(next: Appearance): ThemeDef {
|
|
115
|
+
try { localStorage.setItem(KEY, JSON.stringify(next)) } catch { /* ignore */ }
|
|
116
|
+
syncNative(next)
|
|
117
|
+
return applyDom()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function updateAppearance(patch: Partial<Appearance>): ThemeDef {
|
|
121
|
+
return setAppearance({ ...getAppearance(), ...patch })
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Re-resolve when the OS flips appearance, but only while syncing — a pinned
|
|
125
|
+
// theme should survive the OS changing under it.
|
|
126
|
+
darkQuery()?.addEventListener('change', () => {
|
|
127
|
+
const a = getAppearance()
|
|
128
|
+
if (a.sync) { applyDom(); syncNative(a) }
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
// index.html stamps data-theme before first paint so there is no flash; this
|
|
132
|
+
// re-runs it to pick up anything that changed since (and to seed listeners).
|
|
133
|
+
applyDom()
|
|
134
|
+
|
|
135
|
+
// localStorage is not as durable as it looks: a reset Chromium quota database,
|
|
136
|
+
// or two Electron instances sharing one --user-data-dir, silently empties it,
|
|
137
|
+
// which would reset the choice. The main process mirrors it into settings.json,
|
|
138
|
+
// so recover from there.
|
|
139
|
+
//
|
|
140
|
+
// Order matters — this reads before anything writes, which is why boot does not
|
|
141
|
+
// call syncNative() on this branch. Guarded on "nothing stored" at both ends so
|
|
142
|
+
// a real choice can never be clobbered by a late-arriving reply.
|
|
143
|
+
if (stored() === null) {
|
|
144
|
+
const restore = window.worker?.getAppearance?.()
|
|
145
|
+
if (restore) {
|
|
146
|
+
void restore
|
|
147
|
+
.then((r) => {
|
|
148
|
+
const recovered = coerce(r?.pref)
|
|
149
|
+
if (recovered && stored() === null) setAppearance(recovered)
|
|
150
|
+
})
|
|
151
|
+
.catch(() => { /* older main process */ })
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
syncNative(getAppearance())
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function useAppearance(): {
|
|
158
|
+
appearance: Appearance
|
|
159
|
+
theme: ThemeDef
|
|
160
|
+
update: (patch: Partial<Appearance>) => void
|
|
161
|
+
} {
|
|
162
|
+
const [appearance, setState] = useState<Appearance>(getAppearance)
|
|
163
|
+
const [theme, setTheme] = useState<ThemeDef>(currentTheme)
|
|
164
|
+
|
|
165
|
+
// Read state back off the store rather than tracking it locally, so a change
|
|
166
|
+
// made in one component reaches every other consumer.
|
|
167
|
+
useEffect(() => subscribeTheme((t) => {
|
|
168
|
+
setTheme(t)
|
|
169
|
+
setState(getAppearance())
|
|
170
|
+
}), [])
|
|
171
|
+
|
|
172
|
+
return { appearance, theme, update: updateAppearance }
|
|
173
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// The theme registry.
|
|
2
|
+
//
|
|
3
|
+
// Palette values are transcribed from each project's published palette spec —
|
|
4
|
+
// no upstream code is copied. Scheme names identify which palette a theme
|
|
5
|
+
// reproduces; see THIRD-PARTY-NOTICES.md for licences and attribution.
|
|
6
|
+
//
|
|
7
|
+
// Each entry owns everything that can't be reached from CSS: the xterm palette
|
|
8
|
+
// (canvas, not DOM), the Monaco override colours, and the swatch shown in the
|
|
9
|
+
// picker. The chrome colours themselves live in styles.css, under a
|
|
10
|
+
// `:root[data-theme='<id>']` block keyed by the ids below — the two must stay
|
|
11
|
+
// in step, which is why `id` is the single source of truth for both.
|
|
12
|
+
|
|
13
|
+
export type ThemeMode = 'light' | 'dark'
|
|
14
|
+
|
|
15
|
+
// Structural subset of xterm's ITheme. Declared locally so this module stays
|
|
16
|
+
// importable from places that don't depend on xterm (the Settings dialog).
|
|
17
|
+
export interface XtermPalette {
|
|
18
|
+
background: string
|
|
19
|
+
foreground: string
|
|
20
|
+
cursor: string
|
|
21
|
+
selectionBackground: string
|
|
22
|
+
black: string; red: string; green: string; yellow: string
|
|
23
|
+
blue: string; magenta: string; cyan: string; white: string
|
|
24
|
+
brightBlack: string; brightRed: string; brightGreen: string; brightYellow: string
|
|
25
|
+
brightBlue: string; brightMagenta: string; brightCyan: string; brightWhite: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ThemeDef {
|
|
29
|
+
id: string
|
|
30
|
+
name: string
|
|
31
|
+
family: string
|
|
32
|
+
mode: ThemeMode
|
|
33
|
+
// Preview tile in Settings — drawn from literal values, because a swatch has
|
|
34
|
+
// to show its own palette rather than the active one.
|
|
35
|
+
swatch: { bg: string; surface: string; accent: string; text: string }
|
|
36
|
+
// Painted by Electron before the renderer loads, so it must match --bg-app.
|
|
37
|
+
background: string
|
|
38
|
+
terminal: XtermPalette
|
|
39
|
+
// Monaco ships only 'vs' / 'vs-dark'; these ride on top so the editor at
|
|
40
|
+
// least shares the scheme's background and gutter rather than clashing.
|
|
41
|
+
editor: { base: 'vs' | 'vs-dark'; background: string; foreground: string; lineHighlight: string; selection: string }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const THEMES: ThemeDef[] = [
|
|
45
|
+
{
|
|
46
|
+
id: 'tokyo-night',
|
|
47
|
+
name: 'Tokyo Night',
|
|
48
|
+
family: 'Tokyo Night',
|
|
49
|
+
mode: 'dark',
|
|
50
|
+
swatch: { bg: '#1a1b26', surface: '#2a2b3d', accent: '#7aa2f7', text: '#a9b1d6' },
|
|
51
|
+
background: '#1a1b26',
|
|
52
|
+
terminal: {
|
|
53
|
+
background: '#1a1b26', foreground: '#a9b1d6', cursor: '#c0caf5', selectionBackground: '#33467c',
|
|
54
|
+
black: '#15161e', red: '#f7768e', green: '#9ece6a', yellow: '#e0af68',
|
|
55
|
+
blue: '#7aa2f7', magenta: '#bb9af7', cyan: '#7dcfff', white: '#a9b1d6',
|
|
56
|
+
brightBlack: '#414868', brightRed: '#f7768e', brightGreen: '#9ece6a', brightYellow: '#e0af68',
|
|
57
|
+
brightBlue: '#7aa2f7', brightMagenta: '#bb9af7', brightCyan: '#7dcfff', brightWhite: '#c0caf5',
|
|
58
|
+
},
|
|
59
|
+
editor: { base: 'vs-dark', background: '#1a1b26', foreground: '#a9b1d6', lineHighlight: '#1f2335', selection: '#33467c' },
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: 'tokyo-day',
|
|
63
|
+
name: 'Tokyo Night Day',
|
|
64
|
+
family: 'Tokyo Night',
|
|
65
|
+
mode: 'light',
|
|
66
|
+
swatch: { bg: '#e9eaef', surface: '#d4d7e2', accent: '#2e7de9', text: '#3a4060' },
|
|
67
|
+
background: '#e9eaef',
|
|
68
|
+
terminal: {
|
|
69
|
+
background: '#e9eaef', foreground: '#3760bf', cursor: '#3760bf', selectionBackground: '#b6c8f0',
|
|
70
|
+
black: '#b4b5b9', red: '#c64343', green: '#587539', yellow: '#8c6c3e',
|
|
71
|
+
blue: '#2e7de9', magenta: '#9854f1', cyan: '#007197', white: '#6172b0',
|
|
72
|
+
// Deliberately darker than `black`: remote TUIs use bright-black for dim
|
|
73
|
+
// text, which would vanish on a light background otherwise.
|
|
74
|
+
brightBlack: '#8990ad', brightRed: '#f52a65', brightGreen: '#4d7a1f', brightYellow: '#8f5e15',
|
|
75
|
+
brightBlue: '#1a68d6', brightMagenta: '#7847d1', brightCyan: '#006594', brightWhite: '#3760bf',
|
|
76
|
+
},
|
|
77
|
+
editor: { base: 'vs', background: '#e9eaef', foreground: '#3760bf', lineHighlight: '#dfe1e8', selection: '#b6c8f0' },
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: 'catppuccin-mocha',
|
|
81
|
+
name: 'Catppuccin Mocha',
|
|
82
|
+
family: 'Catppuccin',
|
|
83
|
+
mode: 'dark',
|
|
84
|
+
swatch: { bg: '#1e1e2e', surface: '#313244', accent: '#89b4fa', text: '#cdd6f4' },
|
|
85
|
+
background: '#1e1e2e',
|
|
86
|
+
terminal: {
|
|
87
|
+
background: '#1e1e2e', foreground: '#cdd6f4', cursor: '#f5e0dc', selectionBackground: '#45475a',
|
|
88
|
+
black: '#45475a', red: '#f38ba8', green: '#a6e3a1', yellow: '#f9e2af',
|
|
89
|
+
blue: '#89b4fa', magenta: '#f5c2e7', cyan: '#94e2d5', white: '#bac2de',
|
|
90
|
+
brightBlack: '#585b70', brightRed: '#f38ba8', brightGreen: '#a6e3a1', brightYellow: '#f9e2af',
|
|
91
|
+
brightBlue: '#89b4fa', brightMagenta: '#f5c2e7', brightCyan: '#94e2d5', brightWhite: '#a6adc8',
|
|
92
|
+
},
|
|
93
|
+
editor: { base: 'vs-dark', background: '#1e1e2e', foreground: '#cdd6f4', lineHighlight: '#272739', selection: '#45475a' },
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: 'catppuccin-latte',
|
|
97
|
+
name: 'Catppuccin Latte',
|
|
98
|
+
family: 'Catppuccin',
|
|
99
|
+
mode: 'light',
|
|
100
|
+
swatch: { bg: '#eff1f5', surface: '#ccd0da', accent: '#1e66f5', text: '#4c4f69' },
|
|
101
|
+
background: '#eff1f5',
|
|
102
|
+
terminal: {
|
|
103
|
+
background: '#eff1f5', foreground: '#4c4f69', cursor: '#dc8a78', selectionBackground: '#bcc0cc',
|
|
104
|
+
black: '#5c5f77', red: '#d20f39', green: '#40a02b', yellow: '#df8e1d',
|
|
105
|
+
blue: '#1e66f5', magenta: '#ea76cb', cyan: '#179299', white: '#acb0be',
|
|
106
|
+
brightBlack: '#6c6f85', brightRed: '#de293e', brightGreen: '#49af3d', brightYellow: '#eea416',
|
|
107
|
+
brightBlue: '#456eff', brightMagenta: '#dd63bd', brightCyan: '#2d9fa8', brightWhite: '#8c8fa1',
|
|
108
|
+
},
|
|
109
|
+
editor: { base: 'vs', background: '#eff1f5', foreground: '#4c4f69', lineHighlight: '#e6e9ef', selection: '#bcc0cc' },
|
|
110
|
+
},
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
export const DEFAULT_DARK = 'tokyo-night'
|
|
114
|
+
export const DEFAULT_LIGHT = 'tokyo-day'
|
|
115
|
+
|
|
116
|
+
const byId = new Map(THEMES.map(t => [t.id, t]))
|
|
117
|
+
|
|
118
|
+
export function getTheme(id: string | null | undefined): ThemeDef {
|
|
119
|
+
return (id && byId.get(id)) || byId.get(DEFAULT_DARK)!
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function isThemeId(v: unknown): v is string {
|
|
123
|
+
return typeof v === 'string' && byId.has(v)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export const themesByMode = (mode: ThemeMode): ThemeDef[] => THEMES.filter(t => t.mode === mode)
|