mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Collapse inactive view sections on mobile screens.
Summary: Implement an approach to makind multi-section screens usable on mobile by collapsing inactive sections to a small area. When clicked, they become active and expand, while the rest of the sections are collapsed. Test Plan: Added a basic test case of collapsing inactive sections. Reviewers: paulfitz Reviewed By: paulfitz Differential Revision: https://phab.getgrist.com/D2725
This commit is contained in:
parent
890a8709f3
commit
6c10a43c5d
@ -34,7 +34,7 @@ import {App} from 'app/client/ui/App';
|
|||||||
import {DocHistory} from 'app/client/ui/DocHistory';
|
import {DocHistory} from 'app/client/ui/DocHistory';
|
||||||
import {IPageWidget, toPageWidget} from 'app/client/ui/PageWidgetPicker';
|
import {IPageWidget, toPageWidget} from 'app/client/ui/PageWidgetPicker';
|
||||||
import {IPageWidgetLink, linkFromId, selectBy} from 'app/client/ui/selectBy';
|
import {IPageWidgetLink, linkFromId, selectBy} from 'app/client/ui/selectBy';
|
||||||
import {testId} from 'app/client/ui2018/cssVars';
|
import {mediaSmall, testId} from 'app/client/ui2018/cssVars';
|
||||||
import {IconName} from 'app/client/ui2018/IconList';
|
import {IconName} from 'app/client/ui2018/IconList';
|
||||||
import {ActionGroup} from 'app/common/ActionGroup';
|
import {ActionGroup} from 'app/common/ActionGroup';
|
||||||
import {delay} from 'app/common/delay';
|
import {delay} from 'app/common/delay';
|
||||||
@ -687,6 +687,11 @@ const cssViewContentPane = styled('div', `
|
|||||||
position: relative;
|
position: relative;
|
||||||
min-width: 240px;
|
min-width: 240px;
|
||||||
margin: 12px;
|
margin: 12px;
|
||||||
|
@media ${mediaSmall} {
|
||||||
|
& {
|
||||||
|
margin: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@media print {
|
@media print {
|
||||||
& {
|
& {
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
display: -webkit-flex;
|
display: -webkit-flex;
|
||||||
display: flex;
|
display: flex;
|
||||||
min-width: 0px;
|
min-width: 0px;
|
||||||
|
flex-grow: var(--flex-grow, 1) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout_hbox.layout_fill_window {
|
.layout_hbox.layout_fill_window {
|
||||||
|
@ -140,7 +140,7 @@ LayoutBox.prototype.buildDom = function() {
|
|||||||
kd.cssClass(wrap(function() { return (self.layout.fillWindow ? 'layout_fill_window' :
|
kd.cssClass(wrap(function() { return (self.layout.fillWindow ? 'layout_fill_window' :
|
||||||
(self.isLastChild() ? 'layout_last_child' : null));
|
(self.isLastChild() ? 'layout_last_child' : null));
|
||||||
})),
|
})),
|
||||||
kd.style('flexGrow', wrap(function() {
|
kd.style('--flex-grow', wrap(function() {
|
||||||
return (self.isVBox() || (self.isHBox() && self.layout.fillWindow)) ? self.flexSize() : '';
|
return (self.isVBox() || (self.isHBox() && self.layout.fillWindow)) ? self.flexSize() : '';
|
||||||
})),
|
})),
|
||||||
kd.domData('layoutBox', this),
|
kd.domData('layoutBox', this),
|
||||||
|
@ -4,34 +4,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.viewsection_title {
|
.viewsection_title {
|
||||||
background-color: #e5e5e5;
|
|
||||||
color: black;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
line-height: 2;
|
|
||||||
|
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
|
||||||
|
|
||||||
.active_section > .viewsection_title {
|
|
||||||
background-color: #3e568e;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.viewsection_content.newui > .viewsection_title {
|
|
||||||
height: 24px;
|
height: 24px;
|
||||||
margin-left: -16px; /* to include drag handle that shows up on hover */
|
margin-left: -16px; /* to include drag handle that shows up on hover */
|
||||||
color: var(--grist-color-slate);
|
color: var(--grist-color-slate);
|
||||||
background-color: unset;
|
|
||||||
font-size: var(--grist-small-font-size);
|
font-size: var(--grist-small-font-size);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
line-height: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.active_section.newui > .viewsection_title {
|
|
||||||
background-color: unset;
|
|
||||||
color: var(--grist-color-slate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.viewsection_titletext {
|
.viewsection_titletext {
|
||||||
@ -41,14 +22,8 @@
|
|||||||
|
|
||||||
.viewsection_content {
|
.viewsection_content {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
margin: 4px;
|
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
box-shadow: 0px 1px 3px 0px rgba(0,0,0,0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.viewsection_content.newui {
|
|
||||||
margin: 12px;
|
margin: 12px;
|
||||||
box-shadow: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.viewsection_title_colorbox {
|
.viewsection_title_colorbox {
|
||||||
@ -59,15 +34,10 @@
|
|||||||
box-shadow: inset 0px 0px 5px rgba(0,0,0,0.5);
|
box-shadow: inset 0px 0px 5px rgba(0,0,0,0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* TODO should be switched to use new icon */
|
||||||
.viewsection_drag_indicator {
|
.viewsection_drag_indicator {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
margin: auto;
|
|
||||||
padding: 0px 2px;
|
|
||||||
top: 0px;
|
top: 0px;
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO should be switched to use new icon */
|
|
||||||
.viewsection_drag_indicator.newui {
|
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -131,19 +101,16 @@
|
|||||||
.view_data_pane_container {
|
.view_data_pane_container {
|
||||||
position: relative;
|
position: relative;
|
||||||
flex: auto;
|
flex: auto;
|
||||||
}
|
|
||||||
|
|
||||||
.viewsection_content.newui > .view_data_pane_container {
|
|
||||||
border: 1px solid var(--grist-color-dark-grey);
|
border: 1px solid var(--grist-color-dark-grey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media not print {
|
@media not print {
|
||||||
.active_section.newui > .view_data_pane_container {
|
.active_section > .view_data_pane_container {
|
||||||
box-shadow: -2px 0 0 0px var(--grist-color-light-green);
|
box-shadow: -2px 0 0 0px var(--grist-color-light-green);
|
||||||
border-left: 1px solid var(--grist-color-light-green);
|
border-left: 1px solid var(--grist-color-light-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
.active_section.newui > .view_data_pane_container.viewsection_type_detail {
|
.active_section > .view_data_pane_container.viewsection_type_detail {
|
||||||
/* this color is a translucent version of grist-color-light-green */
|
/* this color is a translucent version of grist-color-light-green */
|
||||||
box-shadow: -2px 0 0 0px var(--grist-color-inactive-cursor);
|
box-shadow: -2px 0 0 0px var(--grist-color-inactive-cursor);
|
||||||
border-left: 1px solid var(--grist-color-inactive-cursor);
|
border-left: 1px solid var(--grist-color-inactive-cursor);
|
||||||
|
@ -13,11 +13,11 @@ import {createObsArray} from 'app/client/lib/koArrayWrap';
|
|||||||
import {ViewRec, ViewSectionRec} from 'app/client/models/DocModel';
|
import {ViewRec, ViewSectionRec} from 'app/client/models/DocModel';
|
||||||
import {reportError} from 'app/client/models/errors';
|
import {reportError} from 'app/client/models/errors';
|
||||||
import {viewSectionMenu} from 'app/client/ui/ViewSectionMenu';
|
import {viewSectionMenu} from 'app/client/ui/ViewSectionMenu';
|
||||||
import {testId} from 'app/client/ui2018/cssVars';
|
import {colors, mediaSmall, testId} from 'app/client/ui2018/cssVars';
|
||||||
import {editableLabel} from 'app/client/ui2018/editableLabel';
|
import {editableLabel} from 'app/client/ui2018/editableLabel';
|
||||||
import {DisposableWithEvents} from 'app/common/DisposableWithEvents';
|
import {DisposableWithEvents} from 'app/common/DisposableWithEvents';
|
||||||
import {mod} from 'app/common/gutil';
|
import {mod} from 'app/common/gutil';
|
||||||
import {computedArray, Disposable, dom, fromKo, Holder, IDomComponent, subscribe} from 'grainjs';
|
import {computedArray, Disposable, dom, fromKo, Holder, IDomComponent, styled, subscribe} from 'grainjs';
|
||||||
import * as ko from 'knockout';
|
import * as ko from 'knockout';
|
||||||
import * as _ from 'underscore';
|
import * as _ from 'underscore';
|
||||||
|
|
||||||
@ -121,6 +121,21 @@ export class ViewLayout extends DisposableWithEvents implements IDomComponent {
|
|||||||
// (See https://stackoverflow.com/questions/2381336/detect-click-into-iframe-using-javascript).
|
// (See https://stackoverflow.com/questions/2381336/detect-click-into-iframe-using-javascript).
|
||||||
this.listenTo(this.gristDoc.app, 'clipboard_blur', this._maybeFocusInSection);
|
this.listenTo(this.gristDoc.app, 'clipboard_blur', this._maybeFocusInSection);
|
||||||
|
|
||||||
|
const classActive = cssLayoutBox.className + '-active';
|
||||||
|
const classInactive = cssLayoutBox.className + '-inactive';
|
||||||
|
this.autoDispose(subscribe(fromKo(this.viewModel.activeSectionId), (use, id) => {
|
||||||
|
this._layout.forEachBox((box: {dom: Element}) => {
|
||||||
|
box.dom.classList.add(classInactive);
|
||||||
|
box.dom.classList.remove(classActive);
|
||||||
|
});
|
||||||
|
let elem: Element|null = this._layout.getLeafBox(id)?.dom;
|
||||||
|
while (elem?.matches('.layout_box')) {
|
||||||
|
elem.classList.remove(classInactive);
|
||||||
|
elem.classList.add(classActive);
|
||||||
|
elem = elem.parentElement;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
const commandGroup = {
|
const commandGroup = {
|
||||||
deleteSection: () => { this._removeViewSection(this.viewModel.activeSectionId()); },
|
deleteSection: () => { this._removeViewSection(this.viewModel.activeSectionId()); },
|
||||||
nextSection: () => { this._otherSection(+1); },
|
nextSection: () => { this._otherSection(+1); },
|
||||||
@ -158,11 +173,13 @@ export class ViewLayout extends DisposableWithEvents implements IDomComponent {
|
|||||||
const vs: ViewSectionRec = this.docModel.viewSections.getRowModel(sectionRowId);
|
const vs: ViewSectionRec = this.docModel.viewSections.getRowModel(sectionRowId);
|
||||||
return dom('div.view_leaf.viewsection_content.flexvbox.flexauto',
|
return dom('div.view_leaf.viewsection_content.flexvbox.flexauto',
|
||||||
testId(`viewlayout-section-${sectionRowId}`),
|
testId(`viewlayout-section-${sectionRowId}`),
|
||||||
this.gristDoc.app.addNewUIClass(),
|
|
||||||
|
cssViewLeaf.cls(''),
|
||||||
|
cssViewLeafInactive.cls('', (use) => !vs.isDisposed() && !use(vs.hasFocus)),
|
||||||
dom.cls('active_section', vs.hasFocus),
|
dom.cls('active_section', vs.hasFocus),
|
||||||
|
|
||||||
dom.maybe((use) => use(vs.viewInstance) !== null, () => dom('div.viewsection_title.flexhbox',
|
dom.maybe((use) => use(vs.viewInstance) !== null, () => dom('div.viewsection_title.flexhbox',
|
||||||
dom('span.viewsection_drag_indicator.glyphicon.glyphicon-option-vertical',
|
dom('span.viewsection_drag_indicator.glyphicon.glyphicon-option-vertical',
|
||||||
this.gristDoc.app.addNewUIClass(),
|
|
||||||
// Makes element grabbable only if grist is not readonly.
|
// Makes element grabbable only if grist is not readonly.
|
||||||
dom.cls('layout_grabbable', (use) => !use(this.gristDoc.isReadonlyKo))),
|
dom.cls('layout_grabbable', (use) => !use(this.gristDoc.isReadonlyKo))),
|
||||||
dom('div.flexitem.flexhbox',
|
dom('div.flexitem.flexhbox',
|
||||||
@ -270,3 +287,79 @@ export class ViewLayout extends DisposableWithEvents implements IDomComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cssViewLeaf = styled('div', `
|
||||||
|
@media ${mediaSmall} {
|
||||||
|
& {
|
||||||
|
margin: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const cssViewLeafInactive = styled('div', `
|
||||||
|
@media ${mediaSmall} {
|
||||||
|
& {
|
||||||
|
overflow: hidden;
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
-45deg,
|
||||||
|
${colors.mediumGreyOpaque},
|
||||||
|
${colors.mediumGreyOpaque} 10px,
|
||||||
|
${colors.lightGrey} 10px,
|
||||||
|
${colors.lightGrey} 20px
|
||||||
|
);
|
||||||
|
border: 1px solid ${colors.darkGrey};
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0 2px;
|
||||||
|
}
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
&.layout_vbox {
|
||||||
|
max-width: 32px;
|
||||||
|
}
|
||||||
|
&.layout_hbox {
|
||||||
|
max-height: 32px;
|
||||||
|
}
|
||||||
|
& > .viewsection_title.flexhbox {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
& > .view_data_pane_container,
|
||||||
|
& .viewsection_buttons,
|
||||||
|
& .grist-single-record__menu {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const cssLayoutBox = styled('div', `
|
||||||
|
@media ${mediaSmall} {
|
||||||
|
&-active, &-inactive {
|
||||||
|
transition: flex-grow 0.4s;
|
||||||
|
}
|
||||||
|
&-active > &-inactive,
|
||||||
|
&-active > &-inactive.layout_hbox .layout_hbox,
|
||||||
|
&-active > &-inactive.layout_vbox .layout_vbox {
|
||||||
|
flex: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-active > &-inactive.layout_hbox.layout_leaf,
|
||||||
|
&-active > &-inactive.layout_hbox .layout_hbox.layout_leaf {
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-active > &-inactive.layout_vbox.layout_leaf,
|
||||||
|
&-active > &-inactive.layout_vbox .layout_vbox.layout_leaf {
|
||||||
|
width: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-inactive.layout_leaf {
|
||||||
|
min-height: 40px;
|
||||||
|
min-width: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
Loading…
Reference in New Issue
Block a user