From 052ddebc3413533a81f108ada2e7383e9bc2989f Mon Sep 17 00:00:00 2001 From: sharevb Date: Sun, 16 Jun 2024 12:34:41 +0200 Subject: [PATCH] feat(TextareaCopyable/FormatTransformer): add download button prop --- src/components/FormatTransformer.vue | 25 ++++++++- src/components/TextareaCopyable.vue | 20 ++++++- src/composable/downloadBase64.ts | 84 +++++++++++++++++++++------- 3 files changed, 106 insertions(+), 23 deletions(-) diff --git a/src/components/FormatTransformer.vue b/src/components/FormatTransformer.vue index 677fc25a..a5d8c1d4 100644 --- a/src/components/FormatTransformer.vue +++ b/src/components/FormatTransformer.vue @@ -1,7 +1,9 @@ diff --git a/src/components/TextareaCopyable.vue b/src/components/TextareaCopyable.vue index 6a61ab25..a0961f1b 100644 --- a/src/components/TextareaCopyable.vue +++ b/src/components/TextareaCopyable.vue @@ -13,7 +13,9 @@ import jsHljs from 'highlight.js/lib/languages/javascript'; import cssHljs from 'highlight.js/lib/languages/css'; import goHljs from 'highlight.js/lib/languages/go'; import csharpHljs from 'highlight.js/lib/languages/csharp'; +import { Base64 } from 'js-base64'; import { useCopy } from '@/composable/copy'; +import { useDownloadFileFromBase64 } from '@/composable/downloadBase64'; const props = withDefaults( defineProps<{ @@ -23,12 +25,16 @@ const props = withDefaults( copyPlacement?: 'top-right' | 'bottom-right' | 'outside' | 'none' copyMessage?: string wordWrap?: boolean + downloadFileName?: string + downloadButtonText?: string }>(), { followHeightOf: null, language: 'txt', copyPlacement: 'top-right', copyMessage: 'Copy to clipboard', + downloadFileName: '', + downloadButtonText: 'Download', }, ); hljs.registerLanguage('sql', sqlHljs); @@ -44,11 +50,18 @@ hljs.registerLanguage('javascript', jsHljs); hljs.registerLanguage('go', goHljs); hljs.registerLanguage('csharp', csharpHljs); -const { value, language, followHeightOf, copyPlacement, copyMessage } = toRefs(props); +const { value, language, followHeightOf, copyPlacement, copyMessage, downloadFileName, downloadButtonText } = toRefs(props); const { height } = followHeightOf.value ? useElementSize(followHeightOf) : { height: ref(null) }; const { copy, isJustCopied } = useCopy({ source: value, createToast: false }); const tooltipText = computed(() => isJustCopied.value ? 'Copied!' : copyMessage.value); + +const valueBase64 = computed(() => Base64.encode(value.value)); +const { download } = useDownloadFileFromBase64( + { + source: valueBase64, + filename: downloadFileName, + }); diff --git a/src/composable/downloadBase64.ts b/src/composable/downloadBase64.ts index 37b0428d..4d9b1e7c 100644 --- a/src/composable/downloadBase64.ts +++ b/src/composable/downloadBase64.ts @@ -1,8 +1,14 @@ -import { extension as getExtensionFromMime } from 'mime-types'; -import type { Ref } from 'vue'; +import { extension as getExtensionFromMimeType, extension as getMimeTypeFromExtension } from 'mime-types'; +import type { MaybeRef } from 'vue'; import _ from 'lodash'; +import { get } from '@vueuse/core'; -export { getMimeTypeFromBase64, useDownloadFileFromBase64 }; +export { + getMimeTypeFromBase64, + getMimeTypeFromExtension, getExtensionFromMimeType, + useDownloadFileFromBase64, + previewImageFromBase64, +}; const commonMimeTypesSignatures = { 'JVBERi0': 'application/pdf', @@ -36,30 +42,68 @@ function getFileExtensionFromMimeType({ defaultExtension?: string }) { if (mimeType) { - return getExtensionFromMime(mimeType) ?? defaultExtension; + return getExtensionFromMimeType(mimeType) ?? defaultExtension; } return defaultExtension; } -function useDownloadFileFromBase64({ source, filename }: { source: Ref; filename?: string }) { +function downloadFromBase64({ sourceValue, filename, extension, fileMimeType }: +{ sourceValue: string; filename?: string; extension?: string; fileMimeType?: string }) { + if (sourceValue === '') { + throw new Error('Base64 string is empty'); + } + + const defaultExtension = extension ?? 'txt'; + const { mimeType } = getMimeTypeFromBase64({ base64String: sourceValue }); + let base64String = sourceValue; + if (!mimeType) { + const targetMimeType = fileMimeType ?? getMimeTypeFromExtension(defaultExtension); + base64String = `data:${targetMimeType};base64,${sourceValue}`; + } + + const cleanExtension = extension ?? getFileExtensionFromMimeType( + { mimeType, defaultExtension }); + let cleanFileName = filename ?? `file.${cleanExtension}`; + if (extension && !cleanFileName.endsWith(`.${extension}`)) { + cleanFileName = `${cleanFileName}.${cleanExtension}`; + } + + const a = document.createElement('a'); + a.href = base64String; + a.download = cleanFileName; + a.click(); +} + +function useDownloadFileFromBase64( + { source, filename, extension }: + { source: MaybeRef; filename?: MaybeRef; extension?: MaybeRef }) { return { download() { - if (source.value === '') { - throw new Error('Base64 string is empty'); - } - - const { mimeType } = getMimeTypeFromBase64({ base64String: source.value }); - const base64String = mimeType - ? source.value - : `data:text/plain;base64,${source.value}`; - - const cleanFileName = filename ?? `file.${getFileExtensionFromMimeType({ mimeType })}`; - - const a = document.createElement('a'); - a.href = base64String; - a.download = cleanFileName; - a.click(); + downloadFromBase64({ sourceValue: get(source), filename: get(filename), extension: get(extension) }); }, }; } + +function previewImageFromBase64(base64String: string): HTMLImageElement { + if (base64String === '') { + throw new Error('Base64 string is empty'); + } + + const img = document.createElement('img'); + img.src = base64String; + + const container = document.createElement('div'); + container.appendChild(img); + + const previewContainer = document.getElementById('previewContainer'); + if (previewContainer) { + previewContainer.innerHTML = ''; + previewContainer.appendChild(container); + } + else { + throw new Error('Preview container element not found'); + } + + return img; +}