(core) Minimazing widgets

Summary:
A feature that allows minimizing widgets on the ViewLayout.
- Code in ViewLayout and Layout hasn't been changed. Only some methods or variables were made public, and some events are now triggered when a section is dragged.
- Widgets can be collapsed or expanded (added back to the main area)
- Collapsed widgets can be expanded and shown as a popup
- Collapsed widgets support drugging, reordering, and transferring between the main and collapsed areas.

Test Plan: New test

Reviewers: georgegevoian

Reviewed By: georgegevoian

Differential Revision: https://phab.getgrist.com/D3779
This commit is contained in:
Jarosław Sadziński
2023-02-24 12:12:55 +01:00
parent e9efac05f7
commit 59cf654190
19 changed files with 1949 additions and 259 deletions

View File

@@ -166,10 +166,17 @@ export class GristDoc extends DisposableWithEvents {
public readonly hasDocTour: Computed<boolean>;
public readonly behavioralPromptsManager = this.docPageModel.appModel.behavioralPromptsManager;
// One of the section can be shown it the popup (as requested from the Layout), we will
// store its id in this variable. When the section is removed, changed or page is changed, we will
// hide it be informing the layout about it.
public sectionInPopup: Observable<number|null> = Observable.create(this, null);
// One of the section can be expanded (as requested from the Layout), we will
// store its id in this variable. NOTE: expanded section looks exactly the same as a section
// in the popup. But they are rendered differently, as section in popup is probably an external
// section (or raw data section) that is not part of this view. Maximized section is a section
// in the view, so there is no need to render it twice, layout just hides all other sections to make
// the space.
public maximizedSectionId: Observable<number|null> = Observable.create(this, null);
// This is id of the section that is currently shown in the popup. Probably this is an external
// section, like raw data view, or a section from another view..
public externalSectionId: Computed<number|null>;
public viewLayout: ViewLayout|null = null;
private _actionLog: ActionLog;
private _undoStack: UndoStack;
@@ -178,7 +185,6 @@ export class GristDoc extends DisposableWithEvents {
private _docHistory: DocHistory;
private _discussionPanel: DiscussionPanel;
private _rightPanelTool = createSessionObs(this, "rightPanelTool", "none", RightPanelTool.guard);
private _viewLayout: ViewLayout|null = null;
private _showGristTour = getUserOrgPrefObs(this.userOrgPrefs, 'showGristTour');
private _seenDocTours = getUserOrgPrefObs(this.userOrgPrefs, 'seenDocTours');
private _rawSectionOptions: Observable<RawSectionOptions|null> = Observable.create(this, null);
@@ -229,6 +235,10 @@ export class GristDoc extends DisposableWithEvents {
return viewId || use(defaultViewId);
});
this._activeContent = Computed.create(this, use => use(this._rawSectionOptions) ?? use(this.activeViewId));
this.externalSectionId = Computed.create(this, use => {
const externalContent = use(this._rawSectionOptions);
return externalContent ? use(externalContent.viewSection.id) : null;
});
// This viewModel reflects the currently active view, relying on the fact that
// createFloatingRowModel() supports an observable rowId for its argument.
// Although typings don't reflect it, createFloatingRowModel() accepts non-numeric values,
@@ -238,10 +248,10 @@ export class GristDoc extends DisposableWithEvents {
// When active section is changed, clear the maximized state.
this.autoDispose(this.viewModel.activeSectionId.subscribe(() => {
this.sectionInPopup.set(null);
this.maximizedSectionId.set(null);
// If we have layout, update it.
if (!this._viewLayout?.isDisposed()) {
this._viewLayout?.maximized.set(null);
if (!this.viewLayout?.isDisposed()) {
this.viewLayout?.maximized.set(null);
}
}));
@@ -444,7 +454,7 @@ export class GristDoc extends DisposableWithEvents {
* Builds the DOM for this GristDoc.
*/
public buildDom() {
const isMaximized = Computed.create(this, use => use(this.sectionInPopup) !== null);
const isMaximized = Computed.create(this, use => use(this.maximizedSectionId) !== null);
const isPopup = Computed.create(this, use => {
return ['data', 'settings'].includes(use(this.activeViewId) as any) // On Raw data or doc settings pages
|| use(isMaximized) // Layout has a maximized section visible
@@ -468,9 +478,10 @@ export class GristDoc extends DisposableWithEvents {
return dom.create(RawDataPopup, this, content.viewSection, content.close);
}) :
dom.create((owner) => {
this._viewLayout = ViewLayout.create(owner, this, content);
this._viewLayout.maximized.addListener(n => this.sectionInPopup.set(n));
return this._viewLayout;
this.viewLayout = ViewLayout.create(owner, this, content);
this.viewLayout.maximized.addListener(n => this.maximizedSectionId.set(n));
owner.onDispose(() => this.viewLayout = null);
return this.viewLayout;
})
);
}),
@@ -700,7 +711,7 @@ export class GristDoc extends DisposableWithEvents {
return section;
}
return await this._viewLayout!.freezeUntil(docData.bundleActions(
return await this.viewLayout!.freezeUntil(docData.bundleActions(
t("Saved linked section {{title}} in view {{name}}", {title:section.title(), name: viewModel.name()}),
async () => {
@@ -1021,7 +1032,7 @@ export class GristDoc extends DisposableWithEvents {
// We can only open a popup for a section.
if (!hash.sectionId) { return; }
// We might open popup either for a section in this view or some other section (like Raw Data Page).
if (this.viewModel.viewSections.peek().peek().some(s => s.id.peek() === hash.sectionId)) {
if (this.viewModel.viewSections.peek().peek().some(s => s.id.peek() === hash.sectionId && !s.isCollapsed.peek())) {
this.viewModel.activeSectionId(hash.sectionId);
// If the anchor link is valid, set the cursor.
if (hash.colRef && hash.rowId) {
@@ -1178,7 +1189,7 @@ export class GristDoc extends DisposableWithEvents {
// we must read the current layout from the view layout because it can override the one in
// `section.layoutSpec` (in particular it provides a default layout when missing from the
// latter).
const layoutSpec = this._viewLayout!.layoutSpec();
const layoutSpec = this.viewLayout!.layoutSpec();
const sectionTitle = section.title();
const sectionId = section.id();