Skip to content

Commit bb55b98

Browse files
authored
Fix: QR Codes Scan & Pending Connections (#12)
* feat: qr code improvements * apply pending connection events * chore: hide import contacts for now
1 parent b802781 commit bb55b98

File tree

10 files changed

+114
-99
lines changed

10 files changed

+114
-99
lines changed

apps/resplice/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"croppie": "^2.6.5",
2525
"date-fns": "^2.30.0",
2626
"date-fns-tz": "^2.0.0",
27+
"html5-qrcode": "^2.3.8",
2728
"js-search": "^2.0.1",
2829
"jsqr": "^1.4.0",
2930
"libphonenumber-js": "^1.10.49",

apps/resplice/src/common/components/NavActions.svelte

+2-2
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,15 @@
6161
class="bg-white rounded-xl p-8 flex flex-col items-start space-y-4"
6262
style="will-change: transform; transform: translateY({$translateY}px) scale({$scale})"
6363
>
64-
<button
64+
<!-- <button
6565
class="flex items-center space-x-2 focus:ring-4 focus:ring-green-200 focus:outline-none rounded-lg w-full"
6666
on:click={() => push('/invite/contacts')}
6767
>
6868
<div class="p-2 rounded-lg bg-brand-primary text-brand-primary bg-opacity-20">
6969
<PeopleIcon width={24} height={24} />
7070
</div>
7171
<p>Import Contacts</p>
72-
</button>
72+
</button> -->
7373
<!-- <button
7474
class="flex items-center space-x-2 focus:ring-4 focus:ring-green-200 focus:outline-none rounded-lg w-full"
7575
on:click={() => push('/invite/create/handle')}

apps/resplice/src/modules/connection/pages/ConnectionListPage.svelte

+11-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
ConnectionEmptyIcon,
77
CameraIcon,
88
QRCodeIcon,
9-
PeopleIcon
9+
// PeopleIcon,
10+
PersonAddIcon
1011
} from '@resplice/components'
1112
import connectionStore from '$modules/connection/connection.store'
1213
import inviteStores from '$modules/invite/invite.store'
@@ -59,13 +60,21 @@
5960
<p class="text-center px-8 py-2">
6061
You can invite others to Resplice even if they don't have an account!
6162
</p>
62-
<Button
63+
<!-- <Button
6364
color="brand-light"
6465
class="flex items-center justify-center w-56"
6566
on:click={() => push('/invite/contacts')}
6667
>
6768
<PeopleIcon width={24} height={24} />
6869
<span class="ml-2">Import Contacts</span>
70+
</Button> -->
71+
<Button
72+
color="brand-light"
73+
class="flex items-center justify-center w-56"
74+
on:click={() => push('/invite/create/phone')}
75+
>
76+
<PersonAddIcon width={24} height={24} />
77+
<span class="ml-2">Invite via Phone</span>
6978
</Button>
7079
</div>
7180

apps/resplice/src/modules/invite/components/QrCode.svelte

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@
33
import QRCode from 'qrcode'
44
55
export let data: string
6+
export let color: string
67
let qrCode: string
78
89
onMount(async () => {
910
qrCode = await QRCode.toDataURL(data, {
11+
type: 'image/webp',
1012
errorCorrectionLevel: 'medium',
1113
scale: 8,
1214
color: {
13-
light: '#1BBC9B'
15+
light: color
1416
}
1517
})
1618
})

