/*
* Copyright (c) 2021 - 2024, 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 { Column, useFilters, usePagination, useSortBy, useTable } from "react-table";
import {
ChevronDoubleLeftIcon,
ChevronDoubleRightIcon,
ChevronLeftIcon,
ChevronRightIcon,
EyeIcon,
EyeSlashIcon
} from "@heroicons/react/24/solid";
import { ReleasesRoute } from "@app/routes";
import { ReleasesListQueryOptions } from "@api/queries";
import { RandomLinuxIsos } from "@utils";
import { RingResizeSpinner, SortDownIcon, SortIcon, SortUpIcon } from "@components/Icons";
import { IndexerSelectColumnFilter, PushStatusSelectColumnFilter, SearchColumnFilter } from "./ReleaseFilters";
import { EmptyListState } from "@components/emptystates";
import { TableButton, TablePageButton } from "@components/data-table/Buttons.tsx";
import { AgeCell, IndexerCell, LinksCell, NameCell, ReleaseStatusCell } from "@components/data-table";
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}`);
}
}
};
const EmptyReleaseList = () => (
);
export const ReleaseTable = () => {
const search = ReleasesRoute.useSearch()
const columns = React.useMemo(() => [
{
Header: "Age",
accessor: "timestamp",
Cell: AgeCell
},
{
Header: "Release",
accessor: "name",
Cell: NameCell,
Filter: SearchColumnFilter
},
{
Header: "Links",
accessor: (row) => ({ download_url: row.download_url, info_url: row.info_url }),
id: "links",
Cell: LinksCell
},
{
Header: "Actions",
accessor: "action_status",
Cell: ReleaseStatusCell,
Filter: PushStatusSelectColumnFilter
},
{
Header: "Indexer",
accessor: "indexer.identifier",
Cell: IndexerCell,
Filter: IndexerSelectColumnFilter,
filter: "equal"
}
] as Column[], []);
if (search.action_status != "") {
initialState.queryFilters = [{id: "action_status", value: search.action_status! }]
}
const [{ queryPageIndex, queryPageSize, totalCount, queryFilters }, dispatch] =
React.useReducer(TableReducer, initialState);
const { isLoading, error, data, isSuccess } = useQuery(ReleasesListQueryOptions(queryPageIndex * queryPageSize, queryPageSize, queryFilters));
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,
name: `${randomNames[index]}.iso`,
indexer: {
id: 0,
name: index % 2 === 0 ? "distrowatch" : "linuxtracker",
identifier: index % 2 === 0 ? "distrowatch" : "linuxtracker",
identifier_external: index % 2 === 0 ? "distrowatch" : "linuxtracker",
},
category: "Linux ISOs",
size: index % 2 === 0 ? 4566784529 : (index % 3 === 0 ? 7427019812 : 2312122455),
source: "",
container: "",
codec: "",
resolution: "",
}));
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: queryFilters,
},
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 });
gotoPage(0);
}, [filters]);
React.useEffect(() => {
if (search.action_status != null) {
dispatch({ type: ActionType.FILTER_CHANGED, payload: [{ id: "action_status", value: search.action_status! }] });
}
}, [search.action_status]);
if (error) {
return Error
;
}
if (isLoading) {
return (
{ headerGroups.map((headerGroup) => headerGroup.headers.map((column) => (
column.Filter ? (
{ column.render("Filter") }
) : null
))
) }
)
}
// Render the UI for your table
return (
{headerGroups.map((headerGroup) =>
headerGroup.headers.map((column) => (
column.Filter ? (
{column.render("Filter")}
) : null
))
)}
{displayData.length === 0
?
: (
{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
<>{column.render("Header")}>
{/* Add a sort direction indicator */}
{column.isSorted ? (
column.isSortedDesc ? (
) : (
)
) : (
)}
);
})}
);
})}
{page.map((row) => {
prepareRow(row);
const {key: bodyRowKey, ...bodyRowRest} = row.getRowProps();
return (
{row.cells.map((cell) => {
const {key: cellRowKey, ...cellRowRest} = cell.getCellProps();
return (
<>{cell.render("Cell")}>
);
})}
);
})}
{/* Pagination */}
previousPage()} disabled={!canPreviousPage}>Previous
nextPage()} disabled={!canNextPage}>Next
Page {pageIndex + 1} of {pageOptions.length}
Items Per Page
{
setPageSize(Number(e.target.value));
}}
>
{[5, 10, 20, 50].map(pageSize => (
{pageSize} entries
))}
gotoPage(0)}
disabled={!canPreviousPage}
>
First
previousPage()}
disabled={!canPreviousPage}
>
Prev
nextPage()}
disabled={!canNextPage}>
Next
gotoPage(pageCount - 1)}
disabled={!canNextPage}
>
Last
{showLinuxIsos ? (
) : (
)}
)}
);
};