(core) Fixing cursor position for filtered linked section.

Summary:
In a selector table, when a selected row is filtered out of view, linked widgets should update based on the newly selected row.
There were a few bugs that contributed to this wrong behavior:
- Gridview wasn't subscribing to the current row id, and the row with id 'new' was being converted to the first row
- Cursor was keeping track of the currently selected row id, it was hiding a problem behind the proper rowIndex
- Undo/redo somehow leveraged the wrong rowId from the cursor during the position restore.

The `No data` text was also changed to be more meaningful.

Test Plan: Added and updated.

Reviewers: georgegevoian

Reviewed By: georgegevoian

Differential Revision: https://phab.getgrist.com/D3937
This commit is contained in:
Jarosław Sadziński
2023-07-07 10:54:01 +02:00
parent 9a80f4bdf5
commit 75d979abdb
25 changed files with 418 additions and 138 deletions

View File

@@ -24,7 +24,7 @@
import koArray, {KoArray} from 'app/client/lib/koArray';
import {DisposableWithEvents} from 'app/common/DisposableWithEvents';
import {CompareFunc, sortedIndex} from 'app/common/gutil';
import {SkippableRows} from 'app/common/TableData';
import {SkippableRows, UIRowId} from 'app/common/TableData';
import {RowFilterFunc} from "app/common/RowFilterFunc";
import {Observable} from 'grainjs';
@@ -36,8 +36,7 @@ export const ALL: unique symbol = Symbol("ALL");
export type ChangeType = 'add' | 'remove' | 'update';
export type ChangeMethod = 'onAddRows' | 'onRemoveRows' | 'onUpdateRows';
export type RowId = number | 'new';
export type RowList = Iterable<RowId>;
export type RowList = Iterable<UIRowId>;
export type RowsChanged = RowList | typeof ALL;
// ----------------------------------------------------------------------
@@ -132,7 +131,7 @@ export class RowListener extends DisposableWithEvents {
* A trivial RowSource returning a fixed list of rows.
*/
export abstract class ArrayRowSource extends RowSource {
constructor(private _rows: RowId[]) { super(); }
constructor(private _rows: UIRowId[]) { super(); }
public getAllRows(): RowList { return this._rows; }
public getNumRows(): number { return this._rows.length; }
}
@@ -146,11 +145,11 @@ export abstract class ArrayRowSource extends RowSource {
* TODO: This class is not used anywhere at the moment, and is a candidate for removal.
*/
export class MappedRowSource extends RowSource {
private _mapperFunc: (row: RowId) => RowId;
private _mapperFunc: (row: UIRowId) => UIRowId;
constructor(
public parentRowSource: RowSource,
mapperFunc: (row: RowId) => RowId,
mapperFunc: (row: UIRowId) => UIRowId,
) {
super();
@@ -182,7 +181,7 @@ export class ExtendedRowSource extends RowSource {
constructor(
public parentRowSource: RowSource,
public extras: RowId[]
public extras: UIRowId[]
) {
super();
@@ -209,9 +208,9 @@ export class ExtendedRowSource extends RowSource {
// ----------------------------------------------------------------------
interface FilterRowChanges {
adds?: RowId[];
updates?: RowId[];
removes?: RowId[];
adds?: UIRowId[];
updates?: UIRowId[];
removes?: UIRowId[];
}
/**
@@ -219,9 +218,9 @@ interface FilterRowChanges {
* does not maintain excluded rows, and does not allow changes to filterFunc.
*/
export class BaseFilteredRowSource extends RowListener implements RowSource {
protected _matchingRows: Set<RowId> = new Set(); // Set of rows matching the filter.
protected _matchingRows: Set<UIRowId> = new Set(); // Set of rows matching the filter.
constructor(protected _filterFunc: RowFilterFunc<RowId>) {
constructor(protected _filterFunc: RowFilterFunc<UIRowId>) {
super();
}
@@ -309,8 +308,8 @@ export class BaseFilteredRowSource extends RowListener implements RowSource {
}
// These are implemented by FilteredRowSource, but the base class doesn't need to do anything.
protected _addExcludedRow(row: RowId): void { /* no-op */ }
protected _deleteExcludedRow(row: RowId): boolean { return true; }
protected _addExcludedRow(row: UIRowId): void { /* no-op */ }
protected _deleteExcludedRow(row: UIRowId): boolean { return true; }
}
/**
@@ -321,13 +320,13 @@ export class BaseFilteredRowSource extends RowListener implements RowSource {
* FilteredRowSource is also a RowListener, so to subscribe to a rowSource, use `subscribeTo()`.
*/
export class FilteredRowSource extends BaseFilteredRowSource {
private _excludedRows: Set<RowId> = new Set(); // Set of rows NOT matching the filter.
private _excludedRows: Set<UIRowId> = new Set(); // Set of rows NOT matching the filter.
/**
* Change the filter function. This may trigger 'remove' and 'add' events as necessary to indicate
* that rows stopped or started matching the new filter.
*/
public updateFilter(filterFunc: RowFilterFunc<RowId>) {
public updateFilter(filterFunc: RowFilterFunc<UIRowId>) {
this._filterFunc = filterFunc;
const changes: FilterRowChanges = {};
// After the first call, _excludedRows may have additional rows, but there is no harm in it,
@@ -356,8 +355,8 @@ export class FilteredRowSource extends BaseFilteredRowSource {
return this._excludedRows.values();
}
protected _addExcludedRow(row: RowId): void { this._excludedRows.add(row); }
protected _deleteExcludedRow(row: RowId): boolean { return this._excludedRows.delete(row); }
protected _addExcludedRow(row: UIRowId): void { this._excludedRows.add(row); }
protected _deleteExcludedRow(row: UIRowId): boolean { return this._excludedRows.delete(row); }
}
// ----------------------------------------------------------------------
@@ -368,7 +367,7 @@ export class FilteredRowSource extends BaseFilteredRowSource {
* Private helper object that maintains a set of rows for a particular group.
*/
class RowGroupHelper<Value> extends RowSource {
private _rows: Set<RowId> = new Set();
private _rows: Set<UIRowId> = new Set();
constructor(public readonly groupValue: Value) {
super();
}
@@ -411,12 +410,12 @@ function _addToMapOfArrays<K, V>(map: Map<K, V[]>, key: K, r: V): void {
*/
export class RowGrouping<Value> extends RowListener {
// Maps row identifiers to groupValues.
private _rowsToValues: Map<RowId, Value> = new Map();
private _rowsToValues: Map<UIRowId, Value> = new Map();
// Maps group values to RowGroupHelpers
private _valuesToGroups: Map<Value, RowGroupHelper<Value>> = new Map();
constructor(private _groupFunc: (row: RowId) => Value) {
constructor(private _groupFunc: (row: UIRowId) => Value) {
super();
// On disposal, dispose all RowGroupHelpers that we maintain.
@@ -538,15 +537,15 @@ export class RowGrouping<Value> extends RowListener {
* SortedRowSet re-emits 'rowNotify(rows, value)' events from RowSources that it subscribes to.
*/
export class SortedRowSet extends RowListener {
private _allRows: Set<RowId> = new Set();
private _allRows: Set<UIRowId> = new Set();
private _isPaused: boolean = false;
private _koArray: KoArray<RowId>;
private _koArray: KoArray<UIRowId>;
private _keepFunc?: (rowId: number|'new') => boolean;
constructor(private _compareFunc: CompareFunc<RowId>,
constructor(private _compareFunc: CompareFunc<UIRowId>,
private _skippableRows?: SkippableRows) {
super();
this._koArray = this.autoDispose(koArray<RowId>());
this._koArray = this.autoDispose(koArray<UIRowId>());
this._keepFunc = _skippableRows?.getKeepFunc();
}
@@ -572,7 +571,7 @@ export class SortedRowSet extends RowListener {
/**
* Re-sorts the array according to the new compareFunc.
*/
public updateSort(compareFunc: CompareFunc<RowId>): void {
public updateSort(compareFunc: CompareFunc<UIRowId>): void {
this._compareFunc = compareFunc;
if (!this._isPaused) {
this._koArray.assign(Array.from(this._koArray.peek()).sort(this._compareFunc));
@@ -650,7 +649,7 @@ export class SortedRowSet extends RowListener {
// Filter out any rows that should be skipped. This is a no-op if no _keepFunc was found.
// All rows that sort within nContext rows of something meant to be kept are also kept.
private _keep(rows: RowId[], nContext: number = 2) {
private _keep(rows: UIRowId[], nContext: number = 2) {
// Nothing to be done if there's no _keepFunc.
if (!this._keepFunc) { return rows; }
@@ -706,7 +705,7 @@ export class SortedRowSet extends RowListener {
}
}
type RowTester = (rowId: RowId) => boolean;
type RowTester = (rowId: UIRowId) => boolean;
/**
* RowWatcher is a RowListener that maintains an observable function that checks whether a row
* is in the connected RowSource.
@@ -718,7 +717,7 @@ export class RowWatcher extends RowListener {
public rowFilter: Observable<RowTester> = Observable.create(this, () => false);
// We count the number of times the row is added or removed from the source.
// In most cases row is added and removed only once.
private _rowCounter: Map<RowId, number> = new Map();
private _rowCounter: Map<UIRowId, number> = new Map();
public clear() {
this._rowCounter.clear();