From 587c777d57213f308c0c56effecdf40f34f942dc Mon Sep 17 00:00:00 2001 From: Faton Ramadani Date: Sat, 20 Apr 2024 10:54:34 +0200 Subject: [PATCH] feat(frontend): Improve tables (#3577) * feat(frontend): wip * feat(frontend): wip * feat(frontend): aggrid theme * feat(frontend): aggrid theme * feat(frontend): aggrid theme * feat(frontend): support old themes --- frontend/src/lib/assets/app.css | 7 +- .../apps/components/RefreshButton.svelte | 39 ++ .../display/dbtable/AppDbExplorer.svelte | 39 +- .../display/dbtable/queries/select.ts | 2 +- .../table/AppAggridExplorerTable.svelte | 44 +- .../display/table/AppAggridTable.svelte | 26 +- .../components/display/table/AppTable.svelte | 609 ++++++++-------- .../display/table/AppTableFooter.svelte | 64 +- .../display/table/theme/windmill-theme.css | 652 ++++++++++++++++++ .../components/helpers/DebouncedInput.svelte | 4 +- .../components/helpers/RunnableWrapper.svelte | 3 +- .../apps/editor/component/components.ts | 3 +- .../settingsPanel/DeleteComponent.svelte | 1 + frontend/src/lib/components/apps/inputType.ts | 1 + 14 files changed, 1152 insertions(+), 342 deletions(-) create mode 100644 frontend/src/lib/components/apps/components/RefreshButton.svelte create mode 100644 frontend/src/lib/components/apps/components/display/table/theme/windmill-theme.css diff --git a/frontend/src/lib/assets/app.css b/frontend/src/lib/assets/app.css index ba3c501dc2..b8d726be43 100644 --- a/frontend/src/lib/assets/app.css +++ b/frontend/src/lib/assets/app.css @@ -135,6 +135,11 @@ @apply flex flex-col justify-start items-end; } .ol-control button { - @apply w-7 h-7 center-center bg-surface border text-secondary + @apply w-7 h-7 center-center bg-surface border text-secondary rounded mt-1 mr-1 shadow duration-200 hover:bg-surface-hover focus:bg-surface-hover; } + +/* Components */ +.component-wrapper { + @apply rounded-md border overflow-hidden border-gray-300 dark:border-gray-600; +} diff --git a/frontend/src/lib/components/apps/components/RefreshButton.svelte b/frontend/src/lib/components/apps/components/RefreshButton.svelte new file mode 100644 index 0000000000..4414f17b0a --- /dev/null +++ b/frontend/src/lib/components/apps/components/RefreshButton.svelte @@ -0,0 +1,39 @@ + + + + - {/if} +
+ + {#if hideInsert !== true} + + {/if} +
{/if} {#if resolvedConfig.type.configuration?.[resolvedConfig?.type?.selected]?.resource && resolvedConfig.type.configuration?.[resolvedConfig?.type?.selected]?.table} diff --git a/frontend/src/lib/components/apps/components/display/dbtable/queries/select.ts b/frontend/src/lib/components/apps/components/display/dbtable/queries/select.ts index 41c5e22be4..132b0e4b19 100644 --- a/frontend/src/lib/components/apps/components/display/dbtable/queries/select.ts +++ b/frontend/src/lib/components/apps/components/display/dbtable/queries/select.ts @@ -269,7 +269,7 @@ export function getSelectInput( }, type: 'runnable', fieldType: 'object', - hideRefreshButton: false + hideRefreshButton: true } return getQuery diff --git a/frontend/src/lib/components/apps/components/display/table/AppAggridExplorerTable.svelte b/frontend/src/lib/components/apps/components/display/table/AppAggridExplorerTable.svelte index f7a5b0a610..d48a10c8a0 100644 --- a/frontend/src/lib/components/apps/components/display/table/AppAggridExplorerTable.svelte +++ b/frontend/src/lib/components/apps/components/display/table/AppAggridExplorerTable.svelte @@ -17,9 +17,10 @@ import type { InitConfig } from '$lib/components/apps/editor/appUtils' import { Button } from '$lib/components/common' import { cellRendererFactory } from './utils' - import { Trash2 } from 'lucide-svelte' + import { Columns, Trash2 } from 'lucide-svelte' import type { ColumnDef } from '../dbtable/utils' import AppAggridTableActions from './AppAggridTableActions.svelte' + import Popover from '$lib/components/Popover.svelte' // import 'ag-grid-community/dist/styles/ag-theme-alpine-dark.css' export let id: string @@ -341,6 +342,10 @@ api?.purgeInfiniteCache() } + export function restoreColumns() { + api?.resetColumnState() + } + function onSelectionChanged(api: GridApi) { if (resolvedConfig?.multipleSelectable) { const rows = api.getSelectedNodes() @@ -391,7 +396,11 @@ {#if Array.isArray(resolvedConfig.columnDefs) && resolvedConfig.columnDefs.every(isObject)}
-
{firstRow}{'->'}{lastRow + 1} of {datasource?.rowCount} rows
+
+ + Restore columns +
{:else if resolvedConfig.columnDefs != undefined} @@ -421,3 +444,14 @@ {:else} The columnDefs are undefined {/if} + + diff --git a/frontend/src/lib/components/apps/components/display/table/AppAggridTable.svelte b/frontend/src/lib/components/apps/components/display/table/AppAggridTable.svelte index 3b4479bb19..e71a1ceb61 100644 --- a/frontend/src/lib/components/apps/components/display/table/AppAggridTable.svelte +++ b/frontend/src/lib/components/apps/components/display/table/AppAggridTable.svelte @@ -17,9 +17,10 @@ import Alert from '$lib/components/common/alert/Alert.svelte' import ResolveConfig from '../../helpers/ResolveConfig.svelte' import { deepEqual } from 'fast-equals' + import RefreshButton from '$lib/components/apps/components/RefreshButton.svelte' import 'ag-grid-community/styles/ag-grid.css' - import 'ag-grid-community/styles/ag-theme-alpine.css' + import './theme/windmill-theme.css' import { Loader2 } from 'lucide-svelte' import { twMerge } from 'tailwind-merge' @@ -389,6 +390,7 @@ sendUserToast("Couldn't update the grid:" + e, true) } } + let loading = false {#each Object.keys(components['aggridcomponent'].initialData.configuration) as key (key)} @@ -410,19 +412,29 @@ /> {/each} - + {#if Array.isArray(value) && value.every(isObject)} {#if Array.isArray(resolvedConfig.columnDefs) && resolvedConfig.columnDefs.every(isObject)}
+
+ {#if componentInput?.type === 'runnable' && componentInput.autoRefresh} + + {/if} +
{ $selectedComponent = [id] 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 3f19a88ee0..676fa2664f 100644 --- a/frontend/src/lib/components/apps/components/display/table/AppTable.svelte +++ b/frontend/src/lib/components/apps/components/display/table/AppTable.svelte @@ -41,9 +41,10 @@ import ResolveStyle from '../../helpers/ResolveStyle.svelte' import { Popup } from '$lib/components/common' import ComponentOutputViewer from '$lib/components/apps/editor/contextPanel/ComponentOutputViewer.svelte' - import { Plug2 } from 'lucide-svelte' + import { EyeIcon, Plug2 } from 'lucide-svelte' import AppCell from './AppCell.svelte' import sum from 'hash-sum' + import RefreshButton from '$lib/components/apps/components/RefreshButton.svelte' export let id: string export let componentInput: AppInput | undefined @@ -344,243 +345,326 @@ bind:loading > {#if Array.isArray(result) && result.every(isObject)} -
- {#if resolvedConfig.search !== 'Disabled'} -
-
+
+ {#if resolvedConfig.search !== 'Disabled' || (componentInput?.hideRefreshButton && componentInput['autoRefresh'])} +
+ {#if resolvedConfig.search !== 'Disabled'}
- +
-
+ {:else} +
+ {/if} + + {#if componentInput?.hideRefreshButton && componentInput['autoRefresh']} + + {/if}
{/if} - -
- - - {#each getHeaderGroups($table) as headerGroup} - - {#each headerGroup.headers as header} - {#if header?.column?.columnDef?.header} - {@const context = header?.getContext()} - {#if context} - {@const component = renderCell(header.column.columnDef.header, context)} - {@const displayName = getDisplayNameById(header.id)} - - {/if} - {/if} - {/each} - {#if actionButtons.length > 0} - - {/if} - - {/each} - - - {#each $table.getRowModel().rows as row (row.id)} - {@const rowIndex = row.original['__index']} - - {#each safeVisibleCell(row) as cell, index (index)} - {#if cell?.column?.columnDef?.cell} - {@const context = cell?.getContext()} - {#if context} - toggleRow(row)} - on:click={() => toggleRow(row)} - type={resolvedConfig.columnDefs?.find( - // TS types are wrong here - // @ts-ignore - (c) => c.field === cell.column.columnDef.accessorKey - )?.type ?? 'text'} - value={cell.getValue()} - width={cell.column.getSize()} - on:update={(event) => { - updateCellValue(rowIndex, index, event.detail.value) - }} - /> - {/if} - {/if} - {/each} - - {#if actionButtons.length > 0} - + {/if} + + {/each} + +
- - {#if displayName} - {displayName} - {:else if !header.isPlaceholder && component} - - {/if} - - - Actions -
toggleRow(row)} - on:click={() => toggleRow(row)} - style="width: {(actionButtons ?? []).length * 130}px" - > -
- {#each actionButtons as actionButton, actionIndex (actionButton?.id)} - - { - if (!inputs[id]) { - inputs[id] = { [rowIndex]: value } - } else { - inputs[id] = { ...inputs[id], [rowIndex]: value } - } - - outputs?.inputs.set(inputs, true) - }} - onRemove={(id) => { - if (inputs?.[id] == undefined) { - return - } - delete inputs[id][rowIndex] - inputs[id] = { ...inputs[id] } - if (Object.keys(inputs?.[id] ?? {}).length == 0) { - delete inputs[id] - inputs = { ...inputs } - } - outputs?.inputs.set(inputs, true) - }} - > - +
+
+ + + {#each getHeaderGroups($table) as headerGroup} + + {#each headerGroup.headers as header} + {#if header?.column?.columnDef?.header} + {@const context = header?.getContext()} + {#if context} + {@const component = renderCell(header.column.columnDef.header, context)} + {@const displayName = getDisplayNameById(header.id)} + + {/if} + {/if} + {/each} + {#if actionButtons.length > 0} + + {/if} + + {/each} + + + {#each $table.getRowModel().rows as row, index (row.id)} + {@const isLastRow = index === filteredResult.length - 1} + {@const rowIndex = row.original['__index']} + + {#each safeVisibleCell(row) as cell, index (index)} + {#if cell?.column?.columnDef?.cell} + {@const context = cell?.getContext()} + {#if context} + toggleRow(row)} + on:click={() => toggleRow(row)} + type={resolvedConfig.columnDefs?.find( + // TS types are wrong here + // @ts-ignore + (c) => c.field === cell.column.columnDef.accessorKey + )?.type ?? 'text'} + value={cell.getValue()} + width={cell.column.getSize()} + on:update={(event) => { + updateCellValue(rowIndex, index, event.detail.value) + }} + /> + {/if} + {/if} + {/each} + + {#if actionButtons.length > 0} + - {/if} - - {/each} - -
{ - if (actionButton.id !== $hoverStore) { - $hoverStore = actionButton.id - } - }} - on:mouseout|stopPropagation={() => { - if ($hoverStore !== undefined) { - $hoverStore = undefined - } - }} - class={classNames( - ($selectedComponent?.includes(actionButton.id) || - $hoverStore === actionButton.id) && - $mode !== 'preview' - ? 'outline outline-indigo-500 outline-1 outline-offset-1 relative' - : 'relative' - )} + class="flex flex-row items-center gap-1 px-4 py-4 text-xs text-primary font-medium border-b" > - {#if $mode !== 'preview'} - - - { - $selectedComponent = [actionButton.id] + {#if displayName} + {displayName} + {:else if !header.isPlaceholder && component} + + {/if} + {#if header.column.getIsVisible()} + - - - connectOutput( - connectingInput, - 'buttoncomponent', - actionButton.id, - detail - )} - componentId={actionButton.id} - /> - -
- {/if} + + {/if} - {#if rowIndex == 0} - {@const controls = { - left: () => { - if (actionIndex === 0) { - $selectedComponent = [id] - return true - } else if (actionIndex > 0) { - $selectedComponent = [actionButtons[actionIndex - 1].id] - return true - } - return false - }, - right: () => { - if (actionIndex === actionButtons.length - 1) { - return id - } else if (actionIndex < actionButtons.length - 1) { - $selectedComponent = [actionButtons[actionIndex + 1].id] - return true - } - return false + +
+
+ Actions +
+
toggleRow(row)} + on:click={() => toggleRow(row)} + style="width: {(actionButtons ?? []).length * 130}px" + > +
+ {#each actionButtons as actionButton, actionIndex (actionButton?.id)} + + { + if (!inputs[id]) { + inputs[id] = { [rowIndex]: value } + } else { + inputs[id] = { ...inputs[id], [rowIndex]: value } + } + + outputs?.inputs.set(inputs, true) + }} + onRemove={(id) => { + if (inputs?.[id] == undefined) { + return + } + delete inputs[id][rowIndex] + inputs[id] = { ...inputs[id] } + if (Object.keys(inputs?.[id] ?? {}).length == 0) { + delete inputs[id] + inputs = { ...inputs } + } + outputs?.inputs.set(inputs, true) + }} + > + +
{ + if (actionButton.id !== $hoverStore) { + $hoverStore = actionButton.id } }} - {#if actionButton.type == 'buttoncomponent'} + on:mouseout|stopPropagation={() => { + if ($hoverStore !== undefined) { + $hoverStore = undefined + } + }} + class={classNames( + ($selectedComponent?.includes(actionButton.id) || + $hoverStore === actionButton.id) && + $mode !== 'preview' + ? 'outline outline-indigo-500 outline-1 outline-offset-1 relative' + : 'relative' + )} + > + {#if $mode !== 'preview'} + + + { + $selectedComponent = [actionButton.id] + }} + > + {actionButton.id} + + + {#if $connectingInput.opened} +
+ + + + + + connectOutput( + connectingInput, + 'buttoncomponent', + actionButton.id, + detail + )} + componentId={actionButton.id} + /> + +
+ {/if} + {/if} + {#if rowIndex == 0} + {@const controls = { + left: () => { + if (actionIndex === 0) { + $selectedComponent = [id] + return true + } else if (actionIndex > 0) { + $selectedComponent = [actionButtons[actionIndex - 1].id] + return true + } + return false + }, + right: () => { + if (actionIndex === actionButtons.length - 1) { + return id + } else if (actionIndex < actionButtons.length - 1) { + $selectedComponent = [actionButtons[actionIndex + 1].id] + return true + } + return false + } + }} + {#if actionButton.type == 'buttoncomponent'} + { + toggleRow(row) + }} + id={actionButton.id} + customCss={actionButton.customCss} + configuration={actionButton.configuration} + recomputeIds={actionButton.recomputeIds} + extraQueryParams={{ row: row.original }} + componentInput={actionButton.componentInput} + {controls} + /> + {:else if actionButton.type == 'checkboxcomponent'} + { + toggleRow(row) + }} + {controls} + /> + {:else if actionButton.type == 'selectcomponent'} +
+ { + toggleRow(row) + }} + {controls} + /> +
+ {/if} + {:else if actionButton.type == 'buttoncomponent'} { - toggleRow(row) - }} id={actionButton.id} customCss={actionButton.customCss} configuration={actionButton.configuration} recomputeIds={actionButton.recomputeIds} + preclickAction={async () => { + toggleRow(row) + }} extraQueryParams={{ row: row.original }} componentInput={actionButton.componentInput} - {controls} /> {:else if actionButton.type == 'checkboxcomponent'} { toggleRow(row) }} - {controls} /> {:else if actionButton.type == 'selectcomponent'}
@@ -612,80 +695,32 @@ preclickAction={async () => { toggleRow(row) }} - {controls} />
{/if} - {:else if actionButton.type == 'buttoncomponent'} - { - toggleRow(row) - }} - extraQueryParams={{ row: row.original }} - componentInput={actionButton.componentInput} - /> - {:else if actionButton.type == 'checkboxcomponent'} - { - toggleRow(row) - }} - /> - {:else if actionButton.type == 'selectcomponent'} -
- { - toggleRow(row) - }} - /> -
- {/if} -
-
- {/each} -
-
-
+
+
+ {/each} +
+
+
- + +
{: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 de0b6876d9..b3dcbb33d3 100644 --- a/frontend/src/lib/components/apps/components/display/table/AppTableFooter.svelte +++ b/frontend/src/lib/components/apps/components/display/table/AppTableFooter.svelte @@ -1,7 +1,8 @@