Skip to content

Commit 2c05e4d

Browse files
committed
[Feat]: #1720 add upload dragger
1 parent 060a20e commit 2c05e4d

File tree

3 files changed

+242
-4
lines changed

3 files changed

+242
-4
lines changed
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
import { default as AntdUpload } from "antd/es/upload";
2+
import { default as Button } from "antd/es/button";
3+
import { UploadFile, UploadChangeParam } from "antd/es/upload/interface";
4+
import { useState, useEffect } from "react";
5+
import styled from "styled-components";
6+
import { trans } from "i18n";
7+
import _ from "lodash";
8+
import {
9+
changeValueAction,
10+
CompAction,
11+
multiChangeAction,
12+
RecordConstructorToView,
13+
} from "lowcoder-core";
14+
import { hasIcon } from "comps/utils";
15+
import { messageInstance } from "lowcoder-design/src/components/GlobalInstances";
16+
import { resolveValue, resolveParsedValue, commonProps } from "./fileComp";
17+
import { FileStyleType } from "comps/controls/styleControlConstants";
18+
19+
const IconWrapper = styled.span`
20+
display: flex;
21+
`;
22+
23+
const StyledDragger = styled(AntdUpload.Dragger)<{
24+
$style: FileStyleType;
25+
$autoHeight: boolean;
26+
}>`
27+
&.ant-upload-drag {
28+
border-color: ${(props) => props.$style.border};
29+
border-width: ${(props) => props.$style.borderWidth};
30+
border-style: ${(props) => props.$style.borderStyle};
31+
border-radius: ${(props) => props.$style.radius};
32+
background: ${(props) => props.$style.background};
33+
${(props) => !props.$autoHeight && `height: 200px; display: flex; align-items: center;`}
34+
35+
.ant-upload-drag-container {
36+
${(props) => !props.$autoHeight && `display: flex; flex-direction: column; justify-content: center; height: 100%;`}
37+
}
38+
39+
&:hover {
40+
border-color: ${(props) => props.$style.accent};
41+
background: ${(props) => props.$style.background};
42+
}
43+
44+
.ant-upload-text {
45+
color: ${(props) => props.$style.text};
46+
font-size: ${(props) => props.$style.textSize};
47+
font-weight: ${(props) => props.$style.textWeight};
48+
font-family: ${(props) => props.$style.fontFamily};
49+
font-style: ${(props) => props.$style.fontStyle};
50+
}
51+
52+
.ant-upload-hint {
53+
color: ${(props) => props.$style.text};
54+
opacity: 0.7;
55+
}
56+
57+
.ant-upload-drag-icon {
58+
margin-bottom: 16px;
59+
60+
.anticon {
61+
color: ${(props) => props.$style.accent};
62+
font-size: 48px;
63+
}
64+
}
65+
}
66+
`;
67+
68+
interface DraggerUploadProps {
69+
value: Array<string | null>;
70+
files: any[];
71+
fileType: string[];
72+
showUploadList: boolean;
73+
disabled: boolean;
74+
onEvent: (eventName: string) => void;
75+
style: FileStyleType;
76+
parseFiles: boolean;
77+
parsedValue: Array<any>;
78+
prefixIcon: any;
79+
suffixIcon: any;
80+
forceCapture: boolean;
81+
minSize: number;
82+
maxSize: number;
83+
maxFiles: number;
84+
uploadType: "single" | "multiple" | "directory";
85+
text: string;
86+
dispatch: (action: CompAction) => void;
87+
autoHeight: boolean;
88+
tabIndex?: number;
89+
}
90+
91+
export const DraggerUpload = (props: DraggerUploadProps) => {
92+
const { dispatch, files, style, autoHeight } = props;
93+
const [fileList, setFileList] = useState<UploadFile[]>(
94+
files.map((f) => ({ ...f, status: "done" })) as UploadFile[]
95+
);
96+
97+
useEffect(() => {
98+
if (files.length === 0 && fileList.length !== 0) {
99+
setFileList([]);
100+
}
101+
}, [files]);
102+
103+
const handleOnChange = (param: UploadChangeParam) => {
104+
const uploadingFiles = param.fileList.filter((f) => f.status === "uploading");
105+
if (uploadingFiles.length !== 0) {
106+
setFileList(param.fileList);
107+
return;
108+
}
109+
110+
let maxFiles = props.maxFiles;
111+
if (props.uploadType === "single") {
112+
maxFiles = 1;
113+
} else if (props.maxFiles <= 0) {
114+
maxFiles = 100;
115+
}
116+
117+
const uploadedFiles = param.fileList.filter((f) => f.status === "done");
118+
119+
if (param.file.status === "removed") {
120+
const index = props.files.findIndex((f) => f.uid === param.file.uid);
121+
dispatch(
122+
multiChangeAction({
123+
value: changeValueAction(
124+
[...props.value.slice(0, index), ...props.value.slice(index + 1)],
125+
false
126+
),
127+
files: changeValueAction(
128+
[...props.files.slice(0, index), ...props.files.slice(index + 1)],
129+
false
130+
),
131+
parsedValue: changeValueAction(
132+
[...props.parsedValue.slice(0, index), ...props.parsedValue.slice(index + 1)],
133+
false
134+
),
135+
})
136+
);
137+
props.onEvent("change");
138+
} else {
139+
const unresolvedValueIdx = Math.min(props.value.length, uploadedFiles.length);
140+
const unresolvedParsedValueIdx = Math.min(props.parsedValue.length, uploadedFiles.length);
141+
142+
Promise.all([
143+
resolveValue(uploadedFiles.slice(unresolvedValueIdx)),
144+
resolveParsedValue(uploadedFiles.slice(unresolvedParsedValueIdx)),
145+
]).then(([value, parsedValue]) => {
146+
dispatch(
147+
multiChangeAction({
148+
value: changeValueAction([...props.value, ...value].slice(-maxFiles), false),
149+
files: changeValueAction(
150+
uploadedFiles
151+
.map((file) => _.pick(file, ["uid", "name", "type", "size", "lastModified"]))
152+
.slice(-maxFiles),
153+
false
154+
),
155+
...(props.parseFiles
156+
? {
157+
parsedValue: changeValueAction(
158+
[...props.parsedValue, ...parsedValue].slice(-maxFiles),
159+
false
160+
),
161+
}
162+
: {}),
163+
})
164+
);
165+
props.onEvent("change");
166+
props.onEvent("parse");
167+
});
168+
}
169+
170+
setFileList(uploadedFiles.slice(-maxFiles));
171+
};
172+
173+
return (
174+
<StyledDragger
175+
{...commonProps(props)}
176+
fileList={fileList}
177+
$style={style}
178+
$autoHeight={autoHeight}
179+
beforeUpload={(file) => {
180+
if (!file.size || file.size <= 0) {
181+
messageInstance.error(`${file.name} ` + trans("file.fileEmptyErrorMsg"));
182+
return AntdUpload.LIST_IGNORE;
183+
}
184+
185+
if (
186+
(!!props.minSize && file.size < props.minSize) ||
187+
(!!props.maxSize && file.size > props.maxSize)
188+
) {
189+
messageInstance.error(`${file.name} ` + trans("file.fileSizeExceedErrorMsg"));
190+
return AntdUpload.LIST_IGNORE;
191+
}
192+
return true;
193+
}}
194+
onChange={handleOnChange}
195+
>
196+
<p className="ant-upload-drag-icon">
197+
{hasIcon(props.prefixIcon) ? (
198+
<IconWrapper>{props.prefixIcon}</IconWrapper>
199+
) : (
200+
<Button type="text" style={{ fontSize: '48px', color: style.accent, border: 'none' }}>
201+
📁
202+
</Button>
203+
)}
204+
</p>
205+
<p className="ant-upload-text">
206+
{props.text || trans("file.dragAreaText")}
207+
</p>
208+
<p className="ant-upload-hint">
209+
{trans("file.dragAreaHint")}
210+
</p>
211+
</StyledDragger>
212+
);
213+
};

