mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +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>;
|
disableModify: ko.Computed<boolean>;
|
||||||
disableEditData: ko.Computed<boolean>;
|
disableEditData: ko.Computed<boolean>;
|
||||||
|
|
||||||
textColor: modelUtil.KoSaveableObservable<string>;
|
textColor: modelUtil.KoSaveableObservable<string|undefined>;
|
||||||
fillColor: modelUtil.KoSaveableObservable<string>;
|
fillColor: modelUtil.KoSaveableObservable<string>;
|
||||||
|
|
||||||
// Helper which adds/removes/updates field's displayCol to match the formula.
|
// 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.disableModify = ko.pureComputed(() => this.column().disableModify());
|
||||||
this.disableEditData = ko.pureComputed(() => this.column().disableEditData());
|
this.disableEditData = ko.pureComputed(() => this.column().disableEditData());
|
||||||
|
|
||||||
this.textColor = modelUtil.fieldWithDefault(
|
this.textColor = this.widgetOptionsJson.prop('textColor') as modelUtil.KoSaveableObservable<string>;
|
||||||
this.widgetOptionsJson.prop('textColor') as modelUtil.KoSaveableObservable<string>, '');
|
|
||||||
|
|
||||||
const fillColorProp = modelUtil.fieldWithDefault(
|
const fillColorProp = modelUtil.fieldWithDefault(
|
||||||
this.widgetOptionsJson.prop('fillColor') as modelUtil.KoSaveableObservable<string>, "#FFFFFF00");
|
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 { colors, testId, vars } from 'app/client/ui2018/cssVars';
|
||||||
import { icon } from "app/client/ui2018/icons";
|
import { icon } from "app/client/ui2018/icons";
|
||||||
import { Computed, Disposable, dom, DomArg, Observable, onKeyDown, styled } from "grainjs";
|
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
|
* 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(
|
cssButtonIcon(
|
||||||
'T',
|
'T',
|
||||||
dom.style('color', textColor),
|
dom.style('color', textColor),
|
||||||
dom.style('background-color', fillColor),
|
dom.style('background-color', (use) => use(fillColor).slice(0, 7)),
|
||||||
cssLightBorder.cls(''),
|
cssLightBorder.cls(''),
|
||||||
testId('btn-icon'),
|
testId('btn-icon'),
|
||||||
),
|
),
|
||||||
@ -28,10 +28,7 @@ export function colorSelect(textColor: Observable<string>, fillColor: Observable
|
|||||||
);
|
);
|
||||||
|
|
||||||
const domCreator = (ctl: IOpenController) => buildColorPicker(ctl, textColor, fillColor, onSave);
|
const domCreator = (ctl: IOpenController) => buildColorPicker(ctl, textColor, fillColor, onSave);
|
||||||
setPopupToCreateDom(selectBtn, domCreator, {
|
setPopupToCreateDom(selectBtn, domCreator, {...defaultMenuOptions, placement: 'bottom-end'});
|
||||||
trigger: ['click'],
|
|
||||||
placement: 'bottom-end',
|
|
||||||
});
|
|
||||||
|
|
||||||
return selectBtn;
|
return selectBtn;
|
||||||
}
|
}
|
||||||
@ -72,13 +69,13 @@ function buildColorPicker(ctl: IOpenController, textColor: Observable<string>, f
|
|||||||
return cssContainer(
|
return cssContainer(
|
||||||
dom.create(PickerComponent, fillColorModel, {
|
dom.create(PickerComponent, fillColorModel, {
|
||||||
colorSquare: colorSquare(),
|
colorSquare: colorSquare(),
|
||||||
title: 'Fill',
|
title: 'fill',
|
||||||
defaultMode: 'lighter'
|
defaultMode: 'lighter'
|
||||||
}),
|
}),
|
||||||
cssVSpacer(),
|
cssVSpacer(),
|
||||||
dom.create(PickerComponent, textColorModel, {
|
dom.create(PickerComponent, textColorModel, {
|
||||||
colorSquare: colorSquare('T'),
|
colorSquare: colorSquare('T'),
|
||||||
title: 'Text',
|
title: 'text',
|
||||||
defaultMode: 'darker'
|
defaultMode: 'darker'
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@ -132,7 +129,7 @@ class PickerModel extends Disposable {
|
|||||||
|
|
||||||
class PickerComponent 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());
|
private _mode = Observable.create<'darker'|'lighter'>(this, this._guessMode());
|
||||||
|
|
||||||
constructor(private _model: PickerModel, private _options: PickerComponentOptions) {
|
constructor(private _model: PickerModel, private _options: PickerComponentOptions) {
|
||||||
@ -155,7 +152,7 @@ class PickerComponent extends Disposable {
|
|||||||
),
|
),
|
||||||
// TODO: make it possible to type in hex value.
|
// TODO: make it possible to type in hex value.
|
||||||
cssHexBox(
|
cssHexBox(
|
||||||
dom.attr('value', (use) => use(this._color || '#000000')),
|
dom.attr('value', this._color),
|
||||||
{readonly: true},
|
{readonly: true},
|
||||||
dom.on('click', (ev, e) => e.select()),
|
dom.on('click', (ev, e) => e.select()),
|
||||||
testId(`${title}-hex`),
|
testId(`${title}-hex`),
|
||||||
@ -244,6 +241,7 @@ const cssHeaderRow = styled('div', `
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
|
text-transform: capitalize;
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
|
||||||
@ -265,6 +263,8 @@ const cssContainer = styled('div', `
|
|||||||
padding: 18px 16px;
|
padding: 18px 16px;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
box-shadow: 0 2px 16px 0 rgba(38,38,51,0.6);
|
box-shadow: 0 2px 16px 0 rgba(38,38,51,0.6);
|
||||||
|
z-index: 20;
|
||||||
|
margin: 2px 0;
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
@ -318,4 +318,5 @@ const cssSelectBtn = styled('div', `
|
|||||||
padding: 5px 9px;
|
padding: 5px 9px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
background-color: white;
|
||||||
`);
|
`);
|
||||||
|
@ -1,24 +1,28 @@
|
|||||||
var dispose = require('../lib/dispose');
|
var dispose = require('../lib/dispose');
|
||||||
const ko = require('knockout');
|
const ko = require('knockout');
|
||||||
const {fromKo} = require('grainjs');
|
const {Computed, fromKo} = require('grainjs');
|
||||||
const ValueFormatter = require('app/common/ValueFormatter');
|
const ValueFormatter = require('app/common/ValueFormatter');
|
||||||
|
|
||||||
const {cssLabel, cssRow} = require('app/client/ui/RightPanel');
|
const {cssLabel, cssRow} = require('app/client/ui/RightPanel');
|
||||||
const {colorSelect} = require('app/client/ui2018/buttonSelect');
|
const {colorSelect} = require('app/client/ui2018/ColorSelect');
|
||||||
const {testId} = require('app/client/ui2018/cssVars');
|
|
||||||
const {cssHalfWidth, cssInlineLabel} = require('app/client/widgets/NewAbstractWidget');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AbstractWidget - The base of the inheritance tree for widgets.
|
* AbstractWidget - The base of the inheritance tree for widgets.
|
||||||
* @param {Function} field - The RowModel for this view field.
|
* @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.field = field;
|
||||||
this.options = field.widgetOptionsJson;
|
this.options = field.widgetOptionsJson;
|
||||||
|
const {defaultTextColor = '#000000'} = opts;
|
||||||
|
|
||||||
this.valueFormatter = this.autoDispose(ko.computed(() => {
|
this.valueFormatter = this.autoDispose(ko.computed(() => {
|
||||||
return ValueFormatter.createFormatter(field.displayColModel().type(), this.options());
|
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);
|
dispose.makeDisposable(AbstractWidget);
|
||||||
|
|
||||||
@ -49,21 +53,11 @@ AbstractWidget.prototype.buildColorConfigDom = function() {
|
|||||||
return [
|
return [
|
||||||
cssLabel('CELL COLOR'),
|
cssLabel('CELL COLOR'),
|
||||||
cssRow(
|
cssRow(
|
||||||
cssHalfWidth(
|
|
||||||
colorSelect(
|
|
||||||
fromKo(this.field.textColor),
|
|
||||||
(val) => this.field.textColor.saveOnly(val),
|
|
||||||
testId('text-color'),
|
|
||||||
),
|
|
||||||
cssInlineLabel('Text')
|
|
||||||
),
|
|
||||||
cssHalfWidth(
|
|
||||||
colorSelect(
|
colorSelect(
|
||||||
|
this.textColor,
|
||||||
fromKo(this.field.fillColor),
|
fromKo(this.field.fillColor),
|
||||||
(val) => this.field.fillColor.saveOnly(val),
|
// Calling `field.widgetOptionsJson.save()` saves both fill and text color settings.
|
||||||
testId('fill-color'),
|
() => this.field.widgetOptionsJson.save()
|
||||||
),
|
|
||||||
cssInlineLabel('Fill')
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
|
@ -8,7 +8,7 @@ var AbstractWidget = require('./AbstractWidget');
|
|||||||
* CheckBox - A bi-state CheckBox widget
|
* CheckBox - A bi-state CheckBox widget
|
||||||
*/
|
*/
|
||||||
function CheckBox(field) {
|
function CheckBox(field) {
|
||||||
AbstractWidget.call(this, field);
|
AbstractWidget.call(this, field, {defaultTextColor: '#606060'});
|
||||||
}
|
}
|
||||||
dispose.makeDisposable(CheckBox);
|
dispose.makeDisposable(CheckBox);
|
||||||
_.extend(CheckBox.prototype, AbstractWidget.prototype);
|
_.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.
|
if (this.isDisposed()) { return null; } // Work around JS errors during field removal.
|
||||||
const cellDom = widget ? widget.buildDom(row) : buildErrorDom(row, this.field);
|
const cellDom = widget ? widget.buildDom(row) : buildErrorDom(row, this.field);
|
||||||
return dom(cellDom, kd.toggleClass('has_cursor', isActive),
|
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));
|
kd.style('--grist-cell-background-color', this.field.fillColor));
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -12,7 +12,7 @@ import {dom, styled} from 'grainjs';
|
|||||||
*/
|
*/
|
||||||
export class HyperLinkTextBox extends NTextBox {
|
export class HyperLinkTextBox extends NTextBox {
|
||||||
constructor(field: ViewFieldRec) {
|
constructor(field: ViewFieldRec) {
|
||||||
super(field);
|
super(field, {defaultTextColor: colors.lightGreen.value});
|
||||||
}
|
}
|
||||||
|
|
||||||
public buildDom(row: DataRowModel) {
|
public buildDom(row: DataRowModel) {
|
||||||
|
@ -4,7 +4,7 @@ import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec';
|
|||||||
import {cssRow} from 'app/client/ui/RightPanel';
|
import {cssRow} from 'app/client/ui/RightPanel';
|
||||||
import {alignmentSelect, makeButtonSelect} from 'app/client/ui2018/buttonSelect';
|
import {alignmentSelect, makeButtonSelect} from 'app/client/ui2018/buttonSelect';
|
||||||
import {testId} from 'app/client/ui2018/cssVars';
|
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';
|
import {dom, DomContents, fromKo, Observable} from 'grainjs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -14,8 +14,8 @@ export class NTextBox extends NewAbstractWidget {
|
|||||||
protected alignment: Observable<string>;
|
protected alignment: Observable<string>;
|
||||||
protected wrapping: Observable<boolean>;
|
protected wrapping: Observable<boolean>;
|
||||||
|
|
||||||
constructor(field: ViewFieldRec) {
|
constructor(field: ViewFieldRec, options: Options = {}) {
|
||||||
super(field);
|
super(field, options);
|
||||||
|
|
||||||
this.alignment = fromKoSave<string>(this.options.prop('alignment'));
|
this.alignment = fromKoSave<string>(this.options.prop('alignment'));
|
||||||
this.wrapping = fromKo(this.field.wrapping);
|
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 {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec';
|
||||||
import {SaveableObjObservable} from 'app/client/models/modelUtil';
|
import {SaveableObjObservable} from 'app/client/models/modelUtil';
|
||||||
import {cssLabel, cssRow} from 'app/client/ui/RightPanel';
|
import {cssLabel, cssRow} from 'app/client/ui/RightPanel';
|
||||||
import {colorSelect} from 'app/client/ui2018/buttonSelect';
|
import {colorSelect} from 'app/client/ui2018/ColorSelect';
|
||||||
import {colors, testId} from 'app/client/ui2018/cssVars';
|
|
||||||
import {BaseFormatter, createFormatter} from 'app/common/ValueFormatter';
|
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';
|
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.
|
* NewAbstractWidget - The base of the inheritance tree for widgets.
|
||||||
* @param {Function} field - The RowModel for this view field.
|
* @param {Function} field - The RowModel for this view field.
|
||||||
@ -31,10 +35,13 @@ export abstract class NewAbstractWidget extends Disposable {
|
|||||||
protected textColor: Observable<string>;
|
protected textColor: Observable<string>;
|
||||||
protected fillColor: Observable<string>;
|
protected fillColor: Observable<string>;
|
||||||
|
|
||||||
constructor(protected field: ViewFieldRec) {
|
constructor(protected field: ViewFieldRec, opts: Options = {}) {
|
||||||
super();
|
super();
|
||||||
|
const {defaultTextColor = '#000000'} = opts;
|
||||||
this.options = field.widgetOptionsJson;
|
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);
|
this.fillColor = fromKo(this.field.fillColor);
|
||||||
|
|
||||||
// Note that its easier to create a knockout computed from the several knockout observables,
|
// 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 [
|
return [
|
||||||
cssLabel('CELL COLOR'),
|
cssLabel('CELL COLOR'),
|
||||||
cssRow(
|
cssRow(
|
||||||
cssHalfWidth(
|
|
||||||
colorSelect(
|
colorSelect(
|
||||||
this.textColor,
|
this.textColor,
|
||||||
(val) => this.field.textColor.saveOnly(val),
|
|
||||||
testId('text-color')
|
|
||||||
),
|
|
||||||
cssInlineLabel('Text'),
|
|
||||||
),
|
|
||||||
cssHalfWidth(
|
|
||||||
colorSelect(
|
|
||||||
this.fillColor,
|
this.fillColor,
|
||||||
(val) => this.field.fillColor.saveOnly(val),
|
// Calling `field.widgetOptionsJson.save()` saves both fill and text color settings.
|
||||||
testId('fill-color')
|
() => this.field.widgetOptionsJson.save()
|
||||||
),
|
|
||||||
cssInlineLabel('Fill')
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
@ -98,14 +95,3 @@ export abstract class NewAbstractWidget extends Disposable {
|
|||||||
*/
|
*/
|
||||||
protected _getDocComm(): DocComm { return this._getDocData().docComm; }
|
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
|
* Switch - A bi-state Switch widget
|
||||||
*/
|
*/
|
||||||
function Switch(field) {
|
function Switch(field) {
|
||||||
AbstractWidget.call(this, field);
|
AbstractWidget.call(this, field, {defaultTextColor: '#2CB0AF'});
|
||||||
}
|
}
|
||||||
dispose.makeDisposable(Switch);
|
dispose.makeDisposable(Switch);
|
||||||
_.extend(Switch.prototype, AbstractWidget.prototype);
|
_.extend(Switch.prototype, AbstractWidget.prototype);
|
||||||
|
Loading…
Reference in New Issue
Block a user