mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-08 19:32:10 +01:00
221 lines
5.8 KiB
Text
221 lines
5.8 KiB
Text
|
<div id="camera-qr-scanner-modal-app" v-cloak class="only-for-js">
|
||
|
<div class="modal fade" data-backdrop="static" :id="modalId">
|
||
|
<div class="modal-dialog" role="document">
|
||
|
<div class="modal-content">
|
||
|
<div class="modal-header">
|
||
|
<h5 class="modal-title">
|
||
|
{{title}}
|
||
|
<span v-if="workload.length > 0">Animated QR detected: {{workload.length}} / {{workload[0].total}} scanned</span>
|
||
|
</h5>
|
||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close" v-on:click="close">
|
||
|
<span aria-hidden="true">×</span>
|
||
|
</button>
|
||
|
</div>
|
||
|
<div class="modal-body p-0" v-if="loaded" v-bind:class="{'alert-danger': errorMessage}">
|
||
|
<qrcode-drop-zone v-on:decode="onDecode" v-on:init="logErrors">
|
||
|
<qrcode-stream v-on:decode="onDecode" v-on:init="onInit" v-bind:camera="camera" v-bind:track="paint">
|
||
|
<div v-if="data || errorMessage" class="pending-action">
|
||
|
|
||
|
<div class="text-danger p-2" v-if="errorMessage">{{errorMessage}}</div>
|
||
|
<span class="text-muted text-truncate">{{data}}</span>
|
||
|
<div class="w-100 btn-group">
|
||
|
<button v-if="data" type="button" class="btn btn-primary" data-dismiss="modal" v-on:click="submitData">Submit</button>
|
||
|
<button type="button" class="btn btn-secondary" v-on:click="retry">Retry</button>
|
||
|
<button type="button" class="btn btn-danger" data-dismiss="modal" v-on:click="close">Cancel</button>
|
||
|
</div>
|
||
|
</div>
|
||
|
</qrcode-stream>
|
||
|
</qrcode-drop-zone>
|
||
|
<qrcode-capture v-if="noStreamApiSupport" v-on:decode="onDecode" v-bind:camera="camera"/>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
<style>
|
||
|
.pending-action {
|
||
|
position: absolute;
|
||
|
width: 100%;
|
||
|
height: 100%;
|
||
|
background-color: rgba(255, 255, 255, .8);
|
||
|
text-align: center;
|
||
|
font-size: 1.4rem;
|
||
|
padding: 10px;
|
||
|
word-wrap: break-word;
|
||
|
display: flex;
|
||
|
flex-flow: column nowrap;
|
||
|
justify-content: center;
|
||
|
}
|
||
|
</style>
|
||
|
|
||
|
|
||
|
<script>
|
||
|
function initCameraScanningApp(title, onDataSubmit, modalId)
|
||
|
{
|
||
|
|
||
|
new Vue(
|
||
|
{
|
||
|
el: '#camera-qr-scanner-modal-app',
|
||
|
data:
|
||
|
{
|
||
|
noStreamApiSupport: false,
|
||
|
loaded: false,
|
||
|
workload: [],
|
||
|
data: "",
|
||
|
title: title,
|
||
|
errorMessage: "",
|
||
|
modalId: modalId
|
||
|
},
|
||
|
mounted: function ()
|
||
|
{
|
||
|
var self = this;
|
||
|
$("#" + this.modalId)
|
||
|
.on("shown.bs.modal", function ()
|
||
|
{
|
||
|
self.loaded = true;
|
||
|
})
|
||
|
.on("hide.bs.modal", function ()
|
||
|
{
|
||
|
self.close();
|
||
|
});
|
||
|
},
|
||
|
computed:
|
||
|
{
|
||
|
camera: function ()
|
||
|
{
|
||
|
return this.data ? "off" : "auto";
|
||
|
}
|
||
|
},
|
||
|
methods:
|
||
|
{
|
||
|
retry: function ()
|
||
|
{
|
||
|
if (!this.data)
|
||
|
{
|
||
|
this.close();
|
||
|
this.$nextTick(function ()
|
||
|
{
|
||
|
this.loaded = true;
|
||
|
});
|
||
|
return;
|
||
|
}
|
||
|
this.data = "";
|
||
|
this.workload = [];
|
||
|
this.errorMessage = "";
|
||
|
},
|
||
|
close: function ()
|
||
|
{
|
||
|
this.loaded = false;
|
||
|
this.data = "";
|
||
|
this.workload = [];
|
||
|
this.errorMessage = "";
|
||
|
},
|
||
|
onDecode: function (content)
|
||
|
{
|
||
|
if (this.data)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
if (!content.toLowerCase().startsWith("ur:"))
|
||
|
{
|
||
|
this.data = content;
|
||
|
this.workload = [];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
const [index, total] = window.bcur.extractSingleWorkload(content);
|
||
|
if (this.workload.length > 0)
|
||
|
{
|
||
|
const currentTotal = this.workload[0].total;
|
||
|
if (total !== currentTotal)
|
||
|
{
|
||
|
this.workload = [];
|
||
|
}
|
||
|
}
|
||
|
if (!this.workload.find(i => i.index === index))
|
||
|
{
|
||
|
this.workload.push(
|
||
|
{
|
||
|
index,
|
||
|
total,
|
||
|
data: content,
|
||
|
});
|
||
|
if (this.workload.length === total)
|
||
|
{
|
||
|
this.data = window.bcur.decodeUR(this.workload.map(i => i.data));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
submitData: function ()
|
||
|
{
|
||
|
if (onDataSubmit)
|
||
|
{
|
||
|
onDataSubmit(this.data);
|
||
|
}
|
||
|
this.close();
|
||
|
},
|
||
|
logErrors: function (promise)
|
||
|
{
|
||
|
promise.catch(console.error)
|
||
|
},
|
||
|
paint: function (location, ctx)
|
||
|
{
|
||
|
ctx.fillStyle = '#137547';
|
||
|
[
|
||
|
location.topLeftFinderPattern,
|
||
|
location.topRightFinderPattern,
|
||
|
location.bottomLeftFinderPattern
|
||
|
].forEach((
|
||
|
{
|
||
|
x,
|
||
|
y
|
||
|
}) =>
|
||
|
{
|
||
|
ctx.fillRect(x - 5, y - 5, 10, 10);
|
||
|
})
|
||
|
},
|
||
|
onInit: function (promise)
|
||
|
{
|
||
|
var self = this;
|
||
|
promise.then(() =>
|
||
|
{
|
||
|
self.errorMessage = "";
|
||
|
})
|
||
|
.catch(error =>
|
||
|
{
|
||
|
if (error.name === 'StreamApiNotSupportedError')
|
||
|
{
|
||
|
self.noStreamApiSupport = true;
|
||
|
}
|
||
|
else if (error.name === 'NotAllowedError')
|
||
|
{
|
||
|
self.errorMessage = 'A permission to the camera is needed to scan the QR code.'
|
||
|
}
|
||
|
else if (error.name === 'NotFoundError')
|
||
|
{
|
||
|
self.errorMessage = 'A camera was not detected on your device.'
|
||
|
}
|
||
|
else if (error.name === 'NotSupportedError')
|
||
|
{
|
||
|
self.errorMessage = 'This page is served in non-secure context (HTTPS, localhost or file://)'
|
||
|
}
|
||
|
else if (error.name === 'NotReadableError')
|
||
|
{
|
||
|
self.errorMessage = 'Couldn\'t access your camera. Is it already in use?'
|
||
|
}
|
||
|
else if (error.name === 'OverconstrainedError')
|
||
|
{
|
||
|
self.errorMessage = 'Constraints don\'t match any installed camera.'
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
self.errorMessage = 'UNKNOWN ERROR: ' + error.message
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
</script>
|