Header colored (#581)

This commit is contained in:
CamilleLegeron 2023-08-07 20:01:35 +02:00 committed by GitHub
parent 7c114bf600
commit 02841bd15c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 408 additions and 39 deletions

View File

@ -367,6 +367,9 @@
visibility: hidden; visibility: hidden;
} }
.column_name .menu_toggle {
z-index: 1;
}
/* Etc */ /* Etc */
.g-column-main-menu { .g-column-main-menu {
@ -408,11 +411,13 @@
width: 13px; width: 13px;
height: 13px; height: 13px;
margin-right: 4px; margin-right: 4px;
z-index: 1;
} }
.g-column-label .kf_editable_label { .g-column-label .kf_editable_label {
padding-left: 1px; padding-left: 1px;
padding-right: 1px; padding-right: 1px;
z-index: 1;
} }
.g-column-label-spacer { .g-column-label-spacer {

View File

@ -1073,8 +1073,28 @@ GridView.prototype.buildDom = function() {
self.editingFormula() && self.editingFormula() &&
ko.unwrap(self.hoverColumn) === field._index() ko.unwrap(self.hoverColumn) === field._index()
); );
const headerTextColor = ko.computed(() => field.headerTextColor() || '');
const headerFillColor = ko.computed(() => field.headerFillColor() || '');
const headerFontBold = ko.computed(() => field.headerFontBold());
const headerFontItalic = ko.computed(() => field.headerFontItalic());
const headerFontUnderline = ko.computed(() => field.headerFontUnderline());
const headerFontStrikethrough = ko.computed(() => field.headerFontStrikethrough());
return dom( return dom(
'div.column_name.field', 'div.column_name.field',
dom.autoDispose(headerTextColor),
dom.autoDispose(headerFillColor),
dom.autoDispose(headerFontBold),
dom.autoDispose(headerFontItalic),
dom.autoDispose(headerFontUnderline),
dom.autoDispose(headerFontStrikethrough),
kd.style('--grist-header-color', headerTextColor),
kd.style('--grist-header-background-color', headerFillColor),
kd.toggleClass('font-bold', headerFontBold),
kd.toggleClass('font-italic', headerFontItalic),
kd.toggleClass('font-underline', headerFontUnderline),
kd.toggleClass('font-strikethrough', headerFontStrikethrough),
kd.style('--frozen-position', () => ko.unwrap(this.frozenPositions.at(field._index()))), kd.style('--frozen-position', () => ko.unwrap(this.frozenPositions.at(field._index()))),
kd.toggleClass("frozen", () => ko.unwrap(this.frozenMap.at(field._index()))), kd.toggleClass("frozen", () => ko.unwrap(this.frozenMap.at(field._index()))),
kd.toggleClass("hover-column", isTooltip), kd.toggleClass("hover-column", isTooltip),

View File

@ -195,8 +195,11 @@
} }
.column_name { .column_name {
color: var(--grist-theme-table-header-fg, unset); color: var(--grist-header-color,
background-color: var(--grist-theme-table-header-bg, var(--grist-color-light-grey)); var(--grist-theme-table-header-fg), unset);
background-color: var(--grist-header-background-color,
var(--grist-theme-table-header-bg,
var(--grist-color-light-grey)));
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
/* Column headers always show vertical gridlines, to make it clear how to resize them */ /* Column headers always show vertical gridlines, to make it clear how to resize them */
@ -207,9 +210,11 @@
border-left-color: var(--grist-theme-table-header-border, var(--grist-color-dark-grey)); border-left-color: var(--grist-theme-table-header-border, var(--grist-color-dark-grey));
} }
.column_name.selected { .column_name.selected > .selection {
color: var(--grist-theme-table-header-selected-fg, unset); background-color: var(--grist-theme-selection-header);
background-color: var(--grist-theme-table-header-selected-bg, var(--grist-color-medium-grey-opaque)); position: absolute;
inset: 0;
pointer-events: none;
} }
.gridview_data_row_num.selected { .gridview_data_row_num.selected {

View File

@ -7,6 +7,15 @@ export interface Style {
fontStrikethrough?: boolean|undefined; fontStrikethrough?: boolean|undefined;
} }
export interface HeaderStyle {
headerTextColor?: string | undefined; // this can be string, undefined or an absent key.
headerFillColor?: string | undefined;
headerFontBold?: boolean | undefined;
headerFontUnderline?: boolean | undefined;
headerFontItalic?: boolean | undefined;
headerFontStrikethrough?: boolean | undefined;
}
export class CombinedStyle implements Style { export class CombinedStyle implements Style {
public readonly textColor?: string; public readonly textColor?: string;
public readonly fillColor?: string; public readonly fillColor?: string;

View File

@ -17,6 +17,8 @@ export class ViewFieldConfig {
public options: CommonOptions; public options: CommonOptions;
/** Style options for a field or multiple fields */ /** Style options for a field or multiple fields */
public style: ko.Computed<StyleOptions>; public style: ko.Computed<StyleOptions>;
/** Header style options for a field or multiple fields */
public headerStyle: ko.Computed<StyleOptions>;
// Rest of the options mimic the same options from ViewFieldRec. // Rest of the options mimic the same options from ViewFieldRec.
public wrap: modelUtil.KoSaveableObservable<boolean|undefined>; public wrap: modelUtil.KoSaveableObservable<boolean|undefined>;
@ -255,6 +257,68 @@ export class ViewFieldConfig {
result.revert = () => { zip(fields, state).forEach(([f, s]) => f!.style(s!)); }; result.revert = () => { zip(fields, state).forEach(([f, s]) => f!.style(s!)); };
return result; return result;
}); });
this.headerStyle = ko.pureComputed(() => {
const fields = this.fields();
const multiSelect = fields.length > 1;
const savableOptions = modelUtil.savingComputed({
read: () => {
// For one column, just proxy this to the field.
if (!multiSelect) {
return this._field.widgetOptionsJson();
}
// Assemble final json object.
const result: any = {};
// First get all widgetOption jsons from all columns/fields.
const optionList = fields.map(f => f.widgetOptionsJson());
// And fill only those that are common
for(const key of ['headerTextColor', 'headerFillColor', 'headerFontBold',
'headerFontItalic', 'headerFontUnderline', 'headerFontStrikethrough']) {
// Setting null means that this options is there, but has no value.
result[key] = null;
// If all columns have the same value, use it.
if (allSame(optionList.map(v => v[key]))) {
result[key] = optionList[0][key] ?? null;
}
}
return result;
},
write: (setter, value) => {
if (!multiSelect) {
return setter(this._field.widgetOptionsJson, value);
}
// When the creator panel is saving widgetOptions, it will pass
// our virtual widgetObject, which has nulls for mixed values.
// If this option wasn't changed (set), we don't want to save it.
value = {...value};
for(const key of Object.keys(value)) {
if (value[key] === null) {
delete value[key];
}
}
// Now update all options, for all fields, by amending the options
// object from the field/column.
for(const item of fields) {
const previous = item.widgetOptionsJson.peek();
setter(item.widgetOptionsJson, {
...previous,
...value,
});
}
}
});
// Style picker needs to be able revert to previous value, if user cancels.
const state = fields.map(f => f.headerStyle.peek());
// We need some additional information about each property.
const result: StyleOptions = extendObservable(modelUtil.objObservable(savableOptions), {
// Property has mixed value, if not all options are the same.
mixed: prop => ko.pureComputed(() => !allSame(fields.map(f => f.widgetOptionsJson.prop(prop)()))),
// Property has empty value, if all options are empty (are null, undefined, empty Array or empty Object).
empty: prop => ko.pureComputed(() => allEmpty(fields.map(f => f.widgetOptionsJson.prop(prop)()))),
});
result.revert = () => { zip(fields, state).forEach(([f, s]) => f!.headerStyle(s!)); };
return result;
});
} }
// Helper for Choice/ChoiceList columns, that saves widget options and renames values in a document // Helper for Choice/ChoiceList columns, that saves widget options and renames values in a document

