Skip to content

Commit 147cf4c

Browse files
optimise table comp, toolbar, filters, summary rows and different column types
1 parent 12f2485 commit 147cf4c

28 files changed

+2526
-1439
lines changed

client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeCompBuilder.tsx

Lines changed: 75 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export class ColumnTypeCompBuilder<
5454
RecordConstructorToComp<NewChildrenCtorMap<ChildrenCtorMap, T>>
5555
>;
5656
private editViewFn?: EditViewFn<T>;
57+
private cleanupFunctions: (() => void)[] = [];
5758

5859
constructor(
5960
childrenMap: ChildrenCtorMap,
@@ -93,22 +94,57 @@ export class ColumnTypeCompBuilder<
9394
if (!this.propertyViewFn) {
9495
throw new Error("need property view fn");
9596
}
97+
98+
// Memoize the props processing
99+
const memoizedViewFn = _.memoize(
100+
(props: any, dispatch: any) => {
101+
const baseValue = this.baseValueFn?.(props, dispatch);
102+
const normalView = this.viewFn(props, dispatch);
103+
return (
104+
<EditableCell<T>
105+
{...props}
106+
normalView={normalView}
107+
dispatch={dispatch}
108+
baseValue={baseValue}
109+
changeValue={props.changeValue as any}
110+
editViewFn={this.editViewFn}
111+
/>
112+
);
113+
},
114+
(props) => {
115+
let safeOptions = [];
116+
let safeAvatars = [];
117+
if(props.options) {
118+
safeOptions = props.options.map((option: Record<string, any>) => {
119+
const {prefixIcon, suffixIcon, ...safeOption} = option;
120+
return safeOption;
121+
})
122+
}
123+
if(props.avatars) {
124+
safeAvatars = props.avatars.map((avatar: Record<string, any>) => {
125+
const {AvatarIcon, ...safeAvatar} = avatar;
126+
return safeAvatar;
127+
})
128+
}
129+
const {
130+
prefixIcon,
131+
suffixIcon,
132+
iconFalse,
133+
iconTrue,
134+
iconNull,
135+
tagColors,
136+
options,
137+
avatars,
138+
...safeProps
139+
} = props;
140+
return safeProps;
141+
}
142+
);
143+
96144
const viewFn: ColumnTypeViewFn<ChildrenCtorMap, T, CellViewReturn> =
97145
(props, dispatch): CellViewReturn =>
98-
(cellProps) => {
99-
const baseValue = this.baseValueFn?.(props, dispatch);
100-
const normalView = this.viewFn(props, dispatch);
101-
return (
102-
<EditableCell<T>
103-
{...cellProps}
104-
normalView={normalView}
105-
dispatch={dispatch}
106-
baseValue={baseValue}
107-
changeValue={props.changeValue as any}
108-
editViewFn={this.editViewFn}
109-
/>
110-
);
111-
};
146+
(cellProps) => memoizedViewFn({ ...props, ...cellProps } as any, dispatch);
147+
112148
const ColumnTypeCompTmp = new MultiCompBuilder(
113149
this.childrenMap as ToConstructor<
114150
RecordConstructorToComp<NewChildrenCtorMap<ChildrenCtorMap, T>>
@@ -117,12 +153,21 @@ export class ColumnTypeCompBuilder<
117153
)
118154
.setPropertyViewFn(this.propertyViewFn)
119155
.build();
156+
120157
const displayValueFn = this.displayValueFn;
121158
const editViewFn = this.editViewFn;
122159

123160
return class extends ColumnTypeCompTmp {
124161
// table cell data
125-
readonly displayValue: JSONValue = null;
162+
private _displayValue: JSONValue = null;
163+
private cleanupFunctions: (() => void)[] = [];
164+
constructor(props: any) {
165+
super(props);
166+
this.cleanupFunctions.push(() => {
167+
this._displayValue = null;
168+
memoizedViewFn.cache.clear?.();
169+
});
170+
}
126171

127172
override extraNode() {
128173
return {
@@ -134,7 +179,8 @@ export class ColumnTypeCompBuilder<
134179
},
135180
updateNodeFields: (value: any) => {
136181
const displayValueFunc = value[__COLUMN_DISPLAY_VALUE_FN];
137-
return { displayValue: displayValueFunc(value) };
182+
this._displayValue = displayValueFunc(value);
183+
return { displayValue: this._displayValue };
138184
},
139185
};
140186
}
@@ -143,12 +189,24 @@ export class ColumnTypeCompBuilder<
143189
* Get the data actually displayed by the table cell
144190
*/
145191
getDisplayValue() {
146-
return this.displayValue;
192+
return this._displayValue;
147193
}
148194

149195
static canBeEditable() {
150196
return !_.isNil(editViewFn);
151197
}
198+
199+
componentWillUnmount() {
200+
// Cleanup all registered cleanup functions
201+
this.cleanupFunctions.forEach(cleanup => cleanup());
202+
this.cleanupFunctions = [];
203+
}
152204
};
153205
}
206+
207+
// Cleanup method to be called when the builder is no longer needed
208+
cleanup() {
209+
this.cleanupFunctions.forEach(cleanup => cleanup());
210+
this.cleanupFunctions = [];
211+
}
154212
}

