mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) Implementing row conditional formatting
Summary: Conditional formatting can now be used for whole rows. Related fix: - Font styles weren't applicable for summary columns. - Checkbox and slider weren't using colors properly Test Plan: Existing and new tests Reviewers: paulfitz, georgegevoian Reviewed By: georgegevoian Differential Revision: https://phab.getgrist.com/D3547
This commit is contained in:
@@ -26,7 +26,7 @@ function AceEditor(options) {
|
||||
this.saveValueOnBlurEvent = !(options.saveValueOnBlurEvent === false);
|
||||
this.calcSize = options.calcSize || ((_elem, size) => size);
|
||||
this.gristDoc = options.gristDoc || null;
|
||||
this.field = options.field || null;
|
||||
this.column = options.column || null;
|
||||
this.editorState = options.editorState || null;
|
||||
this._readonly = options.readonly || false;
|
||||
|
||||
@@ -185,10 +185,10 @@ AceEditor.prototype.setFontSize = function(pxVal) {
|
||||
AceEditor.prototype._setup = function() {
|
||||
// Standard editor setup
|
||||
this.editor = this.autoDisposeWith('destroy', ace.edit(this.editorDom));
|
||||
if (this.gristDoc && this.field) {
|
||||
if (this.gristDoc && this.column) {
|
||||
const getSuggestions = (prefix) => {
|
||||
const tableId = this.gristDoc.viewModel.activeSection().table().tableId();
|
||||
const columnId = this.field.column().colId();
|
||||
const columnId = this.column.colId();
|
||||
return this.gristDoc.docComm.autocomplete(prefix, tableId, columnId);
|
||||
};
|
||||
setupAceEditorCompletions(this.editor, {getSuggestions});
|
||||
|
||||
@@ -12,7 +12,7 @@ import {reportError} from 'app/client/models/errors';
|
||||
import {KoSaveableObservable, ObjObservable, setSaveValue} from 'app/client/models/modelUtil';
|
||||
import {SortedRowSet} from 'app/client/models/rowset';
|
||||
import {IPageWidget, toPageWidget} from 'app/client/ui/PageWidgetPicker';
|
||||
import {cssLabel, cssRow, cssSeparator} from 'app/client/ui/RightPanel';
|
||||
import {cssLabel, cssRow, cssSeparator} from 'app/client/ui/RightPanelStyles';
|
||||
import {cssFieldEntry, cssFieldLabel, IField, VisibleFieldsConfig } from 'app/client/ui/VisibleFieldsConfig';
|
||||
import {IconName} from 'app/client/ui2018/IconList';
|
||||
import {squareCheckbox} from 'app/client/ui2018/checkbox';
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
min-height: 16px;
|
||||
white-space: pre;
|
||||
word-wrap: break-word;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.g_record_detail_value.record-add {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import * as AceEditor from 'app/client/components/AceEditor';
|
||||
import {ColumnTransform} from 'app/client/components/ColumnTransform';
|
||||
import {GristDoc} from 'app/client/components/GristDoc';
|
||||
import {cssButtonRow} from 'app/client/ui/RightPanel';
|
||||
import {cssButtonRow} from 'app/client/ui/RightPanelStyles';
|
||||
import {basicButton, primaryButton} from 'app/client/ui2018/buttons';
|
||||
import {testId} from 'app/client/ui2018/cssVars';
|
||||
import {FieldBuilder} from 'app/client/widgets/FieldBuilder';
|
||||
|
||||
@@ -1,26 +1,28 @@
|
||||
/* globals alert, document, $ */
|
||||
|
||||
var _ = require('underscore');
|
||||
var ko = require('knockout');
|
||||
const _ = require('underscore');
|
||||
const ko = require('knockout');
|
||||
const debounce = require('lodash/debounce');
|
||||
|
||||
var gutil = require('app/common/gutil');
|
||||
var BinaryIndexedTree = require('app/common/BinaryIndexedTree');
|
||||
const gutil = require('app/common/gutil');
|
||||
const BinaryIndexedTree = require('app/common/BinaryIndexedTree');
|
||||
const {Sort} = require('app/common/SortSpec');
|
||||
|
||||
var dom = require('../lib/dom');
|
||||
var kd = require('../lib/koDom');
|
||||
var kf = require('../lib/koForm');
|
||||
var koDomScrolly = require('../lib/koDomScrolly');
|
||||
var tableUtil = require('../lib/tableUtil');
|
||||
var {addToSort, sortBy} = require('../lib/sortUtil');
|
||||
const dom = require('../lib/dom');
|
||||
const kd = require('../lib/koDom');
|
||||
const kf = require('../lib/koForm');
|
||||
const koDomScrolly = require('../lib/koDomScrolly');
|
||||
const tableUtil = require('../lib/tableUtil');
|
||||
const {addToSort, sortBy} = require('../lib/sortUtil');
|
||||
|
||||
var commands = require('./commands');
|
||||
var viewCommon = require('./viewCommon');
|
||||
var Base = require('./Base');
|
||||
var BaseView = require('./BaseView');
|
||||
var selector = require('./Selector');
|
||||
var {CopySelection} = require('./CopySelection');
|
||||
const commands = require('./commands');
|
||||
const viewCommon = require('./viewCommon');
|
||||
const Base = require('./Base');
|
||||
const BaseView = require('./BaseView');
|
||||
const selector = require('./Selector');
|
||||
const {CopySelection} = require('./CopySelection');
|
||||
const koUtil = require('app/client/lib/koUtil');
|
||||
const convert = require('color-convert');
|
||||
|
||||
const {renderAllRows} = require('app/client/components/Printing');
|
||||
const {reportError} = require('app/client/models/AppModel');
|
||||
@@ -40,6 +42,7 @@ const {contextMenu} = require('app/client/ui/contextMenu');
|
||||
const {menuToggle} = require('app/client/ui/MenuToggle');
|
||||
const {showTooltip} = require('app/client/ui/tooltips');
|
||||
const {parsePasteForView} = require("./BaseView2");
|
||||
const {CombinedStyle} = require("app/client/models/Styles");
|
||||
|
||||
|
||||
// A threshold for interpreting a motionless click as a click rather than a drag.
|
||||
@@ -1111,8 +1114,41 @@ GridView.prototype.buildDom = function() {
|
||||
// rows. IsCellActive is only subscribed to columns for the active row. This way, when
|
||||
// the cursor moves, there are (rows+2*columns) calls rather than rows*columns.
|
||||
var isRowActive = ko.computed(() => row._index() === self.cursor.rowIndex());
|
||||
|
||||
const computedFlags = ko.pureComputed(() => {
|
||||
return self.viewSection.rulesColsIds().map(colRef => {
|
||||
if (row.cells[colRef]) { return row.cells[colRef]() || false; }
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
const computedRule = koUtil.withKoUtils(ko.pureComputed(() => {
|
||||
if (row._isAddRow() || !row.id()) { return null; }
|
||||
const flags = computedFlags();
|
||||
if (flags.length === 0) { return null; }
|
||||
const styles = self.viewSection.rulesStyles() || [];
|
||||
return { style : new CombinedStyle(styles, flags) };
|
||||
}, this).extend({deferred: true}));
|
||||
|
||||
const fillColor = buildStyleOption(self, computedRule, 'fillColor');
|
||||
const zebraColor = ko.pureComputed(() => calcZebra(fillColor()));
|
||||
const textColor = buildStyleOption(self, computedRule, 'textColor');
|
||||
const fontBold = buildStyleOption(self, computedRule, 'fontBold');
|
||||
const fontItalic = buildStyleOption(self, computedRule, 'fontItalic');
|
||||
const fontUnderline = buildStyleOption(self, computedRule, 'fontUnderline');
|
||||
const fontStrikethrough = buildStyleOption(self, computedRule, 'fontStrikethrough');
|
||||
|
||||
return dom('div.gridview_row',
|
||||
dom.autoDispose(isRowActive),
|
||||
dom.autoDispose(computedFlags),
|
||||
dom.autoDispose(computedRule),
|
||||
dom.autoDispose(textColor),
|
||||
dom.autoDispose(fillColor),
|
||||
dom.autoDispose(zebraColor),
|
||||
dom.autoDispose(fontBold),
|
||||
dom.autoDispose(fontItalic),
|
||||
dom.autoDispose(fontUnderline),
|
||||
dom.autoDispose(fontStrikethrough),
|
||||
|
||||
// rowid dom
|
||||
dom('div.gridview_data_row_num',
|
||||
@@ -1159,6 +1195,13 @@ GridView.prototype.buildDom = function() {
|
||||
kd.toggleClass('record-add', row._isAddRow),
|
||||
kd.style('borderLeftWidth', v.borderWidthPx),
|
||||
kd.style('borderBottomWidth', v.borderWidthPx),
|
||||
kd.toggleClass('font-bold', fontBold),
|
||||
kd.toggleClass('font-underline', fontUnderline),
|
||||
kd.toggleClass('font-italic', fontItalic),
|
||||
kd.toggleClass('font-strikethrough', fontStrikethrough),
|
||||
kd.style('--grist-row-background-color', fillColor),
|
||||
kd.style('--grist-row-background-color-zebra', zebraColor),
|
||||
kd.style('--grist-row-color', textColor),
|
||||
//These are grabbed from v.optionsObj at start of GridView buildDom
|
||||
kd.toggleClass('record-hlines', vHorizontalGridlines),
|
||||
kd.toggleClass('record-vlines', vVerticalGridlines),
|
||||
@@ -1611,6 +1654,15 @@ GridView.prototype._duplicateRows = async function() {
|
||||
}
|
||||
}
|
||||
|
||||
function buildStyleOption(owner, computedRule, optionName) {
|
||||
return ko.computed(() => {
|
||||
if (owner.isDisposed()) { return null; }
|
||||
const rule = computedRule();
|
||||
if (!rule || !rule.style) { return ''; }
|
||||
return rule.style[optionName] || '';
|
||||
});
|
||||
}
|
||||
|
||||
// Helper to show tooltip over column selection in the full edit mode.
|
||||
class HoverColumnTooltip {
|
||||
constructor(el) {
|
||||
@@ -1631,4 +1683,20 @@ class HoverColumnTooltip {
|
||||
}
|
||||
}
|
||||
|
||||
// Simple function that calculates good color for zebra stripes.
|
||||
function calcZebra(hex) {
|
||||
if (!hex || hex.length !== 7) { return hex; }
|
||||
// HSL: [HUE, SATURATION, LIGHTNESS]
|
||||
const hsl = convert.hex.hsl(hex.substr(1));
|
||||
// For bright color, we will make it darker. Value was picked by hand, to
|
||||
// produce #f8f8f8f out of #ffffff.
|
||||
if (hsl[2] > 50) { hsl[2] -= 2.6; }
|
||||
// For darker color, we will make it brighter. Value was picked by hand to look
|
||||
// good for the darkest colors in our palette.
|
||||
else if (hsl[2] > 1) { hsl[2] += 11; }
|
||||
// For very dark colors
|
||||
else { hsl[2] += 16; }
|
||||
return `#${convert.hsl.hex(hsl)}`;
|
||||
}
|
||||
|
||||
module.exports = GridView;
|
||||
|
||||
@@ -798,7 +798,8 @@ export class Importer extends DisposableWithEvents {
|
||||
const editRow = vsi?.moveEditRowToCursor();
|
||||
const editorHolder = openFormulaEditor({
|
||||
gristDoc: this._gristDoc,
|
||||
field,
|
||||
column: field.column(),
|
||||
editingFormula: field.editingFormula,
|
||||
refElem,
|
||||
editRow,
|
||||
setupCleanup: this._setupFormulaEditorCleanup.bind(this),
|
||||
@@ -819,7 +820,7 @@ export class Importer extends DisposableWithEvents {
|
||||
* focus.
|
||||
*/
|
||||
private _setupFormulaEditorCleanup(
|
||||
owner: MultiHolder, _doc: GristDoc, field: ViewFieldRec, _saveEdit: () => Promise<unknown>
|
||||
owner: MultiHolder, _doc: GristDoc, editingFormula: ko.Computed<boolean>, _saveEdit: () => Promise<unknown>
|
||||
) {
|
||||
const saveEdit = () => _saveEdit().catch(reportError);
|
||||
|
||||
@@ -828,7 +829,7 @@ export class Importer extends DisposableWithEvents {
|
||||
|
||||
owner.onDispose(() => {
|
||||
this.off('importer_focus', saveEdit);
|
||||
field.editingFormula(false);
|
||||
editingFormula(false);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import {ColumnTransform} from 'app/client/components/ColumnTransform';
|
||||
import {GristDoc} from 'app/client/components/GristDoc';
|
||||
import * as TypeConversion from 'app/client/components/TypeConversion';
|
||||
import {reportError} from 'app/client/models/errors';
|
||||
import {cssButtonRow} from 'app/client/ui/RightPanel';
|
||||
import {cssButtonRow} from 'app/client/ui/RightPanelStyles';
|
||||
import {basicButton, primaryButton} from 'app/client/ui2018/buttons';
|
||||
import {testId} from 'app/client/ui2018/cssVars';
|
||||
import {FieldBuilder} from 'app/client/widgets/FieldBuilder';
|
||||
|
||||
@@ -14,7 +14,7 @@ const {addToSort} = require('app/client/lib/sortUtil');
|
||||
const {updatePositions} = require('app/client/lib/sortUtil');
|
||||
const {attachColumnFilterMenu} = require('app/client/ui/ColumnFilterMenu');
|
||||
const {addFilterMenu} = require('app/client/ui/FilterBar');
|
||||
const {cssIcon, cssRow} = require('app/client/ui/RightPanel');
|
||||
const {cssIcon, cssRow} = require('app/client/ui/RightPanelStyles');
|
||||
const {basicButton, primaryButton} = require('app/client/ui2018/buttons');
|
||||
const {labeledLeftSquareCheckbox} = require("app/client/ui2018/checkbox");
|
||||
const {colors} = require('app/client/ui2018/cssVars');
|
||||
|
||||
@@ -4,3 +4,4 @@
|
||||
import ViewConfigTab from 'app/client/components/ViewConfigTab';
|
||||
import * as FieldConfig from 'app/client/ui/FieldConfig';
|
||||
export {ViewConfigTab, FieldConfig};
|
||||
export {ConditionalStyle} from 'app/client/widgets/ConditionalStyle';
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
selected fields - this still remains white.
|
||||
TODO: consider making this color the single source
|
||||
*/
|
||||
background: white;
|
||||
background: var(--grist-row-background-color, white);
|
||||
color: var(--grist-row-color, black);
|
||||
}
|
||||
|
||||
.record.record-hlines { /* Overwrites style, width set on element */
|
||||
@@ -27,7 +28,7 @@
|
||||
}
|
||||
|
||||
.record.record-zebra.record-even {
|
||||
background-color: #f8f8f8;
|
||||
background-color: var(--grist-row-background-color-zebra, #f8f8f8);
|
||||
}
|
||||
|
||||
.record.record-add {
|
||||
@@ -71,12 +72,12 @@
|
||||
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, black);
|
||||
--grist-actual-cell-color: var(--grist-diff-color, var(--grist-cell-color, var(--grist-row-color)));
|
||||
color: var(--grist-actual-cell-color, unset);
|
||||
}
|
||||
|
||||
.field.selected .field_clip {
|
||||
mix-blend-mode: darken;
|
||||
mix-blend-mode: luminosity;
|
||||
}
|
||||
|
||||
.field_clip.invalid, .field_clip.field-error-from-style {
|
||||
|
||||
Reference in New Issue
Block a user