feat(frontend): Table wizard (#2416)

* feat(frontend): add helper to configure AG Grid columns def

* feat(frontend): add all options

* feat(frontend): column definition helper

* feat(frontend): add header name

* feat(frontend): add presets

* feat(frontend): wip

* feat(frontend): rework preset

* feat(frontend): rework preset

* feat(frontend): table wizard

* feat(frontend): wip

* feat(frontend): wip

* feat(frontend): ordering

* feat(frontend): add sizing options

* feat(frontend): add sync button

* feat(frontend): add quick add column buttonsa
This commit is contained in:
Faton Ramadani
2023-10-11 14:12:48 +02:00
committed by GitHub
parent 46d2c13e0d
commit 6f0cda0e1e
8 changed files with 229 additions and 12 deletions
@@ -0,0 +1,16 @@
<script lang="ts">
import Badge from '$lib/components/common/badge/Badge.svelte'
export let type: 'text' | 'badge' | 'link' = 'text'
export let value: any
</script>
{#if type === 'badge'}
<Badge>
{value}
</Badge>
{:else if type === 'link'}
<a href={value} class="underline" target="_blank">{value}</a>
{:else}
{value}
{/if}
@@ -37,6 +37,7 @@
import { Popup } from '$lib/components/common'
import ComponentOutputViewer from '$lib/components/apps/editor/contextPanel/ComponentOutputViewer.svelte'
import { Plug2 } from 'lucide-svelte'
import AppCell from './AppCell.svelte'
export let id: string
export let componentInput: AppInput | undefined
@@ -147,10 +148,6 @@
}
}
function cellIsObject(x: (any) => any, props: any): boolean {
return typeof x != 'string' && typeof x(props) == 'object'
}
let filteredResult: Array<Record<string, any>> = []
function setFilteredResult() {
@@ -250,6 +247,11 @@
}
}
function getDisplayNameById(id: string) {
const component = resolvedConfig.columnDefs.find((columnDef) => columnDef.field === id)
return component?.headerName
}
function safeVisibleCell<T>(row: Row<T>) {
try {
return row.getVisibleCells()
@@ -259,6 +261,25 @@
return []
}
}
function updateTable(resolvedConfig) {
$table.getAllLeafColumns().map((column) => {
const columnConfig = resolvedConfig.columnDefs.find(
// @ts-ignore
(columnDef) => columnDef.field === column.columnDef.accessorKey
)
if (columnConfig?.hideColumn === column.getIsVisible()) {
column.toggleVisibility()
}
})
$table.setColumnOrder(() =>
resolvedConfig.columnDefs.map((columnDef: { field: any }) => columnDef.field)
)
}
$: updateTable(resolvedConfig)
</script>
{#each Object.keys(components['tablecomponent'].initialData.configuration) as key (key)}
@@ -327,9 +348,12 @@
{@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 !header.isPlaceholder && component}
{#if displayName}
{displayName}
{:else if !header.isPlaceholder && component}
<svelte:component this={component} />
{/if}
</span>
@@ -363,18 +387,20 @@
{#if cell?.column?.columnDef?.cell}
{@const context = cell?.getContext()}
{#if context}
{@const component = renderCell(cell.column.columnDef.cell, context)}
<td
on:keydown={() => toggleRow(row)}
on:click={() => toggleRow(row)}
class="p-4 whitespace-pre-wrap truncate text-xs text-primary"
style={'width: ' + cell.column.getSize() + 'px'}
>
{#if typeof cell.column.columnDef.cell != 'string' && cellIsObject(cell.column.columnDef.cell, context)}
{JSON.stringify(cell.column.columnDef.cell(context), null, 4)}
{:else if component != undefined}
<svelte:component this={component} />
{/if}
<AppCell
type={resolvedConfig.columnDefs.find(
// TS types are wrong here
// @ts-ignore
(c) => c.field === cell.column.columnDef.accessorKey
)?.type ?? 'text'}
value={cell.getValue()}
/>
</td>
{/if}
{/if}
@@ -90,6 +90,7 @@ export type PieChartComponent = BaseComponent<'piechartcomponent'>
export type ChartJsComponent = BaseComponent<'chartjscomponent'>
export type ScatterChartComponent = BaseComponent<'scatterchartcomponent'>
export type TableComponent = BaseComponent<'tablecomponent'> & {
actionButtons: (BaseAppComponent & ButtonComponent & GridItem)[]
}
@@ -1387,6 +1388,12 @@ This is a paragraph.
},
initialData: {
configuration: {
columnDefs: {
type: 'static',
fieldType: 'array',
subFieldType: 'table-column',
value: [{ field: 'id' }, { field: 'name' }, { field: 'age' }]
} as StaticAppInput,
search: {
fieldType: 'select',
type: 'static',
@@ -9,6 +9,7 @@
import { dndzone, SOURCES, TRIGGERS } from 'svelte-dnd-action'
import { generateRandomString, pluralize } from '$lib/utils'
import Toggle from '$lib/components/Toggle.svelte'
import QuickAddColumn from './QuickAddColumn.svelte'
const flipDurationMs = 200
@@ -47,6 +48,8 @@
value.push(selectOptions[0])
} else if (subFieldType === 'ag-grid') {
value.push({ field: 'newField', editable: true, flex: 1 })
} else if (subFieldType === 'table-column') {
value.push({ field: 'newColumn', headerName: 'New column', type: 'text' })
}
} else {
value.push('')
@@ -132,7 +135,7 @@
<div class="flex flex-row items-center justify-between">
<div class="text-xs text-tertiary font-semibold">{pluralize(items.length, 'item')}</div>
{#if subFieldType === 'ag-grid'}
{#if subFieldType === 'ag-grid' || subFieldType === 'table-column'}
<Toggle
options={{
right: 'Raw'
@@ -164,7 +167,9 @@
bind:value={item.value}
/>
</div>
<div class="flex justify-between flex-col items-center">
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
tabindex={dragDisabled ? 0 : -1}
class="w-4 h-4 cursor-move"
@@ -190,4 +195,23 @@
<Button size="xs" color="light" startIcon={{ icon: faPlus }} on:click={() => addElementByType()}>
Add
</Button>
{#if subFieldType === 'table-column'}
<QuickAddColumn
columns={componentInput.value?.map((item) => item.field)}
on:add={({ detail }) => {
if (!componentInput.value) componentInput.value = []
componentInput.value.push({ field: detail, headerName: detail, type: 'text' })
componentInput = componentInput
if (componentInput.value) {
items.push({
value: componentInput.value[componentInput.value.length - 1],
id: generateRandomString()
})
}
}}
/>
{/if}
</div>
@@ -0,0 +1,64 @@
<script lang="ts">
import { createEventDispatcher, getContext } from 'svelte'
import type { Output } from '../../rx'
import type { AppViewerContext } from '../../types'
import Button from '$lib/components/common/button/Button.svelte'
import { Plus } from 'lucide-svelte'
export let columns: string[] = []
let remainingColumns: string[] = []
const { worldStore, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
const dispatch = createEventDispatcher()
let result = []
function subscribeToAllOutputs(observableOutputs: Record<string, Output<any>> | undefined) {
if (observableOutputs) {
Object.entries(observableOutputs).forEach(([k, output]) => {
output?.subscribe(
{
id: 'alloutputs' + $selectedComponent?.[0] + '-' + k,
next: (value) => {
if (k === 'result') {
result = value
}
}
},
result
)
})
}
}
function updateRemainingColumns(result: any[], columns: string[]) {
if (result.length > 0) {
const allKeysSet: Set<string> = result.reduce((acc, obj) => {
Object.keys(obj).forEach((key) => acc.add(key))
return acc
}, new Set<string>())
remainingColumns = Array.from(allKeysSet).filter((x: string) => !columns.includes(x))
}
}
$: $selectedComponent?.[0] &&
subscribeToAllOutputs($worldStore?.outputsById?.[$selectedComponent?.[0]])
$: updateRemainingColumns(result, columns)
</script>
{#if remainingColumns.length > 0}
<div class="text-xs font-semibold">Quick add </div>
<div class="flex flex-row gap-2 items-center flex-wrap">
{#each remainingColumns as column}
<Button on:click={() => dispatch('add', column)} size="xs2" color="light" variant="border">
<div class="flex flex-row gap-2 items-center">
<Plus size="12" />
{column}
</div>
</Button>
{/each}
</div>
{/if}
@@ -14,6 +14,7 @@
import Button from '$lib/components/common/button/Button.svelte'
import { Settings } from 'lucide-svelte'
import AgGridWizard from '$lib/components/wizards/AgGridWizard.svelte'
import TableColumnWizard from '$lib/components/wizards/TableColumnWizard.svelte'
export let componentInput: StaticInput<any> | undefined
export let fieldType: InputType | undefined = undefined
@@ -139,6 +140,27 @@
</div>
</div>
</div>
{:else if fieldType === 'table-column'}
<div class="flex flex-row rounded-md bg-surface items-center h-full">
<div class="relative w-full">
<input
class="text-xs px-2 border-y w-full flex flex-row items-center border-r rounded-r-md h-8"
bind:value={componentInput.value.field}
placeholder="Field"
/>
<div class="absolute top-1 right-1">
<TableColumnWizard bind:column={componentInput.value}>
<svelte:fragment slot="trigger">
<Button color="light" size="xs2" nonCaptureEvent={true}>
<div class="flex flex-row items-center gap-2 text-xs font-normal">
<Settings size={16} />
</div>
</Button>
</svelte:fragment>
</TableColumnWizard>
</div>
</div>
</div>
{:else}
<div class="flex gap-1 relative w-full">
<textarea
@@ -23,6 +23,7 @@ export type InputType =
| 'tab-select'
| 'schema'
| 'ag-grid'
| 'table-column'
// Connection to an output of another component
// defined by the id of the component and the path of the output
@@ -187,6 +188,7 @@ export type AppInput =
| AppInputSpec<'array', object[], 'tab-select'>
| AppInputSpec<'schema', object>
| AppInputSpec<'array', object[], 'ag-grid'>
| AppInputSpec<'array', object[], 'table-column'>
export type RowAppInput = Extract<AppInput, { type: 'row' }>
export type StaticAppInput = Extract<AppInput, { type: 'static' }>
@@ -0,0 +1,56 @@
<script lang="ts">
import { Popup } from '../common'
import Label from '../Label.svelte'
import Toggle from '../Toggle.svelte'
import Tooltip from '../Tooltip.svelte'
import { offset, flip, shift } from 'svelte-floating-ui/dom'
export let column: {
headerName: string
hideColumn: boolean
type: 'text' | 'badge' | 'link'
}
</script>
<Popup
floatingConfig={{
strategy: 'fixed',
placement: 'left-end',
middleware: [offset(8), flip(), shift()]
}}
containerClasses="border rounded-lg shadow-lg bg-surface p-4"
>
<svelte:fragment slot="button">
<slot name="trigger" />
</svelte:fragment>
<div class="flex flex-col w-96 p-2 gap-4">
<span class="text-sm mb-2 leading-6 font-semibold">
Table Column
<Tooltip documentationLink="https://www.ag-grid.com/javascript-data-grid/column-definitions/">
Column definitions are used to define columns in ag-Grid.
</Tooltip>
</span>
<Label label="Header name">
<input placeholder="header name" bind:value={column.headerName} />
</Label>
<Label label="Show column">
<Toggle
on:pointerdown={(e) => {
e?.stopPropagation()
}}
options={{ right: 'Hide column' }}
bind:checked={column.hideColumn}
size="xs"
/>
</Label>
<Label label="Type">
<select bind:value={column.type}>
<option value="text">Text</option>
<option value="badge">Badge</option>
<option value="link">Link</option>
</select>
</Label>
</div>
</Popup>