From e932d662d88956afa9fbe74afa8fc2937ab8428f Mon Sep 17 00:00:00 2001
From: Chris Duncan <chris@zoso.dev>
Date: Sun, 3 Nov 2024 00:47:02 -0700
Subject: [PATCH] Switch to zxing-wasm from barcode-detector so we can generate
 them as well as detect them. Replace QR test function with real one. Fix
 selectAccount event-triggered function by checking for blank value for
 account. Load QR code for account when it is selected. Add default
 placeholder QR and styling. Get rid of redundant 'show' class and just use
 'hide'.

---
 nemo-wallet.html | 129 +++++++++++++++++++++++++++++++++--------------
 1 file changed, 90 insertions(+), 39 deletions(-)

diff --git a/nemo-wallet.html b/nemo-wallet.html
index 6f295c6..961003e 100644
--- a/nemo-wallet.html
+++ b/nemo-wallet.html
@@ -4,7 +4,7 @@
 	<meta charset="utf-8" />
 	<meta lang="en" />
 	<meta name="viewport" content="width=device-width, initial-scale=1" />
-	<script type="module" src="https://cdn.jsdelivr.net/npm/barcode-detector@2.2.11/dist/es/side-effects.min.js"></script>
+	<script src="https://cdn.jsdelivr.net/npm/zxing-wasm@1.3.2/dist/iife/full/index.js"></script>
 	<script type="module">
 		import { Buffer } from 'https://esm.run/buffer-browser@6.0.4/index.js'
 		globalThis.Buffer = Buffer
@@ -23,26 +23,15 @@
 
 		window.addEventListener('load', async (event) => {
 			try {
-				document.querySelector(location.hash).classList.add('show')
+				document.querySelector(location.hash).classList.remove('hide')
 			} catch (err) {
 				location.hash = '#transact'
 			}
-			await loadQr()
 			await loadData()
 			await loadUi()
 			console.log('done')
 		})
 
-		async function loadQr () {
-			const barcodeDetector = new BarcodeDetector({
-				formats: ["qr_code"],
-			})
-			const imageFile = await fetch(
-				"https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=Hello%20world!",
-			).then(resp => resp.blob())
-			barcodeDetector.detect(imageFile).then(console.log)
-		}
-
 		async function loadData () {
 			console.log('loading data')
 			const walletSelect = document.querySelector('#wallet')
@@ -55,6 +44,7 @@
 			if (walletSelect.value === '' || accountSelect.value.substring(0, 1) === '_') {
 				accountSelect.setAttribute('disabled', '')
 				accountSelect.value = ''
+				resetQrCode()
 			}
 			console.log('loaded data')
 		}
