Skip to content

Commit e7c41d2

Browse files
Merge pull request #1998 from iamfaran/feat/1720-dragger
[Feat]: #1720 Add DropSpace / DragArea for fileUpload
2 parents 7dc8e77 + af83e43 commit e7c41d2

File tree

4 files changed

+580
-203
lines changed

4 files changed

+580
-203
lines changed
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
import React, { Suspense, useCallback, useEffect, useRef, useState } from "react";
2+
import { default as Button } from "antd/es/button";
3+
import Dropdown from "antd/es/dropdown";
4+
import type { ItemType } from "antd/es/menu/interface";
5+
import Skeleton from "antd/es/skeleton";
6+
import Menu from "antd/es/menu";
7+
import Flex from "antd/es/flex";
8+
import styled from "styled-components";
9+
import { trans } from "i18n";
10+
import { CustomModal } from "lowcoder-design";
11+
12+
const CustomModalStyled = styled(CustomModal)`
13+
top: 10vh;
14+
.react-draggable {
15+
max-width: 100%;
16+
width: 500px;
17+
18+
video {
19+
width: 100%;
20+
}
21+
}
22+
`;
23+
24+
const Error = styled.div`
25+
color: #f5222d;
26+
height: 100px;
27+
width: 100%;
28+
display: flex;
29+
align-items: center;
30+
justify-content: center;
31+
`;
32+
33+
const Wrapper = styled.div`
34+
img,
35+
video,
36+
.ant-skeleton {
37+
width: 100%;
38+
height: 400px;
39+
max-height: 70vh;
40+
position: relative;
41+
object-fit: cover;
42+
background-color: #000;
43+
}
44+
.ant-skeleton {
45+
h3,
46+
li {
47+
background-color: transparent;
48+
}
49+
}
50+
`;
51+
52+
const ReactWebcam = React.lazy(() => import("react-webcam"));
53+
54+
export const ImageCaptureModal = (props: {
55+
showModal: boolean;
56+
onModalClose: () => void;
57+
onImageCapture: (image: string) => void;
58+
}) => {
59+
const [errMessage, setErrMessage] = useState("");
60+
const [videoConstraints, setVideoConstraints] = useState<MediaTrackConstraints>({
61+
facingMode: "environment",
62+
});
63+
const [modeList, setModeList] = useState<ItemType[]>([]);
64+
const [dropdownShow, setDropdownShow] = useState(false);
65+
const [imgSrc, setImgSrc] = useState<string>();
66+
const webcamRef = useRef<any>(null);
67+
68+
useEffect(() => {
69+
if (props.showModal) {
70+
setImgSrc("");
71+
setErrMessage("");
72+
setVideoConstraints({ facingMode: "environment" });
73+
setDropdownShow(false);
74+
}
75+
}, [props.showModal]);
76+
77+
const handleMediaErr = (err: any) => {
78+
if (typeof err === "string") {
79+
setErrMessage(err);
80+
} else {
81+
if (err.message === "getUserMedia is not implemented in this browser") {
82+
setErrMessage(trans("scanner.errTip"));
83+
} else {
84+
setErrMessage(err.message);
85+
}
86+
}
87+
};
88+
89+
const handleCapture = useCallback(() => {
90+
const imageSrc = webcamRef.current?.getScreenshot?.();
91+
setImgSrc(imageSrc);
92+
}, [webcamRef]);
93+
94+
const getModeList = () => {
95+
navigator.mediaDevices.enumerateDevices().then((data) => {
96+
const videoData = data.filter((item) => item.kind === "videoinput");
97+
const faceModeList = videoData.map((item, index) => ({
98+
label: item.label || trans("scanner.camera", { index: index + 1 }),
99+
key: item.deviceId,
100+
}));
101+
setModeList(faceModeList);
102+
});
103+
};
104+
105+
return (
106+
<CustomModalStyled
107+
showOkButton={false}
108+
showCancelButton={false}
109+
open={props.showModal}
110+
maskClosable={true}
111+
destroyOnHidden
112+
onCancel={props.onModalClose}
113+
>
114+
{!!errMessage ? (
115+
<Error>{errMessage}</Error>
116+
) : (
117+
props.showModal && (
118+
<Wrapper>
119+
{imgSrc ? (
120+
<img src={imgSrc} alt="webcam" />
121+
) : (
122+
<Suspense fallback={<Skeleton />}>
123+
<ReactWebcam
124+
key={JSON.stringify(videoConstraints)}
125+
ref={webcamRef}
126+
onUserMediaError={handleMediaErr}
127+
screenshotFormat="image/jpeg"
128+
videoConstraints={videoConstraints}
129+
/>
130+
</Suspense>
131+
)}
132+
{imgSrc ? (
133+
<Flex justify="center" gap={10}>
134+
<Button
135+
type="primary"
136+
style={{ float: "right", marginTop: "10px" }}
137+
onClick={(e) => {
138+
e.stopPropagation();
139+
if (imgSrc) props.onImageCapture(imgSrc);
140+
}}
141+
>
142+
{trans("file.usePhoto")}
143+
</Button>
144+
<Button
145+
style={{ float: "right", marginTop: "10px" }}
146+
onClick={(e) => {
147+
e.stopPropagation();
148+
setImgSrc("");
149+
}}
150+
>
151+
{trans("file.retakePhoto")}
152+
</Button>
153+
</Flex>
154+
) : (
155+
<Flex justify="center" gap={10}>
156+
<Button
157+
type="primary"
158+
style={{ float: "right", marginTop: "10px" }}
159+
onClick={(e) => {
160+
e.stopPropagation();
161+
handleCapture();
162+
}}
163+
>
164+
{trans("file.capture")}
165+
</Button>
166+
<Dropdown
167+
placement="bottomRight"
168+
trigger={["click"]}
169+
open={dropdownShow}
170+
onOpenChange={(value) => setDropdownShow(value)}
171+
popupRender={() => (
172+
<Menu
173+
items={modeList}
174+
onClick={(value) => {
175+
setVideoConstraints({ deviceId: { exact: value.key } });
176+
setDropdownShow(false);
177+
}}
178+
/>
179+
)}
180+
>
181+
<Button
182+
style={{ float: "right", marginTop: "10px" }}
183+
onClick={(e) => {
184+
e.stopPropagation();
185+
getModeList();
186+
}}
187+
>
188+
{trans("scanner.changeCamera")}
189+
</Button>
190+
</Dropdown>
191+
</Flex>
192+
)}
193+
</Wrapper>
194+
)
195+
)}
196+
</CustomModalStyled>
197+
);
198+
};
199+
200+
export default ImageCaptureModal;
201+
202+

0 commit comments

Comments
 (0)