nonworking indicator, sort broken
This commit is contained in:
12
src/App.tsx
12
src/App.tsx
@@ -2,6 +2,7 @@ import { createMemo, ErrorBoundary, Accessor } from "solid-js";
|
||||
import { useKeyboard, useSelectionHandler } from "@opentui/solid";
|
||||
import { TabNavigation } from "./components/TabNavigation";
|
||||
import { CodeValidation } from "@/components/CodeValidation";
|
||||
import { LoadingIndicator } from "@/components/LoadingIndicator";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { useFeedStore } from "@/stores/feed";
|
||||
import { useAudio } from "@/hooks/useAudio";
|
||||
@@ -89,10 +90,13 @@ export function App() {
|
||||
audioSeekBackward: isSeekBackward,
|
||||
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 },
|
||||
);
|
||||
@@ -109,6 +113,7 @@ export function App() {
|
||||
</box>
|
||||
)}
|
||||
>
|
||||
<LoadingIndicator isLoading={feedStore.isLoadingFeeds()} />
|
||||
{DEBUG && (
|
||||
<box flexDirection="row" width="100%" height={1}>
|
||||
<text fg={theme.primary}>█</text>
|
||||
@@ -149,7 +154,6 @@ export function App() {
|
||||
onTabSelect={nav.setActiveTab}
|
||||
/>
|
||||
{LayerGraph[nav.activeTab]()}
|
||||
{/** TODO: Contextual controls based on tab/depth**/}
|
||||
</box>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
||||
36
src/components/LoadingIndicator.tsx
Normal file
36
src/components/LoadingIndicator.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
"inverse": ["shift"],
|
||||
"leader": ":", // will not trigger while focused on input
|
||||
"quit": ["<leader>q"],
|
||||
"refresh": ["<leader>r"],
|
||||
"audio-toggle": ["<leader>p"],
|
||||
"audio-pause": [],
|
||||
"audio-play": [],
|
||||
|
||||
@@ -60,6 +60,7 @@ export const { use: useKeybinds, provider: KeybindProvider } =
|
||||
inverse: [],
|
||||
leader: "",
|
||||
quit: [],
|
||||
refresh: [],
|
||||
"audio-toggle": [],
|
||||
"audio-pause": [],
|
||||
"audio-play": [],
|
||||
@@ -95,9 +96,6 @@ export const { use: useKeybinds, provider: KeybindProvider } =
|
||||
|
||||
for (const key of keys) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,27 +1,48 @@
|
||||
import { createSignal } from "solid-js";
|
||||
import { createSimpleContext } from "./helper";
|
||||
import { TABS } from "../utils/navigation";
|
||||
import { TABS, TabsCount } from "@/utils/navigation";
|
||||
|
||||
export const { use: useNavigation, provider: NavigationProvider } = createSimpleContext({
|
||||
name: "Navigation",
|
||||
init: () => {
|
||||
const [activeTab, setActiveTab] = createSignal<TABS>(TABS.FEED);
|
||||
const [activeDepth, setActiveDepth] = createSignal(0);
|
||||
const [inputFocused, setInputFocused] = createSignal(false);
|
||||
export const { use: useNavigation, provider: NavigationProvider } =
|
||||
createSimpleContext({
|
||||
name: "Navigation",
|
||||
init: () => {
|
||||
const [activeTab, setActiveTab] = createSignal<TABS>(TABS.FEED);
|
||||
const [activeDepth, setActiveDepth] = createSignal(0);
|
||||
const [inputFocused, setInputFocused] = createSignal(false);
|
||||
|
||||
return {
|
||||
get activeTab() {
|
||||
return activeTab();
|
||||
},
|
||||
get activeDepth() {
|
||||
return activeDepth();
|
||||
},
|
||||
get inputFocused() {
|
||||
return inputFocused();
|
||||
},
|
||||
setActiveTab,
|
||||
setActiveDepth,
|
||||
setInputFocused,
|
||||
};
|
||||
},
|
||||
});
|
||||
//conveniences
|
||||
const nextTab = () => {
|
||||
if (activeTab() >= TabsCount) {
|
||||
setActiveTab(1);
|
||||
return;
|
||||
}
|
||||
setActiveTab(activeTab() + 1);
|
||||
};
|
||||
|
||||
const prevTab = () => {
|
||||
if (activeTab() <= 1) {
|
||||
setActiveTab(TabsCount);
|
||||
return;
|
||||
}
|
||||
|
||||
setActiveTab(activeTab() - 1);
|
||||
};
|
||||
|
||||
return {
|
||||
get activeTab() {
|
||||
return activeTab();
|
||||
},
|
||||
get activeDepth() {
|
||||
return activeDepth();
|
||||
},
|
||||
get inputFocused() {
|
||||
return inputFocused();
|
||||
},
|
||||
setActiveTab,
|
||||
setActiveDepth,
|
||||
setInputFocused,
|
||||
nextTab,
|
||||
prevTab,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -48,13 +48,6 @@ export function createFeedStore() {
|
||||
const [sources, setSources] = createSignal<PodcastSource[]>([
|
||||
...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>({
|
||||
visibility: "all",
|
||||
sortBy: "updated" as FeedSortField,
|
||||
@@ -62,6 +55,7 @@ export function createFeedStore() {
|
||||
});
|
||||
const [selectedFeedId, setSelectedFeedId] = createSignal<string | null>(null);
|
||||
const [isLoadingMore, setIsLoadingMore] = createSignal(false);
|
||||
const [isLoadingFeeds, setIsLoadingFeeds] = createSignal(false);
|
||||
|
||||
/** Get filtered and sorted feeds */
|
||||
const getFilteredFeeds = (): Feed[] => {
|
||||
@@ -148,6 +142,13 @@ export function createFeedStore() {
|
||||
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 */
|
||||
const fetchEpisodes = async (
|
||||
feedUrl: string,
|
||||
@@ -164,7 +165,7 @@ export function createFeedStore() {
|
||||
if (!response.ok) return [];
|
||||
const xml = await response.text();
|
||||
const parsed = parseRSSFeed(xml, feedUrl);
|
||||
const allEpisodes = parsed.episodes;
|
||||
const allEpisodes = sortEpisodesReverseChronological(parsed.episodes);
|
||||
|
||||
// Cache all parsed episodes for pagination
|
||||
if (feedId) {
|
||||
@@ -264,12 +265,25 @@ export function createFeedStore() {
|
||||
|
||||
/** Refresh all feeds */
|
||||
const refreshAllFeeds = async () => {
|
||||
const currentFeeds = feeds();
|
||||
for (const feed of currentFeeds) {
|
||||
await refreshFeed(feed.id);
|
||||
setIsLoadingFeeds(true);
|
||||
try {
|
||||
const currentFeeds = feeds();
|
||||
for (const feed of currentFeeds) {
|
||||
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 */
|
||||
const removeFeed = (feedId: string) => {
|
||||
fullEpisodeCache.delete(feedId);
|
||||
@@ -445,6 +459,7 @@ export function createFeedStore() {
|
||||
getFeed,
|
||||
getSelectedFeed,
|
||||
hasMoreEpisodes,
|
||||
isLoadingFeeds,
|
||||
|
||||
// Actions
|
||||
setFilter,
|
||||
|
||||
Reference in New Issue
Block a user