mirror of
				https://github.com/gristlabs/grist-core.git
				synced 2025-06-13 20:53:59 +00:00 
			
		
		
		
	(core) Add new color select to the app
Summary: - Fix transparency support on color select - Fix z-index conflicts with color select and right panel - Makes widget's default text color visible to color select Test Plan: - Updates nbrowser/CellColor and browser/Widget.test to support new interface. Should not cause regression. Reviewers: paulfitz, dsagal Reviewed By: dsagal Differential Revision: https://phab.getgrist.com/D2735
This commit is contained in:
		
							parent
							
								
									4ab096d179
								
							
						
					
					
						commit
						1995a96178
					
				@ -70,7 +70,7 @@ export interface ViewFieldRec extends IRowModel<"_grist_Views_section_field"> {
 | 
			
		||||
  disableModify: ko.Computed<boolean>;
 | 
			
		||||
  disableEditData: ko.Computed<boolean>;
 | 
			
		||||
 | 
			
		||||
  textColor: modelUtil.KoSaveableObservable<string>;
 | 
			
		||||
  textColor: modelUtil.KoSaveableObservable<string|undefined>;
 | 
			
		||||
  fillColor: modelUtil.KoSaveableObservable<string>;
 | 
			
		||||
 | 
			
		||||
  // Helper which adds/removes/updates field's displayCol to match the formula.
 | 
			
		||||
@ -201,8 +201,7 @@ export function createViewFieldRec(this: ViewFieldRec, docModel: DocModel): void
 | 
			
		||||
  this.disableModify = ko.pureComputed(() => this.column().disableModify());
 | 
			
		||||
  this.disableEditData = ko.pureComputed(() => this.column().disableEditData());
 | 
			
		||||
 | 
			
		||||
  this.textColor = modelUtil.fieldWithDefault(
 | 
			
		||||
    this.widgetOptionsJson.prop('textColor') as modelUtil.KoSaveableObservable<string>, '');
 | 
			
		||||
  this.textColor = this.widgetOptionsJson.prop('textColor') as modelUtil.KoSaveableObservable<string>;
 | 
			
		||||
 | 
			
		||||
  const fillColorProp = modelUtil.fieldWithDefault(
 | 
			
		||||
    this.widgetOptionsJson.prop('fillColor') as modelUtil.KoSaveableObservable<string>, "#FFFFFF00");
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@ import { darker, lighter } from "app/client/ui2018/ColorPalette";
 | 
			
		||||
import { colors, testId, vars } from 'app/client/ui2018/cssVars';
 | 
			
		||||
import { icon } from "app/client/ui2018/icons";
 | 
			
		||||
import { Computed, Disposable, dom, DomArg, Observable, onKeyDown, styled } from "grainjs";
 | 
			
		||||
import { IOpenController, setPopupToCreateDom } from "popweasel";
 | 
			
		||||
import { defaultMenuOptions, IOpenController, setPopupToCreateDom } from "popweasel";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * colorSelect allows to select color for both fill and text cell color. It allows for fast
 | 
			
		||||
@ -17,7 +17,7 @@ export function colorSelect(textColor: Observable<string>, fillColor: Observable
 | 
			
		||||
      cssButtonIcon(
 | 
			
		||||
        'T',
 | 
			
		||||
        dom.style('color', textColor),
 | 
			
		||||
        dom.style('background-color', fillColor),
 | 
			
		||||
        dom.style('background-color', (use) => use(fillColor).slice(0, 7)),
 | 
			
		||||
        cssLightBorder.cls(''),
 | 
			
		||||
        testId('btn-icon'),
 | 
			
		||||
      ),
 | 
			
		||||
@ -28,10 +28,7 @@ export function colorSelect(textColor: Observable<string>, fillColor: Observable
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const domCreator = (ctl: IOpenController) => buildColorPicker(ctl, textColor, fillColor, onSave);
 | 
			
		||||
  setPopupToCreateDom(selectBtn, domCreator, {
 | 
			
		||||
    trigger: ['click'],
 | 
			
		||||
    placement: 'bottom-end',
 | 
			
		||||
  });
 | 
			
		||||
  setPopupToCreateDom(selectBtn, domCreator, {...defaultMenuOptions, placement: 'bottom-end'});
 | 
			
		||||
 | 
			
		||||
  return selectBtn;
 | 
			
		||||
}
 | 
			
		||||
@ -72,13 +69,13 @@ function buildColorPicker(ctl: IOpenController, textColor: Observable<string>, f
 | 
			
		||||
  return cssContainer(
 | 
			
		||||
    dom.create(PickerComponent, fillColorModel, {
 | 
			
		||||
      colorSquare: colorSquare(),
 | 
			
		||||
      title: 'Fill',
 | 
			
		||||
      title: 'fill',
 | 
			
		||||
      defaultMode: 'lighter'
 | 
			
		||||
    }),
 | 
			
		||||
    cssVSpacer(),
 | 
			
		||||
    dom.create(PickerComponent, textColorModel, {
 | 
			
		||||
      colorSquare: colorSquare('T'),
 | 
			
		||||
      title: 'Text',
 | 
			
		||||
      title: 'text',
 | 
			
		||||
      defaultMode: 'darker'
 | 
			
		||||
    }),
 | 
			
		||||
 | 
			
		||||
@ -132,7 +129,7 @@ class PickerModel extends Disposable {
 | 
			
		||||
 | 
			
		||||
class PickerComponent extends Disposable {
 | 
			
		||||
 | 
			
		||||
  private _color = Computed.create(this, this._model.obs, (use, val) => val.toUpperCase());
 | 
			
		||||
  private _color = Computed.create(this, this._model.obs, (use, val) => val.toUpperCase().slice(0, 7));
 | 
			
		||||
  private _mode = Observable.create<'darker'|'lighter'>(this, this._guessMode());
 | 
			
		||||
 | 
			
		||||
  constructor(private _model: PickerModel, private _options: PickerComponentOptions) {
 | 
			
		||||
@ -155,7 +152,7 @@ class PickerComponent extends Disposable {
 | 
			
		||||
          ),
 | 
			
		||||
          // TODO: make it possible to type in hex value.
 | 
			
		||||
          cssHexBox(
 | 
			
		||||
            dom.attr('value', (use) => use(this._color || '#000000')),
 | 
			
		||||
            dom.attr('value', this._color),
 | 
			
		||||
            {readonly: true},
 | 
			
		||||
            dom.on('click', (ev, e) => e.select()),
 | 
			
		||||
            testId(`${title}-hex`),
 | 
			
		||||
@ -244,6 +241,7 @@ const cssHeaderRow = styled('div', `
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
  margin-bottom: 8px;
 | 
			
		||||
  text-transform: capitalize;
 | 
			
		||||
`);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -265,6 +263,8 @@ const cssContainer = styled('div', `
 | 
			
		||||
  padding: 18px 16px;
 | 
			
		||||
  background-color: white;
 | 
			
		||||
  box-shadow: 0 2px 16px 0 rgba(38,38,51,0.6);
 | 
			
		||||
  z-index: 20;
 | 
			
		||||
  margin: 2px 0;
 | 
			
		||||
  &:focus {
 | 
			
		||||
    outline: none;
 | 
			
		||||
  }
 | 
			
		||||
@ -318,4 +318,5 @@ const cssSelectBtn = styled('div', `
 | 
			
		||||
  padding: 5px 9px;
 | 
			
		||||
  user-select: none;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  background-color: white;
 | 
			
		||||
`);
 | 
			
		||||
 | 
			
		||||
@ -1,24 +1,28 @@
 | 
			
		||||
var dispose = require('../lib/dispose');
 | 
			
		||||
const ko = require('knockout');
 | 
			
		||||
const {fromKo} = require('grainjs');
 | 
			
		||||
const {Computed, fromKo} = require('grainjs');
 | 
			
		||||
const ValueFormatter = require('app/common/ValueFormatter');
 | 
			
		||||
 | 
			
		||||
const {cssLabel, cssRow} = require('app/client/ui/RightPanel');
 | 
			
		||||
const {colorSelect} = require('app/client/ui2018/buttonSelect');
 | 
			
		||||
const {testId} = require('app/client/ui2018/cssVars');
 | 
			
		||||
const {cssHalfWidth, cssInlineLabel} = require('app/client/widgets/NewAbstractWidget');
 | 
			
		||||
const {colorSelect} = require('app/client/ui2018/ColorSelect');
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * AbstractWidget - The base of the inheritance tree for widgets.
 | 
			
		||||
 * @param {Function} field - The RowModel for this view field.
 | 
			
		||||
 * @param {string|undefined} options.defaultTextColor - A hex value to set the default text color
 | 
			
		||||
 * for the widget. Omit defaults to '#000000'.
 | 
			
		||||
 */
 | 
			
		||||
function AbstractWidget(field) {
 | 
			
		||||
function AbstractWidget(field, opts = {}) {
 | 
			
		||||
  this.field = field;
 | 
			
		||||
  this.options = field.widgetOptionsJson;
 | 
			
		||||
  const {defaultTextColor = '#000000'} = opts;
 | 
			
		||||
 | 
			
		||||
  this.valueFormatter = this.autoDispose(ko.computed(() => {
 | 
			
		||||
    return ValueFormatter.createFormatter(field.displayColModel().type(), this.options());
 | 
			
		||||
  }));
 | 
			
		||||
 | 
			
		||||
  this.textColor = Computed.create(this, (use) => use(this.field.textColor) || defaultTextColor)
 | 
			
		||||
    .onWrite((val) => this.field.textColor(val === defaultTextColor ? undefined : val));
 | 
			
		||||
}
 | 
			
		||||
dispose.makeDisposable(AbstractWidget);
 | 
			
		||||
 | 
			
		||||
@ -49,21 +53,11 @@ AbstractWidget.prototype.buildColorConfigDom = function() {
 | 
			
		||||
  return [
 | 
			
		||||
    cssLabel('CELL COLOR'),
 | 
			
		||||
    cssRow(
 | 
			
		||||
      cssHalfWidth(
 | 
			
		||||
        colorSelect(
 | 
			
		||||
          fromKo(this.field.textColor),
 | 
			
		||||
          (val) => this.field.textColor.saveOnly(val),
 | 
			
		||||
          testId('text-color'),
 | 
			
		||||
        ),
 | 
			
		||||
        cssInlineLabel('Text')
 | 
			
		||||
      ),
 | 
			
		||||
      cssHalfWidth(
 | 
			
		||||
        colorSelect(
 | 
			
		||||
          fromKo(this.field.fillColor),
 | 
			
		||||
          (val) => this.field.fillColor.saveOnly(val),
 | 
			
		||||
          testId('fill-color'),
 | 
			
		||||
        ),
 | 
			
		||||
        cssInlineLabel('Fill')
 | 
			
		||||
      colorSelect(
 | 
			
		||||
        this.textColor,
 | 
			
		||||
        fromKo(this.field.fillColor),
 | 
			
		||||
        // Calling `field.widgetOptionsJson.save()` saves both fill and text color settings.
 | 
			
		||||
        () => this.field.widgetOptionsJson.save()
 | 
			
		||||
      )
 | 
			
		||||
    )
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,7 @@ var AbstractWidget = require('./AbstractWidget');
 | 
			
		||||
 * CheckBox - A bi-state CheckBox widget
 | 
			
		||||
 */
 | 
			
		||||
function CheckBox(field) {
 | 
			
		||||
  AbstractWidget.call(this, field);
 | 
			
		||||
  AbstractWidget.call(this, field, {defaultTextColor: '#606060'});
 | 
			
		||||
}
 | 
			
		||||
dispose.makeDisposable(CheckBox);
 | 
			
		||||
_.extend(CheckBox.prototype, AbstractWidget.prototype);
 | 
			
		||||
 | 
			
		||||
@ -421,7 +421,7 @@ export class FieldBuilder extends Disposable {
 | 
			
		||||
            if (this.isDisposed()) { return null; }   // Work around JS errors during field removal.
 | 
			
		||||
            const cellDom = widget ? widget.buildDom(row) : buildErrorDom(row, this.field);
 | 
			
		||||
            return dom(cellDom, kd.toggleClass('has_cursor', isActive),
 | 
			
		||||
                       kd.style('--grist-cell-color', this.field.textColor),
 | 
			
		||||
                       kd.style('--grist-cell-color', () => this.field.textColor() || ''),
 | 
			
		||||
                       kd.style('--grist-cell-background-color', this.field.fillColor));
 | 
			
		||||
          })
 | 
			
		||||
         );
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,7 @@ import {dom, styled} from 'grainjs';
 | 
			
		||||
 */
 | 
			
		||||
export class HyperLinkTextBox extends NTextBox {
 | 
			
		||||
  constructor(field: ViewFieldRec) {
 | 
			
		||||
    super(field);
 | 
			
		||||
    super(field, {defaultTextColor: colors.lightGreen.value});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public buildDom(row: DataRowModel) {
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,7 @@ import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec';
 | 
			
		||||
import {cssRow} from 'app/client/ui/RightPanel';
 | 
			
		||||
import {alignmentSelect, makeButtonSelect} from 'app/client/ui2018/buttonSelect';
 | 
			
		||||
import {testId} from 'app/client/ui2018/cssVars';
 | 
			
		||||
import {NewAbstractWidget} from 'app/client/widgets/NewAbstractWidget';
 | 
			
		||||
import {NewAbstractWidget, Options} from 'app/client/widgets/NewAbstractWidget';
 | 
			
		||||
import {dom, DomContents, fromKo, Observable} from 'grainjs';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@ -14,8 +14,8 @@ export class NTextBox extends NewAbstractWidget {
 | 
			
		||||
  protected alignment: Observable<string>;
 | 
			
		||||
  protected wrapping: Observable<boolean>;
 | 
			
		||||
 | 
			
		||||
  constructor(field: ViewFieldRec) {
 | 
			
		||||
    super(field);
 | 
			
		||||
  constructor(field: ViewFieldRec, options: Options = {}) {
 | 
			
		||||
    super(field, options);
 | 
			
		||||
 | 
			
		||||
    this.alignment = fromKoSave<string>(this.options.prop('alignment'));
 | 
			
		||||
    this.wrapping = fromKo(this.field.wrapping);
 | 
			
		||||
 | 
			
		||||
@ -7,13 +7,17 @@ import {DocData} from 'app/client/models/DocData';
 | 
			
		||||
import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec';
 | 
			
		||||
import {SaveableObjObservable} from 'app/client/models/modelUtil';
 | 
			
		||||
import {cssLabel, cssRow} from 'app/client/ui/RightPanel';
 | 
			
		||||
import {colorSelect} from 'app/client/ui2018/buttonSelect';
 | 
			
		||||
import {colors, testId} from 'app/client/ui2018/cssVars';
 | 
			
		||||
import {colorSelect} from 'app/client/ui2018/ColorSelect';
 | 
			
		||||
import {BaseFormatter, createFormatter} from 'app/common/ValueFormatter';
 | 
			
		||||
import {Disposable, DomContents, fromKo, Observable, styled} from 'grainjs';
 | 
			
		||||
import {Computed, Disposable, DomContents, fromKo, Observable} from 'grainjs';
 | 
			
		||||
import * as ko from 'knockout';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export interface Options {
 | 
			
		||||
  // A hex value to set the default widget text color. Default to '#000000' if omitted.
 | 
			
		||||
  defaultTextColor?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * NewAbstractWidget - The base of the inheritance tree for widgets.
 | 
			
		||||
 * @param {Function} field - The RowModel for this view field.
 | 
			
		||||
@ -31,10 +35,13 @@ export abstract class NewAbstractWidget extends Disposable {
 | 
			
		||||
  protected textColor: Observable<string>;
 | 
			
		||||
  protected fillColor: Observable<string>;
 | 
			
		||||
 | 
			
		||||
  constructor(protected field: ViewFieldRec) {
 | 
			
		||||
  constructor(protected field: ViewFieldRec, opts: Options = {}) {
 | 
			
		||||
    super();
 | 
			
		||||
    const {defaultTextColor = '#000000'} = opts;
 | 
			
		||||
    this.options = field.widgetOptionsJson;
 | 
			
		||||
    this.textColor = fromKo(this.field.textColor);
 | 
			
		||||
    this.textColor = Computed.create(this, (use) => (
 | 
			
		||||
      use(this.field.textColor) || defaultTextColor
 | 
			
		||||
    )).onWrite((val) => this.field.textColor(val === defaultTextColor ? undefined : val));
 | 
			
		||||
    this.fillColor = fromKo(this.field.fillColor);
 | 
			
		||||
 | 
			
		||||
    // Note that its easier to create a knockout computed from the several knockout observables,
 | 
			
		||||
@ -59,21 +66,11 @@ export abstract class NewAbstractWidget extends Disposable {
 | 
			
		||||
    return [
 | 
			
		||||
      cssLabel('CELL COLOR'),
 | 
			
		||||
      cssRow(
 | 
			
		||||
        cssHalfWidth(
 | 
			
		||||
          colorSelect(
 | 
			
		||||
            this.textColor,
 | 
			
		||||
            (val) => this.field.textColor.saveOnly(val),
 | 
			
		||||
            testId('text-color')
 | 
			
		||||
          ),
 | 
			
		||||
          cssInlineLabel('Text'),
 | 
			
		||||
        ),
 | 
			
		||||
        cssHalfWidth(
 | 
			
		||||
          colorSelect(
 | 
			
		||||
            this.fillColor,
 | 
			
		||||
            (val) => this.field.fillColor.saveOnly(val),
 | 
			
		||||
            testId('fill-color')
 | 
			
		||||
          ),
 | 
			
		||||
          cssInlineLabel('Fill')
 | 
			
		||||
        colorSelect(
 | 
			
		||||
          this.textColor,
 | 
			
		||||
          this.fillColor,
 | 
			
		||||
          // Calling `field.widgetOptionsJson.save()` saves both fill and text color settings.
 | 
			
		||||
          () => this.field.widgetOptionsJson.save()
 | 
			
		||||
        )
 | 
			
		||||
      )
 | 
			
		||||
    ];
 | 
			
		||||
@ -98,14 +95,3 @@ export abstract class NewAbstractWidget extends Disposable {
 | 
			
		||||
   */
 | 
			
		||||
  protected _getDocComm(): DocComm { return this._getDocData().docComm; }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const cssHalfWidth = styled('div', `
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex: 1 1 50%;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
`);
 | 
			
		||||
 | 
			
		||||
export const cssInlineLabel = styled('span', `
 | 
			
		||||
  margin: 0 8px;
 | 
			
		||||
  color: ${colors.dark};
 | 
			
		||||
`);
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,7 @@ var AbstractWidget = require('./AbstractWidget');
 | 
			
		||||
 * Switch - A bi-state Switch widget
 | 
			
		||||
 */
 | 
			
		||||
function Switch(field) {
 | 
			
		||||
  AbstractWidget.call(this, field);
 | 
			
		||||
  AbstractWidget.call(this, field, {defaultTextColor: '#2CB0AF'});
 | 
			
		||||
}
 | 
			
		||||
dispose.makeDisposable(Switch);
 | 
			
		||||
_.extend(Switch.prototype, AbstractWidget.prototype);
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user