(core) Support user variable in dropdown conditions

Summary:
Dropdown conditions can now reference a `user` variable, similar to the
one available in Access Rules.

Test Plan: Browser test.

Reviewers: jarek, paulfitz

Reviewed By: jarek, paulfitz

Differential Revision: https://phab.getgrist.com/D4255
This commit is contained in:
George Gevoian
2024-05-29 14:55:21 -07:00
parent 50077540e2
commit 72066bf0e4
27 changed files with 426 additions and 268 deletions

View File

@@ -21,7 +21,7 @@ dispose.makeDisposable(AbstractWidget);
/**
* Builds the DOM showing configuration buttons and fields in the sidebar.
*/
AbstractWidget.prototype.buildConfigDom = function() {
AbstractWidget.prototype.buildConfigDom = function(_gristDoc) {
throw new Error("Not Implemented");
};
@@ -29,7 +29,7 @@ AbstractWidget.prototype.buildConfigDom = function() {
* Builds the transform prompt config DOM in the few cases where it is necessary.
* Child classes need not override this function if they do not require transform config options.
*/
AbstractWidget.prototype.buildTransformConfigDom = function() {
AbstractWidget.prototype.buildTransformConfigDom = function(_gristDoc) {
return null;
};

View File

@@ -129,7 +129,7 @@ ChoiceEditor.prototype.buildDropdownConditionFilter = function() {
return buildDropdownConditionFilter({
dropdownConditionCompiled: dropdownConditionCompiled.result,
docData: this.options.gristDoc.docData,
gristDoc: this.options.gristDoc,
tableId: this.options.field.tableId(),
rowId: this.options.rowId,
});

View File

@@ -1,10 +1,10 @@
import {createGroup} from 'app/client/components/commands';
import {GristDoc} from 'app/client/components/GristDoc';
import {ACIndexImpl, ACItem, ACResults,
buildHighlightedDom, HighlightFunc, normalizeText} from 'app/client/lib/ACIndex';
import {IAutocompleteOptions} from 'app/client/lib/autocomplete';
import {makeT} from 'app/client/lib/localization';
import {IToken, TokenField, tokenFieldStyles} from 'app/client/lib/TokenField';
import {DocData} from 'app/client/models/DocData';
import {colors, testId, theme} from 'app/client/ui2018/cssVars';
import {menuCssClass} from 'app/client/ui2018/menus';
import {createMobileButtons, getButtonMargins} from 'app/client/widgets/EditorButtons';
@@ -12,7 +12,8 @@ import {EditorPlacement} from 'app/client/widgets/EditorPlacement';
import {FieldOptions, NewBaseEditor} from 'app/client/widgets/NewBaseEditor';
import {csvEncodeRow} from 'app/common/csvFormat';
import {CellValue} from "app/common/DocActions";
import {CompiledPredicateFormula, EmptyRecordView} from 'app/common/PredicateFormula';
import {CompiledPredicateFormula} from 'app/common/PredicateFormula';
import {EmptyRecordView} from 'app/common/RecordView';
import {decodeObject, encodeObject} from 'app/plugin/objtypes';
import {ChoiceOptions, getRenderFillColor, getRenderTextColor} from 'app/client/widgets/ChoiceTextBox';
import {choiceToken, cssChoiceACItem, cssChoiceToken} from 'app/client/widgets/ChoiceToken';
@@ -246,7 +247,7 @@ export class ChoiceListEditor extends NewBaseEditor {
return buildDropdownConditionFilter({
dropdownConditionCompiled: dropdownConditionCompiled.result,
docData: this.options.gristDoc.docData,
gristDoc: this.options.gristDoc,
tableId: this.options.field.tableId(),
rowId: this.options.rowId,
});
@@ -311,7 +312,7 @@ export class ChoiceListEditor extends NewBaseEditor {
export interface GetACFilterFuncParams {
dropdownConditionCompiled: CompiledPredicateFormula;
docData: DocData;
gristDoc: GristDoc;
tableId: string;
rowId: number;
}
@@ -319,12 +320,13 @@ export interface GetACFilterFuncParams {
export function buildDropdownConditionFilter(
params: GetACFilterFuncParams
): (item: ChoiceItem) => boolean {
const {dropdownConditionCompiled, docData, tableId, rowId} = params;
const table = docData.getTable(tableId);
const {dropdownConditionCompiled, gristDoc, tableId, rowId} = params;
const table = gristDoc.docData.getTable(tableId);
if (!table) { throw new Error(`Table ${tableId} not found`); }
const user = gristDoc.docPageModel.user.get() ?? undefined;
const rec = table.getRecord(rowId) || new EmptyRecordView();
return (item: ChoiceItem) => dropdownConditionCompiled({rec, choice: item.label});
return (item: ChoiceItem) => dropdownConditionCompiled({user, rec, choice: item.label});
}
const cssCellEditor = styled('div', `

View File

@@ -4,6 +4,7 @@ import {
FormSelectConfig,
} from 'app/client/components/Forms/FormConfig';
import {DropdownConditionConfig} from 'app/client/components/DropdownConditionConfig';
import {GristDoc} from 'app/client/components/GristDoc';
import {makeT} from 'app/client/lib/localization';
import {DataRowModel} from 'app/client/models/DataRowModel';
import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec';
@@ -79,17 +80,17 @@ export class ChoiceTextBox extends NTextBox {
);
}
public buildConfigDom() {
public buildConfigDom(gristDoc: GristDoc) {
return [
super.buildConfigDom(),
super.buildConfigDom(gristDoc),
this.buildChoicesConfigDom(),
dom.create(DropdownConditionConfig, this.field),
dom.create(DropdownConditionConfig, this.field, gristDoc),
];
}
public buildTransformConfigDom() {
public buildTransformConfigDom(gristDoc: GristDoc) {
return [
super.buildConfigDom(),
super.buildConfigDom(gristDoc),
this.buildChoicesConfigDom(),
];
}

View File

@@ -54,7 +54,7 @@ _.extend(DateTimeTextBox.prototype, DateTextBox.prototype);
* Builds the config dom for the DateTime TextBox. If isTransformConfig is true,
* builds only the necessary dom for the transform config menu.
*/
DateTimeTextBox.prototype.buildConfigDom = function(isTransformConfig) {
DateTimeTextBox.prototype.buildConfigDom = function(_gristDoc, isTransformConfig) {
const disabled = ko.pureComputed(() => {
return this.field.config.options.disabled('timeFormat')() || this.field.column().disableEditData();
});
@@ -92,8 +92,8 @@ DateTimeTextBox.prototype.buildConfigDom = function(isTransformConfig) {
);
};
DateTimeTextBox.prototype.buildTransformConfigDom = function() {
return this.buildConfigDom(true);
DateTimeTextBox.prototype.buildTransformConfigDom = function(gristDoc) {
return this.buildConfigDom(gristDoc, true);
};
// clean up old koform styles

View File

@@ -499,7 +499,7 @@ export class FieldBuilder extends Disposable {
// the dom created by the widgetImpl to get out of sync.
return dom('div',
kd.maybe(() => !this._isTransformingType() && this.widgetImpl(), (widget: NewAbstractWidget) =>
dom('div', widget.buildConfigDom())
dom('div', widget.buildConfigDom(this.gristDoc))
)
);
}

View File

@@ -1,4 +1,5 @@
import { FormFieldRulesConfig } from 'app/client/components/Forms/FormConfig';
import { GristDoc } from 'app/client/components/GristDoc';
import { fromKoSave } from 'app/client/lib/fromKoSave';
import { makeT } from 'app/client/lib/localization';
import { DataRowModel } from 'app/client/models/DataRowModel';
@@ -32,7 +33,7 @@ export class NTextBox extends NewAbstractWidget {
}));
}
public buildConfigDom(): DomContents {
public buildConfigDom(_gristDoc: GristDoc): DomContents {
const toggle = () => {
const newValue = !this.field.config.wrap.peek();
this.field.config.wrap.setAndSave(newValue).catch(reportError);

View File

@@ -60,7 +60,7 @@ export abstract class NewAbstractWidget extends Disposable {
/**
* Builds the DOM showing configuration buttons and fields in the sidebar.
*/
public buildConfigDom(): DomContents {
public buildConfigDom(_gristDoc: GristDoc): DomContents {
return null;
}
@@ -68,7 +68,7 @@ export abstract class NewAbstractWidget extends Disposable {
* Builds the transform prompt config DOM in the few cases where it is necessary.
* Child classes need not override this function if they do not require transform config options.
*/
public buildTransformConfigDom(): DomContents {
public buildTransformConfigDom(_gristDoc: GristDoc): DomContents {
return null;
}

View File

@@ -2,6 +2,7 @@
* See app/common/NumberFormat for description of options we support.
*/
import {FormFieldRulesConfig} from 'app/client/components/Forms/FormConfig';
import {GristDoc} from 'app/client/components/GristDoc';
import {fromKoSave} from 'app/client/lib/fromKoSave';
import {makeT} from 'app/client/lib/localization';
import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec';
@@ -39,7 +40,7 @@ export class NumericTextBox extends NTextBox {
super(field);
}
public buildConfigDom(): DomContents {
public buildConfigDom(gristDoc: GristDoc): DomContents {
// Holder for all computeds created here. It gets disposed with the returned DOM element.
const holder = new MultiHolder();
@@ -89,7 +90,7 @@ export class NumericTextBox extends NTextBox {
const disabledStyle = cssButtonSelect.cls('-disabled', disabled);
return [
super.buildConfigDom(),
super.buildConfigDom(gristDoc),
cssLabel(t('Number Format')),
cssRow(
dom.autoDispose(holder),

View File

@@ -4,6 +4,7 @@ import {
FormSelectConfig
} from 'app/client/components/Forms/FormConfig';
import {DropdownConditionConfig} from 'app/client/components/DropdownConditionConfig';
import {GristDoc} from 'app/client/components/GristDoc';
import {makeT} from 'app/client/lib/localization';
import {DataRowModel} from 'app/client/models/DataRowModel';
import {TableRec} from 'app/client/models/DocModel';
@@ -53,12 +54,12 @@ export class Reference extends NTextBox {
});
}
public buildConfigDom() {
public buildConfigDom(gristDoc: GristDoc) {
return [
this.buildTransformConfigDom(),
dom.create(DropdownConditionConfig, this.field),
dom.create(DropdownConditionConfig, this.field, gristDoc),
cssLabel(t('CELL FORMAT')),
super.buildConfigDom(),
super.buildConfigDom(gristDoc),
];
}

View File

@@ -23,8 +23,8 @@ export class ReferenceEditor extends NTextEditor {
constructor(options: FieldOptions) {
super(options);
const docData = options.gristDoc.docData;
this._utils = new ReferenceUtils(options.field, docData);
const gristDoc = options.gristDoc;
this._utils = new ReferenceUtils(options.field, gristDoc);
const vcol = this._utils.visibleColModel;
this._enableAddNew = (
@@ -47,7 +47,7 @@ export class ReferenceEditor extends NTextEditor {
// The referenced table has probably already been fetched (because there must already be a
// Reference widget instantiated), but it's better to avoid this assumption.
docData.fetchTable(this._utils.refTableId).then(() => {
gristDoc.docData.fetchTable(this._utils.refTableId).then(() => {
if (this.isDisposed()) { return; }
if (needReload && this.textInput.value === '') {
this.textInput.value = undef(options.state, options.editValue, this._idToText());

View File

@@ -55,8 +55,8 @@ export class ReferenceListEditor extends NewBaseEditor {
constructor(protected options: FieldOptions) {
super(options);
const docData = options.gristDoc.docData;
this._utils = new ReferenceUtils(options.field, docData);
const gristDoc = options.gristDoc;
this._utils = new ReferenceUtils(options.field, gristDoc);
const vcol = this._utils.visibleColModel;
this._enableAddNew = (
@@ -130,7 +130,7 @@ export class ReferenceListEditor extends NewBaseEditor {
// The referenced table has probably already been fetched (because there must already be a
// Reference widget instantiated), but it's better to avoid this assumption.
docData.fetchTable(this._utils.refTableId).then(() => {
gristDoc.docData.fetchTable(this._utils.refTableId).then(() => {
if (this.isDisposed()) { return; }
if (needReload) {
this._tokenField.setTokens(