From 7917ce754b871487090f39a4b26d09a0abc2e8cc Mon Sep 17 00:00:00 2001 From: SteelRazor47 Date: Thu, 21 Aug 2025 17:37:05 +0200 Subject: [PATCH 1/4] enable editor and add back theme option --- core/src/editor/index.tsx | 2 +- www/src/App.tsx | 7 +-- www/src/example/default.tsx | 6 +-- www/src/example/editor.tsx | 88 ++++++++++++++++--------------------- 4 files changed, 47 insertions(+), 56 deletions(-) diff --git a/core/src/editor/index.tsx b/core/src/editor/index.tsx index 5aaf8a98..04d219dc 100644 --- a/core/src/editor/index.tsx +++ b/core/src/editor/index.tsx @@ -25,7 +25,7 @@ const JsonViewEditor = forwardRef>(( return ( - + {editable && } {children} diff --git a/www/src/App.tsx b/www/src/App.tsx index d876d27f..3a37fe01 100644 --- a/www/src/App.tsx +++ b/www/src/App.tsx @@ -1,6 +1,7 @@ import { useState } from 'react'; import styled, { css } from 'styled-components'; import { Example } from './example/default'; +import { ExampleEditor } from './example/editor'; const ExampleWrapper = styled.div` max-width: 630px; @@ -33,12 +34,12 @@ export default function App() { - {/* */} + {tabs === 'preview' && } - {/* {tabs === 'editor' && } */} + {tabs === 'editor' && } ); } diff --git a/www/src/example/default.tsx b/www/src/example/default.tsx index b8a1145a..d5dcb373 100644 --- a/www/src/example/default.tsx +++ b/www/src/example/default.tsx @@ -67,7 +67,7 @@ export const example = { myMap, }; -const Label = styled.label` +export const Label = styled.label` margin-top: 0.83rem; display: block; span { @@ -75,12 +75,12 @@ const Label = styled.label` } `; -const Options = styled.div` +export const Options = styled.div` display: grid; grid-template-columns: 50% 60%; `; -const initialState: Partial< +export const initialState: Partial< JsonViewProps & { quote: string; theme: keyof typeof themesData; diff --git a/www/src/example/editor.tsx b/www/src/example/editor.tsx index 54829a5c..51affa88 100644 --- a/www/src/example/editor.tsx +++ b/www/src/example/editor.tsx @@ -1,54 +1,44 @@ -import React, { useReducer } from 'react'; -// import { styled } from 'styled-components'; -import JsonViewEditor, { JsonViewEditorProps } from '@uiw/react-json-view/editor'; -import { themesData } from './default'; - -export const example = { - string: 'Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet', - // integer: 42, - // float: 114.514, - // bigint: BigInt(10086), - // nan: NaN, - // null: null, - // undefined, - // boolean: true, - // timer: 0, - array: [19, 100.86, 'test', NaN, Infinity], - object2: { - 'first-child': true, - 'second-child': false, - 'last-child': null, - }, -}; -const initialState: Partial< - JsonViewEditorProps & { - quote: string; - theme: keyof typeof themesData; - } -> = { - displayObjectSize: true, - displayDataTypes: true, - enableClipboard: true, - highlightUpdates: true, - objectSortKeys: false, - indentWidth: 15, - collapsed: 2, - quote: '"', - shortenTextAfterLength: 50, - theme: 'nord', -}; +import { useReducer } from 'react'; +import JsonViewEditor from '@uiw/react-json-view/editor'; +import { example, initialState, Label, Options, themesData } from './default'; const reducer = (state: typeof initialState, action: typeof initialState) => ({ ...state, ...action }); -export default function Demo() { - const [state] = useReducer(reducer, initialState); +export function ExampleEditor() { + const [state, dispatch] = useReducer(reducer, initialState); + const themeKeys = Object.keys(themesData) as Array; + return ( - { - console.log(':value:', value); - }} - style={{ ...themesData[state.theme!], padding: 6, borderRadius: 6 }} - /> + <> + { + console.log(':value:', value); + }} + style={{ ...themesData[state.theme!], padding: 6, borderRadius: 6 }} + displayObjectSize={state.displayObjectSize} + displayDataTypes={state.displayDataTypes} + enableClipboard={state.enableClipboard} + highlightUpdates={state.highlightUpdates} + indentWidth={state.indentWidth} + shortenTextAfterLength={state.shortenTextAfterLength} + collapsed={state.collapsed} + objectSortKeys={state.objectSortKeys} > + + + + + + + ); } From c445e249e4886fa94b8e61f74b6d227a84c4a692 Mon Sep 17 00:00:00 2001 From: SteelRazor47 Date: Fri, 22 Aug 2025 17:47:24 +0200 Subject: [PATCH 2/4] add CountInfoExtra icons --- core/src/comps/NestedOpen.tsx | 2 +- core/src/editor/CountInfoExtra.tsx | 50 +++++++++++++++++++++++++++++ core/src/editor/icon/add.tsx | 19 +++++++++++ core/src/editor/icon/delete.tsx | 19 +++++++++++ core/src/editor/icon/edit.tsx | 24 ++++++++++++++ core/src/editor/index.tsx | 2 ++ core/src/section/CountInfoExtra.tsx | 6 ++-- core/src/store/Section.tsx | 1 + www/src/example/editor.tsx | 1 - 9 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 core/src/editor/CountInfoExtra.tsx create mode 100644 core/src/editor/icon/add.tsx create mode 100644 core/src/editor/icon/delete.tsx create mode 100644 core/src/editor/icon/edit.tsx diff --git a/core/src/comps/NestedOpen.tsx b/core/src/comps/NestedOpen.tsx index ae0f866b..e8ca99d5 100644 --- a/core/src/comps/NestedOpen.tsx +++ b/core/src/comps/NestedOpen.tsx @@ -60,7 +60,7 @@ export const NestedOpen = (props: NestedOpenProps) => { - + ); diff --git a/core/src/editor/CountInfoExtra.tsx b/core/src/editor/CountInfoExtra.tsx new file mode 100644 index 00000000..9c7d4f5d --- /dev/null +++ b/core/src/editor/CountInfoExtra.tsx @@ -0,0 +1,50 @@ +import { type SectionElementProps } from '../store/Section'; +import { useStore } from './store'; +import { AddIcon } from './icon/add'; +import { DeleteIcon } from './icon/delete'; +import { useShowToolsStore } from '../store/ShowTools'; + +export const CountInfoExtraRender: SectionElementProps['render'] = ( + { children, ...reset }, + { value, parentValue, keyName, expandKey }, +) => { + const showTools = useShowToolsStore(); + const isShowTools = expandKey && showTools[expandKey]; + if (!isShowTools) return null; + + // const { onEdit } = useStore() + + const click = async (event: React.MouseEvent) => { + event.stopPropagation(); + // const keyOrValue = 'AddKeyOrValue'; + // const isArray = Array.isArray(value); + // const isAdd = isArray ? true : !(keyOrValue in value); + // const result = isArray ? [...value, keyOrValue] : { ...value, [keyOrValue]: undefined }; + // if (onAdd && setValue) { + // const maybeAdd = await onAdd(keyOrValue, result as T, props.value, isAdd); + // if (maybeAdd) { + // setValue!(result as T); + // } + // } + }; + const deleteHandle = async (event: React.MouseEvent) => { + event.stopPropagation(); + // if (onDelete && (keyName || typeof keyName === 'number') && parentValue) { + // const maybeDelete = await onDelete(keyName, value, parentValue as T, { namespace }); + // if (maybeDelete && setParentValue) { + // if (Array.isArray(parentValue)) { + // parentValue.splice(keyName as number, 1); + // setParentValue([...parentValue] as T); + // } else if (keyName in parentValue) { + // delete (parentValue as Record)[keyName as string]; + // setParentValue({ ...parentValue } as T); + // } + // } + // } + }; + return <> + + {parentValue && } + +}; + diff --git a/core/src/editor/icon/add.tsx b/core/src/editor/icon/add.tsx new file mode 100644 index 00000000..8a852fbb --- /dev/null +++ b/core/src/editor/icon/add.tsx @@ -0,0 +1,19 @@ +import { CSSProperties } from 'react'; + +export interface AddIconProps extends React.SVGAttributes {} +export const AddIcon = (props: AddIconProps) => { + const { style } = props + const defaultStyle: CSSProperties = { + verticalAlign: 'middle', + display: 'inline-block', + cursor: 'pointer', + marginLeft: 5, + height: '1em', + width: '1em', + } + return ( + + + + ); +} diff --git a/core/src/editor/icon/delete.tsx b/core/src/editor/icon/delete.tsx new file mode 100644 index 00000000..340e54e8 --- /dev/null +++ b/core/src/editor/icon/delete.tsx @@ -0,0 +1,19 @@ +import { CSSProperties } from 'react'; + +export interface DeleteIconProps extends React.SVGAttributes {} +export const DeleteIcon = (props: DeleteIconProps) => { + const { style } = props + const defaultStyle: CSSProperties = { + verticalAlign: 'middle', + display: 'inline-block', + cursor: 'pointer', + marginLeft: 5, + height: '1em', + width: '1em', + } + return ( + + + + ); +} diff --git a/core/src/editor/icon/edit.tsx b/core/src/editor/icon/edit.tsx new file mode 100644 index 00000000..f5ff572c --- /dev/null +++ b/core/src/editor/icon/edit.tsx @@ -0,0 +1,24 @@ +import { CSSProperties } from 'react'; + +export interface EditIconProps extends React.SVGAttributes {} +export const EditIcon = (props: EditIconProps) => { + const { style } = props + const defaultStyle: CSSProperties = { + verticalAlign: 'middle', + display: 'inline-block', + cursor: 'pointer', + marginLeft: 5, + height: '1em', + width: '1em', + } + return ( + + + + ) +} \ No newline at end of file diff --git a/core/src/editor/index.tsx b/core/src/editor/index.tsx index 04d219dc..96d7773b 100644 --- a/core/src/editor/index.tsx +++ b/core/src/editor/index.tsx @@ -1,6 +1,7 @@ import { forwardRef } from 'react'; import JsonView, { type JsonViewProps } from '../'; import { KeyNameRender } from './KeyName'; +import { CountInfoExtraRender } from './CountInfoExtra'; import { Context, Dispatch, useStoreReducer } from './store'; export interface JsonViewEditorProps extends Omit, 'shortenTextAfterLength'> { @@ -27,6 +28,7 @@ const JsonViewEditor = forwardRef>(( {editable && } + {editable && } {children} diff --git a/core/src/section/CountInfoExtra.tsx b/core/src/section/CountInfoExtra.tsx index dbd73988..98f45450 100644 --- a/core/src/section/CountInfoExtra.tsx +++ b/core/src/section/CountInfoExtra.tsx @@ -12,20 +12,22 @@ CountInfoExtra.displayName = 'JVR.CountInfoExtra'; export interface CountInfoExtraCompsProps { value?: T; + parentValue?: T; keyName: string | number; + expandKey?: string; } export const CountInfoExtraComps = ( props: SectionElementProps & CountInfoExtraCompsProps, ) => { - const { value = {}, keyName, ...other } = props; + const { value = {}, parentValue, keyName, expandKey, ...other } = props; const { CountInfoExtra: Comp = {} } = useSectionStore(); const { as, render, ...reset } = Comp; if (!render && !reset.children) return null; const Elm = as || 'span'; const isRender = render && typeof render === 'function'; const elmProps = { ...reset, ...other }; - const child = isRender && render(elmProps as React.HTMLAttributes, { value, keyName }); + const child = isRender && render(elmProps as React.HTMLAttributes, { value, keyName, parentValue, expandKey }); if (child) return child; return ; }; diff --git a/core/src/store/Section.tsx b/core/src/store/Section.tsx index 0ea0d58a..56625f08 100644 --- a/core/src/store/Section.tsx +++ b/core/src/store/Section.tsx @@ -5,6 +5,7 @@ export interface SectionElementResult { value?: T; parentValue?: T; keyName?: K; + expandKey?: string; /** Index of the parent `keyName` */ keys?: K[]; } diff --git a/www/src/example/editor.tsx b/www/src/example/editor.tsx index 51affa88..c47e6396 100644 --- a/www/src/example/editor.tsx +++ b/www/src/example/editor.tsx @@ -11,7 +11,6 @@ export function ExampleEditor() { <> { console.log(':value:', value); }} From 63c8fc639327d579a91d7aff8a0dd422ca3989e0 Mon Sep 17 00:00:00 2001 From: SteelRazor47 Date: Sat, 23 Aug 2025 16:32:19 +0200 Subject: [PATCH 3/4] add ValueExtra section --- core/src/comps/KeyValues.tsx | 2 ++ core/src/editor/ValueExtra.tsx | 38 +++++++++++++++++++++++++++++++ core/src/editor/index.tsx | 2 ++ core/src/index.tsx | 3 +++ core/src/section/ValueExtra.tsx | 40 +++++++++++++++++++++++++++++++++ core/src/store/Section.tsx | 8 +++++++ 6 files changed, 93 insertions(+) create mode 100644 core/src/editor/ValueExtra.tsx create mode 100644 core/src/section/ValueExtra.tsx diff --git a/core/src/comps/KeyValues.tsx b/core/src/comps/KeyValues.tsx index 47bcc19c..fe89cfeb 100644 --- a/core/src/comps/KeyValues.tsx +++ b/core/src/comps/KeyValues.tsx @@ -11,6 +11,7 @@ import { useHighlight } from '../utils/useHighlight'; import { type SectionElementResult } from '../store/Section'; import { Copied } from '../comps/Copied'; import { useIdCompat } from '../comps/useIdCompat'; +import { ValueExtraComp } from '../section/ValueExtra'; interface KeyValuesProps extends SectionElementResult { expandKey?: string; @@ -118,6 +119,7 @@ export const KeyValuesItem = (props: KeyValuesProps) => { + ); diff --git a/core/src/editor/ValueExtra.tsx b/core/src/editor/ValueExtra.tsx new file mode 100644 index 00000000..1ef7db6c --- /dev/null +++ b/core/src/editor/ValueExtra.tsx @@ -0,0 +1,38 @@ +import { type SectionElementProps } from '../store/Section'; +import { useStore } from './store'; +import { EditIcon } from './icon/edit'; +import { DeleteIcon } from './icon/delete'; +import { useShowToolsStore } from '../store/ShowTools'; + +export const ValueExtraRender: SectionElementProps['render'] = ( + { children, ...reset }, + { value, parentValue, keyName, expandKey }, +) => { + const { onEdit } = useStore(); + + const showTools = useShowToolsStore(); + const isShowTools = expandKey && showTools[expandKey]; + if (!isShowTools) return null; + + const click = (evn: React.MouseEvent) => { + evn.stopPropagation(); + }; + + const deleteHandle = async (evn: React.MouseEvent) => { + evn.stopPropagation(); + // if (data && keyName && keyName in data && setValue && onDelete) { + // const maybeDelete = await onDelete(keyName, value as T, parentValue as T, { namespace }); + // if (maybeDelete) { + // delete (data as Record)[keyName as string]; + // setValue({ ...data } as T); + // } + // } + }; + return <> + {/* {visible && editableValue && onEdit && } + {visible && editableValue && onDelete && } */} + + + ; +}; + diff --git a/core/src/editor/index.tsx b/core/src/editor/index.tsx index 96d7773b..d6aadf45 100644 --- a/core/src/editor/index.tsx +++ b/core/src/editor/index.tsx @@ -3,6 +3,7 @@ import JsonView, { type JsonViewProps } from '../'; import { KeyNameRender } from './KeyName'; import { CountInfoExtraRender } from './CountInfoExtra'; import { Context, Dispatch, useStoreReducer } from './store'; +import { ValueExtraRender } from './ValueExtra'; export interface JsonViewEditorProps extends Omit, 'shortenTextAfterLength'> { /** @@ -29,6 +30,7 @@ const JsonViewEditor = forwardRef>(( {editable && } {editable && } + {editable && } {children} diff --git a/core/src/index.tsx b/core/src/index.tsx index 7122e4df..98bc02d0 100644 --- a/core/src/index.tsx +++ b/core/src/index.tsx @@ -32,6 +32,7 @@ import { CountInfoExtra } from './section/CountInfoExtra'; import { Ellipsis } from './section/Ellipsis'; import { KeyName } from './section/KeyName'; import { Row } from './section/Row'; +import { ValueExtra } from './section/ValueExtra'; export * from './store'; export * from './store/Expands'; @@ -107,6 +108,7 @@ type JsonViewComponent = React.FC>> & { CountInfo: typeof CountInfo; CountInfoExtra: typeof CountInfoExtra; KeyName: typeof KeyName; + ValueExtra: typeof ValueExtra; Row: typeof Row; }; @@ -191,6 +193,7 @@ JsonView.Copied = Copied; JsonView.CountInfo = CountInfo; JsonView.CountInfoExtra = CountInfoExtra; JsonView.KeyName = KeyName; +JsonView.ValueExtra = ValueExtra; JsonView.Row = Row; JsonView.displayName = 'JVR.JsonView'; diff --git a/core/src/section/ValueExtra.tsx b/core/src/section/ValueExtra.tsx new file mode 100644 index 00000000..7ba4e122 --- /dev/null +++ b/core/src/section/ValueExtra.tsx @@ -0,0 +1,40 @@ +import { type TagType } from '../store/Types'; +import { type SectionElement, useSectionStore } from '../store/Section'; +import { useSectionRender } from '../utils/useRender'; +import { type SectionElementResult } from '../store/Section'; + +export const ValueExtra = (props: SectionElement) => { + const { ValueExtra: Comp = {} } = useSectionStore(); + useSectionRender(Comp, props, 'ValueExtra'); + return null; +}; + +ValueExtra.displayName = 'JVR.ValueExtra'; + +export interface ValueExtraCompProps extends React.HTMLAttributes, SectionElementResult { +} + +export const ValueExtraComp = (props: React.PropsWithChildren>) => { + const { children, value, parentValue, keyName, keys, expandKey, ...other } = props; + const { ValueExtra: Comp = {} } = useSectionStore(); + const { as, render, children: _, ...reset } = Comp; + const Elm = as || 'div'; + const child = + render && + typeof render === 'function' && + render({ ...other, ...reset, children }, { value, keyName, parentValue, keys, expandKey }); + if (child) return child; + return ( + + {children} + + ); +}; + +ValueExtraComp.displayName = 'JVR.ValueExtraComp'; + + + + + + diff --git a/core/src/store/Section.tsx b/core/src/store/Section.tsx index 56625f08..f81e1255 100644 --- a/core/src/store/Section.tsx +++ b/core/src/store/Section.tsx @@ -24,6 +24,7 @@ type InitialState = { Ellipsis?: SectionElement; Row?: SectionElement; KeyName?: SectionElement; + ValueExtra?: SectionElement; }; type Dispatch = React.Dispatch>; @@ -72,6 +73,13 @@ const initialState: InitialState = { as: 'span', className: 'w-rjv-object-key', }, + ValueExtra: { + as: 'span', + className: 'w-rjv-object-extra', + style: { + paddingLeft: 8, + }, + }, }; const Context = createContext>(initialState); From c5fed95acf004555fb9b1761de6a490fc8c6b3d7 Mon Sep 17 00:00:00 2001 From: SteelRazor47 Date: Sat, 23 Aug 2025 16:38:13 +0200 Subject: [PATCH 4/4] add editable spans --- core/src/comps/KeyValues.tsx | 19 ++++++++++------- core/src/editor/ValueExtra.tsx | 4 +++- core/src/editor/index.tsx | 38 ++++++++++++++++++++++++++++++++-- core/src/editor/store.tsx | 5 +++++ 4 files changed, 56 insertions(+), 10 deletions(-) diff --git a/core/src/comps/KeyValues.tsx b/core/src/comps/KeyValues.tsx index fe89cfeb..2f05c7b1 100644 --- a/core/src/comps/KeyValues.tsx +++ b/core/src/comps/KeyValues.tsx @@ -1,4 +1,4 @@ -import { Fragment, useRef } from 'react'; +import { Fragment, useRef, useState } from 'react'; import { useStore } from '../store'; import { useExpandsStore } from '../store/Expands'; import { useShowToolsDispatch } from '../store/ShowTools'; @@ -12,6 +12,7 @@ import { type SectionElementResult } from '../store/Section'; import { Copied } from '../comps/Copied'; import { useIdCompat } from '../comps/useIdCompat'; import { ValueExtraComp } from '../section/ValueExtra'; +import { KeyValueItemContext } from '../editor/store'; interface KeyValuesProps extends SectionElementResult { expandKey?: string; @@ -115,13 +116,17 @@ export const KeyValuesItem = (props: KeyValuesProps) => { onMouseEnter: () => dispatch({ [subkeyid]: true }), onMouseLeave: () => dispatch({ [subkeyid]: false }), }; + + const [editable, setEditable] = useState(false) return ( - - - - - - + + + + + + + + ); }; diff --git a/core/src/editor/ValueExtra.tsx b/core/src/editor/ValueExtra.tsx index 1ef7db6c..2af4efbc 100644 --- a/core/src/editor/ValueExtra.tsx +++ b/core/src/editor/ValueExtra.tsx @@ -1,5 +1,5 @@ import { type SectionElementProps } from '../store/Section'; -import { useStore } from './store'; +import { useKeyValueItem, useStore } from './store'; import { EditIcon } from './icon/edit'; import { DeleteIcon } from './icon/delete'; import { useShowToolsStore } from '../store/ShowTools'; @@ -9,6 +9,7 @@ export const ValueExtraRender: SectionElementProps['render'] = ( { value, parentValue, keyName, expandKey }, ) => { const { onEdit } = useStore(); + const { setEditable } = useKeyValueItem() const showTools = useShowToolsStore(); const isShowTools = expandKey && showTools[expandKey]; @@ -16,6 +17,7 @@ export const ValueExtraRender: SectionElementProps['render'] = ( const click = (evn: React.MouseEvent) => { evn.stopPropagation(); + setEditable?.(true); }; const deleteHandle = async (evn: React.MouseEvent) => { diff --git a/core/src/editor/index.tsx b/core/src/editor/index.tsx index d6aadf45..8d5f96e4 100644 --- a/core/src/editor/index.tsx +++ b/core/src/editor/index.tsx @@ -1,8 +1,8 @@ -import { forwardRef } from 'react'; +import { forwardRef, useRef } from 'react'; import JsonView, { type JsonViewProps } from '../'; import { KeyNameRender } from './KeyName'; import { CountInfoExtraRender } from './CountInfoExtra'; -import { Context, Dispatch, useStoreReducer } from './store'; +import { Context, Dispatch, useKeyValueItem, useStore, useStoreReducer } from './store'; import { ValueExtraRender } from './ValueExtra'; export interface JsonViewEditorProps extends Omit, 'shortenTextAfterLength'> { @@ -29,6 +29,16 @@ const JsonViewEditor = forwardRef>(( {editable && } + {editable && } + {editable && } + {editable && } + {editable && } + {editable && } + {editable && } + {editable && } + {editable && } + {editable && } + {editable && } {editable && } {editable && } {children} @@ -39,3 +49,27 @@ const JsonViewEditor = forwardRef>(( }); export default JsonViewEditor; + +const EditableSpan = ({ ...props }: any) => { + const ref = useRef(null) + const { editable, setEditable } = useKeyValueItem() + const { onEdit } = useStore() + if (editable) { + ref.current?.setAttribute("contentEditable", "true") + ref.current?.focus() + } + + const onKeyDown = (evn: React.KeyboardEvent) => { + if (evn.key === 'Enter') { + ref.current?.setAttribute('contentEditable', 'false'); + setEditable(false) + } + }; + + const onBlur = (evn: React.FocusEvent) => { + ref.current?.setAttribute('contentEditable', 'false'); + setEditable(false) + onEdit && onEdit({ value: evn.target.textContent, oldValue: props.children, keyName: "keyName" }); + }; + return {props.children} +} diff --git a/core/src/editor/store.tsx b/core/src/editor/store.tsx index ef3f65ad..2fcd6754 100644 --- a/core/src/editor/store.tsx +++ b/core/src/editor/store.tsx @@ -17,6 +17,7 @@ type Dispatch = React.Dispatch; const initialState: InitialState = {}; export const Context = createContext(initialState); +export const KeyValueItemContext = createContext<{ editable: boolean, setEditable: (b: boolean) => void }>({ editable: false, setEditable: () => { } }); const reducer = (state: InitialState, action: InitialState) => ({ ...state, @@ -30,6 +31,10 @@ export const useStore = () => { return useContext(Context); }; +export const useKeyValueItem = () => { + return useContext(KeyValueItemContext); +}; + export function useStoreReducer(initialState: InitialState) { return useReducer(reducer, initialState); }