Skip to content

Commit

Permalink
✨ Allow download files in batch QR codes page (#203)
Browse files Browse the repository at this point in the history
* 🐛 Fix 100% error to generate Bitly link

* 🥅 Guard possible null data from Bitly

* ✨ Allow download QR code files

* ♻️ Refactor download QR codes file

* ⚡️ Set compression for QR codes zip

* 🎨 Simplify .map logic
  • Loading branch information
nwingt authored Sep 25, 2024
1 parent c008291 commit a3c91e5
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 64 deletions.
61 changes: 7 additions & 54 deletions pages/affiliation-link.vue
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,6 @@

<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { type FileExtension } from '@likecoin/qr-code-styling'
import { AFFILIATION_CHANNEL_DEFAULT, AFFILIATION_CHANNELS } from '~/constant'
Expand Down Expand Up @@ -573,60 +572,14 @@ function shortenAllLinks () {
}
}
// TODO: UI for file types selection
const DOWNLOAD_QRCODE_FILE_TYPES: {
value: FileExtension
label: string
}[] = [
{ value: 'svg', label: 'SVG' },
{ value: 'png', label: 'PNG' }
]
async function downloadAllQRCodes () {
try {
const { default: QRCodeStyling } = await import('@likecoin/qr-code-styling')
const qrCodeResults = await Promise.all(tableRows.value.map(async (link) => {
const qrCode = new QRCodeStyling(getQRCodeOptions({ data: link.qrCodeUrl }))
const dataResults = await Promise.all(DOWNLOAD_QRCODE_FILE_TYPES.map(type => qrCode.getRawData(type.value)))
const filename = getQRCodeFilename(link.channelId)
return DOWNLOAD_QRCODE_FILE_TYPES.map(({ value: ext }, index) => {
const data = dataResults[index]
if (!data) {
throw new Error(`Failed to generate QR code for ${filename}.${ext}`)
}
return {
filename: `${filename}.${ext}`,
data
}
})
}))
const { default: JSZip } = await import('jszip')
const zip = new JSZip().folder(productId.value)
if (!zip) {
throw new Error('Failed to create zip file')
}
qrCodeResults.flat().forEach((qrCode) => {
zip.file(qrCode.filename, qrCode.data)
})
const { saveAs } = await import('file-saver')
await zip.generateAsync({ type: 'blob' }).then((content) => {
saveAs(content, `${productName.value || productId.value}_qrcodes.zip`)
})
} catch (error) {
// eslint-disable-next-line no-console
console.error(error)
toast.add({
icon: 'i-heroicons-exclamation-circle',
title: 'Failed to download QR codes',
timeout: 0,
color: 'red',
ui: {
title: 'text-red-400 dark:text-red-400'
}
})
}
const items = tableRows.value.map(link => ({
url: link.qrCodeUrl,
filename: getQRCodeFilename(link.channelId)
}))
await downloadQRCodes(items, {
zipFilename: `${productName.value || productId.value}_QR Codes`
})
}
function prefillChannelIdIfPossible () {
Expand Down
7 changes: 5 additions & 2 deletions pages/batch-bitly.vue
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,13 @@ async function shortenURL ({ url, key }: { url: string, key: string }) {
]
}
})
if (error) {
if (error.value) {
throw error.value
}
return data.value?.link
if (!data.value) {
throw new Error('No data returned from Bitly')
}
return data.value.link
} catch (error) {
// eslint-disable-next-line no-console
console.error(error)
Expand Down
28 changes: 21 additions & 7 deletions pages/batch-qrcode.vue
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,20 @@
:disabled="!csvInput"
@click="drawQRCodes"
/>
<UButton
v-else
label="Print"
size="lg"
variant="outline"
@click="handleClickPrint"
/>
<template v-else>
<UButton
label="Print"
size="lg"
variant="outline"
@click="handleClickPrint"
/>
<UButton
label="Download"
size="lg"
variant="outline"
@click="handleClickDownload"
/>
</template>
</nav>

<div class="flex flex-col items-center gap-[2cm] print:gap-0">
Expand Down Expand Up @@ -216,6 +223,13 @@ function handleClickPrint () {
window.print()
}
}
function handleClickDownload () {
downloadQRCodes(urlItems.value.map(item => ({
filename: item.key,
url: item.url
})))
}
</script>

<style>
Expand Down
67 changes: 66 additions & 1 deletion utils/qrcode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import {
DrawType,
DotType,
CornerSquareType,
CornerDotType
CornerDotType,
FileExtension
} from '@likecoin/qr-code-styling'

import NFCIcon from '~/assets/images/nfc.png'
Expand Down Expand Up @@ -73,3 +74,67 @@ export function getQRCodeOptions ({
data
}
}

const QRCODE_DOWNLOADABLE_FILE_TYPES: {
value: FileExtension
label: string
}[] = [
{ value: 'svg', label: 'SVG' },
{ value: 'png', label: 'PNG' }
]

export async function downloadQRCodes (
items: { filename: string, url: string }[],
options: { zipFilename?: string } = {}
) {
const zipFilename = `${options.zipFilename || 'QR Codes'}-${new Date().getTime()}.zip`
try {
const { default: QRCodeStyling } = await import('@likecoin/qr-code-styling')
const qrCodeResults = await Promise.all(items.map((item) => {
const qrCode = new QRCodeStyling(getQRCodeOptions({ data: item.url }))
return Promise.all(QRCODE_DOWNLOADABLE_FILE_TYPES.map(async (type) => {
const extension = type.value
const data = await qrCode.getRawData(extension)
if (!data) {
throw new Error(`Failed to generate QR code for ${item.filename}.${extension}`)
}
return {
filename: `${item.filename}.${extension}`,
data
}
}))
}))

const { default: JSZip } = await import('jszip')
const zip = new JSZip()
if (!zip) {
throw new Error('Failed to create zip file for QR codes')
}
qrCodeResults.flat().forEach((qrCode) => {
zip.file(qrCode.filename, qrCode.data)
})

const { saveAs } = await import('file-saver')
const zipFileBlob = await zip.generateAsync({
type: 'blob',
compression: 'DEFLATE',
compressionOptions: {
level: 9
}
})
saveAs(zipFileBlob, zipFilename)
} catch (error) {
// eslint-disable-next-line no-console
console.error(error)
const toast = useToast()
toast.add({
icon: 'i-heroicons-exclamation-circle',
title: `Failed to download QR codes file ${zipFilename}`,
timeout: 0,
color: 'red',
ui: {
title: 'text-red-400 dark:text-red-400'
}
})
}
}

0 comments on commit a3c91e5

Please sign in to comment.