import { keepPreviousData, useInfiniteQuery } from '@tanstack/react-query' import api from '~/lib/api' import { useCallback, useEffect, useMemo, useRef } from 'react' import { useVirtualizer } from '@tanstack/react-virtual' import StyledTable from '~/components/StyledTable' import SettingsLayout from '~/layouts/SettingsLayout' import { Head } from '@inertiajs/react' import { ListRemoteZimFilesResponse, RemoteZimFileEntry } from '../../../../types/zim' import { formatBytes } from '~/lib/util' import StyledButton from '~/components/StyledButton' import { useModals } from '~/context/ModalContext' import StyledModal from '~/components/StyledModal' export default function ZimRemoteExplorer() { const tableParentRef = useRef(null) const { openModal, closeAllModals } = useModals() const { data, fetchNextPage, isFetching, isLoading } = useInfiniteQuery({ queryKey: ['remote-zim-files'], queryFn: async ({ pageParam = 0 }) => { const pageParsed = parseInt((pageParam as number).toString(), 10) const start = isNaN(pageParsed) ? 0 : pageParsed * 12 const res = await api.listRemoteZimFiles({ start, count: 12 }) return res.data }, initialPageParam: 0, getNextPageParam: (_lastPage, pages) => { if (!_lastPage.has_more) { return undefined // No more pages to fetch } return pages.length }, refetchOnWindowFocus: false, placeholderData: keepPreviousData, }) const flatData = useMemo(() => data?.pages.flatMap((page) => page.items) || [], [data]) const hasMore = useMemo(() => data?.pages[data.pages.length - 1]?.has_more || false, [data]) const fetchOnBottomReached = useCallback( (parentRef?: HTMLDivElement | null) => { if (parentRef) { const { scrollHeight, scrollTop, clientHeight } = parentRef //once the user has scrolled within 200px of the bottom of the table, fetch more data if we can if (scrollHeight - scrollTop - clientHeight < 200 && !isFetching && hasMore) { fetchNextPage() } } }, [fetchNextPage, isFetching, hasMore] ) const virtualizer = useVirtualizer({ count: flatData.length, estimateSize: () => 48, // Estimate row height getScrollElement: () => tableParentRef.current, overscan: 5, // Number of items to render outside the visible area }) //a check on mount and after a fetch to see if the table is already scrolled to the bottom and immediately needs to fetch more data useEffect(() => { fetchOnBottomReached(tableParentRef.current) }, [fetchOnBottomReached]) async function confirmDownload(record: RemoteZimFileEntry) { openModal( { downloadFile(record) closeAllModals() }} onCancel={closeAllModals} open={true} confirmText="Download" cancelText="Cancel" confirmVariant="primary" >

Are you sure you want to download {record.title}? It may take some time for it to be available depending on the file size and your internet connection.

, 'confirm-download-file-modal' ) } async function downloadFile(record: RemoteZimFileEntry) { try { await api.downloadRemoteZimFile(record.download_url) } catch (error) { console.error('Error downloading file:', error) } } return (

ZIM Remote Explorer

Browse and download remote ZIM files from the Kiwix repository!

data={flatData.map((i, idx) => { const row = virtualizer.getVirtualItems().find((v) => v.index === idx) return { ...i, height: `${row?.size || 48}px`, // Use the size from the virtualizer translateY: row?.start || 0, } })} ref={tableParentRef} loading={isLoading} columns={[ { accessor: 'title', }, { accessor: 'author', }, { accessor: 'summary', }, { accessor: 'updated', render(record) { return new Intl.DateTimeFormat('en-US', { dateStyle: 'medium', }).format(new Date(record.updated)) }, }, { accessor: 'size_bytes', render(record) { return formatBytes(record.size_bytes) }, }, { accessor: 'actions', render(record, index) { return (
{ confirmDownload(record) }} > Download
) }, }, ]} className="relative overflow-x-auto overflow-y-auto h-[600px] w-full " tableBodyStyle={{ position: 'relative', height: `${virtualizer.getTotalSize()}px`, }} containerProps={{ onScroll: (e) => fetchOnBottomReached(e.currentTarget as HTMLDivElement), }} compact rowLines />
) }