feat: feed UI, in-app player, offline mode, settings
Adds an end-to-end integration test that runs the real pipeline against live YouTube Atom feeds, verifying the merged feed is newest-first across channels.
This commit is contained in:
+79
@@ -0,0 +1,79 @@
|
||||
import { invoke, convertFileSrc } from "@tauri-apps/api/core";
|
||||
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
|
||||
import { open } from "@tauri-apps/plugin-dialog";
|
||||
import type {
|
||||
ChannelWithCount,
|
||||
DownloadProgressEvent,
|
||||
DownloadStateEvent,
|
||||
FeedFilter,
|
||||
FeedItem,
|
||||
Prereqs,
|
||||
RefreshProgress,
|
||||
RefreshSummary,
|
||||
} from "./types";
|
||||
|
||||
export const checkPrereqs = () => invoke<Prereqs>("check_prereqs");
|
||||
|
||||
export const listChannels = () => invoke<ChannelWithCount[]>("list_channels");
|
||||
|
||||
export const listFeed = (filter: FeedFilter) =>
|
||||
invoke<FeedItem[]>("list_feed", { filter });
|
||||
|
||||
export const refreshFeeds = () => invoke<RefreshSummary>("refresh_feeds");
|
||||
|
||||
export const downloadVideo = (videoId: string) =>
|
||||
invoke<void>("download_video", { videoId });
|
||||
|
||||
export const cancelDownload = (videoId: string) =>
|
||||
invoke<void>("cancel_download", { videoId });
|
||||
|
||||
export const deleteDownload = (videoId: string) =>
|
||||
invoke<void>("delete_download", { videoId });
|
||||
|
||||
export const getConnectivity = () => invoke<boolean>("get_connectivity");
|
||||
|
||||
export const setLibraryPath = (path: string) =>
|
||||
invoke<string>("set_library_path", { path });
|
||||
|
||||
export const openExternal = (url: string) =>
|
||||
invoke<void>("open_external", { url });
|
||||
|
||||
/** Opens the native file picker for a Takeout subscriptions.csv. */
|
||||
export async function pickAndImportTakeout(): Promise<number | null> {
|
||||
const path = await open({
|
||||
multiple: false,
|
||||
directory: false,
|
||||
filters: [{ name: "Takeout subscriptions", extensions: ["csv"] }],
|
||||
});
|
||||
if (typeof path !== "string") return null;
|
||||
return invoke<number>("import_takeout_csv", { path });
|
||||
}
|
||||
|
||||
export async function pickLibraryFolder(): Promise<string | null> {
|
||||
const path = await open({ directory: true, multiple: false });
|
||||
if (typeof path !== "string") return null;
|
||||
return setLibraryPath(path);
|
||||
}
|
||||
|
||||
export const onRefreshProgress = (cb: (p: RefreshProgress) => void) =>
|
||||
listen<RefreshProgress>("refresh:progress", (e) => cb(e.payload));
|
||||
|
||||
export const onDownloadProgress = (cb: (p: DownloadProgressEvent) => void) =>
|
||||
listen<DownloadProgressEvent>("download:progress", (e) => cb(e.payload));
|
||||
|
||||
export const onDownloadState = (cb: (p: DownloadStateEvent) => void) =>
|
||||
listen<DownloadStateEvent>("download:state", (e) => cb(e.payload));
|
||||
|
||||
export type { UnlistenFn };
|
||||
|
||||
/** Local file path -> a URL the webview is allowed to load. */
|
||||
export const fileUrl = (path: string) => convertFileSrc(path);
|
||||
|
||||
/**
|
||||
* Prefers the on-disk thumbnail so the feed still renders with no network,
|
||||
* falling back to the remote URL for videos not yet cached.
|
||||
*/
|
||||
export const thumbSrc = (item: FeedItem, online: boolean) => {
|
||||
if (item.thumb_path) return convertFileSrc(item.thumb_path);
|
||||
return online ? item.thumb_url : "";
|
||||
};
|
||||
Reference in New Issue
Block a user