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' ;
2022-10-28 16:11:08 +00:00
import { makeT } from 'app/client/lib/localization' ;
2021-11-30 11:53:45 +00:00
import { sameDocumentUrlState } from 'app/client/models/gristUrlState' ;
import { cssButtons , cssLinkBtn , cssLinkIcon } from 'app/client/ui/ExampleCard' ;
import { IOnBoardingMsg , startOnBoarding } from 'app/client/ui/OnBoardingPopups' ;
2021-12-15 15:14:57 +00:00
import { isNarrowScreen } from 'app/client/ui2018/cssVars' ;
2021-11-30 11:53:45 +00:00
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
2022-10-28 16:11:08 +00:00
const t = makeT ( 'DocTour' ) ;
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 [ ] = [ {
2022-12-06 13:57:29 +00:00
title : t ( "No valid document tour" ) ,
body : t ( "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." ) ,
2021-07-23 16:24:17 +00:00
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-12-15 15:14:57 +00:00
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 ) ;
2021-12-15 15:14:57 +00:00
if ( isNarrowScreen ( ) || ! placements . includes ( placement as Placement ) ) {
2021-07-23 16:24:17 +00:00
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
} ) ) ;
}