This commit is contained in:
2026-02-10 15:30:53 -05:00
parent f707594d0c
commit 3d5bc84550
18 changed files with 89 additions and 60 deletions

View File

@@ -6,19 +6,21 @@ const createSignal = <T,>(value: T): [() => T, (next: T) => void] => {
}
import { SyncStatus } from "./SyncStatus"
import { useTheme } from "@/context/ThemeContext"
export function ExportDialog() {
const { theme } = useTheme();
const filename = createSignal("podcast-sync.json")
const format = createSignal<"json" | "xml">("json")
return (
<box border title="Export" style={{ padding: 1, flexDirection: "column", gap: 1 }}>
<box style={{ flexDirection: "row", gap: 1 }}>
<text>File:</text>
<text fg={theme.text}>File:</text>
<input value={filename[0]()} onInput={filename[1]} style={{ width: 30 }} />
</box>
<box style={{ flexDirection: "row", gap: 1 }}>
<text>Format:</text>
<text fg={theme.text}>Format:</text>
<tab_select
options={[
{ name: "JSON", description: "Portable" },
@@ -27,8 +29,8 @@ export function ExportDialog() {
onSelect={(index) => format[1](index === 0 ? "json" : "xml")}
/>
</box>
<box border>
<text>Export {format[0]()} to {filename[0]()}</text>
<box border borderColor={theme.border}>
<text fg={theme.text}>Export {format[0]()} to {filename[0]()}</text>
</box>
<SyncStatus />
</box>

View File

@@ -1,4 +1,5 @@
import { detectFormat } from "@/utils/file-detector";
import { useTheme } from "@/context/ThemeContext";
type FilePickerProps = {
value: string;
@@ -6,6 +7,7 @@ type FilePickerProps = {
};
export function FilePicker(props: FilePickerProps) {
const { theme } = useTheme();
const format = detectFormat(props.value);
return (
@@ -16,7 +18,7 @@ export function FilePicker(props: FilePickerProps) {
placeholder="/path/to/sync-file.json"
style={{ width: 40 }}
/>
<text>Format: {format}</text>
<text fg={theme.text}>Format: {format}</text>
</box>
);
}

View File

@@ -6,15 +6,17 @@ const createSignal = <T,>(value: T): [() => T, (next: T) => void] => {
}
import { FilePicker } from "./FilePicker"
import { useTheme } from "@/context/ThemeContext"
export function ImportDialog() {
const { theme } = useTheme();
const filePath = createSignal("")
return (
<box border title="Import" style={{ padding: 1, flexDirection: "column", gap: 1 }}>
<FilePicker value={filePath[0]()} onChange={filePath[1]} />
<box border>
<text>Import selected file</text>
<box border borderColor={theme.border}>
<text fg={theme.text}>Import selected file</text>
</box>
</box>
)

View File

@@ -83,8 +83,8 @@ export function LoginScreen(props: LoginScreenProps) {
};
return (
<box flexDirection="column" border padding={2} gap={1}>
<text>
<box flexDirection="column" border borderColor={theme.border} padding={2} gap={1}>
<text fg={theme.text}>
<strong>Sign In</strong>
</text>
@@ -92,7 +92,7 @@ export function LoginScreen(props: LoginScreenProps) {
{/* Email field */}
<box flexDirection="column" gap={0}>
<text fg={focusField() === "email" ? theme.primary : undefined}>
<text fg={focusField() === "email" ? theme.primary : theme.textMuted}>
Email:
</text>
<input
@@ -107,7 +107,7 @@ export function LoginScreen(props: LoginScreenProps) {
{/* Password field */}
<box flexDirection="column" gap={0}>
<text fg={focusField() === "password" ? theme.primary : undefined}>
<text fg={focusField() === "password" ? theme.primary : theme.textMuted}>
Password:
</text>
<input
@@ -126,6 +126,7 @@ export function LoginScreen(props: LoginScreenProps) {
<box flexDirection="row" gap={2}>
<box
border
borderColor={theme.border}
padding={1}
backgroundColor={
focusField() === "submit" ? theme.primary : undefined
@@ -148,6 +149,7 @@ export function LoginScreen(props: LoginScreenProps) {
<box flexDirection="row" gap={2}>
<box
border
borderColor={theme.border}
padding={1}
backgroundColor={focusField() === "code" ? theme.primary : undefined}
>
@@ -158,6 +160,7 @@ export function LoginScreen(props: LoginScreenProps) {
<box
border
borderColor={theme.border}
padding={1}
backgroundColor={focusField() === "oauth" ? theme.primary : undefined}
>

View File

@@ -94,7 +94,7 @@ export function PreferencesPanel() {
<text fg={focusField() === "theme" ? theme.primary : theme.textMuted}>
Theme:
</text>
<box border padding={0}>
<box border borderColor={theme.border} padding={0}>
<text fg={theme.text}>
{THEME_LABELS.find((t) => t.value === settings().theme)?.label}
</text>
@@ -106,7 +106,7 @@ export function PreferencesPanel() {
<text fg={focusField() === "font" ? theme.primary : theme.textMuted}>
Font Size:
</text>
<box border padding={0}>
<box border borderColor={theme.border} padding={0}>
<text fg={theme.text}>{settings().fontSize}px</text>
</box>
<text fg={theme.textMuted}>[Left/Right]</text>
@@ -116,7 +116,7 @@ export function PreferencesPanel() {
<text fg={focusField() === "speed" ? theme.primary : theme.textMuted}>
Playback:
</text>
<box border padding={0}>
<box border borderColor={theme.border} padding={0}>
<text fg={theme.text}>{settings().playbackSpeed}x</text>
</box>
<text fg={theme.textMuted}>[Left/Right]</text>
@@ -128,7 +128,7 @@ export function PreferencesPanel() {
>
Show Explicit:
</text>
<box border padding={0}>
<box border borderColor={theme.border} padding={0}>
<text
fg={preferences().showExplicit ? theme.success : theme.textMuted}
>
@@ -142,7 +142,7 @@ export function PreferencesPanel() {
<text fg={focusField() === "auto" ? theme.primary : theme.textMuted}>
Auto Download:
</text>
<box border padding={0}>
<box border borderColor={theme.border} padding={0}>
<text
fg={preferences().autoDownload ? theme.success : theme.textMuted}
>

View File

@@ -37,6 +37,7 @@ export function SettingsPage(props: PageProps) {
{(section, index) => (
<box
border
borderColor={theme.border}
padding={0}
backgroundColor={
activeSection() === section.id ? theme.primary : undefined
@@ -55,7 +56,7 @@ export function SettingsPage(props: PageProps) {
</For>
</box>
<box border flexGrow={1} padding={1} flexDirection="column" gap={1}>
<box border borderColor={theme.border} flexGrow={1} padding={1} flexDirection="column" gap={1}>
{activeSection() === SettingsPaneType.SYNC && <SyncPanel />}
{activeSection() === SettingsPaneType.SOURCES && (
<SourceManager focused />

View File

@@ -166,12 +166,12 @@ export function SourceManager(props: SourceManagerProps) {
const sourceLanguage = () => selectedSource()?.language || "en_us";
return (
<box flexDirection="column" border padding={1} gap={1}>
<box flexDirection="column" border borderColor={theme.border} padding={1} gap={1}>
<box flexDirection="row" justifyContent="space-between">
<text>
<text fg={theme.text}>
<strong>Podcast Sources</strong>
</text>
<box border padding={0} onMouseDown={props.onClose}>
<box border borderColor={theme.border} padding={0} onMouseDown={props.onClose}>
<text fg={theme.primary}>[Esc] Close</text>
</box>
</box>
@@ -179,7 +179,7 @@ export function SourceManager(props: SourceManagerProps) {
<text fg={theme.textMuted}>Manage where to search for podcasts</text>
{/* Source list */}
<box border padding={1} flexDirection="column" gap={1}>
<box border borderColor={theme.border} padding={1} flexDirection="column" gap={1}>
<text fg={focusArea() === "list" ? theme.primary : theme.textMuted}>
Sources:
</text>
@@ -243,6 +243,7 @@ export function SourceManager(props: SourceManagerProps) {
<box flexDirection="row" gap={2}>
<box
border
borderColor={theme.border}
padding={0}
backgroundColor={
focusArea() === "country" ? theme.primary : undefined
@@ -256,6 +257,7 @@ export function SourceManager(props: SourceManagerProps) {
</box>
<box
border
borderColor={theme.border}
padding={0}
backgroundColor={
focusArea() === "language" ? theme.primary : undefined
@@ -272,6 +274,7 @@ export function SourceManager(props: SourceManagerProps) {
</box>
<box
border
borderColor={theme.border}
padding={0}
backgroundColor={
focusArea() === "explicit" ? theme.primary : undefined
@@ -293,7 +296,7 @@ export function SourceManager(props: SourceManagerProps) {
</box>
{/* Add new source form */}
<box border padding={1} flexDirection="column" gap={1}>
<box border borderColor={theme.border} padding={1} flexDirection="column" gap={1}>
<text
fg={
focusArea() === "add" || focusArea() === "url"
@@ -329,7 +332,7 @@ export function SourceManager(props: SourceManagerProps) {
/>
</box>
<box border padding={0} width={15} onMouseDown={handleAddSource}>
<box border borderColor={theme.border} padding={0} width={15} onMouseDown={handleAddSource}>
<text fg={theme.success}>[+] Add Source</text>
</box>
</box>

View File

@@ -1,14 +1,17 @@
import { useTheme } from "@/context/ThemeContext"
type SyncErrorProps = {
message: string
onRetry: () => void
}
export function SyncError(props: SyncErrorProps) {
const { theme } = useTheme();
return (
<box border title="Error" style={{ padding: 1, flexDirection: "column", gap: 1 }}>
<text>{props.message}</text>
<box border onMouseDown={props.onRetry}>
<text>Retry</text>
<text fg={theme.text}>{props.message}</text>
<box border borderColor={theme.border} onMouseDown={props.onRetry}>
<text fg={theme.text}>Retry</text>
</box>
</box>
)

View File

@@ -8,18 +8,20 @@ const createSignal = <T,>(value: T): [() => T, (next: T) => void] => {
import { ImportDialog } from "./ImportDialog"
import { ExportDialog } from "./ExportDialog"
import { SyncStatus } from "./SyncStatus"
import { useTheme } from "@/context/ThemeContext"
export function SyncPanel() {
const { theme } = useTheme();
const mode = createSignal<"import" | "export" | null>(null)
return (
<box style={{ flexDirection: "column", gap: 1 }}>
<box style={{ flexDirection: "row", gap: 1 }}>
<box border onMouseDown={() => mode[1]("import")}>
<text>Import</text>
<box border borderColor={theme.border} onMouseDown={() => mode[1]("import")}>
<text fg={theme.text}>Import</text>
</box>
<box border onMouseDown={() => mode[1]("export")}>
<text>Export</text>
<box border borderColor={theme.border} onMouseDown={() => mode[1]("export")}>
<text fg={theme.text}>Export</text>
</box>
</box>
<SyncStatus />

View File

@@ -1,8 +1,11 @@
import { useTheme } from "@/context/ThemeContext"
type SyncProgressProps = {
value: number
}
export function SyncProgress(props: SyncProgressProps) {
const { theme } = useTheme();
const width = 30
let filled = (props.value / 100) * width
filled = filled >= 0 ? filled : 0
@@ -18,8 +21,8 @@ export function SyncProgress(props: SyncProgressProps) {
return (
<box style={{ flexDirection: "column" }}>
<text>{bar}</text>
<text>{props.value}%</text>
<text fg={theme.text}>{bar}</text>
<text fg={theme.text}>{props.value}%</text>
</box>
)
}

View File

@@ -7,10 +7,12 @@ const createSignal = <T,>(value: T): [() => T, (next: T) => void] => {
import { SyncProgress } from "./SyncProgress"
import { SyncError } from "./SyncError"
import { useTheme } from "@/context/ThemeContext"
type SyncState = "idle" | "syncing" | "complete" | "error"
export function SyncStatus() {
const { theme } = useTheme();
const state = createSignal<SyncState>("idle")
const message = createSignal("Idle")
const progress = createSignal(0)
@@ -35,15 +37,15 @@ export function SyncStatus() {
}
return (
<box border title="Sync Status" style={{ padding: 1, flexDirection: "column", gap: 1 }}>
<box border title="Sync Status" borderColor={theme.border} style={{ padding: 1, flexDirection: "column", gap: 1 }}>
<box style={{ flexDirection: "row", gap: 1 }}>
<text>Status:</text>
<text>{message[0]()}</text>
<text fg={theme.text}>Status:</text>
<text fg={theme.text}>{message[0]()}</text>
</box>
<SyncProgress value={progress[0]()} />
{state[0]() === "error" ? <SyncError message={message[0]()} onRetry={() => toggle()} /> : null}
<box border onMouseDown={toggle}>
<text>Cycle Status</text>
<box border borderColor={theme.border} onMouseDown={toggle}>
<text fg={theme.text}>Cycle Status</text>
</box>
</box>
)

View File

@@ -99,7 +99,7 @@ export function VisualizerSettings() {
<text fg={focusField() === "bars" ? theme.primary : theme.textMuted}>
Bars:
</text>
<box border padding={0}>
<box border borderColor={theme.border} padding={0}>
<text fg={theme.text}>{viz().bars}</text>
</box>
<text fg={theme.textMuted}>[Left/Right +/-8]</text>
@@ -113,7 +113,7 @@ export function VisualizerSettings() {
>
Auto Sensitivity:
</text>
<box border padding={0}>
<box border borderColor={theme.border} padding={0}>
<text
fg={viz().sensitivity === 1 ? theme.success : theme.textMuted}
>
@@ -127,7 +127,7 @@ export function VisualizerSettings() {
<text fg={focusField() === "noise" ? theme.primary : theme.textMuted}>
Noise Reduction:
</text>
<box border padding={0}>
<box border borderColor={theme.border} padding={0}>
<text fg={theme.text}>{viz().noiseReduction.toFixed(2)}</text>
</box>
<text fg={theme.textMuted}>[Left/Right +/-0.05]</text>
@@ -139,7 +139,7 @@ export function VisualizerSettings() {
>
Low Cutoff:
</text>
<box border padding={0}>
<box border borderColor={theme.border} padding={0}>
<text fg={theme.text}>{viz().lowCutOff} Hz</text>
</box>
<text fg={theme.textMuted}>[Left/Right +/-10]</text>
@@ -151,7 +151,7 @@ export function VisualizerSettings() {
>
High Cutoff:
</text>
<box border padding={0}>
<box border borderColor={theme.border} padding={0}>
<text fg={theme.text}>{viz().highCutOff} Hz</text>
</box>
<text fg={theme.textMuted}>[Left/Right +/-500]</text>