Browse Source

Merge pull request #6905 from nocodb/nc-feat/add-barcode-image

Nc feat/add barcode image
pull/6921/head
Raju Udava 1 year ago committed by GitHub
parent
commit
0a30679c1b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 14
      packages/nc-gui/components/virtual-cell/QrCode.vue
  2. 8
      packages/nc-gui/components/virtual-cell/barcode/Barcode.vue
  3. 20
      packages/nc-gui/components/virtual-cell/barcode/JsBarcodeWrapper.vue
  4. 4
      packages/nc-gui/lang/en.json
  5. 68
      packages/nc-gui/utils/svgToPng.ts

14
packages/nc-gui/components/virtual-cell/QrCode.vue

@ -59,9 +59,21 @@ const { showEditNonEditableFieldWarning, showClearNonEditableFieldWarning } = us
@ok="handleModalOkClick" @ok="handleModalOkClick"
> >
<template #footer> <template #footer>
<div class="mr-4 overflow-scroll p-2" data-testid="nc-qr-code-large-value-label"> <div class="flex flex-row">
<div class="flex flex-row flex-grow mr-2 !overflow-y-auto py-2" data-testid="nc-qr-code-large-value-label">
{{ qrValue }} {{ qrValue }}
</div> </div>
<a v-if="showQrCode" :href="qrCodeLarge" :download="`${qrValue}.png`">
<NcTooltip>
<template #title>
{{ $t('labels.clickToDownload') }}
</template>
<NcButton size="small" type="secondary">
<GeneralIcon icon="download" class="w-4 h-4" />
</NcButton>
</NcTooltip>
</a>
</div>
</template> </template>
<img v-if="showQrCode" :src="qrCodeLarge" :alt="$t('title.qrCode')" /> <img v-if="showQrCode" :src="qrCodeLarge" :alt="$t('title.qrCode')" />
</a-modal> </a-modal>

8
packages/nc-gui/components/virtual-cell/barcode/Barcode.vue

@ -46,9 +46,15 @@ const rowHeight = inject(RowHeightInj, ref(undefined))
:footer="null" :footer="null"
@ok="handleModalOkClick" @ok="handleModalOkClick"
> >
<JsBarcodeWrapper v-if="showBarcode" :barcode-value="barcodeValue" :barcode-format="barcodeMeta.barcodeFormat" /> <JsBarcodeWrapper
v-if="showBarcode"
:barcode-value="barcodeValue"
:barcode-format="barcodeMeta.barcodeFormat"
show-download
/>
</a-modal> </a-modal>
<div <div
v-if="!tooManyCharsForBarcode"
class="flex ml-2 w-full items-center" class="flex ml-2 w-full items-center"
:class="{ :class="{
'justify-start': isExpandedFormOpen, 'justify-start': isExpandedFormOpen,

20
packages/nc-gui/components/virtual-cell/barcode/JsBarcodeWrapper.vue

@ -1,18 +1,20 @@
<script lang="ts" setup> <script lang="ts" setup>
import JsBarcode from 'jsbarcode' import JsBarcode from 'jsbarcode'
import { IsGalleryInj, onMounted } from '#imports' import { IsGalleryInj, onMounted } from '#imports'
import { downloadSvg as _downloadSvg } from '~/utils/svgToPng'
const props = defineProps({ const props = defineProps({
barcodeValue: { type: String, required: true }, barcodeValue: { type: String, required: true },
barcodeFormat: { type: String, required: true }, barcodeFormat: { type: String, required: true },
customStyle: { type: Object, required: false }, customStyle: { type: Object, required: false },
showDownload: { type: Boolean, required: false, default: false },
}) })
const emit = defineEmits(['onClickBarcode']) const emit = defineEmits(['onClickBarcode'])
const isGallery = inject(IsGalleryInj, ref(false)) const isGallery = inject(IsGalleryInj, ref(false))
const barcodeSvgRef = ref<HTMLElement>() const barcodeSvgRef = ref<SVGGraphicsElement>()
const errorForCurrentInput = ref(false) const errorForCurrentInput = ref(false)
const generate = () => { const generate = () => {
@ -34,6 +36,12 @@ const generate = () => {
} }
} }
const downloadSvg = () => {
if (!barcodeSvgRef.value) return
_downloadSvg(barcodeSvgRef.value, `${props.barcodeValue}.png`)
}
const onBarcodeClick = (ev: MouseEvent) => { const onBarcodeClick = (ev: MouseEvent) => {
if (isGallery.value) return if (isGallery.value) return
ev.stopPropagation() ev.stopPropagation()
@ -45,6 +53,7 @@ onMounted(generate)
</script> </script>
<template> <template>
<div class="relative">
<svg <svg
v-show="!errorForCurrentInput" v-show="!errorForCurrentInput"
ref="barcodeSvgRef" ref="barcodeSvgRef"
@ -56,4 +65,13 @@ onMounted(generate)
@click="onBarcodeClick" @click="onBarcodeClick"
></svg> ></svg>
<slot v-if="errorForCurrentInput" name="barcodeRenderError" /> <slot v-if="errorForCurrentInput" name="barcodeRenderError" />
<NcTooltip class="!absolute bottom-0 right-0">
<template #title>
{{ $t('labels.clickToDownload') }}
</template>
<NcButton v-if="props.showDownload" size="small" type="secondary" @click="downloadSvg">
<GeneralIcon icon="download" class="w-4 h-4" />
</NcButton>
</NcTooltip>
</div>
</template> </template>

4
packages/nc-gui/lang/en.json

@ -445,6 +445,7 @@
"inUI": "in UI Dashboard", "inUI": "in UI Dashboard",
"projectSettings": "Base Settings", "projectSettings": "Base Settings",
"clickToHide": "Click to hide", "clickToHide": "Click to hide",
"clickToDownload": "Click to download",
"forRole": "for role", "forRole": "for role",
"searchUsers": "Search Users", "searchUsers": "Search Users",
"superAdmin": "Super Admin", "superAdmin": "Super Admin",
@ -1013,7 +1014,8 @@
}, },
"nonEditableFields": { "nonEditableFields": {
"computedFieldUnableToClear": "Warning: Computed field - unable to clear text", "computedFieldUnableToClear": "Warning: Computed field - unable to clear text",
"qrFieldsCannotBeDirectlyChanged": "Warning: QR fields cannot be directly changed." "qrFieldsCannotBeDirectlyChanged": "Warning: QR fields cannot be directly changed.",
"barcodeFieldsCannotBeDirectlyChanged": "Warning: Barcode fields cannot be directly changed."
}, },
"duplicateProject": "Are you sure you want to duplicate the base?", "duplicateProject": "Are you sure you want to duplicate the base?",
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"