@@ -87,15 +77,15 @@
 
 		function switchPage (event) {
 			const pages = document.getElementsByClassName('page')
-			Array.from(pages).map(p => p.classList.remove('show'))
+			Array.from(pages).map(p => p.classList.add('hide'))
 			const oldHash = new URL(event.oldURL)?.hash
 			const newHash = new URL(event.newURL)?.hash
 			if (oldHash) {
-				document.querySelector(oldHash)?.classList.remove('show')
+				document.querySelector(oldHash)?.classList.add('hide')
 			}
 			try {
 				if (newHash) {
-					document.querySelector(newHash).classList.add('show')
+					document.querySelector(newHash).classList.remove('hide')
 				}
 			} catch (err) {
 				notify.error(`Page not found`)
@@ -180,8 +170,8 @@
 				}
 				case '_new': {
 					try {
-						document.querySelector(location.hash).classList.remove('show')
-						document.getElementById('wallet-new').classList.add('show')
+						document.querySelector(location.hash).classList.add('hide')
+						document.getElementById('wallet-new').classList.remove('hide')
 					} catch (err) {
 						walletSelect.value = ''
 						console.error(err)
@@ -192,8 +182,8 @@
 				}
 				case '_import': {
 					try {
-						document.querySelector(location.hash).classList.remove('show')
-						document.getElementById('wallet-import').classList.add('show')
+						document.querySelector(location.hash).classList.add('hide')
+						document.getElementById('wallet-import').classList.remove('hide')
 					} catch (err) {
 						walletSelect.value = ''
 						console.error(err)
@@ -323,23 +313,40 @@
 			if (walletId == null || walletId === '' || walletId === '_new' || walletId === '_import') {
 				accountSelect.value = ''
 				accountSelect.setAttribute('disabled', '')
+				resetQrCode()
 				return
 			}
 			accountSelect.removeAttribute('disabled')
 			switch (accountSelect.value) {
+				case undefined:
+				case null:
+				case '': {
+					resetQrCode()
+					break
+				}
 				case '_new': {
 					try {
-						document.querySelector(location.hash).classList.remove('show')
-						document.getElementById('account-new').classList.add('show')
+						document.querySelector(location.hash).classList.add('hide')
+						document.getElementById('account-new').classList.remove('hide')
 					} catch (err) {
 						accountSelect.value = ''
 						console.error(err)
 						notify.error(`Error creating account`)
-						return
+					} finally {
+						break
 					}
 				}
 				default: {
-					await loadHistory()
+					try {
+						const account = await loadAccount()
+						createQrCode(account.address)
+					} catch (err) {
+						accountSelect.value = ''
+						console.error(err)
+						notify.error(`Error loading account`)
+					} finally {
+						break
+					}
 				}
 			}
 		}
@@ -477,8 +484,8 @@
 
 		async function cancelForm (id) {
 			await resetFormInputs()
-			document.getElementById(id).classList.remove('show')
-			document.querySelector(location.hash).classList.add('show')
+			document.getElementById(id).classList.add('hide')
+			document.querySelector(location.hash).classList.remove('hide')
 		}
 
 		async function resetFormInputs () {
@@ -523,6 +530,44 @@
 				themeCheckbox.value = 'light'
 			}
 		}
+		async function createQrCode (address, options) {
+			if (address == null || typeof address !== 'string' || address === '') {
+				notify.error(`Error creating QR code with invalid address`)
+				return
+			}
+			const nanoUrlOptions = []
+			if (options?.amount != null) {
+				try {
+					const bigAmount = BigInt(options.amount)
+					nanoUrlOptions.push(`amount=${bigAmount.toString()}`)
+				} catch (err) {
+					notify.error(`Error creating QR code with invalid amount`)
+					return
+				}
+			}
+			if (typeof options?.label === 'string') {
+				nanoUrlOptions.push(`label=${encodeURI(options.label)}`)
+			}
+			if (typeof options?.message === 'string') {
+				nanoUrlOptions.push(`message=${encodeURI(options.message)}`)
+			}
+			const nanoUrl = `nano:${encodeURIComponent(address)}?${nanoUrlOptions.join('&')}`
+			const config = {
+				width: 1080,
+				height: 1080,
+				margin: 10,
+			}
+			const { image } = await ZXingWASM.writeBarcodeToImageFile(nanoUrl, config)
+			const dataUrl = URL.createObjectURL(image)
+			const img = document.getElementById('qr-code')
+			URL.revokeObjectURL(img.src)
+			img.src = dataUrl
+		}
+
+		function resetQrCode () {
+			const qrCode = document.getElementById('qr-code')
+			qrCode.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABDgAAAQ4AQMAAADW3v7MAAAABlBMVEUAAAD///+l2Z/dAAAC0klEQVR42u3cPQ6CMBiAYYWFjStwE6+mN+Mq3oCVxKQO4k8N6ABNWvK8G1+HPuuXNBzDIYuOHBwcHBwcHBwcHBwcHMU6rt3cyanf/raxmZu2AwcHBwcHBwcHBwcHR3GOdoiPLudUjvoWj6bLOTg4ODg4ODg4ODg4ynO8d62Q2BGqj52Rg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4Oj986Rg4ODg4ODg4ODg4NjlWMm///g4ODg4ODg4ODg4OBYcuQQBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHB8e+Hdcu/g4cHBwcHBwcHBwcHBz5Otrhaag4ODg4ODg4ODg4ODjKdnzvWo9O/faOsTnUNw4ODg4ODg4ODg4Ojj04XrvW1OWcxPF3n+Pg4ODg4ODg4ODg4CjIMTbPWUjjiOLg4ODg4ODg4ODg4OBYFwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHB8feHVEcHBwcHBwcHBwcHBwcvx0zcXBwcHBwcHBwcHBwcCw5coiDg4ODg4ODg4ODg4ODY013wW3C00//4ZIAAAAASUVORK5CYII='
+		}
 	</script>
 	<style>
 		*{border:0;box-sizing:border-box;font:normal normal normal calc(16px + 0.1dvh) 'sans-serif';margin:0;}
@@ -627,12 +672,8 @@
 			width: fit-content;
 		}
 		.page {
-			display: none;
 			height: 100%;
 		}
-		.page.show {
-			display: initial;
-		}
 		.page.form {
 			z-index: 10;
 		}
@@ -739,6 +780,14 @@
 			left: 1.2em;
 			transition: all 0.2s;
 		}
+		#qr-code {
+			display: block;
+			height: auto;
+			margin: 0 auto;
+			max-height: 50dvh;
+			max-width: 100%;
+			width: auto;
+		}
 	</style>
 </head>
 
@@ -783,7 +832,7 @@
 
 		<!-- New Wallet Form -->
 
-		<div id="wallet-new" class="form page">
+		<div id="wallet-new" class="page form hide">
 			<h1>New Wallet</h1>
 			<div class="flex-y stretch">
 				<div id="wallet-new-name" class="flex-x left mid">
@@ -821,7 +870,7 @@
 
 		<!-- Import Wallet Form -->
 
-		<div id="wallet-import" class="form page">
+		<div id="wallet-import" class="page form hide">
 			<h1>Import Wallet</h1>
 			<div class="flex-y stretch">
 				<div id="wallet-import-name" class="flex-x left mid">
@@ -873,7 +922,7 @@
 
 		<!-- New Account Form -->
 
-		<div id="account-new" class="form page">
+		<div id="account-new" class="page form hide">
 			<h1>New Account</h1>
 			<div class="flex-y stretch">
 				<div id="account-new-name" class="flex-x left mid">
@@ -901,13 +950,13 @@
 
 		<!-- Exchange Page -->
 
-		<div id="exchange" class="page">
+		<div id="exchange" class="page hide">
 			<h1>Exchange</h1>
 		</div>
 
 		<!-- History Page -->
 
-		<div id="history" class="page">
+		<div id="history" class="page hide">
 			<h1>History</h1>
 			<iframe id="history-iframe" src="" loading="eager"></iframe>
 			<!--
@@ -924,19 +973,21 @@
 
 		<!-- Transact Page (default) -->
 
-		<div id="transact" class="page">
-			<h1>Transact</h1>
+		<div id="transact" class="page flex-y center mid hide">
+			<img id="qr-code" alt="QR code" src="" />
+			<canvas id="scanner" class="hide">Scan</canvas>
+			<button>Request</button>
 		</div>
 
 		<!-- Rolodex Page -->
 
-		<div id="rolodex" class="page">
+		<div id="rolodex" class="page hide">
 			<h1>Rolodex</h1>
 		</div>
 
 		<!-- Settings Page -->
 
-		<div id="settings" class="page">
+		<div id="settings" class="page hide">
 			<h1>Settings</h1>
 			<div class="flex-y left mid">
 				<label for="theme-checkbox">Theme</label>
-- 
2.34.1