diff --git a/packages/core/useBase64/index.md b/packages/core/useBase64/index.md index 9bf36a16cc0..077e527b035 100644 --- a/packages/core/useBase64/index.md +++ b/packages/core/useBase64/index.md @@ -4,7 +4,7 @@ category: Utilities # useBase64 -Reactive base64 transforming. Supports plain text, buffer, files, canvas and images. +Reactive base64 transforming. Supports plain text, buffer, files, canvas, objects, maps, sets and images. ## Usage @@ -16,3 +16,7 @@ const text = ref('') const { base64 } = useBase64(text) ``` + +If you use object, array, map or set you can provide serializer in options. Otherwise, your data will be serialized by default serializer. +Objects and arrays will be transformed in string by JSON.stringify. Map and set will be transformed in object and array respectively before stringify. + diff --git a/packages/core/useBase64/index.test.ts b/packages/core/useBase64/index.test.ts new file mode 100644 index 00000000000..ae9f96ffd81 --- /dev/null +++ b/packages/core/useBase64/index.test.ts @@ -0,0 +1,67 @@ +import { useBase64 } from '@vueuse/core' +import { describe, expect } from 'vitest' + +function decode(encoded: string) { + const decodedStr = Buffer.from(encoded.split(',')[1], 'base64').toString('utf-8') + + if (!decodedStr) + return '' + + return JSON.parse(decodedStr) +} + +describe('useBase64', () => { + it('should work with record', async () => { + const template = { test: 5 } + + const { promise, base64 } = useBase64(template) + + await promise.value + + expect(decode(base64.value)).toEqual(template) + }) + + it('should work with map and default serialize function', async () => { + const map = new Map([['test', 1]]) + + const { promise, base64 } = useBase64(map) + + await promise.value + + expect(decode(base64.value)).toEqual(Object.fromEntries(map)) + }) + + it('should work with set', async () => { + const set = new Set([1]) + + const { promise, base64 } = useBase64(set) + + await promise.value + + expect(decode(base64.value)).toEqual(Array.from(set)) + }) + + it('should work with array', async () => { + const arr = [1, 2, 3] + + const { promise, base64 } = useBase64(arr) + + await promise.value + + expect(decode(base64.value)).toEqual(arr) + }) + + it('should work with custom serialize function', async () => { + const arr = [1, 2, 3] + + const serializer = (arr: number[]) => { + return JSON.stringify(arr.map(el => el * 2)) + } + + const { promise, base64 } = useBase64(arr, { serializer }) + + await promise.value + + expect(decode(base64.value)).toEqual(JSON.parse(serializer(arr))) + }) +}) diff --git a/packages/core/useBase64/index.ts b/packages/core/useBase64/index.ts index b02a1f882ec..d8a5bde7f40 100644 --- a/packages/core/useBase64/index.ts +++ b/packages/core/useBase64/index.ts @@ -1,7 +1,8 @@ import type { Ref } from 'vue-demi' -import { ref, unref, watch } from 'vue-demi' +import { isRef, ref, unref, watch } from 'vue-demi' import type { MaybeRef } from '@vueuse/shared' import { isClient } from '@vueuse/shared' +import { getDefaultSerialization } from './serialization' export interface ToDataURLOptions { /** @@ -14,6 +15,10 @@ export interface ToDataURLOptions { quality?: any } +export interface UseBase64ObjectOptions { + serializer: (v: T) => string +} + export interface UseBase64Return { base64: Ref promise: Ref> @@ -25,6 +30,10 @@ export function useBase64(target: MaybeRef): UseBase64Return export function useBase64(target: MaybeRef): UseBase64Return export function useBase64(target: MaybeRef, options?: ToDataURLOptions): UseBase64Return export function useBase64(target: MaybeRef, options?: ToDataURLOptions): UseBase64Return +export function useBase64>(target: MaybeRef, options?: UseBase64ObjectOptions): UseBase64Return +export function useBase64>(target: MaybeRef, options?: UseBase64ObjectOptions): UseBase64Return +export function useBase64>(target: MaybeRef, options?: UseBase64ObjectOptions): UseBase64Return +export function useBase64(target: MaybeRef, options?: UseBase64ObjectOptions): UseBase64Return export function useBase64( target: any, options?: any, @@ -39,12 +48,21 @@ export function useBase64( promise.value = new Promise((resolve, reject) => { try { const _target = unref(target) - // undefined or null - if (_target === undefined || _target === null) { resolve('') } - else if (typeof _target === 'string') { resolve(blobToBase64(new Blob([_target], { type: 'text/plain' }))) } - else if (_target instanceof Blob) { resolve(blobToBase64(_target)) } - else if (_target instanceof ArrayBuffer) { resolve(window.btoa(String.fromCharCode(...new Uint8Array(_target)))) } - else if (_target instanceof HTMLCanvasElement) { resolve(_target.toDataURL(options?.type, options?.quality)) } + if (_target == null) { + resolve('') + } + else if (typeof _target === 'string') { + resolve(blobToBase64(new Blob([_target], { type: 'text/plain' }))) + } + else if (_target instanceof Blob) { + resolve(blobToBase64(_target)) + } + else if (_target instanceof ArrayBuffer) { + resolve(window.btoa(String.fromCharCode(...new Uint8Array(_target)))) + } + else if (_target instanceof HTMLCanvasElement) { + resolve(_target.toDataURL(options?.type, options?.quality)) + } else if (_target instanceof HTMLImageElement) { const img = _target.cloneNode(false) as HTMLImageElement img.crossOrigin = 'Anonymous' @@ -57,6 +75,13 @@ export function useBase64( resolve(canvas.toDataURL(options?.type, options?.quality)) }).catch(reject) } + else if (typeof _target === 'object') { + const _serializeFn = options?.serializer || getDefaultSerialization(_target) + + const serialized = _serializeFn(_target) + + return resolve(blobToBase64(new Blob([serialized], { type: 'application/json' }))) + } else { reject(new Error('target is unsupported types')) } @@ -69,7 +94,10 @@ export function useBase64( return promise.value } - watch(target, execute, { immediate: true }) + if (isRef(target)) + watch(target, execute, { immediate: true }) + else + execute() return { base64, diff --git a/packages/core/useBase64/serialization.ts b/packages/core/useBase64/serialization.ts new file mode 100644 index 00000000000..c95773193b7 --- /dev/null +++ b/packages/core/useBase64/serialization.ts @@ -0,0 +1,21 @@ +const defaults = { + array: (v: unknown[]) => JSON.stringify(v), + object: (v: Record) => JSON.stringify(v), + set: (v: Set) => JSON.stringify(Array.from(v)), + map: (v: Map) => JSON.stringify(Object.fromEntries(v)), + null: () => '', +} + +export function getDefaultSerialization(target: T) { + if (!target) + return defaults.null + + if (target instanceof Map) + return defaults.map + else if (target instanceof Set) + return defaults.set + else if (Array.isArray(target)) + return defaults.array + else + return defaults.object +}