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 1 commit
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)))
})
})
27 changes: 23 additions & 4 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 ObjectOptions<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?: ObjectOptions<T>): UseBase64Return
export function useBase64<T extends Map<string, unknown>>(target: MaybeRef<T>, options?: ObjectOptions<T>): UseBase64Return
export function useBase64<T extends Set<unknown>>(target: MaybeRef<T>, options?: ObjectOptions<T>): UseBase64Return
export function useBase64<T>(target: MaybeRef<T[]>, options?: ObjectOptions<T[]>): UseBase64Return
export function useBase64(
target: any,
options?: any,
Expand All @@ -39,8 +48,8 @@ export function useBase64(
promise.value = new Promise<string>((resolve, reject) => {
try {
const _target = unref(target)
// undefined or null
if (_target === undefined || _target === null) { resolve('') }
// undefined
if (_target === undefined) { resolve('') }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason that null is being excluded here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's more obvious if we check all cases for typeof _target === 'object' in one place. I check this one in getDefaultSerialization but I'm not really sure this decision.

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)))) }
Expand All @@ -57,6 +66,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 +85,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
}