diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_spec.js
index b2342ec463e..673cdfdad4d 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_spec.js
@@ -118,6 +118,8 @@ describe("Table Widget Functionality", function() {
.contains("is exactly")
.click();
cy.get(publish.inputValue).type(tabValue);
+ cy.wait(500);
+ cy.get(publish.applyFiltersBtn).click();
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
cy.get(publish.canvas)
@@ -127,8 +129,8 @@ describe("Table Widget Functionality", function() {
const tabValue = tabData;
expect(tabValue).to.be.equal("Lindsay Ferguson");
});
- cy.get(publish.filterBtn).click();
cy.get(publish.removeFilter).click();
+ cy.get("body").type("{esc}");
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
cy.readTabledataPublish("0", "3").then((tabData) => {
@@ -157,6 +159,8 @@ describe("Table Widget Functionality", function() {
.contains("contains")
.click();
cy.get(publish.inputValue).type("Lindsay");
+ cy.wait(500);
+ cy.get(publish.applyFiltersBtn).click();
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
cy.get(publish.canvas)
@@ -166,8 +170,8 @@ describe("Table Widget Functionality", function() {
const tabValue = tabData;
expect(tabValue).to.be.equal("Lindsay Ferguson");
});
- cy.get(publish.filterBtn).click();
cy.get(publish.removeFilter).click();
+ cy.get("body").type("{esc}");
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
cy.readTabledataPublish("0", "3").then((tabData) => {
@@ -196,6 +200,8 @@ describe("Table Widget Functionality", function() {
.contains("starts with")
.click();
cy.get(publish.inputValue).type("Lindsay");
+ cy.wait(500);
+ cy.get(publish.applyFiltersBtn).click();
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
cy.get(publish.canvas)
@@ -205,8 +211,8 @@ describe("Table Widget Functionality", function() {
const tabValue = tabData;
expect(tabValue).to.be.equal("Lindsay Ferguson");
});
- cy.get(publish.filterBtn).click();
cy.get(publish.removeFilter).click();
+ cy.get("body").type("{esc}");
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
cy.readTabledataPublish("0", "3").then((tabData) => {
@@ -235,6 +241,8 @@ describe("Table Widget Functionality", function() {
.contains("ends with")
.click();
cy.get(publish.inputValue).type("Ferguson");
+ cy.wait(500);
+ cy.get(publish.applyFiltersBtn).click();
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
cy.get(publish.canvas)
@@ -244,8 +252,8 @@ describe("Table Widget Functionality", function() {
const tabValue = tabData;
expect(tabValue).to.be.equal("Lindsay Ferguson");
});
- cy.get(publish.filterBtn).click();
cy.get(publish.removeFilter).click();
+ cy.get("body").type("{esc}");
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
cy.readTabledataPublish("0", "3").then((tabData) => {
diff --git a/app/client/cypress/locators/publishWidgetspage.json b/app/client/cypress/locators/publishWidgetspage.json
index 8e91ef0bfe9..ef6e1c45ea6 100644
--- a/app/client/cypress/locators/publishWidgetspage.json
+++ b/app/client/cypress/locators/publishWidgetspage.json
@@ -25,6 +25,7 @@
"searchInput": ".t--search-input",
"downloadBtn": ".t--table-download-btn",
"filterBtn": ".t--table-filter-toggle-btn",
+ "applyFiltersBtn": ".t--apply-filter-btn",
"attributeDropdown": ".t--table-filter-columns-dropdown",
"attributeValue": ".t--dropdown-option",
"conditionDropdown": ".t--table-filter-conditions-dropdown",
diff --git a/app/client/src/actions/widgetActions.tsx b/app/client/src/actions/widgetActions.tsx
index e45f15e366a..d67748f1c23 100644
--- a/app/client/src/actions/widgetActions.tsx
+++ b/app/client/src/actions/widgetActions.tsx
@@ -101,6 +101,15 @@ export const closePropertyPane = () => {
};
};
+export const closeTableFilterPane = () => {
+ return {
+ type: ReduxActionTypes.HIDE_TABLE_FILTER_PANE,
+ payload: {
+ force: false,
+ },
+ };
+};
+
export const copyWidget = (isShortcut: boolean) => {
return {
type: ReduxActionTypes.COPY_SELECTED_WIDGET_INIT,
diff --git a/app/client/src/assets/icons/control/add-circle.svg b/app/client/src/assets/icons/control/add-circle.svg
new file mode 100644
index 00000000000..e834e2039b2
--- /dev/null
+++ b/app/client/src/assets/icons/control/add-circle.svg
@@ -0,0 +1,5 @@
+
diff --git a/app/client/src/assets/icons/control/close-circle.svg b/app/client/src/assets/icons/control/close-circle.svg
new file mode 100644
index 00000000000..987f2d41a65
--- /dev/null
+++ b/app/client/src/assets/icons/control/close-circle.svg
@@ -0,0 +1,3 @@
+
diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/CascadeFields.tsx b/app/client/src/components/designSystems/appsmith/TableComponent/CascadeFields.tsx
index bbf90c2653a..ab5a34ed791 100644
--- a/app/client/src/components/designSystems/appsmith/TableComponent/CascadeFields.tsx
+++ b/app/client/src/components/designSystems/appsmith/TableComponent/CascadeFields.tsx
@@ -1,11 +1,13 @@
import React, { useState, useEffect, useCallback } from "react";
import styled from "styled-components";
import { Icon, InputGroup } from "@blueprintjs/core";
+import { debounce } from "lodash";
+import { AnyStyledComponent } from "styled-components";
+
import CustomizedDropdown from "pages/common/CustomizedDropdown";
import { Directions } from "utils/helpers";
import { Colors } from "constants/Colors";
import { ControlIcons } from "icons/ControlIcons";
-import { AnyStyledComponent } from "styled-components";
import { Skin } from "constants/DefaultTheme";
import AutoToolTipComponent from "components/designSystems/appsmith/TableComponent/AutoToolTipComponent";
import DatePickerComponent from "components/designSystems/blueprint/DatePickerComponent2";
@@ -14,16 +16,13 @@ import {
Condition,
ColumnTypes,
Operator,
-} from "components/designSystems/appsmith/TableComponent/Constants";
-import {
- DropdownOption,
ReactTableFilter,
-} from "components/designSystems/appsmith/TableComponent/TableFilters";
+} from "components/designSystems/appsmith/TableComponent/Constants";
+import { DropdownOption } from "components/designSystems/appsmith/TableComponent/TableFilters";
import { RenderOptionWrapper } from "components/designSystems/appsmith/TableComponent/TableStyledWrappers";
-import { debounce } from "lodash";
const StyledRemoveIcon = styled(
- ControlIcons.REMOVE_CONTROL as AnyStyledComponent,
+ ControlIcons.CLOSE_CIRCLE_CONTROL as AnyStyledComponent,
)`
padding: 0;
position: relative;
@@ -34,8 +33,8 @@ const StyledRemoveIcon = styled(
`;
const LabelWrapper = styled.div`
- width: 105px;
- text-align: center;
+ width: 95px;
+ margin-left: 10px;
color: ${Colors.BLUE_BAYOUX};
font-size: 14px;
font-weight: 500;
@@ -57,7 +56,7 @@ const StyledInputGroup = styled(InputGroup)`
background: ${Colors.WHITE};
border: 1px solid #d3dee3;
box-sizing: border-box;
- border-radius: 4px;
+ border-radius: 2px;
color: ${Colors.OXFORD_BLUE};
height: 32px;
width: 150px;
@@ -81,7 +80,7 @@ const DropdownTrigger = styled.div`
background: ${Colors.WHITE};
border: 1px solid #d3dee3;
box-sizing: border-box;
- border-radius: 4px;
+ border-radius: 2px;
font-size: 14px;
padding: 5px 12px 7px;
color: ${Colors.OXFORD_BLUE};
@@ -219,7 +218,7 @@ function RenderOptions(props: {
{selectedValue}
-
+
),
},
@@ -512,7 +511,7 @@ function Fields(props: CascadeFieldProps & { state: CascadeFieldState }) {
className={`t--table-filter-remove-btn ${
hasAnyFilters ? "" : "hide-icon"
}`}
- color={Colors.RIVER_BED}
+ color={Colors.GRAY}
height={16}
onClick={handleRemoveFilter}
width={16}
diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/Constants.test.ts b/app/client/src/components/designSystems/appsmith/TableComponent/Constants.test.ts
index 356919af056..6798d2751cf 100644
--- a/app/client/src/components/designSystems/appsmith/TableComponent/Constants.test.ts
+++ b/app/client/src/components/designSystems/appsmith/TableComponent/Constants.test.ts
@@ -49,6 +49,7 @@ describe("ConditionFunctions Constants", () => {
it("works as expected for endsWith", () => {
const conditionFunction = ConditionFunctions["endsWith"];
expect(conditionFunction("subtest", "test")).toStrictEqual(true);
+ expect(conditionFunction("subtest", "t")).toStrictEqual(true);
});
it("works as expected for is", () => {
const conditionFunction = ConditionFunctions["is"];
diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/Constants.ts b/app/client/src/components/designSystems/appsmith/TableComponent/Constants.ts
index 88c74bac352..7cde2342082 100644
--- a/app/client/src/components/designSystems/appsmith/TableComponent/Constants.ts
+++ b/app/client/src/components/designSystems/appsmith/TableComponent/Constants.ts
@@ -206,7 +206,7 @@ export const ConditionFunctions: {
},
endsWith: (a: any, b: any) => {
if (isString(a) && isString(b)) {
- return a.length === a.indexOf(b) + b.length;
+ return a.length === a.lastIndexOf(b) + b.length;
}
return false;
},
diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx b/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx
index 45e16fcd1ef..0380053c00f 100644
--- a/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx
+++ b/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx
@@ -13,7 +13,6 @@ import {
TableHeaderWrapper,
TableHeaderInnerWrapper,
} from "./TableStyledWrappers";
-import { ReactTableFilter } from "components/designSystems/appsmith/TableComponent/TableFilters";
import {
TableHeaderCell,
renderEmptyRows,
@@ -24,6 +23,7 @@ import TableHeader from "./TableHeader";
import { Classes } from "@blueprintjs/core";
import {
ReactTableColumnProps,
+ ReactTableFilter,
TABLE_SIZES,
CompactMode,
CompactModeTypes,
@@ -242,7 +242,6 @@ export function Table(props: TableProps) {
columns={tableHeadercolumns}
compactMode={props.compactMode}
currentPageIndex={currentPageIndex}
- editMode={props.editMode}
filters={props.filters}
isVisibleCompactMode={props.isVisibleCompactMode}
isVisibleDownload={props.isVisibleDownload}
@@ -262,6 +261,7 @@ export function Table(props: TableProps) {
tableSizes={tableSizes}
updateCompactMode={props.updateCompactMode}
updatePageNo={props.updatePageNo}
+ widgetId={props.widgetId}
widgetName={props.widgetName}
/>
diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/TableFilterPane.tsx b/app/client/src/components/designSystems/appsmith/TableComponent/TableFilterPane.tsx
new file mode 100644
index 00000000000..7402afa99d8
--- /dev/null
+++ b/app/client/src/components/designSystems/appsmith/TableComponent/TableFilterPane.tsx
@@ -0,0 +1,140 @@
+import React, { Component } from "react";
+import { connect } from "react-redux";
+import { get } from "lodash";
+import * as log from "loglevel";
+import { AppState } from "reducers";
+import styled from "styled-components";
+
+import { Colors } from "constants/Colors";
+import {
+ ReactTableColumnProps,
+ ReactTableFilter,
+} from "components/designSystems/appsmith/TableComponent/Constants";
+import TableFilterPaneContent from "components/designSystems/appsmith/TableComponent/TableFilterPaneContent";
+import { ThemeMode, getCurrentThemeMode } from "selectors/themeSelectors";
+import { Layers } from "constants/Layers";
+import Popper from "pages/Editor/Popper";
+import { generateClassName } from "utils/generators";
+import { getTableFilterState } from "selectors/tableFilterSelectors";
+import { getWidgetMetaProps } from "sagas/selectors";
+import { ReduxActionTypes } from "constants/ReduxActionConstants";
+import { selectWidgetAction } from "actions/widgetSelectionActions";
+import { ReactComponent as DragHandleIcon } from "assets/icons/ads/app-icons/draghandler.svg";
+
+const DragBlock = styled.div`
+ height: 41px;
+ width: 83px;
+ background: ${Colors.ATHENS_GRAY_DARKER};
+ box-sizing: border-box;
+ font-size: 12px;
+ color: ${Colors.SLATE_GRAY};
+ letter-spacing: 0.04em;
+ font-weight: 500;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ span {
+ padding-left: 8px;
+ color: ${Colors.GRAY};
+ }
+`;
+
+export interface TableFilterPaneProps {
+ widgetId: string;
+ columns: ReactTableColumnProps[];
+ filters?: ReactTableFilter[];
+ applyFilter: (filters: ReactTableFilter[]) => void;
+}
+
+interface PositionPropsInt {
+ top: number;
+ left: number;
+}
+
+type Props = ReturnType &
+ ReturnType &
+ TableFilterPaneProps;
+
+class TableFilterPane extends Component {
+ getPopperTheme() {
+ return ThemeMode.LIGHT;
+ }
+
+ handlePositionUpdate = (position: any) => {
+ this.props.setPanePoistion(
+ this.props.tableFilterPane.widgetId as string,
+ position,
+ );
+ };
+
+ render() {
+ if (
+ this.props.tableFilterPane.isVisible &&
+ this.props.tableFilterPane.widgetId === this.props.widgetId
+ ) {
+ log.debug("tablefilter pane rendered");
+ const className =
+ "t--table-filter-toggle-btn " +
+ generateClassName(this.props.tableFilterPane.widgetId);
+ const el = document.getElementsByClassName(className)[0];
+ return (
+
+
+ Move
+
+ }
+ renderDragBlockPositions={{
+ left: "0px",
+ }}
+ targetNode={el}
+ themeMode={this.getPopperTheme()}
+ zIndex={Layers.tableFilterPane}
+ >
+
+
+ );
+ } else {
+ return null;
+ }
+ }
+}
+
+const mapStateToProps = (state: AppState, ownProps: TableFilterPaneProps) => {
+ return {
+ tableFilterPane: getTableFilterState(state),
+ themeMode: getCurrentThemeMode(state),
+ metaProps: getWidgetMetaProps(state, ownProps.widgetId),
+ };
+};
+
+const mapDispatchToProps = (dispatch: any) => {
+ return {
+ setPanePoistion: (widgetId: string, position: any) => {
+ dispatch({
+ type: ReduxActionTypes.TABLE_PANE_MOVED,
+ payload: {
+ widgetId,
+ position,
+ },
+ });
+ dispatch(selectWidgetAction(widgetId));
+ },
+ hideFilterPane: (widgetId: string) => {
+ dispatch({
+ type: ReduxActionTypes.HIDE_TABLE_FILTER_PANE,
+ payload: { widgetId },
+ });
+ dispatch(selectWidgetAction(widgetId));
+ },
+ };
+};
+export default connect(mapStateToProps, mapDispatchToProps)(TableFilterPane);
diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/TableFilterPaneContent.tsx b/app/client/src/components/designSystems/appsmith/TableComponent/TableFilterPaneContent.tsx
new file mode 100644
index 00000000000..1ae60209780
--- /dev/null
+++ b/app/client/src/components/designSystems/appsmith/TableComponent/TableFilterPaneContent.tsx
@@ -0,0 +1,227 @@
+import React, { useEffect } from "react";
+import styled, { AnyStyledComponent } from "styled-components";
+import { Colors } from "constants/Colors";
+import {
+ ReactTableColumnProps,
+ ReactTableFilter,
+ Operator,
+ OperatorTypes,
+} from "components/designSystems/appsmith/TableComponent/Constants";
+import { DropdownOption } from "components/designSystems/appsmith/TableComponent/TableFilters";
+import Button from "components/editorComponents/Button";
+import CascadeFields from "components/designSystems/appsmith/TableComponent/CascadeFields";
+import {
+ createMessage,
+ TABLE_FILTER_COLUMN_TYPE_CALLOUT,
+} from "constants/messages";
+import { ControlIcons } from "icons/ControlIcons";
+
+const StyledPlusCircleIcon = styled(
+ ControlIcons.ADD_CIRCLE_CONTROL as AnyStyledComponent,
+)`
+ padding: 0;
+ position: relative;
+ cursor: pointer;
+ svg {
+ circle {
+ fill: none !important;
+ stroke: ${Colors.GREEN};
+ }
+ }
+`;
+
+const TableFilterOuterWrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ background: ${Colors.WHITE};
+ box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.2), 0px 2px 10px rgba(0, 0, 0, 0.1);
+`;
+
+const TableFilerWrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ padding: 2px 16px 14px;
+`;
+
+const ButtonWrapper = styled.div`
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+ align-items; center;
+ background: ${Colors.WHITE};
+ margin-top: 14px;
+ &&& button:hover {
+ background: transparent;
+ }
+`;
+
+const ButtonActionsWrapper = styled.div`
+ display: flex;
+ align-items; center;
+ &&& button {
+ margin-left: 14px;
+ }
+`;
+
+// margin-left is same as move block width in TableFilterPane.tsx
+const ColumnTypeBindingMessage = styled.div`
+ height: 41px;
+ line-height: 41px;
+ background: ${Colors.ATHENS_GRAY_DARKER};
+ box-sizing: border-box;
+ font-size: 12px;
+ color: ${Colors.SLATE_GRAY};
+ letter-spacing: 0.04em;
+ font-weight: 500;
+ padding: 0 16px;
+ margin-left: 83px;
+ min-width: 350px;
+ text-align: right;
+`;
+
+interface TableFilterProps {
+ columns: ReactTableColumnProps[];
+ filters?: ReactTableFilter[];
+ applyFilter: (filters: ReactTableFilter[]) => void;
+ hideFilterPane: (widgetId: string) => void;
+ widgetId: string;
+}
+
+const DEFAULT_FILTER = {
+ column: "",
+ operator: OperatorTypes.OR,
+ value: "",
+ condition: "",
+};
+
+function TableFilterPaneContent(props: TableFilterProps) {
+ const [filters, updateFilters] = React.useState(
+ new Array(),
+ );
+
+ useEffect(() => {
+ const filters: ReactTableFilter[] = props.filters ? [...props.filters] : [];
+ if (filters.length === 0) {
+ filters.push({ ...DEFAULT_FILTER });
+ }
+ updateFilters(filters);
+ }, [props.filters]);
+
+ const addFilter = () => {
+ const updatedFilters = filters ? [...filters] : [];
+ let operator: Operator = OperatorTypes.OR;
+ if (updatedFilters.length >= 2) {
+ operator = updatedFilters[1].operator;
+ }
+ updatedFilters.push({ ...DEFAULT_FILTER, operator });
+ updateFilters(updatedFilters);
+ };
+
+ const applyFilter = () => {
+ props.applyFilter(filters);
+ };
+
+ const hideFilter = () => {
+ props.hideFilterPane(props.widgetId);
+ };
+
+ const columns: DropdownOption[] = props.columns
+ .map((column: ReactTableColumnProps) => {
+ const type = column.metaProperties?.type || "text";
+ return {
+ label: column.Header,
+ value: column.accessor,
+ type: type,
+ };
+ })
+ .filter((column: { label: string; value: string; type: string }) => {
+ return !["video", "button", "image"].includes(column.type as string);
+ });
+ const hasAnyFilters = !!(
+ filters.length >= 1 &&
+ filters[0].column &&
+ filters[0].condition
+ );
+ return (
+ {
+ e.stopPropagation();
+ }}
+ >
+
+ {createMessage(TABLE_FILTER_COLUMN_TYPE_CALLOUT)}
+
+ e.stopPropagation()}>
+ {filters.map((filter: ReactTableFilter, index: number) => {
+ return (
+ {
+ // here updated filters store in state, not in redux
+ const updatedFilters = filters ? [...filters] : [];
+ updatedFilters[index] = filter;
+ updateFilters(updatedFilters);
+ }}
+ column={filter.column}
+ columns={columns}
+ condition={filter.condition}
+ hasAnyFilters={hasAnyFilters}
+ index={index}
+ key={index}
+ operator={
+ filters.length >= 2 ? filters[1].operator : filter.operator
+ }
+ removeFilter={(index: number) => {
+ if (index === 1 && filters.length > 2) {
+ filters[2].operator = filters[1].operator;
+ }
+ const newFilters = [
+ ...filters.slice(0, index),
+ ...filters.slice(index + 1),
+ ];
+ if (newFilters.length === 0) {
+ newFilters.push({ ...DEFAULT_FILTER });
+ }
+ // removed filter directly update redux
+ // with redux update, useEffect will update local state too
+ props.applyFilter(newFilters);
+ }}
+ value={filter.value}
+ />
+ );
+ })}
+ {hasAnyFilters ? (
+
+ }
+ intent="primary"
+ onClick={addFilter}
+ size="small"
+ text="Add Filter"
+ />
+
+
+
+
+
+ ) : null}
+
+
+ );
+}
+
+export default TableFilterPaneContent;
diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/TableFilters.tsx b/app/client/src/components/designSystems/appsmith/TableComponent/TableFilters.tsx
index d05dfbb7532..c53c4d848c8 100644
--- a/app/client/src/components/designSystems/appsmith/TableComponent/TableFilters.tsx
+++ b/app/client/src/components/designSystems/appsmith/TableComponent/TableFilters.tsx
@@ -1,53 +1,21 @@
-import React, { useEffect } from "react";
-import {
- Popover,
- PopoverInteractionKind,
- Position,
- Classes,
-} from "@blueprintjs/core";
+import React, { useEffect, useCallback } from "react";
+import { useDispatch, useSelector } from "react-redux";
import { IconWrapper } from "constants/IconConstants";
import styled from "styled-components";
import { Colors } from "constants/Colors";
import { ReactComponent as FilterIcon } from "assets/icons/control/filter-icon.svg";
import { TableIconWrapper } from "components/designSystems/appsmith/TableComponent/TableStyledWrappers";
-import Button from "components/editorComponents/Button";
-import CascadeFields from "components/designSystems/appsmith/TableComponent/CascadeFields";
import TableAction from "components/designSystems/appsmith/TableComponent/TableAction";
+import TableFilterPane from "components/designSystems/appsmith/TableComponent/TableFilterPane";
import {
ReactTableColumnProps,
- Condition,
- Operator,
+ ReactTableFilter,
OperatorTypes,
} from "components/designSystems/appsmith/TableComponent/Constants";
-import {
- createMessage,
- TABLE_FILTER_COLUMN_TYPE_CALLOUT,
-} from "constants/messages";
-
-const TableFilterOuterWrapper = styled.div`
- display: flex;
- flex-direction: column;
- width: 100%;
-`;
-
-const TableFilerWrapper = styled.div`
- display: flex;
- flex-direction: column;
- width: 100%;
- padding: 2px 16px 14px;
-`;
-
-const ButtonWrapper = styled.div`
- display: flex;
- width: 100%;
- justify-content: flex-start;
- align-items; center;
- background: ${Colors.WHITE};
- margin-top: 14px;
- &&& button:hover {
- background: transparent;
- }
-`;
+import { hidePropertyPane } from "actions/propertyPaneActions";
+import { ReduxActionTypes } from "constants/ReduxActionConstants";
+import { generateClassName } from "utils/generators";
+import { getTableFilterState } from "selectors/tableFilterSelectors";
const SelectedFilterWrapper = styled.div`
position: absolute;
@@ -67,29 +35,6 @@ const SelectedFilterWrapper = styled.div`
color: ${Colors.WHITE};
`;
-const ColumnTypeBindingMessage = styled.div`
- width: 100%;
- height: 41px;
- line-height: 41px;
- background: ${Colors.ATHENS_GRAY_DARKER};
- border: 1px dashed ${Colors.GEYSER_LIGHT};
- box-sizing: border-box;
- font-size: 12px;
- color: ${Colors.SLATE_GRAY};
- letter-spacing: 0.04em;
- font-weight: 500;
- padding: 0 16px;
- min-width: 350px;
- border-bottom-left-radius: 4px;
- border-bottom-right-radius: 4px;
-`;
-export interface ReactTableFilter {
- column: string;
- operator: Operator;
- condition: Condition;
- value: any;
-}
-
export interface DropdownOption {
label: string;
value: string;
@@ -99,15 +44,17 @@ interface TableFilterProps {
columns: ReactTableColumnProps[];
filters?: ReactTableFilter[];
applyFilter: (filters: ReactTableFilter[]) => void;
- editMode: boolean;
+ widgetId: string;
}
function TableFilters(props: TableFilterProps) {
- const [selected, selectMenu] = React.useState(false);
const [filters, updateFilters] = React.useState(
new Array(),
);
+ const dispatch = useDispatch();
+ const tableFilterPaneState = useSelector(getTableFilterState);
+
useEffect(() => {
const filters: ReactTableFilter[] = props.filters ? [...props.filters] : [];
if (filters.length === 0) {
@@ -121,20 +68,26 @@ function TableFilters(props: TableFilterProps) {
updateFilters(filters);
}, [props.filters]);
- const addFilter = () => {
- const updatedFilters = props.filters ? [...props.filters] : [];
- let operator: Operator = OperatorTypes.OR;
- if (updatedFilters.length >= 2) {
- operator = updatedFilters[1].operator;
- }
- updatedFilters.push({
- column: "",
- operator: operator,
- value: "",
- condition: "",
- });
- props.applyFilter(updatedFilters);
- };
+ const toggleFilterPane = useCallback(
+ (selected: boolean) => {
+ if (selected) {
+ // filter button select
+ dispatch(hidePropertyPane());
+ dispatch({
+ type: ReduxActionTypes.SHOW_TABLE_FILTER_PANE,
+ payload: { widgetId: props.widgetId, force: true },
+ });
+ } else {
+ // filter button de-select
+ dispatch({
+ type: ReduxActionTypes.HIDE_TABLE_FILTER_PANE,
+ payload: { widgetId: props.widgetId },
+ });
+ }
+ },
+ [props.widgetId],
+ );
+
if (props.columns.length === 0) {
return (
@@ -144,107 +97,36 @@ function TableFilters(props: TableFilterProps) {
);
}
- const columns: DropdownOption[] = props.columns
- .map((column: ReactTableColumnProps) => {
- const type = column.metaProperties?.type || "text";
- return {
- label: column.Header,
- value: column.accessor,
- type: type,
- };
- })
- .filter((column: { label: string; value: string; type: string }) => {
- return !["video", "button", "image"].includes(column.type as string);
- });
+
const hasAnyFilters = !!(
filters.length >= 1 &&
filters[0].column &&
filters[0].condition
);
+ const className =
+ "t--table-filter-toggle-btn " + generateClassName(props.widgetId);
+ const isTableFilterPaneVisible =
+ tableFilterPaneState.isVisible &&
+ tableFilterPaneState.widgetId === props.widgetId;
+
return (
- {
- selectMenu(false);
- }}
- position={Position.BOTTOM}
- usePortal
- >
+ <>
{filters.length}
) : null
}
- selectMenu={(selected: boolean) => {
- selectMenu(selected);
- }}
- selected={selected}
+ selectMenu={toggleFilterPane}
+ selected={isTableFilterPaneVisible}
title={`Filters${hasAnyFilters ? ` (${filters.length})` : ""}`}
titleColor={hasAnyFilters ? Colors.CODE_GRAY : Colors.GRAY}
>
-
- e.stopPropagation()}>
- {filters.map((filter: ReactTableFilter, index: number) => {
- return (
- {
- const updatedFilters = props.filters
- ? [...props.filters]
- : [];
- updatedFilters[index] = filter;
- props.applyFilter(updatedFilters);
- }}
- column={filter.column}
- columns={columns}
- condition={filter.condition}
- hasAnyFilters={hasAnyFilters}
- index={index}
- key={index}
- operator={
- filters.length >= 2 ? filters[1].operator : filter.operator
- }
- removeFilter={(index: number) => {
- const filters: ReactTableFilter[] = props.filters || [];
- if (index === 1 && filters.length > 2) {
- filters[2].operator = filters[1].operator;
- }
- const newFilters = [
- ...filters.slice(0, index),
- ...filters.slice(index + 1),
- ];
- props.applyFilter(newFilters);
- }}
- value={filter.value}
- />
- );
- })}
- {hasAnyFilters ? (
-
-
-
- ) : null}
-
- {props.editMode && (
-
- {createMessage(TABLE_FILTER_COLUMN_TYPE_CALLOUT)}
-
- )}
-
-
+
+ >
);
}
diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/TableHeader.tsx b/app/client/src/components/designSystems/appsmith/TableComponent/TableHeader.tsx
index 01fb8b0188d..2c13131211d 100644
--- a/app/client/src/components/designSystems/appsmith/TableComponent/TableHeader.tsx
+++ b/app/client/src/components/designSystems/appsmith/TableComponent/TableHeader.tsx
@@ -9,11 +9,10 @@ import {
} from "./TableStyledWrappers";
import SearchComponent from "components/designSystems/appsmith/SearchComponent";
// import TableColumnsVisibility from "components/designSystems/appsmith/TableColumnsVisibility";
-import TableFilters, {
- ReactTableFilter,
-} from "components/designSystems/appsmith/TableComponent/TableFilters";
+import TableFilters from "components/designSystems/appsmith/TableComponent/TableFilters";
import {
ReactTableColumnProps,
+ ReactTableFilter,
CompactMode,
TableSizes,
} from "components/designSystems/appsmith/TableComponent/Constants";
@@ -101,12 +100,12 @@ interface TableHeaderProps {
columns: ReactTableColumnProps[];
hiddenColumns?: string[];
widgetName: string;
+ widgetId: string;
searchKey: string;
searchTableData: (searchKey: any) => void;
serverSidePaginationEnabled: boolean;
filters?: ReactTableFilter[];
applyFilter: (filters: ReactTableFilter[]) => void;
- editMode: boolean;
compactMode?: CompactMode;
updateCompactMode: (compactMode: CompactMode) => void;
tableSizes: TableSizes;
@@ -135,8 +134,8 @@ function TableHeader(props: TableHeaderProps) {
)}
diff --git a/app/client/src/components/editorComponents/DraggableComponent.tsx b/app/client/src/components/editorComponents/DraggableComponent.tsx
index 19f0b04e9bd..14a4758b325 100644
--- a/app/client/src/components/editorComponents/DraggableComponent.tsx
+++ b/app/client/src/components/editorComponents/DraggableComponent.tsx
@@ -8,6 +8,7 @@ import { AppState } from "reducers";
import { getColorWithOpacity } from "constants/DefaultTheme";
import {
useShowPropertyPane,
+ useShowTableFilterPane,
useWidgetDragResize,
} from "utils/hooks/dragResizeHooks";
import AnalyticsUtil from "utils/AnalyticsUtil";
@@ -61,6 +62,7 @@ export const canDrag = (
function DraggableComponent(props: DraggableComponentProps) {
// Dispatch hook handy to toggle property pane
const showPropertyPane = useShowPropertyPane();
+ const showTableFilterPane = useShowTableFilterPane();
// Dispatch hook handy to set a widget as focused/selected
const { focusWidget, selectWidget } = useWidgetSelection();
@@ -115,7 +117,8 @@ function DraggableComponent(props: DraggableComponentProps) {
selectWidget &&
selectedWidget !== props.widgetId &&
selectWidget(props.widgetId);
-
+ // Make sure that this tableFilterPane should close
+ showTableFilterPane && showTableFilterPane();
// Tell the rest of the application that a widget has started dragging
setIsDragging && setIsDragging(true);
diff --git a/app/client/src/components/editorComponents/ResizableComponent.tsx b/app/client/src/components/editorComponents/ResizableComponent.tsx
index 7bfc735b255..c18439e5109 100644
--- a/app/client/src/components/editorComponents/ResizableComponent.tsx
+++ b/app/client/src/components/editorComponents/ResizableComponent.tsx
@@ -16,6 +16,7 @@ import {
} from "./ResizableUtils";
import {
useShowPropertyPane,
+ useShowTableFilterPane,
useWidgetDragResize,
} from "utils/hooks/dragResizeHooks";
import { useSelector } from "react-redux";
@@ -60,6 +61,7 @@ export const ResizableComponent = memo(function ResizableComponent(
const isCommentMode = useSelector(commentModeSelector);
const showPropertyPane = useShowPropertyPane();
+ const showTableFilterPane = useShowTableFilterPane();
const { selectWidget } = useWidgetSelection();
const { setIsResizing } = useWidgetDragResize();
const selectedWidget = useSelector(
@@ -256,6 +258,8 @@ export const ResizableComponent = memo(function ResizableComponent(
selectWidget &&
selectedWidget !== props.widgetId &&
selectWidget(props.widgetId);
+ // Make sure that this tableFilterPane should close
+ showTableFilterPane && showTableFilterPane();
AnalyticsUtil.logEvent("WIDGET_RESIZE_START", {
widgetName: props.widgetName,
widgetType: props.type,
diff --git a/app/client/src/components/editorComponents/WidgetNameComponent/index.tsx b/app/client/src/components/editorComponents/WidgetNameComponent/index.tsx
index 61bf20578f0..32a561ba365 100644
--- a/app/client/src/components/editorComponents/WidgetNameComponent/index.tsx
+++ b/app/client/src/components/editorComponents/WidgetNameComponent/index.tsx
@@ -4,12 +4,16 @@ import { useSelector } from "react-redux";
import { AppState } from "reducers";
import { PropertyPaneReduxState } from "reducers/uiReducers/propertyPaneReducer";
import SettingsControl, { Activities } from "./SettingsControl";
-import { useShowPropertyPane } from "utils/hooks/dragResizeHooks";
+import {
+ useShowPropertyPane,
+ useShowTableFilterPane,
+} from "utils/hooks/dragResizeHooks";
import AnalyticsUtil from "utils/AnalyticsUtil";
import { WidgetType, WidgetTypes } from "constants/WidgetConstants";
import PerformanceTracker, {
PerformanceTransactionName,
} from "utils/PerformanceTracker";
+import { getIsTableFilterPaneVisible } from "selectors/tableFilterSelectors";
import { useWidgetSelection } from "utils/hooks/useWidgetSelection";
const PositionStyle = styled.div<{ topRow: number }>`
@@ -46,6 +50,7 @@ type WidgetNameComponentProps = {
export function WidgetNameComponent(props: WidgetNameComponentProps) {
const showPropertyPane = useShowPropertyPane();
+ const showTableFilterPane = useShowTableFilterPane();
// Dispatch hook handy to set a widget as focused/selected
const { selectWidget } = useWidgetSelection();
const propertyPaneState: PropertyPaneReduxState = useSelector(
@@ -68,6 +73,8 @@ export function WidgetNameComponent(props: WidgetNameComponentProps) {
(state: AppState) => state.ui.widgetDragResize.isDragging,
);
+ const isTableFilterPaneVisible = useSelector(getIsTableFilterPaneVisible);
+
const togglePropertyEditor = (e: any) => {
if (
(!propertyPaneState.isVisible &&
@@ -84,6 +91,8 @@ export function WidgetNameComponent(props: WidgetNameComponentProps) {
widgetType: props.type,
widgetId: props.widgetId,
});
+ // hide table filter pane if open
+ isTableFilterPaneVisible && showTableFilterPane && showTableFilterPane();
showPropertyPane && showPropertyPane(props.widgetId, undefined, true);
selectWidget && selectWidget(props.widgetId);
} else {
diff --git a/app/client/src/constants/Layers.tsx b/app/client/src/constants/Layers.tsx
index 939e25e5d89..4dbcc028e3c 100644
--- a/app/client/src/constants/Layers.tsx
+++ b/app/client/src/constants/Layers.tsx
@@ -39,6 +39,7 @@ export const Layers = {
// Sidebar needs to be more than modal so that u can use side bar whilst u have the modal showing up on the canvas.
sideBar: Indices.Layer3,
propertyPane: Indices.Layer3,
+ tableFilterPane: Indices.Layer6,
help: Indices.Layer4,
contextMenu: Indices.Layer4,
diff --git a/app/client/src/constants/ReduxActionConstants.tsx b/app/client/src/constants/ReduxActionConstants.tsx
index b107d57ef88..c0cd024666e 100644
--- a/app/client/src/constants/ReduxActionConstants.tsx
+++ b/app/client/src/constants/ReduxActionConstants.tsx
@@ -475,6 +475,9 @@ export const ReduxActionTypes: { [key: string]: string } = {
SET_RECENT_ENTITIES: "SET_RECENT_ENTITIES",
RESET_RECENT_ENTITIES: "RESET_RECENT_ENTITIES",
UPDATE_API_ACTION_BODY_CONTENT_TYPE: "UPDATE_API_ACTION_BODY_CONTENT_TYPE",
+ SHOW_TABLE_FILTER_PANE: "SHOW_TABLE_FILTER_PANE",
+ HIDE_TABLE_FILTER_PANE: "HIDE_TABLE_FILTER_PANE",
+ TABLE_PANE_MOVED: "TABLE_PANE_MOVED",
EXECUTE_COMMAND: "EXECUTE_COMMAND",
};
diff --git a/app/client/src/icons/ControlIcons.tsx b/app/client/src/icons/ControlIcons.tsx
index 97820ad8fef..33be1802ef0 100644
--- a/app/client/src/icons/ControlIcons.tsx
+++ b/app/client/src/icons/ControlIcons.tsx
@@ -11,6 +11,8 @@ import { ReactComponent as IncreaseIcon } from "assets/icons/control/increase.sv
import { ReactComponent as DecreaseIcon } from "assets/icons/control/decrease.svg";
import { ReactComponent as DraggableIcon } from "assets/icons/control/draggable.svg";
import { ReactComponent as CloseIcon } from "assets/icons/control/close.svg";
+import { ReactComponent as CloseCircleIcon } from "assets/icons/control/close-circle.svg";
+import { ReactComponent as AddCircleIcon } from "assets/icons/control/add-circle.svg";
import { ReactComponent as HelpIcon } from "assets/icons/control/help.svg";
import { ReactComponent as CollapseIcon } from "assets/icons/control/collapse.svg";
import { ReactComponent as PickMyLocationSelectedIcon } from "assets/icons/control/pick-location-selected.svg";
@@ -109,6 +111,16 @@ export const ControlIcons: {
),
+ CLOSE_CIRCLE_CONTROL: (props: IconProps) => (
+
+
+
+ ),
+ ADD_CIRCLE_CONTROL: (props: IconProps) => (
+
+
+
+ ),
PICK_MY_LOCATION_SELECTED_CONTROL: (props: IconProps) => (
diff --git a/app/client/src/pages/Editor/GlobalHotKeys.tsx b/app/client/src/pages/Editor/GlobalHotKeys.tsx
index e29910838a3..96fc6ef9176 100644
--- a/app/client/src/pages/Editor/GlobalHotKeys.tsx
+++ b/app/client/src/pages/Editor/GlobalHotKeys.tsx
@@ -5,6 +5,7 @@ import { Hotkey, Hotkeys } from "@blueprintjs/core";
import { HotkeysTarget } from "@blueprintjs/core/lib/esnext/components/hotkeys/hotkeysTarget.js";
import {
closePropertyPane,
+ closeTableFilterPane,
copyWidget,
cutWidget,
deleteSelectedWidget,
@@ -36,6 +37,7 @@ type Props = {
resetCommentMode: () => void;
openDebugger: () => void;
closeProppane: () => void;
+ closeTableFilterProppane: () => void;
executeAction: () => void;
selectAllWidgetsInit: () => void;
deselectAllWidgets: () => void;
@@ -189,6 +191,7 @@ class GlobalHotKeys extends React.Component {
this.props.resetCommentMode();
this.props.deselectAllWidgets();
this.props.closeProppane();
+ this.props.closeTableFilterProppane();
e.preventDefault();
}}
/>
@@ -238,6 +241,7 @@ const mapDispatchToProps = (dispatch: any) => {
resetCommentMode: () => dispatch(setCommentModeAction(false)),
openDebugger: () => dispatch(showDebugger()),
closeProppane: () => dispatch(closePropertyPane()),
+ closeTableFilterProppane: () => dispatch(closeTableFilterPane()),
selectAllWidgetsInit: () => dispatch(selectAllWidgetsInCanvasInitAction()),
deselectAllWidgets: () => dispatch(selectMultipleWidgetsAction([])),
executeAction: () => dispatch(runActionViaShortcut()),
diff --git a/app/client/src/pages/Editor/Popper.tsx b/app/client/src/pages/Editor/Popper.tsx
index 652b5db9617..569bfa1b0e4 100644
--- a/app/client/src/pages/Editor/Popper.tsx
+++ b/app/client/src/pages/Editor/Popper.tsx
@@ -15,6 +15,13 @@ export type PopperProps = {
themeMode?: ThemeMode;
targetNode?: Element;
children: JSX.Element | null;
+ renderDragBlock?: JSX.Element;
+ renderDragBlockPositions?: {
+ left?: string;
+ top?: string;
+ zIndex?: string;
+ position?: string;
+ };
placement: Placement;
modifiers?: Partial;
isDraggable?: boolean;
@@ -79,8 +86,10 @@ export default (props: PopperProps) => {
isDraggable = false,
disablePopperEvents = false,
position,
+ renderDragBlock,
onPositionChange = noop,
themeMode = props.themeMode || ThemeMode.LIGHT,
+ renderDragBlockPositions,
} = props;
// Meomoizing to avoid rerender of draggable icon.
// What is the cost of memoizing?
@@ -144,11 +153,15 @@ export default (props: PopperProps) => {
_popper.popper,
onPositionChange,
position,
- () => (
-
-
-
- ),
+ renderDragBlockPositions,
+ () =>
+ !!renderDragBlock ? (
+ renderDragBlock
+ ) : (
+
+
+
+ ),
);
}
diff --git a/app/client/src/pages/Editor/PropertyPane/index.tsx b/app/client/src/pages/Editor/PropertyPane/index.tsx
index e4628835192..6a13c80624b 100644
--- a/app/client/src/pages/Editor/PropertyPane/index.tsx
+++ b/app/client/src/pages/Editor/PropertyPane/index.tsx
@@ -216,18 +216,17 @@ class PropertyPane extends Component {
const el = document.getElementsByClassName(
generateClassName(this.props.widgetProperties?.widgetId),
)[0];
-
return (
+ onPositionChange={(position: any) => {
this.props.setPropPanePoistion(
position,
this.props.widgetProperties?.widgetId,
- )
- }
+ );
+ }}
placement="right-start"
position={this.props?.propPanePreference?.position}
targetNode={el}
diff --git a/app/client/src/pages/Editor/WidgetsEditor.tsx b/app/client/src/pages/Editor/WidgetsEditor.tsx
index 5886fd12061..23ce712da31 100644
--- a/app/client/src/pages/Editor/WidgetsEditor.tsx
+++ b/app/client/src/pages/Editor/WidgetsEditor.tsx
@@ -24,7 +24,7 @@ import { getCurrentApplication } from "selectors/applicationSelectors";
import { MainContainerLayoutControl } from "./MainContainerLayoutControl";
import { useDynamicAppLayout } from "utils/hooks/useDynamicAppLayout";
import Debugger from "components/editorComponents/Debugger";
-import { closePropertyPane } from "actions/widgetActions";
+import { closePropertyPane, closeTableFilterPane } from "actions/widgetActions";
import { useWidgetSelection } from "utils/hooks/useWidgetSelection";
const EditorWrapper = styled.div`
@@ -102,6 +102,7 @@ function WidgetsEditor() {
focusWidget && focusWidget();
deselectAll && deselectAll();
dispatch(closePropertyPane());
+ dispatch(closeTableFilterPane());
}, [focusWidget, deselectAll]);
const pageLoading = (
diff --git a/app/client/src/pages/Editor/utils.ts b/app/client/src/pages/Editor/utils.ts
index 8371ff6387e..765b69ae23e 100644
--- a/app/client/src/pages/Editor/utils.ts
+++ b/app/client/src/pages/Editor/utils.ts
@@ -7,6 +7,12 @@ export const draggableElement = (
element: any,
onPositionChange: any,
initPostion?: any,
+ renderDragBlockPositions?: {
+ left?: string;
+ top?: string;
+ zIndex?: string;
+ position?: string;
+ },
dragHandle?: () => JSX.Element,
) => {
let newXPos = 0,
@@ -113,7 +119,12 @@ export const draggableElement = (
const OnInit = () => {
if (dragHandle) {
- dragHandler = createDragHandler(id, element, dragHandle);
+ dragHandler = createDragHandler(
+ id,
+ element,
+ dragHandle,
+ renderDragBlockPositions,
+ );
}
if (initPostion) {
setElementPosition();
@@ -130,14 +141,20 @@ const createDragHandler = (
id: string,
el: any,
dragHandle: () => JSX.Element,
+ renderDragBlockPositions?: {
+ left?: string;
+ top?: string;
+ zIndex?: string;
+ position?: string;
+ },
) => {
const oldDragHandler = document.getElementById(`${id}-draghandler`);
const dragElement = document.createElement("div");
dragElement.setAttribute("id", `${id}-draghandler`);
- dragElement.style.position = "absolute";
- dragElement.style.left = "135px";
- dragElement.style.top = "0px";
- dragElement.style.zIndex = "3";
+ dragElement.style.position = renderDragBlockPositions?.position ?? "absolute";
+ dragElement.style.left = renderDragBlockPositions?.left ?? "135px";
+ dragElement.style.top = renderDragBlockPositions?.top ?? "0px";
+ dragElement.style.zIndex = renderDragBlockPositions?.zIndex ?? "3";
oldDragHandler
? el.replaceChild(dragElement, oldDragHandler)
: el.appendChild(dragElement);
diff --git a/app/client/src/reducers/entityReducers/metaReducer.ts b/app/client/src/reducers/entityReducers/metaReducer.ts
index ab009269948..53696616a0c 100644
--- a/app/client/src/reducers/entityReducers/metaReducer.ts
+++ b/app/client/src/reducers/entityReducers/metaReducer.ts
@@ -22,6 +22,27 @@ export const metaReducer = createReducer(initialState, {
return next;
},
+ [ReduxActionTypes.TABLE_PANE_MOVED]: (
+ state: MetaState,
+ action: ReduxAction,
+ ) => {
+ const next = { ...state };
+ let widgetMetaProps: Record = next[action.payload.widgetId];
+ if (widgetMetaProps === undefined) {
+ widgetMetaProps = {
+ isMoved: true,
+ position: { ...action.payload.position },
+ };
+ } else {
+ widgetMetaProps = {
+ ...widgetMetaProps,
+ isMoved: true,
+ position: { ...action.payload.position },
+ };
+ }
+ next[action.payload.widgetId] = widgetMetaProps;
+ return next;
+ },
[ReduxActionTypes.WIDGET_DELETE]: (
state: MetaState,
action: ReduxAction<{ widgetId: string }>,
@@ -54,4 +75,13 @@ export const metaReducer = createReducer(initialState, {
},
});
+interface TableFilterPanePositionConfig {
+ widgetId: string;
+ isMoved: boolean;
+ position: {
+ left: number;
+ top: number;
+ };
+}
+
export default metaReducer;
diff --git a/app/client/src/reducers/index.tsx b/app/client/src/reducers/index.tsx
index 0099c6d984b..b9eeaf3dca3 100644
--- a/app/client/src/reducers/index.tsx
+++ b/app/client/src/reducers/index.tsx
@@ -45,6 +45,7 @@ import { CommentsReduxState } from "./uiReducers/commentsReducer/interfaces";
import { WebsocketReduxState } from "./uiReducers/websocketReducer";
import { DebuggerReduxState } from "./uiReducers/debuggerReducer";
import { TourReducerState } from "./uiReducers/tourReducer";
+import { TableFilterPaneReduxState } from "./uiReducers/tableFilterPaneReducer";
import { NotificationReducerState } from "./uiReducers/notificationsReducer";
import { CanvasSelectionState } from "./uiReducers/canvasSelectionReducer";
@@ -62,6 +63,7 @@ export interface AppState {
widgetSidebar: WidgetSidebarReduxState;
editor: EditorReduxState;
propertyPane: PropertyPaneReduxState;
+ tableFilterPane: TableFilterPaneReduxState;
errors: ErrorReduxState;
appView: AppViewReduxState;
applications: ApplicationsReduxState;
diff --git a/app/client/src/reducers/uiReducers/index.tsx b/app/client/src/reducers/uiReducers/index.tsx
index f8f9a85893d..e4be412893c 100644
--- a/app/client/src/reducers/uiReducers/index.tsx
+++ b/app/client/src/reducers/uiReducers/index.tsx
@@ -30,6 +30,7 @@ import commentsReducer from "./commentsReducer/commentsReducer";
import websocketReducer from "./websocketReducer";
import debuggerReducer from "./debuggerReducer";
import tourReducer from "./tourReducer";
+import tableFilterPaneReducer from "./tableFilterPaneReducer";
import notificationsReducer from "./notificationsReducer";
import canvasSelectionReducer from "./canvasSelectionReducer";
@@ -38,6 +39,7 @@ const uiReducer = combineReducers({
editor: editorReducer,
errors: errorReducer,
propertyPane: propertyPaneReducer,
+ tableFilterPane: tableFilterPaneReducer,
appView: appViewReducer,
applications: applicationsReducer,
apiPane: apiPaneReducer,
diff --git a/app/client/src/reducers/uiReducers/tableFilterPaneReducer.tsx b/app/client/src/reducers/uiReducers/tableFilterPaneReducer.tsx
new file mode 100644
index 00000000000..7d163e25c91
--- /dev/null
+++ b/app/client/src/reducers/uiReducers/tableFilterPaneReducer.tsx
@@ -0,0 +1,72 @@
+import { createReducer } from "utils/AppsmithUtils";
+import {
+ ReduxActionTypes,
+ ReduxAction,
+ ShowPropertyPanePayload,
+} from "constants/ReduxActionConstants";
+
+const initialState: TableFilterPaneReduxState = {
+ isVisible: false,
+ widgetId: undefined,
+ lastWidgetId: undefined,
+};
+
+const tableFilterPaneReducer = createReducer(initialState, {
+ [ReduxActionTypes.SHOW_TABLE_FILTER_PANE]: (
+ state: TableFilterPaneReduxState,
+ action: ReduxAction,
+ ) => {
+ if (
+ action.payload.widgetId &&
+ state.lastWidgetId === action.payload.widgetId &&
+ !action.payload.force
+ ) {
+ return state;
+ }
+ const { callForDragOrResize, widgetId } = action.payload;
+ // If callForDragOrResize is true, an action has started or ended.
+ // If the action has started, isVisibleBeforeAction should be undefined
+ // If the action has ended, isVisibleBeforeAction should be the visible state
+ // of the property pane to use.
+ let isVisibleBeforeAction = undefined;
+ if (callForDragOrResize && state.isVisibleBeforeAction === undefined) {
+ isVisibleBeforeAction = state.isVisible;
+ }
+
+ // If callForDragOrResize is true, an action has started or ended
+ // If isVisibleBeforeAction is undefined, show property pane
+ // If isVisibleBeforeAction is defined, set visibility to its value
+ let isVisible = true;
+ if (callForDragOrResize && state.isVisibleBeforeAction === undefined) {
+ isVisible = false;
+ } else if (
+ callForDragOrResize &&
+ state.isVisibleBeforeAction !== undefined
+ ) {
+ isVisible = state.isVisibleBeforeAction;
+ } else {
+ isVisible = true;
+ }
+
+ return { ...state, widgetId, isVisible, isVisibleBeforeAction };
+ },
+ [ReduxActionTypes.HIDE_TABLE_FILTER_PANE]: (
+ state: TableFilterPaneReduxState,
+ ) => {
+ return {
+ ...state,
+ isVisible: false,
+ isVisibleBeforeAction: undefined,
+ lastWidgetId: state.widgetId,
+ };
+ },
+});
+
+export interface TableFilterPaneReduxState {
+ isVisible: boolean;
+ widgetId?: string;
+ lastWidgetId?: string;
+ isVisibleBeforeAction?: boolean;
+}
+
+export default tableFilterPaneReducer;
diff --git a/app/client/src/sagas/WidgetOperationSagas.tsx b/app/client/src/sagas/WidgetOperationSagas.tsx
index 0577df0b6e2..efc37242f9c 100644
--- a/app/client/src/sagas/WidgetOperationSagas.tsx
+++ b/app/client/src/sagas/WidgetOperationSagas.tsx
@@ -91,6 +91,7 @@ import {
} from "selectors/editorSelectors";
import {
closePropertyPane,
+ closeTableFilterPane,
forceOpenPropertyPane,
} from "actions/widgetActions";
import {
@@ -514,6 +515,7 @@ export function* deleteAllSelectedWidgetsSaga(
if (saveStatus && !disallowUndo) {
// close property pane after delete
yield put(closePropertyPane());
+ yield put(closeTableFilterPane());
Toaster.show({
text: createMessage(WIDGET_BULK_DELETE, `${selectedWidgets.length}`),
hideProgressBar: false,
diff --git a/app/client/src/selectors/tableFilterSelectors.tsx b/app/client/src/selectors/tableFilterSelectors.tsx
new file mode 100644
index 00000000000..937c33720dc
--- /dev/null
+++ b/app/client/src/selectors/tableFilterSelectors.tsx
@@ -0,0 +1,37 @@
+import { AppState } from "reducers";
+import { createSelector } from "reselect";
+
+import { TableFilterPaneReduxState } from "reducers/uiReducers/tableFilterPaneReducer";
+import { getSelectedWidget, getSelectedWidgets } from "./ui";
+
+export const getTableFilterState = (
+ state: AppState,
+): TableFilterPaneReduxState => state.ui.tableFilterPane;
+
+const isResizingorDragging = (state: AppState) =>
+ state.ui.widgetDragResize.isResizing || state.ui.widgetDragResize.isDragging;
+
+export const getIsTableFilterPaneVisible = createSelector(
+ getTableFilterState,
+ isResizingorDragging,
+ getSelectedWidget,
+ getSelectedWidgets,
+ (
+ pane: TableFilterPaneReduxState,
+ isResizingorDragging: boolean,
+ lastSelectedWidget,
+ widgets,
+ ) => {
+ const isWidgetSelected = pane?.widgetId
+ ? lastSelectedWidget === pane.widgetId || widgets.includes(pane.widgetId)
+ : false;
+ const multipleWidgetsSelected = !!(widgets && widgets.length >= 2);
+ return !!(
+ isWidgetSelected &&
+ !multipleWidgetsSelected &&
+ !isResizingorDragging &&
+ pane.isVisible &&
+ pane.widgetId
+ );
+ },
+);
diff --git a/app/client/src/utils/generators.tsx b/app/client/src/utils/generators.tsx
index ed6c892c235..bfca8af51e0 100644
--- a/app/client/src/utils/generators.tsx
+++ b/app/client/src/utils/generators.tsx
@@ -13,6 +13,7 @@ export const generateReactKey = ({
// This className is used for the following:
// 1. Resize bounds
// 2. Property pane reference for positioning
+// 3. Table widget filter pan reference for positioning
export const generateClassName = (seed?: string) => {
return `appsmith_widget_${seed}`;
};
diff --git a/app/client/src/utils/hooks/dragResizeHooks.tsx b/app/client/src/utils/hooks/dragResizeHooks.tsx
index 65039ba6282..e6bf915befc 100644
--- a/app/client/src/utils/hooks/dragResizeHooks.tsx
+++ b/app/client/src/utils/hooks/dragResizeHooks.tsx
@@ -31,6 +31,34 @@ export const useShowPropertyPane = () => {
);
};
+export const useShowTableFilterPane = () => {
+ const dispatch = useDispatch();
+ const isCommentMode = useSelector(commentModeSelector);
+
+ return useCallback(
+ (widgetId?: string, callForDragOrResize?: boolean, force = false) => {
+ // Don't show property pane in comment mode
+ if (isCommentMode) return;
+
+ dispatch(
+ // If widgetId is not provided, we don't show the table filter pane.
+ // However, if callForDragOrResize is provided, it will be a start or end of a drag or resize action
+ // callForDragOrResize payload is handled in SHOW_TABLE_FILTER_PANE action.
+ // Ergo, when either widgetId or callForDragOrResize are provided, SHOW_TABLE_FILTER_PANE
+ // Else, HIDE_TABLE_FILTER_PANE
+ {
+ type:
+ widgetId || callForDragOrResize
+ ? ReduxActionTypes.SHOW_TABLE_FILTER_PANE
+ : ReduxActionTypes.HIDE_TABLE_FILTER_PANE,
+ payload: { widgetId, callForDragOrResize, force },
+ },
+ );
+ },
+ [dispatch, isCommentMode],
+ );
+};
+
export const useToggleEditWidgetName = () => {
const dispatch = useDispatch();
return useCallback(
diff --git a/app/client/src/utils/hooks/useClickOpenPropPane.tsx b/app/client/src/utils/hooks/useClickOpenPropPane.tsx
index 0b05f4b7470..0f9ef6ca63e 100644
--- a/app/client/src/utils/hooks/useClickOpenPropPane.tsx
+++ b/app/client/src/utils/hooks/useClickOpenPropPane.tsx
@@ -4,6 +4,7 @@ import {
getCurrentWidgetId,
getIsPropertyPaneVisible,
} from "selectors/propertyPaneSelectors";
+import { getIsTableFilterPaneVisible } from "selectors/tableFilterSelectors";
import { MAIN_CONTAINER_WIDGET_ID } from "constants/WidgetConstants";
import { useSelector } from "store";
import { AppState } from "reducers";
@@ -53,6 +54,7 @@ export const useClickOpenPropPane = () => {
const showPropertyPane = useShowPropertyPane();
const { focusWidget, selectWidget } = useWidgetSelection();
const isPropPaneVisible = useSelector(getIsPropertyPaneVisible);
+ const isTableFilterPaneVisible = useSelector(getIsTableFilterPaneVisible);
const widgets: CanvasWidgetsReduxState = useSelector(getWidgets);
const selectedWidgetId = useSelector(getCurrentWidgetId);
const focusedWidgetId = useSelector(
@@ -71,12 +73,15 @@ export const useClickOpenPropPane = () => {
const parentWidgetToOpen = getParentToOpenIfAny(focusedWidgetId, widgets);
const openPropertyPane = (e: any, targetWidgetId: string) => {
- // ignore click captures if the component was resizing or dragging coz it is handled internally in draggable component
+ // ignore click captures
+ // 1. if the component was resizing or dragging coz it is handled internally in draggable component
+ // 2. table filter property pane is open
if (
isResizing ||
isDragging ||
appMode !== APP_MODE.EDIT ||
- targetWidgetId !== focusedWidgetId
+ targetWidgetId !== focusedWidgetId ||
+ isTableFilterPaneVisible
)
return;
if (
diff --git a/app/client/src/widgets/TableWidget/derived.js b/app/client/src/widgets/TableWidget/derived.js
index fb552096f7f..b478685a9bb 100644
--- a/app/client/src/widgets/TableWidget/derived.js
+++ b/app/client/src/widgets/TableWidget/derived.js
@@ -358,7 +358,7 @@ export default {
const _a = a.toString().toLowerCase();
const _b = b.toString().toLowerCase();
- return _a.length === _a.indexOf(_b) + _b.length;
+ return _a.length === _a.lastIndexOf(_b) + _b.length;
} catch (e) {
return false;
}
diff --git a/app/client/src/widgets/TableWidget/index.tsx b/app/client/src/widgets/TableWidget/index.tsx
index 30a9e78a3c7..91748ab18ea 100644
--- a/app/client/src/widgets/TableWidget/index.tsx
+++ b/app/client/src/widgets/TableWidget/index.tsx
@@ -1,4 +1,9 @@
import React, { lazy, Suspense } from "react";
+import log from "loglevel";
+import moment from "moment";
+import { isNumber, isString, isNil, isEqual, xor, without } from "lodash";
+import * as Sentry from "@sentry/react";
+
import BaseWidget, { WidgetState } from "../BaseWidget";
import { RenderModes, WidgetType } from "constants/WidgetConstants";
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
@@ -11,14 +16,10 @@ import {
} from "components/designSystems/appsmith/TableComponent/TableUtilities";
import { getAllTableColumnKeys } from "components/designSystems/appsmith/TableComponent/TableHelpers";
import Skeleton from "components/utils/Skeleton";
-import moment from "moment";
-import { isNumber, isString, isNil, isEqual, xor, without } from "lodash";
-import * as Sentry from "@sentry/react";
import { noop, retryPromise } from "utils/AppsmithUtils";
import withMeta from "../MetaHOC";
import { getDynamicBindings } from "utils/DynamicBindingUtils";
-import log from "loglevel";
-import { ReactTableFilter } from "components/designSystems/appsmith/TableComponent/TableFilters";
+import { ReactTableFilter } from "components/designSystems/appsmith/TableComponent/Constants";
import { TableWidgetProps } from "./TableWidgetConstants";
import derivedProperties from "./parseDerivedProperties";