Tom Butcher a590650d80
Some checks failed
farmcontrol/farmcontrol-ui/pipeline/head There was a failure building this commit
Refactor CodeMirror Language Support and Enhance Template Handling
- Updated fcTemplateLang to utilize new helper functions for improved template type handling.
- Introduced typedScope for better autocompletion support in JavaScript language integration.
- Enhanced CodeBlockEditor and TemplateEditor components to support cursor change events and template type management.
- Added fetchTemplateIntellisense function in ApiServerContext for improved intellisense capabilities based on cursor position.
- Refactored related components to ensure consistent handling of template types and cursor changes, enhancing user experience.
2026-09-12 23:30:00 +01:00

1184 lines
33 KiB
JavaScript

import PropTypes from 'prop-types'
import {
Typography,
Badge,
Input,
InputNumber,
Form,
Select,
Switch,
Tree
} from 'antd'
import IdDisplay from './IdDisplay'
import TimeDisplay from './TimeDisplay'
import TimeEdit from './TimeEdit'
import dayjs from 'dayjs'
import EmailDisplay from './EmailDisplay'
import UrlDisplay from './UrlDisplay'
import CountryDisplay from './CountryDisplay'
import CountrySelect from './CountrySelect'
import TagsDisplay from './TagsDisplay'
import TagsInput from './TagsInput'
import BoolDisplay from './BoolDisplay'
import ColorSelector from './ColorSelector'
import SecretDisplay from './SecretDisplay'
import EyeIcon from '../../Icons/EyeIcon'
import EyeSlashIcon from '../../Icons/EyeSlashIcon'
import { getPropertyValue } from '../../../database/ObjectModels'
import PropertyChanges from './PropertyChanges'
import NetGrossDisplay from './NetGrossDisplay'
import NetGrossInput from './NetGrossInput'
import MinMaxDisplay from './MinMaxDisplay'
import MinMaxInput from './MinMaxInput'
import InputNumberCal from './InputNumberCal'
import ObjectList from './ObjectList'
import VarianceDisplay from './VarianceDisplay'
import OperationDisplay from './OperationDisplay'
import MarkdownDisplay from './MarkdownDisplay'
import MarkdownInput from './MarkdownInput'
import ObjectSelect from './ObjectSelect'
import ObjectDisplay from './ObjectDisplay'
import ObjectTypeSelect from './ObjectTypeSelect'
import ObjectTypeDisplay from './ObjectTypeDisplay'
import CodeBlockEditor from './CodeBlockEditor'
import CustomSelect from './CustomSelect'
import CustomTreeSelect, { findTreeLabel } from './CustomTreeSelect'
import StateDisplay from './StateDisplay'
import AlertsDisplay from './AlertsDisplay'
import FileUpload from './FileUpload'
import DataTree from './DataTree'
import FileList from './FileList'
import ObjectChildTable from './ObjectChildTable'
import MiscId from './MiscId'
import AddressDisplay from './AddressDisplay'
import ArrayDisplay from './ArrayDisplay'
import ArrayInput from './ArrayInput'
import ElipsisText from './ElipsisText'
import PermissionsMatrix from './PermissionsMatrix'
import { round } from '../utils/Utils'
const { Text } = Typography
const getTextValue = (value) => {
if (value == null || typeof value === 'string' || typeof value === 'number') {
return value
}
if (typeof value === 'object') {
if (typeof value._id === 'string') {
return value._id
}
if (typeof value._id?._id === 'string') {
return value._id._id
}
try {
return JSON.stringify(value)
} catch {
return String(value)
}
}
return String(value)
}
const timeEditFormItemProps = {
getValueProps: (v) => ({ value: v ? dayjs(v) : null })
}
const ObjectProperty = ({
modelType = 'unknown',
type = 'text',
prefix,
size,
suffix,
value,
min,
max,
difference,
step,
isEditing = false,
formItemProps = {},
required = false,
name,
inTable = false,
label,
showLabel = false,
masterFilter = {},
language = '',
objectData = null,
parentData = null,
objectType = 'unknown',
readOnly = false,
disabled = false,
empty = false,
initial = false,
height = 'auto',
minimal = false,
autoCompleteObject = null,
errorLines = [],
templateType,
onCursorChange,
previewOpen = false,
showPreview = true,
useFormItem = true,
options = [],
roundNumber = false,
fixedNumber = false,
canAddRemove = true,
showHyperlink,
showSince,
properties = [],
onChange = null,
maxWidth = '100%',
loading = false,
rollups = [],
showCard = true,
showDownload = true,
columns = [],
style = {},
hiddenPropertyWidth = '100px',
inherit = true,
scrollHeight,
gallery = false,
...rest
}) => {
if (typeof value === 'function') {
value = value(objectData || {}, parentData)
}
if (max && typeof max == 'function' && objectData) {
max = max(objectData, parentData)
}
if (min && typeof min == 'function' && objectData) {
min = min(objectData, parentData)
}
if (objectType && typeof objectType == 'function' && objectData) {
objectType = objectType(objectData, parentData)
}
if (disabled && typeof disabled == 'function' && objectData) {
disabled = disabled(objectData, parentData)
}
if (empty && typeof empty == 'function' && objectData) {
empty = empty(objectData, parentData)
}
if (difference && typeof difference == 'function' && objectData) {
difference = difference(objectData, parentData)
}
if (prefix && typeof prefix == 'function' && objectData) {
prefix = prefix(objectData, parentData)
}
if (suffix && typeof suffix == 'function' && objectData) {
suffix = suffix(objectData, parentData)
}
if (masterFilter && typeof masterFilter == 'function' && objectData) {
masterFilter = masterFilter(objectData, parentData)
}
if (options && typeof options == 'function' && objectData) {
options = options(objectData, parentData)
}
if (readOnly && typeof readOnly == 'function' && objectData) {
readOnly = readOnly(objectData, parentData)
}
if (type && typeof type == 'function' && objectData) {
type = type(objectData, parentData)
}
if (columns && typeof columns == 'function' && objectData) {
columns = columns(objectData, parentData)
}
if (value == null) {
value = getPropertyValue(objectData, name)
}
// Split the name by "." to handle nested object properties
var formItemName = name
if (name?.includes('.')) {
formItemName = name ? name.split('.') : undefined
}
// For state type with options (editable), form field is state.type
if (type === 'state' && options?.length && !readOnly) {
formItemName = ['state', 'type']
}
var textParams = { style: { whiteSpace: 'nowrap', minWidth: '0' } }
if (disabled == true) {
textParams = { ...textParams, delete: true, type: 'secondary' }
}
const renderProperty = () => {
if (empty == true) {
return <Text type='secondary'>n/a</Text>
}
if (!isEditing || (readOnly && !initial)) {
switch (type) {
case 'netGross':
return (
<NetGrossDisplay value={value} prefix={prefix} suffix={suffix} />
)
case 'minMax':
return (
<MinMaxDisplay
value={value}
prefix={prefix}
suffix={suffix}
step={step}
/>
)
case 'secret':
if (value != null) {
return <SecretDisplay value={value} {...rest} />
} else {
return (
<Text type='secondary' {...textParams}>
n/a
</Text>
)
}
case 'wsprotocol':
switch (value) {
case 'ws':
return <Text {...textParams}>Websocket</Text>
case 'wss':
return <Text {...textParams}>Websocket Secure</Text>
default:
return (
<Text type='secondary' {...textParams}>
n/a
</Text>
)
}
case 'select': {
if (options && Array.isArray(options)) {
const selectValue =
options.find((option) => option.value === value) || 'n/a'
return <Text {...textParams}>{selectValue.label}</Text>
} else {
return (
<Text type='secondary' {...textParams}>
n/a
</Text>
)
}
}
case 'treeSelect': {
if (Array.isArray(value) && value.length) {
return (
<Tree
treeData={value}
fieldNames={{
title: 'title',
key: 'value',
children: 'children'
}}
height={320}
virtual
defaultExpandAll={false}
/>
)
}
const treeLabel = findTreeLabel(options, value)
if (treeLabel || (value != null && value !== '')) {
return <Text {...textParams}>{treeLabel || String(value)}</Text>
}
return (
<Text type='secondary' {...textParams}>
n/a
</Text>
)
}
case 'priceMode':
switch (value) {
case 'margin':
return <Text {...textParams}>Margin %</Text>
case 'amount':
return <Text {...textParams}>£ Amount</Text>
default:
return (
<Text type='secondary' {...textParams}>
n/a
</Text>
)
}
case 'bool': {
if (value != null) {
return <BoolDisplay value={value} yesNo={true} {...rest} />
} else {
return (
<Text type='secondary' {...textParams}>
n/a
</Text>
)
}
}
case 'dateTime': {
if (value != null) {
return (
<TimeDisplay dateTime={value} showSince={showSince} {...rest} />
)
} else {
return (
<Text type='secondary' {...textParams}>
n/a
</Text>
)
}
}
case 'country': {
if (value != null) {
return <CountryDisplay countryCode={value} />
} else {
return (
<Text type='secondary' {...textParams}>
n/a
</Text>
)
}
}
case 'color': {
if (value) {
return <Badge color={value} count={value.toUpperCase()} />
} else {
return (
<Text type='secondary' {...textParams}>
n/a
</Text>
)
}
}
case 'number': {
if (value != null) {
if (Array.isArray(value)) {
return (
<Text {...textParams}>
{prefix}
{value.length}
{suffix}
</Text>
)
} else {
var roundedValue = value
if (roundNumber != false && typeof value === 'number') {
roundedValue = round(value, roundNumber)
}
if (fixedNumber != false && typeof value === 'number') {
roundedValue = value.toFixed(fixedNumber)
}
return (
<Text {...textParams}>
{prefix}
{typeof value === 'number' ? roundedValue : value}
{suffix}
</Text>
)
}
} else {
return (
<Text type='secondary' {...textParams}>
n/a
</Text>
)
}
}
case 'numberList':
case 'stringList':
return <ArrayDisplay value={value} prefix={prefix} suffix={suffix} />
case 'variance': {
if (value != null) {
return (
<VarianceDisplay value={value} prefix={prefix} suffix={suffix} />
)
} else {
return (
<Text type='secondary' {...textParams}>
n/a
</Text>
)
}
}
case 'text':
if (value != null && value != '') {
return (
<ElipsisText style={style}>
{prefix}
{getTextValue(value)}
{suffix}
</ElipsisText>
)
} else {
return (
<Text type='secondary' {...textParams}>
n/a
</Text>
)
}
case 'codeBlock':
if (value != null && value != '') {
return (
<CodeBlockEditor
code={value}
language={language}
height={height}
readOnly={true}
minimal={minimal}
autoCompleteObject={autoCompleteObject}
errorLines={errorLines}
templateType={templateType}
onCursorChange={onCursorChange}
/>
)
} else {
return (
<Text type='secondary' {...textParams}>
n/a
</Text>
)
}
case 'markdown':
if (typeof value === 'string' && value !== '') {
return <MarkdownDisplay content={value} />
} else {
return (
<Text type='secondary' {...textParams}>
n/a
</Text>
)
}
case 'email':
if (value != null && value != '') {
return <EmailDisplay email={value} />
} else {
return (
<Text type='secondary' {...textParams}>
n/a
</Text>
)
}
case 'url':
if (value != null && value != '') {
return <UrlDisplay url={value} />
} else {
return (
<Text type='secondary' {...textParams}>
n/a
</Text>
)
}
case 'object': {
if (value && value._id) {
return (
<ObjectDisplay
object={value}
objectType={objectType}
showHyperlink={showHyperlink}
/>
)
} else {
return (
<Text type='secondary' {...textParams}>
n/a
</Text>
)
}
}
case 'objectType': {
if (value) {
return <ObjectTypeDisplay objectType={value} />
} else {
return (
<Text type='secondary' {...textParams}>
n/a
</Text>
)
}
}
case 'objectList': {
return (
<ObjectList
value={value}
objectType={objectType}
showHyperlink={showHyperlink}
scrollHorizontal={inTable}
/>
)
}
case 'objectChildren': {
return (
<ObjectChildTable
name={name}
value={value}
properties={properties}
objectData={objectData}
maxWidth={maxWidth}
loading={loading}
rollups={rollups}
size={size}
hiddenPropertyWidth={hiddenPropertyWidth}
minimal={minimal}
label={label}
columns={columns}
canAddRemove={canAddRemove}
/>
)
}
case 'state': {
if (value && value?.type) {
return <StateDisplay {...rest} state={value} />
} else if (value && value?.message) {
return <Text {...textParams}>{value.message}</Text>
} else {
return (
<Text type='secondary' {...textParams}>
n/a
</Text>
)
}
}
case 'id': {
if (value) {
return (
<IdDisplay
id={value}
type={objectType}
showHyperlink={showHyperlink}
{...rest}
/>
)
} else {
return (
<Text type='secondary' {...textParams}>
n/a
</Text>
)
}
}
case 'reference': {
if (value) {
return (
<IdDisplay
id={value}
reference={value}
type={objectType}
{...rest}
/>
)
} else {
return (
<Text type='secondary' {...textParams}>
n/a
</Text>
)
}
}
case 'miscId': {
return <MiscId value={value} {...rest} />
}
case 'density': {
if (value != null) {
return <Text {...textParams}>{`${value} g/cm³`}</Text>
} else {
return (
<Text type='secondary' {...textParams}>
n/a
</Text>
)
}
}
case 'alerts': {
if (value != null && value?.length != 0) {
return (
<AlertsDisplay
object={objectData}
objectType={objectType}
showDismiss={false}
showActions={false}
/>
)
} else {
return (
<Text type='secondary' {...textParams}>
n/a
</Text>
)
}
}
case 'tags': {
if (value != null || value?.length != 0) {
return (
<TagsDisplay
tags={value}
propertyName={name}
scrollHorizontal={inTable}
objectType={objectType}
/>
)
} else {
return (
<Text type='secondary' {...textParams}>
n/a
</Text>
)
}
}
case 'address': {
return (
<AddressDisplay
value={value}
isEditing={false}
disabled={disabled}
/>
)
}
case 'operation': {
if (value != null) {
return <OperationDisplay operation={value} />
} else {
return (
<Text type='secondary' {...textParams}>
n/a
</Text>
)
}
}
case 'propertyChanges': {
return <PropertyChanges type={objectType} value={value} />
}
case 'data': {
return <DataTree data={value} />
}
case 'permissionsMatrix': {
return (
<PermissionsMatrix
value={value}
disabled={true}
inherit={inherit}
scrollHeight={scrollHeight}
/>
)
}
case 'file': {
if (value == null || value?.length == 0 || value == undefined) {
return (
<Text type='secondary' {...textParams}>
n/a
</Text>
)
} else {
return (
<FileList
files={value}
minimal={minimal}
multiple={false}
card={false}
maxWidth={maxWidth}
defaultPreviewOpen={previewOpen}
showPreview={showPreview}
showInfo={showHyperlink}
showDownload={showDownload}
gallery={gallery}
editing={false}
masterFilter={masterFilter}
/>
)
}
}
case 'fileList': {
return (
<FileList
files={value}
minimal={minimal}
multiple={true}
maxWidth={maxWidth}
defaultPreviewOpen={previewOpen}
showPreview={showPreview}
showInfo={showHyperlink}
gallery={gallery}
editing={false}
masterFilter={masterFilter}
/>
)
}
default: {
if (value != null && value !== '') {
return <Text {...textParams}>{getTextValue(value)}</Text>
} else {
return (
<Text type='secondary' {...textParams}>
n/a
</Text>
)
}
}
}
}
// Editable mode: wrap in Form.Item
// Merge required rule if needed
let mergedFormItemProps = {
...formItemProps,
style: { flexGrow: 1, width: '100%', minWidth: 0 }
}
if (required && disabled == false) {
let rules
if (mergedFormItemProps.rules) {
rules = [...mergedFormItemProps.rules]
} else {
rules = []
}
const hasRequiredRule = rules.some((rule) => rule && rule.required)
if (!hasRequiredRule) {
rules.push({ required: true, message: '' })
}
mergedFormItemProps.rules = rules
}
if (type === 'minMax' && disabled == false) {
const rules = [...(mergedFormItemProps.rules || [])]
rules.push({
validator: (_, range) => {
if (range == null) return Promise.resolve()
if (typeof range !== 'object' || Array.isArray(range)) {
return Promise.reject(new Error('Value must be a min/max range'))
}
const values = [range.min, range.max].filter((item) => item != null)
const valid = values.every(
(item) =>
typeof item === 'number' &&
Number.isFinite(item) &&
(min == null || item >= min) &&
(max == null || item <= max)
)
return valid
? Promise.resolve()
: Promise.reject(new Error('Enter valid min and max values'))
}
})
mergedFormItemProps.rules = rules
}
if ((type === 'numberList' || type === 'stringList') && disabled == false) {
const rules = [...(mergedFormItemProps.rules || [])]
rules.push({
validator: (_, items) => {
if (items == null) return Promise.resolve()
if (!Array.isArray(items)) {
return Promise.reject(new Error('Value must be a list'))
}
const valid = items.every((item) => {
if (type === 'numberList') {
return (
typeof item === 'number' &&
Number.isFinite(item) &&
(min == null || item >= min) &&
(max == null || item <= max)
)
}
return typeof item === 'string' && item.trim().length > 0
})
return valid
? Promise.resolve()
: Promise.reject(new Error('Complete or remove empty list items'))
}
})
mergedFormItemProps.rules = rules
}
// Remove name from mergedFormItemProps if present
if (mergedFormItemProps.name) {
delete mergedFormItemProps.name
}
// If label is provided, set it on Form.Item
if (label && showLabel == true) {
mergedFormItemProps.label = label
}
// Always apply style: { margin: 0 } unless overridden
mergedFormItemProps.style = {
margin: 0,
...(mergedFormItemProps.style || {})
}
if (typeof onChange === 'function') {
mergedFormItemProps.onChange = onChange
}
const inputProps = useFormItem
? { disabled }
: {
value,
onChange,
disabled
}
const renderInput = () => {
switch (type) {
case 'netGross':
return (
<NetGrossInput
difference={difference}
prefix={prefix}
suffix={suffix}
{...inputProps}
/>
)
case 'minMax':
return (
<MinMaxInput
prefix={prefix}
suffix={suffix}
min={min}
max={max}
step={step}
{...inputProps}
/>
)
case 'secret':
return (
<Input.Password
placeholder={label}
iconRender={(visible) =>
visible ? <EyeSlashIcon /> : <EyeIcon />
}
{...mergedFormItemProps}
{...inputProps}
/>
)
case 'wsprotocol':
return (
<Select
defaultValue='ws'
options={[
{ value: 'ws', label: 'Websocket' },
{ value: 'wss', label: 'Websocket Secure' }
]}
{...inputProps}
/>
)
case 'select':
return (
<CustomSelect
placeholder={'Select a ' + label.toLowerCase() + '...'}
options={Array.isArray(options) ? options : []}
{...inputProps}
/>
)
case 'treeSelect':
return (
<CustomTreeSelect
placeholder={'Select a ' + label.toLowerCase() + '...'}
options={Array.isArray(options) ? options : []}
{...inputProps}
/>
)
case 'state':
if (options?.length && !readOnly) {
return (
<CustomSelect
placeholder={'Select a ' + label.toLowerCase() + '...'}
options={Array.isArray(options) ? options : []}
{...inputProps}
{...(useFormItem ? {} : { value: value?.type })}
/>
)
}
return null
case 'priceMode':
return (
<Select
defaultValue='margin'
options={[
{ value: 'margin', label: 'Margin %' },
{ value: 'amount', label: '£ Amount' }
]}
{...inputProps}
/>
)
case 'bool':
return (
<Switch
{...inputProps}
{...(useFormItem ? {} : { checked: value })}
/>
)
case 'dateTime':
return <TimeEdit {...inputProps} />
case 'country':
return <CountrySelect {...inputProps} />
case 'color':
return (
<ColorSelector
required={required}
{...inputProps}
{...(useFormItem ? {} : { value })}
/>
)
case 'weight':
return (
<InputNumber
suffix='g'
style={{ width: '100%' }}
placeholder={label}
{...inputProps}
/>
)
case 'number':
return (
<InputNumberCal
placeholder={label}
prefix={prefix}
suffix={suffix}
min={min}
max={max}
step={step}
style={{ width: '100%' }}
{...inputProps}
/>
)
case 'numberList':
return (
<ArrayInput
numeric
min={min}
max={max}
step={step}
placeholder={label}
disabled={disabled}
{...inputProps}
/>
)
case 'stringList':
return (
<ArrayInput
placeholder={label}
disabled={disabled}
{...inputProps}
/>
)
case 'text':
return <Input placeholder={label} {...inputProps} />
case 'email':
if (Array.isArray(options) && options.length) {
return (
<CustomSelect
placeholder={'Select a ' + label.toLowerCase() + '...'}
options={options}
{...inputProps}
/>
)
}
return <Input type='email' placeholder={label} {...inputProps} />
case 'codeBlock':
return (
<CodeBlockEditor
code={value}
language={language}
height={height}
minimal={minimal}
autoCompleteObject={autoCompleteObject}
errorLines={errorLines}
templateType={templateType}
onCursorChange={onCursorChange}
{...(onChange && !useFormItem ? { onChange } : {})}
{...inputProps}
/>
)
case 'markdown':
return (
<MarkdownInput {...inputProps} size={size} showCard={showCard} />
)
case 'id':
// id is not editable, just show view mode
if (value) {
return <IdDisplay id={value} type={objectType} {...rest} />
} else {
return (
<Text type='secondary' {...textParams}>
n/a
</Text>
)
}
case 'object':
return (
<ObjectSelect
type={objectType}
masterFilter={masterFilter}
{...inputProps}
/>
)
case 'objectType':
return (
<ObjectTypeSelect masterFilter={masterFilter} {...inputProps} />
)
case 'objectList':
return (
<ObjectSelect
type={objectType}
multiple
showHyperlink={showHyperlink}
masterFilter={masterFilter}
{...inputProps}
/>
)
case 'tags':
return (
<TagsInput
propertyName={name}
modelType={modelType}
disabled={disabled}
placeholder={label}
{...inputProps}
/>
)
case 'address':
return (
<AddressDisplay
value={value}
isEditing={true}
onChange={onChange}
disabled={disabled}
{...inputProps}
/>
)
case 'file':
return (
<FileUpload
multiple={false}
minimal={minimal}
defaultPreviewOpen={previewOpen}
showPreview={showPreview}
showInfo={showHyperlink}
gallery={gallery}
masterFilter={masterFilter}
{...inputProps}
/>
)
case 'fileList':
return (
<FileUpload
multiple={true}
minimal={minimal}
defaultPreviewOpen={previewOpen}
showPreview={showPreview}
showInfo={showHyperlink}
gallery={gallery}
masterFilter={masterFilter}
{...inputProps}
/>
)
case 'objectChildren': {
return (
<ObjectChildTable
name={name}
properties={properties}
columns={columns}
objectData={objectData}
isEditing={true}
rollups={rollups}
hiddenPropertyWidth={hiddenPropertyWidth}
size={size}
canAddRemove={canAddRemove}
{...inputProps}
/>
)
}
case 'permissionsMatrix':
return (
<PermissionsMatrix
inherit={inherit}
scrollHeight={scrollHeight}
disabled={disabled}
{...inputProps}
/>
)
default:
return <Input placeholder={label} {...inputProps} />
}
}
if (!useFormItem) {
return renderInput()
}
return (
<Form.Item
name={formItemName}
{...mergedFormItemProps}
{...(type === 'bool' ? { valuePropName: 'checked' } : {})}
{...(type === 'color'
? { valuePropName: 'value', getValueFromEvent: (v) => v }
: {})}
{...(type === 'dateTime' ? timeEditFormItemProps : {})}
>
{renderInput()}
</Form.Item>
)
}
const property = renderProperty()
// Render the property directly (remove useDescriptions functionality)
return property
}
ObjectProperty.propTypes = {
type: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
modelType: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.any, PropTypes.func]),
isEditing: PropTypes.bool,
formItemProps: PropTypes.object,
masterFilter: PropTypes.object,
required: PropTypes.bool,
name: PropTypes.string,
language: PropTypes.string,
autoCompleteObject: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
errorLines: PropTypes.arrayOf(PropTypes.number),
templateType: PropTypes.string,
onCursorChange: PropTypes.func,
prefix: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
suffix: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
min: PropTypes.number,
max: PropTypes.number,
step: PropTypes.number,
showLabel: PropTypes.bool,
objectType: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
readOnly: PropTypes.bool,
disabled: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
empty: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
difference: PropTypes.oneOfType([PropTypes.any, PropTypes.func]),
objectData: PropTypes.object,
height: PropTypes.string,
previewOpen: PropTypes.bool,
showPreview: PropTypes.bool,
useFormItem: PropTypes.bool,
showHyperlink: PropTypes.bool,
options: PropTypes.oneOfType([PropTypes.array, PropTypes.func]),
showSince: PropTypes.bool,
inTable: PropTypes.bool,
loading: PropTypes.bool,
rollups: PropTypes.arrayOf(PropTypes.object),
canAddRemove: PropTypes.bool,
showCard: PropTypes.bool,
showDownload: PropTypes.bool,
style: PropTypes.object,
hiddenPropertyWidth: PropTypes.string,
inherit: PropTypes.bool,
scrollHeight: PropTypes.string,
gallery: PropTypes.bool
}
export default ObjectProperty