(core) Add button for removing doc tours

Summary:
Document owners can now remove doc tours by pressing the button located
to the right of 'Tour of this Document' in the left panel.

Test Plan: Browser test.

Reviewers: jarek

Reviewed By: jarek

Subscribers: jarek

Differential Revision: https://phab.getgrist.com/D3202
This commit is contained in:
George Gevoian 2022-01-03 18:20:58 -06:00
parent 89dc9334c3
commit 62a6190970
4 changed files with 55 additions and 41 deletions

View File

@ -33,7 +33,7 @@ import {DocPageModel} from 'app/client/models/DocPageModel';
import {UserError} from 'app/client/models/errors'; import {UserError} from 'app/client/models/errors';
import {urlState} from 'app/client/models/gristUrlState'; import {urlState} from 'app/client/models/gristUrlState';
import {getFilterFunc, QuerySetManager} from 'app/client/models/QuerySet'; import {getFilterFunc, QuerySetManager} from 'app/client/models/QuerySet';
import {getUserOrgPrefObs} from "app/client/models/UserPrefs"; import {getUserOrgPrefObs, getUserOrgPrefsObs} from 'app/client/models/UserPrefs';
import {App} from 'app/client/ui/App'; import {App} from 'app/client/ui/App';
import {DocHistory} from 'app/client/ui/DocHistory'; import {DocHistory} from 'app/client/ui/DocHistory';
import {startDocTour} from "app/client/ui/DocTour"; import {startDocTour} from "app/client/ui/DocTour";
@ -128,6 +128,8 @@ export class GristDoc extends DisposableWithEvents {
// Holds current cursor position with a view id // Holds current cursor position with a view id
public cursorPosition: Computed<ViewCursorPos | undefined>; public cursorPosition: Computed<ViewCursorPos | undefined>;
public readonly userOrgPrefs = getUserOrgPrefsObs(this.docPageModel.appModel);
private _actionLog: ActionLog; private _actionLog: ActionLog;
private _undoStack: UndoStack; private _undoStack: UndoStack;
private _lastOwnActionGroup: ActionGroupWithCursorPos|null = null; private _lastOwnActionGroup: ActionGroupWithCursorPos|null = null;
@ -135,7 +137,7 @@ export class GristDoc extends DisposableWithEvents {
private _docHistory: DocHistory; private _docHistory: DocHistory;
private _rightPanelTool = createSessionObs(this, "rightPanelTool", "none", RightPanelTool.guard); private _rightPanelTool = createSessionObs(this, "rightPanelTool", "none", RightPanelTool.guard);
private _viewLayout: ViewLayout|null = null; private _viewLayout: ViewLayout|null = null;
private _showGristTour = getUserOrgPrefObs(this.docPageModel.appModel, 'showGristTour'); private _showGristTour = getUserOrgPrefObs(this.userOrgPrefs, 'showGristTour');
constructor( constructor(
public readonly app: App, public readonly app: App,

View File

@ -29,13 +29,12 @@ export function getUserOrgPrefsObs(appModel: AppModel): Observable<UserOrgPrefs>
} }
/** /**
* Creates an observable that returns a particular preference value from UserOrgPrefs, and which * Creates an observable that returns a particular preference value from `prefsObs`, and which
* stores it when set. * stores it when set.
*/ */
export function getUserOrgPrefObs<Name extends keyof UserOrgPrefs>( export function getUserOrgPrefObs<Name extends keyof UserOrgPrefs>(
appModel: AppModel, prefName: Name prefsObs: Observable<UserOrgPrefs>, prefName: Name
): Observable<UserOrgPrefs[Name]> { ): Observable<UserOrgPrefs[Name]> {
const prefsObs = getUserOrgPrefsObs(appModel);
return Computed.create(null, (use) => use(prefsObs)[prefName]) return Computed.create(null, (use) => use(prefsObs)[prefName])
.onWrite(value => prefsObs.set({...prefsObs.get(), [prefName]: value})); .onWrite(value => prefsObs.set({...prefsObs.get(), [prefName]: value}));
} }

View File

@ -156,17 +156,18 @@ export const cssSpacer = styled('div', `
height: 18px; height: 18px;
`); `);
const cssSplitPageEntry = styled('div', ` export const cssSplitPageEntry = styled('div', `
display: flex; display: flex;
align-items: center; align-items: center;
`); `);
const cssPageEntryMain = styled(cssPageEntry, ` export const cssPageEntryMain = styled(cssPageEntry, `
flex: auto; flex: auto;
margin: 0; margin: 0;
min-width: 0px;
`); `);
const cssPageEntrySmall = styled(cssPageEntry, ` export const cssPageEntrySmall = styled(cssPageEntry, `
flex: none; flex: none;
border-radius: 3px; border-radius: 3px;
--icon-color: ${colors.lightGreen}; --icon-color: ${colors.lightGreen};

View File

@ -1,24 +1,28 @@
import { GristDoc } from "app/client/components/GristDoc"; import {GristDoc} from 'app/client/components/GristDoc';
import { urlState } from "app/client/models/gristUrlState"; import {loadGristDoc} from 'app/client/lib/imports';
import { showExampleCard } from 'app/client/ui/ExampleCard'; import {urlState} from 'app/client/models/gristUrlState';
import { examples } from 'app/client/ui/ExampleInfo'; import {getUserOrgPrefObs} from 'app/client/models/UserPrefs';
import { createHelpTools, cssSectionHeader, cssSpacer, cssTools } from 'app/client/ui/LeftPanelCommon'; import {showExampleCard} from 'app/client/ui/ExampleCard';
import { cssLinkText, cssPageEntry, cssPageIcon, cssPageLink } from 'app/client/ui/LeftPanelCommon'; import {examples} from 'app/client/ui/ExampleInfo';
import { hoverTooltip, tooltipCloseButton } from 'app/client/ui/tooltips'; import {createHelpTools, cssLinkText, cssPageEntry, cssPageEntryMain, cssPageEntrySmall,
import { colors } from 'app/client/ui2018/cssVars'; cssPageIcon, cssPageLink, cssSectionHeader, cssSpacer, cssSplitPageEntry,
import { icon } from 'app/client/ui2018/icons'; cssTools} from 'app/client/ui/LeftPanelCommon';
import { cssLink } from 'app/client/ui2018/links'; import {hoverTooltip, tooltipCloseButton} from 'app/client/ui/tooltips';
import { menuAnnotate } from 'app/client/ui2018/menus'; import {colors} from 'app/client/ui2018/cssVars';
import { userOverrideParams } from 'app/common/gristUrls'; import {icon} from 'app/client/ui2018/icons';
import { Disposable, dom, makeTestId, Observable, observable, styled } from "grainjs"; import {cssLink} from 'app/client/ui2018/links';
import {getUserOrgPrefObs} from "app/client/models/UserPrefs"; import {menuAnnotate} from 'app/client/ui2018/menus';
import {loadGristDoc} from "app/client/lib/imports"; import {confirmModal} from 'app/client/ui2018/modals';
import {userOverrideParams} from 'app/common/gristUrls';
import {Computed, Disposable, dom, makeTestId, Observable, observable, styled} from 'grainjs';
const testId = makeTestId('test-tools-'); const testId = makeTestId('test-tools-');
export function tools(owner: Disposable, gristDoc: GristDoc, leftPanelOpen: Observable<boolean>): Element { export function tools(owner: Disposable, gristDoc: GristDoc, leftPanelOpen: Observable<boolean>): Element {
const isOwner = gristDoc.docPageModel.currentDoc.get()?.access === 'owners'; const isOwner = gristDoc.docPageModel.currentDoc.get()?.access === 'owners';
const isOverridden = Boolean(gristDoc.docPageModel.userOverride.get()); const isOverridden = Boolean(gristDoc.docPageModel.userOverride.get());
const hasDocTour = Computed.create(owner, use =>
use(gristDoc.docModel.allTableIds.getObservable()).includes('GristDocTour'));
const canViewAccessRules = observable(false); const canViewAccessRules = observable(false);
function updateCanViewAccessRules() { function updateCanViewAccessRules() {
canViewAccessRules.set((isOwner && !isOverridden) || canViewAccessRules.set((isOwner && !isOverridden) ||
@ -84,23 +88,32 @@ export function tools(owner: Disposable, gristDoc: GristDoc, leftPanelOpen: Obse
), ),
); );
}), }),
// Shows the 'Tour of this Document' button if a GristDocTour table exists // Show the 'Tour of this Document' button if a GristDocTour table exists.
// at the time of running. Currently doesn't observe the set of existing tables dom.maybe(hasDocTour, () =>
gristDoc.docData.getTable('GristDocTour') && cssSplitPageEntry(
cssPageEntry( cssPageEntryMain(
cssPageLink( cssPageLink(cssPageIcon('Page'),
cssPageIcon('Page'), cssLinkText('Tour of this Document'),
cssLinkText('Tour of this Document'), automaticHelpTool(
testId('doctour'), async ({markAsSeen}) => {
automaticHelpTool( const gristDocModule = await loadGristDoc();
async ({markAsSeen}) => { await gristDocModule.startDocTour(gristDoc.docData, gristDoc.docComm, markAsSeen);
const gristDocModule = await loadGristDoc(); },
await gristDocModule.startDocTour(gristDoc.docData, gristDoc.docComm, markAsSeen); gristDoc,
}, "seenDocTours",
gristDoc, gristDoc.docId()
"seenDocTours", ),
gristDoc.docId() testId('doctour'),
),
), ),
!isOwner ? null : cssPageEntrySmall(
cssPageLink(cssPageIcon('Remove'),
dom.on('click', () => confirmModal('Delete document tour?', 'Delete', () =>
gristDoc.docData.sendAction(['RemoveTable', 'GristDocTour']))
),
testId('remove-doctour')
),
)
), ),
), ),
createHelpTools(gristDoc.docPageModel.appModel, false) createHelpTools(gristDoc.docPageModel.appModel, false)
@ -127,8 +140,7 @@ function automaticHelpTool(
itemId: number | string itemId: number | string
) { ) {
function show(elem: HTMLElement, reopen: boolean) { function show(elem: HTMLElement, reopen: boolean) {
const appModel = gristDoc.docPageModel.appModel; const prefObs: Observable<typeof itemId[] | undefined> = getUserOrgPrefObs(gristDoc.userOrgPrefs, prefKey);
const prefObs: Observable<typeof itemId[] | undefined> = getUserOrgPrefObs(appModel, prefKey);
const seenIds = prefObs.get() || []; const seenIds = prefObs.get() || [];
// If this help was previously dismissed, don't show it again, unless the user is reopening it. // If this help was previously dismissed, don't show it again, unless the user is reopening it.