Skip to content

Commit 052ddeb

Browse files
committed
feat(TextareaCopyable/FormatTransformer): add download button prop
1 parent 135b311 commit 052ddeb

File tree

3 files changed

+105
-22
lines changed

3 files changed

+105
-22
lines changed

src/components/FormatTransformer.vue

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
<script setup lang="ts">
22
import _ from 'lodash';
3+
import { Base64 } from 'js-base64';
34
import type { UseValidationRule } from '@/composable/validation';
45
import CInputText from '@/ui/c-input-text/c-input-text.vue';
6+
import { useDownloadFileFromBase64 } from '@/composable/downloadBase64';
57
68
const props = withDefaults(
79
defineProps<{
@@ -12,6 +14,8 @@ const props = withDefaults(
1214
inputDefault?: string
1315
outputLabel?: string
1416
outputLanguage?: string
17+
downloadFileName?: string
18+
downloadButtonText?: string
1519
}>(),
1620
{
1721
transformer: _.identity,
@@ -21,16 +25,27 @@ const props = withDefaults(
2125
inputPlaceholder: 'Input...',
2226
outputLabel: 'Output',
2327
outputLanguage: '',
28+
downloadFileName: '',
29+
downloadButtonText: 'Download',
2430
},
2531
);
2632
27-
const { transformer, inputValidationRules, inputLabel, outputLabel, outputLanguage, inputPlaceholder, inputDefault }
28-
= toRefs(props);
33+
const {
34+
transformer, inputValidationRules, inputLabel, outputLabel, outputLanguage,
35+
inputPlaceholder, inputDefault, downloadFileName, downloadButtonText,
36+
} = toRefs(props);
2937
3038
const inputElement = ref<typeof CInputText>();
3139
3240
const input = ref(inputDefault.value);
3341
const output = computed(() => transformer.value(input.value));
42+
43+
const outputBase64 = computed(() => Base64.encode(output.value));
44+
const { download } = useDownloadFileFromBase64(
45+
{
46+
source: outputBase64,
47+
filename: downloadFileName,
48+
});
3449
</script>
3550

3651
<template>
@@ -53,5 +68,11 @@ const output = computed(() => transformer.value(input.value));
5368
{{ outputLabel }}
5469
</div>
5570
<textarea-copyable :value="output" :language="outputLanguage" :follow-height-of="inputElement?.inputWrapperRef" />
71+
72+
<div v-if="downloadFileName !== '' && output !== ''" mt-5 flex justify-center>
73+
<c-button secondary @click="download">
74+
{{ downloadButtonText }}
75+
</c-button>
76+
</div>
5677
</div>
5778
</template>

src/components/TextareaCopyable.vue

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ import jsHljs from 'highlight.js/lib/languages/javascript';
1313
import cssHljs from 'highlight.js/lib/languages/css';
1414
import goHljs from 'highlight.js/lib/languages/go';
1515
import csharpHljs from 'highlight.js/lib/languages/csharp';
16+
import { Base64 } from 'js-base64';
1617
import { useCopy } from '@/composable/copy';
18+
import { useDownloadFileFromBase64 } from '@/composable/downloadBase64';
1719
1820
const props = withDefaults(
1921
defineProps<{
@@ -23,12 +25,16 @@ const props = withDefaults(
2325
copyPlacement?: 'top-right' | 'bottom-right' | 'outside' | 'none'
2426
copyMessage?: string
2527
wordWrap?: boolean
28+
downloadFileName?: string
29+
downloadButtonText?: string
2630
}>(),
2731
{
2832
followHeightOf: null,
2933
language: 'txt',
3034
copyPlacement: 'top-right',
3135
copyMessage: 'Copy to clipboard',
36+
downloadFileName: '',
37+
downloadButtonText: 'Download',
3238
},
3339
);
3440
hljs.registerLanguage('sql', sqlHljs);
@@ -44,11 +50,18 @@ hljs.registerLanguage('javascript', jsHljs);
4450
hljs.registerLanguage('go', goHljs);
4551
hljs.registerLanguage('csharp', csharpHljs);
4652
47-
const { value, language, followHeightOf, copyPlacement, copyMessage } = toRefs(props);
53+
const { value, language, followHeightOf, copyPlacement, copyMessage, downloadFileName, downloadButtonText } = toRefs(props);
4854
const { height } = followHeightOf.value ? useElementSize(followHeightOf) : { height: ref(null) };
4955
5056
const { copy, isJustCopied } = useCopy({ source: value, createToast: false });
5157
const tooltipText = computed(() => isJustCopied.value ? 'Copied!' : copyMessage.value);
58+
59+
const valueBase64 = computed(() => Base64.encode(value.value));
60+
const { download } = useDownloadFileFromBase64(
61+
{
62+
source: valueBase64,
63+
filename: downloadFileName,
64+
});
5265
</script>
5366

5467
<template>
@@ -81,6 +94,11 @@ const tooltipText = computed(() => isJustCopied.value ? 'Copied!' : copyMessage.
8194
{{ tooltipText }}
8295
</c-button>
8396
</div>
97+
<div v-if="downloadFileName !== '' && value !== ''" mt-5 flex justify-center>
98+
<c-button secondary @click="download">
99+
{{ downloadButtonText }}
100+
</c-button>
101+
</div>
84102
</div>
85103
</template>
86104

src/composable/downloadBase64.ts

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1-
import { extension as getExtensionFromMime } from 'mime-types';
2-
import type { Ref } from 'vue';
1+
import { extension as getExtensionFromMimeType, extension as getMimeTypeFromExtension } from 'mime-types';
2+
import type { MaybeRef } from 'vue';
33
import _ from 'lodash';
4+
import { get } from '@vueuse/core';
45

5-
export { getMimeTypeFromBase64, useDownloadFileFromBase64 };
6+
export {
7+
getMimeTypeFromBase64,
8+
getMimeTypeFromExtension, getExtensionFromMimeType,
9+
useDownloadFileFromBase64,
10+
previewImageFromBase64,
11+
};
612

713
const commonMimeTypesSignatures = {
814
'JVBERi0': 'application/pdf',
@@ -36,30 +42,68 @@ function getFileExtensionFromMimeType({
3642
defaultExtension?: string
3743
}) {
3844
if (mimeType) {
39-
return getExtensionFromMime(mimeType) ?? defaultExtension;
45+
return getExtensionFromMimeType(mimeType) ?? defaultExtension;
4046
}
4147

4248
return defaultExtension;
4349
}
4450

45-
function useDownloadFileFromBase64({ source, filename }: { source: Ref<string>; filename?: string }) {
46-
return {
47-
download() {
48-
if (source.value === '') {
49-
throw new Error('Base64 string is empty');
50-
}
51+
function downloadFromBase64({ sourceValue, filename, extension, fileMimeType }:
52+
{ sourceValue: string; filename?: string; extension?: string; fileMimeType?: string }) {
53+
if (sourceValue === '') {
54+
throw new Error('Base64 string is empty');
55+
}
56+
57+
const defaultExtension = extension ?? 'txt';
58+
const { mimeType } = getMimeTypeFromBase64({ base64String: sourceValue });
59+
let base64String = sourceValue;
60+
if (!mimeType) {
61+
const targetMimeType = fileMimeType ?? getMimeTypeFromExtension(defaultExtension);
62+
base64String = `data:${targetMimeType};base64,${sourceValue}`;
63+
}
5164

52-
const { mimeType } = getMimeTypeFromBase64({ base64String: source.value });
53-
const base64String = mimeType
54-
? source.value
55-
: `data:text/plain;base64,${source.value}`;
65+
const cleanExtension = extension ?? getFileExtensionFromMimeType(
66+
{ mimeType, defaultExtension });
67+
let cleanFileName = filename ?? `file.${cleanExtension}`;
68+
if (extension && !cleanFileName.endsWith(`.${extension}`)) {
69+
cleanFileName = `${cleanFileName}.${cleanExtension}`;
70+
}
5671

57-
const cleanFileName = filename ?? `file.${getFileExtensionFromMimeType({ mimeType })}`;
72+
const a = document.createElement('a');
73+
a.href = base64String;
74+
a.download = cleanFileName;
75+
a.click();
76+
}
5877

59-
const a = document.createElement('a');
60-
a.href = base64String;
61-
a.download = cleanFileName;
62-
a.click();
78+
function useDownloadFileFromBase64(
79+
{ source, filename, extension }:
80+
{ source: MaybeRef<string>; filename?: MaybeRef<string>; extension?: MaybeRef<string> }) {
81+
return {
82+
download() {
83+
downloadFromBase64({ sourceValue: get(source), filename: get(filename), extension: get(extension) });
6384
},
6485
};
6586
}
87+
88+
function previewImageFromBase64(base64String: string): HTMLImageElement {
89+
if (base64String === '') {
90+
throw new Error('Base64 string is empty');
91+
}
92+
93+
const img = document.createElement('img');
94+
img.src = base64String;
95+
96+
const container = document.createElement('div');
97+
container.appendChild(img);
98+
99+
const previewContainer = document.getElementById('previewContainer');
100+
if (previewContainer) {
101+
previewContainer.innerHTML = '';
102+
previewContainer.appendChild(container);
103+
}
104+
else {
105+
throw new Error('Preview container element not found');
106+
}
107+
108+
return img;
109+
}

0 commit comments

Comments
 (0)