Skip to content

Commit c1ba8f2

Browse files
Merge pull request #2110 from iamfaran/feat/2099-fileUpload
[Feat]: #2099 #2100 add clearValueAt + file name validation to the file upload component
2 parents ff471ee + 56dd3fd commit c1ba8f2

File tree

4 files changed

+178
-66
lines changed

4 files changed

+178
-66
lines changed

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

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { Suspense, useCallback, useEffect, useRef, useState } from "react";
1+
import React, { Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react";
22
import { default as Button } from "antd/es/button";
33
import Dropdown from "antd/es/dropdown";
44
import type { ItemType } from "antd/es/menu/interface";
@@ -8,6 +8,7 @@ import Flex from "antd/es/flex";
88
import styled from "styled-components";
99
import { trans } from "i18n";
1010
import { CustomModal } from "lowcoder-design";
11+
import { CaptureResolution, RESOLUTION_CONSTRAINTS } from "./fileComp";
1112

1213
const CustomModalStyled = styled(CustomModal)`
1314
top: 10vh;
@@ -53,23 +54,32 @@ const ReactWebcam = React.lazy(() => import("react-webcam"));
5354

5455
export const ImageCaptureModal = (props: {
5556
showModal: boolean;
57+
captureResolution?: CaptureResolution;
5658
onModalClose: () => void;
5759
onImageCapture: (image: string) => void;
5860
}) => {
5961
const [errMessage, setErrMessage] = useState("");
60-
const [videoConstraints, setVideoConstraints] = useState<MediaTrackConstraints>({
61-
facingMode: "environment",
62-
});
62+
const [selectedDeviceId, setSelectedDeviceId] = useState<string | null>(null);
6363
const [modeList, setModeList] = useState<ItemType[]>([]);
6464
const [dropdownShow, setDropdownShow] = useState(false);
6565
const [imgSrc, setImgSrc] = useState<string>();
6666
const webcamRef = useRef<any>(null);
6767

68+
const resolution = props.captureResolution ?? "auto";
69+
const resolutionSize = RESOLUTION_CONSTRAINTS[resolution] ?? {};
70+
71+
const videoConstraints = useMemo<MediaTrackConstraints>(() => {
72+
const base: MediaTrackConstraints = selectedDeviceId
73+
? { deviceId: { exact: selectedDeviceId } }
74+
: { facingMode: "environment" };
75+
return { ...base, ...resolutionSize };
76+
}, [selectedDeviceId, resolutionSize]);
77+
6878
useEffect(() => {
6979
if (props.showModal) {
7080
setImgSrc("");
7181
setErrMessage("");
72-
setVideoConstraints({ facingMode: "environment" });
82+
setSelectedDeviceId(null);
7383
setDropdownShow(false);
7484
}
7585
}, [props.showModal]);
@@ -125,6 +135,8 @@ export const ImageCaptureModal = (props: {
125135
ref={webcamRef}
126136
onUserMediaError={handleMediaErr}
127137
screenshotFormat="image/jpeg"
138+
screenshotQuality={1}
139+
forceScreenshotSourceSize
128140
videoConstraints={videoConstraints}
129141
/>
130142
</Suspense>
@@ -172,7 +184,7 @@ export const ImageCaptureModal = (props: {
172184
<Menu
173185
items={modeList}
174186
onClick={(value) => {
175-
setVideoConstraints({ deviceId: { exact: value.key } });
187+
setSelectedDeviceId(value.key);
176188
setDropdownShow(false);
177189
}}
178190
/>

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

Lines changed: 23 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { default as AntdUpload } from "antd/es/upload";
22
import { default as Button } from "antd/es/button";
33
import { UploadFile, UploadChangeParam, UploadFileStatus, RcFile } from "antd/es/upload/interface";
4-
import { useState, useEffect } from "react";
4+
import { useState, useMemo } from "react";
55
import styled, { css } from "styled-components";
66
import { trans } from "i18n";
77
import _ from "lodash";
@@ -11,8 +11,7 @@ import {
1111
multiChangeAction,
1212
} from "lowcoder-core";
1313
import { hasIcon } from "comps/utils";
14-
import { messageInstance } from "lowcoder-design/src/components/GlobalInstances";
15-
import { resolveValue, resolveParsedValue, commonProps } from "./fileComp";
14+
import { resolveValue, resolveParsedValue, commonProps, validateFile, CaptureResolution } from "./fileComp";
1615
import { FileStyleType, AnimationStyleType, heightCalculator, widthCalculator } from "comps/controls/styleControlConstants";
1716
import { ImageCaptureModal } from "./ImageCaptureModal";
1817
import { v4 as uuidv4 } from "uuid";
@@ -149,9 +148,11 @@ interface DraggerUploadProps {
149148
prefixIcon: any;
150149
suffixIcon: any;
151150
forceCapture: boolean;
151+
captureResolution: CaptureResolution;
152152
minSize: number;
153153
maxSize: number;
154154
maxFiles: number;
155+
fileNamePattern: string;
155156
uploadType: "single" | "multiple" | "directory";
156157
text: string;
157158
dragHintText?: string;
@@ -162,25 +163,27 @@ interface DraggerUploadProps {
162163

163164
export const DraggerUpload = (props: DraggerUploadProps) => {
164165
const { dispatch, files, style, autoHeight, animationStyle } = props;
165-
const [fileList, setFileList] = useState<UploadFile[]>(
166-
files.map((f) => ({ ...f, status: "done" })) as UploadFile[]
167-
);
166+
// Track only files currently being uploaded (not yet in props.files)
167+
const [uploadingFiles, setUploadingFiles] = useState<UploadFile[]>([]);
168168
const [showModal, setShowModal] = useState(false);
169169
const isMobile = checkIsMobile(window.innerWidth);
170170

171-
useEffect(() => {
172-
if (files.length === 0 && fileList.length !== 0) {
173-
setFileList([]);
174-
}
175-
}, [files]);
171+
// Derive fileList from props.files (source of truth) + currently uploading files
172+
const fileList = useMemo<UploadFile[]>(() => [
173+
...(files.map((f) => ({ ...f, status: "done" as const })) as UploadFile[]),
174+
...uploadingFiles,
175+
], [files, uploadingFiles]);
176176

177177
const handleOnChange = (param: UploadChangeParam) => {
178-
const uploadingFiles = param.fileList.filter((f) => f.status === "uploading");
179-
if (uploadingFiles.length !== 0) {
180-
setFileList(param.fileList);
178+
const currentlyUploading = param.fileList.filter((f) => f.status === "uploading");
179+
if (currentlyUploading.length !== 0) {
180+
setUploadingFiles(currentlyUploading);
181181
return;
182182
}
183183

184+
// Clear uploading state when all uploads complete
185+
setUploadingFiles([]);
186+
184187
let maxFiles = props.maxFiles;
185188
if (props.uploadType === "single") {
186189
maxFiles = 1;
@@ -240,8 +243,6 @@ export const DraggerUpload = (props: DraggerUploadProps) => {
240243
props.onEvent("parse");
241244
});
242245
}
243-
244-
setFileList(uploadedFiles.slice(-maxFiles));
245246
};
246247

247248
return (
@@ -254,21 +255,11 @@ export const DraggerUpload = (props: DraggerUploadProps) => {
254255
$auto={autoHeight}
255256
capture={props.forceCapture}
256257
openFileDialogOnClick={!(props.forceCapture && !isMobile)}
257-
beforeUpload={(file) => {
258-
if (!file.size || file.size <= 0) {
259-
messageInstance.error(`${file.name} ` + trans("file.fileEmptyErrorMsg"));
260-
return AntdUpload.LIST_IGNORE;
261-
}
262-
263-
if (
264-
(!!props.minSize && file.size < props.minSize) ||
265-
(!!props.maxSize && file.size > props.maxSize)
266-
) {
267-
messageInstance.error(`${file.name} ` + trans("file.fileSizeExceedErrorMsg"));
268-
return AntdUpload.LIST_IGNORE;
269-
}
270-
return true;
271-
}}
258+
beforeUpload={(file) => validateFile(file, {
259+
minSize: props.minSize,
260+
maxSize: props.maxSize,
261+
fileNamePattern: props.fileNamePattern,
262+
})}
272263
onChange={handleOnChange}
273264
>
274265
<p className="ant-upload-drag-icon">
@@ -301,6 +292,7 @@ export const DraggerUpload = (props: DraggerUploadProps) => {
301292

302293
<ImageCaptureModal
303294
showModal={showModal}
295+
captureResolution={props.captureResolution as CaptureResolution}
304296
onModalClose={() => setShowModal(false)}
305297
onImageCapture={async (image) => {
306298
setShowModal(false);

0 commit comments

Comments
 (0)