client/packages/lowcoder/src/comps/comps/tableComp/column/columnTypeComps/ColumnNumberComp.tsx

Lines changed: 107 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { default as InputNumber } from "antd/es/input-number";
1+
import React, { useState, useRef, useEffect, useCallback, useMemo, ReactNode } from "react";
2+
import { default as InputNumber } from "antd/es/input-number";
23
import { NumberControl, RangeControl, StringControl } from "comps/controls/codeControl";
34
import { BoolControl } from "comps/controls/boolControl";
45
import { trans } from "i18n";
@@ -35,59 +36,123 @@ const childrenMap = {
3536
suffix: StringControl,
3637
};
3738

38-
let float = false;
39-
let step = 1;
40-
let precision = 0;
39+
const getBaseValue: ColumnTypeViewFn<typeof childrenMap, number, number> = (props) => props.text;
4140

42-
const getBaseValue: ColumnTypeViewFn<typeof childrenMap, number, number> = (
43-
props
44-
) => {
45-
return props.text
41+
type NumberViewProps = {
42+
value: number;
43+
prefix: string;
44+
suffix: string;
45+
prefixIcon: ReactNode;
46+
suffixIcon: ReactNode;
47+
float: boolean;
48+
precision: number;
4649
};
4750

51+
type NumberEditProps = {
52+
value: number;
53+
onChange: (value: number) => void;
54+
onChangeEnd: () => void;
55+
step: number;
56+
precision: number;
57+
float: boolean;
58+
};
59+
60+
const ColumnNumberView = React.memo((props: NumberViewProps) => {
61+
const formattedValue = useMemo(() => {
62+
let result = !props.float ? Math.floor(props.value) : props.value;
63+
if (props.float) {
64+
result = Number(result.toFixed(props.precision + 1));
65+
}
66+
return result;
67+
}, [props.value, props.float, props.precision]);
68+
69+
return (
70+
<>
71+
{hasIcon(props.prefixIcon) && (
72+
<span>{props.prefixIcon}</span>
73+
)}
74+
<span>{props.prefix + formattedValue + props.suffix}</span>
75+
{hasIcon(props.suffixIcon) && (
76+
<span>{props.suffixIcon}</span>
77+
)}
78+
</>
79+
);
80+
});
81+
82+
ColumnNumberView.displayName = 'ColumnNumberView';
83+
84+
85+
const ColumnNumberEdit = React.memo((props: NumberEditProps) => {
86+
const [currentValue, setCurrentValue] = useState(props.value);
87+
const mountedRef = useRef(true);
88+
89+
// Cleanup on unmount
90+
useEffect(() => {
91+
return () => {
92+
mountedRef.current = false;
93+
setCurrentValue(0);
94+
};
95+
}, []);
96+
97+
const handleChange = useCallback((value: string | number | null) => {
98+
if (!mountedRef.current) return;
99+
const newValue = typeof value === 'number' ? value : 0;
100+
const finalValue = !props.float ? Math.floor(newValue) : newValue;
101+
props.onChange(finalValue);
102+
setCurrentValue(finalValue);
103+
}, [props.onChange, props.float]);
104+
105+
const handleBlur = useCallback(() => {
106+
if (!mountedRef.current) return;
107+
props.onChangeEnd();
108+
}, [props.onChangeEnd]);
109+
110+
const handlePressEnter = useCallback(() => {
111+
if (!mountedRef.current) return;
112+
props.onChangeEnd();
113+
}, [props.onChangeEnd]);
114+
115+
return (
116+
<InputNumberWrapper>
117+
<InputNumber
118+
step={props.step}
119+
value={currentValue}
120+
autoFocus
121+
variant="borderless"
122+
onChange={handleChange}
123+
precision={props.float ? props.precision : 0}
124+
onBlur={handleBlur}
125+
onPressEnter={handlePressEnter}
126+
/>
127+
</InputNumberWrapper>
128+
);
129+
});
130+
131+
ColumnNumberEdit.displayName = 'NumberEdit';
132+
48133
export const ColumnNumberComp = (function () {
49134
return new ColumnTypeCompBuilder(
50135
childrenMap,
51136
(props, dispatch) => {
52-
float = props.float;
53-
step = props.step;
54-
precision = props.precision;
55137
const value = props.changeValue ?? getBaseValue(props, dispatch);
56-
let formattedValue: string | number = !float ? Math.floor(value) : value;
57-
if(float) {
58-
formattedValue = formattedValue.toFixed(precision + 1);
59-
}
60-
return (
61-
<>{hasIcon(props.prefixIcon) && (
62-
<span>{props.prefixIcon}</span>
63-
)}
64-
<span>{props.prefix + formattedValue + props.suffix}</span>
65-
{hasIcon(props.suffixIcon) && (
66-
<span>{props.suffixIcon}</span>
67-
)} </>
68-
);
138+
return <ColumnNumberView value={value} {...props} />;
69139
},
70140
(nodeValue) => nodeValue.text.value,
71-
getBaseValue,
141+
getBaseValue
72142
)
73143
.setEditViewFn((props) => {
144+
const { value, onChange, onChangeEnd, otherProps } = props;
74145
return (
75-
<InputNumberWrapper>
76-
<InputNumber
77-
step={step}
78-
defaultValue={props.value}
79-
autoFocus
80-
variant="borderless"
81-
onChange={(value) => {
82-
value = value ?? 0;
83-
props.onChange(!float ? Math.floor(value) : value);
84-
}}
85-
precision={float ? precision : 0}
86-
onBlur={props.onChangeEnd}
87-
onPressEnter={props.onChangeEnd}
146+
<ColumnNumberEdit
147+
value={value}
148+
onChange={onChange}
149+
onChangeEnd={onChangeEnd}
150+
step={otherProps?.step ?? 1}
151+
precision={otherProps?.precision ?? 0}
152+
float={otherProps?.float ?? false}
88153
/>
89-
</InputNumberWrapper>
90-
)})
154+
);
155+
})
91156
.setPropertyViewFn((children) => {
92157
return (
93158
<>
@@ -99,15 +164,15 @@ export const ColumnNumberComp = (function () {
99164
label: trans("table.numberStep"),
100165
tooltip: trans("table.numberStepTooltip"),
101166
onFocus: (focused) => {
102-
if(!focused) {
167+
if (!focused) {
103168
const value = children.step.getView();
104169
const isFloat = children.float.getView();
105170
const newValue = !isFloat ? Math.floor(value) : value;
106171
children.step.dispatchChangeValueAction(String(newValue));
107172
}
108173
}
109174
})}
110-
{float && (
175+
{children.float.getView() && (
111176
children.precision.propertyView({
112177
label: trans("table.precision"),
113178
})

0 commit comments

Comments
 (0)