import { NEW_FILTER_JSON } from "app/client/models/ColumnFilter"; import { ColumnRec, ViewSectionRec } from "app/client/models/DocModel"; import { FilterInfo } from "app/client/models/entities/ViewSectionRec"; import { attachColumnFilterMenu } from "app/client/ui/ColumnFilterMenu"; import { cssButton } from "app/client/ui2018/buttons"; import { testId, theme, vars } 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"; import { IMenuOptions, PopupControl } from "popweasel"; export function filterBar(_owner: IDisposableOwner, viewSection: ViewSectionRec) { const popupControls = new WeakMap(); return cssFilterBar( testId('filter-bar'), dom.forEach(viewSection.activeFilters, (filterInfo) => makeFilterField(filterInfo, popupControls)), makePlusButton(viewSection, popupControls), cssFilterBar.cls('-hidden', use => use(viewSection.pinnedActiveFilters).length === 0), ); } function makeFilterField(filterInfo: FilterInfo, popupControls: WeakMap) { const {fieldOrColumn, filter, pinned, isPinned} = filterInfo; return cssFilterBarItem( testId('filter-field'), primaryButton( testId('btn'), cssIcon('FilterSimple'), cssMenuTextLabel(dom.text(fieldOrColumn.origCol().label)), cssBtn.cls('-grayed', use => use(filter.isSaved) && use(pinned.isSaved)), attachColumnFilterMenu(filterInfo, { popupOptions: { placement: 'bottom-start', attach: 'body', trigger: [ 'click', (_el, popupControl) => popupControls.set(fieldOrColumn.origCol(), popupControl), ], }, showAllFiltersButton: true, }), ), cssFilterBarItem.cls('-unpinned', use => !use(isPinned)), ); } export interface AddFilterMenuOptions { /** * If 'only-unfiltered', only columns without active filters will be selectable in * the menu. * * If 'unpinned-or-unfiltered', columns that have active filters but are not pinned * will also be selectable. * * Defaults to `only-unfiltered'. */ allowedColumns?: 'only-unfiltered' | 'unpinned-or-unfiltered'; /** * Options that are passed to the menu component. */ menuOptions?: IMenuOptions; } export function addFilterMenu( filters: FilterInfo[], popupControls: WeakMap, options: AddFilterMenuOptions = {} ) { const {allowedColumns, menuOptions} = options; return ( menu((ctl) => [ ...filters.map((filterInfo) => ( menuItemAsync( () => openFilter(filterInfo, popupControls), filterInfo.fieldOrColumn.origCol().label.peek(), dom.cls('disabled', allowedColumns === 'unpinned-or-unfiltered' ? use => use(filterInfo.isPinned) && use(filterInfo.isFiltered) : use => use(filterInfo.isFiltered) ), testId('add-filter-item'), ) )), // We need to stop click event to propagate otherwise it would cause view section menu to // close. dom.on('click', (ev) => { ctl.close(); ev.stopPropagation(); }), ], menuOptions) ); } function openFilter( {fieldOrColumn, isFiltered, viewSection}: FilterInfo, popupControls: WeakMap, ) { viewSection.setFilter(fieldOrColumn.origCol().origColRef(), { filter: isFiltered.peek() ? undefined : NEW_FILTER_JSON, pinned: true, }); popupControls.get(fieldOrColumn.origCol())?.open(); } function makePlusButton(viewSectionRec: ViewSectionRec, popupControls: WeakMap) { return dom.domComputed((use) => { const filters = use(viewSectionRec.filters); return cssPlusButton( cssBtn.cls('-grayed'), cssIcon('Plus'), addFilterMenu(filters, popupControls, { allowedColumns: 'unpinned-or-unfiltered', }), testId('add-filter-btn') ); }); } const cssFilterBar = styled('div.filter_bar', ` display: flex; flex-direction: row; margin-bottom: 8px; margin-left: -4px; overflow-x: scroll; scrollbar-width: none; &::-webkit-scrollbar { display: none; } &-hidden { display: none; } `); const cssFilterBarItem = styled('div', ` border-radius: ${vars.controlBorderRadius}; flex-shrink: 0; margin: 0 4px; &-unpinned { display: none; } `); const cssMenuTextLabel = styled('span', ` flex-grow: 1; padding: 0 4px; overflow: hidden; text-overflow: ellipsis; `); const cssIcon = styled(icon, ` margin-top: -3px; `); const cssBtn = styled('div', ` height: 24px; padding: 3px 8px; .${cssFilterBar.className} > & { margin: 0 4px; } &-grayed { color: ${theme.filterBarButtonSavedFg}; --icon-color: ${theme.filterBarButtonSavedFg}; background-color: ${theme.filterBarButtonSavedBg}; border-color: ${theme.filterBarButtonSavedBg}; } &-grayed:hover { background-color: ${theme.filterBarButtonSavedHoverBg}; border-color: ${theme.filterBarButtonSavedHoverBg}; } `); const primaryButton = (...args: IDomArgs) => ( dom('div', cssButton.cls(''), cssButton.cls('-primary'), cssBtn.cls(''), ...args) ); const cssPlusButton = styled(primaryButton, ` padding: 3px 3px `);