gristlabs_grist-core/app/client/models/entities/ViewRec.ts
Jarosław Sadziński 75d979abdb (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
2023-07-07 19:04:30 +02:00

87 lines
3.8 KiB
TypeScript

import {BoxSpec} from 'app/client/components/Layout';
import {KoArray} from 'app/client/lib/koArray';
import * as koUtil from 'app/client/lib/koUtil';
import {DocModel, IRowModel, recordSet, refRecord} from 'app/client/models/DocModel';
import {TabBarRec, ViewSectionRec} from 'app/client/models/DocModel';
import * as modelUtil from 'app/client/models/modelUtil';
import * as ko from 'knockout';
// Represents a view (now also referred to as a "page") containing one or more view sections.
export interface ViewRec extends IRowModel<"_grist_Views"> {
viewSections: ko.Computed<KoArray<ViewSectionRec>>;
tabBarItem: ko.Computed<KoArray<TabBarRec>>;
layoutSpecObj: modelUtil.SaveableObjObservable<BoxSpec>;
// An observable for the ref of the section last selected by the user.
activeSectionId: ko.Computed<number>;
// This is active collapsed section id. Set when the widget is clicked.
activeCollapsedSectionId: ko.Observable<number>;
// Saved collapsed sections.
collapsedSections: ko.Computed<number[]>;
// Active collapsed sections, changed by the user, can be different from the
// saved collapsed sections, for a brief moment (editor is buffering changes).
activeCollapsedSections: ko.Observable<number[]>;
activeSection: ko.Computed<ViewSectionRec>;
// If the active section is removed, set the next active section to be the default.
_isActiveSectionGone: ko.Computed<boolean>;
}
export function createViewRec(this: ViewRec, docModel: DocModel): void {
this.viewSections = recordSet(this, docModel.viewSections, 'parentId');
this.tabBarItem = recordSet(this, docModel.tabBar, 'viewRef');
this.layoutSpecObj = modelUtil.jsonObservable(this.layoutSpec);
this.activeCollapsedSectionId = ko.observable(0);
this.collapsedSections = this.autoDispose(ko.pureComputed(() => {
const allSections = new Set(this.viewSections().all().map(x => x.id()));
const collapsed: number[] = (this.layoutSpecObj().collapsed || []).map(x => x.leaf as number);
return collapsed.filter(x => allSections.has(x));
}));
this.activeCollapsedSections = ko.observable(this.collapsedSections.peek());
// An observable for the ref of the section last selected by the user.
this.activeSectionId = koUtil.observableWithDefault(ko.observable(), () => {
// The default function which is used when the conditional case is true.
// Read may occur for recently disposed sections, must check condition first.
// `!this.getRowId()` implies that this is an empty (non-existent) view record
// which happens when viewing the raw data tables, in which case the default is no active view section.
if (this.isDisposed() || !this.getRowId()) { return 0; }
const all = this.viewSections().all();
const collapsed = new Set(this.activeCollapsedSections());
const visible = all.filter(x => !collapsed.has(x.id()));
// Default to the first leaf from layoutSpec (which corresponds to the top-left section), or
// fall back to the first item in the list if anything goes wrong (previous behavior).
const firstLeaf = getFirstLeaf(this.layoutSpecObj.peek());
const result = visible.find(s => s.id() === firstLeaf) ? firstLeaf as number :
(visible[0]?.id() || 0);
return result;
});
this.activeSection = refRecord(docModel.viewSections, this.activeSectionId);
// If the active section is removed, set the next active section to be the default.
this._isActiveSectionGone = this.autoDispose(ko.computed(() => this.activeSection()._isDeleted()));
this.autoDispose(this._isActiveSectionGone.subscribe(gone => {
if (gone) {
this.activeSectionId(0);
}
}));
}
function getFirstLeaf(layoutSpec: BoxSpec|undefined): BoxSpec['leaf'] {
while (layoutSpec?.children?.length) {
layoutSpec = layoutSpec.children[0];
}
return layoutSpec?.leaf;
}