mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
wip: working implementation
todo: tests, notification of success
This commit is contained in:
parent
cc8c6003f0
commit
adc288b88a
@ -159,6 +159,7 @@ const cssItemName = styled('div', `
|
|||||||
`);
|
`);
|
||||||
|
|
||||||
const cssItemDescription = styled('div', `
|
const cssItemDescription = styled('div', `
|
||||||
|
max-width: 320px;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
margin-bottom: -1px; /* aligns with the value */
|
margin-bottom: -1px; /* aligns with the value */
|
||||||
`);
|
`);
|
||||||
|
@ -42,7 +42,6 @@ export class DocSettingsPage extends Disposable {
|
|||||||
private _timezone = this._docInfo.timezone;
|
private _timezone = this._docInfo.timezone;
|
||||||
private _locale: KoSaveableObservable<string> = this._docInfo.documentSettingsJson.prop('locale');
|
private _locale: KoSaveableObservable<string> = this._docInfo.documentSettingsJson.prop('locale');
|
||||||
private _currency: KoSaveableObservable<string|undefined> = this._docInfo.documentSettingsJson.prop('currency');
|
private _currency: KoSaveableObservable<string|undefined> = this._docInfo.documentSettingsJson.prop('currency');
|
||||||
// private _type: KoSaveableObservable<string|undefined> = this._docInfo.documentSettingsJson.prop('type');
|
|
||||||
private _engine: Computed<EngineCode|undefined> = Computed.create(this, (
|
private _engine: Computed<EngineCode|undefined> = Computed.create(this, (
|
||||||
use => use(this._docInfo.documentSettingsJson.prop('engine'))
|
use => use(this._docInfo.documentSettingsJson.prop('engine'))
|
||||||
))
|
))
|
||||||
@ -87,6 +86,18 @@ export class DocSettingsPage extends Disposable {
|
|||||||
{defaultCurrencyLabel: t("Local currency ({{currency}})", {currency: getCurrency(l)})})
|
{defaultCurrencyLabel: t("Local currency ({{currency}})", {currency: getCurrency(l)})})
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
dom.create(AdminSectionItem, {
|
||||||
|
id: 'templateMode',
|
||||||
|
name: t('Template mode'),
|
||||||
|
description: t('Special document mode'),
|
||||||
|
value: cssDocTypeContainer(
|
||||||
|
dom.create(displayCurrentType, docPageModel.type, /*docPageModel.currentDocId.get()*/),
|
||||||
|
cssSmallButton(t('Edit'),
|
||||||
|
dom.on('click', this._doSetDocumentType.bind(this, true))
|
||||||
|
)
|
||||||
|
),
|
||||||
|
disabled: isDocEditor ? false : t('Only available to document editors'),
|
||||||
|
}),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
dom.create(cssAdminSection, t('Data Engine'), [
|
dom.create(cssAdminSection, t('Data Engine'), [
|
||||||
@ -193,15 +204,6 @@ export class DocSettingsPage extends Disposable {
|
|||||||
value: cssSmallLinkButton(t('Manage webhooks'), urlState().setLinkUrl({docPage: 'webhook'})),
|
value: cssSmallLinkButton(t('Manage webhooks'), urlState().setLinkUrl({docPage: 'webhook'})),
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
dom.create(cssAdminSection, t('Document conversion'), [
|
|
||||||
dom.create(AdminSectionItem, {
|
|
||||||
id: 'document-type',
|
|
||||||
name: t('Document type'),
|
|
||||||
description: t('Convert the document'),
|
|
||||||
value: dom.create(buildTypeSelect, docPageModel.type, docPageModel.currentDocId.get()),
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,11 +234,11 @@ export class DocSettingsPage extends Disposable {
|
|||||||
const docPageModel = this._gristDoc.docPageModel;
|
const docPageModel = this._gristDoc.docPageModel;
|
||||||
modal((ctl, owner) => {
|
modal((ctl, owner) => {
|
||||||
this.onDispose(() => ctl.close());
|
this.onDispose(() => ctl.close());
|
||||||
const selected = Observable.create<Option>(owner, Option.Adhoc);
|
const selected = Observable.create<TimingModalOption>(owner, TimingModalOption.Adhoc);
|
||||||
const page = Observable.create<TimingModalPage>(owner, TimingModalPage.Start);
|
const page = Observable.create<TimingModalPage>(owner, TimingModalPage.Start);
|
||||||
|
|
||||||
const startTiming = async () => {
|
const startTiming = async () => {
|
||||||
if (selected.get() === Option.Reload) {
|
if (selected.get() === TimingModalOption.Reload) {
|
||||||
page.set(TimingModalPage.Spinner);
|
page.set(TimingModalPage.Spinner);
|
||||||
await this._gristDoc.docApi.startTiming();
|
await this._gristDoc.docApi.startTiming();
|
||||||
await docPageModel.appModel.api.getDocAPI(docPageModel.currentDocId.get()!).forceReload();
|
await docPageModel.appModel.api.getDocAPI(docPageModel.currentDocId.get()!).forceReload();
|
||||||
@ -251,7 +253,7 @@ export class DocSettingsPage extends Disposable {
|
|||||||
const startPage = () => [
|
const startPage = () => [
|
||||||
cssRadioCheckboxOptions(
|
cssRadioCheckboxOptions(
|
||||||
dom.style('max-width', '400px'),
|
dom.style('max-width', '400px'),
|
||||||
radioCheckboxOption(selected, Option.Adhoc, dom('div',
|
radioCheckboxOption(selected, TimingModalOption.Adhoc, dom('div',
|
||||||
dom('div',
|
dom('div',
|
||||||
dom('strong', t('Start timing')),
|
dom('strong', t('Start timing')),
|
||||||
),
|
),
|
||||||
@ -261,7 +263,7 @@ export class DocSettingsPage extends Disposable {
|
|||||||
),
|
),
|
||||||
testId('timing-modal-option-adhoc'),
|
testId('timing-modal-option-adhoc'),
|
||||||
)),
|
)),
|
||||||
radioCheckboxOption(selected, Option.Reload, dom('div',
|
radioCheckboxOption(selected, TimingModalOption.Reload, dom('div',
|
||||||
dom('div',
|
dom('div',
|
||||||
dom('strong', t('Time reload')),
|
dom('strong', t('Time reload')),
|
||||||
),
|
),
|
||||||
@ -297,6 +299,99 @@ export class DocSettingsPage extends Disposable {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _doSetDocumentType() {
|
||||||
|
const docPageModel = this._gristDoc.docPageModel;
|
||||||
|
modal((ctl, owner) => {
|
||||||
|
this.onDispose(() => ctl.close());
|
||||||
|
const currentDocType = docPageModel.type.get() as string;
|
||||||
|
let currentDocTypeOption;
|
||||||
|
switch (currentDocType) {
|
||||||
|
case "":
|
||||||
|
currentDocTypeOption = DocTypeOption.Regular;
|
||||||
|
break;
|
||||||
|
case "Template":
|
||||||
|
currentDocTypeOption = DocTypeOption.Template;
|
||||||
|
break;
|
||||||
|
case "Tutorial":
|
||||||
|
currentDocTypeOption = DocTypeOption.Tutorial;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
currentDocTypeOption = DocTypeOption.Regular;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selected = Observable.create<DocTypeOption>(owner, currentDocTypeOption);
|
||||||
|
|
||||||
|
const doSetDocumentType = async () => {
|
||||||
|
const docId = docPageModel.currentDocId.get();
|
||||||
|
let docType;
|
||||||
|
if (selected.get() === DocTypeOption.Regular) {
|
||||||
|
docType = "";
|
||||||
|
} else if (selected.get() === DocTypeOption.Template) {
|
||||||
|
docType = "Template";
|
||||||
|
} else {
|
||||||
|
docType = "Tutorial";
|
||||||
|
}
|
||||||
|
persistType(docType, docId)
|
||||||
|
.then(()=>window.location.reload())
|
||||||
|
.catch(err=>console.log(err));
|
||||||
|
|
||||||
|
ctl.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
const documentTypeOptions = () => [
|
||||||
|
cssRadioCheckboxOptions(
|
||||||
|
dom.style('max-width', '400px'),
|
||||||
|
radioCheckboxOption(selected, DocTypeOption.Regular, dom('div',
|
||||||
|
dom('div',
|
||||||
|
dom('strong', t('Regular document')),
|
||||||
|
),
|
||||||
|
dom('div',
|
||||||
|
dom.style('margin-top', '16px'),
|
||||||
|
dom('span', t('Regular document behavior, all users work on the same copy of the document.'))
|
||||||
|
),
|
||||||
|
testId('doctype-modal-option-regular'),
|
||||||
|
)),
|
||||||
|
radioCheckboxOption(selected, DocTypeOption.Template, dom('div',
|
||||||
|
dom('div',
|
||||||
|
dom('strong', t('Template')),
|
||||||
|
),
|
||||||
|
dom('div',
|
||||||
|
dom.style('margin-top', '16px'),
|
||||||
|
dom('span',
|
||||||
|
t('Document automatically opens in '),
|
||||||
|
dom('a', t('fiddle mode')),
|
||||||
|
t('. Any edit (open to anybody) will create a new unsaved copy.'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
testId('doctype-modal-option-template'),
|
||||||
|
)),
|
||||||
|
radioCheckboxOption(selected, DocTypeOption.Tutorial, dom('div',
|
||||||
|
dom('div',
|
||||||
|
dom('strong', t('Tutorial')),
|
||||||
|
),
|
||||||
|
dom('div',
|
||||||
|
dom.style('margin-top', '16px'),
|
||||||
|
dom('span', t('Document automatically opens with a new copy.')),
|
||||||
|
),
|
||||||
|
testId('doctype-modal-option-tutorial'),
|
||||||
|
))
|
||||||
|
),
|
||||||
|
cssModalButtons(
|
||||||
|
bigBasicButton(t('Cancel'), dom.on('click', () => ctl.close()), testId('doctype-modal-cancel')),
|
||||||
|
bigPrimaryButton(t(`Confirm change`),
|
||||||
|
dom.on('click', doSetDocumentType),
|
||||||
|
testId('doctype-modal-confirm'),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
];
|
||||||
|
return [
|
||||||
|
cssModalTitle(t(`Change nature of document`)),
|
||||||
|
documentTypeOptions(),
|
||||||
|
testId('timing-modal'),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private async _doSetEngine(val: EngineCode|undefined) {
|
private async _doSetEngine(val: EngineCode|undefined) {
|
||||||
const docPageModel = this._gristDoc.docPageModel;
|
const docPageModel = this._gristDoc.docPageModel;
|
||||||
if (this._engine.get() !== val) {
|
if (this._engine.get() !== val) {
|
||||||
@ -361,41 +456,32 @@ function buildLocaleSelect(
|
|||||||
|
|
||||||
type DocumentTypeItem = ACSelectItem & {type?: string};
|
type DocumentTypeItem = ACSelectItem & {type?: string};
|
||||||
|
|
||||||
function buildTypeSelect(
|
const typeList: DocumentTypeItem[] = [{
|
||||||
|
label: t('Regular'),
|
||||||
|
type: ''
|
||||||
|
}, {
|
||||||
|
label: t('Template'),
|
||||||
|
type: 'template'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Tutorial'),
|
||||||
|
type: 'tutorial'
|
||||||
|
}].map((el) => ({
|
||||||
|
...el,
|
||||||
|
value: el.label,
|
||||||
|
cleanText: el.label.trim().toLowerCase()
|
||||||
|
}));
|
||||||
|
|
||||||
|
function displayCurrentType(
|
||||||
owner: IDisposableOwner,
|
owner: IDisposableOwner,
|
||||||
type: Observable<DocumentType|null>,
|
type: Observable<DocumentType|null>,
|
||||||
id: string|undefined,
|
|
||||||
) {
|
) {
|
||||||
const typeList: DocumentTypeItem[] = [{
|
|
||||||
label: t('Regular'),
|
|
||||||
type: ''
|
|
||||||
}, {
|
|
||||||
label: t('Template'),
|
|
||||||
type: 'template'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('Tutorial'),
|
|
||||||
type: 'tutorial'
|
|
||||||
}].map((el) => ({
|
|
||||||
...el,
|
|
||||||
value: el.label,
|
|
||||||
cleanText: el.label.trim().toLowerCase()
|
|
||||||
}));
|
|
||||||
const typeObs = Computed.create(owner, use => {
|
const typeObs = Computed.create(owner, use => {
|
||||||
const typeCode = use(type)??"";
|
const typeCode = use(type)??"";
|
||||||
const typeName = typeList.find(ty => ty.type === typeCode)?.label || typeCode;
|
const typeName = typeList.find(ty => ty.type === typeCode)?.label || typeCode;
|
||||||
return typeName;
|
return typeName;
|
||||||
});
|
});
|
||||||
const acIndex = new ACIndexImpl<DocumentTypeItem>(typeList, {maxResults: 200, keepOrder: true});
|
return dom('div', typeObs.get());
|
||||||
return buildACSelect(owner, {
|
|
||||||
acIndex, valueObs: typeObs,
|
|
||||||
save(_value, item: DocumentTypeItem | undefined) {
|
|
||||||
if (!item) { throw new Error("Invalid DocumentType"); }
|
|
||||||
persistType(item.type!, id)
|
|
||||||
.then(()=>window.location.reload())
|
|
||||||
.catch(err=>console.log(err));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const cssContainer = styled('div', `
|
const cssContainer = styled('div', `
|
||||||
@ -517,7 +603,7 @@ enum TimingModalPage {
|
|||||||
/**
|
/**
|
||||||
* Enum for the different options in the timing modal.
|
* Enum for the different options in the timing modal.
|
||||||
*/
|
*/
|
||||||
enum Option {
|
enum TimingModalOption {
|
||||||
/**
|
/**
|
||||||
* Start timing and immediately forces a reload of the document and waits for the
|
* Start timing and immediately forces a reload of the document and waits for the
|
||||||
* document to be loaded, to show the results.
|
* document to be loaded, to show the results.
|
||||||
@ -529,6 +615,15 @@ enum Option {
|
|||||||
Adhoc,
|
Adhoc,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum for the different options in the document type Modal.
|
||||||
|
*/
|
||||||
|
enum DocTypeOption {
|
||||||
|
Regular,
|
||||||
|
Template,
|
||||||
|
Tutorial,
|
||||||
|
}
|
||||||
|
|
||||||
// A version that is not underlined, and on hover mouse pointer indicates that copy is available
|
// A version that is not underlined, and on hover mouse pointer indicates that copy is available
|
||||||
const cssCopyLink = styled(cssLink, `
|
const cssCopyLink = styled(cssLink, `
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
@ -568,3 +663,12 @@ const cssRedText = styled('span', `
|
|||||||
const cssAdminSection = styled(AdminSection, `
|
const cssAdminSection = styled(AdminSection, `
|
||||||
max-width: 750px;
|
max-width: 750px;
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
const cssDocTypeContainer = styled('div', `
|
||||||
|
display: flex;
|
||||||
|
width: 172px;
|
||||||
|
justify-content: space-between;
|
||||||
|
& > * {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
@ -26,7 +26,6 @@ export const cssLabel = styled('label', `
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
outline: none;
|
outline: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
Loading…
Reference in New Issue
Block a user