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
This commit is contained in:
Faton Ramadani
2024-04-20 10:54:34 +02:00
committed by GitHub
parent a162de0b75
commit 587c777d57
14 changed files with 1152 additions and 342 deletions
+6 -1
View File
@@ -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;
}
@@ -0,0 +1,39 @@
<script lang="ts">
import { Button } from '$lib/components/common'
import { LoaderIcon, RefreshCw } from 'lucide-svelte'
import { getContext } from 'svelte'
import { twMerge } from 'tailwind-merge'
import type { AppViewerContext } from '../types'
import Popover from '$lib/components/Popover.svelte'
export let id: string
export let loading: boolean
const { runnableComponents } = getContext<AppViewerContext>('AppViewerContext')
</script>
<Popover>
<Button
startIcon={{
icon: loading ? LoaderIcon : RefreshCw,
classes: twMerge(
loading ? 'animate-spin text-blue-800' : '',
'transition-all text-gray-500 dark:text-white'
)
}}
color="light"
size="xs2"
btnClasses={twMerge(loading ? ' bg-blue-100 dark:bg-blue-400' : '', 'transition-all')}
on:click={() => {
$runnableComponents[id]?.cb?.map((cb) => cb())
}}
iconOnly
/>
<svelte:fragment slot="text">
{#if loading}
Refreshing...
{:else}
Refresh
{/if}
</svelte:fragment>
</Popover>
@@ -39,6 +39,7 @@
import { getSelectInput } from './queries/select'
import DebouncedInput from '../../helpers/DebouncedInput.svelte'
import { CancelablePromise } from '$lib/gen'
import RefreshButton from '$lib/components/apps/components/RefreshButton.svelte'
export let id: string
export let configuration: RichConfigurations
@@ -570,6 +571,8 @@
$: hideSearch = resolvedConfig.hideSearch as boolean
$: hideInsert = resolvedConfig.hideInsert as boolean
let loading: boolean = false
</script>
{#each Object.keys(components['dbexplorercomponent'].initialData.configuration) as key (key)}
@@ -623,35 +626,39 @@
noInitialize
bind:runnableComponent
componentInput={input}
autoRefresh={false}
autoRefresh={true}
bind:loading
{render}
{id}
{outputs}
>
<div class="h-full" bind:clientHeight={componentContainerHeight}>
{#if !(hideSearch === true && hideInsert === true)}
<div class="flex p-2 justify-between gap-4" bind:clientHeight={buttonContainerHeight}>
<div class="flex py-2 h-12 justify-between gap-4" bind:clientHeight={buttonContainerHeight}>
{#if hideSearch !== true}
<DebouncedInput
class="w-full max-w-[300px]"
type="text"
bind:value={quicksearch}
placeholder="Quicksearch"
placeholder="Search..."
/>
{/if}
{#if hideInsert !== true}
<Button
startIcon={{ icon: Plus }}
color="dark"
size="xs"
on:click={() => {
args = {}
insertDrawer?.openDrawer()
}}
>
Insert
</Button>
{/if}
<div class="flex flex-row gap-2">
<RefreshButton {id} {loading} />
{#if hideInsert !== true}
<Button
startIcon={{ icon: Plus }}
color="dark"
size="xs2"
on:click={() => {
args = {}
insertDrawer?.openDrawer()
}}
>
Insert
</Button>
{/if}
</div>
</div>
{/if}
{#if resolvedConfig.type.configuration?.[resolvedConfig?.type?.selected]?.resource && resolvedConfig.type.configuration?.[resolvedConfig?.type?.selected]?.table}
@@ -269,7 +269,7 @@ export function getSelectInput(
},
type: 'runnable',
fieldType: 'object',
hideRefreshButton: false
hideRefreshButton: true
}
return getQuery
@@ -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<any>) {
if (resolvedConfig?.multipleSelectable) {
const rows = api.getSelectedNodes()
@@ -391,7 +396,11 @@
{#if Array.isArray(resolvedConfig.columnDefs) && resolvedConfig.columnDefs.every(isObject)}
<div
class={twMerge('divide-y flex flex-col h-full', css?.container?.class, 'wm-aggrid-container')}
class={twMerge(
'flex flex-col h-full component-wrapper divide-y',
css?.container?.class,
'wm-aggrid-container'
)}
style={containerHeight ? `height: ${containerHeight}px;` : css?.container?.style}
bind:clientHeight
bind:clientWidth
@@ -407,9 +416,23 @@
>
<div bind:this={eGui} style:height="100%" />
</div>
<div class="flex gap-1 w-full justify-end text-sm text-secondary py-1"
>{firstRow}{'->'}{lastRow + 1} of {datasource?.rowCount} rows</div
>
<div class="flex gap-1 w-full justify-between items-center text-sm text-secondary/80 p-2">
<Popover>
<svelte:fragment slot="text">Restore columns</svelte:fragment>
<Button
startIcon={{ icon: Columns }}
color="light"
size="xs2"
on:click={() => {
// Restore the columnDefs to the original state
restoreColumns()
}}
iconOnly
/>
</Popover>
{firstRow}{'->'}{lastRow + 1} of {datasource?.rowCount} rows
</div>
</div>
{:else if resolvedConfig.columnDefs != undefined}
<Alert title="Parsing issues" type="error" size="xs">
@@ -421,3 +444,14 @@
{:else}
<Alert title="Parsing issues" type="error" size="xs">The columnDefs are undefined</Alert>
{/if}
<style>
.ag-theme-alpine {
/* disable all borders */
--ag-borders: none;
--ag-row-border-style: solid;
--ag-border-color: rgb(209 213 219);
--ag-header-border-style: solid;
--ag-border-radius: 0;
}
</style>
@@ -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
</script>
{#each Object.keys(components['aggridcomponent'].initialData.configuration) as key (key)}
@@ -410,19 +412,29 @@
/>
{/each}
<RunnableWrapper {outputs} {render} {componentInput} {id} bind:initializing bind:result>
<RunnableWrapper
{outputs}
{render}
{componentInput}
{id}
bind:initializing
bind:result
bind:loading
hideRefreshButton={true}
>
{#if Array.isArray(value) && value.every(isObject)}
{#if Array.isArray(resolvedConfig.columnDefs) && resolvedConfig.columnDefs.every(isObject)}
<div
class={twMerge(
'border shadow-sm divide-y flex flex-col h-full',
css?.container?.class,
'wm-aggrid-container'
)}
class={twMerge('flex flex-col h-full', css?.container?.class, 'wm-aggrid-container')}
style={css?.container?.style}
bind:clientHeight
bind:clientWidth
>
<div class="py-1 flex flex-row justify-end items-center">
{#if componentInput?.type === 'runnable' && componentInput.autoRefresh}
<RefreshButton {id} {loading} />
{/if}
</div>
<div
on:pointerdown|stopPropagation={() => {
$selectedComponent = [id]
@@ -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)}
<div
class={twMerge(
'border shadow-sm divide-y h-full',
css?.container?.class ?? '',
'wm-table-container',
'flex flex-col'
)}
style={css?.container?.style ?? ''}
>
{#if resolvedConfig.search !== 'Disabled'}
<div class="px-2 py-1">
<div class="flex items-center">
<div class="flex flex-col h-full">
{#if resolvedConfig.search !== 'Disabled' || (componentInput?.hideRefreshButton && componentInput['autoRefresh'])}
<div class="flex flex-row w-full justify-between items-center h-12">
{#if resolvedConfig.search !== 'Disabled'}
<div class="grow max-w-[300px]">
<DebouncedInput placeholder="Search..." bind:value={searchValue} />
<DebouncedInput
placeholder="Search..."
bind:value={searchValue}
parentClass="h-full "
/>
</div>
</div>
{:else}
<div />
{/if}
{#if componentInput?.hideRefreshButton && componentInput['autoRefresh']}
<RefreshButton {id} {loading} />
{/if}
</div>
{/if}
<div class="overflow-x-auto flex-1 w-full">
<table class="relative w-full border-b">
<thead
class={twMerge(
'bg-surface-secondary text-left',
css?.tableHeader?.class ?? '',
'wm-table-header',
'sticky top-0 z-40'
)}
style={css?.tableHeader?.style ?? ''}
>
{#each getHeaderGroups($table) as headerGroup}
<tr class="divide-x">
{#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)}
<th class="!p-0">
<span class="block px-4 py-4 text-sm font-semibold border-b">
{#if displayName}
{displayName}
{:else if !header.isPlaceholder && component}
<svelte:component this={component} />
{/if}
</span>
</th>
{/if}
{/if}
{/each}
{#if actionButtons.length > 0}
<th class="!p-0">
<span class="block px-4 py-4 text-sm font-semibold border-b"> Actions </span>
</th>
{/if}
</tr>
{/each}
</thead>
<tbody
class={twMerge('divide-y bg-surface', css?.tableBody?.class ?? '', 'wm-table-body')}
style={css?.tableBody?.style ?? ''}
>
{#each $table.getRowModel().rows as row (row.id)}
{@const rowIndex = row.original['__index']}
<tr
class={classNames(
'last-of-type:!border-b-0 divide-x w-full',
selectedRowIndex === rowIndex
? 'bg-blue-100 hover:bg-blue-200 dark:bg-surface-selected dark:hover:bg-surface-hover divide-blue-200 hover:divide-blue-300 dark:divide-gray-600 dark:hover:divide-gray-700 wm-table-row-selected'
: 'hover:bg-blue-50 dark:hover:bg-surface-hover wm-table-row'
)}
>
{#each safeVisibleCell(row) as cell, index (index)}
{#if cell?.column?.columnDef?.cell}
{@const context = cell?.getContext()}
{#if context}
<AppCell
on:keydown={() => 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}
<td
class="p-2"
on:keypress={() => toggleRow(row)}
on:click={() => toggleRow(row)}
style="width: {(actionButtons ?? []).length * 130}px"
>
<div class="center-center h-full w-full flex-wrap gap-2">
{#each actionButtons as actionButton, actionIndex (actionButton?.id)}
<!-- svelte-ignore a11y-no-static-element-interactions -->
<RowWrapper
value={row.original}
index={rowIndex}
onSet={(id, value) => {
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)
}}
>
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
<div
class={twMerge(
'component-wrapper bg-surface',
'divide-y h-full',
css?.container?.class ?? '',
'wm-table-container',
'flex flex-col'
)}
style={css?.container?.style ?? ''}
>
<div class="overflow-x-auto flex-1 w-full">
<table class="relative w-full border-b">
<thead
class={twMerge(
'bg-surface-secondary text-left',
css?.tableHeader?.class ?? '',
'wm-table-header',
'sticky top-0 z-40'
)}
style={css?.tableHeader?.style ?? ''}
>
{#each getHeaderGroups($table) as headerGroup}
<tr class="divide-x">
{#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)}
<th class="!p-0">
<div
on:mouseover|stopPropagation={() => {
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'}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<span
title={`Id: ${actionButton.id}`}
class={classNames(
'px-2 text-2xs font-bold w-fit absolute shadow -top-2 -left-2 border z-50 rounded-sm',
'bg-indigo-500/90 border-indigo-600 text-white',
$selectedComponent?.includes(actionButton.id) ||
$hoverStore === actionButton.id
? 'opacity-100'
: 'opacity-0'
)}
on:click|stopPropagation={() => {
$selectedComponent = [actionButton.id]
{#if displayName}
{displayName}
{:else if !header.isPlaceholder && component}
<svelte:component this={component} />
{/if}
{#if header.column.getIsVisible()}
<button
class="w-6 flex justify-center items-center"
on:click={() => {
header.column.toggleVisibility()
}}
>
{actionButton.id}
</span>
{#if $connectingInput.opened}
<div class="absolute z-50 left-8 -top-[10px]">
<Popup
floatingConfig={{
strategy: 'absolute',
placement: 'bottom-start'
}}
>
<svelte:fragment slot="button">
<button
class="bg-red-500/70 border border-red-600 px-1 py-0.5"
title="Outputs"
aria-label="Open output"><Plug2 size={12} /></button
>
</svelte:fragment>
<ComponentOutputViewer
suffix="table"
on:select={({ detail }) =>
connectOutput(
connectingInput,
'buttoncomponent',
actionButton.id,
detail
)}
componentId={actionButton.id}
/>
</Popup>
</div>
{/if}
<EyeIcon
size="14"
class="hover:text-gray-600 text-gray-400 rounded-full "
/>
</button>
{/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
</div>
</th>
{/if}
{/if}
{/each}
{#if actionButtons.length > 0}
<th class="!p-0">
<div
class="flex flex-row items-center px-4 py-4 text-xs text-primary font-medium border-b"
>
Actions
</div>
</th>
{/if}
</tr>
{/each}
</thead>
<tbody
class={twMerge('divide-y bg-surface', css?.tableBody?.class ?? '', 'wm-table-body')}
style={css?.tableBody?.style ?? ''}
>
{#each $table.getRowModel().rows as row, index (row.id)}
{@const isLastRow = index === filteredResult.length - 1}
{@const rowIndex = row.original['__index']}
<tr
class={classNames(
isLastRow ? '!border-b-0' : '',
'divide-x w-full',
index % 2 === 0 ? 'bg-gray-50/50' : '',
selectedRowIndex === rowIndex
? 'bg-blue-100 hover:bg-blue-200 dark:bg-surface-selected dark:hover:bg-surface-hover divide-blue-200 hover:divide-blue-300 dark:divide-gray-600 dark:hover:divide-gray-700 wm-table-row-selected'
: 'hover:bg-blue-50 dark:hover:bg-surface-hover wm-table-row'
)}
>
{#each safeVisibleCell(row) as cell, index (index)}
{#if cell?.column?.columnDef?.cell}
{@const context = cell?.getContext()}
{#if context}
<AppCell
on:keydown={() => 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}
<td
class="p-2"
on:keypress={() => toggleRow(row)}
on:click={() => toggleRow(row)}
style="width: {(actionButtons ?? []).length * 130}px"
>
<div class="center-center h-full w-full flex-wrap gap-2">
{#each actionButtons as actionButton, actionIndex (actionButton?.id)}
<!-- svelte-ignore a11y-no-static-element-interactions -->
<RowWrapper
value={row.original}
index={rowIndex}
onSet={(id, value) => {
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)
}}
>
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
<div
on:mouseover|stopPropagation={() => {
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'}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<span
title={`Id: ${actionButton.id}`}
class={classNames(
'px-2 text-2xs font-bold w-fit absolute shadow -top-2 -left-2 border z-50 rounded-sm',
'bg-indigo-500/90 border-indigo-600 text-white',
$selectedComponent?.includes(actionButton.id) ||
$hoverStore === actionButton.id
? 'opacity-100'
: 'opacity-0'
)}
on:click|stopPropagation={() => {
$selectedComponent = [actionButton.id]
}}
>
{actionButton.id}
</span>
{#if $connectingInput.opened}
<div class="absolute z-50 left-8 -top-[10px]">
<Popup
floatingConfig={{
strategy: 'absolute',
placement: 'bottom-start'
}}
>
<svelte:fragment slot="button">
<button
class="bg-red-500/70 border border-red-600 px-1 py-0.5"
title="Outputs"
aria-label="Open output"><Plug2 size={12} /></button
>
</svelte:fragment>
<ComponentOutputViewer
suffix="table"
on:select={({ detail }) =>
connectOutput(
connectingInput,
'buttoncomponent',
actionButton.id,
detail
)}
componentId={actionButton.id}
/>
</Popup>
</div>
{/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'}
<AppButton
noInitialize
extraKey={'idx' + rowIndex}
{render}
noWFull
preclickAction={async () => {
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'}
<AppCheckbox
noInitialize
extraKey={'idx' + rowIndex}
{render}
id={actionButton.id}
customCss={actionButton.customCss}
configuration={actionButton.configuration}
recomputeIds={actionButton.recomputeIds}
onToggle={actionButton.onToggle}
preclickAction={async () => {
toggleRow(row)
}}
{controls}
/>
{:else if actionButton.type == 'selectcomponent'}
<div class="w-40">
<AppSelect
noDefault
noInitialize
extraKey={'idx' + rowIndex}
{render}
id={actionButton.id}
customCss={actionButton.customCss}
configuration={actionButton.configuration}
recomputeIds={actionButton.recomputeIds}
onSelect={actionButton.onSelect}
preclickAction={async () => {
toggleRow(row)
}}
{controls}
/>
</div>
{/if}
{:else if actionButton.type == 'buttoncomponent'}
<AppButton
noInitialize
extraKey={'idx' + rowIndex}
{render}
noWFull
preclickAction={async () => {
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'}
<AppCheckbox
@@ -595,7 +679,6 @@
preclickAction={async () => {
toggleRow(row)
}}
{controls}
/>
{:else if actionButton.type == 'selectcomponent'}
<div class="w-40">
@@ -612,80 +695,32 @@
preclickAction={async () => {
toggleRow(row)
}}
{controls}
/>
</div>
{/if}
{:else if actionButton.type == 'buttoncomponent'}
<AppButton
noInitialize
extraKey={'idx' + rowIndex}
{render}
noWFull
id={actionButton.id}
customCss={actionButton.customCss}
configuration={actionButton.configuration}
recomputeIds={actionButton.recomputeIds}
preclickAction={async () => {
toggleRow(row)
}}
extraQueryParams={{ row: row.original }}
componentInput={actionButton.componentInput}
/>
{:else if actionButton.type == 'checkboxcomponent'}
<AppCheckbox
noInitialize
extraKey={'idx' + rowIndex}
{render}
id={actionButton.id}
customCss={actionButton.customCss}
configuration={actionButton.configuration}
recomputeIds={actionButton.recomputeIds}
onToggle={actionButton.onToggle}
preclickAction={async () => {
toggleRow(row)
}}
/>
{:else if actionButton.type == 'selectcomponent'}
<div class="w-40">
<AppSelect
noDefault
noInitialize
extraKey={'idx' + rowIndex}
{render}
id={actionButton.id}
customCss={actionButton.customCss}
configuration={actionButton.configuration}
recomputeIds={actionButton.recomputeIds}
onSelect={actionButton.onSelect}
preclickAction={async () => {
toggleRow(row)
}}
/>
</div>
{/if}
</div>
</RowWrapper>
{/each}
</div>
</td>
{/if}
</tr>
{/each}
</tbody>
</table>
</div>
</div>
</RowWrapper>
{/each}
</div>
</td>
{/if}
</tr>
{/each}
</tbody>
</table>
</div>
<AppTableFooter
download={resolvedConfig?.downloadButton}
pageSize={resolvedConfig?.pagination?.configuration?.auto?.pageSize ?? 20}
manualPagination={resolvedConfig?.pagination?.selected == 'manual'}
result={filteredResult}
{table}
class={twMerge(css?.tableFooter?.class, 'wm-table-footer')}
style={css?.tableFooter?.style}
{loading}
/>
<AppTableFooter
download={resolvedConfig?.downloadButton}
pageSize={resolvedConfig?.pagination?.configuration?.auto?.pageSize ?? 20}
manualPagination={resolvedConfig?.pagination?.selected == 'manual'}
result={filteredResult}
{table}
class={twMerge(css?.tableFooter?.class, 'wm-table-footer')}
style={css?.tableFooter?.style}
{loading}
/>
</div>
</div>
{:else if result != undefined}
<div class="flex flex-col h-full w-full overflow-auto">
@@ -1,7 +1,8 @@
<script lang="ts">
import Popover from '$lib/components/Popover.svelte'
import Button from '$lib/components/common/button/Button.svelte'
import type { Table } from '@tanstack/svelte-table'
import { ChevronLeft, ChevronRight, Download } from 'lucide-svelte'
import { ChevronLeft, ChevronRight, Columns, Download } from 'lucide-svelte'
import type { Readable } from 'svelte/store'
import { twMerge } from 'tailwind-merge'
@@ -11,11 +12,12 @@
export let manualPagination: boolean
export let pageSize: number
export let table: Readable<Table<T>>
export let download: boolean = true
export let loading: boolean = false
let c = ''
export { c as class }
export let style = ''
export let download: boolean = true
export let loading: boolean = false
function convertJSONToCSV(objArray: Record<string, any>[]) {
let str = ''
@@ -60,6 +62,37 @@
class={twMerge('px-2 py-1 text-xs gap-2 items-center justify-between', c, 'flex flex-row')}
{style}
>
<div class="flex items-center gap-1 flex-row">
{#if download}
<Popover>
<svelte:fragment slot="text">Download as CSV</svelte:fragment>
<Button
size="xs2"
color="light"
on:click={downloadResultAsCSV}
startIcon={{ icon: Download }}
wrapperClasses="app-table-footer-btn"
iconOnly
/>
</Popover>
{/if}
{#if !$table.getIsAllColumnsVisible()}
<Popover>
<svelte:fragment slot="text">Display hidden columns</svelte:fragment>
<Button
size="xs2"
color="light"
on:click={() => {
$table.getAllColumns().forEach((column) => column.toggleVisibility(true))
}}
startIcon={{ icon: Columns }}
wrapperClasses="app-table-footer-btn"
iconOnly
/>
</Popover>
{/if}
</div>
{#if result.length > pageSize || manualPagination}
<div class="flex items-center gap-2 flex-row">
<Button
@@ -77,6 +110,7 @@
>
Previous
</Button>
<Button
size="xs2"
variant="border"
@@ -92,26 +126,12 @@
>
Next
</Button>
{$table.getState().pagination.pageIndex + 1}
{$table.getPageCount() > 0 ? ` of ${$table.getPageCount()}` : ''}
<div>
Page:
{$table.getState().pagination.pageIndex + 1}
{$table.getPageCount() > 0 ? ` of ${$table.getPageCount()}` : ''}
</div>
</div>
{:else}
<div />
{/if}
<div class="flex items-center gap-2 flex-row">
{#if download}
<Button
size="xs"
variant="border"
color="light"
btnClasses="!py-1"
on:click={downloadResultAsCSV}
startIcon={{ icon: Download }}
wrapperClasses="app-table-footer-btn"
>
Download
</Button>
{/if}
</div>
</div>
{/if}
File diff suppressed because one or more lines are too long
@@ -1,4 +1,6 @@
<script lang="ts">
import { twMerge } from 'tailwind-merge'
export let placeholder: string = 'Search...'
// Using 'any' so 'type="number"' can be passed to the input
// which should return a number
@@ -25,6 +27,6 @@
on:pointerdown|stopPropagation
on:keyup={debounce}
on:keydown|stopPropagation
class={parentClass}
class={twMerge(parentClass, 'mb-1 h-8 !rounded-md !shadow-none')}
{...$$restProps}
/>
@@ -11,6 +11,7 @@
export let componentInput: AppInput | undefined
export let noInitialize = false
export let hideRefreshButton: boolean | undefined = undefined
type SideEffectAction =
| {
@@ -251,7 +252,7 @@
fields={componentInput.fields}
bind:result
runnable={componentInput.runnable}
hideRefreshButton={componentInput.hideRefreshButton}
hideRefreshButton={componentInput.hideRefreshButton ?? hideRefreshButton}
transformer={componentInput.transformer}
{autoRefresh}
recomputableByRefreshButton={componentInput.autoRefresh ?? true}
@@ -1726,7 +1726,8 @@ This is a paragraph.
name: 'A briefer cell',
age: 84
}
]
],
hideRefreshButton: true
} as StaticAppInput,
actionButtons: true
}
@@ -30,6 +30,7 @@
componentSettings?.forEach((componentSetting) => {
const id = componentSetting?.item?.id
const onDeleteComponentControl = id ? $componentControl[id]?.onDelete : undefined
if (onDeleteComponentControl) {
onDeleteComponentControl()
@@ -170,6 +170,7 @@ type InputConfiguration<T extends InputType, V extends InputType> = {
}
noStatic?: boolean
onDemandOnly?: boolean
hideRefreshButton?: boolean
}
export type StaticOptions = {