diff --git a/package.json b/package.json index 51495c5e..73c77def 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "highlight.js": "^11.7.0", "iarna-toml-esm": "^3.0.5", "ibantools": "^4.3.3", + "js-base64": "^3.7.6", "json5": "^2.2.3", "jwt-decode": "^3.1.2", "libphonenumber-js": "^1.10.28", @@ -139,5 +140,6 @@ "vitest": "^0.34.0", "workbox-window": "^7.0.0", "zx": "^7.2.1" - } + }, + "packageManager": "pnpm@8.15.3" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 958116ca..a3341eea 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -92,6 +92,9 @@ dependencies: ibantools: specifier: ^4.3.3 version: 4.3.3 + js-base64: + specifier: ^3.7.6 + version: 3.7.7 json5: specifier: ^2.2.3 version: 2.2.3 @@ -4003,6 +4006,7 @@ packages: resolution: {integrity: sha512-Uud2IWncmAfJvRaFYzv5OHDli+FbOzxiVEQdLCKQKLyhz94PIyFC3CHcH7EDMwIn8NPtD06+PNbC/PiO0LGLtw==} dependencies: vue-demi: 0.14.7(vue@3.3.4) + vue-demi: 0.14.7(vue@3.3.4) transitivePeerDependencies: - '@vue/composition-api' - vue @@ -6478,6 +6482,10 @@ packages: hasBin: true dev: true + /js-base64@3.7.7: + resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} + dev: false + /js-beautify@1.14.6: resolution: {integrity: sha512-GfofQY5zDp+cuHc+gsEXKPpNw2KbPddreEo35O6jT6i0RVK6LhsoYBhq5TvK4/n74wnA0QbK8gGd+jUZwTMKJw==} engines: {node: '>=10'} @@ -9172,6 +9180,8 @@ packages: vue: 3.3.4 dev: false + /vue-demi@0.14.7(vue@3.3.4): + resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==} /vue-demi@0.14.7(vue@3.3.4): resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==} engines: {node: '>=12'} diff --git a/src/components/FormatTransformer.vue b/src/components/FormatTransformer.vue index 677fc25a..bcfcdb58 100644 --- a/src/components/FormatTransformer.vue +++ b/src/components/FormatTransformer.vue @@ -48,7 +48,7 @@ const output = computed(() => transformer.value(input.value)); monospace /> -
+
{{ outputLabel }}
diff --git a/src/composable/debouncedref.ts b/src/composable/debouncedref.ts new file mode 100644 index 00000000..09dd10e9 --- /dev/null +++ b/src/composable/debouncedref.ts @@ -0,0 +1,21 @@ +import _ from 'lodash'; + +function useDebouncedRef(initialValue: T, delay: number, immediate: boolean = false) { + const state = ref(initialValue); + const debouncedRef = customRef((track, trigger) => ({ + get() { + track(); + return state.value; + }, + set: _.debounce( + (value) => { + state.value = value; + trigger(); + }, + delay, + { leading: immediate }, + ), + })); + return debouncedRef; +} +export default useDebouncedRef; diff --git a/src/composable/downloadBase64.ts b/src/composable/downloadBase64.ts index 367c6a3e..773541e2 100644 --- a/src/composable/downloadBase64.ts +++ b/src/composable/downloadBase64.ts @@ -1,11 +1,13 @@ import { extension as getExtensionFromMimeType, extension as getMimeTypeFromExtension } from 'mime-types'; -import type { Ref } from 'vue'; +import type { MaybeRef, Ref } from 'vue'; import _ from 'lodash'; +import { get } from '@vueuse/core'; export { getMimeTypeFromBase64, getMimeTypeFromExtension, getExtensionFromMimeType, useDownloadFileFromBase64, useDownloadFileFromBase64Refs, + previewImageFromBase64, }; const commonMimeTypesSignatures = { @@ -74,15 +76,38 @@ function downloadFromBase64({ sourceValue, filename, extension, fileMimeType }: } function useDownloadFileFromBase64( - { source, filename, extension, fileMimeType }: - { source: Ref; filename?: string; extension?: string; fileMimeType?: string }) { + { source, filename, extension }: + { source: MaybeRef; filename?: MaybeRef; extension?: MaybeRef }) { return { download() { - downloadFromBase64({ sourceValue: source.value, filename, extension, fileMimeType }); + 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; +} + function useDownloadFileFromBase64Refs( { source, filename, extension }: { source: Ref; filename?: Ref; extension?: Ref }) { diff --git a/src/tools/base64-file-converter/base64-file-converter.vue b/src/tools/base64-file-converter/base64-file-converter.vue index 377625bd..a489f9a1 100644 --- a/src/tools/base64-file-converter/base64-file-converter.vue +++ b/src/tools/base64-file-converter/base64-file-converter.vue @@ -2,12 +2,19 @@ import { useBase64 } from '@vueuse/core'; import type { Ref } from 'vue'; import { useCopy } from '@/composable/copy'; -import { useDownloadFileFromBase64 } from '@/composable/downloadBase64'; +import { getExtensionFromMimeType, getMimeTypeFromBase64, previewImageFromBase64, useDownloadFileFromBase64Refs } from '@/composable/downloadBase64'; import { useValidation } from '@/composable/validation'; import { isValidBase64 } from '@/utils/base64'; +const fileName = ref('file'); +const fileExtension = ref(''); const base64Input = ref(''); -const { download } = useDownloadFileFromBase64({ source: base64Input }); +const { download } = useDownloadFileFromBase64Refs( + { + source: base64Input, + filename: fileName, + extension: fileExtension, + }); const base64InputValidation = useValidation({ source: base64Input, rules: [ @@ -18,6 +25,35 @@ const base64InputValidation = useValidation({ ], }); +watch( + base64Input, + (newValue, _) => { + const { mimeType } = getMimeTypeFromBase64({ base64String: newValue }); + if (mimeType) { + fileExtension.value = getExtensionFromMimeType(mimeType) || fileExtension.value; + } + }, +); + +function previewImage() { + if (!base64InputValidation.isValid) { + return; + } + try { + const image = previewImageFromBase64(base64Input.value); + image.style.maxWidth = '100%'; + image.style.maxHeight = '400px'; + const previewContainer = document.getElementById('previewContainer'); + if (previewContainer) { + previewContainer.innerHTML = ''; + previewContainer.appendChild(image); + } + } + catch (_) { + // + } +} + function downloadFile() { if (!base64InputValidation.isValid) { return; @@ -44,6 +80,24 @@ async function onUpload(file: File) {