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..2e975109d2 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' }, @@ -266,6 +267,24 @@ export const PropertiesTabElements = ({ )} )} + {column.columnType === 'json' && ( +
+
+ +
+
+ )}
)} - {['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/ProgramaticallyHandleProperties.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ProgramaticallyHandleProperties.jsx index e559369396..c3fb47d612 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ProgramaticallyHandleProperties.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Table/ProgramaticallyHandleProperties.jsx @@ -50,6 +50,8 @@ export const ProgramaticallyHandleProperties = ({ return props?.parseInUnixTimestamp; case 'isDateSelectionEnabled': return props?.isDateSelectionEnabled; + case 'jsonIndentation': + return props?.jsonIndentation; default: return; } @@ -81,6 +83,9 @@ export const ProgramaticallyHandleProperties = ({ if (property === 'linkColor') { return definitionObj?.value ?? '#1B1F24'; } + if (property === 'jsonIndentation') { + return definitionObj?.value ?? `{{true}}`; + } return definitionObj?.value ?? `{{false}}`; }; @@ -111,7 +116,9 @@ export const ProgramaticallyHandleProperties = ({ const fxActiveFieldsPropExists = props?.hasOwnProperty('fxActiveFields') ?? false; //to support backward compatibility, when fxActive is true for a particular column, we are passing all possible combinations which should render codehinter const fxActive = - props?.fxActive && resolveReferences(props.fxActive) ? ['isEditable', 'columnVisibility', 'linkTarget'] : []; + props?.fxActive && resolveReferences(props.fxActive) + ? ['isEditable', 'columnVisibility', 'jsonIndentation', 'linkTarget'] + : []; const checkFxActiveFieldIsArrray = (fxActiveFieldsProperty) => { // 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/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/Json.jsx b/frontend/src/AppBuilder/Widgets/Table/Json.jsx new file mode 100644 index 0000000000..fcae72459e --- /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, + jsonIndentation, + darkMode, + handleCellValueChange, + cellTextColor, + cellValue, + column, + containerWidth, + cell, + horizontalAlignment, + isMaxRowHeightAuto, + cellSize, + maxRowHeightValue, +}) => { + 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 b4875d4b31..257de7c4ae 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, @@ -714,6 +715,27 @@ export default function generateColumnsData({
); } + case 'json': { + const jsonIndentation = getResolvedValue(column?.jsonIndentation) ?? true; + return ( + + ); + } } return cellValue || ''; },