import { useCallback, useEffect, useRef } from 'react'; import { markAllRead } from '../../fetchers/article'; import { fetchAllFeeds, fetchFeed, removeFeed } from '../../fetchers/feed'; import { isTuiShutdownError, useTuiLifecycle } from '../lifecycle'; import { dispatchError, useAppDispatch, useAppState } from '../state/context'; import type { ArticleWithFeed, FeedWithCount } from '../state/types'; import { getArticleLoadState, isSameArticleLoadState, markRead, refreshVisibleData, runFeedFetchSession, toggleRead, toggleStar, } from './useDataLoader'; export function useTuiActions() { const state = useAppState(); const dispatch = useAppDispatch(); const lifecycle = useTuiLifecycle(); const stateRef = useRef(state); const mountedRef = useRef(true); useEffect(() => { stateRef.current = state; }, [state]); useEffect(() => { mountedRef.current = true; return () => { mountedRef.current = false; }; }, []); const canDispatch = useCallback(() => mountedRef.current && lifecycle.isAcceptingWork(), [lifecycle]); const reloadVisibleData = useCallback(async () => { const criteria = getArticleLoadState(stateRef.current); await refreshVisibleData(criteria, dispatch, { canDispatch: () => canDispatch() && isSameArticleLoadState(criteria, getArticleLoadState(stateRef.current)), }); }, [canDispatch, dispatch]); const markAllReadForFeed = useCallback( (feedId: number | null) => { return lifecycle .run(async () => { await markAllRead(feedId ? { feedId } : {}); if (canDispatch()) await reloadVisibleData(); }) .catch((err) => { if (!isTuiShutdownError(err) && canDispatch()) dispatchError(dispatch, err); }); }, [canDispatch, dispatch, lifecycle, reloadVisibleData], ); const fetchOneFeed = useCallback( (feed: FeedWithCount) => { return lifecycle .run((signal) => runFeedFetchSession( dispatch, async (reportProgress) => { reportProgress({ current: 1, total: 1, feedTitle: feed.title }); await fetchFeed(feed, { force: true, signal }); if (canDispatch()) await reloadVisibleData(); }, canDispatch, ), ) .catch((err) => { if (!isTuiShutdownError(err) && canDispatch()) dispatchError(dispatch, err); }); }, [canDispatch, dispatch, lifecycle, reloadVisibleData], ); const fetchAll = useCallback(() => { return lifecycle .run((signal) => runFeedFetchSession( dispatch, async (reportProgress) => { await fetchAllFeeds({ signal }, (progress) => { reportProgress({ current: progress.current, total: progress.total, feedTitle: progress.feed?.title, }); }); if (canDispatch()) await reloadVisibleData(); }, canDispatch, ), ) .catch((err) => { if (!isTuiShutdownError(err) && canDispatch()) dispatchError(dispatch, err); }); }, [canDispatch, dispatch, lifecycle, reloadVisibleData]); const deleteFeed = useCallback( (feedId: number) => { return lifecycle .run(async () => { await removeFeed(feedId); if (canDispatch()) await reloadVisibleData(); }) .catch((err) => { if (isTuiShutdownError(err)) return; throw err; }); }, [canDispatch, lifecycle, reloadVisibleData], ); const openArticle = useCallback( (article: ArticleWithFeed) => { dispatch({ type: 'SET_SELECTED_ARTICLE', article }); dispatch({ type: 'SET_PANEL', panel: 'reader' }); if (!article.isRead) { void lifecycle .run(() => markRead(article.id, dispatch, canDispatch)) .catch((err) => { if (!isTuiShutdownError(err) && canDispatch()) dispatchError(dispatch, err); }); } }, [canDispatch, dispatch, lifecycle], ); const toggleArticleRead = useCallback( (article: ArticleWithFeed) => { void lifecycle .run(() => toggleRead(article.id, dispatch, canDispatch)) .catch((err) => { if (!isTuiShutdownError(err) && canDispatch()) dispatchError(dispatch, err); }); }, [canDispatch, dispatch, lifecycle], ); const toggleArticleStar = useCallback( (article: ArticleWithFeed) => { void lifecycle .run(() => toggleStar(article.id, dispatch, canDispatch)) .catch((err) => { if (!isTuiShutdownError(err) && canDispatch()) dispatchError(dispatch, err); }); }, [canDispatch, dispatch, lifecycle], ); return { deleteFeed, fetchAll, fetchOneFeed, markAllReadForFeed, openArticle, reloadVisibleData, toggleArticleRead, toggleArticleStar, }; }