Skip to content

Commit 0f5b172

Browse files
authored
Add sorting to all tables (#8267)
2 parents 47bd1e0 + f9f5919 commit 0f5b172

File tree

21 files changed

+171
-149
lines changed

21 files changed

+171
-149
lines changed

src/tribler/ui/src/components/ui/simple-table.tsx

+53-4
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,55 @@
11
import { SetStateAction, useEffect, useRef, useState } from 'react';
22
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
33
import { getCoreRowModel, useReactTable, flexRender, getFilteredRowModel, getPaginationRowModel, getExpandedRowModel, getSortedRowModel } from '@tanstack/react-table';
4-
import type { ColumnDef, Row, PaginationState, RowSelectionState, ColumnFiltersState, ExpandedState } from '@tanstack/react-table';
4+
import type { ColumnDef, Row, PaginationState, RowSelectionState, ColumnFiltersState, ExpandedState, ColumnDefTemplate, HeaderContext, SortingState } from '@tanstack/react-table';
55
import { cn } from '@/lib/utils';
66
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel } from './select';
77
import { Button } from './button';
8-
import { ChevronLeftIcon, ChevronRightIcon, DoubleArrowLeftIcon, DoubleArrowRightIcon } from '@radix-ui/react-icons';
8+
import { ArrowDownIcon, ArrowUpIcon, ChevronLeftIcon, ChevronRightIcon, DoubleArrowLeftIcon, DoubleArrowRightIcon } from '@radix-ui/react-icons';
99
import * as SelectPrimitive from "@radix-ui/react-select"
1010
import type { Table as ReactTable } from '@tanstack/react-table';
1111
import { useTranslation } from 'react-i18next';
1212
import { useResizeObserver } from '@/hooks/useResizeObserver';
1313

1414

