Now with QR code scanner!
This commit is contained in:
8
package-lock.json
generated
8
package-lock.json
generated
@@ -12,7 +12,8 @@
|
||||
"axios": "^1.6.7",
|
||||
"dayjs": "^1.11.10",
|
||||
"dexie": "^4.0.1-beta.14",
|
||||
"highlight.js": "11.9.0"
|
||||
"highlight.js": "11.9.0",
|
||||
"html5-qrcode": "^2.3.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.28.1",
|
||||
@@ -2928,6 +2929,11 @@
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html5-qrcode": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/html5-qrcode/-/html5-qrcode-2.3.8.tgz",
|
||||
"integrity": "sha512-jsr4vafJhwoLVEDW3n1KvPnCCXWaQfRng0/EEYk1vNcQGcG/htAdhJX0be8YyqMoSz7+hZvOZSTAepsabiuhiQ=="
|
||||
},
|
||||
"node_modules/human-signals": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"axios": "^1.6.7",
|
||||
"dayjs": "^1.11.10",
|
||||
"dexie": "^4.0.1-beta.14",
|
||||
"highlight.js": "11.9.0"
|
||||
"highlight.js": "11.9.0",
|
||||
"html5-qrcode": "^2.3.8"
|
||||
}
|
||||
}
|
||||
|
||||
395
src/lib/element_qr_scanner.svelte
Normal file
395
src/lib/element_qr_scanner.svelte
Normal file
@@ -0,0 +1,395 @@
|
||||
<script lang="ts">
|
||||
// *** Import Svelte core
|
||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte';
|
||||
// import 'html5-qrcode';
|
||||
import {Html5Qrcode, Html5QrcodeScannerState, Html5QrcodeSupportedFormats} from 'html5-qrcode';
|
||||
|
||||
// *** Import Aether core variables and functions
|
||||
|
||||
// *** Import Aether core components
|
||||
// import Element_input from './element_input.svelte';
|
||||
// import ae from '/element_input.svelte';
|
||||
// import Input_element from '/element_input.svelte';
|
||||
|
||||
// *** Import Aether module variables and functions
|
||||
|
||||
// *** Import Aether module components
|
||||
|
||||
// *** Export/Exposed variables and functions for component
|
||||
export let start_qr_scanner: boolean = true;
|
||||
export let show_pause_btn: boolean = false; // pause and resume buttons
|
||||
export let show_qr_manual_text_entry_option: boolean = false;
|
||||
export let show_qr_manual_badge_id_entry_option: boolean = false;
|
||||
export let show_qr_scan_result: boolean = true;
|
||||
|
||||
export let qr_fps = 15;
|
||||
export let qr_viewfinder_width = 275; // 275 seems good... Need to not let the this be larger than the container which changes based on the width of the screen/window.
|
||||
export let qr_facing_mode = 'environment'; // environment, user, { exact: 'environment'}, { exact: 'user'}
|
||||
|
||||
// *** Set initial variables
|
||||
let scanning_status: string = 'not_started';
|
||||
let qr_scan_result: null|string = null;
|
||||
let qr_found_text: null|string = null;
|
||||
let qr_entered_text: null|string = null;
|
||||
let qr_entered_badge_id: null|string = null;
|
||||
let show_qr_manual_entry: null|boolean = null;
|
||||
let disable_submit_badge_id_btn: boolean = true;
|
||||
|
||||
// let max_results: number = 50;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let html5_qr_code: null|string = null;
|
||||
// let html5_qr_code = new Html5Qrcode(
|
||||
// 'qr_scanner_viewfinder', { formatsToSupport: [ Html5QrcodeSupportedFormats.QR_CODE ] }
|
||||
// );
|
||||
|
||||
// let qr_scan_cfg = { fps: 10, qrbox: 400 }; // default was 250 and using 300 when 600px
|
||||
let qr_scan_cfg = { fps: qr_fps, qrbox: qr_viewfinder_width }; // 275 seems good... Need to not let the this be larger than the container which changes based on the width of the screen/window.
|
||||
|
||||
// let mounted = false;
|
||||
|
||||
|
||||
onMount(() => {
|
||||
console.log('** Element Mounted: ** QR Scanner');
|
||||
|
||||
// NOTE: This can only be done after the page has fully loaded. At least that is what my tests have shown so far. -2022-12-02
|
||||
if (start_qr_scanner) {
|
||||
handle_start_qr_scanning();
|
||||
}
|
||||
|
||||
navigator.mediaDevices.getUserMedia({video: true})
|
||||
.then(successCallback, errorCallback);
|
||||
});
|
||||
|
||||
|
||||
onDestroy(() => {
|
||||
console.log('** Element Destroyed: ** QR Scanner');
|
||||
|
||||
handle_stop_qr_scanning();
|
||||
});
|
||||
|
||||
|
||||
var successCallback = function(error) {
|
||||
console.log('Camera access allowed');
|
||||
|
||||
dispatch('qr_camera', {
|
||||
status: 'allowed',
|
||||
});
|
||||
};
|
||||
var errorCallback = function(error) {
|
||||
if (error.name == 'NotAllowedError') {
|
||||
console.log('Camera access not allowed!');
|
||||
|
||||
dispatch('qr_camera', {
|
||||
status: 'denied',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// $: if (mounted && start_qr_scanner) {
|
||||
// console.log('START QR SCANNING');
|
||||
// handle_start_qr_scanning();
|
||||
// } else if (mounted && !start_qr_scanner) {
|
||||
// console.log('STOP QR SCANNING');
|
||||
// handle_stop_qr_scanning();
|
||||
// }
|
||||
|
||||
|
||||
function handle_start_qr_scanning() {
|
||||
console.log('*** handle_start_qr_scanning() ***');
|
||||
|
||||
qr_scan_result = null;
|
||||
qr_found_text = null;
|
||||
|
||||
if (html5_qr_code) {
|
||||
console.log('html5_qr_code object found. Clearing and creating new Html5Qrcode...');
|
||||
// html5_qr_code.clear();
|
||||
document.getElementById('qr_scanner_viewfinder').classList.remove('d_none');
|
||||
|
||||
html5_qr_code = new Html5Qrcode(
|
||||
'qr_scanner_viewfinder', { formatsToSupport: [ Html5QrcodeSupportedFormats.QR_CODE ] }
|
||||
);
|
||||
} else {
|
||||
console.log('html5_qr_code not found. Creating new Html5Qrcode...');
|
||||
html5_qr_code = new Html5Qrcode(
|
||||
'qr_scanner_viewfinder', { formatsToSupport: [ Html5QrcodeSupportedFormats.QR_CODE ] }
|
||||
);
|
||||
}
|
||||
|
||||
// if (html5_qr_code.getState() == Html5QrcodeScannerState.NOT_STARTED) {
|
||||
// // console.log('Scanner is not started');
|
||||
// } else {
|
||||
// console.log('Scanner is already started');
|
||||
// return;
|
||||
// }
|
||||
|
||||
html5_qr_code.start({ facingMode: qr_facing_mode }, qr_scan_cfg, handle_qr_scan_success, handle_qr_scan_error).then((ignore) => {
|
||||
console.log('Scanning has started');
|
||||
scanning_status = 'scanning';
|
||||
return true;
|
||||
}).catch((err) => {
|
||||
console.log('There was an error while trying to start the QR scanner');
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function handle_pause_qr_scanning() {
|
||||
if (html5_qr_code && html5_qr_code.getState() != Html5QrcodeScannerState.SCANNING) {
|
||||
console.log('Scanner is not scanning!');
|
||||
return;
|
||||
}
|
||||
|
||||
html5_qr_code.pause();
|
||||
scanning_status = 'paused';
|
||||
}
|
||||
|
||||
function handle_resume_qr_scanning() {
|
||||
if (html5_qr_code && html5_qr_code.getState() != Html5QrcodeScannerState.PAUSED) {
|
||||
console.log('Scanner is not paused!');
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('qr_scanner_viewfinder').classList.remove('d_none');
|
||||
html5_qr_code.resume();
|
||||
scanning_status = 'scanning';
|
||||
}
|
||||
|
||||
|
||||
function handle_stop_qr_scanning() {
|
||||
if (html5_qr_code && html5_qr_code.getState() == Html5QrcodeScannerState.NOT_STARTED) {
|
||||
console.log('Scanner is not started');
|
||||
return;
|
||||
}
|
||||
|
||||
// qr_scan_result = null;
|
||||
// qr_found_text = null;
|
||||
|
||||
// html5_qr_code.pause();
|
||||
|
||||
html5_qr_code.stop().then((ignore) => {
|
||||
console.log('Scanning has stopped');
|
||||
document.getElementById('qr_scanner_viewfinder').classList.add('d_none');
|
||||
scanning_status = 'not_started';
|
||||
}).then((ignore) => {
|
||||
// html5_qr_code = null;
|
||||
html5_qr_code.clear();
|
||||
}).catch((err) => {
|
||||
console.log('There was an error while trying to stop the scanning');
|
||||
return false;
|
||||
});
|
||||
|
||||
// html5_qr_code = null;
|
||||
}
|
||||
|
||||
|
||||
// Callback function for QrcodeSuccessCallback (decodedText: string, result: Html5QrcodeResult)
|
||||
function handle_qr_scan_success(decoded_text, decoded_result) {
|
||||
console.log('*** handle_qr_scan_success() ***');
|
||||
|
||||
console.log(`QR scanned = ${decoded_text}`, decoded_result);
|
||||
|
||||
qr_scan_result = decoded_text; // NOTE: decoded_result is not currently used by html5-qrcode
|
||||
qr_found_text = decoded_text;
|
||||
|
||||
dispatch('qr_scan_result', {
|
||||
result: qr_scan_result, // This text will need to be parsed to get more info.
|
||||
text: qr_found_text, // This text will need to be parsed to get more info.
|
||||
entry_method: 'QR',
|
||||
});
|
||||
|
||||
// handle_pause_qr_scanning();
|
||||
|
||||
handle_stop_qr_scanning();
|
||||
|
||||
// html5_qr_code.stop().then((ignore) => {
|
||||
// console.log('Scanning has stopped');
|
||||
// scanning_status = 'not_started';
|
||||
// document.getElementById('qr_scanner_viewfinder').classList.add('d_none');
|
||||
|
||||
// qr_scan_result = decoded_text;
|
||||
// qr_found_text = decoded_text;
|
||||
|
||||
// dispatch('qr_scan_result', {
|
||||
// result: qr_scan_result,
|
||||
// entry_method: 'QR',
|
||||
// });
|
||||
|
||||
// // qr_scan_result = null;
|
||||
// // qr_found_text = null;
|
||||
// return true;
|
||||
// }).catch((err) => {
|
||||
// console.log('There was an error while trying to stop the scanning');
|
||||
// return false;
|
||||
// });
|
||||
}
|
||||
|
||||
|
||||
// Callback function for QrcodeErrorCallback (errorMessage: string, error: Html5QrcodeError)
|
||||
// NOTE: Most of the time this is normal and not an actual error. It just did not find something to scan.
|
||||
function handle_qr_scan_error(qr_error_message, qr_code_error) {
|
||||
// console.log('*** handle_qr_scan_error() ***');
|
||||
|
||||
if (qr_code_error.type) {
|
||||
console.log(`Error scanning code = ${qr_error_message}`, qr_code_error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$: if ( qr_entered_badge_id && qr_entered_badge_id.length == 11 ) {
|
||||
disable_submit_badge_id_btn = false;
|
||||
} else {
|
||||
disable_submit_badge_id_btn = true;
|
||||
}
|
||||
|
||||
|
||||
function handle_qr_manual_entry() {
|
||||
console.log('*** handle_qr_manual_entry() ***');
|
||||
|
||||
if (qr_entered_text) {
|
||||
console.log(`QR entered text = ${qr_entered_text}`);
|
||||
} else if (qr_entered_badge_id) {
|
||||
console.log(`QR entered badge ID = ${qr_entered_badge_id}`);
|
||||
qr_entered_text = `OBJ:ot:event_badge,oi:${qr_entered_badge_id}`;
|
||||
console.log(`Parse to proper QR badge ID = ${qr_entered_text}`);
|
||||
}
|
||||
|
||||
// html5_qr_code.stop().then((ignore) => {
|
||||
// console.log('Scanning has stopped');
|
||||
// document.getElementById('qr_scanner_viewfinder').classList.add('d_none');
|
||||
// }).catch((err) => {
|
||||
// console.log('There was an error while trying to stop the scanning');
|
||||
// });
|
||||
|
||||
qr_scan_result = qr_entered_text;
|
||||
|
||||
dispatch('qr_scan_result', {
|
||||
result: qr_scan_result,
|
||||
entry_method: 'manual',
|
||||
});
|
||||
|
||||
qr_scan_result = null;
|
||||
qr_entered_text = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<section
|
||||
class="ae_element qr_scanner border border-2 border-primary p-2 space-y-2 flex flex-col gap-1 justify-center items-center"
|
||||
class:not_started={scanning_status == 'not_started'}
|
||||
class:paused={scanning_status == 'paused'}
|
||||
class:scanning={scanning_status == 'scanning'}
|
||||
>
|
||||
|
||||
<!-- <header>
|
||||
<h2>QR Scanner</h2>
|
||||
</header> -->
|
||||
|
||||
<!-- <fieldset class=""> -->
|
||||
<!-- <legend class="d_none">QR Scanner:</legend> -->
|
||||
|
||||
<div
|
||||
class="ae_container qr_scanning_container flex flex-col gap-1 justify-center"
|
||||
>
|
||||
<div
|
||||
class="ae_options"
|
||||
>
|
||||
{#if scanning_status == 'not_started' }
|
||||
<button on:click={handle_start_qr_scanning} class="btn btn-lg variant-soft-primary btn_start"><span class="fas fa-qrcode"></span> Start Scanning</button>
|
||||
{:else if scanning_status == 'paused' && show_pause_btn}
|
||||
<button on:click={handle_resume_qr_scanning} class="btn btn-lg variant-soft-primary btn_resume"><span class="fas fa-play"></span> Resume</button>
|
||||
<span>Scanning paused</span>
|
||||
{:else if scanning_status == 'scanning'}
|
||||
<button on:click={handle_stop_qr_scanning} class="btn btn-lg variant-soft-secondary btn_stop"><span class="fas fa-stop-circle"></span> Stop</button>
|
||||
{#if show_pause_btn}
|
||||
<button on:click={handle_pause_qr_scanning} class="btn btn-lg variant-soft-secondary btn_pause"><span class="fas fa-pause-circle"></span> Pause</button>
|
||||
{/if}
|
||||
<span>Scanning for QR code...</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div id="qr_scanner_viewfinder" class="qr_scanner_viewfinder grow flex flex-col justify-center items-center" style=""></div> <!-- width: 600px -->
|
||||
</div>
|
||||
|
||||
{#if show_qr_manual_text_entry_option}
|
||||
<div class="ae_container qr_manual_entry text_entry">
|
||||
{#if show_qr_manual_entry}
|
||||
<label for="entered_text" class="">Enter text</label>
|
||||
<input type="text" name="entered_text" id="entered_text" bind:value="{qr_entered_text}">
|
||||
<button on:click={handle_qr_manual_entry} class="btn btn-md variant-soft-warning"><span class="fas fa-paper-plane"></span> Submit Text</button>
|
||||
|
||||
|
||||
<div class="search_by_text">
|
||||
<input type='text' placeholder="Name or Email" label="Name or Email" value={search_query_str} focus={true} on:oninput={handle_oninput_search_query_str} />
|
||||
</div>
|
||||
|
||||
{:else}
|
||||
<button on:click={() => show_qr_manual_entry=true} class="btn btn-md variant-soft-warning"><span class="fas fa-keyboard"></span> Enter Text</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if show_qr_manual_badge_id_entry_option}
|
||||
<div class="ae_container qr_manual_entry badge_id_entry">
|
||||
{#if show_qr_manual_entry}
|
||||
<form on:submit|preventDefault={() => handle_qr_manual_entry} class="form-floating">
|
||||
|
||||
<!-- <label for="entered_badge_id" class="">Enter badge ID</label>
|
||||
<input type="text" name="entered_badge_id" id="entered_badge_id" bind:value="{qr_entered_badge_id}"> -->
|
||||
|
||||
<input
|
||||
bind:value="{qr_entered_badge_id}"
|
||||
type="text"
|
||||
name="entered_badge_id"
|
||||
id="entered_badge_id"
|
||||
required
|
||||
placeholder="Enter Badge ID"
|
||||
class="input"
|
||||
/>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
on:click={handle_qr_manual_entry}
|
||||
disabled={disable_submit_badge_id_btn}
|
||||
class="btn btn-md variant-soft-primary"
|
||||
class:btn_default={disable_submit_badge_id_btn}
|
||||
class:btn_primary={!disable_submit_badge_id_btn}
|
||||
>
|
||||
<span class="fas fa-paper-plane"></span> Submit Badge ID
|
||||
</button>
|
||||
|
||||
</form>
|
||||
{:else}
|
||||
<button on:click={() => show_qr_manual_entry=true} class="btn btn-md variant-soft-secondary"><span class="fas fa-keyboard"></span> Enter Badge ID</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if show_qr_scan_result && qr_scan_result}
|
||||
<div class="ae_container qr_scan_result">
|
||||
<span class="label">Raw Result:</span>
|
||||
<span id="qr_scan_result_value" class="value">{qr_scan_result}</span>
|
||||
</div>
|
||||
{/if}
|
||||
<!-- </fieldset> -->
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
<style>
|
||||
.not_started {
|
||||
background-color: hsla(0, 100%, 75%, 0.3);
|
||||
border-color: hsla(0, 100%, 75%, 0.6);
|
||||
}
|
||||
.paused {
|
||||
background-color: hsla(60, 100%, 75%, 0.3);
|
||||
border-color: hsla(60, 100%, 75%, 0.6);
|
||||
}
|
||||
.scanning {
|
||||
background-color: hsla(120, 100%, 75%, 0.3);
|
||||
border-color: hsla(120, 100%, 75%, 0.6);
|
||||
}
|
||||
</style>
|
||||
@@ -21,6 +21,7 @@ import { clipboard, FileDropzone, getModalStore, localStorageStore, ProgressRadi
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
import Element_data_store from '$lib/element_data_store.svelte';
|
||||
import Element_qr_scanner from '$lib/element_qr_scanner.svelte';
|
||||
|
||||
const store_current_tab: Writable<string> = localStorageStore('ae_events_leads_current_tab', 'start');
|
||||
console.log(`ae_events_leads exhibit +page.svelte [slug] store_current_tab:`, $store_current_tab);
|
||||
@@ -45,10 +46,14 @@ $events_slct.exhibit_id = param_slug_event_exhibit_id;
|
||||
console.log('Selected Event Exhibit ID:', $events_slct.exhibit_id);
|
||||
$events_trigger = 'load__event_exhibit_obj';
|
||||
|
||||
$store_current_tab = 'start';
|
||||
|
||||
let license_submit_results: Promise<any>|key_val;
|
||||
|
||||
if ($events_loc.leads.auth_exhibit_li && $events_loc.leads.auth_exhibit_li[$events_slct.exhibit_id]) {
|
||||
console.log('Already logged in.');
|
||||
} else {
|
||||
console.log('Not logged in.');
|
||||
$store_current_tab = 'start';
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
console.log('Events Leads Exhibit [slug]: +page.svelte');
|
||||
@@ -838,6 +843,7 @@ function send_init_confirm_email({to_email}) {
|
||||
console.log('Show/Hide Details');
|
||||
$events_loc.show_details = !$events_loc.show_details;
|
||||
}}
|
||||
class="btn variant-soft-primary w-48"
|
||||
>
|
||||
<span class="fas fa-eye mx-1"></span>
|
||||
Show/Hide Details
|
||||
@@ -851,7 +857,7 @@ function send_init_confirm_email({to_email}) {
|
||||
$events_loc.leads.auth_exhibit_li[$events_slct.exhibit_id] = null;
|
||||
}}
|
||||
title="Log out"
|
||||
class="btn variant-soft-primary w-40"
|
||||
class="btn variant-soft-primary w-48"
|
||||
>
|
||||
<span class="fas fa-sign-out-alt mx-1"></span>
|
||||
Log Out
|
||||
@@ -866,7 +872,12 @@ function send_init_confirm_email({to_email}) {
|
||||
{:else if $store_current_tab == 'add_scan'}
|
||||
|
||||
{#if $events_loc?.leads.auth_exhibit_li && $events_loc.leads.auth_exhibit_li[$events_slct.exhibit_id]}
|
||||
<div>Add/Scan: Logged in. Nothing here yet</div>
|
||||
<div>Add/Scan: Logged in. Nothing here yet</div>
|
||||
|
||||
<Element_qr_scanner show_qr_manual_badge_id_entry_option={true} />
|
||||
|
||||
|
||||
|
||||
{:else}
|
||||
<div>Add/Scan: Not logged in? Nothing here yet.</div>
|
||||
{/if} <!-- $events_loc?.leads.auth_exhibit_li && $events_loc.leads.auth_exhibit_li[$events_slct.exhibit_id] -->
|
||||
|
||||
Reference in New Issue
Block a user