From 73c4efa31517e89e451266ee9b2b22a086cd64bb Mon Sep 17 00:00:00 2001 From: Alex Hall Date: Fri, 30 Jul 2021 20:05:16 +0200 Subject: [PATCH] (core) Hide the GristDocTour table by default but reveal it when /p/GristDocTour is in the URL Summary: Adds 'GristDocTour' as a possible value of urlState().docPage GristDoc checks for this and converts it to a normal view record ID It also then sets a flag showGristDocTour=true which tells Pages.ts to show the page in the sidebar Otherwise the page is 'hidden' in the sidebar in the same way it would be if blocked by ACL rules This all feels very hacky, but I don't know this code well enough to know if there's a better way. Hopefully this behaviour is temporary. Test Plan: Tested manually, not sure if this is worth an automated test at this stage Reviewers: paulfitz, dsagal Reviewed By: paulfitz, dsagal Subscribers: dsagal Differential Revision: https://phab.getgrist.com/D2953 --- app/client/components/GristDoc.ts | 16 ++++++++++++++-- app/client/ui/Pages.ts | 10 +++++++++- app/client/ui2018/pages.ts | 8 ++------ app/common/gristUrls.ts | 6 +++--- 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/app/client/components/GristDoc.ts b/app/client/components/GristDoc.ts index 08e41337..0b85cf5d 100644 --- a/app/client/components/GristDoc.ts +++ b/app/client/components/GristDoc.ts @@ -59,6 +59,7 @@ import { CursorMonitor, ViewCursorPos } from "app/client/components/CursorMonito import { EditorMonitor } from "app/client/components/EditorMonitor"; import { FieldEditor } from "app/client/widgets/FieldEditor"; import { Drafts } from "app/client/components/Drafts"; +import {findIndex} from "lodash"; const G = getBrowserGlobals('document', 'window'); @@ -94,6 +95,7 @@ export class GristDoc extends DisposableWithEvents { public viewModel: ViewRec; public activeViewId: Computed; public currentPageName: Observable; + public showDocTourTable: boolean = false; public docData: DocData; public docInfo: DocInfoRec; public docPluginManager: DocPluginManager; @@ -158,7 +160,17 @@ export class GristDoc extends DisposableWithEvents { // Grainjs observable for current view id, which may be a string such as 'code'. this.activeViewId = Computed.create(this, (use) => { - return use(urlState().state).docPage || use(defaultViewId); + let result = use(urlState().state).docPage; + if (result === 'GristDocTour') { + // GristDocTour is a special table that is usually hidden from users, but putting /p/GristDocTour + // in the URL navigates to it and makes it visible in the list of pages in the sidebar + this.showDocTourTable = true; + result = findIndex(this.docModel.views.rowModels, view => view?.name.peek() === result); + if (result === -1) { + result = undefined; // not found, back to the default + } + } + return result || use(defaultViewId); }); // This viewModel reflects the currently active view, relying on the fact that @@ -335,7 +347,7 @@ export class GristDoc extends DisposableWithEvents { dom.domComputed(this.activeViewId, (viewId) => ( viewId === 'code' ? dom.create((owner) => owner.autoDispose(CodeEditorPanel.create(this))) : viewId === 'acl' ? dom.create((owner) => owner.autoDispose(AccessRules.create(this, this))) : - viewId === 'new' ? null : + viewId === 'new' || viewId == 'GristDocTour' ? null : dom.create((owner) => (this._viewLayout = ViewLayout.create(owner, this, viewId))) )), ); diff --git a/app/client/ui/Pages.ts b/app/client/ui/Pages.ts index ec6dd43e..907b1455 100644 --- a/app/client/ui/Pages.ts +++ b/app/client/ui/Pages.ts @@ -69,7 +69,15 @@ function buildDomFromTable(pagesTable: MetaTableModel, activeDoc: Grist actions.isRemoveDisabled = () => (docModel.allTables.all().length <= 1); } - return buildPageDom(pageName, actions, urlState().setLinkUrl({docPage: viewId})); + const maybeHiddenPageName = Computed.create(activeDoc, (use) => { + const name = use(pageName); + if (name === 'GristDocTour' && !activeDoc.showDocTourTable) { + return ''; + } + return name; + }); + + return buildPageDom(maybeHiddenPageName, actions, urlState().setLinkUrl({docPage: viewId})); } // Select another page in cyclic ordering of pages. Order is downard if given a positive `delta`, diff --git a/app/client/ui2018/pages.ts b/app/client/ui2018/pages.ts index dbca3e6d..d43e7766 100644 --- a/app/client/ui2018/pages.ts +++ b/app/client/ui2018/pages.ts @@ -5,7 +5,6 @@ import { colors } from "app/client/ui2018/cssVars"; import { icon } from "app/client/ui2018/icons"; import { menu, menuItem, menuText } from "app/client/ui2018/menus"; import { dom, domComputed, DomElementArg, makeTestId, observable, Observable, styled } from "grainjs"; -import * as ko from "knockout"; const testId = makeTestId('test-docpage-'); @@ -18,15 +17,12 @@ export interface PageActions { isReadonly: Observable; } -// to work with existing code we need to support both knockout and grainjs observable -type NameType = Observable|ko.Observable; - // build the dom for a document page entry. It shows an icon (for now the first letter of the name, // but later we'll support user selected icon), the name and a dots menu containing a "Rename" and // "Remove" entries. Clicking "Rename" turns the page name into an editable input, which then call // the actions.onRename callback with the new name. Setting actions.onRemove to undefined disables // the item in the menu. -export function buildPageDom(name: NameType, actions: PageActions, ...args: DomElementArg[]) { +export function buildPageDom(name: Observable, actions: PageActions, ...args: DomElementArg[]) { const isRenaming = observable(false); const pageMenu = () => [ @@ -59,7 +55,7 @@ export function buildPageDom(name: NameType, actions: PageActions, ...args: DomE cssPageInitial(dom.text((use) => use(name)[0])), cssEditorInput( { - initialValue: typeof name === 'function' ? name() : name.get() || '', + initialValue: name.get() || '', save: (val) => actions.onRename(val), close: () => isRenaming.set(false) }, diff --git a/app/common/gristUrls.ts b/app/common/gristUrls.ts index 05ae49e8..ea3f6dac 100644 --- a/app/common/gristUrls.ts +++ b/app/common/gristUrls.ts @@ -7,7 +7,7 @@ import {Document} from 'app/common/UserAPI'; import clone = require('lodash/clone'); import pickBy = require('lodash/pickBy'); -export type IDocPage = number | 'new' | 'code' | 'acl'; +export type IDocPage = number | 'new' | 'code' | 'acl' | 'GristDocTour'; // What page to show in the user's home area. Defaults to 'workspace' if a workspace is set, and // to 'all' otherwise. @@ -329,8 +329,8 @@ export function userOverrideParams(email: string|null, extraState?: IGristUrlSta * parseDocPage is a noop if p is 'new' or 'code', otherwise parse to integer */ function parseDocPage(p: string) { - if (['new', 'code', 'acl'].includes(p)) { - return p as 'new'|'code'|'acl'; + if (['new', 'code', 'acl', 'GristDocTour'].includes(p)) { + return p as 'new'|'code'|'acl'|'GristDocTour'; } return parseInt(p, 10); }