68
packages/nc-gui/utils/svgToPng.ts

@ -0,0 +1,68 @@
function copyStylesInline(destinationNode: any, sourceNode: any) {
const containerElements = ['svg', 'g']
for (let cd = 0; cd < destinationNode.childNodes.length; cd++) {
const child = destinationNode.childNodes[cd]
if (containerElements.includes(child.tagName)) {
copyStylesInline(child, sourceNode.childNodes[cd])
continue
}
const style = sourceNode.childNodes[cd].currentStyle || window.getComputedStyle(sourceNode.childNodes[cd])
if (style === 'undefined' || style == null) continue
for (let st = 0; st < style.length; st++) {
child.style.setProperty(style[st], style.getPropertyValue(style[st]))
}
}
}
function triggerDownload(imgURI: string, fileName: string) {
const evt = new MouseEvent('click', {
view: window,
bubbles: false,
cancelable: true,
})
const a = document.createElement('a')
a.setAttribute('download', fileName)
a.setAttribute('href', imgURI)
a.setAttribute('target', '_blank')
a.dispatchEvent(evt)
}
function downloadSvg(svg: SVGGraphicsElement, fileName: string) {
const copy = svg.cloneNode(true)
copyStylesInline(copy, svg)
const canvas = document.createElement('canvas')
const bbox = svg.getBBox()
canvas.width = bbox.width
canvas.height = bbox.height
const ctx = canvas.getContext('2d')!
ctx.clearRect(0, 0, bbox.width, bbox.height)
const data = new XMLSerializer().serializeToString(copy)
const DOMURL = window.URL || window.webkitURL || window
const img = new Image()
const svgBlob = new Blob([data], { type: 'image/svg+xml;charset=utf-8' })
const url = DOMURL.createObjectURL(svgBlob)
img.onload = function () {
ctx.drawImage(img, 0, 0)
DOMURL.revokeObjectURL(url)
if (typeof navigator !== 'undefined' && navigator.msSaveOrOpenBlob) {
const blob = canvas.msToBlob()
navigator.msSaveOrOpenBlob(blob, fileName)
} else {
const imgURI = canvas.toDataURL('image/png').replace('image/png', 'image/octet-stream')
triggerDownload(imgURI, fileName)
}
console.log(canvas)
// TODO: Somehow canvas dom element is getting deleted
// document.removeChild(canvas)
}
img.src = url
}
export { downloadSvg }
Loading…
Cancel
Save