gristlabs_grist-core/app/client/ui/DocTour.ts
Paul Fitzpatrick b3b7410ede (core) open documents without blocking on data engine
Summary:
With this diff, when a user opens a Grist document in a browser, they will be able to view its contents without waiting for the data engine to start up. Once the data engine starts, it will run a calculation and send any updates made. Changes to the document will be blocked until the engine is started and the initial calculation is complete.

The increase in responsiveness is useful in its own right, and also reduces the impact of an extra startup time in a candidate next-generation sandbox.

A small unrelated fix is included for `core/package.json`, to catch up with a recent change to `package.json`.

A small `./build schema` convenience is added to just rebuild the typescript schema file.

Test Plan: added test; existing tests pass - small fixes needed in some cases because of new timing

Reviewers: dsagal

Reviewed By: dsagal

Differential Revision: https://phab.getgrist.com/D3036
2021-10-01 10:18:56 -04:00

107 lines
3.4 KiB
TypeScript

import {DocComm} from "app/client/components/DocComm";
import {IOnBoardingMsg, startOnBoarding} from "app/client/ui/OnBoardingPopups";
import {DocData} from "../../common/DocData";
import * as _ from "lodash";
import {Placement} from "@popperjs/core";
import {placements} from "@popperjs/core/lib/enums";
import {sameDocumentUrlState} from "../models/gristUrlState";
import {dom} from "grainjs";
import {IconList, IconName} from "../ui2018/IconList";
import {cssButtons, cssLinkBtn, cssLinkIcon} from "./ExampleCard";
export async function startDocTour(docData: DocData, docComm: DocComm, onFinishCB: () => void) {
const docTour: IOnBoardingMsg[] = await makeDocTour(docData, docComm) || invalidDocTour;
exposeDocTour(docTour);
startOnBoarding(docTour, onFinishCB);
}
const invalidDocTour: IOnBoardingMsg[] = [{
title: 'No valid document tour',
body: 'Cannot construct a document tour from the data in this document. ' +
'Ensure there is a table named GristDocTour with columns Title, Body, Placement, and Location.',
selector: 'document',
showHasModal: true,
}];
async function makeDocTour(docData: DocData, docComm: DocComm): Promise<IOnBoardingMsg[] | null> {
const tableId = "GristDocTour";
if (!docData.getTable(tableId)) {
return null;
}
// Make sure any formulas in GristDocTour table have had time to evaluate. For example, for a
// first time open of a new document copy, any use of SELF_HYPERLINK will be stale since the URL
// of the document has changed.
await docComm.waitForInitialization();
await docData.fetchTable(tableId);
const tableData = docData.getTable(tableId)!;
const result = _.sortBy(tableData.getRowIds(), tableData.getRowPropFunc('manualSort') as any).map(rowId => {
function getValue(colId: string): string {
return String(tableData.getValue(rowId, colId) || "");
}
const title = getValue("Title");
let body: HTMLElement | string = getValue("Body");
const linkText = getValue("Link_Text");
const linkUrl = getValue("Link_URL");
const linkIcon = getValue("Link_Icon") as IconName;
const locationValue = getValue("Location");
let placement = getValue("Placement");
if (!(title || body)) {
return null;
}
const urlState = sameDocumentUrlState(locationValue);
if (!placements.includes(placement as Placement)) {
placement = "auto";
}
let validLinkUrl = true;
try {
new URL(linkUrl);
} catch {
validLinkUrl = false;
}
if (validLinkUrl && linkText) {
body = dom(
'div',
dom('p', body),
dom('p',
cssButtons(cssLinkBtn(
IconList.includes(linkIcon) ? cssLinkIcon(linkIcon) : null,
linkText,
{href: linkUrl, target: '_blank'},
))
),
);
}
return {
title,
body,
placement,
urlState,
selector: '.active_cursor',
// Center the popup if the user doesn't provide a link to a cell
showHasModal: !urlState?.hash
};
}).filter(x => x !== null) as IOnBoardingMsg[];
if (!result.length) {
return null;
}
return result;
}
// for easy testing
function exposeDocTour(docTour: IOnBoardingMsg[]) {
(window as any)._gristDocTour = () =>
docTour.map(msg => ({
...msg,
body: typeof msg.body === "string" ? msg.body
: (msg.body as HTMLElement)?.outerHTML
.replace(/_grain\d+_/g, "_grainXXX_"),
urlState: msg.urlState?.hash
}));
}