qrread_ui.js (8634B)
1 const constraints = { 2 audio: false, 3 video: { 4 width: 400, height: 400, 5 } 6 }; 7 8 const addressPrePad = "000000000000000000000000"; 9 10 11 const video = document.createElement('video'); 12 video.setAttribute('id', 'video'); 13 video.setAttribute('autoplay', true); 14 video.setAttribute('playsinline', true); 15 16 const STATUS_OK = 0; 17 const STATUS_BUSY = 1; 18 const STATUS_INFO = 2; 19 const STATUS_WARN = 3; 20 const STATUS_ERROR = 4; 21 22 function setStatus(s, typ) { 23 const em = document.getElementById('statusText'); 24 em.innerHTML = s; 25 switch (typ) { 26 case STATUS_ERROR: 27 em.setAttribute('class', 'statusError'); 28 break; 29 case STATUS_BUSY: 30 em.setAttribute('class', 'statusBusy'); 31 break; 32 case STATUS_OK: 33 em.setAttribute('class', 'statusOk'); 34 break; 35 case STATUS_INFO: 36 em.setAttribute('class', 'statusInfo'); 37 break; 38 case STATUS_WARN: 39 em.setAttribute('class', 'statusWarn'); 40 break; 41 default: 42 em.setAttribute('class', 'statusBusy'); 43 } 44 } 45 46 window.addEventListener('uistate', (e) => { 47 console.debug('statechange', e); 48 switch (e.detail.delta) { 49 case STATE.WALLET_GENERATED: 50 document.getElementById("keyFile").value = e.detail.settings.keyFile; 51 break; 52 case STATE.WALLET_FAIL: 53 const btn = document.getElementById("keyFileSubmit"); 54 btn.removeAttribute('disabled'); 55 console.debug(btn); 56 break; 57 case STATE.WALLET_SETTINGS: 58 updateSettingsView('Wallet address', e.detail.settings.wallet.address); 59 document.getElementById("start").style.display = "none"; 60 document.getElementById("connect").style.display = "block"; 61 document.getElementById("keyFileSubmit").style.display = "none"; 62 break; 63 case STATE.CHAIN_SETTINGS: 64 updateSettingsView('RPC', e.detail.settings.provider.connection.url); 65 updateSettingsView('Chain ID', e.detail.settings.chainId); 66 document.getElementById("connect").style.display = "none"; 67 document.getElementById("contract").style.display = "block"; 68 break; 69 case STATE.CONTRACT_SETTINGS: 70 updateSettingsView('NFT contract address', e.detail.settings.tokenAddress); 71 updateSettingsView('NFT name', e.detail.settings.tokenName); 72 updateSettingsView('NFT symbol', e.detail.settings.tokenSymbol); 73 updateSettingsView('Voucher contract address', e.detail.settings.voucherAddress); 74 updateSettingsView('Voucher name', e.detail.settings.voucherName); 75 updateSettingsView('Voucher symbol', e.detail.settings.voucherSymbol); 76 updateSettingsView('Voucher decimals', e.detail.settings.voucherDecimals); 77 document.getElementById("contract").style.display = "none"; 78 document.getElementById("product").style.display = "block"; 79 document.getElementById("setup").style.display = "none"; 80 document.getElementById("runtime").style.display = "block"; 81 break; 82 case STATE.MINT: 83 document.getElementById("scanTokenId").innerHTML = settings.tokenId; 84 document.getElementById("scanTokenBatch").innerHTML = settings.batchNumber; 85 document.getElementById("scanTokenAmount").innerHTML = settings.mintAmount; 86 document.getElementById("scanVoucherAmount").innerHTML = settings.voucherTransferAmount / (10 ** settings.voucherDecimals); 87 document.getElementById("requestAmount").value = null; 88 document.getElementById("product").style.display = "none"; 89 document.getElementById("read").style.display = "block"; 90 document.getElementById("scanConfirm").style.display = "none"; 91 document.getElementById("scanReturn").style.display = "none"; 92 document.getElementById("scanAbort").style.display = "block"; 93 document.getElementById("scanManualMint").style.display = "block"; 94 live(); 95 break; 96 case STATE.SCAN_RESULT: 97 document.getElementById('scanAddress').value = e.detail.settings.recipient; 98 document.getElementById("scanManualMint").style.display = "none"; 99 document.getElementById("scanConfirm").style.display = "block"; 100 break; 101 case STATE.SCAN_STOP: 102 window.stream.getTracks().forEach(track => track.stop()); 103 break; 104 case STATE.SCAN_CONFIRM: 105 document.getElementById('scanAddress').value = e.detail.settings.recipient; 106 document.getElementById("scanManualMint").style.display = "none"; 107 document.getElementById("scanConfirm").style.display = "none"; 108 document.getElementById("scanAbort").style.display = "none"; 109 document.getElementById("scanReturn").style.display = "block"; 110 signAndSend(); 111 break; 112 case STATE.SCAN_DONE: 113 document.getElementById("read").style.display = "none"; 114 document.getElementById("product").style.display = "block"; 115 document.getElementById("scanAddress").value = ""; 116 const txList = document.getElementById("txList"); 117 while (txList.lastChild !== null) { 118 txList.removeChild(txList.lastChild); 119 } 120 break; 121 case STATE.AIEE: 122 throw 'execution terminated'; 123 default: 124 throw 'invalid state ' + e.detail.delta; 125 } 126 }); 127 128 window.addEventListener('token', (e) => { 129 const ls = document.getElementById('tokenChooser'); 130 const tid = e.detail.tokenId + '.' + e.detail.batch; 131 let v = e.detail.nice + ' (batch ' + e.detail.batch + ')'; 132 if (v === null) { 133 v = tid; 134 } 135 const input = document.createElement('input'); 136 input.setAttribute('id', 'tokenBatch.' + tid); 137 input.setAttribute('name', 'tokenBatch'); 138 input.setAttribute('type', 'radio'); 139 input.setAttribute('value', v); 140 console.log('lastchild', ls.lastChild); 141 if (ls.lastChild === null) { 142 input.setAttribute('checked', 'checked'); 143 } 144 const label = document.createElement('label'); 145 label.setAttribute('for', 'tokenBatch.' + tid); 146 label.innerHTML = v; 147 ls.appendChild(input); 148 ls.appendChild(label); 149 }); 150 151 window.addEventListener('tx', (e) => { 152 const ls = document.getElementById('txList'); 153 const li = document.createElement('li'); 154 const l = document.createElement('span'); 155 l.innerHTML = e.detail.tx; 156 const r = document.createElement('span'); 157 r.setAttribute('id', 'status.' + e.detail.tx); 158 r.setAttribute('class', 'statusBusy'); 159 r.innerHTML = 'status: pending'; 160 li.appendChild(l); 161 li.appendChild(r); 162 ls.appendChild(li); 163 watchTx(e.detail.tx, e.detail.serial); 164 }); 165 166 async function watchTx(hsh, i) { 167 const rcpt = await settings.provider.waitForTransaction(hsh); 168 const txRow = document.getElementById('status.' + hsh); 169 console.debug('rcpt', rcpt); 170 settings.minedAmount++; 171 if (rcpt.status == 1) { 172 if (i !== undefined) { 173 actUpdate(i, true); 174 } 175 txRow.setAttribute('class', 'statusOk'); 176 txRow.innerHTML = 'status: confirmed'; 177 setStatus('transaction ' + i + ' of ' + settings.mintAmount + ' confirmed', STATUS_OK); 178 } else { 179 if (i !== undefined) { 180 actUpdate(i, false); 181 } 182 txRow.setAttribute('class', 'statusError'); 183 txRow.innerHTML = 'status: failed'; 184 setStatus('transaction ' + i + ' of ' + settings.mintAmount + ' failed', STATUS_ERROR); 185 settings.failedAmount++; 186 } 187 if (settings.failedAmount > 0) { 188 setStatus('some transactions failed', STATUS_ERROR); 189 } 190 else { 191 setStatus('token minting successully completed', STATUS_OK); 192 } 193 } 194 195 function updateSettingsView(k, v) { 196 const dl = document.getElementById("settingsView"); 197 const dt = document.createElement("dt"); 198 dt.innerHTML = k; 199 dl.appendChild(dt); 200 const dd = document.createElement("dd"); 201 dd.innerHTML = v; 202 dl.appendChild(dd); 203 } 204 205 // Access webcam 206 async function initCamera() { 207 console.debug('starting camera'); 208 try { 209 const stream = await navigator.mediaDevices.getUserMedia(constraints); 210 handleSuccess(stream); 211 } catch (e) { 212 console.error(e); 213 } 214 } 215 216 // Success 217 function handleSuccess(stream) { 218 //const video = document.getElementById('video'); 219 window.stream = stream; 220 video.srcObject = stream; 221 } 222 223 // Draw image 224 var canvas; 225 var ctx; 226 227 // Load init 228 function live(handler) { 229 initCamera(); 230 canvas = document.getElementById('qr-canvas'); 231 ctx = canvas.getContext('2d', { willReadFrequently: true }); 232 setStatus('waiting for address', STATUS_INFO); 233 scan(handler); 234 } 235 236 async function addressHandler(addr) { 237 const e = new CustomEvent('uistate', { 238 detail: { 239 delta: STATE.SCAN_RESULT, 240 settings: settings, 241 }, 242 bubbles: true, 243 cancelable: true, 244 composed: false, 245 }); 246 window.dispatchEvent(e); 247 setStatus('confirm address...', STATUS_BUSY); 248 } 249 250 function scanHandler(addr, handler) { 251 try { 252 settings.recipient = checkAddress(addr); 253 } catch(e) { 254 console.error(e); 255 return false; 256 } 257 handler(settings.recipient); 258 return true; 259 } 260 261 function scan(handler) { 262 if (handler === undefined) { 263 handler = addressHandler; 264 } 265 ctx.drawImage(video, 0, 0, 400, 400); 266 const imageData = ctx.getImageData(0, 0, 400, 400).data; 267 const code = jsQR(imageData, 400, 400); 268 if (code) { 269 console.log("Found QR code", code); 270 if (scanHandler(code.data, handler)) { 271 return; 272 } 273 } 274 setTimeout(scan, 10, handler); 275 }