You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
139 lines
4.8 KiB
139 lines
4.8 KiB
import LibraryPublicationGrid from '@/components/Library/LibraryPublicationGrid' |
|
import LibrarySearchBar from '@/components/Library/LibrarySearchBar' |
|
import { RefreshButton } from '@/components/RefreshButton' |
|
import { Button } from '@/components/ui/button' |
|
import PrimaryPageLayout, { TPrimaryPageLayoutRef } from '@/layouts/PrimaryPageLayout' |
|
import { useLibraryPublications } from '@/hooks/useLibraryPublications' |
|
import { LIBRARY_PAGE_SIZE } from '@/lib/library-publication-index' |
|
import { usePrimaryPage } from '@/contexts/primary-page-context' |
|
import { TPageRef } from '@/types' |
|
import { BookOpen } from 'lucide-react' |
|
import { forwardRef, useImperativeHandle, useMemo, useRef } from 'react' |
|
import { useTranslation } from 'react-i18next' |
|
|
|
const LibraryPage = forwardRef<TPageRef>((_props, ref) => { |
|
const { t } = useTranslation() |
|
const { current, display } = usePrimaryPage() |
|
const isActive = useMemo(() => current === 'library' && display, [current, display]) |
|
const layoutRef = useRef<TPrimaryPageLayoutRef>(null) |
|
const { |
|
entries, |
|
searchQuery, |
|
setSearchQuery, |
|
searchAxis, |
|
setSearchAxis, |
|
showOnlyMine, |
|
setShowOnlyMine, |
|
mineFilterLoading, |
|
loading, |
|
engagementLoading, |
|
searchLoading, |
|
relaySearchLoading, |
|
error, |
|
allIndexCount, |
|
topLevelCount, |
|
refresh, |
|
searchOnRelays, |
|
hasIndexData, |
|
loadMoreFeed, |
|
defaultFeedHasMore, |
|
feedTotalCount |
|
} = useLibraryPublications(isActive) |
|
|
|
useImperativeHandle( |
|
ref, |
|
() => ({ |
|
scrollToTop: (behavior: ScrollBehavior = 'smooth') => layoutRef.current?.scrollToTop(behavior), |
|
refresh |
|
}), |
|
[refresh] |
|
) |
|
|
|
const statusLine = |
|
!loading && !error |
|
? t('Library status line', { |
|
shown: entries.length, |
|
topLevel: topLevelCount, |
|
total: allIndexCount |
|
}) |
|
: null |
|
|
|
return ( |
|
<PrimaryPageLayout |
|
ref={layoutRef} |
|
pageName="library" |
|
titlebar={<LibraryPageTitlebar onRefresh={refresh} />} |
|
displayScrollToTopButton |
|
> |
|
<div className="min-w-0 px-4 pb-4 pt-4"> |
|
<div className="mb-4"> |
|
<LibrarySearchBar |
|
searchQuery={searchQuery} |
|
onSearchQueryChange={setSearchQuery} |
|
searchAxis={searchAxis} |
|
onSearchAxisChange={setSearchAxis} |
|
showOnlyMine={showOnlyMine} |
|
onShowOnlyMineChange={setShowOnlyMine} |
|
mineFilterLoading={mineFilterLoading} |
|
onSearchRelays={() => void searchOnRelays()} |
|
relaySearchLoading={relaySearchLoading} |
|
disabled={loading && !hasIndexData} |
|
/> |
|
</div> |
|
{error ? ( |
|
<div className="mb-4 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive"> |
|
{error} |
|
</div> |
|
) : null} |
|
{loading ? ( |
|
<p className="mb-4 text-xs text-muted-foreground">{t('Library loading')}</p> |
|
) : engagementLoading ? ( |
|
<p className="mb-4 text-xs text-muted-foreground">{t('Library engagement loading')}</p> |
|
) : searchLoading ? ( |
|
<p className="mb-4 text-xs text-muted-foreground">{t('Library search loading')}</p> |
|
) : mineFilterLoading ? ( |
|
<p className="mb-4 text-xs text-muted-foreground">{t('Library mine filter loading')}</p> |
|
) : relaySearchLoading ? ( |
|
<p className="mb-4 text-xs text-muted-foreground">{t('Library relay search loading')}</p> |
|
) : null} |
|
{statusLine ? ( |
|
<p className="mb-4 text-xs text-muted-foreground">{statusLine}</p> |
|
) : null} |
|
<LibraryPublicationGrid |
|
entries={entries} |
|
loading={ |
|
(loading && entries.length === 0 && !hasIndexData) || |
|
(showOnlyMine && mineFilterLoading) |
|
} |
|
emptyMessage={ |
|
searchQuery.trim() || showOnlyMine ? t('Library empty filtered') : t('Library empty') |
|
} |
|
/> |
|
{defaultFeedHasMore ? ( |
|
<div className="mt-6 flex justify-center"> |
|
<Button type="button" variant="outline" onClick={loadMoreFeed}> |
|
{t('Library load more', { |
|
count: Math.min(LIBRARY_PAGE_SIZE, feedTotalCount - entries.length) |
|
})} |
|
</Button> |
|
</div> |
|
) : null} |
|
</div> |
|
</PrimaryPageLayout> |
|
) |
|
}) |
|
LibraryPage.displayName = 'LibraryPage' |
|
export default LibraryPage |
|
|
|
function LibraryPageTitlebar({ onRefresh }: { onRefresh: () => void }) { |
|
const { t } = useTranslation() |
|
return ( |
|
<div className="flex h-full w-full items-center justify-between gap-2 pr-1"> |
|
<div className="flex items-center gap-2 pl-3"> |
|
<BookOpen className="size-5" /> |
|
<div className="app-chrome-title">{t('Library page title')}</div> |
|
</div> |
|
<RefreshButton onClick={onRefresh} /> |
|
</div> |
|
) |
|
}
|
|
|