/* * Copyright (c) 2021 - 2023, Ludvig Lundgren and the autobrr contributors. * SPDX-License-Identifier: GPL-2.0-or-later */ import React, { useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { CellProps, Column, useFilters, usePagination, useSortBy, useTable } from "react-table"; import { ChevronDoubleLeftIcon, ChevronDoubleRightIcon, ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/24/solid"; import { EyeIcon, EyeSlashIcon } from "@heroicons/react/24/solid"; import { RandomLinuxIsos } from "@utils/index"; import { APIClient } from "@api/APIClient"; import { EmptyListState } from "@components/emptystates"; import * as Icons from "@components/Icons"; import * as DataTable from "@components/data-table"; import { IndexerSelectColumnFilter, PushStatusSelectColumnFilter, SearchColumnFilter } from "./Filters"; import { classNames } from "@utils"; import { ArrowTopRightOnSquareIcon, ArrowDownTrayIcon } from "@heroicons/react/24/outline"; import { Tooltip } from "@components/tooltips/Tooltip"; import { ExternalLink } from "@components/ExternalLink"; export const releaseKeys = { all: ["releases"] as const, lists: () => [...releaseKeys.all, "list"] as const, list: (pageIndex: number, pageSize: number, filters: ReleaseFilter[]) => [...releaseKeys.lists(), { pageIndex, pageSize, filters }] as const, details: () => [...releaseKeys.all, "detail"] as const, detail: (id: number) => [...releaseKeys.details(), id] as const }; type TableState = { queryPageIndex: number; queryPageSize: number; totalCount: number; queryFilters: ReleaseFilter[]; }; const initialState: TableState = { queryPageIndex: 0, queryPageSize: 10, totalCount: 0, queryFilters: [] }; enum ActionType { PAGE_CHANGED = "PAGE_CHANGED", PAGE_SIZE_CHANGED = "PAGE_SIZE_CHANGED", TOTAL_COUNT_CHANGED = "TOTAL_COUNT_CHANGED", FILTER_CHANGED = "FILTER_CHANGED" } type Actions = | { type: ActionType.FILTER_CHANGED; payload: ReleaseFilter[]; } | { type: ActionType.PAGE_CHANGED; payload: number; } | { type: ActionType.PAGE_SIZE_CHANGED; payload: number; } | { type: ActionType.TOTAL_COUNT_CHANGED; payload: number; }; const TableReducer = (state: TableState, action: Actions): TableState => { switch (action.type) { case ActionType.PAGE_CHANGED: { return { ...state, queryPageIndex: action.payload }; } case ActionType.PAGE_SIZE_CHANGED: { return { ...state, queryPageSize: action.payload }; } case ActionType.FILTER_CHANGED: { return { ...state, queryFilters: action.payload }; } case ActionType.TOTAL_COUNT_CHANGED: { return { ...state, totalCount: action.payload }; } default: { throw new Error(`Unhandled action type: ${action}`); } } }; export const ReleaseTable = () => { const columns = React.useMemo(() => [ { Header: "Age", accessor: "timestamp", Cell: DataTable.AgeCell }, { Header: "Release", accessor: "torrent_name", Cell: (props: CellProps) => { return (
{String(props.cell.value)}
{props.row.original.download_url && ( )} {props.row.original.info_url && ( )}
); }, Filter: SearchColumnFilter }, { Header: "Actions", accessor: "action_status", Cell: DataTable.ReleaseStatusCell, Filter: PushStatusSelectColumnFilter }, { Header: "Indexer", accessor: "indexer", Cell: DataTable.IndexerCell, Filter: IndexerSelectColumnFilter, filter: "equal" } ] as Column[], []); const [{ queryPageIndex, queryPageSize, totalCount, queryFilters }, dispatch] = React.useReducer(TableReducer, initialState); const { isLoading, error, data, isSuccess } = useQuery({ queryKey: releaseKeys.list(queryPageIndex, queryPageSize, queryFilters), queryFn: () => APIClient.release.findQuery(queryPageIndex * queryPageSize, queryPageSize, queryFilters), keepPreviousData: true, staleTime: 5000 }); const [modifiedData, setModifiedData] = useState([]); const [showLinuxIsos, setShowLinuxIsos] = useState(false); const toggleReleaseNames = () => { setShowLinuxIsos(!showLinuxIsos); if (!showLinuxIsos && data && data.data) { const randomNames = RandomLinuxIsos(data.data.length); const newData: Release[] = data.data.map((item, index) => ({ ...item, torrent_name: `${randomNames[index]}.iso`, indexer: index % 2 === 0 ? "distrowatch" : "linuxtracker" })); setModifiedData(newData); } }; const displayData = showLinuxIsos ? modifiedData : (data?.data ?? []); const { getTableProps, getTableBodyProps, headerGroups, prepareRow, page, // Instead of using 'rows', we'll use page, // which has only the rows for the active page // The rest of these things are super handy, too ;) canPreviousPage, canNextPage, pageOptions, pageCount, gotoPage, nextPage, previousPage, setPageSize, state: { pageIndex, pageSize, filters } } = useTable( { columns, data: displayData, // Use displayData here initialState: { pageIndex: queryPageIndex, pageSize: queryPageSize, filters: [] }, manualPagination: true, manualFilters: true, manualSortBy: true, pageCount: isSuccess ? Math.ceil(totalCount / queryPageSize) : 0, autoResetSortBy: false, autoResetExpanded: false, autoResetPage: false }, useFilters, useSortBy, usePagination ); React.useEffect(() => { dispatch({ type: ActionType.PAGE_CHANGED, payload: pageIndex }); }, [pageIndex]); React.useEffect(() => { dispatch({ type: ActionType.PAGE_SIZE_CHANGED, payload: pageSize }); gotoPage(0); }, [pageSize, gotoPage]); React.useEffect(() => { if (data?.count) { dispatch({ type: ActionType.TOTAL_COUNT_CHANGED, payload: data.count }); } }, [data?.count]); React.useEffect(() => { dispatch({ type: ActionType.FILTER_CHANGED, payload: filters }); }, [filters]); if (error) { return

Error

; } if (isLoading) { return (
{headerGroups.map((headerGroup) => headerGroup.headers.map((column) => ( column.Filter ? ( {column.render("Filter")} ) : null )) )}
{/* Add a sort direction indicator */}
     
     
     
     

Loading release table...

     
     
     
     
     
{/* Pagination */}
previousPage()} disabled={!canPreviousPage}>Previous nextPage()} disabled={!canNextPage}>Next
Page {pageIndex + 1} of {pageOptions.length}
); } if (!data) { return ; } // Render the UI for your table return (
{headerGroups.map((headerGroup) => headerGroup.headers.map((column) => ( column.Filter ? ( {column.render("Filter")} ) : null )) )}
{headerGroups.map((headerGroup) => { const { key: rowKey, ...rowRest } = headerGroup.getHeaderGroupProps(); return ( {headerGroup.headers.map((column) => { const { key: columnKey, ...columnRest } = column.getHeaderProps(column.getSortByToggleProps()); return ( // Add the sorting props to control sorting. For this example // we can add them into the header props ); })} ); })} {page.map((row) => { prepareRow(row); const { key: bodyRowKey, ...bodyRowRest } = row.getRowProps(); return ( {row.cells.map((cell) => { const { key: cellRowKey, ...cellRowRest } = cell.getCellProps(); return ( ); })} ); })}
<>{column.render("Header")} {/* Add a sort direction indicator */} {column.isSorted ? ( column.isSortedDesc ? ( ) : ( ) ) : ( )}
<>{cell.render("Cell")}
{/* Pagination */}
previousPage()} disabled={!canPreviousPage}>Previous nextPage()} disabled={!canNextPage}>Next
Page {pageIndex + 1} of {pageOptions.length}
); };