2021-11-30 11:53:45 +00:00
|
|
|
import {Placement} from '@popperjs/core';
|
|
|
|
import {placements} from '@popperjs/core/lib/enums';
|
|
|
|
import {DocComm} from 'app/client/components/DocComm';
|
|
|
|
import {sameDocumentUrlState} from 'app/client/models/gristUrlState';
|
|
|
|
import {cssButtons, cssLinkBtn, cssLinkIcon} from 'app/client/ui/ExampleCard';
|
|
|
|
import {IOnBoardingMsg, startOnBoarding} from 'app/client/ui/OnBoardingPopups';
|
|
|
|
import {IconList, IconName} from 'app/client/ui2018/IconList';
|
|
|
|
import {DocData} from 'app/common/DocData';
|
|
|
|
import {dom} from 'grainjs';
|
|
|
|
import sortBy = require('lodash/sortBy');
|
2021-07-23 16:24:17 +00:00
|
|
|
|
|
|
|
|
2021-10-01 13:45:27 +00:00
|
|
|
export async function startDocTour(docData: DocData, docComm: DocComm, onFinishCB: () => void) {
|
|
|
|
const docTour: IOnBoardingMsg[] = await makeDocTour(docData, docComm) || invalidDocTour;
|
2021-07-28 18:20:14 +00:00
|
|
|
exposeDocTour(docTour);
|
2021-07-23 16:24:17 +00:00
|
|
|
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,
|
|
|
|
}];
|
|
|
|
|
2021-10-01 13:45:27 +00:00
|
|
|
async function makeDocTour(docData: DocData, docComm: DocComm): Promise<IOnBoardingMsg[] | null> {
|
2021-07-23 16:24:17 +00:00
|
|
|
const tableId = "GristDocTour";
|
|
|
|
if (!docData.getTable(tableId)) {
|
|
|
|
return null;
|
|
|
|
}
|
2021-10-01 13:45:27 +00:00
|
|
|
// 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();
|
2021-07-23 16:24:17 +00:00
|
|
|
await docData.fetchTable(tableId);
|
|
|
|
const tableData = docData.getTable(tableId)!;
|
2021-11-30 11:53:45 +00:00
|
|
|
const result = sortBy(tableData.getRowIds(), tableData.getRowPropFunc('manualSort') as any).map(rowId => {
|
2021-07-23 16:24:17 +00:00
|
|
|
function getValue(colId: string): string {
|
|
|
|
return String(tableData.getValue(rowId, colId) || "");
|
|
|
|
}
|
|
|
|
const title = getValue("Title");
|
2021-07-28 18:20:14 +00:00
|
|
|
let body: HTMLElement | string = getValue("Body");
|
|
|
|
const linkText = getValue("Link_Text");
|
|
|
|
const linkUrl = getValue("Link_URL");
|
|
|
|
const linkIcon = getValue("Link_Icon") as IconName;
|
2021-07-23 16:24:17 +00:00
|
|
|
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";
|
|
|
|
}
|
|
|
|
|
2021-07-28 18:20:14 +00:00
|
|
|
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'},
|
|
|
|
))
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-07-23 16:24:17 +00:00
|
|
|
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;
|
|
|
|
}
|
2021-07-28 18:20:14 +00:00
|
|
|
|
|
|
|
// 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
|
|
|
|
}));
|
|
|
|
}
|