Skip to content

Commit a909c64

Browse files
committed
refactor / add virtualization
1 parent 8b492c2 commit a909c64

File tree

11 files changed

+556
-441
lines changed

11 files changed

+556
-441
lines changed

client/packages/lowcoder/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
"pigeon-maps": "^0.22.1",
6969
"qrcode.react": "^3.1.0",
7070
"rc-trigger": "^5.3.1",
71+
"rc-virtual-list": "^3.19.1",
7172
"react": "18.3.0",
7273
"react-best-gradient-color-picker": "^3.0.10",
7374
"react-colorful": "^5.5.1",

client/packages/lowcoder/src/comps/comps/tableLiteComp/column/tableColumnComp.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ export const columnChildrenMap = {
127127
dataIndex: valueComp<string>(""),
128128
hide: BoolControl,
129129
sortable: BoolControl,
130+
// header filters
131+
filterable: withDefault(BoolControl, false),
132+
filteredValue: stateComp<string[]>([]),
130133
width: NumberControl,
131134
autoWidth: dropdownControl(columnWidthOptions, "auto"),
132135
render: RenderComp,
@@ -261,6 +264,9 @@ const ColumnPropertyView = React.memo(({
261264
{comp.children.sortable.propertyView({
262265
label: trans("table.sortable"),
263266
})}
267+
{comp.children.filterable.propertyView({
268+
label: "Filterable",
269+
})}
264270
{comp.children.hide.propertyView({
265271
label: trans("prop.hide"),
266272
})}

client/packages/lowcoder/src/comps/comps/tableLiteComp/column/tableColumnListComp.tsx

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import {
1313
fromRecord,
1414
isMyCustomAction,
1515
RecordNode,
16+
wrapChildAction,
17+
changeChildAction,
1618
} from "lowcoder-core";
1719
import { shallowEqual } from "react-redux";
1820
import { JSONObject, JSONValue } from "util/jsonTypes";
@@ -27,14 +29,20 @@ const ColumnListTmpComp = list(ColumnComp);
2729
* rowExample is used for code prompts
2830
*/
2931
type RowExampleType = JSONObject | undefined;
30-
type ActionDataType = {
32+
33+
type DataChangedActionType = {
3134
type: "dataChanged";
3235
rowExample: RowExampleType;
3336
doGeneColumn: boolean;
3437
dynamicColumn: boolean;
3538
data: Array<JSONObject>;
3639
};
3740

41+
type SetFilteredValuesActionType = {
42+
type: "setFilteredValues";
43+
values: Record<string, string[]>; // dataIndex -> filtered values
44+
};
45+
3846
export function tableDataRowExample(data: Array<JSONObject>) {
3947
if (!data?.length) {
4048
return undefined;
@@ -58,7 +66,7 @@ export function tableDataRowExample(data: Array<JSONObject>) {
5866

5967
export class ColumnListComp extends ColumnListTmpComp {
6068
override reduce(action: CompAction): this {
61-
if (isMyCustomAction<ActionDataType>(action, "dataChanged")) {
69+
if (isMyCustomAction<DataChangedActionType>(action, "dataChanged")) {
6270
const rowExample = action.value.rowExample;
6371
const { readOnly } = getReduceContext();
6472
let comp = this;
@@ -68,9 +76,39 @@ export class ColumnListComp extends ColumnListTmpComp {
6876
}
6977
return comp;
7078
}
79+
if (isMyCustomAction<SetFilteredValuesActionType>(action, "setFilteredValues")) {
80+
const values = action.value.values;
81+
const columnsView = this.getView();
82+
const actions: Array<any> = [];
83+
columnsView.forEach((column, index) => {
84+
const dataIndex = column.getView().dataIndex;
85+
if (values.hasOwnProperty(dataIndex)) {
86+
actions.push(
87+
wrapChildAction(
88+
index + "",
89+
changeChildAction("filteredValue", values[dataIndex] ?? [], false)
90+
)
91+
);
92+
}
93+
});
94+
if (actions.length > 0) {
95+
return this.reduce(this.multiAction(actions));
96+
}
97+
return this;
98+
}
7199
return super.reduce(action);
72100
}
73101

102+
setFilteredValuesAction(values: Record<string, string[]>) {
103+
return customAction<SetFilteredValuesActionType>(
104+
{
105+
type: "setFilteredValues",
106+
values,
107+
},
108+
false
109+
);
110+
}
111+
74112
getChangeSet(filterNewRowsChange?: boolean) {
75113
const changeSet: Record<string, Record<string, JSONValue>> = {};
76114
const columns = this.getView();
@@ -107,7 +145,7 @@ export class ColumnListComp extends ColumnListTmpComp {
107145
dynamicColumn: boolean;
108146
data: Array<JSONObject>;
109147
}) {
110-
return customAction<ActionDataType>(
148+
return customAction<DataChangedActionType>(
111149
{
112150
type: "dataChanged",
113151
...param,

client/packages/lowcoder/src/comps/comps/tableLiteComp/tableMethodExposings.ts renamed to client/packages/lowcoder/src/comps/comps/tableLiteComp/methods/tableMethodExposings.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import _ from "lodash";
2-
import { OB_ROW_ORI_INDEX } from "./tableUtils";
3-
import { SortValue } from "./tableTypes";
2+
import { OB_ROW_ORI_INDEX } from "../tableUtils";
3+
import { SortValue } from "../tableTypes";
44

55
export const tableMethodExposings = [
66
{
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import _ from "lodash";
2+
import { fromRecord, Node, RecordNode, ValueAndMsg, withFunction } from "lowcoder-core";
3+
import { lastValueIfEqual, shallowEqual } from "util/objectUtils";
4+
import { JSONObject } from "util/jsonTypes";
5+
import { getPageSize } from "../paginationControl";
6+
import {
7+
getOriDisplayData,
8+
getColumnsAggr,
9+
OB_ROW_ORI_INDEX,
10+
RecordType,
11+
sortData,
12+
tranToTableRecord,
13+
} from "../tableUtils";
14+
import type { SortValue } from "../tableTypes";
15+
import type { TableImplComp } from "../tableComp";
16+
17+
export function buildSortedDataNode(comp: TableImplComp) {
18+
const nodes: {
19+
data: Node<JSONObject[]>;
20+
sort: Node<SortValue[]>;
21+
dataIndexes: RecordNode<Record<string, Node<string>>>;
22+
sortables: RecordNode<Record<string, Node<ValueAndMsg<boolean>>>>;
23+
withParams: RecordNode<_.Dictionary<any>>;
24+
} = {
25+
data: comp.children.data.exposingNode(),
26+
sort: comp.children.sort.node(),
27+
dataIndexes: comp.children.columns.getColumnsNode("dataIndex"),
28+
sortables: comp.children.columns.getColumnsNode("sortable"),
29+
withParams: comp.children.columns.withParamsNode(),
30+
};
31+
32+
const sortedDataNode = withFunction(fromRecord(nodes), (input) => {
33+
const { data, sort, dataIndexes, sortables } = input;
34+
const sortColumns = _(dataIndexes)
35+
.mapValues((dataIndex, idx) => ({ sortable: !!sortables[idx] }))
36+
.mapKeys((sortable, idx) => dataIndexes[idx])
37+
.value();
38+
const dataColumns = _(dataIndexes)
39+
.mapValues((dataIndex, idx) => ({
40+
dataIndex,
41+
render: input.withParams[idx] as any,
42+
}))
43+
.value();
44+
45+
const updatedData: Array<RecordType> = data.map((row, index) => ({
46+
...row,
47+
[OB_ROW_ORI_INDEX]: index + "",
48+
}));
49+
50+
const updatedDataMap: Record<string, RecordType> = {};
51+
updatedData.forEach((row) => {
52+
updatedDataMap[row[OB_ROW_ORI_INDEX]] = row;
53+
});
54+
55+
const originalData = getOriDisplayData(updatedData, 1000, Object.values(dataColumns));
56+
const sorted = sortData(originalData, sortColumns, sort);
57+
58+
const newData = sorted.map((row) => ({
59+
...row,
60+
...updatedDataMap[row[OB_ROW_ORI_INDEX]],
61+
}));
62+
return newData;
63+
});
64+
65+
return lastValueIfEqual(comp, "sortedDataNode", [sortedDataNode, nodes] as const, (a, b) =>
66+
shallowEqual(a[1], b[1])
67+
)[0];
68+
}
69+
70+
export function buildFilteredDataNode(comp: TableImplComp) {
71+
const nodes = {
72+
data: buildSortedDataNode(comp),
73+
};
74+
const filteredDataNode = withFunction(fromRecord(nodes), ({ data }) => {
75+
// No pre-filtering here; AntD header filters are handled internally by Table
76+
return data.map((row) => tranToTableRecord(row, (row as any)[OB_ROW_ORI_INDEX]));
77+
});
78+
return lastValueIfEqual(comp, "filteredDataNode", [filteredDataNode, nodes] as const, (a, b) =>
79+
shallowEqual(a[1], b[1])
80+
)[0];
81+
}
82+
83+
export function buildOriDisplayDataNode(comp: TableImplComp) {
84+
const nodes = {
85+
data: buildFilteredDataNode(comp),
86+
showSizeChanger: comp.children.pagination.children.showSizeChanger.node(),
87+
pageSize: comp.children.pagination.children.pageSize.node(),
88+
pageSizeOptions: comp.children.pagination.children.pageSizeOptions.node(),
89+
changablePageSize: comp.children.pagination.children.changeablePageSize.node(),
90+
withParams: comp.children.columns.withParamsNode(),
91+
dataIndexes: comp.children.columns.getColumnsNode("dataIndex"),
92+
};
93+
const resNode = withFunction(fromRecord(nodes), (input) => {
94+
const columns = _(input.dataIndexes)
95+
.mapValues((dataIndex, idx) => ({
96+
dataIndex,
97+
render: input.withParams[idx],
98+
}))
99+
.value();
100+
const pageSize = getPageSize(
101+
input.showSizeChanger.value,
102+
input.pageSize.value,
103+
input.pageSizeOptions.value,
104+
input.changablePageSize
105+
);
106+
return getOriDisplayData(input.data, pageSize, Object.values(columns));
107+
});
108+
return lastValueIfEqual(comp, "oriDisplayDataNode", [resNode, nodes] as const, (a, b) =>
109+
shallowEqual(a[1], b[1])
110+
)[0];
111+
}
112+
113+
export function buildColumnAggrNode(comp: TableImplComp) {
114+
const nodes = {
115+
oriDisplayData: buildOriDisplayDataNode(comp),
116+
withParams: comp.children.columns.withParamsNode(),
117+
dataIndexes: comp.children.columns.getColumnsNode("dataIndex"),
118+
};
119+
const resNode = withFunction(fromRecord(nodes), (input) => {
120+
const dataIndexWithParamsDict = _(input.dataIndexes)
121+
.mapValues((dataIndex, idx) => input.withParams[idx])
122+
.mapKeys((withParams, idx) => input.dataIndexes[idx])
123+
.value();
124+
const res = getColumnsAggr(input.oriDisplayData, dataIndexWithParamsDict);
125+
return res;
126+
});
127+
return lastValueIfEqual(comp, "columnAggrNode", [resNode, nodes] as const, (a, b) =>
128+
shallowEqual(a[1], b[1])
129+
)[0];
130+
}

0 commit comments

Comments
 (0)