mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(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:
@@ -5,24 +5,24 @@ import {CustomView} from 'app/client/components/CustomView';
|
||||
import * as DetailView from 'app/client/components/DetailView';
|
||||
import * as GridView from 'app/client/components/GridView';
|
||||
import {GristDoc} from 'app/client/components/GristDoc';
|
||||
import {Layout} from 'app/client/components/Layout';
|
||||
import {BoxSpec, Layout} from 'app/client/components/Layout';
|
||||
import {LayoutEditor} from 'app/client/components/LayoutEditor';
|
||||
import {printViewSection} from 'app/client/components/Printing';
|
||||
import {Delay} from 'app/client/lib/Delay';
|
||||
import {createObsArray} from 'app/client/lib/koArrayWrap';
|
||||
import {ViewRec, ViewSectionRec} from 'app/client/models/DocModel';
|
||||
import {reportError} from 'app/client/models/errors';
|
||||
import {filterBar} from 'app/client/ui/FilterBar';
|
||||
import {viewSectionMenu} from 'app/client/ui/ViewSectionMenu';
|
||||
import {buildWidgetTitle} from 'app/client/ui/WidgetTitle';
|
||||
import {colors, isNarrowScreen, isNarrowScreenObs, mediaSmall, testId, theme} from 'app/client/ui2018/cssVars';
|
||||
import {isNarrowScreen, mediaSmall, testId, theme} from 'app/client/ui2018/cssVars';
|
||||
import {icon} from 'app/client/ui2018/icons';
|
||||
import {DisposableWithEvents} from 'app/common/DisposableWithEvents';
|
||||
import {LayoutTray} from 'app/client/components/LayoutTray';
|
||||
import {buildViewSectionDom} from 'app/client/components/buildViewSectionDom';
|
||||
import {mod} from 'app/common/gutil';
|
||||
import * as ko from 'knockout';
|
||||
import * as _ from 'underscore';
|
||||
import debounce from 'lodash/debounce';
|
||||
import {computedArray, Disposable, dom, fromKo, Holder, IDomComponent, Observable, styled, subscribe} from 'grainjs';
|
||||
import {Computed, computedArray, Disposable, dom, fromKo, Holder,
|
||||
IDomComponent, Observable, styled, subscribe} from 'grainjs';
|
||||
|
||||
// tslint:disable:no-console
|
||||
|
||||
@@ -49,8 +49,23 @@ export class ViewSectionHelper extends Disposable {
|
||||
constructor(gristDoc: GristDoc, vs: ViewSectionRec) {
|
||||
super();
|
||||
this.onDispose(() => vs.viewInstance(null));
|
||||
// If this is a collapsed section (but not active), don't create an instance (or remove the old one).
|
||||
// Collapsed section can be expanded and shown in the popup window, it will be active then.
|
||||
// This is important to avoid recreating the instance when the section is collapsed, but mainly for the
|
||||
// charts as they are not able to handle being detached from the dom.
|
||||
const hidden = Computed.create(this, (use) => {
|
||||
// Note: this is a separate computed from the one below (with subscribe method), because we don't want
|
||||
// trigger it unnecessarily.
|
||||
return use(vs.isCollapsed) && use(gristDoc.externalSectionId) !== use(vs.id);
|
||||
});
|
||||
|
||||
this.autoDispose(subscribe((use) => {
|
||||
// Destroy the instance if the section is hidden.
|
||||
if (use(hidden)) {
|
||||
this._instance.clear();
|
||||
vs.viewInstance(null);
|
||||
return;
|
||||
}
|
||||
// Rebuild the section when its type changes or its underlying table.
|
||||
const table = use(vs.table);
|
||||
const Cons = getInstanceConstructor(use(vs.parentKey));
|
||||
@@ -69,18 +84,22 @@ export class ViewSectionHelper extends Disposable {
|
||||
export class ViewLayout extends DisposableWithEvents implements IDomComponent {
|
||||
public docModel = this.gristDoc.docModel;
|
||||
public viewModel: ViewRec;
|
||||
public layoutSpec: ko.Computed<object>;
|
||||
public layoutSpec: ko.Computed<BoxSpec>;
|
||||
public maximized: Observable<number|null>;
|
||||
public isResizing = Observable.create(this, false);
|
||||
public layout: Layout;
|
||||
public layoutEditor: LayoutEditor;
|
||||
public layoutTray: LayoutTray;
|
||||
public layoutSaveDelay = this.autoDispose(new Delay());
|
||||
|
||||
private _freeze = false;
|
||||
private _layout: Layout;
|
||||
private _sectionIds: number[];
|
||||
private _isResizing = Observable.create(this, false);
|
||||
|
||||
// Exposed for test to indicate that save has not yet been called.
|
||||
private _savePending = Observable.create(this, false);
|
||||
constructor(public readonly gristDoc: GristDoc, viewId: number) {
|
||||
super();
|
||||
this.viewModel = this.docModel.views.getRowModel(viewId);
|
||||
|
||||
|
||||
// A Map from viewSection RowModels to corresponding View class instances.
|
||||
// TODO add a test that creating / deleting a section creates/destroys one instance, and
|
||||
// switching pages destroys all instances.
|
||||
@@ -93,40 +112,34 @@ export class ViewLayout extends DisposableWithEvents implements IDomComponent {
|
||||
() => this._updateLayoutSpecWithSections(this.viewModel.layoutSpecObj()))
|
||||
.extend({rateLimit: 0}));
|
||||
|
||||
this._layout = this.autoDispose(Layout.create(this.layoutSpec(),
|
||||
this.layout = this.autoDispose(Layout.create(this.layoutSpec(),
|
||||
this._buildLeafContent.bind(this), true));
|
||||
this._sectionIds = this._layout.getAllLeafIds();
|
||||
|
||||
|
||||
// When the layoutSpec changes by some means other than the layout editor, rebuild.
|
||||
// This includes adding/removing sections and undo/redo.
|
||||
this.autoDispose(this.layoutSpec.subscribe((spec) => this._freeze || this._rebuildLayout(spec)));
|
||||
this.autoDispose(this.layoutSpec.subscribe((spec) => this._freeze || this.rebuildLayout(spec)));
|
||||
|
||||
const layoutSaveDelay = this.autoDispose(new Delay());
|
||||
|
||||
this.listenTo(this._layout, 'layoutUserEditStop', () => {
|
||||
this._isResizing.set(false);
|
||||
layoutSaveDelay.schedule(1000, () => {
|
||||
if (!this._layout) { return; }
|
||||
|
||||
// Only save layout changes when the document isn't read-only.
|
||||
if (!this.gristDoc.isReadonly.get()) {
|
||||
(this.viewModel.layoutSpecObj as any).setAndSave(this._layout.getLayoutSpec());
|
||||
}
|
||||
this._onResize();
|
||||
this.listenTo(this.layout, 'layoutUserEditStop', () => {
|
||||
this.isResizing.set(false);
|
||||
this.layoutSaveDelay.schedule(1000, () => {
|
||||
this.saveLayoutSpec();
|
||||
});
|
||||
});
|
||||
|
||||
// Do not save if the user has started editing again.
|
||||
this.listenTo(this._layout, 'layoutUserEditStart', () => {
|
||||
layoutSaveDelay.cancel();
|
||||
this._isResizing.set(true);
|
||||
this.listenTo(this.layout, 'layoutUserEditStart', () => {
|
||||
this.layoutSaveDelay.cancel();
|
||||
this._savePending.set(true);
|
||||
this.isResizing.set(true);
|
||||
});
|
||||
|
||||
this.autoDispose(LayoutEditor.create(this._layout));
|
||||
this.layoutEditor = this.autoDispose(LayoutEditor.create(this.layout));
|
||||
this.layoutTray = LayoutTray.create(this, this);
|
||||
|
||||
// Add disposal of this._layout after layoutEditor, so that it gets disposed first, and
|
||||
// layoutEditor doesn't attempt to update it in its own disposal logic.
|
||||
this.onDispose(() => this._layout.dispose());
|
||||
this.onDispose(() => this.layout.dispose());
|
||||
|
||||
this.autoDispose(this.gristDoc.resizeEmitter.addListener(this._onResize, this));
|
||||
|
||||
@@ -148,19 +161,19 @@ export class ViewLayout extends DisposableWithEvents implements IDomComponent {
|
||||
// Make resize.
|
||||
view.onResize();
|
||||
}, 0);
|
||||
this._layout.rootElem.addEventListener('transitionend', handler);
|
||||
this.layout.rootElem.addEventListener('transitionend', handler);
|
||||
// Don't need to dispose the listener, as the rootElem is disposed with the layout.
|
||||
|
||||
const classActive = cssLayoutBox.className + '-active';
|
||||
const classInactive = cssLayoutBox.className + '-inactive';
|
||||
this.autoDispose(subscribe(fromKo(this.viewModel.activeSection), (use, section) => {
|
||||
const id = section.getRowId();
|
||||
this._layout.forEachBox(box => {
|
||||
this.layout.forEachBox(box => {
|
||||
box.dom!.classList.add(classInactive);
|
||||
box.dom!.classList.remove(classActive);
|
||||
box.dom!.classList.remove("transition");
|
||||
});
|
||||
let elem: Element|null = this._layout.getLeafBox(id)?.dom || null;
|
||||
let elem: Element|null = this.layout.getLeafBox(id)?.dom || null;
|
||||
while (elem?.matches('.layout_box')) {
|
||||
elem.classList.remove(classInactive);
|
||||
elem.classList.add(classActive);
|
||||
@@ -172,10 +185,10 @@ export class ViewLayout extends DisposableWithEvents implements IDomComponent {
|
||||
}));
|
||||
|
||||
const commandGroup = {
|
||||
deleteSection: () => { this._removeViewSection(this.viewModel.activeSectionId()); },
|
||||
deleteSection: () => { this.removeViewSection(this.viewModel.activeSectionId()); },
|
||||
nextSection: () => { this._otherSection(+1); },
|
||||
prevSection: () => { this._otherSection(-1); },
|
||||
printSection: () => { printViewSection(this._layout, this.viewModel.activeSection()).catch(reportError); },
|
||||
printSection: () => { printViewSection(this.layout, this.viewModel.activeSection()).catch(reportError); },
|
||||
sortFilterMenuOpen: (sectionId?: number) => { this._openSortFilterMenu(sectionId); },
|
||||
maximizeActiveSection: () => { this._maximizeActiveSection(); },
|
||||
cancel: () => {
|
||||
@@ -186,7 +199,7 @@ export class ViewLayout extends DisposableWithEvents implements IDomComponent {
|
||||
};
|
||||
this.autoDispose(commands.createGroup(commandGroup, this, true));
|
||||
|
||||
this.maximized = fromKo(this._layout.maximized) as any;
|
||||
this.maximized = fromKo(this.layout.maximized) as any;
|
||||
this.autoDispose(this.maximized.addListener(val => {
|
||||
const section = this.viewModel.activeSection.peek();
|
||||
// If section is not disposed and it is not a deleted row.
|
||||
@@ -201,9 +214,12 @@ export class ViewLayout extends DisposableWithEvents implements IDomComponent {
|
||||
return cssOverlay(
|
||||
cssOverlay.cls('-active', use => !!use(this.maximized)),
|
||||
testId('viewLayout-overlay'),
|
||||
cssLayoutWrapper(
|
||||
cssLayoutWrapper.cls('-active', use => !!use(this.maximized)),
|
||||
this._layout.rootElem,
|
||||
cssLayoutContainer(
|
||||
this.layoutTray.buildDom(),
|
||||
cssLayoutWrapper(
|
||||
cssLayoutWrapper.cls('-active', use => !!use(this.maximized)),
|
||||
this.layout.rootElem,
|
||||
),
|
||||
),
|
||||
dom.maybe(use => !!use(this.maximized), () =>
|
||||
cssCloseButton('CrossBig',
|
||||
@@ -212,7 +228,8 @@ export class ViewLayout extends DisposableWithEvents implements IDomComponent {
|
||||
)
|
||||
),
|
||||
// Close the lightbox when user clicks exactly on the overlay.
|
||||
dom.on('click', (ev, elem) => void (ev.target === elem && this.maximized.get() ? close() : null))
|
||||
dom.on('click', (ev, elem) => void (ev.target === elem && this.maximized.get() ? close() : null)),
|
||||
dom.cls('test-viewLayout-save-pending', this._savePending)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -225,20 +242,41 @@ export class ViewLayout extends DisposableWithEvents implements IDomComponent {
|
||||
return await promise;
|
||||
} finally {
|
||||
this._freeze = false;
|
||||
this._rebuildLayout(this.layoutSpec.peek());
|
||||
this.rebuildLayout(this.layoutSpec.peek());
|
||||
}
|
||||
}
|
||||
|
||||
public saveLayoutSpec(specs?: BoxSpec) {
|
||||
this._savePending.set(false);
|
||||
// Cancel the automatic delay.
|
||||
this.layoutSaveDelay.cancel();
|
||||
if (!this.layout) { return; }
|
||||
// Only save layout changes when the document isn't read-only.
|
||||
if (!this.gristDoc.isReadonly.get()) {
|
||||
if (!specs) {
|
||||
specs = this.layout.getLayoutSpec();
|
||||
specs.collapsed = this.viewModel.activeCollapsedSections.peek().map((leaf)=> ({leaf}));
|
||||
}
|
||||
this.viewModel.layoutSpecObj.setAndSave(specs).catch(reportError);
|
||||
}
|
||||
this._onResize();
|
||||
}
|
||||
|
||||
// Removes a view section from the current view. Should only be called if there is
|
||||
// more than one viewsection in the view.
|
||||
private _removeViewSection(viewSectionRowId: number) {
|
||||
public removeViewSection(viewSectionRowId: number) {
|
||||
this.gristDoc.docData.sendAction(['RemoveViewSection', viewSectionRowId]).catch(reportError);
|
||||
}
|
||||
|
||||
public rebuildLayout(layoutSpec: object) {
|
||||
this.layout.buildLayout(layoutSpec, true);
|
||||
this._onResize();
|
||||
}
|
||||
|
||||
private _maximizeActiveSection() {
|
||||
const activeSection = this.viewModel.activeSection();
|
||||
const activeSectionId = activeSection.getRowId();
|
||||
const activeSectionBox = this._layout.getLeafBox(activeSectionId);
|
||||
const activeSectionBox = this.layout.getLeafBox(activeSectionId);
|
||||
if (!activeSectionBox) { return; }
|
||||
activeSectionBox.maximize();
|
||||
}
|
||||
@@ -247,7 +285,7 @@ export class ViewLayout extends DisposableWithEvents implements IDomComponent {
|
||||
return buildViewSectionDom({
|
||||
gristDoc: this.gristDoc,
|
||||
sectionRowId,
|
||||
isResizing: this._isResizing,
|
||||
isResizing: this.isResizing,
|
||||
viewModel: this.viewModel
|
||||
});
|
||||
}
|
||||
@@ -256,7 +294,7 @@ export class ViewLayout extends DisposableWithEvents implements IDomComponent {
|
||||
* If there is no layout saved, we can create a default layout just from the list of fields for
|
||||
* this view section. By default we just arrange them into a list of rows, two fields per row.
|
||||
*/
|
||||
private _updateLayoutSpecWithSections(spec: object) {
|
||||
private _updateLayoutSpecWithSections(spec: BoxSpec) {
|
||||
// We use tmpLayout as a way to manipulate the layout before we get a final spec from it.
|
||||
const tmpLayout = Layout.create(spec, () => dom('div'), true);
|
||||
|
||||
@@ -278,15 +316,18 @@ export class ViewLayout extends DisposableWithEvents implements IDomComponent {
|
||||
}
|
||||
|
||||
// For any stale fields (no longer among viewFields), remove them from tmpLayout.
|
||||
_.difference(specFieldIds, viewSectionIds).forEach(function(leafId: any) {
|
||||
_.difference(specFieldIds, viewSectionIds).forEach(function(leafId: string|number) {
|
||||
tmpLayout.getLeafBox(leafId)?.dispose();
|
||||
});
|
||||
|
||||
// For all fields that should be in the spec but aren't, add them to tmpLayout. We maintain a
|
||||
// two-column layout, so add a new row, or a second box to the last row if it's a leaf.
|
||||
_.difference(viewSectionIds, specFieldIds).forEach(function(leafId: any) {
|
||||
// Only add the builder box if it hasn`t already been created
|
||||
addToSpec(leafId);
|
||||
const missingLeafs = _.difference(viewSectionIds, specFieldIds);
|
||||
const collapsedLeafs = new Set((spec.collapsed || []).map(c => c.leaf));
|
||||
missingLeafs.forEach(function(leafId: any) {
|
||||
if (!collapsedLeafs.has(leafId)) {
|
||||
addToSpec(leafId);
|
||||
}
|
||||
});
|
||||
|
||||
spec = tmpLayout.getLayoutSpec();
|
||||
@@ -294,11 +335,7 @@ export class ViewLayout extends DisposableWithEvents implements IDomComponent {
|
||||
return spec;
|
||||
}
|
||||
|
||||
private _rebuildLayout(layoutSpec: object) {
|
||||
this._layout.buildLayout(layoutSpec, true);
|
||||
this._onResize();
|
||||
this._sectionIds = this._layout.getAllLeafIds();
|
||||
}
|
||||
|
||||
|
||||
// Resizes the scrolly windows of all viewSection classes with a 'scrolly' property.
|
||||
private _onResize() {
|
||||
@@ -313,17 +350,17 @@ export class ViewLayout extends DisposableWithEvents implements IDomComponent {
|
||||
// Select another section in cyclic ordering of sections. Order is counter-clockwise if given a
|
||||
// positive `delta`, clockwise otherwise.
|
||||
private _otherSection(delta: number) {
|
||||
const sectionIds = this.layout.getAllLeafIds();
|
||||
const sectionId = this.viewModel.activeSectionId.peek();
|
||||
const currentIndex = this._sectionIds.indexOf(sectionId);
|
||||
const index = mod(currentIndex + delta, this._sectionIds.length);
|
||||
|
||||
const currentIndex = sectionIds.indexOf(sectionId);
|
||||
const index = mod(currentIndex + delta, sectionIds.length);
|
||||
// update the active section id
|
||||
this.viewModel.activeSectionId(this._sectionIds[index]);
|
||||
this.viewModel.activeSectionId(sectionIds[index]);
|
||||
}
|
||||
|
||||
private _maybeFocusInSection() {
|
||||
// If the focused element is inside a view section, make that section active.
|
||||
const layoutBox = this._layout.getContainingBox(document.activeElement);
|
||||
const layoutBox = this.layout.getContainingBox(document.activeElement);
|
||||
if (layoutBox && layoutBox.leafId) {
|
||||
this.gristDoc.viewModel.activeSectionId(layoutBox.leafId.peek());
|
||||
}
|
||||
@@ -336,7 +373,7 @@ export class ViewLayout extends DisposableWithEvents implements IDomComponent {
|
||||
*/
|
||||
private _openSortFilterMenu(sectionId?: number) {
|
||||
const id = sectionId ?? this.viewModel.activeSectionId();
|
||||
const leafBoxDom = this._layout.getLeafBox(id)?.dom;
|
||||
const leafBoxDom = this.layout.getLeafBox(id)?.dom;
|
||||
if (!leafBoxDom) { return; }
|
||||
|
||||
const menu: HTMLElement | null = leafBoxDom.querySelector('.test-section-menu-sortAndFilter');
|
||||
@@ -344,125 +381,6 @@ export class ViewLayout extends DisposableWithEvents implements IDomComponent {
|
||||
}
|
||||
}
|
||||
|
||||
export function buildViewSectionDom(options: {
|
||||
gristDoc: GristDoc,
|
||||
sectionRowId: number,
|
||||
isResizing?: Observable<boolean>
|
||||
viewModel?: ViewRec,
|
||||
// Should show drag anchor.
|
||||
draggable?: boolean, /* defaults to true */
|
||||
// Should show green bar on the left (but preserves active-section class).
|
||||
focusable?: boolean, /* defaults to true */
|
||||
tableNameHidden?: boolean,
|
||||
widgetNameHidden?: boolean,
|
||||
}) {
|
||||
const isResizing = options.isResizing ?? Observable.create(null, false);
|
||||
const {gristDoc, sectionRowId, viewModel, draggable = true, focusable = true} = options;
|
||||
|
||||
// Creating normal section dom
|
||||
const vs: ViewSectionRec = gristDoc.docModel.viewSections.getRowModel(sectionRowId);
|
||||
return dom('div.view_leaf.viewsection_content.flexvbox.flexauto',
|
||||
testId(`viewlayout-section-${sectionRowId}`),
|
||||
!options.isResizing ? dom.autoDispose(isResizing) : null,
|
||||
cssViewLeaf.cls(''),
|
||||
cssViewLeafInactive.cls('', (use) => !vs.isDisposed() && !use(vs.hasFocus)),
|
||||
dom.cls('active_section', vs.hasFocus),
|
||||
dom.cls('active_section--no-indicator', !focusable),
|
||||
dom.maybe<BaseView|null>((use) => use(vs.viewInstance), (viewInstance) => dom('div.viewsection_title.flexhbox',
|
||||
cssDragIcon('DragDrop',
|
||||
dom.cls("viewsection_drag_indicator"),
|
||||
// Makes element grabbable only if grist is not readonly.
|
||||
dom.cls('layout_grabbable', (use) => !use(gristDoc.isReadonlyKo)),
|
||||
!draggable ? dom.style("visibility", "hidden") : null
|
||||
),
|
||||
dom.maybe((use) => use(use(viewInstance.viewSection.table).summarySourceTable), () =>
|
||||
cssSigmaIcon('Pivot', testId('sigma'))),
|
||||
buildWidgetTitle(vs, options, testId('viewsection-title'), cssTestClick(testId("viewsection-blank"))),
|
||||
viewInstance.buildTitleControls(),
|
||||
dom('div.viewsection_buttons',
|
||||
dom.create(viewSectionMenu, gristDoc, vs)
|
||||
)
|
||||
)),
|
||||
dom.create(filterBar, gristDoc, vs),
|
||||
dom.maybe<BaseView|null>(vs.viewInstance, (viewInstance) => [
|
||||
dom('div.view_data_pane_container.flexvbox',
|
||||
cssResizing.cls('', isResizing),
|
||||
dom.maybe(viewInstance.disableEditing, () =>
|
||||
dom('div.disable_viewpane.flexvbox', 'No data')
|
||||
),
|
||||
dom.maybe(viewInstance.isTruncated, () =>
|
||||
dom('div.viewsection_truncated', 'Not all data is shown')
|
||||
),
|
||||
dom.cls((use) => 'viewsection_type_' + use(vs.parentKey)),
|
||||
viewInstance.viewPane,
|
||||
),
|
||||
dom.maybe(use => !use(isNarrowScreenObs()), () => viewInstance.selectionSummary?.buildDom()),
|
||||
]),
|
||||
dom.on('mousedown', () => { viewModel?.activeSectionId(sectionRowId); }),
|
||||
);
|
||||
}
|
||||
|
||||
// With new widgetPopup it is hard to click on viewSection without a activating it, hence we
|
||||
// add a little blank space to use in test.
|
||||
const cssTestClick = styled(`div`, `
|
||||
min-width: 2px;
|
||||
`);
|
||||
|
||||
const cssSigmaIcon = styled(icon, `
|
||||
bottom: 1px;
|
||||
margin-right: 5px;
|
||||
background-color: ${theme.lightText}
|
||||
`);
|
||||
|
||||
const cssViewLeaf = styled('div', `
|
||||
@media ${mediaSmall} {
|
||||
& {
|
||||
margin: 4px;
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const cssViewLeafInactive = styled('div', `
|
||||
@media screen and ${mediaSmall} {
|
||||
& {
|
||||
overflow: hidden;
|
||||
background: repeating-linear-gradient(
|
||||
-45deg,
|
||||
${theme.widgetInactiveStripesDark},
|
||||
${theme.widgetInactiveStripesDark} 10px,
|
||||
${theme.widgetInactiveStripesLight} 10px,
|
||||
${theme.widgetInactiveStripesLight} 20px
|
||||
);
|
||||
border: 1px solid ${theme.widgetBorder};
|
||||
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,
|
||||
& > .filter_bar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const cssLayoutBox = styled('div', `
|
||||
@media screen and ${mediaSmall} {
|
||||
&-active, &-inactive {
|
||||
@@ -491,27 +409,11 @@ const cssLayoutBox = styled('div', `
|
||||
}
|
||||
`);
|
||||
|
||||
// z-index ensure it's above the resizer line, since it's hard to grab otherwise
|
||||
const cssDragIcon = styled(icon, `
|
||||
visibility: hidden;
|
||||
--icon-color: ${colors.slate};
|
||||
top: -1px;
|
||||
z-index: 100;
|
||||
|
||||
.viewsection_title:hover &.layout_grabbable {
|
||||
visibility: visible;
|
||||
}
|
||||
`);
|
||||
|
||||
// This class is added while sections are being resized (or otherwise edited), to ensure that the
|
||||
// content of the section (such as an iframe) doesn't interfere with mouse drag-related events.
|
||||
// (It assumes that contained elements do not set pointer-events to another value; if that were
|
||||
// important then we'd need to use an overlay element during dragging.)
|
||||
const cssResizing = styled('div', `
|
||||
pointer-events: none;
|
||||
`);
|
||||
|
||||
const cssLayoutWrapper = styled('div', `
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
@media not print {
|
||||
&-active {
|
||||
background: ${theme.mainPanelBg};
|
||||
@@ -536,6 +438,7 @@ const cssLayoutWrapper = styled('div', `
|
||||
`);
|
||||
|
||||
const cssOverlay = styled('div', `
|
||||
height: 100%;
|
||||
@media screen {
|
||||
&-active {
|
||||
background-color: ${theme.modalBackdrop};
|
||||
@@ -545,6 +448,9 @@ const cssOverlay = styled('div', `
|
||||
padding: 20px 56px 20px 56px;
|
||||
position: absolute;
|
||||
}
|
||||
&-active .collapsed_layout {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
@media screen and ${mediaSmall} {
|
||||
&-active {
|
||||
@@ -572,3 +478,9 @@ const cssCloseButton = styled(icon, `
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const cssLayoutContainer = styled('div', `
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`);
|
||||
|
||||
Reference in New Issue
Block a user