diff --git a/packages/nc-gui/components/virtual-cell/QrCode.vue b/packages/nc-gui/components/virtual-cell/QrCode.vue index 9ce66087e3..9dba2538ff 100644 --- a/packages/nc-gui/components/virtual-cell/QrCode.vue +++ b/packages/nc-gui/components/virtual-cell/QrCode.vue @@ -59,8 +59,20 @@ const { showEditNonEditableFieldWarning, showClearNonEditableFieldWarning } = us @ok="handleModalOkClick" > diff --git a/packages/nc-gui/components/virtual-cell/barcode/Barcode.vue b/packages/nc-gui/components/virtual-cell/barcode/Barcode.vue index 502a8076d9..2c27509ecf 100644 --- a/packages/nc-gui/components/virtual-cell/barcode/Barcode.vue +++ b/packages/nc-gui/components/virtual-cell/barcode/Barcode.vue @@ -46,9 +46,15 @@ const rowHeight = inject(RowHeightInj, ref(undefined)) :footer="null" @ok="handleModalOkClick" > - +
import JsBarcode from 'jsbarcode' import { IsGalleryInj, onMounted } from '#imports' +import { downloadSvg as _downloadSvg } from '~/utils/svgToPng' const props = defineProps({ barcodeValue: { type: String, required: true }, barcodeFormat: { type: String, required: true }, customStyle: { type: Object, required: false }, + showDownload: { type: Boolean, required: false, default: false }, }) const emit = defineEmits(['onClickBarcode']) const isGallery = inject(IsGalleryInj, ref(false)) -const barcodeSvgRef = ref() +const barcodeSvgRef = ref() const errorForCurrentInput = ref(false) const generate = () => { @@ -34,6 +36,12 @@ const generate = () => { } } +const downloadSvg = () => { + if (!barcodeSvgRef.value) return + + _downloadSvg(barcodeSvgRef.value, `${props.barcodeValue}.png`) +} + const onBarcodeClick = (ev: MouseEvent) => { if (isGallery.value) return ev.stopPropagation() @@ -45,15 +53,25 @@ onMounted(generate) diff --git a/packages/nc-gui/lang/en.json b/packages/nc-gui/lang/en.json index 712a084886..d91045d5fe 100644 --- a/packages/nc-gui/lang/en.json +++ b/packages/nc-gui/lang/en.json @@ -445,6 +445,7 @@ "inUI": "in UI Dashboard", "projectSettings": "Base Settings", "clickToHide": "Click to hide", + "clickToDownload": "Click to download", "forRole": "for role", "searchUsers": "Search Users", "superAdmin": "Super Admin", @@ -1013,7 +1014,8 @@ }, "nonEditableFields": { "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?", "duplicateTable": "Are you sure you want to duplicate the table?" diff --git a/packages/nc-gui/utils/svgToPng.ts b/packages/nc-gui/utils/svgToPng.ts new file mode 100644 index 0000000000..a98a1c2862 --- /dev/null +++ b/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 }