From 5479159960312930ea01ea2c4e8dbe4da8a15992 Mon Sep 17 00:00:00 2001 From: Cyprien P Date: Tue, 20 Apr 2021 17:16:03 +0200 Subject: [PATCH] (core) add `+` button to the filter bar Summary: - Adds a + button to the filter. Button triggers a menu that allow to add one of the column that does not already have a filter set. Caveats: - for now menu only allows to choose from visible column. - This diff introduces a slight change of behavior of how filter works: - Filter used to be automatically removed when user set them to all inclusive (ie: by clicking the `All` button). - With this diff, it is no longer the case. - indeed, when filter are added to the filter bar with the `+` btn they are initially in the `all inclusive` state, hence would have been removed with the above mention behaviour. Test Plan: Added new test to nbrowser/FilterBar Reviewers: paulfitz Reviewed By: paulfitz Differential Revision: https://phab.getgrist.com/D2776 --- app/client/ui/ColumnFilterMenu.ts | 4 +++- app/client/ui/FilterBar.ts | 37 +++++++++++++++++++++++++++---- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/app/client/ui/ColumnFilterMenu.ts b/app/client/ui/ColumnFilterMenu.ts index eb5aea6f..41ef7bba 100644 --- a/app/client/ui/ColumnFilterMenu.ts +++ b/app/client/ui/ColumnFilterMenu.ts @@ -264,6 +264,7 @@ export function createFilterMenu(openCtl: IOpenController, sectionFilter: Sectio const labelGetter = tableData.getRowPropFunc(field.displayColModel().colId())!; const formatter = field.createVisibleColFormatter(); const valueMapFunc = (rowId: number) => formatter.formatAny(labelGetter(rowId)); + const activeFilterBar = field.viewSection.peek().activeFilterBar; const valueCounts: Map = new Map(); // TODO: as of now, this is not working for non text-or-numeric columns, ie: for Date column it is @@ -282,7 +283,8 @@ export function createFilterMenu(openCtl: IOpenController, sectionFilter: Sectio onClose: () => openCtl.close(), doSave: (reset: boolean = false) => { const spec = columnFilter.makeFilterJson(); - field.activeFilter(spec === allInclusive ? '' : spec); + // If filter is moot and filter bar is hidden, let's remove the filter. + field.activeFilter((spec === allInclusive && !activeFilterBar.peek()) ? '' : spec); if (reset) { sectionFilter.resetTemporaryRows(); } diff --git a/app/client/ui/FilterBar.ts b/app/client/ui/FilterBar.ts index 2737a5cf..9e56dc83 100644 --- a/app/client/ui/FilterBar.ts +++ b/app/client/ui/FilterBar.ts @@ -1,14 +1,17 @@ +import { allInclusive } from "app/client/models/ColumnFilter"; import { ViewFieldRec, ViewSectionRec } from "app/client/models/DocModel"; import { attachColumnFilterMenu } from "app/client/ui/ColumnFilterMenu"; import { cssButton, cssButtonGroup } from "app/client/ui2018/buttons"; import { colors, testId } from "app/client/ui2018/cssVars"; import { icon } from "app/client/ui2018/icons"; +import { menu, menuItemAsync } from "app/client/ui2018/menus"; import { dom, IDisposableOwner, IDomArgs, styled } from "grainjs"; export function filterBar(_owner: IDisposableOwner, viewSection: ViewSectionRec) { return cssFilterBar( testId('filter-bar'), dom.forEach(viewSection.filteredFields, (field) => makeFilterField(viewSection, field)), + makePlusButton(viewSection), cssSpacer(), dom.maybe(viewSection.filterSpecChanged, () => [ primaryButton( @@ -30,18 +33,38 @@ function makeFilterField(viewSection: ViewSectionRec, field: ViewFieldRec) { testId('btn'), cssIcon('FilterSimple'), cssMenuTextLabel(dom.text(field.label)), - cssBtn.cls('-disabled', field.activeFilter.isSaved), + cssBtn.cls('-saved', field.activeFilter.isSaved), attachColumnFilterMenu(viewSection, field, {placement: 'bottom-start', attach: 'body'}), ), deleteButton( testId('delete'), cssIcon('CrossSmall'), - cssBtn.cls('-disabled', field.activeFilter.isSaved), + cssBtn.cls('-saved', field.activeFilter.isSaved), dom.on('click', () => field.activeFilter('')), ) ); } +function makePlusButton(viewSectionRec: ViewSectionRec) { + return dom.domComputed((use) => { + const fields = use(use(viewSectionRec.viewFields).getObservable()); + const anyFilter = fields.find((f) => use(f.isFiltered)); + return cssPlusButton( + cssBtn.cls('-saved'), + cssIcon('Plus'), + menu(() => fields.map((f) => ( + menuItemAsync( + () => f.activeFilter(allInclusive), + f.label.peek(), + dom.cls('disabled', f.isFiltered) + ) + ))), + anyFilter ? null : cssPlusLabel('Add Filter'), + testId('add-filter-btn') + ); + }); +} + const cssFilterBar = styled('div', ` display: flex; flex-direction: row; @@ -75,13 +98,13 @@ const cssBtn = styled('div', ` .${cssFilterBar.className} > & { margin: 0 4px; } - &-disabled { + &-saved { color: ${colors.light}; --icon-color: ${colors.light}; background-color: ${colors.slate}; border-color: ${colors.slate}; } - &-disabled:hover { + &-saved:hover { background-color: ${colors.darkGrey}; border-color: ${colors.darkGrey}; } @@ -100,3 +123,9 @@ const cssSpacer = styled('div', ` width: 8px; flex-shrink: 0; `); +const cssPlusButton = styled(primaryButton, ` + padding: 3px 3px +`); +const cssPlusLabel = styled('span', ` + margin: 0 12px 0 4px; +`);