Files
web-terminal/public/settings.ts
Yaojia Wang 67b0a43b39 polish(v0.6): replace toolbar emoji with themed line icons + hover tooltips
The glossy emoji (🔍⚙▦🕘🔗📱) clashed with the Amber dark theme. Swap them
for clean lucide line icons (MIT, stroke=currentColor) so they take the theme
colour — muted by default, Amber on hover — and add a styled CSS hover tooltip
(data-tip, right-anchored so it never clips the edge) showing what each does.

- public/icons.ts: 7 line icons (search/settings/dashboard/history/keyboard/
  share/device), script-generated from lucide
- 7 toolbar modules: innerHTML = ICON_* + data-tip + aria-label
- style.css .toolbtn: 18px svg, --text-dim → --accent on hover, [data-tip] tooltip

Frontend-only. web typecheck + build clean; bundle has 0 toolbar emoji; verified
in-browser: line icons in theme colour, amber + tooltip on hover.
2026-06-30 14:36:26 +02:00

136 lines
3.7 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'
import { ICON_SETTINGS } from './icons.js'
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.innerHTML = ICON_SETTINGS
toggle.dataset.tip = 'Settings'
toggle.setAttribute('aria-label', 'Settings')
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)
}