(core) implement a safe mode for opening documents with rule problems

Summary:
Adds an "enter safe mode" option and explanation in modal that appears when a document fails to load, if user is owner. If "enter safe mode" is selected, document is reloaded on server in a special mode. Currently, the only difference is that if the acl rules fail to load, they are replaced with a fallback that grants full access to owners and no access to anyone else. An extra tag is shown to mark the document as safe mode, with an "x" for cancelling safe mode.

There are other ways a document could fail to load than just acl rules, so this is just a start.

Test Plan: added test

Reviewers: dsagal

Reviewed By: dsagal

Differential Revision: https://phab.getgrist.com/D2686
This commit is contained in:
Paul Fitzpatrick
2020-12-14 12:42:09 -05:00
parent 02ed4c59a0
commit 3b3ae87ade
14 changed files with 149 additions and 15 deletions

View File

@@ -59,6 +59,14 @@ const cssTag = styled('span', `
margin-left: 4px;
`);
const cssAlertTag = styled(cssTag, `
background-color: ${colors.error};
--icon-color: white;
a {
cursor: pointer;
}
`);
interface PartialWorkspace {
id: number;
name: string;
@@ -76,10 +84,12 @@ export function docBreadcrumbs(
options: {
docNameSave: (val: string) => Promise<void>,
pageNameSave: (val: string) => Promise<void>,
cancelRecoveryMode: () => Promise<void>,
isDocNameReadOnly?: BindableValue<boolean>,
isPageNameReadOnly?: BindableValue<boolean>,
isFork: Observable<boolean>,
isFiddle: Observable<boolean>,
isRecoveryMode: Observable<boolean>,
isSnapshot?: Observable<boolean>,
isPublic?: Observable<boolean>,
}
@@ -106,6 +116,13 @@ export function docBreadcrumbs(
if (use(options.isFork)) {
return cssTag('unsaved', testId('unsaved-tag'));
}
if (use(options.isRecoveryMode)) {
return cssAlertTag('recovery mode',
dom('a', dom.on('click', async () => {
await options.cancelRecoveryMode()
}), icon('CrossSmall')),
testId('recovery-mode-tag'));
}
if (use(options.isFiddle)) {
return cssTag('fiddle', tooltip({title: fiddleExplanation}), testId('fiddle-tag'));
}

View File

@@ -4,7 +4,7 @@ import {reportError} from 'app/client/models/errors';
import {bigBasicButton, bigPrimaryButton, cssButton} from 'app/client/ui2018/buttons';
import {colors, testId, vars} from 'app/client/ui2018/cssVars';
import {loadingSpinner} from 'app/client/ui2018/loaders';
import {Computed, dom, DomElementArg, MultiHolder, Observable, styled} from 'grainjs';
import {Computed, dom, DomContents, DomElementArg, MultiHolder, Observable, styled} from 'grainjs';
export interface IModalControl {
close(): void;
@@ -90,6 +90,7 @@ export interface ISaveModalOptions {
hideCancel?: boolean; // If set, hide the Cancel button
width?: ModalWidth; // Set a width style for the dialog.
modalArgs?: DomElementArg; // Extra args to apply to the outer cssModalDialog element.
extraButtons?: DomContents; // More buttons!
}
/**
@@ -160,6 +161,7 @@ export function saveModal(createFunc: (ctl: IModalControl, owner: MultiHolder) =
dom.on('click', save),
testId('modal-confirm'),
),
options.extraButtons,
options.hideCancel ? null : bigBasicButton('Cancel',
dom.on('click', () => ctl.close()),
testId('modal-cancel'),
@@ -182,7 +184,7 @@ export function confirmModal(
btnText: string,
onConfirm: () => Promise<void>,
explanation?: Element|string,
{hideCancel}: {hideCancel?: boolean} = {},
{hideCancel, extraButtons}: {hideCancel?: boolean, extraButtons?: DomContents} = {},
): void {
return saveModal((ctl, owner): ISaveModalOptions => ({
title,
@@ -191,6 +193,7 @@ export function confirmModal(
saveFunc: onConfirm,
hideCancel,
width: 'normal',
extraButtons,
}));
}