(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:
Cyprien P 2021-03-02 13:27:08 +01:00
parent 4ab096d179
commit 1995a96178
9 changed files with 51 additions and 71 deletions

View File

@ -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");

View File

@ -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;
`);

View File

@ -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(
this.textColor,
fromKo(this.field.fillColor),
(val) => this.field.fillColor.saveOnly(val),
testId('fill-color'),
),
cssInlineLabel('Fill')
// Calling `field.widgetOptionsJson.save()` saves both fill and text color settings.
() => this.field.widgetOptionsJson.save()
)
)
];

View File

@ -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);

View File

@ -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));
})
);

View File

@ -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) {

View File

@ -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);

View File

@ -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')
// 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};
`);

View File

@ -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);