feat(TextareaCopyable/FormatTransformer): add download button prop

This commit is contained in:
sharevb 2024-06-16 12:34:41 +02:00
parent 135b31190d
commit 052ddebc34
3 changed files with 106 additions and 23 deletions

View file

@ -1,7 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import _ from 'lodash'; import _ from 'lodash';
import { Base64 } from 'js-base64';
import type { UseValidationRule } from '@/composable/validation'; import type { UseValidationRule } from '@/composable/validation';
import CInputText from '@/ui/c-input-text/c-input-text.vue'; import CInputText from '@/ui/c-input-text/c-input-text.vue';
import { useDownloadFileFromBase64 } from '@/composable/downloadBase64';
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
@ -12,6 +14,8 @@ const props = withDefaults(
inputDefault?: string inputDefault?: string
outputLabel?: string outputLabel?: string
outputLanguage?: string outputLanguage?: string
downloadFileName?: string
downloadButtonText?: string
}>(), }>(),
{ {
transformer: _.identity, transformer: _.identity,
@ -21,16 +25,27 @@ const props = withDefaults(
inputPlaceholder: 'Input...', inputPlaceholder: 'Input...',
outputLabel: 'Output', outputLabel: 'Output',
outputLanguage: '', outputLanguage: '',
downloadFileName: '',
downloadButtonText: 'Download',
}, },
); );
const { transformer, inputValidationRules, inputLabel, outputLabel, outputLanguage, inputPlaceholder, inputDefault } const {
= toRefs(props); transformer, inputValidationRules, inputLabel, outputLabel, outputLanguage,
inputPlaceholder, inputDefault, downloadFileName, downloadButtonText,
} = toRefs(props);
const inputElement = ref<typeof CInputText>(); const inputElement = ref<typeof CInputText>();
const input = ref(inputDefault.value); const input = ref(inputDefault.value);
const output = computed(() => transformer.value(input.value)); const output = computed(() => transformer.value(input.value));
const outputBase64 = computed(() => Base64.encode(output.value));
const { download } = useDownloadFileFromBase64(
{
source: outputBase64,
filename: downloadFileName,
});
</script> </script>
<template> <template>
@ -53,5 +68,11 @@ const output = computed(() => transformer.value(input.value));
{{ outputLabel }} {{ outputLabel }}
</div> </div>
<textarea-copyable :value="output" :language="outputLanguage" :follow-height-of="inputElement?.inputWrapperRef" /> <textarea-copyable :value="output" :language="outputLanguage" :follow-height-of="inputElement?.inputWrapperRef" />
<div v-if="downloadFileName !== '' && output !== ''" mt-5 flex justify-center>
<c-button secondary @click="download">
{{ downloadButtonText }}
</c-button>
</div>
</div> </div>
</template> </template>

View file

