2023-02-24 11:12:55 +00:00
|
|
|
import {BoxSpec} from 'app/client/components/Layout';
|
2020-10-02 15:10:00 +00:00
|
|
|
import {KoArray} from 'app/client/lib/koArray';
|
|
|
|
import * as koUtil from 'app/client/lib/koUtil';
|
2024-01-12 17:35:24 +00:00
|
|
|
import {DocModel, IRowModel, PageRec, recordSet, refRecord} from 'app/client/models/DocModel';
|
2021-11-30 17:46:21 +00:00
|
|
|
import {TabBarRec, ViewSectionRec} from 'app/client/models/DocModel';
|
2020-10-02 15:10:00 +00:00
|
|
|
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>>;
|
|
|
|
|
2023-02-24 11:12:55 +00:00
|
|
|
layoutSpecObj: modelUtil.SaveableObjObservable<BoxSpec>;
|
2020-10-02 15:10:00 +00:00
|
|
|
|
|
|
|
// An observable for the ref of the section last selected by the user.
|
|
|
|
activeSectionId: ko.Computed<number>;
|
|
|
|
|
2023-02-24 11:12:55 +00:00
|
|
|
// 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[]>;
|
|
|
|
|
2020-10-02 15:10:00 +00:00
|
|
|
activeSection: ko.Computed<ViewSectionRec>;
|
|
|
|
|
|
|
|
// If the active section is removed, set the next active section to be the default.
|
|
|
|
_isActiveSectionGone: ko.Computed<boolean>;
|
2024-01-12 17:35:24 +00:00
|
|
|
|
|
|
|
page: ko.Computed<PageRec|null>;
|
2020-10-02 15:10:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
2023-03-22 15:21:53 +00:00
|
|
|
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());
|
|
|
|
|
2020-10-02 15:10:00 +00:00
|
|
|
// 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.
|
2023-03-22 15:21:53 +00:00
|
|
|
// `!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()));
|
|
|
|
|
2023-06-21 14:43:22 +00:00
|
|
|
// 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());
|
2023-07-07 08:54:01 +00:00
|
|
|
const result = visible.find(s => s.id() === firstLeaf) ? firstLeaf as number :
|
|
|
|
(visible[0]?.id() || 0);
|
|
|
|
return result;
|
2020-10-02 15:10:00 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}));
|
2024-01-12 17:35:24 +00:00
|
|
|
|
|
|
|
this.page = this.autoDispose(ko.pureComputed(() => {
|
|
|
|
const viewRef = this.id();
|
|
|
|
return docModel.allPages().find(p => p.viewRef() === viewRef) ?? null;
|
|
|
|
}));
|
2020-10-02 15:10:00 +00:00
|
|
|
}
|
2023-06-21 14:43:22 +00:00
|
|
|
|
|
|
|
function getFirstLeaf(layoutSpec: BoxSpec|undefined): BoxSpec['leaf'] {
|
|
|
|
while (layoutSpec?.children?.length) {
|
|
|
|
layoutSpec = layoutSpec.children[0];
|
|
|
|
}
|
|
|
|
return layoutSpec?.leaf;
|
|
|
|
}
|