nonworking indicator, sort broken

This commit is contained in:
2026-02-19 17:52:57 -05:00
parent 1c65c85d02
commit d1e1dd28b4
6 changed files with 116 additions and 41 deletions

View File

@@ -2,6 +2,7 @@ import { createMemo, ErrorBoundary, Accessor } from "solid-js";
import { useKeyboard, useSelectionHandler } from "@opentui/solid"; import { useKeyboard, useSelectionHandler } from "@opentui/solid";
import { TabNavigation } from "./components/TabNavigation"; import { TabNavigation } from "./components/TabNavigation";
import { CodeValidation } from "@/components/CodeValidation"; import { CodeValidation } from "@/components/CodeValidation";
import { LoadingIndicator } from "@/components/LoadingIndicator";
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";
@@ -89,10 +90,13 @@ export function App() {
audioSeekBackward: isSeekBackward, audioSeekBackward: isSeekBackward,
quit: isQuit, quit: isQuit,
}); });
if (isCycle) {
}
// only handling top // only handling top navigation here, cycle through tabs, just to high priority(player) all else to be handled in each tab
if (nav.activeDepth == 0) {
if (isCycle) {
nav.nextTab();
}
}
}, },
{ release: false }, { release: false },
); );
@@ -109,6 +113,7 @@ export function App() {
</box> </box>
)} )}
> >
<LoadingIndicator isLoading={feedStore.isLoadingFeeds()} />
{DEBUG && ( {DEBUG && (
<box flexDirection="row" width="100%" height={1}> <box flexDirection="row" width="100%" height={1}>
<text fg={theme.primary}></text> <text fg={theme.primary}></text>
@@ -149,7 +154,6 @@ export function App() {
onTabSelect={nav.setActiveTab} onTabSelect={nav.setActiveTab}
/> />
{LayerGraph[nav.activeTab]()} {LayerGraph[nav.activeTab]()}
{/** TODO: Contextual controls based on tab/depth**/}
</box> </box>
</ErrorBoundary> </ErrorBoundary>
); );

View File

@@ -0,0 +1,36 @@
/**
* Loading indicator component
* Displays an animated sliding bar at the top of the screen
*/
import { For } from "solid-js";
import { useTheme } from "@/context/ThemeContext";
interface LoadingIndicatorProps {
isLoading: boolean;
}
export function LoadingIndicator(props: LoadingIndicatorProps) {
const { theme } = useTheme();
if (!props.isLoading) return null;
return (
<box
flexDirection="row"
width="100%"
height={1}
backgroundColor={theme.background}
>
<For each={Array.from({ length: 10 })}>
{(_, index) => (
<box
width={2}
backgroundColor={theme.primary}
style={{ opacity: 0.1 + index() * 0.1 }}
/>
)}
</For>
</box>
);
}

View File

@@ -9,6 +9,7 @@
"inverse": ["shift"], "inverse": ["shift"],
"leader": ":", // will not trigger while focused on input "leader": ":", // will not trigger while focused on input
"quit": ["<leader>q"], "quit": ["<leader>q"],
"refresh": ["<leader>r"],
"audio-toggle": ["<leader>p"], "audio-toggle": ["<leader>p"],
"audio-pause": [], "audio-pause": [],
"audio-play": [], "audio-play": [],

View File

@@ -60,6 +60,7 @@ export const { use: useKeybinds, provider: KeybindProvider } =
inverse: [], inverse: [],
leader: "", leader: "",
quit: [], quit: [],
refresh: [],
"audio-toggle": [], "audio-toggle": [],
"audio-pause": [], "audio-pause": [],
"audio-play": [], "audio-play": [],
@@ -95,9 +96,6 @@ export const { use: useKeybinds, provider: KeybindProvider } =
for (const key of keys) { for (const key of keys) {
if (evt.name === key) return true; if (evt.name === key) return true;
if (evt.shift && key.toLowerCase() !== key) return false;
if (evt.ctrl && !key.toLowerCase().includes("ctrl")) return false;
if (evt.meta && !key.toLowerCase().includes("meta")) return false;
} }
return false; return false;
} }

View File

@@ -1,14 +1,33 @@
import { createSignal } from "solid-js"; import { createSignal } from "solid-js";
import { createSimpleContext } from "./helper"; import { createSimpleContext } from "./helper";
import { TABS } from "../utils/navigation"; import { TABS, TabsCount } from "@/utils/navigation";
export const { use: useNavigation, provider: NavigationProvider } = createSimpleContext({ export const { use: useNavigation, provider: NavigationProvider } =
createSimpleContext({
name: "Navigation", name: "Navigation",
init: () => { init: () => {
const [activeTab, setActiveTab] = createSignal<TABS>(TABS.FEED); const [activeTab, setActiveTab] = createSignal<TABS>(TABS.FEED);
const [activeDepth, setActiveDepth] = createSignal(0); const [activeDepth, setActiveDepth] = createSignal(0);
const [inputFocused, setInputFocused] = createSignal(false); const [inputFocused, setInputFocused] = createSignal(false);
//conveniences
const nextTab = () => {
if (activeTab() >= TabsCount) {
setActiveTab(1);
return;
}
setActiveTab(activeTab() + 1);
};
const prevTab = () => {
if (activeTab() <= 1) {
setActiveTab(TabsCount);
return;
}
setActiveTab(activeTab() - 1);
};
return { return {
get activeTab() { get activeTab() {
return activeTab(); return activeTab();
@@ -22,6 +41,8 @@ export const { use: useNavigation, provider: NavigationProvider } = createSimple
setActiveTab, setActiveTab,
setActiveDepth, setActiveDepth,
setInputFocused, setInputFocused,
nextTab,
prevTab,
}; };
}, },
}); });

View File

@@ -48,13 +48,6 @@ export function createFeedStore() {
const [sources, setSources] = createSignal<PodcastSource[]>([ const [sources, setSources] = createSignal<PodcastSource[]>([
...DEFAULT_SOURCES, ...DEFAULT_SOURCES,
]); ]);
(async () => {
const loadedFeeds = await loadFeedsFromFile();
if (loadedFeeds.length > 0) setFeeds(loadedFeeds);
const loadedSources = await loadSourcesFromFile<PodcastSource>();
if (loadedSources && loadedSources.length > 0) setSources(loadedSources);
})();
const [filter, setFilter] = createSignal<FeedFilter>({ const [filter, setFilter] = createSignal<FeedFilter>({
visibility: "all", visibility: "all",
sortBy: "updated" as FeedSortField, sortBy: "updated" as FeedSortField,
@@ -62,6 +55,7 @@ export function createFeedStore() {
}); });
const [selectedFeedId, setSelectedFeedId] = createSignal<string | null>(null); const [selectedFeedId, setSelectedFeedId] = createSignal<string | null>(null);
const [isLoadingMore, setIsLoadingMore] = createSignal(false); const [isLoadingMore, setIsLoadingMore] = createSignal(false);
const [isLoadingFeeds, setIsLoadingFeeds] = createSignal(false);
/** Get filtered and sorted feeds */ /** Get filtered and sorted feeds */
const getFilteredFeeds = (): Feed[] => { const getFilteredFeeds = (): Feed[] => {
@@ -148,6 +142,13 @@ export function createFeedStore() {
return allEpisodes; return allEpisodes;
}; };
/** Sort episodes in reverse chronological order (newest first) */
const sortEpisodesReverseChronological = (episodes: Episode[]): Episode[] => {
return [...episodes].sort(
(a, b) => b.pubDate.getTime() - a.pubDate.getTime(),
);
};
/** Fetch latest episodes from an RSS feed URL, caching all parsed episodes */ /** Fetch latest episodes from an RSS feed URL, caching all parsed episodes */
const fetchEpisodes = async ( const fetchEpisodes = async (
feedUrl: string, feedUrl: string,
@@ -164,7 +165,7 @@ export function createFeedStore() {
if (!response.ok) return []; if (!response.ok) return [];
const xml = await response.text(); const xml = await response.text();
const parsed = parseRSSFeed(xml, feedUrl); const parsed = parseRSSFeed(xml, feedUrl);
const allEpisodes = parsed.episodes; const allEpisodes = sortEpisodesReverseChronological(parsed.episodes);
// Cache all parsed episodes for pagination // Cache all parsed episodes for pagination
if (feedId) { if (feedId) {
@@ -264,12 +265,25 @@ export function createFeedStore() {
/** Refresh all feeds */ /** Refresh all feeds */
const refreshAllFeeds = async () => { const refreshAllFeeds = async () => {
setIsLoadingFeeds(true);
try {
const currentFeeds = feeds(); const currentFeeds = feeds();
for (const feed of currentFeeds) { for (const feed of currentFeeds) {
await refreshFeed(feed.id); await refreshFeed(feed.id);
} }
} finally {
setIsLoadingFeeds(false);
}
}; };
(async () => {
const loadedFeeds = await loadFeedsFromFile();
if (loadedFeeds.length > 0) setFeeds(loadedFeeds);
const loadedSources = await loadSourcesFromFile<PodcastSource>();
if (loadedSources && loadedSources.length > 0) setSources(loadedSources);
await refreshAllFeeds();
})();
/** Remove a feed */ /** Remove a feed */
const removeFeed = (feedId: string) => { const removeFeed = (feedId: string) => {
fullEpisodeCache.delete(feedId); fullEpisodeCache.delete(feedId);
@@ -445,6 +459,7 @@ export function createFeedStore() {
getFeed, getFeed,
getSelectedFeed, getSelectedFeed,
hasMoreEpisodes, hasMoreEpisodes,
isLoadingFeeds,
// Actions // Actions
setFilter, setFilter,