feat: move qrcode scanner into reuseable component (#3567)

Co-authored-by: Tiago Vasconcelos <talvasconcelos@gmail.com>
This commit is contained in:
dni ⚡
2025-11-28 17:58:50 +01:00
committed by GitHub
co-authored by Tiago Vasconcelos
parent 4da651b74a
commit 6449276003
12 changed files with 111 additions and 65 deletions
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
@@ -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
}
}
}
})
+10 -1
View File
@@ -11,6 +11,7 @@ const localStore = (key, defaultValue) => {
window.g = Vue.reactive({
isUserAuthorized: !!Quasar.Cookies.get('is_lnbits_user_authorized'),
offline: !navigator.onLine,
hasCamera: false,
visibleDrawer: false,
extensions: WINDOW_SETTINGS.EXTENSIONS,
user: null,
@@ -18,6 +19,7 @@ window.g = Vue.reactive({
fiatBalance: 0,
exchangeRate: 0,
fiatTracking: false,
showScanner: false,
payments: [],
walletEventListeners: [],
showNewWalletDialog: false,
@@ -52,7 +54,8 @@ window.g = Vue.reactive({
),
ads: WINDOW_SETTINGS.AD_SPACE.split(',').map(ad => ad.split(';')),
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'
@@ -78,3 +81,9 @@ if (navigator.serviceWorker != null) {
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')
})
}
+2 -37
View File
@@ -97,12 +97,6 @@ window.PageWallet = {
msatoshiFormat(value) {
return LNbits.utils.formatSat(value / 1000)
},
closeCamera() {
this.parse.camera.show = false
},
showCamera() {
this.parse.camera.show = true
},
showReceiveDialog() {
this.receive.show = true
this.receive.status = 'pending'
@@ -205,35 +199,6 @@ window.PageWallet = {
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() {
LNbits.api
.request('POST', '/api/v1/lnurlscan', this.g.wallet.adminkey, {
@@ -281,8 +246,8 @@ window.PageWallet = {
LNbits.utils.notifyApiError(err)
})
},
decodeQR(res) {
this.parse.data.request = res[0].rawValue
decodeQR(val) {
this.parse.data.request = val
this.decodeRequest()
this.parse.camera.show = false
},
+10
View File
@@ -1,5 +1,15 @@
window.windowMixin = {
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') {
this.g.newWalletType = walletType
this.g.showNewWalletDialog = true
+1
View File
@@ -83,6 +83,7 @@
"js/components/lnbits-header-wallets.js",
"js/components/lnbits-drawer.js",
"js/components/lnbits-theme.js",
"js/components/lnbits-qrcode-scanner.js",
"js/components/lnbits-manage-extension-list.js",
"js/components/lnbits-manage-wallet-list.js",
"js/components/lnbits-language-dropdown.js",