Skip to content

Commit

Permalink
feat: expose copy function to onCopy callback
Browse files Browse the repository at this point in the history
expose internal `safeStringify` util
  • Loading branch information
pionxzh committed Apr 19, 2023
1 parent 21805a3 commit 5e4c7f3
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 38 deletions.
2 changes: 1 addition & 1 deletion docs/pages/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ Check the [source code](https://github.com/TexteaInc/json-viewer/blob/main/docs/
| `keyRenderer` | `{when: (props) => boolean}` | - | Customize a key, if `keyRenderer.when` returns `true`. |
| `valueTypes` | `ValueTypes` | - | Customize value types. |
| `onChange` | `(path, oldVal, newVal) => void` | - | Callback when value changed. |
| `onCopy` | `(path, value) => void` | - | Callback when value copied, you can use it to customize the copy behavior.<br />\*Note: you will have to write the data to the clipboard by yourself. |
| `onCopy` | `(path, value, copy) => void` | - | Callback when value copied, you can use it to customize the copy behavior.<br />\*Note: you can use the third argument `copy` to copy string to clipborad. |
| `onSelect` | `(path, value) => void` | - | Callback when value selected. |
| `enableClipboard` | `boolean` | `true` | Whether enable clipboard feature. |
| `editable` | `boolean` \|<br />`(path, currentValue) => boolean` | `false` | Whether enable edit feature. You can pass a function to customize the result. |
Expand Down
4 changes: 2 additions & 2 deletions src/components/DataKeyPair.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useInspect } from '../hooks/useInspect'
import { useJsonViewerStore } from '../stores/JsonViewerStore'
import { useTypeComponents } from '../stores/typeRegistry'
import type { DataItemProps } from '../type'
import { getValueSize } from '../utils'
import { copyString, getValueSize } from '../utils'
import {
CheckIcon,
ChevronRightIcon,
Expand Down Expand Up @@ -167,7 +167,7 @@ export const DataKeyPair: FC<DataKeyPairProps> = (props) => {
onClick={event => {
event.preventDefault()
try {
copy(path, value)
copy(path, value, copyString)
} catch (e) {
// in some case, this will throw error
// fixme: `useAlert` hook
Expand Down
48 changes: 18 additions & 30 deletions src/hooks/useCopyToClipboard.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import copyToClipboard from 'copy-to-clipboard'
import { useCallback, useRef, useState } from 'react'

import { useJsonViewerStore } from '../stores/JsonViewerStore'
import type { JsonViewerOnCopy } from '../type'
import { safeStringify } from '../utils'
import { copyString, safeStringify } from '../utils'

/**
* useClipboard hook accepts one argument options in which copied status timeout duration is defined (defaults to 2000). Hook returns object with properties:
Expand All @@ -25,24 +24,11 @@ export function useClipboard ({ timeout = 2000 } = {}) {
}, [timeout])
const onCopy = useJsonViewerStore(store => store.onCopy)

const copy = useCallback<JsonViewerOnCopy>((path, value: unknown) => {
const copy = useCallback<JsonViewerOnCopy>(async (path, value: unknown) => {
if (typeof onCopy === 'function') {
try {
const result = onCopy(path, value)
if (result instanceof Promise) {
result.then(() => {
handleCopyResult(true)
}).catch((error) => {
console.error(
`error when copy ${path.length === 0
? 'src'
: `src[${path.join(
'.')}`
}]`, error)
})
} else {
handleCopyResult(true)
}
await onCopy(path, value, copyString)
handleCopyResult(true)
} catch (error) {
console.error(
`error when copy ${path.length === 0
Expand All @@ -52,18 +38,20 @@ export function useClipboard ({ timeout = 2000 } = {}) {
}]`, error)
}
} else {
const valueToCopy = safeStringify(
typeof value === 'function' ? value.toString() : value,
' '
)
if ('clipboard' in navigator) {
navigator.clipboard.writeText(valueToCopy)
.then(() => handleCopyResult(true))
// When navigator.clipboard throws an error, fallback to copy-to-clipboard package
.catch(() => copyToClipboard(valueToCopy))
} else {
// fallback to copy-to-clipboard when navigator.clipboard is not available
copyToClipboard(valueToCopy)
try {
const valueToCopy = safeStringify(
typeof value === 'function' ? value.toString() : value,
' '
)
await copyString(valueToCopy)
handleCopyResult(true)
} catch (error) {
console.error(
`error when copy ${path.length === 0
? 'src'
: `src[${path.join(
'.')}`
}]`, error)
}
}
}, [handleCopyResult, onCopy])
Expand Down
4 changes: 2 additions & 2 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ import {
} from './stores/typeRegistry'
import { darkColorspace, lightColorspace } from './theme/base16'
import type { JsonViewerProps } from './type'
import { applyValue, createDataType, isCycleReference } from './utils'
import { applyValue, createDataType, isCycleReference, safeStringify } from './utils'

export { applyValue, createDataType, isCycleReference }
export { applyValue, createDataType, isCycleReference, safeStringify }

/**
* @internal
Expand Down
8 changes: 5 additions & 3 deletions src/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ export type JsonViewerOnChange = <U = unknown>(
newValue: U /*, type: ChangeType */) => void

/**
* @param path path to the target value
* @param value
* @param path path to the target value
* @param value
* @param copy the function to copy the value to clipboard
*/
export type JsonViewerOnCopy = <U = unknown>(
path: Path,
value: U
value: U,
copy: (value: string) => Promise<void>
) => unknown | Promise<unknown>

/**
Expand Down
23 changes: 23 additions & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import copyToClipboard from 'copy-to-clipboard'
import type { ComponentType } from 'react'

import type { DataItemProps, EditorProps, Path } from '../type'
Expand Down Expand Up @@ -187,6 +188,15 @@ export function segmentArray<T> (arr: T[], size: number): T[][] {
return result
}

/**
* A safe version of `JSON.stringify` that handles circular references and BigInts.
*
* *This function might be changed in the future to support more types. Use it with caution.*
*
* @param obj A JavaScript value, usually an object or array, to be converted.
* @param space Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.
* @returns
*/
export function safeStringify (obj: any, space?: string | number) {
const seenValues: any[] = []

Expand Down Expand Up @@ -235,3 +245,16 @@ export function safeStringify (obj: any, space?: string | number) {

return JSON.stringify(obj, replacer, space)
}

export async function copyString (value: string) {
if ('clipboard' in navigator) {
try {
await navigator.clipboard.writeText(value)
} catch {
// When navigator.clipboard throws an error, fallback to copy-to-clipboard package
}
}

// fallback to copy-to-clipboard when navigator.clipboard is not available
copyToClipboard(value)
}

0 comments on commit 5e4c7f3

Please sign in to comment.