feat: v0.6.0 light mode, command palette, dialogs, toasts
- Light mode with the house ☀/◐ toggle and lt_theme preference; light terminal palette; Claude runs with dark-ansi so its colours follow the palette; tmux and the statusLine use ANSI colours. - Command palette (⌕, Ctrl+Shift+P) with session, terminal, view, link and help commands. - .lt-modal dialogs for new/rename/end and a keyboard shortcuts sheet. - .lt-toast results for actions, uploads, copy and CI. - ttyd dropped the reply of fast one-shot commands; replies now wait for the browser to hang up, and the client hangs up as soon as it has one. - Sessions panel re-fetches after an action instead of reusing a stale poll. Closes #31 Closes #32 Closes #33 Closes #34 Closes #35 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
// Dark / light theme, interchangeable with web_template's lt.theme (devdesk#31):
|
||||
// same localStorage key (lt_theme), same data-theme attribute on <html>, the OS
|
||||
// preference while nothing is saved, and tabs follow each other via `storage`.
|
||||
// The inline script in index.template.html applies it before first paint.
|
||||
|
||||
const KEY = 'lt_theme';
|
||||
const prefersLight = window.matchMedia('(prefers-color-scheme: light)');
|
||||
const listeners = new Set();
|
||||
|
||||
function saved() {
|
||||
try {
|
||||
const value = localStorage.getItem(KEY);
|
||||
return value === 'light' || value === 'dark' ? value : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function getTheme() {
|
||||
return document.documentElement.getAttribute('data-theme') === 'light' ? 'light' : 'dark';
|
||||
}
|
||||
|
||||
function apply(theme) {
|
||||
const root = document.documentElement;
|
||||
root.setAttribute('data-theme', theme);
|
||||
root.style.colorScheme = theme;
|
||||
document.querySelector('meta[name="theme-color"]')?.setAttribute('content', theme === 'light' ? '#edf0f5' : '#030508');
|
||||
const next = theme === 'light' ? 'dark' : 'light';
|
||||
for (const button of document.querySelectorAll('.lt-theme-btn')) {
|
||||
button.textContent = theme === 'light' ? '◐' : '☀';
|
||||
button.setAttribute('aria-label', `Switch to ${next} mode`);
|
||||
button.title = `Switch to ${next} mode`;
|
||||
}
|
||||
for (const listener of listeners) listener(theme);
|
||||
}
|
||||
|
||||
export function setTheme(theme) {
|
||||
try {
|
||||
localStorage.setItem(KEY, theme);
|
||||
} catch {
|
||||
// Private mode: the choice lasts until the tab closes.
|
||||
}
|
||||
apply(theme);
|
||||
}
|
||||
|
||||
export function toggleTheme() {
|
||||
setTheme(getTheme() === 'light' ? 'dark' : 'light');
|
||||
}
|
||||
|
||||
export function onThemeChange(listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
export function initTheme() {
|
||||
apply(saved() ?? (prefersLight.matches ? 'light' : 'dark'));
|
||||
prefersLight.addEventListener('change', (event) => {
|
||||
if (!saved()) apply(event.matches ? 'light' : 'dark');
|
||||
});
|
||||
window.addEventListener('storage', (event) => {
|
||||
if (event.key === KEY) apply(saved() ?? (prefersLight.matches ? 'light' : 'dark'));
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user