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, PageRec, 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>; tabBarItem: ko.Computed>; layoutSpecObj: modelUtil.SaveableObjObservable; // An observable for the ref of the section last selected by the user. activeSectionId: ko.Computed; // This is active collapsed section id. Set when the widget is clicked. activeCollapsedSectionId: ko.Observable; // Saved collapsed sections. collapsedSections: ko.Computed; // 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; activeSection: ko.Computed; // If the active section is removed, set the next active section to be the default. _isActiveSectionGone: ko.Computed; page: ko.Computed; } 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); } })); this.page = this.autoDispose(ko.pureComputed(() => { const viewRef = this.id(); return docModel.allPages().find(p => p.viewRef() === viewRef) ?? null; })); } function getFirstLeaf(layoutSpec: BoxSpec|undefined): BoxSpec['leaf'] { while (layoutSpec?.children?.length) { layoutSpec = layoutSpec.children[0]; } return layoutSpec?.leaf; }