UNPKG

@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
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 }); } };