craft-nft

A standalone NFT implementation for real-world arts and crafts assets
Log | Files | Refs | README

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 }