Skip to content

Commit b2cf0cf

Browse files
committed
feat: update
1 parent 288aaac commit b2cf0cf

File tree

17 files changed

+412
-51
lines changed

17 files changed

+412
-51
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
@import '../../styles';
2+
3+
.container {
4+
margin-bottom: 8px;
5+
padding: 0 16px;
6+
7+
.scanBox {
8+
border: 2px solid var(--primary-color);
9+
width: 400px;
10+
height: 400px;
11+
display: flex;
12+
justify-content: center;
13+
align-items: center;
14+
}
15+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import React, { useEffect, useRef, useCallback, useState } from 'react'
2+
import { useTranslation } from 'react-i18next'
3+
import Dialog from 'widgets/Dialog'
4+
import jsQR from 'jsqr'
5+
6+
import styles from './cameraScanDialog.module.scss'
7+
8+
const IMAGE_SIZE = 400
9+
10+
export interface Point {
11+
x: number
12+
y: number
13+
}
14+
15+
const CameraScanDialog = ({ close, onConfirm }: { close: () => void; onConfirm: (result: string) => void }) => {
16+
const [t] = useTranslation()
17+
const videoRef = useRef<HTMLVideoElement>()
18+
const canvasRef = useRef<HTMLCanvasElement>(null)
19+
const canvas2dRef = useRef<CanvasRenderingContext2D>()
20+
const [loading, setLoading] = useState(true)
21+
22+
const drawLine = (begin: Point, end: Point) => {
23+
if (!canvas2dRef.current) return
24+
canvas2dRef.current.beginPath()
25+
canvas2dRef.current.moveTo(begin.x, begin.y)
26+
canvas2dRef.current.lineTo(end.x, end.y)
27+
canvas2dRef.current.lineWidth = 4
28+
canvas2dRef.current.strokeStyle = '#00c891'
29+
canvas2dRef.current.stroke()
30+
}
31+
32+
const scan = useCallback(() => {
33+
if (videoRef.current?.readyState === HTMLMediaElement.HAVE_ENOUGH_DATA) {
34+
setLoading(false)
35+
const canvas2d = canvasRef.current?.getContext('2d')
36+
if (canvas2d) {
37+
canvas2d.drawImage(videoRef.current, 0, 0, IMAGE_SIZE, IMAGE_SIZE)
38+
canvas2dRef.current = canvas2d
39+
const imageData = canvas2d.getImageData(0, 0, IMAGE_SIZE, IMAGE_SIZE)
40+
const code = jsQR(imageData.data, imageData.width, imageData.height, {
41+
inversionAttempts: 'dontInvert',
42+
})
43+
if (code) {
44+
drawLine(code.location.topLeftCorner, code.location.topRightCorner)
45+
drawLine(code.location.topRightCorner, code.location.bottomRightCorner)
46+
drawLine(code.location.bottomRightCorner, code.location.bottomLeftCorner)
47+
drawLine(code.location.bottomLeftCorner, code.location.topLeftCorner)
48+
onConfirm(code.data)
49+
}
50+
}
51+
}
52+
requestAnimationFrame(scan)
53+
}, [])
54+
55+
useEffect(() => {
56+
let mediaStream: MediaStream
57+
navigator.mediaDevices
58+
.getUserMedia({
59+
audio: false,
60+
video: { width: IMAGE_SIZE, height: IMAGE_SIZE },
61+
})
62+
.then(res => {
63+
if (res) {
64+
videoRef.current = document.createElement('video')
65+
videoRef.current.srcObject = res
66+
videoRef.current.play()
67+
mediaStream = res
68+
requestAnimationFrame(scan)
69+
}
70+
})
71+
72+
return () => {
73+
if (mediaStream) {
74+
mediaStream.getTracks().forEach(track => {
75+
track.stop()
76+
})
77+
}
78+
}
79+
}, [])
80+
81+
return (
82+
<Dialog
83+
show
84+
title={t('wallet-connect.scan-with-camera')}
85+
onCancel={close}
86+
showCancel={false}
87+
showConfirm={false}
88+
showFooter={false}
89+
>
90+
<div className={styles.container}>
91+
<div className={styles.scanBox}>
92+
{loading ? (
93+
<div>{t('wallet-connect.waiting-camera')}</div>
94+
) : (
95+
<canvas ref={canvasRef} width="400px" height="400px" />
96+
)}
97+
</div>
98+
</div>
99+
</Dialog>
100+
)
101+
}
102+
103+
CameraScanDialog.displayName = 'CameraScanDialog'
104+
export default CameraScanDialog
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import React, { useEffect, useRef, useMemo, useState } from 'react'
2+
import { captureScreen } from 'services/remote'
3+
import Button from 'widgets/Button'
4+
import { isSuccessResponse } from 'utils'
5+
import { useTranslation } from 'react-i18next'
6+
import Dialog from 'widgets/Dialog'
7+
import jsQR from 'jsqr'
8+
9+
import styles from './screenScanDialog.module.scss'
10+
11+
interface Point {
12+
x: number
13+
y: number
14+
}
15+
16+
const ScreenScanDialog = ({ close, onConfirm }: { close: () => void; onConfirm: (result: string) => void }) => {
17+
const [t] = useTranslation()
18+
const imgRef = useRef<HTMLImageElement>(null)
19+
const canvasRef = useRef<HTMLCanvasElement>(null)
20+
const canvas2dRef = useRef<CanvasRenderingContext2D>()
21+
const [sources, setSources] = useState<Controller.CaptureScreenSource[]>([])
22+
const [selectId, setSelectId] = useState('')
23+
const [uri, setUri] = useState('')
24+
25+
const drawLine = (begin: Point, end: Point) => {
26+
if (!canvas2dRef.current) return
27+
canvas2dRef.current.beginPath()
28+
canvas2dRef.current.moveTo(begin.x, begin.y)
29+
canvas2dRef.current.lineTo(end.x, end.y)
30+
canvas2dRef.current.lineWidth = 4
31+
canvas2dRef.current.strokeStyle = '#00c891'
32+
canvas2dRef.current.stroke()
33+
}
34+
35+
const source = useMemo(() => sources.find(item => item.id === selectId), [selectId, sources])
36+
37+
useEffect(() => {
38+
if (!source) return
39+
40+
if (imgRef.current) {
41+
const { width, height } = imgRef.current
42+
canvasRef.current?.setAttribute('height', `${height}px`)
43+
canvasRef.current?.setAttribute('width', `${width}px`)
44+
const canvas2d = canvasRef.current?.getContext('2d')
45+
if (canvas2d) {
46+
canvas2d.drawImage(imgRef.current, 0, 0, width, height)
47+
48+
canvas2dRef.current = canvas2d
49+
const imageData = canvas2d.getImageData(0, 0, width, height)
50+
const code = jsQR(imageData.data, imageData.width, imageData.height, {
51+
inversionAttempts: 'dontInvert',
52+
})
53+
54+
if (code?.data) {
55+
drawLine(code.location.topLeftCorner, code.location.topRightCorner)
56+
drawLine(code.location.topRightCorner, code.location.bottomRightCorner)
57+
drawLine(code.location.bottomRightCorner, code.location.bottomLeftCorner)
58+
drawLine(code.location.bottomLeftCorner, code.location.topLeftCorner)
59+
setUri(code.data)
60+
}
61+
}
62+
}
63+
}, [source])
64+
65+
useEffect(() => {
66+
captureScreen().then(res => {
67+
if (isSuccessResponse(res)) {
68+
const result = res.result as Controller.CaptureScreenSource[]
69+
setSources(result)
70+
if (result.length) {
71+
setSelectId(result[0].id)
72+
}
73+
}
74+
})
75+
}, [])
76+
77+
const handleSelect = (e: React.SyntheticEvent<HTMLButtonElement>) => {
78+
const { idx = '' } = e.currentTarget.dataset
79+
if (idx !== selectId) {
80+
setSelectId(idx)
81+
setUri('')
82+
}
83+
}
84+
85+
const handleConfirm = () => {
86+
onConfirm(uri)
87+
}
88+
89+
return (
90+
<Dialog
91+
show
92+
title={t('wallet-connect.scan-with-camera')}
93+
onCancel={close}
94+
disabled={!uri}
95+
onConfirm={handleConfirm}
96+
className={styles.scanDialog}
97+
>
98+
<div className={styles.container}>
99+
<div className={styles.chooseBox}>
100+
{sources.map(({ dataUrl, id }) => (
101+
<Button
102+
key={id}
103+
className={styles.chooseItem}
104+
data-idx={id}
105+
data-active={selectId === id}
106+
onClick={handleSelect}
107+
>
108+
<img src={dataUrl} alt="" />
109+
</Button>
110+
))}
111+
</div>
112+
<div className={styles.scanBox}>
113+
<canvas ref={canvasRef} />
114+
{source ? <img ref={imgRef} src={source?.dataUrl} alt="" /> : null}
115+
</div>
116+
</div>
117+
</Dialog>
118+
)
119+
}
120+
121+
ScreenScanDialog.displayName = 'ScreenScanDialog'
122+
export default ScreenScanDialog
123+
124+
// {loading ? (
125+
// <div>{t('wallet-connect.waiting-camera')}</div>
126+
// )
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
@import '../../styles';
2+
3+
.scanDialog {
4+
max-width: 100vw;
5+
}
6+
.container {
7+
margin-bottom: 8px;
8+
padding: 0 12px;
9+
display: flex;
10+
gap: 20px;
11+
min-height: 300px;
12+
.chooseBox {
13+
display: flex;
14+
flex-direction: column;
15+
justify-content: center;
16+
gap: 16px;
17+
18+
.chooseItem {
19+
border: 2px solid var(--divide-line-color);
20+
height: auto;
21+
border-radius: 4px;
22+
padding: 6px;
23+
min-width: 0;
24+
&[data-active='true'] {
25+
border-color: var(--primary-color);
26+
scale: 1.03;
27+
}
28+
img {
29+
width: 100px;
30+
}
31+
}
32+
}
33+
34+
.scanBox {
35+
display: flex;
36+
justify-content: center;
37+
align-items: center;
38+
position: relative;
39+
overflow: scroll;
40+
width: calc(100vw - 200px);
41+
canvas {
42+
position: absolute;
43+
}
44+
img {
45+
width: 100%;
46+
}
47+
}
48+
}

packages/neuron-ui/src/components/WalletConnect/hooks.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,8 @@ export const useWalletConnect = () => {
99
walletConnect: { proposals, sessions, requests, identity },
1010
} = useGlobalState()
1111

12-
const onConnect = useCallback(async (type, uri: string) => {
13-
const res: ControllerResponse = await connect({
14-
type,
15-
uri,
16-
})
12+
const onConnect = useCallback(async (uri: string) => {
13+
const res: ControllerResponse = await connect(uri)
1714
return res
1815
}, [])
1916

0 commit comments

Comments
 (0)