Skip to content

Commit 7c6634b

Browse files
authored
Add Image custom component (#207)
* Add Image custom component
1 parent 10eec71 commit 7c6634b

File tree

10 files changed

+107
-21
lines changed

10 files changed

+107
-21
lines changed

custom-component-library/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ These are the ones currently available:
3030
- [x] `BigInt`
3131
- [x] Markdown
3232
- [x] "Enhanced" link
33-
- [ ] Image (to-do)
33+
- [x] Image
34+
- [ ] Colour picker (to-do)
3435

3536
## Development
3637

@@ -59,7 +60,7 @@ Custom components should consider the following:
5960
- `Tab`/`Shift-Tab` to navigate
6061
- `Enter` to submit
6162
- `Escape` to cancel
62-
- Provide customisation options, particularly styles
63+
- Provide customisation options, particularly styles (but make sure to specify defaults)
6364
- If the data contains non-JSON types, add a "stringify" and "reviver" function definition (see `BigInt`, `NaN` and `Symbol` components)
6465

6566
If your custom component is "string-like", there are two helper components exported with the package: `StringDisplay` and `StringEdit` -- these are the same components used for the actual "string" elements in the main package. See the [Hyperlink](https://github.com/CarlosNZ/json-edit-react/blob/main/custom-component-library/components/Hyperlink/component.tsx) and [BigInt](https://github.com/CarlosNZ/json-edit-react/blob/main/custom-component-library/components/BigInt/component.tsx) components for examples of how to use them.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* An Image display Custom Component
3+
*/
4+
5+
import React from 'react'
6+
import { type CustomNodeProps } from '@json-edit-react'
7+
8+
export interface ImageProps {
9+
imageStyles?: React.CSSProperties
10+
altText?: string
11+
}
12+
13+
export const ImageComponent: React.FC<CustomNodeProps<ImageProps>> = (props) => {
14+
const { value, customNodeProps = {} } = props
15+
16+
const { imageStyles = { maxWidth: 200, maxHeight: 200 }, altText = value as string } =
17+
customNodeProps
18+
19+
return (
20+
<a href={value as string} target="_blank" rel="noreferrer">
21+
<img src={value as string} title={altText} style={imageStyles} alt={altText} />
22+
</a>
23+
)
24+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { type CustomNodeDefinition } from '@json-edit-react'
2+
import { ImageComponent, ImageProps } from './component'
3+
4+
const imageLinkRegex = /^https?:\/\/[^\s]+?\.(?:jpe?g|png|svg|gif)/i
5+
6+
export const ImageNodeDefinition: CustomNodeDefinition<ImageProps> = {
7+
condition: ({ value }) => typeof value === 'string' && imageLinkRegex.test(value),
8+
element: ImageComponent,
9+
// customNodeProps: {},
10+
showOnView: true,
11+
showOnEdit: false,
12+
name: 'Image',
13+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './definition'

custom-component-library/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ export * from './NaN'
88
export * from './Symbol'
99
export * from './BigInt'
1010
export * from './Markdown'
11+
export * from './Image'

custom-component-library/src/App.tsx

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ import {
1313
BigIntDefinition,
1414
MarkdownNodeDefinition,
1515
EnhancedLinkCustomNodeDefinition,
16+
ImageNodeDefinition,
1617
} from '../components'
1718
import { testData } from './data'
18-
import { JsonData, JsonEditor } from '@json-edit-react'
19+
import { JsonEditor } from '@json-edit-react'
1920

2021
if (testData?.['Date & Time']) {
2122
// @ts-expect-error redefine after initialisation
@@ -33,25 +34,32 @@ if (testData?.['Date & Time']) {
3334
type TestData = typeof testData
3435

3536
function App() {
36-
const [data, setData] = useState<JsonData>(testData)
37+
const [data, setData] = useState<TestData>(testData)
3738

3839
console.log('Current data', data)
3940

41+
// Properties that are conditional on some data property:
42+
43+
// Display the time depending on whether or not the "Show time" toggle is
44+
// checked
45+
const showTime = data?.['Date & Time']?.['Show Time in Date?'] ?? false
46+
47+
// Image sizing
48+
const maxWidth = data?.Images?.['Image properties']?.maxWidth
49+
const maxHeight = data?.Images?.['Image properties']?.maxHeight
50+
4051
return (
4152
<div id="container">
4253
<JsonEditor
4354
// restrictEdit={true}
4455
data={data}
45-
setData={setData}
56+
setData={setData as (data: unknown) => void}
4657
customNodeDefinitions={[
58+
{ ...ImageNodeDefinition, customNodeProps: { imageStyles: { maxWidth, maxHeight } } },
4759
LinkCustomNodeDefinition,
4860
{
4961
...(STORE_DATE_AS_DATE_OBJECT ? DateObjectDefinition : DatePickerDefinition),
50-
customNodeProps: {
51-
// Display the time depending on whether or not the "Show time"
52-
// toggle is checked
53-
showTime: (data as TestData)?.['Date & Time']?.['Show Time in Date?'] ?? false,
54-
},
62+
customNodeProps: { showTime },
5563
},
5664
EnhancedLinkCustomNodeDefinition,
5765
UndefinedDefinition,

custom-component-library/src/data.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export const testData = {
1919
- BooleanToggle
2020
- NaN
2121
- Symbol
22+
- Image
2223
2324
Click [here](https://github.com/CarlosNZ/json-edit-react/blob/main/custom-component-library/README.md) for more info
2425
`,
@@ -48,4 +49,13 @@ export const testData = {
4849
},
4950
Markdown:
5051
'Uses [react-markdown](https://www.npmjs.com/package/react-markdown) to render **Markdown** *text content*. ',
52+
Images: {
53+
JPG: 'https://film-grab.com/wp-content/uploads/2014/07/51.jpg',
54+
PNG: 'https://github.com/CarlosNZ/json-edit-react/blob/main/image/logo192.png?raw=true',
55+
GIF: 'https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExdnV0aHB0c2xiMHFmdGY3Z2NkenBkb3Rmd3hvdTlkaTlkNGYxOXFtOSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/9E7kUhnT9eDok/giphy.gif',
56+
'Image properties': {
57+
maxWidth: 200,
58+
maxHeight: 100,
59+
},
60+
},
5161
}

demo/src/App.tsx

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,9 @@ import { ArrowBackIcon, ArrowForwardIcon, InfoIcon } from '@chakra-ui/icons'
4646
import { demoDataDefinitions } from './demoData'
4747
import { useDatabase } from './useDatabase'
4848
import './style.css'
49-
import { getLineHeight, truncate } from './helpers'
49+
import { getConditionalDefinitions, getLineHeight, truncate } from './helpers'
5050
import { Loading } from '../../custom-component-library/components/_common/Loading'
51+
import { testData } from '../../custom-component-library/src/data'
5152

5253
const CodeEditor = lazy(() => import('./CodeEditor'))
5354
const SourceIndicator = lazy(() => import('./SourceIndicator'))
@@ -155,14 +156,11 @@ function App() {
155156
// }, [])
156157

157158
const customNodeDefinitions =
158-
selectedDataSet === 'customComponentLibrary' &&
159-
typeof data === 'object' &&
160-
(data as Record<string, Record<string, unknown>>)?.['Date & Time']?.['Show Time in Date?'] &&
161-
Array.isArray(dataDefinition.customNodeDefinitions)
162-
? [
163-
{ ...dataDefinition.customNodeDefinitions[0], customNodeProps: { showTime: true } },
164-
...dataDefinition.customNodeDefinitions.slice(1),
165-
]
159+
selectedDataSet === 'customComponentLibrary'
160+
? getConditionalDefinitions(
161+
data as typeof testData,
162+
dataDefinition?.customNodeDefinitions ?? []
163+
)
166164
: dataDefinition.customNodeDefinitions
167165

168166
const updateState = (patch: Partial<AppState>) => setState({ ...state, ...patch })

demo/src/demoData/dataDefinitions.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
BigIntDefinition,
1313
MarkdownNodeDefinition,
1414
EnhancedLinkCustomNodeDefinition,
15+
ImageNodeDefinition,
1516
} from '../../../custom-component-library/components'
1617
import { testData } from '../../../custom-component-library/src/data'
1718
import {
@@ -831,14 +832,15 @@ export const demoDataDefinitions: Record<string, DemoData> = {
831832
</Flex>
832833
),
833834
rootName: 'components',
834-
collapse: 2,
835+
collapse: 3,
835836
data: testData,
836837
customNodeDefinitions: [
837838
// Must keep this one first as we override it by index in App.tsx
838839
{
839840
...DateObjectDefinition,
840841
customNodeProps: { showTime: false },
841842
},
843+
ImageNodeDefinition,
842844
LinkCustomNodeDefinition,
843845
EnhancedLinkCustomNodeDefinition,
844846
UndefinedDefinition,

demo/src/helpers.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { JsonData } from '@json-edit-react'
1+
import { JsonData, type CustomNodeDefinition } from '@json-edit-react'
2+
import { testData } from '../../custom-component-library/src/data'
23

34
export const truncate = (string: string, length = 200) =>
45
string.length < length ? string : `${string.slice(0, length - 2).trim()}...`
@@ -20,3 +21,30 @@ const jsonStringify = (data: JsonData) =>
2021
},
2122
2
2223
)
24+
25+
// For the "CustomNodeLibrary" data, returns modified definitions dependent on
26+
// the data
27+
export const getConditionalDefinitions = (
28+
data: typeof testData,
29+
customNodeDefinitions: CustomNodeDefinition[]
30+
) =>
31+
customNodeDefinitions.map((definition) => {
32+
if (definition?.name === 'Image')
33+
return {
34+
...definition,
35+
customNodeProps: {
36+
imageStyles: {
37+
maxHeight: data?.Images?.['Image properties']?.maxHeight,
38+
maxWidth: data?.Images?.['Image properties']?.maxWidth,
39+
},
40+
},
41+
}
42+
43+
if (definition?.name === 'Date Object')
44+
return {
45+
...definition,
46+
customNodeProps: { showTime: data?.['Date & Time']?.['Show Time in Date?'] ?? false },
47+
}
48+
49+
return definition
50+
})

0 commit comments

Comments
 (0)