mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Updating UI for Document Settings
Summary: Updating UI for Document Settings, by reusing components from Admin panel Test Plan: Existing Reviewers: georgegevoian Reviewed By: georgegevoian Differential Revision: https://phab.getgrist.com/D4250
This commit is contained in:
parent
3fc221f3e2
commit
00c8343e8a
@ -397,7 +397,7 @@ export const cssButtonGroup = styled('div', `
|
||||
|
||||
|
||||
export const cssSmallLinkButton = styled(basicButtonLink, `
|
||||
display: flex;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
min-height: 26px;
|
||||
|
@ -10,12 +10,10 @@ import {leftPanelBasic} from 'app/client/ui/LeftPanelCommon';
|
||||
import {pagePanels} from 'app/client/ui/PagePanels';
|
||||
import {SupportGristPage} from 'app/client/ui/SupportGristPage';
|
||||
import {createTopBarHome} from 'app/client/ui/TopBar';
|
||||
import {transition} from 'app/client/ui/transitions';
|
||||
import {cssBreadcrumbs, separator} from 'app/client/ui2018/breadcrumbs';
|
||||
import {basicButton} from 'app/client/ui2018/buttons';
|
||||
import {toggle} from 'app/client/ui2018/checkbox';
|
||||
import {mediaSmall, testId, theme, vars} from 'app/client/ui2018/cssVars';
|
||||
import {icon} from 'app/client/ui2018/icons';
|
||||
import {cssLink, makeLinks} from 'app/client/ui2018/links';
|
||||
import {SandboxingBootProbeDetails} from 'app/common/BootProbe';
|
||||
import {commonUrls, getPageTitleSuffix} from 'app/common/gristUrls';
|
||||
@ -23,8 +21,9 @@ import {InstallAPI, InstallAPIImpl, LatestVersion} from 'app/common/InstallAPI';
|
||||
import {naturalCompare} from 'app/common/SortFunc';
|
||||
import {getGristConfig} from 'app/common/urlUtils';
|
||||
import * as version from 'app/common/version';
|
||||
import {Computed, Disposable, dom, DomContents, IDisposable,
|
||||
import {Computed, Disposable, dom, IDisposable,
|
||||
IDisposableOwner, MultiHolder, Observable, styled} from 'grainjs';
|
||||
import {AdminSection, AdminSectionItem, HidableToggle} from 'app/client/ui/AdminPanelCss';
|
||||
|
||||
|
||||
const t = makeT('AdminPanel');
|
||||
@ -82,43 +81,41 @@ export class AdminPanel extends Disposable {
|
||||
return cssPageContainer(
|
||||
dom.cls('clipboard'),
|
||||
{tabIndex: "-1"},
|
||||
cssSection(
|
||||
cssSectionTitle(t('Support Grist')),
|
||||
this._buildItem(owner, {
|
||||
dom.create(AdminSection, t('Support Grist'), [
|
||||
dom.create(AdminSectionItem, {
|
||||
id: 'telemetry',
|
||||
name: t('Telemetry'),
|
||||
description: t('Help us make Grist better'),
|
||||
value: maybeSwitchToggle(this._supportGrist.getTelemetryOptInObservable()),
|
||||
value: dom.create(HidableToggle, this._supportGrist.getTelemetryOptInObservable()),
|
||||
expandedContent: this._supportGrist.buildTelemetrySection(),
|
||||
}),
|
||||
this._buildItem(owner, {
|
||||
dom.create(AdminSectionItem, {
|
||||
id: 'sponsor',
|
||||
name: t('Sponsor'),
|
||||
description: t('Support Grist Labs on GitHub'),
|
||||
value: this._supportGrist.buildSponsorshipSmallButton(),
|
||||
expandedContent: this._supportGrist.buildSponsorshipSection(),
|
||||
}),
|
||||
),
|
||||
cssSection(
|
||||
cssSectionTitle(t('Security Settings')),
|
||||
this._buildItem(owner, {
|
||||
]),
|
||||
dom.create(AdminSection, t('Security Settings'), [
|
||||
dom.create(AdminSectionItem, {
|
||||
id: 'sandboxing',
|
||||
name: t('Sandboxing'),
|
||||
description: t('Sandbox settings for data engine'),
|
||||
value: this._buildSandboxingDisplay(owner),
|
||||
expandedContent: this._buildSandboxingNotice(),
|
||||
}),
|
||||
),
|
||||
cssSection(
|
||||
cssSectionTitle(t('Version')),
|
||||
this._buildItem(owner, {
|
||||
]),
|
||||
|
||||
dom.create(AdminSection, t('Version'), [
|
||||
dom.create(AdminSectionItem, {
|
||||
id: 'version',
|
||||
name: t('Current'),
|
||||
description: t('Current version of Grist'),
|
||||
value: cssValueLabel(`Version ${version.version}`),
|
||||
}),
|
||||
this._buildUpdates(owner),
|
||||
),
|
||||
]),
|
||||
testId('admin-panel'),
|
||||
);
|
||||
}
|
||||
@ -137,9 +134,9 @@ export class AdminPanel extends Disposable {
|
||||
const configured = details.configured;
|
||||
return cssValueLabel(
|
||||
configured ?
|
||||
(success ? cssHappy(t('OK') + `: ${flavor}`) :
|
||||
cssError(t('Error') + `: ${flavor}`)) :
|
||||
cssError(t('unconfigured')));
|
||||
(success ? cssHappyText(t('OK') + `: ${flavor}`) :
|
||||
cssErrorText(t('Error') + `: ${flavor}`)) :
|
||||
cssErrorText(t('unconfigured')));
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -159,49 +156,6 @@ isolated from other documents and isolated from the network.'),
|
||||
];
|
||||
}
|
||||
|
||||
private _buildItem(owner: IDisposableOwner, options: {
|
||||
id: string,
|
||||
name: DomContents,
|
||||
description: DomContents,
|
||||
value: DomContents,
|
||||
expandedContent?: DomContents,
|
||||
}) {
|
||||
const itemContent = [
|
||||
cssItemName(options.name, testId(`admin-panel-item-name-${options.id}`)),
|
||||
cssItemDescription(options.description),
|
||||
cssItemValue(options.value,
|
||||
testId(`admin-panel-item-value-${options.id}`),
|
||||
dom.on('click', ev => ev.stopPropagation())),
|
||||
];
|
||||
if (options.expandedContent) {
|
||||
const isCollapsed = Observable.create(owner, true);
|
||||
return cssItem(
|
||||
cssItemShort(
|
||||
dom.domComputed(isCollapsed, (c) => cssCollapseIcon(c ? 'Expand' : 'Collapse')),
|
||||
itemContent,
|
||||
cssItemShort.cls('-expandable'),
|
||||
dom.on('click', () => isCollapsed.set(!isCollapsed.get())),
|
||||
),
|
||||
cssExpandedContentWrap(
|
||||
transition(isCollapsed, {
|
||||
prepare(elem, close) { elem.style.maxHeight = close ? elem.scrollHeight + 'px' : '0'; },
|
||||
run(elem, close) { elem.style.maxHeight = close ? '0' : elem.scrollHeight + 'px'; },
|
||||
finish(elem, close) { elem.style.maxHeight = close ? '0' : 'unset'; },
|
||||
}),
|
||||
cssExpandedContent(
|
||||
options.expandedContent,
|
||||
),
|
||||
),
|
||||
testId(`admin-panel-item-${options.id}`),
|
||||
);
|
||||
} else {
|
||||
return cssItem(
|
||||
cssItemShort(itemContent),
|
||||
testId(`admin-panel-item-${options.id}`),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private _buildUpdates(owner: MultiHolder) {
|
||||
// We can be in those states:
|
||||
enum State {
|
||||
@ -365,7 +319,7 @@ isolated from other documents and isolated from the network.'),
|
||||
}
|
||||
});
|
||||
|
||||
return this._buildItem(owner, {
|
||||
return dom.create(AdminSectionItem, {
|
||||
id: 'updates',
|
||||
name: t('Updates'),
|
||||
description: dom('span', testId('admin-panel-updates-message'), dom.text(description)),
|
||||
@ -422,10 +376,6 @@ isolated from other documents and isolated from the network.'),
|
||||
}
|
||||
}
|
||||
|
||||
function maybeSwitchToggle(value: Observable<boolean|null>): DomContents {
|
||||
return toggle(value, dom.hide((use) => use(value) === null));
|
||||
}
|
||||
|
||||
const cssPageContainer = styled('div', `
|
||||
overflow: auto;
|
||||
padding: 40px;
|
||||
@ -440,111 +390,12 @@ const cssPageContainer = styled('div', `
|
||||
}
|
||||
`);
|
||||
|
||||
const cssSection = styled('div', `
|
||||
padding: 24px;
|
||||
max-width: 600px;
|
||||
width: 100%;
|
||||
margin: 16px auto;
|
||||
border: 1px solid ${theme.widgetBorder};
|
||||
border-radius: 4px;
|
||||
|
||||
@media ${mediaSmall} {
|
||||
& {
|
||||
width: auto;
|
||||
padding: 12px;
|
||||
margin: 8px;
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const cssSectionTitle = styled('div', `
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin-bottom: 16px;
|
||||
font-size: ${vars.headerControlFontSize};
|
||||
font-weight: ${vars.headerControlTextWeight};
|
||||
`);
|
||||
|
||||
const cssItem = styled('div', `
|
||||
margin-top: 8px;
|
||||
`);
|
||||
|
||||
const cssItemShort = styled('div', `
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
margin: 0 -8px;
|
||||
border-radius: 4px;
|
||||
&-expandable {
|
||||
cursor: pointer;
|
||||
}
|
||||
&-expandable:hover {
|
||||
background-color: ${theme.lightHover};
|
||||
}
|
||||
`);
|
||||
|
||||
const cssItemName = styled('div', `
|
||||
width: 112px;
|
||||
font-weight: bold;
|
||||
font-size: ${vars.largeFontSize};
|
||||
&:first-child {
|
||||
margin-left: 24px;
|
||||
}
|
||||
@media ${mediaSmall} {
|
||||
& {
|
||||
width: calc(100% - 28px);
|
||||
}
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const cssItemDescription = styled('div', `
|
||||
margin-right: auto;
|
||||
`);
|
||||
|
||||
const cssItemValue = styled('div', `
|
||||
flex: none;
|
||||
margin: -16px;
|
||||
padding: 16px;
|
||||
cursor: auto;
|
||||
`);
|
||||
|
||||
const cssCollapseIcon = styled(icon, `
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 4px;
|
||||
margin-left: -4px;
|
||||
--icon-color: ${theme.lightText};
|
||||
`);
|
||||
|
||||
const cssExpandedContentWrap = styled('div', `
|
||||
transition: max-height 0.3s ease-in-out;
|
||||
overflow: hidden;
|
||||
max-height: 0;
|
||||
`);
|
||||
|
||||
const cssExpandedContent = styled('div', `
|
||||
margin-left: 24px;
|
||||
padding: 24px 0;
|
||||
border-bottom: 1px solid ${theme.widgetBorder};
|
||||
.${cssItem.className}:last-child & {
|
||||
padding-bottom: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
`);
|
||||
|
||||
const cssValueLabel = styled('div', `
|
||||
export const cssValueLabel = styled('div', `
|
||||
padding: 4px 8px;
|
||||
color: ${theme.text};
|
||||
border: 1px solid ${theme.inputBorder};
|
||||
border-radius: ${vars.controlBorderRadius};
|
||||
&-empty {
|
||||
visibility: hidden;
|
||||
content: " ";
|
||||
}
|
||||
`);
|
||||
|
||||
// A wrapper for the version details panel. Shows two columns.
|
||||
@ -591,10 +442,10 @@ const cssGrayed = styled('span', `
|
||||
color: ${theme.lightText};
|
||||
`);
|
||||
|
||||
export const cssError = styled('div', `
|
||||
const cssErrorText = styled('span', `
|
||||
color: ${theme.errorText};
|
||||
`);
|
||||
|
||||
export const cssHappy = styled('div', `
|
||||
const cssHappyText = styled('span', `
|
||||
color: ${theme.controlFg};
|
||||
`);
|
||||
|
194
app/client/ui/AdminPanelCss.ts
Normal file
194
app/client/ui/AdminPanelCss.ts
Normal file
@ -0,0 +1,194 @@
|
||||
import {transition} from 'app/client/ui/transitions';
|
||||
import {toggle} from 'app/client/ui2018/checkbox';
|
||||
import {mediaSmall, testId, theme, vars} from 'app/client/ui2018/cssVars';
|
||||
import {icon} from 'app/client/ui2018/icons';
|
||||
import {dom, DomContents, IDisposableOwner, Observable, styled} from 'grainjs';
|
||||
|
||||
export function HidableToggle(owner: IDisposableOwner, value: Observable<boolean|null>) {
|
||||
return toggle(value, dom.hide((use) => use(value) === null));
|
||||
}
|
||||
|
||||
export function AdminSection(owner: IDisposableOwner, title: DomContents, items: DomContents[]) {
|
||||
return cssSection(
|
||||
cssSectionTitle(title),
|
||||
...items,
|
||||
);
|
||||
}
|
||||
|
||||
export function AdminSectionItem(owner: IDisposableOwner, options: {
|
||||
id: string,
|
||||
name?: DomContents,
|
||||
description?: DomContents,
|
||||
value?: DomContents,
|
||||
expandedContent?: DomContents,
|
||||
}) {
|
||||
const itemContent = (...prefix: DomContents[]) => [
|
||||
cssItemName(
|
||||
...prefix,
|
||||
options.name,
|
||||
testId(`admin-panel-item-name-${options.id}`),
|
||||
prefix.length ? cssItemName.cls('-prefixed') : null,
|
||||
),
|
||||
cssItemDescription(options.description),
|
||||
cssItemValue(options.value,
|
||||
testId(`admin-panel-item-value-${options.id}`),
|
||||
dom.on('click', ev => ev.stopPropagation())),
|
||||
];
|
||||
if (options.expandedContent) {
|
||||
const isCollapsed = Observable.create(owner, true);
|
||||
return cssItem(
|
||||
cssItemShort(
|
||||
itemContent(dom.domComputed(isCollapsed, (c) => cssCollapseIcon(c ? 'Expand' : 'Collapse'))),
|
||||
cssItemShort.cls('-expandable'),
|
||||
dom.on('click', () => isCollapsed.set(!isCollapsed.get())),
|
||||
),
|
||||
cssExpandedContentWrap(
|
||||
transition(isCollapsed, {
|
||||
prepare(elem, close) { elem.style.maxHeight = close ? elem.scrollHeight + 'px' : '0'; },
|
||||
run(elem, close) { elem.style.maxHeight = close ? '0' : elem.scrollHeight + 'px'; },
|
||||
finish(elem, close) { elem.style.maxHeight = close ? '0' : 'unset'; },
|
||||
}),
|
||||
cssExpandedContent(
|
||||
options.expandedContent,
|
||||
),
|
||||
),
|
||||
testId(`admin-panel-item-${options.id}`),
|
||||
);
|
||||
} else {
|
||||
return cssItem(
|
||||
cssItemShort(itemContent()),
|
||||
testId(`admin-panel-item-${options.id}`),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const cssSection = styled('div', `
|
||||
padding: 24px;
|
||||
max-width: 600px;
|
||||
width: 100%;
|
||||
margin: 16px auto;
|
||||
border: 1px solid ${theme.widgetBorder};
|
||||
border-radius: 4px;
|
||||
& > div + div {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
@media ${mediaSmall} {
|
||||
& {
|
||||
width: auto;
|
||||
padding: 12px;
|
||||
margin: 8px;
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const cssSectionTitle = styled('div', `
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin-bottom: 8px;
|
||||
font-size: ${vars.headerControlFontSize};
|
||||
font-weight: ${vars.headerControlTextWeight};
|
||||
`);
|
||||
|
||||
const cssItem = styled('div', `
|
||||
margin-top: 8px;
|
||||
container-type: inline-size;
|
||||
container-name: line;
|
||||
`);
|
||||
|
||||
const cssItemShort = styled('div', `
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
margin: 0 -8px;
|
||||
border-radius: 4px;
|
||||
&-expandable {
|
||||
cursor: pointer;
|
||||
}
|
||||
&-expandable:hover {
|
||||
background-color: ${theme.lightHover};
|
||||
}
|
||||
|
||||
@container line (max-width: 500px) {
|
||||
& {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const cssItemName = styled('div', `
|
||||
width: 136px;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: ${vars.largeFontSize};
|
||||
padding-left: 24px;
|
||||
&-prefixed {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
@container line (max-width: 500px) {
|
||||
& {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
@media ${mediaSmall} {
|
||||
& {
|
||||
width: calc(100% - 28px);
|
||||
padding-left: 0;
|
||||
}
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const cssItemDescription = styled('div', `
|
||||
margin-right: auto;
|
||||
`);
|
||||
|
||||
const cssItemValue = styled('div', `
|
||||
flex: none;
|
||||
margin: -16px;
|
||||
padding: 16px;
|
||||
cursor: auto;
|
||||
`);
|
||||
|
||||
const cssCollapseIcon = styled(icon, `
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 4px;
|
||||
margin-left: -4px;
|
||||
--icon-color: ${theme.lightText};
|
||||
`);
|
||||
|
||||
const cssExpandedContentWrap = styled('div', `
|
||||
transition: max-height 0.3s ease-in-out;
|
||||
overflow: hidden;
|
||||
max-height: 0;
|
||||
`);
|
||||
|
||||
const cssExpandedContent = styled('div', `
|
||||
margin-left: 24px;
|
||||
padding: 24px 0;
|
||||
border-bottom: 1px solid ${theme.widgetBorder};
|
||||
.${cssItem.className}:last-child & {
|
||||
padding-bottom: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
@container line (max-width: 500px) {
|
||||
& {
|
||||
margin-left: 0px;
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export const cssValueLabel = styled('div', `
|
||||
padding: 4px 8px;
|
||||
color: ${theme.text};
|
||||
border: 1px solid ${theme.inputBorder};
|
||||
border-radius: ${vars.controlBorderRadius};
|
||||
`);
|
@ -2,6 +2,7 @@
|
||||
* This module export a component for editing some document settings consisting of the timezone,
|
||||
* (new settings to be added here ...).
|
||||
*/
|
||||
import {cssSmallButton, cssSmallLinkButton} from 'app/client/components/Forms/styles';
|
||||
import {GristDoc} from 'app/client/components/GristDoc';
|
||||
import {ACIndexImpl} from 'app/client/lib/ACIndex';
|
||||
import {ACSelectItem, buildACSelect} from 'app/client/lib/ACSelect';
|
||||
@ -11,16 +12,17 @@ import {reportError} from 'app/client/models/AppModel';
|
||||
import type {DocPageModel} from 'app/client/models/DocPageModel';
|
||||
import {urlState} from 'app/client/models/gristUrlState';
|
||||
import {KoSaveableObservable} from 'app/client/models/modelUtil';
|
||||
import {docListHeader} from 'app/client/ui/DocMenuCss';
|
||||
import {showTransientTooltip} from 'app/client/ui/tooltips';
|
||||
import {primaryButtonLink} from 'app/client/ui2018/buttons';
|
||||
import {mediaSmall, testId, theme, vars} from 'app/client/ui2018/cssVars';
|
||||
import {AdminSection, AdminSectionItem} from 'app/client/ui/AdminPanelCss';
|
||||
import {hoverTooltip, showTransientTooltip} from 'app/client/ui/tooltips';
|
||||
import {colors, mediaSmall, testId, theme} from 'app/client/ui2018/cssVars';
|
||||
import {icon} from 'app/client/ui2018/icons';
|
||||
import {cssLink} from 'app/client/ui2018/links';
|
||||
import {select} from 'app/client/ui2018/menus';
|
||||
import {confirmModal} from 'app/client/ui2018/modals';
|
||||
import {buildCurrencyPicker} from 'app/client/widgets/CurrencyPicker';
|
||||
import {buildTZAutocomplete} from 'app/client/widgets/TZAutocomplete';
|
||||
import {EngineCode} from 'app/common/DocumentSettings';
|
||||
import {GristLoadConfig} from 'app/common/gristUrls';
|
||||
import {commonUrls, GristLoadConfig} from 'app/common/gristUrls';
|
||||
import {propertyCompare} from 'app/common/gutil';
|
||||
import {getCurrency, locales} from 'app/common/Locales';
|
||||
import {Computed, Disposable, dom, fromKo, IDisposableOwner, styled} from 'grainjs';
|
||||
@ -39,6 +41,11 @@ export class DocSettingsPage extends Disposable {
|
||||
))
|
||||
.onWrite(val => this._setEngine(val));
|
||||
|
||||
private _engines = getSupportedEngineChoices().map((engine) => ({
|
||||
value: engine,
|
||||
label: engine === 'python3' ? t(`python3 (recommended)`) : t(`python2 (legacy)`),
|
||||
}));
|
||||
|
||||
constructor(private _gristDoc: GristDoc) {
|
||||
super();
|
||||
}
|
||||
@ -48,50 +55,118 @@ export class DocSettingsPage extends Disposable {
|
||||
const docPageModel = this._gristDoc.docPageModel;
|
||||
|
||||
return cssContainer(
|
||||
cssHeader(t('Document Settings')),
|
||||
cssDataRow(t("Time Zone:")),
|
||||
cssDataRow(
|
||||
dom.create(buildTZAutocomplete, moment, fromKo(this._timezone), (val) => this._timezone.saveOnly(val))
|
||||
),
|
||||
cssDataRow(t("Locale:")),
|
||||
cssDataRow(dom.create(buildLocaleSelect, this._locale)),
|
||||
cssDataRow(t("Currency:")),
|
||||
cssDataRow(dom.domComputed(fromKo(this._locale), (l) =>
|
||||
dom.create(buildCurrencyPicker, fromKo(this._currency), (val) => this._currency.saveOnly(val),
|
||||
{defaultCurrencyLabel: t("Local currency ({{currency}})", {currency: getCurrency(l)})})
|
||||
)),
|
||||
canChangeEngine ? cssDataRow([
|
||||
// Small easter egg: you can click on the skull-and-crossbones to
|
||||
// force a reload of the document.
|
||||
cssDataRow(t("Engine (experimental {{span}} change at own risk):", {span:
|
||||
dom('span', '☠',
|
||||
dom.style('cursor', 'pointer'),
|
||||
dom.on('click', async () => {
|
||||
await docPageModel.appModel.api.getDocAPI(docPageModel.currentDocId.get()!).forceReload();
|
||||
document.location.reload();
|
||||
}))
|
||||
})),
|
||||
select(this._engine, getSupportedEngineChoices()),
|
||||
]) : null,
|
||||
cssHeader(t('API')),
|
||||
cssDataRow(t("This document's ID (for API use):")),
|
||||
cssDataRow(cssHoverWrapper(
|
||||
dom('tt', docPageModel.currentDocId.get()),
|
||||
dom.on('click', async (e, d) => {
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
showTransientTooltip(d, t("Document ID copied to clipboard"), {
|
||||
key: 'copy-document-id'
|
||||
});
|
||||
await copyToClipboard(docPageModel.currentDocId.get()!);
|
||||
dom.create(AdminSection, t('Document Settings'), [
|
||||
dom.create(AdminSectionItem, {
|
||||
id: 'timezone',
|
||||
name: t('Time Zone'),
|
||||
description: t('Default for DateTime columns'),
|
||||
value: dom.create(cssTZAutoComplete, moment, fromKo(this._timezone), (val) => this._timezone.saveOnly(val)),
|
||||
}),
|
||||
)),
|
||||
cssDataRow(primaryButtonLink(t('API Console'), {
|
||||
target: '_blank',
|
||||
href: getApiConsoleLink(docPageModel),
|
||||
})),
|
||||
cssHeader(t('Webhooks'), cssBeta('Beta')),
|
||||
cssDataRow(primaryButtonLink(t('Manage Webhooks'), urlState().setLinkUrl({docPage: 'webhook'}))),
|
||||
dom.create(AdminSectionItem, {
|
||||
id: 'locale',
|
||||
name: t('Locale'),
|
||||
description: t('For number and date formats'),
|
||||
value: dom.create(cssLocalePicker, this._locale),
|
||||
}),
|
||||
dom.create(AdminSectionItem, {
|
||||
id: 'currency',
|
||||
name: t('Currency'),
|
||||
description: t('For currency columns'),
|
||||
value: dom.domComputed(fromKo(this._locale), (l) =>
|
||||
dom.create(cssCurrencyPicker, fromKo(this._currency), (val) => this._currency.saveOnly(val),
|
||||
{defaultCurrencyLabel: t("Local currency ({{currency}})", {currency: getCurrency(l)})})
|
||||
)
|
||||
}),
|
||||
]),
|
||||
|
||||
dom.create(AdminSection, t('Data Engine'), [
|
||||
// dom.create(AdminSectionItem, {
|
||||
// id: 'timings',
|
||||
// name: t('Formula times'),
|
||||
// description: t('Find slow formulas'),
|
||||
// value: dom('div', t('Coming soon')),
|
||||
// expandedContent: dom('div', t(
|
||||
// 'Once you start timing, Grist will measure the time it takes to evaluate each formula. ' +
|
||||
// 'This allows diagnosing which formulas are responsible for slow performance when a ' +
|
||||
// 'document is first open, or when a document responds to changes.'
|
||||
// )),
|
||||
// }),
|
||||
|
||||
dom.create(AdminSectionItem, {
|
||||
id: 'reload',
|
||||
name: t('Reload'),
|
||||
description: t('Hard reset of data engine'),
|
||||
value: cssSmallButton('Reload data engine', dom.on('click', async () => {
|
||||
await docPageModel.appModel.api.getDocAPI(docPageModel.currentDocId.get()!).forceReload();
|
||||
document.location.reload();
|
||||
}))
|
||||
}),
|
||||
|
||||
canChangeEngine ? dom.create(AdminSectionItem, {
|
||||
id: 'python',
|
||||
name: t('Python'),
|
||||
description: t('Python version used'),
|
||||
value: cssSelect(this._engine, this._engines),
|
||||
}) : null,
|
||||
]),
|
||||
|
||||
dom.create(AdminSection, t('API'), [
|
||||
dom.create(AdminSectionItem, {
|
||||
id: 'documentId',
|
||||
name: t('Document ID'),
|
||||
description: t('ID for API use'),
|
||||
value: cssHoverWrapper(
|
||||
cssInput(docPageModel.currentDocId.get(), {tabIndex: "-1"}, clickToSelect(), readonly()),
|
||||
cssCopyButton(
|
||||
cssIcon('Copy'),
|
||||
hoverTooltip(t('Copy to clipboard'), {
|
||||
key: TOOLTIP_KEY,
|
||||
}),
|
||||
copyHandler(() => docPageModel.currentDocId.get()!, t("Document ID copied to clipboard")),
|
||||
),
|
||||
),
|
||||
expandedContent: dom('div',
|
||||
cssWrap(
|
||||
t('Document ID to use whenever the REST API calls for {{docId}}. See {{apiURL}}', {
|
||||
apiURL: cssLink({href: commonUrls.helpAPI, target: '_blank'}, t('API documentation.')),
|
||||
docId: dom('code', 'docId')
|
||||
})
|
||||
),
|
||||
dom.domComputed(urlState().makeUrl({
|
||||
api: true,
|
||||
docPage: undefined,
|
||||
doc: docPageModel.currentDocId.get(),
|
||||
}), url => [
|
||||
cssWrap(t('Base doc URL: {{docApiUrl}}', {
|
||||
docApiUrl: cssCopyLink(
|
||||
{href: url},
|
||||
url,
|
||||
copyHandler(() => url, t("API URL copied to clipboard")),
|
||||
hoverTooltip(t('Copy to clipboard'), {
|
||||
key: TOOLTIP_KEY,
|
||||
}),
|
||||
)
|
||||
})),
|
||||
]),
|
||||
),
|
||||
}),
|
||||
dom.create(AdminSectionItem, {
|
||||
id: 'api-console',
|
||||
name: t('API Console'),
|
||||
description: t('Try API calls from the browser'),
|
||||
value: cssSmallLinkButton(t('API console'), {
|
||||
target: '_blank',
|
||||
href: getApiConsoleLink(docPageModel),
|
||||
}),
|
||||
}),
|
||||
|
||||
dom.create(AdminSectionItem, {
|
||||
id: 'webhooks',
|
||||
name: t('Webhooks'),
|
||||
description: t('Notify other services on doc changes'),
|
||||
value: cssSmallLinkButton(t('Manage webhooks'), urlState().setLinkUrl({docPage: 'webhook'})),
|
||||
}),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
@ -151,13 +226,6 @@ function buildLocaleSelect(
|
||||
);
|
||||
}
|
||||
|
||||
const cssHeader = styled(docListHeader, `
|
||||
margin-bottom: 0;
|
||||
&:not(:first-of-type) {
|
||||
margin-top: 40px;
|
||||
}
|
||||
`);
|
||||
|
||||
const cssContainer = styled('div', `
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
@ -170,33 +238,129 @@ const cssContainer = styled('div', `
|
||||
}
|
||||
`);
|
||||
|
||||
const cssHoverWrapper = styled('div', `
|
||||
display: inline-block;
|
||||
cursor: default;
|
||||
color: ${theme.lightText};
|
||||
transition: background 0.05s;
|
||||
const cssCopyButton = styled('div', `
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: 24px;
|
||||
right: 0;
|
||||
top: 0;
|
||||
--icon-color: ${theme.lightText};
|
||||
&:hover {
|
||||
background: ${theme.lightHover};
|
||||
--icon-color: ${colors.lightGreen};
|
||||
}
|
||||
`);
|
||||
|
||||
// This matches the style used in showProfileModal in app/client/ui/AccountWidget.
|
||||
const cssDataRow = styled('div', `
|
||||
margin: 16px 0px;
|
||||
font-size: ${vars.largeFontSize};
|
||||
color: ${theme.text};
|
||||
width: 360px;
|
||||
const cssIcon = styled(icon, `
|
||||
`);
|
||||
|
||||
const cssBeta = styled('sup', `
|
||||
text-transform: uppercase;
|
||||
color: ${theme.text};
|
||||
font-size: ${vars.smallFontSize};
|
||||
margin-left: 8px;
|
||||
const cssInput = styled('div', `
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
width: 100%;
|
||||
min-width: 180px;
|
||||
height: 100%;
|
||||
padding: 5px;
|
||||
padding-right: 20px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
`);
|
||||
|
||||
const cssHoverWrapper = styled('div', `
|
||||
max-width: 280px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
text-wrap: nowrap;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
transition: all 0.05s;
|
||||
border-radius: 4px;
|
||||
border-color: ${theme.inputBorder};
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
height: 30px;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
`);
|
||||
|
||||
// This matches the style used in showProfileModal in app/client/ui/AccountWidget.
|
||||
|
||||
|
||||
// Check which engines can be selected in the UI, if any.
|
||||
export function getSupportedEngineChoices(): EngineCode[] {
|
||||
const gristConfig: GristLoadConfig = (window as any).gristConfig || {};
|
||||
return gristConfig.supportEngines || [];
|
||||
}
|
||||
|
||||
const cssSelect = styled(select, `
|
||||
min-width: 170px; /* to match the width of the timezone picker */
|
||||
`);
|
||||
|
||||
const TOOLTIP_KEY = 'copy-on-settings';
|
||||
|
||||
|
||||
function copyHandler(value: () => string, confirmation: string) {
|
||||
return dom.on('click', async (e, d) => {
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
showTransientTooltip(d as Element, confirmation, {
|
||||
key: TOOLTIP_KEY
|
||||
});
|
||||
await copyToClipboard(value());
|
||||
});
|
||||
}
|
||||
|
||||
function readonly() {
|
||||
return [
|
||||
{ contentEditable: 'false', spellcheck: 'false' },
|
||||
];
|
||||
}
|
||||
|
||||
function clickToSelect() {
|
||||
return dom.on('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(e.target as Node);
|
||||
const selection = window.getSelection();
|
||||
if (selection) {
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// A version that is not underlined, and on hover mouse pointer indicates that copy is available
|
||||
const cssCopyLink = styled(cssLink, `
|
||||
word-wrap: break-word;
|
||||
&:hover {
|
||||
border-radius: 4px;
|
||||
text-decoration: none;
|
||||
background: ${theme.lightHover};
|
||||
outline-color: ${theme.linkHover};
|
||||
outline-offset: 1px;
|
||||
}
|
||||
`);
|
||||
|
||||
const cssAutoComplete = `
|
||||
width: 172px;
|
||||
cursor: pointer;
|
||||
& input {
|
||||
text-overflow: ellipsis;
|
||||
padding-right: 24px;
|
||||
}
|
||||
`;
|
||||
|
||||
const cssTZAutoComplete = styled(buildTZAutocomplete, cssAutoComplete);
|
||||
const cssCurrencyPicker = styled(buildCurrencyPicker, cssAutoComplete);
|
||||
const cssLocalePicker = styled(buildLocaleSelect, cssAutoComplete);
|
||||
|
||||
const cssWrap = styled('p', `
|
||||
overflow-wrap: anywhere;
|
||||
& * {
|
||||
word-break: break-all;
|
||||
}
|
||||
`);
|
||||
|
@ -88,6 +88,7 @@ export const commonUrls = {
|
||||
helpLinkKeys: "https://support.getgrist.com/examples/2021-04-link-keys",
|
||||
helpFilteringReferenceChoices: "https://support.getgrist.com/col-refs/#filtering-reference-choices-in-dropdown",
|
||||
helpSandboxing: "https://support.getgrist.com/self-managed/#how-do-i-sandbox-documents",
|
||||
helpAPI: 'https://support.getgrist.com/api',
|
||||
freeCoachingCall: getFreeCoachingCallUrl(),
|
||||
contactSupport: getContactSupportUrl(),
|
||||
plans: "https://www.getgrist.com/pricing",
|
||||
|
@ -109,7 +109,7 @@ describe('WebhookOverflow', function () {
|
||||
|
||||
async function openWebhookPageWithoutWaitForServer() {
|
||||
await openDocumentSettings();
|
||||
const button = await driver.findContentWait('a', /Manage Webhooks/, 3000);
|
||||
const button = await driver.findContentWait('a', /Manage Webhooks/i, 3000);
|
||||
await gu.scrollIntoView(button).click();
|
||||
await waitForWebhookPage();
|
||||
}
|
||||
|
@ -281,9 +281,9 @@ async function getField(rowNum: number, col: string) {
|
||||
}
|
||||
|
||||
async function openWebhookPage() {
|
||||
await gu.wipeToasts();
|
||||
await gu.openDocumentSettings();
|
||||
const button = await driver.findContentWait('a', /Manage Webhooks/, 3000);
|
||||
await gu.scrollIntoView(button).click();
|
||||
await gu.scrollIntoView(driver.findContentWait('a', /Manage Webhooks/i, 3000)).click();
|
||||
await waitForWebhookPage();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user