Files
web-terminal/public/settings.ts
Yaojia Wang 524f2073a0 style: modern dark UI refresh
- design-token palette (:root vars): deeper #0e0f13 base, indigo #7c8cff accent,
  layered surfaces, softer borders, shadow + radius scale
- tabs as floating rounded chips (active = elevated surface); refined dots
- key bar as rounded chips with gaps; Esc gets the accent tint
- toolbar icon buttons with rounded hover
- overlays (history/dashboard/qr/search/settings) get backdrop-blur + card +
  shadow; indigo primary buttons; consistent inputs with accent focus ring
- chrome uses a system UI font (terminal stays monospace)
- xterm dark theme + meta theme-color + manifest + app icon synced to the
  palette (indigo-gradient icon, accent cursor)
216 tests green; tsc + build clean.
2026-06-18 09:08:02 +02:00

134 lines
3.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* public/settings.ts — theme + font-size settings (M3).
*
* A ⚙ toolbar button opens a small panel; choices persist to localStorage and
* apply to every terminal via the onChange callback.
*/
import type { ITheme } from '@xterm/xterm'
export interface Settings {
theme: string
fontSize: number
}
export const THEMES: Record<string, ITheme> = {
dark: {
background: '#0e0f13',
foreground: '#e7e8ec',
cursor: '#7c8cff',
selectionBackground: 'rgba(124,140,255,0.28)',
},
light: {
background: '#f6f7f9',
foreground: '#1a1d24',
cursor: '#5b6cff',
selectionBackground: '#cdd6ff',
},
solarized: { background: '#002b36', foreground: '#93a1a1', cursor: '#268bd2' },
}
const KEY = 'web-terminal:settings'
export const DEFAULT_SETTINGS: Settings = { theme: 'dark', fontSize: 14 }
export function loadSettings(): Settings {
try {
const raw = localStorage.getItem(KEY)
if (raw) {
const s = JSON.parse(raw) as Partial<Settings>
return {
theme: typeof s.theme === 'string' && THEMES[s.theme] ? s.theme : DEFAULT_SETTINGS.theme,
fontSize:
typeof s.fontSize === 'number' && s.fontSize >= 10 && s.fontSize <= 24
? s.fontSize
: DEFAULT_SETTINGS.fontSize,
}
}
} catch {
// ignore
}
return { ...DEFAULT_SETTINGS }
}
function saveSettings(s: Settings): void {
try {
localStorage.setItem(KEY, JSON.stringify(s))
} catch {
// ignore
}
}
export function mountSettings(
toolbar: HTMLElement,
current: () => Settings,
onChange: (s: Settings) => void,
): void {
const panel = document.createElement('div')
panel.id = 'settingspanel'
panel.style.display = 'none'
const apply = (next: Settings): void => {
saveSettings(next)
onChange(next)
render()
}
function render(): void {
const s = current()
panel.replaceChildren()
const themeRow = document.createElement('div')
themeRow.className = 'settings-row'
themeRow.append(label('Theme'))
for (const key of Object.keys(THEMES)) {
const b = document.createElement('button')
b.textContent = key
b.className = key === s.theme ? 'settings-opt active' : 'settings-opt'
b.addEventListener('click', () => apply({ ...s, theme: key }))
themeRow.appendChild(b)
}
const fontRow = document.createElement('div')
fontRow.className = 'settings-row'
fontRow.append(label('Font'))
const minus = document.createElement('button')
minus.className = 'settings-opt'
minus.textContent = 'A'
minus.addEventListener('click', () => apply({ ...s, fontSize: Math.max(10, s.fontSize - 1) }))
const size = document.createElement('span')
size.className = 'settings-size'
size.textContent = String(s.fontSize)
const plus = document.createElement('button')
plus.className = 'settings-opt'
plus.textContent = 'A+'
plus.addEventListener('click', () => apply({ ...s, fontSize: Math.min(24, s.fontSize + 1) }))
fontRow.append(minus, size, plus)
panel.append(themeRow, fontRow)
}
function label(text: string): HTMLElement {
const el = document.createElement('span')
el.className = 'settings-label'
el.textContent = text
return el
}
document.body.appendChild(panel)
const toggle = document.createElement('button')
toggle.className = 'toolbtn'
toggle.textContent = '⚙'
toggle.title = 'Settings (theme, font)'
toggle.setAttribute('aria-label', 'Settings')
toggle.addEventListener('click', () => {
if (panel.style.display === 'none') {
render()
panel.style.display = 'block'
} else {
panel.style.display = 'none'
}
})
toolbar.appendChild(toggle)
}