diff --git a/.changeset/little-drinks-work.md b/.changeset/little-drinks-work.md new file mode 100644 index 000000000..360d2faa4 --- /dev/null +++ b/.changeset/little-drinks-work.md @@ -0,0 +1,5 @@ +--- +'renterd': minor +--- + +Files can now be sorted by name and health, ascending or descending. diff --git a/.changeset/rude-ways-hang.md b/.changeset/rude-ways-hang.md new file mode 100644 index 000000000..36456ef3e --- /dev/null +++ b/.changeset/rude-ways-hang.md @@ -0,0 +1,5 @@ +--- +'@siafoundation/react-renterd': minor +--- + +useObjectDirectory now supports sortBy and sortDir. diff --git a/apps/renterd/contexts/files/dataset.tsx b/apps/renterd/contexts/files/dataset.tsx index d82e0bf28..677a880dc 100644 --- a/apps/renterd/contexts/files/dataset.tsx +++ b/apps/renterd/contexts/files/dataset.tsx @@ -2,7 +2,7 @@ import { useBuckets, useObjectDirectory } from '@siafoundation/react-renterd' import { sortBy, toPairs } from 'lodash' import useSWR from 'swr' import { useContracts } from '../contracts' -import { ObjectData } from './types' +import { ObjectData, SortField } from './types' import { bucketAndKeyParamsFromPath, bucketAndResponseKeyToFilePath, @@ -18,11 +18,18 @@ import { useRouter } from 'next/router' type Props = { activeDirectoryPath: string uploadsList: ObjectData[] + sortDirection: 'asc' | 'desc' + sortField: SortField } const defaultLimit = 50 -export function useDataset({ activeDirectoryPath, uploadsList }: Props) { +export function useDataset({ + activeDirectoryPath, + uploadsList, + sortDirection, + sortField, +}: Props) { const buckets = useBuckets() const router = useRouter() @@ -34,6 +41,8 @@ export function useDataset({ activeDirectoryPath, uploadsList }: Props) { disabled: !activeBucketName, params: { ...bucketAndKeyParamsFromPath(activeDirectoryPath), + sortBy: sortField, + sortDir: sortDirection, offset, limit, }, @@ -96,8 +105,11 @@ export function useDataset({ activeDirectoryPath, uploadsList }: Props) { } const all = sortBy( toPairs(dataMap).map((p) => p[1]), - 'path' + sortField as keyof ObjectData ) + if (sortDirection === 'desc') { + all.reverse() + } return all }, { diff --git a/apps/renterd/contexts/files/index.tsx b/apps/renterd/contexts/files/index.tsx index 7e924128c..953f0b71c 100644 --- a/apps/renterd/contexts/files/index.tsx +++ b/apps/renterd/contexts/files/index.tsx @@ -1,25 +1,40 @@ import { - useClientFilteredDataset, - useClientFilters, useDatasetEmptyState, + useServerFilters, useTableState, } from '@siafoundation/design-system' import { useRouter } from 'next/router' import { createContext, useCallback, useContext, useMemo } from 'react' import { columns } from './columns' -import { - defaultSortField, - columnsDefaultVisible, - ObjectData, - sortOptions, -} from './types' +import { defaultSortField, columnsDefaultVisible, sortOptions } from './types' import { FullPath, FullPathSegments, pathSegmentsToPath } from './paths' import { useUploads } from './uploads' import { useDownloads } from './downloads' import { useDataset } from './dataset' function useFilesMain() { + const { + configurableColumns, + enabledColumns, + sortableColumns, + toggleColumnVisibility, + setColumnsVisible, + setColumnsHidden, + toggleSort, + setSortDirection, + setSortField, + sortField, + sortDirection, + resetDefaultColumnVisibility, + } = useTableState('renterd/v0/objects', { + columns, + columnsDefaultVisible, + sortOptions, + defaultSortField, + }) const router = useRouter() + const { filters, setFilter, removeFilter, removeLastFilter, resetFilters } = + useServerFilters() // [bucket, key, directory] const activeDirectory = useMemo( @@ -57,44 +72,15 @@ function useFilesMain() { const { limit, offset, response, dataset } = useDataset({ activeDirectoryPath, uploadsList, - }) - - const { - configurableColumns, - enabledColumns, - sortableColumns, - toggleColumnVisibility, - setColumnsVisible, - setColumnsHidden, - toggleSort, - setSortDirection, - setSortField, - sortField, - sortDirection, - resetDefaultColumnVisibility, - } = useTableState('renterd/v0/objects', { - columns, - columnsDefaultVisible, - sortOptions, - defaultSortField, - }) - - const { filters, setFilter, removeFilter, removeLastFilter, resetFilters } = - useClientFilters() - - const datasetFiltered = useClientFilteredDataset({ - dataset, - filters, sortField, sortDirection, }) - const pageCount = datasetFiltered?.length || 0 const datasetPage = useMemo(() => { - if (!datasetFiltered) { + if (!dataset) { return null } - if (activeDirectory.length > 0 && datasetFiltered.length > 0) { + if (activeDirectory.length > 0 && dataset.length > 0) { return [ { id: '..', @@ -102,14 +88,14 @@ function useFilesMain() { path: '..', type: 'directory', }, - ...datasetFiltered, + ...dataset, ] } - return datasetFiltered + return dataset // Purposely do not include activeDirectory - we only want to update // when new data fetching is complete. // eslint-disable-next-line react-hooks/exhaustive-deps - }, [datasetFiltered]) + }, [dataset]) const filteredTableColumns = useMemo( () => @@ -120,7 +106,7 @@ function useFilesMain() { ) const dataState = useDatasetEmptyState( - datasetFiltered, + dataset, response.isValidating, response.error, filters @@ -142,8 +128,7 @@ function useFilesMain() { limit, offset, datasetPage, - pageCount, - datasetCount: datasetFiltered?.length || 0, + pageCount: dataset?.length || 0, columns: filteredTableColumns, uploadFiles, uploadsList, diff --git a/apps/renterd/contexts/files/types.ts b/apps/renterd/contexts/files/types.ts index 9e070d119..3c1543947 100644 --- a/apps/renterd/contexts/files/types.ts +++ b/apps/renterd/contexts/files/types.ts @@ -33,7 +33,7 @@ export const columnsDefaultVisible: TableColumnId[] = [ 'health', ] -export type SortField = 'name' +export type SortField = 'name' | 'health' export const defaultSortField: SortField = 'name' @@ -44,4 +44,9 @@ export const sortOptions: { id: SortField; label: string; category: string }[] = label: 'name', category: 'general', }, + { + id: 'health', + label: 'health', + category: 'general', + }, ] diff --git a/libs/design-system/src/hooks/useSorting.ts b/libs/design-system/src/hooks/useSorting.ts new file mode 100644 index 000000000..a3d3c0245 --- /dev/null +++ b/libs/design-system/src/hooks/useSorting.ts @@ -0,0 +1,60 @@ +'use client' + +import { intersection } from 'lodash' +import { useCallback, useMemo } from 'react' +import useLocalStorageState from 'use-local-storage-state' + +type Params = { + enabledColumns: ColumnId[] + defaultSortField?: SortField + sortOptions?: { id: SortField }[] +} + +export function useSorting( + scope: string, + params: Params +) { + const { defaultSortField, sortOptions, enabledColumns } = params + + const [sortField, setSortField] = useLocalStorageState( + `${scope}/sortField`, + { + defaultValue: defaultSortField, + } + ) + + const [sortDirection, setSortDirection] = useLocalStorageState< + 'desc' | 'asc' + >(`${scope}/sortDirection`, { + defaultValue: 'desc', + }) + + const toggleSort = useCallback( + (field: SortField) => { + if (sortField !== field) { + setSortField(field) + setSortDirection('asc') + return + } + setSortDirection((dir) => (dir === 'desc' ? 'asc' : 'desc')) + }, + [sortField, setSortField, setSortDirection] + ) + + const sortableColumns = useMemo(() => { + if (!sortOptions) { + return [] + } + const sortFieldIds = sortOptions.map((o) => o.id) + return intersection(sortFieldIds, enabledColumns as string[]) as SortField[] + }, [sortOptions, enabledColumns]) + + return { + toggleSort, + setSortDirection, + setSortField, + sortableColumns, + sortField, + sortDirection, + } +} diff --git a/libs/design-system/src/hooks/useTableState.ts b/libs/design-system/src/hooks/useTableState.ts index b4323762d..78f9dd88c 100644 --- a/libs/design-system/src/hooks/useTableState.ts +++ b/libs/design-system/src/hooks/useTableState.ts @@ -3,6 +3,7 @@ import { difference, intersection, uniq } from 'lodash' import { useCallback, useMemo } from 'react' import useLocalStorageState from 'use-local-storage-state' +import { useSorting } from './useSorting' type Column = { id: ColumnId @@ -44,19 +45,6 @@ export function useTableState< } ) - const [sortField, setSortField] = useLocalStorageState( - `${scope}/sortField`, - { - defaultValue: defaultSortField, - } - ) - - const [sortDirection, setSortDirection] = useLocalStorageState< - 'desc' | 'asc' - >(`${scope}/sortDirection`, { - defaultValue: 'desc', - }) - const toggleColumnVisibility = useCallback( (column: string) => { setEnabledColumns((enabled) => { @@ -91,18 +79,6 @@ export function useTableState< setEnabledColumns(columnsDefaultVisible) }, [setEnabledColumns, columnsDefaultVisible]) - const toggleSort = useCallback( - (field: SortField) => { - if (sortField !== field) { - setSortField(field) - setSortDirection('asc') - return - } - setSortDirection((dir) => (dir === 'desc' ? 'asc' : 'desc')) - }, - [sortField, setSortField, setSortDirection] - ) - const configurableColumns = useMemo( () => columns.filter((column) => { @@ -129,6 +105,18 @@ export function useTableState< [columns, _enabledColumns, disabledCategories] ) + const { + sortField, + sortDirection, + setSortField, + setSortDirection, + toggleSort, + } = useSorting(scope, { + defaultSortField, + sortOptions, + enabledColumns, + }) + const sortableColumns = useMemo(() => { if (!sortOptions) { return [] diff --git a/libs/react-renterd/src/bus.ts b/libs/react-renterd/src/bus.ts index 89eb2949e..878fa8460 100644 --- a/libs/react-renterd/src/bus.ts +++ b/libs/react-renterd/src/bus.ts @@ -484,7 +484,14 @@ export type ObjEntry = { export function useObjectDirectory( args: HookArgsSwr< - { key: string; bucket: string; limit?: number; offset?: number }, + { + key: string + bucket: string + limit?: number + offset?: number + sortBy?: 'name' | 'health' + sortDir?: 'asc' | 'desc' + }, { entries: ObjEntry[] } > ) {