Add a customizable logo to QR (#1955)

* add QR_LOGO variable
* add logo to images folder
* css for overlaying logo on qr
* qr url on settings
* add qr url to global env
* add QR url input on theme tab in adminUI
* new component added
* use svg
* remove white border around logo in the QR

---------

Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
Co-authored-by: dni  <office@dnilabs.com>
This commit is contained in:
Tiago Vasconcelos 2023-10-09 20:11:11 +01:00 committed by GitHub
parent f286a91253
commit c2a11fa0bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 99 additions and 18 deletions

View File

@ -1,6 +1,6 @@
// update cache version every time there is a new deployment // update cache version every time there is a new deployment
// so the service worker reinitializes the cache // so the service worker reinitializes the cache
const CACHE_VERSION = 58 const CACHE_VERSION = 61
const CURRENT_CACHE = `lnbits-${CACHE_VERSION}-` const CURRENT_CACHE = `lnbits-${CACHE_VERSION}-`
const getApiKey = request => { const getApiKey = request => {

View File

@ -36,7 +36,7 @@
</div> </div>
<br /> <br />
<div class="row q-col-gutter-md"> <div class="row q-col-gutter-md">
<div class="col-12 col-md-6"> <div class="col-12 col-md-4">
<p>Default Wallet Name</p> <p>Default Wallet Name</p>
<q-input <q-input
filled filled
@ -46,7 +46,7 @@
></q-input> ></q-input>
<br /> <br />
</div> </div>
<div class="col-12 col-md-6"> <div class="col-12 col-md-4">
<p>Denomination</p> <p>Denomination</p>
<q-input <q-input
filled filled
@ -57,6 +57,17 @@
></q-input> ></q-input>
<br /> <br />
</div> </div>
<div class="col-12 col-md-4">
<p>QR code logo</p>
<q-input
filled
type="text"
v-model="formData.lnbits_qr_logo"
label="https://example.com/image.svg"
hint="URL to logo image in QR code"
></q-input>
<br />
</div>
</div> </div>
<div class="row q-col-gutter-md"> <div class="row q-col-gutter-md">
<div class="col-12 col-md-6"> <div class="col-12 col-md-6">

View File

@ -240,11 +240,9 @@
> >
<a :href="'lightning:' + props.row.bolt11"> <a :href="'lightning:' + props.row.bolt11">
<q-responsive :ratio="1" class="q-mx-xl"> <q-responsive :ratio="1" class="q-mx-xl">
<qrcode <lnbits-qrcode
:value="'lightning:' + props.row.bolt11.toUpperCase()" :value="'lightning:' + props.row.bolt11.toUpperCase()"
:options="{width: 340}" ></lnbits-qrcode>
class="rounded-borders"
></qrcode>
</q-responsive> </q-responsive>
</a> </a>
</div> </div>
@ -330,10 +328,9 @@
<q-card> <q-card>
<q-card-section class="text-center"> <q-card-section class="text-center">
<a href="lightning:{{wallet.lnurlwithdraw_full}}"> <a href="lightning:{{wallet.lnurlwithdraw_full}}">
<qrcode <lnbits-qrcode
value="lightning:{{wallet.lnurlwithdraw_full}}" :value="lightning:{{wallet.lnurlwithdraw_full}}"
:options="{width:240}" ></lnbits-qrcode>
></qrcode>
</a> </a>
<p v-text="$t('drain_funds_desc')"></p> <p v-text="$t('drain_funds_desc')"></p>
</q-card-section> </q-card-section>
@ -352,7 +349,6 @@
<p v-text="$t('export_to_phone_desc')"></p> <p v-text="$t('export_to_phone_desc')"></p>
<qrcode <qrcode
:value="'{{request.base_url}}' +'wallet?usr={{user.id}}&wal={{wallet.id}}'" :value="'{{request.base_url}}' +'wallet?usr={{user.id}}&wal={{wallet.id}}'"
:options="{width:240}"
></qrcode> ></qrcode>
</q-card-section> </q-card-section>
</q-card> </q-card>
@ -552,11 +548,9 @@
<div class="text-center q-mb-lg"> <div class="text-center q-mb-lg">
<a :href="'lightning:' + receive.paymentReq"> <a :href="'lightning:' + receive.paymentReq">
<q-responsive :ratio="1" class="q-mx-xl"> <q-responsive :ratio="1" class="q-mx-xl">
<qrcode <lnbits-qrcode
:value="'lightning:' + receive.paymentReq.toUpperCase()" :value="'lightning:' + receive.paymentReq.toUpperCase()"
:options="{width: 340}" ></lnbits-qrcode>
class="rounded-borders"
></qrcode>
</q-responsive> </q-responsive>
</a> </a>
</div> </div>

View File

@ -49,6 +49,7 @@ def template_renderer(additional_folders: Optional[List] = None) -> Jinja2Templa
t.env.globals["SITE_TAGLINE"] = settings.lnbits_site_tagline t.env.globals["SITE_TAGLINE"] = settings.lnbits_site_tagline
t.env.globals["SITE_DESCRIPTION"] = settings.lnbits_site_description t.env.globals["SITE_DESCRIPTION"] = settings.lnbits_site_description
t.env.globals["LNBITS_THEME_OPTIONS"] = settings.lnbits_theme_options t.env.globals["LNBITS_THEME_OPTIONS"] = settings.lnbits_theme_options
t.env.globals["LNBITS_QR_LOGO"] = settings.lnbits_qr_logo
t.env.globals["LNBITS_VERSION"] = settings.version t.env.globals["LNBITS_VERSION"] = settings.version
t.env.globals["LNBITS_ADMIN_UI"] = settings.lnbits_admin_ui t.env.globals["LNBITS_ADMIN_UI"] = settings.lnbits_admin_ui
t.env.globals["LNBITS_NODE_UI"] = ( t.env.globals["LNBITS_NODE_UI"] = (

View File

@ -85,6 +85,7 @@ class ThemesSettings(LNbitsSettings):
lnbits_ad_space_enabled: bool = Field(default=False) lnbits_ad_space_enabled: bool = Field(default=False)
lnbits_allowed_currencies: List[str] = Field(default=[]) lnbits_allowed_currencies: List[str] = Field(default=[])
lnbits_default_accounting_currency: Optional[str] = Field(default=None) lnbits_default_accounting_currency: Optional[str] = Field(default=None)
lnbits_qr_logo: str = Field(default="/static/images/logos/lnbits.svg")
class OpsSettings(LNbitsSettings): class OpsSettings(LNbitsSettings):

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -535,3 +535,21 @@ video {
.q-card code { .q-card code {
overflow-wrap: break-word; overflow-wrap: break-word;
} }
.qrcode__wrapper > canvas {
position: relative;
min-width: 100%;
min-height: 100%;
}
.qrcode__image {
width: 15%;
height: 15%;
overflow: hidden;
left: 50%;
overflow: hidden;
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
padding: 0.3rem;
}

View File

@ -0,0 +1,19 @@
<svg width="900" height="900" viewBox="0 0 900 900" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_i_10_29)">
<circle cx="450" cy="450" r="450" fill="#1E1E1E"/>
</g>
<path d="M387.802 476.423L281.613 730.887L584.54 389.763H459.418L619.113 168.387H407.558L305.485 476.423H387.802Z" fill="#FF1FE1"/>
<defs>
<filter id="filter0_i_10_29" x="-0.1" y="-0.1" width="900.1" height="900.1" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feMorphology radius="0.6" operator="dilate" in="SourceAlpha" result="effect1_innerShadow_10_29"/>
<feOffset dx="-1" dy="-0.4"/>
<feGaussianBlur stdDeviation="0.35"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 0.121569 0 0 0 0 0.882353 0 0 0 1 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_10_29"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -356,6 +356,24 @@ Vue.component('lnbits-lnurlpay-success-action', {
} }
}) })
Vue.component('lnbits-qrcode', {
mixins: [windowMixin],
props: ['value'],
components: {[VueQrcode.name]: VueQrcode},
data() {
return {
logo: LNBITS_QR_LOGO
}
},
template: `
<div class="qrcode__wrapper">
<qrcode :value="value"
:options="{errorCorrectionLevel: 'Q'}" class="rounded-borders"></qrcode>
<img class="qrcode__image" :src="logo" alt="..." />
</div>
`
})
Vue.component('lnbits-notifications-btn', { Vue.component('lnbits-notifications-btn', {
mixins: [windowMixin], mixins: [windowMixin],
props: ['pubkey'], props: ['pubkey'],

View File

@ -213,3 +213,21 @@ video {
overflow-wrap: break-word; overflow-wrap: break-word;
} }
} }
.qrcode__wrapper > canvas {
position: relative;
min-width: 100%;
min-height: 100%;
}
.qrcode__image {
width: 15%;
height: 15%;
overflow: hidden;
left: 50%;
overflow: hidden;
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
padding: 0.3rem;
}

View File

@ -298,6 +298,7 @@
const themes = {{ LNBITS_THEME_OPTIONS | tojson }} const themes = {{ LNBITS_THEME_OPTIONS | tojson }}
const LNBITS_DENOMINATION = {{ LNBITS_DENOMINATION | tojson }} const LNBITS_DENOMINATION = {{ LNBITS_DENOMINATION | tojson }}
const LNBITS_VERSION = {{ LNBITS_VERSION | tojson }} const LNBITS_VERSION = {{ LNBITS_VERSION | tojson }}
const LNBITS_QR_LOGO = {{ LNBITS_QR_LOGO | tojson }}
if (themes && themes.length) { if (themes && themes.length) {
window.allowedThemes = themes.map(str => str.trim()) window.allowedThemes = themes.map(str => str.trim())
} }