UNPKG

@react-awesome-query-builder-dev/ui

Version:
402 lines (362 loc) 13.6 kB
import React, { Component } from "react"; import { Utils } from "@react-awesome-query-builder-dev/core"; import PropTypes from "prop-types"; import RuleContainer from "../containers/RuleContainer"; import Draggable from "../containers/Draggable"; import OperatorWrapper from "../rule/OperatorWrapper"; import FieldWrapper from "../rule/FieldWrapper"; import Widget from "../rule/Widget"; import OperatorOptions from "../rule/OperatorOptions"; import {useOnPropsChanged} from "../../utils/reactUtils"; import {Col, dummyFn, WithConfirmFn, getRenderFromConfig} from "../utils"; import classNames from "classnames"; const {getFieldConfig, getOperatorConfig, getFieldWidgetConfig, getFieldId} = Utils.ConfigUtils; const {isEmptyRuleProperties} = Utils.RuleUtils; class Rule extends Component { static propTypes = { id: PropTypes.string.isRequired, groupId: PropTypes.string, selectedField: PropTypes.any, selectedFieldSrc: PropTypes.string, selectedFieldType: PropTypes.string, selectedOperator: PropTypes.string, operatorOptions: PropTypes.object, config: PropTypes.object.isRequired, value: PropTypes.any, //depends on widget valueSrc: PropTypes.any, valueType: PropTypes.any, asyncListValues: PropTypes.array, isDraggingMe: PropTypes.bool, isDraggingTempo: PropTypes.bool, parentField: PropTypes.string, //from RuleGroup valueError: PropTypes.any, fieldError: PropTypes.string, isLocked: PropTypes.bool, isTrueLocked: PropTypes.bool, //path: PropTypes.instanceOf(Immutable.List), //actions handleDraggerMouseDown: PropTypes.func, setField: PropTypes.func, setFieldSrc: PropTypes.func, setOperator: PropTypes.func, setOperatorOption: PropTypes.func, setLock: PropTypes.func, removeSelf: PropTypes.func, setValue: PropTypes.func, setValueSrc: PropTypes.func, setFuncValue: PropTypes.func, reordableNodesCnt: PropTypes.number, totalRulesCnt: PropTypes.number, parentReordableNodesCnt: PropTypes.number, parentFieldCanReorder: PropTypes.bool, }; constructor(props) { super(props); useOnPropsChanged(this); this.removeSelf = this.removeSelf.bind(this); this.setLock = this.setLock.bind(this); this.onPropsChanged(props); } onPropsChanged(nextProps) { const prevProps = this.props; const configChanged = !this.Icon || prevProps?.config !== nextProps?.config; const keysForMeta = ["selectedField", "selectedFieldSrc", "selectedFieldType", "selectedOperator", "config", "reordableNodesCnt", "isLocked", "parentField", "parentFieldCanReorder"]; const needUpdateMeta = !this.meta || keysForMeta.map(k => (nextProps[k] !== prevProps[k])).filter(ch => ch).length > 0; if (needUpdateMeta) { this.meta = this.getMeta(nextProps); } if (configChanged) { const { config } = nextProps; const { renderIcon, renderButton, renderButtonGroup, renderSwitch, renderBeforeWidget, renderAfterWidget, renderRuleError, } = config.settings; this.Icon = getRenderFromConfig(config, renderIcon); this.Btn = getRenderFromConfig(config, renderButton); this.BtnGrp = getRenderFromConfig(config, renderButtonGroup); this.Switch = getRenderFromConfig(config, renderSwitch); this.BeforeWidget = getRenderFromConfig(config, renderBeforeWidget); this.AfterWidget = getRenderFromConfig(config, renderAfterWidget); this.RuleError = getRenderFromConfig(config, renderRuleError); } this.doRemove = () => { this.props.removeSelf(); }; } getMeta({selectedField, selectedFieldType, selectedOperator, config, reordableNodesCnt, isLocked, parentField, parentFieldCanReorder}) { const {keepInputOnChangeFieldSrc} = config.settings; const selectedFieldId = getFieldId(selectedField, config); const selectedFieldConfig = getFieldConfig(config, selectedField); const isSelectedGroup = selectedFieldConfig && selectedFieldConfig.type === "!struct"; const isOkWithoutField = keepInputOnChangeFieldSrc && selectedFieldType; const isFieldSelected = !!selectedField || isOkWithoutField; const isFieldAndOpSelected = isFieldSelected && selectedOperator; const selectedOperatorConfig = getOperatorConfig(config, selectedOperator, selectedField); const selectedOperatorHasOptions = selectedOperatorConfig && selectedOperatorConfig.options != null; const selectedFieldWidgetConfig = getFieldWidgetConfig(config, selectedField, selectedOperator, null, null) || {}; const hideOperator = selectedFieldWidgetConfig.hideOperator; let showDragIcon = config.settings.canReorder && reordableNodesCnt > 1 && !isLocked; if (parentField) { showDragIcon = showDragIcon && parentFieldCanReorder; } const showOperator = isFieldSelected && !hideOperator; const showOperatorLabel = isFieldSelected && hideOperator && selectedFieldWidgetConfig.operatorInlineLabel; const showWidget = isFieldAndOpSelected && !isSelectedGroup; const showOperatorOptions = isFieldAndOpSelected && selectedOperatorHasOptions; return { selectedFieldId, selectedFieldWidgetConfig, showDragIcon, showOperator, showOperatorLabel, showWidget, showOperatorOptions }; } setLock(lock) { this.props.setLock(lock); } removeSelf() { const {confirmFn, config} = this.props; const {renderConfirm, removeRuleConfirmOptions: confirmOptions} = config.settings; if (confirmOptions && !this.isEmptyCurrentRule()) { renderConfirm.call(config.ctx, {...confirmOptions, onOk: this.doRemove, onCancel: null, confirmFn: confirmFn }, config.ctx); } else { this.doRemove(); } } _buildWidgetProps({ selectedField, selectedFieldSrc, selectedFieldType, selectedOperator, operatorOptions, value, valueType, valueSrc, asyncListValues, valueError, fieldError, parentField, }, { selectedFieldId }) { return { field: selectedField, fieldSrc: selectedFieldSrc, fieldType: selectedFieldType, fieldId: selectedFieldId, operator: selectedOperator, operatorOptions, value, valueType, valueSrc, asyncListValues, valueError, fieldError, parentField, }; } isEmptyCurrentRule() { const {config} = this.props; const ruleData = this._buildWidgetProps(this.props, this.meta); return isEmptyRuleProperties(ruleData, config); } renderField() { const { config, isLocked, parentField, groupId, id, selectedFieldSrc, selectedField, selectedFieldType, setField, setFuncValue, setFieldSrc, fieldError, } = this.props; const { immutableFieldsMode } = config.settings; const { selectedFieldId } = this.meta; // tip: don't allow function inside !group (yet) return <FieldWrapper key="field" classname={classNames( selectedFieldSrc == "func" ? "rule--field--func" : "rule--field", )} config={config} canSelectFieldSource={!parentField} selectedField={selectedField} selectedFieldSrc={selectedFieldSrc} selectedFieldType={selectedFieldType} selectedFieldId={selectedFieldId} fieldError={fieldError} setField={!immutableFieldsMode ? setField : dummyFn} setFuncValue={!immutableFieldsMode ? setFuncValue : dummyFn} setFieldSrc={!immutableFieldsMode ? setFieldSrc : dummyFn} parentField={parentField} readonly={immutableFieldsMode || isLocked} id={id} groupId={groupId} />; } renderOperator () { const {config, isLocked} = this.props; const { selectedFieldId, selectedFieldWidgetConfig, showOperator, showOperatorLabel } = this.meta; const { immutableOpsMode } = config.settings; return <OperatorWrapper key="operator" config={config} selectedField={this.props.selectedField} selectedFieldSrc={this.props.selectedFieldSrc} selectedFieldType={this.props.selectedFieldType} selectedFieldId={selectedFieldId} selectedOperator={this.props.selectedOperator} setOperator={!immutableOpsMode ? this.props.setOperator : dummyFn} showOperator={showOperator} showOperatorLabel={showOperatorLabel} selectedFieldWidgetConfig={selectedFieldWidgetConfig} readonly={immutableOpsMode || isLocked} id={this.props.id} groupId={this.props.groupId} />; } renderWidget() { const {config, isLocked} = this.props; const { showWidget } = this.meta; const { immutableValuesMode } = config.settings; if (!showWidget) return null; const widget = <Widget key="values" {...this._buildWidgetProps(this.props, this.meta)} config={config} setValue={!immutableValuesMode ? this.props.setValue : dummyFn} setValueSrc={!immutableValuesMode ? this.props.setValueSrc : dummyFn} setFuncValue={!immutableValuesMode ? this.props.setFuncValue : dummyFn} readonly={immutableValuesMode || isLocked} id={this.props.id} groupId={this.props.groupId} />; return ( <Col key={"widget-for-"+this.props.selectedOperator} className="rule--value"> {widget} </Col> ); } renderOperatorOptions() { const {config} = this.props; const { showOperatorOptions } = this.meta; const { immutableOpsMode, immutableValuesMode } = config.settings; if (!showOperatorOptions) return null; const opOpts = <OperatorOptions key="operatorOptions" selectedField={this.props.selectedField} selectedOperator={this.props.selectedOperator} operatorOptions={this.props.operatorOptions} setOperatorOption={!immutableOpsMode ? this.props.setOperatorOption : dummyFn} config={config} readonly={immutableValuesMode} />; return ( <Col key={"op-options-for-"+this.props.selectedOperator} className="rule--operator-options"> {opOpts} </Col> ); } renderBeforeWidget() { const BeforeWidget = this.BeforeWidget; if (!BeforeWidget) return null; return <Col key={"before-widget-for-" +this.props.selectedOperator} className="rule--before-widget"> <BeforeWidget {...this.props} /> </Col>; } renderAfterWidget() { const AfterWidget = this.AfterWidget; if (!AfterWidget) return null; return <Col key={"after-widget-for-" +this.props.selectedOperator} className="rule--after-widget"> <AfterWidget {...this.props} /> </Col>; } renderError() { const {config, valueError, fieldError} = this.props; const { showErrorMessage } = config.settings; const RuleError = this.RuleError; const oneError = [fieldError, ...(valueError?.toArray() || [])].filter(e => !!e).shift() || null; return showErrorMessage && oneError && <div className="rule--error"> {RuleError ? <RuleError error={oneError} /> : oneError} </div>; } renderDrag() { const { handleDraggerMouseDown } = this.props; const { showDragIcon } = this.meta; const Icon = this.Icon; const icon = <Icon type="drag" />; return showDragIcon && (<div key="rule-drag-icon" onMouseDown={handleDraggerMouseDown} className={"qb-drag-handler rule--drag-handler"} >{icon}</div>); } renderDel() { const {config, isLocked} = this.props; const { deleteLabel, immutableGroupsMode, canDeleteLocked } = config.settings; const Icon = this.Icon; const Btn = this.Btn; return !immutableGroupsMode && (!isLocked || isLocked && canDeleteLocked) && ( <Btn key="rule-del" type="delRule" onClick={this.removeSelf} label={deleteLabel} config={config} renderIcon={Icon} /> ); } renderLock() { const {config, isLocked, isTrueLocked, id} = this.props; const { lockLabel, lockedLabel, showLock, } = config.settings; const Switch = this.Switch; return showLock && !(isLocked && !isTrueLocked) && ( <Switch key="rule-lock" type="lock" id={id} value={isLocked} setValue={this.setLock} label={lockLabel} checkedLabel={lockedLabel} hideLabel={true} config={config} /> ); } render () { const { showOperatorOptions, selectedFieldWidgetConfig } = this.meta; const { valueSrc, value, config } = this.props; const canShrinkValue = valueSrc?.first() == "value" && !showOperatorOptions && value.size == 1 && selectedFieldWidgetConfig.fullWidth; const BtnGrp = this.BtnGrp; const parts = [ this.renderField(), this.renderOperator(), this.renderBeforeWidget(), this.renderWidget(), this.renderAfterWidget(), this.renderOperatorOptions(), ]; const body = <div key="rule-body" className={classNames("rule--body", canShrinkValue && "can--shrink--value")}>{parts}</div>; const error = this.renderError(); const drag = this.renderDrag(); const lock = this.renderLock(); const del = this.renderDel(); return ( <> {drag} <div key="rule-body-wrapper" className="rule--body--wrapper"> {body}{error} </div> <div key="rule-header-wrapper" className="rule--header"> <BtnGrp key="rule-header-group" config={config}> {lock} {del} </BtnGrp> </div> </> ); } } export default RuleContainer(Draggable("rule")(WithConfirmFn(Rule)));