mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) New document settings page
Summary: This diff implement new page for document settings which replaces the old modal settings. Diff also adds a new `Settings + API` page item below tools in the left panel that link to that very doc settings page. Test Plan: Updated existing tests. Reviewers: jarek Reviewed By: jarek Differential Revision: https://phab.getgrist.com/D3761
This commit is contained in:
parent
ca5ae6fa3f
commit
a8827b152e
@ -19,6 +19,7 @@ import {EditorMonitor} from "app/client/components/EditorMonitor";
|
|||||||
import * as GridView from 'app/client/components/GridView';
|
import * as GridView from 'app/client/components/GridView';
|
||||||
import {Importer} from 'app/client/components/Importer';
|
import {Importer} from 'app/client/components/Importer';
|
||||||
import {RawDataPage, RawDataPopup} from 'app/client/components/RawDataPage';
|
import {RawDataPage, RawDataPopup} from 'app/client/components/RawDataPage';
|
||||||
|
import {DocSettingsPage} from 'app/client/ui/DocumentSettings';
|
||||||
import {ActionGroupWithCursorPos, UndoStack} from 'app/client/components/UndoStack';
|
import {ActionGroupWithCursorPos, UndoStack} from 'app/client/components/UndoStack';
|
||||||
import {ViewLayout} from 'app/client/components/ViewLayout';
|
import {ViewLayout} from 'app/client/components/ViewLayout';
|
||||||
import {get as getBrowserGlobals} from 'app/client/lib/browserGlobals';
|
import {get as getBrowserGlobals} from 'app/client/lib/browserGlobals';
|
||||||
@ -42,7 +43,6 @@ import {getUserOrgPrefObs, getUserOrgPrefsObs, markAsSeen} from 'app/client/mode
|
|||||||
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";
|
||||||
import {showDocSettingsModal} from 'app/client/ui/DocumentSettings';
|
|
||||||
import {isTourActive} from "app/client/ui/OnBoardingPopups";
|
import {isTourActive} from "app/client/ui/OnBoardingPopups";
|
||||||
import {IPageWidget, toPageWidget} from 'app/client/ui/PageWidgetPicker';
|
import {IPageWidget, toPageWidget} from 'app/client/ui/PageWidgetPicker';
|
||||||
import {linkFromId, selectBy} from 'app/client/ui/selectBy';
|
import {linkFromId, selectBy} from 'app/client/ui/selectBy';
|
||||||
@ -92,7 +92,7 @@ const t = makeT('GristDoc');
|
|||||||
const G = getBrowserGlobals('document', 'window');
|
const G = getBrowserGlobals('document', 'window');
|
||||||
|
|
||||||
// Re-export some tools to move them from main webpack bundle to the one with GristDoc.
|
// Re-export some tools to move them from main webpack bundle to the one with GristDoc.
|
||||||
export {DocComm, showDocSettingsModal, startDocTour};
|
export {DocComm, startDocTour};
|
||||||
|
|
||||||
export interface TabContent {
|
export interface TabContent {
|
||||||
showObs?: any;
|
showObs?: any;
|
||||||
@ -446,7 +446,7 @@ export class GristDoc extends DisposableWithEvents {
|
|||||||
public buildDom() {
|
public buildDom() {
|
||||||
const isMaximized = Computed.create(this, use => use(this.sectionInPopup) !== null);
|
const isMaximized = Computed.create(this, use => use(this.sectionInPopup) !== null);
|
||||||
const isPopup = Computed.create(this, use => {
|
const isPopup = Computed.create(this, use => {
|
||||||
return use(this.activeViewId) === 'data' // On Raw data page
|
return ['data', 'settings'].includes(use(this.activeViewId) as any) // On Raw data or doc settings pages
|
||||||
|| use(isMaximized) // Layout has a maximized section visible
|
|| use(isMaximized) // Layout has a maximized section visible
|
||||||
|| typeof use(this._activeContent) === 'object'; // We are on show raw data popup
|
|| typeof use(this._activeContent) === 'object'; // We are on show raw data popup
|
||||||
});
|
});
|
||||||
@ -458,6 +458,7 @@ export class GristDoc extends DisposableWithEvents {
|
|||||||
content === 'code' ? dom.create(CodeEditorPanel, this) :
|
content === 'code' ? dom.create(CodeEditorPanel, this) :
|
||||||
content === 'acl' ? dom.create(AccessRules, this) :
|
content === 'acl' ? dom.create(AccessRules, this) :
|
||||||
content === 'data' ? dom.create(RawDataPage, this) :
|
content === 'data' ? dom.create(RawDataPage, this) :
|
||||||
|
content === 'settings' ? dom.create(DocSettingsPage, this) :
|
||||||
content === 'GristDocTour' ? null :
|
content === 'GristDocTour' ? null :
|
||||||
(typeof content === 'object') ? dom.create(owner => {
|
(typeof content === 'object') ? dom.create(owner => {
|
||||||
// In case user changes a page, close the popup.
|
// In case user changes a page, close the popup.
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import {loadGristDoc} from 'app/client/lib/imports';
|
|
||||||
import {AppModel} from 'app/client/models/AppModel';
|
import {AppModel} from 'app/client/models/AppModel';
|
||||||
import {DocPageModel} from 'app/client/models/DocPageModel';
|
import {DocPageModel} from 'app/client/models/DocPageModel';
|
||||||
import {getLoginOrSignupUrl, getLoginUrl, getLogoutUrl, urlState} from 'app/client/models/gristUrlState';
|
import {getLoginOrSignupUrl, getLoginUrl, getLogoutUrl, urlState} from 'app/client/models/gristUrlState';
|
||||||
@ -52,14 +51,13 @@ export class AccountWidget extends Disposable {
|
|||||||
*/
|
*/
|
||||||
private _makeAccountMenu(user: FullUser|null): DomElementArg[] {
|
private _makeAccountMenu(user: FullUser|null): DomElementArg[] {
|
||||||
const currentOrg = this._appModel.currentOrg;
|
const currentOrg = this._appModel.currentOrg;
|
||||||
const gristDoc = this._docPageModel ? this._docPageModel.gristDoc.get() : null;
|
|
||||||
|
|
||||||
// The 'Document Settings' item, when there is an open document.
|
// The 'Document Settings' item, when there is an open document.
|
||||||
const documentSettingsItem = (gristDoc ?
|
const documentSettingsItem = this._docPageModel ? menuItemLink(
|
||||||
menuItem(async () => (await loadGristDoc()).showDocSettingsModal(gristDoc.docInfo, this._docPageModel!),
|
urlState().setLinkUrl({docPage: 'settings'}),
|
||||||
t("Document Settings"),
|
t("Document Settings"),
|
||||||
testId('dm-doc-settings')) :
|
testId('dm-doc-settings')
|
||||||
null);
|
) : null;
|
||||||
|
|
||||||
// The item to toggle mobile mode (presence of viewport meta tag).
|
// The item to toggle mobile mode (presence of viewport meta tag).
|
||||||
const mobileModeToggle = menuItem(viewport.toggleViewport,
|
const mobileModeToggle = menuItem(viewport.toggleViewport,
|
||||||
|
@ -3,110 +3,107 @@
|
|||||||
* (new settings to be added here ...).
|
* (new settings to be added here ...).
|
||||||
*/
|
*/
|
||||||
import {makeT} from 'app/client/lib/localization';
|
import {makeT} from 'app/client/lib/localization';
|
||||||
import {dom, IDisposableOwner, styled} from 'grainjs';
|
import {Computed, Disposable, dom, fromKo, IDisposableOwner, styled} from 'grainjs';
|
||||||
import {Computed, Observable} from 'grainjs';
|
|
||||||
|
|
||||||
|
|
||||||
import {ACSelectItem, buildACSelect} from "app/client/lib/ACSelect";
|
import {ACSelectItem, buildACSelect} from "app/client/lib/ACSelect";
|
||||||
|
import {copyToClipboard} from 'app/client/lib/copyToClipboard';
|
||||||
import {ACIndexImpl} from "app/client/lib/ACIndex";
|
import {ACIndexImpl} from "app/client/lib/ACIndex";
|
||||||
import {loadMomentTimezone} from 'app/client/lib/imports';
|
import {docListHeader} from "app/client/ui/DocMenuCss";
|
||||||
import {DocInfoRec} from 'app/client/models/DocModel';
|
import {showTransientTooltip} from 'app/client/ui/tooltips';
|
||||||
import {DocPageModel} from 'app/client/models/DocPageModel';
|
import {mediaSmall, testId, theme, vars} from 'app/client/ui2018/cssVars';
|
||||||
import {testId, vars} from 'app/client/ui2018/cssVars';
|
|
||||||
import {select} from 'app/client/ui2018/menus';
|
import {select} from 'app/client/ui2018/menus';
|
||||||
import {saveModal} from 'app/client/ui2018/modals';
|
|
||||||
import {buildCurrencyPicker} from 'app/client/widgets/CurrencyPicker';
|
import {buildCurrencyPicker} from 'app/client/widgets/CurrencyPicker';
|
||||||
import {buildTZAutocomplete} from 'app/client/widgets/TZAutocomplete';
|
import {buildTZAutocomplete} from 'app/client/widgets/TZAutocomplete';
|
||||||
import {EngineCode} from 'app/common/DocumentSettings';
|
import {EngineCode} from 'app/common/DocumentSettings';
|
||||||
import {GristLoadConfig} from 'app/common/gristUrls';
|
import {GristLoadConfig} from 'app/common/gristUrls';
|
||||||
import {propertyCompare} from "app/common/gutil";
|
import {propertyCompare} from "app/common/gutil";
|
||||||
import {getCurrency, locales} from "app/common/Locales";
|
import {getCurrency, locales} from "app/common/Locales";
|
||||||
|
import {GristDoc} from 'app/client/components/GristDoc';
|
||||||
|
import * as moment from "moment-timezone";
|
||||||
|
import {KoSaveableObservable} from 'app/client/models/modelUtil';
|
||||||
|
import {reportError} from 'app/client/models/AppModel';
|
||||||
|
import {confirmModal} from 'app/client/ui2018/modals';
|
||||||
|
|
||||||
const t = makeT('DocumentSettings');
|
const t = makeT('DocumentSettings');
|
||||||
|
|
||||||
/**
|
export class DocSettingsPage extends Disposable {
|
||||||
* Builds a simple saveModal for saving settings.
|
private _docInfo = this._gristDoc.docInfo;
|
||||||
*/
|
|
||||||
export async function showDocSettingsModal(docInfo: DocInfoRec, docPageModel: DocPageModel): Promise<void> {
|
|
||||||
const moment = await loadMomentTimezone();
|
|
||||||
return saveModal((ctl, owner) => {
|
|
||||||
const timezoneObs = Observable.create(owner, docInfo.timezone.peek());
|
|
||||||
|
|
||||||
const docSettings = docInfo.documentSettingsJson.peek();
|
private _timezone = this._docInfo.timezone;
|
||||||
const {locale, currency, engine} = docSettings;
|
private _locale: KoSaveableObservable<string> = this._docInfo.documentSettingsJson.prop('locale');
|
||||||
const localeObs = Observable.create(owner, locale);
|
private _currency: KoSaveableObservable<string|undefined> = this._docInfo.documentSettingsJson.prop('currency');
|
||||||
const currencyObs = Observable.create(owner, currency);
|
private _engine: Computed<EngineCode|undefined> = Computed.create(this, (
|
||||||
const engineObs = Observable.create(owner, engine);
|
use => use(this._docInfo.documentSettingsJson.prop('engine'))
|
||||||
|
))
|
||||||
|
.onWrite(val => this._setEngine(val));
|
||||||
|
|
||||||
// Check if server supports engine choices - if so, we will allow user to pick.
|
constructor(private _gristDoc: GristDoc) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public buildDom() {
|
||||||
const canChangeEngine = getSupportedEngineChoices().length > 0;
|
const canChangeEngine = getSupportedEngineChoices().length > 0;
|
||||||
|
const docPageModel = this._gristDoc.docPageModel;
|
||||||
|
|
||||||
return {
|
return cssContainer(
|
||||||
title: t("Document Settings"),
|
cssHeader(t('Document Settings')),
|
||||||
body: [
|
cssDataRow(t("Time Zone:")),
|
||||||
cssDataRow(t("This document's ID (for API use):")),
|
cssDataRow(
|
||||||
cssDataRow(dom('tt', docPageModel.currentDocId.get())),
|
dom.create(buildTZAutocomplete, moment, fromKo(this._timezone), (val) => this._timezone.saveOnly(val))
|
||||||
cssDataRow(t("Time Zone:")),
|
),
|
||||||
cssDataRow(dom.create(buildTZAutocomplete, moment, timezoneObs, (val) => timezoneObs.set(val))),
|
cssDataRow(t("Locale:")),
|
||||||
cssDataRow(t("Locale:")),
|
cssDataRow(dom.create(buildLocaleSelect, this._locale)),
|
||||||
cssDataRow(dom.create(buildLocaleSelect, localeObs)),
|
cssDataRow(t("Currency:")),
|
||||||
cssDataRow(t("Currency:")),
|
cssDataRow(dom.domComputed(fromKo(this._locale), (l) =>
|
||||||
cssDataRow(dom.domComputed(localeObs, (l) =>
|
dom.create(buildCurrencyPicker, fromKo(this._currency), (val) => this._currency.saveOnly(val),
|
||||||
dom.create(buildCurrencyPicker, currencyObs, (val) => currencyObs.set(val),
|
{defaultCurrencyLabel: t("Local currency ({{currency}})", {currency: getCurrency(l)})})
|
||||||
{defaultCurrencyLabel: t("Local currency ({{currency}})", {currency: getCurrency(l)})})
|
)),
|
||||||
)),
|
canChangeEngine ? [
|
||||||
canChangeEngine ? [
|
// Small easter egg: you can click on the skull-and-crossbones to
|
||||||
// Small easter egg: you can click on the skull-and-crossbones to
|
// force a reload of the document.
|
||||||
// force a reload of the document.
|
cssDataRow(t("Engine (experimental {{span}} change at own risk):", {span:
|
||||||
cssDataRow(t("Engine (experimental {{span}} change at own risk):", {span:
|
dom('span', '☠',
|
||||||
dom('span', '☠',
|
dom.style('cursor', 'pointer'),
|
||||||
dom.style('cursor', 'pointer'),
|
dom.on('click', async () => {
|
||||||
dom.on('click', async () => {
|
await docPageModel.appModel.api.getDocAPI(docPageModel.currentDocId.get()!).forceReload();
|
||||||
await docPageModel.appModel.api.getDocAPI(docPageModel.currentDocId.get()!).forceReload();
|
document.location.reload();
|
||||||
document.location.reload();
|
}))
|
||||||
}))
|
|
||||||
})),
|
})),
|
||||||
select(engineObs, getSupportedEngineChoices()),
|
select(this._engine, getSupportedEngineChoices()),
|
||||||
] : null,
|
] : null,
|
||||||
],
|
cssHeader(t('API')),
|
||||||
// Modal label is "Save", unless engine is changed. If engine is changed, the document will
|
cssDataRow(t("This document's ID (for API use):")),
|
||||||
// need a reload to switch engines, so we replace the label with "Save and Reload".
|
cssDataRow(cssHoverWrapper(
|
||||||
saveLabel: dom.text((use) => (use(engineObs) === docSettings.engine) ? t("Save") : t("Save and Reload")),
|
dom('tt', docPageModel.currentDocId.get()),
|
||||||
saveFunc: async () => {
|
dom.on('click', async (e, d) => {
|
||||||
await docInfo.updateColValues({
|
e.stopImmediatePropagation();
|
||||||
timezone: timezoneObs.get(),
|
e.preventDefault();
|
||||||
documentSettings: JSON.stringify({
|
showTransientTooltip(d, t("Document ID copied to clipboard"), {
|
||||||
...docInfo.documentSettingsJson.peek(),
|
key: 'copy-document-id'
|
||||||
locale: localeObs.get(),
|
});
|
||||||
currency: currencyObs.get(),
|
await copyToClipboard(docPageModel.currentDocId.get()!);
|
||||||
engine: engineObs.get()
|
}),
|
||||||
})
|
)),
|
||||||
});
|
);
|
||||||
// Reload the document if the engine is changed.
|
}
|
||||||
if (engineObs.get() !== docSettings.engine) {
|
|
||||||
await docPageModel.appModel.api.getDocAPI(docPageModel.currentDocId.get()!).forceReload();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// If timezone, locale, or currency hasn't changed, disable the Save button.
|
|
||||||
saveDisabled: Computed.create(owner,
|
|
||||||
(use) => {
|
|
||||||
return (
|
|
||||||
use(timezoneObs) === docInfo.timezone.peek() &&
|
|
||||||
use(localeObs) === docSettings.locale &&
|
|
||||||
use(currencyObs) === docSettings.currency &&
|
|
||||||
use(engineObs) === docSettings.engine
|
|
||||||
);
|
|
||||||
})
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
private async _setEngine(val: EngineCode|undefined) {
|
||||||
|
confirmModal(t('Save and Reload'), t('Ok'), () => this._doSetEngine(val));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _doSetEngine(val: EngineCode|undefined) {
|
||||||
|
const docPageModel = this._gristDoc.docPageModel;
|
||||||
|
if (this._engine.get() !== val) {
|
||||||
|
await this._docInfo.documentSettingsJson.prop('engine').saveOnly(val);
|
||||||
|
await docPageModel.appModel.api.getDocAPI(docPageModel.currentDocId.get()!).forceReload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type LocaleItem = ACSelectItem & {locale?: string};
|
type LocaleItem = ACSelectItem & {locale?: string};
|
||||||
|
|
||||||
function buildLocaleSelect(
|
function buildLocaleSelect(
|
||||||
owner: IDisposableOwner,
|
owner: IDisposableOwner,
|
||||||
locale: Observable<string>
|
locale: KoSaveableObservable<string>,
|
||||||
) {
|
) {
|
||||||
const localeList: LocaleItem[] = locales.map(l => ({
|
const localeList: LocaleItem[] = locales.map(l => ({
|
||||||
value: l.name, // Use name as a value, we will translate the name into the locale on save
|
value: l.name, // Use name as a value, we will translate the name into the locale on save
|
||||||
@ -118,26 +115,58 @@ function buildLocaleSelect(
|
|||||||
// AC select will show the value (in this case locale) not a label when something is selected.
|
// AC select will show the value (in this case locale) not a label when something is selected.
|
||||||
// To show the label - create another observable that will be in sync with the value, but
|
// To show the label - create another observable that will be in sync with the value, but
|
||||||
// will contain text.
|
// will contain text.
|
||||||
const localeCode = locale.get();
|
const textObs = Computed.create(owner, use => {
|
||||||
const localeName = locales.find(l => l.code === localeCode)?.name || localeCode;
|
const localeCode = use(locale);
|
||||||
const textObs = Observable.create(owner, localeName);
|
const localeName = locales.find(l => l.code === localeCode)?.name || localeCode;
|
||||||
|
return localeName;
|
||||||
|
});
|
||||||
return buildACSelect(owner,
|
return buildACSelect(owner,
|
||||||
{
|
{
|
||||||
acIndex, valueObs: textObs,
|
acIndex, valueObs: textObs,
|
||||||
save(value, item: LocaleItem | undefined) {
|
save(_value, item: LocaleItem | undefined) {
|
||||||
if (!item) { throw new Error("Invalid locale"); }
|
if (!item) { throw new Error("Invalid locale"); }
|
||||||
textObs.set(value);
|
locale.saveOnly(item.locale!).catch(reportError);
|
||||||
locale.set(item.locale!);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
testId("locale-autocomplete")
|
testId("locale-autocomplete")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cssHeader = styled(docListHeader, `
|
||||||
|
margin-bottom: 0;
|
||||||
|
&:not(:first-of-type) {
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const cssContainer = styled('div', `
|
||||||
|
overflow-y: auto;
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
padding: 32px 64px 24px 64px;
|
||||||
|
max-width: 487px;
|
||||||
|
@media ${mediaSmall} {
|
||||||
|
& {
|
||||||
|
padding: 32px 24px 24px 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const cssHoverWrapper = styled('div', `
|
||||||
|
display: inline-block;
|
||||||
|
cursor: default;
|
||||||
|
color: ${theme.lightText};
|
||||||
|
transition: background 0.05s;
|
||||||
|
&:hover {
|
||||||
|
background: ${theme.lightHover};
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
// This matches the style used in showProfileModal in app/client/ui/AccountWidget.
|
// This matches the style used in showProfileModal in app/client/ui/AccountWidget.
|
||||||
const cssDataRow = styled('div', `
|
const cssDataRow = styled('div', `
|
||||||
margin: 16px 0px;
|
margin: 16px 0px;
|
||||||
font-size: ${vars.largeFontSize};
|
font-size: ${vars.largeFontSize};
|
||||||
|
color: ${theme.text};
|
||||||
`);
|
`);
|
||||||
|
|
||||||
// Check which engines can be selected in the UI, if any.
|
// Check which engines can be selected in the UI, if any.
|
||||||
|
@ -91,6 +91,14 @@ export function tools(owner: Disposable, gristDoc: GristDoc, leftPanelOpen: Obse
|
|||||||
),
|
),
|
||||||
testId('code'),
|
testId('code'),
|
||||||
),
|
),
|
||||||
|
cssPageEntry(
|
||||||
|
cssPageEntry.cls('-selected', (use) => use(gristDoc.activeViewId) === 'settings'),
|
||||||
|
cssPageLink(cssPageIcon('Settings'),
|
||||||
|
cssLinkText(t("Settings")),
|
||||||
|
urlState().setLinkUrl({docPage: 'settings'})
|
||||||
|
),
|
||||||
|
testId('settings'),
|
||||||
|
),
|
||||||
cssSpacer(),
|
cssSpacer(),
|
||||||
dom.maybe(docPageModel.currentDoc, (doc) => {
|
dom.maybe(docPageModel.currentDoc, (doc) => {
|
||||||
const ex = buildExamples().find(e => e.urlId === doc.urlId);
|
const ex = buildExamples().find(e => e.urlId === doc.urlId);
|
||||||
|
@ -10,7 +10,7 @@ import {Document} from 'app/common/UserAPI';
|
|||||||
import clone = require('lodash/clone');
|
import clone = require('lodash/clone');
|
||||||
import pickBy = require('lodash/pickBy');
|
import pickBy = require('lodash/pickBy');
|
||||||
|
|
||||||
export const SpecialDocPage = StringUnion('code', 'acl', 'data', 'GristDocTour');
|
export const SpecialDocPage = StringUnion('code', 'acl', 'data', 'GristDocTour', 'settings');
|
||||||
type SpecialDocPage = typeof SpecialDocPage.type;
|
type SpecialDocPage = typeof SpecialDocPage.type;
|
||||||
export type IDocPage = number | SpecialDocPage;
|
export type IDocPage = number | SpecialDocPage;
|
||||||
|
|
||||||
|
@ -266,7 +266,10 @@
|
|||||||
"Save": "Save",
|
"Save": "Save",
|
||||||
"Save and Reload": "Save and Reload",
|
"Save and Reload": "Save and Reload",
|
||||||
"This document's ID (for API use):": "This document's ID (for API use):",
|
"This document's ID (for API use):": "This document's ID (for API use):",
|
||||||
"Time Zone:": "Time Zone:"
|
"Time Zone:": "Time Zone:",
|
||||||
|
"API": "API",
|
||||||
|
"Document ID copied to clipboard": "Document ID copied to clipboard",
|
||||||
|
"Ok": "Ok"
|
||||||
},
|
},
|
||||||
"DocumentUsage": {
|
"DocumentUsage": {
|
||||||
"Attachments Size": "Attachments Size",
|
"Attachments Size": "Attachments Size",
|
||||||
|
@ -260,7 +260,8 @@
|
|||||||
"Local currency ({{currency}})": "Devise locale ({{currency}})",
|
"Local currency ({{currency}})": "Devise locale ({{currency}})",
|
||||||
"Engine (experimental {{span}} change at own risk):": "Moteur (expérimental {{span}} changez à vos risques et périls):",
|
"Engine (experimental {{span}} change at own risk):": "Moteur (expérimental {{span}} changez à vos risques et périls):",
|
||||||
"Save": "Enregistrer",
|
"Save": "Enregistrer",
|
||||||
"Save and Reload": "Enregistrer et recharger"
|
"Save and Reload": "Enregistrer et recharger",
|
||||||
|
"Document ID copied to clipboard": "Identifiant de document copié"
|
||||||
},
|
},
|
||||||
"DocumentUsage": {
|
"DocumentUsage": {
|
||||||
"Usage statistics are only available to users with full access to the document data.": "Les statistiques d'utilisation ne sont disponibles qu'aux utilisateurs ayant un accès complet aux données du document.",
|
"Usage statistics are only available to users with full access to the document data.": "Les statistiques d'utilisation ne sont disponibles qu'aux utilisateurs ayant un accès complet aux données du document.",
|
||||||
|
@ -2476,8 +2476,8 @@ export async function openProfileSettingsPage() {
|
|||||||
|
|
||||||
export async function openDocumentSettings() {
|
export async function openDocumentSettings() {
|
||||||
await openAccountMenu();
|
await openAccountMenu();
|
||||||
await driver.findContent('.grist-floating-menu li', 'Document Settings').click();
|
await driver.findContent('.grist-floating-menu a', 'Document Settings').click();
|
||||||
await driver.findWait('.test-modal-title', 5000);
|
await waitForUrl(/settings/, 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user