Browse Source

refactor: camera ready

fee_issues
Eneko Illarramendi 5 years ago
parent
commit
75d97ddfc1
  1. 6
      lnbits/__init__.py
  2. 2
      lnbits/core/crud.py
  3. 7
      lnbits/core/static/js/index.js
  4. 13
      lnbits/core/static/js/lnurl.js
  5. 43
      lnbits/core/static/js/wallet.js
  6. 35
      lnbits/core/templates/core/index.html
  7. 22
      lnbits/core/templates/core/lnurl.html
  8. 61
      lnbits/core/templates/core/wallet.html
  9. 2
      lnbits/core/views/generic.py
  10. 20
      lnbits/core/views/lnurl.py
  11. 3
      lnbits/static/css/base.css
  12. BIN
      lnbits/static/images/note.jpg
  13. 6
      lnbits/static/scss/base.scss
  14. 1
      lnbits/static/vendor/vue-qrcode-reader@2.1.1/vue-qrcode-reader.min.css
  15. 1
      lnbits/static/vendor/vue-qrcode-reader@2.1.1/vue-qrcode-reader.min.js

6
lnbits/__init__.py

@ -3,6 +3,8 @@ import importlib
from flask import Flask from flask import Flask
from flask_assets import Environment, Bundle from flask_assets import Environment, Bundle
from flask_compress import Compress from flask_compress import Compress
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
from flask_talisman import Talisman from flask_talisman import Talisman
from os import getenv from os import getenv
@ -19,6 +21,7 @@ valid_extensions = [ext for ext in ExtensionManager().extensions if ext.is_valid
# ----------------------- # -----------------------
Compress(app) Compress(app)
Limiter(app, key_func=get_remote_address, default_limits=["1 per second"])
Talisman( Talisman(
app, app,
force_https=getenv("LNBITS_WITH_ONION", 0) == 0, force_https=getenv("LNBITS_WITH_ONION", 0) == 0,
@ -27,6 +30,7 @@ Talisman(
"'self'", "'self'",
"'unsafe-eval'", "'unsafe-eval'",
"'unsafe-inline'", "'unsafe-inline'",
"blob:",
"cdnjs.cloudflare.com", "cdnjs.cloudflare.com",
"code.ionicframework.com", "code.ionicframework.com",
"code.jquery.com", "code.jquery.com",
@ -78,5 +82,5 @@ def init():
init_databases() init_databases()
if __name__ == '__main__': if __name__ == "__main__":
app.run() app.run()

2
lnbits/core/crud.py

@ -63,7 +63,7 @@ def update_user_extension(*, user_id: str, extension: str, active: int) -> None:
# ------- # -------
def create_wallet(*, user_id: str, wallet_name: Optional[str]) -> Wallet: def create_wallet(*, user_id: str, wallet_name: Optional[str] = None) -> Wallet:
with open_db() as db: with open_db() as db:
wallet_id = uuid4().hex wallet_id = uuid4().hex
db.execute( db.execute(

7
lnbits/core/static/js/index.js

@ -9,6 +9,13 @@ new Vue({
methods: { methods: {
createWallet: function () { createWallet: function () {
LNbits.href.createWallet(this.walletName); LNbits.href.createWallet(this.walletName);
},
processing: function () {
this.$q.notify({
timeout: 0,
message: 'Processing...',
icon: null
});
} }
} }
}); });

13
lnbits/core/static/js/lnurl.js

@ -1,13 +0,0 @@
new Vue({
el: '#vue',
mixins: [windowMixin],
methods: {
notify: function () {
this.$q.notify({
timeout: 0,
message: 'Processing...',
icon: null
});
}
}
});

43
lnbits/core/static/js/wallet.js

@ -1,4 +1,5 @@
Vue.component(VueQrcode.name, VueQrcode); Vue.component(VueQrcode.name, VueQrcode);
Vue.use(VueQrcodeReader);
function generateChart(canvas, payments) { function generateChart(canvas, payments) {
@ -119,6 +120,10 @@ new Vue({
bolt11: '' bolt11: ''
} }
}, },
sendCamera: {
show: false,
camera: 'auto'
},
payments: [], payments: [],
paymentsTable: { paymentsTable: {
columns: [ columns: [
@ -147,15 +152,26 @@ new Vue({
}, },
canPay: function () { canPay: function () {
if (!this.send.invoice) return false; if (!this.send.invoice) return false;
return this.send.invoice.sat < this.balance; return this.send.invoice.sat <= this.balance;
}, },
pendingPaymentsExist: function () { pendingPaymentsExist: function () {
return (this.payments) return (this.payments)
? _.where(this.payments, {pending: 1}).length > 0 ? _.where(this.payments, {pending: 1}).length > 0
: false; : false;
},
paymentsFiltered: function () {
return this.payments.filter(function (obj) {
return obj.isPaid;
});
} }
}, },
methods: { methods: {
closeCamera: function () {
this.sendCamera.show = false;
},
showCamera: function () {
this.sendCamera.show = true;
},
showChart: function () { showChart: function () {
this.paymentsChart.show = true; this.paymentsChart.show = true;
this.$nextTick(function () { this.$nextTick(function () {
@ -180,7 +196,8 @@ new Vue({
invoice: null, invoice: null,
data: { data: {
bolt11: '' bolt11: ''
} },
paymentChecker: null
}; };
}, },
closeReceiveDialog: function () { closeReceiveDialog: function () {
@ -189,6 +206,13 @@ new Vue({
clearInterval(checker); clearInterval(checker);
}, 10000); }, 10000);
}, },
closeSendDialog: function () {
this.sendCamera.show = false;
var checker = this.send.paymentChecker;
setTimeout(function () {
clearInterval(checker);
}, 1000);
},
createInvoice: function () { createInvoice: function () {
var self = this; var self = this;
this.receive.status = 'loading'; this.receive.status = 'loading';
@ -212,6 +236,11 @@ new Vue({
self.receive.status = 'pending'; self.receive.status = 'pending';
}); });
}, },
decodeQR: function (res) {
this.send.data.bolt11 = res;
this.decodeInvoice();
this.sendCamera.show = false;
},
decodeInvoice: function () { decodeInvoice: function () {
try { try {
var invoice = decode(this.send.data.bolt11); var invoice = decode(this.send.data.bolt11);
@ -259,11 +288,11 @@ new Vue({
LNbits.utils.notifyApiError(error); LNbits.utils.notifyApiError(error);
}); });
paymentChecker = setInterval(function () { self.send.paymentChecker = setInterval(function () {
LNbits.api.getPayment(self.w.wallet, self.send.invoice.hash).then(function (response) { LNbits.api.getPayment(self.w.wallet, self.send.invoice.hash).then(function (response) {
if (response.data.paid) { if (response.data.paid) {
this.send.show = false; self.send.show = false;
clearInterval(paymentChecker); clearInterval(self.send.paymentChecker);
dismissPaymentMsg(); dismissPaymentMsg();
self.fetchPayments(); self.fetchPayments();
} }
@ -298,6 +327,8 @@ new Vue({
}, },
created: function () { created: function () {
this.fetchPayments(); this.fetchPayments();
this.checkPendingPayments(); setTimeout(function () {
this.checkPendingPayments();
}, 1100);
} }
}); });

35
lnbits/core/templates/core/index.html

@ -15,22 +15,29 @@
<div class="row q-col-gutter-md justify-between"> <div class="row q-col-gutter-md justify-between">
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md"> <div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
{% block call_to_action %} <q-card>
<q-card> <q-card-section>
<q-card-section> {% if lnurl %}
<q-form class="q-gutter-md">
<q-input filled dense
v-model="walletName"
label="Name your LNbits wallet *"
></q-input>
<q-btn unelevated <q-btn unelevated
color="deep-purple" color="deep-purple"
:disable="walletName == ''" @click="notify"
@click="createWallet">Add a new wallet</q-btn> type="a" href="{{ url_for('core.lnurlwallet', lightning=lnurl) }}">
</q-form> Press to claim bitcoin
</q-card-section> </q-btn>
</q-card> {% else %}
{% endblock %} <q-form class="q-gutter-md">
<q-input filled dense
v-model="walletName"
label="Name your LNbits wallet *"
></q-input>
<q-btn unelevated
color="deep-purple"
:disable="walletName == ''"
@click="createWallet">Add a new wallet</q-btn>
</q-form>
{% endif %}
</q-card-section>
</q-card>
<q-card> <q-card>
<q-card-section> <q-card-section>

22
lnbits/core/templates/core/lnurl.html

@ -1,22 +0,0 @@
{% extends "core/index.html" %}
{% block scripts %}
{% assets filters='rjsmin', output='__bundle__/core/lnurl.js',
'core/js/lnurl.js' %}
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
{% endassets %}
{% endblock %}
{% block call_to_action %}
<q-card>
<q-card-section>
<q-btn unelevated
color="deep-purple"
@click="notify"
type="a" href="{{ url_for('core.lnurlwallet', lightning=lnurl) }}">
Press to claim bitcoin
</q-btn>
</q-card-section>
</q-card>
{% endblock %}

61
lnbits/core/templates/core/wallet.html

@ -3,6 +3,10 @@
{% from "macros.jinja" import window_vars with context %} {% from "macros.jinja" import window_vars with context %}
{% block styles %}
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='vendor/vue-qrcode-reader@2.1.1/vue-qrcode-reader.min.css') }}">
{% endblock %}
{% block scripts %} {% block scripts %}
{{ window_vars(user, wallet) }} {{ window_vars(user, wallet) }}
{% assets filters='rjsmin', output='__bundle__/core/chart.js', {% assets filters='rjsmin', output='__bundle__/core/chart.js',
@ -14,6 +18,7 @@
'vendor/bolt11/utils.js', 'vendor/bolt11/utils.js',
'vendor/bolt11/decoder.js', 'vendor/bolt11/decoder.js',
'vendor/vue-qrcode@1.0.2/vue-qrcode.min.js', 'vendor/vue-qrcode@1.0.2/vue-qrcode.min.js',
'vendor/vue-qrcode-reader@2.1.1/vue-qrcode-reader.min.js',
'core/js/wallet.js' %} 'core/js/wallet.js' %}
<script type="text/javascript" src="{{ ASSET_URL }}"></script> <script type="text/javascript" src="{{ ASSET_URL }}"></script>
{% endassets %} {% endassets %}
@ -59,7 +64,7 @@
</div> </div>
</div> </div>
<q-table dense flat <q-table dense flat
:data="payments" :data="paymentsFiltered"
row-key="payhash" row-key="payhash"
:columns="paymentsTable.columns" :columns="paymentsTable.columns"
:pagination.sync="paymentsTable.pagination"> :pagination.sync="paymentsTable.pagination">
@ -74,7 +79,7 @@
</q-tr> </q-tr>
</template> </template>
<template v-slot:body="props"> <template v-slot:body="props">
<q-tr :props="props" v-if="props.row.isPaid"> <q-tr :props="props">
<q-td auto-width class="lnbits__q-table__icon-td"> <q-td auto-width class="lnbits__q-table__icon-td">
<q-icon v-if="props.row.isPaid" size="14px" <q-icon v-if="props.row.isPaid" size="14px"
:name="(props.row.sat < 0) ? 'call_made' : 'call_received'" :name="(props.row.sat < 0) ? 'call_made' : 'call_received'"
@ -198,7 +203,7 @@
<div v-else> <div v-else>
<div class="text-center q-mb-md"> <div class="text-center q-mb-md">
<a :href="'lightning:' + receive.paymentReq"> <a :href="'lightning:' + receive.paymentReq">
<qrcode :value="receive.paymentReq" :options="{width: 340}"></qrcode> <qrcode :value="receive.paymentReq" :options="{width: 340}" class="rounded-borders"></qrcode>
</a> </a>
</div> </div>
<!--<q-separator class="q-my-md"></q-separator> <!--<q-separator class="q-my-md"></q-separator>
@ -213,28 +218,38 @@
</q-card> </q-card>
</q-dialog> </q-dialog>
<q-dialog v-model="send.show" position="top"> <q-dialog v-model="send.show" position="top" @hide="closeSendDialog">
<q-card class="q-pa-lg" style="width: 500px"> <q-card class="q-pa-lg" style="width: 500px">
<q-form v-if="!send.invoice" class="q-gutter-md"> <div v-if="!send.invoice">
<q-input filled dense <q-form v-if="!sendCamera.show" class="q-gutter-md">
v-model="send.data.bolt11" <q-input filled dense
type="textarea" v-model="send.data.bolt11"
label="Paste an invoice *" type="textarea"
> label="Paste an invoice *"
<template v-slot:after> >
<q-btn round dense flat icon="photo_camera"> <template v-slot:after>
<q-tooltip>Use camera to scan an invoice</q-tooltip> <q-btn round dense flat icon="photo_camera" @click="showCamera">
</q-btn> <q-tooltip>Use camera to scan an invoice</q-tooltip>
</template> </q-btn>
</q-input> </template>
<div class="row justify-between"> </q-input>
<q-btn unelevated <div class="row justify-between">
color="deep-purple" <q-btn unelevated
:disable="send.data.bolt11 == ''" color="deep-purple"
@click="decodeInvoice">Read invoice</q-btn> :disable="send.data.bolt11 == ''"
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn> @click="decodeInvoice">Read invoice</q-btn>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
</div>
</q-form>
<div v-else>
<q-responsive :ratio="1">
<qrcode-stream @decode="decodeQR" class="rounded-borders"></qrcode-stream>
</q-responsive>
<div class="row justify-between q-mt-md">
<q-btn @click="closeCamera" flat color="grey" class="q-ml-auto">Cancel</q-btn>
</div>
</div> </div>
</q-form> </div>
<div v-else> <div v-else>
{% raw %} {% raw %}
<h6 class="q-my-none">{{ send.invoice.fsat }} sat</h6> <h6 class="q-my-none">{{ send.invoice.fsat }} sat</h6>

2
lnbits/core/views/generic.py

@ -21,7 +21,7 @@ def favicon():
@core_app.route("/") @core_app.route("/")
def home(): def home():
return render_template("core/index.html") return render_template("core/index.html", lnurl=request.args.get("lightning", None))
@core_app.route("/extensions") @core_app.route("/extensions")

20
lnbits/core/views/lnurl.py

@ -1,29 +1,23 @@
import json
import requests import requests
from flask import redirect, render_template, request, url_for from flask import abort, redirect, request, url_for
from lnurl import LnurlWithdrawResponse, handle as handle_lnurl from lnurl import LnurlWithdrawResponse, handle as handle_lnurl
from lnurl.exceptions import LnurlException from lnurl.exceptions import LnurlException
from time import sleep from time import sleep
from lnbits.core import core_app from lnbits.core import core_app
from lnbits.helpers import Status
from lnbits.settings import WALLET from lnbits.settings import WALLET
from ..crud import create_account, get_user, create_wallet, create_payment from ..crud import create_account, get_user, create_wallet, create_payment
@core_app.route("/lnurl")
def lnurl():
lnurl = request.args.get("lightning")
return render_template("core/lnurl.html", lnurl=lnurl)
@core_app.route("/lnurlwallet") @core_app.route("/lnurlwallet")
def lnurlwallet(): def lnurlwallet():
try: try:
withdraw_res = handle_lnurl(request.args.get("lightning"), response_class=LnurlWithdrawResponse) withdraw_res = handle_lnurl(request.args.get("lightning"), response_class=LnurlWithdrawResponse)
except LnurlException: except LnurlException:
return redirect(url_for("core.home")) abort(Status.INTERNAL_SERVER_ERROR, "Could not process withdraw LNURL.")
_, payhash, payment_request = WALLET.create_invoice(withdraw_res.max_sats, "LNbits LNURL funding") _, payhash, payment_request = WALLET.create_invoice(withdraw_res.max_sats, "LNbits LNURL funding")
@ -33,8 +27,7 @@ def lnurlwallet():
) )
if not r.ok: if not r.ok:
return redirect(url_for("core.home")) # TODO: custom error abort(Status.INTERNAL_SERVER_ERROR, "Could not process withdraw LNURL.")
data = json.loads(r.text)
for i in range(10): for i in range(10):
r = WALLET.get_invoice_status(payhash).raw_response r = WALLET.get_invoice_status(payhash).raw_response
@ -46,10 +39,7 @@ def lnurlwallet():
user = get_user(create_account().id) user = get_user(create_account().id)
wallet = create_wallet(user_id=user.id) wallet = create_wallet(user_id=user.id)
create_payment( # TODO: not pending? create_payment( # TODO: not pending?
wallet_id=wallet.id, wallet_id=wallet.id, payhash=payhash, amount=withdraw_res.max_sats * 1000, memo="LNbits lnurl funding"
payhash=payhash,
amount=withdraw_res.max_sats * 1000,
memo="LNbits lnurl funding",
) )
return redirect(url_for("core.wallet", usr=user.id, wal=wallet.id)) return redirect(url_for("core.wallet", usr=user.id, wal=wallet.id))

3
lnbits/static/css/base.css

@ -31,3 +31,6 @@
.lnbits-drawer__q-list .q-item.q-item--active { .lnbits-drawer__q-list .q-item.q-item--active {
color: inherit; color: inherit;
font-weight: bold; } font-weight: bold; }
video {
border-radius: 3px; }

BIN
lnbits/static/images/note.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

6
lnbits/static/scss/base.scss

@ -51,3 +51,9 @@ body.body--dark .q-field--error {
font-weight: bold; font-weight: bold;
} }
} }
// QR video
video {
border-radius: 3px;
}

1
lnbits/static/vendor/vue-qrcode-reader@2.1.1/vue-qrcode-reader.min.css

@ -0,0 +1 @@
.wrapper[data-v-1f90552a]{position:relative;z-index:0;width:100%;height:100%}.overlay[data-v-1f90552a],.tracking-layer[data-v-1f90552a]{position:absolute;width:100%;height:100%;top:0;left:0}.camera[data-v-1f90552a],.pause-frame[data-v-1f90552a]{display:block;object-fit:cover;width:100%;height:100%}

1
lnbits/static/vendor/vue-qrcode-reader@2.1.1/vue-qrcode-reader.min.js

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save