(core) support ?embed=true and &style=light for a clean embed experience

Summary:
This adds query parameters useful for tailoring the Grist experience, with an eye to embedding.

Setting `style=light` removes side and top bars, as a first pass at a focused view of a single document page (this would benefit from refining).

Setting `embed=true` has no significant effect just yet other than it restricts document access to viewer at most (this can be overridden by specifying `/m/default`).

Test Plan: added tests

Reviewers: dsagal

Reviewed By: dsagal

Differential Revision: https://phab.getgrist.com/D2585
This commit is contained in:
Paul Fitzpatrick
2020-08-14 12:40:39 -04:00
parent 48ca124f23
commit 20d8124f45
10 changed files with 415 additions and 4 deletions

View File

@@ -8,8 +8,9 @@ export type DocEntryTag = ''|'sample'|'invite'|'shared';
export const OpenDocMode = StringUnion(
'default', // open doc with user's maximal access level
'fork', // open doc limited to view access (if user has at least that level of access)
'view' // as for 'view', but suggest a fork on any attempt to edit
'view', // open doc limited to view access (if user has at least that level of access)
'fork', // as for 'view', but suggest a fork on any attempt to edit - the client will
// enable the editing UI experience and trigger a fork on any edit.
);
export type OpenDocMode = typeof OpenDocMode.type;

View File

@@ -1,6 +1,6 @@
import {BillingPage, BillingSubPage, BillingTask} from 'app/common/BillingAPI';
import {OpenDocMode} from 'app/common/DocListAPI';
import {encodeQueryParams} from 'app/common/gutil';
import {encodeQueryParams, isAffirmative} from 'app/common/gutil';
import {localhostRegex} from 'app/common/LoginState';
import {Document} from 'app/common/UserAPI';
import identity = require('lodash/identity');
@@ -17,6 +17,10 @@ export type IHomePage = typeof HomePage.type;
export const WelcomePage = StringUnion('user', 'teams');
export type WelcomePage = typeof WelcomePage.type;
// Overall UI style. "full" is normal, "light" is a single page focused, panels hidden experience.
export const InterfaceStyle = StringUnion('light', 'full');
export type InterfaceStyle = typeof InterfaceStyle.type;
// Default subdomain for home api service if not otherwise specified.
export const DEFAULT_HOME_SUBDOMAIN = 'api';
@@ -59,6 +63,8 @@ export interface IGristUrlState {
params?: {
billingPlan?: string;
billingTask?: BillingTask;
embed?: boolean;
style?: InterfaceStyle;
};
hash?: HashLink; // if present, this specifies an individual row within a section of a page.
}
@@ -240,6 +246,16 @@ export function decodeUrl(gristConfig: Partial<GristLoadConfig>, location: Locat
if (sp.has('billingTask')) {
state.params!.billingTask = parseBillingTask(sp.get('billingTask')!);
}
if (sp.has('style')) {
state.params!.style = parseInterfaceStyle(sp.get('style')!);
}
if (sp.has('embed')) {
const embed = state.params!.embed = isAffirmative(sp.get('embed'));
// Turn view mode on if no mode has been specified.
if (embed && !state.mode) { state.mode = 'view'; }
// Turn on light style if no style has been specified.
if (embed && !state.params!.style) { state.params!.style = 'light'; }
}
if (location.hash) {
const hash = location.hash;
const hashParts = hash.split('.');
@@ -301,6 +317,13 @@ function parseWelcomePage(p: string): WelcomePage {
return WelcomePage.guard(p) ? p : 'user';
}
/**
* Read interface style and make sure it is either valid or left undefined.
*/
function parseInterfaceStyle(t: string): InterfaceStyle|undefined {
return InterfaceStyle.guard(t) ? t : undefined;
}
/**
* Parses the URL like "foo.bar.baz" into the pair {org: "foo", base: ".bar.baz"}.
* Port is allowed and included into base.

View File

@@ -780,3 +780,12 @@ export async function isLongerThan(promise: Promise<any>, timeoutMsec: number):
]);
return isPending;
}
/**
* Returns true if the parameter, when rendered as a string, matches
* 1, on, or true (case insensitively). Useful for processing query
* parameters that may have been manually set.
*/
export function isAffirmative(parameter: any): boolean {
return ['1', 'on', 'true', 'yes'].includes(String(parameter).toLowerCase());
}