From c411a9e8690862055b214f74213afb2244d2cd21 Mon Sep 17 00:00:00 2001 From: Carl Smith <5456533+CarlosNZ@users.noreply.github.com> Date: Sun, 15 Jun 2025 14:40:09 +1200 Subject: [PATCH 1/2] Add Image custom component --- custom-component-library/README.md | 5 ++-- .../components/Image/component.tsx | 23 ++++++++++++++ .../components/Image/definition.ts | 13 ++++++++ .../components/Image/index.ts | 1 + custom-component-library/components/index.ts | 1 + custom-component-library/src/App.tsx | 18 +++++++++-- custom-component-library/src/data.ts | 10 +++++++ demo/src/App.tsx | 16 +++++----- demo/src/demoData/dataDefinitions.tsx | 4 ++- demo/src/helpers.ts | 30 ++++++++++++++++++- 10 files changed, 105 insertions(+), 16 deletions(-) create mode 100644 custom-component-library/components/Image/component.tsx create mode 100644 custom-component-library/components/Image/definition.ts create mode 100644 custom-component-library/components/Image/index.ts diff --git a/custom-component-library/README.md b/custom-component-library/README.md index ae83076..73e5c7a 100644 --- a/custom-component-library/README.md +++ b/custom-component-library/README.md @@ -30,7 +30,8 @@ These are the ones currently available: - [x] `BigInt` - [x] Markdown - [x] "Enhanced" link -- [ ] Image (to-do) +- [x] Image +- [ ] Colour picker (to-do) ## Development @@ -59,7 +60,7 @@ Custom components should consider the following: - `Tab`/`Shift-Tab` to navigate - `Enter` to submit - `Escape` to cancel -- Provide customisation options, particularly styles +- Provide customisation options, particularly styles (but make sure to specify defaults) - If the data contains non-JSON types, add a "stringify" and "reviver" function definition (see `BigInt`, `NaN` and `Symbol` components) 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. diff --git a/custom-component-library/components/Image/component.tsx b/custom-component-library/components/Image/component.tsx new file mode 100644 index 0000000..cbfb454 --- /dev/null +++ b/custom-component-library/components/Image/component.tsx @@ -0,0 +1,23 @@ +/** + * An Image display Custom Component + */ + +import React from 'react' +import { type CustomNodeProps } from '@json-edit-react' + +export interface ImageProps { + imageStyles?: React.CSSProperties + altText?: string +} + +export const ImageComponent: React.FC> = (props) => { + const { value, customNodeProps = {} } = props + + const { imageStyles = { maxWidth: 200, maxHeight: 200 }, altText = 'image' } = customNodeProps + + return ( + + {altText} + + ) +} diff --git a/custom-component-library/components/Image/definition.ts b/custom-component-library/components/Image/definition.ts new file mode 100644 index 0000000..1c1903e --- /dev/null +++ b/custom-component-library/components/Image/definition.ts @@ -0,0 +1,13 @@ +import { type CustomNodeDefinition } from '@json-edit-react' +import { ImageComponent, ImageProps } from './component' + +const imageLinkRegex = /^https?:\/\/[^\s]+?\.(?:jpe?g|png|svg|gif)/i + +export const ImageNodeDefinition: CustomNodeDefinition = { + condition: ({ value }) => typeof value === 'string' && imageLinkRegex.test(value), + element: ImageComponent, + // customNodeProps: {}, + showOnView: true, + showOnEdit: false, + name: 'Image', +} diff --git a/custom-component-library/components/Image/index.ts b/custom-component-library/components/Image/index.ts new file mode 100644 index 0000000..75c618f --- /dev/null +++ b/custom-component-library/components/Image/index.ts @@ -0,0 +1 @@ +export * from './definition' diff --git a/custom-component-library/components/index.ts b/custom-component-library/components/index.ts index e1933a4..43f1381 100644 --- a/custom-component-library/components/index.ts +++ b/custom-component-library/components/index.ts @@ -8,3 +8,4 @@ export * from './NaN' export * from './Symbol' export * from './BigInt' export * from './Markdown' +export * from './Image' diff --git a/custom-component-library/src/App.tsx b/custom-component-library/src/App.tsx index fd85bc0..0f4af25 100644 --- a/custom-component-library/src/App.tsx +++ b/custom-component-library/src/App.tsx @@ -13,6 +13,7 @@ import { BigIntDefinition, MarkdownNodeDefinition, EnhancedLinkCustomNodeDefinition, + ImageNodeDefinition, } from '../components' import { testData } from './data' import { JsonData, JsonEditor } from '@json-edit-react' @@ -33,24 +34,35 @@ if (testData?.['Date & Time']) { type TestData = typeof testData function App() { - const [data, setData] = useState(testData) + const [data, setData] = useState(testData) console.log('Current data', data) + // Properties that are conditional on some data property: + + // Display the time depending on whether or not the "Show time" toggle is + // checked + const showTime = data?.['Date & Time']?.['Show Time in Date?'] ?? false + + // Image sizing + const maxWidth = data?.Images?.['Image properties']?.maxWidth + const maxHeight = data?.Images?.['Image properties']?.maxHeight + return (
void} customNodeDefinitions={[ + { ...ImageNodeDefinition, customNodeProps: { imageStyles: { maxWidth, maxHeight } } }, LinkCustomNodeDefinition, { ...(STORE_DATE_AS_DATE_OBJECT ? DateObjectDefinition : DatePickerDefinition), customNodeProps: { // Display the time depending on whether or not the "Show time" // toggle is checked - showTime: (data as TestData)?.['Date & Time']?.['Show Time in Date?'] ?? false, + showTime, }, }, EnhancedLinkCustomNodeDefinition, diff --git a/custom-component-library/src/data.ts b/custom-component-library/src/data.ts index 2c66106..4dcca9b 100644 --- a/custom-component-library/src/data.ts +++ b/custom-component-library/src/data.ts @@ -19,6 +19,7 @@ export const testData = { - BooleanToggle - NaN - Symbol + - Image Click [here](https://github.com/CarlosNZ/json-edit-react/blob/main/custom-component-library/README.md) for more info `, @@ -48,4 +49,13 @@ export const testData = { }, Markdown: 'Uses [react-markdown](https://www.npmjs.com/package/react-markdown) to render **Markdown** *text content*. ', + Images: { + JPG: 'https://film-grab.com/wp-content/uploads/2014/07/51.jpg', + PNG: 'https://github.com/CarlosNZ/json-edit-react/blob/main/image/logo192.png?raw=true', + GIF: 'https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExdnV0aHB0c2xiMHFmdGY3Z2NkenBkb3Rmd3hvdTlkaTlkNGYxOXFtOSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/9E7kUhnT9eDok/giphy.gif', + 'Image properties': { + maxWidth: 200, + maxHeight: 100, + }, + }, } diff --git a/demo/src/App.tsx b/demo/src/App.tsx index 0dcadd2..b7ab2f4 100644 --- a/demo/src/App.tsx +++ b/demo/src/App.tsx @@ -46,8 +46,9 @@ import { ArrowBackIcon, ArrowForwardIcon, InfoIcon } from '@chakra-ui/icons' import { demoDataDefinitions } from './demoData' import { useDatabase } from './useDatabase' import './style.css' -import { getLineHeight, truncate } from './helpers' +import { getConditionalDefinitions, getLineHeight, truncate } from './helpers' import { Loading } from '../../custom-component-library/components/_common/Loading' +import { testData } from '../../custom-component-library/src/data' const CodeEditor = lazy(() => import('./CodeEditor')) const SourceIndicator = lazy(() => import('./SourceIndicator')) @@ -155,14 +156,11 @@ function App() { // }, []) const customNodeDefinitions = - selectedDataSet === 'customComponentLibrary' && - typeof data === 'object' && - (data as Record>)?.['Date & Time']?.['Show Time in Date?'] && - Array.isArray(dataDefinition.customNodeDefinitions) - ? [ - { ...dataDefinition.customNodeDefinitions[0], customNodeProps: { showTime: true } }, - ...dataDefinition.customNodeDefinitions.slice(1), - ] + selectedDataSet === 'customComponentLibrary' + ? getConditionalDefinitions( + data as typeof testData, + dataDefinition?.customNodeDefinitions ?? [] + ) : dataDefinition.customNodeDefinitions const updateState = (patch: Partial) => setState({ ...state, ...patch }) diff --git a/demo/src/demoData/dataDefinitions.tsx b/demo/src/demoData/dataDefinitions.tsx index 69f386d..04a2abc 100644 --- a/demo/src/demoData/dataDefinitions.tsx +++ b/demo/src/demoData/dataDefinitions.tsx @@ -12,6 +12,7 @@ import { BigIntDefinition, MarkdownNodeDefinition, EnhancedLinkCustomNodeDefinition, + ImageNodeDefinition, } from '../../../custom-component-library/components' import { testData } from '../../../custom-component-library/src/data' import { @@ -831,7 +832,7 @@ export const demoDataDefinitions: Record = { ), rootName: 'components', - collapse: 2, + collapse: 3, data: testData, customNodeDefinitions: [ // Must keep this one first as we override it by index in App.tsx @@ -839,6 +840,7 @@ export const demoDataDefinitions: Record = { ...DateObjectDefinition, customNodeProps: { showTime: false }, }, + ImageNodeDefinition, LinkCustomNodeDefinition, EnhancedLinkCustomNodeDefinition, UndefinedDefinition, diff --git a/demo/src/helpers.ts b/demo/src/helpers.ts index 244b656..a60ce3c 100644 --- a/demo/src/helpers.ts +++ b/demo/src/helpers.ts @@ -1,4 +1,5 @@ -import { JsonData } from '@json-edit-react' +import { JsonData, type CustomNodeDefinition } from '@json-edit-react' +import { testData } from '../../custom-component-library/src/data' export const truncate = (string: string, length = 200) => string.length < length ? string : `${string.slice(0, length - 2).trim()}...` @@ -20,3 +21,30 @@ const jsonStringify = (data: JsonData) => }, 2 ) + +// For the "CustomNodeLibrary" data, returns modified definitions dependent on +// the data +export const getConditionalDefinitions = ( + data: typeof testData, + customNodeDefinitions: CustomNodeDefinition[] +) => + customNodeDefinitions.map((definition) => { + if (definition?.name === 'Image') + return { + ...definition, + customNodeProps: { + imageStyles: { + maxHeight: data?.Images?.['Image properties']?.maxHeight, + maxWidth: data?.Images?.['Image properties']?.maxWidth, + }, + }, + } + + if (definition?.name === 'Date Object') + return { + ...definition, + customNodeProps: { showTime: data?.['Date & Time']?.['Show Time in Date?'] ?? false }, + } + + return definition + }) From 5fe92bdf6403b7a12446bb9b361b88fba0c1a186 Mon Sep 17 00:00:00 2001 From: Carl Smith <5456533+CarlosNZ@users.noreply.github.com> Date: Sun, 15 Jun 2025 16:07:00 +1200 Subject: [PATCH 2/2] Tweaks --- custom-component-library/components/Image/component.tsx | 5 +++-- custom-component-library/src/App.tsx | 8 ++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/custom-component-library/components/Image/component.tsx b/custom-component-library/components/Image/component.tsx index cbfb454..fdabe32 100644 --- a/custom-component-library/components/Image/component.tsx +++ b/custom-component-library/components/Image/component.tsx @@ -13,11 +13,12 @@ export interface ImageProps { export const ImageComponent: React.FC> = (props) => { const { value, customNodeProps = {} } = props - const { imageStyles = { maxWidth: 200, maxHeight: 200 }, altText = 'image' } = customNodeProps + const { imageStyles = { maxWidth: 200, maxHeight: 200 }, altText = value as string } = + customNodeProps return ( - {altText} + {altText} ) } diff --git a/custom-component-library/src/App.tsx b/custom-component-library/src/App.tsx index 0f4af25..6159ed2 100644 --- a/custom-component-library/src/App.tsx +++ b/custom-component-library/src/App.tsx @@ -16,7 +16,7 @@ import { ImageNodeDefinition, } from '../components' import { testData } from './data' -import { JsonData, JsonEditor } from '@json-edit-react' +import { JsonEditor } from '@json-edit-react' if (testData?.['Date & Time']) { // @ts-expect-error redefine after initialisation @@ -59,11 +59,7 @@ function App() { LinkCustomNodeDefinition, { ...(STORE_DATE_AS_DATE_OBJECT ? DateObjectDefinition : DatePickerDefinition), - customNodeProps: { - // Display the time depending on whether or not the "Show time" - // toggle is checked - showTime, - }, + customNodeProps: { showTime }, }, EnhancedLinkCustomNodeDefinition, UndefinedDefinition,