set
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import type { TabId } from "./Tab"
|
import type { TabId } from "./Tab"
|
||||||
|
import { useTheme } from "@/context/ThemeContext"
|
||||||
|
|
||||||
type NavigationProps = {
|
type NavigationProps = {
|
||||||
activeTab: TabId
|
activeTab: TabId
|
||||||
@@ -6,9 +7,10 @@ type NavigationProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Navigation(props: NavigationProps) {
|
export function Navigation(props: NavigationProps) {
|
||||||
|
const { theme } = useTheme();
|
||||||
return (
|
return (
|
||||||
<box style={{ flexDirection: "row", width: "100%", height: 1 }}>
|
<box style={{ flexDirection: "row", width: "100%", height: 1 }}>
|
||||||
<text>
|
<text fg={theme.text}>
|
||||||
{props.activeTab === "feed" ? "[" : " "}Feed{props.activeTab === "feed" ? "]" : " "}
|
{props.activeTab === "feed" ? "[" : " "}Feed{props.activeTab === "feed" ? "]" : " "}
|
||||||
<span> </span>
|
<span> </span>
|
||||||
{props.activeTab === "shows" ? "[" : " "}My Shows{props.activeTab === "shows" ? "]" : " "}
|
{props.activeTab === "shows" ? "[" : " "}My Shows{props.activeTab === "shows" ? "]" : " "}
|
||||||
|
|||||||
@@ -1,24 +1,26 @@
|
|||||||
import { shortcuts } from "@/config/shortcuts";
|
import { shortcuts } from "@/config/shortcuts";
|
||||||
|
import { useTheme } from "@/context/ThemeContext";
|
||||||
|
|
||||||
export function ShortcutHelp() {
|
export function ShortcutHelp() {
|
||||||
|
const { theme } = useTheme();
|
||||||
return (
|
return (
|
||||||
<box border title="Shortcuts" style={{ padding: 1 }}>
|
<box border title="Shortcuts" style={{ padding: 1 }}>
|
||||||
<box style={{ flexDirection: "column" }}>
|
<box style={{ flexDirection: "column" }}>
|
||||||
<box style={{ flexDirection: "row" }}>
|
<box style={{ flexDirection: "row" }}>
|
||||||
<text>{shortcuts[0]?.keys ?? ""} </text>
|
<text fg={theme.text}>{shortcuts[0]?.keys ?? ""} </text>
|
||||||
<text>{shortcuts[0]?.action ?? ""}</text>
|
<text fg={theme.text}>{shortcuts[0]?.action ?? ""}</text>
|
||||||
</box>
|
</box>
|
||||||
<box style={{ flexDirection: "row" }}>
|
<box style={{ flexDirection: "row" }}>
|
||||||
<text>{shortcuts[1]?.keys ?? ""} </text>
|
<text fg={theme.text}>{shortcuts[1]?.keys ?? ""} </text>
|
||||||
<text>{shortcuts[1]?.action ?? ""}</text>
|
<text fg={theme.text}>{shortcuts[1]?.action ?? ""}</text>
|
||||||
</box>
|
</box>
|
||||||
<box style={{ flexDirection: "row" }}>
|
<box style={{ flexDirection: "row" }}>
|
||||||
<text>{shortcuts[2]?.keys ?? ""} </text>
|
<text fg={theme.text}>{shortcuts[2]?.keys ?? ""} </text>
|
||||||
<text>{shortcuts[2]?.action ?? ""}</text>
|
<text fg={theme.text}>{shortcuts[2]?.action ?? ""}</text>
|
||||||
</box>
|
</box>
|
||||||
<box style={{ flexDirection: "row" }}>
|
<box style={{ flexDirection: "row" }}>
|
||||||
<text>{shortcuts[3]?.keys ?? ""} </text>
|
<text fg={theme.text}>{shortcuts[3]?.keys ?? ""} </text>
|
||||||
<text>{shortcuts[3]?.action ?? ""}</text>
|
<text fg={theme.text}>{shortcuts[3]?.action ?? ""}</text>
|
||||||
</box>
|
</box>
|
||||||
</box>
|
</box>
|
||||||
</box>
|
</box>
|
||||||
|
|||||||
@@ -129,10 +129,10 @@ export function FeedDetail(props: FeedDetailProps) {
|
|||||||
|
|
||||||
{/* Episodes header */}
|
{/* Episodes header */}
|
||||||
<box flexDirection="row" justifyContent="space-between">
|
<box flexDirection="row" justifyContent="space-between">
|
||||||
<text>
|
<text fg={theme.text}>
|
||||||
<strong>Episodes</strong>
|
<strong>Episodes</strong>
|
||||||
</text>
|
</text>
|
||||||
<text fg="gray">({episodes().length} total)</text>
|
<text fg={theme.textMuted}>({episodes().length} total)</text>
|
||||||
</box>
|
</box>
|
||||||
|
|
||||||
{/* Episode list */}
|
{/* Episode list */}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export function PlayerPage(props: PageProps) {
|
|||||||
return (
|
return (
|
||||||
<box flexDirection="column" gap={1} width="100%">
|
<box flexDirection="column" gap={1} width="100%">
|
||||||
<box flexDirection="row" justifyContent="space-between">
|
<box flexDirection="row" justifyContent="space-between">
|
||||||
<text>
|
<text fg={theme.text}>
|
||||||
<strong>Now Playing</strong>
|
<strong>Now Playing</strong>
|
||||||
</text>
|
</text>
|
||||||
<text fg={theme.muted}>
|
<text fg={theme.muted}>
|
||||||
@@ -40,7 +40,7 @@ export function PlayerPage(props: PageProps) {
|
|||||||
|
|
||||||
{audio.error() && <text fg={theme.error}>{audio.error()}</text>}
|
{audio.error() && <text fg={theme.error}>{audio.error()}</text>}
|
||||||
|
|
||||||
<box border padding={1} flexDirection="column" gap={1}>
|
<box border borderColor={theme.border} padding={1} flexDirection="column" gap={1}>
|
||||||
<text fg={theme.text}>
|
<text fg={theme.text}>
|
||||||
<strong>{audio.currentEpisode()?.title}</strong>
|
<strong>{audio.currentEpisode()?.title}</strong>
|
||||||
</text>
|
</text>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
} from "@/utils/cavacore";
|
} from "@/utils/cavacore";
|
||||||
import { AudioStreamReader } from "@/utils/audio-stream-reader";
|
import { AudioStreamReader } from "@/utils/audio-stream-reader";
|
||||||
import { useAudio } from "@/hooks/useAudio";
|
import { useAudio } from "@/hooks/useAudio";
|
||||||
|
import { useTheme } from "@/context/ThemeContext";
|
||||||
|
|
||||||
// ── Types ────────────────────────────────────────────────────────────
|
// ── Types ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -44,6 +45,7 @@ const SAMPLES_PER_FRAME = 512;
|
|||||||
// ── Component ────────────────────────────────────────────────────────
|
// ── Component ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export function RealtimeWaveform(props: RealtimeWaveformProps) {
|
export function RealtimeWaveform(props: RealtimeWaveformProps) {
|
||||||
|
const { theme } = useTheme();
|
||||||
const audio = useAudio();
|
const audio = useAudio();
|
||||||
|
|
||||||
// Frequency bar values (0.0–1.0 per bar)
|
// Frequency bar values (0.0–1.0 per bar)
|
||||||
@@ -247,7 +249,7 @@ export function RealtimeWaveform(props: RealtimeWaveformProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<box border padding={1} onMouseDown={handleClick}>
|
<box border borderColor={theme.border} padding={1} onMouseDown={handleClick}>
|
||||||
{renderLine()}
|
{renderLine()}
|
||||||
</box>
|
</box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export function SearchPage(props: PageProps) {
|
|||||||
<box flexDirection="column" height="100%" gap={1} width="100%">
|
<box flexDirection="column" height="100%" gap={1} width="100%">
|
||||||
{/* Search Header */}
|
{/* Search Header */}
|
||||||
<box flexDirection="column" gap={1}>
|
<box flexDirection="column" gap={1}>
|
||||||
<text>
|
<text fg={theme.text}>
|
||||||
<strong>Search Podcasts</strong>
|
<strong>Search Podcasts</strong>
|
||||||
</text>
|
</text>
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ export function SearchPage(props: PageProps) {
|
|||||||
{/* Main Content - Results or History */}
|
{/* Main Content - Results or History */}
|
||||||
<box flexDirection="row" height="100%" gap={2}>
|
<box flexDirection="row" height="100%" gap={2}>
|
||||||
{/* Results Panel */}
|
{/* Results Panel */}
|
||||||
<box flexDirection="column" flexGrow={1} border>
|
<box flexDirection="column" flexGrow={1} border borderColor={theme.border}>
|
||||||
<box padding={1}>
|
<box padding={1}>
|
||||||
<text
|
<text
|
||||||
fg={props.depth() === SearchPaneType.RESULTS ? theme.primary : theme.muted}
|
fg={props.depth() === SearchPaneType.RESULTS ? theme.primary : theme.muted}
|
||||||
@@ -134,7 +134,7 @@ export function SearchPage(props: PageProps) {
|
|||||||
</box>
|
</box>
|
||||||
|
|
||||||
{/* History Sidebar */}
|
{/* History Sidebar */}
|
||||||
<box width={30} border>
|
<box width={30} border borderColor={theme.border}>
|
||||||
<box padding={1} flexDirection="column">
|
<box padding={1} flexDirection="column">
|
||||||
<box paddingBottom={1}>
|
<box paddingBottom={1}>
|
||||||
<text
|
<text
|
||||||
|
|||||||
@@ -6,19 +6,21 @@ const createSignal = <T,>(value: T): [() => T, (next: T) => void] => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
import { SyncStatus } from "./SyncStatus"
|
import { SyncStatus } from "./SyncStatus"
|
||||||
|
import { useTheme } from "@/context/ThemeContext"
|
||||||
|
|
||||||
export function ExportDialog() {
|
export function ExportDialog() {
|
||||||
|
const { theme } = useTheme();
|
||||||
const filename = createSignal("podcast-sync.json")
|
const filename = createSignal("podcast-sync.json")
|
||||||
const format = createSignal<"json" | "xml">("json")
|
const format = createSignal<"json" | "xml">("json")
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<box border title="Export" style={{ padding: 1, flexDirection: "column", gap: 1 }}>
|
<box border title="Export" style={{ padding: 1, flexDirection: "column", gap: 1 }}>
|
||||||
<box style={{ flexDirection: "row", 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 }} />
|
<input value={filename[0]()} onInput={filename[1]} style={{ width: 30 }} />
|
||||||
</box>
|
</box>
|
||||||
<box style={{ flexDirection: "row", gap: 1 }}>
|
<box style={{ flexDirection: "row", gap: 1 }}>
|
||||||
<text>Format:</text>
|
<text fg={theme.text}>Format:</text>
|
||||||
<tab_select
|
<tab_select
|
||||||
options={[
|
options={[
|
||||||
{ name: "JSON", description: "Portable" },
|
{ name: "JSON", description: "Portable" },
|
||||||
@@ -27,8 +29,8 @@ export function ExportDialog() {
|
|||||||
onSelect={(index) => format[1](index === 0 ? "json" : "xml")}
|
onSelect={(index) => format[1](index === 0 ? "json" : "xml")}
|
||||||
/>
|
/>
|
||||||
</box>
|
</box>
|
||||||
<box border>
|
<box border borderColor={theme.border}>
|
||||||
<text>Export {format[0]()} to {filename[0]()}</text>
|
<text fg={theme.text}>Export {format[0]()} to {filename[0]()}</text>
|
||||||
</box>
|
</box>
|
||||||
<SyncStatus />
|
<SyncStatus />
|
||||||
</box>
|
</box>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { detectFormat } from "@/utils/file-detector";
|
import { detectFormat } from "@/utils/file-detector";
|
||||||
|
import { useTheme } from "@/context/ThemeContext";
|
||||||
|
|
||||||
type FilePickerProps = {
|
type FilePickerProps = {
|
||||||
value: string;
|
value: string;
|
||||||
@@ -6,6 +7,7 @@ type FilePickerProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function FilePicker(props: FilePickerProps) {
|
export function FilePicker(props: FilePickerProps) {
|
||||||
|
const { theme } = useTheme();
|
||||||
const format = detectFormat(props.value);
|
const format = detectFormat(props.value);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -16,7 +18,7 @@ export function FilePicker(props: FilePickerProps) {
|
|||||||
placeholder="/path/to/sync-file.json"
|
placeholder="/path/to/sync-file.json"
|
||||||
style={{ width: 40 }}
|
style={{ width: 40 }}
|
||||||
/>
|
/>
|
||||||
<text>Format: {format}</text>
|
<text fg={theme.text}>Format: {format}</text>
|
||||||
</box>
|
</box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,15 +6,17 @@ const createSignal = <T,>(value: T): [() => T, (next: T) => void] => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
import { FilePicker } from "./FilePicker"
|
import { FilePicker } from "./FilePicker"
|
||||||
|
import { useTheme } from "@/context/ThemeContext"
|
||||||
|
|
||||||
export function ImportDialog() {
|
export function ImportDialog() {
|
||||||
|
const { theme } = useTheme();
|
||||||
const filePath = createSignal("")
|
const filePath = createSignal("")
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<box border title="Import" style={{ padding: 1, flexDirection: "column", gap: 1 }}>
|
<box border title="Import" style={{ padding: 1, flexDirection: "column", gap: 1 }}>
|
||||||
<FilePicker value={filePath[0]()} onChange={filePath[1]} />
|
<FilePicker value={filePath[0]()} onChange={filePath[1]} />
|
||||||
<box border>
|
<box border borderColor={theme.border}>
|
||||||
<text>Import selected file</text>
|
<text fg={theme.text}>Import selected file</text>
|
||||||
</box>
|
</box>
|
||||||
</box>
|
</box>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ export function LoginScreen(props: LoginScreenProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<box flexDirection="column" border padding={2} gap={1}>
|
<box flexDirection="column" border borderColor={theme.border} padding={2} gap={1}>
|
||||||
<text>
|
<text fg={theme.text}>
|
||||||
<strong>Sign In</strong>
|
<strong>Sign In</strong>
|
||||||
</text>
|
</text>
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ export function LoginScreen(props: LoginScreenProps) {
|
|||||||
|
|
||||||
{/* Email field */}
|
{/* Email field */}
|
||||||
<box flexDirection="column" gap={0}>
|
<box flexDirection="column" gap={0}>
|
||||||
<text fg={focusField() === "email" ? theme.primary : undefined}>
|
<text fg={focusField() === "email" ? theme.primary : theme.textMuted}>
|
||||||
Email:
|
Email:
|
||||||
</text>
|
</text>
|
||||||
<input
|
<input
|
||||||
@@ -107,7 +107,7 @@ export function LoginScreen(props: LoginScreenProps) {
|
|||||||
|
|
||||||
{/* Password field */}
|
{/* Password field */}
|
||||||
<box flexDirection="column" gap={0}>
|
<box flexDirection="column" gap={0}>
|
||||||
<text fg={focusField() === "password" ? theme.primary : undefined}>
|
<text fg={focusField() === "password" ? theme.primary : theme.textMuted}>
|
||||||
Password:
|
Password:
|
||||||
</text>
|
</text>
|
||||||
<input
|
<input
|
||||||
@@ -126,6 +126,7 @@ export function LoginScreen(props: LoginScreenProps) {
|
|||||||
<box flexDirection="row" gap={2}>
|
<box flexDirection="row" gap={2}>
|
||||||
<box
|
<box
|
||||||
border
|
border
|
||||||
|
borderColor={theme.border}
|
||||||
padding={1}
|
padding={1}
|
||||||
backgroundColor={
|
backgroundColor={
|
||||||
focusField() === "submit" ? theme.primary : undefined
|
focusField() === "submit" ? theme.primary : undefined
|
||||||
@@ -148,6 +149,7 @@ export function LoginScreen(props: LoginScreenProps) {
|
|||||||
<box flexDirection="row" gap={2}>
|
<box flexDirection="row" gap={2}>
|
||||||
<box
|
<box
|
||||||
border
|
border
|
||||||
|
borderColor={theme.border}
|
||||||
padding={1}
|
padding={1}
|
||||||
backgroundColor={focusField() === "code" ? theme.primary : undefined}
|
backgroundColor={focusField() === "code" ? theme.primary : undefined}
|
||||||
>
|
>
|
||||||
@@ -158,6 +160,7 @@ export function LoginScreen(props: LoginScreenProps) {
|
|||||||
|
|
||||||
<box
|
<box
|
||||||
border
|
border
|
||||||
|
borderColor={theme.border}
|
||||||
padding={1}
|
padding={1}
|
||||||
backgroundColor={focusField() === "oauth" ? theme.primary : undefined}
|
backgroundColor={focusField() === "oauth" ? theme.primary : undefined}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ export function PreferencesPanel() {
|
|||||||
<text fg={focusField() === "theme" ? theme.primary : theme.textMuted}>
|
<text fg={focusField() === "theme" ? theme.primary : theme.textMuted}>
|
||||||
Theme:
|
Theme:
|
||||||
</text>
|
</text>
|
||||||
<box border padding={0}>
|
<box border borderColor={theme.border} padding={0}>
|
||||||
<text fg={theme.text}>
|
<text fg={theme.text}>
|
||||||
{THEME_LABELS.find((t) => t.value === settings().theme)?.label}
|
{THEME_LABELS.find((t) => t.value === settings().theme)?.label}
|
||||||
</text>
|
</text>
|
||||||
@@ -106,7 +106,7 @@ export function PreferencesPanel() {
|
|||||||
<text fg={focusField() === "font" ? theme.primary : theme.textMuted}>
|
<text fg={focusField() === "font" ? theme.primary : theme.textMuted}>
|
||||||
Font Size:
|
Font Size:
|
||||||
</text>
|
</text>
|
||||||
<box border padding={0}>
|
<box border borderColor={theme.border} padding={0}>
|
||||||
<text fg={theme.text}>{settings().fontSize}px</text>
|
<text fg={theme.text}>{settings().fontSize}px</text>
|
||||||
</box>
|
</box>
|
||||||
<text fg={theme.textMuted}>[Left/Right]</text>
|
<text fg={theme.textMuted}>[Left/Right]</text>
|
||||||
@@ -116,7 +116,7 @@ export function PreferencesPanel() {
|
|||||||
<text fg={focusField() === "speed" ? theme.primary : theme.textMuted}>
|
<text fg={focusField() === "speed" ? theme.primary : theme.textMuted}>
|
||||||
Playback:
|
Playback:
|
||||||
</text>
|
</text>
|
||||||
<box border padding={0}>
|
<box border borderColor={theme.border} padding={0}>
|
||||||
<text fg={theme.text}>{settings().playbackSpeed}x</text>
|
<text fg={theme.text}>{settings().playbackSpeed}x</text>
|
||||||
</box>
|
</box>
|
||||||
<text fg={theme.textMuted}>[Left/Right]</text>
|
<text fg={theme.textMuted}>[Left/Right]</text>
|
||||||
@@ -128,7 +128,7 @@ export function PreferencesPanel() {
|
|||||||
>
|
>
|
||||||
Show Explicit:
|
Show Explicit:
|
||||||
</text>
|
</text>
|
||||||
<box border padding={0}>
|
<box border borderColor={theme.border} padding={0}>
|
||||||
<text
|
<text
|
||||||
fg={preferences().showExplicit ? theme.success : theme.textMuted}
|
fg={preferences().showExplicit ? theme.success : theme.textMuted}
|
||||||
>
|
>
|
||||||
@@ -142,7 +142,7 @@ export function PreferencesPanel() {
|
|||||||
<text fg={focusField() === "auto" ? theme.primary : theme.textMuted}>
|
<text fg={focusField() === "auto" ? theme.primary : theme.textMuted}>
|
||||||
Auto Download:
|
Auto Download:
|
||||||
</text>
|
</text>
|
||||||
<box border padding={0}>
|
<box border borderColor={theme.border} padding={0}>
|
||||||
<text
|
<text
|
||||||
fg={preferences().autoDownload ? theme.success : theme.textMuted}
|
fg={preferences().autoDownload ? theme.success : theme.textMuted}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export function SettingsPage(props: PageProps) {
|
|||||||
{(section, index) => (
|
{(section, index) => (
|
||||||
<box
|
<box
|
||||||
border
|
border
|
||||||
|
borderColor={theme.border}
|
||||||
padding={0}
|
padding={0}
|
||||||
backgroundColor={
|
backgroundColor={
|
||||||
activeSection() === section.id ? theme.primary : undefined
|
activeSection() === section.id ? theme.primary : undefined
|
||||||
@@ -55,7 +56,7 @@ export function SettingsPage(props: PageProps) {
|
|||||||
</For>
|
</For>
|
||||||
</box>
|
</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.SYNC && <SyncPanel />}
|
||||||
{activeSection() === SettingsPaneType.SOURCES && (
|
{activeSection() === SettingsPaneType.SOURCES && (
|
||||||
<SourceManager focused />
|
<SourceManager focused />
|
||||||
|
|||||||
@@ -166,12 +166,12 @@ export function SourceManager(props: SourceManagerProps) {
|
|||||||
const sourceLanguage = () => selectedSource()?.language || "en_us";
|
const sourceLanguage = () => selectedSource()?.language || "en_us";
|
||||||
|
|
||||||
return (
|
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">
|
<box flexDirection="row" justifyContent="space-between">
|
||||||
<text>
|
<text fg={theme.text}>
|
||||||
<strong>Podcast Sources</strong>
|
<strong>Podcast Sources</strong>
|
||||||
</text>
|
</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>
|
<text fg={theme.primary}>[Esc] Close</text>
|
||||||
</box>
|
</box>
|
||||||
</box>
|
</box>
|
||||||
@@ -179,7 +179,7 @@ export function SourceManager(props: SourceManagerProps) {
|
|||||||
<text fg={theme.textMuted}>Manage where to search for podcasts</text>
|
<text fg={theme.textMuted}>Manage where to search for podcasts</text>
|
||||||
|
|
||||||
{/* Source list */}
|
{/* 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}>
|
<text fg={focusArea() === "list" ? theme.primary : theme.textMuted}>
|
||||||
Sources:
|
Sources:
|
||||||
</text>
|
</text>
|
||||||
@@ -243,6 +243,7 @@ export function SourceManager(props: SourceManagerProps) {
|
|||||||
<box flexDirection="row" gap={2}>
|
<box flexDirection="row" gap={2}>
|
||||||
<box
|
<box
|
||||||
border
|
border
|
||||||
|
borderColor={theme.border}
|
||||||
padding={0}
|
padding={0}
|
||||||
backgroundColor={
|
backgroundColor={
|
||||||
focusArea() === "country" ? theme.primary : undefined
|
focusArea() === "country" ? theme.primary : undefined
|
||||||
@@ -256,6 +257,7 @@ export function SourceManager(props: SourceManagerProps) {
|
|||||||
</box>
|
</box>
|
||||||
<box
|
<box
|
||||||
border
|
border
|
||||||
|
borderColor={theme.border}
|
||||||
padding={0}
|
padding={0}
|
||||||
backgroundColor={
|
backgroundColor={
|
||||||
focusArea() === "language" ? theme.primary : undefined
|
focusArea() === "language" ? theme.primary : undefined
|
||||||
@@ -272,6 +274,7 @@ export function SourceManager(props: SourceManagerProps) {
|
|||||||
</box>
|
</box>
|
||||||
<box
|
<box
|
||||||
border
|
border
|
||||||
|
borderColor={theme.border}
|
||||||
padding={0}
|
padding={0}
|
||||||
backgroundColor={
|
backgroundColor={
|
||||||
focusArea() === "explicit" ? theme.primary : undefined
|
focusArea() === "explicit" ? theme.primary : undefined
|
||||||
@@ -293,7 +296,7 @@ export function SourceManager(props: SourceManagerProps) {
|
|||||||
</box>
|
</box>
|
||||||
|
|
||||||
{/* Add new source form */}
|
{/* Add new source form */}
|
||||||
<box border padding={1} flexDirection="column" gap={1}>
|
<box border borderColor={theme.border} padding={1} flexDirection="column" gap={1}>
|
||||||
<text
|
<text
|
||||||
fg={
|
fg={
|
||||||
focusArea() === "add" || focusArea() === "url"
|
focusArea() === "add" || focusArea() === "url"
|
||||||
@@ -329,7 +332,7 @@ export function SourceManager(props: SourceManagerProps) {
|
|||||||
/>
|
/>
|
||||||
</box>
|
</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>
|
<text fg={theme.success}>[+] Add Source</text>
|
||||||
</box>
|
</box>
|
||||||
</box>
|
</box>
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
|
import { useTheme } from "@/context/ThemeContext"
|
||||||
|
|
||||||
type SyncErrorProps = {
|
type SyncErrorProps = {
|
||||||
message: string
|
message: string
|
||||||
onRetry: () => void
|
onRetry: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SyncError(props: SyncErrorProps) {
|
export function SyncError(props: SyncErrorProps) {
|
||||||
|
const { theme } = useTheme();
|
||||||
return (
|
return (
|
||||||
<box border title="Error" style={{ padding: 1, flexDirection: "column", gap: 1 }}>
|
<box border title="Error" style={{ padding: 1, flexDirection: "column", gap: 1 }}>
|
||||||
<text>{props.message}</text>
|
<text fg={theme.text}>{props.message}</text>
|
||||||
<box border onMouseDown={props.onRetry}>
|
<box border borderColor={theme.border} onMouseDown={props.onRetry}>
|
||||||
<text>Retry</text>
|
<text fg={theme.text}>Retry</text>
|
||||||
</box>
|
</box>
|
||||||
</box>
|
</box>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,18 +8,20 @@ const createSignal = <T,>(value: T): [() => T, (next: T) => void] => {
|
|||||||
import { ImportDialog } from "./ImportDialog"
|
import { ImportDialog } from "./ImportDialog"
|
||||||
import { ExportDialog } from "./ExportDialog"
|
import { ExportDialog } from "./ExportDialog"
|
||||||
import { SyncStatus } from "./SyncStatus"
|
import { SyncStatus } from "./SyncStatus"
|
||||||
|
import { useTheme } from "@/context/ThemeContext"
|
||||||
|
|
||||||
export function SyncPanel() {
|
export function SyncPanel() {
|
||||||
|
const { theme } = useTheme();
|
||||||
const mode = createSignal<"import" | "export" | null>(null)
|
const mode = createSignal<"import" | "export" | null>(null)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<box style={{ flexDirection: "column", gap: 1 }}>
|
<box style={{ flexDirection: "column", gap: 1 }}>
|
||||||
<box style={{ flexDirection: "row", gap: 1 }}>
|
<box style={{ flexDirection: "row", gap: 1 }}>
|
||||||
<box border onMouseDown={() => mode[1]("import")}>
|
<box border borderColor={theme.border} onMouseDown={() => mode[1]("import")}>
|
||||||
<text>Import</text>
|
<text fg={theme.text}>Import</text>
|
||||||
</box>
|
</box>
|
||||||
<box border onMouseDown={() => mode[1]("export")}>
|
<box border borderColor={theme.border} onMouseDown={() => mode[1]("export")}>
|
||||||
<text>Export</text>
|
<text fg={theme.text}>Export</text>
|
||||||
</box>
|
</box>
|
||||||
</box>
|
</box>
|
||||||
<SyncStatus />
|
<SyncStatus />
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
|
import { useTheme } from "@/context/ThemeContext"
|
||||||
|
|
||||||
type SyncProgressProps = {
|
type SyncProgressProps = {
|
||||||
value: number
|
value: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SyncProgress(props: SyncProgressProps) {
|
export function SyncProgress(props: SyncProgressProps) {
|
||||||
|
const { theme } = useTheme();
|
||||||
const width = 30
|
const width = 30
|
||||||
let filled = (props.value / 100) * width
|
let filled = (props.value / 100) * width
|
||||||
filled = filled >= 0 ? filled : 0
|
filled = filled >= 0 ? filled : 0
|
||||||
@@ -18,8 +21,8 @@ export function SyncProgress(props: SyncProgressProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<box style={{ flexDirection: "column" }}>
|
<box style={{ flexDirection: "column" }}>
|
||||||
<text>{bar}</text>
|
<text fg={theme.text}>{bar}</text>
|
||||||
<text>{props.value}%</text>
|
<text fg={theme.text}>{props.value}%</text>
|
||||||
</box>
|
</box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ const createSignal = <T,>(value: T): [() => T, (next: T) => void] => {
|
|||||||
|
|
||||||
import { SyncProgress } from "./SyncProgress"
|
import { SyncProgress } from "./SyncProgress"
|
||||||
import { SyncError } from "./SyncError"
|
import { SyncError } from "./SyncError"
|
||||||
|
import { useTheme } from "@/context/ThemeContext"
|
||||||
|
|
||||||
type SyncState = "idle" | "syncing" | "complete" | "error"
|
type SyncState = "idle" | "syncing" | "complete" | "error"
|
||||||
|
|
||||||
export function SyncStatus() {
|
export function SyncStatus() {
|
||||||
|
const { theme } = useTheme();
|
||||||
const state = createSignal<SyncState>("idle")
|
const state = createSignal<SyncState>("idle")
|
||||||
const message = createSignal("Idle")
|
const message = createSignal("Idle")
|
||||||
const progress = createSignal(0)
|
const progress = createSignal(0)
|
||||||
@@ -35,15 +37,15 @@ export function SyncStatus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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 }}>
|
<box style={{ flexDirection: "row", gap: 1 }}>
|
||||||
<text>Status:</text>
|
<text fg={theme.text}>Status:</text>
|
||||||
<text>{message[0]()}</text>
|
<text fg={theme.text}>{message[0]()}</text>
|
||||||
</box>
|
</box>
|
||||||
<SyncProgress value={progress[0]()} />
|
<SyncProgress value={progress[0]()} />
|
||||||
{state[0]() === "error" ? <SyncError message={message[0]()} onRetry={() => toggle()} /> : null}
|
{state[0]() === "error" ? <SyncError message={message[0]()} onRetry={() => toggle()} /> : null}
|
||||||
<box border onMouseDown={toggle}>
|
<box border borderColor={theme.border} onMouseDown={toggle}>
|
||||||
<text>Cycle Status</text>
|
<text fg={theme.text}>Cycle Status</text>
|
||||||
</box>
|
</box>
|
||||||
</box>
|
</box>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ export function VisualizerSettings() {
|
|||||||
<text fg={focusField() === "bars" ? theme.primary : theme.textMuted}>
|
<text fg={focusField() === "bars" ? theme.primary : theme.textMuted}>
|
||||||
Bars:
|
Bars:
|
||||||
</text>
|
</text>
|
||||||
<box border padding={0}>
|
<box border borderColor={theme.border} padding={0}>
|
||||||
<text fg={theme.text}>{viz().bars}</text>
|
<text fg={theme.text}>{viz().bars}</text>
|
||||||
</box>
|
</box>
|
||||||
<text fg={theme.textMuted}>[Left/Right +/-8]</text>
|
<text fg={theme.textMuted}>[Left/Right +/-8]</text>
|
||||||
@@ -113,7 +113,7 @@ export function VisualizerSettings() {
|
|||||||
>
|
>
|
||||||
Auto Sensitivity:
|
Auto Sensitivity:
|
||||||
</text>
|
</text>
|
||||||
<box border padding={0}>
|
<box border borderColor={theme.border} padding={0}>
|
||||||
<text
|
<text
|
||||||
fg={viz().sensitivity === 1 ? theme.success : theme.textMuted}
|
fg={viz().sensitivity === 1 ? theme.success : theme.textMuted}
|
||||||
>
|
>
|
||||||
@@ -127,7 +127,7 @@ export function VisualizerSettings() {
|
|||||||
<text fg={focusField() === "noise" ? theme.primary : theme.textMuted}>
|
<text fg={focusField() === "noise" ? theme.primary : theme.textMuted}>
|
||||||
Noise Reduction:
|
Noise Reduction:
|
||||||
</text>
|
</text>
|
||||||
<box border padding={0}>
|
<box border borderColor={theme.border} padding={0}>
|
||||||
<text fg={theme.text}>{viz().noiseReduction.toFixed(2)}</text>
|
<text fg={theme.text}>{viz().noiseReduction.toFixed(2)}</text>
|
||||||
</box>
|
</box>
|
||||||
<text fg={theme.textMuted}>[Left/Right +/-0.05]</text>
|
<text fg={theme.textMuted}>[Left/Right +/-0.05]</text>
|
||||||
@@ -139,7 +139,7 @@ export function VisualizerSettings() {
|
|||||||
>
|
>
|
||||||
Low Cutoff:
|
Low Cutoff:
|
||||||
</text>
|
</text>
|
||||||
<box border padding={0}>
|
<box border borderColor={theme.border} padding={0}>
|
||||||
<text fg={theme.text}>{viz().lowCutOff} Hz</text>
|
<text fg={theme.text}>{viz().lowCutOff} Hz</text>
|
||||||
</box>
|
</box>
|
||||||
<text fg={theme.textMuted}>[Left/Right +/-10]</text>
|
<text fg={theme.textMuted}>[Left/Right +/-10]</text>
|
||||||
@@ -151,7 +151,7 @@ export function VisualizerSettings() {
|
|||||||
>
|
>
|
||||||
High Cutoff:
|
High Cutoff:
|
||||||
</text>
|
</text>
|
||||||
<box border padding={0}>
|
<box border borderColor={theme.border} padding={0}>
|
||||||
<text fg={theme.text}>{viz().highCutOff} Hz</text>
|
<text fg={theme.text}>{viz().highCutOff} Hz</text>
|
||||||
</box>
|
</box>
|
||||||
<text fg={theme.textMuted}>[Left/Right +/-500]</text>
|
<text fg={theme.textMuted}>[Left/Right +/-500]</text>
|
||||||
|
|||||||
Reference in New Issue
Block a user