diff --git a/examples/basic/pages/index.tsx b/examples/basic/pages/index.tsx index d5a013c6..772fc823 100644 --- a/examples/basic/pages/index.tsx +++ b/examples/basic/pages/index.tsx @@ -1,18 +1,21 @@ -import JsonViewer from '@textea/json-viewer' +import { TextField } from '@mui/material' +import { + applyValue, + JsonViewer, + JsonViewerOnChange +} from '@textea/json-viewer' import type React from 'react' -import { useCallback, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' -import type { InteractionProps } from '../../../src/type' - -function aPlusB (a:number, b: number) { +function aPlusB (a: number, b: number) { return a + b } const example = { - string: 'this is a test string', + string: 'this is a string', integer: 42, - array: [1, 2, 3, 'test', NaN], - float: 3.14159, + array: [19, 19, 810, 'test', NaN], + float: 114.514, undefined, object: { 'first-child': true, @@ -21,17 +24,50 @@ const example = { }, fn: aPlusB, string_number: '1234', + timer: 0, date: new Date('Tue Sep 13 2022 14:07:44 GMT-0500 (Central Daylight Time)') } const IndexPage: React.FC = () => { - const [src, setSrc] = useState(() => example) - const handleEdit = useCallback((update: InteractionProps) => { - setSrc(update.updated_src) + const [indent, setIndent] = useState(2) + const [src, setSrc] = useState(() => example) + useEffect(() => { + const loop = () => { + setSrc(src => ({ + ...src, + timer: src.timer + 1 + })) + } + const id = setInterval(loop, 1000) + return () => { + clearInterval(id) + } }, []) return (
- + { + const indent = parseInt(event.target.value) + if (indent > -1 && indent < 10) { + setIndent(indent) + } + } + } + /> + ( + (path, oldValue, newValue) => { + setSrc(src => applyValue(src, path, newValue)) + }, [] + ) + } + />
) } diff --git a/examples/next/next-env.d.ts b/examples/next/next-env.d.ts deleted file mode 100644 index 4f11a03d..00000000 --- a/examples/next/next-env.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -/// -/// - -// NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/examples/next/next.config.js b/examples/next/next.config.js deleted file mode 100644 index 987cc510..00000000 --- a/examples/next/next.config.js +++ /dev/null @@ -1,15 +0,0 @@ -/** @type {import('next').NextConfig} */ -const nextConfig = { - reactStrictMode: true, - optimizeFonts: true, - experimental: { - externalDir: true - } -} - -const withTM = require('next-transpile-modules')([], { - resolveSymlinks: true, - debug: false -}) - -module.exports = withTM(nextConfig) diff --git a/examples/next/package.json b/examples/next/package.json deleted file mode 100644 index 79b40635..00000000 --- a/examples/next/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "@textea/json-viewer-example-next", - "private": true, - "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start", - "lint": "next lint" - }, - "dependencies": { - "@textea/json-viewer": "workspace:^", - "next": "^12.3.0", - "react": "^18.2.0", - "react-dom": "^18.2.0" - }, - "devDependencies": { - "@textea/dev-kit": "^0.12.16", - "@types/node": "^18.7.17", - "@types/react": "^18.0.19", - "@types/react-dom": "^18.0.6", - "next-transpile-modules": "^9.0.0", - "typescript": "^4.8.3" - } -} diff --git a/examples/next/pages/index.tsx b/examples/next/pages/index.tsx deleted file mode 100644 index 68b441fb..00000000 --- a/examples/next/pages/index.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { TextField } from '@mui/material' -import { unstable_JsonViewer as JsonViewer } from '@textea/json-viewer' -import type React from 'react' -import { useCallback, useState } from 'react' - -import type { InteractionProps } from '../../../src/type' - -function aPlusB (a: number, b: number) { - return a + b -} - -const example = { - string: 'this is a test string', - integer: 42, - array: [1, 2, 3, 'test', NaN], - float: 3.14159, - undefined, - object: { - 'first-child': true, - 'second-child': false, - 'last-child': null - }, - fn: aPlusB, - string_number: '1234', - date: new Date('Tue Sep 13 2022 14:07:44 GMT-0500 (Central Daylight Time)') -} - -const IndexPage: React.FC = () => { - const [indent, setIndent] = useState(2) - const [src, setSrc] = useState(() => example) - const handleEdit = useCallback((update: InteractionProps) => { - setSrc(update.updated_src) - }, []) - return ( -
- { - const indent = parseInt(event.target.value) - if (indent > -1 && indent < 10) { - setIndent(indent) - } - } - } - /> - -
- ) -} - -export default IndexPage diff --git a/examples/next/tsconfig.json b/examples/next/tsconfig.json deleted file mode 100644 index 3c8e48c4..00000000 --- a/examples/next/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extends": "@textea/dev-kit/config/tsconfig.root.json", - "compilerOptions": { - "baseUrl": ".", - "paths": { - "@textea/json-viewer": ["../../src/index"] - }, - "jsx": "preserve", - "jsxImportSource": "react", - "noEmit": true - }, - "include": [ - "next-env.d.ts", - "**/*.ts", - "**/*.tsx" - ], - "exclude": [ - "node_modules" - ] -} diff --git a/package.json b/package.json index c17e8588..77e300a2 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "tree-view", "treeview" ], - "types": "src/type.d.ts", + "types": "dist/src/index.d.ts", "main": "dist/index.js", "module": "dist/index.mjs", "exports": { @@ -35,7 +35,6 @@ } }, "files": [ - "src/**.d.ts", "dist" ], "scripts": { diff --git a/src/components/ArrayGroup.jsx b/src/components/ArrayGroup.jsx deleted file mode 100644 index 5bc054ac..00000000 --- a/src/components/ArrayGroup.jsx +++ /dev/null @@ -1,150 +0,0 @@ -import React from 'react' - -import Theme from './../themes/getStyle' -import ObjectComponent from './DataTypes/Object' -import { ObjectName } from './ObjectName' -// icons -import { CollapsedIcon, ExpandedIcon } from './ToggleIcons' -import VariableMeta from './VariableMeta' - -// single indent is 5px -const SINGLE_INDENT = 5 - -export default class extends React.PureComponent { - constructor (props) { - super(props) - this.state = { - expanded: [] - } - } - - toggleCollapsed = i => { - const newExpanded = [] - for (const j in this.state.expanded) { - newExpanded.push(this.state.expanded[j]) - } - newExpanded[i] = !newExpanded[i] - this.setState({ - expanded: newExpanded - }) - } - - getExpandedIcon (i) { - const { theme, iconStyle } = this.props - - if (this.state.expanded[i]) { - return - } - - return - } - - render () { - const { - src, - groupArraysAfterLength, - depth, - name, - theme, - jsvRoot, - namespace, - parent_type, - ...rest - } = this.props - - let object_padding_left = 0 - - const array_group_padding_left = this.props.indentWidth * SINGLE_INDENT - - if (!jsvRoot) { - object_padding_left = this.props.indentWidth * SINGLE_INDENT - } - - const size = groupArraysAfterLength - const groups = Math.ceil(src.length / size) - - return ( -
- - - - - - {[...Array(groups)].map((_, i) => ( -
- -
{ - this.toggleCollapsed(i) - }} - > - {this.getExpandedIcon(i)} -
- {this.state.expanded[i] - ? ( - - ) - : ( - { - this.toggleCollapsed(i) - }} - className='array-group-brace' - > - [ -
- - {i * size} - {' - '} - {i * size + size > src.length - ? src.length - : i * size + size} - -
- ] -
- )} -
-
- ))} -
- ) - } -} diff --git a/src/components/CopyToClipboard.tsx b/src/components/CopyToClipboard.tsx deleted file mode 100644 index a905b2a4..00000000 --- a/src/components/CopyToClipboard.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import copy from 'copy-to-clipboard' -import React, { useCallback, useEffect, useMemo, useState } from 'react' - -import { toType } from '../helpers/util' -// theme -import Theme from '../themes/getStyle' -// clibboard icon -import { Clippy } from './icons' - -export const CopyToClipboard: React.FC = React.memo((props) => { - const { theme, hidden, rowHovered } = props - - const [copied, setCopied] = useState(false) - useEffect(() => { - if (copied) { - const number = setTimeout(() => { - setCopied(false) - }, 5500) - return () => { - clearTimeout(number) - } - } - return () => {} - }, [copied]) - const handleCopy = useCallback(() => { - const clipboardValue = (value: any) => { - const type = toType(value) - switch (type) { - case 'function': - case 'regexp': - return value.toString() - default: - return value - } - } - const { clickCallback, src, namespace } = props - copy( - JSON.stringify( - clipboardValue(src), - null, - ' ' - ) - ) - setCopied(true) - if (typeof clickCallback === 'function') { - clickCallback({ - src, - namespace, - name: namespace[namespace.length - 1] - }) - } - }, [props]) - const style = useMemo(() => Theme(theme, 'copy-to-clipboard').style, []) - const display = hidden ? 'none' : 'inline' - copied && console.log(copied) - return ( - - - {copied - ? ( - - - - - ) - : ( - - )} - - - ) -}) diff --git a/src/components/DataKeyPair.tsx b/src/components/DataKeyPair.tsx new file mode 100644 index 00000000..0ae64dee --- /dev/null +++ b/src/components/DataKeyPair.tsx @@ -0,0 +1,173 @@ +import CheckIcon from '@mui/icons-material/Check' +import CloseIcon from '@mui/icons-material/Close' +import ContentCopyIcon from '@mui/icons-material/ContentCopy' +import EditIcon from '@mui/icons-material/Edit' +import { Box, styled } from '@mui/material' +import copy from 'copy-to-clipboard' +import type React from 'react' +import { useCallback, useMemo, useState } from 'react' + +import { useTextColor } from '../hooks/useColor' +import { useJsonViewerStore } from '../stores/JsonViewerStore' +import { useTypeComponents } from '../stores/typeRegistry' +import type { DataItemProps } from '../type' +import { DataBox } from './mui/DataBox' + +export type DataKeyPairProps = { + value: unknown + path: string[] +} + +const IconBox = styled(props => )` + cursor: pointer; + padding-left: 0.7rem; +` as typeof Box + +export const DataKeyPair: React.FC = (props) => { + const { value, path } = props + const [tempValue, setTempValue] = useState(value) + const key = path[path.length - 1] + const hoverPath = useJsonViewerStore(store => store.hoverPath) + const isHover = useMemo(() => { + return hoverPath && path.every((value, index) => value === hoverPath[index]) + }, [hoverPath, path]) + const setHover = useJsonViewerStore(store => store.setHover) + const [inspect, setInspect] = useState( + !useJsonViewerStore(store => store.defaultCollapsed) + ) + const [editing, setEditing] = useState(false) + const onChange = useJsonViewerStore(store => store.onChange) + const keyColor = useTextColor() + const numberKeyColor = useJsonViewerStore( + store => store.colorNamespace.base0C) + const { Component, PreComponent, PostComponent, Editor } = useTypeComponents(value) + const rootName = useJsonViewerStore(store => store.rootName) + const isRoot = useJsonViewerStore(store => store.value) === value + const isNumberKey = Number.isInteger(Number(key)) + const displayKey = isRoot ? rootName : key + const downstreamProps: DataItemProps = { + path, + inspect, + setInspect, + value + } + const actionIcons = useMemo(() => { + if (editing) { + return ( + <> + + { + // abort editing + setEditing(false) + setTempValue(value) + }} + /> + + + { + // finish editing, save data + setEditing(false) + onChange(path, value, tempValue) + }} + /> + + + ) + } + return ( + <> + { + copy( + JSON.stringify( + value, + null, + ' ' + ) + ) + event.preventDefault() + }} + > + + + {/* todo: support edit object */} + {Editor && + ( + { + event.preventDefault() + setEditing(true) + }} + > + + + ) + } + + ) + }, [Editor, editing, onChange, path, tempValue, value]) + + const expandable = PreComponent && PostComponent + + return ( + setHover(path), [setHover, path]) + } + > + ) => { + if (event.isDefaultPrevented()) { + return + } + setInspect(state => !state) + }, []) + } + > + {isNumberKey + ? {displayKey} + : <>"{displayKey}" + } + : + {PreComponent && } + {(isHover && expandable && inspect) && actionIcons} + + {editing + ? (Editor && ) + : Component + ? + : {JSON.stringify(value)} + } + {PostComponent && } + {(isHover && expandable && !inspect) && actionIcons} + {(isHover && !expandable) && actionIcons} + + ) +} diff --git a/src/components/DataTypeLabel.tsx b/src/components/DataTypeLabel.tsx new file mode 100644 index 00000000..320f3f78 --- /dev/null +++ b/src/components/DataTypeLabel.tsx @@ -0,0 +1,27 @@ +import type React from 'react' + +import { DataBox } from './mui/DataBox' + +export type DataLabelProps = { + dataType: string + enable?: boolean +} + +export const DataTypeLabel: React.FC = ({ + dataType + , enable = true +}) => { + if (!enable) { + return null + } + return ( + {dataType} + ) +} diff --git a/src/components/DataTypes/Boolean.tsx b/src/components/DataTypes/Boolean.tsx deleted file mode 100644 index d0d8bd74..00000000 --- a/src/components/DataTypes/Boolean.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import type React from 'react' - -import type { DataTypeProps } from '../../types/data-type' -// theme -import Theme from './../../themes/getStyle' -import { DataTypeLabel } from './DataTypeLabel' - -export const JsonBoolean: React.FC> = (props) => { - return ( -
- - {props.value ? 'true' : 'false'} -
- ) -} diff --git a/src/components/DataTypes/DataTypeLabel.tsx b/src/components/DataTypes/DataTypeLabel.tsx deleted file mode 100644 index 922286bd..00000000 --- a/src/components/DataTypes/DataTypeLabel.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type React from 'react' - -import type { DataTypeLabelProps } from '../../types/data-type' -// theme -import Theme from './../../themes/getStyle' - -export const DataTypeLabel: React.FC = ({ - type_name, - displayDataTypes, - theme -}) => { - if (!displayDataTypes) { - return null - } - return ( - - {type_name} - - ) -} diff --git a/src/components/DataTypes/DataTypes.tsx b/src/components/DataTypes/DataTypes.tsx deleted file mode 100644 index 9f6c3bc2..00000000 --- a/src/components/DataTypes/DataTypes.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { JsonBoolean } from './Boolean' -import { JsonDate } from './Date' -import { JsonFloat } from './Float' -import { JsonFunction } from './Function' -import { JsonInteger } from './Integer' -import { JsonNaN } from './NaN' -import { JsonNull } from './Null' -import { JsonRegExp } from './RegExp' -import { JsonString } from './String' -import { JsonUndefined } from './Undefined' - -export const dataTypes = { - string: JsonString, - integer: JsonInteger, - float: JsonFloat, - boolean: JsonBoolean, - function: JsonFunction, - null: JsonNull, - nan: JsonNaN, - undefined: JsonUndefined, - date: JsonDate, - regexp: JsonRegExp - // don't import JsonObject here -} as const diff --git a/src/components/DataTypes/Date.tsx b/src/components/DataTypes/Date.tsx deleted file mode 100644 index b83bc7ac..00000000 --- a/src/components/DataTypes/Date.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import type React from 'react' - -import type { DataTypeProps } from '../../types/data-type' -// theme -import Theme from './../../themes/getStyle' -import { DataTypeLabel } from './DataTypeLabel' - -const display_options = { - weekday: 'short', - year: 'numeric', - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit' -} as const - -export const JsonDate: React.FC> = (props) => { - return ( -
- - - {props.value.toLocaleTimeString('en-us', display_options)} - -
- ) -} diff --git a/src/components/DataTypes/Float.tsx b/src/components/DataTypes/Float.tsx deleted file mode 100644 index 765f00df..00000000 --- a/src/components/DataTypes/Float.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import type React from 'react' - -import type { DataTypeProps } from '../../types/data-type' -// theme -import Theme from './../../themes/getStyle' -import { DataTypeLabel } from './DataTypeLabel' - -export const JsonFloat: React.FC> = (props) => { - return ( -
- - {props.value} -
- ) -} diff --git a/src/components/DataTypes/Function.tsx b/src/components/DataTypes/Function.tsx index 02130fa9..5a339586 100644 --- a/src/components/DataTypes/Function.tsx +++ b/src/components/DataTypes/Function.tsx @@ -1,50 +1,70 @@ -import React, { useCallback, useState } from 'react' - -import type { DataTypeProps } from '../../types/data-type' -// attribute store for storing collapsed state -import AttributeStore from './../../stores/ObjectAttributes' -// theme -import Theme from './../../themes/getStyle' -import { DataTypeLabel } from './DataTypeLabel' - -export const JsonFunction: React.FC> = (props) => { - const [collapsed, setCollapsed] = useState(() => AttributeStore.get( - props.rjvId, - props.namespace, - 'collapsed', - true - )) - const toggleCollapsed = useCallback(() => { - setCollapsed(value => !value) - AttributeStore.set( - props.rjvId, - props.namespace, - 'collapsed', - collapsed - ) - }, [collapsed, props.namespace, props.rjvId]) +import { Box } from '@mui/material' +import type React from 'react' +import { useJsonViewerStore } from '../../stores/JsonViewerStore' +import type { DataItemProps } from '../../type' +import { DataTypeLabel } from '../DataTypeLabel' + +const functionBody = (func: Function) => { + const funcString = func.toString() + + return funcString.substring( + funcString.indexOf('{', funcString.indexOf(')')) + 1, + funcString.lastIndexOf('}') + ) +} + +const functionName = (func: Function) => { + return func.toString() + .slice(9, -1) + .replace(/\{[\s\S]+/, '') +} + +const lb = '{' +const rb = '}' + +export const PreFunctionType: React.FC> = (props) => { + return ( + + + {functionName(props.value)} + {lb} + + ) +} + +export const PostFunctionType: React.FC> = () => { + return ( + + {rb} + + ) +} + +export const FunctionType: React.FC> = (props) => { + const functionColor = useJsonViewerStore(store => store.colorNamespace.base05) return ( -
- - - {collapsed - ? ( - - {props.value.toString() - .slice(9, -1) - .replace(/\{[\s\S]+/, '')}{'{'}...{'}'} - ) - : props.value.toString().slice(9)} - -
+ + {props.inspect + ? functionBody(props.value) + : ( + + ... + + ) + } + ) } diff --git a/src/components/DataTypes/Integer.tsx b/src/components/DataTypes/Integer.tsx deleted file mode 100644 index 4ce08c71..00000000 --- a/src/components/DataTypes/Integer.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import type React from 'react' - -import type { DataTypeProps } from '../../types/data-type' -// theme -import Theme from './../../themes/getStyle' -import { DataTypeLabel } from './DataTypeLabel' - -export const JsonInteger: React.FC> = (props) => { - return ( -
- - {props.value} -
- ) -} diff --git a/src/components/DataTypes/NaN.tsx b/src/components/DataTypes/NaN.tsx deleted file mode 100644 index 7bf392f4..00000000 --- a/src/components/DataTypes/NaN.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import type React from 'react' - -import type { DataTypeProps } from '../../types/data-type' -// theme -import Theme from './../../themes/getStyle' - -export const JsonNaN: React.FC> = (props) => { - return
NaN
-} diff --git a/src/components/DataTypes/Null.tsx b/src/components/DataTypes/Null.tsx deleted file mode 100644 index 69449a93..00000000 --- a/src/components/DataTypes/Null.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import type React from 'react' - -import type { DataTypeProps } from '../../types/data-type' -// theme -import Theme from './../../themes/getStyle' - -export const JsonNull: React.FC> = (props) => { - return
NULL
-} diff --git a/src/components/DataTypes/Object.jsx b/src/components/DataTypes/Object.jsx deleted file mode 100644 index 2a661302..00000000 --- a/src/components/DataTypes/Object.jsx +++ /dev/null @@ -1,314 +0,0 @@ -import React from 'react' - -import { toType } from '../../helpers/util' -import { ObjectName } from '../ObjectName' -// icons -import { CollapsedIcon, ExpandedIcon } from '../ToggleIcons' -// attribute store -import AttributeStore from './../../stores/ObjectAttributes' -// theme -import Theme from './../../themes/getStyle' -import ArrayGroup from './../ArrayGroup' -import VariableEditor from './../VariableEditor' -import VariableMeta from './../VariableMeta' - -const createJsonVariable = (name, value) => ({ - name, - value, - type: toType(value) -}) - -// increment 1 with each nested object & array -const DEPTH_INCREMENT = 1 -// single indent is 5px -const SINGLE_INDENT = 5 - -class RjvObject extends React.PureComponent { - constructor (props) { - super(props) - const state = RjvObject.getState(props) - this.state = { - ...state, - prevProps: {} - } - } - - static getState = props => { - const size = Object.keys(props.src).length - const expanded = - (props.collapsed === false || - (props.collapsed !== true && props.collapsed > props.depth)) && - (!props.shouldCollapse || - props.shouldCollapse({ - name: props.name, - src: props.src, - type: toType(props.src), - namespace: props.namespace - }) === false) && - // initialize closed if object has no items - size !== 0 - const state = { - expanded: AttributeStore.get( - props.rjvId, - props.namespace, - 'expanded', - expanded - ), - object_type: props.type === 'array' ? 'array' : 'object', - parent_type: props.type === 'array' ? 'array' : 'object', - size, - hovered: false - } - return state - } - - static getDerivedStateFromProps (nextProps, prevState) { - const { prevProps } = prevState - if ( - nextProps.src !== prevProps.src || - nextProps.collapsed !== prevProps.collapsed || - nextProps.name !== prevProps.name || - nextProps.namespace !== prevProps.namespace || - nextProps.rjvId !== prevProps.rjvId - ) { - const newState = RjvObject.getState(nextProps) - return { - ...newState, - prevProps: nextProps - } - } - return null - } - - toggleCollapsed = () => { - this.setState( - { - expanded: !this.state.expanded - }, - () => { - AttributeStore.set( - this.props.rjvId, - this.props.namespace, - 'expanded', - this.state.expanded - ) - } - ) - } - - getObjectContent = (depth, src, props) => { - return ( -
-
- {this.renderObjectContents(src, props)} -
-
- ) - } - - getEllipsis = () => { - const { size } = this.state - - if (size === 0) { - // don't render an ellipsis when an object has no items - return null - } else { - return ( -
- ... -
- ) - } - } - - getObjectMetaData = () => { - const { size, hovered } = this.state - return ( - - ) - } - - getBraceStart (object_type, expanded) { - const { src, theme, iconStyle, parent_type } = this.props - - if (parent_type === 'array_group') { - return ( - - - {object_type === 'array' ? '[' : '{'} - - {expanded ? this.getObjectMetaData() : null} - - ) - } - - const IconComponent = expanded ? ExpandedIcon : CollapsedIcon - - return ( - - { - this.toggleCollapsed() - }} - {...Theme(theme, 'brace-row')} - > -
- -
- - - {object_type === 'array' ? '[' : '{'} - -
- {expanded ? this.getObjectMetaData() : null} -
- ) - } - - render () { - // `indentWidth` and `collapsed` props will - // perpetuate to children via `...rest` - const { - depth, - src, - namespace, - name, - type, - parent_type, - theme, - jsvRoot, - iconStyle, - ...rest - } = this.props - - const { object_type, expanded } = this.state - - const styles = {} - if (!jsvRoot && parent_type !== 'array_group') { - styles.paddingLeft = this.props.indentWidth * SINGLE_INDENT - } else if (parent_type === 'array_group') { - styles.borderLeft = 0 - styles.display = 'inline' - } - - return ( -
- this.setState({ ...this.state, hovered: true }) - } - onMouseLeave={() => - this.setState({ ...this.state, hovered: false }) - } - {...Theme(theme, jsvRoot ? 'jsv-root' : 'objectKeyVal', styles)} - > - {this.getBraceStart(object_type, expanded)} - {expanded - ? this.getObjectContent(depth, src, { - theme, - iconStyle, - ...rest - }) - : this.getEllipsis()} - - - {object_type === 'array' ? ']' : '}'} - - {expanded ? null : this.getObjectMetaData()} - -
- ) - } - - renderObjectContents = (variables, props) => { - const { - depth, - parent_type, - index_offset, - groupArraysAfterLength, - namespace - } = this.props - const { object_type } = this.state - const elements = [] - let variable - let keys = Object.keys(variables || {}) - if (this.props.sortKeys && object_type !== 'array') { - keys = keys.sort() - } - - keys.forEach(name => { - variable = createJsonVariable(name, variables[name]) - - if (parent_type === 'array_group' && index_offset) { - variable.name = parseInt(variable.name) + index_offset - } - if (!Object.prototype.hasOwnProperty.call(variables, name)) { - // do nothing - } else if (variable.type === 'object') { - elements.push( - - ) - } else if (variable.type === 'array') { - let ObjectComponent = RjvObject - - if ( - groupArraysAfterLength && - variable.value.length > groupArraysAfterLength - ) { - ObjectComponent = ArrayGroup - } - - elements.push( - - ) - } else { - elements.push( - - ) - } - }) - return elements - } -} - -// export component -export default RjvObject diff --git a/src/components/DataTypes/Object.tsx b/src/components/DataTypes/Object.tsx new file mode 100644 index 00000000..135cc34b --- /dev/null +++ b/src/components/DataTypes/Object.tsx @@ -0,0 +1,91 @@ +import { Box } from '@mui/material' +import type React from 'react' +import { useMemo } from 'react' + +import { useTextColor } from '../../hooks/useColor' +import { useJsonViewerStore } from '../../stores/JsonViewerStore' +import type { DataItemProps } from '../../type' +import { DataKeyPair } from '../DataKeyPair' + +const lb = '{' +const rb = '}' + +export const PreObjectType: React.FC> = (props) => { + const metadataColor = useJsonViewerStore(store => store.colorNamespace.base04) + const sizeOfValue = useMemo( + () => props.inspect ? `${Object.keys(props.value).length} Items` : '', + [props.inspect, props.value] + ) + return ( + + {lb} + + {sizeOfValue} + + + ) +} + +export const PostObjectType: React.FC> = (props) => { + const metadataColor = useJsonViewerStore(store => store.colorNamespace.base04) + const sizeOfValue = useMemo( + () => !props.inspect ? `${Object.keys(props.value).length} Items` : '', + [props.inspect, props.value] + ) + return ( + + {rb} + + {sizeOfValue} + + + ) +} + +export const ObjectType: React.FC> = (props) => { + const keyColor = useTextColor() + return ( + + { + props.inspect + ? ( + Object.entries(props.value).map(([key, value]) => { + const path = [...props.path, key] + return + }) + ) + : ( + + ... + + ) + } + + ) +} diff --git a/src/components/DataTypes/RegExp.tsx b/src/components/DataTypes/RegExp.tsx deleted file mode 100644 index 793360f9..00000000 --- a/src/components/DataTypes/RegExp.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import type React from 'react' - -import type { DataTypeProps } from '../../types/data-type' -// theme -import Theme from './../../themes/getStyle' -import { DataTypeLabel } from './DataTypeLabel' - -export const JsonRegExp: React.FC> = (props) => { - return ( -
- - {props.value.toString()} -
- ) -} diff --git a/src/components/DataTypes/String.tsx b/src/components/DataTypes/String.tsx deleted file mode 100644 index 98ea9eea..00000000 --- a/src/components/DataTypes/String.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import React, { useCallback, useState } from 'react' - -import { toType } from '../../helpers/util' -import type { DataTypeProps } from '../../types/data-type' -// attribute store for storing collapsed state -import AttributeStore from './../../stores/ObjectAttributes' -// theme -import Theme from './../../themes/getStyle' -import { DataTypeLabel } from './DataTypeLabel' - -export const JsonString: React.FC> = (props) => { - const [collapsed, setCollapsed] = useState(() => AttributeStore.get( - props.rjvId, - props.namespace, - 'collapsed', - true - )) - const toggleCollapsed = useCallback(() => { - setCollapsed(value => !value) - AttributeStore.set( - props.rjvId, - props.namespace, - 'collapsed', - collapsed - ) - }, [collapsed, props.namespace, props.rjvId]) - const { collapseStringsAfterLength, theme } = props - let value: string | React.ReactElement = props.value - const collapsible = toType(collapseStringsAfterLength) === 'integer' - const style = { style: { cursor: 'default' } } - - if (collapsible && value.length > Number(collapseStringsAfterLength)) { - style.style.cursor = 'pointer' - if (collapsed) { - value = ( - - {value.substring(0, Number(collapseStringsAfterLength))} - ... - - ) - } - } - - return ( -
- - - "{value}" - -
- ) -} diff --git a/src/components/DataTypes/Undefined.tsx b/src/components/DataTypes/Undefined.tsx deleted file mode 100644 index ca5c55c3..00000000 --- a/src/components/DataTypes/Undefined.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import type React from 'react' - -import type { DataTypeProps } from '../../types/data-type' -// theme -import Theme from './../../themes/getStyle' - -export const JsonUndefined: React.FC> = (props) => { - return (
undefined
) -} diff --git a/src/components/DataTypes/createEasyType.tsx b/src/components/DataTypes/createEasyType.tsx new file mode 100644 index 00000000..1da54a09 --- /dev/null +++ b/src/components/DataTypes/createEasyType.tsx @@ -0,0 +1,78 @@ +import { InputBase } from '@mui/material' +import React, { useCallback } from 'react' + +import type { ColorNamespace } from '../../stores/JsonViewerStore' +import { useJsonViewerStore } from '../../stores/JsonViewerStore' +import type { DataItemProps, DataType, EditorProps } from '../../type' +import { DataTypeLabel } from '../DataTypeLabel' +import { DataBox } from '../mui/DataBox' + +export function createEasyType ( + type: string, + renderValue: React.ComponentType, 'value'>>, + config: { + colorKey: keyof ColorNamespace + fromString?: (value: string) => Value + displayTypeLabel?: boolean + } +): Omit, 'is'> { + const displayTypeLabel = config.displayTypeLabel ?? true + const Render = React.memo(renderValue) + const EasyType: React.FC> = (props) => { + const color = useJsonViewerStore( + store => store.colorNamespace[config.colorKey]) + return ( + + {displayTypeLabel && } + + + + + ) + } + EasyType.displayName = `easy-${type}-type` + if (!config.fromString) { + return { + Component: EasyType + } + } + const fromString = config.fromString + const EasyTypeEditor: React.FC> = ({ value, setValue }) => { + const color = useJsonViewerStore( + store => store.colorNamespace[config.colorKey]) + return ( + >( + (event) => { + const value = fromString(event.target.value) + setValue(value) + }, [setValue] + ) + } + size='small' + multiline + sx={{ + color, + padding: 0.5, + borderStyle: 'solid', + borderColor: 'black', + borderWidth: 1, + fontSize: '0.8rem', + fontFamily: 'monospace', + display: 'inline-flex' + }} + /> + ) + } + EasyTypeEditor.displayName = `easy-${type}-type-editor` + return { + Component: EasyType, + Editor: EasyTypeEditor + } +} diff --git a/src/components/JsonViewer.tsx b/src/components/JsonViewer.tsx deleted file mode 100644 index cbb4c183..00000000 --- a/src/components/JsonViewer.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import type React from 'react' -import { useMemo } from 'react' - -import type { ReactJsonViewProps } from '../type' -import ArrayGroup from './ArrayGroup' -import JsonObject from './DataTypes/Object' - -type JsonViewerProps = ReactJsonViewProps & { - src: object - name: string - theme: any - type: string | undefined - rjvId: string -} - -export const JsonViewer: React.FC = (props) => { - const namespace = [props.name] - const ObjectComponent = useMemo(() => { - if (Array.isArray(props.src) && - props.groupArraysAfterLength && - props.src.length > props.groupArraysAfterLength) { - return ArrayGroup - } else { - return JsonObject - } - }, [props.groupArraysAfterLength, props.src]) - return ( -
-
- -
-
- ) -} diff --git a/src/components/ObjectKeyModal/AddKeyRequest.tsx b/src/components/ObjectKeyModal/AddKeyRequest.tsx deleted file mode 100644 index ea630dba..00000000 --- a/src/components/ObjectKeyModal/AddKeyRequest.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import type React from 'react' -import { useCallback } from 'react' - -import type { ReactJsonViewProps } from '../../type' -import dispatcher from './../../helpers/dispatcher' -import ObjectAttributes from './../../stores/ObjectAttributes' -// global theme -import { ObjectKeyModal } from './ObjectKeyModal' - -type AddKeyRequestProps = { - active: boolean - theme: string - rjvId: string - defaultValue: ReactJsonViewProps['defaultValue'] -} - -// this input appears when adding a new value to an object -export const AddKeyRequest: React.FC = (props) => { - const rjvId = props.rjvId - const isValid = useCallback((input: any) => { - const request = ObjectAttributes.get( - rjvId, - 'action', - 'new-key-request' - ) - return ( - input !== '' && - Object.keys(request.existing_value).indexOf(input) === -1 - ) - }, [rjvId]) - - const submit = useCallback((input: any) => { - const request = ObjectAttributes.get(rjvId, 'action', 'new-key-request') - request.new_value = { ...request.existing_value } - request.new_value[input] = props.defaultValue - dispatcher.dispatch({ - name: 'VARIABLE_ADDED', - rjvId, - data: request - }) - }, [props.defaultValue, rjvId]) - if (!props.active) { - return null - } - - return ( - - ) -} diff --git a/src/components/ObjectKeyModal/ObjectKeyModal.tsx b/src/components/ObjectKeyModal/ObjectKeyModal.tsx deleted file mode 100644 index 91887d69..00000000 --- a/src/components/ObjectKeyModal/ObjectKeyModal.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import React, { useState } from 'react' - -import dispatcher from './../../helpers/dispatcher' -// global theme -import Theme from './../../themes/getStyle' -import { Add as Cancel, CheckCircle } from './../icons' - -// this input appears when adding a new value to an object -export const ObjectKeyModal: React.FC = ({ theme, isValid, rjvId, submit }) => { - const [input, setInput] = useState('') - const closeModal = () => { - dispatcher.dispatch({ - rjvId, - name: 'RESET' - }) - } - - const handleSubmit = () => { - submit(input) - } - - const valid = isValid(input) - return ( -
-
{ - e.stopPropagation() - }} - > -
Key Name:
-
- el && el.focus()} - spellCheck={false} - value={input} - placeholder='...' - onChange={e => { - setInput(e.target.value) - }} - onKeyDown={e => { - if (valid && e.key === 'Enter') { - handleSubmit() - } else if (e.key === 'Escape') { - closeModal() - } - }} - /> - {valid - ? ( - handleSubmit()} - /> - ) - : null} -
- - { - dispatcher.dispatch({ - rjvId, - name: 'RESET' - }) - }} - /> - -
-
- ) -} diff --git a/src/components/ObjectName.tsx b/src/components/ObjectName.tsx deleted file mode 100644 index fb3f497d..00000000 --- a/src/components/ObjectName.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import type React from 'react' - -import Theme from './../themes/getStyle' - -export const ObjectName: React.FC = ({ - parent_type, - namespace, - quotesOnKeys, - theme, - jsvRoot, - name, - displayArrayKey -}) => { - const display_name = name || '' - - if (jsvRoot && (name === false || name === null)) { - return - } else if (parent_type === 'array') { - return displayArrayKey - ? ( - - {display_name} - : - - ) - : () - } else { - return ( - - - {quotesOnKeys && ( - " - )} - {display_name} - {quotesOnKeys && ( - " - )} - - : - - ) - } -} diff --git a/src/components/ToggleIcons.jsx b/src/components/ToggleIcons.jsx deleted file mode 100644 index 026b414e..00000000 --- a/src/components/ToggleIcons.jsx +++ /dev/null @@ -1,65 +0,0 @@ -import React from 'react' - -import Theme from './../themes/getStyle' -import { - ArrowDown, - ArrowRight, - CircleMinus, - CirclePlus, - SquareMinus, - SquarePlus -} from './icons' - -export function ExpandedIcon (props) { - const { theme, iconStyle } = props - switch (iconStyle) { - case 'triangle': - return ( - - ) - case 'square': - return ( - - ) - default: - return ( - - ) - } -} - -export function CollapsedIcon (props) { - const { theme, iconStyle } = props - switch (iconStyle) { - case 'triangle': - return ( - - ) - case 'square': - return ( - - ) - default: - return ( - - ) - } -} diff --git a/src/components/ValidationFailure.tsx b/src/components/ValidationFailure.tsx deleted file mode 100644 index 34148db8..00000000 --- a/src/components/ValidationFailure.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import type React from 'react' - -import dispatcher from './../helpers/dispatcher' -// global theme -import Theme from './../themes/getStyle' -import { Add as Clear } from './icons' - -export type ValidationFailureProps = { - message: string - active: boolean - theme: any - rjvId: string -} - -// this input appears when adding a new value to an object -export const ValidationFailure: React.FC = ({ - message, active, theme, rjvId -}) => { - if (!active) { - return null - } - return ( -
{ - dispatcher.dispatch({ - rjvId, - name: 'RESET' - }) - }} - > - - {message} - - -
- ) -} diff --git a/src/components/VariableEditor.jsx b/src/components/VariableEditor.jsx deleted file mode 100644 index acf986bd..00000000 --- a/src/components/VariableEditor.jsx +++ /dev/null @@ -1,425 +0,0 @@ -import TextareaAutosize from '@mui/material/TextareaAutosize' -import React from 'react' - -import { parseInput } from '../helpers/parseInput' -import { stringifyVariable } from '../helpers/stringifyVariable' -import dispatcher from './../helpers/dispatcher' -// theme -import Theme from './../themes/getStyle' -import { CopyToClipboard } from './CopyToClipboard' -// data type components -import { - dataTypes -} from './DataTypes/DataTypes' -// clipboard icon -import { CheckCircle, Edit, RemoveCircle as Remove } from './icons' - -class VariableEditor extends React.PureComponent { - constructor (props) { - super(props) - this.state = { - editMode: false, - editValue: '', - hovered: false, - renameKey: false, - parsedInput: { - type: false, - value: null - } - } - } - - render () { - const { - variable, - singleIndent, - type, - theme, - namespace, - indentWidth, - enableClipboard, - onEdit, - onDelete, - onSelect, - displayArrayKey, - quotesOnKeys - } = this.props - const { editMode } = this.state - return ( -
- this.setState({ ...this.state, hovered: true }) - } - onMouseLeave={() => - this.setState({ ...this.state, hovered: false }) - } - className='variable-row' - key={variable.name} - > - {type === 'array' - ? ( - displayArrayKey - ? ( - - {variable.name} -
:
-
- ) - : null - ) - : ( - - - {!!quotesOnKeys && ( - " - )} - - {variable.name} - - {!!quotesOnKeys && ( - " - )} - - : - - )} -
{ - const location = [...namespace] - if ( - (e.ctrlKey || e.metaKey) && - onEdit !== false - ) { - this.prepopInput(variable) - } else if (onSelect !== false) { - location.shift() - onSelect({ - ...variable, - namespace: location - }) - } - } - } - {...Theme(theme, 'variableValue', { - cursor: onSelect === false ? 'default' : 'pointer' - })} - > - {this.getValue(variable, editMode)} -
- {enableClipboard - ? ( -
- ) - } - - getEditIcon = () => { - const { variable, theme } = this.props - - return ( -
- { - this.prepopInput(variable) - }} - /> -
- ) - } - - prepopInput = variable => { - if (this.props.onEdit !== false) { - const stringifiedValue = stringifyVariable(variable.value) - const detected = parseInput(stringifiedValue) - this.setState({ - editMode: true, - editValue: stringifiedValue, - parsedInput: { - type: detected.type, - value: detected.value - } - }) - } - } - - getRemoveIcon = () => { - const { variable, namespace, theme, rjvId } = this.props - - return ( -
- { - dispatcher.dispatch({ - name: 'VARIABLE_REMOVED', - rjvId, - data: { - name: variable.name, - namespace, - existing_value: variable.value, - variable_removed: true - } - }) - }} - /> -
- ) - } - - getValue = (variable, editMode) => { - const type = editMode ? false : variable.type - const { props } = this - switch (type) { - case false: - return this.getEditInput() - default: { - const JsonComponent = dataTypes[type] - if (!JsonComponent) { - // catch-all for types that weren't anticipated - return ( -
- {JSON.stringify(variable.value)} -
- ) - } else { - return - } - } - } - } - - getEditInput = () => { - const { theme } = this.props - const { editValue } = this.state - - return ( -
- input && input.focus()} - value={editValue} - className='variable-editor' - onChange={event => { - const value = event.target.value - const detected = parseInput(value) - console.log(2) - this.setState({ - editValue: value, - parsedInput: { - type: detected.type, - value: detected.value - } - }) - }} - onKeyDown={e => { - switch (e.key) { - case 'Escape': { - this.setState({ - editMode: false, - editValue: '' - }) - break - } - case 'Enter': { - if (e.ctrlKey || e.metaKey) { - this.submitEdit(true) - } - break - } - } - e.stopPropagation() - }} - placeholder='update this value' - minRows={2} - {...Theme(theme, 'edit-input')} - /> -
- { - this.setState({ editMode: false, editValue: '' }) - }} - /> - { - this.submitEdit() - }} - /> -
{this.showDetected()}
-
-
- ) - } - - submitEdit = submit_detected => { - const { variable, namespace, rjvId } = this.props - const { editValue, parsedInput } = this.state - let new_value = editValue - if (submit_detected && parsedInput.type) { - new_value = parsedInput.value - } - this.setState({ - editMode: false - }) - dispatcher.dispatch({ - name: 'VARIABLE_UPDATED', - rjvId, - data: { - name: variable.name, - namespace, - existing_value: variable.value, - new_value, - variable_removed: false - } - }) - } - - showDetected = () => { - const { theme } = this.props - const detected = this.getDetectedInput() - if (detected) { - return ( -
-
- {detected} - { - this.submitEdit(true) - }} - /> -
-
- ) - } - } - - getDetectedInput = () => { - const { parsedInput } = this.state - const { type, value } = parsedInput - const { props } = this - const { theme } = props - - if (typeof type === 'string') { - const JsonComponent = dataTypes[type] - switch (type) { - case 'object': - return ( - - - {'{'} - - - ... - - - {'}'} - - - ) - case 'array': - return ( - - - {'['} - - - ... - - - {']'} - - - ) - default: { - return - } - } - } - return null - } -} - -// export component -export default VariableEditor diff --git a/src/components/VariableMeta.jsx b/src/components/VariableMeta.jsx deleted file mode 100644 index 680f7718..00000000 --- a/src/components/VariableMeta.jsx +++ /dev/null @@ -1,143 +0,0 @@ -import React from 'react' - -import { toType } from '../helpers/util' -import dispatcher from './../helpers/dispatcher' -// theme -import Theme from './../themes/getStyle' -import { CopyToClipboard } from './CopyToClipboard' -// icons -import { AddCircle as Add, RemoveCircle as Remove } from './icons' - -export default class extends React.PureComponent { - getObjectSize = () => { - const { size, theme, displayObjectSize } = this.props - if (displayObjectSize) { - return ( - - {size} item{size === 1 ? '' : 's'} - - ) - } - } - - getAddAttribute = rowHovered => { - const { theme, namespace, name, src, rjvId, depth } = this.props - - return ( - - { - const request = { - name: depth > 0 ? name : null, - namespace: namespace.splice( - 0, - namespace.length - 1 - ), - existing_value: src, - variable_removed: false, - key_name: null - } - if (toType(src) === 'object') { - dispatcher.dispatch({ - name: 'ADD_VARIABLE_KEY_REQUEST', - rjvId, - data: request - }) - } else { - dispatcher.dispatch({ - name: 'VARIABLE_ADDED', - rjvId, - data: { - ...request, - new_value: [...src, null] - } - }) - } - }} - /> - - ) - } - - getRemoveObject = rowHovered => { - const { theme, namespace, name, src, rjvId } = this.props - - // don't allow deleting of root node - if (namespace.length === 1) { - return - } - return ( - - { - dispatcher.dispatch({ - name: 'VARIABLE_REMOVED', - rjvId, - data: { - name, - namespace: namespace.splice( - 0, - namespace.length - 1 - ), - existing_value: src, - variable_removed: true - } - }) - }} - /> - - ) - } - - render = () => { - const { - theme, - onDelete, - onAdd, - enableClipboard, - src, - namespace, - rowHovered - } = this.props - return ( -
{ - e.stopPropagation() - }} - > - {/* size badge display */} - {this.getObjectSize()} - {/* copy to clipboard icon */} - {enableClipboard - ? ( - - ) - : null} - {/* copy add/remove icons */} - {onAdd !== false ? this.getAddAttribute(rowHovered) : null} - {onDelete !== false ? this.getRemoveObject(rowHovered) : null} -
- ) - } -} diff --git a/src/components/icons.tsx b/src/components/icons.tsx deleted file mode 100644 index 8bb5b299..00000000 --- a/src/components/icons.tsx +++ /dev/null @@ -1,279 +0,0 @@ -import React from 'react' - -const DEFAULT_COLOR = '#000000' -function getIconStyle (style: any) { - if (!style) { - style = {} - } - return { - style: { - verticalAlign: 'middle', - ...style, - color: style.color ? style.color : DEFAULT_COLOR, - height: '1em', - width: '1em' - } - } -} - -export class CircleMinus extends React.PureComponent { - override render () { - const { props } = this - const { style, ...rest } = props - - return ( - - - - - - ) - } -} - -export class CirclePlus extends React.PureComponent { - override render () { - const { props } = this - const { style, ...rest } = props - - return ( - - - - - - ) - } -} - -export class SquareMinus extends React.PureComponent { - override render () { - const { props } = this - const { style, ...rest } = props - const svgStyle = getIconStyle(style).style - - return ( - - - - - - ) - } -} - -export class SquarePlus extends React.PureComponent { - override render () { - const { props } = this - const { style, ...rest } = props - const svgStyle = getIconStyle(style).style - - return ( - - - - - - ) - } -} - -export class ArrowRight extends React.PureComponent { - override render () { - const { props } = this - const { style, ...rest } = props - - return ( - - - - - - ) - } -} - -export class ArrowDown extends React.PureComponent { - override render () { - const { props } = this - const { style, ...rest } = props - - return ( - - - - - - ) - } -} - -export class Clippy extends React.PureComponent { - override render () { - const { props } = this - const { style, ...rest } = props - - return ( - - - - - - - - ) - } -} - -export class RemoveCircle extends React.PureComponent { - override render () { - const { props } = this - const { style, ...rest } = props - - return ( - - - - - - - - ) - } -} - -export class AddCircle extends React.PureComponent { - override render () { - const { props } = this - const { style, ...rest } = props - - return ( - - - - - - - - ) - } -} - -export class Add extends React.PureComponent { - override render () { - const { props } = this - const { style, ...rest } = props - - return ( - - - - - - - - ) - } -} - -export class Edit extends React.PureComponent { - override render () { - const { props } = this - const { style, ...rest } = props - - return ( - - - - - - - - ) - } -} - -export class CheckCircle extends React.PureComponent { - override render () { - const { props } = this - const { style, ...rest } = props - - return ( - - - - - - - - ) - } -} diff --git a/src/components/mui/DataBox.tsx b/src/components/mui/DataBox.tsx new file mode 100644 index 00000000..4a935b94 --- /dev/null +++ b/src/components/mui/DataBox.tsx @@ -0,0 +1,6 @@ +import { Box, styled } from '@mui/material' +import type React from 'react' + +export const DataBox = styled(props => )` + display: inline-block; +` as typeof Box diff --git a/src/helpers/dispatcher.ts b/src/helpers/dispatcher.ts deleted file mode 100644 index 7773453e..00000000 --- a/src/helpers/dispatcher.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Dispatcher } from 'flux' - -const dispatcher = new Dispatcher() -export default dispatcher diff --git a/src/helpers/parseInput.ts b/src/helpers/parseInput.ts deleted file mode 100644 index 4cd6ebdb..00000000 --- a/src/helpers/parseInput.ts +++ /dev/null @@ -1,78 +0,0 @@ -function formatResponse (type: string, value: any) { - return { - type, - value - } -} - -export function parseInput (input: string) { - // following code is to make the best guess at - // the type for a variable being submitted. - - // we are working with a serialized data representation - input = input.trim() - try { - input = JSON.stringify(JSON.parse(input)) - if (input.startsWith('[') && input.endsWith(']')) { - // array - return formatResponse('array', JSON.parse(input)) - } else if (input.startsWith('{') && input.endsWith('}')) { - // object - return formatResponse('object', JSON.parse(input)) - } else if ( - input.match(/-?\d+\.\d+/) && - input.match(/-?\d+\.\d+/)?.[0] === input - ) { - // float - return formatResponse('float', parseFloat(input)) - } else if ( - input.match(/-?\d+e-\d+/) && - input.match(/-?\d+e-\d+/)?.[0] === input - ) { - // scientific float - return formatResponse('float', Number(input)) - } else if ( - input.match(/-?\d+/) && - input.match(/-?\d+/)?.[0] === input - ) { - // integer - return formatResponse('integer', parseInt(input)) - } else if ( - input.match(/-?\d+e\+\d+/) && - input.match(/-?\d+e\+\d+/)?.[0] === input - ) { - // scientific integer - return formatResponse('integer', Number(input)) - } - } catch (e) { - // no-op - } - - // run in case input was not serializable - input = input.toLowerCase() - switch (input) { - case 'undefined': { - return formatResponse('undefined', undefined) - } - case 'nan': { - return formatResponse('nan', NaN) - } - case 'null': { - return formatResponse('null', null) - } - case 'true': { - return formatResponse('boolean', true) - } - case 'false': { - return formatResponse('boolean', false) - } - default: { - // check to see if this is a date - if (!Number.isNaN(Date.parse(input))) { - return formatResponse('date', new Date(input)) - } - } - } - - return formatResponse('string', input) -} diff --git a/src/helpers/stringifyVariable.ts b/src/helpers/stringifyVariable.ts deleted file mode 100644 index 39e53b54..00000000 --- a/src/helpers/stringifyVariable.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { toType } from './util' - -export const stringifyVariable = (value: any) => { - const type = toType(value) - let string_value - switch (type) { - case 'undefined': { - string_value = 'undefined' - break - } - case 'nan': - string_value = 'NaN' - break - case 'string': - string_value = value - break - case 'date': - string_value = value.toString() - break - case 'function': - string_value = value.toString() - break - case 'regexp': - string_value = value.toString() - break - default: { - try { - string_value = JSON.stringify(value, null, ' ') - } catch (e) { - console.error(`cannot stringify value: ${value}`) - string_value = '' - } - } - } - - return string_value -} diff --git a/src/helpers/util.ts b/src/helpers/util.ts deleted file mode 100644 index 4861352d..00000000 --- a/src/helpers/util.ts +++ /dev/null @@ -1,52 +0,0 @@ -// source: http://stackoverflow.com/questions/7390426/better-way-to-get-type-of-a-javascript-variable/7390612#7390612 -function getType (obj: any) { - return {}.toString.call(obj).match(/\s([a-zA-Z]+)/)?.[1].toLowerCase() -} - -// returns a string "type" of input object -export function toType (obj: any) { - let type = getType(obj) - // some extra disambiguation for numbers - if (type === 'number') { - if (isNaN(obj)) { - type = 'nan' - } else if (Number.isInteger(obj)) { - type = 'integer' - } else { - // bitwise OR produces integers - type = 'float' - } - } - return type -} - -// validation for base-16 themes -export function isTheme (theme: any) { - const theme_keys = [ - 'base00', - 'base01', - 'base02', - 'base03', - 'base04', - 'base05', - 'base06', - 'base07', - 'base08', - 'base09', - 'base0A', - 'base0B', - 'base0C', - 'base0D', - 'base0E', - 'base0F' - ] - if (toType(theme) === 'object') { - for (let i = 0; i < theme_keys.length; i++) { - if (!(theme_keys[i] in theme)) { - return false - } - } - return true - } - return false -} diff --git a/src/hooks/useColor.ts b/src/hooks/useColor.ts new file mode 100644 index 00000000..788894be --- /dev/null +++ b/src/hooks/useColor.ts @@ -0,0 +1,5 @@ +import { useJsonViewerStore } from '../stores/JsonViewerStore' + +export const useTextColor = () => { + return useJsonViewerStore(store => store.colorNamespace.base07) +} diff --git a/src/index.tsx b/src/index.tsx index b4113c20..15af2ae8 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,279 +1,68 @@ -import React from 'react' - -import { JsonViewer } from './components/JsonViewer' -import { AddKeyRequest } from './components/ObjectKeyModal/AddKeyRequest' -import { ValidationFailure } from './components/ValidationFailure' -import { isTheme, toType } from './helpers/util' -import { JsonViewer as unstable_JsonViewer } from './next' -import ObjectAttributes from './stores/ObjectAttributes' -// global theme -import Theme from './themes/getStyle' -import type { ReactJsonViewProps } from './type' - -export { - unstable_JsonViewer -} - -// forward src through to JsonObject component -class ReactJsonView extends React.PureComponent { - constructor (props: ReactJsonViewProps) { - super(props) - this.state = { - // listen to request to add/edit a key to an object - addKeyRequest: false, - editKeyRequest: false, - validationFailure: false, - src: ReactJsonView.defaultProps.src, - name: ReactJsonView.defaultProps.name, - theme: ReactJsonView.defaultProps.theme, - validationMessage: ReactJsonView.defaultProps.validationMessage, - // the state object also needs to remember the prev prop values, because we need to compare - // old and new props in getDerivedStateFromProps(). - prevSrc: ReactJsonView.defaultProps.src, - prevName: ReactJsonView.defaultProps.name, - prevTheme: ReactJsonView.defaultProps.theme - } - } - - // reference id for this instance - rjvId = Date.now().toString() - - // all acceptable props and default values - static defaultProps = { - src: {}, - name: 'root', - theme: 'rjv-default', - collapsed: false, - collapseStringsAfterLength: false, - shouldCollapse: false, - sortKeys: false, - quotesOnKeys: true, - groupArraysAfterLength: 100, - indentWidth: 4, - enableClipboard: true, - displayObjectSize: true, - displayDataTypes: true, - onEdit: false, - onDelete: false, - onAdd: false, - onSelect: false, - iconStyle: 'triangle', - style: {}, - validationMessage: 'Validation Error', - defaultValue: null, - displayArrayKey: true - } - - // will trigger whenever setState() is called, or parent passes in new props. - static getDerivedStateFromProps (nextProps: any, prevState: any) { - if ( - nextProps.src !== prevState.prevSrc || - nextProps.name !== prevState.prevName || - nextProps.theme !== prevState.prevTheme - ) { - // if we pass in new props, we re-validate - const newPartialState = { - src: nextProps.src, - name: nextProps.name, - theme: nextProps.theme, - validationMessage: nextProps.validationMessage, - prevSrc: nextProps.src, - prevName: nextProps.name, - prevTheme: nextProps.theme - } - return ReactJsonView.validateState(newPartialState) - } - return null - } - - override componentDidMount () { - // initialize - ObjectAttributes.set(this.rjvId, 'global', 'src', this.state.src) - // bind to events - const listeners = this.getListeners() - for (const i in listeners) { - // @ts-ignore - ObjectAttributes.on(i + '-' + this.rjvId, listeners[i]) - } - // reset key request to false once it's observed - this.setState({ - addKeyRequest: false, - editKeyRequest: false - }) - } - - override componentDidUpdate (prevProps: any, prevState: any) { - // reset key request to false once it's observed - if (prevState.addKeyRequest !== false) { - this.setState({ - addKeyRequest: false - }) - } - if (prevState.editKeyRequest !== false) { - this.setState({ - editKeyRequest: false - }) - } - if (prevProps.src !== this.state.src) { - ObjectAttributes.set(this.rjvId, 'global', 'src', this.state.src) - } - } - - override componentWillUnmount () { - const listeners = this.getListeners() - for (const i in listeners) { - // @ts-ignore - ObjectAttributes.removeListener(i + '-' + this.rjvId, listeners[i]) - } - } - - getListeners = () => { - return { - reset: this.resetState, - 'variable-update': this.updateSrc, - 'add-key-request': this.addKeyRequest - } - } - - // make sure props are passed in as expected - static validateState = (state: any) => { - const validatedState = {} - // make sure theme is valid - if (toType(state.theme) === 'object' && !isTheme(state.theme)) { - console.error( - 'react-json-view error:', - 'theme prop must be a theme name or valid base-16 theme object.', - 'defaulting to "rjv-default" theme' - ) - // @ts-ignore - validatedState.theme = 'rjv-default' - } - // make sure `src` prop is valid - if (toType(state.src) !== 'object' && toType(state.src) !== 'array') { - console.error( - 'react-json-view error:', - 'src property must be a valid json object' - ) - // @ts-ignore - validatedState.name = 'ERROR' - // @ts-ignore - validatedState.src = { - message: 'src property must be a valid json object' +import { + Box, + createTheme, + ThemeProvider +} from '@mui/material' +import type React from 'react' +import { useCallback, useEffect, useMemo } from 'react' + +import { DataKeyPair } from './components/DataKeyPair' +import { + createJsonViewerStore, + JsonViewerProvider, useJsonViewerStore, + useJsonViewerStoreApi +} from './stores/JsonViewerStore' +import type { JsonViewerProps } from './type' +import { applyValue } from './utils' + +export { applyValue } + +export type JsonViewerOnChange = (path: string[], oldValue: U, newValue: U) => void + +const JsonViewerInner: React.FC = (props) => { + const api = useJsonViewerStoreApi() + useEffect(() => { + api.setState(() => ({ + value: props.value, + indentWidth: props.indentWidth, + defaultCollapsed: props.defaultCollapsed, + onChange: props.onChange + })) + }, [api, props.defaultCollapsed, props.indentWidth, props.onChange, props.value]) + + const value = useJsonViewerStore(store => store.value) + const setHover = useJsonViewerStore(store => store.setHover) + return ( + { + setHover(null) + }, [setHover]) } - } - return { - // get the original state - ...state, - // override the original state - ...validatedState - } - } - - override render () { - const { - validationFailure, - validationMessage, - addKeyRequest, - theme, - src, - name - } = this.state - - const { className, style, defaultValue } = this.props - - return ( -
- - - -
- ) - } - - updateSrc = () => { - const { - name, - namespace, - new_value, - existing_value, - updated_src, - type - } = ObjectAttributes.get(this.rjvId, 'action', 'variable-update') - const { onEdit, onDelete, onAdd } = this.props - - const { src } = this.state - - let result - - const on_edit_payload = { - existing_src: src, - new_value, - updated_src, - name, - namespace, - existing_value - } - - switch (type) { - case 'variable-added': - // @ts-ignore - result = onAdd(on_edit_payload) - break - case 'variable-edited': - // @ts-ignore - result = onEdit(on_edit_payload) - break - case 'variable-removed': - // @ts-ignore - result = onDelete(on_edit_payload) - break - } - - if (result !== false) { - ObjectAttributes.set(this.rjvId, 'global', 'src', updated_src) - this.setState({ - src: updated_src - }) - } else { - this.setState({ - validationFailure: true - }) - } - } - - addKeyRequest = () => { - this.setState({ - addKeyRequest: true - }) - } - - resetState = () => { - this.setState({ - validationFailure: false, - addKeyRequest: false - }) - } + > + [], [])} + /> +
+ ) } -export default ReactJsonView +export const JsonViewer: React.FC = (props) => { + const theme = useMemo(() => createTheme({ + // todo: inject theme based on base16 + }), []) + return ( + + + + + + ) +} diff --git a/src/next.tsx b/src/next.tsx deleted file mode 100644 index cac4ee33..00000000 --- a/src/next.tsx +++ /dev/null @@ -1,251 +0,0 @@ -import ChevronRightIcon from '@mui/icons-material/ChevronRight' -import ExpandMoreIcon from '@mui/icons-material/ExpandMore' -import { TreeItem, TreeItemClassKey, TreeView } from '@mui/lab' -import type { TreeViewClassKey } from '@mui/lab/TreeView/treeViewClasses' -import { - Box, - createTheme, - ThemeProvider -} from '@mui/material' -import type { OverridesStyleRules } from '@mui/material/styles/overrides' -import { DevelopmentError } from '@textea/dev-kit/utils' -import type React from 'react' -import { useCallback, useDebugValue, useEffect, useMemo } from 'react' - -import { - createJsonViewerStore, DEFAULT_INDENT_WIDTH, - JsonViewerProvider, useJsonViewerStore, useJsonViewerStoreApi -} from './stores/JsonViewerStore' -import type { ReactJsonViewProps } from './type' - -declare module '@mui/material/styles' { - export interface Components { - MuiTreeView?: { - styleOverrides?: Partial> - } - MuiTreeItem?: { - styleOverrides?: Partial> - } - } -} - -export type DataProps = { - father: string - isRoot: boolean - value: Data -} - -function getType (value: unknown) { - // todo: enhance this - const type = typeof value - - if (type === 'object') { - if (value instanceof RegExp) { - return 'regexp' - } else if (value instanceof Date) { - return 'date' - } else if (value === null) { - return 'null' - } else if (Array.isArray(value)) { - return 'array' - } - } - return type -} - -const needExpand = (value: unknown): boolean => { - const type = getType(value) - switch (type) { - case 'object': - case 'function': - case 'array': - return true - default: - return false - } -} - -const getEndingClosure = (value: unknown): string => { - const type = getType(value) - switch (type) { - case 'object': - case 'function': - return '}' - case 'array': - return ']' - default: - throw new DevelopmentError() - } -} - -function shortPreviewValue (value: unknown): string { - const type = getType(value) - if (type === 'function') { - return (value as Function).toString().slice(9, -1).replace(/\{[\s\S]+/, '') - } else if (type === 'array') { - return '[...]' - } else if (type === 'object') { - return '{...}' - } else { - return `${value}` - } -} - -function longPreviewValue (value: unknown): string { - const type = getType(value) - if (type === 'function') { - const functionHead = (value as Function).toString() - .slice(9, -1) - .replace(/\{[\s\S]+/, '') - return `${functionHead} {` - } else if (type === 'array') { - return ' [' - } else if (type === 'object') { - return '{' - } else { - return '' - } -} - -function shortPreviewKeyValuePair (key: string, value: unknown): string { - return `${key}: ${shortPreviewValue(value)}` -} - -function longPreviewKeyValuePair (key: string, value: unknown): string { - return `${key}: ${longPreviewValue(value)}` -} - -const ObjectJson: React.FC = ({ - value, - isRoot, - father -}) => { - const type = useMemo(() => getType(value), [value]) - useDebugValue(type, type => `value type: ${type}`) - const expanded = useJsonViewerStore(store => store.expanded) - const setExpanded = useJsonViewerStore(store => store.setExpanded) - const handleToggle = useCallback( - (event: React.SyntheticEvent, nodeIds: string[]) => { - setExpanded(nodeIds) - }, [setExpanded]) - const elements = useMemo(() => { - if (type === 'object') { - if (isRoot) { - return ( - } - defaultExpandIcon={} - expanded={expanded} - onNodeToggle={handleToggle} - > - - - - - ) - } else { - return ( - Object.entries(value as object).map(([key, value]) => { - const path = `${father}${father ? '.' : ''}${key}` - const isExpend = expanded.includes(path) - const shouldExpand = needExpand(value) - if (shouldExpand) { - return ( - - { - shouldExpand - ? ( - - ) - : null - } - {shouldExpand && isExpend && ( - - )} - - ) - } else { - return ( - - ) - } - }) - ) - } - } else { - const path = `${father}${father ? '.' : ''}${value}` - const type = getType(value) - if (type === 'function') { - const entire = (value as Function).toString() - const label = entire.slice(entire.indexOf('{') + 1, - entire.lastIndexOf('}')) - return - } else { - return - } - } - }, [expanded, father, handleToggle, isRoot, type, value]) - return <>{elements} -} - -const JsonViewerInner: React.FC = (props) => { - const api = useJsonViewerStoreApi() - useEffect(() => { - api.setState(state => ({ - ...state, - src: props.src, - indentWidth: props.indentWidth - })) - }, [api, props.src, props.indentWidth]) - // todo: still working on it - return ( - - - - ) -} - -export const JsonViewer: React.FC = (props) => { - const theme = useMemo(() => createTheme({ - components: { - MuiTreeItem: { - styleOverrides: { - group: { - marginLeft: (props.indentWidth ?? DEFAULT_INDENT_WIDTH) * 4 - } - } - } - } - // todo: inject theme based on base16 - }), [props.indentWidth]) - return ( - - - - - - ) -} diff --git a/src/stores/JsonViewerStore.ts b/src/stores/JsonViewerStore.ts index b2e37fdb..1a0f237c 100644 --- a/src/stores/JsonViewerStore.ts +++ b/src/stores/JsonViewerStore.ts @@ -2,19 +2,101 @@ import create from 'zustand' import createContext from 'zustand/context' import { combine } from 'zustand/middleware' -export const DEFAULT_INDENT_WIDTH = 4 +import type { JsonViewerOnChange } from '..' + +export type ColorNamespace = { + base00: string + base01: string + base02: string + base03: string + base04: string + base05: string + base06: string + base07: string + base08: string + base09: string + base0A: string + base0B: string + base0C: string + base0D: string + base0E: string + base0F: string +} + +export const defaultColorNamespace: ColorNamespace = { + base00: 'rgba(0, 0, 0, 0)', + base01: 'rgb(245, 245, 245)', + base02: 'rgb(235, 235, 235)', + base03: '#93a1a1', + base04: 'rgba(0, 0, 0, 0.3)', + base05: '#586e75', + base06: '#073642', + base07: '#002b36', + base08: '#d33682', + base09: '#cb4b16', + base0A: '#dc322f', + base0B: '#859900', + base0C: '#6c71c4', + base0D: '#586e75', + base0E: '#2aa198', + base0F: '#268bd2' +} + +export const darkNamespace: ColorNamespace = { + base00: 'rgba(1, 1, 1, 0)', + base01: 'rgba(1, 1, 1, 0.1)', + base02: 'rgba(0, 0, 0, 0.2)', + base03: 'rgba(1, 1, 1, 0.3)', + base04: 'rgba(0, 0, 0, 0.4)', + base05: 'rgba(1, 1, 1, 0.5)', + base06: 'rgba(1, 1, 1, 0.6)', + base07: 'rgba(1, 1, 1, 0.7)', + base08: 'rgba(1, 1, 1, 0.8)', + base09: 'rgba(1, 1, 1, 0.8)', + base0A: 'rgba(1, 1, 1, 0.8)', + base0B: 'rgba(1, 1, 1, 0.8)', + base0C: 'rgba(1, 1, 1, 0.8)', + base0D: 'rgba(1, 1, 1, 0.8)', + base0E: 'rgba(1, 1, 1, 0.8)', + base0F: 'rgba(1, 1, 1, 0.8)' +} + +export type JsonViewerState = { + hoverPath: string[] | null + defaultCollapsed: number | boolean + colorNamespace: ColorNamespace + expanded: string[] + rootName: string + value: unknown + onChange: JsonViewerOnChange +} + +export type JsonViewerActions = { + setHover: (path: string[] | null) => void +} // todo -export const createJsonViewerStore = () => create(combine({ - expanded: [] as string[], - src: {} as any -}, (set) => ({ - setExpanded: (expanded: string[]) => { - set({ - expanded - }) - } -}))) +export const createJsonViewerStore = () => + create( + combine( + { + hoverPath: null, + rootName: 'root', + defaultCollapsed: false, + colorNamespace: defaultColorNamespace, + expanded: ['data-viewer-root'], + value: {}, + onChange: () => {} + }, + (set) => ({ + setHover: (path) => { + set({ + hoverPath: path + }) + } + }) + ) + ) export type JsonViewerStore = ReturnType export const { diff --git a/src/stores/ObjectAttributes.js b/src/stores/ObjectAttributes.js deleted file mode 100644 index 36c100f8..00000000 --- a/src/stores/ObjectAttributes.js +++ /dev/null @@ -1,128 +0,0 @@ -import { EventEmitter } from 'events' - -import { toType } from '../helpers/util' -import dispatcher from './../helpers/dispatcher' - -// store persistent display attributes for objects and arrays -class ObjectAttributes extends EventEmitter { - objects = {} - - set = (rjvId, name, key, value) => { - if (this.objects[rjvId] === undefined) { - this.objects[rjvId] = {} - } - if (this.objects[rjvId][name] === undefined) { - this.objects[rjvId][name] = {} - } - this.objects[rjvId][name][key] = value - } - - get = (rjvId, name, key, default_value) => { - if ( - this.objects[rjvId] === undefined || - this.objects[rjvId][name] === undefined || - this.objects[rjvId][name][key] == null - ) { - return default_value - } - return this.objects[rjvId][name][key] - } - - handleAction = action => { - const { rjvId, data, name } = action - switch (name) { - case 'RESET': - this.emit('reset-' + rjvId) - break - case 'VARIABLE_UPDATED': - action.data.updated_src = this.updateSrc(rjvId, data) - this.set(rjvId, 'action', 'variable-update', { - ...data, - type: 'variable-edited' - }) - this.emit('variable-update-' + rjvId) - break - case 'VARIABLE_REMOVED': - action.data.updated_src = this.updateSrc(rjvId, data) - this.set(rjvId, 'action', 'variable-update', { - ...data, - type: 'variable-removed' - }) - this.emit('variable-update-' + rjvId) - break - case 'VARIABLE_ADDED': - action.data.updated_src = this.updateSrc(rjvId, data) - this.set(rjvId, 'action', 'variable-update', { - ...data, - type: 'variable-added' - }) - this.emit('variable-update-' + rjvId) - break - case 'ADD_VARIABLE_KEY_REQUEST': - this.set(rjvId, 'action', 'new-key-request', data) - this.emit('add-key-request-' + rjvId) - break - } - } - - updateSrc = (rjvId, request) => { - const { - name, - namespace, - new_value, - existing_value, - variable_removed - } = request - - namespace.shift() - - // deepy copy src - const src = this.get(rjvId, 'global', 'src') - // deep copy of src variable - let updated_src = this.deepCopy(src, [...namespace]) - - // point at current index - let walk = updated_src - for (const idx of namespace) { - walk = walk[idx] - } - - if (variable_removed) { - if (toType(walk) === 'array') { - walk.splice(name, 1) - } else { - delete walk[name] - } - } else { - // update copied variable at specified namespace - if (name !== null) { - walk[name] = new_value - } else { - updated_src = new_value - } - } - - this.set(rjvId, 'global', 'src', updated_src) - - return updated_src - } - - deepCopy = (src, copy_namespace) => { - const type = toType(src) - let result - const idx = copy_namespace.shift() - if (type === 'array') { - result = [...src] - } else if (type === 'object') { - result = { ...src } - } - if (idx !== undefined) { - result[idx] = this.deepCopy(src[idx], copy_namespace) - } - return result - } -} - -const attributeStore = new ObjectAttributes() -dispatcher.register(attributeStore.handleAction.bind(attributeStore)) -export default attributeStore diff --git a/src/stores/typeRegistry.tsx b/src/stores/typeRegistry.tsx new file mode 100644 index 00000000..59809527 --- /dev/null +++ b/src/stores/typeRegistry.tsx @@ -0,0 +1,211 @@ +import { Box } from '@mui/material' +import { DevelopmentError } from '@textea/dev-kit/utils' +import React, { useMemo } from 'react' + +import { createEasyType } from '../components/DataTypes/createEasyType' +import { + FunctionType, PostFunctionType, + PreFunctionType +} from '../components/DataTypes/Function' +import { + ObjectType, + PostObjectType, + PreObjectType +} from '../components/DataTypes/Object' +import type { DataType } from '../type' +import { useJsonViewerStore } from './JsonViewerStore' + +const typeRegistry: DataType[] = [] + +export function registerType (type: DataType) { + typeRegistry.push(type) +} + +export function matchTypeComponents (value: Value): DataType { + for (const T of typeRegistry) { + if (T.is(value)) { + return T + } + } + throw new DevelopmentError('this is not possible') +} + +export function useTypeComponents (value: unknown) { + return useMemo(() => matchTypeComponents(value), [value]) +} + +registerType( + { + is: (value): value is boolean => typeof value === 'boolean', + ...createEasyType( + 'bool', + ({ value }) => <>{value ? 'true' : 'false'}, + { + colorKey: 'base0E', + fromString: value => Boolean(value) + } + ) + } +) + +const displayOptions: Intl.DateTimeFormatOptions = { + weekday: 'short', + year: 'numeric', + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' +} + +registerType( + { + is: (value): value is Date => value instanceof Date, + ...createEasyType( + 'date', + ({ value }) => <>{value.toLocaleTimeString('en-us', displayOptions)}, + { + colorKey: 'base0D' + } + ) + } +) + +registerType( + { + is: (value): value is null => value === null, + ...createEasyType( + 'null', + () => { + const backgroundColor = useJsonViewerStore( + store => store.colorNamespace.base02) + return ( + + NULL + + ) + }, + { colorKey: 'base08', displayTypeLabel: false } + ) + } +) + +registerType( + { + is: (value): value is undefined => value === undefined, + ...createEasyType( + 'undefined', + () => { + const backgroundColor = useJsonViewerStore( + store => store.colorNamespace.base02) + return ( + + undefined + + ) + }, + { + colorKey: 'base05', + displayTypeLabel: false + } + ) + } +) + +registerType( + { + is: (value): value is string => typeof value === 'string', + ...createEasyType( + 'string', + ({ value }) => <>"{`${value}`}", + { + colorKey: 'base09', + fromString: value => value + } + ) + } +) + +registerType( + { + is: (value): value is Function => typeof value === 'function', + Component: FunctionType, + PreComponent: PreFunctionType, + PostComponent: PostFunctionType + } +) + +const isInt = (n: number) => n % 1 === 0 + +registerType( + { + is: (value): value is number => typeof value === 'number' && isNaN(value), + ...createEasyType( + 'NaN', + () => { + const backgroundColor = useJsonViewerStore( + store => store.colorNamespace.base02) + return ( + + NaN + + ) + }, + { + colorKey: 'base08', + displayTypeLabel: false + } + ) + } +) + +registerType( + { + is: (value): value is number => typeof value === 'number' && !isInt(value), + ...createEasyType( + 'float', + ({ value }) => <>{`${value}`}, + { + colorKey: 'base0B', + fromString: value => parseFloat(value) + } + ) + } +) + +registerType( + { + is: (value): value is number => typeof value === 'number' && isInt(value), + ...createEasyType( + 'int', + ({ value }) => <>{`${value}`}, + { + colorKey: 'base0F', + fromString: value => parseInt(value) + } + ) + } +) + +// fallback for all data like 'object' +registerType( + { + is: (value): value is object => typeof value === 'object', + Component: ObjectType, + PreComponent: PreObjectType, + PostComponent: PostObjectType + } +) diff --git a/src/themes/base16/rjv-themes.ts b/src/themes/base16/rjv-themes.ts deleted file mode 100644 index 16c6b5ac..00000000 --- a/src/themes/base16/rjv-themes.ts +++ /dev/null @@ -1,64 +0,0 @@ -export type ColorNamespace = { - scheme: string - author: string - // transparent main background - base00: string - base01: string - base02: string - base03: string - base04: string - base05: string - base06: string - base07: string - base08: string - base09: string - base0A: string - base0B: string - base0C: string - base0D: string - base0E: string - base0F: string -} - -export const rjv_default: ColorNamespace = { - scheme: 'rjv-default', - author: 'mac gainor', - // transparent main background - base00: 'rgba(0, 0, 0, 0)', - base01: 'rgb(245, 245, 245)', - base02: 'rgb(235, 235, 235)', - base03: '#93a1a1', - base04: 'rgba(0, 0, 0, 0.3)', - base05: '#586e75', - base06: '#073642', - base07: '#002b36', - base08: '#d33682', - base09: '#cb4b16', - base0A: '#dc322f', - base0B: '#859900', - base0C: '#6c71c4', - base0D: '#586e75', - base0E: '#2aa198', - base0F: '#268bd2' -} as const - -export const rjv_grey: ColorNamespace = { - scheme: 'rjv-grey', - author: 'mac gainor', - base00: 'rgba(1, 1, 1, 0)', - base01: 'rgba(1, 1, 1, 0.1)', - base02: 'rgba(0, 0, 0, 0.2)', - base03: 'rgba(1, 1, 1, 0.3)', - base04: 'rgba(0, 0, 0, 0.4)', - base05: 'rgba(1, 1, 1, 0.5)', - base06: 'rgba(1, 1, 1, 0.6)', - base07: 'rgba(1, 1, 1, 0.7)', - base08: 'rgba(1, 1, 1, 0.8)', - base09: 'rgba(1, 1, 1, 0.8)', - base0A: 'rgba(1, 1, 1, 0.8)', - base0B: 'rgba(1, 1, 1, 0.8)', - base0C: 'rgba(1, 1, 1, 0.8)', - base0D: 'rgba(1, 1, 1, 0.8)', - base0E: 'rgba(1, 1, 1, 0.8)', - base0F: 'rgba(1, 1, 1, 0.8)' -} as const diff --git a/src/themes/getStyle.ts b/src/themes/getStyle.ts deleted file mode 100644 index 44e1259a..00000000 --- a/src/themes/getStyle.ts +++ /dev/null @@ -1,408 +0,0 @@ -import { createStyling } from 'react-base16-styling' - -import type { ColorMap } from '../types/theme' -import { rjv_default, rjv_grey } from './base16/rjv-themes' -import constants from './styleConstants' - -const colorMap = (theme: any): ColorMap => ({ - backgroundColor: theme.base00, - ellipsisColor: theme.base09, - braceColor: theme.base07, - expandedIcon: theme.base0D, - collapsedIcon: theme.base0E, - keyColor: theme.base07, - arrayKeyColor: theme.base0C, - objectSize: theme.base04, - copyToClipboard: theme.base0F, - copyToClipboardCheck: theme.base0D, - objectBorder: theme.base02, - dataTypes: { - boolean: theme.base0E, - date: theme.base0D, - float: theme.base0B, - function: theme.base0D, - integer: theme.base0F, - string: theme.base09, - nan: theme.base08, - null: theme.base0A, - undefined: theme.base05, - regexp: theme.base0A, - background: theme.base02 - }, - editVariable: { - editIcon: theme.base0E, - cancelIcon: theme.base09, - removeIcon: theme.base09, - addIcon: theme.base0E, - checkIcon: theme.base0E, - background: theme.base01, - color: theme.base0A, - border: theme.base07 - }, - addKeyModal: { - background: theme.base05, - border: theme.base04, - color: theme.base0A, - labelColor: theme.base01 - }, - validationFailure: { - background: theme.base09, - iconColor: theme.base01, - fontColor: theme.base01 - } -}) - -const getDefaultThemeStyling = (theme: any): any => { - const colors = colorMap(theme) - - return { - 'app-container': { - fontFamily: constants.globalFontFamily, - cursor: constants.globalCursor, - backgroundColor: colors.backgroundColor, - position: 'relative' - }, - ellipsis: { - display: 'inline-block', - color: colors.ellipsisColor, - fontSize: constants.ellipsisFontSize, - lineHeight: constants.ellipsisLineHeight, - cursor: constants.ellipsisCursor - }, - 'brace-row': { - display: 'inline-block', - cursor: 'pointer' - }, - brace: { - display: 'inline-block', - cursor: constants.braceCursor, - fontWeight: constants.braceFontWeight, - color: colors.braceColor - }, - 'expanded-icon': { - color: colors.expandedIcon - }, - 'collapsed-icon': { - color: colors.collapsedIcon - }, - colon: { - display: 'inline-block', - margin: constants.keyMargin, - color: colors.keyColor, - verticalAlign: 'top' - }, - objectKeyVal: (component: any, variable_style: any) => { - return { - style: { - paddingTop: constants.keyValPaddingTop, - paddingRight: constants.keyValPaddingRight, - paddingBottom: constants.keyValPaddingBottom, - borderLeft: - constants.keyValBorderLeft + ' ' + colors.objectBorder, - ':hover': { - paddingLeft: variable_style.paddingLeft - 1 + 'px', - borderLeft: - constants.keyValBorderHover + - ' ' + - colors.objectBorder - }, - ...variable_style - } - } - }, - 'pushed-content': { - marginLeft: constants.pushedContentMarginLeft - }, - variableValue: (component: any, variable_style: any) => { - return { - style: { - display: 'inline-block', - paddingRight: constants.variableValuePaddingRight, - position: 'relative', - ...variable_style - } - } - }, - 'object-name': { - display: 'inline-block', - color: colors.keyColor, - letterSpacing: constants.keyLetterSpacing, - fontStyle: constants.keyFontStyle, - verticalAlign: constants.keyVerticalAlign, - opacity: constants.keyOpacity, - ':hover': { - opacity: constants.keyOpacityHover - } - }, - 'array-key': { - display: 'inline-block', - color: colors.arrayKeyColor, - letterSpacing: constants.keyLetterSpacing, - fontStyle: constants.keyFontStyle, - verticalAlign: constants.keyVerticalAlign, - opacity: constants.keyOpacity, - ':hover': { - opacity: constants.keyOpacityHover - } - }, - 'object-size': { - color: colors.objectSize, - borderRadius: constants.objectSizeBorderRadius, - fontStyle: constants.objectSizeFontStyle, - margin: constants.objectSizeMargin, - cursor: 'default' - }, - 'data-type-label': { - fontSize: constants.dataTypeFontSize, - marginRight: constants.dataTypeMarginRight, - opacity: constants.datatypeOpacity - }, - boolean: { - display: 'inline-block', - color: colors.dataTypes.boolean - }, - date: { - display: 'inline-block', - color: colors.dataTypes.date - }, - 'date-value': { - marginLeft: constants.dateValueMarginLeft - }, - float: { - display: 'inline-block', - color: colors.dataTypes.float - }, - function: { - display: 'inline-block', - color: colors.dataTypes.function, - cursor: 'pointer', - whiteSpace: 'pre-line' - }, - 'function-value': { - fontStyle: 'italic' - }, - integer: { - display: 'inline-block', - color: colors.dataTypes.integer - }, - string: { - display: 'inline-block', - color: colors.dataTypes.string - }, - nan: { - display: 'inline-block', - color: colors.dataTypes.nan, - fontSize: constants.nanFontSize, - fontWeight: constants.nanFontWeight, - backgroundColor: colors.dataTypes.background, - padding: constants.nanPadding, - borderRadius: constants.nanBorderRadius - }, - null: { - display: 'inline-block', - color: colors.dataTypes.null, - fontSize: constants.nullFontSize, - fontWeight: constants.nullFontWeight, - backgroundColor: colors.dataTypes.background, - padding: constants.nullPadding, - borderRadius: constants.nullBorderRadius - }, - undefined: { - display: 'inline-block', - color: colors.dataTypes.undefined, - fontSize: constants.undefinedFontSize, - padding: constants.undefinedPadding, - borderRadius: constants.undefinedBorderRadius, - backgroundColor: colors.dataTypes.background - }, - regexp: { - display: 'inline-block', - color: colors.dataTypes.regexp - }, - 'copy-to-clipboard': { - cursor: constants.clipboardCursor - }, - 'copy-icon': { - color: colors.copyToClipboard, - fontSize: constants.iconFontSize, - marginRight: constants.iconMarginRight, - verticalAlign: 'top' - }, - 'copy-icon-copied': { - color: colors.copyToClipboardCheck, - marginLeft: constants.clipboardCheckMarginLeft - }, - 'array-group-meta-data': { - display: 'inline-block', - padding: constants.arrayGroupMetaPadding - }, - 'object-meta-data': { - display: 'inline-block', - padding: constants.metaDataPadding - }, - 'icon-container': { - display: 'inline-block', - width: constants.iconContainerWidth - }, - tooltip: { - padding: constants.tooltipPadding - }, - removeVarIcon: { - verticalAlign: 'top', - display: 'inline-block', - color: colors.editVariable.removeIcon, - cursor: constants.iconCursor, - fontSize: constants.iconFontSize, - marginRight: constants.iconMarginRight - }, - addVarIcon: { - verticalAlign: 'top', - display: 'inline-block', - color: colors.editVariable.addIcon, - cursor: constants.iconCursor, - fontSize: constants.iconFontSize, - marginRight: constants.iconMarginRight - }, - editVarIcon: { - verticalAlign: 'top', - display: 'inline-block', - color: colors.editVariable.editIcon, - cursor: constants.iconCursor, - fontSize: constants.iconFontSize, - marginRight: constants.iconMarginRight - }, - 'edit-icon-container': { - display: 'inline-block', - verticalAlign: 'top' - }, - 'check-icon': { - display: 'inline-block', - cursor: constants.iconCursor, - color: colors.editVariable.checkIcon, - fontSize: constants.iconFontSize, - paddingRight: constants.iconPaddingRight - }, - 'cancel-icon': { - display: 'inline-block', - cursor: constants.iconCursor, - color: colors.editVariable.cancelIcon, - fontSize: constants.iconFontSize, - paddingRight: constants.iconPaddingRight - }, - 'edit-input': { - display: 'inline-block', - minWidth: constants.editInputMinWidth, - borderRadius: constants.editInputBorderRadius, - backgroundColor: colors.editVariable.background, - color: colors.editVariable.color, - padding: constants.editInputPadding, - marginRight: constants.editInputMarginRight, - fontFamily: constants.editInputFontFamily - }, - 'detected-row': { - paddingTop: constants.detectedRowPaddingTop - }, - 'key-modal-request': { - position: constants.addKeyCoverPosition, - top: constants.addKeyCoverPositionPx, - left: constants.addKeyCoverPositionPx, - right: constants.addKeyCoverPositionPx, - bottom: constants.addKeyCoverPositionPx, - backgroundColor: constants.addKeyCoverBackground - }, - 'key-modal': { - width: constants.addKeyModalWidth, - backgroundColor: colors.addKeyModal.background, - marginLeft: constants.addKeyModalMargin, - marginRight: constants.addKeyModalMargin, - padding: constants.addKeyModalPadding, - borderRadius: constants.addKeyModalRadius, - marginTop: '15px', - position: 'relative' - }, - 'key-modal-label': { - color: colors.addKeyModal.labelColor, - marginLeft: '2px', - marginBottom: '5px', - fontSize: '11px' - }, - 'key-modal-input-container': { - overflow: 'hidden' - }, - 'key-modal-input': { - width: '100%', - padding: '3px 6px', - fontFamily: 'monospace', - color: colors.addKeyModal.color, - border: 'none', - boxSizing: 'border-box', - borderRadius: '2px' - }, - 'key-modal-cancel': { - backgroundColor: colors.editVariable.removeIcon, - position: 'absolute', - top: '0px', - right: '0px', - borderRadius: '0px 3px 0px 3px', - cursor: 'pointer' - }, - 'key-modal-cancel-icon': { - color: colors.addKeyModal.labelColor, - fontSize: constants.iconFontSize, - transform: 'rotate(45deg)' - }, - 'key-modal-submit': { - color: colors.editVariable.addIcon, - fontSize: constants.iconFontSize, - position: 'absolute', - right: '2px', - top: '3px', - cursor: 'pointer' - }, - 'function-ellipsis': { - display: 'inline-block', - color: colors.ellipsisColor, - fontSize: constants.ellipsisFontSize, - lineHeight: constants.ellipsisLineHeight, - cursor: constants.ellipsisCursor - }, - 'validation-failure': { - float: 'right', - padding: '3px 6px', - borderRadius: '2px', - cursor: 'pointer', - color: colors.validationFailure.fontColor, - backgroundColor: colors.validationFailure.background - }, - 'validation-failure-label': { - marginRight: '6px' - }, - 'validation-failure-clear': { - position: 'relative', - verticalAlign: 'top', - cursor: 'pointer', - color: colors.validationFailure.iconColor, - fontSize: constants.iconFontSize, - transform: 'rotate(45deg)' - } - } -} - -const getStyle = (theme: any) => { - let rjv_theme = rjv_default - if (theme === false || theme === 'none') { - rjv_theme = rjv_grey - } - - return createStyling(getDefaultThemeStyling, { defaultBase16: rjv_theme })( - theme - ) -} - -export default function style (theme: any, component: any, args?: any) { - if (!theme) { - console.error('theme has not been set') - } - - return getStyle(theme)(component, args) -} diff --git a/src/themes/styleConstants.js b/src/themes/styleConstants.js deleted file mode 100644 index 98c1ba68..00000000 --- a/src/themes/styleConstants.js +++ /dev/null @@ -1,95 +0,0 @@ -export default { - white: '#fff', - black: '#000', - transparent: 'rgba(1, 1, 1, 0)', - - globalFontFamily: 'monospace', - globalCursor: 'default', - - indentBlockWidth: '5px', - - braceFontWeight: 'bold', - braceCursor: 'pointer', - - ellipsisFontSize: '18px', - ellipsisLineHeight: '10px', - ellipsisCursor: 'pointer', - - keyMargin: '0px 5px', - keyLetterSpacing: '0.5px', - keyFontStyle: 'none', - keyBorderRadius: '3px', - keyColonWeight: 'bold', - keyVerticalAlign: 'top', - keyOpacity: '0.85', - keyOpacityHover: '1', - - keyValPaddingTop: '3px', - keyValPaddingBottom: '3px', - keyValPaddingRight: '5px', - keyValBorderLeft: '1px solid', - keyValBorderHover: '2px solid', - keyValPaddingHover: '3px 5px 3px 4px', - - pushedContentMarginLeft: '6px', - - variableValuePaddingRight: '6px', - - nullFontSize: '11px', - nullFontWeight: 'bold', - nullPadding: '1px 2px', - nullBorderRadius: '3px', - - nanFontSize: '11px', - nanFontWeight: 'bold', - nanPadding: '1px 2px', - nanBorderRadius: '3px', - - undefinedFontSize: '11px', - undefinedFontWeight: 'bold', - undefinedPadding: '1px 2px', - undefinedBorderRadius: '3px', - - dataTypeFontSize: '11px', - dataTypeMarginRight: '4px', - datatypeOpacity: '0.8', - - objectSizeBorderRadius: '3px', - objectSizeFontStyle: 'italic', - objectSizeMargin: '0px 6px 0px 0px', - - clipboardCursor: 'pointer', - clipboardCheckMarginLeft: '-12px', - - metaDataPadding: '0px 0px 0px 10px', - - arrayGroupMetaPadding: '0px 0px 0px 4px', - - iconContainerWidth: '17px', - - tooltipPadding: '4px', - - editInputMinWidth: '130px', - editInputBorderRadius: '2px', - editInputPadding: '5px', - editInputMarginRight: '4px', - editInputFontFamily: 'monospace', - - iconCursor: 'pointer', - iconFontSize: '15px', - iconPaddingRight: '1px', - - dateValueMarginLeft: '2px', - - iconMarginRight: '3px', - - detectedRowPaddingTop: '3px', - - addKeyCoverBackground: 'rgba(255, 255, 255, 0.3)', - addKeyCoverPosition: 'absolute', - addKeyCoverPositionPx: '0px', - addKeyModalWidth: '200px', - addKeyModalMargin: 'auto', - addKeyModalPadding: '10px', - addKeyModalRadius: '3px' -} diff --git a/src/type.d.ts b/src/type.d.ts index b4f3736b..ab089bd3 100644 --- a/src/type.d.ts +++ b/src/type.d.ts @@ -1,6 +1,53 @@ +import type { Dispatch, SetStateAction } from 'react' import * as React from 'react' -export interface ReactJsonViewProps { +export interface DataItemProps { + inspect: boolean + setInspect: Dispatch> + value: ValueType + path: string[] +} + +export type EditorProps = { + value: ValueType + setValue: React.Dispatch +} + +export type DataType = { + is: (value: unknown) => value is ValueType + Component: React.ComponentType> + Editor?: React.ComponentType> + PreComponent?: React.ComponentType> + PostComponent?: React.ComponentType> +} + +export type JsonViewerProps = { + /** + * any value + */ + value: T + /** + * indent width for nested objects + */ + indentWidth?: number + /** + * + * @param path path to the target value + * @param oldValue + * @param newValue + */ + onChange?: (path: string[], oldValue: U, newValue: U) => void + /** + * collapsed depth, true for all collapsed, false for all expanded. + * number for depth that default expanded. + * @default false + */ + defaultCollapsed?: boolean | number + className?: string + style?: React.CSSProperties +} + +export interface OldReactJsonViewProps { /** * This property contains your input JSON. * @@ -164,136 +211,3 @@ export interface OnCopyProps { */ name: string | null } - -export interface CollapsedFieldProps { - /** - * The name of the entry. - */ - name: string | null - /** - * The corresponding JSON subtree. - */ - src: object - /** - * The type of src. Can only be "array" or "object". - */ - type: 'array' | 'object' - /** - * The scopes above the current entry. - */ - namespace: Array -} - -export interface InteractionProps { - /** - * The updated subtree of the JSON tree. - */ - updated_src: object - /** - * The existing subtree of the JSON tree. - */ - existing_src: object - /** - * The key of the entry that is interacted with. - */ - name: string | null - /** - * List of keys. - */ - namespace: Array - /** - * The original value of the entry that is interacted with. - */ - existing_value: object | string | number | boolean | null - /** - * The updated value of the entry that is interacted with. - */ - new_value?: object | string | number | boolean | null -} - -export interface OnSelectProps { - /** - * The name of the currently selected entry. - */ - name: string | null - /** - * The value of the currently selected entry. - */ - value: object | string | number | boolean | null - /** - * The type of the value. For "number" type, it will be replaced with the more - * accurate types: "float", "integer", or "nan". - */ - type: string - /** - * List of keys representing the scopes above the selected entry. - */ - namespace: Array - -} - -export type TypeDefaultValue = string | number | boolean | object; - -export interface ThemeObject { - base00: string - base01: string - base02: string - base03: string - base04: string - base05: string - base06: string - base07: string - base08: string - base09: string - base0A: string - base0B: string - base0C: string - base0D: string - base0E: string - base0F: string -} - -export type ThemeKeys = - | 'apathy' - | 'apathy:inverted' - | 'ashes' - | 'bespin' - | 'brewer' - | 'bright:inverted' - | 'bright' - | 'chalk' - | 'codeschool' - | 'colors' - | 'eighties' - | 'embers' - | 'flat' - | 'google' - | 'grayscale' - | 'grayscale:inverted' - | 'greenscreen' - | 'harmonic' - | 'hopscotch' - | 'isotope' - | 'marrakesh' - | 'mocha' - | 'monokai' - | 'ocean' - | 'paraiso' - | 'pop' - | 'railscasts' - | 'rjv-default' - | 'shapeshifter' - | 'shapeshifter:inverted' - | 'solarized' - | 'summerfruit' - | 'summerfruit:inverted' - | 'threezerotwofour' - | 'tomorrow' - | 'tube' - | 'twilight'; - -declare const JsonViewer: React.ComponentType -export default JsonViewer - -// unstable components -export const unstable_JsonViewer: React.FC diff --git a/src/types/data-type.ts b/src/types/data-type.ts deleted file mode 100644 index 25cdc1a4..00000000 --- a/src/types/data-type.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { ReactJsonViewProps } from '../type' - -type TODO = any -export type Theme = TODO - -export type DataTypeLabelProps = { - type_name: string - displayDataTypes: boolean - theme: Theme -} - -export type DataTypeProps = DataTypeLabelProps & { - rjvId: string - namespace: string - collapseStringsAfterLength: ReactJsonViewProps['collapseStringsAfterLength'] - theme: Theme - value: Value -} diff --git a/src/types/theme.ts b/src/types/theme.ts deleted file mode 100644 index a1ca99a3..00000000 --- a/src/types/theme.ts +++ /dev/null @@ -1,47 +0,0 @@ -export interface ColorMap { - backgroundColor: string - ellipsisColor: string - braceColor: string - expandedIcon: string - collapsedIcon: string - keyColor: string - arrayKeyColor: string - objectSize: string - copyToClipboard: string - copyToClipboardCheck: string - objectBorder: string - dataTypes: { - boolean: string - date: string - float: string - function: string - integer: string - string: string - nan: string - null: string - undefined: string - regexp: string - background: string - } - editVariable: { - editIcon: string - cancelIcon: string - removeIcon: string - addIcon: string - checkIcon: string - background: string - color: string - border: string - } - addKeyModal: { - background: string - border: string - color: string - labelColor: string - } - validationFailure: { - background: string - iconColor: string - fontColor: string - } -} diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 00000000..5cb649f0 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,18 @@ +export const applyValue = (obj: any, path: string[], value: any) => { + let arr + let key + if (!obj || typeof obj !== 'object') { + obj = {} + } + if (path.length > 0) { + arr = path + key = arr[0] + if (arr.length > 1) { + arr.shift() + obj[key] = applyValue(obj[key], arr, value) + } else { + obj[key] = value + } + } + return obj +} diff --git a/vite.config.ts b/vite.config.ts index 2832a266..51ac3d66 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,5 +1,6 @@ import react from '@vitejs/plugin-react' import { resolve } from 'path' +import dts from 'vite-plugin-dts' import { defineConfig } from 'vitest/config' import { peerDependencies } from './package.json' @@ -25,11 +26,7 @@ export default defineConfig({ } }, plugins: [ - react() - // dts({ - // outputDir: ['dist'], - // insertTypesEntry: true, - // staticImport: true - // }) + react(), + dts() ] }) diff --git a/yarn.lock b/yarn.lock index 379ad101..5e0905f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1615,23 +1615,6 @@ __metadata: languageName: unknown linkType: soft -"@textea/json-viewer-example-next@workspace:examples/next": - version: 0.0.0-use.local - resolution: "@textea/json-viewer-example-next@workspace:examples/next" - dependencies: - "@textea/dev-kit": ^0.12.16 - "@textea/json-viewer": "workspace:^" - "@types/node": ^18.7.17 - "@types/react": ^18.0.19 - "@types/react-dom": ^18.0.6 - next: ^12.3.0 - next-transpile-modules: ^9.0.0 - react: ^18.2.0 - react-dom: ^18.2.0 - typescript: ^4.8.3 - languageName: unknown - linkType: soft - "@textea/json-viewer@workspace:., @textea/json-viewer@workspace:^": version: 0.0.0-use.local resolution: "@textea/json-viewer@workspace:."