From a5daa68e53d098ea7abbec976224a928729908ce Mon Sep 17 00:00:00 2001 From: Alex Freska Date: Fri, 13 Dec 2024 11:33:18 -0500 Subject: [PATCH] refactor: dataset and datastate --- .../Contracts/ContractsFiltersBar.tsx | 4 +- apps/hostd/components/Contracts/index.tsx | 19 ++-- apps/hostd/components/Node/Logs.tsx | 4 +- apps/hostd/components/Volumes/index.tsx | 8 +- .../components/Wallet/WalletFilterBar.tsx | 4 +- apps/hostd/components/Wallet/index.tsx | 27 ++--- apps/hostd/contexts/contracts/index.tsx | 23 ++--- apps/hostd/contexts/transactions/index.tsx | 34 ++++--- apps/hostd/contexts/volumes/dataset.ts | 5 +- apps/hostd/contexts/volumes/index.tsx | 13 +-- .../components/Alerts/AlertsFilterMenu.tsx | 6 +- apps/renterd/components/Alerts/index.tsx | 10 +- .../Contracts/ContractsFilterBar.tsx | 4 +- apps/renterd/components/Contracts/index.tsx | 19 ++-- apps/renterd/components/Files/Layout.tsx | 7 +- .../FilesDirectory/EmptyState/index.tsx | 23 +++-- .../index.tsx | 9 +- .../FilesDirectory/FilesExplorer.tsx | 4 +- .../components/FilesFlat/EmptyState/index.tsx | 13 ++- .../components/FilesFlat/FilesExplorer.tsx | 4 +- .../index.tsx | 8 +- .../components/Hosts/HostsFilterBar.tsx | 4 +- apps/renterd/components/Hosts/StateEmpty.tsx | 19 +++- apps/renterd/components/Hosts/index.tsx | 4 +- .../components/Keys/KeysStatsMenu/index.tsx | 4 +- apps/renterd/components/Keys/index.tsx | 19 ++-- .../components/Uploads/EmptyState/index.tsx | 13 ++- .../Uploads/UploadsStatsMenu/index.tsx | 6 +- .../components/Uploads/UploadsTable.tsx | 4 +- .../components/Wallet/WalletFilterBar.tsx | 5 +- apps/renterd/components/Wallet/index.tsx | 27 ++--- apps/renterd/contexts/alerts/index.tsx | 18 ++-- apps/renterd/contexts/contracts/dataset.tsx | 8 +- apps/renterd/contexts/contracts/index.tsx | 19 ++-- .../contexts/filesDirectory/dataset.tsx | 9 +- .../renterd/contexts/filesDirectory/index.tsx | 25 +++-- apps/renterd/contexts/filesFlat/dataset.tsx | 9 +- apps/renterd/contexts/filesFlat/index.tsx | 32 +++--- .../renterd/contexts/filesManager/dataset.tsx | 3 +- apps/renterd/contexts/hosts/dataset.ts | 16 +-- apps/renterd/contexts/hosts/index.tsx | 18 ++-- apps/renterd/contexts/keys/index.tsx | 39 ++++---- apps/renterd/contexts/transactions/index.tsx | 30 +++--- apps/renterd/contexts/uploads/index.tsx | 36 ++++--- .../components/Wallet/EventsFilterBar.tsx | 4 +- apps/walletd/components/Wallet/index.tsx | 19 ++-- .../components/WalletAddresses/index.tsx | 19 ++-- .../WalletsList/WalletsFiltersBar.tsx | 2 +- apps/walletd/components/WalletsList/index.tsx | 24 ++--- apps/walletd/contexts/addresses/dataset.tsx | 24 +++-- apps/walletd/contexts/addresses/index.tsx | 4 +- apps/walletd/contexts/events/index.tsx | 34 +++++-- apps/walletd/contexts/wallets/index.tsx | 27 ++--- .../dialogs/AddressUpdateDialog/index.tsx | 4 +- apps/walletd/hooks/useWalletAddresses.tsx | 4 +- .../src/app/AlertsDialog/index.tsx | 15 ++- .../src/components/EmptyState/StateError.tsx | 20 ++++ .../src/components/EmptyState/StateNoData.tsx | 15 +++ .../EmptyState/StateNoneMatching.tsx | 15 +++ .../components/EmptyState/StateNoneOnPage.tsx | 28 ++++++ .../components/EmptyState/StateNoneYet.tsx | 15 +++ .../src/components/EmptyState/index.tsx | 30 ++++++ .../src/components/PaginatorMarker.tsx | 14 +-- .../src/hooks/useClientFilteredDataset.ts | 5 +- .../src/hooks/useDatasetEmptyState.tsx | 49 --------- .../src/hooks/useDatasetState.tsx | 99 +++++++++++++++++++ libs/design-system/src/index.ts | 8 +- libs/react-core/src/arrayResponse.tsx | 20 ++++ libs/react-core/src/index.ts | 1 + libs/renterd-types/src/bus.ts | 6 +- libs/types/src/utils.ts | 2 + 71 files changed, 706 insertions(+), 416 deletions(-) rename apps/renterd/components/FilesDirectory/{FilesStatsMenu => FilesDirectoryStatsMenu}/index.tsx (78%) rename apps/renterd/components/FilesFlat/{FilesStatsMenu => FilesFlatStatsMenu}/index.tsx (73%) create mode 100644 libs/design-system/src/components/EmptyState/StateError.tsx create mode 100644 libs/design-system/src/components/EmptyState/StateNoData.tsx create mode 100644 libs/design-system/src/components/EmptyState/StateNoneMatching.tsx create mode 100644 libs/design-system/src/components/EmptyState/StateNoneOnPage.tsx create mode 100644 libs/design-system/src/components/EmptyState/StateNoneYet.tsx create mode 100644 libs/design-system/src/components/EmptyState/index.tsx delete mode 100644 libs/design-system/src/hooks/useDatasetEmptyState.tsx create mode 100644 libs/design-system/src/hooks/useDatasetState.tsx create mode 100644 libs/react-core/src/arrayResponse.tsx diff --git a/apps/hostd/components/Contracts/ContractsFiltersBar.tsx b/apps/hostd/components/Contracts/ContractsFiltersBar.tsx index 4f8bed918..36da58857 100644 --- a/apps/hostd/components/Contracts/ContractsFiltersBar.tsx +++ b/apps/hostd/components/Contracts/ContractsFiltersBar.tsx @@ -3,7 +3,7 @@ import { useContracts } from '../../contexts/contracts' import { ContractsFilterMenu } from './ContractsFilterMenu' export function ContractsFiltersBar() { - const { offset, limit, totalCount, pageCount, dataState } = useContracts() + const { offset, limit, totalCount, pageCount, datasetState } = useContracts() return (
@@ -11,7 +11,7 @@ export function ContractsFiltersBar() { diff --git a/apps/hostd/components/Contracts/index.tsx b/apps/hostd/components/Contracts/index.tsx index dee309951..5c331603f 100644 --- a/apps/hostd/components/Contracts/index.tsx +++ b/apps/hostd/components/Contracts/index.tsx @@ -1,4 +1,4 @@ -import { Table } from '@siafoundation/design-system' +import { EmptyState, Table } from '@siafoundation/design-system' import { useContracts } from '../../contexts/contracts' import { StateNoneMatching } from './StateNoneMatching' import { StateNoneYet } from './StateNoneYet' @@ -13,7 +13,7 @@ export function Contracts() { sortableColumns, toggleSort, limit, - dataState, + datasetState, cellContext, } = useContracts() @@ -22,15 +22,14 @@ export function Contracts() { - ) : dataState === 'noneYet' ? ( - - ) : dataState === 'error' ? ( - - ) : null + } + noneYet={} + error={} + /> } pageSize={limit} data={datasetPage} diff --git a/apps/hostd/components/Node/Logs.tsx b/apps/hostd/components/Node/Logs.tsx index fde74e77b..eb85d0b8b 100644 --- a/apps/hostd/components/Node/Logs.tsx +++ b/apps/hostd/components/Node/Logs.tsx @@ -4,7 +4,7 @@ import { Panel, Skeleton, Text, - useDatasetEmptyState, + useDatasetState, } from '@siafoundation/design-system' import { useLogsSearch } from '@siafoundation/hostd-react' import { humanDate } from '@siafoundation/units' @@ -21,7 +21,7 @@ export function Logs() { }, }) - const loadingState = useDatasetEmptyState( + const loadingState = useDatasetState( logs.data?.entries, logs.isValidating, logs.error, diff --git a/apps/hostd/components/Volumes/index.tsx b/apps/hostd/components/Volumes/index.tsx index ec0358316..160cf61f8 100644 --- a/apps/hostd/components/Volumes/index.tsx +++ b/apps/hostd/components/Volumes/index.tsx @@ -1,9 +1,9 @@ -import { Table } from '@siafoundation/design-system' +import { EmptyState, Table } from '@siafoundation/design-system' import { useVolumes } from '../../contexts/volumes' import { StateNoneYet } from './StateNoneYet' export function Volumes() { - const { dataset, isLoading, columns } = useVolumes() + const { dataset, datasetState, isLoading, columns } = useVolumes() return (
} + emptyState={ + } /> + } /> ) diff --git a/apps/hostd/components/Wallet/WalletFilterBar.tsx b/apps/hostd/components/Wallet/WalletFilterBar.tsx index bef137535..d3a72fa43 100644 --- a/apps/hostd/components/Wallet/WalletFilterBar.tsx +++ b/apps/hostd/components/Wallet/WalletFilterBar.tsx @@ -8,7 +8,7 @@ import { useTransactions } from '../../contexts/transactions' export function WalletFilterBar() { const { isSynced, syncPercent, isWalletSynced, walletScanPercent } = useSyncStatus() - const { offset, limit, pageCount, dataState } = useTransactions() + const { offset, limit, pageCount, datasetState } = useTransactions() return (
) diff --git a/apps/hostd/components/Wallet/index.tsx b/apps/hostd/components/Wallet/index.tsx index 72b6b0d75..1a7391345 100644 --- a/apps/hostd/components/Wallet/index.tsx +++ b/apps/hostd/components/Wallet/index.tsx @@ -1,4 +1,8 @@ -import { BalanceEvolution, Table } from '@siafoundation/design-system' +import { + BalanceEvolution, + EmptyState, + Table, +} from '@siafoundation/design-system' import { useTransactions } from '../../contexts/transactions' import { StateNoneMatching } from './StateNoneMatching' import { StateNoneYet } from './StateNoneYet' @@ -8,8 +12,8 @@ export function Wallet() { const { balances, metrics, - dataset, - dataState, + datasetPage, + datasetState, columns, cellContext, sortableColumns, @@ -29,18 +33,17 @@ export function Wallet() { ) : null}
- ) : dataState === 'noneYet' ? ( - - ) : dataState === 'error' ? ( - - ) : null + } + noneYet={} + error={} + /> } pageSize={defaultPageSize} - data={dataset} + data={datasetPage} context={cellContext} columns={columns} sortableColumns={sortableColumns} diff --git a/apps/hostd/contexts/contracts/index.tsx b/apps/hostd/contexts/contracts/index.tsx index a8881ecd3..d7f501a6c 100644 --- a/apps/hostd/contexts/contracts/index.tsx +++ b/apps/hostd/contexts/contracts/index.tsx @@ -1,6 +1,6 @@ import { useTableState, - useDatasetEmptyState, + useDatasetState, useServerFilters, getContractsTimeRangeBlockHeight, useMultiSelect, @@ -82,15 +82,6 @@ function useContractsMain() { [enabledColumns] ) - const isValidating = response.isValidating - const error = response.error - const dataState = useDatasetEmptyState( - _datasetPage, - isValidating, - error, - filters - ) - const { estimatedBlockHeight, isSynced, nodeBlockHeight } = useSyncStatus() const currentHeight = isSynced ? nodeBlockHeight : estimatedBlockHeight @@ -115,6 +106,16 @@ function useContractsMain() { }) }, [_datasetPage, multiSelect]) + const isValidating = response.isValidating + const error = response.error + const datasetState = useDatasetState({ + datasetPage, + isValidating, + error, + filters, + offset, + }) + const siascanUrl = useSiascanUrl() const cellContext = useMemo( @@ -128,7 +129,7 @@ function useContractsMain() { ) return { - dataState, + datasetState, offset, limit, cellContext, diff --git a/apps/hostd/contexts/transactions/index.tsx b/apps/hostd/contexts/transactions/index.tsx index 0751fb8a4..dc9fa8c6d 100644 --- a/apps/hostd/contexts/transactions/index.tsx +++ b/apps/hostd/contexts/transactions/index.tsx @@ -1,5 +1,5 @@ import { - useDatasetEmptyState, + useDatasetState, useServerFilters, useTableState, } from '@siafoundation/design-system' @@ -28,6 +28,7 @@ import { defaultSortField, sortOptions, } from './types' +import { Maybe } from '@siafoundation/types' const defaultPageSize = 50 @@ -58,7 +59,7 @@ function useTransactionsMain() { useServerFilters() const syncStatus = useSyncStatus() - const dataset = useMemo(() => { + const datasetPage = useMemo>(() => { if (!events.data || !pending.data) { return null } @@ -97,8 +98,12 @@ function useTransactionsMain() { } return res }) - return [...dataPending.reverse(), ...dataEvents] - }, [events.data, pending.data, syncStatus.nodeBlockHeight]) + if (offset === 0) { + return [...dataPending.reverse(), ...dataEvents] + } else { + return [...dataEvents] + } + }, [events.data, pending.data, syncStatus.nodeBlockHeight, offset]) const { configurableColumns, @@ -128,11 +133,6 @@ function useTransactionsMain() { [enabledColumns] ) - const isValidating = events.isValidating || pending.isValidating - const error = events.error || pending.error - - const dataState = useDatasetEmptyState(dataset, isValidating, error, filters) - const siascanUrl = useSiascanUrl() const cellContext = useMemo( () => ({ @@ -168,15 +168,25 @@ function useTransactionsMain() { [metrics.data] ) + const isValidating = events.isValidating || pending.isValidating + const error = events.error || pending.error + const datasetState = useDatasetState({ + datasetPage, + isValidating, + error, + filters, + offset, + }) + return { balances, metrics, - dataset, + datasetPage, error, - dataState, + datasetState, offset, limit, - pageCount: dataset?.length || 0, + pageCount: datasetPage?.length || 0, defaultPageSize, cellContext, configurableColumns, diff --git a/apps/hostd/contexts/volumes/dataset.ts b/apps/hostd/contexts/volumes/dataset.ts index 7481f9ed6..21b9b4832 100644 --- a/apps/hostd/contexts/volumes/dataset.ts +++ b/apps/hostd/contexts/volumes/dataset.ts @@ -4,15 +4,16 @@ import { useVolumes } from '@siafoundation/hostd-react' import { VolumeData } from './types' import BigNumber from 'bignumber.js' import { MiBToBytes } from '@siafoundation/units' +import { Maybe } from '@siafoundation/types' export function useDataset({ response, }: { response: ReturnType }) { - return useMemo(() => { + return useMemo>(() => { if (!response.data) { - return null + return undefined } return ( response.data?.map((contract) => { diff --git a/apps/hostd/contexts/volumes/index.tsx b/apps/hostd/contexts/volumes/index.tsx index ce21b86e0..b4a83d57a 100644 --- a/apps/hostd/contexts/volumes/index.tsx +++ b/apps/hostd/contexts/volumes/index.tsx @@ -1,7 +1,4 @@ -import { - useTableState, - useDatasetEmptyState, -} from '@siafoundation/design-system' +import { useTableState, useDatasetState } from '@siafoundation/design-system' import { VolumeMeta } from '@siafoundation/hostd-types' import { useVolumes as useVolumesData } from '@siafoundation/hostd-react' import { createContext, useContext, useMemo } from 'react' @@ -51,10 +48,14 @@ function useVolumesMain() { const isValidating = response.isValidating const error = response.error - const dataState = useDatasetEmptyState(dataset, isValidating, error, []) + const datasetState = useDatasetState({ + datasetPage: dataset, + isValidating, + error, + }) return { - dataState, + datasetState, totalCount: dataset?.length || 0, isLoading: response.isValidating, columns: filteredTableColumns, diff --git a/apps/renterd/components/Alerts/AlertsFilterMenu.tsx b/apps/renterd/components/Alerts/AlertsFilterMenu.tsx index 48cde694b..0b05516b5 100644 --- a/apps/renterd/components/Alerts/AlertsFilterMenu.tsx +++ b/apps/renterd/components/Alerts/AlertsFilterMenu.tsx @@ -12,7 +12,7 @@ export function AlertsFilterMenu() { limit, totals, pageCount, - dataState, + datasetState, datasetPage, dismissMany, } = useAlerts() @@ -53,7 +53,7 @@ export function AlertsFilterMenu() {
- {!dataState && !!pageCount && ( + {datasetState === 'loaded' && !!pageCount && (
- ) : dataState === 'noneYet' ? ( + ) : datasetState === 'noneYet' ? ( - ) : dataState === 'error' ? ( + ) : datasetState === 'error' ? ( ) : null } diff --git a/apps/renterd/components/Contracts/ContractsFilterBar.tsx b/apps/renterd/components/Contracts/ContractsFilterBar.tsx index 7a0cfd01d..03be224c8 100644 --- a/apps/renterd/components/Contracts/ContractsFilterBar.tsx +++ b/apps/renterd/components/Contracts/ContractsFilterBar.tsx @@ -3,7 +3,7 @@ import { useContracts } from '../../contexts/contracts' import { ContractsFilterMenu } from './ContractsFilterMenu' export function ContractsFilterBar() { - const { dataState, offset, limit, datasetFilteredCount, pageCount } = + const { datasetState, offset, limit, datasetFilteredCount, pageCount } = useContracts() return ( @@ -11,7 +11,7 @@ export function ContractsFilterBar() {
- ) : dataState === 'noneYet' ? ( - - ) : dataState === 'error' ? ( - - ) : null + } + noneYet={} + error={} + /> } sortableColumns={sortableColumns} pageSize={limit} diff --git a/apps/renterd/components/Files/Layout.tsx b/apps/renterd/components/Files/Layout.tsx index 4c226c745..11dae035e 100644 --- a/apps/renterd/components/Files/Layout.tsx +++ b/apps/renterd/components/Files/Layout.tsx @@ -8,7 +8,8 @@ import { RenterdAuthedPageLayoutProps, } from '../RenterdAuthedLayout' import { FilesActionsMenu } from '../FilesDirectory/FilesActionsMenu' -import { FilesStatsMenu } from '../FilesDirectory/FilesStatsMenu' +import { FilesDirectoryStatsMenu } from '../FilesDirectory/FilesDirectoryStatsMenu' +import { FilesFlatStatsMenu } from '../FilesFlat/FilesFlatStatsMenu' import { FilesDirectoryBulkMenu } from '../FilesDirectory/FilesDirectoryBulkMenu' import { useFilesManager } from '../../contexts/filesManager' import { FilesFlatBulkMenu } from '../FilesFlat/FilesFlatBulkMenu' @@ -29,7 +30,7 @@ export function useLayoutProps(): RenterdAuthedPageLayoutProps { sidenav: , openSettings: () => openDialog('settings'), nav: , - stats: , + stats: , actions: , dockedControls: , } @@ -42,7 +43,7 @@ export function useLayoutProps(): RenterdAuthedPageLayoutProps { sidenav: , openSettings: () => openDialog('settings'), nav: , - stats: , + stats: , actions: , dockedControls: , } diff --git a/apps/renterd/components/FilesDirectory/EmptyState/index.tsx b/apps/renterd/components/FilesDirectory/EmptyState/index.tsx index 14722c89a..370c9bab7 100644 --- a/apps/renterd/components/FilesDirectory/EmptyState/index.tsx +++ b/apps/renterd/components/FilesDirectory/EmptyState/index.tsx @@ -1,4 +1,9 @@ -import { Code, LinkButton, Text } from '@siafoundation/design-system' +import { + Code, + LinkButton, + StateNoneOnPage, + Text, +} from '@siafoundation/design-system' import { CloudUpload32 } from '@siafoundation/react-icons' import { routes } from '../../../config/routes' import { useFilesDirectory } from '../../../contexts/filesDirectory' @@ -12,23 +17,27 @@ import { StateNoneYetBuckets } from './StateNoneYetBuckets' export function EmptyState() { const { isViewingRootOfABucket, isViewingBuckets } = useFilesManager() - const { dataState } = useFilesDirectory() + const { datasetState } = useFilesDirectory() const autopilotNotEnabled = useAutopilotNotEnabled() const notEnoughContracts = useNotEnoughContracts() - if (dataState === 'noneMatchingFilters') { + if (datasetState === 'noneOnPage') { + return + } + + if (datasetState === 'noneMatchingFilters') { return } - if (dataState === 'error') { + if (datasetState === 'error') { return } // Only show on root directory and when there are no files. if ( isViewingRootOfABucket && - dataState === 'noneYet' && + datasetState === 'noneYet' && autopilotNotEnabled.active ) { return ( @@ -54,7 +63,7 @@ export function EmptyState() { // Only show on root directory and when there are no files. if ( isViewingRootOfABucket && - dataState === 'noneYet' && + datasetState === 'noneYet' && notEnoughContracts.active ) { return ( @@ -76,7 +85,7 @@ export function EmptyState() { ) } - if (dataState === 'noneYet') { + if (datasetState === 'noneYet') { if (isViewingBuckets) { return } diff --git a/apps/renterd/components/FilesDirectory/FilesStatsMenu/index.tsx b/apps/renterd/components/FilesDirectory/FilesDirectoryStatsMenu/index.tsx similarity index 78% rename from apps/renterd/components/FilesDirectory/FilesStatsMenu/index.tsx rename to apps/renterd/components/FilesDirectory/FilesDirectoryStatsMenu/index.tsx index ebad019c3..7d1a6dbd5 100644 --- a/apps/renterd/components/FilesDirectory/FilesStatsMenu/index.tsx +++ b/apps/renterd/components/FilesDirectory/FilesDirectoryStatsMenu/index.tsx @@ -4,9 +4,10 @@ import { FilesStatsMenuShared } from '../../Files/FilesStatsMenuShared' import { FilesFilterDirectoryMenu } from '../../Files/FilesFilterDirectoryMenu' import { useFilesManager } from '../../../contexts/filesManager' -export function FilesStatsMenu() { +export function FilesDirectoryStatsMenu() { const { isViewingABucket, isViewingBuckets } = useFilesManager() - const { limit, marker, isMore, pageCount, dataState } = useFilesDirectory() + const { limit, nextMarker, isMore, pageCount, datasetState } = + useFilesDirectory() return (
{isViewingBuckets ? ( @@ -18,10 +19,10 @@ export function FilesStatsMenu() { {isViewingABucket && ( )}
diff --git a/apps/renterd/components/FilesDirectory/FilesExplorer.tsx b/apps/renterd/components/FilesDirectory/FilesExplorer.tsx index f896c78cb..26a3d440f 100644 --- a/apps/renterd/components/FilesDirectory/FilesExplorer.tsx +++ b/apps/renterd/components/FilesDirectory/FilesExplorer.tsx @@ -18,7 +18,7 @@ export function FilesExplorer() { const { datasetPage, pageCount, - dataState, + datasetState, cellContext, onDragEnd, onDragOver, @@ -38,7 +38,7 @@ export function FilesExplorer() { >
} pageSize={10} data={datasetPage} diff --git a/apps/renterd/components/FilesFlat/EmptyState/index.tsx b/apps/renterd/components/FilesFlat/EmptyState/index.tsx index 21789cf4e..8c1a8b7ae 100644 --- a/apps/renterd/components/FilesFlat/EmptyState/index.tsx +++ b/apps/renterd/components/FilesFlat/EmptyState/index.tsx @@ -2,19 +2,24 @@ import { StateError } from './StateError' import { StateNoneMatching } from './StateNoneMatching' import { StateNoneYet } from './StateNoneYet' import { useFilesFlat } from '../../../contexts/filesFlat' +import { StateNoneOnPage } from '@siafoundation/design-system' export function EmptyState() { - const { dataState } = useFilesFlat() + const { datasetState } = useFilesFlat() - if (dataState === 'noneMatchingFilters') { + if (datasetState === 'noneOnPage') { + return + } + + if (datasetState === 'noneMatchingFilters') { return } - if (dataState === 'error') { + if (datasetState === 'error') { return } - if (dataState === 'noneYet') { + if (datasetState === 'noneYet') { return } diff --git a/apps/renterd/components/FilesFlat/FilesExplorer.tsx b/apps/renterd/components/FilesFlat/FilesExplorer.tsx index 06053f043..0f453e32d 100644 --- a/apps/renterd/components/FilesFlat/FilesExplorer.tsx +++ b/apps/renterd/components/FilesFlat/FilesExplorer.tsx @@ -6,13 +6,13 @@ import { columns } from '../../contexts/filesFlat/columns' export function FilesExplorer() { const { sortableColumns, toggleSort } = useFilesManager() - const { datasetPage, dataState, cellContext, sortField, sortDirection } = + const { datasetPage, datasetState, cellContext, sortField, sortDirection } = useFilesFlat() return (
} pageSize={10} data={datasetPage} diff --git a/apps/renterd/components/FilesFlat/FilesStatsMenu/index.tsx b/apps/renterd/components/FilesFlat/FilesFlatStatsMenu/index.tsx similarity index 73% rename from apps/renterd/components/FilesFlat/FilesStatsMenu/index.tsx rename to apps/renterd/components/FilesFlat/FilesFlatStatsMenu/index.tsx index ae573d840..1aff66059 100644 --- a/apps/renterd/components/FilesFlat/FilesStatsMenu/index.tsx +++ b/apps/renterd/components/FilesFlat/FilesFlatStatsMenu/index.tsx @@ -3,18 +3,18 @@ import { useFilesFlat } from '../../../contexts/filesFlat' import { FilesFilterDirectoryMenu } from '../../Files/FilesFilterDirectoryMenu' import { FilesStatsMenuShared } from '../../Files/FilesStatsMenuShared' -export function FilesStatsMenu() { - const { limit, pageCount, dataState, nextMarker, isMore } = useFilesFlat() +export function FilesFlatStatsMenu() { + const { limit, pageCount, datasetState, nextMarker, isMore } = useFilesFlat() return (
) diff --git a/apps/renterd/components/Hosts/HostsFilterBar.tsx b/apps/renterd/components/Hosts/HostsFilterBar.tsx index 94c7880b8..252c0483c 100644 --- a/apps/renterd/components/Hosts/HostsFilterBar.tsx +++ b/apps/renterd/components/Hosts/HostsFilterBar.tsx @@ -3,7 +3,7 @@ import { useHosts } from '../../contexts/hosts' import { HostsFilterMenu } from './HostsFilterMenu' export function HostsFilterBar() { - const { offset, limit, pageCount, dataState } = useHosts() + const { offset, limit, pageCount, datasetState } = useHosts() return (
@@ -12,7 +12,7 @@ export function HostsFilterBar() { offset={offset} limit={limit} pageTotal={pageCount} - isLoading={dataState === 'loading'} + isLoading={datasetState === 'loading'} />
) diff --git a/apps/renterd/components/Hosts/StateEmpty.tsx b/apps/renterd/components/Hosts/StateEmpty.tsx index 304aadd2d..9cc2b04d8 100644 --- a/apps/renterd/components/Hosts/StateEmpty.tsx +++ b/apps/renterd/components/Hosts/StateEmpty.tsx @@ -1,4 +1,9 @@ -import { Code, LinkButton, Text } from '@siafoundation/design-system' +import { + Code, + LinkButton, + StateNoneOnPage, + Text, +} from '@siafoundation/design-system' import { Filter32, HardDriveIcon, @@ -8,9 +13,13 @@ import { routes } from '../../config/routes' import { useHosts } from '../../contexts/hosts' export function StateEmpty() { - const { dataState } = useHosts() + const { datasetState } = useHosts() + + if (datasetState === 'noneOnPage') { + return + } - if (dataState === 'error') { + if (datasetState === 'error') { return (
@@ -23,7 +32,7 @@ export function StateEmpty() { ) } - if (dataState === 'noneMatchingFilters') { + if (datasetState === 'noneMatchingFilters') { return (
@@ -35,7 +44,7 @@ export function StateEmpty() {
) } - if (dataState === 'noneYet') { + if (datasetState === 'noneYet') { return (
diff --git a/apps/renterd/components/Hosts/index.tsx b/apps/renterd/components/Hosts/index.tsx index 063fd18ad..1fd7e2cdb 100644 --- a/apps/renterd/components/Hosts/index.tsx +++ b/apps/renterd/components/Hosts/index.tsx @@ -11,7 +11,7 @@ export function Hosts() { activeHost, columns, limit, - dataState, + datasetState, tableContext, viewMode, } = useHosts() @@ -56,7 +56,7 @@ export function Hosts() { focusColor={ activeHost ? getHostStatus(activeHost).colorName : undefined } - isLoading={dataState === 'loading'} + isLoading={datasetState === 'loading'} emptyState={} context={tableContext} pageSize={limit} diff --git a/apps/renterd/components/Keys/KeysStatsMenu/index.tsx b/apps/renterd/components/Keys/KeysStatsMenu/index.tsx index 7e5d4de82..543093aaa 100644 --- a/apps/renterd/components/Keys/KeysStatsMenu/index.tsx +++ b/apps/renterd/components/Keys/KeysStatsMenu/index.tsx @@ -2,7 +2,7 @@ import { PaginatorKnownTotal } from '@siafoundation/design-system' import { useKeys } from '../../../contexts/keys' export function KeysStatsMenu() { - const { limit, offset, datasetCount, pageCount, dataState } = useKeys() + const { limit, offset, datasetCount, pageCount, datasetState } = useKeys() return (
@@ -11,7 +11,7 @@ export function KeysStatsMenu() { limit={limit} datasetTotal={datasetCount} pageTotal={pageCount} - isLoading={dataState === 'loading'} + isLoading={datasetState === 'loading'} />
) diff --git a/apps/renterd/components/Keys/index.tsx b/apps/renterd/components/Keys/index.tsx index e4dcc0758..483602752 100644 --- a/apps/renterd/components/Keys/index.tsx +++ b/apps/renterd/components/Keys/index.tsx @@ -1,4 +1,4 @@ -import { Table } from '@siafoundation/design-system' +import { EmptyState, Table } from '@siafoundation/design-system' import { StateNoneMatching } from './StateNoneMatching' import { StateNoneYet } from './StateNoneYet' import { StateError } from './StateError' @@ -13,7 +13,7 @@ export function Keys() { sortableColumns, toggleSort, limit, - dataState, + datasetState, cellContext, } = useKeys() @@ -21,15 +21,14 @@ export function Keys() {
- ) : dataState === 'noneYet' ? ( - - ) : dataState === 'error' ? ( - - ) : null + } + noneYet={} + error={} + /> } sortableColumns={sortableColumns} pageSize={limit} diff --git a/apps/renterd/components/Uploads/EmptyState/index.tsx b/apps/renterd/components/Uploads/EmptyState/index.tsx index a6ccb146e..58047f6f1 100644 --- a/apps/renterd/components/Uploads/EmptyState/index.tsx +++ b/apps/renterd/components/Uploads/EmptyState/index.tsx @@ -2,19 +2,24 @@ import { StateError } from './StateError' import { StateNoneMatching } from './StateNoneMatching' import { StateNoneYet } from './StateNoneYet' import { useUploads } from '../../../contexts/uploads' +import { StateNoneOnPage } from '@siafoundation/design-system' export function EmptyState() { - const { dataState } = useUploads() + const { datasetState } = useUploads() - if (dataState === 'noneMatchingFilters') { + if (datasetState === 'noneOnPage') { + return + } + + if (datasetState === 'noneMatchingFilters') { return } - if (dataState === 'error') { + if (datasetState === 'error') { return } - if (dataState === 'noneYet') { + if (datasetState === 'noneYet') { return } diff --git a/apps/renterd/components/Uploads/UploadsStatsMenu/index.tsx b/apps/renterd/components/Uploads/UploadsStatsMenu/index.tsx index 651b121c7..539240641 100644 --- a/apps/renterd/components/Uploads/UploadsStatsMenu/index.tsx +++ b/apps/renterd/components/Uploads/UploadsStatsMenu/index.tsx @@ -2,18 +2,18 @@ import { Button, PaginatorMarker } from '@siafoundation/design-system' import { useUploads } from '../../../contexts/uploads' export function UploadsStatsMenu() { - const { abortAll, limit, pageCount, dataState, nextMarker, hasMore } = + const { abortAll, limit, pageCount, datasetState, nextMarker, hasMore } = useUploads() return (
{pageCount > 0 && }
) diff --git a/apps/renterd/components/Uploads/UploadsTable.tsx b/apps/renterd/components/Uploads/UploadsTable.tsx index 65365434c..13cd0121c 100644 --- a/apps/renterd/components/Uploads/UploadsTable.tsx +++ b/apps/renterd/components/Uploads/UploadsTable.tsx @@ -8,14 +8,14 @@ export function UploadsTable() { sortableColumns, toggleSort, datasetPage, - dataState, + datasetState, sortField, sortDirection, } = useUploads() return (
} pageSize={10} data={datasetPage} diff --git a/apps/renterd/components/Wallet/WalletFilterBar.tsx b/apps/renterd/components/Wallet/WalletFilterBar.tsx index bef137535..1c8eb1746 100644 --- a/apps/renterd/components/Wallet/WalletFilterBar.tsx +++ b/apps/renterd/components/Wallet/WalletFilterBar.tsx @@ -8,7 +8,8 @@ import { useTransactions } from '../../contexts/transactions' export function WalletFilterBar() { const { isSynced, syncPercent, isWalletSynced, walletScanPercent } = useSyncStatus() - const { offset, limit, pageCount, dataState } = useTransactions() + const { offset, limit, pageCount, datasetState } = useTransactions() + console.log(pageCount) return (
) diff --git a/apps/renterd/components/Wallet/index.tsx b/apps/renterd/components/Wallet/index.tsx index 921d636b6..dd109880c 100644 --- a/apps/renterd/components/Wallet/index.tsx +++ b/apps/renterd/components/Wallet/index.tsx @@ -1,4 +1,8 @@ -import { BalanceEvolution, Table } from '@siafoundation/design-system' +import { + BalanceEvolution, + EmptyState, + Table, +} from '@siafoundation/design-system' import { useTransactions } from '../../contexts/transactions' import { StateNoneMatching } from './StateNoneMatching' import { StateNoneYet } from './StateNoneYet' @@ -8,8 +12,8 @@ export function Wallet() { const { balances, metrics, - dataset, - dataState, + datasetPage, + datasetState, columns, cellContext, sortableColumns, @@ -31,18 +35,17 @@ export function Wallet() { ) : null}
- ) : dataState === 'noneYet' ? ( - - ) : dataState === 'error' ? ( - - ) : null + } + noneYet={} + error={} + /> } pageSize={defaultPageSize} - data={dataset} + data={datasetPage} context={cellContext} columns={columns} sortableColumns={sortableColumns} diff --git a/apps/renterd/contexts/alerts/index.tsx b/apps/renterd/contexts/alerts/index.tsx index edc7e7845..2e7a11533 100644 --- a/apps/renterd/contexts/alerts/index.tsx +++ b/apps/renterd/contexts/alerts/index.tsx @@ -1,6 +1,6 @@ import { useTableState, - useDatasetEmptyState, + useDatasetState, useServerFilters, } from '@siafoundation/design-system' import { useRouter } from 'next/router' @@ -23,6 +23,7 @@ import { useAlertsDismiss, } from '@siafoundation/renterd-react' import { useCallback } from 'react' +import { Maybe } from '@siafoundation/types' const defaultLimit = 50 @@ -106,7 +107,7 @@ function useAlertsMain() { [dismiss] ) - const datasetPage = useMemo(() => { + const datasetPage = useMemo>(() => { if (!response.data) { return undefined } @@ -150,12 +151,13 @@ function useAlertsMain() { [enabledColumns] ) - const dataState = useDatasetEmptyState( + const datasetState = useDatasetState({ datasetPage, - response.isValidating, - response.error, - filters - ) + isValidating: response.isValidating, + error: response.error, + filters, + offset, + }) const totals = useMemo( () => ({ @@ -171,7 +173,7 @@ function useAlertsMain() { ) return { - dataState, + datasetState, limit, offset, isLoading: response.isLoading, diff --git a/apps/renterd/contexts/contracts/dataset.tsx b/apps/renterd/contexts/contracts/dataset.tsx index 51ecb51ef..f66b24a49 100644 --- a/apps/renterd/contexts/contracts/dataset.tsx +++ b/apps/renterd/contexts/contracts/dataset.tsx @@ -8,6 +8,7 @@ import { blockHeightToTime } from '@siafoundation/units' import { defaultDatasetRefreshInterval } from '../../config/swr' import { usePrunableContractSizes } from './usePrunableContractSizes' import { Maybe } from '@siafoundation/types' +import { maybeFromNullishArrayResponse } from '@siafoundation/react-core' export function useDataset() { const response = useContractsData({ @@ -28,11 +29,12 @@ export function useDataset() { const datasetWithoutPrunable = useMemo< Maybe >(() => { - if (!response.data) { + const data = maybeFromNullishArrayResponse(response.data) + if (!data) { return undefined } const datums = - response.data?.map((c) => { + data.map((c) => { const isRenewed = c.renewedFrom !== '0000000000000000000000000000000000000000000000000000000000000000' @@ -70,7 +72,7 @@ export function useDataset() { return datum }) || [] return datums - }, [response.data, geoHosts, currentHeight]) + }, [response, geoHosts, currentHeight]) const { prunableSizes, diff --git a/apps/renterd/contexts/contracts/index.tsx b/apps/renterd/contexts/contracts/index.tsx index bcf732558..f78225dc6 100644 --- a/apps/renterd/contexts/contracts/index.tsx +++ b/apps/renterd/contexts/contracts/index.tsx @@ -1,7 +1,7 @@ import { useTableState, getContractsTimeRangeBlockHeight, - useDatasetEmptyState, + useDatasetState, useClientFilters, useClientFilteredDataset, useMultiSelect, @@ -108,13 +108,6 @@ function useContractsMain() { [enabledColumns] ) - const dataState = useDatasetEmptyState( - datasetFiltered, - response.isValidating, - response.error, - filters - ) - const siascanUrl = useSiascanUrl() const filteredStats = useFilteredStats({ datasetFiltered }) @@ -135,6 +128,14 @@ function useContractsMain() { }) }, [_datasetPage, multiSelect]) + const datasetState = useDatasetState({ + datasetPage, + isValidating: response.isValidating, + error: response.error, + offset, + filters, + }) + const cellContext = useMemo(() => { const context: ContractTableContext = { currentHeight: syncStatus.estimatedBlockHeight, @@ -177,7 +178,7 @@ function useContractsMain() { }) return { - dataState, + datasetState, limit, offset, isLoading: response.isLoading, diff --git a/apps/renterd/contexts/filesDirectory/dataset.tsx b/apps/renterd/contexts/filesDirectory/dataset.tsx index f1355702e..c9838b7d0 100644 --- a/apps/renterd/contexts/filesDirectory/dataset.tsx +++ b/apps/renterd/contexts/filesDirectory/dataset.tsx @@ -1,7 +1,7 @@ import { useObjects } from '@siafoundation/renterd-react' import { useDataset as useDatasetGeneric } from '../filesManager/dataset' import { bucketAndKeyParamsFromPath } from '../../lib/paths' -import { useRouter } from 'next/router' +import { useSearchParams } from '@siafoundation/next' import { useMemo } from 'react' import { useFilesManager } from '../filesManager' import { defaultDatasetRefreshInterval } from '../../config/swr' @@ -17,9 +17,9 @@ export function useDataset() { sortDirection, sortField, } = useFilesManager() - const router = useRouter() - const limit = Number(router.query.limit || defaultLimit) - const marker = router.query.marker as string + const searchParams = useSearchParams() + const limit = Number(searchParams.get('limit') || defaultLimit) + const marker = searchParams.get('marker') const pathParams = bucketAndKeyParamsFromPath(activeDirectoryPath) @@ -77,6 +77,7 @@ export function useDataset() { return { limit, marker, + nextMarker: response.data?.nextMarker || null, isMore: !!response.data?.hasMore, response, dataset: d.data, diff --git a/apps/renterd/contexts/filesDirectory/index.tsx b/apps/renterd/contexts/filesDirectory/index.tsx index 3401c8877..50c16e441 100644 --- a/apps/renterd/contexts/filesDirectory/index.tsx +++ b/apps/renterd/contexts/filesDirectory/index.tsx @@ -1,7 +1,4 @@ -import { - useDatasetEmptyState, - useMultiSelect, -} from '@siafoundation/design-system' +import { useDatasetState, useMultiSelect } from '@siafoundation/design-system' import { createContext, MouseEvent, @@ -25,7 +22,8 @@ function useFilesDirectoryMain() { isViewingBuckets, } = useFilesManager() - const { limit, marker, isMore, response, refresh, dataset } = useDataset() + const { limit, marker, nextMarker, isMore, response, refresh, dataset } = + useDataset() // Add parent directory to the dataset. const _datasetPage = useMemo(() => { @@ -125,12 +123,13 @@ function useFilesDirectoryMain() { }) }, [datasetPageWithOnClick, draggingObjects]) - const dataState = useDatasetEmptyState( - dataset, - response.isValidating, - response.error, - filters - ) + const datasetState = useDatasetState({ + datasetPage, + isValidating: response.isValidating, + error: response.error, + marker, + filters, + }) const filteredTableColumns = useMemo( () => @@ -150,13 +149,13 @@ function useFilesDirectoryMain() { ) return { - dataState, + datasetState, columns: filteredTableColumns, multiSelect, cellContext, refresh, limit, - marker, + nextMarker, isMore, datasetPage, pageCount: dataset?.length || 0, diff --git a/apps/renterd/contexts/filesFlat/dataset.tsx b/apps/renterd/contexts/filesFlat/dataset.tsx index 6f6badf71..5287983a7 100644 --- a/apps/renterd/contexts/filesFlat/dataset.tsx +++ b/apps/renterd/contexts/filesFlat/dataset.tsx @@ -1,7 +1,7 @@ import { useObjects } from '@siafoundation/renterd-react' import { SortField } from '../filesManager/types' import { useDataset as useDatasetGeneric } from '../filesManager/dataset' -import { useRouter } from 'next/router' +import { useSearchParams } from '@siafoundation/next' import { useMemo } from 'react' import { useFilesManager } from '../filesManager' import { defaultDatasetRefreshInterval } from '../../config/swr' @@ -16,9 +16,9 @@ const defaultLimit = 50 export function useDataset({ sortDirection, sortField }: Props) { const { activeBucketName, fileNamePrefixFilter } = useFilesManager() - const router = useRouter() - const limit = Number(router.query.limit || defaultLimit) - const marker = router.query.marker as string + const searchParams = useSearchParams() + const limit = Number(searchParams.get('limit') || defaultLimit) + const marker = searchParams.get('marker') const params = useMemo(() => { let prefix = '' @@ -74,6 +74,7 @@ export function useDataset({ sortDirection, sortField }: Props) { return { limit, marker, + nextMarker: response.data?.nextMarker || null, response, isMore: !!response.data?.hasMore, dataset: d.data, diff --git a/apps/renterd/contexts/filesFlat/index.tsx b/apps/renterd/contexts/filesFlat/index.tsx index dcc0bfaa8..c5bca156b 100644 --- a/apps/renterd/contexts/filesFlat/index.tsx +++ b/apps/renterd/contexts/filesFlat/index.tsx @@ -1,7 +1,4 @@ -import { - useDatasetEmptyState, - useMultiSelect, -} from '@siafoundation/design-system' +import { useDatasetState, useMultiSelect } from '@siafoundation/design-system' import { createContext, MouseEvent, @@ -23,23 +20,16 @@ function useFilesFlatMain() { enabledColumns, isViewingBuckets, } = useFilesManager() - const { limit, response, isMore, refresh, dataset } = useDataset({ - sortField, - sortDirection, - }) - const nextMarker = response.data?.nextMarker + const { limit, marker, nextMarker, response, isMore, refresh, dataset } = + useDataset({ + sortField, + sortDirection, + }) const _datasetPage = useMemo(() => { return dataset }, [dataset]) - const dataState = useDatasetEmptyState( - dataset, - response.isValidating, - response.error, - filters - ) - const filteredTableColumns = useMemo( () => columns.filter( @@ -72,6 +62,14 @@ function useFilesFlatMain() { }) }, [_datasetPage, multiSelect]) + const datasetState = useDatasetState({ + datasetPage, + isValidating: response.isValidating, + error: response.error, + marker, + filters, + }) + const cellContext = useMemo( () => ({ @@ -82,7 +80,7 @@ function useFilesFlatMain() { ) return { - dataState, + datasetState, multiSelect, cellContext, refresh, diff --git a/apps/renterd/contexts/filesManager/dataset.tsx b/apps/renterd/contexts/filesManager/dataset.tsx index fec8a14c3..f2aac5915 100644 --- a/apps/renterd/contexts/filesManager/dataset.tsx +++ b/apps/renterd/contexts/filesManager/dataset.tsx @@ -12,6 +12,7 @@ import { } from '../../lib/paths' import { useFilesManager } from '.' import { useEffect } from 'react' +import { Maybe } from '@siafoundation/types' type Props = { id: string @@ -34,7 +35,7 @@ export function useDataset({ id, objects }: Props) { setActiveDirectory, } = useFilesManager() const { dataset: allContracts } = useContracts() - const response = useSWR( + const response = useSWR>( objects.isValidating || buckets.isValidating ? undefined : [id, activeBucketName, activeDirectoryPath], diff --git a/apps/renterd/contexts/hosts/dataset.ts b/apps/renterd/contexts/hosts/dataset.ts index efad1db4b..041448210 100644 --- a/apps/renterd/contexts/hosts/dataset.ts +++ b/apps/renterd/contexts/hosts/dataset.ts @@ -11,6 +11,7 @@ import { ContractData } from '../contracts/types' import { SiaCentralHost } from '@siafoundation/sia-central-types' import { Maybe } from '@siafoundation/types' import { objectEntries } from '@siafoundation/design-system' +import { maybeFromNullishArrayResponse } from '@siafoundation/react-core' export function useDataset({ response, @@ -28,12 +29,13 @@ export function useDataset({ geoHosts: SiaCentralHost[] }) { return useMemo>(() => { - const allow = allowlist.data - const block = blocklist.data - if (!response.data || !allow || !block) { + const data = maybeFromNullishArrayResponse(response.data) + const allow = maybeFromNullishArrayResponse(allowlist.data) + const block = maybeFromNullishArrayResponse(blocklist.data) + if (!data || !allow || !block) { return undefined } - return response.data.map((host) => { + return data.map((host) => { const sch = geoHosts.find((gh) => gh.public_key === host.publicKey) return { ...getHostFields(host, allContracts), @@ -52,10 +54,10 @@ export function useDataset({ } }) }, [ - response.data, + response, allContracts, - allowlist.data, - blocklist.data, + allowlist, + blocklist, isAllowlistActive, geoHosts, ]) diff --git a/apps/renterd/contexts/hosts/index.tsx b/apps/renterd/contexts/hosts/index.tsx index 46657eab9..f661fe1ff 100644 --- a/apps/renterd/contexts/hosts/index.tsx +++ b/apps/renterd/contexts/hosts/index.tsx @@ -1,7 +1,7 @@ import { triggerToast, truncate, - useDatasetEmptyState, + useDatasetState, useMultiSelect, useServerFilters, useTableState, @@ -164,10 +164,6 @@ function useHostsMain() { [enabledColumns] ) - const isValidating = response.isValidating - const error = response.error - const dataState = useDatasetEmptyState(dataset, isValidating, error, filters) - const hostsWithLocation = useMemo( () => dataset?.filter((h) => h.location) as HostDataWithLocation[], [dataset] @@ -239,6 +235,16 @@ function useHostsMain() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [activeHost]) + const isValidating = response.isValidating + const error = response.error + const datasetState = useDatasetState({ + datasetPage, + isValidating, + error, + offset, + filters, + }) + return { setCmd, viewMode, @@ -247,7 +253,7 @@ function useHostsMain() { setViewMode, hostsWithLocation, error, - dataState, + datasetState, offset, limit, pageCount: datasetPage?.length || 0, diff --git a/apps/renterd/contexts/keys/index.tsx b/apps/renterd/contexts/keys/index.tsx index 4e0bc55f1..73555582b 100644 --- a/apps/renterd/contexts/keys/index.tsx +++ b/apps/renterd/contexts/keys/index.tsx @@ -1,6 +1,6 @@ import { useTableState, - useDatasetEmptyState, + useDatasetState, useClientFilters, useClientFilteredDataset, useMultiSelect, @@ -17,6 +17,7 @@ import { import { columns } from './columns' import { defaultDatasetRefreshInterval } from '../../config/swr' import { useSettingsS3 } from '@siafoundation/renterd-react' +import { Maybe } from '@siafoundation/types' const defaultLimit = 50 @@ -32,20 +33,19 @@ function useKeysMain() { }, }) - const dataset = useMemo(() => { + const dataset = useMemo>(() => { if (!response.data) { return undefined } - const data: KeyData[] = - Object.entries(response.data?.authentication.v4Keypairs || {}).map( - ([key, secret]) => { - return { - id: key, - key, - secret, - } - } - ) || [] + const data: KeyData[] = Object.entries( + response.data?.authentication.v4Keypairs || {} + ).map(([key, secret]) => { + return { + id: key, + key, + secret, + } + }) return data }, [response.data]) @@ -79,7 +79,7 @@ function useKeysMain() { sortDirection, }) - const _datasetPage = useMemo(() => { + const _datasetPage = useMemo(() => { if (!datasetFiltered) { return undefined } @@ -110,12 +110,13 @@ function useKeysMain() { [enabledColumns] ) - const dataState = useDatasetEmptyState( + const datasetState = useDatasetState({ datasetPage, - response.isValidating, - response.error, - filters - ) + isValidating: response.isValidating, + error: response.error, + offset, + filters, + }) const cellContext = useMemo( () => @@ -126,7 +127,7 @@ function useKeysMain() { ) return { - dataState, + datasetState, limit, offset, isLoading: response.isLoading, diff --git a/apps/renterd/contexts/transactions/index.tsx b/apps/renterd/contexts/transactions/index.tsx index a9030b468..cd6795722 100644 --- a/apps/renterd/contexts/transactions/index.tsx +++ b/apps/renterd/contexts/transactions/index.tsx @@ -1,7 +1,4 @@ -import { - useDatasetEmptyState, - useTableState, -} from '@siafoundation/design-system' +import { useDatasetState, useTableState } from '@siafoundation/design-system' import { useMetricsWallet, useWalletEvents, @@ -28,6 +25,7 @@ import { sortOptions, } from './types' import BigNumber from 'bignumber.js' +import { Maybe } from '@siafoundation/types' const defaultPageSize = 50 const filters = [] as string[] @@ -56,7 +54,7 @@ function useTransactionsMain() { }) const syncStatus = useSyncStatus() - const dataset = useMemo(() => { + const datasetPage = useMemo>(() => { if (!events.data || !pending.data) { return undefined } @@ -95,8 +93,12 @@ function useTransactionsMain() { } return res }) - return [...dataPending.reverse(), ...dataEvents] - }, [events.data, pending.data, syncStatus.nodeBlockHeight]) + if (offset === 0) { + return [...dataPending.reverse(), ...dataEvents] + } else { + return [...dataEvents] + } + }, [events.data, pending.data, syncStatus.nodeBlockHeight, offset]) const { configurableColumns, @@ -129,7 +131,13 @@ function useTransactionsMain() { const isValidating = events.isValidating || pending.isValidating const error = events.error || pending.error - const dataState = useDatasetEmptyState(dataset, isValidating, error, filters) + const datasetState = useDatasetState({ + datasetPage, + isValidating, + error, + offset, + filters, + }) const siascanUrl = useSiascanUrl() const cellContext = useMemo( @@ -177,12 +185,12 @@ function useTransactionsMain() { return { balances, metrics, - dataset, + datasetPage, error, - dataState, + datasetState, offset, limit, - pageCount: dataset?.length || 0, + pageCount: datasetPage?.length || 0, defaultPageSize, cellContext, configurableColumns, diff --git a/apps/renterd/contexts/uploads/index.tsx b/apps/renterd/contexts/uploads/index.tsx index 2edf51281..6017ac5a3 100644 --- a/apps/renterd/contexts/uploads/index.tsx +++ b/apps/renterd/contexts/uploads/index.tsx @@ -1,6 +1,6 @@ import { useTableState, - useDatasetEmptyState, + useDatasetState, useServerFilters, } from '@siafoundation/design-system' import { useSearchParams } from '@siafoundation/next' @@ -15,6 +15,8 @@ import { join, getFilename } from '../../lib/paths' import { useFilesManager } from '../filesManager' import { ObjectUploadData } from '../filesManager/types' import { MultipartUploadListUploadsPayload } from '@siafoundation/renterd-types' +import { maybeFromNullishArrayResponse } from '@siafoundation/react-core' +import { Maybe } from '@siafoundation/types' const defaultLimit = 50 @@ -69,11 +71,12 @@ function useUploadsMain() { ) }, [response.data, apiBusUploadAbort, activeBucket, uploadsMap]) - const dataset: ObjectUploadData[] = useMemo(() => { - if (!response.data?.uploads || !activeBucket?.name) { - return [] + const datasetPage = useMemo>(() => { + const uploads = maybeFromNullishArrayResponse(response.data?.uploads) + if (!uploads || !activeBucket?.name) { + return undefined } - return response.data.uploads.map((upload) => { + return uploads.map((upload) => { const id = upload.uploadID const key = upload.key const name = getFilename(key) @@ -106,7 +109,7 @@ function useUploadsMain() { }, } }) - }, [uploadsMap, activeBucket, response.data, apiBusUploadAbort]) + }, [uploadsMap, activeBucket, response, apiBusUploadAbort]) const { configurableColumns, @@ -136,24 +139,25 @@ function useUploadsMain() { [enabledColumns] ) - const dataState = useDatasetEmptyState( - dataset, - response.isValidating, - response.error, - filters - ) + const datasetState = useDatasetState({ + datasetPage, + isValidating: response.isValidating, + error: response.error, + marker, + filters, + }) return { abortAll, - dataState, + datasetState, limit, - nextMarker: response.data?.nextUploadIDMarker, + nextMarker: response.data?.nextUploadIDMarker || null, hasMore: !!response.data?.hasMore, isLoading: response.isLoading, error: response.error, - pageCount: dataset?.length || 0, + pageCount: datasetPage?.length || 0, columns: filteredTableColumns, - datasetPage: dataset, + datasetPage, configurableColumns, enabledColumns, sortableColumns, diff --git a/apps/walletd/components/Wallet/EventsFilterBar.tsx b/apps/walletd/components/Wallet/EventsFilterBar.tsx index 9c1935d29..2c118f927 100644 --- a/apps/walletd/components/Wallet/EventsFilterBar.tsx +++ b/apps/walletd/components/Wallet/EventsFilterBar.tsx @@ -2,7 +2,7 @@ import { PaginatorUnknownTotal } from '@siafoundation/design-system' import { useEvents } from '../../contexts/events' export function EventsFilterBar() { - const { offset, limit, pageCount, dataState } = useEvents() + const { offset, limit, pageCount, datasetState } = useEvents() return (
@@ -10,7 +10,7 @@ export function EventsFilterBar() { offset={offset} limit={limit} pageTotal={pageCount} - isLoading={dataState === 'loading'} + isLoading={datasetState === 'loading'} />
) diff --git a/apps/walletd/components/Wallet/index.tsx b/apps/walletd/components/Wallet/index.tsx index 37b35e13a..7cfe42f02 100644 --- a/apps/walletd/components/Wallet/index.tsx +++ b/apps/walletd/components/Wallet/index.tsx @@ -1,4 +1,4 @@ -import { Table } from '@siafoundation/design-system' +import { EmptyState, Table } from '@siafoundation/design-system' import { useEvents } from '../../contexts/events' import { StateNoneMatching } from './StateNoneMatching' import { StateNoneYet } from './StateNoneYet' @@ -7,7 +7,7 @@ import { StateError } from './StateError' export function Wallet() { const { dataset, - dataState, + datasetState, columns, cellContext, sortableColumns, @@ -20,15 +20,14 @@ export function Wallet() {
- ) : dataState === 'noneYet' ? ( - - ) : dataState === 'error' ? ( - - ) : null + } + noneYet={} + error={} + /> } pageSize={6} data={dataset} diff --git a/apps/walletd/components/WalletAddresses/index.tsx b/apps/walletd/components/WalletAddresses/index.tsx index 0bea96982..43bd506a5 100644 --- a/apps/walletd/components/WalletAddresses/index.tsx +++ b/apps/walletd/components/WalletAddresses/index.tsx @@ -1,4 +1,4 @@ -import { Table } from '@siafoundation/design-system' +import { EmptyState, Table } from '@siafoundation/design-system' import { useAddresses } from '../../contexts/addresses' import { StateNoneMatching } from './StateNoneMatching' import { StateNoneYet } from './StateNoneYet' @@ -7,7 +7,7 @@ import { StateError } from './StateError' export function WalletAddresses() { const { dataset, - dataState, + datasetState, columns, cellContext, sortableColumns, @@ -19,15 +19,14 @@ export function WalletAddresses() { return (
- ) : dataState === 'noneYet' ? ( - - ) : dataState === 'error' ? ( - - ) : null + } + noneYet={} + error={} + /> } pageSize={6} data={dataset} diff --git a/apps/walletd/components/WalletsList/WalletsFiltersBar.tsx b/apps/walletd/components/WalletsList/WalletsFiltersBar.tsx index 121ca2944..cbfcb6a12 100644 --- a/apps/walletd/components/WalletsList/WalletsFiltersBar.tsx +++ b/apps/walletd/components/WalletsList/WalletsFiltersBar.tsx @@ -4,7 +4,7 @@ import { useWallets } from '../../contexts/wallets' import { pluralize } from '@siafoundation/units' export function WalletsFiltersBar() { - const { datasetCount, unlockedCount } = useWallets() + const { pageCount: datasetCount, unlockedCount } = useWallets() return (
diff --git a/apps/walletd/components/WalletsList/index.tsx b/apps/walletd/components/WalletsList/index.tsx index 16b0cb4b7..d93bfeea6 100644 --- a/apps/walletd/components/WalletsList/index.tsx +++ b/apps/walletd/components/WalletsList/index.tsx @@ -1,4 +1,4 @@ -import { Table } from '@siafoundation/design-system' +import { EmptyState, Table } from '@siafoundation/design-system' import { useWallets } from '../../contexts/wallets' import { StateNoneYet } from './StateNoneYet' import { StateNoneMatching } from './StateNoneMatching' @@ -6,8 +6,8 @@ import { StateError } from './StateError' export function WalletsList() { const { - dataset, - dataState, + datasetPage, + datasetState, context, columns, sortableColumns, @@ -18,20 +18,20 @@ export function WalletsList() { return (
- {dataState === 'noneYet' && } - {dataState !== 'noneYet' && ( + {datasetState === 'noneYet' && } + {datasetState !== 'noneYet' && (
- ) : dataState === 'error' ? ( - - ) : null + } + error={} + /> } pageSize={6} - data={dataset} + data={datasetPage} context={context} columns={columns} sortableColumns={sortableColumns} diff --git a/apps/walletd/contexts/addresses/dataset.tsx b/apps/walletd/contexts/addresses/dataset.tsx index 2e2a4fa4a..d9d95f1ff 100644 --- a/apps/walletd/contexts/addresses/dataset.tsx +++ b/apps/walletd/contexts/addresses/dataset.tsx @@ -1,7 +1,4 @@ -import { - useDatasetEmptyState, - ClientFilterItem, -} from '@siafoundation/design-system' +import { useDatasetState, ClientFilterItem } from '@siafoundation/design-system' import { WalletAddressMetadata, WalletAddressesResponse, @@ -10,6 +7,7 @@ import { useWalletAddresses } from '@siafoundation/walletd-react' import { useMemo } from 'react' import { AddressData } from './types' import { OpenDialog, useDialog } from '../dialog' +import { Maybe } from '@siafoundation/types' export function transformAddressesResponse( response: WalletAddressesResponse, @@ -47,19 +45,19 @@ export function useDataset({ filters: ClientFilterItem[] }) { const { openDialog } = useDialog() - const dataset = useMemo(() => { + const dataset = useMemo>(() => { if (!response.data) { - return null + return undefined } return transformAddressesResponse(response.data, walletId, openDialog) }, [response.data, openDialog, walletId]) - const dataState = useDatasetEmptyState( - dataset, - response.isValidating, - response.error, - filters - ) + const datasetState = useDatasetState({ + datasetPage: dataset, + isValidating: response.isValidating, + error: response.error, + filters, + }) const lastIndex = (dataset || []).reduce( (highest, { metadata }) => @@ -69,7 +67,7 @@ export function useDataset({ return { dataset, - dataState, + datasetState, error: response.error, lastIndex, filters, diff --git a/apps/walletd/contexts/addresses/index.tsx b/apps/walletd/contexts/addresses/index.tsx index 13d776cc5..243454519 100644 --- a/apps/walletd/contexts/addresses/index.tsx +++ b/apps/walletd/contexts/addresses/index.tsx @@ -37,7 +37,7 @@ export function useAddressesMain() { const { filters, setFilter, removeFilter, removeLastFilter, resetFilters } = useClientFilters() - const { dataset, dataState, lastIndex } = useDataset({ + const { dataset, datasetState, lastIndex } = useDataset({ walletId, response, filters, @@ -87,7 +87,7 @@ export function useAddressesMain() { ) return { - dataState, + datasetState, error: response.error, datasetCount: datasetFiltered?.length || 0, columns: filteredTableColumns, diff --git a/apps/walletd/contexts/events/index.tsx b/apps/walletd/contexts/events/index.tsx index 64d5df722..942a62ac5 100644 --- a/apps/walletd/contexts/events/index.tsx +++ b/apps/walletd/contexts/events/index.tsx @@ -1,7 +1,7 @@ import { useTableState, - useDatasetEmptyState, useServerFilters, + useDatasetState, } from '@siafoundation/design-system' import { useWalletEvents, @@ -27,6 +27,7 @@ import { useRouter } from 'next/router' import { useSiascanUrl } from '../../hooks/useSiascanUrl' import { defaultDatasetRefreshInterval } from '../../config/swr' import { useSyncStatus } from '../../hooks/useSyncStatus' +import { Maybe } from '@siafoundation/types' const defaultLimit = 100 @@ -63,9 +64,9 @@ export function useEventsMain() { }) const syncStatus = useSyncStatus() - const dataset = useMemo(() => { + const datasetPage = useMemo>(() => { if (!responseEvents.data || !responseTxPool.data) { - return null + return undefined } const dataTxPool: EventData[] = responseTxPool.data.map((e) => { const amountSc = calculateScValue(e) @@ -106,8 +107,17 @@ export function useEventsMain() { } return res }) - return [...dataTxPool.reverse(), ...dataEvents] - }, [responseEvents.data, responseTxPool.data, syncStatus.nodeBlockHeight]) + if (offset === 0) { + return [...dataTxPool.reverse(), ...dataEvents] + } else { + return [...dataEvents] + } + }, [ + responseEvents.data, + responseTxPool.data, + syncStatus.nodeBlockHeight, + offset, + ]) const { configurableColumns, @@ -141,7 +151,13 @@ export function useEventsMain() { responseEvents.isValidating || responseTxPool.isValidating const error = responseEvents.error || responseTxPool.error - const dataState = useDatasetEmptyState(dataset, isValidating, error, filters) + const datasetState = useDatasetState({ + datasetPage, + isValidating, + error, + filters, + offset, + }) const siascanUrl = useSiascanUrl() const cellContext = useMemo( @@ -152,11 +168,11 @@ export function useEventsMain() { ) return { - dataState, + datasetState, error: responseEvents.error, - pageCount: dataset?.length || 0, + pageCount: datasetPage?.length || 0, columns: filteredTableColumns, - dataset, + dataset: datasetPage, cellContext, configurableColumns, enabledColumns, diff --git a/apps/walletd/contexts/wallets/index.tsx b/apps/walletd/contexts/wallets/index.tsx index 2c450c906..79ccff749 100644 --- a/apps/walletd/contexts/wallets/index.tsx +++ b/apps/walletd/contexts/wallets/index.tsx @@ -1,6 +1,6 @@ import { useTableState, - useDatasetEmptyState, + useDatasetState, useClientFilters, useClientFilteredDataset, } from '@siafoundation/design-system' @@ -20,6 +20,7 @@ import { useWalletSeedCache } from './useWalletSeedCache' import { useDialog } from '../dialog' import { useAppSettings } from '@siafoundation/react-core' import { defaultDatasetRefreshInterval } from '../../config/swr' +import { Maybe } from '@siafoundation/types' function useWalletsMain() { const response = useWalletsData({ @@ -51,9 +52,9 @@ function useWalletsMain() { // eslint-disable-next-line react-hooks/exhaustive-deps }, []) - const dataset = useMemo(() => { + const dataset = useMemo>(() => { if (!response.data) { - return null + return undefined } const data: WalletData[] = response.data.map((wallet) => { const { id, name, description, dateCreated, lastUpdated, metadata } = @@ -126,7 +127,7 @@ function useWalletsMain() { defaultSortField, }) - const datasetFiltered = useClientFilteredDataset({ + const datasetPage = useClientFilteredDataset({ dataset, filters, sortField, @@ -141,12 +142,12 @@ function useWalletsMain() { [enabledColumns] ) - const dataState = useDatasetEmptyState( - dataset, - response.isValidating, - response.error, - filters - ) + const datasetState = useDatasetState({ + datasetPage, + isValidating: response.isValidating, + error: response.error, + filters, + }) const context = useMemo( () => ({ @@ -157,12 +158,12 @@ function useWalletsMain() { ) return { - dataState, + datasetState, error: response.error, - datasetCount: datasetFiltered?.length || 0, + pageCount: datasetPage?.length || 0, unlockedCount: cachedMnemonicCount, columns: filteredTableColumns, - dataset: datasetFiltered, + datasetPage, context, wallet, configurableColumns, diff --git a/apps/walletd/dialogs/AddressUpdateDialog/index.tsx b/apps/walletd/dialogs/AddressUpdateDialog/index.tsx index 2340fb934..bf6e6dd28 100644 --- a/apps/walletd/dialogs/AddressUpdateDialog/index.tsx +++ b/apps/walletd/dialogs/AddressUpdateDialog/index.tsx @@ -56,7 +56,7 @@ export function AddressUpdateDialog({ }: Props) { const { walletId, address: addr } = params || {} const { openDialog } = useDialog() - const { dataset, dataState } = useWalletAddresses({ id: walletId }) + const { dataset, datasetState } = useWalletAddresses({ id: walletId }) const address = dataset?.find((d) => d.id === addr) const addressAdd = useWalletAddressAdd() const defaultValues = getDefaultValues({ @@ -74,7 +74,7 @@ export function AddressUpdateDialog({ // Resets form with latest default values after elements change and are // all thruthy // This is used because address data is async and can be intially undefined - initKey: [params, dataState === undefined], + initKey: [params, datasetState === 'loaded'], }) const fields = getFields() diff --git a/apps/walletd/hooks/useWalletAddresses.tsx b/apps/walletd/hooks/useWalletAddresses.tsx index bedb3693f..fbaba6876 100644 --- a/apps/walletd/hooks/useWalletAddresses.tsx +++ b/apps/walletd/hooks/useWalletAddresses.tsx @@ -20,14 +20,14 @@ export function useWalletAddresses({ id }: { id: string }) { }, }) - const { dataset, dataState, lastIndex } = useDataset({ + const { dataset, datasetState, lastIndex } = useDataset({ walletId: id, response, filters, }) return { - dataState, + datasetState, error: response.error, datasetCount: dataset?.length || 0, dataset: dataset, diff --git a/libs/design-system/src/app/AlertsDialog/index.tsx b/libs/design-system/src/app/AlertsDialog/index.tsx index 762b45f5c..c066bf7f3 100644 --- a/libs/design-system/src/app/AlertsDialog/index.tsx +++ b/libs/design-system/src/app/AlertsDialog/index.tsx @@ -6,7 +6,7 @@ import { Heading } from '../../core/Heading' import { Checkmark16 } from '@siafoundation/react-icons' import { Skeleton } from '../../core/Skeleton' import { Text } from '../../core/Text' -import { useDatasetEmptyState } from '../../hooks/useDatasetEmptyState' +import { useDatasetState } from '../../hooks/useDatasetState' import { humanDate } from '@siafoundation/units' import { cx } from 'class-variance-authority' import { times } from '@technically/lodash' @@ -36,8 +36,6 @@ type Props = { > } -const stubFilters: unknown[] = [] - export function AlertsDialog({ open, onOpenChange, @@ -47,12 +45,11 @@ export function AlertsDialog({ dataFieldOrder, dataFields, }: Props) { - const loadingState = useDatasetEmptyState( - alerts.data, - alerts.isValidating, - alerts.error, - stubFilters - ) + const loadingState = useDatasetState({ + datasetPage: alerts.data, + isValidating: alerts.isValidating, + error: alerts.error, + }) const [filter, setFilter] = useState() const dataset = useMemo( diff --git a/libs/design-system/src/components/EmptyState/StateError.tsx b/libs/design-system/src/components/EmptyState/StateError.tsx new file mode 100644 index 000000000..7f35eb623 --- /dev/null +++ b/libs/design-system/src/components/EmptyState/StateError.tsx @@ -0,0 +1,20 @@ +import { Text } from '@siafoundation/design-system' +import { MisuseOutline32 } from '@siafoundation/react-icons' +import { SWRError } from '@siafoundation/react-core' + +type Props = { + error?: SWRError +} + +export function StateError({ error }: Props) { + return ( +
+ + + + + Error loading data. Please try again later. + +
+ ) +} diff --git a/libs/design-system/src/components/EmptyState/StateNoData.tsx b/libs/design-system/src/components/EmptyState/StateNoData.tsx new file mode 100644 index 000000000..80c6aa8b2 --- /dev/null +++ b/libs/design-system/src/components/EmptyState/StateNoData.tsx @@ -0,0 +1,15 @@ +import { Text } from '@siafoundation/design-system' +import { ChartArea32 } from '@siafoundation/react-icons' + +export function StateNoData() { + return ( +
+ + + + + No data available. + +
+ ) +} diff --git a/libs/design-system/src/components/EmptyState/StateNoneMatching.tsx b/libs/design-system/src/components/EmptyState/StateNoneMatching.tsx new file mode 100644 index 000000000..28451c2d2 --- /dev/null +++ b/libs/design-system/src/components/EmptyState/StateNoneMatching.tsx @@ -0,0 +1,15 @@ +import { Text } from '@siafoundation/design-system' +import { Filter32 } from '@siafoundation/react-icons' + +export function StateNoneMatching() { + return ( +
+ + + + + No data matching filters. + +
+ ) +} diff --git a/libs/design-system/src/components/EmptyState/StateNoneOnPage.tsx b/libs/design-system/src/components/EmptyState/StateNoneOnPage.tsx new file mode 100644 index 000000000..bccd71c2e --- /dev/null +++ b/libs/design-system/src/components/EmptyState/StateNoneOnPage.tsx @@ -0,0 +1,28 @@ +import { Button, Text } from '@siafoundation/design-system' +import { usePagesRouter } from '@siafoundation/next' +import { Reset32 } from '@siafoundation/react-icons' +import { useCallback } from 'react' + +export function StateNoneOnPage() { + const router = usePagesRouter() + const back = useCallback(() => { + router.push({ + query: { + ...router.query, + offset: 0, + marker: undefined, + }, + }) + }, [router]) + return ( +
+ + + + + No data on this page, reset pagination to continue. + + +
+ ) +} diff --git a/libs/design-system/src/components/EmptyState/StateNoneYet.tsx b/libs/design-system/src/components/EmptyState/StateNoneYet.tsx new file mode 100644 index 000000000..14a9970fa --- /dev/null +++ b/libs/design-system/src/components/EmptyState/StateNoneYet.tsx @@ -0,0 +1,15 @@ +import { Text } from '@siafoundation/design-system' +import { DataBase32 } from '@siafoundation/react-icons' + +export function StateNoneYet() { + return ( +
+ + + + + There is no data yet. + +
+ ) +} diff --git a/libs/design-system/src/components/EmptyState/index.tsx b/libs/design-system/src/components/EmptyState/index.tsx new file mode 100644 index 000000000..ce203989e --- /dev/null +++ b/libs/design-system/src/components/EmptyState/index.tsx @@ -0,0 +1,30 @@ +import { DatasetState } from '@siafoundation/design-system' +import { StateNoneMatching } from './StateNoneMatching' +import { StateNoneYet } from './StateNoneYet' +import { StateError } from './StateError' +import { StateNoneOnPage } from './StateNoneOnPage' +import React from 'react' + +export function EmptyState({ + datasetState, + noneOnPage, + noneMatching, + noneYet, + error, +}: { + datasetState: DatasetState + noneOnPage?: React.ReactNode + noneMatching?: React.ReactNode + noneYet?: React.ReactNode + error?: React.ReactNode +}) { + return datasetState === 'noneOnPage' + ? noneOnPage || + : datasetState === 'noneMatchingFilters' + ? noneMatching || + : datasetState === 'noneYet' + ? noneYet || + : datasetState === 'error' + ? error || + : null +} diff --git a/libs/design-system/src/components/PaginatorMarker.tsx b/libs/design-system/src/components/PaginatorMarker.tsx index 63fb1b6b3..f6995331e 100644 --- a/libs/design-system/src/components/PaginatorMarker.tsx +++ b/libs/design-system/src/components/PaginatorMarker.tsx @@ -7,7 +7,7 @@ import { usePagesRouter } from '@siafoundation/next' import { LoadingDots } from './LoadingDots' type Props = { - marker?: string + nextMarker: string | null isMore: boolean limit: number pageTotal: number @@ -15,7 +15,7 @@ type Props = { } export function PaginatorMarker({ - marker, + nextMarker, isMore, pageTotal, isLoading, @@ -28,7 +28,6 @@ export function PaginatorMarker({ size="small" variant="gray" className="rounded-r-none" - disabled={!marker} onClick={() => router.push({ query: { @@ -61,14 +60,17 @@ export function PaginatorMarker({ size="small" variant="gray" className="rounded-none" - onClick={() => + onClick={() => { + console.log('xxx', { + marker: nextMarker, + }) router.push({ query: { ...router.query, - marker, + marker: nextMarker, }, }) - } + }} > diff --git a/libs/design-system/src/hooks/useClientFilteredDataset.ts b/libs/design-system/src/hooks/useClientFilteredDataset.ts index b69d7c63d..f6a28443f 100644 --- a/libs/design-system/src/hooks/useClientFilteredDataset.ts +++ b/libs/design-system/src/hooks/useClientFilteredDataset.ts @@ -1,6 +1,7 @@ import BigNumber from 'bignumber.js' import { useMemo } from 'react' import { ClientFilterItem } from './useClientFilters' +import { Maybe } from '@siafoundation/types' type DatumValue = | BigNumber @@ -12,7 +13,7 @@ type DatumValue = | object type Props> = { - dataset: Datum[] | undefined + dataset: Maybe filters: ClientFilterItem[] sortField: string sortDirection: 'asc' | 'desc' @@ -21,7 +22,7 @@ type Props> = { export function useClientFilteredDataset< Datum extends Record >({ dataset, filters, sortField, sortDirection }: Props) { - return useMemo(() => { + return useMemo>(() => { if (!dataset) { return undefined } diff --git a/libs/design-system/src/hooks/useDatasetEmptyState.tsx b/libs/design-system/src/hooks/useDatasetEmptyState.tsx deleted file mode 100644 index ee86e61e8..000000000 --- a/libs/design-system/src/hooks/useDatasetEmptyState.tsx +++ /dev/null @@ -1,49 +0,0 @@ -'use client' - -import { useEffect, useMemo, useState } from 'react' - -type EmptyState = - | 'loading' - | 'noneYet' - | 'noneMatchingFilters' - | 'error' - | undefined - -export function useDatasetEmptyState( - dataset: unknown[] | undefined, - isFetching: boolean, - error: Error | undefined, - filters: unknown[] -): EmptyState { - const [lastDatasetSize, setLastDatasetSize] = useState() - useEffect(() => { - // Update last dataset size every time refetching completes - if (!isFetching && dataset) { - setLastDatasetSize(dataset.length) - } - }, [isFetching, dataset, setLastDatasetSize]) - - return useMemo(() => { - if (error) { - return 'error' - } - // No previous dataset, initialize in loading state. - if (lastDatasetSize === undefined) { - return 'loading' - } - // Previous dataset not empty and loading. - // If a loading state between dataset is not desired, turn on - // swr keepPreviousData on the dataset. - // Note that dataset will be defined if revalidating same key. - if (lastDatasetSize > 0 && !dataset) { - return 'loading' - } - // Previous dataset was empty, show none state until results. - // This sticks to none state even when new data is loading to avoid a - // flickering skeleton loader. - if (lastDatasetSize === 0) { - return filters.length === 0 ? 'noneYet' : 'noneMatchingFilters' - } - return undefined - }, [dataset, lastDatasetSize, error, filters]) -} diff --git a/libs/design-system/src/hooks/useDatasetState.tsx b/libs/design-system/src/hooks/useDatasetState.tsx new file mode 100644 index 000000000..c41c2e235 --- /dev/null +++ b/libs/design-system/src/hooks/useDatasetState.tsx @@ -0,0 +1,99 @@ +'use client' + +import { useEffect, useMemo, useState } from 'react' + +export type DatasetState = + | 'loading' + | 'noneYet' + | 'noneMatchingFilters' + | 'noneOnPage' + | 'error' + | 'loaded' + +/** + * Returns the current sate of the dataset. Note that an empty dataset should + * be an empty array. An undefined value represents data that has not finished + * initial fetch or fetch after a key change. + **/ +export function useDatasetState({ + datasetPage, + isValidating, + error, + filters, + offset, + marker, +}: { + datasetPage: unknown[] | undefined + isValidating: boolean + error: Error | undefined + offset?: number + marker?: string | null + filters?: unknown[] +}): DatasetState { + const isOnFirstPage = getIsOnFirstPage({ offset, marker }) + const [lastDatasetSize, setLastDatasetSize] = useState() + useEffect(() => { + // Update last dataset size every time refetching completes. + if (!isValidating && datasetPage) { + setLastDatasetSize(datasetPage.length) + } + }, [isValidating, datasetPage, setLastDatasetSize]) + + return useMemo(() => { + if (error) { + return 'error' + } + // No previous dataset, initialize in loading state. + if (lastDatasetSize === undefined) { + return 'loading' + } + // Previous dataset not empty and loading. + // If a loading state between dataset is not desired, turn on + // swr keepPreviousData on the dataset. + // Note that dataset will be defined if revalidating same key. + if (lastDatasetSize > 0 && !datasetPage) { + return 'loading' + } + // Previous dataset was empty, show none state until results. + // This sticks to none state even when new data is loading to avoid a + // flickering skeleton loader. + if (lastDatasetSize === 0) { + if (!isOnFirstPage) { + return 'noneOnPage' + } + return !filters || filters.length === 0 + ? 'noneYet' + : 'noneMatchingFilters' + } + return 'loaded' + }, [datasetPage, lastDatasetSize, error, filters, isOnFirstPage]) +} + +function getIsOnFirstPage({ + offset, + marker, +}: { + offset?: number + marker?: string | null +}): boolean { + // If marker is undefined it is not in use. + if (marker !== undefined) { + // Page marker. + if (marker) { + return false + } + // If marker is null, its the first page. + if (marker === null) { + return true + } + } + // If both marker and offset are undefined, there is no paging. + if (offset === undefined) { + return true + } + // Offset based pagination. + if (offset > 0) { + return false + } + return true +} diff --git a/libs/design-system/src/index.ts b/libs/design-system/src/index.ts index 7e7926b99..33822148b 100644 --- a/libs/design-system/src/index.ts +++ b/libs/design-system/src/index.ts @@ -77,6 +77,12 @@ export * from './components/PaginatorUnknownTotal' export * from './components/PaginatorMarker' export * from './components/ListWithSeparators' export * from './components/ClientSideOnly' +export * from './components/EmptyState' +export * from './components/EmptyState/StateNoneOnPage' +export * from './components/EmptyState/StateNoneYet' +export * from './components/EmptyState/StateNoData' +export * from './components/EmptyState/StateNoneMatching' +export * from './components/EmptyState/StateError' // app export * from './app/AppPublicLayout' @@ -163,7 +169,7 @@ export * from './hooks/useClientFilters' export * from './hooks/useClientFilteredDataset' export * from './hooks/useServerFilters' export * from './hooks/useFormChanged' -export * from './hooks/useDatasetEmptyState' +export * from './hooks/useDatasetState' export * from './hooks/useSiacoinFiat' export * from './hooks/useOS' diff --git a/libs/react-core/src/arrayResponse.tsx b/libs/react-core/src/arrayResponse.tsx new file mode 100644 index 000000000..39bb8f49d --- /dev/null +++ b/libs/react-core/src/arrayResponse.tsx @@ -0,0 +1,20 @@ +import { Maybe, Nullish } from '@siafoundation/types' + +/** + * Ensure the data is an empty array if the response returns null. + * This makes responses consistent and allows other methods to distinguish + * between a loading state and an empty state. + * @param data Nullish + * @returns Maybe + */ +export function maybeFromNullishArrayResponse( + data: Nullish +): Maybe { + if (data) { + return data + } + if (data === null) { + return [] + } + return undefined +} diff --git a/libs/react-core/src/index.ts b/libs/react-core/src/index.ts index abae5f095..aa2e9b4a2 100644 --- a/libs/react-core/src/index.ts +++ b/libs/react-core/src/index.ts @@ -13,6 +13,7 @@ export * from './useExchangeRate' export * from './useTryUntil' export * from './userPrefersReducedMotion' export * from './mutate' +export * from './arrayResponse' export * from './workflows' export * from './coreProvider' diff --git a/libs/renterd-types/src/bus.ts b/libs/renterd-types/src/bus.ts index 9614b7578..c9c5b6a96 100644 --- a/libs/renterd-types/src/bus.ts +++ b/libs/renterd-types/src/bus.ts @@ -9,7 +9,7 @@ import { Transaction, TransactionID, WalletEvent, - Maybe, + Nullable, } from '@siafoundation/types' import { ConsensusState, @@ -232,7 +232,7 @@ export type HostsPayload = { limit?: number maxLastScan?: string } -export type HostsResponse = Maybe +export type HostsResponse = Nullable export type HostParams = { hostkey: string } export type HostPayload = Host @@ -291,7 +291,7 @@ export type HostScanResponse = { export type ContractsParams = void export type ContractsPayload = void -export type ContractsResponse = Maybe +export type ContractsResponse = Nullable export type ContractAcquireParams = { id: string diff --git a/libs/types/src/utils.ts b/libs/types/src/utils.ts index 4ada1062b..44d529936 100644 --- a/libs/types/src/utils.ts +++ b/libs/types/src/utils.ts @@ -1,4 +1,6 @@ export type Maybe = T | undefined +export type Nullable = T | null +export type Nullish = T | null | undefined export type NoUndefined = { [K in keyof T]: Exclude