@adaptabletools/adaptable
Version:
Powerful data-agnostic HTML5 AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements
220 lines (219 loc) • 12.2 kB
JavaScript
import React from 'react';
import { Draggable, Droppable } from '../../dnd';
import { Box, Flex } from 'rebass';
import { isArgumentColumnOrField, isQlLogicalOperator, } from '../../../parser/src/predicate';
import { mapColumnDataTypeToExpressionFunctionType } from '../../../Utilities/adaptableQlUtils';
import { booleanExpressionFunctions } from '../../../Utilities/ExpressionFunctions/booleanExpressionFunctions';
import { useAdaptable } from '../../../View/AdaptableContext';
import DropdownButton from '../../DropdownButton';
import ErrorBox from '../../ErrorBox';
import { Icon } from '../../icons';
import SimpleButton from '../../SimpleButton';
import { CombinatorSelector, ExpressionSelector, PrimiteValueInput, PrimitiveColumnOrFieldSelector, PrimitiveMultiValueInput, } from './QueryBuilderInputs';
import { getOperatorMatchingInputs as getFunctionMatchingInputTypes, mapExpressionToFieldValue, } from './utils';
const ITEM_HEIGHT = 40;
const BASE_CLASS_NAME = 'ab-QueryBuilder-predicate-editor';
const Handle = (props) => (React.createElement(Flex, { className: `${BASE_CLASS_NAME}__handle`, height: ITEM_HEIGHT, alignItems: "center", mr: 1, ...props },
React.createElement(Icon, { name: "drag" })));
const QueryPredicateButtons = (props) => {
return (React.createElement(React.Fragment, null,
!props.hideAdd && (React.createElement(DropdownButton, { listMinWidth: 150, columns: ['label'], items: [
{ label: 'Condition', onClick: () => props.onNewPredicate('filter') },
{ label: 'AND / OR Group', onClick: () => props.onNewPredicate('group') },
], variant: "text" },
React.createElement(Icon, { name: "plus" }))),
!props.hideDelete && (React.createElement(SimpleButton, { icon: "delete", variant: "text", onClick: () => {
props.onChange(null);
} }))));
};
const LogicalFunctionEditor = (props) => {
const level = props.id.split('/').length - 1;
const className = `
${BASE_CLASS_NAME}
${BASE_CLASS_NAME}-level-${level}
${BASE_CLASS_NAME}-combinator
${props.lastChild ? `${BASE_CLASS_NAME}--last-child` : ''}
${props.isRoot ? `${BASE_CLASS_NAME}--root` : `${BASE_CLASS_NAME}--child`}
`;
const getCombinatorEl = (handleProps, className) => (React.createElement(Droppable, { droppableId: props.id, type: props.id }, (provided, snapshot) => {
return (React.createElement("div", { ...provided.droppableProps, ref: provided.innerRef, className: className },
React.createElement(Flex, null,
props.isRoot ? null : React.createElement(Handle, { ...handleProps }),
React.createElement(Flex, { flex: 1, alignItems: "center", height: ITEM_HEIGHT },
React.createElement(CombinatorSelector, { value: props.predicate.operator, onChange: (combinator) => {
props.onChange({
...props.predicate,
operator: combinator,
});
} }),
React.createElement(Box, { flex: 1 }),
React.createElement(QueryPredicateButtons, { hideDelete: props.isRoot, hideAdd: true, ...props }))),
React.createElement(Box, { className: `${BASE_CLASS_NAME}__children-wrapper` },
props.predicate.args.map((arg, index) => {
const id = `${props.id}/${index}`;
return (React.createElement(QueryPredicateBuilder, { key: id, lastChild: index === props.predicate.args.length - 1, index: index, id: id, predicate: arg, onNewPredicate: (type) => {
const newPredicate = {
operator: type === 'filter' ? undefined : 'AND',
args: [],
};
if (typeof arg === 'object' &&
'operator' in arg &&
isQlLogicalOperator(arg.operator)) {
// add as a child
const newArg = {
...arg,
args: [...arg.args, newPredicate],
};
const args = [...props.predicate.args];
args[index] = newArg;
props.onChange({
...props.predicate,
args,
});
}
else {
// add as a sibling
const prevArgs = [...props.predicate.args];
prevArgs.splice(index + 1, 0, newPredicate);
props.onChange({
...props.predicate,
args: prevArgs,
});
}
}, onChange: (predicate) => {
const args = [...props.predicate.args];
if (predicate) {
args[index] = predicate;
}
else {
args.splice(index, 1);
}
props.onChange({
...props.predicate,
args,
});
} }));
}),
provided.placeholder,
React.createElement("div", { className: `${BASE_CLASS_NAME}__root-actions` },
React.createElement(QueryPredicateButtons, { ...props, hideDelete: true })))));
}));
if (props.isRoot) {
return getCombinatorEl({ className: className });
}
else {
return (React.createElement(Draggable, { key: props.id, draggableId: props.id, index: props.index }, (provided, snapshot) => {
return (React.createElement("div", { ...provided.draggableProps, ref: provided.innerRef, className: className }, getCombinatorEl(provided.dragHandleProps)));
}));
}
};
const PrimitiveFunctionEditor = (props) => {
// [handle] [column] [operator-dropdown] [...args] [delete-button] [plus-button]
const adaptable = useAdaptable();
const [columnOrFieldExpression, ...restOfArgs] = props.predicate.args;
const columnOrField = columnOrFieldExpression;
let columnOrFieldId = null;
let columnOrFieldDataType = null;
let columnInputDataType = null;
let functionInputInputDataTypes = null;
// Thsese are the type of inputs ommiting the column
// [[column-data-type], number, number]
let restOfFunctionInputDataTypes = [];
if (columnOrField) {
if (!isArgumentColumnOrField(columnOrField)) {
return React.createElement(ErrorBox, null, "Expression must start with a column or a filed!");
}
if (columnOrField.includes('FIELD')) {
// we let the full expression so we can difirienciate between column and field
columnOrFieldId = columnOrField;
const fieldValue = mapExpressionToFieldValue(columnOrField);
columnOrFieldDataType = adaptable.api.expressionApi.internalApi.getFieldType(fieldValue);
}
else if (columnOrField.includes('[')) {
columnOrFieldId = columnOrField;
const columnId = columnOrField.replace(/[\[\]]/g, '');
columnOrFieldDataType = adaptable.api.columnApi.getColumnDataTypeForColumnId(columnId);
}
columnInputDataType = mapColumnDataTypeToExpressionFunctionType(columnOrFieldDataType);
functionInputInputDataTypes = booleanExpressionFunctions[props.predicate.operator]?.inputs;
restOfFunctionInputDataTypes = functionInputInputDataTypes
? getFunctionMatchingInputTypes(columnInputDataType, functionInputInputDataTypes)
: [];
}
const level = props.id.split('/').length - 1;
return (React.createElement(Draggable, { key: props.id, draggableId: props.id, index: props.index }, (provided) => {
return (React.createElement(Flex, { className: `
${BASE_CLASS_NAME}
${BASE_CLASS_NAME}-level-${level}
${BASE_CLASS_NAME}-primitive
${props.lastChild ? `${BASE_CLASS_NAME}--last-child` : ''}
`, pb: 2, ref: provided.innerRef, ...provided.draggableProps, style: { ...provided.draggableProps.style, minHeight: ITEM_HEIGHT } },
React.createElement(Handle, { ...provided.dragHandleProps }),
React.createElement(Flex, { alignItems: "center", height: ITEM_HEIGHT },
React.createElement(Box, { mr: 2 },
React.createElement(PrimitiveColumnOrFieldSelector, { onChange: (colOrField) => {
props.onChange({
...props.predicate,
args: [colOrField],
operator: null,
});
}, fieldOrColumn: columnOrFieldId })),
columnOrFieldId && columnOrFieldDataType && (React.createElement(ExpressionSelector, { dataType: columnOrFieldDataType, onExpressionChange: (operator) => {
let args = [props.predicate.args[0]];
if (columnOrFieldDataType === 'boolean' && operator !== 'NOT') {
args = [props.predicate.args[0], 'TRUE'];
}
props.onChange({
...props.predicate,
operator,
// discard arguments
args,
});
}, value: props.predicate.operator })),
React.createElement(Flex, { flex: 1, ml: 2 }, restOfFunctionInputDataTypes.map((type, index) => {
const key = type + index;
const commonProps = {
lefthandColumnIdParam: columnOrFieldId,
inputType: type,
};
if (type.includes('[]')) {
return (React.createElement(PrimitiveMultiValueInput, { ...commonProps, key: key, value: restOfArgs, onChange: (values) => {
const args = [...props.predicate.args.slice(0, 1), ...values];
props.onChange({
...props.predicate,
args,
});
} }));
}
return (React.createElement(PrimiteValueInput, { ...commonProps, key: key, value: restOfArgs[index] ?? null, onChange: (value) => {
const args = [...props.predicate.args];
// +1 because col is the first argument
args[index + 1] = value;
props.onChange({
...props.predicate,
args,
});
} }));
}))),
React.createElement(Box, { flex: 1 }),
React.createElement(QueryPredicateButtons, { ...props })));
}));
};
/**
* Two types:
* - combinatory operator
* - can contain both expressons and combinators
* [handle] [combinator-dropdown]
* [children]
*
* - boolean function: ars do not contain other combinators
* [handle] [column] [operator-dropdown] [...args] [delete-button] [plus-button]
*/
export const QueryPredicateBuilder = (props) => {
if (isQlLogicalOperator(props.predicate.operator)) {
return React.createElement(LogicalFunctionEditor, { ...props });
}
else {
return React.createElement(PrimitiveFunctionEditor, { ...props });
}
};