From 3a92d092d4460d5fec40b6d1d8f5116a90ac3ea4 Mon Sep 17 00:00:00 2001 From: Faton Ramadani Date: Wed, 16 Aug 2023 15:00:07 +0200 Subject: [PATCH] feat(frontend): Add List pagination + add loading state in tables (#2096) * feat(frontend): Add List pagination + add loading state in tables * feat(frontend): Use the same configuration as the Table, and managed pagination properly * feat(frontend): Fix wording + update default code + preconnect page output * feat(frontend): Fix wording + update default code + preconnect page output * feat(frontend): fix default code * feat(frontend): add comment in the default code to explain what the page parameter is * feat(frontend): revert changes --- .../components/display/table/AppTable.svelte | 12 +- .../display/table/AppTableFooter.svelte | 48 ++++- .../apps/components/layout/AppList.svelte | 192 ++++++++++++++---- .../apps/editor/component/components.ts | 37 +++- .../apps/editor/component/default-codes.ts | 13 ++ .../OneOfInputSpecsEditor.svelte | 3 +- 6 files changed, 245 insertions(+), 60 deletions(-) diff --git a/frontend/src/lib/components/apps/components/display/table/AppTable.svelte b/frontend/src/lib/components/apps/components/display/table/AppTable.svelte index b68c3f3b0a..749096d681 100644 --- a/frontend/src/lib/components/apps/components/display/table/AppTable.svelte +++ b/frontend/src/lib/components/apps/components/display/table/AppTable.svelte @@ -65,6 +65,7 @@ }) let inputs = {} + let loading: boolean = false $: setSearch(searchValue) @@ -251,7 +252,15 @@ /> {/each} - + {#if Array.isArray(result) && result.every(isObject)}
{:else if result != undefined} diff --git a/frontend/src/lib/components/apps/components/display/table/AppTableFooter.svelte b/frontend/src/lib/components/apps/components/display/table/AppTableFooter.svelte index 8d0566da84..c7fb043583 100644 --- a/frontend/src/lib/components/apps/components/display/table/AppTableFooter.svelte +++ b/frontend/src/lib/components/apps/components/display/table/AppTableFooter.svelte @@ -2,7 +2,7 @@ import Button from '$lib/components/common/button/Button.svelte' import { faDownload } from '@fortawesome/free-solid-svg-icons' import type { Table } from '@tanstack/svelte-table' - import { ChevronLeft, ChevronRight } from 'lucide-svelte' + import { ChevronLeft, ChevronRight, Loader2 } from 'lucide-svelte' import type { Readable } from 'svelte/store' import { twMerge } from 'tailwind-merge' @@ -16,6 +16,7 @@ export { c as class } export let style = '' export let download: boolean = true + export let loading: boolean = false function convertJSONToCSV(objArray: Record[]) { let str = '' @@ -45,6 +46,14 @@ downloadAnchorNode.click() downloadAnchorNode.remove() } + + let isPreviousLoading = false + let isNextLoading = false + + $: if (!loading) { + isPreviousLoading = false + isNextLoading = false + } {#if result.length > pageSize || manualPagination || download} @@ -55,26 +64,43 @@ {#if result.length > pageSize || manualPagination}
{$table.getState().pagination.pageIndex + 1} of {$table.getPageCount()}
diff --git a/frontend/src/lib/components/apps/components/layout/AppList.svelte b/frontend/src/lib/components/apps/components/layout/AppList.svelte index 061379ec9c..89fd850d17 100644 --- a/frontend/src/lib/components/apps/components/layout/AppList.svelte +++ b/frontend/src/lib/components/apps/components/layout/AppList.svelte @@ -10,6 +10,8 @@ import ResolveConfig from '../helpers/ResolveConfig.svelte' import { components } from '../../editor/component' import RunnableWrapper from '../helpers/RunnableWrapper.svelte' + import { Button } from '$lib/components/common' + import { Loader2, ChevronLeft, ChevronRight } from 'lucide-svelte' export let id: string export let componentInput: AppInput | undefined @@ -20,11 +22,13 @@ const { app, focusedGrid, selectedComponent, worldStore, connectingInput } = getContext('AppViewerContext') + let page = 0 const outputs = initOutput($worldStore, id, { result: undefined, loading: false, - inputs: {} + inputs: {}, + page: 0 }) let resolvedConfig = initConfig( @@ -45,6 +49,57 @@ $: isCard = resolvedConfig.width?.selected == 'card' let inputs = {} + let loading: boolean = false + let isPreviousLoading = false + let isNextLoading = false + + $: if (!loading) { + isPreviousLoading = false + isNextLoading = false + } + + function getPagination( + configuration: { + auto: { pageSize: number | undefined } + manual: { pageCount: number | undefined } + }, + mode: 'auto' | 'manual' = 'auto', + initialData: Array | undefined = [], + page: number = 0 + ) { + if (mode === 'auto') { + const pageSize: number = configuration.auto.pageSize ?? 0 + const data = initialData?.slice(0 + page * pageSize, pageSize + page * pageSize) ?? [] + const shouldDisplayPagination = pageSize < initialData?.length ?? false + const total = Math.ceil(initialData?.length / pageSize ?? 0) + + return { + data, + shouldDisplayPagination, + indexOffset: page * pageSize, + disableNext: pageSize > 0 && (page + 1) * pageSize >= initialData?.length, + total: total + } + } else { + const pageCount = configuration.manual.pageCount ?? 0 + const total = pageCount + + return { + shouldDisplayPagination: true, + data: initialData ?? [], + indexOffset: 0, + disableNext: page + 1 >= pageCount, + total: total + } + } + } + + $: pagination = getPagination( + resolvedConfig.pagination?.configuration, + resolvedConfig.pagination?.selected, + result, + page + ) {#each Object.keys(components['listcomponent'].initialData.configuration) as key (key)} @@ -66,54 +121,103 @@ {id} bind:initializing bind:result + bind:loading > -
- {#if $app.subgrids?.[`${id}-0`]} - {#if Array.isArray(result) && result.length > 0} - {#each result ?? [] as value, index} -
- { - outputs?.inputs.set(inputs, true) - }} - bind:inputs - {value} - {index} +
+
+ {#if $app.subgrids?.[`${id}-0`]} + {#if Array.isArray(pagination.data) && pagination.data.length > 0} + {#each pagination?.data ?? [] as value, index} +
- { - if (!$connectingInput.opened) { - $selectedComponent = [id] - } - onFocus() + { + outputs?.inputs.set(inputs, true) }} - /> - -
- {/each} - {:else} - - - - {#if !Array.isArray(result)} -
Input data is not an array
+ bind:inputs + {value} + index={index + pagination.indexOffset} + > + { + if (!$connectingInput.opened) { + $selectedComponent = [id] + } + onFocus() + }} + /> + +
+ {/each} + {:else} + + + + {#if !Array.isArray(result)} +
Input data is not an array
+ {/if} {/if} {/if} +
+ {#if pagination.shouldDisplayPagination} +
+ + +
{page + 1} of {pagination.total}
+
{/if}
diff --git a/frontend/src/lib/components/apps/editor/component/components.ts b/frontend/src/lib/components/apps/editor/component/components.ts index b34a1313ed..6a7111c49f 100644 --- a/frontend/src/lib/components/apps/editor/component/components.ts +++ b/frontend/src/lib/components/apps/editor/component/components.ts @@ -431,9 +431,11 @@ const paginationOneOf = { type: 'oneOf', selected: 'auto', labels: { - auto: 'Auto', - manual: 'Manual' + auto: 'Managed by component', + manual: 'Managed by runnable' }, + tooltip: + 'Pagination can be managed using two methods: By the component: Based on a specified page size, the component divides the array into several pages. By the runnable: The component shows all items, leaving the task of pagination to the runnable. The current page number is available in the component outputs.', configuration: { auto: { pageSize: { @@ -603,7 +605,36 @@ export const components = { fieldType: 'number', value: 280, tooltip: 'Height in pixels' - } + }, + + pagination: { + type: 'oneOf', + selected: 'auto', + labels: { + auto: 'Managed by component', + manual: 'Managed by runnable' + }, + tooltip: + 'Pagination can be managed using two methods: By the component: Based on a specified page size, the component divides the array into several pages. By the runnable: The component shows all items, leaving the task of pagination to the runnable. The current page number is available in the component outputs.', + configuration: { + manual: { + pageCount: { + type: 'static', + fieldType: 'number', + value: -1, + tooltip: 'Number of pages (-1 if you do not know)' + } + }, + auto: { + pageSize: { + type: 'static', + fieldType: 'number', + value: 20, + tooltip: 'Number of items per page' + } + } + } + } as const }, componentInput: { type: 'static', diff --git a/frontend/src/lib/components/apps/editor/component/default-codes.ts b/frontend/src/lib/components/apps/editor/component/default-codes.ts index d6fc891ddd..9f2490e9dc 100644 --- a/frontend/src/lib/components/apps/editor/component/default-codes.ts +++ b/frontend/src/lib/components/apps/editor/component/default-codes.ts @@ -530,5 +530,18 @@ return { "required": [] } ` + }, + listcomponent: { + deno: `export async function main() { + return [{ + "foo": 1, + }, { + "foo": 2, + }, { + "foo": 3, + }]; +}`, + python3: `def main(): + return [{"foo": 1}, {"foo": 2}, {"foo": 3}]` } } as const diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/OneOfInputSpecsEditor.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/OneOfInputSpecsEditor.svelte index d01ac5fbe5..7894ed7b95 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/OneOfInputSpecsEditor.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/OneOfInputSpecsEditor.svelte @@ -33,7 +33,7 @@
{capitalize(addWhitespaceBeforeCapitals(key))}  {#if tooltip} - {tooltip} + {tooltip} {/if}