View File

@ -2,7 +2,7 @@ import {ColumnRec, DocModel, IRowModel, refListRecords, refRecord, ViewSectionRe
import {formatterForRec} from 'app/client/models/entities/ColumnRec'; import {formatterForRec} from 'app/client/models/entities/ColumnRec';
import * as modelUtil from 'app/client/models/modelUtil'; import * as modelUtil from 'app/client/models/modelUtil';
import {removeRule, RuleOwner} from 'app/client/models/RuleOwner'; import {removeRule, RuleOwner} from 'app/client/models/RuleOwner';
import {Style} from 'app/client/models/Styles'; import { HeaderStyle, Style } from 'app/client/models/Styles';
import {ViewFieldConfig} from 'app/client/models/ViewFieldConfig'; import {ViewFieldConfig} from 'app/client/models/ViewFieldConfig';
import * as UserType from 'app/client/widgets/UserType'; import * as UserType from 'app/client/widgets/UserType';
import {DocumentSettings} from 'app/common/DocumentSettings'; import {DocumentSettings} from 'app/common/DocumentSettings';
@ -76,8 +76,15 @@ export interface ViewFieldRec extends IRowModel<"_grist_Views_section_field">, R
fontUnderline: modelUtil.KoSaveableObservable<boolean|undefined>; fontUnderline: modelUtil.KoSaveableObservable<boolean|undefined>;
fontItalic: modelUtil.KoSaveableObservable<boolean|undefined>; fontItalic: modelUtil.KoSaveableObservable<boolean|undefined>;
fontStrikethrough: modelUtil.KoSaveableObservable<boolean|undefined>; fontStrikethrough: modelUtil.KoSaveableObservable<boolean|undefined>;
// Helper computed to change style of a cell without saving it. headerTextColor: modelUtil.KoSaveableObservable<string|undefined>;
headerFillColor: modelUtil.KoSaveableObservable<string|undefined>;
headerFontBold: modelUtil.KoSaveableObservable<boolean|undefined>;
headerFontUnderline: modelUtil.KoSaveableObservable<boolean|undefined>;
headerFontItalic: modelUtil.KoSaveableObservable<boolean|undefined>;
headerFontStrikethrough: modelUtil.KoSaveableObservable<boolean|undefined>;
// Helper computed to change style of a cell and headerStyle without saving it.
style: ko.PureComputed<Style>; style: ko.PureComputed<Style>;
headerStyle: ko.PureComputed<HeaderStyle>;
config: ViewFieldConfig; config: ViewFieldConfig;
@ -236,6 +243,12 @@ export function createViewFieldRec(this: ViewFieldRec, docModel: DocModel): void
this.fontUnderline = this.widgetOptionsJson.prop('fontUnderline'); this.fontUnderline = this.widgetOptionsJson.prop('fontUnderline');
this.fontItalic = this.widgetOptionsJson.prop('fontItalic'); this.fontItalic = this.widgetOptionsJson.prop('fontItalic');
this.fontStrikethrough = this.widgetOptionsJson.prop('fontStrikethrough'); this.fontStrikethrough = this.widgetOptionsJson.prop('fontStrikethrough');
this.headerTextColor = this.widgetOptionsJson.prop('headerTextColor');
this.headerFillColor = this.widgetOptionsJson.prop('headerFillColor');
this.headerFontBold = this.widgetOptionsJson.prop('headerFontBold');
this.headerFontUnderline = this.widgetOptionsJson.prop('headerFontUnderline');
this.headerFontItalic = this.widgetOptionsJson.prop('headerFontItalic');
this.headerFontStrikethrough = this.widgetOptionsJson.prop('headerFontStrikethrough');
this.documentSettings = ko.pureComputed(() => docModel.docInfoRow.documentSettingsJson()); this.documentSettings = ko.pureComputed(() => docModel.docInfoRow.documentSettingsJson());
this.style = ko.pureComputed({ this.style = ko.pureComputed({
@ -251,6 +264,19 @@ export function createViewFieldRec(this: ViewFieldRec, docModel: DocModel): void
this.widgetOptionsJson.update(style); this.widgetOptionsJson.update(style);
}, },
}); });
this.headerStyle = ko.pureComputed({
read: () => ({
headerTextColor: this.headerTextColor(),
headerFillColor: this.headerFillColor(),
headerFontBold: this.headerFontBold(),
headerFontUnderline: this.headerFontUnderline(),
headerFontItalic: this.headerFontItalic(),
headerFontStrikethrough: this.headerFontStrikethrough(),
}) as HeaderStyle,
write: (headerStyle: HeaderStyle) => {
this.widgetOptionsJson.update(headerStyle);
},
});
this.tableId = ko.pureComputed(() => this.column().table().tableId()); this.tableId = ko.pureComputed(() => this.column().table().tableId());
this.rulesList = ko.pureComputed(() => this._fieldOrColumn().rules()); this.rulesList = ko.pureComputed(() => this._fieldOrColumn().rules());

