import React, { useEffect, useRef } from "react"; import FormControl from "pages/Editor/FormControl"; import styled from "styled-components"; import { FieldArray, getFormValues } from "redux-form"; import type { ControlProps } from "./BaseControl"; import _ from "lodash"; import { useSelector } from "react-redux"; import { getBindingOrConfigPathsForWhereClauseControl } from "entities/Action/actionProperties"; import { WhereClauseSubComponent } from "./utils"; import useResponsiveBreakpoints from "utils/hooks/useResponsiveBreakpoints"; import { Button, Tooltip } from "design-system"; //Dropdwidth and Icon have fixed widths const DropdownWidth = 82; //pixel value const OperatorDropdownWidth = 100; // operators should have longer dropdown widths. const Margin = 8; // Type of the value for each condition export type whereClauseValueType = { condition?: string; children?: [whereClauseValueType]; key?: string; value?: string; }; // Form config for the value field const valueFieldConfig: any = { key: "value", controlType: "QUERY_DYNAMIC_INPUT_TEXT", placeholderText: "Value", }; // Form config for the key field const keyFieldConfig: any = { key: "key", controlType: "QUERY_DYNAMIC_INPUT_TEXT", placeholderText: "Column name", }; // Form config for the condition field const conditionFieldConfig: any = { key: "operator", controlType: "DROP_DOWN", initialValue: "EQ", options: [], }; // Form config for the operator field const logicalFieldConfig: any = { key: "condition", controlType: "DROP_DOWN", initialValue: "EQ", }; const LogicalFieldValue: any = styled.p<{ width: string | undefined; size: string; }>` ${(props) => (props.width ? "width: " + props.width + ";" : "")} text-align: right; color: var(--ads-v2-color-fg-muted); flex-shrink: 0; ${(props) => props.size === "small" && ` margin: 4px 0 0; text-align: left; `} `; // Component for the delete Icon const CenteredIconButton = styled(Button)<{ alignSelf?: string; top?: string; }>` position: relative; align-self: ${(props) => (props.alignSelf ? props.alignSelf : "center")}; top: ${(props) => (props.top ? props.top : "0px")}; `; // We are setting a background color for the last two nested levels const handleSecondaryBoxBackgroudColor = ( currentNestingLevel: number, nestedLevels: number, ) => { if (currentNestingLevel === nestedLevels) { return `background-color: var(--ads-v2-color-bg-muted);`; } else if (currentNestingLevel === nestedLevels - 1) { return `background-color: var(--ads-v2-color-bg-subtle);`; } else { return ""; } }; // Wrapper inside the main box, contains the dropdown and ConditionWrapper const SecondaryBox = styled.div<{ currentNestingLevel: number; nestedLevels: number; showBorder: boolean; size: string; }>` display: flex; flex-direction: column; position: relative; border-radius: var(--ads-v2-border-radius); border: solid 1px var(--ads-v2-color-border); border-width: ${(props) => (props?.showBorder ? "1px" : "0px")}; padding: ${(props) => props?.showBorder ? "0px 12px 12px 8px" : "4px 12px 12px 0px"}; width: 100%; // Setting a max width to not have it really elongate on very large screens max-width: 2000px; ${(props) => props.size === "small" && ` ${handleSecondaryBoxBackgroudColor( props.currentNestingLevel, props.nestedLevels, )} padding-bottom: 12px; `} `; // Wrapper to contain either a ConditionComponent or ConditionBlock const ConditionWrapper = styled.div<{ size: string }>` display: flex; flex-direction: row; align-items: center; width: 100%; gap: 5px; margin-top: var(--ads-v2-spaces-3); margin-bottom: 5px; ${(props) => props.size === "small" && ` // margin-top: 0px; gap: 0px; flex-direction: column; align-items: start; `} .ads-v2-select>.rc-select-selector { min-width: 80px; background-color: var(--ads-v2-color-bg); } `; // Wrapper to contain a single condition statement const ConditionBox = styled.div<{ size?: string }>` display: grid; // The 4 elements(3 input fields and a close button) are horizontally aligned // by default grid-template-columns: auto 100px auto max-content; grid-column-gap: 5px; grid-row-gap: 5px; width: 100%; ${(props) => props.size === "small" && ` // Smallest width of the component such that the text CTA's "ADD GROUP CONDITION" // fits in the available space without overflow min-width: 325px; margin: 5px 0px; // In small space we shift to a two column layout where the three inputs // are verticall aligned one below the other. grid-template-columns: repeat(2, max-content); grid-template-rows: repeat(3, max-content); grid-column-gap: 5px; // The three input fields will be in the first column & :not(:nth-child(4)) { grid-column-start: 1; } // The fourth element i.e the close button will be placed in the second row // to have it center aligned & :nth-child(4) { grid-column-start: 2; grid-row-start: 2; } `} `; // Box containing the action buttons to add more filters const ActionBox = styled.div<{ marginLeft: string; size: string }>` display: flex; flex-direction: row; gap: 5px; background-color: inherit; margin-left: ${(props) => props.marginLeft}; ${(props) => props.size === "small" && ` margin-left: 0; `} `; const GroupConditionBox = styled.div<{ size: string }>` display: flex; flex-direction: row; gap: 5px; width: 100%; ${(props) => props.size === "small" && ` gap: 5px; margin: 5px 0px; flex-direction: row; min-width: max-content; `} `; // Component to display single line of condition, includes 2 inputs and 1 dropdown function ConditionComponent(props: any, index: number) { // Custom styles have to be passed as props, otherwise the UI will be disproportional const keyPath = getBindingOrConfigPathsForWhereClauseControl( props.field, WhereClauseSubComponent.Key, ); const valuePath = getBindingOrConfigPathsForWhereClauseControl( props.field, WhereClauseSubComponent.Value, ); const conditionPath = getBindingOrConfigPathsForWhereClauseControl( props.field, WhereClauseSubComponent.Condition, ); return ( {/* Component to input the LHS for single condition */} {/* Component to select the operator for the 2 inputs */} {/* Component to input the RHS for single component */} {/* Component to render the delete icon */} { e.stopPropagation(); props.onDeletePressed(index); }} size="md" startIcon="close" /> ); } // This is the block which contains an operator and multiple conditions/ condition blocks function ConditionBlock(props: any) { const targetRef = useRef(null); // Smallest width of the component below which the individual input fields don't // decrease in width anymore so we decide to shift to small space layout at this point const size = useResponsiveBreakpoints(targetRef, [{ small: 505 }]); const formValues: any = useSelector((state) => getFormValues(props.formName)(state), ); const onDeletePressed = (index: number) => { props.fields.remove(index); }; // sometimes, this condition runs before the appropriate formValues has been initialized with the correct query values. useEffect(() => { // so make sure the new formValue has been initialized with the where object, // especially when switching between various queries across the same Query editor form. const whereConfigValue = _.get(formValues, props.configProperty); // if the where object exists then it means the initialization of the form has been completed. // if the where object exists and the length of children field is less than one, add a new field. if (props.fields.length < 1 && !!whereConfigValue) { if (props.currentNestingLevel === 0) { props.fields.push({ condition: props.comparisonTypes[0].value, }); } else { props.onDeletePressed(props.index); } } }, [props.fields.length]); let isDisabled = false; if (props.logicalTypes.length === 1) { isDisabled = true; } const logicalFieldPath = getBindingOrConfigPathsForWhereClauseControl( props.configProperty, WhereClauseSubComponent.Condition, ); const logicalFieldValue = _.get(formValues, logicalFieldPath); return ( = 1} size={size} > {props.fields && props.fields.length > 0 && props.fields.map((field: any, index: number) => { const fieldValue: whereClauseValueType = props.fields.get(index); return ( {/* Component to render the joining operator between multiple conditions */} {index == 0 ? ( Where ) : index == 1 ? ( ) : ( {logicalFieldValue} )} {!!fieldValue && "children" in fieldValue ? ( { e.stopPropagation(); onDeletePressed(index); }} size="md" startIcon="close" top={"24px"} /> ) : ( ConditionComponent( { onDeletePressed, field, formName: props.formName, comparisonTypes: props.comparisonTypes, maxWidth: props.maxWidth, currentNestingLevel: props.currentNestingLevel, size, }, index, ) )} ); })} {/* Check if the config allows more nesting, if it does, allow for adding more blocks */} For S3 only 4 nested where
condition group is allowed. } isDisabled={props.currentNestingLevel < props.nestedLevels} placement="bottom" >
); } export default function WhereClauseControl(props: WhereClauseControlProps) { const { comparisonTypes, // All possible keys for the comparison configProperty, // JSON path for the where clause data formName, // Name of the form, used by redux-form lib to store the data in redux store logicalTypes, // All possible keys for the logical operators joining multiple conditions nestedLevels, // Number of nested levels allowed } = props; // Max width is designed in a way that the proportion stays same even after nesting const maxWidth = 60; //in vw return ( ); } export type WhereClauseControlProps = ControlProps;