client/packages/lowcoder/src/comps/comps/fileComp/fileComp.tsx

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import { CommonNameConfig, NameConfig, withExposingConfigs } from "../../generat
4242
import { formDataChildren, FormDataPropertyView } from "../formComp/formDataConstants";
4343
import { messageInstance } from "lowcoder-design/src/components/GlobalInstances";
4444
import { CustomModal } from "lowcoder-design";
45+
import { DraggerUpload } from "./draggerUpload";
4546

4647
import React, { useContext } from "react";
4748
import { EditorContext } from "comps/editorState";
@@ -50,6 +51,7 @@ import Skeleton from "antd/es/skeleton";
5051
import Menu from "antd/es/menu";
5152
import Flex from "antd/es/flex";
5253
import { checkIsMobile } from "@lowcoder-ee/util/commonUtils";
54+
import { AutoHeightControl } from "@lowcoder-ee/comps/controls/autoHeightControl";
5355

5456
const FileSizeControl = codeControl((value) => {
5557
if (typeof value === "number") {
@@ -131,7 +133,7 @@ const commonValidationFields = (children: RecordConstructorToComp<typeof validat
131133
}),
132134
];
133135

134-
const commonProps = (
136+
export const commonProps = (
135137
props: RecordConstructorToView<typeof commonChildren> & {
136138
uploadType: "single" | "multiple" | "directory";
137139
}
@@ -619,25 +621,43 @@ const UploadTypeOptions = [
619621
{ label: trans("file.directory"), value: "directory" },
620622
] as const;
621623

624+
const UploadModeOptions = [
625+
{ label: trans("file.button"), value: "button" },
626+
{ label: trans("file.dragArea"), value: "dragArea" },
627+
] as const;
628+
622629
const childrenMap = {
623630
text: withDefault(StringControl, trans("file.upload")),
624631
uploadType: dropdownControl(UploadTypeOptions, "single"),
632+
uploadMode: dropdownControl(UploadModeOptions, "button"),
633+
autoHeight: withDefault(AutoHeightControl, "fixed"),
625634
tabIndex: NumberControl,
626635
...commonChildren,
627636
...formDataChildren,
628637
};
629638

630639
let FileTmpComp = new UICompBuilder(childrenMap, (props, dispatch) => {
631-
return(
632-
<Upload {...props} dispatch={dispatch} />
633-
)})
640+
const uploadMode = props.uploadMode;
641+
const autoHeight = props.autoHeight;
642+
643+
if (uploadMode === "dragArea") {
644+
return <DraggerUpload {...props} dispatch={dispatch} autoHeight={autoHeight} />;
645+
}
646+
647+
return <Upload {...props} dispatch={dispatch} />;
648+
})
634649
.setPropertyViewFn((children) => (
635650
<>
636651
<Section name={sectionNames.basic}>
637652
{children.text.propertyView({
638653
label: trans("text"),
639654
})}
655+
{children.uploadMode.propertyView({
656+
label: trans("file.uploadMode"),
657+
radioButton: true,
658+
})}
640659
{children.uploadType.propertyView({ label: trans("file.uploadType") })}
660+
{children.uploadMode.getView() === "dragArea" && children.autoHeight.getPropertyView()}
641661
</Section>
642662

643663
<FormDataPropertyView {...children} />

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1925,6 +1925,11 @@ export const en = {
19251925
"usePhoto": "Use Photo",
19261926
"retakePhoto": "Retake Photo",
19271927
"capture": "Capture",
1928+
"button": "Button",
1929+
"dragArea": "Drag Area",
1930+
"uploadMode": "Upload Mode",
1931+
"dragAreaText": "Click or drag file to this area to upload",
1932+
"dragAreaHint": "Support for a single or bulk upload. Strictly prohibited from uploading company data or other band files.",
19281933
},
19291934
"date": {
19301935
"format": "Format",

0 commit comments

Comments
 (0)