@ -13,7 +13,9 @@ import jsHljs from 'highlight.js/lib/languages/javascript';
import cssHljs from 'highlight.js/lib/languages/css'; import cssHljs from 'highlight.js/lib/languages/css';
import goHljs from 'highlight.js/lib/languages/go'; import goHljs from 'highlight.js/lib/languages/go';
import csharpHljs from 'highlight.js/lib/languages/csharp'; import csharpHljs from 'highlight.js/lib/languages/csharp';
import { Base64 } from 'js-base64';
import { useCopy } from '@/composable/copy'; import { useCopy } from '@/composable/copy';
import { useDownloadFileFromBase64 } from '@/composable/downloadBase64';
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
@ -23,12 +25,16 @@ const props = withDefaults(
copyPlacement?: 'top-right' | 'bottom-right' | 'outside' | 'none' copyPlacement?: 'top-right' | 'bottom-right' | 'outside' | 'none'
copyMessage?: string copyMessage?: string
wordWrap?: boolean wordWrap?: boolean
downloadFileName?: string
downloadButtonText?: string
}>(), }>(),
{ {
followHeightOf: null, followHeightOf: null,
language: 'txt', language: 'txt',
copyPlacement: 'top-right', copyPlacement: 'top-right',
copyMessage: 'Copy to clipboard', copyMessage: 'Copy to clipboard',
downloadFileName: '',
downloadButtonText: 'Download',
}, },
); );
hljs.registerLanguage('sql', sqlHljs); hljs.registerLanguage('sql', sqlHljs);
@ -44,11 +50,18 @@ hljs.registerLanguage('javascript', jsHljs);
hljs.registerLanguage('go', goHljs); hljs.registerLanguage('go', goHljs);
hljs.registerLanguage('csharp', csharpHljs); 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 { height } = followHeightOf.value ? useElementSize(followHeightOf) : { height: ref(null) };
const { copy, isJustCopied } = useCopy({ source: value, createToast: false }); const { copy, isJustCopied } = useCopy({ source: value, createToast: false });
const tooltipText = computed(() => isJustCopied.value ? 'Copied!' : copyMessage.value); const tooltipText = computed(() => isJustCopied.value ? 'Copied!' : copyMessage.value);
const valueBase64 = computed(() => Base64.encode(value.value));
const { download } = useDownloadFileFromBase64(
{
source: valueBase64,
filename: downloadFileName,
});
</script> </script>
<template> <template>
@ -81,6 +94,11 @@ const tooltipText = computed(() => isJustCopied.value ? 'Copied!' : copyMessage.
{{ tooltipText }} {{ tooltipText }}
</c-button> </c-button>
</div> </div>
<div v-if="downloadFileName !== '' && value !== ''" mt-5 flex justify-center>
<c-button secondary @click="download">
{{ downloadButtonText }}
</c-button>
</div>
</div> </div>
</template> </template>

View file

@ -1,8 +1,14 @@
import { extension as getExtensionFromMime } from 'mime-types'; import { extension as getExtensionFromMimeType, extension as getMimeTypeFromExtension } from 'mime-types';
import type { Ref } from 'vue'; import type { MaybeRef } from 'vue';
import _ from 'lodash'; import _ from 'lodash';
import { get } from '@vueuse/core';
export { getMimeTypeFromBase64, useDownloadFileFromBase64 }; export {
getMimeTypeFromBase64,
getMimeTypeFromExtension, getExtensionFromMimeType,
useDownloadFileFromBase64,
previewImageFromBase64,
};
const commonMimeTypesSignatures = { const commonMimeTypesSignatures = {
'JVBERi0': 'application/pdf', 'JVBERi0': 'application/pdf',
@ -36,30 +42,68 @@ function getFileExtensionFromMimeType({
defaultExtension?: string defaultExtension?: string
}) { }) {
if (mimeType) { if (mimeType) {
return getExtensionFromMime(mimeType) ?? defaultExtension; return getExtensionFromMimeType(mimeType) ?? defaultExtension;
} }
return defaultExtension; return defaultExtension;
} }
function useDownloadFileFromBase64({ source, filename }: { source: Ref<string>; filename?: string }) { function downloadFromBase64({ sourceValue, filename, extension, fileMimeType }:
return { { sourceValue: string; filename?: string; extension?: string; fileMimeType?: string }) {
download() { if (sourceValue === '') {
if (source.value === '') {
throw new Error('Base64 string is empty'); throw new Error('Base64 string is empty');
} }
const { mimeType } = getMimeTypeFromBase64({ base64String: source.value }); const defaultExtension = extension ?? 'txt';
const base64String = mimeType const { mimeType } = getMimeTypeFromBase64({ base64String: sourceValue });
? source.value let base64String = sourceValue;
: `data:text/plain;base64,${source.value}`; if (!mimeType) {
const targetMimeType = fileMimeType ?? getMimeTypeFromExtension(defaultExtension);
base64String = `data:${targetMimeType};base64,${sourceValue}`;
}
const cleanFileName = filename ?? `file.${getFileExtensionFromMimeType({ mimeType })}`; const cleanExtension = extension ?? getFileExtensionFromMimeType(
{ mimeType, defaultExtension });
let cleanFileName = filename ?? `file.${cleanExtension}`;
if (extension && !cleanFileName.endsWith(`.${extension}`)) {
cleanFileName = `${cleanFileName}.${cleanExtension}`;
}
const a = document.createElement('a'); const a = document.createElement('a');
a.href = base64String; a.href = base64String;
a.download = cleanFileName; a.download = cleanFileName;
a.click(); a.click();
}
function useDownloadFileFromBase64(
{ source, filename, extension }:
{ source: MaybeRef<string>; filename?: MaybeRef<string>; extension?: MaybeRef<string> }) {
return {
download() {
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;
}