Skip to content

Commit

Permalink
Fix: QR Codes Scan & Pending Connections (#12)
Browse files Browse the repository at this point in the history
* feat: qr code improvements

* apply pending connection events

* chore: hide import contacts for now
  • Loading branch information
MarcusVirg authored Dec 15, 2023
1 parent b802781 commit bb55b98
Show file tree
Hide file tree
Showing 10 changed files with 114 additions and 99 deletions.
1 change: 1 addition & 0 deletions apps/resplice/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"croppie": "^2.6.5",
"date-fns": "^2.30.0",
"date-fns-tz": "^2.0.0",
"html5-qrcode": "^2.3.8",
"js-search": "^2.0.1",
"jsqr": "^1.4.0",
"libphonenumber-js": "^1.10.49",
Expand Down
4 changes: 2 additions & 2 deletions apps/resplice/src/common/components/NavActions.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,15 @@
class="bg-white rounded-xl p-8 flex flex-col items-start space-y-4"
style="will-change: transform; transform: translateY({$translateY}px) scale({$scale})"
>
<button
<!-- <button
class="flex items-center space-x-2 focus:ring-4 focus:ring-green-200 focus:outline-none rounded-lg w-full"
on:click={() => push('/invite/contacts')}
>
<div class="p-2 rounded-lg bg-brand-primary text-brand-primary bg-opacity-20">
<PeopleIcon width={24} height={24} />
</div>
<p>Import Contacts</p>
</button>
</button> -->
<!-- <button
class="flex items-center space-x-2 focus:ring-4 focus:ring-green-200 focus:outline-none rounded-lg w-full"
on:click={() => push('/invite/create/handle')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
ConnectionEmptyIcon,
CameraIcon,
QRCodeIcon,
PeopleIcon
// PeopleIcon,
PersonAddIcon
} from '@resplice/components'
import connectionStore from '$modules/connection/connection.store'
import inviteStores from '$modules/invite/invite.store'
Expand Down Expand Up @@ -59,13 +60,21 @@
<p class="text-center px-8 py-2">
You can invite others to Resplice even if they don't have an account!
</p>
<Button
<!-- <Button
color="brand-light"
class="flex items-center justify-center w-56"
on:click={() => push('/invite/contacts')}
>
<PeopleIcon width={24} height={24} />
<span class="ml-2">Import Contacts</span>
</Button> -->
<Button
color="brand-light"
class="flex items-center justify-center w-56"
on:click={() => push('/invite/create/phone')}
>
<PersonAddIcon width={24} height={24} />
<span class="ml-2">Invite via Phone</span>
</Button>
</div>

Expand Down
4 changes: 3 additions & 1 deletion apps/resplice/src/modules/invite/components/QrCode.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@
import QRCode from 'qrcode'
export let data: string
export let color: string
let qrCode: string
onMount(async () => {
qrCode = await QRCode.toDataURL(data, {
type: 'image/webp',
errorCorrectionLevel: 'medium',
scale: 8,
color: {
light: '#1BBC9B'
light: color
}
})
})
Expand Down
2 changes: 1 addition & 1 deletion apps/resplice/src/modules/invite/invite.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function inviteProtocolFactory({
}: Dependencies): InviteProtocol {
commuter.messages$.pipe(onlyEvents()).subscribe((event) => {
store.invites.update((state) => applyInviteEvent(state, event))
// store.pendingConnections.update((state) => applyPendingConnectionEvent(state, event))
store.pendingConnections.update((state) => applyPendingConnectionEvent(state, event))
})

return {
Expand Down
2 changes: 1 addition & 1 deletion apps/resplice/src/modules/invite/pages/QrInvitePage.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
class="w-full flex-none flex items-center justify-center mb-8 p-2 rounded-2xl bg-brand-primary bg-opacity-20 overflow-hidden"
>
{#if url}
<QrCode data={url} />
<QrCode data={url} color="#d9f2eb" />
{:else}
<Skeleton variant="rect" width="100%" height="333px" />
{/if}
Expand Down
70 changes: 21 additions & 49 deletions apps/resplice/src/modules/invite/pages/ScanQrPage.svelte
Original file line number Diff line number Diff line change
@@ -1,66 +1,38 @@
<script lang="ts">
import { onDestroy } from 'svelte'
import QR, { type QRCode } from 'jsqr'
import { onMount } from 'svelte'
import { Html5Qrcode, type Html5QrcodeResult } from 'html5-qrcode'
import { replace, pop } from 'svelte-spa-router'
import { Camera, CloseIcon, IconButton } from '@resplice/components'
import { CloseIcon, IconButton } from '@resplice/components'
let qrCode: QRCode
let streamInterval: number
async function handleQr(qrData: string | null) {
if (!qrData) return
const url = new URL(qrData)
function getScanBox(width: number, height: number) {
const constraint = Math.min(width, height)
const size = Math.min(constraint - 48, 512)
return { width: size, height: size }
}
async function onScan(data: string, _result: Html5QrcodeResult) {
const url = new URL(data)
replace(url.hash.replace('#', ''))
}
$: handleQr(qrCode?.data)
async function onVideoStream(e: CustomEvent<HTMLVideoElement>) {
// TODO: Look into Screen Wake Lock API
// https://developer.mozilla.org/en-US/docs/Web/API/Screen_Wake_Lock_API
const stream = e.detail
const canvasEl = document.createElement('canvas')
const canvas = canvasEl.getContext('2d')
onMount(() => {
const scanner = new Html5Qrcode('camera', { formatsToSupport: [0], verbose: false })
// Maybe use requestAnimationFrame here instead of interval
streamInterval = window.setInterval(async () => {
if (qrCode) return
if (stream.readyState !== stream.HAVE_ENOUGH_DATA) return
const { videoWidth: width, videoHeight: height } = stream
canvasEl.height = height
canvasEl.width = width
if (!canvas) return
scanner.start(
{ facingMode: 'environment' },
{ fps: 10, qrbox: getScanBox, aspectRatio: 0.5625 },
onScan,
() => {}
)
canvas.drawImage(stream, 0, 0, width, height)
// Might need to make these dimensions smaller for performance
const imageData = canvas.getImageData(0, 0, width, height)
const code = QR(imageData.data, imageData.width, imageData.height, {
inversionAttempts: 'dontInvert'
})
if (code) {
clearInterval(streamInterval)
qrCode = code
}
}, 500)
}
onDestroy(() => {
clearInterval(streamInterval)
return () => scanner.stop()
})
</script>

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

<div class="absolute bottom-0 z-10 flex items-center justify-center w-full p-4">
<IconButton Icon={CloseIcon} on:click={() => pop()} />
</div>

<div
class="border-4 border-brand-primary bg-transparent w-5/6 h-1/2 z-10 rounded-2xl flex justify-center items-center"
/>
</main>
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"clsx": "^2.0.0",
"croppie": "^2.6.5",
"happy-dom": "^12.9.0",
"html5-qrcode": "^2.3.8",
"postcss": "^8.4.31",
"publint": "^0.2.3",
"svelte": "^4.2.3",
Expand Down
109 changes: 66 additions & 43 deletions packages/components/src/routes/camera/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,56 +1,79 @@
<script lang="ts">
import { onDestroy } from 'svelte'
import { Camera, CloseIcon, IconButton } from '$lib'
import { browser } from '$app/environment'
let streamInterval: number
async function onVideoStream(e: CustomEvent<HTMLVideoElement>) {
const stream = e.detail
const canvasEl = document.createElement('canvas')
const canvas = canvasEl.getContext('2d')
// Maybe use requestAnimationFrame here instead of interval
streamInterval = window.setInterval(async () => {
// if (qrCode) return
if (stream.readyState !== stream.HAVE_ENOUGH_DATA) return
const { videoWidth: width, videoHeight: height } = stream
console.log(stream)
canvasEl.height = height
canvasEl.width = width
if (!canvas) return
canvas.drawImage(stream, 0, 0, width, height)
// Might need to make these dimensions smaller for performance
const imageData = canvas.getImageData(0, 0, width, height)
console.log(imageData)
// const code = QR(imageData.data, imageData.width, imageData.height, {
// inversionAttempts: 'dontInvert'
// })
// if (code) {
// clearInterval(streamInterval)
// qrCode = code
// }
}, 500)
import { onMount } from 'svelte'
import { Html5Qrcode, type Html5QrcodeResult } from 'html5-qrcode'
import { CloseIcon, IconButton } from '$lib'
function getScanBox(width: number, height: number) {
const constraint = Math.min(width, height)
const size = Math.min(constraint - 48, 512)
return { width: size, height: size }
}
async function onScan(data: string, _result: Html5QrcodeResult) {
console.log(data, _result)
}
onDestroy(() => {
clearInterval(streamInterval)
onMount(() => {
const scanner = new Html5Qrcode('camera', { formatsToSupport: [0], verbose: false })
scanner.start(
{ facingMode: 'environment' },
{ fps: 10, qrbox: getScanBox, aspectRatio: 0.5625 },
onScan,
() => {}
)
return () => scanner.stop()
})
// async function handleQr(qrData: string | null) {
// if (!qrData) return
// const url = new URL(qrData)
// replace(url.hash.replace('#', ''))
// }
// $: handleQr(qrCode?.data)
// async function onVideoStream(e: CustomEvent<HTMLVideoElement>) {
// // TODO: Look into Screen Wake Lock API
// // https://developer.mozilla.org/en-US/docs/Web/API/Screen_Wake_Lock_API
// const stream = e.detail
// const canvasEl = document.createElement('canvas')
// const canvas = canvasEl.getContext('2d')
// // Maybe use requestAnimationFrame here instead of interval
// streamInterval = window.setInterval(async () => {
// if (qrCode) return
// if (stream.readyState !== stream.HAVE_ENOUGH_DATA) return
// const { videoWidth: width, videoHeight: height } = stream
// canvasEl.height = height
// canvasEl.width = width
// if (!canvas) return
// canvas.drawImage(stream, 0, 0, width, height)
// // Might need to make these dimensions smaller for performance
// const imageData = canvas.getImageData(0, 0, width, height)
// const code = QR(imageData.data, imageData.width, imageData.height, {
// inversionAttempts: 'dontInvert'
// })
// if (code) {
// clearInterval(streamInterval)
// qrCode = code
// }
// }, 500)
// }
</script>

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

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

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

0 comments on commit bb55b98

Please sign in to comment.