Skip to content

Commit

Permalink
feat: implement reverse paging (#381)
Browse files Browse the repository at this point in the history
Use the new `pre` caveat and `before` and `after` cursors from
storacha/w3infra#139 to implement reverse
paging in the uploads list headless components and w3console


https://user-images.githubusercontent.com/1113/218706799-a0cac0cd-7da2-47d5-b19e-a2bc41ceda2f.mov
  • Loading branch information
travis authored Feb 23, 2023
1 parent 73a061a commit 10f059a
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 61 deletions.
2 changes: 1 addition & 1 deletion packages/react-uploads-list/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"dependencies": {
"@w3ui/react-keyring": "workspace:^",
"@w3ui/uploads-list-core": "workspace:^",
"@web3-storage/capabilities": "^2.2.0",
"@web3-storage/capabilities": "^2.3.0",
"ariakit-react-utils": "0.17.0-next.27"
},
"peerDependencies": {
Expand Down
73 changes: 44 additions & 29 deletions packages/react-uploads-list/src/UploadsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,26 +29,29 @@ export type UploadsListComponentContextValue = [
actions: UploadsListComponentContextActions
]

export const UploadsListComponentContext =
createContext<UploadsListComponentContextValue>([
{
/**
* A boolean indicating whether the uploads list
* is currently loading data from the server.
*/
loading: false
},
{
/**
* A function that will load the next page of results.
*/
next: async () => {},
/**
* A function that will reload the uploads list.
*/
reload: async () => {}
}
])
export const UploadsListComponentContext = createContext<UploadsListComponentContextValue>([
{
/**
* A boolean indicating whether the uploads list
* is currently loading data from the server.
*/
loading: false
},
{
/**
* A function that will load the previous page of results.
*/
prev: async () => {},
/**
* A function that will load the next page of results.
*/
next: async () => {},
/**
* A function that will reload the uploads list.
*/
reload: async () => {}
}
])

export type UploadsListRootOptions = Options<typeof Fragment>
export type UploadsListRenderProps = Omit<
Expand Down Expand Up @@ -94,9 +97,26 @@ export const UploadsListRoot = (props: UploadsListRootProps): JSX.Element => {
)
}

export type PrevButtonOptions<T extends As = 'button'> = Options<T>
export type PrevButtonProps<T extends As = 'button'> = Props<PrevButtonOptions<T>>

/**
* Button that loads the next page of results.
*
* A 'button' designed to work with `UploadsList`. Any passed props will
* be passed along to the `button` component.
*/
export const PrevButton: Component<PrevButtonProps> = createComponent((props: any) => {
const [, { prev }] = useContext(UploadsListComponentContext)
const onClick = useCallback((e: React.MouseEvent) => {
e.preventDefault()
void prev()
}, [prev])
return createElement('button', { ...props, onClick })
})

export type NextButtonOptions<T extends As = 'button'> = Options<T>
export type NextButtonProps<T extends As = 'button'> =
Props<NextButtonOptions<T>>
export type NextButtonProps<T extends As = 'button'> = Props<NextButtonOptions<T>>

/**
* Button that loads the next page of results.
Expand All @@ -119,9 +139,7 @@ export const NextButton: Component<NextButtonProps> = createComponent(
)

export type ReloadButtonOptions<T extends As = 'button'> = Options<T>
export type ReloadButtonProps<T extends As = 'button'> = Props<
ReloadButtonOptions<T>
>
export type ReloadButtonProps<T extends As = 'button'> = Props<ReloadButtonOptions<T>>

/**
* Button that reloads an `UploadsList`.
Expand Down Expand Up @@ -150,7 +168,4 @@ export function useUploadsListComponent (): UploadsListComponentContextValue {
return useContext(UploadsListComponentContext)
}

export const UploadsList = Object.assign(UploadsListRoot, {
NextButton,
ReloadButton
})
export const UploadsList = Object.assign(UploadsListRoot, { PrevButton, NextButton, ReloadButton })
25 changes: 17 additions & 8 deletions packages/react-uploads-list/src/providers/UploadsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const uploadsListContextDefaultValue: UploadsListContextValue = [
loading: false
},
{
prev: async () => {},
next: async () => {},
reload: async () => {}
}
Expand Down Expand Up @@ -46,13 +47,14 @@ export function UploadsListProvider ({
children
}: UploadsListProviderProps): JSX.Element {
const [{ space, agent }, { getProofs }] = useKeyring()
const [cursor, setCursor] = useState<string>()
const [before, setBefore] = useState<string>()
const [after, setAfter] = useState<string>()
const [loading, setLoading] = useState(false)
const [error, setError] = useState<Error>()
const [data, setData] = useState<UploadListResult[]>()
const [controller, setController] = useState(new AbortController())

const loadPage = async (cursor?: string): Promise<void> => {
const loadPage = async (cursor?: string, pre?: boolean): Promise<void> => {
if (space == null) return
if (agent == null) return

Expand All @@ -71,11 +73,15 @@ export function UploadsListProvider ({
const page = await list(conf, {
cursor,
size,
pre,
signal: newController.signal,
connection
})
setCursor(page.cursor)
setData(page.results)
if (page.size > 0) {
setBefore(page.before)
setAfter(page.after)
setData(page.results)
}
} catch (error_: any) {
if (error_.name !== 'AbortError') {
/* eslint-disable no-console */
Expand All @@ -90,17 +96,20 @@ export function UploadsListProvider ({

const state = { data, loading, error }
const actions = {
next: async (): Promise<void> => {
await loadPage(cursor)
},
next: async (): Promise<void> => { await loadPage(after) },
prev: async (): Promise<void> => { await loadPage(before, true) },
reload: async (): Promise<void> => {
setCursor(undefined)
setBefore(undefined)
setAfter(undefined)
await loadPage()
}
}

// we should reload the page any time the space or agent change
useEffect(() => {
setBefore(undefined)
setAfter(undefined)
setData([])
void loadPage()
}, [space, agent])

Expand Down
33 changes: 21 additions & 12 deletions packages/react/src/UploadsList.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import type { UploadListResult } from '@w3ui/uploads-list-core'
import React from 'react'
import { ChevronLeftIcon, ChevronRightIcon, ArrowPathIcon } from '@heroicons/react/20/solid'
import type { UploadListResult } from '@w3ui/uploads-list-core'
import { UploadsList as UploadsListCore } from '@w3ui/react-uploads-list'

function Uploads ({ uploads }: { uploads?: UploadListResult[] }): JSX.Element {
interface UploadsProps {
uploads?: UploadListResult[]
loading: boolean
}

function Uploads ({ uploads, loading }: UploadsProps): JSX.Element {
return uploads === undefined || uploads.length === 0
? (
<>
<div className='w3-uploads-list-no-uploads'>No uploads</div>
<nav>
<UploadsListCore.ReloadButton className='reload w3ui-button'>
Reload
<nav className='flex flex-row justify-center'>
<UploadsListCore.ReloadButton className='reload w3ui-button w-auto px-2'>
<ArrowPathIcon className={`h-6 w-6 ${loading ? 'animate-spin' : ''}`}/>
</UploadsListCore.ReloadButton>
</nav>
</>
Expand All @@ -36,13 +42,16 @@ function Uploads ({ uploads }: { uploads?: UploadListResult[] }): JSX.Element {
</tbody>
</table>
</div>
<nav>
<UploadsListCore.NextButton className='next w3ui-button'>
Next
</UploadsListCore.NextButton>
<UploadsListCore.ReloadButton className='reload w3ui-button'>
Reload
<nav className='flex flex-row justify-center'>
<UploadsListCore.PrevButton className='prev w3ui-button w-auto px-2'>
<ChevronLeftIcon className='h-6 w-6'/>
</UploadsListCore.PrevButton>
<UploadsListCore.ReloadButton className='reload w3ui-button w-auto px-2'>
<ArrowPathIcon className={`h-6 w-6 ${loading ? 'animate-spin' : ''}`}/>
</UploadsListCore.ReloadButton>
<UploadsListCore.NextButton className='next w3ui-button w-auto px-2'>
<ChevronRightIcon className='h-6 w-6'/>
</UploadsListCore.NextButton>
</nav>
</>
)
Expand All @@ -53,7 +62,7 @@ export const UploadsList = (): JSX.Element => {
<UploadsListCore>
{(props) => (
<div className='w3-uploads-list'>
<Uploads uploads={props.uploadsList?.[0].data} />
<Uploads uploads={props.uploadsList?.[0].data} loading={props.uploadsList?.[0].loading ?? false}/>
</div>
)}
</UploadsListCore>
Expand Down
2 changes: 1 addition & 1 deletion packages/uploader-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"homepage": "https://github.com/web3-storage/w3ui/tree/main/packages/uploader-core",
"dependencies": {
"@ucanto/interface": "^4.2.3",
"@web3-storage/upload-client": "^5.4.0",
"@web3-storage/upload-client": "^6.0.0",
"multiformats": "^11.0.1"
},
"eslintConfig": {
Expand Down
2 changes: 1 addition & 1 deletion packages/uploads-list-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"homepage": "https://github.com/web3-storage/w3ui/tree/main/packages/uploads-list-core",
"dependencies": {
"@ucanto/interface": "^4.2.3",
"@web3-storage/upload-client": "^5.4.0"
"@web3-storage/upload-client": "^6.0.0"
},
"eslintConfig": {
"extends": [
Expand Down
4 changes: 4 additions & 0 deletions packages/uploads-list-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ export interface UploadsListContextState {
}

export interface UploadsListContextActions {
/**
* Load the next page of results.
*/
prev: () => Promise<void>
/**
* Load the next page of results.
*/
Expand Down
18 changes: 9 additions & 9 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 10f059a

Please sign in to comment.