2020-10-02 15:10:00 +00:00
|
|
|
/**
|
|
|
|
* TypeTransform extends ColumnTransform, creating the transform dom prompt that is shown when the
|
|
|
|
* user changes the type of a data column. The purpose is to aid the user in converting data to the new
|
|
|
|
* type by allowing a formula to be applied prior to conversion. It also allows for program-generated formulas
|
|
|
|
* to be pre-entered for certain transforms (to Reference / Date) which the user can modify via dropdown menus.
|
|
|
|
*/
|
|
|
|
|
|
|
|
import {ColumnTransform} from 'app/client/components/ColumnTransform';
|
|
|
|
import {GristDoc} from 'app/client/components/GristDoc';
|
|
|
|
import * as TypeConversion from 'app/client/components/TypeConversion';
|
|
|
|
import {reportError} from 'app/client/models/errors';
|
2022-08-08 13:32:50 +00:00
|
|
|
import {cssButtonRow} from 'app/client/ui/RightPanelStyles';
|
2020-10-02 15:10:00 +00:00
|
|
|
import {basicButton, primaryButton} from 'app/client/ui2018/buttons';
|
|
|
|
import {testId} from 'app/client/ui2018/cssVars';
|
|
|
|
import {FieldBuilder} from 'app/client/widgets/FieldBuilder';
|
2022-12-27 15:30:36 +00:00
|
|
|
import {ColumnRec} from 'app/client/models/DocModel';
|
2020-10-02 15:10:00 +00:00
|
|
|
import {NewAbstractWidget} from 'app/client/widgets/NewAbstractWidget';
|
2022-02-21 14:45:17 +00:00
|
|
|
import {UserAction} from 'app/common/DocActions';
|
2020-10-02 15:10:00 +00:00
|
|
|
import {Computed, dom, fromKo, Observable} from 'grainjs';
|
2022-10-28 16:11:08 +00:00
|
|
|
import {makeT} from 'app/client/lib/localization';
|
|
|
|
|
2023-01-09 17:49:58 +00:00
|
|
|
const t = makeT('TypeTransform');
|
2020-10-02 15:10:00 +00:00
|
|
|
|
|
|
|
// To simplify diff (avoid rearranging methods to satisfy private/public order).
|
2021-04-26 21:54:09 +00:00
|
|
|
/* eslint-disable @typescript-eslint/member-ordering */
|
2020-10-02 15:10:00 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates an instance of TypeTransform for a single field. Extends ColumnTransform.
|
|
|
|
*/
|
|
|
|
export class TypeTransform extends ColumnTransform {
|
2021-05-23 17:43:11 +00:00
|
|
|
private _reviseTypeChange = Observable.create(this, false);
|
|
|
|
private _transformWidget: Computed<NewAbstractWidget|null>;
|
2022-12-27 15:30:36 +00:00
|
|
|
private _convertColumn: ColumnRec; // Set in prepare()
|
2020-10-02 15:10:00 +00:00
|
|
|
|
|
|
|
constructor(gristDoc: GristDoc, fieldBuilder: FieldBuilder) {
|
|
|
|
super(gristDoc, fieldBuilder);
|
2020-11-09 23:40:43 +00:00
|
|
|
this._shouldExecute = true;
|
2020-10-02 15:10:00 +00:00
|
|
|
|
|
|
|
// The display widget of the new transform column. Used to build the transform config menu.
|
|
|
|
// Only set while transforming.
|
2021-05-23 17:43:11 +00:00
|
|
|
this._transformWidget = Computed.create(this, fromKo(fieldBuilder.widgetImpl), (use, widget) => {
|
2020-10-02 15:10:00 +00:00
|
|
|
return use(this.origColumn.isTransforming) ? widget : null;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Build the transform menu for a type transform
|
|
|
|
*/
|
|
|
|
public buildDom() {
|
|
|
|
// An observable to disable all buttons before the dom get removed.
|
|
|
|
const disableButtons = Observable.create(null, false);
|
2021-05-23 17:43:11 +00:00
|
|
|
this._reviseTypeChange.set(false);
|
2020-10-02 15:10:00 +00:00
|
|
|
return dom('div',
|
|
|
|
testId('type-transform-top'),
|
2021-05-23 17:43:11 +00:00
|
|
|
dom.maybe(this._transformWidget, transformWidget => transformWidget.buildTransformConfigDom()),
|
|
|
|
dom.maybe(this._reviseTypeChange, () =>
|
2020-10-02 15:10:00 +00:00
|
|
|
dom('div.transform_editor', this.buildEditorDom(),
|
|
|
|
testId("type-transform-formula")
|
|
|
|
)
|
|
|
|
),
|
|
|
|
cssButtonRow(
|
2020-11-09 23:40:43 +00:00
|
|
|
basicButton(dom.on('click', () => { this.cancel().catch(reportError); disableButtons.set(true); }),
|
2022-10-28 16:11:08 +00:00
|
|
|
t('Cancel'), testId("type-transform-cancel"),
|
2020-10-02 15:10:00 +00:00
|
|
|
dom.cls('disabled', disableButtons)
|
|
|
|
),
|
2021-05-23 17:43:11 +00:00
|
|
|
dom.domComputed(this._reviseTypeChange, revising => {
|
2020-10-02 15:10:00 +00:00
|
|
|
if (revising) {
|
2023-08-02 10:37:00 +00:00
|
|
|
return basicButton(dom.on('click', () => this.preview()),
|
2022-10-28 16:11:08 +00:00
|
|
|
t('Preview'), testId("type-transform-update"),
|
2020-10-02 15:10:00 +00:00
|
|
|
dom.cls('disabled', (use) => use(disableButtons) || use(this.formulaUpToDate)),
|
2022-12-13 16:26:42 +00:00
|
|
|
{ title: t('Update formula (Shift+Enter)') }
|
2020-10-02 15:10:00 +00:00
|
|
|
);
|
|
|
|
} else {
|
2021-05-23 17:43:11 +00:00
|
|
|
return basicButton(dom.on('click', () => { this._reviseTypeChange.set(true); }),
|
2022-10-28 16:11:08 +00:00
|
|
|
t('Revise'), testId("type-transform-revise"),
|
2020-10-02 15:10:00 +00:00
|
|
|
dom.cls('disabled', disableButtons)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
primaryButton(dom.on('click', () => { this.execute().catch(reportError); disableButtons.set(true); }),
|
2022-10-28 16:11:08 +00:00
|
|
|
t('Apply'), testId("type-transform-apply"),
|
2020-10-02 15:10:00 +00:00
|
|
|
dom.cls('disabled', disableButtons)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Overrides parent method to initialize the transform column with guesses as to the particular
|
|
|
|
* type and column options.
|
|
|
|
* @param {String} toType: A pure or complete type for the transformed column.
|
|
|
|
*/
|
|
|
|
protected async addTransformColumn(toType: string) {
|
|
|
|
const docModel = this.gristDoc.docModel;
|
2022-12-27 15:30:36 +00:00
|
|
|
const newColInfos = await this._tableData.sendTableActions([
|
|
|
|
['AddColumn', 'gristHelper_Converted', {type: 'Any'}],
|
|
|
|
['AddColumn', 'gristHelper_Transform', {type: 'Any'}],
|
|
|
|
]);
|
|
|
|
const gristHelper_ConvertedRef = newColInfos[0].colRef;
|
|
|
|
const gristHelper_TransformRef = newColInfos[1].colRef;
|
|
|
|
this.transformColumn = docModel.columns.getRowModel(gristHelper_TransformRef);
|
|
|
|
this._convertColumn = docModel.columns.getRowModel(gristHelper_ConvertedRef);
|
2023-10-30 10:40:28 +00:00
|
|
|
const colInfo = await TypeConversion.prepTransformColInfo({
|
|
|
|
docModel,
|
|
|
|
origCol: this.origColumn,
|
|
|
|
origDisplayCol: this.origDisplayCol,
|
|
|
|
toTypeMaybeFull: toType,
|
|
|
|
convertedRef: this._convertColumn.colId.peek()
|
|
|
|
});
|
2022-04-07 14:58:16 +00:00
|
|
|
// NOTE: We could add rules with AddColumn action, but there are some optimizations that converts array values.
|
|
|
|
const rules = colInfo.rules;
|
(core) Speed up and upgrade build.
Summary:
- Upgrades to build-related packages:
- Upgrade typescript, related libraries and typings.
- Upgrade webpack, eslint; add tsc-watch, node-dev, eslint_d.
- Build organization changes:
- Build webpack from original typescript, transpiling only; with errors still
reported by a background tsc watching process.
- Typescript-related changes:
- Reduce imports of AWS dependencies (very noticeable speedup)
- Avoid auto-loading global @types
- Client code is now built with isolatedModules flag (for safe transpilation)
- Use allowJs to avoid copying JS files manually.
- Linting changes
- Enhance Arcanist ESLintLinter to run before/after commands, and set up to use eslint_d
- Update eslint config, and include .eslintignore to avoid linting generated files.
- Include a bunch of eslint-prompted and eslint-generated fixes
- Add no-unused-expression rule to eslint, and fix a few warnings about it
- Other items:
- Refactor cssInput to avoid circular dependency
- Remove a bit of unused code, libraries, dependencies
Test Plan: No behavior changes, all existing tests pass. There are 30 tests fewer reported because `test_gpath.py` was removed (it's been unused for years)
Reviewers: paulfitz
Reviewed By: paulfitz
Subscribers: paulfitz
Differential Revision: https://phab.getgrist.com/D3498
2022-06-27 20:09:41 +00:00
|
|
|
delete (colInfo as any).rules;
|
2022-12-27 15:30:36 +00:00
|
|
|
await this._tableData.sendTableActions([
|
|
|
|
['ModifyColumn', this._convertColumn.colId.peek(), {...colInfo, isFormula: false, formula: ''}],
|
|
|
|
['ModifyColumn', this.transformColumn.colId.peek(), colInfo],
|
2022-02-04 11:13:03 +00:00
|
|
|
]);
|
2022-04-07 14:58:16 +00:00
|
|
|
if (rules) {
|
|
|
|
await this.gristDoc.docData.sendActions([
|
2022-12-27 15:30:36 +00:00
|
|
|
['UpdateRecord', '_grist_Tables_column', gristHelper_TransformRef, { rules }]
|
2022-04-07 14:58:16 +00:00
|
|
|
]);
|
|
|
|
}
|
2022-02-04 11:13:03 +00:00
|
|
|
await this.convertValues();
|
2022-12-27 15:30:36 +00:00
|
|
|
return gristHelper_TransformRef;
|
2022-02-04 11:13:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
protected convertValuesActions(): UserAction[] {
|
|
|
|
const tableId = this._tableData.tableId;
|
|
|
|
const srcColId = this.origColumn.colId.peek();
|
2022-12-27 15:30:36 +00:00
|
|
|
const dstColId = this._convertColumn.colId.peek();
|
2022-02-04 11:13:03 +00:00
|
|
|
const type = this.transformColumn.type.peek();
|
|
|
|
const widgetOptions = this.transformColumn.widgetOptions.peek();
|
|
|
|
const visibleColRef = this.transformColumn.visibleCol.peek();
|
|
|
|
return [['ConvertFromColumn', tableId, srcColId, dstColId, type, widgetOptions, visibleColRef]];
|
|
|
|
}
|
|
|
|
|
|
|
|
protected async convertValues() {
|
|
|
|
await Promise.all([
|
|
|
|
this.gristDoc.docData.sendActions(this.convertValuesActions()),
|
|
|
|
TypeConversion.setDisplayFormula(this.gristDoc.docModel, this.transformColumn),
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected executeActions(): UserAction[] {
|
|
|
|
return [...this.convertValuesActions(), ...super.executeActions()];
|
2020-10-02 15:10:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Overrides parent method to subscribe to changes to the transform column.
|
|
|
|
*/
|
|
|
|
protected postAddTransformColumn() {
|
2022-02-04 11:13:03 +00:00
|
|
|
// When a user-initiated change is saved to type or widgetOptions, reconvert the values
|
|
|
|
// Need to subscribe to both 'change' and 'save' for type which can come from setting the type itself
|
|
|
|
// or e.g. a change to DateTime timezone.
|
|
|
|
this.autoDispose(this.transformColumn.type.subscribe(this.convertValues, this, "change"));
|
|
|
|
this.autoDispose(this.transformColumn.type.subscribe(this.convertValues, this, "save"));
|
|
|
|
this.autoDispose(this.transformColumn.visibleCol.subscribe(this.convertValues, this, "save"));
|
|
|
|
this.autoDispose(this.field.widgetOptionsJson.subscribe(this.convertValues, this, "save"));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Overrides parent method to delete extra column
|
|
|
|
*/
|
|
|
|
protected cleanup() {
|
2022-12-27 15:30:36 +00:00
|
|
|
void this._tableData.sendTableAction(['RemoveColumn', this._convertColumn.colId.peek()]);
|
2020-10-02 15:10:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* When a type is changed, again guess appropriate column options.
|
|
|
|
*/
|
|
|
|
public async setType(toType: string) {
|
|
|
|
const docModel = this.gristDoc.docModel;
|
2023-10-30 10:40:28 +00:00
|
|
|
const colInfo = await TypeConversion.prepTransformColInfo({
|
|
|
|
docModel,
|
|
|
|
origCol: this.origColumn,
|
|
|
|
origDisplayCol: this.origDisplayCol,
|
|
|
|
toTypeMaybeFull: toType,
|
|
|
|
convertedRef: this._convertColumn.colId.peek()
|
|
|
|
});
|
2020-10-02 15:10:00 +00:00
|
|
|
const tcol = this.transformColumn;
|
2022-02-21 14:45:17 +00:00
|
|
|
await tcol.updateColValues(colInfo as any);
|
2020-10-02 15:10:00 +00:00
|
|
|
}
|
|
|
|
}
|