Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 65 additions & 30 deletions README.md

Large diffs are not rendered by default.

148 changes: 85 additions & 63 deletions demo/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'react-datepicker/dist/react-datepicker.css'

import React, { useEffect, useRef } from 'react'
import { JsonEditor, themes, ThemeName, Theme, assign } from './JsonEditImport'
import { JsonEditor, themes, ThemeName, Theme, FilterFunction } from './JsonEditImport'
import { FaNpm, FaExternalLinkAlt, FaGithub } from 'react-icons/fa'
import { BiReset } from 'react-icons/bi'
import { AiOutlineCloudUpload } from 'react-icons/ai'
Expand Down Expand Up @@ -36,7 +36,6 @@ import { ArrowBackIcon, ArrowForwardIcon } from '@chakra-ui/icons'
import { demoData } from './demoData'
import { useDatabase } from './useDatabase'
import './style.css'
import { FilterFunction } from './json-edit-react/src/types'
import { version } from './version'

function App() {
Expand All @@ -54,6 +53,7 @@ function App() {
const [showIndices, setShowIndices] = useState(true)
const [defaultNewValue, setDefaultNewValue] = useState('New data!')
const [isSaving, setIsSaving] = useState(false)
const [searchText, setSearchText] = useState('')
const previousThemeName = useRef('') // Used when resetting after theme editing
const toast = useToast()

Expand Down Expand Up @@ -105,6 +105,7 @@ function App() {

const handleChangeData = (e) => {
setSelectedData(e.target.value)
setSearchText('')
if (e.target.value === 'editTheme') {
previousThemeName.current = theme as string
setCollapseLevel(demoData.editTheme.collapse as number)
Expand All @@ -121,6 +122,7 @@ function App() {
}

const handleReset = async () => {
setSearchText('')
switch (selectedData) {
case 'editTheme':
reset(themes[previousThemeName.current])
Expand Down Expand Up @@ -202,67 +204,87 @@ function App() {
<Heading size="lg" variant="accent">
Demo
</Heading>
<JsonEditor
data={data}
rootName={rootName}
theme={[theme, demoData[selectedData]?.styles ?? {}]}
indent={indent}
onUpdate={
// ({ newValue }) => {
// if (newValue === 'wrong') return 'NOPE'
// }
demoData[selectedData]?.onUpdate
? demoData[selectedData]?.onUpdate
: ({ newData }) => {
setData(newData)
if (selectedData === 'editTheme') setTheme(newData as ThemeName | Theme)
}
}
onEdit={
demoData[selectedData]?.onEdit
? (data) => {
const updateData = (demoData[selectedData] as any).onEdit(data)
if (updateData) setData(updateData)
}
: undefined
}
onAdd={
demoData[selectedData]?.onAdd
? (data) => {
const updateData = (demoData[selectedData] as any).onAdd(data)
if (updateData) setData(updateData)
}
: undefined
}
collapse={collapseLevel}
showCollectionCount={
showCount === 'Yes' ? true : showCount === 'When closed' ? 'when-closed' : false
}
enableClipboard={
allowCopy
? ({ stringValue, type }) =>
toast({
title: `${type === 'value' ? 'Value' : 'Path'} copied to clipboard:`,
description: truncate(String(stringValue)),
status: 'success',
duration: 5000,
isClosable: true,
})
: false
}
restrictEdit={restrictEdit}
restrictDelete={restrictDelete}
restrictAdd={restrictAdd}
restrictTypeSelection={demoData[selectedData]?.restrictTypeSelection}
keySort={sortKeys}
defaultValue={demoData[selectedData]?.defaultValue ?? defaultNewValue}
showArrayIndices={showIndices}
maxWidth="min(650px, 90vw)"
className="block-shadow"
stringTruncate={90}
customNodeDefinitions={demoData[selectedData]?.customNodeDefinitions}
customText={demoData[selectedData]?.customTextDefinitions}
/>
<Box position="relative">
<Input
placeholder={demoData[selectedData].searchPlaceholder ?? 'Search values'}
bgColor={'#f6f6f6'}
borderColor="gainsboro"
borderRadius={50}
size="sm"
w={60}
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
position="absolute"
right={2}
top={2}
zIndex={100}
/>
<JsonEditor
data={data}
rootName={rootName}
theme={[
theme,
demoData[selectedData]?.styles ?? {},
{ container: { paddingTop: '1em' } },
]}
indent={indent}
onUpdate={
demoData[selectedData]?.onUpdate
? demoData[selectedData]?.onUpdate
: ({ newData }) => {
setData(newData)
if (selectedData === 'editTheme') setTheme(newData as ThemeName | Theme)
}
}
onEdit={
demoData[selectedData]?.onEdit
? (data) => {
const updateData = (demoData[selectedData] as any).onEdit(data)
if (updateData) setData(updateData)
}
: undefined
}
onAdd={
demoData[selectedData]?.onAdd
? (data) => {
const updateData = (demoData[selectedData] as any).onAdd(data)
if (updateData) setData(updateData)
}
: undefined
}
collapse={collapseLevel}
showCollectionCount={
showCount === 'Yes' ? true : showCount === 'When closed' ? 'when-closed' : false
}
enableClipboard={
allowCopy
? ({ stringValue, type }) =>
toast({
title: `${type === 'value' ? 'Value' : 'Path'} copied to clipboard:`,
description: truncate(String(stringValue)),
status: 'success',
duration: 5000,
isClosable: true,
})
: false
}
restrictEdit={restrictEdit}
restrictDelete={restrictDelete}
restrictAdd={restrictAdd}
restrictTypeSelection={demoData[selectedData]?.restrictTypeSelection}
searchFilter={demoData[selectedData]?.searchFilter}
searchText={searchText}
keySort={sortKeys}
defaultValue={demoData[selectedData]?.defaultValue ?? defaultNewValue}
showArrayIndices={showIndices}
minWidth={450}
maxWidth="min(650px, 90vw)"
className="block-shadow"
stringTruncate={90}
customNodeDefinitions={demoData[selectedData]?.customNodeDefinitions}
customText={demoData[selectedData]?.customTextDefinitions}
/>
</Box>
<VStack w="100%" align="flex-end" gap={4}>
<HStack w="100%" justify="space-between" mt={4}>
<Button
Expand Down
6 changes: 4 additions & 2 deletions demo/src/JsonEditImport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import {
FilterFunction,
LinkCustomComponent,
LinkCustomNodeDefinition,
matchNode,
assign,
// } from './json-edit-react/src'
} from 'json-edit-react'
} from './json-edit-react/src'
// } from 'json-edit-react'
// } from './package'

export {
Expand All @@ -27,5 +28,6 @@ export {
type FilterFunction,
LinkCustomComponent,
LinkCustomNodeDefinition,
matchNode,
assign,
}
96 changes: 80 additions & 16 deletions demo/src/demoData/dataDefinitions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import {
CustomTextDefinitions,
LinkCustomNodeDefinition,
assign,
matchNode,
} from '../JsonEditImport'
import {
CollectionKey,
DataType,
DefaultValueFunction,
SearchFilterFunction,
ThemeStyles,
UpdateFunction,
} from '../json-edit-react/src/types'
Expand All @@ -28,6 +30,8 @@ interface DemoData {
restrictDelete?: FilterFunction
restrictAdd?: FilterFunction
restrictTypeSelection?: boolean | DataType[]
searchFilter?: 'key' | 'value' | 'all' | SearchFilterFunction
searchPlaceholder?: string
onUpdate?: UpdateFunction
onAdd?: (props: {
newData: object
Expand Down Expand Up @@ -141,16 +145,36 @@ export const demoData: Record<string, DemoData> = {
to the main "Person" objects.
</Text>
<Text>
Also, notice that when a new item is added at the top level, a correctly structured{' '}
Also, notice that when you add a new item in the top level array, a correctly structured{' '}
"Person" object is added, but adding new items elsewhere adds simple string values. This
is done by specifying a function for the <span className="code">defaultValue</span> prop.
</Text>
<Text>
We've also changed the behaviour of the "Search" input, so that it matches specific people
(on <span className="code">name</span> and <span className="code">username</span>) and
displays all fields associated with the matching people. This is achieved by specifying a
custom{' '}
<Link href="https://github.com/CarlosNZ/json-edit-react#searchfiltering" isExternal>
Search filter function
</Link>
.
</Text>
</Flex>
),
restrictEdit: ({ key, level }) => key === 'id' || level === 0 || level === 1,
restrictAdd: ({ level }) => level === 1,
restrictDelete: ({ key }) => key === 'id',
collapse: 2,
searchFilter: ({ path, fullData }, searchText) => {
if (path?.length >= 2) {
const index = path?.[0]
return (
matchNode({ value: fullData[index].name }, searchText) ||
matchNode({ value: fullData[index].username }, searchText)
)
} else return false
},
searchPlaceholder: 'Search by name or username',
defaultValue: ({ level }) => {
if (level === 0)
return {
Expand Down Expand Up @@ -183,51 +207,70 @@ export const demoData: Record<string, DemoData> = {
vsCode: {
name: '⚙️ VSCode settings file',
description: (
<>
<Flex flexDir="column" gap={2}>
<Text>
A typical{' '}
<Link href="https://code.visualstudio.com/" isExternal>
VSCode
</Link>{' '}
config file.
</Text>
<Text mt={3}>
<Text>
The only restriction here is that you can't set any boolean values to{' '}
<span className="code">false</span>. It uses a custom{' '}
<span className="code">onUpdate</span> function to return an error string when you attempt
to do so, and the value is reset to <span className="code">true</span>.
</Text>
</>
<Text>
Note the "Search" input is configured to filter for object <em>properties</em> rather than{' '}
<em>values</em> (by setting <span className="code">searchFilter: "key"</span>).
</Text>
</Flex>
),
collapse: 2,
data: data.vsCode,
onUpdate: ({ newValue }) => {
if (newValue === false) return "Don't use FALSE, just delete the value"
},
searchFilter: 'key',
searchPlaceholder: 'Search properties',
},
liveData: {
name: '📖 Live Data (from database)',
description: (
<>
<Flex flexDir="column" gap={2}>
<Text>
Here's a live "guestbook" — your changes can be saved permanently to the cloud. However,
there are restrictions:
<UnorderedList>
<ListItem>You can only add new messages, or fields within your message</ListItem>
<ListItem>Only the most recent message is editable, and only for five minutes</ListItem>
</UnorderedList>
</Text>
<Text mt={3}>
<UnorderedList>
<ListItem>
<Text>You can only add new messages, or fields within your message</Text>
</ListItem>
<ListItem>
<Text>Only the most recent message is editable, and only for five minutes</Text>
</ListItem>
</UnorderedList>
<Text>
Notice also (these are achieved by customising the <span className="code">onEdit</span>{' '}
and <span className="code">onAdd</span> props):
<UnorderedList>
<ListItem>
</Text>
<UnorderedList>
<ListItem>
<Text>
The messages list gets sorted so the most recent is at the <em>top</em>
</ListItem>
<ListItem>The timestamps get updated automatically after each edit</ListItem>
</UnorderedList>
</Text>
</ListItem>
<ListItem>
<Text>The timestamps get updated automatically after each edit</Text>
</ListItem>
</UnorderedList>
<Text>
You can also filter full "Message" objects by searching any text value (
<span className="code">message</span>, <span className="code">name</span>,{' '}
<span className="code">from</span>).
</Text>
</>
</Flex>
),
rootName: 'liveData',
collapse: 3,
Expand Down Expand Up @@ -290,6 +333,18 @@ export const demoData: Record<string, DemoData> = {
}
return 'New value'
},
searchFilter: ({ path, fullData }, searchText) => {
if (path?.length >= 2 && path[0] === 'messages') {
const index = path?.[1]
const messages = (fullData as { messages: unknown[] })?.messages
return (
matchNode({ value: messages[index].message }, searchText) ||
matchNode({ value: messages[index].name }, searchText) ||
matchNode({ value: messages[index].from }, searchText)
)
} else return true
},
searchPlaceholder: 'Search guestbook',
data: {},
customNodeDefinitions: [
{
Expand Down Expand Up @@ -337,6 +392,8 @@ export const demoData: Record<string, DemoData> = {
restrictAdd: ({ level }) => level === 0,
restrictTypeSelection: ['string', 'object', 'array'],
collapse: 2,
searchFilter: 'key',
searchPlaceholder: 'Search Theme keys',
data: {},
},
customNodes: {
Expand Down Expand Up @@ -373,6 +430,13 @@ export const demoData: Record<string, DemoData> = {
),
rootName: 'Superheroes',
collapse: 2,
searchFilter: ({ path, fullData }, searchText = '') => {
if (path?.length >= 2) {
const index = path?.[0]
return matchNode({ value: fullData[index].name }, searchText)
} else return false
},
searchPlaceholder: 'Search by character name',
data: data.customNodes,
customNodeDefinitions: [
{
Expand Down
Loading