Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(useBase64): accept objects #1706

Merged
merged 5 commits into from Jul 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 5 additions & 1 deletion packages/core/useBase64/index.md
Expand Up @@ -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

Expand All @@ -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.

67 changes: 67 additions & 0 deletions 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)))
})
})
44 changes: 36 additions & 8 deletions 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 {
/**
Expand All @@ -14,6 +15,10 @@ export interface ToDataURLOptions {
quality?: any
}

export interface UseBase64ObjectOptions<T> {
serializer: (v: T) => string
}

export interface UseBase64Return {
base64: Ref<string>
promise: Ref<Promise<string>>
Expand All @@ -25,6 +30,10 @@ export function useBase64(target: MaybeRef<Blob>): UseBase64Return
export function useBase64(target: MaybeRef<ArrayBuffer>): UseBase64Return
export function useBase64(target: MaybeRef<HTMLCanvasElement>, options?: ToDataURLOptions): UseBase64Return
export function useBase64(target: MaybeRef<HTMLImageElement>, options?: ToDataURLOptions): UseBase64Return
export function useBase64<T extends Record<string, unknown>>(target: MaybeRef<T>, options?: UseBase64ObjectOptions<T>): UseBase64Return
export function useBase64<T extends Map<string, unknown>>(target: MaybeRef<T>, options?: UseBase64ObjectOptions<T>): UseBase64Return
export function useBase64<T extends Set<unknown>>(target: MaybeRef<T>, options?: UseBase64ObjectOptions<T>): UseBase64Return
export function useBase64<T>(target: MaybeRef<T[]>, options?: UseBase64ObjectOptions<T[]>): UseBase64Return
export function useBase64(
target: any,
options?: any,
Expand All @@ -39,12 +48,21 @@ export function useBase64(
promise.value = new Promise<string>((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'
Expand All @@ -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'))
}
Expand All @@ -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,
Expand Down
21 changes: 21 additions & 0 deletions packages/core/useBase64/serialization.ts
@@ -0,0 +1,21 @@
const defaults = {
array: (v: unknown[]) => JSON.stringify(v),
object: (v: Record<string, unknown>) => JSON.stringify(v),
set: (v: Set<unknown>) => JSON.stringify(Array.from(v)),
map: (v: Map<string, unknown>) => JSON.stringify(Object.fromEntries(v)),
null: () => '',
}

export function getDefaultSerialization<T extends Object>(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
}