From 868b36ebcb65e22f0c531fcd593266339d978d03 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Mon, 6 Jan 2025 02:59:50 +0530 Subject: [PATCH 1/5] Add JSON in column type dropdown. --- .../Table/ColumnManager/PropertiesTabElements.jsx | 1 + .../Table/ColumnManager/StylesTabElements.jsx | 15 ++++++++++++--- .../Inspector/Components/Table/Table.jsx | 2 ++ .../Widgets/Table/GenerateEachCellValue.jsx | 2 +- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/PropertiesTabElements.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/PropertiesTabElements.jsx index 27ba100f52..c161156e8b 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/PropertiesTabElements.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/PropertiesTabElements.jsx @@ -52,6 +52,7 @@ export const PropertiesTabElements = ({ { label: 'Boolean', value: 'boolean' }, { label: 'Image', value: 'image' }, { label: 'Link', value: 'link' }, + { label: 'JSON', value: 'json' }, // Following column types are deprecated { label: 'Default', value: 'default' }, { label: 'Dropdown', value: 'dropdown' }, diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/StylesTabElements.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/StylesTabElements.jsx index e647626438..5091e56c6c 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/StylesTabElements.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/StylesTabElements.jsx @@ -122,9 +122,18 @@ export const StylesTabElements = ({ )} - {['string', 'default', undefined, 'number', 'boolean', 'select', 'text', 'newMultiSelect', 'datepicker'].includes( - column.columnType - ) && ( + {[ + 'string', + 'default', + undefined, + 'number', + 'json', + 'boolean', + 'select', + 'text', + 'newMultiSelect', + 'datepicker', + ].includes(column.columnType) && ( <> {column.columnType !== 'boolean' && (
diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/Table.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/Table.jsx index 5a2175cfe1..b11a675e80 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/Table.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/Table.jsx @@ -624,6 +624,8 @@ class TableComponent extends React.Component { return 'Select'; case 'newMultiSelect': return 'Multiselect'; + case 'json': + return 'JSON'; default: capitalize(text ?? ''); } diff --git a/frontend/src/AppBuilder/Widgets/Table/GenerateEachCellValue.jsx b/frontend/src/AppBuilder/Widgets/Table/GenerateEachCellValue.jsx index 0d2d8530c7..312e57b760 100644 --- a/frontend/src/AppBuilder/Widgets/Table/GenerateEachCellValue.jsx +++ b/frontend/src/AppBuilder/Widgets/Table/GenerateEachCellValue.jsx @@ -29,7 +29,7 @@ export default function GenerateEachCellValue({ const [showHighlightedCells, setHighlighterCells] = React.useState(globalFilter ? true : false); // const [isNullCellClicked, setIsNullCellClicked] = React.useState(false); - const columnTypeAllowToRenderMarkElement = ['text', 'string', 'default', 'number', undefined]; + const columnTypeAllowToRenderMarkElement = ['text', 'string', 'default', 'number', 'json', undefined]; const ref = useRef(); const [showOverlay, setShowOverlay] = useState(false); const [hovered, setHovered] = useState(false); From 8972ff730ae144cdf8b3376c800dd8e89445f455 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Mon, 6 Jan 2025 04:16:55 +0530 Subject: [PATCH 2/5] Added basic logic for autogenerating JSON columns. --- .../AppBuilder/Widgets/Table/columns/autogenerateColumns.js | 6 ++++-- frontend/src/AppBuilder/Widgets/Table/columns/index.jsx | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/src/AppBuilder/Widgets/Table/columns/autogenerateColumns.js b/frontend/src/AppBuilder/Widgets/Table/columns/autogenerateColumns.js index 98faa12da0..83c07fc0ac 100644 --- a/frontend/src/AppBuilder/Widgets/Table/columns/autogenerateColumns.js +++ b/frontend/src/AppBuilder/Widgets/Table/columns/autogenerateColumns.js @@ -50,6 +50,7 @@ export default function autogenerateColumns( const currentKey = parentKey ? `${parentKey}.${key}` : key; if (isValueIsPlainObject(value)) { // if value is object particularly, then we only want nested keys till one level of nesting + accumulator.push(currentKey); accumulator.push(...limitToOneLevelNestingHelperFunc(value, currentKey)); } else if (isValueIsPremitiveOrArray(value)) { // check if value is premitive or array then simply push current key in the accumulator. @@ -98,7 +99,7 @@ export default function autogenerateColumns( id: uuidv4(), name: key, key: key, - columnType: convertDataTypeToColumnType(dataType), + columnType: convertDataTypeToColumnType(dataType, firstRow[key]), autogenerated: true, })); @@ -123,7 +124,8 @@ const dataTypeToColumnTypeMapping = { boolean: 'boolean', }; -const convertDataTypeToColumnType = (dataType) => { +const convertDataTypeToColumnType = (dataType, value) => { if (Object.keys(dataTypeToColumnTypeMapping).includes(dataType)) return dataTypeToColumnTypeMapping[dataType]; + if (dataType === 'object' && !Array.isArray(value)) return 'json'; else return 'string'; }; diff --git a/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx b/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx index b4875d4b31..c6a5945a49 100644 --- a/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx +++ b/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx @@ -186,6 +186,7 @@ export default function generateColumnsData({ switch (columnType) { case 'string': + case 'json': case undefined: case 'default': { const cellTextColor = getResolvedValue(column.textColor, { cellValue, rowData }) ?? ''; From f30eae736639cdb80dd0538216cd279de7197a4a Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Tue, 7 Jan 2025 16:48:52 +0530 Subject: [PATCH 3/5] Added JSON component for rendering JSON column cell with formatting. --- .../Widgets/Table/GenerateEachCellValue.jsx | 2 +- .../src/AppBuilder/Widgets/Table/Json.jsx | 191 ++++++++++++++++++ .../Widgets/Table/columns/index.jsx | 21 +- 3 files changed, 212 insertions(+), 2 deletions(-) create mode 100644 frontend/src/AppBuilder/Widgets/Table/Json.jsx diff --git a/frontend/src/AppBuilder/Widgets/Table/GenerateEachCellValue.jsx b/frontend/src/AppBuilder/Widgets/Table/GenerateEachCellValue.jsx index 312e57b760..0d2d8530c7 100644 --- a/frontend/src/AppBuilder/Widgets/Table/GenerateEachCellValue.jsx +++ b/frontend/src/AppBuilder/Widgets/Table/GenerateEachCellValue.jsx @@ -29,7 +29,7 @@ export default function GenerateEachCellValue({ const [showHighlightedCells, setHighlighterCells] = React.useState(globalFilter ? true : false); // const [isNullCellClicked, setIsNullCellClicked] = React.useState(false); - const columnTypeAllowToRenderMarkElement = ['text', 'string', 'default', 'number', 'json', undefined]; + const columnTypeAllowToRenderMarkElement = ['text', 'string', 'default', 'number', undefined]; const ref = useRef(); const [showOverlay, setShowOverlay] = useState(false); const [hovered, setHovered] = useState(false); diff --git a/frontend/src/AppBuilder/Widgets/Table/Json.jsx b/frontend/src/AppBuilder/Widgets/Table/Json.jsx new file mode 100644 index 0000000000..9eda847d3f --- /dev/null +++ b/frontend/src/AppBuilder/Widgets/Table/Json.jsx @@ -0,0 +1,191 @@ +/* eslint-disable no-undef */ +import React, { useState, useEffect } from 'react'; +import { determineJustifyContentValue } from '@/_helpers/utils'; +import OverlayTrigger from 'react-bootstrap/OverlayTrigger'; + +const Json = ({ + isEditable, + darkMode, + handleCellValueChange, + cellTextColor, + cellValue, + column, + containerWidth, + cell, + horizontalAlignment, + isMaxRowHeightAuto, + cellSize, + maxRowHeightValue, +}) => { + const jsonIndentation = false; + const ref = React.useRef(null); + + const [hovered, setHovered] = useState(false); + const [isEditing, setIsEditing] = useState(false); + + const cellStyles = { + color: cellTextColor ?? 'inherit', + }; + + useEffect(() => { + if (!isEditable && isEditing) { + setIsEditing(false); + } + }, [isEditable]); + + function format(obj) { + if (typeof obj !== 'object' || obj === null) { + return typeof obj === 'string' ? `"${obj}"` : obj; + } + if (Array.isArray(obj)) { + return `[ ${obj.map(format).join(', ')} ]`; + } + return `{ ${Object.entries(obj) + .map(([key, value]) => `"${key}": ${format(value)}`) + .join(', ')} }`; + } + + const formatCellValue = (value, overlay = false) => { + try { + if (typeof value === 'object') { + if (jsonIndentation === true && !overlay) { + return JSON.stringify(value, null, 4).replace(/":/g, '": '); + } + const formattedJSON = format(value); + return formattedJSON; + } else { + if (jsonIndentation === true && !overlay) { + return JSON.stringify(JSON.parse(value), null, 4).replace(/":/g, '": '); + } + const formattedJSON = format(JSON.parse(value)); + return formattedJSON; + } + } catch (error) { + return value; + } + }; + + const _renderString = () => ( +
{ + setIsEditing(false); + if (cellValue !== e.target.textContent) { + const value = JSON.stringify(JSON.parse(e.target.textContent.replace(/\n/g, ''))); + handleCellValueChange(cell.row.index, column.key || column.name, value, cell.row.original); + } + }} + onKeyDown={(e) => { + if (e.key === 'Enter') { + ref.current.blur(); + if (cellValue !== e.target.textContent) { + const value = JSON.stringify(JSON.parse(e.target.textContent.replace(/\n/g, ''))); + handleCellValueChange(cell.row.index, column.key || column.name, value, cell.row.original); + } + } + }} + onFocus={(e) => { + setIsEditing(true); + e.stopPropagation(); + }} + > + {String(formatCellValue(cellValue))} +
+ ); + + const getOverlay = () => { + return ( +
setHovered(true)} + onMouseLeave={() => setHovered(false)} + style={{ color: 'var(--text-primary)' }} + > + + {String(formatCellValue(cellValue, true))} + +
+ ); + }; + + const _showOverlay = + ref?.current && + (ref?.current?.clientWidth < ref?.current?.children[0]?.offsetWidth || + ref?.current?.clientHeight < ref?.current?.children[0]?.offsetHeight); + + return ( + <> +
} + trigger={_showOverlay && ['hover', 'focus']} + rootClose={true} + show={_showOverlay && hovered && !isEditing} + > + {!isEditable ? ( +
{ + if (!hovered) setHovered(true); + }} + onMouseLeave={() => { + setHovered(false); + }} + ref={ref} + > + + {String(formatCellValue(cellValue))} + +
+ ) : ( +
+
{ + if (!hovered) setHovered(true); + }} + onMouseLeave={() => setHovered(false)} + className={`${isEditing ? 'h-100 content-editing' : ''} h-100`} + > + {_renderString()} +
+
+ )} + + + ); +}; + +export default Json; diff --git a/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx b/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx index c6a5945a49..e7013d0b1e 100644 --- a/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx +++ b/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx @@ -14,6 +14,7 @@ import { CustomSelect } from '../CustomSelect'; import SolidIcon from '@/_ui/Icon/SolidIcons'; import Text from '../Text'; import StringColumn from '../String'; +import Json from '../Json'; export default function generateColumnsData({ columnProperties, @@ -186,7 +187,6 @@ export default function generateColumnsData({ switch (columnType) { case 'string': - case 'json': case undefined: case 'default': { const cellTextColor = getResolvedValue(column.textColor, { cellValue, rowData }) ?? ''; @@ -715,6 +715,25 @@ export default function generateColumnsData({ ); } + case 'json': { + return ( + + ); + } } return cellValue || ''; }, From 8304a7245833b8b7f0f1f12f934433108225724c Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Tue, 7 Jan 2025 17:59:16 +0530 Subject: [PATCH 4/5] Add toggle for indentation. --- .../ColumnManager/PropertiesTabElements.jsx | 18 ++++++++++++++++++ .../Table/ProgramaticallyHandleProperties.jsx | 9 ++++++++- frontend/src/AppBuilder/Widgets/Table/Json.jsx | 2 +- .../AppBuilder/Widgets/Table/columns/index.jsx | 2 ++ 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/PropertiesTabElements.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/PropertiesTabElements.jsx index c161156e8b..2e975109d2 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/PropertiesTabElements.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ColumnManager/PropertiesTabElements.jsx @@ -267,6 +267,24 @@ export const PropertiesTabElements = ({ )} )} + {column.columnType === 'json' && ( +
+
+ +
+
+ )}
{ // adding error handling mechanism for fxActiveFieldsProperty , if props.fxActiveFields is array , then return props.fxActiveFields or else return [], this will make sure, fxActiveFields wil always be array diff --git a/frontend/src/AppBuilder/Widgets/Table/Json.jsx b/frontend/src/AppBuilder/Widgets/Table/Json.jsx index 9eda847d3f..fcae72459e 100644 --- a/frontend/src/AppBuilder/Widgets/Table/Json.jsx +++ b/frontend/src/AppBuilder/Widgets/Table/Json.jsx @@ -5,6 +5,7 @@ import OverlayTrigger from 'react-bootstrap/OverlayTrigger'; const Json = ({ isEditable, + jsonIndentation, darkMode, handleCellValueChange, cellTextColor, @@ -17,7 +18,6 @@ const Json = ({ cellSize, maxRowHeightValue, }) => { - const jsonIndentation = false; const ref = React.useRef(null); const [hovered, setHovered] = useState(false); diff --git a/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx b/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx index e7013d0b1e..257de7c4ae 100644 --- a/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx +++ b/frontend/src/AppBuilder/Widgets/Table/columns/index.jsx @@ -716,9 +716,11 @@ export default function generateColumnsData({ ); } case 'json': { + const jsonIndentation = getResolvedValue(column?.jsonIndentation) ?? true; return ( Date: Tue, 7 Jan 2025 18:04:32 +0530 Subject: [PATCH 5/5] Removed autogeneration logic. --- .../AppBuilder/Widgets/Table/columns/autogenerateColumns.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/frontend/src/AppBuilder/Widgets/Table/columns/autogenerateColumns.js b/frontend/src/AppBuilder/Widgets/Table/columns/autogenerateColumns.js index 83c07fc0ac..98faa12da0 100644 --- a/frontend/src/AppBuilder/Widgets/Table/columns/autogenerateColumns.js +++ b/frontend/src/AppBuilder/Widgets/Table/columns/autogenerateColumns.js @@ -50,7 +50,6 @@ export default function autogenerateColumns( const currentKey = parentKey ? `${parentKey}.${key}` : key; if (isValueIsPlainObject(value)) { // if value is object particularly, then we only want nested keys till one level of nesting - accumulator.push(currentKey); accumulator.push(...limitToOneLevelNestingHelperFunc(value, currentKey)); } else if (isValueIsPremitiveOrArray(value)) { // check if value is premitive or array then simply push current key in the accumulator. @@ -99,7 +98,7 @@ export default function autogenerateColumns( id: uuidv4(), name: key, key: key, - columnType: convertDataTypeToColumnType(dataType, firstRow[key]), + columnType: convertDataTypeToColumnType(dataType), autogenerated: true, })); @@ -124,8 +123,7 @@ const dataTypeToColumnTypeMapping = { boolean: 'boolean', }; -const convertDataTypeToColumnType = (dataType, value) => { +const convertDataTypeToColumnType = (dataType) => { if (Object.keys(dataTypeToColumnTypeMapping).includes(dataType)) return dataTypeToColumnTypeMapping[dataType]; - if (dataType === 'object' && !Array.isArray(value)) return 'json'; else return 'string'; };