From 927e92e3e88ef70bb46c1b8fcce499705b4ea453 Mon Sep 17 00:00:00 2001 From: George Gevoian Date: Mon, 18 Sep 2023 22:48:56 -0400 Subject: [PATCH] (core) Add tip for calendar widget configuration Summary: The tip is shown in the creator panel, in the subtab that lists the column mapping configuration for the calendar widget. The panel now automatically opens the first time a calendar widget is added to a page (via the Add New menu). Test Plan: Manual. Reviewers: JakubSerafin Reviewed By: JakubSerafin Subscribers: JakubSerafin, jarek Differential Revision: https://phab.getgrist.com/D4047 --- .../components/BehavioralPromptsManager.ts | 2 ++ app/client/components/GristDoc.ts | 23 +++++++++++++- app/client/ui/CustomSectionConfig.ts | 31 ++++++++++++++----- app/client/ui/GristTooltips.ts | 12 +++++++ app/common/Prefs.ts | 1 + app/common/gristUrls.ts | 1 + ...achedWidget.ts => AttachedCustomWidget.ts} | 4 +-- 7 files changed, 64 insertions(+), 10 deletions(-) rename test/nbrowser/{CustomAttachedWidget.ts => AttachedCustomWidget.ts} (97%) diff --git a/app/client/components/BehavioralPromptsManager.ts b/app/client/components/BehavioralPromptsManager.ts index 3cfa95b7..8627869d 100644 --- a/app/client/components/BehavioralPromptsManager.ts +++ b/app/client/components/BehavioralPromptsManager.ts @@ -21,6 +21,7 @@ export interface AttachOptions { showOnMobile?: boolean; popupOptions?: IPopupOptions; onDispose?(): void; + shouldShow?(): boolean; } interface QueuedTip { @@ -146,6 +147,7 @@ export class BehavioralPromptsManager extends Disposable { private _shouldQueueTip(prompt: BehavioralPrompt, options: AttachOptions) { if ( this._isDisabled || + options.shouldShow?.() === false || (isNarrowScreen() && !options.showOnMobile) || (this._prefs.get().dontShowTips && !options.forceShow) || this.hasSeenTip(prompt) diff --git a/app/client/components/GristDoc.ts b/app/client/components/GristDoc.ts index c7b535fa..fc93cabf 100644 --- a/app/client/components/GristDoc.ts +++ b/app/client/components/GristDoc.ts @@ -47,7 +47,7 @@ import {IPageWidget, toPageWidget} from 'app/client/ui/PageWidgetPicker'; import {linkFromId, selectBy} from 'app/client/ui/selectBy'; import {WebhookPage} from 'app/client/ui/WebhookPage'; import {startWelcomeTour} from 'app/client/ui/WelcomeTour'; -import {IWidgetType} from 'app/common/widgetTypes'; +import {AttachedCustomWidgets, IAttachedCustomWidget, IWidgetType} from 'app/common/widgetTypes'; import {PlayerState, YouTubePlayer} from 'app/client/ui/YouTubePlayer'; import {isNarrowScreen, mediaSmall, mediaXSmall, testId, theme} from 'app/client/ui2018/cssVars'; import {IconName} from 'app/client/ui2018/IconList'; @@ -837,6 +837,10 @@ export class GristDoc extends DisposableWithEvents { this.viewModel.activeSectionId(res.sectionRef); this._maybeShowEditCardLayoutTip(val.type).catch(reportError); + + if (AttachedCustomWidgets.guard(val.type)) { + this._handleNewAttachedCustomWidget(val.type).catch(reportError); + } } /** @@ -881,6 +885,10 @@ export class GristDoc extends DisposableWithEvents { this.viewModel.activeSectionId(result.sectionRef); this._maybeShowEditCardLayoutTip(val.type).catch(reportError); + + if (AttachedCustomWidgets.guard(val.type)) { + this._handleNewAttachedCustomWidget(val.type).catch(reportError); + } } } @@ -1438,6 +1446,19 @@ export class GristDoc extends DisposableWithEvents { }); } + private async _handleNewAttachedCustomWidget(widget: IAttachedCustomWidget) { + switch (widget) { + case 'custom.calendar': { + // Open the right panel to the calendar subtab. + commands.allCommands.viewTabOpen.run(); + + // Wait for the right panel to finish animation if it was collapsed before. + await commands.allCommands.rightPanelOpen.run(); + break; + } + } + } + private async _promptForName() { return await invokePrompt("Table name", "Create", '', "Default table name"); } diff --git a/app/client/ui/CustomSectionConfig.ts b/app/client/ui/CustomSectionConfig.ts index 840a3ad2..234e1743 100644 --- a/app/client/ui/CustomSectionConfig.ts +++ b/app/client/ui/CustomSectionConfig.ts @@ -233,7 +233,7 @@ class ColumnListPicker extends Disposable { class CustomSectionConfigurationConfig extends Disposable{ // Does widget has custom configuration. private readonly _hasConfiguration: Computed; - constructor(private _section: ViewSectionRec) { + constructor(private _section: ViewSectionRec, private _gristDoc: GristDoc) { super(); this._hasConfiguration = Computed.create(this, use => use(_section.hasCustomOptions)); } @@ -268,11 +268,12 @@ class CustomSectionConfigurationConfig extends Disposable{ value: createObs(column), column })); - return [ + return dom('div', + this._attachColumnMappingTip(this._section.customDef.url()), ...mappings.map(m => m.column.allowMultiple ? dom.create(ColumnListPicker, m.value, m.column, this._section) - : dom.create(ColumnPicker, m.value, m.column, this._section)) - ]; + : dom.create(ColumnPicker, m.value, m.column, this._section)), + ); }) ); } @@ -280,7 +281,19 @@ class CustomSectionConfigurationConfig extends Disposable{ allCommands.openWidgetConfiguration.run(); } - + private _attachColumnMappingTip(widgetUrl: string | null) { + switch (widgetUrl) { + // TODO: come up with a way to attach tips without hardcoding widget URLs. + case 'https://gristlabs.github.io/grist-widget/calendar/index.html': { + return this._gristDoc.behavioralPromptsManager.attachTip('calendarConfig', { + popupOptions: {placement: 'left-start'}, + }); + } + default: { + return null; + } + } + } } export class CustomSectionConfig extends Disposable { @@ -305,7 +318,7 @@ export class CustomSectionConfig extends Disposable { constructor(protected _section: ViewSectionRec, private _gristDoc: GristDoc) { super(); - this._customSectionConfigurationConfig = new CustomSectionConfigurationConfig(_section); + this._customSectionConfigurationConfig = new CustomSectionConfigurationConfig(_section, _gristDoc); // Test if we can offer widget list. const gristConfig: GristLoadConfig = (window as any).gristConfig || {}; @@ -467,7 +480,11 @@ export class CustomSectionConfig extends Disposable { this._gristDoc.behavioralPromptsManager.attachTip('customURL', { popupOptions: { placement: 'left-start', - } + }, + shouldShow: () => { + // Only show tip if a custom widget isn't already selected. + return !this._selectedId.get() || (isCustom.get() && this._url.get().trim() === ''); + }, }) ), ]), diff --git a/app/client/ui/GristTooltips.ts b/app/client/ui/GristTooltips.ts index 6c997f95..ddfff8ef 100644 --- a/app/client/ui/GristTooltips.ts +++ b/app/client/ui/GristTooltips.ts @@ -240,4 +240,16 @@ to determine who can see or edit which parts of your document.')), ), deploymentTypes: ['saas'], }, + calendarConfig: { + title: () => t('Calendar'), + content: (...args: DomElementArg[]) => cssTooltipContent( + dom('div', t("To configure your calendar, select columns for start/end dates and event titles. \ +Note each column's type.")), + dom('div', t("Can't find the right columns? Click 'Change Widget' to select the table with events \ +data.")), + dom('div', cssLink({href: commonUrls.helpCalendarWidget, target: '_blank'}, t('Learn more.'))), + ...args, + ), + deploymentTypes: ['saas'], + }, }; diff --git a/app/common/Prefs.ts b/app/common/Prefs.ts index 2af133fa..7d153ba8 100644 --- a/app/common/Prefs.ts +++ b/app/common/Prefs.ts @@ -87,6 +87,7 @@ export const BehavioralPrompt = StringUnion( 'addNew', 'rickRow', 'customURL', + 'calendarConfig', ); export type BehavioralPrompt = typeof BehavioralPrompt.type; diff --git a/app/common/gristUrls.ts b/app/common/gristUrls.ts index 472232cb..053f53a1 100644 --- a/app/common/gristUrls.ts +++ b/app/common/gristUrls.ts @@ -79,6 +79,7 @@ export const commonUrls = { helpTryingOutChanges: "https://support.getgrist.com/copying-docs/#trying-out-changes", helpCustomWidgets: "https://support.getgrist.com/widget-custom", helpTelemetryLimited: "https://support.getgrist.com/telemetry-limited", + helpCalendarWidget: "https://support.getgrist.com/widget-calendar", plans: "https://www.getgrist.com/pricing", sproutsProgram: "https://www.getgrist.com/sprouts-program", contact: "https://www.getgrist.com/contact", diff --git a/test/nbrowser/CustomAttachedWidget.ts b/test/nbrowser/AttachedCustomWidget.ts similarity index 97% rename from test/nbrowser/CustomAttachedWidget.ts rename to test/nbrowser/AttachedCustomWidget.ts index 98043770..2be96d50 100644 --- a/test/nbrowser/CustomAttachedWidget.ts +++ b/test/nbrowser/AttachedCustomWidget.ts @@ -7,7 +7,7 @@ import {server, setupTestSuite} from "test/nbrowser/testUtils"; import {serveSomething} from "test/server/customUtil"; import {EnvironmentSnapshot} from "test/server/testUtils"; -describe('attachedCustomWidget NotepadWidget', function () { +describe('AttachedCustomWidget', function () { this.timeout(20000); const cleanup = setupTestSuite(); let oldEnv: EnvironmentSnapshot; @@ -84,7 +84,7 @@ describe('attachedCustomWidget NotepadWidget', function () { it('should not ask for permission', async () => { await gu.addNewSection(/Calendar/, /Table1/, {selectBy: /TABLE1/}); await gu.getSection('TABLE1 Calendar').click(); - await gu.toggleSidePanel('right', 'open'); + await gu.waitForSidePanel(); await driver.find('.test-right-tab-pagewidget').click(); await gu.waitForServer();