gristlabs_grist-core/app/client/models/SectionFilter.ts
Dmitry S dd2eadc86e (core) Speed up and upgrade build.
Summary:
- Upgrades to build-related packages:
  - Upgrade typescript, related libraries and typings.
  - Upgrade webpack, eslint; add tsc-watch, node-dev, eslint_d.

- Build organization changes:
  - Build webpack from original typescript, transpiling only; with errors still
    reported by a background tsc watching process.

- Typescript-related changes:
  - Reduce imports of AWS dependencies (very noticeable speedup)
  - Avoid auto-loading global @types
  - Client code is now built with isolatedModules flag (for safe transpilation)
  - Use allowJs to avoid copying JS files manually.

- Linting changes
  - Enhance Arcanist ESLintLinter to run before/after commands, and set up to use eslint_d
  - Update eslint config, and include .eslintignore to avoid linting generated files.
  - Include a bunch of eslint-prompted and eslint-generated fixes
  - Add no-unused-expression rule to eslint, and fix a few warnings about it

- Other items:
  - Refactor cssInput to avoid circular dependency
  - Remove a bit of unused code, libraries, dependencies

Test Plan: No behavior changes, all existing tests pass. There are 30 tests fewer reported because `test_gpath.py` was removed (it's been unused for years)

Reviewers: paulfitz

Reviewed By: paulfitz

Subscribers: paulfitz

Differential Revision: https://phab.getgrist.com/D3498
2022-06-27 16:10:10 -04:00

117 lines
5.0 KiB
TypeScript

import {ColumnFilter} from 'app/client/models/ColumnFilter';
import {ColumnRec, ViewFieldRec, ViewSectionRec} from 'app/client/models/DocModel';
import {RowId} from 'app/client/models/rowset';
import {TableData} from 'app/client/models/TableData';
import {buildColFilter, ColumnFilterFunc} from 'app/common/ColumnFilterFunc';
import {buildRowFilter, RowFilterFunc, RowValueFunc } from 'app/common/RowFilterFunc';
import {Computed, Disposable, MutableObsArray, obsArray, Observable, UseCB} from 'grainjs';
export type {ColumnFilterFunc};
interface OpenColumnFilter {
colRef: number;
colFilter: ColumnFilter;
}
type ColFilterCB = (fieldOrColumn: ViewFieldRec|ColumnRec, colFilter: ColumnFilterFunc|null) => ColumnFilterFunc|null;
/**
* SectionFilter represents a collection of column filters in place for a view section. It is created
* out of `filters` (in `viewSection`) and `tableData`, and provides a Computed `sectionFilterFunc` that users can
* subscribe to in order to update their FilteredRowSource.
*
* Additionally, `setFilterOverride()` provides a way to override the current filter for a given colRef,
* to reflect the changes in an open filter dialog. Also, `addTemporaryRow()` allows to add a rowId
* that should be present regardless of filters. These rows are removed automatically when an update to the filter
* results in their being displayed (obviating the need to maintain their rowId explicitly).
*/
export class SectionFilter extends Disposable {
public readonly sectionFilterFunc: Observable<RowFilterFunc<RowId>>;
private _openFilterOverride: Observable<OpenColumnFilter|null> = Observable.create(this, null);
private _tempRows: MutableObsArray<RowId> = obsArray();
constructor(public viewSection: ViewSectionRec, private _tableData: TableData) {
super();
const columnFilterFunc = Computed.create(this, this._openFilterOverride, (use, openFilter) => {
const openFilterFilterFunc = openFilter && use(openFilter.colFilter.filterFunc);
function getFilterFunc(fieldOrColumn: ViewFieldRec|ColumnRec, colFilter: ColumnFilterFunc|null) {
if (openFilter?.colRef === fieldOrColumn.origCol().getRowId()) {
return openFilterFilterFunc;
}
return colFilter;
}
return this._buildPlainFilterFunc(getFilterFunc, use);
});
this.sectionFilterFunc = Computed.create(this, columnFilterFunc, this._tempRows,
(_use, filterFunc, tempRows) => this._addRowsToFilter(filterFunc, tempRows));
// Prune temporary rowIds that are no longer being filtered out.
this.autoDispose(columnFilterFunc.addListener(f => {
this._tempRows.set(this._tempRows.get().filter(rowId => !f(rowId)));
}));
}
/**
* Allows to override a single filter for a given colRef. Multiple calls to `setFilterOverride` will overwrite
* previously set values.
*/
public setFilterOverride(colRef: number, colFilter: ColumnFilter) {
this._openFilterOverride.set(({colRef, colFilter}));
colFilter.onDispose(() => {
const override = this._openFilterOverride.get();
if (override && override.colFilter === colFilter) {
this._openFilterOverride.set(null);
}
});
}
public addTemporaryRow(rowId: number) {
// Only add the rowId if it would otherwise be filtered out
if (!this.sectionFilterFunc.get()(rowId)) {
this._tempRows.push(rowId);
}
}
public resetTemporaryRows() {
this._tempRows.set([]);
}
/**
* Builds a filter function that combines the filter function of all the columns. You can use
* `getFilterFunc(column, colFilter)` to customize the filter func for each columns. It calls
* `getFilterFunc` right away. Also, all the rows that were added with `addTemporaryRow()` bypass
* the filter.
*/
public buildFilterFunc(getFilterFunc: ColFilterCB, use: UseCB) {
return this._addRowsToFilter(this._buildPlainFilterFunc(getFilterFunc, use), this._tempRows.get());
}
private _addRowsToFilter(filterFunc: RowFilterFunc<RowId>, rows: RowId[]) {
return (rowId: RowId) => rows.includes(rowId) || (typeof rowId !== 'number') || filterFunc(rowId);
}
/**
* Internal that helps build a filter function that combines the filter function of all
* columns. You can use `getFilterFunc(column, colFilter)` to customize the filter func for each
* column. It calls `getFilterFunc` right away.
*/
private _buildPlainFilterFunc(getFilterFunc: ColFilterCB, use: UseCB): RowFilterFunc<RowId> {
const filters = use(this.viewSection.filters);
const funcs: Array<RowFilterFunc<RowId> | null> = filters.map(({filter, fieldOrColumn}) => {
const colFilter = buildColFilter(use(filter), use(use(fieldOrColumn.origCol).type));
const filterFunc = getFilterFunc(fieldOrColumn, colFilter);
const getter = this._tableData.getRowPropFunc(fieldOrColumn.colId.peek());
if (!filterFunc || !getter) { return null; }
return buildRowFilter(getter as RowValueFunc<RowId>, filterFunc);
}).filter(f => f !== null); // Filter out columns that don't have a filter
return (rowId: RowId) => funcs.every(f => Boolean(f && f(rowId)));
}
}