feat(releases): incognito mode (#1282)

* feat(web): incognito mode

* removed unused variable

* move RandomLinuxIsos into utils/index
This commit is contained in:
soup 2023-12-14 22:20:36 +01:00 committed by GitHub
parent 3580472cbd
commit da365da17c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 231 additions and 134 deletions

View file

@ -3,7 +3,7 @@
* SPDX-License-Identifier: GPL-2.0-or-later * SPDX-License-Identifier: GPL-2.0-or-later
*/ */
import * as React from "react"; import React, { useState } from "react";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { import {
useTable, useTable,
@ -16,7 +16,9 @@ import {
import { APIClient } from "@api/APIClient"; import { APIClient } from "@api/APIClient";
import { EmptyListState } from "@components/emptystates"; import { EmptyListState } from "@components/emptystates";
import * as Icons from "@components/Icons"; import * as Icons from "@components/Icons";
import { EyeIcon, EyeSlashIcon } from "@heroicons/react/24/solid";
import * as DataTable from "@components/data-table"; import * as DataTable from "@components/data-table";
import { RandomLinuxIsos } from "@utils/index";
// This is a custom filter UI for selecting // This is a custom filter UI for selecting
// a unique option from a list // a unique option from a list
@ -202,13 +204,45 @@ export const ActivityTable = () => {
); );
} }
const [modifiedData, setModifiedData] = useState<Release[]>([]);
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 ?? []);
return ( return (
<div className="flex flex-col mt-12"> <div className="flex flex-col mt-12 relative">
<h3 className="text-2xl font-medium leading-6 text-gray-900 dark:text-gray-200"> <h3 className="text-2xl font-medium leading-6 text-black dark:text-white">
Recent activity Recent activity
</h3> </h3>
<Table columns={columns} data={data?.data ?? []} /> <Table columns={columns} data={displayData} />
<button
onClick={toggleReleaseNames}
className="p-2 absolute -bottom-8 right-0 bg-gray-750 text-white rounded-full opacity-10 hover:opacity-100 transition-opacity duration-300"
aria-label="Toggle view"
title="Go incognito"
>
{showLinuxIsos ? (
<EyeIcon className="h-4 w-4" />
) : (
<EyeSlashIcon className="h-4 w-4" />
)}
</button>
</div> </div>
); );
}; };

View file