apps/resplice/src/modules/invite/invite.protocol.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ function inviteProtocolFactory({
3636
}: Dependencies): InviteProtocol {
3737
commuter.messages$.pipe(onlyEvents()).subscribe((event) => {
3838
store.invites.update((state) => applyInviteEvent(state, event))
39-
// store.pendingConnections.update((state) => applyPendingConnectionEvent(state, event))
39+
store.pendingConnections.update((state) => applyPendingConnectionEvent(state, event))
4040
})
4141

4242
return {

apps/resplice/src/modules/invite/pages/QrInvitePage.svelte

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
class="w-full flex-none flex items-center justify-center mb-8 p-2 rounded-2xl bg-brand-primary bg-opacity-20 overflow-hidden"
7070
>
7171
{#if url}
72-
<QrCode data={url} />
72+
<QrCode data={url} color="#d9f2eb" />
7373
{:else}
7474
<Skeleton variant="rect" width="100%" height="333px" />
7575
{/if}
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,38 @@
11
<script lang="ts">
2-
import { onDestroy } from 'svelte'
3-
import QR, { type QRCode } from 'jsqr'
2+
import { onMount } from 'svelte'
3+
import { Html5Qrcode, type Html5QrcodeResult } from 'html5-qrcode'
44
import { replace, pop } from 'svelte-spa-router'
5-
import { Camera, CloseIcon, IconButton } from '@resplice/components'
5+
import { CloseIcon, IconButton } from '@resplice/components'
66
7-
let qrCode: QRCode
8-
let streamInterval: number
9-
10-
async function handleQr(qrData: string | null) {
11-
if (!qrData) return
12-
13-
const url = new URL(qrData)
7+
function getScanBox(width: number, height: number) {
8+
const constraint = Math.min(width, height)
9+
const size = Math.min(constraint - 48, 512)
10+
return { width: size, height: size }
11+
}
1412
13+
async function onScan(data: string, _result: Html5QrcodeResult) {
14+
const url = new URL(data)
1515
replace(url.hash.replace('#', ''))
1616
}
1717
18-
$: handleQr(qrCode?.data)
19-
20-
async function onVideoStream(e: CustomEvent<HTMLVideoElement>) {
21-
// TODO: Look into Screen Wake Lock API
22-
// https://developer.mozilla.org/en-US/docs/Web/API/Screen_Wake_Lock_API
23-
const stream = e.detail
24-
const canvasEl = document.createElement('canvas')
25-
const canvas = canvasEl.getContext('2d')
18+
onMount(() => {
19+
const scanner = new Html5Qrcode('camera', { formatsToSupport: [0], verbose: false })
2620
27-
// Maybe use requestAnimationFrame here instead of interval
28-
streamInterval = window.setInterval(async () => {
29-
if (qrCode) return
30-
if (stream.readyState !== stream.HAVE_ENOUGH_DATA) return
31-
const { videoWidth: width, videoHeight: height } = stream
32-
canvasEl.height = height
33-
canvasEl.width = width
34-
if (!canvas) return
21+
scanner.start(
22+
{ facingMode: 'environment' },
23+
{ fps: 10, qrbox: getScanBox, aspectRatio: 0.5625 },
24+
onScan,
25+
() => {}
26+
)
3527
36-
canvas.drawImage(stream, 0, 0, width, height)
37-
// Might need to make these dimensions smaller for performance
38-
const imageData = canvas.getImageData(0, 0, width, height)
39-
const code = QR(imageData.data, imageData.width, imageData.height, {
40-
inversionAttempts: 'dontInvert'
41-
})
42-
if (code) {
43-
clearInterval(streamInterval)
44-
qrCode = code
45-
}
46-
}, 500)
47-
}
48-
49-
onDestroy(() => {
50-
clearInterval(streamInterval)
28+
return () => scanner.stop()
5129
})
5230
</script>
5331

54-
<main
55-
class="h-full w-full bg-zinc-800 rounded-t-3xl rounded-b-3xl flex-1 flex flex-col justify-center items-center"
56-
>
57-
<Camera hideControls on:stream={onVideoStream} />
32+
<main class="relative h-full w-full bg-zinc-800 flex-1 flex flex-col justify-center items-center">
33+
<div id="camera" class="w-full top-0 left-0" />
5834

5935
<div class="absolute bottom-0 z-10 flex items-center justify-center w-full p-4">
6036
<IconButton Icon={CloseIcon} on:click={() => pop()} />
6137
</div>
62-
63-
<div
64-
class="border-4 border-brand-primary bg-transparent w-5/6 h-1/2 z-10 rounded-2xl flex justify-center items-center"
65-
/>
6638
</main>

package-lock.json

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/components/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"clsx": "^2.0.0",
5353
"croppie": "^2.6.5",
5454
"happy-dom": "^12.9.0",
55+
"html5-qrcode": "^2.3.8",
5556
"postcss": "^8.4.31",
5657
"publint": "^0.2.3",
5758
"svelte": "^4.2.3",
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,79 @@
11
<script lang="ts">
2-
import { onDestroy } from 'svelte'
3-
import { Camera, CloseIcon, IconButton } from '$lib'
4-
import { browser } from '$app/environment'
5-
6-
let streamInterval: number
7-
8-
async function onVideoStream(e: CustomEvent<HTMLVideoElement>) {
9-
const stream = e.detail
10-
const canvasEl = document.createElement('canvas')
11-
const canvas = canvasEl.getContext('2d')
12-
13-
// Maybe use requestAnimationFrame here instead of interval
14-
streamInterval = window.setInterval(async () => {
15-
// if (qrCode) return
16-
if (stream.readyState !== stream.HAVE_ENOUGH_DATA) return
17-
const { videoWidth: width, videoHeight: height } = stream
18-
console.log(stream)
19-
canvasEl.height = height
20-
canvasEl.width = width
21-
if (!canvas) return
22-
23-
canvas.drawImage(stream, 0, 0, width, height)
24-
// Might need to make these dimensions smaller for performance
25-
const imageData = canvas.getImageData(0, 0, width, height)
26-
console.log(imageData)
27-
// const code = QR(imageData.data, imageData.width, imageData.height, {
28-
// inversionAttempts: 'dontInvert'
29-
// })
30-
// if (code) {
31-
// clearInterval(streamInterval)
32-
// qrCode = code
33-
// }
34-
}, 500)
2+
import { onMount } from 'svelte'
3+
import { Html5Qrcode, type Html5QrcodeResult } from 'html5-qrcode'
4+
import { CloseIcon, IconButton } from '$lib'
5+
6+
function getScanBox(width: number, height: number) {
7+
const constraint = Math.min(width, height)
8+
const size = Math.min(constraint - 48, 512)
9+
return { width: size, height: size }
10+
}
11+
12+
async function onScan(data: string, _result: Html5QrcodeResult) {
13+
console.log(data, _result)
3514
}
3615
37-
onDestroy(() => {
38-
clearInterval(streamInterval)
16+
onMount(() => {
17+
const scanner = new Html5Qrcode('camera', { formatsToSupport: [0], verbose: false })
18+
19+
scanner.start(
20+
{ facingMode: 'environment' },
21+
{ fps: 10, qrbox: getScanBox, aspectRatio: 0.5625 },
22+
onScan,
23+
() => {}
24+
)
25+
26+
return () => scanner.stop()
3927
})
28+
29+
// async function handleQr(qrData: string | null) {
30+
// if (!qrData) return
31+
32+
// const url = new URL(qrData)
33+
34+
// replace(url.hash.replace('#', ''))
35+
// }
36+
37+
// $: handleQr(qrCode?.data)
38+
39+
// async function onVideoStream(e: CustomEvent<HTMLVideoElement>) {
40+
// // TODO: Look into Screen Wake Lock API
41+
// // https://developer.mozilla.org/en-US/docs/Web/API/Screen_Wake_Lock_API
42+
// const stream = e.detail
43+
// const canvasEl = document.createElement('canvas')
44+
// const canvas = canvasEl.getContext('2d')
45+
46+
// // Maybe use requestAnimationFrame here instead of interval
47+
// streamInterval = window.setInterval(async () => {
48+
// if (qrCode) return
49+
// if (stream.readyState !== stream.HAVE_ENOUGH_DATA) return
50+
// const { videoWidth: width, videoHeight: height } = stream
51+
// canvasEl.height = height
52+
// canvasEl.width = width
53+
// if (!canvas) return
54+
55+
// canvas.drawImage(stream, 0, 0, width, height)
56+
// // Might need to make these dimensions smaller for performance
57+
// const imageData = canvas.getImageData(0, 0, width, height)
58+
// const code = QR(imageData.data, imageData.width, imageData.height, {
59+
// inversionAttempts: 'dontInvert'
60+
// })
61+
// if (code) {
62+
// clearInterval(streamInterval)
63+
// qrCode = code
64+
// }
65+
// }, 500)
66+
// }
4067
</script>
4168

42-
<main
43-
class="h-full w-full bg-zinc-800 rounded-t-3xl rounded-b-3xl flex-1 flex flex-col justify-center items-center"
44-
>
45-
{#if browser}
46-
<Camera hideControls on:stream={onVideoStream} />
47-
{/if}
69+
<main class="relative h-full w-full bg-zinc-800 flex-1 flex flex-col justify-center items-center">
70+
<div id="camera" class="w-full top-0 left-0" />
4871

4972
<div class="absolute bottom-0 z-10 flex items-center justify-center w-full p-4">
5073
<IconButton Icon={CloseIcon} />
5174
</div>
5275

53-
<div
76+
<!-- <div
5477
class="border-4 border-brand-primary bg-transparent w-5/6 h-1/2 z-10 rounded-2xl flex justify-center items-center"
55-
/>
78+
/> -->
5679
</main>

0 commit comments

Comments
 (0)