(core) Formula UI redesign

Summary:
Redesigning column type section to make it more user-friendly. Introducing column behavior concept.
Column can be either:
- Empty Formula Column: initial state (user can convert to Formula/Data Column)
- Data Column: non formula column with or without trigger (with option to add trigger, or convert to formula)
- Formula Column: pure formula column, with an option to convert to data column with a trigger.

Test Plan: Existing tests.

Reviewers: dsagal

Reviewed By: dsagal

Differential Revision: https://phab.getgrist.com/D3092
This commit is contained in:
Jarosław Sadziński
2021-11-05 11:25:05 +01:00
parent 877542225d
commit e8e614c584
12 changed files with 532 additions and 126 deletions

View File

@@ -517,13 +517,22 @@ export class FieldBuilder extends Disposable {
/**
* Open the formula editor in the side pane. It will be positioned over refElem.
*/
public openSideFormulaEditor(editRow: DataRowModel, refElem: Element) {
public openSideFormulaEditor(
editRow: DataRowModel,
refElem: Element,
editValue?: string,
onSave?: (formula: string) => Promise<void>,
onCancel?: () => void) {
const editorHolder = openSideFormulaEditor({
gristDoc: this.gristDoc,
field: this.field,
editRow,
refElem,
editValue,
onSave,
onCancel
});
// Add editor to document holder - this will prevent multiple formula editor instances.
this.gristDoc.fieldEditorHolder.autoDispose(editorHolder);
}
}

View File

@@ -13,7 +13,7 @@ import {asyncOnce} from "app/common/AsyncCreate";
import {CellValue} from "app/common/DocActions";
import {isRaisedException} from 'app/common/gristTypes';
import * as gutil from 'app/common/gutil';
import {Disposable, Emitter, Holder, IDisposable, MultiHolder, Observable} from 'grainjs';
import {Disposable, Emitter, Holder, MultiHolder, Observable} from 'grainjs';
import isEqual = require('lodash/isEqual');
import { CellPosition } from "app/client/components/CellPosition";
@@ -380,7 +380,10 @@ export function openSideFormulaEditor(options: {
field: ViewFieldRec,
editRow: DataRowModel, // Needed to get exception value, if any.
refElem: Element, // Element in the side pane over which to position the editor.
}): IDisposable {
editValue?: string,
onSave?: (formula: string) => Promise<void>,
onCancel?: () => void,
}): Disposable {
const {gristDoc, field, editRow, refElem} = options;
const holder = MultiHolder.create(null);
const column = field.column();
@@ -388,17 +391,19 @@ export function openSideFormulaEditor(options: {
// AsyncOnce ensures it's called once even if triggered multiple times.
const saveEdit = asyncOnce(async () => {
const formula = editor.getCellValue();
if (formula !== column.formula.peek()) {
if (options.onSave) {
await options.onSave(formula as string);
} else if (formula !== column.formula.peek()) {
await column.updateColValues({formula});
}
holder.dispose(); // Deactivate the editor.
holder.dispose();
});
// These are the commands for while the editor is active.
const editCommands = {
fieldEditSave: () => { saveEdit().catch(reportError); },
fieldEditSaveHere: () => { saveEdit().catch(reportError); },
fieldEditCancel: () => { holder.dispose(); },
fieldEditCancel: () => { holder.dispose(); options.onCancel?.(); },
};
// Replace the item in the Holder with a new one, disposing the previous one.
@@ -407,7 +412,7 @@ export function openSideFormulaEditor(options: {
field,
cellValue: column.formula(),
formulaError: getFormulaError(gristDoc, editRow, column),
editValue: undefined,
editValue: options.editValue,
cursorPos: Number.POSITIVE_INFINITY, // Position of the caret within the editor.
commands: editCommands,
cssClass: 'formula_editor_sidepane',