Files
payload/packages/next/src/views/API/RenderJSON/index.tsx
2024-03-05 15:17:36 -05:00

152 lines
4.3 KiB
TypeScript

'use client'
import { Chevron } from '@payloadcms/ui'
import * as React from 'react'
import './index.scss'
const chars = {
leftCurlyBracket: '\u007B',
leftSquareBracket: '\u005B',
rightCurlyBracket: '\u007D',
rightSquareBracket: '\u005D',
}
const baseClass = 'query-inspector'
const Bracket = ({
type,
comma = false,
position,
}: {
comma?: boolean
position: 'end' | 'start'
type: 'array' | 'object'
}) => {
const rightBracket = type === 'object' ? chars.rightCurlyBracket : chars.rightSquareBracket
const leftBracket = type === 'object' ? chars.leftCurlyBracket : chars.leftSquareBracket
const bracketToRender = position === 'end' ? rightBracket : leftBracket
return (
<span className={`${baseClass}__bracket ${baseClass}__bracket--position-${position}`}>
{bracketToRender}
{position === 'end' && comma ? ',' : null}
</span>
)
}
type Args = {
isEmpty?: boolean
object: Record<string, any> | any[]
objectKey?: string
parentType?: 'array' | 'object'
trailingComma?: boolean
}
export const RenderJSON = ({
isEmpty = false,
object,
objectKey,
parentType = 'object',
trailingComma = false,
}: Args) => {
const objectKeys = object ? Object.keys(object) : []
const objectLength = objectKeys.length
const [isOpen, setIsOpen] = React.useState<boolean>(true)
return (
<li>
<button
aria-label="toggle"
className={`${baseClass}__list-toggle ${isEmpty ? `${baseClass}__list-toggle--empty` : ''}`}
onClick={() => setIsOpen(!isOpen)}
type="button"
>
{isEmpty ? null : (
<Chevron
className={`${baseClass}__toggle-row-icon ${baseClass}__toggle-row-icon--${
isOpen ? 'open' : 'closed'
}`}
/>
)}
<span>
{objectKey && `"${objectKey}": `}
<Bracket position="start" type={parentType} />
{isEmpty ? <Bracket comma={trailingComma} position="end" type={parentType} /> : null}
</span>
</button>
<ul className={`${baseClass}__json-children`}>
{isOpen &&
objectKeys.map((key, keyIndex) => {
let value = object[key]
let type = 'string'
const isLastKey = keyIndex === objectLength - 1
if (value === null) {
type = 'null'
} else if (value instanceof Date) {
type = 'date'
value = value.toISOString()
} else if (Array.isArray(value)) {
type = 'array'
} else if (typeof value === 'object') {
type = 'object'
} else if (typeof value === 'number') {
type = 'number'
} else if (typeof value === 'boolean') {
type = 'boolean'
} else {
type = 'string'
}
if (type === 'object' || type === 'array') {
return (
<RenderJSON
isEmpty={value.length === 0 || Object.keys(value).length === 0}
key={`${key}-${keyIndex}`}
object={value}
objectKey={parentType === 'object' ? key : undefined}
parentType={type}
trailingComma={!isLastKey}
/>
)
}
if (
type === 'date' ||
type === 'string' ||
type === 'null' ||
type === 'number' ||
type === 'boolean'
) {
const parentHasKey = Boolean(parentType === 'object' && key)
const rowClasses = [
`${baseClass}__row-line`,
`${baseClass}__value-type--${type}`,
`${baseClass}__row-line--${objectKey ? 'nested' : 'top'}`,
]
.filter(Boolean)
.join(' ')
return (
<li className={rowClasses} key={`${key}-${keyIndex}`}>
{parentHasKey ? <span>{`"${key}": `}</span> : null}
<span className={`${baseClass}__value`}>{JSON.stringify(value)}</span>
{isLastKey ? '' : ','}
</li>
)
}
})}
</ul>
{!isEmpty && (
<span>
<Bracket comma={trailingComma} position="end" type={parentType} />
</span>
)}
</li>
)
}