15+
export function getHeader<T>(name: string, translate: boolean = true, addSorting: boolean = true): ColumnDefTemplate<HeaderContext<T, unknown>> | undefined {
16+
if (!addSorting) {
17+
return () => {
18+
const { t } = useTranslation();
19+
return <span className='select-none'>{translate ? t(name) : name}</span>;
20+
}
21+
}
22+
23+
return ({ column }) => {
24+
const { t } = useTranslation();
25+
return (
26+
<div className='select-none flex'>
27+
<span
28+
className="cursor-pointer hover:text-black dark:hover:text-white flex flex-row items-center"
29+
onClick={() => column.toggleSorting()}>
30+
{translate ? t(name) : name}
31+
{column.getIsSorted() === "desc" ? (
32+
<ArrowDownIcon className="ml-2" />
33+
) : column.getIsSorted() === "asc" ? (
34+
<ArrowUpIcon className="ml-2" />
35+
) : (
36+
<></>
37+
)}
38+
</span>
39+
</div>
40+
)
41+
}
42+
}
43+
44+
function getStoredSortingState(key?: string) {
45+
if (key) {
46+
let sortingString = localStorage.getItem(key);
47+
if (sortingString) {
48+
return JSON.parse(sortingString);
49+
}
50+
}
51+
}
52+
1553
interface ReactTableProps<T extends object> {
1654
data: T[];
1755
columns: ColumnDef<T>[];
@@ -29,6 +67,7 @@ interface ReactTableProps<T extends object> {
2967
filters?: { id: string, value: string }[];
3068
maxHeight?: string | number;
3169
expandable?: boolean;
70+
storeSortingState?: string;
3271
}
3372

3473
function SimpleTable<T extends object>({
@@ -46,7 +85,8 @@ function SimpleTable<T extends object>({
4685
allowMultiSelect,
4786
filters,
4887
maxHeight,
49-
expandable
88+
expandable,
89+
storeSortingState
5090
}: ReactTableProps<T>) {
5191
const [pagination, setPagination] = useState<PaginationState>({
5292
pageIndex: pageIndex ?? 0,
@@ -55,6 +95,7 @@ function SimpleTable<T extends object>({
5595
const [rowSelection, setRowSelection] = useState<RowSelectionState>(initialRowSelection || {});
5696
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>(filters || [])
5797
const [expanded, setExpanded] = useState<ExpandedState>({});
98+
const [sorting, setSorting] = useState<SortingState>(getStoredSortingState(storeSortingState) || []);
5899

59100
const table = useReactTable({
60101
data,
@@ -69,7 +110,8 @@ function SimpleTable<T extends object>({
69110
pagination,
70111
rowSelection,
71112
columnFilters,
72-
expanded
113+
expanded,
114+
sorting
73115
},
74116
getFilteredRowModel: getFilteredRowModel(),
75117
onColumnFiltersChange: setColumnFilters,
@@ -78,6 +120,7 @@ function SimpleTable<T extends object>({
78120
if (allowSelect || allowSelectCheckbox || allowMultiSelect) setRowSelection(arg);
79121
},
80122
onExpandedChange: setExpanded,
123+
onSortingChange: setSorting,
81124
getSubRows: (row: any) => row?.subRows,
82125
});
83126

@@ -104,6 +147,12 @@ function SimpleTable<T extends object>({
104147
}
105148
}, [filters])
106149

150+
useEffect(() => {
151+
if (storeSortingState) {
152+
localStorage.setItem(storeSortingState, JSON.stringify(sorting));
153+
}
154+
}, [sorting]);
155+
107156
// For some reason the ScrollArea scrollbar is only shown when it's set to a specific height.
108157
// So, we wrap it in a parent div, monitor its size, and set the height of the table accordingly.
109158
const parentRef = useRef<HTMLTableElement>(null);

src/tribler/ui/src/dialogs/SaveAs.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import SimpleTable from "@/components/ui/simple-table";
1+
import SimpleTable, { getHeader } from "@/components/ui/simple-table";
22
import { useEffect, useMemo, useState } from "react";
33
import toast from 'react-hot-toast';
44
import { triblerService } from "@/services/tribler.service";
55
import { isErrorDict } from "@/services/reporting";
6-
import { filesToTree, fixTreeProps, formatBytes, getFilesFromMetainfo, getRowSelection, getSelectedFilesFromTree, translateHeader } from "@/lib/utils";
6+
import { filesToTree, fixTreeProps, formatBytes, getFilesFromMetainfo, getRowSelection, getSelectedFilesFromTree } from "@/lib/utils";
77
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
88
import { Button } from "@/components/ui/button";
99
import { DialogProps } from "@radix-ui/react-dialog";
@@ -32,7 +32,7 @@ function startDownloadCallback(response: any, t: TFunction) {
3232
const getFileColumns = ({ onSelectedFiles }: { onSelectedFiles: (row: Row<FileTreeItem>) => void }): ColumnDef<FileTreeItem>[] => [
3333
{
3434
accessorKey: "name",
35-
header: translateHeader('Name'),
35+
header: getHeader('Name'),
3636
cell: ({ row }) => {
3737
return (
3838
<div
@@ -55,7 +55,7 @@ const getFileColumns = ({ onSelectedFiles }: { onSelectedFiles: (row: Row<FileTr
5555
},
5656
{
5757
accessorKey: "size",
58-
header: translateHeader('Size'),
58+
header: getHeader('Size'),
5959
cell: ({ row }) => {
6060
return (
6161
<div className='flex items-center'>

src/tribler/ui/src/lib/utils.ts

-8
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import es from 'javascript-time-ago/locale/es'
77
import pt from 'javascript-time-ago/locale/pt'
88
import ru from 'javascript-time-ago/locale/ru'
99
import zh from 'javascript-time-ago/locale/zh'
10-
import Cookies from "js-cookie";
1110
import { useTranslation } from "react-i18next";
1211
import { triblerService } from "@/services/tribler.service";
1312
import { FileLink, FileTreeItem } from "@/models/file.model";
@@ -141,13 +140,6 @@ export function getRowSelection(input: any[], selected_func: (item: any) => bool
141140
return selection;
142141
}
143142

144-
export function translateHeader(name: string) {
145-
return () => {
146-
const { t } = useTranslation();
147-
return t(name);
148-
}
149-
}
150-
151143
export function filterDuplicates(data: any[], key: string) {
152144
const seen = new Set();
153145
return data.filter(item => {

src/tribler/ui/src/pages/Debug/Asyncio/Tasks.tsx

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import SimpleTable from "@/components/ui/simple-table";
1+
import SimpleTable, { getHeader } from "@/components/ui/simple-table";
22
import { ColumnDef } from "@tanstack/react-table";
33
import { useState } from "react";
44
import { ipv8Service } from "@/services/ipv8.service";
@@ -11,26 +11,26 @@ import { formatTimeDiff } from "@/lib/utils";
1111
const taskColumns: ColumnDef<Task>[] = [
1212
{
1313
accessorKey: "taskmanager",
14-
header: "Taskmanager",
14+
header: getHeader("Taskmanager", false),
1515
},
1616
{
1717
accessorKey: "name",
18-
header: "Name",
18+
header: getHeader("Name", false),
1919
cell: ({ row }) => {
2020
return <span className="line-clamp-1">{row.original.name}</span>
2121
},
2222
},
2323
{
2424
accessorKey: "running",
25-
header: "Running?",
25+
header: getHeader("Running?", false),
2626
},
2727
{
2828
accessorKey: "interval",
29-
header: "Interval",
29+
header: getHeader("Interval", false),
3030
},
3131
{
3232
accessorKey: "start_time",
33-
header: "Started",
33+
header: getHeader("Started", false),
3434
cell: ({ row }) => {
3535
return row.original.start_time && <span>{formatTimeDiff(row.original.start_time)}</span>
3636
},

src/tribler/ui/src/pages/Debug/DHT/Buckets.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import SimpleTable from "@/components/ui/simple-table";
1+
import SimpleTable, { getHeader } from "@/components/ui/simple-table";
22
import { useState } from "react";
33
import { ipv8Service } from "@/services/ipv8.service";
44
import { isErrorDict } from "@/services/reporting";
@@ -11,18 +11,18 @@ import { useInterval } from '@/hooks/useInterval';
1111
const bucketColumns: ColumnDef<Bucket>[] = [
1212
{
1313
accessorKey: "prefix",
14-
header: "Prefix",
14+
header: getHeader("Prefix", false),
1515
},
1616
{
1717
accessorKey: "last_changed",
18-
header: "Last changed",
18+
header: getHeader("Last changed", false),
1919
cell: ({ row }) => {
2020
return <span>{formatTimeDiff(row.original.last_changed)}</span>
2121
},
2222
},
2323
{
2424
accessorKey: "peer",
25-
header: "# Peers",
25+
header: getHeader("# Peers", false),
2626
cell: ({ row }) => {
2727
return <span>{row.original.peers.length}</span>
2828
},

src/tribler/ui/src/pages/Debug/DHT/Statistics.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import SimpleTable from "@/components/ui/simple-table";
1+
import SimpleTable, { getHeader } from "@/components/ui/simple-table";
22
import { useState } from "react";
33
import { ipv8Service } from "@/services/ipv8.service";
44
import { isErrorDict } from "@/services/reporting";
@@ -10,11 +10,11 @@ import { useInterval } from '@/hooks/useInterval';
1010
const statisticColumns: ColumnDef<KeyValue>[] = [
1111
{
1212
accessorKey: "key",
13-
header: "Key",
13+
header: getHeader("Key", false),
1414
},
1515
{
1616
accessorKey: "value",
17-
header: "Value",
17+
header: getHeader("Value", false),
1818
cell: ({ row }) => {
1919
return <span className="whitespace-pre">{row.original.value}</span>
2020
},

src/tribler/ui/src/pages/Debug/General/index.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import SimpleTable from "@/components/ui/simple-table";
1+
import SimpleTable, { getHeader } from "@/components/ui/simple-table";
22
import { formatBytes } from "@/lib/utils";
33
import { KeyValue } from "@/models/keyvalue.model";
44
import { triblerService } from "@/services/tribler.service";
@@ -11,11 +11,11 @@ import { useInterval } from '@/hooks/useInterval';
1111
const generalColumns: ColumnDef<KeyValue>[] = [
1212
{
1313
accessorKey: "key",
14-
header: "Key",
14+
header: getHeader("Key", false),
1515
},
1616
{
1717
accessorKey: "value",
18-
header: "Value",
18+
header: getHeader("Value", false),
1919
},
2020
]
2121

src/tribler/ui/src/pages/Debug/IPv8/Details.tsx

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import SimpleTable from "@/components/ui/simple-table";
1+
import SimpleTable, { getHeader } from "@/components/ui/simple-table";
22
import { useState } from "react";
33
import { ipv8Service } from "@/services/ipv8.service";
44
import { isErrorDict } from "@/services/reporting";
@@ -10,35 +10,35 @@ import { useInterval } from '@/hooks/useInterval';
1010
const statisticColumns: ColumnDef<OverlayMsgStats>[] = [
1111
{
1212
accessorKey: "name",
13-
header: "Name",
13+
header: getHeader("Name", false),
1414
},
1515
{
1616
accessorKey: "bytes_up",
17-
header: "Upload (MB)",
17+
header: getHeader("Upload (MB)", false),
1818
cell: ({ row }) => {
1919
if (row.original.identifier < 0) { return }
2020
return <span>{(row.original.bytes_up / 1024 ** 2).toFixed(3)}</span>
2121
},
2222
},
2323
{
2424
accessorKey: "bytes_down",
25-
header: "Download (MB)",
25+
header: getHeader("Download (MB)", false),
2626
cell: ({ row }) => {
2727
if (row.original.identifier < 0) { return }
2828
return <span>{(row.original.bytes_down / 1024 ** 2).toFixed(3)}</span>
2929
},
3030
},
3131
{
3232
accessorKey: "num_up",
33-
header: "# Msgs sent",
33+
header: getHeader("# Msgs sent", false),
3434
cell: ({ row }) => {
3535
if (row.original.identifier < 0) { return }
3636
return <span>{row.original.num_up}</span>
3737
},
3838
},
3939
{
4040
accessorKey: "num_down",
41-
header: "# Msgs received",
41+
header: getHeader("# Msgs received", false),
4242
cell: ({ row }) => {
4343
if (row.original.identifier < 0) { return }
4444
return <span>{row.original.num_down}</span>

0 commit comments

Comments
 (0)