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 { 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>
|
||||||
);
|
);
|
||||||
|
|||||||
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"],
|
"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": [],
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user