Skip to content

Commit 43c7fa3

Browse files
committed
#1720 add dragger image capture modal + custom dragger styling
1 parent 5db3f5a commit 43c7fa3

File tree

3 files changed

+344
-221
lines changed

3 files changed

+344
-221
lines changed
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
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+
}
73+
}, [props.showModal]);
74+
75+
const handleMediaErr = (err: any) => {
76+
if (typeof err === "string") {
77+
setErrMessage(err);
78+
} else {
79+
if (err.message === "getUserMedia is not implemented in this browser") {
80+
setErrMessage(trans("scanner.errTip"));
81+
} else {
82+
setErrMessage(err.message);
83+
}
84+
}
85+
};
86+
87+
const handleCapture = useCallback(() => {
88+
const imageSrc = webcamRef.current?.getScreenshot?.();
89+
setImgSrc(imageSrc);
90+
}, [webcamRef]);
91+
92+
const getModeList = () => {
93+
navigator.mediaDevices.enumerateDevices().then((data) => {
94+
const videoData = data.filter((item) => item.kind === "videoinput");
95+
const faceModeList = videoData.map((item, index) => ({
96+
label: item.label || trans("scanner.camera", { index: index + 1 }),
97+
key: item.deviceId,
98+
}));
99+
setModeList(faceModeList);
100+
});
101+
};
102+
103+
return (
104+
<CustomModalStyled
105+
showOkButton={false}
106+
showCancelButton={false}
107+
open={props.showModal}
108+
maskClosable={true}
109+
destroyOnHidden
110+
onCancel={props.onModalClose}
111+
>
112+
{!!errMessage ? (
113+
<Error>{errMessage}</Error>
114+
) : (
115+
props.showModal && (
116+
<Wrapper>
117+
{imgSrc ? (
118+
<img src={imgSrc} alt="webcam" />
119+
) : (
120+
<Suspense fallback={<Skeleton />}>
121+
<ReactWebcam
122+
ref={webcamRef}
123+
onUserMediaError={handleMediaErr}
124+
screenshotFormat="image/jpeg"
125+
/>
126+
</Suspense>
127+
)}
128+
{imgSrc ? (
129+
<Flex justify="center" gap={10}>
130+
<Button
131+
type="primary"
132+
style={{ float: "right", marginTop: "10px" }}
133+
onClick={(e) => {
134+
e.stopPropagation();
135+
if (imgSrc) props.onImageCapture(imgSrc);
136+
}}
137+
>
138+
{trans("file.usePhoto")}
139+
</Button>
140+
<Button
141+
style={{ float: "right", marginTop: "10px" }}
142+
onClick={(e) => {
143+
e.stopPropagation();
144+
setImgSrc("");
145+
}}
146+
>
147+
{trans("file.retakePhoto")}
148+
</Button>
149+
</Flex>
150+
) : (
151+
<Flex justify="center" gap={10}>
152+
<Button
153+
type="primary"
154+
style={{ float: "right", marginTop: "10px" }}
155+
onClick={(e) => {
156+
e.stopPropagation();
157+
handleCapture();
158+
}}
159+
>
160+
{trans("file.capture")}
161+
</Button>
162+
<Dropdown
163+
placement="bottomRight"
164+
trigger={["click"]}
165+
open={dropdownShow}
166+
onOpenChange={(value) => setDropdownShow(value)}
167+
popupRender={() => (
168+
<Menu
169+
items={modeList}
170+
onClick={(value) =>
171+
setVideoConstraints({ ...videoConstraints, deviceId: value.key })
172+
}
173+
/>
174+
)}
175+
>
176+
<Button
177+
style={{ float: "right", marginTop: "10px" }}
178+
onClick={(e) => {
179+
e.stopPropagation();
180+
getModeList();
181+
}}
182+
>
183+
{trans("scanner.changeCamera")}
184+
</Button>
185+
</Dropdown>
186+
</Flex>
187+
)}
188+
</Wrapper>
189+
)
190+
)}
191+
</CustomModalStyled>
192+
);
193+
};
194+
195+
export default ImageCaptureModal;
196+
197+

0 commit comments

Comments
 (0)