From 51f7402297969d9aae74caca07590c8161f485f1 Mon Sep 17 00:00:00 2001 From: George Gevoian Date: Sun, 29 Oct 2023 23:21:28 -0400 Subject: [PATCH] (core) Show tooltips in other Grist flavors Summary: This enables tooltips in other Grist deployment types (e.g. grist-core). Previously, most of these tooltips were only enabled in the SaaS offering of Grist. Test Plan: Browser tests. Reviewers: jarek Reviewed By: jarek Subscribers: jarek Differential Revision: https://phab.getgrist.com/D4097 --- .../components/BehavioralPromptsManager.ts | 17 +- app/client/components/GristDoc.ts | 9 + app/client/ui/GristTooltips.ts | 26 +-- app/client/ui/LeftPanelCommon.ts | 6 +- app/client/ui/WelcomeCoachingCall.ts | 7 +- app/client/ui/WelcomeTour.ts | 171 +++++++++--------- 6 files changed, 128 insertions(+), 108 deletions(-) diff --git a/app/client/components/BehavioralPromptsManager.ts b/app/client/components/BehavioralPromptsManager.ts index 2842e9ca..11335ba2 100644 --- a/app/client/components/BehavioralPromptsManager.ts +++ b/app/client/components/BehavioralPromptsManager.ts @@ -74,20 +74,27 @@ export class BehavioralPromptsManager extends Disposable { return this._dismissedTips.get().has(prompt); } - public shouldShowTips() { - return !this._prefs.get().dontShowTips; - } - public shouldShowTip(prompt: BehavioralPrompt): boolean { if (this._isDisabled) { return false; } + // For non-SaaS flavors of Grist, don't show tips if the Help Center is explicitly + // disabled. A separate opt-out feature could be added down the road for more granularity, + // but will require communication in advance to avoid disrupting users. + const {deploymentType, features} = getGristConfig(); + if ( + !features?.includes('helpCenter') && + // This one is an easter egg, so we make an exception. + prompt !== 'rickRow' + ) { + return false; + } + const { showContext = 'desktop', showDeploymentTypes, forceShow = false, } = GristBehavioralPrompts[prompt]; - const {deploymentType} = getGristConfig(); if ( showDeploymentTypes !== '*' && (!deploymentType || !showDeploymentTypes.includes(deploymentType)) diff --git a/app/client/components/GristDoc.ts b/app/client/components/GristDoc.ts index a89cce6c..4d89ad6a 100644 --- a/app/client/components/GristDoc.ts +++ b/app/client/components/GristDoc.ts @@ -67,6 +67,7 @@ import {undef, waitObs} from 'app/common/gutil'; import {LocalPlugin} from "app/common/plugin"; import {StringUnion} from 'app/common/StringUnion'; import {TableData} from 'app/common/TableData'; +import {getGristConfig} from 'app/common/urlUtils'; import {DocStateComparison} from 'app/common/UserAPI'; import {AttachedCustomWidgets, IAttachedCustomWidget, IWidgetType} from 'app/common/widgetTypes'; import {CursorPos} from 'app/plugin/GristAPI'; @@ -1644,6 +1645,14 @@ export class GristDoc extends DisposableWithEvents { * a doc tutorial or tour isn't available. */ private _shouldAutoStartWelcomeTour(): boolean { + // For non-SaaS flavors of Grist, don't show the tour if the Help Center is explicitly + // disabled. A separate opt-out feature could be added down the road for more granularity, + // but will require communication in advance to avoid disrupting users. + const {features} = getGristConfig(); + if (!features?.includes('helpCenter')) { + return false; + } + // If a doc tutorial or tour are available, leave the welcome tour for another // doc (e.g. a new one). if (this._disableAutoStartingTours || this.docModel.isTutorial() || this.docModel.hasDocTour()) { diff --git a/app/client/ui/GristTooltips.ts b/app/client/ui/GristTooltips.ts index f2d3fa04..8812ae29 100644 --- a/app/client/ui/GristTooltips.ts +++ b/app/client/ui/GristTooltips.ts @@ -144,7 +144,7 @@ export const GristBehavioralPrompts: Record t('Reference Columns'), @@ -159,7 +159,7 @@ record in that table, but you may select which column from that record to show.' ), ...args, ), - showDeploymentTypes: ['saas'], + showDeploymentTypes: ['saas', 'core', 'enterprise', 'electron'], }, rawDataPage: { title: () => t('Raw Data page'), @@ -169,7 +169,7 @@ including summary tables and tables not included in page layouts.')), dom('div', cssLink({href: commonUrls.helpRawData, target: '_blank'}, t('Learn more.'))), ...args, ), - showDeploymentTypes: ['saas'], + showDeploymentTypes: ['saas', 'core', 'enterprise', 'electron'], }, accessRules: { title: () => t('Access Rules'), @@ -179,7 +179,7 @@ to determine who can see or edit which parts of your document.')), dom('div', cssLink({href: commonUrls.helpAccessRules, target: '_blank'}, t('Learn more.'))), ...args, ), - showDeploymentTypes: ['saas'], + showDeploymentTypes: ['saas', 'core', 'enterprise', 'electron'], }, filterButtons: { title: () => t('Pinning Filters'), @@ -189,7 +189,7 @@ to determine who can see or edit which parts of your document.')), dom('div', cssLink({href: commonUrls.helpFilterButtons, target: '_blank'}, t('Learn more.'))), ...args, ), - showDeploymentTypes: ['saas'], + showDeploymentTypes: ['saas', 'core', 'enterprise', 'electron'], }, nestedFiltering: { title: () => t('Nested Filtering'), @@ -198,7 +198,7 @@ to determine who can see or edit which parts of your document.')), dom('div', t('Only those rows will appear which match all of the filters.')), ...args, ), - showDeploymentTypes: ['saas'], + showDeploymentTypes: ['saas', 'core', 'enterprise', 'electron'], }, pageWidgetPicker: { title: () => t('Selecting Data'), @@ -207,7 +207,7 @@ to determine who can see or edit which parts of your document.')), dom('div', t('Use the 𝚺 icon to create summary (or pivot) tables, for totals or subtotals.')), ...args, ), - showDeploymentTypes: ['saas'], + showDeploymentTypes: ['saas', 'core', 'enterprise', 'electron'], }, pageWidgetPickerSelectBy: { title: () => t('Linking Widgets'), @@ -217,7 +217,7 @@ to determine who can see or edit which parts of your document.')), dom('div', cssLink({href: commonUrls.helpLinkingWidgets, target: '_blank'}, t('Learn more.'))), ...args, ), - showDeploymentTypes: ['saas'], + showDeploymentTypes: ['saas', 'core', 'enterprise', 'electron'], }, editCardLayout: { title: () => t('Editing Card Layout'), @@ -228,7 +228,7 @@ to determine who can see or edit which parts of your document.')), })), ...args, ), - showDeploymentTypes: ['saas'], + showDeploymentTypes: ['saas', 'core', 'enterprise', 'electron'], }, addNew: { title: () => t('Add New'), @@ -236,7 +236,7 @@ to determine who can see or edit which parts of your document.')), dom('div', t('Click the Add New button to create new documents or workspaces, or import data.')), ...args, ), - showDeploymentTypes: ['saas'], + showDeploymentTypes: ['saas', 'core', 'enterprise', 'electron'], }, rickRow: { title: () => t('Anchor Links'), @@ -261,13 +261,13 @@ to determine who can see or edit which parts of your document.')), content: (...args: DomElementArg[]) => cssTooltipContent( dom('div', t( - 'You can choose one of our pre-made widgets or embed your own by providing its full URL.' + 'You can choose from widgets available to you in the dropdown, or embed your own by providing its full URL.' ), ), dom('div', cssLink({href: commonUrls.helpCustomWidgets, target: '_blank'}, t('Learn more.'))), ...args, ), - showDeploymentTypes: ['saas'], + showDeploymentTypes: ['saas', 'core', 'enterprise', 'electron'], }, calendarConfig: { title: () => t('Calendar'), @@ -279,6 +279,6 @@ data.")), dom('div', cssLink({href: commonUrls.helpCalendarWidget, target: '_blank'}, t('Learn more.'))), ...args, ), - showDeploymentTypes: ['saas'], + showDeploymentTypes: ['saas', 'core', 'enterprise', 'electron'], }, }; diff --git a/app/client/ui/LeftPanelCommon.ts b/app/client/ui/LeftPanelCommon.ts index 1fb75f56..533fd9e8 100644 --- a/app/client/ui/LeftPanelCommon.ts +++ b/app/client/ui/LeftPanelCommon.ts @@ -19,6 +19,7 @@ import {AppModel} from 'app/client/models/AppModel'; import {testId, theme, vars} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {commonUrls, isFeatureEnabled} from 'app/common/gristUrls'; +import {getGristConfig} from 'app/common/urlUtils'; import {dom, DomContents, Observable, styled} from 'grainjs'; const t = makeT('LeftPanelCommon'); @@ -31,12 +32,15 @@ export function createHelpTools(appModel: AppModel): DomContents { if (!isFeatureEnabled("helpCenter")) { return []; } + const {deploymentType} = getGristConfig(); return cssSplitPageEntry( cssPageEntryMain( cssPageLink(cssPageIcon('Help'), cssLinkText(t("Help Center")), dom.cls('tour-help-center'), - dom.on('click', (ev) => beaconOpenMessage({appModel})), + deploymentType === 'saas' + ? dom.on('click', () => beaconOpenMessage({appModel})) + : {href: commonUrls.help, target: '_blank'}, testId('left-feedback'), ), ), diff --git a/app/client/ui/WelcomeCoachingCall.ts b/app/client/ui/WelcomeCoachingCall.ts index 1621a81e..51077907 100644 --- a/app/client/ui/WelcomeCoachingCall.ts +++ b/app/client/ui/WelcomeCoachingCall.ts @@ -13,12 +13,9 @@ export function shouldShowWelcomeCoachingCall(appModel: AppModel) { const {deploymentType} = getGristConfig(); if (deploymentType !== 'saas') { return false; } - const {behavioralPromptsManager, dismissedWelcomePopups} = appModel; - // Defer showing coaching call until Add New tip is dismissed. - const hasSeenAddNewTip = behavioralPromptsManager.hasSeenTip('addNew'); - const shouldShowTips = behavioralPromptsManager.shouldShowTips(); - if (!hasSeenAddNewTip && shouldShowTips) { return false; } + const {behavioralPromptsManager, dismissedWelcomePopups} = appModel; + if (behavioralPromptsManager.shouldShowTip('addNew')) { return false; } const popup = dismissedWelcomePopups.get().find(p => p.id === 'coachingCall'); return ( diff --git a/app/client/ui/WelcomeTour.ts b/app/client/ui/WelcomeTour.ts index 6cafb14d..40907348 100644 --- a/app/client/ui/WelcomeTour.ts +++ b/app/client/ui/WelcomeTour.ts @@ -6,98 +6,101 @@ import { ShortcutKey, ShortcutKeyContent } from 'app/client/ui/ShortcutKey'; import { theme } from 'app/client/ui2018/cssVars'; import { icon } from "app/client/ui2018/icons"; import { cssLink } from "app/client/ui2018/links"; +import { getGristConfig } from "app/common/urlUtils"; import { dom, styled } from "grainjs"; const t = makeT('WelcomeTour'); -export const WelcomeTour: IOnBoardingMsg[] = [ - { - title: t('Editing Data'), - body: () => [ - dom('p', - t('Double-click or hit {{enter}} on a cell to edit it. ', { - enter: ShortcutKey(ShortcutKeyContent(t('Enter'))), - }), - t('Start with {{equal}} to enter a formula.', { - equal: ShortcutKey(ShortcutKeyContent('=')), - })), - ], - selector: '.field_clip', - placement: 'bottom', - }, - { - selector: '.tour-creator-panel', - title: t('Configuring your document'), - body: () => [ - dom('p', - t('Toggle the {{creatorPanel}} to format columns, ', {creatorPanel: dom('em', t('creator panel'))}), - t('convert to card view, select data, and more.') - ) - ], - placement: 'left', - cropPadding: true, - }, - { - selector: '.tour-type-selector', - title: t('Customizing columns'), - body: () => [ - dom('p', - t('Set formatting options, formulas, or column types, such as dates, choices, or attachments. ')), - dom('p', - t('Make it relational! Use the {{ref}} type to link tables. ', { - ref: ShortcutKey(t('Reference')), +export function getOnBoardingMessages(): IOnBoardingMsg[] { + const {features} = getGristConfig(); + return [ + { + title: t('Editing Data'), + body: () => [ + dom('p', + t('Double-click or hit {{enter}} on a cell to edit it. ', { + enter: ShortcutKey(ShortcutKeyContent(t('Enter'))), + }), + t('Start with {{equal}} to enter a formula.', { + equal: ShortcutKey(ShortcutKeyContent('=')), })), - ], - placement: 'right', - }, - { - selector: '.tour-add-new', - title: t('Building up'), - body: () => [ - dom('p', t('Use {{addNew}} to add widgets, pages, or import more data. ', { - addNew: ShortcutKey(t('Add New')), - })), - ], - placement: 'right', - }, - { - selector: '.tour-share-icon', - title: t('Sharing'), - body: () => [ - dom('p', t('Use the Share button ({{share}}) to share the document or export data.', - {share: TopBarButtonIcon(t('Share'))})) - ], - placement: 'bottom', - cropPadding: true, - }, - { - selector: '.tour-help-center', - title: t('Flying higher'), - body: () => [ - dom('p', t('Use {{helpCenter}} for documentation or questions.', - {helpCenter: ShortcutKey(GreyIcon('Help'), t('Help Center'))})) - ], - placement: 'right', - }, - { - selector: '.tour-welcome', - title: t('Welcome to Grist!'), - body: () => [ - dom('p', t("Browse our {{templateLibrary}} to discover what's possible and get inspired.", - { - templateLibrary: cssLink({ target: '_blank', href: urlState().makeUrl({ homePage: "templates" }) }, - t('template library'), cssInlineIcon('FieldLink')) - } - )), - ], - showHasModal: true, - } - -]; + ], + selector: '.field_clip', + placement: 'bottom', + }, + { + selector: '.tour-creator-panel', + title: t('Configuring your document'), + body: () => [ + dom('p', + t('Toggle the {{creatorPanel}} to format columns, ', {creatorPanel: dom('em', t('creator panel'))}), + t('convert to card view, select data, and more.') + ) + ], + placement: 'left', + cropPadding: true, + }, + { + selector: '.tour-type-selector', + title: t('Customizing columns'), + body: () => [ + dom('p', + t('Set formatting options, formulas, or column types, such as dates, choices, or attachments. ')), + dom('p', + t('Make it relational! Use the {{ref}} type to link tables. ', { + ref: ShortcutKey(t('Reference')), + })), + ], + placement: 'right', + }, + { + selector: '.tour-add-new', + title: t('Building up'), + body: () => [ + dom('p', t('Use {{addNew}} to add widgets, pages, or import more data. ', { + addNew: ShortcutKey(t('Add New')), + })), + ], + placement: 'right', + }, + { + selector: '.tour-share-icon', + title: t('Sharing'), + body: () => [ + dom('p', t('Use the Share button ({{share}}) to share the document or export data.', + {share: TopBarButtonIcon(t('Share'))})) + ], + placement: 'bottom', + cropPadding: true, + }, + { + selector: '.tour-help-center', + title: t('Flying higher'), + body: () => [ + dom('p', t('Use {{helpCenter}} for documentation or questions.', + {helpCenter: ShortcutKey(GreyIcon('Help'), t('Help Center'))})) + ], + placement: 'right', + }, + ...(features?.includes('templates') && Boolean(getGristConfig().templateOrg) ? [{ + selector: '.tour-welcome', + title: t('Welcome to Grist!'), + body: () => [ + dom('p', t("Browse our {{templateLibrary}} to discover what's possible and get inspired.", + { + templateLibrary: cssLink({ target: '_blank', href: urlState().makeUrl({ homePage: "templates" }) }, + t('template library'), cssInlineIcon('FieldLink')) + } + )), + ], + showHasModal: true, + }] : []), + ]; +} export function startWelcomeTour(onFinishCB: () => void) { commands.allCommands.fieldTabOpen.run(); - startOnBoarding(WelcomeTour, onFinishCB); + startOnBoarding(getOnBoardingMessages(), onFinishCB); } const TopBarButtonIcon = styled(icon, `