mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Fix calendar and card tip bug on mobile
Summary: On mobile, tips for calendar and card list aren't currently shown, but the creator panel was still automatically being opened in preparation for showing the tip. Test Plan: Manual and existing tests. Reviewers: jarek Reviewed By: jarek Subscribers: jarek Differential Revision: https://phab.getgrist.com/D4053
This commit is contained in:
parent
82c95ec074
commit
e033889b6a
@ -8,26 +8,32 @@ import {getGristConfig} from 'app/common/urlUtils';
|
|||||||
import {Computed, Disposable, dom, Observable} from 'grainjs';
|
import {Computed, Disposable, dom, Observable} from 'grainjs';
|
||||||
import {IPopupOptions} from 'popweasel';
|
import {IPopupOptions} from 'popweasel';
|
||||||
|
|
||||||
export interface AttachOptions {
|
/**
|
||||||
/** Defaults to false. */
|
* Options for showing a tip.
|
||||||
forceShow?: boolean;
|
*/
|
||||||
/** Defaults to false. */
|
export interface ShowTipOptions {
|
||||||
|
/** Defaults to `false`. */
|
||||||
hideArrow?: boolean;
|
hideArrow?: boolean;
|
||||||
/** Defaults to false. */
|
|
||||||
hideDontShowTips?: boolean;
|
|
||||||
/** Defaults to true. */
|
|
||||||
markAsSeen?: boolean;
|
|
||||||
/** Defaults to false. */
|
|
||||||
showOnMobile?: boolean;
|
|
||||||
popupOptions?: IPopupOptions;
|
popupOptions?: IPopupOptions;
|
||||||
onDispose?(): void;
|
onDispose?(): void;
|
||||||
shouldShow?(): boolean;
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for attaching a tip to a DOM element.
|
||||||
|
*/
|
||||||
|
export interface AttachTipOptions extends ShowTipOptions {
|
||||||
|
/**
|
||||||
|
* Optional callback that should return true if the tip should be disabled.
|
||||||
|
*
|
||||||
|
* If omitted, the tip is enabled.
|
||||||
|
*/
|
||||||
|
isDisabled?(): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface QueuedTip {
|
interface QueuedTip {
|
||||||
prompt: BehavioralPrompt;
|
prompt: BehavioralPrompt;
|
||||||
refElement: Element;
|
refElement: Element;
|
||||||
options: AttachOptions;
|
options: ShowTipOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -52,12 +58,14 @@ export class BehavioralPromptsManager extends Disposable {
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
public showTip(refElement: Element, prompt: BehavioralPrompt, options: AttachOptions = {}) {
|
public showTip(refElement: Element, prompt: BehavioralPrompt, options: ShowTipOptions = {}) {
|
||||||
this._queueTip(refElement, prompt, options);
|
this._queueTip(refElement, prompt, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public attachTip(prompt: BehavioralPrompt, options: AttachOptions = {}) {
|
public attachTip(prompt: BehavioralPrompt, options: AttachTipOptions = {}) {
|
||||||
return (element: Element) => {
|
return (element: Element) => {
|
||||||
|
if (options.isDisabled?.()) { return; }
|
||||||
|
|
||||||
this._queueTip(element, prompt, options);
|
this._queueTip(element, prompt, options);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -70,6 +78,29 @@ export class BehavioralPromptsManager extends Disposable {
|
|||||||
return !this._prefs.get().dontShowTips;
|
return !this._prefs.get().dontShowTips;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public shouldShowTip(prompt: BehavioralPrompt): boolean {
|
||||||
|
if (this._isDisabled) { return false; }
|
||||||
|
|
||||||
|
const {
|
||||||
|
showContext = 'desktop',
|
||||||
|
showDeploymentTypes,
|
||||||
|
forceShow = false,
|
||||||
|
} = GristBehavioralPrompts[prompt];
|
||||||
|
|
||||||
|
const {deploymentType} = getGristConfig();
|
||||||
|
if (
|
||||||
|
showDeploymentTypes !== '*' &&
|
||||||
|
(!deploymentType || !showDeploymentTypes.includes(deploymentType))
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const context = isNarrowScreen() ? 'mobile' : 'desktop';
|
||||||
|
if (showContext !== '*' && showContext !== context) { return false; }
|
||||||
|
|
||||||
|
return forceShow || (!this._prefs.get().dontShowTips && !this.hasSeenTip(prompt));
|
||||||
|
}
|
||||||
|
|
||||||
public enable() {
|
public enable() {
|
||||||
this._isDisabled = false;
|
this._isDisabled = false;
|
||||||
}
|
}
|
||||||
@ -83,8 +114,8 @@ export class BehavioralPromptsManager extends Disposable {
|
|||||||
this.enable();
|
this.enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _queueTip(refElement: Element, prompt: BehavioralPrompt, options: AttachOptions) {
|
private _queueTip(refElement: Element, prompt: BehavioralPrompt, options: ShowTipOptions) {
|
||||||
if (!this._shouldQueueTip(prompt, options)) { return; }
|
if (!this.shouldShowTip(prompt)) { return; }
|
||||||
|
|
||||||
this._queuedTips.push({prompt, refElement, options});
|
this._queuedTips.push({prompt, refElement, options});
|
||||||
if (this._queuedTips.length > 1) {
|
if (this._queuedTips.length > 1) {
|
||||||
@ -96,15 +127,15 @@ export class BehavioralPromptsManager extends Disposable {
|
|||||||
this._showTip(refElement, prompt, options);
|
this._showTip(refElement, prompt, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _showTip(refElement: Element, prompt: BehavioralPrompt, options: AttachOptions) {
|
private _showTip(refElement: Element, prompt: BehavioralPrompt, options: ShowTipOptions) {
|
||||||
const close = () => {
|
const close = () => {
|
||||||
if (!ctl.isDisposed()) {
|
if (!ctl.isDisposed()) {
|
||||||
ctl.close();
|
ctl.close();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const {hideArrow, hideDontShowTips, markAsSeen = true, onDispose, popupOptions} = options;
|
const {hideArrow, onDispose, popupOptions} = options;
|
||||||
const {title, content} = GristBehavioralPrompts[prompt];
|
const {title, content, hideDontShowTips = false, markAsSeen = true} = GristBehavioralPrompts[prompt];
|
||||||
const ctl = showBehavioralPrompt(refElement, title(), content(), {
|
const ctl = showBehavioralPrompt(refElement, title(), content(), {
|
||||||
onClose: (dontShowTips) => {
|
onClose: (dontShowTips) => {
|
||||||
if (dontShowTips) { this._dontShowTips(); }
|
if (dontShowTips) { this._dontShowTips(); }
|
||||||
@ -143,27 +174,4 @@ export class BehavioralPromptsManager extends Disposable {
|
|||||||
this._prefs.set({...this._prefs.get(), dontShowTips: true});
|
this._prefs.set({...this._prefs.get(), dontShowTips: true});
|
||||||
this._queuedTips = [];
|
this._queuedTips = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
private _shouldQueueTip(prompt: BehavioralPrompt, options: AttachOptions) {
|
|
||||||
if (
|
|
||||||
this._isDisabled ||
|
|
||||||
options.shouldShow?.() === false ||
|
|
||||||
(isNarrowScreen() && !options.showOnMobile) ||
|
|
||||||
(this._prefs.get().dontShowTips && !options.forceShow) ||
|
|
||||||
this.hasSeenTip(prompt)
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const {deploymentType} = getGristConfig();
|
|
||||||
const {deploymentTypes} = GristBehavioralPrompts[prompt];
|
|
||||||
if (
|
|
||||||
deploymentTypes !== '*' &&
|
|
||||||
(!deploymentType || !deploymentTypes.includes(deploymentType))
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -336,10 +336,6 @@ export class GristDoc extends DisposableWithEvents {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.behavioralPromptsManager.showTip(cursor, 'rickRow', {
|
this.behavioralPromptsManager.showTip(cursor, 'rickRow', {
|
||||||
forceShow: true,
|
|
||||||
hideDontShowTips: true,
|
|
||||||
markAsSeen: false,
|
|
||||||
showOnMobile: true,
|
|
||||||
onDispose: () => this.playRickRollVideo(),
|
onDispose: () => this.playRickRollVideo(),
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@ -1422,8 +1418,8 @@ export class GristDoc extends DisposableWithEvents {
|
|||||||
if (
|
if (
|
||||||
// Don't show the tip if a non-card widget was selected.
|
// Don't show the tip if a non-card widget was selected.
|
||||||
!['single', 'detail'].includes(selectedWidgetType) ||
|
!['single', 'detail'].includes(selectedWidgetType) ||
|
||||||
// Or if we've already seen it.
|
// Or if we shouldn't see the tip.
|
||||||
this.behavioralPromptsManager.hasSeenTip('editCardLayout')
|
!this.behavioralPromptsManager.shouldShowTip('editCardLayout')
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1449,11 +1445,13 @@ export class GristDoc extends DisposableWithEvents {
|
|||||||
private async _handleNewAttachedCustomWidget(widget: IAttachedCustomWidget) {
|
private async _handleNewAttachedCustomWidget(widget: IAttachedCustomWidget) {
|
||||||
switch (widget) {
|
switch (widget) {
|
||||||
case 'custom.calendar': {
|
case 'custom.calendar': {
|
||||||
|
if (this.behavioralPromptsManager.shouldShowTip('calendarConfig')) {
|
||||||
// Open the right panel to the calendar subtab.
|
// Open the right panel to the calendar subtab.
|
||||||
commands.allCommands.viewTabOpen.run();
|
commands.allCommands.viewTabOpen.run();
|
||||||
|
|
||||||
// Wait for the right panel to finish animation if it was collapsed before.
|
// Wait for the right panel to finish animation if it was collapsed before.
|
||||||
await commands.allCommands.rightPanelOpen.run();
|
await commands.allCommands.rightPanelOpen.run();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -481,9 +481,9 @@ export class CustomSectionConfig extends Disposable {
|
|||||||
popupOptions: {
|
popupOptions: {
|
||||||
placement: 'left-start',
|
placement: 'left-start',
|
||||||
},
|
},
|
||||||
shouldShow: () => {
|
isDisabled: () => {
|
||||||
// Only show tip if a custom widget isn't already selected.
|
// Disable tip if a custom widget is already selected.
|
||||||
return !this._selectedId.get() || (isCustom.get() && this._url.get().trim() === '');
|
return Boolean(this._selectedId.get() && !(isCustom.get() && this._url.get().trim() === ''));
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
@ -103,7 +103,15 @@ see or edit which parts of your document.')
|
|||||||
export interface BehavioralPromptContent {
|
export interface BehavioralPromptContent {
|
||||||
title: () => string;
|
title: () => string;
|
||||||
content: (...domArgs: DomElementArg[]) => DomContents;
|
content: (...domArgs: DomElementArg[]) => DomContents;
|
||||||
deploymentTypes: GristDeploymentType[] | '*';
|
showDeploymentTypes: GristDeploymentType[] | '*';
|
||||||
|
/** Defaults to `desktop`. */
|
||||||
|
showContext?: 'mobile' | 'desktop' | '*';
|
||||||
|
/** Defaults to `false`. */
|
||||||
|
hideDontShowTips?: boolean;
|
||||||
|
/** Defaults to `false`. */
|
||||||
|
forceShow?: boolean;
|
||||||
|
/** Defaults to `true`. */
|
||||||
|
markAsSeen?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GristBehavioralPrompts: Record<BehavioralPrompt, BehavioralPromptContent> = {
|
export const GristBehavioralPrompts: Record<BehavioralPrompt, BehavioralPromptContent> = {
|
||||||
@ -119,7 +127,7 @@ export const GristBehavioralPrompts: Record<BehavioralPrompt, BehavioralPromptCo
|
|||||||
),
|
),
|
||||||
...args,
|
...args,
|
||||||
),
|
),
|
||||||
deploymentTypes: ['saas'],
|
showDeploymentTypes: ['saas'],
|
||||||
},
|
},
|
||||||
referenceColumnsConfig: {
|
referenceColumnsConfig: {
|
||||||
title: () => t('Reference Columns'),
|
title: () => t('Reference Columns'),
|
||||||
@ -134,7 +142,7 @@ record in that table, but you may select which column from that record to show.'
|
|||||||
),
|
),
|
||||||
...args,
|
...args,
|
||||||
),
|
),
|
||||||
deploymentTypes: ['saas'],
|
showDeploymentTypes: ['saas'],
|
||||||
},
|
},
|
||||||
rawDataPage: {
|
rawDataPage: {
|
||||||
title: () => t('Raw Data page'),
|
title: () => t('Raw Data page'),
|
||||||
@ -144,7 +152,7 @@ including summary tables and tables not included in page layouts.')),
|
|||||||
dom('div', cssLink({href: commonUrls.helpRawData, target: '_blank'}, t('Learn more.'))),
|
dom('div', cssLink({href: commonUrls.helpRawData, target: '_blank'}, t('Learn more.'))),
|
||||||
...args,
|
...args,
|
||||||
),
|
),
|
||||||
deploymentTypes: ['saas'],
|
showDeploymentTypes: ['saas'],
|
||||||
},
|
},
|
||||||
accessRules: {
|
accessRules: {
|
||||||
title: () => t('Access Rules'),
|
title: () => t('Access Rules'),
|
||||||
@ -154,7 +162,7 @@ to determine who can see or edit which parts of your document.')),
|
|||||||
dom('div', cssLink({href: commonUrls.helpAccessRules, target: '_blank'}, t('Learn more.'))),
|
dom('div', cssLink({href: commonUrls.helpAccessRules, target: '_blank'}, t('Learn more.'))),
|
||||||
...args,
|
...args,
|
||||||
),
|
),
|
||||||
deploymentTypes: ['saas'],
|
showDeploymentTypes: ['saas'],
|
||||||
},
|
},
|
||||||
filterButtons: {
|
filterButtons: {
|
||||||
title: () => t('Pinning Filters'),
|
title: () => t('Pinning Filters'),
|
||||||
@ -164,7 +172,7 @@ to determine who can see or edit which parts of your document.')),
|
|||||||
dom('div', cssLink({href: commonUrls.helpFilterButtons, target: '_blank'}, t('Learn more.'))),
|
dom('div', cssLink({href: commonUrls.helpFilterButtons, target: '_blank'}, t('Learn more.'))),
|
||||||
...args,
|
...args,
|
||||||
),
|
),
|
||||||
deploymentTypes: ['saas'],
|
showDeploymentTypes: ['saas'],
|
||||||
},
|
},
|
||||||
nestedFiltering: {
|
nestedFiltering: {
|
||||||
title: () => t('Nested Filtering'),
|
title: () => t('Nested Filtering'),
|
||||||
@ -173,7 +181,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.')),
|
dom('div', t('Only those rows will appear which match all of the filters.')),
|
||||||
...args,
|
...args,
|
||||||
),
|
),
|
||||||
deploymentTypes: ['saas'],
|
showDeploymentTypes: ['saas'],
|
||||||
},
|
},
|
||||||
pageWidgetPicker: {
|
pageWidgetPicker: {
|
||||||
title: () => t('Selecting Data'),
|
title: () => t('Selecting Data'),
|
||||||
@ -182,7 +190,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.')),
|
dom('div', t('Use the 𝚺 icon to create summary (or pivot) tables, for totals or subtotals.')),
|
||||||
...args,
|
...args,
|
||||||
),
|
),
|
||||||
deploymentTypes: ['saas'],
|
showDeploymentTypes: ['saas'],
|
||||||
},
|
},
|
||||||
pageWidgetPickerSelectBy: {
|
pageWidgetPickerSelectBy: {
|
||||||
title: () => t('Linking Widgets'),
|
title: () => t('Linking Widgets'),
|
||||||
@ -192,7 +200,7 @@ to determine who can see or edit which parts of your document.')),
|
|||||||
dom('div', cssLink({href: commonUrls.helpLinkingWidgets, target: '_blank'}, t('Learn more.'))),
|
dom('div', cssLink({href: commonUrls.helpLinkingWidgets, target: '_blank'}, t('Learn more.'))),
|
||||||
...args,
|
...args,
|
||||||
),
|
),
|
||||||
deploymentTypes: ['saas'],
|
showDeploymentTypes: ['saas'],
|
||||||
},
|
},
|
||||||
editCardLayout: {
|
editCardLayout: {
|
||||||
title: () => t('Editing Card Layout'),
|
title: () => t('Editing Card Layout'),
|
||||||
@ -203,7 +211,7 @@ to determine who can see or edit which parts of your document.')),
|
|||||||
})),
|
})),
|
||||||
...args,
|
...args,
|
||||||
),
|
),
|
||||||
deploymentTypes: ['saas'],
|
showDeploymentTypes: ['saas'],
|
||||||
},
|
},
|
||||||
addNew: {
|
addNew: {
|
||||||
title: () => t('Add New'),
|
title: () => t('Add New'),
|
||||||
@ -211,7 +219,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.')),
|
dom('div', t('Click the Add New button to create new documents or workspaces, or import data.')),
|
||||||
...args,
|
...args,
|
||||||
),
|
),
|
||||||
deploymentTypes: ['saas'],
|
showDeploymentTypes: ['saas'],
|
||||||
},
|
},
|
||||||
rickRow: {
|
rickRow: {
|
||||||
title: () => t('Anchor Links'),
|
title: () => t('Anchor Links'),
|
||||||
@ -225,7 +233,11 @@ to determine who can see or edit which parts of your document.')),
|
|||||||
),
|
),
|
||||||
...args,
|
...args,
|
||||||
),
|
),
|
||||||
deploymentTypes: '*',
|
showDeploymentTypes: '*',
|
||||||
|
showContext: '*',
|
||||||
|
hideDontShowTips: true,
|
||||||
|
forceShow: true,
|
||||||
|
markAsSeen: false,
|
||||||
},
|
},
|
||||||
customURL: {
|
customURL: {
|
||||||
title: () => t('Custom Widgets'),
|
title: () => t('Custom Widgets'),
|
||||||
@ -238,7 +250,7 @@ to determine who can see or edit which parts of your document.')),
|
|||||||
dom('div', cssLink({href: commonUrls.helpCustomWidgets, target: '_blank'}, t('Learn more.'))),
|
dom('div', cssLink({href: commonUrls.helpCustomWidgets, target: '_blank'}, t('Learn more.'))),
|
||||||
...args,
|
...args,
|
||||||
),
|
),
|
||||||
deploymentTypes: ['saas'],
|
showDeploymentTypes: ['saas'],
|
||||||
},
|
},
|
||||||
calendarConfig: {
|
calendarConfig: {
|
||||||
title: () => t('Calendar'),
|
title: () => t('Calendar'),
|
||||||
@ -250,6 +262,6 @@ data.")),
|
|||||||
dom('div', cssLink({href: commonUrls.helpCalendarWidget, target: '_blank'}, t('Learn more.'))),
|
dom('div', cssLink({href: commonUrls.helpCalendarWidget, target: '_blank'}, t('Learn more.'))),
|
||||||
...args,
|
...args,
|
||||||
),
|
),
|
||||||
deploymentTypes: ['saas'],
|
showDeploymentTypes: ['saas'],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -84,7 +84,7 @@ describe('AttachedCustomWidget', function () {
|
|||||||
it('should not ask for permission', async () => {
|
it('should not ask for permission', async () => {
|
||||||
await gu.addNewSection(/Calendar/, /Table1/, {selectBy: /TABLE1/});
|
await gu.addNewSection(/Calendar/, /Table1/, {selectBy: /TABLE1/});
|
||||||
await gu.getSection('TABLE1 Calendar').click();
|
await gu.getSection('TABLE1 Calendar').click();
|
||||||
await gu.waitForSidePanel();
|
await gu.toggleSidePanel('right', 'open');
|
||||||
await driver.find('.test-right-tab-pagewidget').click();
|
await driver.find('.test-right-tab-pagewidget').click();
|
||||||
|
|
||||||
await gu.waitForServer();
|
await gu.waitForServer();
|
||||||
|
Loading…
Reference in New Issue
Block a user