mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) updates from grist-core
This commit is contained in:
@@ -367,6 +367,9 @@
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.column_name .menu_toggle {
|
||||
z-index: 1;
|
||||
}
|
||||
/* Etc */
|
||||
|
||||
.g-column-main-menu {
|
||||
@@ -408,11 +411,13 @@
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
margin-right: 4px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.g-column-label .kf_editable_label {
|
||||
padding-left: 1px;
|
||||
padding-right: 1px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.g-column-label-spacer {
|
||||
|
||||
@@ -1073,8 +1073,28 @@ GridView.prototype.buildDom = function() {
|
||||
self.editingFormula() &&
|
||||
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(
|
||||
'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.toggleClass("frozen", () => ko.unwrap(this.frozenMap.at(field._index()))),
|
||||
kd.toggleClass("hover-column", isTooltip),
|
||||
|
||||
@@ -195,8 +195,11 @@
|
||||
}
|
||||
|
||||
.column_name {
|
||||
color: var(--grist-theme-table-header-fg, unset);
|
||||
background-color: var(--grist-theme-table-header-bg, var(--grist-color-light-grey));
|
||||
color: var(--grist-header-color,
|
||||
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;
|
||||
cursor: pointer;
|
||||
/* 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));
|
||||
}
|
||||
|
||||
.column_name.selected {
|
||||
color: var(--grist-theme-table-header-selected-fg, unset);
|
||||
background-color: var(--grist-theme-table-header-selected-bg, var(--grist-color-medium-grey-opaque));
|
||||
.column_name.selected > .selection {
|
||||
background-color: var(--grist-theme-selection-header);
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.gridview_data_row_num.selected {
|
||||
|
||||
@@ -7,6 +7,15 @@ export interface Style {
|
||||
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 {
|
||||
public readonly textColor?: string;
|
||||
public readonly fillColor?: string;
|
||||
|
||||
@@ -17,6 +17,8 @@ export class ViewFieldConfig {
|
||||
public options: CommonOptions;
|
||||
/** Style options for a field or multiple fields */
|
||||
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.
|
||||
public wrap: modelUtil.KoSaveableObservable<boolean|undefined>;
|
||||
@@ -255,6 +257,68 @@ export class ViewFieldConfig {
|
||||
result.revert = () => { zip(fields, state).forEach(([f, s]) => f!.style(s!)); };
|
||||
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
|
||||
|
||||
@@ -2,7 +2,7 @@ import {ColumnRec, DocModel, IRowModel, refListRecords, refRecord, ViewSectionRe
|
||||
import {formatterForRec} from 'app/client/models/entities/ColumnRec';
|
||||
import * as modelUtil from 'app/client/models/modelUtil';
|
||||
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 * as UserType from 'app/client/widgets/UserType';
|
||||
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>;
|
||||
fontItalic: 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>;
|
||||
headerStyle: ko.PureComputed<HeaderStyle>;
|
||||
|
||||
config: ViewFieldConfig;
|
||||
|
||||
@@ -236,6 +243,12 @@ export function createViewFieldRec(this: ViewFieldRec, docModel: DocModel): void
|
||||
this.fontUnderline = this.widgetOptionsJson.prop('fontUnderline');
|
||||
this.fontItalic = this.widgetOptionsJson.prop('fontItalic');
|
||||
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.style = ko.pureComputed({
|
||||
@@ -251,6 +264,19 @@ export function createViewFieldRec(this: ViewFieldRec, docModel: DocModel): void
|
||||
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.rulesList = ko.pureComputed(() => this._fieldOrColumn().rules());
|
||||
|
||||
@@ -344,6 +344,7 @@ export const theme = {
|
||||
colors.selectionOpaque),
|
||||
selectionOpaqueDarkBg: new CustomProp('theme-selection-opaque-dark-bg', undefined,
|
||||
colors.selectionDarkerOpaque),
|
||||
selectionHeader: new CustomProp('theme-selection-header', undefined, colors.mediumGrey),
|
||||
|
||||
/* Widgets */
|
||||
widgetBg: new CustomProp('theme-widget-bg', undefined, 'white'),
|
||||
|
||||
@@ -4,7 +4,7 @@ import {GristDoc} from 'app/client/components/GristDoc';
|
||||
import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec';
|
||||
import {textButton} from 'app/client/ui2018/buttons';
|
||||
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 {Computed, Disposable, dom, DomContents, fromKo, styled} from 'grainjs';
|
||||
|
||||
@@ -21,12 +21,61 @@ export class CellStyle extends Disposable {
|
||||
}
|
||||
|
||||
public buildDom(): DomContents {
|
||||
const isTableWidget = this._field.viewSection().parentKey() === 'record';
|
||||
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(
|
||||
cssLabel(t('CELL STYLE')),
|
||||
cssButton(t('Open row styles'), dom.on('click', allCommands.viewTabOpen.run)),
|
||||
),
|
||||
cssRow(
|
||||
testId('cell-color-select'),
|
||||
dom.domComputedOwned(fromKo(this._field.config.style), (holder, options) => {
|
||||
const textColor = fromKo(options.prop("textColor"));
|
||||
const fillColor = fromKo(options.prop("fillColor"));
|
||||
|
||||
Reference in New Issue
Block a user