mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Guessing column widget options when transforming from
Summary: When converting changing the type of Any column, try to guess the widgetOptions. Especially important for choice and choiceList types. Test Plan: Existing Reviewers: alexmojaki Reviewed By: alexmojaki Differential Revision: https://phab.getgrist.com/D4088
This commit is contained in:
parent
cc9a9ae8c5
commit
c7ba31eb7d
@ -17,15 +17,16 @@ import {dateTimeWidgetOptions, guessDateFormat, timeFormatOptions} from 'app/com
|
|||||||
import {TableData} from 'app/common/TableData';
|
import {TableData} from 'app/common/TableData';
|
||||||
import {decodeObject} from 'app/plugin/objtypes';
|
import {decodeObject} from 'app/plugin/objtypes';
|
||||||
|
|
||||||
interface ColInfo {
|
interface PrepColInfo {
|
||||||
type: string;
|
type: string;
|
||||||
isFormula: boolean;
|
isFormula: boolean;
|
||||||
formula: string;
|
formula?: string;
|
||||||
visibleCol: number;
|
visibleCol: number;
|
||||||
widgetOptions?: string;
|
widgetOptions?: string;
|
||||||
rules: gristTypes.RefListValue
|
rules: gristTypes.RefListValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the suggested full type for `column` given a desired pure type to convert it to.
|
* Returns the suggested full type for `column` given a desired pure type to convert it to.
|
||||||
* Specifically, a pure type of "DateTime" returns a full type of "DateTime:{timezone}", and "Ref"
|
* Specifically, a pure type of "DateTime" returns a full type of "DateTime:{timezone}", and "Ref"
|
||||||
@ -85,8 +86,14 @@ function getRefTableIdFromData(docModel: DocModel, column: ColumnRec): string|nu
|
|||||||
// ColInfo to use for the transform column. Note that isFormula will be set to true, and formula
|
// ColInfo to use for the transform column. Note that isFormula will be set to true, and formula
|
||||||
// will be set to the expression to compute the new values from the old ones.
|
// will be set to the expression to compute the new values from the old ones.
|
||||||
// @param toTypeMaybeFull: Type to convert the column to, either full ('Ref:Foo') or pure ('Ref').
|
// @param toTypeMaybeFull: Type to convert the column to, either full ('Ref:Foo') or pure ('Ref').
|
||||||
export async function prepTransformColInfo(docModel: DocModel, origCol: ColumnRec, origDisplayCol: ColumnRec,
|
export async function prepTransformColInfo(options: {
|
||||||
toTypeMaybeFull: string, convertedRef: string): Promise<ColInfo> {
|
docModel: DocModel;
|
||||||
|
origCol: ColumnRec;
|
||||||
|
origDisplayCol: ColumnRec;
|
||||||
|
toTypeMaybeFull: string;
|
||||||
|
convertedRef?: string
|
||||||
|
}): Promise<PrepColInfo> {
|
||||||
|
const {docModel, origCol, origDisplayCol, toTypeMaybeFull, convertedRef} = options;
|
||||||
const toType = gristTypes.extractTypeFromColType(toTypeMaybeFull);
|
const toType = gristTypes.extractTypeFromColType(toTypeMaybeFull);
|
||||||
const tableData: TableData = docModel.docData.getTable(origCol.table().tableId())!;
|
const tableData: TableData = docModel.docData.getTable(origCol.table().tableId())!;
|
||||||
|
|
||||||
@ -115,7 +122,7 @@ export async function prepTransformColInfo(docModel: DocModel, origCol: ColumnRe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const colInfo: ColInfo = {
|
const colInfo: PrepColInfo = {
|
||||||
type: addColTypeSuffix(toTypeMaybeFull, origCol, docModel),
|
type: addColTypeSuffix(toTypeMaybeFull, origCol, docModel),
|
||||||
isFormula: true,
|
isFormula: true,
|
||||||
visibleCol: 0,
|
visibleCol: 0,
|
||||||
@ -123,6 +130,71 @@ export async function prepTransformColInfo(docModel: DocModel, origCol: ColumnRe
|
|||||||
rules: origCol.rules(),
|
rules: origCol.rules(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
switch (toType) {
|
||||||
|
case 'Ref':
|
||||||
|
case 'RefList':
|
||||||
|
{
|
||||||
|
// Set suggested destination table and visible column.
|
||||||
|
// Undefined if toTypeMaybeFull is a pure type (e.g. converting to Ref before a table is chosen).
|
||||||
|
const optTableId = gutil.removePrefix(toTypeMaybeFull, `${toType}:`) || undefined;
|
||||||
|
|
||||||
|
let suggestedColRef: number;
|
||||||
|
let suggestedTableId: string;
|
||||||
|
const origColTypeInfo = gristTypes.extractInfoFromColType(origCol.type.peek());
|
||||||
|
if (!optTableId && (origColTypeInfo.type === "Ref" || origColTypeInfo.type === "RefList")) {
|
||||||
|
// When converting between Ref and Reflist, initially suggest the same table and visible column.
|
||||||
|
// When converting, if the table is the same, it's a special case.
|
||||||
|
// The visible column will not affect conversion.
|
||||||
|
// It will simply wrap the reference (row ID) in a list or extract the one element of a reference list.
|
||||||
|
suggestedColRef = origCol.visibleCol.peek();
|
||||||
|
suggestedTableId = origColTypeInfo.tableId;
|
||||||
|
} else {
|
||||||
|
// Finds a reference suggestion column and sets it as the current reference value.
|
||||||
|
const columnData = tableData.getDistinctValues(origDisplayCol.colId(), 100);
|
||||||
|
if (!columnData) { break; }
|
||||||
|
columnData.delete(gristTypes.getDefaultForType(origCol.type()));
|
||||||
|
|
||||||
|
// 'findColFromValues' function requires an array since it sends the values to the sandbox.
|
||||||
|
const matches: number[] = await docModel.docData.findColFromValues(Array.from(columnData), 2, optTableId);
|
||||||
|
suggestedColRef = matches.find(match => match !== origCol.getRowId())!;
|
||||||
|
if (!suggestedColRef) { break; }
|
||||||
|
const suggestedCol = docModel.columns.getRowModel(suggestedColRef);
|
||||||
|
suggestedTableId = suggestedCol.table().tableId();
|
||||||
|
if (optTableId && suggestedTableId !== optTableId) {
|
||||||
|
console.warn("Inappropriate column received from findColFromValues");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
colInfo.type = `${toType}:${suggestedTableId}`;
|
||||||
|
colInfo.visibleCol = suggestedColRef;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
widgetOptions = guessWidgetOptionsSync({docModel, origCol, toTypeMaybeFull, widgetOptions});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(widgetOptions).length) {
|
||||||
|
colInfo.widgetOptions = JSON.stringify(widgetOptions);
|
||||||
|
}
|
||||||
|
return colInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to guess widget options for a given column, based on the type it's being converted to.
|
||||||
|
* It works synchronously, so it can't reason about options that require async calls to the data-engine.
|
||||||
|
*/
|
||||||
|
export function guessWidgetOptionsSync(options: {
|
||||||
|
docModel: DocModel;
|
||||||
|
origCol: ColumnRec;
|
||||||
|
toTypeMaybeFull: string;
|
||||||
|
widgetOptions?: any;
|
||||||
|
}): object {
|
||||||
|
const {docModel, origCol, toTypeMaybeFull} = options;
|
||||||
|
const toType = gristTypes.extractTypeFromColType(toTypeMaybeFull);
|
||||||
|
let widgetOptions = {...(options.widgetOptions ?? {})};
|
||||||
|
const tableData: TableData = docModel.docData.getTable(origCol.table().tableId())!;
|
||||||
|
const visibleCol = origCol.visibleColModel();
|
||||||
|
const sourceCol = visibleCol.getRowId() !== 0 ? visibleCol : origCol;
|
||||||
switch (toType) {
|
switch (toType) {
|
||||||
case 'Bool':
|
case 'Bool':
|
||||||
// Most types use a TextBox as the default widget.
|
// Most types use a TextBox as the default widget.
|
||||||
@ -162,9 +234,9 @@ export async function prepTransformColInfo(docModel: DocModel, origCol: ColumnRe
|
|||||||
// trouble than desired behavior. For many choices, recommend using a Ref to helper table.
|
// trouble than desired behavior. For many choices, recommend using a Ref to helper table.
|
||||||
const columnData = tableData.getDistinctValues(sourceCol.colId(), 100);
|
const columnData = tableData.getDistinctValues(sourceCol.colId(), 100);
|
||||||
if (columnData) {
|
if (columnData) {
|
||||||
const choices = Array.from(columnData, String).filter((choice) => {
|
const choices = Array.from(columnData).filter(isNonNullish)
|
||||||
return choice !== null && choice.trim() !== '';
|
.map(v => String(v).trim())
|
||||||
});
|
.filter(Boolean);
|
||||||
widgetOptions = {...widgetOptions, choices};
|
widgetOptions = {...widgetOptions, choices};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -181,7 +253,7 @@ export async function prepTransformColInfo(docModel: DocModel, origCol: ColumnRe
|
|||||||
value = String(decodeObject(value)).trim();
|
value = String(decodeObject(value)).trim();
|
||||||
const tags: unknown[] = (value.startsWith('[') && gutil.safeJsonParse(value, null)) || csvDecodeRow(value);
|
const tags: unknown[] = (value.startsWith('[') && gutil.safeJsonParse(value, null)) || csvDecodeRow(value);
|
||||||
for (const tag of tags) {
|
for (const tag of tags) {
|
||||||
const choice = String(tag).trim();
|
const choice = !tag ? '' : String(tag).trim();
|
||||||
if (choice === '') { continue; }
|
if (choice === '') { continue; }
|
||||||
choices.add(choice);
|
choices.add(choice);
|
||||||
if (choices.size > 100) { break; } // Don't suggest excessively many choices.
|
if (choices.size > 100) { break; } // Don't suggest excessively many choices.
|
||||||
@ -191,52 +263,11 @@ export async function prepTransformColInfo(docModel: DocModel, origCol: ColumnRe
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'Ref':
|
|
||||||
case 'RefList':
|
|
||||||
{
|
|
||||||
// Set suggested destination table and visible column.
|
|
||||||
// Undefined if toTypeMaybeFull is a pure type (e.g. converting to Ref before a table is chosen).
|
|
||||||
const optTableId = gutil.removePrefix(toTypeMaybeFull, `${toType}:`) || undefined;
|
|
||||||
|
|
||||||
let suggestedColRef: number;
|
|
||||||
let suggestedTableId: string;
|
|
||||||
const origColTypeInfo = gristTypes.extractInfoFromColType(origCol.type.peek());
|
|
||||||
if (!optTableId && (origColTypeInfo.type === "Ref" || origColTypeInfo.type === "RefList")) {
|
|
||||||
// When converting between Ref and Reflist, initially suggest the same table and visible column.
|
|
||||||
// When converting, if the table is the same, it's a special case.
|
|
||||||
// The visible column will not affect conversion.
|
|
||||||
// It will simply wrap the reference (row ID) in a list or extract the one element of a reference list.
|
|
||||||
suggestedColRef = origCol.visibleCol.peek();
|
|
||||||
suggestedTableId = origColTypeInfo.tableId;
|
|
||||||
} else {
|
|
||||||
// Finds a reference suggestion column and sets it as the current reference value.
|
|
||||||
const columnData = tableData.getDistinctValues(origDisplayCol.colId(), 100);
|
|
||||||
if (!columnData) { break; }
|
|
||||||
columnData.delete(gristTypes.getDefaultForType(origCol.type()));
|
|
||||||
|
|
||||||
// 'findColFromValues' function requires an array since it sends the values to the sandbox.
|
|
||||||
const matches: number[] = await docModel.docData.findColFromValues(Array.from(columnData), 2, optTableId);
|
|
||||||
suggestedColRef = matches.find(match => match !== origCol.getRowId())!;
|
|
||||||
if (!suggestedColRef) { break; }
|
|
||||||
const suggestedCol = docModel.columns.getRowModel(suggestedColRef);
|
|
||||||
suggestedTableId = suggestedCol.table().tableId();
|
|
||||||
if (optTableId && suggestedTableId !== optTableId) {
|
|
||||||
console.warn("Inappropriate column received from findColFromValues");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
colInfo.type = `${toType}:${suggestedTableId}`;
|
|
||||||
colInfo.visibleCol = suggestedColRef;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return widgetOptions;
|
||||||
if (Object.keys(widgetOptions).length) {
|
|
||||||
colInfo.widgetOptions = JSON.stringify(widgetOptions);
|
|
||||||
}
|
|
||||||
return colInfo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Given the transformCol, calls (if needed) a user action to update its displayCol.
|
// Given the transformCol, calls (if needed) a user action to update its displayCol.
|
||||||
export async function setDisplayFormula(
|
export async function setDisplayFormula(
|
||||||
docModel: DocModel, transformCol: ColumnRec, visibleCol?: number
|
docModel: DocModel, transformCol: ColumnRec, visibleCol?: number
|
||||||
|
@ -100,9 +100,13 @@ export class TypeTransform extends ColumnTransform {
|
|||||||
const gristHelper_TransformRef = newColInfos[1].colRef;
|
const gristHelper_TransformRef = newColInfos[1].colRef;
|
||||||
this.transformColumn = docModel.columns.getRowModel(gristHelper_TransformRef);
|
this.transformColumn = docModel.columns.getRowModel(gristHelper_TransformRef);
|
||||||
this._convertColumn = docModel.columns.getRowModel(gristHelper_ConvertedRef);
|
this._convertColumn = docModel.columns.getRowModel(gristHelper_ConvertedRef);
|
||||||
const colInfo = await TypeConversion.prepTransformColInfo(
|
const colInfo = await TypeConversion.prepTransformColInfo({
|
||||||
docModel, this.origColumn,
|
docModel,
|
||||||
this.origDisplayCol, toType, this._convertColumn.colId.peek());
|
origCol: this.origColumn,
|
||||||
|
origDisplayCol: this.origDisplayCol,
|
||||||
|
toTypeMaybeFull: toType,
|
||||||
|
convertedRef: this._convertColumn.colId.peek()
|
||||||
|
});
|
||||||
// NOTE: We could add rules with AddColumn action, but there are some optimizations that converts array values.
|
// NOTE: We could add rules with AddColumn action, but there are some optimizations that converts array values.
|
||||||
const rules = colInfo.rules;
|
const rules = colInfo.rules;
|
||||||
delete (colInfo as any).rules;
|
delete (colInfo as any).rules;
|
||||||
@ -165,9 +169,13 @@ export class TypeTransform extends ColumnTransform {
|
|||||||
*/
|
*/
|
||||||
public async setType(toType: string) {
|
public async setType(toType: string) {
|
||||||
const docModel = this.gristDoc.docModel;
|
const docModel = this.gristDoc.docModel;
|
||||||
const colInfo = await TypeConversion.prepTransformColInfo(
|
const colInfo = await TypeConversion.prepTransformColInfo({
|
||||||
docModel, this.origColumn, this.origDisplayCol,
|
docModel,
|
||||||
toType, this._convertColumn.colId.peek());
|
origCol: this.origColumn,
|
||||||
|
origDisplayCol: this.origDisplayCol,
|
||||||
|
toTypeMaybeFull: toType,
|
||||||
|
convertedRef: this._convertColumn.colId.peek()
|
||||||
|
});
|
||||||
const tcol = this.transformColumn;
|
const tcol = this.transformColumn;
|
||||||
await tcol.updateColValues(colInfo as any);
|
await tcol.updateColValues(colInfo as any);
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import { ColumnTransform } from 'app/client/components/ColumnTransform';
|
|||||||
import { Cursor } from 'app/client/components/Cursor';
|
import { Cursor } from 'app/client/components/Cursor';
|
||||||
import { FormulaTransform } from 'app/client/components/FormulaTransform';
|
import { FormulaTransform } from 'app/client/components/FormulaTransform';
|
||||||
import { GristDoc } from 'app/client/components/GristDoc';
|
import { GristDoc } from 'app/client/components/GristDoc';
|
||||||
import { addColTypeSuffix } from 'app/client/components/TypeConversion';
|
import { addColTypeSuffix, guessWidgetOptionsSync } from 'app/client/components/TypeConversion';
|
||||||
import { TypeTransform } from 'app/client/components/TypeTransform';
|
import { TypeTransform } from 'app/client/components/TypeTransform';
|
||||||
import { FloatingEditor } from 'app/client/widgets/FloatingEditor';
|
import { FloatingEditor } from 'app/client/widgets/FloatingEditor';
|
||||||
import { UnsavedChange } from 'app/client/components/UnsavedChanges';
|
import { UnsavedChange } from 'app/client/components/UnsavedChanges';
|
||||||
@ -36,8 +36,9 @@ import * as UserTypeImpl from 'app/client/widgets/UserTypeImpl';
|
|||||||
import * as gristTypes from 'app/common/gristTypes';
|
import * as gristTypes from 'app/common/gristTypes';
|
||||||
import { getReferencedTableId, isFullReferencingType } from 'app/common/gristTypes';
|
import { getReferencedTableId, isFullReferencingType } from 'app/common/gristTypes';
|
||||||
import { CellValue } from 'app/plugin/GristData';
|
import { CellValue } from 'app/plugin/GristData';
|
||||||
import { Computed, Disposable, fromKo, dom as grainjsDom,
|
import { bundleChanges, Computed, Disposable, fromKo,
|
||||||
makeTestId, MultiHolder, Observable, styled, toKo } from 'grainjs';
|
dom as grainjsDom, makeTestId, MultiHolder, Observable, styled, toKo } from 'grainjs';
|
||||||
|
import isEqual from 'lodash/isEqual';
|
||||||
import * as ko from 'knockout';
|
import * as ko from 'knockout';
|
||||||
import * as _ from 'underscore';
|
import * as _ from 'underscore';
|
||||||
|
|
||||||
@ -160,9 +161,9 @@ export class FieldBuilder extends Disposable {
|
|||||||
write: val => {
|
write: val => {
|
||||||
const type = this.field.column().type();
|
const type = this.field.column().type();
|
||||||
if (type.startsWith('Ref:')) {
|
if (type.startsWith('Ref:')) {
|
||||||
void this._setType(`Ref:${val}`);
|
this._setType(`Ref:${val}`);
|
||||||
} else {
|
} else {
|
||||||
void this._setType(`RefList:${val}`);
|
this._setType(`RefList:${val}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@ -331,28 +332,57 @@ export class FieldBuilder extends Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to set the column type to newType.
|
// Helper function to set the column type to newType.
|
||||||
public _setType(newType: string): Promise<unknown>|undefined {
|
public _setType(newType: string): void {
|
||||||
|
// If the original column is a formula, we won't be showing any transform UI, so we can
|
||||||
|
// just set the type directly. We test original column as this field might be in the middle
|
||||||
|
// of transformation and temporary be connected to a helper column (but formula columns are
|
||||||
|
// never transformed using UI).
|
||||||
if (this.origColumn.isFormula()) {
|
if (this.origColumn.isFormula()) {
|
||||||
// Do not type transform a new/empty column or a formula column. Just make a best guess for
|
// Do not type transform a new/empty column or a formula column. Just make a best guess for
|
||||||
// the full type, and set it. If multiple columns are selected (and all are formulas/empty),
|
// the full type, and set it. If multiple columns are selected (and all are formulas/empty),
|
||||||
// then we will set the type for all of them using full type guessed from the first column.
|
// then we will set the type for all of them using full type guessed from the first column.
|
||||||
const column = this.field.column();
|
const column = this.field.column(); // same as this.origColumn.
|
||||||
const calculatedType = addColTypeSuffix(newType, column, this._docModel);
|
const calculatedType = addColTypeSuffix(newType, column, this._docModel);
|
||||||
|
const fields = this.field.viewSection.peek().selectedFields.peek();
|
||||||
// If we selected multiple empty/formula columns, make the change for all of them.
|
// If we selected multiple empty/formula columns, make the change for all of them.
|
||||||
if (this.field.viewSection.peek().selectedFields.peek().length > 1 &&
|
if (
|
||||||
['formula', 'empty'].indexOf(this.field.viewSection.peek().columnsBehavior.peek())) {
|
fields.length > 1 &&
|
||||||
return this.gristDoc.docData.bundleActions(t("Changing multiple column types"), () =>
|
fields.every(f => f.column.peek().isFormula() || f.column.peek().isEmpty())
|
||||||
|
) {
|
||||||
|
this.gristDoc.docData.bundleActions(t("Changing multiple column types"), () =>
|
||||||
Promise.all(this.field.viewSection.peek().selectedFields.peek().map(f =>
|
Promise.all(this.field.viewSection.peek().selectedFields.peek().map(f =>
|
||||||
f.column.peek().type.setAndSave(calculatedType)
|
f.column.peek().type.setAndSave(calculatedType)
|
||||||
))).catch(reportError);
|
))).catch(reportError);
|
||||||
|
} else if (column.pureType() === 'Any') {
|
||||||
|
// If this is Any column, guess the final options.
|
||||||
|
const guessedOptions = guessWidgetOptionsSync({
|
||||||
|
docModel: this._docModel,
|
||||||
|
origCol: this.origColumn,
|
||||||
|
toTypeMaybeFull: newType,
|
||||||
|
});
|
||||||
|
const existingOptions = column.widgetOptionsJson.peek();
|
||||||
|
const widgetOptions = JSON.stringify({...existingOptions, ...guessedOptions});
|
||||||
|
bundleChanges(() => {
|
||||||
|
this.gristDoc.docData.bundleActions(t("Changing column type"), () =>
|
||||||
|
Promise.all([
|
||||||
|
// This order is better for any other UI modifications, as first we are updating options
|
||||||
|
// and then saving type.
|
||||||
|
!isEqual(existingOptions, guessedOptions)
|
||||||
|
? column.widgetOptions.setAndSave(widgetOptions)
|
||||||
|
: Promise.resolve(),
|
||||||
|
column.type.setAndSave(calculatedType),
|
||||||
|
])
|
||||||
|
).catch(reportError);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
column.type.setAndSave(calculatedType).catch(reportError);
|
||||||
}
|
}
|
||||||
column.type.setAndSave(calculatedType).catch(reportError);
|
|
||||||
} else if (!this.columnTransform) {
|
} else if (!this.columnTransform) {
|
||||||
this.columnTransform = TypeTransform.create(null, this.gristDoc, this);
|
this.columnTransform = TypeTransform.create(null, this.gristDoc, this);
|
||||||
return this.columnTransform.prepare(newType);
|
this.columnTransform.prepare(newType).catch(reportError);
|
||||||
} else {
|
} else {
|
||||||
if (this.columnTransform instanceof TypeTransform) {
|
if (this.columnTransform instanceof TypeTransform) {
|
||||||
return this.columnTransform.setType(newType);
|
this.columnTransform.setType(newType).catch(reportError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import { stackWrapFunc, stackWrapOwnMethods, WebDriver } from 'mocha-webdriver';
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as PluginApi from 'app/plugin/grist-plugin-api';
|
import * as PluginApi from 'app/plugin/grist-plugin-api';
|
||||||
|
|
||||||
|
import {CommandName} from 'app/client/components/commandList';
|
||||||
import {csvDecodeRow} from 'app/common/csvFormat';
|
import {csvDecodeRow} from 'app/common/csvFormat';
|
||||||
import { AccessLevel } from 'app/common/CustomWidget';
|
import { AccessLevel } from 'app/common/CustomWidget';
|
||||||
import { decodeUrl } from 'app/common/gristUrls';
|
import { decodeUrl } from 'app/common/gristUrls';
|
||||||
@ -3413,6 +3414,21 @@ class Clipboard implements IClipboard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs a Grist command in the browser window.
|
||||||
|
*/
|
||||||
|
export async function sendCommand(name: CommandName) {
|
||||||
|
await driver.executeAsyncScript((name: any, done: any) => {
|
||||||
|
const result = (window as any).gristApp.allCommands[name].run();
|
||||||
|
if (result?.finally) {
|
||||||
|
result.finally(done);
|
||||||
|
} else {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}, name);
|
||||||
|
await waitForServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
} // end of namespace gristUtils
|
} // end of namespace gristUtils
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user