mirror of
				https://github.com/gristlabs/grist-core.git
				synced 2025-06-13 20:53:59 +00:00 
			
		
		
		
	(core) Add tip for "Add New" button
Summary: Adds a new tip for the doc menu's Add New button. The tip is shown only when the current user is an editor or owner, and the site is non-empty. The presence of welcome videos or popups will also cause the tip to not be shown; it will instead be shown the next time the doc menu is visited. Test Plan: Browser tests. Reviewers: jarek Reviewed By: jarek Differential Revision: https://phab.getgrist.com/D3757
This commit is contained in:
		
							parent
							
								
									b7f65ff408
								
							
						
					
					
						commit
						db64dfeef0
					
				| @ -335,7 +335,7 @@ export class AccessRules extends Disposable { | ||||
| 
 | ||||
|   public buildDom() { | ||||
|     return cssOuter( | ||||
|       dom('div', this._gristDoc.behavioralPrompts.attachTip('accessRules', { | ||||
|       dom('div', this._gristDoc.behavioralPromptsManager.attachTip('accessRules', { | ||||
|         hideArrow: true, | ||||
|       })), | ||||
|       cssAddTableRow( | ||||
|  | ||||
| @ -1,10 +1,11 @@ | ||||
| import {showBehavioralPrompt} from 'app/client/components/modals'; | ||||
| import {AppModel} from 'app/client/models/AppModel'; | ||||
| import {getUserPrefObs} from 'app/client/models/UserPrefs'; | ||||
| import {GristBehavioralPrompts} from 'app/client/ui/GristTooltips'; | ||||
| import {isNarrowScreen} from 'app/client/ui2018/cssVars'; | ||||
| import {BehavioralPrompt} from 'app/common/Prefs'; | ||||
| import {BehavioralPrompt, BehavioralPromptPrefs} from 'app/common/Prefs'; | ||||
| import {getGristConfig} from 'app/common/urlUtils'; | ||||
| import {Computed, Disposable, dom} from 'grainjs'; | ||||
| import {Computed, Disposable, dom, Observable} from 'grainjs'; | ||||
| import {IPopupOptions} from 'popweasel'; | ||||
| 
 | ||||
| export interface AttachOptions { | ||||
| @ -25,12 +26,15 @@ interface QueuedTip { | ||||
|  * | ||||
|  * Tips are shown in the order that they are attached. | ||||
|  */ | ||||
| export class BehavioralPrompts extends Disposable { | ||||
|   private _prefs = this._appModel.behavioralPrompts; | ||||
| export class BehavioralPromptsManager extends Disposable { | ||||
|   private readonly _prefs = getUserPrefObs(this._appModel.userPrefsObs, 'behavioralPrompts', | ||||
|     { defaultValue: { dontShowTips: false, dismissedTips: [] } }) as Observable<BehavioralPromptPrefs>; | ||||
| 
 | ||||
|   private _dismissedTips: Computed<Set<BehavioralPrompt>> = Computed.create(this, use => { | ||||
|     const {dismissedTips} = use(this._prefs); | ||||
|     return new Set(dismissedTips.filter(BehavioralPrompt.guard)); | ||||
|   }); | ||||
| 
 | ||||
|   private _queuedTips: QueuedTip[] = []; | ||||
| 
 | ||||
|   constructor(private _appModel: AppModel) { | ||||
| @ -6,7 +6,6 @@ | ||||
| import {AccessRules} from 'app/client/aclui/AccessRules'; | ||||
| import {ActionLog} from 'app/client/components/ActionLog'; | ||||
| import BaseView from 'app/client/components/BaseView'; | ||||
| import {BehavioralPrompts} from 'app/client/components/BehavioralPrompts'; | ||||
| import {isNumericLike, isNumericOnly} from 'app/client/components/ChartView'; | ||||
| import {CodeEditorPanel} from 'app/client/components/CodeEditorPanel'; | ||||
| import * as commands from 'app/client/components/commands'; | ||||
| @ -166,7 +165,7 @@ export class GristDoc extends DisposableWithEvents { | ||||
|   // If the doc has a docTour. Used also to enable the UI button to restart the tour.
 | ||||
|   public readonly hasDocTour: Computed<boolean>; | ||||
| 
 | ||||
|   public readonly behavioralPrompts = BehavioralPrompts.create(this, this.docPageModel.appModel); | ||||
|   public readonly behavioralPromptsManager = this.docPageModel.appModel.behavioralPromptsManager; | ||||
| 
 | ||||
|   private _actionLog: ActionLog; | ||||
|   private _undoStack: UndoStack; | ||||
| @ -1100,7 +1099,7 @@ export class GristDoc extends DisposableWithEvents { | ||||
|       // Don't show the tip if a non-card widget was selected.
 | ||||
|       !['single', 'detail'].includes(selectedWidgetType) || | ||||
|       // Or if we've already seen it.
 | ||||
|       this.behavioralPrompts.hasSeenTip('editCardLayout') | ||||
|       this.behavioralPromptsManager.hasSeenTip('editCardLayout') | ||||
|     ) { | ||||
|       return; | ||||
|     } | ||||
| @ -1114,7 +1113,7 @@ export class GristDoc extends DisposableWithEvents { | ||||
|     const editLayoutButton = document.querySelector('.behavioral-prompt-edit-card-layout'); | ||||
|     if (!editLayoutButton) { throw new Error('GristDoc failed to find edit card layout button'); } | ||||
| 
 | ||||
|     this.behavioralPrompts.showTip(editLayoutButton, 'editCardLayout', { | ||||
|     this.behavioralPromptsManager.showTip(editLayoutButton, 'editCardLayout', { | ||||
|       popupOptions: { | ||||
|         placement: 'left-start', | ||||
|       } | ||||
|  | ||||
| @ -42,7 +42,7 @@ export class RawDataPage extends Disposable { | ||||
| 
 | ||||
|   public buildDom() { | ||||
|     return cssContainer( | ||||
|       dom('div', this._gristDoc.behavioralPrompts.attachTip('rawDataPage', {hideArrow: true})), | ||||
|       dom('div', this._gristDoc.behavioralPromptsManager.attachTip('rawDataPage', {hideArrow: true})), | ||||
|       dom('div', | ||||
|         dom.create(DataTables, this._gristDoc), | ||||
|         dom.create(DocumentUsage, this._gristDoc.docPageModel), | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
| import {BehavioralPromptsManager} from 'app/client/components/BehavioralPromptsManager'; | ||||
| import {get as getBrowserGlobals} from 'app/client/lib/browserGlobals'; | ||||
| import {makeT} from 'app/client/lib/localization'; | ||||
| import {error} from 'app/client/lib/log'; | ||||
| @ -12,9 +13,8 @@ import {Features, isLegacyPlan, Product} from 'app/common/Features'; | ||||
| import {GristLoadConfig} from 'app/common/gristUrls'; | ||||
| import {FullUser} from 'app/common/LoginSessionAPI'; | ||||
| import {LocalPlugin} from 'app/common/plugin'; | ||||
| import {BehavioralPromptPrefs, DeprecationWarning, DismissedPopup, DismissedReminder, | ||||
|         UserPrefs} from 'app/common/Prefs'; | ||||
| import {isOwner} from 'app/common/roles'; | ||||
| import {DeprecationWarning, DismissedPopup, DismissedReminder, UserPrefs} from 'app/common/Prefs'; | ||||
| import {isOwner, isOwnerOrEditor} from 'app/common/roles'; | ||||
| import {getTagManagerScript} from 'app/common/tagManager'; | ||||
| import {getDefaultThemePrefs, Theme, ThemeAppearance, ThemeColors, ThemePrefs, | ||||
|         ThemePrefsChecker} from 'app/common/ThemePrefs'; | ||||
| @ -99,19 +99,21 @@ export interface AppModel { | ||||
|    */ | ||||
|   deprecatedWarnings: Observable<DeprecationWarning[]>; | ||||
|   dismissedWelcomePopups: Observable<DismissedReminder[]>; | ||||
|   behavioralPrompts: Observable<BehavioralPromptPrefs>; | ||||
| 
 | ||||
|   pageType: Observable<PageType>; | ||||
| 
 | ||||
|   notifier: Notifier; | ||||
|   planName: string|null; | ||||
| 
 | ||||
|   behavioralPromptsManager: BehavioralPromptsManager; | ||||
| 
 | ||||
|   refreshOrgUsage(): Promise<void>; | ||||
|   showUpgradeModal(): void; | ||||
|   showNewSiteModal(): void; | ||||
|   isBillingManager(): boolean;          // If user is a billing manager for this org
 | ||||
|   isSupport(): boolean;                 // If user is a Support user
 | ||||
|   isOwner(): boolean;                   // If user is an owner of this org
 | ||||
|   isOwnerOrEditor(): boolean;           // If user is an owner or editor of this org
 | ||||
| } | ||||
| 
 | ||||
| export class TopAppModelImpl extends Disposable implements TopAppModel { | ||||
| @ -246,8 +248,6 @@ export class AppModelImpl extends Disposable implements AppModel { | ||||
|     { defaultValue: [] }) as Observable<DeprecationWarning[]>; | ||||
|   public readonly dismissedWelcomePopups = getUserPrefObs(this.userPrefsObs, 'dismissedWelcomePopups', | ||||
|     { defaultValue: [] }) as Observable<DismissedReminder[]>; | ||||
|   public readonly behavioralPrompts = getUserPrefObs(this.userPrefsObs, 'behavioralPrompts', | ||||
|     { defaultValue: { dontShowTips: false, dismissedTips: [] } }) as Observable<BehavioralPromptPrefs>; | ||||
| 
 | ||||
|   // Get the current PageType from the URL.
 | ||||
|   public readonly pageType: Observable<PageType> = Computed.create(this, urlState().state, | ||||
| @ -255,6 +255,9 @@ export class AppModelImpl extends Disposable implements AppModel { | ||||
| 
 | ||||
|   public readonly notifier = this.topAppModel.notifier; | ||||
| 
 | ||||
|   public readonly behavioralPromptsManager: BehavioralPromptsManager = | ||||
|     BehavioralPromptsManager.create(this, this); | ||||
| 
 | ||||
|   constructor( | ||||
|     public readonly topAppModel: TopAppModel, | ||||
|     public readonly currentUser: FullUser|null, | ||||
| @ -314,6 +317,10 @@ export class AppModelImpl extends Disposable implements AppModel { | ||||
|     return Boolean(this.currentOrg && isOwner(this.currentOrg)); | ||||
|   } | ||||
| 
 | ||||
|   public isOwnerOrEditor() { | ||||
|     return Boolean(this.currentOrg && isOwnerOrEditor(this.currentOrg)); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Fetch and update the current org's usage. | ||||
|    */ | ||||
|  | ||||
| @ -75,6 +75,8 @@ export interface HomeModel { | ||||
|   // user isn't allowed to create a doc.
 | ||||
|   newDocWorkspace: Observable<Workspace|null|"unsaved">; | ||||
| 
 | ||||
|   shouldShowAddNewTip: Observable<boolean>; | ||||
| 
 | ||||
|   createWorkspace(name: string): Promise<void>; | ||||
|   renameWorkspace(id: number, name: string): Promise<void>; | ||||
|   deleteWorkspace(id: number, forever: boolean): Promise<void>; | ||||
| @ -154,6 +156,9 @@ export class HomeModelImpl extends Disposable implements HomeModel, ViewSettings | ||||
|   public readonly showIntro = Computed.create(this, this.workspaces, (use, wss) => ( | ||||
|     wss.every((ws) => ws.isSupportWorkspace || ws.docs.length === 0))); | ||||
| 
 | ||||
|   public readonly shouldShowAddNewTip = Observable.create(this, | ||||
|     !this._app.behavioralPromptsManager.hasSeenTip('addNew')); | ||||
| 
 | ||||
|   private _userOrgPrefs = Observable.create<UserOrgPrefs|undefined>(this, this._app.currentOrg?.userOrgPrefs); | ||||
| 
 | ||||
|   constructor(private _app: AppModel, clientScope: ClientScope) { | ||||
|  | ||||
							
								
								
									
										53
									
								
								app/client/ui/AddNewTip.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								app/client/ui/AddNewTip.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,53 @@ | ||||
| import {HomeModel} from 'app/client/models/HomeModel'; | ||||
| import {shouldShowWelcomeQuestions} from 'app/client/ui/WelcomeQuestions'; | ||||
| 
 | ||||
| export function attachAddNewTip(home: HomeModel): (el: Element) => void { | ||||
|   return () => { | ||||
|     const {app: {userPrefsObs}} = home; | ||||
|     if (shouldShowWelcomeQuestions(userPrefsObs)) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     if (shouldShowAddNewTip(home)) { | ||||
|       showAddNewTip(home); | ||||
|     } | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| function shouldShowAddNewTip(home: HomeModel): boolean { | ||||
|   return ( | ||||
|     // Only show if the user is an owner or editor.
 | ||||
|     home.app.isOwnerOrEditor() && | ||||
|     // And the tip hasn't been shown before.
 | ||||
|     home.shouldShowAddNewTip.get() && | ||||
|     // And the intro isn't being shown.
 | ||||
|     !home.showIntro.get() && | ||||
|     // And the workspace loaded correctly.
 | ||||
|     home.available.get() && | ||||
|     // And the current page isn't /p/trash; the Add New button is limited there.
 | ||||
|     home.currentPage.get() !== 'trash' | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| function showAddNewTip(home: HomeModel): void { | ||||
|   const addNewButton = document.querySelector('.behavioral-prompt-add-new'); | ||||
|   if (!addNewButton) { | ||||
|     console.warn('AddNewTip failed to find Add New button'); | ||||
|     return; | ||||
|   } | ||||
|   if (!isVisible(addNewButton as HTMLElement)) { | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   home.app.behavioralPromptsManager.showTip(addNewButton, 'addNew', { | ||||
|     popupOptions: { | ||||
|       placement: 'right-start', | ||||
|     }, | ||||
|     onDispose: () => home.shouldShowAddNewTip.set(false), | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| function isVisible(element: HTMLElement): boolean { | ||||
|   // From https://github.com/jquery/jquery/blob/c66d4700dcf98efccb04061d575e242d28741223/src/css/hiddenVisibleSelectors.js.
 | ||||
|   return Boolean(element.offsetWidth || element.offsetHeight || element.getClientRects().length); | ||||
| } | ||||
| @ -352,7 +352,7 @@ export function columnFilterMenu(owner: IDisposableOwner, opts: IFilterMenuOptio | ||||
|               icon('PinTilted'), | ||||
|               cssPinButton.cls('-pinned', model.filterInfo.isPinned), | ||||
|               dom.on('click', () => filterInfo.pinned(!filterInfo.pinned())), | ||||
|               gristDoc.behavioralPrompts.attachTip('filterButtons', { | ||||
|               gristDoc.behavioralPromptsManager.attachTip('filterButtons', { | ||||
|                 popupOptions: { | ||||
|                   attach: null, | ||||
|                   modifiers: { | ||||
|  | ||||
| @ -4,18 +4,19 @@ | ||||
|  * Orgs, workspaces and docs are fetched asynchronously on build via the passed in API. | ||||
|  */ | ||||
| import {loadUserManager} from 'app/client/lib/imports'; | ||||
| import {AppModel, reportError} from 'app/client/models/AppModel'; | ||||
| import {reportError} from 'app/client/models/AppModel'; | ||||
| import {docUrl, urlState} from 'app/client/models/gristUrlState'; | ||||
| import {getTimeFromNow, HomeModel, makeLocalViewSettings, ViewSettings} from 'app/client/models/HomeModel'; | ||||
| import {getWorkspaceInfo, workspaceName} from 'app/client/models/WorkspaceInfo'; | ||||
| import {attachAddNewTip} from 'app/client/ui/AddNewTip'; | ||||
| import * as css from 'app/client/ui/DocMenuCss'; | ||||
| import {buildHomeIntro, buildWorkspaceIntro} from 'app/client/ui/HomeIntro'; | ||||
| import {buildUpgradeButton} from 'app/client/ui/ProductUpgrades'; | ||||
| import {buildPinnedDoc, createPinnedDocs} from 'app/client/ui/PinnedDocs'; | ||||
| import {shadowScroll} from 'app/client/ui/shadowScroll'; | ||||
| import {transition} from 'app/client/ui/transitions'; | ||||
| import {showWelcomeCoachingCall} from 'app/client/ui/WelcomeCoachingCall'; | ||||
| import {showWelcomeQuestions} from 'app/client/ui/WelcomeQuestions'; | ||||
| import {shouldShowWelcomeCoachingCall, showWelcomeCoachingCall} from 'app/client/ui/WelcomeCoachingCall'; | ||||
| import {shouldShowWelcomeQuestions, showWelcomeQuestions} from 'app/client/ui/WelcomeQuestions'; | ||||
| import {createVideoTourTextButton} from 'app/client/ui/OpenVideoTour'; | ||||
| import {buttonSelect, cssButtonSelect} from 'app/client/ui2018/buttonSelect'; | ||||
| import {isNarrowScreenObs, theme} from 'app/client/ui2018/cssVars'; | ||||
| @ -47,7 +48,7 @@ const testId = makeTestId('test-dm-'); | ||||
|  */ | ||||
| export function createDocMenu(home: HomeModel): DomElementArg[] { | ||||
|   return [ | ||||
|     attachWelcomePopups(home.app), | ||||
|     attachWelcomePopups(home), | ||||
|     dom.domComputed(home.loading, loading => ( | ||||
|       loading === 'slow' ? css.spinner(loadingSpinner()) : | ||||
|       loading ? null : | ||||
| @ -56,12 +57,14 @@ export function createDocMenu(home: HomeModel): DomElementArg[] { | ||||
|   ]; | ||||
| } | ||||
| 
 | ||||
| function attachWelcomePopups(app: AppModel): (el: Element) => void { | ||||
| function attachWelcomePopups(home: HomeModel): (el: Element) => void { | ||||
|   return (element: Element) => { | ||||
|     const isShowingPopup = showWelcomeQuestions(app.userPrefsObs); | ||||
|     if (isShowingPopup) { return; } | ||||
| 
 | ||||
|     showWelcomeCoachingCall(element, app); | ||||
|     const {app, app: {userPrefsObs}} = home; | ||||
|     if (shouldShowWelcomeQuestions(userPrefsObs)) { | ||||
|       showWelcomeQuestions(userPrefsObs); | ||||
|     } else if (shouldShowWelcomeCoachingCall(app)) { | ||||
|       showWelcomeCoachingCall(element, app); | ||||
|     } | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| @ -70,6 +73,8 @@ function createLoadedDocMenu(owner: IDisposableOwner, home: HomeModel) { | ||||
|   const upgradeButton = buildUpgradeButton(owner, home.app); | ||||
|   return css.docList( | ||||
|     css.docMenu( | ||||
|       attachAddNewTip(home), | ||||
| 
 | ||||
|       dom.maybe(!home.app.currentFeatures.workspaces, () => [ | ||||
|         css.docListHeader(t("This service is not available right now")), | ||||
|         dom('span', t("(The organization needs a paid plan)")), | ||||
|  | ||||
| @ -24,7 +24,7 @@ export function filterBar( | ||||
|     dom.forEach(viewSection.activeFilters, (filterInfo) => makeFilterField(filterInfo, popupControls)), | ||||
|     dom.maybe(viewSection.showNestedFilteringPopup, () => { | ||||
|       return dom('div', | ||||
|         gristDoc.behavioralPrompts.attachTip('nestedFiltering', { | ||||
|         gristDoc.behavioralPromptsManager.attachTip('nestedFiltering', { | ||||
|           onDispose: () => viewSection.showNestedFilteringPopup.set(false), | ||||
|         }), | ||||
|       ); | ||||
|  | ||||
| @ -188,4 +188,12 @@ export const GristBehavioralPrompts: Record<BehavioralPrompt, BehavioralPromptCo | ||||
|       ...args, | ||||
|     ), | ||||
|   }, | ||||
|   addNew: { | ||||
|     title: 'Add New', | ||||
|     content: (...args: DomElementArg[]) => cssTooltipContent( | ||||
|       dom('div', 'Click the Add New button to create new documents or workspaces, ' | ||||
|        + 'or import data.'), | ||||
|       ...args, | ||||
|     ), | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| @ -38,7 +38,8 @@ export function createHomeLeftPane(leftPanelOpen: Observable<boolean>, home: Hom | ||||
|         // "Add New" menu should have the same width as the "Add New" button that opens it.
 | ||||
|         stretchToSelector: `.${cssAddNewButton.className}` | ||||
|       }), | ||||
|       testId('dm-add-new') | ||||
|       dom.cls('behavioral-prompt-add-new'), | ||||
|       testId('dm-add-new'), | ||||
|     ), | ||||
|     cssScrollPane( | ||||
|       cssPageEntry( | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| import { BehavioralPrompts } from 'app/client/components/BehavioralPrompts'; | ||||
| import { BehavioralPromptsManager } from 'app/client/components/BehavioralPromptsManager'; | ||||
| import { GristDoc } from 'app/client/components/GristDoc'; | ||||
| import { makeT } from 'app/client/lib/localization'; | ||||
| import { reportError } from 'app/client/models/AppModel'; | ||||
| @ -138,7 +138,7 @@ export function buildPageWidgetPicker( | ||||
|   onSave: ISaveFunc, | ||||
|   options: IOptions = {} | ||||
| ) { | ||||
|   const {behavioralPrompts, docModel} = gristDoc; | ||||
|   const {behavioralPromptsManager, docModel} = gristDoc; | ||||
|   const tables = fromKo(docModel.visibleTables.getObservable()); | ||||
|   const columns = fromKo(docModel.columns.createAllRowsModel('parentPos').getObservable()); | ||||
| 
 | ||||
| @ -207,7 +207,8 @@ export function buildPageWidgetPicker( | ||||
| 
 | ||||
|   // dom
 | ||||
|   return cssPopupWrapper( | ||||
|     dom.create(PageWidgetSelect, value, tables, columns, onSaveCB, behavioralPrompts, options), | ||||
|     dom.create(PageWidgetSelect, | ||||
|       value, tables, columns, onSaveCB, behavioralPromptsManager, options), | ||||
| 
 | ||||
|     // gives focus and binds keydown events
 | ||||
|     (elem: any) => { setTimeout(() => elem.focus(), 0); }, | ||||
| @ -276,7 +277,7 @@ export class PageWidgetSelect extends Disposable { | ||||
|     private _tables: Observable<TableRec[]>, | ||||
|     private _columns: Observable<ColumnRec[]>, | ||||
|     private _onSave: () => Promise<void>, | ||||
|     private _behavioralPrompts: BehavioralPrompts, | ||||
|     private _behavioralPromptsManager: BehavioralPromptsManager, | ||||
|     private _options: ISelectOptions = {} | ||||
|   ) { super(); } | ||||
| 
 | ||||
| @ -307,7 +308,7 @@ export class PageWidgetSelect extends Disposable { | ||||
|             cssIcon('TypeTable'), 'New Table', | ||||
|             // prevent the selection of 'New Table' if it is disabled
 | ||||
|             dom.on('click', (ev) => !this._isNewTableDisabled.get() && this._selectTable('New Table')), | ||||
|             this._behavioralPrompts.attachTip('pageWidgetPicker', { | ||||
|             this._behavioralPromptsManager.attachTip('pageWidgetPicker', { | ||||
|               popupOptions: { | ||||
|                 attach: null, | ||||
|                 placement: 'right-start', | ||||
| @ -365,7 +366,7 @@ export class PageWidgetSelect extends Disposable { | ||||
|               ), | ||||
|               GristTooltips.selectBy(), | ||||
|               {tooltipMenuOptions: {attach: null}, domArgs: [ | ||||
|                 this._behavioralPrompts.attachTip('pageWidgetPickerSelectBy', { | ||||
|                 this._behavioralPromptsManager.attachTip('pageWidgetPickerSelectBy', { | ||||
|                   popupOptions: { | ||||
|                     attach: null, | ||||
|                     placement: 'bottom', | ||||
|  | ||||
| @ -1,5 +1,9 @@ | ||||
| import {AppModel} from 'app/client/models/AppModel'; | ||||
| 
 | ||||
| export function showWelcomeCoachingCall(_triggerElement: Element, _app: AppModel): boolean { | ||||
| export function shouldShowWelcomeCoachingCall(_app: AppModel) { | ||||
|   return false; | ||||
| } | ||||
| 
 | ||||
| export function showWelcomeCoachingCall(_triggerElement: Element, _app: AppModel) { | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -12,17 +12,15 @@ import {dom, input, Observable, styled, subscribeElem} from 'grainjs'; | ||||
| 
 | ||||
| const t = makeT('WelcomeQuestions'); | ||||
| 
 | ||||
| export function shouldShowWelcomeQuestions(userPrefsObs: Observable<UserPrefs>): boolean { | ||||
|   return Boolean(getGristConfig().survey && userPrefsObs.get()?.showNewUserQuestions); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Shows a modal with welcome questions if surveying is enabled and the user hasn't | ||||
|  * dismissed the modal before. | ||||
|  * | ||||
|  * Returns a boolean indicating whether the modal was shown or not. | ||||
|  */ | ||||
| export function showWelcomeQuestions(userPrefsObs: Observable<UserPrefs>): boolean { | ||||
|   if (!(getGristConfig().survey && userPrefsObs.get()?.showNewUserQuestions)) { | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
| export function showWelcomeQuestions(userPrefsObs: Observable<UserPrefs>) { | ||||
|   saveModal((ctl, owner): ISaveModalOptions => { | ||||
|     const selection = choices.map(c => Observable.create(owner, false)); | ||||
|     const otherText = Observable.create(owner, ''); | ||||
| @ -60,8 +58,6 @@ export function showWelcomeQuestions(userPrefsObs: Observable<UserPrefs>): boole | ||||
|       modalArgs: cssModalCentered.cls(''), | ||||
|     }; | ||||
|   }); | ||||
| 
 | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| const choices: Array<{icon: IconName, color: string, textKey: string}> = [ | ||||
|  | ||||
| @ -294,7 +294,7 @@ export class FieldBuilder extends Disposable { | ||||
|             } | ||||
| 
 | ||||
|             if (op.label === 'Reference') { | ||||
|               return this.gristDoc.behavioralPrompts.attachTip('referenceColumns', { | ||||
|               return this.gristDoc.behavioralPromptsManager.attachTip('referenceColumns', { | ||||
|                 popupOptions: { | ||||
|                   attach: `.${cssTypeSelectMenu.className}`, | ||||
|                   placement: 'left-start', | ||||
| @ -370,7 +370,7 @@ export class FieldBuilder extends Disposable { | ||||
|     }); | ||||
|     return [ | ||||
|       cssLabel('DATA FROM TABLE', | ||||
|         !this._showRefConfigPopup.peek() ? null : this.gristDoc.behavioralPrompts.attachTip( | ||||
|         !this._showRefConfigPopup.peek() ? null : this.gristDoc.behavioralPromptsManager.attachTip( | ||||
|           'referenceColumnsConfig', | ||||
|           { | ||||
|             onDispose: () => this._showRefConfigPopup(false), | ||||
|  | ||||
| @ -77,6 +77,7 @@ export const BehavioralPrompt = StringUnion( | ||||
|   'pageWidgetPicker', | ||||
|   'pageWidgetPickerSelectBy', | ||||
|   'editCardLayout', | ||||
|   'addNew', | ||||
| ); | ||||
| export type BehavioralPrompt = typeof BehavioralPrompt.type; | ||||
| 
 | ||||
|  | ||||
| @ -45,6 +45,10 @@ export function isOwner(resource: {access: Role}|null): resource is {access: Rol | ||||
|   return resource?.access === OWNER; | ||||
| } | ||||
| 
 | ||||
| export function isOwnerOrEditor(resource: {access: Role}|null): resource is {access: Role} { | ||||
|   return canEdit(resource?.access ?? null); | ||||
| } | ||||
| 
 | ||||
| export function canUpgradeOrg(org: Organization|null): org is Organization { | ||||
|   // TODO: Need to consider billing managers and support user.
 | ||||
|   return isOwner(org); | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user