will be reworking in just a moment
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { createSignal } from "solid-js"
|
||||
import { useKeyboard } from "@opentui/solid"
|
||||
import { useAppStore } from "../stores/app"
|
||||
import { createColorResolver } from "../utils/color"
|
||||
import type { ThemeName } from "../types/settings"
|
||||
|
||||
type FocusField = "theme" | "font" | "speed" | "explicit" | "auto"
|
||||
@@ -21,6 +22,8 @@ export function PreferencesPanel() {
|
||||
const settings = () => appStore.state().settings
|
||||
const preferences = () => appStore.state().preferences
|
||||
|
||||
const resolveColor = createColorResolver(appStore.resolveTheme())
|
||||
|
||||
const handleKey = (key: { name: string; shift?: boolean }) => {
|
||||
if (key.name === "tab") {
|
||||
const fields: FocusField[] = ["theme", "font", "speed", "explicit", "auto"]
|
||||
@@ -76,55 +79,55 @@ export function PreferencesPanel() {
|
||||
|
||||
return (
|
||||
<box flexDirection="column" gap={1}>
|
||||
<text fg="var(--color-muted)">Preferences</text>
|
||||
<text fg={resolveColor("--color-muted")}>Preferences</text>
|
||||
|
||||
<box flexDirection="column" gap={1}>
|
||||
<box flexDirection="row" gap={1} alignItems="center">
|
||||
<text fg={focusField() === "theme" ? "var(--color-primary)" : "var(--color-muted)"}>Theme:</text>
|
||||
<text fg={focusField() === "theme" ? resolveColor("--color-primary") : resolveColor("--color-muted")}>Theme:</text>
|
||||
<box border padding={0}>
|
||||
<text fg="var(--color-text)">{THEME_LABELS.find((t) => t.value === settings().theme)?.label}</text>
|
||||
<text fg={resolveColor("--color-text")}>{THEME_LABELS.find((t) => t.value === settings().theme)?.label}</text>
|
||||
</box>
|
||||
<text fg="var(--color-muted)">[Left/Right]</text>
|
||||
<text fg={resolveColor("--color-muted")}>[Left/Right]</text>
|
||||
</box>
|
||||
|
||||
<box flexDirection="row" gap={1} alignItems="center">
|
||||
<text fg={focusField() === "font" ? "var(--color-primary)" : "var(--color-muted)"}>Font Size:</text>
|
||||
<text fg={focusField() === "font" ? resolveColor("--color-primary") : resolveColor("--color-muted")}>Font Size:</text>
|
||||
<box border padding={0}>
|
||||
<text fg="var(--color-text)">{settings().fontSize}px</text>
|
||||
<text fg={resolveColor("--color-text")}>{settings().fontSize}px</text>
|
||||
</box>
|
||||
<text fg="var(--color-muted)">[Left/Right]</text>
|
||||
<text fg={resolveColor("--color-muted")}>[Left/Right]</text>
|
||||
</box>
|
||||
|
||||
<box flexDirection="row" gap={1} alignItems="center">
|
||||
<text fg={focusField() === "speed" ? "var(--color-primary)" : "var(--color-muted)"}>Playback:</text>
|
||||
<text fg={focusField() === "speed" ? resolveColor("--color-primary") : resolveColor("--color-muted")}>Playback:</text>
|
||||
<box border padding={0}>
|
||||
<text fg="var(--color-text)">{settings().playbackSpeed}x</text>
|
||||
<text fg={resolveColor("--color-text")}>{settings().playbackSpeed}x</text>
|
||||
</box>
|
||||
<text fg="var(--color-muted)">[Left/Right]</text>
|
||||
<text fg={resolveColor("--color-muted")}>[Left/Right]</text>
|
||||
</box>
|
||||
|
||||
<box flexDirection="row" gap={1} alignItems="center">
|
||||
<text fg={focusField() === "explicit" ? "var(--color-primary)" : "var(--color-muted)"}>Show Explicit:</text>
|
||||
<text fg={focusField() === "explicit" ? resolveColor("--color-primary") : resolveColor("--color-muted")}>Show Explicit:</text>
|
||||
<box border padding={0}>
|
||||
<text fg={preferences().showExplicit ? "var(--color-success)" : "var(--color-muted)"}>
|
||||
<text fg={preferences().showExplicit ? resolveColor("--color-success") : resolveColor("--color-muted")}>
|
||||
{preferences().showExplicit ? "On" : "Off"}
|
||||
</text>
|
||||
</box>
|
||||
<text fg="var(--color-muted)">[Space]</text>
|
||||
<text fg={resolveColor("--color-muted")}>[Space]</text>
|
||||
</box>
|
||||
|
||||
<box flexDirection="row" gap={1} alignItems="center">
|
||||
<text fg={focusField() === "auto" ? "var(--color-primary)" : "var(--color-muted)"}>Auto Download:</text>
|
||||
<text fg={focusField() === "auto" ? resolveColor("--color-primary") : resolveColor("--color-muted")}>Auto Download:</text>
|
||||
<box border padding={0}>
|
||||
<text fg={preferences().autoDownload ? "var(--color-success)" : "var(--color-muted)"}>
|
||||
<text fg={preferences().autoDownload ? resolveColor("--color-success") : resolveColor("--color-muted")}>
|
||||
{preferences().autoDownload ? "On" : "Off"}
|
||||
</text>
|
||||
</box>
|
||||
<text fg="var(--color-muted)">[Space]</text>
|
||||
<text fg={resolveColor("--color-muted")}>[Space]</text>
|
||||
</box>
|
||||
</box>
|
||||
|
||||
<text fg="var(--color-muted)">Tab to move focus, Left/Right to adjust</text>
|
||||
<text fg={resolveColor("--color-muted")}>Tab to move focus, Left/Right to adjust</text>
|
||||
</box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { createSignal, For } from "solid-js"
|
||||
import { useFeedStore } from "../stores/feed"
|
||||
import { SourceType } from "../types/source"
|
||||
import { createColorResolver } from "../utils/color"
|
||||
import type { PodcastSource } from "../types/source"
|
||||
|
||||
interface SourceManagerProps {
|
||||
@@ -148,6 +149,8 @@ export function SourceManager(props: SourceManagerProps) {
|
||||
const sourceExplicit = () => selectedSource()?.allowExplicit !== false
|
||||
const sourceLanguage = () => selectedSource()?.language || "en_us"
|
||||
|
||||
const resolveColor = createColorResolver({})
|
||||
|
||||
return (
|
||||
<box flexDirection="column" border padding={1} gap={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
@@ -155,15 +158,15 @@ export function SourceManager(props: SourceManagerProps) {
|
||||
<strong>Podcast Sources</strong>
|
||||
</text>
|
||||
<box border padding={0} onMouseDown={props.onClose}>
|
||||
<text fg="var(--color-primary)">[Esc] Close</text>
|
||||
<text fg={resolveColor("--color-primary")}>[Esc] Close</text>
|
||||
</box>
|
||||
</box>
|
||||
|
||||
<text fg="var(--color-muted)">Manage where to search for podcasts</text>
|
||||
<text fg={resolveColor("--color-muted")}>Manage where to search for podcasts</text>
|
||||
|
||||
{/* Source list */}
|
||||
<box border padding={1} flexDirection="column" gap={1}>
|
||||
<text fg={focusArea() === "list" ? "var(--color-primary)" : "var(--color-muted)"}>Sources:</text>
|
||||
<text fg={focusArea() === "list" ? resolveColor("--color-primary") : resolveColor("--color-muted")}>Sources:</text>
|
||||
<scrollbox height={6}>
|
||||
<For each={sources()}>
|
||||
{(source, index) => (
|
||||
@@ -173,7 +176,7 @@ export function SourceManager(props: SourceManagerProps) {
|
||||
padding={0}
|
||||
backgroundColor={
|
||||
focusArea() === "list" && index() === selectedIndex()
|
||||
? "var(--color-primary)"
|
||||
? resolveColor("--color-primary")
|
||||
: undefined
|
||||
}
|
||||
onMouseDown={() => {
|
||||
@@ -184,21 +187,21 @@ export function SourceManager(props: SourceManagerProps) {
|
||||
>
|
||||
<text fg={
|
||||
focusArea() === "list" && index() === selectedIndex()
|
||||
? "var(--color-primary)"
|
||||
: "var(--color-muted)"
|
||||
? resolveColor("--color-primary")
|
||||
: resolveColor("--color-muted")
|
||||
}>
|
||||
{focusArea() === "list" && index() === selectedIndex()
|
||||
? ">"
|
||||
: " "}
|
||||
</text>
|
||||
<text fg={source.enabled ? "var(--color-success)" : "var(--color-error)"}>
|
||||
<text fg={source.enabled ? resolveColor("--color-success") : resolveColor("--color-error")}>
|
||||
{source.enabled ? "[x]" : "[ ]"}
|
||||
</text>
|
||||
<text fg="var(--color-accent)">{getSourceIcon(source)}</text>
|
||||
<text fg={resolveColor("--color-accent")}>{getSourceIcon(source)}</text>
|
||||
<text
|
||||
fg={
|
||||
focusArea() === "list" && index() === selectedIndex()
|
||||
? "var(--color-text)"
|
||||
? resolveColor("--color-text")
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
@@ -208,7 +211,7 @@ export function SourceManager(props: SourceManagerProps) {
|
||||
)}
|
||||
</For>
|
||||
</scrollbox>
|
||||
<text fg="var(--color-muted)">Space/Enter to toggle, d to delete, a to add</text>
|
||||
<text fg={resolveColor("--color-muted")}>Space/Enter to toggle, d to delete, a to add</text>
|
||||
|
||||
{/* API settings */}
|
||||
<box flexDirection="column" gap={1}>
|
||||
|
||||
@@ -42,6 +42,8 @@ export function ThemeProvider({ children }: { children: any }) {
|
||||
// Handle system theme changes
|
||||
createEffect(() => {
|
||||
if (isSystemTheme()) {
|
||||
// Check if window and matchMedia are available
|
||||
if (typeof window !== "undefined" && typeof window.matchMedia === "function") {
|
||||
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)")
|
||||
const handler = () => {
|
||||
const newMode = getSystemThemeMode()
|
||||
@@ -55,6 +57,7 @@ export function ThemeProvider({ children }: { children: any }) {
|
||||
mediaQuery.removeEventListener("change", handler)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
|
||||
93
src/utils/color.ts
Normal file
93
src/utils/color.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Color Resolution Utility
|
||||
* Converts CSS variable references to actual color values
|
||||
*/
|
||||
|
||||
/**
|
||||
* Map of CSS variable names to their default color values
|
||||
* Used as fallback when CSS variables aren't resolved
|
||||
*/
|
||||
const CSS_VARIABLE_MAP: Record<string, string> = {
|
||||
"--color-background": "transparent",
|
||||
"--color-surface": "#1b1f27",
|
||||
"--color-primary": "#6fa8ff",
|
||||
"--color-secondary": "#a9b1d6",
|
||||
"--color-accent": "#f6c177",
|
||||
"--color-text": "#e6edf3",
|
||||
"--color-muted": "#7d8590",
|
||||
"--color-warning": "#f0b429",
|
||||
"--color-error": "#f47067",
|
||||
"--color-success": "#3fb950",
|
||||
"--color-layer0": "transparent",
|
||||
"--color-layer1": "#1e222e",
|
||||
"--color-layer2": "#161b22",
|
||||
"--color-layer3": "#0d1117",
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a CSS variable reference to an actual color value
|
||||
* @param variable The CSS variable string (e.g., "var(--color-primary)")
|
||||
* @returns The resolved color value or a default fallback
|
||||
*/
|
||||
export function resolveCSSVariable(variable: string): string {
|
||||
if (!variable || typeof variable !== "string") {
|
||||
return "#ff00ff" // Default magenta fallback
|
||||
}
|
||||
|
||||
// Extract the variable name from var(--name)
|
||||
const match = variable.match(/var\(([^)]+)\)/)
|
||||
if (match && match[1]) {
|
||||
const varName = match[1].trim()
|
||||
// Get the computed style value from the document
|
||||
if (typeof document !== "undefined") {
|
||||
const root = document.documentElement
|
||||
const computedValue = getComputedStyle(root).getPropertyValue(varName)
|
||||
if (computedValue) {
|
||||
return computedValue.trim()
|
||||
}
|
||||
}
|
||||
// Fall back to the map
|
||||
return CSS_VARIABLE_MAP[varName] || "#ff00ff"
|
||||
}
|
||||
|
||||
// Return the original value if it's not a CSS variable
|
||||
return variable
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function that resolves CSS variables from a theme object
|
||||
* @param theme The theme object with color properties
|
||||
* @returns A function that resolves CSS variable strings to actual colors
|
||||
*/
|
||||
export function createColorResolver(theme: {
|
||||
background?: string
|
||||
surface?: string
|
||||
primary?: string
|
||||
secondary?: string
|
||||
accent?: string
|
||||
text?: string
|
||||
muted?: string
|
||||
warning?: string
|
||||
error?: string
|
||||
success?: string
|
||||
layerBackgrounds?: {
|
||||
layer0?: string
|
||||
layer1?: string
|
||||
layer2?: string
|
||||
layer3?: string
|
||||
}
|
||||
}) {
|
||||
return (color: string | undefined): string => {
|
||||
if (!color) {
|
||||
return "#ff00ff" // Default magenta fallback
|
||||
}
|
||||
|
||||
// If it's already a valid hex or named color, return it
|
||||
if (/^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/.test(color) || /^[a-z]+$/i.test(color)) {
|
||||
return color
|
||||
}
|
||||
|
||||
// Otherwise, try to resolve it as a CSS variable
|
||||
return resolveCSSVariable(color)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user