mirror of
				https://github.com/gristlabs/grist-core.git
				synced 2025-06-13 20:53:59 +00:00 
			
		
		
		
	(core) updates from grist-core
This commit is contained in:
		
						commit
						85f1040439
					
				@ -51,7 +51,7 @@ export class DropdownConditionConfig extends Disposable {
 | 
			
		||||
 | 
			
		||||
    const {recColIds = [], choiceColIds = []} = properties;
 | 
			
		||||
    const columns = use(this._columns);
 | 
			
		||||
    const validRecColIds = new Set(columns.map((({colId}) => use(colId))));
 | 
			
		||||
    const validRecColIds = new Set(['id', ...columns.map((({colId}) => use(colId)))]);
 | 
			
		||||
    const invalidRecColIds = recColIds.filter(colId => !validRecColIds.has(colId));
 | 
			
		||||
    if (invalidRecColIds.length > 0) {
 | 
			
		||||
      return t('Invalid columns: {{colIds}}', {colIds: invalidRecColIds.join(', ')});
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,5 @@
 | 
			
		||||
import * as css from 'app/client/components/FormRendererCss';
 | 
			
		||||
import {makeT} from 'app/client/lib/localization';
 | 
			
		||||
import {FormField} from 'app/client/ui/FormAPI';
 | 
			
		||||
import {sanitizeHTML} from 'app/client/ui/sanitizeHTML';
 | 
			
		||||
import {dropdownWithSearch} from 'app/client/ui/searchDropdown';
 | 
			
		||||
@ -11,6 +12,8 @@ import {IPopupOptions, PopupControl} from 'popweasel';
 | 
			
		||||
 | 
			
		||||
const testId = makeTestId('test-form-');
 | 
			
		||||
 | 
			
		||||
const t = makeT('FormRenderer');
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A node in a recursive, tree-like hierarchy comprising the layout of a form.
 | 
			
		||||
 */
 | 
			
		||||
@ -165,7 +168,7 @@ class SubmitRenderer extends FormRenderer {
 | 
			
		||||
      css.error(dom.text(use => use(this.context.error) ?? '')),
 | 
			
		||||
      css.submitButtons(
 | 
			
		||||
        css.resetButton(
 | 
			
		||||
          'Reset',
 | 
			
		||||
          t('Reset'),
 | 
			
		||||
          dom.boolAttr('disabled', this.context.disabled),
 | 
			
		||||
          {type: 'button'},
 | 
			
		||||
          dom.on('click', () => {
 | 
			
		||||
@ -182,7 +185,7 @@ class SubmitRenderer extends FormRenderer {
 | 
			
		||||
            dom.boolAttr('disabled', this.context.disabled),
 | 
			
		||||
            {
 | 
			
		||||
              type: 'submit',
 | 
			
		||||
              value: this.context.rootLayoutNode.submitText || 'Submit',
 | 
			
		||||
              value: this.context.rootLayoutNode.submitText || t('Submit'),
 | 
			
		||||
            },
 | 
			
		||||
            dom.on('click', () => validateRequiredLists()),
 | 
			
		||||
          )
 | 
			
		||||
@ -356,7 +359,7 @@ class DateTimeRenderer extends TextRenderer {
 | 
			
		||||
  protected inputType = 'datetime-local';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const SELECT_PLACEHOLDER = 'Select...';
 | 
			
		||||
export const selectPlaceholder = () => t('Select...');
 | 
			
		||||
 | 
			
		||||
class ChoiceRenderer extends BaseFieldRenderer  {
 | 
			
		||||
  protected value: Observable<string>;
 | 
			
		||||
@ -417,7 +420,7 @@ class ChoiceRenderer extends BaseFieldRenderer  {
 | 
			
		||||
      this._selectElement = css.select(
 | 
			
		||||
        {name: this.name(), required: this.field.options.formRequired},
 | 
			
		||||
        dom.on('input', (_e, elem) => this.value.set(elem.value)),
 | 
			
		||||
        dom('option', {value: ''}, SELECT_PLACEHOLDER),
 | 
			
		||||
        dom('option', {value: ''}, selectPlaceholder()),
 | 
			
		||||
        this._choices.map((choice) => dom('option',
 | 
			
		||||
          {value: choice},
 | 
			
		||||
          dom.prop('selected', use => use(this.value) === choice),
 | 
			
		||||
@ -434,18 +437,18 @@ class ChoiceRenderer extends BaseFieldRenderer  {
 | 
			
		||||
      ),
 | 
			
		||||
      dom.maybe(use => !use(isXSmallScreenObs()), () =>
 | 
			
		||||
        css.searchSelect(
 | 
			
		||||
          dom('div', dom.text(use => use(this.value) || SELECT_PLACEHOLDER)),
 | 
			
		||||
          dom('div', dom.text(use => use(this.value) || selectPlaceholder())),
 | 
			
		||||
          dropdownWithSearch<string>({
 | 
			
		||||
            action: (value) => this.value.set(value),
 | 
			
		||||
            options: () => [
 | 
			
		||||
              {label: SELECT_PLACEHOLDER, value: '', placeholder: true},
 | 
			
		||||
              {label: selectPlaceholder(), value: '', placeholder: true},
 | 
			
		||||
              ...this._choices.map((choice) => ({
 | 
			
		||||
                label: choice,
 | 
			
		||||
                value: choice,
 | 
			
		||||
              }),
 | 
			
		||||
            )],
 | 
			
		||||
            onClose: () => { setTimeout(() => this._selectElement.focus()); },
 | 
			
		||||
            placeholder: 'Search',
 | 
			
		||||
            placeholder: t('Search'),
 | 
			
		||||
            acOptions: {maxResults: 1000, keepOrder: true, showEmptyItems: true},
 | 
			
		||||
            popupOptions: {
 | 
			
		||||
              trigger: [
 | 
			
		||||
@ -757,7 +760,7 @@ class RefRenderer extends BaseFieldRenderer {
 | 
			
		||||
        dom.on('input', (_e, elem) => this.value.set(elem.value)),
 | 
			
		||||
        dom('option',
 | 
			
		||||
          {value: ''},
 | 
			
		||||
          SELECT_PLACEHOLDER,
 | 
			
		||||
          selectPlaceholder(),
 | 
			
		||||
          dom.prop('selected', use => use(this.value) === ''),
 | 
			
		||||
        ),
 | 
			
		||||
        this._choices.map((choice) => dom('option',
 | 
			
		||||
@ -778,12 +781,12 @@ class RefRenderer extends BaseFieldRenderer {
 | 
			
		||||
        css.searchSelect(
 | 
			
		||||
          dom('div', dom.text(use => {
 | 
			
		||||
            const choice = this._choices.find((c) => String(c[0]) === use(this.value));
 | 
			
		||||
            return String(choice?.[1] || SELECT_PLACEHOLDER);
 | 
			
		||||
            return String(choice?.[1] || selectPlaceholder());
 | 
			
		||||
          })),
 | 
			
		||||
          dropdownWithSearch<string>({
 | 
			
		||||
            action: (value) => this.value.set(value),
 | 
			
		||||
            options: () => [
 | 
			
		||||
              {label: SELECT_PLACEHOLDER, value: '', placeholder: true},
 | 
			
		||||
              {label: selectPlaceholder(), value: '', placeholder: true},
 | 
			
		||||
              ...this._choices.map((choice) => ({
 | 
			
		||||
                label: String(choice[1]),
 | 
			
		||||
                value: String(choice[0]),
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import {FormLayoutNode, SELECT_PLACEHOLDER} from 'app/client/components/FormRenderer';
 | 
			
		||||
import {FormLayoutNode, selectPlaceholder} from 'app/client/components/FormRenderer';
 | 
			
		||||
import {buildEditor} from 'app/client/components/Forms/Editor';
 | 
			
		||||
import {FormView} from 'app/client/components/Forms/FormView';
 | 
			
		||||
import {BoxModel, ignoreClick} from 'app/client/components/Forms/Model';
 | 
			
		||||
@ -406,7 +406,7 @@ class ChoiceModel extends Question {
 | 
			
		||||
      ignoreClick,
 | 
			
		||||
      dom.prop('name', use => use(use(this.field).colId)),
 | 
			
		||||
      dom('option',
 | 
			
		||||
        SELECT_PLACEHOLDER,
 | 
			
		||||
        selectPlaceholder(),
 | 
			
		||||
        {value: ''},
 | 
			
		||||
      ),
 | 
			
		||||
      dom.forEach(this.choices, (choice) => dom('option',
 | 
			
		||||
@ -616,7 +616,7 @@ class RefModel extends RefListModel {
 | 
			
		||||
      ignoreClick,
 | 
			
		||||
      dom.prop('name', this.model.colId),
 | 
			
		||||
      dom('option',
 | 
			
		||||
        SELECT_PLACEHOLDER,
 | 
			
		||||
        selectPlaceholder(),
 | 
			
		||||
        {value: ''},
 | 
			
		||||
      ),
 | 
			
		||||
      dom.forEach(this.options, ({label, value}) => dom('option',
 | 
			
		||||
 | 
			
		||||
@ -172,7 +172,7 @@ export function showTipPopup(
 | 
			
		||||
        cssBehavioralPromptHeader(
 | 
			
		||||
          cssHeaderIconAndText(
 | 
			
		||||
            icon('Idea'),
 | 
			
		||||
            cssHeaderText('TIP'),
 | 
			
		||||
            cssHeaderText(t('TIP')),
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
        cssBehavioralPromptBody(
 | 
			
		||||
 | 
			
		||||
@ -309,7 +309,7 @@ class OnBoardingPopupsCtl extends Disposable {
 | 
			
		||||
      ),
 | 
			
		||||
      Buttons(
 | 
			
		||||
        bigBasicButton(
 | 
			
		||||
          'Previous', testId('previous'),
 | 
			
		||||
          t('Previous'), testId('previous'),
 | 
			
		||||
          dom.on('click', () => this._move(-1)),
 | 
			
		||||
          dom.prop('disabled', isFirstStep),
 | 
			
		||||
          {style: `margin-right: 8px; visibility: ${isFirstStep ? 'hidden' : 'visible'}`},
 | 
			
		||||
 | 
			
		||||
@ -88,7 +88,7 @@ export type RemoveTable = ['RemoveTable', string];
 | 
			
		||||
export type RenameTable = ['RenameTable', string, string];
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Data actions take a numeric `rowId` (or a list of them, for “bulk” actions) and a set of values:
 | 
			
		||||
Data actions take a string `tableId` and a numeric `rowId` (or a list of them, for “bulk” actions) and a set of values:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
export interface ColValues { [colId: string]: CellValue; }
 | 
			
		||||
 | 
			
		||||
@ -157,11 +157,11 @@ describe('DropdownConditionEditor', function () {
 | 
			
		||||
          'choice\n.Supervisor\n '
 | 
			
		||||
        ]);
 | 
			
		||||
      });
 | 
			
		||||
      await gu.sendKeys('.Role == "Supervisor" and $Role != "Supervisor"', Key.ENTER);
 | 
			
		||||
      await gu.sendKeys('.Role == "Supervisor" and $Role != "Supervisor" and $id != 2', Key.ENTER);
 | 
			
		||||
      await gu.waitForServer();
 | 
			
		||||
      assert.equal(
 | 
			
		||||
        await driver.find('.test-field-dropdown-condition .ace_line').getAttribute('textContent'),
 | 
			
		||||
        'choice.Role == "Supervisor" and $Role != "Supervisor"\n'
 | 
			
		||||
        'choice.Role == "Supervisor" and $Role != "Supervisor" and $id != 2\n'
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      // Check that autocomplete values are filtered.
 | 
			
		||||
@ -171,6 +171,23 @@ describe('DropdownConditionEditor', function () {
 | 
			
		||||
        'Marie Ziyad',
 | 
			
		||||
      ]);
 | 
			
		||||
      await gu.sendKeys(Key.ESCAPE);
 | 
			
		||||
 | 
			
		||||
      // Should be no options on row 2 because of $id != 2 part of condition.
 | 
			
		||||
      await gu.getCell(2, 2).click();
 | 
			
		||||
      await gu.sendKeys(Key.ENTER);
 | 
			
		||||
      assert.deepEqual(await driver.findAll('.test-autocomplete li', (el) => el.getText()), [
 | 
			
		||||
      ]);
 | 
			
		||||
      await gu.sendKeys(Key.ESCAPE);
 | 
			
		||||
 | 
			
		||||
      // Row 3 should be like row 1.
 | 
			
		||||
      await gu.getCell(2, 3).click();
 | 
			
		||||
      await gu.sendKeys(Key.ENTER);
 | 
			
		||||
      assert.deepEqual(await driver.findAll('.test-autocomplete li', (el) => el.getText()), [
 | 
			
		||||
        'Pavan Madilyn',
 | 
			
		||||
        'Marie Ziyad',
 | 
			
		||||
      ]);
 | 
			
		||||
      await gu.sendKeys(Key.ESCAPE);
 | 
			
		||||
 | 
			
		||||
      await gu.getCell(2, 4).click();
 | 
			
		||||
      await gu.sendKeys(Key.ENTER);
 | 
			
		||||
      assert.isEmpty(await driver.findAll('.test-autocomplete li', (el) => el.getText()));
 | 
			
		||||
@ -187,7 +204,7 @@ describe('DropdownConditionEditor', function () {
 | 
			
		||||
      await gu.setType('Reference List', {apply: true});
 | 
			
		||||
      assert.equal(
 | 
			
		||||
        await driver.find('.test-field-dropdown-condition .ace_line').getAttribute('textContent'),
 | 
			
		||||
        'choice.Role == "Supervisor" and $Role != "Supervisor"\n'
 | 
			
		||||
        'choice.Role == "Supervisor" and $Role != "Supervisor" and $id != 2\n'
 | 
			
		||||
      );
 | 
			
		||||
      await gu.getCell(2, 4).click();
 | 
			
		||||
      await gu.sendKeys(Key.ENTER);
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user