feat: move qrcode scanner into reuseable component (#3567)
Co-authored-by: Tiago Vasconcelos <talvasconcelos@gmail.com>
This commit is contained in:
parent
4da651b74a
commit
6449276003
12 changed files with 111 additions and 65 deletions
2
lnbits/static/bundle-components.min.js
vendored
2
lnbits/static/bundle-components.min.js
vendored
File diff suppressed because one or more lines are too long
2
lnbits/static/bundle.min.js
vendored
2
lnbits/static/bundle.min.js
vendored
File diff suppressed because one or more lines are too long
55
lnbits/static/js/components/lnbits-qrcode-scanner.js
Normal file
55
lnbits/static/js/components/lnbits-qrcode-scanner.js
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
window.app.component('lnbits-qrcode-scanner', {
|
||||||
|
template: '#lnbits-qrcode-scanner',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
|
watch: {
|
||||||
|
'g.showScanner'(newVal) {
|
||||||
|
if (newVal === true) {
|
||||||
|
if (this.g.hasCamera === false) {
|
||||||
|
Quasar.Notify.create({
|
||||||
|
message: 'No camera found on this device.',
|
||||||
|
type: 'negative'
|
||||||
|
})
|
||||||
|
this.g.showScanner = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
detect(val) {
|
||||||
|
const rawValue = val[0].rawValue
|
||||||
|
console.log('Detected QR code value:', rawValue)
|
||||||
|
this.$emit('detect', rawValue)
|
||||||
|
this.g.showScanner = false
|
||||||
|
},
|
||||||
|
async onInitQR(promise) {
|
||||||
|
try {
|
||||||
|
await promise
|
||||||
|
} catch (error) {
|
||||||
|
const mapping = {
|
||||||
|
NotAllowedError: 'ERROR: you need to grant camera access permission',
|
||||||
|
NotFoundError: 'ERROR: no camera on this device',
|
||||||
|
NotSupportedError:
|
||||||
|
'ERROR: secure context required (HTTPS, localhost)',
|
||||||
|
NotReadableError: 'ERROR: is the camera already in use?',
|
||||||
|
OverconstrainedError: 'ERROR: installed cameras are not suitable',
|
||||||
|
StreamApiNotSupportedError:
|
||||||
|
'ERROR: Stream API is not supported in this browser',
|
||||||
|
InsecureContextError:
|
||||||
|
'ERROR: Camera access is only permitted in secure context. Use HTTPS or localhost rather than HTTP.'
|
||||||
|
}
|
||||||
|
const valid_error = Object.keys(mapping).filter(key => {
|
||||||
|
return error.name === key
|
||||||
|
})
|
||||||
|
const camera_error = valid_error
|
||||||
|
? mapping[valid_error]
|
||||||
|
: `ERROR: Camera error (${error.name})`
|
||||||
|
Quasar.Notify.create({
|
||||||
|
message: camera_error,
|
||||||
|
type: 'negative'
|
||||||
|
})
|
||||||
|
this.g.hasCamera = false
|
||||||
|
this.showScanner = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
@ -11,6 +11,7 @@ const localStore = (key, defaultValue) => {
|
||||||
window.g = Vue.reactive({
|
window.g = Vue.reactive({
|
||||||
isUserAuthorized: !!Quasar.Cookies.get('is_lnbits_user_authorized'),
|
isUserAuthorized: !!Quasar.Cookies.get('is_lnbits_user_authorized'),
|
||||||
offline: !navigator.onLine,
|
offline: !navigator.onLine,
|
||||||
|
hasCamera: false,
|
||||||
visibleDrawer: false,
|
visibleDrawer: false,
|
||||||
extensions: WINDOW_SETTINGS.EXTENSIONS,
|
extensions: WINDOW_SETTINGS.EXTENSIONS,
|
||||||
user: null,
|
user: null,
|
||||||
|
|
@ -18,6 +19,7 @@ window.g = Vue.reactive({
|
||||||
fiatBalance: 0,
|
fiatBalance: 0,
|
||||||
exchangeRate: 0,
|
exchangeRate: 0,
|
||||||
fiatTracking: false,
|
fiatTracking: false,
|
||||||
|
showScanner: false,
|
||||||
payments: [],
|
payments: [],
|
||||||
walletEventListeners: [],
|
walletEventListeners: [],
|
||||||
showNewWalletDialog: false,
|
showNewWalletDialog: false,
|
||||||
|
|
@ -52,7 +54,8 @@ window.g = Vue.reactive({
|
||||||
),
|
),
|
||||||
ads: WINDOW_SETTINGS.AD_SPACE.split(',').map(ad => ad.split(';')),
|
ads: WINDOW_SETTINGS.AD_SPACE.split(',').map(ad => ad.split(';')),
|
||||||
denomination: WINDOW_SETTINGS.LNBITS_DENOMINATION,
|
denomination: WINDOW_SETTINGS.LNBITS_DENOMINATION,
|
||||||
isSatsDenomination: WINDOW_SETTINGS.LNBITS_DENOMINATION == 'sats'
|
isSatsDenomination: WINDOW_SETTINGS.LNBITS_DENOMINATION == 'sats',
|
||||||
|
scanDetectCallback: null
|
||||||
})
|
})
|
||||||
|
|
||||||
window.dateFormat = 'YYYY-MM-DD HH:mm'
|
window.dateFormat = 'YYYY-MM-DD HH:mm'
|
||||||
|
|
@ -78,3 +81,9 @@ if (navigator.serviceWorker != null) {
|
||||||
console.log('Registered events at scope: ', registration.scope)
|
console.log('Registered events at scope: ', registration.scope)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) {
|
||||||
|
navigator.mediaDevices.enumerateDevices().then(devices => {
|
||||||
|
window.g.hasCamera = devices.some(device => device.kind === 'videoinput')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -97,12 +97,6 @@ window.PageWallet = {
|
||||||
msatoshiFormat(value) {
|
msatoshiFormat(value) {
|
||||||
return LNbits.utils.formatSat(value / 1000)
|
return LNbits.utils.formatSat(value / 1000)
|
||||||
},
|
},
|
||||||
closeCamera() {
|
|
||||||
this.parse.camera.show = false
|
|
||||||
},
|
|
||||||
showCamera() {
|
|
||||||
this.parse.camera.show = true
|
|
||||||
},
|
|
||||||
showReceiveDialog() {
|
showReceiveDialog() {
|
||||||
this.receive.show = true
|
this.receive.show = true
|
||||||
this.receive.status = 'pending'
|
this.receive.status = 'pending'
|
||||||
|
|
@ -205,35 +199,6 @@ window.PageWallet = {
|
||||||
this.receive.status = 'pending'
|
this.receive.status = 'pending'
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
async onInitQR(promise) {
|
|
||||||
try {
|
|
||||||
await promise
|
|
||||||
} catch (error) {
|
|
||||||
const mapping = {
|
|
||||||
NotAllowedError: 'ERROR: you need to grant camera access permission',
|
|
||||||
NotFoundError: 'ERROR: no camera on this device',
|
|
||||||
NotSupportedError:
|
|
||||||
'ERROR: secure context required (HTTPS, localhost)',
|
|
||||||
NotReadableError: 'ERROR: is the camera already in use?',
|
|
||||||
OverconstrainedError: 'ERROR: installed cameras are not suitable',
|
|
||||||
StreamApiNotSupportedError:
|
|
||||||
'ERROR: Stream API is not supported in this browser',
|
|
||||||
InsecureContextError:
|
|
||||||
'ERROR: Camera access is only permitted in secure context. Use HTTPS or localhost rather than HTTP.'
|
|
||||||
}
|
|
||||||
const valid_error = Object.keys(mapping).filter(key => {
|
|
||||||
return error.name === key
|
|
||||||
})
|
|
||||||
const camera_error = valid_error
|
|
||||||
? mapping[valid_error]
|
|
||||||
: `ERROR: Camera error (${error.name})`
|
|
||||||
this.parse.camera.show = false
|
|
||||||
Quasar.Notify.create({
|
|
||||||
message: camera_error,
|
|
||||||
type: 'negative'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
lnurlScan() {
|
lnurlScan() {
|
||||||
LNbits.api
|
LNbits.api
|
||||||
.request('POST', '/api/v1/lnurlscan', this.g.wallet.adminkey, {
|
.request('POST', '/api/v1/lnurlscan', this.g.wallet.adminkey, {
|
||||||
|
|
@ -281,8 +246,8 @@ window.PageWallet = {
|
||||||
LNbits.utils.notifyApiError(err)
|
LNbits.utils.notifyApiError(err)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
decodeQR(res) {
|
decodeQR(val) {
|
||||||
this.parse.data.request = res[0].rawValue
|
this.parse.data.request = val
|
||||||
this.decodeRequest()
|
this.decodeRequest()
|
||||||
this.parse.camera.show = false
|
this.parse.camera.show = false
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,15 @@
|
||||||
window.windowMixin = {
|
window.windowMixin = {
|
||||||
methods: {
|
methods: {
|
||||||
|
handleScan(val) {
|
||||||
|
if (this.g.scanDetectCallback) {
|
||||||
|
this.g.scanDetectCallback(val)
|
||||||
|
this.g.scanDetectCallback = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openScanDialog(scanDetectCallback) {
|
||||||
|
this.g.showScanner = true
|
||||||
|
this.g.scanDetectCallback = scanDetectCallback
|
||||||
|
},
|
||||||
openNewWalletDialog(walletType = 'lightning') {
|
openNewWalletDialog(walletType = 'lightning') {
|
||||||
this.g.newWalletType = walletType
|
this.g.newWalletType = walletType
|
||||||
this.g.showNewWalletDialog = true
|
this.g.showNewWalletDialog = true
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,7 @@
|
||||||
"js/components/lnbits-header-wallets.js",
|
"js/components/lnbits-header-wallets.js",
|
||||||
"js/components/lnbits-drawer.js",
|
"js/components/lnbits-drawer.js",
|
||||||
"js/components/lnbits-theme.js",
|
"js/components/lnbits-theme.js",
|
||||||
|
"js/components/lnbits-qrcode-scanner.js",
|
||||||
"js/components/lnbits-manage-extension-list.js",
|
"js/components/lnbits-manage-extension-list.js",
|
||||||
"js/components/lnbits-manage-wallet-list.js",
|
"js/components/lnbits-manage-wallet-list.js",
|
||||||
"js/components/lnbits-language-dropdown.js",
|
"js/components/lnbits-language-dropdown.js",
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
<div id="vue">
|
<div id="vue">
|
||||||
<q-layout view="hHh lpR lfr" v-cloak>
|
<q-layout view="hHh lpR lfr" v-cloak>
|
||||||
<lnbits-disclaimer></lnbits-disclaimer>
|
<lnbits-disclaimer></lnbits-disclaimer>
|
||||||
|
<lnbits-qrcode-scanner @detect="handleScan"></lnbits-qrcode-scanner>
|
||||||
<lnbits-theme></lnbits-theme>
|
<lnbits-theme></lnbits-theme>
|
||||||
<lnbits-header></lnbits-header>
|
<lnbits-header></lnbits-header>
|
||||||
{% block drawer %}
|
{% block drawer %}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ include('components/admin/extensions.vue') %} {%
|
||||||
include('components/admin/assets-config.vue') %} {%
|
include('components/admin/assets-config.vue') %} {%
|
||||||
include('components/admin/notifications.vue') %} {%
|
include('components/admin/notifications.vue') %} {%
|
||||||
include('components/admin/server.vue') %} {%
|
include('components/admin/server.vue') %} {%
|
||||||
|
include('components/lnbits-qrcode-scanner.vue') %} {%
|
||||||
include('components/lnbits-disclaimer.vue') %} {%
|
include('components/lnbits-disclaimer.vue') %} {%
|
||||||
include('components/lnbits-footer.vue') %} {%
|
include('components/lnbits-footer.vue') %} {%
|
||||||
include('components/lnbits-header.vue') %} {%
|
include('components/lnbits-header.vue') %} {%
|
||||||
|
|
|
||||||
22
lnbits/templates/components/lnbits-qrcode-scanner.vue
Normal file
22
lnbits/templates/components/lnbits-qrcode-scanner.vue
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
<template id="lnbits-qrcode-scanner">
|
||||||
|
<q-dialog v-model="g.showScanner" position="top">
|
||||||
|
<q-card class="q-pa-lg q-pt-xl">
|
||||||
|
<div class="text-center q-mb-lg">
|
||||||
|
<qrcode-stream
|
||||||
|
@detect="detect"
|
||||||
|
@camera-on="onInitQR"
|
||||||
|
class="rounded-borders"
|
||||||
|
></qrcode-stream>
|
||||||
|
</div>
|
||||||
|
<div class="row q-mt-lg">
|
||||||
|
<q-btn
|
||||||
|
@click="g.showScanner = false"
|
||||||
|
flat
|
||||||
|
color="grey"
|
||||||
|
class="q-ml-auto"
|
||||||
|
:label="$t('cancel')"
|
||||||
|
></q-btn>
|
||||||
|
</div>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
</template>
|
||||||
|
|
@ -156,14 +156,15 @@
|
||||||
icon="file_upload"
|
icon="file_upload"
|
||||||
></q-btn>
|
></q-btn>
|
||||||
<q-btn
|
<q-btn
|
||||||
|
v-if="g.hasCamera"
|
||||||
unelevated
|
unelevated
|
||||||
color="secondary"
|
|
||||||
icon="qr_code_scanner"
|
icon="qr_code_scanner"
|
||||||
|
color="secondary"
|
||||||
|
@click="openScanDialog(decodeQR)"
|
||||||
:disable="
|
:disable="
|
||||||
!this.g.wallet.canReceivePayments &&
|
!this.g.wallet.canReceivePayments &&
|
||||||
!this.g.wallet.canSendPayments
|
!this.g.wallet.canSendPayments
|
||||||
"
|
"
|
||||||
@click="showCamera"
|
|
||||||
>
|
>
|
||||||
<q-tooltip
|
<q-tooltip
|
||||||
><span v-text="$t('camera_tooltip')"></span
|
><span v-text="$t('camera_tooltip')"></span
|
||||||
|
|
@ -842,27 +843,6 @@
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
|
|
||||||
<q-dialog v-model="parse.camera.show" position="top">
|
|
||||||
<q-card class="q-pa-lg q-pt-xl">
|
|
||||||
<div class="text-center q-mb-lg">
|
|
||||||
<qrcode-stream
|
|
||||||
@detect="decodeQR"
|
|
||||||
@camera-on="onInitQR"
|
|
||||||
class="rounded-borders"
|
|
||||||
></qrcode-stream>
|
|
||||||
</div>
|
|
||||||
<div class="row q-mt-lg">
|
|
||||||
<q-btn
|
|
||||||
@click="closeCamera"
|
|
||||||
flat
|
|
||||||
color="grey"
|
|
||||||
class="q-ml-auto"
|
|
||||||
:label="$t('cancel')"
|
|
||||||
></q-btn>
|
|
||||||
</div>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="lt-md fixed-bottom left-0 right-0 bg-primary text-white shadow-2 z-top"
|
class="lt-md fixed-bottom left-0 right-0 bg-primary text-white shadow-2 z-top"
|
||||||
>
|
>
|
||||||
|
|
@ -877,12 +857,13 @@
|
||||||
<q-tab @click="showParseDialog" icon="file_upload" :label="$t('send')">
|
<q-tab @click="showParseDialog" icon="file_upload" :label="$t('send')">
|
||||||
</q-tab>
|
</q-tab>
|
||||||
</q-tabs>
|
</q-tabs>
|
||||||
|
|
||||||
<q-btn
|
<q-btn
|
||||||
|
@click="openScanDialog(decodeQR)"
|
||||||
round
|
round
|
||||||
size="35px"
|
|
||||||
unelevated
|
unelevated
|
||||||
|
size="35px"
|
||||||
icon="qr_code_scanner"
|
icon="qr_code_scanner"
|
||||||
@click="showCamera"
|
|
||||||
class="text-white bg-primary z-top vertical-bottom absolute-center absolute"
|
class="text-white bg-primary z-top vertical-bottom absolute-center absolute"
|
||||||
>
|
>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
|
|
|
||||||
|
|
@ -135,6 +135,7 @@
|
||||||
"js/components/lnbits-header-wallets.js",
|
"js/components/lnbits-header-wallets.js",
|
||||||
"js/components/lnbits-drawer.js",
|
"js/components/lnbits-drawer.js",
|
||||||
"js/components/lnbits-theme.js",
|
"js/components/lnbits-theme.js",
|
||||||
|
"js/components/lnbits-qrcode-scanner.js",
|
||||||
"js/components/lnbits-manage-extension-list.js",
|
"js/components/lnbits-manage-extension-list.js",
|
||||||
"js/components/lnbits-manage-wallet-list.js",
|
"js/components/lnbits-manage-wallet-list.js",
|
||||||
"js/components/lnbits-language-dropdown.js",
|
"js/components/lnbits-language-dropdown.js",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue