redoing navigation logic to favor more local

This commit is contained in:
2026-02-19 15:59:50 -05:00
parent 8e0f90f449
commit 1c65c85d02
9 changed files with 230 additions and 266 deletions

View File

@@ -1,4 +1,4 @@
import { createSignal, createMemo, ErrorBoundary, Accessor } from "solid-js";
import { createMemo, ErrorBoundary, Accessor } from "solid-js";
import { useKeyboard, useSelectionHandler } from "@opentui/solid";
import { TabNavigation } from "./components/TabNavigation";
import { CodeValidation } from "@/components/CodeValidation";
@@ -15,25 +15,13 @@ import type { Episode } from "@/types/episode";
import { DIRECTION, LayerGraph, TABS, LayerDepths } from "./utils/navigation";
import { useTheme, ThemeProvider } from "./context/ThemeContext";
import { KeybindProvider, useKeybinds } from "./context/KeybindContext";
import { NavigationProvider, useNavigation } from "./context/NavigationContext";
import { useAudioNavStore, AudioSource } from "./stores/audio-nav";
const DEBUG = import.meta.env.DEBUG;
export interface PageProps {
depth: Accessor<number>;
focusedIndex?: Accessor<number> | number;
focusedIndexValue?: number;
}
export function App() {
const [activeTab, setActiveTab] = createSignal<TABS>(TABS.FEED);
const [activeDepth, setActiveDepth] = createSignal(0); // not fixed matrix size
const [authScreen, setAuthScreen] = createSignal<AuthScreen>("login");
const [showAuthPanel, setShowAuthPanel] = createSignal(false);
const [inputFocused, setInputFocused] = createSignal(false);
const [layerDepth, setLayerDepth] = createSignal(0);
const [focusedIndex, setFocusedIndex] = createSignal(0);
const nav = useNavigation();
const auth = useAuthStore();
const feedStore = useFeedStore();
const audio = useAudio();
@@ -44,15 +32,15 @@ export function App() {
const audioNav = useAudioNavStore();
useMultimediaKeys({
playerFocused: () => activeTab() === TABS.PLAYER && layerDepth() > 0,
inputFocused: () => inputFocused(),
playerFocused: () => nav.activeTab === TABS.PLAYER && nav.activeDepth > 0,
inputFocused: () => nav.inputFocused,
hasEpisode: () => !!audio.currentEpisode(),
});
const handlePlayEpisode = (episode: Episode) => {
audio.play(episode);
setActiveTab(TABS.PLAYER);
setLayerDepth(1);
nav.setActiveTab(TABS.PLAYER);
nav.setActiveDepth(1);
audioNav.setSource(AudioSource.FEED);
};
@@ -86,159 +74,83 @@ export function App() {
const isSeekForward = keybind.match("audio-seek-forward", keyEvent);
const isSeekBackward = keybind.match("audio-seek-backward", keyEvent);
const isQuit = keybind.match("quit", keyEvent);
if (DEBUG) {
console.log("KeyEvent:", keyEvent);
console.log("Keybinds loaded:", {
up: keybind.keybinds.up,
down: keybind.keybinds.down,
left: keybind.keybinds.left,
right: keybind.keybinds.right,
});
}
if (isUp || isDown) {
const currentDepth = activeDepth();
const maxDepth = LayerDepths[activeTab()];
console.log("Navigation:", { isUp, isDown, currentDepth, maxDepth });
// Navigate within current depth layer
if (currentDepth < maxDepth) {
const newIndex = isUp ? focusedIndex() - 1 : focusedIndex() + 1;
setFocusedIndex(Math.max(0, Math.min(newIndex, maxDepth)));
}
}
// Horizontal movement - move within current layer
if (isLeft || isRight) {
const currentDepth = activeDepth();
const maxDepth = LayerDepths[activeTab()];
if (currentDepth < maxDepth) {
const newIndex = isLeft ? focusedIndex() - 1 : focusedIndex() + 1;
setFocusedIndex(Math.max(0, Math.min(newIndex, maxDepth)));
}
}
// Cycle through current depth
console.log({
up: isUp,
down: isDown,
left: isLeft,
right: isRight,
cycle: isCycle,
dive: isDive,
out: isOut,
audioToggle: isToggle,
audioNext: isNext,
audioPrev: isPrev,
audioSeekForward: isSeekForward,
audioSeekBackward: isSeekBackward,
quit: isQuit,
});
if (isCycle) {
const currentDepth = activeDepth();
const maxDepth = LayerDepths[activeTab()];
if (currentDepth < maxDepth) {
const newIndex = (focusedIndex() + 1) % (maxDepth + 1);
setFocusedIndex(newIndex);
}
}
// Increase depth
if (isDive) {
const currentDepth = activeDepth();
const maxDepth = LayerDepths[activeTab()];
if (currentDepth < maxDepth) {
setActiveDepth(currentDepth + 1);
setFocusedIndex(0);
}
}
// Decrease depth
if (isOut) {
const currentDepth = activeDepth();
if (currentDepth > 0) {
setActiveDepth(currentDepth - 1);
setFocusedIndex(0);
}
}
if (isToggle) {
audio.togglePlayback();
}
if (isNext) {
audio.next();
}
if (isPrev) {
audio.prev();
}
if (isSeekForward) {
audio.seekRelative(15);
}
if (isSeekBackward) {
audio.seekRelative(-15);
}
// Quit application
if (isQuit) {
process.exit(0);
}
// only handling top
},
{ release: false },
);
return (
<KeybindProvider>
<ThemeProvider mode="dark">
<ErrorBoundary
fallback={(err) => (
<box border padding={2} borderColor={theme.error}>
<text fg={theme.error}>
Error: {err?.message ?? String(err)}
{"\n"}
Press a number key (1-6) to switch tabs.
</text>
</box>
)}
>
{DEBUG && (
<box flexDirection="row" width="100%" height={1}>
<text fg={theme.primary}></text>
<text fg={theme.secondary}></text>
<text fg={theme.accent}></text>
<text fg={theme.error}></text>
<text fg={theme.warning}></text>
<text fg={theme.success}></text>
<text fg={theme.info}></text>
<text fg={theme.text}></text>
<text fg={theme.textMuted}></text>
<text fg={theme.surface}></text>
<text fg={theme.background}></text>
<text fg={theme.border}></text>
<text fg={theme.borderActive}></text>
<text fg={theme.diffAdded}></text>
<text fg={theme.diffRemoved}></text>
<text fg={theme.diffContext}></text>
<text fg={theme.markdownText}></text>
<text fg={theme.markdownHeading}></text>
<text fg={theme.markdownLink}></text>
<text fg={theme.markdownCode}></text>
<text fg={theme.syntaxKeyword}></text>
<text fg={theme.syntaxString}></text>
<text fg={theme.syntaxNumber}></text>
<text fg={theme.syntaxFunction}></text>
</box>
)}
<box flexDirection="row" width="100%" height={1} />
<box
flexDirection="row"
width="100%"
height="100%"
backgroundColor={theme.surface}
>
<TabNavigation activeTab={activeTab()} onTabSelect={setActiveTab} />
{LayerGraph[activeTab()]({
depth: activeDepth,
focusedIndex: focusedIndex(),
})}
{/** TODO: Contextual controls based on tab/depth**/}
</box>
</ErrorBoundary>
</ThemeProvider>
</KeybindProvider>
<ErrorBoundary
fallback={(err) => (
<box border padding={2} borderColor={theme.error}>
<text fg={theme.error}>
Error: {err?.message ?? String(err)}
{"\n"}
Press a number key (1-6) to switch tabs.
</text>
</box>
)}
>
{DEBUG && (
<box flexDirection="row" width="100%" height={1}>
<text fg={theme.primary}></text>
<text fg={theme.secondary}></text>
<text fg={theme.accent}></text>
<text fg={theme.error}></text>
<text fg={theme.warning}></text>
<text fg={theme.success}></text>
<text fg={theme.info}></text>
<text fg={theme.text}></text>
<text fg={theme.textMuted}></text>
<text fg={theme.surface}></text>
<text fg={theme.background}></text>
<text fg={theme.border}></text>
<text fg={theme.borderActive}></text>
<text fg={theme.diffAdded}></text>
<text fg={theme.diffRemoved}></text>
<text fg={theme.diffContext}></text>
<text fg={theme.markdownText}></text>
<text fg={theme.markdownHeading}></text>
<text fg={theme.markdownLink}></text>
<text fg={theme.markdownCode}></text>
<text fg={theme.syntaxKeyword}></text>
<text fg={theme.syntaxString}></text>
<text fg={theme.syntaxNumber}></text>
<text fg={theme.syntaxFunction}></text>
</box>
)}
<box flexDirection="row" width="100%" height={1} />
<box
flexDirection="row"
width="100%"
height="100%"
backgroundColor={theme.surface}
>
<TabNavigation
activeTab={nav.activeTab}
onTabSelect={nav.setActiveTab}
/>
{LayerGraph[nav.activeTab]()}
{/** TODO: Contextual controls based on tab/depth**/}
</box>
</ErrorBoundary>
);
}