mirror of
				https://github.com/gristlabs/grist-core.git
				synced 2025-06-13 20:53:59 +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