128 lines
4.1 KiB
Vue

<template>
<div ref="svg" :class="border ? 'border' : ''" v-bind="$attrs" v-html="svg"></div>
<button
v-if="download && svg"
type="button"
class="btn btn-outline-primary btn-sm m-3"
:disabled="generating"
@click="generateDownload"
>
<Spinner v-if="generating" />
<template v-else>
<Icon v="download" />
<T>user.qr.download</T>
</template>
</button>
</template>
<script>
import { mapState } from 'pinia';
import { makeId } from '../src/helpers.ts';
import { useMainStore } from '../store/index.ts';
const primary = '#C71585';
const primaryDark = '#810e57';
const primaryLight = '#ff63cb';
const light = '#F8F9FA';
const dark = '#212529';
export default {
props: {
url: { required: true },
text1: { },
text2: { },
download: { type: Boolean },
border: { type: Boolean },
},
data() {
return {
id: makeId(6),
QRCodeStyling: undefined,
html2canvas: undefined,
svg: undefined,
generating: false,
};
},
mounted() {
this.render();
},
methods: {
async render() {
this.QRCodeStyling = this.QRCodeStyling || (await import('qr-code-styling')).default;
const qrCode = new this.QRCodeStyling({
type: 'svg',
data: this.url,
image: `/logo/logo-${this.darkMode ? 'light' : 'primary'}.svg`,
dotsOptions: {
color: this.darkMode ? light : primary,
type: 'extra-rounded',
},
cornersSquareOptions: {
type: 'extra-rounded',
color: this.darkMode ? primaryLight : primaryDark,
},
cornersDotOptionsHelper: {
color: this.darkMode ? primaryLight : primaryDark,
},
backgroundOptions: {
color: this.darkMode ? dark : light,
},
margin: 8,
});
this.svg = (await (await qrCode.getRawData('svg')).text())
.replace('<?xml version="1.0" standalone="no"?>', '')
.replace('width="300" height="300"', `viewBox="0 0 300 ${this.text1 ? 370 : 300}" shape-rendering="geometricPrecision"`)
.replace(/id="/g, `id="${this.id}-`)
.replace(/'#/g, `'#${this.id}-`)
;
if (this.text1) {
this.svg = this.svg.replace('</svg>', `
<text x="150" y="316" dominant-baseline="middle" text-anchor="middle" stroke="none"
fill="${this.darkMode ? primaryLight : primary}" font-size="1.5em">${this.text1}</text>
<text x="150" y="346" dominant-baseline="middle" text-anchor="middle" stroke="none"
fill="${this.darkMode ? primaryLight : primary}" font-size="1.8em" font-weight="bold">${this.text2}</text>
</svg>`)
.replace('<rect x="0" y="0" height="300" width="300"', '<rect x="0" y="0" height="370" width="300"');
}
},
async generateDownload() {
if (this.generating) {
return;
}
this.generating = true;
this.html2canvas = this.html2canvas || (await import('html2canvas')).default;
this.html2canvas(this.$refs.svg).then((canvas) => {
const dlLink = document.createElement('a');
dlLink.download = 'image';
dlLink.href = canvas.toDataURL('image/png');
dlLink.dataset.downloadurl = ['image/png', dlLink.download, dlLink.href].join(':');
document.body.appendChild(dlLink);
dlLink.click();
document.body.removeChild(dlLink);
this.generating = false;
});
},
},
computed: {
...mapState(useMainStore, [
'darkMode',
]),
},
watch: {
darkMode() {
this.render();
},
url() {
this.render();
},
},
};
</script>