diff --git a/app/client/models/DocPageModel.ts b/app/client/models/DocPageModel.ts index f0a2683b63..8c196e2ef5 100644 --- a/app/client/models/DocPageModel.ts +++ b/app/client/models/DocPageModel.ts @@ -29,6 +29,7 @@ import {Holder, Observable, subscribe} from 'grainjs'; import {Computed, Disposable, dom, DomArg, DomElementArg} from 'grainjs'; import {makeT} from 'app/client/lib/localization'; import {logTelemetryEvent} from 'app/client/lib/telemetry'; +import {DocumentType} from 'app/common/UserAPI'; // tslint:disable:no-console @@ -87,7 +88,7 @@ export interface DocPageModel { isTutorialTrunk: Observable; isTutorialFork: Observable; isTemplate: Observable; - + type: Observable; importSources: ImportSource[]; undoState: Observable; // See UndoStack for details. @@ -147,6 +148,8 @@ export class DocPageModelImpl extends Disposable implements DocPageModel { (use, doc) => doc ? doc.isTutorialFork : false); public readonly isTemplate = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.isTemplate : false); + public readonly type = Computed.create(this, this.currentDoc, + (use, doc) => doc?.type ?? null); public readonly importSources: ImportSource[] = []; @@ -499,7 +502,8 @@ function buildDocInfo(doc: Document, mode: OpenDocMode | undefined): DocInfo { const isFork = Boolean(idParts.forkId || idParts.snapshotId); const isBareFork = isFork && idParts.trunkId === NEW_DOCUMENT_CODE; const isSnapshot = Boolean(idParts.snapshotId); - const isTutorial = doc.type === 'tutorial'; + const type = doc.type; + const isTutorial = type === 'tutorial'; const isTutorialTrunk = isTutorial && !isFork && mode !== 'default'; const isTutorialFork = isTutorial && isFork; @@ -511,7 +515,7 @@ function buildDocInfo(doc: Document, mode: OpenDocMode | undefined): DocInfo { // mode. Since the document's 'openMode' has no effect, don't bother trying // to set it here, as it'll potentially be confusing for other code reading it. openMode = 'default'; - } else if (!isFork && doc.type === 'template') { + } else if (!isFork && type === 'template') { // Templates should always open in fork mode by default. openMode = 'fork'; } else { @@ -521,7 +525,7 @@ function buildDocInfo(doc: Document, mode: OpenDocMode | undefined): DocInfo { } const isPreFork = openMode === 'fork'; - const isTemplate = doc.type === 'template' && (isFork || isPreFork); + const isTemplate = type === 'template' && (isFork || isPreFork); const isEditable = !isSnapshot && (canEdit(doc.access) || isPreFork); return { ...doc, @@ -534,6 +538,7 @@ function buildDocInfo(doc: Document, mode: OpenDocMode | undefined): DocInfo { isSnapshot, isTutorialTrunk, isTutorialFork, + type, isTemplate, isReadonly: !isEditable, idParts, diff --git a/app/client/ui/DocumentSettings.ts b/app/client/ui/DocumentSettings.ts index b2c9ba287b..ff583067eb 100644 --- a/app/client/ui/DocumentSettings.ts +++ b/app/client/ui/DocumentSettings.ts @@ -29,7 +29,18 @@ import {commonUrls, GristLoadConfig} from 'app/common/gristUrls'; import {not, propertyCompare} from 'app/common/gutil'; import {getCurrency, locales} from 'app/common/Locales'; import {isOwner, isOwnerOrEditor} from 'app/common/roles'; -import {Computed, Disposable, dom, fromKo, IDisposableOwner, makeTestId, Observable, styled} from 'grainjs'; +import {DOCTYPE_NORMAL, DOCTYPE_TEMPLATE, DOCTYPE_TUTORIAL, DocumentType, persistType} from 'app/common/UserAPI'; +import { + Computed, + Disposable, + dom, + DomElementMethod, + fromKo, + IDisposableOwner, + makeTestId, + Observable, + styled +} from 'grainjs'; import * as moment from 'moment-timezone'; const t = makeT('DocumentSettings'); @@ -85,6 +96,22 @@ export class DocSettingsPage extends Disposable { {defaultCurrencyLabel: t("Local currency ({{currency}})", {currency: getCurrency(l)})}) ) }), + dom.create(AdminSectionItem, { + id: 'templateMode', + name: t('Template mode'), + description: t('Change document type'), + value: cssDocTypeContainer( + dom.create( + displayCurrentType, + docPageModel.type, + ), + cssSmallButton(t('Edit'), + dom.on('click', this._buildDocumentTypeModal.bind(this, true)), + testId('doctype-edit') + ), + ), + disabled: isDocOwner ? false : t('Only available to document owners'), + }), ]), dom.create(AdminSection, t('Data Engine'), [ @@ -120,7 +147,6 @@ export class DocSettingsPage extends Disposable { )), disabled: isDocOwner ? false : t('Only available to document owners'), }), - dom.create(AdminSectionItem, { id: 'reload', name: t('Reload'), @@ -128,7 +154,6 @@ export class DocSettingsPage extends Disposable { value: cssSmallButton(t('Reload data engine'), dom.on('click', this._reloadEngine.bind(this, true))), disabled: isDocEditor ? false : t('Only available to document editors'), }), - canChangeEngine ? dom.create(AdminSectionItem, { id: 'python', name: t('Python'), @@ -186,7 +211,6 @@ export class DocSettingsPage extends Disposable { href: getApiConsoleLink(docPageModel), }), }), - dom.create(AdminSectionItem, { id: 'webhooks', name: t('Webhooks'), @@ -224,11 +248,11 @@ export class DocSettingsPage extends Disposable { const docPageModel = this._gristDoc.docPageModel; modal((ctl, owner) => { this.onDispose(() => ctl.close()); - const selected = Observable.create