mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Polish Importer UI
Summary: Changes include: * Hide the colum matching section for new destinations (for now). * Make the preview table read-only. * Don't show helper column IDs when the formula editor is open. * Fix the formula editor autocomplete to show suggestions from the active transform section. * Hide the formula icons in the preview table, and other unnecessary UI elements such as row dropdown menus. * Keep preview loading spinner shown if scheduled (i.e. debounced) diff updates exist. Test Plan: Browser tests. Reviewers: paulfitz Reviewed By: paulfitz Differential Revision: https://phab.getgrist.com/D3148
This commit is contained in:
parent
7fe4423a6f
commit
32bb89235e
@ -30,6 +30,7 @@ const {encodeObject} = require("app/plugin/objtypes");
|
|||||||
/**
|
/**
|
||||||
* BaseView forms the basis for ViewSection classes.
|
* BaseView forms the basis for ViewSection classes.
|
||||||
* @param {Object} viewSectionModel - The model for the viewSection represented.
|
* @param {Object} viewSectionModel - The model for the viewSection represented.
|
||||||
|
* @param {Boolean} options.isPreview - Whether the view is a read-only preview (e.g. Importer view).
|
||||||
* @param {Boolean} options.addNewRow - Whether to include an add row in the model.
|
* @param {Boolean} options.addNewRow - Whether to include an add row in the model.
|
||||||
*/
|
*/
|
||||||
function BaseView(gristDoc, viewSectionModel, options) {
|
function BaseView(gristDoc, viewSectionModel, options) {
|
||||||
@ -168,6 +169,8 @@ function BaseView(gristDoc, viewSectionModel, options) {
|
|||||||
return linking && linking.disableEditing();
|
return linking && linking.disableEditing();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
this.isPreview = this.options.isPreview;
|
||||||
|
|
||||||
this.enableAddRow = this.autoDispose(ko.computed(() => this.options.addNewRow &&
|
this.enableAddRow = this.autoDispose(ko.computed(() => this.options.addNewRow &&
|
||||||
!this.viewSection.disableAddRemoveRows() && !this.disableEditing()));
|
!this.viewSection.disableAddRemoveRows() && !this.disableEditing()));
|
||||||
|
|
||||||
@ -198,7 +201,9 @@ function BaseView(gristDoc, viewSectionModel, options) {
|
|||||||
|
|
||||||
// A koArray of FieldBuilder objects, one for each view-section field.
|
// A koArray of FieldBuilder objects, one for each view-section field.
|
||||||
this.fieldBuilders = this.autoDispose(
|
this.fieldBuilders = this.autoDispose(
|
||||||
FieldBuilder.createAllFieldWidgets(this.gristDoc, this.viewSection.viewFields, this.cursor)
|
FieldBuilder.createAllFieldWidgets(this.gristDoc, this.viewSection.viewFields, this.cursor, {
|
||||||
|
isPreview: this.isPreview,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// An observable evaluating to the FieldBuilder for the field where the cursor is.
|
// An observable evaluating to the FieldBuilder for the field where the cursor is.
|
||||||
|
@ -55,7 +55,7 @@ const ROW_NUMBER_WIDTH = 52;
|
|||||||
* GridView component implements the view of a grid of cells.
|
* GridView component implements the view of a grid of cells.
|
||||||
*/
|
*/
|
||||||
function GridView(gristDoc, viewSectionModel, isPreview = false) {
|
function GridView(gristDoc, viewSectionModel, isPreview = false) {
|
||||||
BaseView.call(this, gristDoc, viewSectionModel, { 'addNewRow': true });
|
BaseView.call(this, gristDoc, viewSectionModel, { isPreview, 'addNewRow': true });
|
||||||
|
|
||||||
this.viewSection = viewSectionModel;
|
this.viewSection = viewSectionModel;
|
||||||
|
|
||||||
@ -878,7 +878,7 @@ GridView.prototype.buildDom = function() {
|
|||||||
kd.style('borderLeftWidth', v.borderWidthPx),
|
kd.style('borderLeftWidth', v.borderWidthPx),
|
||||||
kd.foreach(v.viewFields(), field => {
|
kd.foreach(v.viewFields(), field => {
|
||||||
var isEditingLabel = ko.pureComputed({
|
var isEditingLabel = ko.pureComputed({
|
||||||
read: () => this.gristDoc.isReadonlyKo() ? false : editIndex() === field._index(),
|
read: () => this.gristDoc.isReadonlyKo() || self.isPreview ? false : editIndex() === field._index(),
|
||||||
write: val => editIndex(val ? field._index() : -1)
|
write: val => editIndex(val ? field._index() : -1)
|
||||||
}).extend({ rateLimit: 0 });
|
}).extend({ rateLimit: 0 });
|
||||||
let filterTriggerCtl;
|
let filterTriggerCtl;
|
||||||
@ -899,10 +899,10 @@ GridView.prototype.buildDom = function() {
|
|||||||
if (btn) { btn.click(); }
|
if (btn) { btn.click(); }
|
||||||
}),
|
}),
|
||||||
dom('div.g-column-label',
|
dom('div.g-column-label',
|
||||||
kf.editableLabel(field.displayLabel, isEditingLabel, renameCommands),
|
kf.editableLabel(self.isPreview ? field.label : field.displayLabel, isEditingLabel, renameCommands),
|
||||||
dom.on('mousedown', ev => isEditingLabel() ? ev.stopPropagation() : true)
|
dom.on('mousedown', ev => isEditingLabel() ? ev.stopPropagation() : true)
|
||||||
),
|
),
|
||||||
this.isPreview ? null : menuToggle(null,
|
self.isPreview ? null : menuToggle(null,
|
||||||
kd.cssClass('g-column-main-menu'),
|
kd.cssClass('g-column-main-menu'),
|
||||||
kd.cssClass('g-column-menu-btn'),
|
kd.cssClass('g-column-menu-btn'),
|
||||||
// Prevent mousedown on the dropdown triangle from initiating column drag.
|
// Prevent mousedown on the dropdown triangle from initiating column drag.
|
||||||
@ -1001,7 +1001,7 @@ GridView.prototype.buildDom = function() {
|
|||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.currentTarget.querySelector('.menu_toggle').click();
|
ev.currentTarget.querySelector('.menu_toggle').click();
|
||||||
}),
|
}),
|
||||||
menuToggle(null,
|
self.isPreview ? null : menuToggle(null,
|
||||||
dom.on('click', ev => self.maybeSelectRow(ev.currentTarget.parentNode, row.getRowId())),
|
dom.on('click', ev => self.maybeSelectRow(ev.currentTarget.parentNode, row.getRowId())),
|
||||||
menu(() => RowContextMenu({
|
menu(() => RowContextMenu({
|
||||||
disableInsert: Boolean(self.gristDoc.isReadonly.get() || self.viewSection.disableAddRemoveRows() || self.tableModel.tableMetaRow.onDemand()),
|
disableInsert: Boolean(self.gristDoc.isReadonly.get() || self.viewSection.disableAddRemoveRows() || self.tableModel.tableMetaRow.onDemand()),
|
||||||
@ -1378,7 +1378,7 @@ GridView.prototype._getColumnMenuOptions = function(copySelection) {
|
|||||||
numColumns: copySelection.fields.length,
|
numColumns: copySelection.fields.length,
|
||||||
numFrozen: this.viewSection.numFrozen.peek(),
|
numFrozen: this.viewSection.numFrozen.peek(),
|
||||||
disableModify: calcFieldsCondition(copySelection.fields, f => f.disableModify.peek()),
|
disableModify: calcFieldsCondition(copySelection.fields, f => f.disableModify.peek()),
|
||||||
isReadonly: this.gristDoc.isReadonly.get(),
|
isReadonly: this.gristDoc.isReadonly.get() || this.isPreview,
|
||||||
isFiltered: this.isFiltered(),
|
isFiltered: this.isFiltered(),
|
||||||
isFormula: calcFieldsCondition(copySelection.fields, f => f.column.peek().isRealFormula.peek()),
|
isFormula: calcFieldsCondition(copySelection.fields, f => f.column.peek().isRealFormula.peek()),
|
||||||
};
|
};
|
||||||
|
@ -170,7 +170,16 @@ export class Importer extends DisposableWithEvents {
|
|||||||
// Promise for the most recent generateImportDiff action.
|
// Promise for the most recent generateImportDiff action.
|
||||||
private _lastGenImportDiffPromise: Promise<any>|null = null;
|
private _lastGenImportDiffPromise: Promise<any>|null = null;
|
||||||
|
|
||||||
private _updateImportDiff = debounce(this._updateDiff, 1000, {leading: true, trailing: true});
|
private _debouncedUpdateDiff = debounce(this._updateDiff, 1000, {leading: true, trailing: true});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag that is set when _updateImportDiff is called, and unset when _debouncedUpdateDiff begins executing.
|
||||||
|
*
|
||||||
|
* This is a workaround until Lodash's next release, which supports checking if a debounced function is
|
||||||
|
* pending. We need to know if more debounced calls are pending so that we can decide to take down the
|
||||||
|
* loading spinner over the preview table, or leave it up until all scheduled calls settle.
|
||||||
|
*/
|
||||||
|
private _hasScheduledDiffUpdate = false;
|
||||||
|
|
||||||
// destTables is a list of options for import destinations, and includes all tables in the
|
// destTables is a list of options for import destinations, and includes all tables in the
|
||||||
// document, plus two values: to import as a new table, and to skip an import table entirely.
|
// document, plus two values: to import as a new table, and to skip an import table entirely.
|
||||||
@ -297,6 +306,9 @@ export class Importer extends DisposableWithEvents {
|
|||||||
// Otherwise, update the transform section for `sourceInfo`.
|
// Otherwise, update the transform section for `sourceInfo`.
|
||||||
sourceInfo.transformSection.set(this._gristDoc.docModel.viewSections.getRowModel(transformSectionRef));
|
sourceInfo.transformSection.set(this._gristDoc.docModel.viewSections.getRowModel(transformSectionRef));
|
||||||
sourceInfo.isLoadingSection.set(false);
|
sourceInfo.isLoadingSection.set(false);
|
||||||
|
|
||||||
|
// Change the active section to the transform section, so that formula autocomplete works.
|
||||||
|
this._gristDoc.viewModel.activeSectionId(transformSectionRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getTransformedDataSource(upload: UploadResult): DataSourceTransformed {
|
private _getTransformedDataSource(upload: UploadResult): DataSourceTransformed {
|
||||||
@ -479,16 +491,28 @@ export class Importer extends DisposableWithEvents {
|
|||||||
* Triggers an update of the import diff in the preview table. When called in quick succession,
|
* Triggers an update of the import diff in the preview table. When called in quick succession,
|
||||||
* only the most recent call will result in an update being made to the preview table.
|
* only the most recent call will result in an update being made to the preview table.
|
||||||
*
|
*
|
||||||
* NOTE: This method should not be called directly. Instead, use _updateImportDiff, which
|
* @param {SourceInfo} info The source to update the diff for.
|
||||||
* wraps this method and debounces it.
|
*/
|
||||||
|
private async _updateImportDiff(info: SourceInfo) {
|
||||||
|
this._hasScheduledDiffUpdate = true;
|
||||||
|
this._isLoadingDiff.set(true);
|
||||||
|
await this._debouncedUpdateDiff(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOTE: This method should not be called directly. Instead, use _updateImportDiff above, which
|
||||||
|
* wraps this method and calls a debounced version of it.
|
||||||
|
*
|
||||||
|
* Triggers an update of the import diff in the preview table. When called in quick succession,
|
||||||
|
* only the most recent call will result in an update being made to the preview table.
|
||||||
*
|
*
|
||||||
* @param {SourceInfo} info The source to update the diff for.
|
* @param {SourceInfo} info The source to update the diff for.
|
||||||
*/
|
*/
|
||||||
private async _updateDiff(info: SourceInfo) {
|
private async _updateDiff(info: SourceInfo) {
|
||||||
|
// Reset the flag tracking scheduled updates since the debounced update has started.
|
||||||
|
this._hasScheduledDiffUpdate = false;
|
||||||
|
|
||||||
const mergeOptions = this._mergeOptions[info.hiddenTableId]!;
|
const mergeOptions = this._mergeOptions[info.hiddenTableId]!;
|
||||||
|
|
||||||
this._isLoadingDiff.set(true);
|
|
||||||
|
|
||||||
if (!mergeOptions.updateExistingRecords.get() || mergeOptions.mergeCols.get().length === 0) {
|
if (!mergeOptions.updateExistingRecords.get() || mergeOptions.mergeCols.get().length === 0) {
|
||||||
// We can simply disable document comparison mode when merging isn't configured.
|
// We can simply disable document comparison mode when merging isn't configured.
|
||||||
this._gristDoc.comparison = null;
|
this._gristDoc.comparison = null;
|
||||||
@ -506,7 +530,10 @@ export class Importer extends DisposableWithEvents {
|
|||||||
this._gristDoc.comparison = diff;
|
this._gristDoc.comparison = diff;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._isLoadingDiff.set(false);
|
// If more updates where scheduled since we started the update, leave the loading spinner up.
|
||||||
|
if (!this._hasScheduledDiffUpdate) {
|
||||||
|
this._isLoadingDiff.set(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -523,8 +550,9 @@ export class Importer extends DisposableWithEvents {
|
|||||||
* is functionally equivalent to canceling the outstanding requests.
|
* is functionally equivalent to canceling the outstanding requests.
|
||||||
*/
|
*/
|
||||||
private _cancelPendingDiffRequests() {
|
private _cancelPendingDiffRequests() {
|
||||||
this._updateImportDiff.cancel();
|
this._debouncedUpdateDiff.cancel();
|
||||||
this._lastGenImportDiffPromise = null;
|
this._lastGenImportDiffPromise = null;
|
||||||
|
this._hasScheduledDiffUpdate = false;
|
||||||
this._isLoadingDiff.set(false);
|
this._isLoadingDiff.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -579,14 +607,14 @@ export class Importer extends DisposableWithEvents {
|
|||||||
const {mergeCols, updateExistingRecords, hasInvalidMergeCols} = this._mergeOptions[info.hiddenTableId]!;
|
const {mergeCols, updateExistingRecords, hasInvalidMergeCols} = this._mergeOptions[info.hiddenTableId]!;
|
||||||
|
|
||||||
return cssConfigAndPreview(
|
return cssConfigAndPreview(
|
||||||
cssConfigColumn(
|
dom.maybe(info.destTableId, () => cssConfigColumn(
|
||||||
dom.maybe(info.transformSection, section => [
|
dom.maybe(info.transformSection, section => {
|
||||||
dom.maybe(info.destTableId, () => {
|
const updateRecordsListener = updateExistingRecords.addListener(async () => {
|
||||||
const updateRecordsListener = updateExistingRecords.addListener(async () => {
|
await this._updateImportDiff(info);
|
||||||
await this._updateImportDiff(info);
|
});
|
||||||
});
|
|
||||||
|
|
||||||
return cssMergeOptions(
|
return [
|
||||||
|
cssMergeOptions(
|
||||||
cssMergeOptionsToggle(labeledSquareCheckbox(
|
cssMergeOptionsToggle(labeledSquareCheckbox(
|
||||||
updateExistingRecords,
|
updateExistingRecords,
|
||||||
'Update existing records',
|
'Update existing records',
|
||||||
@ -619,77 +647,77 @@ export class Importer extends DisposableWithEvents {
|
|||||||
)
|
)
|
||||||
];
|
];
|
||||||
})
|
})
|
||||||
);
|
),
|
||||||
}),
|
dom.domComputed(this._unmatchedFields, fields =>
|
||||||
dom.domComputed(this._unmatchedFields, fields =>
|
fields && fields.length > 0 ?
|
||||||
fields && fields.length > 0 ?
|
cssUnmatchedFields(
|
||||||
cssUnmatchedFields(
|
dom('div',
|
||||||
dom('div',
|
cssGreenText(
|
||||||
cssGreenText(
|
`${fields.length} unmatched ${fields.length > 1 ? 'fields' : 'field'}`
|
||||||
`${fields.length} unmatched ${fields.length > 1 ? 'fields' : 'field'}`
|
),
|
||||||
|
' in import:'
|
||||||
),
|
),
|
||||||
' in import:'
|
cssUnmatchedFieldsList(fields.join(', ')),
|
||||||
),
|
testId('importer-unmatched-fields')
|
||||||
cssUnmatchedFieldsList(fields.join(', ')),
|
) : null
|
||||||
testId('importer-unmatched-fields')
|
),
|
||||||
) : null
|
cssColumnMatchOptions(
|
||||||
),
|
dom.forEach(fromKo(section.viewFields().getObservable()), field => cssColumnMatchRow(
|
||||||
cssColumnMatchOptions(
|
cssColumnMatchIcon('ImportArrow'),
|
||||||
dom.forEach(fromKo(section.viewFields().getObservable()), field => cssColumnMatchRow(
|
cssSourceAndDestination(
|
||||||
cssColumnMatchIcon('ImportArrow'),
|
cssDestinationFieldRow(
|
||||||
cssSourceAndDestination(
|
cssDestinationFieldLabel(
|
||||||
cssDestinationFieldRow(
|
dom.text(field.label),
|
||||||
cssDestinationFieldLabel(
|
),
|
||||||
dom.text(field.label),
|
cssDestinationFieldSettings(
|
||||||
),
|
icon('Dots'),
|
||||||
cssDestinationFieldSettings(
|
menu(
|
||||||
icon('Dots'),
|
() => {
|
||||||
menu(
|
const sourceColId = field.origCol().id();
|
||||||
() => {
|
const sourceColIdsAndLabels = [...this._sourceColLabelsById.get()!.entries()];
|
||||||
const sourceColId = field.origCol().id();
|
return [
|
||||||
const sourceColIdsAndLabels = [...this._sourceColLabelsById.get()!.entries()];
|
|
||||||
return [
|
|
||||||
menuItem(
|
|
||||||
() => this._gristDoc.clearColumns([sourceColId]),
|
|
||||||
'Skip',
|
|
||||||
testId('importer-column-match-menu-item')
|
|
||||||
),
|
|
||||||
menuDivider(),
|
|
||||||
...sourceColIdsAndLabels.map(([id, label]) =>
|
|
||||||
menuItem(
|
menuItem(
|
||||||
() => this._setColumnFormula(sourceColId, '$' + id),
|
() => this._gristDoc.clearColumns([sourceColId]),
|
||||||
label,
|
'Skip',
|
||||||
testId('importer-column-match-menu-item')
|
testId('importer-column-match-menu-item')
|
||||||
),
|
),
|
||||||
),
|
menuDivider(),
|
||||||
testId('importer-column-match-menu'),
|
...sourceColIdsAndLabels.map(([id, label]) =>
|
||||||
];
|
menuItem(
|
||||||
},
|
() => this._setColumnFormula(sourceColId, '$' + id),
|
||||||
{placement: 'right-start'},
|
label,
|
||||||
|
testId('importer-column-match-menu-item')
|
||||||
|
),
|
||||||
|
),
|
||||||
|
testId('importer-column-match-menu'),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
{ placement: 'right-start' },
|
||||||
|
),
|
||||||
|
testId('importer-column-match-destination-settings')
|
||||||
),
|
),
|
||||||
testId('importer-column-match-destination-settings')
|
testId('importer-column-match-destination')
|
||||||
),
|
),
|
||||||
testId('importer-column-match-destination')
|
dom.domComputed(use => dom.create(
|
||||||
),
|
this._buildColMappingFormula.bind(this),
|
||||||
dom.domComputed(use => dom.create(
|
use(field.column),
|
||||||
this._buildColMappingFormula.bind(this),
|
(elem: Element) => this._activateFormulaEditor(elem, field),
|
||||||
use(field.column),
|
'Skip'
|
||||||
(elem: Element) => this._activateFormulaEditor(elem, field),
|
)),
|
||||||
'Skip'
|
testId('importer-column-match-source-destination'),
|
||||||
)),
|
)
|
||||||
testId('importer-column-match-source-destination'),
|
)),
|
||||||
)
|
testId('importer-column-match-options'),
|
||||||
)),
|
)
|
||||||
testId('importer-column-match-options'),
|
];
|
||||||
)
|
}),
|
||||||
]),
|
)),
|
||||||
),
|
|
||||||
cssPreviewColumn(
|
cssPreviewColumn(
|
||||||
cssSectionHeader('Preview'),
|
cssSectionHeader('Preview'),
|
||||||
dom.domComputed(use => {
|
dom.domComputed(use => {
|
||||||
const previewSection = use(this._previewViewSection);
|
const previewSection = use(this._previewViewSection);
|
||||||
if (use(this._isLoadingDiff) || !previewSection) {
|
if (use(this._isLoadingDiff) || !previewSection) {
|
||||||
return cssPreviewSpinner(loadingSpinner());
|
return cssPreviewSpinner(loadingSpinner(), testId('importer-preview-spinner'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const gridView = this._createPreview(previewSection);
|
const gridView = this._createPreview(previewSection);
|
||||||
|
@ -36,10 +36,10 @@ const testId = makeTestId('test-fbuilder-');
|
|||||||
|
|
||||||
// Creates a FieldBuilder object for each field in viewFields
|
// Creates a FieldBuilder object for each field in viewFields
|
||||||
export function createAllFieldWidgets(gristDoc: GristDoc, viewFields: ko.Computed<KoArray<ViewFieldRec>>,
|
export function createAllFieldWidgets(gristDoc: GristDoc, viewFields: ko.Computed<KoArray<ViewFieldRec>>,
|
||||||
cursor: Cursor) {
|
cursor: Cursor, options: { isPreview?: boolean } = {}) {
|
||||||
// TODO: Handle disposal from the map when fields are removed.
|
// TODO: Handle disposal from the map when fields are removed.
|
||||||
return viewFields().map(function(field) {
|
return viewFields().map(function(field) {
|
||||||
return new FieldBuilder(gristDoc, field, cursor);
|
return new FieldBuilder(gristDoc, field, cursor, options);
|
||||||
}).setAutoDisposeValues();
|
}).setAutoDisposeValues();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ export class FieldBuilder extends Disposable {
|
|||||||
private readonly _readonly: Computed<boolean>;
|
private readonly _readonly: Computed<boolean>;
|
||||||
|
|
||||||
public constructor(public readonly gristDoc: GristDoc, public readonly field: ViewFieldRec,
|
public constructor(public readonly gristDoc: GristDoc, public readonly field: ViewFieldRec,
|
||||||
private _cursor: Cursor) {
|
private _cursor: Cursor, private _options: { isPreview?: boolean } = {}) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this._docModel = gristDoc.docModel;
|
this._docModel = gristDoc.docModel;
|
||||||
@ -89,7 +89,8 @@ export class FieldBuilder extends Disposable {
|
|||||||
|
|
||||||
this._readOnlyPureType = ko.pureComputed(() => this.field.column().pureType());
|
this._readOnlyPureType = ko.pureComputed(() => this.field.column().pureType());
|
||||||
|
|
||||||
this._readonly = Computed.create(this, (use) => use(gristDoc.isReadonly) || use(field.disableEditData));
|
this._readonly = Computed.create(this, (use) =>
|
||||||
|
use(gristDoc.isReadonly) || use(field.disableEditData) || Boolean(this._options.isPreview));
|
||||||
|
|
||||||
// Observable with a list of available types.
|
// Observable with a list of available types.
|
||||||
this._availableTypes = Computed.create(this, (use) => {
|
this._availableTypes = Computed.create(this, (use) => {
|
||||||
@ -428,7 +429,7 @@ export class FieldBuilder extends Disposable {
|
|||||||
this._rowMap.set(row, elem);
|
this._rowMap.set(row, elem);
|
||||||
dom(elem,
|
dom(elem,
|
||||||
dom.autoDispose(widgetObs),
|
dom.autoDispose(widgetObs),
|
||||||
kd.cssClass(this.field.formulaCssClass),
|
this._options.isPreview ? null : kd.cssClass(this.field.formulaCssClass),
|
||||||
kd.toggleClass("readonly", toKo(ko, this._readonly)),
|
kd.toggleClass("readonly", toKo(ko, this._readonly)),
|
||||||
kd.maybe(isSelected, () => dom('div.selected_cursor',
|
kd.maybe(isSelected, () => dom('div.selected_cursor',
|
||||||
kd.toggleClass('active_cursor', isActive)
|
kd.toggleClass('active_cursor', isActive)
|
||||||
|
Loading…
Reference in New Issue
Block a user