From 13d057e4f762e348668d6390c1c8e6af2accb63a Mon Sep 17 00:00:00 2001 From: "SPRINX0\\prochazka" Date: Tue, 8 Apr 2025 13:54:04 +0200 Subject: [PATCH] SYNC: filterable table control --- packages/filterparser/src/filterTool.ts | 31 +++++++++- .../web/src/elements/ObjectListControl.svelte | 8 ++- packages/web/src/elements/TableControl.svelte | 57 ++++++++++++++++++- .../web/src/tableeditor/TableEditor.svelte | 6 ++ 4 files changed, 99 insertions(+), 3 deletions(-) diff --git a/packages/filterparser/src/filterTool.ts b/packages/filterparser/src/filterTool.ts index 09e5c5f51..2e502db96 100644 --- a/packages/filterparser/src/filterTool.ts +++ b/packages/filterparser/src/filterTool.ts @@ -1,6 +1,9 @@ -import { arrayToHexString, isTypeDateTime } from 'dbgate-tools'; +import { arrayToHexString, evalFilterBehaviour, isTypeDateTime } from 'dbgate-tools'; import { format, toDate } from 'date-fns'; import _isString from 'lodash/isString'; +import _cloneDeepWith from 'lodash/cloneDeepWith'; +import { Condition, Expression } from 'dbgate-sqltree'; +import { parseFilter } from './parseFilter'; export type FilterMultipleValuesMode = 'is' | 'is_not' | 'contains' | 'begins' | 'ends'; @@ -61,3 +64,29 @@ export function createMultiLineFilter(mode: FilterMultipleValuesMode, text: stri } return res; } + +export function compileCompoudEvalCondition(filters: { [column: string]: string }): Condition { + if (!filters) return null; + const conditions = []; + for (const name in filters) { + try { + const condition = parseFilter(filters[name], evalFilterBehaviour); + const replaced = _cloneDeepWith(condition, (expr: Expression) => { + if (expr.exprType == 'placeholder') + return { + exprType: 'column', + columnName: name, + }; + }); + conditions.push(replaced); + } catch (err) { + // filter parse error - ignore filter + } + } + + if (conditions.length == 0) return null; + return { + conditionType: 'and', + conditions, + }; +} diff --git a/packages/web/src/elements/ObjectListControl.svelte b/packages/web/src/elements/ObjectListControl.svelte index 763878e5a..c4c1cd36a 100644 --- a/packages/web/src/elements/ObjectListControl.svelte +++ b/packages/web/src/elements/ObjectListControl.svelte @@ -3,6 +3,7 @@ import FontIcon from '../icons/FontIcon.svelte'; import Link from './Link.svelte'; import TableControl from './TableControl.svelte'; + import { writable } from 'svelte/store'; export let title; export let collection; @@ -12,6 +13,9 @@ export let hideDisplayName = false; export let clickable = false; export let onAddNew = null; + export let displayNameFieldName = null; + + export let filters = writable({}); let collapsed = false; @@ -43,14 +47,16 @@ rows={collection || []} columns={_.compact([ !hideDisplayName && { - fieldName: 'displayName', + fieldName: displayNameFieldName || 'displayName', header: 'Name', slot: -1, sortable: true, + filterable: !!displayNameFieldName, }, ...columns, ])} {clickable} + {filters} on:clickrow > diff --git a/packages/web/src/elements/TableControl.svelte b/packages/web/src/elements/TableControl.svelte index 835902349..1259de159 100644 --- a/packages/web/src/elements/TableControl.svelte +++ b/packages/web/src/elements/TableControl.svelte @@ -9,6 +9,8 @@ slot?: number; isHighlighted?: Function; sortable?: boolean; + filterable?: boolean; + filteredExpression?: (row: any) => string; } @@ -19,6 +21,10 @@ import keycodes from '../utility/keycodes'; import { createEventDispatcher } from 'svelte'; import FontIcon from '../icons/FontIcon.svelte'; + import DataFilterControl from '../datagrid/DataFilterControl.svelte'; + import { evalFilterBehaviour } from 'dbgate-tools'; + import { evaluateCondition } from 'dbgate-sqltree'; + import { compileCompoudEvalCondition } from 'dbgate-filterparser'; export let columns: (TableControlColumn | false)[]; export let rows; @@ -36,6 +42,7 @@ export let checkedKeys = null; export let onSetCheckedKeys = null; export let extractCheckedKey = x => x.id; + export let filters = null; const dispatch = createEventDispatcher(); @@ -54,10 +61,28 @@ } }; + function filterRows(rows, filters) { + const condition = compileCompoudEvalCondition(filters); + + if (!condition) return rows; + + return rows.filter(row => { + const newrow = { ...row }; + for (const col of columnList) { + if (col.filteredExpression) { + newrow[col.fieldName] = col.filteredExpression(row); + } + } + return evaluateCondition(condition, newrow); + }); + } + let sortedByField = null; let sortOrderIsDesc = false; - $: sortedRowsTmp = sortedByField ? _.sortBy(rows || [], sortedByField) : rows; + $: filteredRows = filters ? filterRows(rows, $filters) : rows; + + $: sortedRowsTmp = sortedByField ? _.sortBy(filteredRows || [], sortedByField) : filteredRows; $: sortedRows = sortOrderIsDesc ? [...sortedRowsTmp].reverse() : sortedRowsTmp; @@ -101,6 +126,25 @@ {/each} + {#if filters} + + {#if checkedKeys} + + {/if} + {#each columnList as col} + + {#if col.filterable} + filters.update(f => ({ ...f, [col.fieldName]: value }))} + placeholder="Data filter" + /> + {/if} + + {/each} + + {/if} {#each sortedRows as row, index} @@ -232,4 +276,15 @@ border-collapse: separate; border-left: 1px solid var(--theme-border); } + + .filter-cell { + text-align: left; + overflow: hidden; + margin: 0; + padding: 0; + } + + .empty-cell { + background-color: var(--theme-bg-1); + } diff --git a/packages/web/src/tableeditor/TableEditor.svelte b/packages/web/src/tableeditor/TableEditor.svelte index 1faf594e2..05234b405 100644 --- a/packages/web/src/tableeditor/TableEditor.svelte +++ b/packages/web/src/tableeditor/TableEditor.svelte @@ -192,6 +192,7 @@ clickable on:clickrow={e => showModal(ColumnEditorModal, { columnInfo: e.detail, tableInfo, setTableInfo, driver })} onAddNew={isWritable ? addColumn : null} + displayNameFieldName="columnName" columns={[ !driver?.dialect?.specificNullabilityImplementation && { fieldName: 'notNull', @@ -203,11 +204,13 @@ fieldName: 'dataType', header: 'Data Type', sortable: true, + filterable: true, }, { fieldName: 'defaultValue', header: 'Default value', sortable: true, + filterable: true, }, driver?.dialect?.columnProperties?.isSparse && { fieldName: 'isSparse', @@ -219,6 +222,7 @@ fieldName: 'computedExpression', header: 'Computed Expression', sortable: true, + filterable: true, }, driver?.dialect?.columnProperties?.isPersisted && { fieldName: 'isPersisted', @@ -242,10 +246,12 @@ fieldName: 'columnComment', header: 'Comment', sortable: true, + filterable: true, }, isWritable ? { fieldName: 'actions', + filterable: false, slot: 3, } : null,