hangwin--mcp-chrome
172 行
4.2 KiB
TypeScript
172 行
4.2 KiB
TypeScript
/**
|
|
* Composable for managing AgentChat theme.
|
|
* Handles theme persistence and application.
|
|
*/
|
|
import { ref, type Ref } from 'vue';
|
|
|
|
/** Available theme identifiers */
|
|
export type AgentThemeId =
|
|
| 'warm-editorial'
|
|
| 'blueprint-architect'
|
|
| 'zen-journal'
|
|
| 'neo-pop'
|
|
| 'dark-console'
|
|
| 'swiss-grid';
|
|
|
|
/** Storage key for persisting theme preference */
|
|
const STORAGE_KEY_THEME = 'agentTheme';
|
|
|
|
/** Default theme when none is set */
|
|
const DEFAULT_THEME: AgentThemeId = 'warm-editorial';
|
|
|
|
/** Valid theme IDs for validation */
|
|
const VALID_THEMES: AgentThemeId[] = [
|
|
'warm-editorial',
|
|
'blueprint-architect',
|
|
'zen-journal',
|
|
'neo-pop',
|
|
'dark-console',
|
|
'swiss-grid',
|
|
];
|
|
|
|
/** Theme display names for UI */
|
|
export const THEME_LABELS: Record<AgentThemeId, string> = {
|
|
'warm-editorial': 'Editorial',
|
|
'blueprint-architect': 'Blueprint',
|
|
'zen-journal': 'Zen',
|
|
'neo-pop': 'Neo-Pop',
|
|
'dark-console': 'Console',
|
|
'swiss-grid': 'Swiss',
|
|
};
|
|
|
|
export interface UseAgentTheme {
|
|
/** Current theme ID */
|
|
theme: Ref<AgentThemeId>;
|
|
/** Whether theme has been loaded from storage */
|
|
ready: Ref<boolean>;
|
|
/** Set and persist a new theme */
|
|
setTheme: (id: AgentThemeId) => Promise<void>;
|
|
/** Load theme from storage (call on mount) */
|
|
initTheme: () => Promise<void>;
|
|
/** Apply theme to a DOM element */
|
|
applyTo: (el: HTMLElement) => void;
|
|
/** Get the preloaded theme from document (set by main.ts) */
|
|
getPreloadedTheme: () => AgentThemeId;
|
|
}
|
|
|
|
/**
|
|
* Check if a string is a valid theme ID
|
|
*/
|
|
function isValidTheme(value: unknown): value is AgentThemeId {
|
|
return typeof value === 'string' && VALID_THEMES.includes(value as AgentThemeId);
|
|
}
|
|
|
|
/**
|
|
* Get theme from document element (preloaded by main.ts)
|
|
*/
|
|
function getThemeFromDocument(): AgentThemeId {
|
|
const value = document.documentElement.dataset.agentTheme;
|
|
return isValidTheme(value) ? value : DEFAULT_THEME;
|
|
}
|
|
|
|
/**
|
|
* Composable for managing AgentChat theme
|
|
*/
|
|
export function useAgentTheme(): UseAgentTheme {
|
|
// Initialize with preloaded theme (or default)
|
|
const theme = ref<AgentThemeId>(getThemeFromDocument());
|
|
const ready = ref(false);
|
|
|
|
/**
|
|
* Load theme from chrome.storage.local
|
|
*/
|
|
async function initTheme(): Promise<void> {
|
|
try {
|
|
const result = await chrome.storage.local.get(STORAGE_KEY_THEME);
|
|
const stored = result[STORAGE_KEY_THEME];
|
|
|
|
if (isValidTheme(stored)) {
|
|
theme.value = stored;
|
|
} else {
|
|
// Use preloaded or default
|
|
theme.value = getThemeFromDocument();
|
|
}
|
|
} catch (error) {
|
|
console.error('[useAgentTheme] Failed to load theme:', error);
|
|
theme.value = getThemeFromDocument();
|
|
} finally {
|
|
ready.value = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set and persist a new theme
|
|
*/
|
|
async function setTheme(id: AgentThemeId): Promise<void> {
|
|
if (!isValidTheme(id)) {
|
|
console.warn('[useAgentTheme] Invalid theme ID:', id);
|
|
return;
|
|
}
|
|
|
|
// Update immediately for responsive UI
|
|
theme.value = id;
|
|
|
|
// Also update document element for consistency
|
|
document.documentElement.dataset.agentTheme = id;
|
|
|
|
// Persist to storage
|
|
try {
|
|
await chrome.storage.local.set({ [STORAGE_KEY_THEME]: id });
|
|
} catch (error) {
|
|
console.error('[useAgentTheme] Failed to save theme:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Apply theme to a DOM element
|
|
*/
|
|
function applyTo(el: HTMLElement): void {
|
|
el.dataset.agentTheme = theme.value;
|
|
}
|
|
|
|
/**
|
|
* Get the preloaded theme from document
|
|
*/
|
|
function getPreloadedTheme(): AgentThemeId {
|
|
return getThemeFromDocument();
|
|
}
|
|
|
|
return {
|
|
theme,
|
|
ready,
|
|
setTheme,
|
|
initTheme,
|
|
applyTo,
|
|
getPreloadedTheme,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Preload theme before Vue mounts (call in main.ts)
|
|
* This prevents theme flashing on page load.
|
|
*/
|
|
export async function preloadAgentTheme(): Promise<AgentThemeId> {
|
|
let themeId: AgentThemeId = DEFAULT_THEME;
|
|
|
|
try {
|
|
const result = await chrome.storage.local.get(STORAGE_KEY_THEME);
|
|
const stored = result[STORAGE_KEY_THEME];
|
|
|
|
if (isValidTheme(stored)) {
|
|
themeId = stored;
|
|
}
|
|
} catch (error) {
|
|
console.error('[preloadAgentTheme] Failed to load theme:', error);
|
|
}
|
|
|
|
// Set on document element for immediate application
|
|
document.documentElement.dataset.agentTheme = themeId;
|
|
|
|
return themeId;
|
|
}
|