mirror of
				https://github.com/gristlabs/grist-core.git
				synced 2025-06-13 20:53:59 +00:00 
			
		
		
		
	(core) Making cells colors effective also in Card and Card List views
Summary: . Makes cell color work well in comparison mode . Do not apply cell color to the add new row . Allow to change color for all widget (including changing color for the checkbox and the switch widget) . Fix an issue that was setting color to black when opening the picker . Do not apply color to invalid cell Test Plan: . Added nbrowser/CellColor Reviewers: paulfitz, dsagal Reviewed By: dsagal Differential Revision: https://phab.getgrist.com/D2720
This commit is contained in:
		
							parent
							
								
									de1719ee08
								
							
						
					
					
						commit
						890a8709f3
					
				@ -935,10 +935,6 @@ GridView.prototype.buildDom = function() {
 | 
			
		||||
            //TODO: Ensure that fields in a row resize when
 | 
			
		||||
            //a cell in that row becomes larger
 | 
			
		||||
            kd.style('borderRightWidth', v.borderWidthPx),
 | 
			
		||||
            kd.style('color', field.textColor),
 | 
			
		||||
            // If making a comparison, use the background exclusively for
 | 
			
		||||
            // marking that up.
 | 
			
		||||
            self.comparison ? null : kd.style('background-color', () => (row._isAddRow() || isSelected()) ? '' : field.fillColor()),
 | 
			
		||||
 | 
			
		||||
            kd.toggleClass('selected', isSelected),
 | 
			
		||||
            fieldBuilder.buildDomWithCursor(row, isCellActive, isCellSelected)
 | 
			
		||||
 | 
			
		||||
@ -55,10 +55,14 @@
 | 
			
		||||
  text-overflow: ellipsis;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  background-color: var(--grist-diff-background-color, var(--grist-cell-background-color, unset));
 | 
			
		||||
  --grist-actual-cell-color: var(--grist-diff-color, var(--grist-cell-color));
 | 
			
		||||
  color: var(--grist-actual-cell-color, unset);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.field_clip.invalid {
 | 
			
		||||
  background-color: #ffb6c1;
 | 
			
		||||
  color: unset;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.field_clip.invalid:empty {
 | 
			
		||||
@ -159,20 +163,28 @@
 | 
			
		||||
 | 
			
		||||
.diff-local, .diff-local-add {
 | 
			
		||||
  background-color: #dfdfff;
 | 
			
		||||
  --grist-diff-background-color: #dfdfff;
 | 
			
		||||
  --grist-diff-color: black;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.diff-parent, .diff-remote-remove {
 | 
			
		||||
  background-color: #ffdfdf;
 | 
			
		||||
  --grist-diff-background-color: #ffdfdf;
 | 
			
		||||
  --grist-diff-color: black;
 | 
			
		||||
  text-decoration: line-through;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.diff-local-remove {
 | 
			
		||||
  background-color: #dfdfdf;
 | 
			
		||||
  --grist-diff-background-color: #dfdfdf;
 | 
			
		||||
  --grist-diff-color: black;
 | 
			
		||||
  text-decoration: line-through;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.diff-remote, .diff-remote-add {
 | 
			
		||||
  background-color: #afffaf;
 | 
			
		||||
  --grist-diff-background-color: #afffaf;
 | 
			
		||||
  --grist-diff-color: black;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.diff-common {
 | 
			
		||||
 | 
			
		||||
@ -139,7 +139,14 @@ exports.attr = attr;
 | 
			
		||||
 */
 | 
			
		||||
function style(property, valueOrFunc) {
 | 
			
		||||
  return makeBinding(valueOrFunc, function(elem, value) {
 | 
			
		||||
    elem.style[property] = value;
 | 
			
		||||
    // `style.setProperty` must be use to set custom property (ie: properties starting with '--').
 | 
			
		||||
    // However since it does not support camelCase property, we still need to use the other form
 | 
			
		||||
    // `elem.style[prop] = val;` for other properties.
 | 
			
		||||
    if (property.startsWith('--')) {
 | 
			
		||||
      elem.style.setProperty(property, value);
 | 
			
		||||
    } else {
 | 
			
		||||
      elem.style[property] = value;
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
exports.style = style;
 | 
			
		||||
 | 
			
		||||
@ -202,13 +202,14 @@ export function createViewFieldRec(this: ViewFieldRec, docModel: DocModel): void
 | 
			
		||||
  this.disableEditData = ko.pureComputed(() => this.column().disableEditData());
 | 
			
		||||
 | 
			
		||||
  this.textColor = modelUtil.fieldWithDefault(
 | 
			
		||||
    this.widgetOptionsJson.prop('textColor') as modelUtil.KoSaveableObservable<string>, "#000000");
 | 
			
		||||
    this.widgetOptionsJson.prop('textColor') as modelUtil.KoSaveableObservable<string>, '');
 | 
			
		||||
 | 
			
		||||
  const fillColorProp = this.widgetOptionsJson.prop('fillColor') as modelUtil.KoSaveableObservable<string>;
 | 
			
		||||
  const fillColorProp = modelUtil.fieldWithDefault(
 | 
			
		||||
    this.widgetOptionsJson.prop('fillColor') as modelUtil.KoSaveableObservable<string>, "#FFFFFF00");
 | 
			
		||||
  // Store empty string in place of the default white color, so that we can keep it transparent in
 | 
			
		||||
  // GridView, to avoid interfering with zebra stripes.
 | 
			
		||||
  this.fillColor = modelUtil.savingComputed({
 | 
			
		||||
    read: () => fillColorProp(),
 | 
			
		||||
    write: (setter, val) => setter(fillColorProp, val === '#ffffff' ? '' : val),
 | 
			
		||||
    write: (setter, val) => setter(fillColorProp, val.toUpperCase() === '#FFFFFF' ? '' : val),
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -71,7 +71,7 @@ export function alignmentSelect(obs: Observable<string>) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Color selector button. Observable should contain a hex color value, e.g. #a4ba23
 | 
			
		||||
 * Color selector button. Observable should contain a hex color value, e.g. #a4ba23.
 | 
			
		||||
 */
 | 
			
		||||
export function colorSelect(value: Observable<string>, save: (val: string) => Promise<void>,
 | 
			
		||||
                            ...domArgs: DomElementArg[]) {
 | 
			
		||||
@ -89,12 +89,12 @@ export function colorSelect(value: Observable<string>, save: (val: string) => Pr
 | 
			
		||||
    // by the server.
 | 
			
		||||
    cssColorPicker(
 | 
			
		||||
      {type: 'color'},
 | 
			
		||||
      dom.attr('value', value),
 | 
			
		||||
      dom.attr('value', (use) => use(value).slice(0, 7)),
 | 
			
		||||
      dom.on('input', setValue),
 | 
			
		||||
      dom.on('change', onSave)
 | 
			
		||||
    ),
 | 
			
		||||
    dom.style('background-color', (use) => use(value) || '#ffffff'),
 | 
			
		||||
    cssColorBtn.cls('-dark', (use) => isColorDark(use(value) || '#ffffff')),
 | 
			
		||||
    dom.style('background-color', (use) => use(value) || '#000000'),
 | 
			
		||||
    cssColorBtn.cls('-dark', (use) => isColorDark(use(value) || '#000000')),
 | 
			
		||||
    cssColorIcon('Dots'),
 | 
			
		||||
    ...domArgs
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
@ -42,8 +42,8 @@
 | 
			
		||||
  position: relative;
 | 
			
		||||
  width: 3px;
 | 
			
		||||
  height: 12px;
 | 
			
		||||
  background-color: #606060;
 | 
			
		||||
  border: 1px solid #606060;
 | 
			
		||||
  background-color: var(--grist-cell-color, #606060);
 | 
			
		||||
  border: 1px solid var(--grist-cell-color, #606060);
 | 
			
		||||
  left: 3px;
 | 
			
		||||
  top: -5px;
 | 
			
		||||
}
 | 
			
		||||
@ -52,7 +52,7 @@
 | 
			
		||||
  position: relative;
 | 
			
		||||
  width: 3px;
 | 
			
		||||
  height: 3px;
 | 
			
		||||
  background-color: #606060;
 | 
			
		||||
  border: 1px solid #606060;
 | 
			
		||||
  background-color: var(--grist-cell-color, #606060);
 | 
			
		||||
  border: 1px solid var(--grist-cell-color, #606060);
 | 
			
		||||
  top: 7px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ import { DataRowModel } from 'app/client/models/DataRowModel';
 | 
			
		||||
import { NewAbstractWidget } from 'app/client/widgets/NewAbstractWidget';
 | 
			
		||||
import { CellValue } from 'app/common/DocActions';
 | 
			
		||||
import { isVersions } from 'app/common/gristTypes';
 | 
			
		||||
import { inlineStyle } from 'app/common/gutil';
 | 
			
		||||
import { BaseFormatter } from 'app/common/ValueFormatter';
 | 
			
		||||
import { Diff, DIFF_DELETE, DIFF_INSERT, diff_match_patch as DiffMatchPatch, DIFF_EQUAL } from 'diff-match-patch';
 | 
			
		||||
import { Computed, dom } from 'grainjs';
 | 
			
		||||
@ -37,6 +38,8 @@ export class DiffBox extends NewAbstractWidget {
 | 
			
		||||
      dom.autoDispose(formattedValue),
 | 
			
		||||
      dom.style('text-align', this.options.prop('alignment')),
 | 
			
		||||
      dom.cls('text_wrapping', (use) => Boolean(use(this.options.prop('wrap')))),
 | 
			
		||||
      inlineStyle('--grist-diff-color', '#000000'),
 | 
			
		||||
      inlineStyle('--grist-diff-background-color', '#00000000'),
 | 
			
		||||
      dom.forEach(formattedValue, ([code, txt]) => {
 | 
			
		||||
        if (code === DIFF_DELETE) {
 | 
			
		||||
          return dom("span.diff-parent", txt);
 | 
			
		||||
 | 
			
		||||
@ -128,11 +128,13 @@ export class FieldBuilder extends Disposable {
 | 
			
		||||
      write(widget) {
 | 
			
		||||
        // Reset the entire JSON, so that all options revert to their defaults.
 | 
			
		||||
 | 
			
		||||
        const previous = this.options.peek();
 | 
			
		||||
        this.options.setAndSave({
 | 
			
		||||
          widget,
 | 
			
		||||
          // persists color settings across widgets
 | 
			
		||||
          fillColor: field.fillColor.peek(),
 | 
			
		||||
          textColor: field.textColor.peek()
 | 
			
		||||
          // Persists color settings across widgets (note: we cannot use `field.fillColor` to get the
 | 
			
		||||
          // current value because it returns a default value for `undefined`. Same for `field.textColor`.
 | 
			
		||||
          fillColor: previous.fillColor,
 | 
			
		||||
          textColor: previous.textColor,
 | 
			
		||||
        }).catch(reportError);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
@ -418,7 +420,9 @@ export class FieldBuilder extends Disposable {
 | 
			
		||||
          kd.scope(widgetObs, (widget: NewAbstractWidget) => {
 | 
			
		||||
            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));
 | 
			
		||||
            return dom(cellDom, kd.toggleClass('has_cursor', isActive),
 | 
			
		||||
                       kd.style('--grist-cell-color', this.field.textColor),
 | 
			
		||||
                       kd.style('--grist-cell-background-color', this.field.fillColor));
 | 
			
		||||
          })
 | 
			
		||||
         );
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
@ -28,7 +28,7 @@ export class HyperLinkTextBox extends NTextBox {
 | 
			
		||||
          // from running on the same process as Grist:
 | 
			
		||||
          // https://developers.google.com/web/tools/lighthouse/audits/noopener
 | 
			
		||||
          dom.attr('rel', 'noopener noreferrer'),
 | 
			
		||||
          cssLinkIcon('FieldLink'),
 | 
			
		||||
          cssLinkIcon('FieldLink', testId('tb-link-icon')),
 | 
			
		||||
          testId('tb-link')
 | 
			
		||||
        )
 | 
			
		||||
      ),
 | 
			
		||||
@ -62,10 +62,10 @@ function _constructUrl(value: string): string {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const cssFieldClip = styled('div.field_clip', `
 | 
			
		||||
  color: ${colors.lightGreen};
 | 
			
		||||
  color: var(--grist-actual-cell-color, ${colors.lightGreen});
 | 
			
		||||
`);
 | 
			
		||||
 | 
			
		||||
const cssLinkIcon = styled(icon, `
 | 
			
		||||
  background-color: ${colors.lightGreen};
 | 
			
		||||
  background-color: var(--grist-actual-cell-color, ${colors.lightGreen});
 | 
			
		||||
  margin: -1px 2px 2px 0;
 | 
			
		||||
`);
 | 
			
		||||
 | 
			
		||||
@ -28,10 +28,14 @@ export abstract class NewAbstractWidget extends Disposable {
 | 
			
		||||
 | 
			
		||||
  protected options: SaveableObjObservable<any>;
 | 
			
		||||
  protected valueFormatter: Observable<BaseFormatter>;
 | 
			
		||||
  protected textColor: Observable<string>;
 | 
			
		||||
  protected fillColor: Observable<string>;
 | 
			
		||||
 | 
			
		||||
  constructor(protected field: ViewFieldRec) {
 | 
			
		||||
    super();
 | 
			
		||||
    this.options = field.widgetOptionsJson;
 | 
			
		||||
    this.textColor = fromKo(this.field.textColor);
 | 
			
		||||
    this.fillColor = fromKo(this.field.fillColor);
 | 
			
		||||
 | 
			
		||||
    // Note that its easier to create a knockout computed from the several knockout observables,
 | 
			
		||||
    // but then we turn it into a grainjs observable.
 | 
			
		||||
@ -57,7 +61,7 @@ export abstract class NewAbstractWidget extends Disposable {
 | 
			
		||||
      cssRow(
 | 
			
		||||
        cssHalfWidth(
 | 
			
		||||
          colorSelect(
 | 
			
		||||
            fromKo(this.field.textColor) as Observable<string>,
 | 
			
		||||
            this.textColor,
 | 
			
		||||
            (val) => this.field.textColor.saveOnly(val),
 | 
			
		||||
            testId('text-color')
 | 
			
		||||
          ),
 | 
			
		||||
@ -65,7 +69,7 @@ export abstract class NewAbstractWidget extends Disposable {
 | 
			
		||||
        ),
 | 
			
		||||
        cssHalfWidth(
 | 
			
		||||
          colorSelect(
 | 
			
		||||
            fromKo(this.field.fillColor) as Observable<string>,
 | 
			
		||||
            this.fillColor,
 | 
			
		||||
            (val) => this.field.fillColor.saveOnly(val),
 | 
			
		||||
            testId('fill-color')
 | 
			
		||||
          ),
 | 
			
		||||
 | 
			
		||||
@ -33,7 +33,7 @@
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.switch_on > .switch_slider {
 | 
			
		||||
  background-color: #2CB0AF;
 | 
			
		||||
  background-color: var(--grist-cell-color, #2CB0AF);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.switch_on > .switch_circle {
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import {delay} from 'app/common/delay';
 | 
			
		||||
import {Listener, Observable} from 'grainjs';
 | 
			
		||||
import {BindableValue, DomElementMethod, Listener, Observable, subscribeElem} from 'grainjs';
 | 
			
		||||
import {Observable as KoObservable} from 'knockout';
 | 
			
		||||
import constant = require('lodash/constant');
 | 
			
		||||
import identity = require('lodash/identity');
 | 
			
		||||
@ -727,6 +727,17 @@ export async function waitGrainObs<T>(observable: Observable<T>,
 | 
			
		||||
  return res;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// `dom.style` does not work here because custom css property (ie: `--foo`) needs to be set using
 | 
			
		||||
// `style.setProperty` (credit: https://vanseodesign.com/css/custom-properties-and-javascript/).
 | 
			
		||||
// TODO: consider making PR to fix `dom.style` in grainjs.
 | 
			
		||||
export function inlineStyle(property: string, valueObs: BindableValue<string>): DomElementMethod {
 | 
			
		||||
  return (elem) => subscribeElem(elem, valueObs, (val) => {
 | 
			
		||||
    elem.style.setProperty(property, val);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class to maintain a chain of promise-returning callbacks. All scheduled callbacks will be
 | 
			
		||||
 * called in order as long as the previous one is successful. If a callback fails is rejected,
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user