View File

@ -344,6 +344,7 @@ export const theme = {
colors.selectionOpaque), colors.selectionOpaque),
selectionOpaqueDarkBg: new CustomProp('theme-selection-opaque-dark-bg', undefined, selectionOpaqueDarkBg: new CustomProp('theme-selection-opaque-dark-bg', undefined,
colors.selectionDarkerOpaque), colors.selectionDarkerOpaque),
selectionHeader: new CustomProp('theme-selection-header', undefined, colors.mediumGrey),
/* Widgets */ /* Widgets */
widgetBg: new CustomProp('theme-widget-bg', undefined, 'white'), widgetBg: new CustomProp('theme-widget-bg', undefined, 'white'),

View File

@ -4,7 +4,7 @@ import {GristDoc} from 'app/client/components/GristDoc';
import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec'; import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec';
import {textButton} from 'app/client/ui2018/buttons'; import {textButton} from 'app/client/ui2018/buttons';
import {ColorOption, colorSelect} from 'app/client/ui2018/ColorSelect'; import {ColorOption, colorSelect} from 'app/client/ui2018/ColorSelect';
import {theme, vars} from 'app/client/ui2018/cssVars'; import {testId, theme, vars} from 'app/client/ui2018/cssVars';
import {ConditionalStyle} from 'app/client/widgets/ConditionalStyle'; import {ConditionalStyle} from 'app/client/widgets/ConditionalStyle';
import {Computed, Disposable, dom, DomContents, fromKo, styled} from 'grainjs'; import {Computed, Disposable, dom, DomContents, fromKo, styled} from 'grainjs';
@ -21,12 +21,61 @@ export class CellStyle extends Disposable {
} }
public buildDom(): DomContents { public buildDom(): DomContents {
const isTableWidget = this._field.viewSection().parentKey() === 'record';
return [ return [
dom.maybe(use => isTableWidget, () => {
return [
cssLine(
cssLabel(t('HEADER STYLE')),
),
cssRow(
testId('header-color-select'),
dom.domComputedOwned(fromKo(this._field.config.headerStyle), (holder, options) => {
const headerTextColor = fromKo(options.prop("headerTextColor"));
const headerFillColor = fromKo(options.prop("headerFillColor"));
const headerFontBold = fromKo(options.prop("headerFontBold"));
const headerFontUnderline = fromKo(options.prop("headerFontUnderline"));
const headerFontItalic = fromKo(options.prop("headerFontItalic"));
const headerFontStrikethrough = fromKo(options.prop("headerFontStrikethrough"));
const hasMixedStyle = Computed.create(holder, use => {
if (!use(this._field.config.multiselect)) { return false; }
const commonStyle = [
use(options.mixed('headerTextColor')),
use(options.mixed('headerFillColor')),
use(options.mixed('headerFontBold')),
use(options.mixed('headerFontUnderline')),
use(options.mixed('headerFontItalic')),
use(options.mixed('headerFontStrikethrough'))
];
return commonStyle.some(Boolean);
});
return colorSelect(
{
textColor: new ColorOption(
{ color: headerTextColor, defaultColor: this._defaultTextColor, noneText: 'default' }
),
fillColor: new ColorOption(
{ color: headerFillColor, allowsNone: true, noneText: 'none' }
),
fontBold: headerFontBold,
fontItalic: headerFontItalic,
fontUnderline: headerFontUnderline,
fontStrikethrough: headerFontStrikethrough
}, {
onSave: () => options.save(),
onRevert: () => options.revert(),
placeholder: use => use(hasMixedStyle) ? t('Mixed style') : t('Default header style')
}
);
}),
)];
}),
cssLine( cssLine(
cssLabel(t('CELL STYLE')), cssLabel(t('CELL STYLE')),
cssButton(t('Open row styles'), dom.on('click', allCommands.viewTabOpen.run)), cssButton(t('Open row styles'), dom.on('click', allCommands.viewTabOpen.run)),
), ),
cssRow( cssRow(
testId('cell-color-select'),
dom.domComputedOwned(fromKo(this._field.config.style), (holder, options) => { dom.domComputedOwned(fromKo(this._field.config.style), (holder, options) => {
const textColor = fromKo(options.prop("textColor")); const textColor = fromKo(options.prop("textColor"));
const fillColor = fromKo(options.prop("fillColor")); const fillColor = fromKo(options.prop("fillColor"));

View File

@ -173,6 +173,7 @@ export interface ThemeColors {
'selection-opaque-fg': string; 'selection-opaque-fg': string;
'selection-opaque-bg': string; 'selection-opaque-bg': string;
'selection-opaque-dark-bg': string; 'selection-opaque-dark-bg': string;
'selection-header': string;
/* Widgets */ /* Widgets */
'widget-bg': string; 'widget-bg': string;

View File

@ -152,6 +152,7 @@ export const GristDark: ThemeColors = {
'selection-opaque-fg': 'white', 'selection-opaque-fg': 'white',
'selection-opaque-bg': '#2F4748', 'selection-opaque-bg': '#2F4748',
'selection-opaque-dark-bg': '#253E3E', 'selection-opaque-dark-bg': '#253E3E',
'selection-header': 'rgba(107,107,144,0.4)',
/* Widgets */ /* Widgets */
'widget-bg': '#32323F', 'widget-bg': '#32323F',

View File

@ -152,6 +152,7 @@ export const GristLight: ThemeColors = {
'selection-opaque-fg': 'black', 'selection-opaque-fg': 'black',
'selection-opaque-bg': '#DCF4EB', 'selection-opaque-bg': '#DCF4EB',
'selection-opaque-dark-bg': '#D6EEE5', 'selection-opaque-dark-bg': '#D6EEE5',
'selection-header': 'rgba(217,217,217,0.6)',
/* Widgets */ /* Widgets */
'widget-bg': 'white', 'widget-bg': 'white',

View File

@ -851,7 +851,9 @@
"Cell Style": "Cell Style", "Cell Style": "Cell Style",
"Default cell style": "Default cell style", "Default cell style": "Default cell style",
"Mixed style": "Mixed style", "Mixed style": "Mixed style",
"Open row styles": "Open row styles" "Open row styles": "Open row styles",
"Default header style": "Default header style",
"Header Style": "Header Style"
}, },
"ChoiceTextBox": { "ChoiceTextBox": {
"CHOICES": "CHOICES" "CHOICES": "CHOICES"

View File

@ -23,7 +23,7 @@ describe('CellColor', function() {
it('should save by clicking away', async function() { it('should save by clicking away', async function() {
await gu.getCell('A', 1).click(); await gu.getCell('A', 1).click();
// open color picker // open color picker
await gu.openColorPicker(); await gu.openCellColorPicker();
await gu.setFillColor('red'); await gu.setFillColor('red');
await gu.setTextColor('blue'); await gu.setTextColor('blue');
// Save by clicking B column // Save by clicking B column
@ -46,7 +46,7 @@ describe('CellColor', function() {
it('should undo and redo colors when clicked away', async function() { it('should undo and redo colors when clicked away', async function() {
await gu.getCell('A', 1).click(); await gu.getCell('A', 1).click();
// open color picker // open color picker
await gu.openColorPicker(); await gu.openCellColorPicker();
await gu.setFillColor('red'); await gu.setFillColor('red');
await gu.setTextColor('blue'); await gu.setTextColor('blue');
// Save by clicking B column // Save by clicking B column
@ -78,7 +78,7 @@ describe('CellColor', function() {
await cell.click(); await cell.click();
// open color picker // open color picker
await driver.find('.test-color-select').click(); await gu.openCellColorPicker();
// set cell colors // set cell colors
await gu.setColor(driver.find('.test-text-input'), 'rgb(0, 255, 0)'); await gu.setColor(driver.find('.test-text-input'), 'rgb(0, 255, 0)');
@ -247,7 +247,7 @@ describe('CellColor', function() {
assert.equal(await cell.matches('.test-attachment-widget'), true); assert.equal(await cell.matches('.test-attachment-widget'), true);
// open color picker // open color picker
await driver.find('.test-color-select').click(); await gu.openCellColorPicker();
// set and check cellColor // set and check cellColor
await gu.setColor(driver.find('.test-text-input'), 'rgb(0, 255, 0)'); await gu.setColor(driver.find('.test-text-input'), 'rgb(0, 255, 0)');
@ -277,7 +277,7 @@ describe('CellColor', function() {
assert.equal(await cell.find('.widget_checkbox').isPresent(), true); assert.equal(await cell.find('.widget_checkbox').isPresent(), true);
// open color picker // open color picker
await driver.find('.test-color-select').click(); await gu.openCellColorPicker();
// set and check cell color // set and check cell color
await gu.setColor(driver.find('.test-text-input'), 'rgb(0, 255, 0)'); await gu.setColor(driver.find('.test-text-input'), 'rgb(0, 255, 0)');
@ -311,7 +311,7 @@ describe('CellColor', function() {
const clip = cell.find('.field_clip'); const clip = cell.find('.field_clip');
// open color picker // open color picker
await driver.find('.test-color-select').click(); await gu.openCellColorPicker();
// set and check cell color // set and check cell color
await gu.setColor(driver.find('.test-text-input'), 'rgb(0, 255, 0)'); await gu.setColor(driver.find('.test-text-input'), 'rgb(0, 255, 0)');
@ -338,7 +338,7 @@ describe('CellColor', function() {
const cell = gu.getCell('A', 1).find('.field_clip'); const cell = gu.getCell('A', 1).find('.field_clip');
// open color picker // open color picker
await driver.find('.test-color-select').click(); await gu.openCellColorPicker();
// set and check cell color // set and check cell color
await gu.setColor(driver.find('.test-text-input'), 'rgb(0, 255, 0)'); await gu.setColor(driver.find('.test-text-input'), 'rgb(0, 255, 0)');
@ -369,7 +369,7 @@ describe('CellColor', function() {
await gu.enterCell(Key.DELETE); await gu.enterCell(Key.DELETE);
// open color picker // open color picker
await driver.find('.test-color-select').click(); await gu.openCellColorPicker();
// set and check cell color // set and check cell color
await gu.setColor(driver.find('.test-text-input'), 'rgb(0, 255, 0)'); await gu.setColor(driver.find('.test-text-input'), 'rgb(0, 255, 0)');
@ -400,7 +400,7 @@ describe('CellColor', function() {
await gu.enterCell('foo'); await gu.enterCell('foo');
// open color picker // open color picker
await driver.find('.test-color-select').click(); await gu.openCellColorPicker();
// set and check cell color // set and check cell color
const cell = await gu.getCell('A', 1).find('.field_clip'); const cell = await gu.getCell('A', 1).find('.field_clip');
@ -430,7 +430,7 @@ describe('CellColor', function() {
let cell = gu.getCell('A', 1).find('.field_clip'); let cell = gu.getCell('A', 1).find('.field_clip');
// open color picker // open color picker
await driver.find('.test-color-select').click(); await gu.openCellColorPicker();
// set and check cell color // set and check cell color
await gu.setColor(driver.find('.test-text-input'), 'rgb(0, 255, 0)'); await gu.setColor(driver.find('.test-text-input'), 'rgb(0, 255, 0)');
@ -465,7 +465,7 @@ describe('CellColor', function() {
await gu.setType(/Toggle/); await gu.setType(/Toggle/);
// open the color picker // open the color picker
await driver.find('.test-color-select').click(); await gu.openCellColorPicker();
// check color preview is correct // check color preview is correct
assert.equal(await driver.find('.test-text-hex').value(), '#606060'); assert.equal(await driver.find('.test-text-hex').value(), '#606060');
@ -479,7 +479,7 @@ describe('CellColor', function() {
await gu.waitForServer(); await gu.waitForServer();
// open the color picker // open the color picker
await driver.find('.test-color-select').click(); await gu.openCellColorPicker();
// check color preview is correct // check color preview is correct
assert.equal(await driver.find('.test-text-hex').value(), '#2CB0AF'); assert.equal(await driver.find('.test-text-hex').value(), '#2CB0AF');
@ -505,7 +505,7 @@ describe('CellColor', function() {
assert.equal(await cell().find('.checkmark_stem').getCssValue('background-color'), gu.hexToRgb('#606060')); assert.equal(await cell().find('.checkmark_stem').getCssValue('background-color'), gu.hexToRgb('#606060'));
// open the color picker and press ESC // open the color picker and press ESC
await driver.find('.test-color-select').click(); await gu.openCellColorPicker();
await driver.wait(() => driver.find('.test-fill-palette').isPresent(), 3000); await driver.wait(() => driver.find('.test-fill-palette').isPresent(), 3000);
await driver.sendKeys(Key.ESCAPE); await driver.sendKeys(Key.ESCAPE);
await driver.wait(async () => !(await driver.find('.test-fill-palette').isPresent()), 3000); await driver.wait(async () => !(await driver.find('.test-fill-palette').isPresent()), 3000);

View File

@ -0,0 +1,112 @@
import { assert, driver, Key } from 'mocha-webdriver';
import * as gu from 'test/nbrowser/gristUtils';
import { setupTestSuite } from 'test/nbrowser/testUtils';
const defaultHeaderBackgroundColor = '#f7f7f7';
describe('HeaderColor', function () {
this.timeout(20000);
const cleanup = setupTestSuite();
before(async () => {
// Create a new document
const mainSession = await gu.session().login();
await mainSession.tempNewDoc(cleanup, 'HeaderColor');
// add records
await gu.enterCell('a');
await gu.enterCell('b');
await gu.enterCell('c');
await gu.toggleSidePanel('right', 'open');
await driver.find('.test-right-tab-field').click();
});
it('should save by clicking away', async function () {
await gu.getCell('A', 1).click();
// open color picker
await gu.openHeaderColorPicker();
await gu.setFillColor('red');
await gu.setTextColor('blue');
// Save by clicking B column
await gu.getCell('B', 1).click();
await gu.waitForServer();
// Make sure the color is saved.
await gu.assertHeaderFillColor('A', 'red');
await gu.assertHeaderTextColor('A', 'blue');
await gu.assertHeaderFillColor('B', defaultHeaderBackgroundColor);
await gu.assertHeaderTextColor('B', 'black');
// Make sure it sticks after reload.
await driver.navigate().refresh();
await gu.waitForDocToLoad();
await gu.assertHeaderFillColor('A', 'red');
await gu.assertHeaderTextColor('A', 'blue');
await gu.assertHeaderFillColor('B', defaultHeaderBackgroundColor);
await gu.assertHeaderTextColor('B', 'black');
});
it('should undo and redo colors when clicked away', async function () {
await gu.getCell('A', 1).click();
// open color picker
await gu.openHeaderColorPicker();
await gu.setFillColor('red');
await gu.setTextColor('blue');
// Save by clicking B column
await gu.getCell('B', 1).click();
await gu.waitForServer();
// Make sure the color is saved.
await gu.assertHeaderFillColor('A', 'red');
await gu.assertHeaderTextColor('A', 'blue');
await gu.assertHeaderFillColor('B', defaultHeaderBackgroundColor);
await gu.assertHeaderTextColor('B', 'black');
// Make sure then undoing works.
await gu.undo();
await gu.assertHeaderFillColor('A', defaultHeaderBackgroundColor);
await gu.assertHeaderTextColor('A', 'black');
await gu.assertHeaderFillColor('B', defaultHeaderBackgroundColor);
await gu.assertHeaderTextColor('B', 'black');
await gu.redo();
await gu.assertHeaderFillColor('A', 'red');
await gu.assertHeaderTextColor('A', 'blue');
await gu.assertHeaderFillColor('B', defaultHeaderBackgroundColor);
await gu.assertHeaderTextColor('B', 'black');
});
it('should work correctly on Grid view', async function () {
const columnHeader = gu.getColumnHeader('C');
await columnHeader.click();
// open color picker
await gu.openHeaderColorPicker();
// set header colors
await gu.setColor(driver.find('.test-text-input'), 'rgb(255, 0, 0)');
await gu.setColor(driver.find('.test-fill-input'), 'rgb(0, 0, 255)');
// press enter to close color picker
await driver.sendKeys(Key.ENTER);
// check header colors
assert.equal(await columnHeader.getCssValue('color'), 'rgba(255, 0, 0, 1)');
assert.equal(await columnHeader.getCssValue('background-color'), 'rgba(0, 0, 255, 1)');
});
it('should not exist in Detail view', async function () {
// Color the A column in Grid View
const columnHeader = gu.getColumnHeader('A');
await columnHeader.click();
await gu.openHeaderColorPicker();
await gu.setColor(driver.find('.test-text-input'), 'rgb(255, 0, 0)');
await driver.sendKeys(Key.ENTER);
// Add a card list widget of Table1
await gu.addNewSection(/Card List/, /Table1/);
// check header colors
const detailHeader = await driver.findContent('.g_record_detail_label', gu.exactMatch('A'));
assert.equal(await detailHeader.getCssValue('color'), 'rgba(146, 146, 153, 1)');
// There is no header color picker
assert.isFalse(await driver.find('.test-header-color-select .test-color-select').isPresent());
});
});

View File

@ -86,7 +86,7 @@ describe('MultiColumn', function() {
await gu.getCell('Test2', 3).click(); await gu.getCell('Test2', 3).click();
await gu.enterCell('Table1', Key.ENTER); await gu.enterCell('Table1', Key.ENTER);
await selectColumns('Test1', 'Test2'); await selectColumns('Test1', 'Test2');
await gu.openColorPicker(); await gu.openCellColorPicker();
await gu.setFillColor(blue); await gu.setFillColor(blue);
// Clicking on one of the cell caused that the color was not saved. // Clicking on one of the cell caused that the color was not saved.
await gu.getCell('Test2', 1).click(); await gu.getCell('Test2', 1).click();
@ -346,21 +346,21 @@ describe('MultiColumn', function() {
await removeColumn('Test1'); await removeColumn('Test1');
await removeColumn('Test2'); await removeColumn('Test2');
}); });
it('should change background for multiple columns', async () => { it('should change cell background for multiple columns', async () => {
await selectColumns('Test1', 'Test2'); await selectColumns('Test1', 'Test2');
assert.equal(await colorLabel(), "Default cell style"); assert.equal(await cellColorLabel(), "Default cell style");
await gu.openColorPicker(); await gu.openCellColorPicker();
await gu.setFillColor(blue); await gu.setFillColor(blue);
await gu.assertFillColor(await gu.getCell('Test1', 1).find(".field_clip"), blue); await gu.assertFillColor(await gu.getCell('Test1', 1).find(".field_clip"), blue);
await gu.assertFillColor(await gu.getCell('Test2', 1).find(".field_clip"), blue); await gu.assertFillColor(await gu.getCell('Test2', 1).find(".field_clip"), blue);
await driver.sendKeys(Key.ESCAPE); await driver.sendKeys(Key.ESCAPE);
await gu.assertFillColor(await gu.getCell('Test1', 1).find(".field_clip"), transparent); await gu.assertFillColor(await gu.getCell('Test1', 1).find(".field_clip"), transparent);
await gu.assertFillColor(await gu.getCell('Test2', 1).find(".field_clip"), transparent); await gu.assertFillColor(await gu.getCell('Test2', 1).find(".field_clip"), transparent);
assert.equal(await colorLabel(), "Default cell style"); assert.equal(await cellColorLabel(), "Default cell style");
// Change one cell to red // Change one cell to red
await selectColumns('Test1'); await selectColumns('Test1');
await gu.openColorPicker(); await gu.openCellColorPicker();
await gu.setFillColor(red); await gu.setFillColor(red);
await driver.sendKeys(Key.ENTER); await driver.sendKeys(Key.ENTER);
await gu.waitForServer(); await gu.waitForServer();
@ -369,9 +369,9 @@ describe('MultiColumn', function() {
// Check label and colors for multicolumn selection. // Check label and colors for multicolumn selection.
await selectColumns('Test1', 'Test2'); await selectColumns('Test1', 'Test2');
assert.equal(await colorLabel(), "Mixed style"); assert.equal(await cellColorLabel(), "Mixed style");
// Try to change to blue, but press escape. // Try to change to blue, but press escape.
await gu.openColorPicker(); await gu.openCellColorPicker();
await gu.setFillColor(blue); await gu.setFillColor(blue);
await gu.assertFillColor(await gu.getCell('Test1', 1).find(".field_clip"), blue); await gu.assertFillColor(await gu.getCell('Test1', 1).find(".field_clip"), blue);
await gu.assertFillColor(await gu.getCell('Test2', 1).find(".field_clip"), blue); await gu.assertFillColor(await gu.getCell('Test2', 1).find(".field_clip"), blue);
@ -381,21 +381,73 @@ describe('MultiColumn', function() {
await gu.assertFillColor(await gu.getCell('Test2', 1).find(".field_clip"), transparent); await gu.assertFillColor(await gu.getCell('Test2', 1).find(".field_clip"), transparent);
// Change both colors. // Change both colors.
await gu.openColorPicker(); await gu.openCellColorPicker();
await gu.setFillColor(blue); await gu.setFillColor(blue);
await driver.sendKeys(Key.ENTER); await driver.sendKeys(Key.ENTER);
await gu.waitForServer(); await gu.waitForServer();
assert.equal(await colorLabel(), "Default cell style"); assert.equal(await cellColorLabel(), "Default cell style");
await gu.assertFillColor(await gu.getCell('Test1', 1).find(".field_clip"), blue); await gu.assertFillColor(await gu.getCell('Test1', 1).find(".field_clip"), blue);
await gu.assertFillColor(await gu.getCell('Test2', 1).find(".field_clip"), blue); await gu.assertFillColor(await gu.getCell('Test2', 1).find(".field_clip"), blue);
// Make sure they stick. // Make sure they stick.
await driver.navigate().refresh(); await driver.navigate().refresh();
await gu.waitForDocToLoad(); await gu.waitForDocToLoad();
assert.equal(await colorLabel(), "Default cell style"); assert.equal(await cellColorLabel(), "Default cell style");
await gu.assertFillColor(await gu.getCell('Test1', 1).find(".field_clip"), blue); await gu.assertFillColor(await gu.getCell('Test1', 1).find(".field_clip"), blue);
await gu.assertFillColor(await gu.getCell('Test2', 1).find(".field_clip"), blue); await gu.assertFillColor(await gu.getCell('Test2', 1).find(".field_clip"), blue);
}); });
it('should change header background for multiple columns', async () => {
const defaultHeaderFillColor = 'rgba(247, 247, 247, 1)';
await selectColumns('Test1', 'Test2');
assert.equal(await headerColorLabel(), "Default header style");
await gu.openHeaderColorPicker();
await gu.setFillColor(blue);
await gu.assertHeaderFillColor('Test1', blue);
await gu.assertHeaderFillColor('Test2', blue);
await driver.sendKeys(Key.ESCAPE);
await gu.assertHeaderFillColor('Test1', defaultHeaderFillColor);
await gu.assertHeaderFillColor('Test2', defaultHeaderFillColor);
assert.equal(await headerColorLabel(), "Default header style");
// Change one header to red
await selectColumns('Test1');
await gu.openHeaderColorPicker();
await gu.setFillColor(red);
await driver.sendKeys(Key.ENTER);
await gu.waitForServer();
await gu.assertHeaderFillColor('Test1', red);
await gu.assertHeaderFillColor('Test2', defaultHeaderFillColor);
// Check label and colors for multicolumn selection.
await selectColumns('Test1', 'Test2');
assert.equal(await headerColorLabel(), "Mixed style");
// Try to change to blue, but press escape.
await gu.openHeaderColorPicker();
await gu.setFillColor(blue);
await gu.assertHeaderFillColor('Test1', blue);
await gu.assertHeaderFillColor('Test2', blue);
await driver.sendKeys(Key.ESCAPE);
await gu.assertHeaderFillColor('Test1', red);
await gu.assertHeaderFillColor('Test2', defaultHeaderFillColor);
// Change both colors.
await gu.openHeaderColorPicker();
await gu.setFillColor(blue);
await driver.sendKeys(Key.ENTER);
await gu.waitForServer();
assert.equal(await headerColorLabel(), "Default header style");
await gu.assertHeaderFillColor('Test1', blue);
await gu.assertHeaderFillColor('Test2', blue);
// Make sure they stick.
await driver.navigate().refresh();
await gu.waitForDocToLoad();
assert.equal(await headerColorLabel(), "Default header style");
await gu.assertHeaderFillColor('Test1', blue);
await gu.assertHeaderFillColor('Test2', blue);
});
}); });
describe(`test for Integer column`, function() { describe(`test for Integer column`, function() {
@ -1344,8 +1396,14 @@ async function slider(value?: number) {
return parseInt(await driver.find(".test-pw-thumbnail-size").getAttribute('value')); return parseInt(await driver.find(".test-pw-thumbnail-size").getAttribute('value'));
} }
async function colorLabel() { async function cellColorLabel() {
// Text actually contains T symbol before. // Text actually contains T symbol before.
const label = await driver.find(".test-color-select").getText(); const label = await driver.find(".test-cell-color-select .test-color-select").getText();
return label.replace(/^T/, '').trim();
}
async function headerColorLabel() {
// Text actually contains T symbol before.
const label = await driver.find(".test-header-color-select .test-color-select").getText();
return label.replace(/^T/, '').trim(); return label.replace(/^T/, '').trim();
} }

View File

@ -2203,15 +2203,30 @@ export async function clickAway() {
} }
/** /**
* Opens a color picker, either the default one or the one for a specific style rule. * Opens the header color picker.
*/ */
export function openColorPicker(nr?: number) { export function openHeaderColorPicker() {
return driver.find('.test-header-color-select .test-color-select').click();
}
export async function assertHeaderTextColor(col: string, color: string) {
await assertTextColor(await getColumnHeader(col), color);
}
export async function assertHeaderFillColor(col: string, color: string) {
await assertFillColor(await getColumnHeader(col), color);
}
/**
* Opens a cell color picker, either the default one or the one for a specific style rule.
*/
export function openCellColorPicker(nr?: number) {
if (nr !== undefined) { if (nr !== undefined) {
return driver return driver
.find(`.test-widget-style-conditional-rule-${nr} .test-color-select`) .find(`.test-widget-style-conditional-rule-${nr} .test-cell-color-select .test-color-select`)
.click(); .click();
} }
return driver.find('.test-color-select').click(); return driver.find('.test-cell-color-select .test-color-select').click();
} }
export async function assertCellTextColor(col: string, row: number, color: string) { export async function assertCellTextColor(col: string, row: number, color: string) {