mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Better UX in full-edit mode for the formula editor
Summary: Improving UX for the formula editor - Formula editor will go into full edit mode only on formula change (not on a mouse click) - Adding column highlight and a tooltip when in full edit mode Test Plan: nbrowser tests Reviewers: cyprien Reviewed By: cyprien Differential Revision: https://phab.getgrist.com/D3194
This commit is contained in:
parent
e99cc3ae08
commit
0482c83771
@ -42,7 +42,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.gridview_data_header {
|
.gridview_data_header {
|
||||||
border-bottom: 1px solid lightgray;
|
|
||||||
position:relative;
|
position:relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,8 +51,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.field.column_name {
|
.field.column_name {
|
||||||
|
border-bottom: 1px solid lightgray;
|
||||||
line-height: var(--gridview-header-height);
|
line-height: var(--gridview-header-height);
|
||||||
height: var(--gridview-header-height); /* Also should match height for overlay elements */
|
height: calc(var(--gridview-header-height) + 1px); /* Also should match height for overlay elements */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* also .field.column_name, style set in viewCommon */
|
/* also .field.column_name, style set in viewCommon */
|
||||||
@ -350,6 +350,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Column hover effect */
|
||||||
|
|
||||||
|
.gridview_row .field.hover-column, /* normal field in a row */
|
||||||
|
.gridview_row .field.frozen.hover-column, /* frozen field in a row */
|
||||||
|
.column_name.hover-column, /* column name */
|
||||||
|
.column_name.hover-column.selected /* selected column name */ {
|
||||||
|
/* for frozen fields can't use alpha channel */
|
||||||
|
background-color: var(--grist-color-selection-opaque);
|
||||||
|
}
|
||||||
|
/* For zebra stripes, make the selection little darker */
|
||||||
|
.record-zebra.record-even .field.hover-column {
|
||||||
|
background-color: var(--grist-color-selection-darker-opaque);
|
||||||
|
}
|
||||||
|
/* When column has a hover, remove menu button. */
|
||||||
|
.column_name.hover-column .menu_toggle {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
/* Etc */
|
/* Etc */
|
||||||
|
|
||||||
.g-column-main-menu {
|
.g-column-main-menu {
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
var _ = require('underscore');
|
var _ = require('underscore');
|
||||||
var ko = require('knockout');
|
var ko = require('knockout');
|
||||||
|
const debounce = require('lodash/debounce');
|
||||||
|
|
||||||
var gutil = require('app/common/gutil');
|
var gutil = require('app/common/gutil');
|
||||||
var BinaryIndexedTree = require('app/common/BinaryIndexedTree');
|
var BinaryIndexedTree = require('app/common/BinaryIndexedTree');
|
||||||
@ -36,6 +37,7 @@ const {RowContextMenu} = require('../ui/RowContextMenu');
|
|||||||
const {setPopupToCreateDom} = require('popweasel');
|
const {setPopupToCreateDom} = require('popweasel');
|
||||||
const {testId} = require('app/client/ui2018/cssVars');
|
const {testId} = require('app/client/ui2018/cssVars');
|
||||||
const {menuToggle} = require('app/client/ui/MenuToggle');
|
const {menuToggle} = require('app/client/ui/MenuToggle');
|
||||||
|
const {showTooltip} = require('app/client/ui/tooltips');
|
||||||
|
|
||||||
|
|
||||||
// A threshold for interpreting a motionless click as a click rather than a drag.
|
// A threshold for interpreting a motionless click as a click rather than a drag.
|
||||||
@ -187,6 +189,20 @@ function GridView(gristDoc, viewSectionModel, isPreview = false) {
|
|||||||
return ko.pureComputed(() => field._index() < this.numFrozen());
|
return ko.pureComputed(() => field._index() < this.numFrozen());
|
||||||
}, this));
|
}, this));
|
||||||
|
|
||||||
|
// Holds column index that is hovered, works only in full-edit formula mode.
|
||||||
|
this.hoverColumn = this.autoDispose(ko.observable(-1));
|
||||||
|
// Debounced method to change current hover column, this is needed
|
||||||
|
// as mouse when moved from field to field will switch the hover-column
|
||||||
|
// observable from current index to -1 and then immediately back to current index.
|
||||||
|
// With debounced version, call to set -1 that is followed by call to set back to the field index
|
||||||
|
// will be discarded.
|
||||||
|
this.changeHover = debounce((index) => {
|
||||||
|
if (this.isDisposed()) { return; }
|
||||||
|
if (this.gristDoc.docModel.editingFormula()) {
|
||||||
|
this.hoverColumn(index);
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// Create and attach the DOM for the view.
|
// Create and attach the DOM for the view.
|
||||||
|
|
||||||
@ -852,7 +868,10 @@ GridView.prototype.buildDom = function() {
|
|||||||
dom('div.gridview_left_border'), //these hide behind the actual headers to keep them from flashing
|
dom('div.gridview_left_border'), //these hide behind the actual headers to keep them from flashing
|
||||||
// left shadow that will be visible on top of frozen columns
|
// left shadow that will be visible on top of frozen columns
|
||||||
dom('div.scroll_shadow_frozen', kd.show(this.frozenShadow)),
|
dom('div.scroll_shadow_frozen', kd.show(this.frozenShadow)),
|
||||||
|
// When cursor leaves the GridView, remove hover immediately (without debounce).
|
||||||
|
// This guards mouse leaving gridView from the top, as leaving from bottom or left, right, is
|
||||||
|
// guarded on the row level.
|
||||||
|
dom.on("mouseleave", () => !this.isDisposed() && this.hoverColumn(-1)),
|
||||||
// Drag indicators
|
// Drag indicators
|
||||||
self.colLine = dom(
|
self.colLine = dom(
|
||||||
'div.col_indicator_line',
|
'div.col_indicator_line',
|
||||||
@ -900,12 +919,30 @@ GridView.prototype.buildDom = function() {
|
|||||||
write: val => editIndex(val ? field._index() : -1)
|
write: val => editIndex(val ? field._index() : -1)
|
||||||
}).extend({ rateLimit: 0 });
|
}).extend({ rateLimit: 0 });
|
||||||
let filterTriggerCtl;
|
let filterTriggerCtl;
|
||||||
|
const isTooltip = ko.pureComputed(() =>
|
||||||
|
self.gristDoc.docModel.editingFormula() &&
|
||||||
|
ko.unwrap(self.hoverColumn) === field._index());
|
||||||
return dom(
|
return dom(
|
||||||
'div.column_name.field',
|
'div.column_name.field',
|
||||||
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),
|
||||||
dom.autoDispose(isEditingLabel),
|
dom.autoDispose(isEditingLabel),
|
||||||
|
dom.autoDispose(isTooltip),
|
||||||
dom.testId("GridView_columnLabel"),
|
dom.testId("GridView_columnLabel"),
|
||||||
|
(el) => {
|
||||||
|
const tooltip = new HoverColumnTooltip(el);
|
||||||
|
return [
|
||||||
|
dom.autoDispose(tooltip),
|
||||||
|
dom.autoDispose(isTooltip.subscribe((show) => {
|
||||||
|
if (show) {
|
||||||
|
tooltip.show(`Click to insert $${field.colId.peek()}`);
|
||||||
|
} else {
|
||||||
|
tooltip.hide();
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
]
|
||||||
|
},
|
||||||
kd.style('width', field.widthPx),
|
kd.style('width', field.widthPx),
|
||||||
kd.style('borderRightWidth', v.borderWidthPx),
|
kd.style('borderRightWidth', v.borderWidthPx),
|
||||||
viewCommon.makeResizable(field.width, {shouldSave: !this.gristDoc.isReadonly.get()}),
|
viewCommon.makeResizable(field.width, {shouldSave: !this.gristDoc.isReadonly.get()}),
|
||||||
@ -920,6 +957,8 @@ GridView.prototype.buildDom = function() {
|
|||||||
kf.editableLabel(self.isPreview ? field.label : field.displayLabel, isEditingLabel, renameCommands),
|
kf.editableLabel(self.isPreview ? field.label : field.displayLabel, isEditingLabel, renameCommands),
|
||||||
dom.on('mousedown', ev => isEditingLabel() ? ev.stopPropagation() : true)
|
dom.on('mousedown', ev => isEditingLabel() ? ev.stopPropagation() : true)
|
||||||
),
|
),
|
||||||
|
dom.on("mouseenter", () => self.changeHover(field._index())),
|
||||||
|
dom.on("mouseleave", () => self.changeHover(-1)),
|
||||||
self.isPreview ? null : menuToggle(null,
|
self.isPreview ? null : menuToggle(null,
|
||||||
kd.cssClass('g-column-main-menu'),
|
kd.cssClass('g-column-main-menu'),
|
||||||
kd.cssClass('g-column-menu-btn'),
|
kd.cssClass('g-column-menu-btn'),
|
||||||
@ -1043,7 +1082,12 @@ GridView.prototype.buildDom = function() {
|
|||||||
kd.toggleClass('record-zebra', vZebraStripes),
|
kd.toggleClass('record-zebra', vZebraStripes),
|
||||||
// even by 1-indexed rownum, so +1 (makes more sense for user-facing display stuff)
|
// even by 1-indexed rownum, so +1 (makes more sense for user-facing display stuff)
|
||||||
kd.toggleClass('record-even', () => (row._index()+1) % 2 === 0 ),
|
kd.toggleClass('record-even', () => (row._index()+1) % 2 === 0 ),
|
||||||
|
dom.on("mouseleave", (ev) => {
|
||||||
|
// Leave only when leaving record row.
|
||||||
|
if (!ev.relatedTarget || !ev.relatedTarget.classList.contains("record")){
|
||||||
|
self.changeHover(-1);
|
||||||
|
}
|
||||||
|
}),
|
||||||
self.comparison ? kd.cssClass(() => {
|
self.comparison ? kd.cssClass(() => {
|
||||||
const rowType = self.extraRows.getRowType(row.id());
|
const rowType = self.extraRows.getRowType(row.id());
|
||||||
return rowType && `diff-${rowType}` || '';
|
return rowType && `diff-${rowType}` || '';
|
||||||
@ -1078,6 +1122,10 @@ GridView.prototype.buildDom = function() {
|
|||||||
dom.autoDispose(isCellSelected),
|
dom.autoDispose(isCellSelected),
|
||||||
dom.autoDispose(isCellActive),
|
dom.autoDispose(isCellActive),
|
||||||
dom.autoDispose(isSelected),
|
dom.autoDispose(isSelected),
|
||||||
|
dom.on("mouseenter", () => self.changeHover(field._index())),
|
||||||
|
kd.toggleClass("hover-column", () =>
|
||||||
|
self.gristDoc.docModel.editingFormula() &&
|
||||||
|
ko.unwrap(self.hoverColumn) === (field._index())),
|
||||||
kd.style('width', field.widthPx),
|
kd.style('width', field.widthPx),
|
||||||
//TODO: Ensure that fields in a row resize when
|
//TODO: Ensure that fields in a row resize when
|
||||||
//a cell in that row becomes larger
|
//a cell in that row becomes larger
|
||||||
@ -1428,10 +1476,30 @@ GridView.prototype.maybeSelectRow = function(elem, rowId) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// End Context Menus
|
||||||
|
|
||||||
GridView.prototype.revealActiveRecord = function() {
|
GridView.prototype.revealActiveRecord = function() {
|
||||||
return kd.doScrollChildIntoView(this.scrollPane, this.cursor.rowIndex());
|
return kd.doScrollChildIntoView(this.scrollPane, this.cursor.rowIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
// End Context Menus
|
// Helper to show tooltip over column selection in the full edit mode.
|
||||||
|
class HoverColumnTooltip {
|
||||||
|
constructor(el) {
|
||||||
|
this.el = el;
|
||||||
|
}
|
||||||
|
show(text) {
|
||||||
|
this.hide();
|
||||||
|
this.tooltip = showTooltip(this.el, () => dom("span", text, testId("column-formula-tooltip")))
|
||||||
|
}
|
||||||
|
hide() {
|
||||||
|
if (this.tooltip ) {
|
||||||
|
this.tooltip.close();
|
||||||
|
this.tooltip = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dispose() {
|
||||||
|
this.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = GridView;
|
module.exports = GridView;
|
||||||
|
@ -47,6 +47,7 @@ export const colors = {
|
|||||||
cursor: new CustomProp('color-cursor', '#16B378'), // cursor is lightGreen
|
cursor: new CustomProp('color-cursor', '#16B378'), // cursor is lightGreen
|
||||||
selection: new CustomProp('color-selection', 'rgba(22,179,120,0.15)'),
|
selection: new CustomProp('color-selection', 'rgba(22,179,120,0.15)'),
|
||||||
selectionOpaque: new CustomProp('color-selection-opaque', '#DCF4EB'),
|
selectionOpaque: new CustomProp('color-selection-opaque', '#DCF4EB'),
|
||||||
|
selectionDarkerOpaque: new CustomProp('color-selection-darker-opaque', '#d6eee5'),
|
||||||
|
|
||||||
inactiveCursor: new CustomProp('color-inactive-cursor', '#A2E1C9'),
|
inactiveCursor: new CustomProp('color-inactive-cursor', '#A2E1C9'),
|
||||||
|
|
||||||
|
@ -429,8 +429,13 @@ export function openFormulaEditor(options: {
|
|||||||
});
|
});
|
||||||
editor.attach(refElem);
|
editor.attach(refElem);
|
||||||
|
|
||||||
// Enter formula-editing mode (highlight formula icons; click on a column inserts its ID).
|
// When formula is empty enter formula-editing mode (highlight formula icons; click on a column inserts its ID).
|
||||||
field.editingFormula(true);
|
// This function is used for primarily for switching between diffrent column behaviors, so we want to enter full
|
||||||
|
// edit mode right away.
|
||||||
|
// TODO: consider converting it to parameter, when this will be used in diffrent scenarios.
|
||||||
|
if (!column.formula()) {
|
||||||
|
field.editingFormula(true);
|
||||||
|
}
|
||||||
setupCleanup(holder, gristDoc, field, saveEdit);
|
setupCleanup(holder, gristDoc, field, saveEdit);
|
||||||
return holder;
|
return holder;
|
||||||
}
|
}
|
||||||
|
@ -97,12 +97,6 @@ export class FormulaEditor extends NewBaseEditor {
|
|||||||
this._formulaEditor.getEditor().focus();
|
this._formulaEditor.getEditor().focus();
|
||||||
}),
|
}),
|
||||||
dom('div.formula_editor.formula_field_edit', testId('formula-editor'),
|
dom('div.formula_editor.formula_field_edit', testId('formula-editor'),
|
||||||
// We don't always enter editing mode immediately, e.g. not on double-clicking a cell.
|
|
||||||
// In those cases, we'll switch as soon as the user types or clicks into the editor.
|
|
||||||
dom.on('mousedown', () => {
|
|
||||||
// but don't do it when this is a readonly mode
|
|
||||||
options.field.editingFormula(true);
|
|
||||||
}),
|
|
||||||
this._formulaEditor.buildDom((aceObj: any) => {
|
this._formulaEditor.buildDom((aceObj: any) => {
|
||||||
aceObj.setFontSize(11);
|
aceObj.setFontSize(11);
|
||||||
aceObj.setHighlightActiveLine(false);
|
aceObj.setHighlightActiveLine(false);
|
||||||
|
Loading…
Reference in New Issue
Block a user