mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) JS error on creator panel and formula editor.
Summary: Fixing js error that happens when closing creator panel with active formula editor. Styling behavior menu with common styles. Test Plan: Browser tests Reviewers: dsagal Reviewed By: dsagal Differential Revision: https://phab.getgrist.com/D3150
This commit is contained in:
parent
551ea28fc4
commit
e482427e83
@ -625,10 +625,19 @@ export class GristDoc extends DisposableWithEvents {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert column to pure formula column.
|
// Updates formula for a column.
|
||||||
public async convertToFormula(colRefs: number, formula: string): Promise<void> {
|
public async updateFormula(colRef: number, formula: string): Promise<void> {
|
||||||
return this.docModel.columns.sendTableAction(
|
return this.docModel.columns.sendTableAction(
|
||||||
['UpdateRecord', colRefs, {
|
['UpdateRecord', colRef, {
|
||||||
|
formula,
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert column to pure formula column.
|
||||||
|
public async convertToFormula(colRef: number, formula: string): Promise<void> {
|
||||||
|
return this.docModel.columns.sendTableAction(
|
||||||
|
['UpdateRecord', colRef, {
|
||||||
isFormula: true,
|
isFormula: true,
|
||||||
formula,
|
formula,
|
||||||
recalcWhen: RecalcWhen.DEFAULT,
|
recalcWhen: RecalcWhen.DEFAULT,
|
||||||
|
@ -66,10 +66,11 @@ export function buildNameConfig(owner: MultiHolder, origColumn: ColumnRec, curso
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SaveHandler = (column: ColumnRec, formula: string) => Promise<void>;
|
||||||
type BuildEditor = (
|
type BuildEditor = (
|
||||||
cellElem: Element,
|
cellElem: Element,
|
||||||
editValue?: string,
|
editValue?: string,
|
||||||
onSave?: (formula: string) => Promise<void>,
|
onSave?: SaveHandler,
|
||||||
onCancel?: () => void) => void;
|
onCancel?: () => void) => void;
|
||||||
|
|
||||||
type BEHAVIOR = "empty"|"formula"|"data";
|
type BEHAVIOR = "empty"|"formula"|"data";
|
||||||
@ -111,6 +112,8 @@ export function buildFormulaConfig(
|
|||||||
|
|
||||||
// Clear state when column has changed
|
// Clear state when column has changed
|
||||||
owner.autoDispose(origColumn.id.subscribe(clearState));
|
owner.autoDispose(origColumn.id.subscribe(clearState));
|
||||||
|
owner.autoDispose(origColumn.formula.subscribe(clearState));
|
||||||
|
owner.autoDispose(origColumn.isFormula.subscribe(clearState));
|
||||||
|
|
||||||
// Menu helper that will show normal menu with some default options
|
// Menu helper that will show normal menu with some default options
|
||||||
const menu = (label: DomContents, options: DomElementArg[]) =>
|
const menu = (label: DomContents, options: DomElementArg[]) =>
|
||||||
@ -177,31 +180,44 @@ export function buildFormulaConfig(
|
|||||||
const setFormula = () => (maybeFormula.set(true), formulaField?.focus());
|
const setFormula = () => (maybeFormula.set(true), formulaField?.focus());
|
||||||
const setTrigger = () => (maybeTrigger.set(true), formulaField?.focus());
|
const setTrigger = () => (maybeTrigger.set(true), formulaField?.focus());
|
||||||
|
|
||||||
// Actions on save formula
|
// Actions on save formula. Those actions are using column that comes from FormulaEditor.
|
||||||
|
// Formula editor scope is broader then RightPanel, it can be disposed after RightPanel is closed,
|
||||||
|
// and in some cases, when window is in background, it won't be disposed at all when panel is closed.
|
||||||
|
|
||||||
// Converts column to formula column or updates formula on a formula column.
|
// Converts column to formula column.
|
||||||
const onSaveConvertToFormula = async (formula: string) => {
|
const onSaveConvertToFormula = async (column: ColumnRec, formula: string) => {
|
||||||
|
// For non formula column, we will not convert it to formula column when expression is empty,
|
||||||
|
// as it means we were trying to convert data column to formula column, but changed our mind.
|
||||||
const notBlank = Boolean(formula);
|
const notBlank = Boolean(formula);
|
||||||
const trueFormula = !maybeFormula.get();
|
// But when the column is a formula column, empty formula expression is acceptable (it will
|
||||||
if (notBlank || trueFormula) { await gristDoc.convertToFormula(origColumn.id.peek(), formula); }
|
// convert column to empty column).
|
||||||
clearState();
|
const trueFormula = column.formula.peek();
|
||||||
|
if (notBlank || trueFormula) { await gristDoc.convertToFormula(column.id.peek(), formula); }
|
||||||
|
// Clear state only when owner was not disposed
|
||||||
|
if (!owner.isDisposed()) {
|
||||||
|
clearState();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Updates formula or convert column to trigger formula column if necessary.
|
// Updates formula or convert column to trigger formula column if necessary.
|
||||||
const onSaveConvertToTrigger = async (formula: string) => {
|
const onSaveConvertToTrigger = async (column: ColumnRec, formula: string) => {
|
||||||
if (formula && maybeTrigger.get()) {
|
// If formula expression is not empty, and column was plain data column (without a formula)
|
||||||
// Convert column to trigger
|
if (formula && !column.hasTriggerFormula.peek()) {
|
||||||
await gristDoc.convertToTrigger(origColumn.id.peek(), formula);
|
// then convert column to a trigger formula column
|
||||||
} else if (origColumn.hasTriggerFormula.peek()) {
|
await gristDoc.convertToTrigger(column.id.peek(), formula);
|
||||||
// This is true trigger formula, just update the formula (or make it blank)
|
} else if (column.hasTriggerFormula.peek()) {
|
||||||
await origColumn.formula.setAndSave(formula);
|
// else, if it was already a trigger formula column, just update formula.
|
||||||
|
await gristDoc.updateFormula(column.id.peek(), formula);
|
||||||
|
}
|
||||||
|
// Clear state only when owner was not disposed
|
||||||
|
if (!owner.isDisposed()) {
|
||||||
|
clearState();
|
||||||
}
|
}
|
||||||
clearState();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const errorMessage = createFormulaErrorObs(owner, gristDoc, origColumn);
|
const errorMessage = createFormulaErrorObs(owner, gristDoc, origColumn);
|
||||||
// Helper that will create different flavors for formula builder.
|
// Helper that will create different flavors for formula builder.
|
||||||
const formulaBuilder = (onSave: (formula: string) => Promise<void>) => [
|
const formulaBuilder = (onSave: SaveHandler) => [
|
||||||
cssRow(formulaField = buildFormula(
|
cssRow(formulaField = buildFormula(
|
||||||
origColumn,
|
origColumn,
|
||||||
buildEditor,
|
buildEditor,
|
||||||
@ -286,7 +302,7 @@ function buildFormula(
|
|||||||
column: ColumnRec,
|
column: ColumnRec,
|
||||||
buildEditor: BuildEditor,
|
buildEditor: BuildEditor,
|
||||||
placeholder: string,
|
placeholder: string,
|
||||||
onSave?: (formula: string) => Promise<void>,
|
onSave?: SaveHandler,
|
||||||
onCancel?: () => void) {
|
onCancel?: () => void) {
|
||||||
return cssFieldFormula(column.formula, {placeholder, maxLines: 2},
|
return cssFieldFormula(column.formula, {placeholder, maxLines: 2},
|
||||||
dom.cls('formula_field_sidepane'),
|
dom.cls('formula_field_sidepane'),
|
||||||
|
@ -22,7 +22,7 @@ import {domAsync} from 'app/client/lib/domAsync';
|
|||||||
import * as imports from 'app/client/lib/imports';
|
import * as imports from 'app/client/lib/imports';
|
||||||
import {createSessionObs} from 'app/client/lib/sessionObs';
|
import {createSessionObs} from 'app/client/lib/sessionObs';
|
||||||
import {reportError} from 'app/client/models/AppModel';
|
import {reportError} from 'app/client/models/AppModel';
|
||||||
import {ViewSectionRec} from 'app/client/models/DocModel';
|
import {ColumnRec, ViewSectionRec} from 'app/client/models/DocModel';
|
||||||
import {GridOptions} from 'app/client/ui/GridOptions';
|
import {GridOptions} from 'app/client/ui/GridOptions';
|
||||||
import {attachPageWidgetPicker, IPageWidget, toPageWidget} from 'app/client/ui/PageWidgetPicker';
|
import {attachPageWidgetPicker, IPageWidget, toPageWidget} from 'app/client/ui/PageWidgetPicker';
|
||||||
import {linkFromId, linkId, selectBy} from 'app/client/ui/selectBy';
|
import {linkFromId, linkId, selectBy} from 'app/client/ui/selectBy';
|
||||||
@ -236,7 +236,7 @@ export class RightPanel extends Disposable {
|
|||||||
// Simulate user typing on the cell - open editor with an initial value.
|
// Simulate user typing on the cell - open editor with an initial value.
|
||||||
editValue?: string,
|
editValue?: string,
|
||||||
// Custom save handler.
|
// Custom save handler.
|
||||||
onSave?: (formula: string) => Promise<void>,
|
onSave?: (column: ColumnRec, formula: string) => Promise<void>,
|
||||||
// Custom cancel handler.
|
// Custom cancel handler.
|
||||||
onCancel?: () => void,) {
|
onCancel?: () => void,) {
|
||||||
const vsi = this._gristDoc.viewModel.activeSection().viewInstance();
|
const vsi = this._gristDoc.viewModel.activeSection().viewInstance();
|
||||||
|
@ -335,6 +335,7 @@ export function selectMenu(
|
|||||||
items: () => DomElementArg[],
|
items: () => DomElementArg[],
|
||||||
...args: IDomArgs<HTMLDivElement>
|
...args: IDomArgs<HTMLDivElement>
|
||||||
) {
|
) {
|
||||||
|
const _menu = cssSelectMenuElem(testId('select-menu'));
|
||||||
return cssSelectBtn(
|
return cssSelectBtn(
|
||||||
label,
|
label,
|
||||||
icon('Dropdown'),
|
icon('Dropdown'),
|
||||||
@ -342,7 +343,7 @@ export function selectMenu(
|
|||||||
items,
|
items,
|
||||||
{
|
{
|
||||||
...weasel.defaultMenuOptions,
|
...weasel.defaultMenuOptions,
|
||||||
menuCssClass: cssSelectMenuElem.className + ' grist-floating-menu',
|
menuCssClass: _menu.className + ' grist-floating-menu',
|
||||||
stretchToSelector : `.${cssSelectBtn.className}`,
|
stretchToSelector : `.${cssSelectBtn.className}`,
|
||||||
trigger : [(triggerElem, ctl) => {
|
trigger : [(triggerElem, ctl) => {
|
||||||
const isDisabled = () => triggerElem.classList.contains('disabled');
|
const isDisabled = () => triggerElem.classList.contains('disabled');
|
||||||
|
@ -522,7 +522,7 @@ export class FieldBuilder extends Disposable {
|
|||||||
editRow: DataRowModel,
|
editRow: DataRowModel,
|
||||||
refElem: Element,
|
refElem: Element,
|
||||||
editValue?: string,
|
editValue?: string,
|
||||||
onSave?: (formula: string) => Promise<void>,
|
onSave?: (column: ColumnRec, formula: string) => Promise<void>,
|
||||||
onCancel?: () => void) {
|
onCancel?: () => void) {
|
||||||
const editorHolder = openFormulaEditor({
|
const editorHolder = openFormulaEditor({
|
||||||
gristDoc: this.gristDoc,
|
gristDoc: this.gristDoc,
|
||||||
|
@ -383,7 +383,7 @@ export function openFormulaEditor(options: {
|
|||||||
// Element over which to position the editor.
|
// Element over which to position the editor.
|
||||||
refElem: Element,
|
refElem: Element,
|
||||||
editValue?: string,
|
editValue?: string,
|
||||||
onSave?: (formula: string) => Promise<void>,
|
onSave?: (column: ColumnRec, formula: string) => Promise<void>,
|
||||||
onCancel?: () => void,
|
onCancel?: () => void,
|
||||||
// Called after editor is created to set up editor cleanup (e.g. saving on click-away).
|
// Called after editor is created to set up editor cleanup (e.g. saving on click-away).
|
||||||
setupCleanup: (
|
setupCleanup: (
|
||||||
@ -401,7 +401,7 @@ export function openFormulaEditor(options: {
|
|||||||
const saveEdit = asyncOnce(async () => {
|
const saveEdit = asyncOnce(async () => {
|
||||||
const formula = editor.getCellValue();
|
const formula = editor.getCellValue();
|
||||||
if (options.onSave) {
|
if (options.onSave) {
|
||||||
await options.onSave(formula as string);
|
await options.onSave(column, formula as string);
|
||||||
} else if (formula !== column.formula.peek()) {
|
} else if (formula !== column.formula.peek()) {
|
||||||
await column.updateColValues({formula});
|
await column.updateColValues({formula});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user