mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Add usage to data tables page
Summary: Currently, usage is only shown for free team sites, and only for total number of rows used in a document. Future diffs will include other usage metrics and browser tests. Test Plan: Planned for future diffs; UI is still under development. Reviewers: jarek Reviewed By: jarek Subscribers: alexmojaki Differential Revision: https://phab.getgrist.com/D3343
This commit is contained in:
parent
bf271c822b
commit
d8af25de9d
@ -1,33 +1,22 @@
|
|||||||
import * as commands from 'app/client/components/commands';
|
|
||||||
import {GristDoc} from 'app/client/components/GristDoc';
|
import {GristDoc} from 'app/client/components/GristDoc';
|
||||||
import {printViewSection} from 'app/client/components/Printing';
|
|
||||||
import {buildViewSectionDom, ViewSectionHelper} from 'app/client/components/ViewLayout';
|
|
||||||
import {copyToClipboard} from 'app/client/lib/copyToClipboard';
|
import {copyToClipboard} from 'app/client/lib/copyToClipboard';
|
||||||
import {localStorageObs} from 'app/client/lib/localStorageObs';
|
import {localStorageObs} from 'app/client/lib/localStorageObs';
|
||||||
import {setTestState} from 'app/client/lib/testState';
|
import {setTestState} from 'app/client/lib/testState';
|
||||||
import {TableRec} from 'app/client/models/DocModel';
|
import {TableRec} from 'app/client/models/DocModel';
|
||||||
import {reportError} from 'app/client/models/errors';
|
import {docListHeader, docMenuTrigger} from 'app/client/ui/DocMenuCss';
|
||||||
import {docList, docListHeader, docMenuTrigger} from 'app/client/ui/DocMenuCss';
|
|
||||||
import {showTransientTooltip} from 'app/client/ui/tooltips';
|
import {showTransientTooltip} from 'app/client/ui/tooltips';
|
||||||
import {buttonSelect, cssButtonSelect} from 'app/client/ui2018/buttonSelect';
|
import {buttonSelect, cssButtonSelect} from 'app/client/ui2018/buttonSelect';
|
||||||
import * as css from 'app/client/ui2018/cssVars';
|
import * as css from 'app/client/ui2018/cssVars';
|
||||||
import {icon} from 'app/client/ui2018/icons';
|
import {icon} from 'app/client/ui2018/icons';
|
||||||
import {menu, menuItem, menuText} from 'app/client/ui2018/menus';
|
import {menu, menuItem, menuText} from 'app/client/ui2018/menus';
|
||||||
import {confirmModal} from 'app/client/ui2018/modals';
|
import {confirmModal} from 'app/client/ui2018/modals';
|
||||||
import {Computed, Disposable, dom, fromKo, makeTestId, MultiHolder, styled} from 'grainjs';
|
import {Disposable, dom, fromKo, makeTestId, MultiHolder, styled} from 'grainjs';
|
||||||
|
|
||||||
const testId = makeTestId('test-raw-data-');
|
const testId = makeTestId('test-raw-data-');
|
||||||
|
|
||||||
export class DataTables extends Disposable {
|
export class DataTables extends Disposable {
|
||||||
private _popupVisible = Computed.create(this, use => Boolean(use(this._gristDoc.viewModel.activeSectionId)));
|
|
||||||
|
|
||||||
constructor(private _gristDoc: GristDoc) {
|
constructor(private _gristDoc: GristDoc) {
|
||||||
super();
|
super();
|
||||||
const commandGroup = {
|
|
||||||
cancel: () => { this._close(); },
|
|
||||||
printSection: () => { printViewSection(null, this._gristDoc.viewModel.activeSection()).catch(reportError); },
|
|
||||||
};
|
|
||||||
this.autoDispose(commands.createGroup(commandGroup, this, true));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public buildDom() {
|
public buildDom() {
|
||||||
@ -35,11 +24,9 @@ export class DataTables extends Disposable {
|
|||||||
// Get the user id, to remember selected layout on the next visit.
|
// Get the user id, to remember selected layout on the next visit.
|
||||||
const userId = this._gristDoc.app.topAppModel.appObs.get()?.currentUser?.id ?? 0;
|
const userId = this._gristDoc.app.topAppModel.appObs.get()?.currentUser?.id ?? 0;
|
||||||
const view = holder.autoDispose(localStorageObs(`u=${userId}:raw:viewType`, "list"));
|
const view = holder.autoDispose(localStorageObs(`u=${userId}:raw:viewType`, "list"));
|
||||||
// Handler to close the lightbox.
|
|
||||||
const close = this._close.bind(this);
|
|
||||||
return container(
|
return container(
|
||||||
dom.autoDispose(holder),
|
dom.autoDispose(holder),
|
||||||
docList(
|
cssTableList(
|
||||||
/*************** List section **********/
|
/*************** List section **********/
|
||||||
testId('list'),
|
testId('list'),
|
||||||
cssBetween(
|
cssBetween(
|
||||||
@ -108,37 +95,7 @@ export class DataTables extends Disposable {
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
/*************** Lightbox section **********/
|
|
||||||
container.cls("-lightbox", this._popupVisible),
|
|
||||||
dom.domComputedOwned(fromKo(this._gristDoc.viewModel.activeSection), (owner, viewSection) => {
|
|
||||||
if (!viewSection.getRowId()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
ViewSectionHelper.create(owner, this._gristDoc, viewSection);
|
|
||||||
return cssOverlay(
|
|
||||||
testId('overlay'),
|
|
||||||
cssSectionWrapper(
|
|
||||||
buildViewSectionDom({
|
|
||||||
gristDoc: this._gristDoc,
|
|
||||||
sectionRowId: viewSection.getRowId(),
|
|
||||||
draggable: false,
|
|
||||||
focusable: false,
|
|
||||||
onRename: this._renameSection.bind(this)
|
|
||||||
})
|
|
||||||
),
|
|
||||||
cssCloseButton('CrossBig',
|
|
||||||
testId('close-button'),
|
|
||||||
dom.on('click', close)
|
|
||||||
),
|
|
||||||
// Close the lightbox when user clicks exactly on the overlay.
|
|
||||||
dom.on('click', (ev, elem) => void (ev.target === elem ? close() : null))
|
|
||||||
);
|
);
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _close() {
|
|
||||||
this._gristDoc.viewModel.activeSectionId(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _menuItems(t: TableRec) {
|
private _menuItems(t: TableRec) {
|
||||||
@ -157,12 +114,6 @@ export class DataTables extends Disposable {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _renameSection(name: string) {
|
|
||||||
// here we will rename primary page for active primary viewSection
|
|
||||||
const primaryViewName = this._gristDoc.viewModel.activeSection.peek().table.peek().primaryView.peek().name;
|
|
||||||
await primaryViewName.saveOnly(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _removeTable(t: TableRec) {
|
private _removeTable(t: TableRec) {
|
||||||
const {docModel} = this._gristDoc;
|
const {docModel} = this._gristDoc;
|
||||||
function doRemove() {
|
function doRemove() {
|
||||||
@ -177,9 +128,8 @@ export class DataTables extends Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const container = styled('div', `
|
const container = styled('div', `
|
||||||
overflow: hidden;
|
overflow-y: auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const cssBetween = styled('div', `
|
const cssBetween = styled('div', `
|
||||||
@ -353,58 +303,8 @@ const cssDots = styled('div', `
|
|||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const cssOverlay = styled('div', `
|
const cssTableList = styled('div', `
|
||||||
z-index: 10;
|
overflow-y: auto;
|
||||||
background-color: ${css.colors.backdrop};
|
position: relative;
|
||||||
inset: 0px;
|
margin-bottom: 56px;
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
padding: 32px 56px 0px 56px;
|
|
||||||
position: absolute;
|
|
||||||
@media ${css.mediaSmall} {
|
|
||||||
& {
|
|
||||||
padding: 22px;
|
|
||||||
padding-top: 30px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
const cssSectionWrapper = styled('div', `
|
|
||||||
background: white;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
border-radius: 5px;
|
|
||||||
border-bottom-left-radius: 0px;
|
|
||||||
border-bottom-right-radius: 0px;
|
|
||||||
& .viewsection_content {
|
|
||||||
margin: 0px;
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
& .viewsection_title {
|
|
||||||
padding: 0px 12px;
|
|
||||||
}
|
|
||||||
& .filter_bar {
|
|
||||||
margin-left: 6px;
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
const cssCloseButton = styled(icon, `
|
|
||||||
position: absolute;
|
|
||||||
top: 16px;
|
|
||||||
right: 16px;
|
|
||||||
height: 24px;
|
|
||||||
width: 24px;
|
|
||||||
cursor: pointer;
|
|
||||||
--icon-color: ${css.vars.primaryBg};
|
|
||||||
&:hover {
|
|
||||||
--icon-color: ${css.colors.lighterGreen};
|
|
||||||
}
|
|
||||||
@media ${css.mediaSmall} {
|
|
||||||
& {
|
|
||||||
top: 6px;
|
|
||||||
right: 6px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
`);
|
||||||
|
163
app/client/components/DocumentUsage.ts
Normal file
163
app/client/components/DocumentUsage.ts
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import {DocPageModel} from 'app/client/models/DocPageModel';
|
||||||
|
import {docListHeader} from 'app/client/ui/DocMenuCss';
|
||||||
|
import {colors, mediaXSmall} from 'app/client/ui2018/cssVars';
|
||||||
|
import {icon} from 'app/client/ui2018/icons';
|
||||||
|
import {cssLink} from 'app/client/ui2018/links';
|
||||||
|
import {DataLimitStatus} from 'app/common/ActiveDocAPI';
|
||||||
|
import {commonUrls} from 'app/common/gristUrls';
|
||||||
|
import {Computed, Disposable, dom, IDisposableOwner, Observable, styled} from 'grainjs';
|
||||||
|
|
||||||
|
const limitStatusMessages: Record<NonNullable<DataLimitStatus>, string> = {
|
||||||
|
approachingLimit: 'This document is approaching free plan limits.',
|
||||||
|
deleteOnly: 'This document is now in delete-only mode.',
|
||||||
|
gracePeriod: 'This document has exceeded free plan limits.',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays statistics about document usage, such as number of rows used.
|
||||||
|
*
|
||||||
|
* Currently only shows usage if current site is a free team site.
|
||||||
|
*/
|
||||||
|
export class DocumentUsage extends Disposable {
|
||||||
|
constructor(private _docPageModel: DocPageModel) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public buildDom() {
|
||||||
|
const features = this._docPageModel.appModel.currentFeatures;
|
||||||
|
if (features.baseMaxRowsPerDocument === undefined) { return null; }
|
||||||
|
|
||||||
|
return dom('div',
|
||||||
|
cssHeader('Usage'),
|
||||||
|
dom.domComputed(this._docPageModel.dataLimitStatus, status => {
|
||||||
|
if (!status) { return null; }
|
||||||
|
|
||||||
|
return cssLimitWarning(
|
||||||
|
cssIcon('Idea'),
|
||||||
|
cssLightlyBoldedText(
|
||||||
|
limitStatusMessages[status],
|
||||||
|
' For higher limits, ',
|
||||||
|
cssUnderlinedLink('start your 30-day free trial of the Pro plan.', {
|
||||||
|
href: commonUrls.plans,
|
||||||
|
target: '_blank',
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
cssUsageMetrics(
|
||||||
|
dom.create(buildUsageMetric, {
|
||||||
|
name: 'Rows',
|
||||||
|
currentValue: this._docPageModel.rowCount,
|
||||||
|
maximumValue: features.baseMaxRowsPerDocument,
|
||||||
|
units: 'rows',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a component which displays the current and maximum values for
|
||||||
|
* a particular metric (e.g. rows), and a progress meter showing how
|
||||||
|
* close `currentValue` is to hitting `maximumValue`.
|
||||||
|
*/
|
||||||
|
function buildUsageMetric(owner: IDisposableOwner, {name, currentValue, maximumValue, units}: {
|
||||||
|
name: string;
|
||||||
|
currentValue: Observable<number | undefined>;
|
||||||
|
maximumValue: number;
|
||||||
|
units?: string;
|
||||||
|
}) {
|
||||||
|
const percentUsed = Computed.create(owner, currentValue, (_use, value) => {
|
||||||
|
return Math.min(100, Math.floor(((value ?? 0) / maximumValue) * 100));
|
||||||
|
});
|
||||||
|
return cssUsageMetric(
|
||||||
|
cssMetricName(name),
|
||||||
|
cssProgressBarContainer(
|
||||||
|
cssProgressBarFill(
|
||||||
|
dom.style('width', use => `${use(percentUsed)}%`),
|
||||||
|
cssProgressBarFill.cls('-approaching-limit', use => use(percentUsed) >= 90)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
dom.maybe(currentValue, value =>
|
||||||
|
dom('div', `${value} of ${maximumValue}` + (units ? ` ${units}` : ''))
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const cssLightlyBoldedText = styled('div', `
|
||||||
|
font-weight: 500;
|
||||||
|
`);
|
||||||
|
|
||||||
|
const cssIconAndText = styled('div', `
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
`);
|
||||||
|
|
||||||
|
const cssLimitWarning = styled(cssIconAndText, `
|
||||||
|
margin-top: 16px;
|
||||||
|
`);
|
||||||
|
|
||||||
|
const cssIcon = styled(icon, `
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
`);
|
||||||
|
|
||||||
|
const cssMetricName = styled('div', `
|
||||||
|
font-weight: 700;
|
||||||
|
`);
|
||||||
|
|
||||||
|
const cssHeader = styled(docListHeader, `
|
||||||
|
margin-bottom: 0px;
|
||||||
|
`);
|
||||||
|
|
||||||
|
const cssUnderlinedLink = styled(cssLink, `
|
||||||
|
display: inline-block;
|
||||||
|
color: unset;
|
||||||
|
text-decoration: underline;
|
||||||
|
|
||||||
|
&:hover, &:focus {
|
||||||
|
color: unset;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const cssUsageMetrics = styled('div', `
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-top: 24px;
|
||||||
|
gap: 56px;
|
||||||
|
|
||||||
|
@media ${mediaXSmall} {
|
||||||
|
& {
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const cssUsageMetric = styled('div', `
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 180px;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
@media ${mediaXSmall} {
|
||||||
|
& {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const cssProgressBarContainer = styled('div', `
|
||||||
|
width: 100%;
|
||||||
|
height: 4px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: ${colors.darkGrey};
|
||||||
|
`);
|
||||||
|
|
||||||
|
const cssProgressBarFill = styled(cssProgressBarContainer, `
|
||||||
|
background: ${colors.lightGreen};
|
||||||
|
|
||||||
|
&-approaching-limit {
|
||||||
|
background: ${colors.error};
|
||||||
|
}
|
||||||
|
`);
|
@ -11,13 +11,13 @@ import * as CodeEditorPanel from 'app/client/components/CodeEditorPanel';
|
|||||||
import * as commands from 'app/client/components/commands';
|
import * as commands from 'app/client/components/commands';
|
||||||
import {CursorPos} from 'app/client/components/Cursor';
|
import {CursorPos} from 'app/client/components/Cursor';
|
||||||
import {CursorMonitor, ViewCursorPos} from "app/client/components/CursorMonitor";
|
import {CursorMonitor, ViewCursorPos} from "app/client/components/CursorMonitor";
|
||||||
import {DataTables} from 'app/client/components/DataTables';
|
|
||||||
import {DocComm, DocUserAction} from 'app/client/components/DocComm';
|
import {DocComm, DocUserAction} from 'app/client/components/DocComm';
|
||||||
import * as DocConfigTab from 'app/client/components/DocConfigTab';
|
import * as DocConfigTab from 'app/client/components/DocConfigTab';
|
||||||
import {Drafts} from "app/client/components/Drafts";
|
import {Drafts} from "app/client/components/Drafts";
|
||||||
import {EditorMonitor} from "app/client/components/EditorMonitor";
|
import {EditorMonitor} from "app/client/components/EditorMonitor";
|
||||||
import * as GridView from 'app/client/components/GridView';
|
import * as GridView from 'app/client/components/GridView';
|
||||||
import {Importer} from 'app/client/components/Importer';
|
import {Importer} from 'app/client/components/Importer';
|
||||||
|
import {RawData} from 'app/client/components/RawData';
|
||||||
import {ActionGroupWithCursorPos, UndoStack} from 'app/client/components/UndoStack';
|
import {ActionGroupWithCursorPos, UndoStack} from 'app/client/components/UndoStack';
|
||||||
import {ViewLayout} from 'app/client/components/ViewLayout';
|
import {ViewLayout} from 'app/client/components/ViewLayout';
|
||||||
import {get as getBrowserGlobals} from 'app/client/lib/browserGlobals';
|
import {get as getBrowserGlobals} from 'app/client/lib/browserGlobals';
|
||||||
@ -382,7 +382,7 @@ export class GristDoc extends DisposableWithEvents {
|
|||||||
dom.domComputed<IDocPage>(this.activeViewId, (viewId) => (
|
dom.domComputed<IDocPage>(this.activeViewId, (viewId) => (
|
||||||
viewId === 'code' ? dom.create((owner) => owner.autoDispose(CodeEditorPanel.create(this))) :
|
viewId === 'code' ? dom.create((owner) => owner.autoDispose(CodeEditorPanel.create(this))) :
|
||||||
viewId === 'acl' ? dom.create((owner) => owner.autoDispose(AccessRules.create(this, this))) :
|
viewId === 'acl' ? dom.create((owner) => owner.autoDispose(AccessRules.create(this, this))) :
|
||||||
viewId === 'data' ? dom.create((owner) => owner.autoDispose(DataTables.create(this, this))) :
|
viewId === 'data' ? dom.create((owner) => owner.autoDispose(RawData.create(this, this))) :
|
||||||
viewId === 'GristDocTour' ? null :
|
viewId === 'GristDocTour' ? null :
|
||||||
dom.create((owner) => (this._viewLayout = ViewLayout.create(owner, this, viewId)))
|
dom.create((owner) => (this._viewLayout = ViewLayout.create(owner, this, viewId)))
|
||||||
)),
|
)),
|
||||||
|
136
app/client/components/RawData.ts
Normal file
136
app/client/components/RawData.ts
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import * as commands from 'app/client/components/commands';
|
||||||
|
import {DataTables} from 'app/client/components/DataTables';
|
||||||
|
import {DocumentUsage} from 'app/client/components/DocumentUsage';
|
||||||
|
import {GristDoc} from 'app/client/components/GristDoc';
|
||||||
|
import {printViewSection} from 'app/client/components/Printing';
|
||||||
|
import {buildViewSectionDom, ViewSectionHelper} from 'app/client/components/ViewLayout';
|
||||||
|
import {colors, mediaSmall, vars} from 'app/client/ui2018/cssVars';
|
||||||
|
import {icon} from 'app/client/ui2018/icons';
|
||||||
|
import {Disposable, dom, fromKo, makeTestId, styled} from 'grainjs';
|
||||||
|
import {reportError} from 'app/client/models/errors';
|
||||||
|
|
||||||
|
const testId = makeTestId('test-raw-data-');
|
||||||
|
|
||||||
|
export class RawData extends Disposable {
|
||||||
|
constructor(private _gristDoc: GristDoc) {
|
||||||
|
super();
|
||||||
|
const commandGroup = {
|
||||||
|
cancel: () => { this._close(); },
|
||||||
|
printSection: () => { printViewSection(null, this._gristDoc.viewModel.activeSection()).catch(reportError); },
|
||||||
|
};
|
||||||
|
this.autoDispose(commands.createGroup(commandGroup, this, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public buildDom() {
|
||||||
|
// Handler to close the lightbox.
|
||||||
|
const close = this._close.bind(this);
|
||||||
|
|
||||||
|
return cssContainer(
|
||||||
|
dom.create(DataTables, this._gristDoc),
|
||||||
|
dom.create(DocumentUsage, this._gristDoc.docPageModel),
|
||||||
|
/*************** Lightbox section **********/
|
||||||
|
dom.domComputedOwned(fromKo(this._gristDoc.viewModel.activeSection), (owner, viewSection) => {
|
||||||
|
if (!viewSection.getRowId()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ViewSectionHelper.create(owner, this._gristDoc, viewSection);
|
||||||
|
return cssOverlay(
|
||||||
|
testId('overlay'),
|
||||||
|
cssSectionWrapper(
|
||||||
|
buildViewSectionDom({
|
||||||
|
gristDoc: this._gristDoc,
|
||||||
|
sectionRowId: viewSection.getRowId(),
|
||||||
|
draggable: false,
|
||||||
|
focusable: false,
|
||||||
|
onRename: this._renameSection.bind(this)
|
||||||
|
})
|
||||||
|
),
|
||||||
|
cssCloseButton('CrossBig',
|
||||||
|
testId('close-button'),
|
||||||
|
dom.on('click', close)
|
||||||
|
),
|
||||||
|
// Close the lightbox when user clicks exactly on the overlay.
|
||||||
|
dom.on('click', (ev, elem) => void (ev.target === elem ? close() : null))
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _close() {
|
||||||
|
this._gristDoc.viewModel.activeSectionId(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _renameSection(name: string) {
|
||||||
|
// here we will rename primary page for active primary viewSection
|
||||||
|
const primaryViewName = this._gristDoc.viewModel.activeSection.peek().table.peek().primaryView.peek().name;
|
||||||
|
await primaryViewName.saveOnly(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cssContainer = styled('div', `
|
||||||
|
overflow-y: auto;
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
padding: 32px 64px 24px 64px;
|
||||||
|
@media ${mediaSmall} {
|
||||||
|
& {
|
||||||
|
padding: 32px 24px 24px 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const cssOverlay = styled('div', `
|
||||||
|
z-index: 10;
|
||||||
|
background-color: ${colors.backdrop};
|
||||||
|
inset: 0px;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
padding: 32px 56px 0px 56px;
|
||||||
|
position: absolute;
|
||||||
|
@media ${mediaSmall} {
|
||||||
|
& {
|
||||||
|
padding: 22px;
|
||||||
|
padding-top: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const cssSectionWrapper = styled('div', `
|
||||||
|
background: white;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 5px;
|
||||||
|
border-bottom-left-radius: 0px;
|
||||||
|
border-bottom-right-radius: 0px;
|
||||||
|
& .viewsection_content {
|
||||||
|
margin: 0px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
& .viewsection_title {
|
||||||
|
padding: 0px 12px;
|
||||||
|
}
|
||||||
|
& .filter_bar {
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const cssCloseButton = styled(icon, `
|
||||||
|
position: absolute;
|
||||||
|
top: 16px;
|
||||||
|
right: 16px;
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
--icon-color: ${vars.primaryBg};
|
||||||
|
&:hover {
|
||||||
|
--icon-color: ${colors.lighterGreen};
|
||||||
|
}
|
||||||
|
@media ${mediaSmall} {
|
||||||
|
& {
|
||||||
|
top: 6px;
|
||||||
|
right: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
@ -31,14 +31,6 @@ export function tools(owner: Disposable, gristDoc: GristDoc, leftPanelOpen: Obse
|
|||||||
return cssTools(
|
return cssTools(
|
||||||
cssTools.cls('-collapsed', (use) => !use(leftPanelOpen)),
|
cssTools.cls('-collapsed', (use) => !use(leftPanelOpen)),
|
||||||
cssSectionHeader("TOOLS"),
|
cssSectionHeader("TOOLS"),
|
||||||
|
|
||||||
// TODO proper UI
|
|
||||||
// cssPageEntry(
|
|
||||||
// dom.domComputed(docPageModel.rowCount, rowCount =>
|
|
||||||
// `${rowCount} of ${docPageModel.appModel.currentFeatures.baseMaxRowsPerDocument || "infinity"} rows used`
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
|
|
||||||
cssPageEntry(
|
cssPageEntry(
|
||||||
cssPageEntry.cls('-selected', (use) => use(gristDoc.activeViewId) === 'acl'),
|
cssPageEntry.cls('-selected', (use) => use(gristDoc.activeViewId) === 'acl'),
|
||||||
cssPageEntry.cls('-disabled', (use) => !use(canViewAccessRules)),
|
cssPageEntry.cls('-disabled', (use) => !use(canViewAccessRules)),
|
||||||
|
Loading…
Reference in New Issue
Block a user