@ -3,7 +3,7 @@
* SPDX-License-Identifier: GPL-2.0-or-later * SPDX-License-Identifier: GPL-2.0-or-later
*/ */
import * as React from "react"; import React, { useState } from "react";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { CellProps, Column, useFilters, usePagination, useSortBy, useTable } from "react-table"; import { CellProps, Column, useFilters, usePagination, useSortBy, useTable } from "react-table";
import { import {
@ -12,7 +12,9 @@ import {
ChevronLeftIcon, ChevronLeftIcon,
ChevronRightIcon ChevronRightIcon
} from "@heroicons/react/24/solid"; } from "@heroicons/react/24/solid";
import { EyeIcon, EyeSlashIcon } from "@heroicons/react/24/solid";
import { RandomLinuxIsos } from "@utils/index";
import { APIClient } from "@api/APIClient"; import { APIClient } from "@api/APIClient";
import { EmptyListState } from "@components/emptystates"; import { EmptyListState } from "@components/emptystates";
@ -148,7 +150,25 @@ export const ReleaseTable = () => {
staleTime: 5000 staleTime: 5000
}); });
// Use the state and functions returned from useTable to build your UI const [modifiedData, setModifiedData] = useState<Release[]>([]);
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 { const {
getTableProps, getTableProps,
getTableBodyProps, getTableBodyProps,
@ -170,7 +190,7 @@ export const ReleaseTable = () => {
} = useTable( } = useTable(
{ {
columns, columns,
data: data && isSuccess ? data.data : [], data: displayData, // Use displayData here
initialState: { initialState: {
pageIndex: queryPageIndex, pageIndex: queryPageIndex,
pageSize: queryPageSize, pageSize: queryPageSize,
@ -392,138 +412,154 @@ export const ReleaseTable = () => {
)) ))
)} )}
</div> </div>
<div className="bg-white dark:bg-gray-800 border border-gray-250 dark:border-gray-775 shadow-table rounded-md overflow-auto"> <div className="relative">
<table {...getTableProps()} className="min-w-full rounded-md divide-y divide-gray-200 dark:divide-gray-750"> <div className="bg-white dark:bg-gray-800 border border-gray-250 dark:border-gray-775 shadow-table rounded-md overflow-auto">
<thead className="bg-gray-100 dark:bg-gray-850"> <table {...getTableProps()} className="min-w-full rounded-md divide-y divide-gray-200 dark:divide-gray-750">
{headerGroups.map((headerGroup) => { <thead className="bg-gray-100 dark:bg-gray-850">
const { key: rowKey, ...rowRest } = headerGroup.getHeaderGroupProps(); {headerGroups.map((headerGroup) => {
return ( const { key: rowKey, ...rowRest } = headerGroup.getHeaderGroupProps();
<tr key={rowKey} {...rowRest}> return (
{headerGroup.headers.map((column) => { <tr key={rowKey} {...rowRest}>
const { key: columnKey, ...columnRest } = column.getHeaderProps(column.getSortByToggleProps()); {headerGroup.headers.map((column) => {
return ( const { key: columnKey, ...columnRest } = column.getHeaderProps(column.getSortByToggleProps());
return (
// Add the sorting props to control sorting. For this example // Add the sorting props to control sorting. For this example
// we can add them into the header props // we can add them into the header props
<th <th
key={`${rowKey}-${columnKey}`} key={`${rowKey}-${columnKey}`}
scope="col" scope="col"
className="first:pl-5 first:rounded-tl-md last:rounded-tr-md pl-3 pr-3 py-3 text-xs font-medium tracking-wider text-left uppercase group text-gray-600 dark:text-gray-400 transition hover:bg-gray-200 dark:hover:bg-gray-775" className="first:pl-5 first:rounded-tl-md last:rounded-tr-md pl-3 pr-3 py-3 text-xs font-medium tracking-wider text-left uppercase group text-gray-600 dark:text-gray-400 transition hover:bg-gray-200 dark:hover:bg-gray-775"
{...columnRest} {...columnRest}
> >
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<>{column.render("Header")}</> <>{column.render("Header")}</>
{/* Add a sort direction indicator */} {/* Add a sort direction indicator */}
<span> <span>
{column.isSorted ? ( {column.isSorted ? (
column.isSortedDesc ? ( column.isSortedDesc ? (
<Icons.SortDownIcon className="w-4 h-4 text-gray-400" /> <Icons.SortDownIcon className="w-4 h-4 text-gray-400" />
) : (
<Icons.SortUpIcon className="w-4 h-4 text-gray-400" />
)
) : ( ) : (
<Icons.SortUpIcon className="w-4 h-4 text-gray-400" /> <Icons.SortIcon className="w-4 h-4 text-gray-400 opacity-0 group-hover:opacity-100" />
) )}
) : ( </span>
<Icons.SortIcon className="w-4 h-4 text-gray-400 opacity-0 group-hover:opacity-100" /> </div>
)} </th>
</span> );
</div> })}
</th> </tr>
); );
})} })}
</tr> </thead>
); <tbody
})} {...getTableBodyProps()}
</thead> className="divide-y divide-gray-150 dark:divide-gray-750"
<tbody >
{...getTableBodyProps()} {page.map((row) => {
className="divide-y divide-gray-150 dark:divide-gray-750" prepareRow(row);
>
{page.map((row) => {
prepareRow(row);
const { key: bodyRowKey, ...bodyRowRest } = row.getRowProps(); const { key: bodyRowKey, ...bodyRowRest } = row.getRowProps();
return ( return (
<tr key={bodyRowKey} {...bodyRowRest}> <tr key={bodyRowKey} {...bodyRowRest}>
{row.cells.map((cell) => { {row.cells.map((cell) => {
const { key: cellRowKey, ...cellRowRest } = cell.getCellProps(); const { key: cellRowKey, ...cellRowRest } = cell.getCellProps();
return ( return (
<td <td
key={cellRowKey} key={cellRowKey}
className="first:pl-5 pl-3 pr-3 whitespace-nowrap" className="first:pl-5 pl-3 pr-3 whitespace-nowrap"
role="cell" role="cell"
{...cellRowRest} {...cellRowRest}
> >
<>{cell.render("Cell")}</> <>{cell.render("Cell")}</>
</td> </td>
); );
})} })}
</tr> </tr>
); );
})} })}
</tbody> </tbody>
</table> </table>
{/* Pagination */} {/* Pagination */}
<div className="flex items-center justify-between px-6 py-3 border-t border-gray-200 dark:border-gray-700"> <div className="flex items-center justify-between px-6 py-3 border-t border-gray-200 dark:border-gray-700">
<div className="flex justify-between flex-1 sm:hidden"> <div className="flex justify-between flex-1 sm:hidden">
<DataTable.Button onClick={() => previousPage()} disabled={!canPreviousPage}>Previous</DataTable.Button> <DataTable.Button onClick={() => previousPage()} disabled={!canPreviousPage}>Previous</DataTable.Button>
<DataTable.Button onClick={() => nextPage()} disabled={!canNextPage}>Next</DataTable.Button> <DataTable.Button onClick={() => nextPage()} disabled={!canNextPage}>Next</DataTable.Button>
</div> </div>
<div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between"> <div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
<div className="flex items-baseline gap-x-2"> <div className="flex items-baseline gap-x-2">
<span className="text-sm text-gray-700 dark:text-gray-500"> <span className="text-sm text-gray-700 dark:text-gray-500">
Page <span className="font-medium">{pageIndex + 1}</span> of <span className="font-medium">{pageOptions.length}</span> Page <span className="font-medium">{pageIndex + 1}</span> of <span className="font-medium">{pageOptions.length}</span>
</span> </span>
<label> <label>
<span className="sr-only bg-gray-700">Items Per Page</span> <span className="sr-only bg-gray-700">Items Per Page</span>
<select <select
className="py-1 pl-2 pr-8 text-sm block w-full border-gray-300 rounded-md shadow-sm cursor-pointer transition-colors dark:bg-gray-800 dark:border-gray-600 dark:text-gray-400 dark:hover:text-gray-200 focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50" className="py-1 pl-2 pr-8 text-sm block w-full border-gray-300 rounded-md shadow-sm cursor-pointer transition-colors dark:bg-gray-800 dark:border-gray-600 dark:text-gray-400 dark:hover:text-gray-200 focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50"
value={pageSize} value={pageSize}
onChange={e => { onChange={e => {
setPageSize(Number(e.target.value)); setPageSize(Number(e.target.value));
}} }}
> >
{[5, 10, 20, 50].map(pageSize => ( {[5, 10, 20, 50].map(pageSize => (
<option key={pageSize} value={pageSize}> <option key={pageSize} value={pageSize}>
{pageSize} entries {pageSize} entries
</option> </option>
))} ))}
</select> </select>
</label> </label>
</div> </div>
<div> <div>
<nav className="inline-flex -space-x-px rounded-md shadow-sm" aria-label="Pagination"> <nav className="inline-flex -space-x-px rounded-md shadow-sm" aria-label="Pagination">
<DataTable.PageButton <DataTable.PageButton
className="rounded-l-md" className="rounded-l-md"
onClick={() => gotoPage(0)} onClick={() => gotoPage(0)}
disabled={!canPreviousPage} disabled={!canPreviousPage}
> >
<span className="sr-only">First</span> <span className="sr-only">First</span>
<ChevronDoubleLeftIcon className="w-4 h-4" aria-hidden="true" /> <ChevronDoubleLeftIcon className="w-4 h-4" aria-hidden="true" />
</DataTable.PageButton> </DataTable.PageButton>
<DataTable.PageButton <DataTable.PageButton
className="pl-1 pr-2" className="pl-1 pr-2"
onClick={() => previousPage()} onClick={() => previousPage()}
disabled={!canPreviousPage} disabled={!canPreviousPage}
> >
<ChevronLeftIcon className="w-4 h-4 mr-1" aria-hidden="true" /> <ChevronLeftIcon className="w-4 h-4 mr-1" aria-hidden="true" />
<span>Prev</span> <span>Prev</span>
</DataTable.PageButton> </DataTable.PageButton>
<DataTable.PageButton <DataTable.PageButton
className="pl-2 pr-1" className="pl-2 pr-1"
onClick={() => nextPage()} onClick={() => nextPage()}
disabled={!canNextPage}> disabled={!canNextPage}>
<span>Next</span> <span>Next</span>
<ChevronRightIcon className="w-4 h-4 ml-1" aria-hidden="true" /> <ChevronRightIcon className="w-4 h-4 ml-1" aria-hidden="true" />
</DataTable.PageButton> </DataTable.PageButton>
<DataTable.PageButton <DataTable.PageButton
className="rounded-r-md" className="rounded-r-md"
onClick={() => gotoPage(pageCount - 1)} onClick={() => gotoPage(pageCount - 1)}
disabled={!canNextPage} disabled={!canNextPage}
> >
<ChevronDoubleRightIcon className="w-4 h-4" aria-hidden="true" /> <ChevronDoubleRightIcon className="w-4 h-4" aria-hidden="true" />
<span className="sr-only">Last</span> <span className="sr-only">Last</span>
</DataTable.PageButton> </DataTable.PageButton>
</nav> </nav>
</div>
</div> </div>
</div> </div>
<div className="absolute -bottom-11 right-0 p-2">
<button
onClick={toggleReleaseNames}
className="p-2 absolute bottom-0 right-0 bg-gray-750 text-white rounded-full opacity-10 hover:opacity-100 transition-opacity duration-300"
aria-label="Toggle view"
title="Go incognito"
>
{showLinuxIsos ? (
<EyeIcon className="h-4 w-4" />
) : (
<EyeSlashIcon className="h-4 w-4" />
)}
</button>
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -83,3 +83,30 @@ export const get = <T> (obj: T, path: string|Array<any>, defValue?: string) => {
// If found value is undefined return default value; otherwise return the value // If found value is undefined return default value; otherwise return the value
return result === undefined ? defValue : result; return result === undefined ? defValue : result;
}; };
export const RandomLinuxIsos = (count: number) => {
const linuxIsos = [
"ubuntu-20.04.4-lts-focal-fossa-desktop-amd64-secure-boot",
"debian-11.3.0-bullseye-amd64-DVD-1-with-nonfree-firmware-netinst",
"fedora-36-workstation-x86_64-live-iso-with-rpmfusion-free-and-nonfree",
"archlinux-2023.04.01-x86_64-advanced-installation-environment",
"linuxmint-20.3-uma-cinnamon-64bit-full-multimedia-support-edition",
"centos-stream-9-x86_64-dvd1-full-install-iso-with-extended-repositories",
"opensuse-tumbleweed-20230415-DVD-x86_64-full-packaged-desktop-environments",
"manjaro-kde-21.1.6-210917-linux514-full-hardware-support-edition",
"elementaryos-6.1-odin-amd64-20230104-iso-with-pantheon-desktop-environment",
"pop_os-21.10-amd64-nvidia-proprietary-drivers-included-live",
"kali-linux-2023.2-live-amd64-iso-with-persistent-storage-and-custom-tools",
"zorin-os-16-pro-ultimate-edition-64-bit-r1-iso-with-windows-app-support",
"endeavouros-2023.04.15-x86_64-iso-with-offline-installer-and-xfce4",
"mx-linux-21.2-aarch64-xfce-iso-with-ahs-enabled-kernel-and-snapshot-feature",
"solus-4.3-budgie-desktop-environment-full-iso-with-software-center",
"slackware-15.0-install-dvd-iso-with-extended-documentation-and-extras",
"alpine-standard-3.15.0-x86_64-iso-for-container-and-server-use",
"gentoo-livecd-amd64-minimal-20230407-stage3-tarball-included",
"peppermint-11-20210903-amd64-iso-with-hybrid-lxde-xfce-desktop",
"deepin-20.3-amd64-iso-with-deepin-desktop-environment-and-app-store"
];
return Array.from({ length: count }, () => linuxIsos[Math.floor(Math.random() * linuxIsos.length)]);
};