renders
This commit is contained in:
14
src/App.tsx
14
src/App.tsx
@@ -1,15 +1,9 @@
|
|||||||
import { createSignal, createMemo, ErrorBoundary, Accessor } from "solid-js";
|
import { createSignal, createMemo, ErrorBoundary, Accessor } from "solid-js";
|
||||||
import { useSelectionHandler } from "@opentui/solid";
|
import { useSelectionHandler } from "@opentui/solid";
|
||||||
import { TabNavigation } from "./components/TabNavigation";
|
import { TabNavigation } from "./components/TabNavigation";
|
||||||
import { FeedPage } from "@/tabs/Feed/FeedPage";
|
|
||||||
import { MyShowsPage } from "@/tabs/MyShows/MyShowsPage";
|
|
||||||
import { LoginScreen } from "@/tabs/Settings/LoginScreen";
|
|
||||||
import { CodeValidation } from "@/components/CodeValidation";
|
import { CodeValidation } from "@/components/CodeValidation";
|
||||||
import { OAuthPlaceholder } from "@/tabs/Settings/OAuthPlaceholder";
|
|
||||||
import { SyncProfile } from "@/tabs/Settings/SyncProfile";
|
|
||||||
import { SearchPage } from "@/tabs/Search/SearchPage";
|
|
||||||
import { DiscoverPage } from "@/tabs/Discover/DiscoverPage";
|
|
||||||
import { SettingsScreen } from "@/tabs/Settings/SettingsScreen";
|
|
||||||
import { useAuthStore } from "@/stores/auth";
|
import { useAuthStore } from "@/stores/auth";
|
||||||
import { useFeedStore } from "@/stores/feed";
|
import { useFeedStore } from "@/stores/feed";
|
||||||
import { useAudio } from "@/hooks/useAudio";
|
import { useAudio } from "@/hooks/useAudio";
|
||||||
@@ -21,8 +15,7 @@ import { useToast } from "@/ui/toast";
|
|||||||
import { useRenderer } from "@opentui/solid";
|
import { useRenderer } from "@opentui/solid";
|
||||||
import type { AuthScreen } from "@/types/auth";
|
import type { AuthScreen } from "@/types/auth";
|
||||||
import type { Episode } from "@/types/episode";
|
import type { Episode } from "@/types/episode";
|
||||||
import { DIRECTION } from "./types/navigation";
|
import { DIRECTION, LayerGraph, TABS } from "./utils/navigation";
|
||||||
import { LayerGraph, TABS } from "./utils/navigation";
|
|
||||||
import { useTheme } from "./context/ThemeContext";
|
import { useTheme } from "./context/ThemeContext";
|
||||||
|
|
||||||
export interface PageProps {
|
export interface PageProps {
|
||||||
@@ -119,6 +112,7 @@ export function App() {
|
|||||||
>
|
>
|
||||||
<TabNavigation activeTab={activeTab()} onTabSelect={setActiveTab} />
|
<TabNavigation activeTab={activeTab()} onTabSelect={setActiveTab} />
|
||||||
{LayerGraph[activeTab()]({ depth: activeDepth })}
|
{LayerGraph[activeTab()]({ depth: activeDepth })}
|
||||||
|
{/**TODO: Contextual controls based on tab/depth**/}
|
||||||
</box>
|
</box>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ import type { Feed } from "@/types/feed";
|
|||||||
import { useTheme } from "@/context/ThemeContext";
|
import { useTheme } from "@/context/ThemeContext";
|
||||||
import { PageProps } from "@/App";
|
import { PageProps } from "@/App";
|
||||||
|
|
||||||
|
enum FeedPaneType {
|
||||||
|
FEED = 1,
|
||||||
|
}
|
||||||
export const FeedPaneCount = 1;
|
export const FeedPaneCount = 1;
|
||||||
|
|
||||||
export function FeedPage(props: PageProps) {
|
export function FeedPage(props: PageProps) {
|
||||||
@@ -60,7 +63,7 @@ export function FeedPage(props: PageProps) {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{/**TODO: figure out wtf to do here **/}
|
{/**TODO: figure out wtf to do here **/}
|
||||||
<scrollbox height="100%" focused={true}>
|
<scrollbox height="100%" focused={props.depth() == FeedPaneType.FEED}>
|
||||||
<For each={allEpisodes()}>
|
<For each={allEpisodes()}>
|
||||||
{(item, index) => (
|
{(item, index) => (
|
||||||
<box
|
<box
|
||||||
|
|||||||
@@ -15,16 +15,15 @@ import type { Feed } from "@/types/feed";
|
|||||||
import { PageProps } from "@/App";
|
import { PageProps } from "@/App";
|
||||||
|
|
||||||
enum MyShowsPaneType {
|
enum MyShowsPaneType {
|
||||||
SHOWS,
|
SHOWS = 1,
|
||||||
EPISODES,
|
EPISODES = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MyShowsPaneCount = 2
|
export const MyShowsPaneCount = 2;
|
||||||
|
|
||||||
export function MyShowsPage(props: PageProps) {
|
export function MyShowsPage(props: PageProps) {
|
||||||
const feedStore = useFeedStore();
|
const feedStore = useFeedStore();
|
||||||
const downloadStore = useDownloadStore();
|
const downloadStore = useDownloadStore();
|
||||||
const [focusPane, setFocusPane] = createSignal<>("shows");
|
|
||||||
const [showIndex, setShowIndex] = createSignal(0);
|
const [showIndex, setShowIndex] = createSignal(0);
|
||||||
const [episodeIndex, setEpisodeIndex] = createSignal(0);
|
const [episodeIndex, setEpisodeIndex] = createSignal(0);
|
||||||
const [isRefreshing, setIsRefreshing] = createSignal(false);
|
const [isRefreshing, setIsRefreshing] = createSignal(false);
|
||||||
@@ -128,8 +127,8 @@ export function MyShowsPage(props: PageProps) {
|
|||||||
setEpisodeIndex(0);
|
setEpisodeIndex(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return (
|
||||||
showsPanel: () => (
|
<box flexDirection="row" flexGrow={1}>
|
||||||
<box flexDirection="column" height="100%">
|
<box flexDirection="column" height="100%">
|
||||||
<Show when={isRefreshing()}>
|
<Show when={isRefreshing()}>
|
||||||
<text fg="yellow">Refreshing...</text>
|
<text fg="yellow">Refreshing...</text>
|
||||||
@@ -144,7 +143,10 @@ export function MyShowsPage(props: PageProps) {
|
|||||||
</box>
|
</box>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<scrollbox height="100%" focused={props.depth}>
|
<scrollbox
|
||||||
|
height="100%"
|
||||||
|
focused={props.depth() == MyShowsPaneType.SHOWS}
|
||||||
|
>
|
||||||
<For each={shows()}>
|
<For each={shows()}>
|
||||||
{(feed, index) => (
|
{(feed, index) => (
|
||||||
<box
|
<box
|
||||||
@@ -171,9 +173,6 @@ export function MyShowsPage(props: PageProps) {
|
|||||||
</scrollbox>
|
</scrollbox>
|
||||||
</Show>
|
</Show>
|
||||||
</box>
|
</box>
|
||||||
),
|
|
||||||
|
|
||||||
episodesPanel: () => (
|
|
||||||
<box flexDirection="column" height="100%">
|
<box flexDirection="column" height="100%">
|
||||||
<Show
|
<Show
|
||||||
when={selectedShow()}
|
when={selectedShow()}
|
||||||
@@ -193,7 +192,7 @@ export function MyShowsPage(props: PageProps) {
|
|||||||
>
|
>
|
||||||
<scrollbox
|
<scrollbox
|
||||||
height="100%"
|
height="100%"
|
||||||
focused={props.focused && focusPane() === "episodes"}
|
focused={props.depth() == MyShowsPaneType.EPISODES}
|
||||||
>
|
>
|
||||||
<For each={episodes()}>
|
<For each={episodes()}>
|
||||||
{(episode, index) => (
|
{(episode, index) => (
|
||||||
@@ -252,9 +251,6 @@ export function MyShowsPage(props: PageProps) {
|
|||||||
</Show>
|
</Show>
|
||||||
</Show>
|
</Show>
|
||||||
</box>
|
</box>
|
||||||
),
|
</box>
|
||||||
|
);
|
||||||
focusPane,
|
|
||||||
selectedShow,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
|
import { PageProps } from "@/App";
|
||||||
import { PlaybackControls } from "./PlaybackControls";
|
import { PlaybackControls } from "./PlaybackControls";
|
||||||
import { RealtimeWaveform } from "./RealtimeWaveform";
|
import { RealtimeWaveform } from "./RealtimeWaveform";
|
||||||
import { useAudio } from "@/hooks/useAudio";
|
import { useAudio } from "@/hooks/useAudio";
|
||||||
import { useAppStore } from "@/stores/app";
|
import { useAppStore } from "@/stores/app";
|
||||||
|
|
||||||
export function PlayerPage() {
|
enum PlayerPaneType {
|
||||||
|
PLAYER = 1,
|
||||||
|
}
|
||||||
|
export const PlayerPaneCount = 1;
|
||||||
|
|
||||||
|
export function PlayerPage(props: PageProps) {
|
||||||
const audio = useAudio();
|
const audio = useAudio();
|
||||||
|
|
||||||
const progressPercent = () => {
|
const progressPercent = () => {
|
||||||
@@ -63,11 +69,6 @@ export function PlayerPage() {
|
|||||||
onSpeedChange={(s: number) => audio.setSpeed(s)}
|
onSpeedChange={(s: number) => audio.setSpeed(s)}
|
||||||
onVolumeChange={(v: number) => audio.setVolume(v)}
|
onVolumeChange={(v: number) => audio.setVolume(v)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<text fg="gray">
|
|
||||||
Space play/pause | Left/Right seek 10s | Up/Down volume | S speed | Esc
|
|
||||||
back
|
|
||||||
</text>
|
|
||||||
</box>
|
</box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,35 +8,35 @@ import { useSearchStore } from "@/stores/search";
|
|||||||
import { SearchResults } from "./SearchResults";
|
import { SearchResults } from "./SearchResults";
|
||||||
import { SearchHistory } from "./SearchHistory";
|
import { SearchHistory } from "./SearchHistory";
|
||||||
import type { SearchResult } from "@/types/source";
|
import type { SearchResult } from "@/types/source";
|
||||||
|
import { PageProps } from "@/App";
|
||||||
|
import { MyShowsPage } from "../MyShows/MyShowsPage";
|
||||||
|
|
||||||
type SearchPageProps = {
|
enum SearchPaneType {
|
||||||
focused: boolean;
|
INPUT = 1,
|
||||||
onSubscribe?: (result: SearchResult) => void;
|
RESULTS = 2,
|
||||||
onInputFocusChange?: (focused: boolean) => void;
|
HISTORY = 3,
|
||||||
onExit?: () => void;
|
}
|
||||||
};
|
export const SearchPaneCount = 3;
|
||||||
|
|
||||||
type FocusArea = "input" | "results" | "history";
|
export function SearchPage(props: PageProps) {
|
||||||
|
|
||||||
export function SearchPage(props: SearchPageProps) {
|
|
||||||
const searchStore = useSearchStore();
|
const searchStore = useSearchStore();
|
||||||
const [focusArea, setFocusArea] = createSignal<FocusArea>("input");
|
|
||||||
const [inputValue, setInputValue] = createSignal("");
|
const [inputValue, setInputValue] = createSignal("");
|
||||||
const [resultIndex, setResultIndex] = createSignal(0);
|
const [resultIndex, setResultIndex] = createSignal(0);
|
||||||
const [historyIndex, setHistoryIndex] = createSignal(0);
|
const [historyIndex, setHistoryIndex] = createSignal(0);
|
||||||
|
|
||||||
// Keep parent informed about input focus state
|
// Keep parent informed about input focus state
|
||||||
createEffect(() => {
|
// TODO: have a global input focused prop in useKeyboard hook
|
||||||
const isInputFocused = props.focused && focusArea() === "input";
|
//createEffect(() => {
|
||||||
props.onInputFocusChange?.(isInputFocused);
|
//const isInputFocused = props.focused && focusArea() === "input";
|
||||||
});
|
//props.onInputFocusChange?.(isInputFocused);
|
||||||
|
//});
|
||||||
|
|
||||||
const handleSearch = async () => {
|
const handleSearch = async () => {
|
||||||
const query = inputValue().trim();
|
const query = inputValue().trim();
|
||||||
if (query) {
|
if (query) {
|
||||||
await searchStore.search(query);
|
await searchStore.search(query);
|
||||||
if (searchStore.results().length > 0) {
|
if (searchStore.results().length > 0) {
|
||||||
setFocusArea("results");
|
//setFocusArea("results"); //TODO: move level
|
||||||
setResultIndex(0);
|
setResultIndex(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,120 +46,16 @@ export function SearchPage(props: SearchPageProps) {
|
|||||||
setInputValue(query);
|
setInputValue(query);
|
||||||
await searchStore.search(query);
|
await searchStore.search(query);
|
||||||
if (searchStore.results().length > 0) {
|
if (searchStore.results().length > 0) {
|
||||||
setFocusArea("results");
|
//setFocusArea("results"); //TODO: move level
|
||||||
setResultIndex(0);
|
setResultIndex(0);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleResultSelect = (result: SearchResult) => {
|
const handleResultSelect = (result: SearchResult) => {
|
||||||
props.onSubscribe?.(result);
|
//props.onSubscribe?.(result);
|
||||||
searchStore.markSubscribed(result.podcast.id);
|
searchStore.markSubscribed(result.podcast.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Keyboard navigation
|
|
||||||
useKeyboard((key) => {
|
|
||||||
if (!props.focused) return;
|
|
||||||
|
|
||||||
const area = focusArea();
|
|
||||||
|
|
||||||
// Enter to search from input
|
|
||||||
if (key.name === "return" && area === "input") {
|
|
||||||
handleSearch();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tab to cycle focus areas
|
|
||||||
if (key.name === "tab" && !key.shift) {
|
|
||||||
if (area === "input") {
|
|
||||||
if (searchStore.results().length > 0) {
|
|
||||||
setFocusArea("results");
|
|
||||||
} else if (searchStore.history().length > 0) {
|
|
||||||
setFocusArea("history");
|
|
||||||
}
|
|
||||||
} else if (area === "results") {
|
|
||||||
if (searchStore.history().length > 0) {
|
|
||||||
setFocusArea("history");
|
|
||||||
} else {
|
|
||||||
setFocusArea("input");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setFocusArea("input");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key.name === "tab" && key.shift) {
|
|
||||||
if (area === "input") {
|
|
||||||
if (searchStore.history().length > 0) {
|
|
||||||
setFocusArea("history");
|
|
||||||
} else if (searchStore.results().length > 0) {
|
|
||||||
setFocusArea("results");
|
|
||||||
}
|
|
||||||
} else if (area === "history") {
|
|
||||||
if (searchStore.results().length > 0) {
|
|
||||||
setFocusArea("results");
|
|
||||||
} else {
|
|
||||||
setFocusArea("input");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setFocusArea("input");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Up/Down for results and history
|
|
||||||
if (area === "results") {
|
|
||||||
const results = searchStore.results();
|
|
||||||
if (key.name === "down" || key.name === "j") {
|
|
||||||
setResultIndex((i) => Math.min(i + 1, results.length - 1));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (key.name === "up" || key.name === "k") {
|
|
||||||
setResultIndex((i) => Math.max(i - 1, 0));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (key.name === "return" || key.name === "enter") {
|
|
||||||
const result = results[resultIndex()];
|
|
||||||
if (result) handleResultSelect(result);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (area === "history") {
|
|
||||||
const history = searchStore.history();
|
|
||||||
if (key.name === "down" || key.name === "j") {
|
|
||||||
setHistoryIndex((i) => Math.min(i + 1, history.length - 1));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (key.name === "up" || key.name === "k") {
|
|
||||||
setHistoryIndex((i) => Math.max(i - 1, 0));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (key.name === "return" || key.name === "enter") {
|
|
||||||
const query = history[historyIndex()];
|
|
||||||
if (query) handleHistorySelect(query);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Escape goes back to input or up one level
|
|
||||||
if (key.name === "escape") {
|
|
||||||
if (area === "input") {
|
|
||||||
props.onExit?.();
|
|
||||||
} else {
|
|
||||||
setFocusArea("input");
|
|
||||||
key.stopPropagation();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// "/" focuses search input
|
|
||||||
if (key.name === "/" && area !== "input") {
|
|
||||||
setFocusArea("input");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<box flexDirection="column" height="100%" gap={1}>
|
<box flexDirection="column" height="100%" gap={1}>
|
||||||
{/* Search Header */}
|
{/* Search Header */}
|
||||||
@@ -177,7 +73,7 @@ export function SearchPage(props: SearchPageProps) {
|
|||||||
setInputValue(value);
|
setInputValue(value);
|
||||||
}}
|
}}
|
||||||
placeholder="Enter podcast name, topic, or author..."
|
placeholder="Enter podcast name, topic, or author..."
|
||||||
focused={props.focused && focusArea() === "input"}
|
focused={props.depth() === SearchPaneType.INPUT}
|
||||||
width={50}
|
width={50}
|
||||||
/>
|
/>
|
||||||
<box
|
<box
|
||||||
@@ -205,7 +101,9 @@ export function SearchPage(props: SearchPageProps) {
|
|||||||
{/* Results Panel */}
|
{/* Results Panel */}
|
||||||
<box flexDirection="column" flexGrow={1} border>
|
<box flexDirection="column" flexGrow={1} border>
|
||||||
<box padding={1}>
|
<box padding={1}>
|
||||||
<text fg={focusArea() === "results" ? "cyan" : "gray"}>
|
<text
|
||||||
|
fg={props.depth() === SearchPaneType.RESULTS ? "cyan" : "gray"}
|
||||||
|
>
|
||||||
Results ({searchStore.results().length})
|
Results ({searchStore.results().length})
|
||||||
</text>
|
</text>
|
||||||
</box>
|
</box>
|
||||||
@@ -224,7 +122,7 @@ export function SearchPage(props: SearchPageProps) {
|
|||||||
<SearchResults
|
<SearchResults
|
||||||
results={searchStore.results()}
|
results={searchStore.results()}
|
||||||
selectedIndex={resultIndex()}
|
selectedIndex={resultIndex()}
|
||||||
focused={focusArea() === "results"}
|
focused={props.depth() === SearchPaneType.RESULTS}
|
||||||
onSelect={handleResultSelect}
|
onSelect={handleResultSelect}
|
||||||
onChange={setResultIndex}
|
onChange={setResultIndex}
|
||||||
isSearching={searchStore.isSearching()}
|
isSearching={searchStore.isSearching()}
|
||||||
@@ -237,14 +135,16 @@ export function SearchPage(props: SearchPageProps) {
|
|||||||
<box width={30} border>
|
<box width={30} border>
|
||||||
<box padding={1} flexDirection="column">
|
<box padding={1} flexDirection="column">
|
||||||
<box paddingBottom={1}>
|
<box paddingBottom={1}>
|
||||||
<text fg={focusArea() === "history" ? "cyan" : "gray"}>
|
<text
|
||||||
|
fg={props.depth() === SearchPaneType.HISTORY ? "cyan" : "gray"}
|
||||||
|
>
|
||||||
History
|
History
|
||||||
</text>
|
</text>
|
||||||
</box>
|
</box>
|
||||||
<SearchHistory
|
<SearchHistory
|
||||||
history={searchStore.history()}
|
history={searchStore.history()}
|
||||||
selectedIndex={historyIndex()}
|
selectedIndex={historyIndex()}
|
||||||
focused={focusArea() === "history"}
|
focused={props.depth() === SearchPaneType.HISTORY}
|
||||||
onSelect={handleHistorySelect}
|
onSelect={handleHistorySelect}
|
||||||
onRemove={searchStore.removeFromHistory}
|
onRemove={searchStore.removeFromHistory}
|
||||||
onClear={searchStore.clearHistory}
|
onClear={searchStore.clearHistory}
|
||||||
@@ -253,14 +153,6 @@ export function SearchPage(props: SearchPageProps) {
|
|||||||
</box>
|
</box>
|
||||||
</box>
|
</box>
|
||||||
</box>
|
</box>
|
||||||
|
|
||||||
{/* Footer Hints */}
|
|
||||||
<box flexDirection="row" gap={2}>
|
|
||||||
<text fg="gray">[Tab] Switch focus</text>
|
|
||||||
<text fg="gray">[/] Focus search</text>
|
|
||||||
<text fg="gray">[Enter] Select</text>
|
|
||||||
<text fg="gray">[Esc] Up</text>
|
|
||||||
</box>
|
|
||||||
</box>
|
</box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,65 +5,33 @@ import { useTheme } from "@/context/ThemeContext";
|
|||||||
import { PreferencesPanel } from "./PreferencesPanel";
|
import { PreferencesPanel } from "./PreferencesPanel";
|
||||||
import { SyncPanel } from "./SyncPanel";
|
import { SyncPanel } from "./SyncPanel";
|
||||||
import { VisualizerSettings } from "./VisualizerSettings";
|
import { VisualizerSettings } from "./VisualizerSettings";
|
||||||
|
import { PageProps } from "@/App";
|
||||||
|
|
||||||
type SettingsScreenProps = {
|
enum SettingsPaneType {
|
||||||
accountLabel: string;
|
SYNC = 1,
|
||||||
accountStatus: "signed-in" | "signed-out";
|
SOURCES = 2,
|
||||||
onOpenAccount?: () => void;
|
PREFERENCES = 3,
|
||||||
onExit?: () => void;
|
VISUALIZER = 4,
|
||||||
};
|
ACCOUNT = 5,
|
||||||
|
}
|
||||||
|
export const SettingsPaneCount = 5;
|
||||||
|
|
||||||
type SectionId = "sync" | "sources" | "preferences" | "visualizer" | "account";
|
const SECTIONS: Array<{ id: SettingsPaneType; label: string }> = [
|
||||||
|
{ id: SettingsPaneType.SYNC, label: "Sync" },
|
||||||
const SECTIONS: Array<{ id: SectionId; label: string }> = [
|
{ id: SettingsPaneType.SOURCES, label: "Sources" },
|
||||||
{ id: "sync", label: "Sync" },
|
{ id: SettingsPaneType.PREFERENCES, label: "Preferences" },
|
||||||
{ id: "sources", label: "Sources" },
|
{ id: SettingsPaneType.VISUALIZER, label: "Visualizer" },
|
||||||
{ id: "preferences", label: "Preferences" },
|
{ id: SettingsPaneType.ACCOUNT, label: "Account" },
|
||||||
{ id: "visualizer", label: "Visualizer" },
|
|
||||||
{ id: "account", label: "Account" },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export function SettingsPage(props: SettingsScreenProps) {
|
export function SettingsPage(props: PageProps) {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
const [activeSection, setActiveSection] = createSignal<SectionId>("sync");
|
const [activeSection, setActiveSection] = createSignal<SettingsPaneType>(
|
||||||
|
SettingsPaneType.SYNC,
|
||||||
useKeyboard((key) => {
|
);
|
||||||
if (key.name === "escape") {
|
|
||||||
props.onExit?.();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key.name === "tab") {
|
|
||||||
const idx = SECTIONS.findIndex((s) => s.id === activeSection());
|
|
||||||
const next = key.shift
|
|
||||||
? (idx - 1 + SECTIONS.length) % SECTIONS.length
|
|
||||||
: (idx + 1) % SECTIONS.length;
|
|
||||||
setActiveSection(SECTIONS[next].id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key.name === "1") setActiveSection("sync");
|
|
||||||
if (key.name === "2") setActiveSection("sources");
|
|
||||||
if (key.name === "3") setActiveSection("preferences");
|
|
||||||
if (key.name === "4") setActiveSection("visualizer");
|
|
||||||
if (key.name === "5") setActiveSection("account");
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<box flexDirection="column" gap={1} height="100%">
|
<box flexDirection="column" gap={1} height="100%">
|
||||||
<box
|
|
||||||
flexDirection="row"
|
|
||||||
justifyContent="space-between"
|
|
||||||
alignItems="center"
|
|
||||||
>
|
|
||||||
<text>
|
|
||||||
<strong>Settings</strong>
|
|
||||||
</text>
|
|
||||||
<text fg={theme.textMuted}>
|
|
||||||
[Tab] Switch section | 1-5 jump | Esc up
|
|
||||||
</text>
|
|
||||||
</box>
|
|
||||||
|
|
||||||
<box flexDirection="row" gap={1}>
|
<box flexDirection="row" gap={1}>
|
||||||
<For each={SECTIONS}>
|
<For each={SECTIONS}>
|
||||||
{(section, index) => (
|
{(section, index) => (
|
||||||
@@ -88,33 +56,22 @@ export function SettingsPage(props: SettingsScreenProps) {
|
|||||||
</box>
|
</box>
|
||||||
|
|
||||||
<box border flexGrow={1} padding={1} flexDirection="column" gap={1}>
|
<box border flexGrow={1} padding={1} flexDirection="column" gap={1}>
|
||||||
{activeSection() === "sync" && <SyncPanel />}
|
{activeSection() === SettingsPaneType.SYNC && <SyncPanel />}
|
||||||
{activeSection() === "sources" && <SourceManager focused />}
|
{activeSection() === SettingsPaneType.SOURCES && (
|
||||||
{activeSection() === "preferences" && <PreferencesPanel />}
|
<SourceManager focused />
|
||||||
{activeSection() === "visualizer" && <VisualizerSettings />}
|
)}
|
||||||
{activeSection() === "account" && (
|
{activeSection() === SettingsPaneType.PREFERENCES && (
|
||||||
|
<PreferencesPanel />
|
||||||
|
)}
|
||||||
|
{activeSection() === SettingsPaneType.VISUALIZER && (
|
||||||
|
<VisualizerSettings />
|
||||||
|
)}
|
||||||
|
{activeSection() === SettingsPaneType.ACCOUNT && (
|
||||||
<box flexDirection="column" gap={1}>
|
<box flexDirection="column" gap={1}>
|
||||||
<text fg={theme.textMuted}>Account</text>
|
<text fg={theme.textMuted}>Account</text>
|
||||||
<box flexDirection="row" gap={2} alignItems="center">
|
|
||||||
<text fg={theme.textMuted}>Status:</text>
|
|
||||||
<text
|
|
||||||
fg={
|
|
||||||
props.accountStatus === "signed-in"
|
|
||||||
? theme.success
|
|
||||||
: theme.warning
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{props.accountLabel}
|
|
||||||
</text>
|
|
||||||
</box>
|
|
||||||
<box border padding={0} onMouseDown={() => props.onOpenAccount?.()}>
|
|
||||||
<text fg={theme.primary}>[A] Manage Account</text>
|
|
||||||
</box>
|
|
||||||
</box>
|
</box>
|
||||||
)}
|
)}
|
||||||
</box>
|
</box>
|
||||||
|
|
||||||
<text fg={theme.textMuted}>Enter to dive | Esc up</text>
|
|
||||||
</box>
|
</box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
export enum DIRECTION {
|
|
||||||
Increment,
|
|
||||||
Decrement,
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
import { DiscoverPage } from "@/pages/Discover/DiscoverPage";
|
import { DiscoverPage, DiscoverPaneCount } from "@/pages/Discover/DiscoverPage";
|
||||||
import { FeedPage, FeedPaneCount } from "@/pages/Feed/FeedPage";
|
import { FeedPage, FeedPaneCount } from "@/pages/Feed/FeedPage";
|
||||||
import { MyShowsPage, MyShowsPaneCount } from "@/pages/MyShows/MyShowsPage";
|
import { MyShowsPage, MyShowsPaneCount } from "@/pages/MyShows/MyShowsPage";
|
||||||
import { PlayerPage } from "@/pages/Player/PlayerPage";
|
import { PlayerPage, PlayerPaneCount } from "@/pages/Player/PlayerPage";
|
||||||
import { SearchPage } from "@/pages/Search/SearchPage";
|
import { SearchPage, SearchPaneCount } from "@/pages/Search/SearchPage";
|
||||||
import { SettingsPage } from "@/pages/Settings/SettingsPage";
|
import { SettingsPage, SettingsPaneCount } from "@/pages/Settings/SettingsPage";
|
||||||
|
|
||||||
|
export enum DIRECTION {
|
||||||
|
Increment,
|
||||||
|
Decrement,
|
||||||
|
}
|
||||||
|
|
||||||
export enum TABS {
|
export enum TABS {
|
||||||
FEED,
|
FEED,
|
||||||
@@ -28,5 +33,5 @@ export const LayerDepths = {
|
|||||||
[TABS.DISCOVER]: DiscoverPaneCount,
|
[TABS.DISCOVER]: DiscoverPaneCount,
|
||||||
[TABS.SEARCH]: SearchPaneCount,
|
[TABS.SEARCH]: SearchPaneCount,
|
||||||
[TABS.PLAYER]: PlayerPaneCount,
|
[TABS.PLAYER]: PlayerPaneCount,
|
||||||
[TABS.SETTINGS]: SettingPaneCount,
|
[TABS.SETTINGS]: SettingsPaneCount,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user