Skip to content

Commit c10f018

Browse files
committed
[Feat]: Add multiline column type
1 parent dafdca9 commit c10f018

File tree

4 files changed

+142
-8
lines changed

4 files changed

+142
-8
lines changed

client/packages/lowcoder/src/components/table/EditableCell.tsx

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ export type EditViewFn<T> = (props: {
5454
value: T;
5555
onChange: (value: T) => void;
5656
onChangeEnd: () => void;
57+
onCommit?: (value: T) => void;
58+
onCancel?: () => void;
5759
onImmediateSave?: (value: T) => void;
5860
otherProps?: Record<string, any>;
5961
}) => ReactNode;
@@ -152,22 +154,38 @@ function EditableCellComp<T extends JSONValue>(props: EditableCellProps<T>) {
152154
[]
153155
);
154156

155-
const onChangeEnd = useCallback(() => {
157+
const commitValue = useCallback((finalValue: T | null) => {
156158
if (!mountedRef.current) return;
157-
159+
158160
setIsEditing(false);
159-
const newValue = _.isNil(tmpValue) || _.isEqual(tmpValue, baseValue) ? null : tmpValue;
161+
const newValue = _.isNil(finalValue) || _.isEqual(finalValue, baseValue) ? null : finalValue;
160162
dispatch(
161163
changeChildAction(
162164
"changeValue",
163165
newValue,
164166
false
165167
)
166168
);
167-
if(!_.isEqual(tmpValue, value)) {
169+
if(!_.isEqual(finalValue, value)) {
168170
onTableEvent?.('columnEdited');
169171
}
170-
}, [dispatch, tmpValue, baseValue, value, onTableEvent, setIsEditing]);
172+
}, [dispatch, baseValue, value, onTableEvent, setIsEditing]);
173+
174+
const onChangeEnd = useCallback(() => {
175+
commitValue(tmpValue);
176+
}, [commitValue, tmpValue]);
177+
178+
const onCommit = useCallback((nextValue: T) => {
179+
if (!mountedRef.current) return;
180+
setTmpValue(nextValue);
181+
commitValue(nextValue);
182+
}, [commitValue]);
183+
184+
const onCancel = useCallback(() => {
185+
if (!mountedRef.current) return;
186+
setIsEditing(false);
187+
setTmpValue(value);
188+
}, [setIsEditing, value]);
171189

172190
const onImmediateSave = useCallback((newValue: T) => {
173191
if (!mountedRef.current) return;
@@ -187,8 +205,8 @@ function EditableCellComp<T extends JSONValue>(props: EditableCellProps<T>) {
187205
}, [dispatch, baseValue, value, onTableEvent]);
188206

189207
const editView = useMemo(
190-
() => editViewFn?.({ value, onChange, onChangeEnd, onImmediateSave, otherProps }) ?? <></>,
191-
[editViewFn, value, onChange, onChangeEnd, onImmediateSave, otherProps]
208+
() => editViewFn?.({ value, onChange, onChangeEnd, onCommit, onCancel, onImmediateSave, otherProps }) ?? <></>,
209+
[editViewFn, value, onChange, onChangeEnd, onCommit, onCancel, onImmediateSave, otherProps]
192210
);
193211

194212
const enterEditFn = useCallback(() => {
@@ -243,4 +261,4 @@ function EditableCellComp<T extends JSONValue>(props: EditableCellProps<T>) {
243261
);
244262
}
245263

246-
export const EditableCell = React.memo(EditableCellComp) as typeof EditableCellComp;
264+
export const EditableCell = React.memo(EditableCellComp) as typeof EditableCellComp;

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { ColumnNumberComp } from "./columnTypeComps/ColumnNumberComp";
2323
import { ColumnAvatarsComp } from "./columnTypeComps/columnAvatarsComp";
2424
import { ColumnDropdownComp } from "./columnTypeComps/columnDropdownComp";
2525
import { ColumnPasswordComp } from "./columnTypeComps/columnPasswordComp";
26+
import { ColumnMultilineTextComp } from "./columnTypeComps/columnMultilineTextComp";
2627

2728
const actionOptions = [
2829
{
@@ -106,6 +107,10 @@ const actionOptions = [
106107
label: "Password",
107108
value: "password",
108109
},
110+
{
111+
label: trans("table.multilineText"),
112+
value: "multilineText",
113+
},
109114
] as const;
110115

111116
export const ColumnTypeCompMap = {
@@ -129,6 +134,7 @@ export const ColumnTypeCompMap = {
129134
date: DateComp,
130135
time: TimeComp,
131136
password: ColumnPasswordComp,
137+
multilineText: ColumnMultilineTextComp,
132138
};
133139

134140
type ColumnTypeMapType = typeof ColumnTypeCompMap;
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import React, { useCallback, useEffect, useRef, useState } from "react";
2+
import { default as AntdModal } from "antd/es/modal";
3+
import { default as Input } from "antd/es/input";
4+
import { StringOrNumberControl } from "comps/controls/codeControl";
5+
import { trans } from "i18n";
6+
import { ColumnTypeCompBuilder, ColumnTypeViewFn } from "../columnTypeCompBuilder";
7+
import { ColumnValueTooltip } from "../simpleColumnTypeComps";
8+
import { RecordConstructorToComp } from "lowcoder-core";
9+
import styled from "styled-components";
10+
11+
const { TextArea } = Input;
12+
13+
const TextView = styled.div`
14+
white-space: pre-wrap;
15+
word-break: break-word;
16+
cursor: pointer;
17+
`;
18+
19+
const childrenMap = {
20+
text: StringOrNumberControl,
21+
};
22+
23+
const getBaseValue: ColumnTypeViewFn<typeof childrenMap, string, string> = (props) =>
24+
typeof props.text === "string" ? props.text : String(props.text);
25+
26+
const MultilineContent = React.memo(({ value }: { value: string }) => <TextView>{value}</TextView>);
27+
28+
const MultilineEditModal = React.memo((props: {
29+
value: string;
30+
onCommit: (value: string) => void;
31+
onCancel: () => void;
32+
}) => {
33+
const { value, onCommit, onCancel } = props;
34+
const [localValue, setLocalValue] = useState(value);
35+
const textAreaRef = useRef<any>(null);
36+
37+
useEffect(() => {
38+
setLocalValue(value);
39+
}, [value]);
40+
41+
useEffect(() => {
42+
const timeout = setTimeout(() => textAreaRef.current?.focus({ cursor: "end" }), 0);
43+
return () => clearTimeout(timeout);
44+
}, []);
45+
46+
const handleSave = useCallback(() => {
47+
onCommit(localValue);
48+
}, [localValue, onCommit]);
49+
50+
const handleCancel = useCallback(() => {
51+
onCancel();
52+
}, [onCancel]);
53+
54+
return (
55+
<AntdModal
56+
open={true}
57+
title={trans("table.multilineEditorTitle")}
58+
onOk={handleSave}
59+
onCancel={handleCancel}
60+
okText={trans("table.multilineEditorSave")}
61+
cancelText={trans("table.multilineEditorCancel")}
62+
width={560}
63+
maskClosable={false}
64+
destroyOnClose
65+
>
66+
<TextArea
67+
ref={textAreaRef}
68+
value={localValue}
69+
onChange={(e) => setLocalValue(e.target.value)}
70+
autoSize={{ minRows: 4, maxRows: 16 }}
71+
placeholder={trans("table.multilineEditorPlaceholder")}
72+
/>
73+
</AntdModal>
74+
);
75+
});
76+
77+
const MultilinePropertyView = React.memo(
78+
({ children }: { children: RecordConstructorToComp<typeof childrenMap> }) => (
79+
<>
80+
{children.text.propertyView({
81+
label: trans("table.columnValue"),
82+
tooltip: ColumnValueTooltip,
83+
})}
84+
</>
85+
)
86+
);
87+
88+
export const ColumnMultilineTextComp = new ColumnTypeCompBuilder(
89+
childrenMap,
90+
(props, dispatch) => {
91+
const value = props.changeValue ?? getBaseValue(props, dispatch);
92+
return <MultilineContent value={String(value)} />;
93+
},
94+
(nodeValue) => nodeValue.text.value,
95+
getBaseValue
96+
)
97+
.setEditViewFn((props) => (
98+
<MultilineEditModal
99+
value={String(props.value)}
100+
onCommit={(value) => props.onCommit!(value as any)}
101+
onCancel={props.onCancel!}
102+
/>
103+
))
104+
.setPropertyViewFn((children) => <MultilinePropertyView children={children} />)
105+
.build();

client/packages/lowcoder/src/i18n/locales/en.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2128,6 +2128,11 @@ export const en = {
21282128
"switch": "Switch",
21292129
"rating": "Rating",
21302130
"progress": "Progress",
2131+
"multilineText": "Multiline Text",
2132+
"multilineEditorTitle": "Edit Multiline Text",
2133+
"multilineEditorSave": "Save",
2134+
"multilineEditorCancel": "Cancel",
2135+
"multilineEditorPlaceholder": "Enter text here...",
21312136
"option": "Operation",
21322137
"optionList": "Operation List",
21332138
"option1": "Operation 1",

0 commit comments

Comments
 (0)