From 66cb28307505ba540ecebfa5d1a6a317adc0736f Mon Sep 17 00:00:00 2001 From: Camille Date: Tue, 17 Jan 2023 11:39:23 +0100 Subject: [PATCH 01/18] feat(columnDescription): show description textarea in right panel --- app/client/ui/FieldConfig.ts | 33 +++++++++++++++++++++++++++++++++ app/client/ui/RightPanel.ts | 4 ++++ 2 files changed, 37 insertions(+) diff --git a/app/client/ui/FieldConfig.ts b/app/client/ui/FieldConfig.ts index 626ac49e..bba473a0 100644 --- a/app/client/ui/FieldConfig.ts +++ b/app/client/ui/FieldConfig.ts @@ -18,6 +18,7 @@ import {sanitizeIdent} from 'app/common/gutil'; import {bundleChanges, Computed, dom, DomContents, DomElementArg, fromKo, MultiHolder, Observable, styled} from 'grainjs'; import * as ko from 'knockout'; +import { textarea } from './inputs'; const t = makeT('FieldConfig'); @@ -88,6 +89,22 @@ export function buildNameConfig( ]; } +export function buildDescriptionConfig( + owner: MultiHolder, + origColumn: ColumnRec, + cursor: ko.Computed, +) { + const editedDescription = Observable.create(owner, ''); + + return [ + cssLabel(t("DESCRIPTION")), + cssRow( + cssTextArea(editedDescription, {}) + ), + + ]; +} + type SaveHandler = (column: ColumnRec, formula: string) => Promise; type BuildEditor = ( cellElem: Element, @@ -494,3 +511,19 @@ const cssInput = styled(textInput, ` color: ${theme.inputDisabledFg}; } `); + +const cssTextArea = styled(textarea, ` + color: ${theme.inputFg}; + background-color: ${theme.mainPanelBg}; + border: 1px solid ${theme.inputBorder}; + width: 100%; + + &::placeholder { + color: ${theme.inputPlaceholderFg}; + } + + &[readonly] { + background-color: ${theme.inputDisabledBg}; + color: ${theme.inputDisabledFg}; + } +`); diff --git a/app/client/ui/RightPanel.ts b/app/client/ui/RightPanel.ts index 8aceb998..04778f1a 100644 --- a/app/client/ui/RightPanel.ts +++ b/app/client/ui/RightPanel.ts @@ -43,6 +43,7 @@ import {bundleChanges, Computed, Disposable, dom, domComputed, DomContents, DomElementArg, DomElementMethod, IDomComponent} from 'grainjs'; import {MultiHolder, Observable, styled, subscribe} from 'grainjs'; import * as ko from 'knockout'; +import { buildDescriptionConfig } from './FieldConfig'; const t = makeT('RightPanel'); @@ -235,6 +236,9 @@ export class RightPanel extends Disposable { cssSection( dom.create(buildNameConfig, origColumn, cursor, isMultiSelect), ), + cssSection( + dom.create(buildDescriptionConfig, origColumn, cursor, isMultiSelect), + ), cssSeparator(), cssSection( dom.create(buildFormulaConfig, From 0a90fc0c7b6bf49d7b2aeab52745c1dc0557d71e Mon Sep 17 00:00:00 2001 From: Camille Date: Wed, 18 Jan 2023 18:48:02 +0100 Subject: [PATCH 02/18] feat(columnDesc): add front description in single card widget --- app/client/components/DetailView.css | 17 +++++++++++++++++ app/client/components/DetailView.js | 3 +++ app/client/ui2018/cssVars.ts | 5 +++++ 3 files changed, 25 insertions(+) diff --git a/app/client/components/DetailView.css b/app/client/components/DetailView.css index d7f4d876..84e7e171 100644 --- a/app/client/components/DetailView.css +++ b/app/client/components/DetailView.css @@ -37,6 +37,13 @@ font-weight: bold; } +.g_record_detail_description { + min-height: 1rem; + color: #666; + font-size: 0.9rem; + font-weight: normal; + margin-bottom: 2px; +} .g_record_detail_value { position: relative; min-height: 16px; @@ -241,6 +248,16 @@ margin-right: -8px; } +.detail_theme_field_form>.g_record_detail_description { + font-size: var(--grist-small-font-size); + color: var(--grist-theme-card-form-label, var(--grist-color-slate)); + font-weight: normal; + min-height: 0px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin-right: -8px; +} /* TODO want to style better the values themselves (e.g. more padding, rounded corners, move label * inside value box for compact view for better cursor looks, etc), but first the cell editor * needs to learn to match the value box's style. Right now, the cell editor style is hard-coded. diff --git a/app/client/components/DetailView.js b/app/client/components/DetailView.js index 6054a53d..9cd34461 100644 --- a/app/client/components/DetailView.js +++ b/app/client/components/DetailView.js @@ -228,6 +228,7 @@ DetailView.prototype.buildFieldDom = function(field, row) { return dom('div.g_record_detail_el.flexitem', kd.cssClass(function() { return 'detail_theme_field_' + self.viewSection.themeDef(); }), dom('div.g_record_detail_label', field.label), + dom('div.g_record_detail_description', field.description), dom('div.g_record_detail_value', field.value) ); } @@ -258,6 +259,8 @@ DetailView.prototype.buildFieldDom = function(field, row) { dom.autoDispose(isCellActive), kd.cssClass(function() { return 'detail_theme_field_' + self.viewSection.themeDef(); }), dom('div.g_record_detail_label', kd.text(field.displayLabel)), + // TODO : show the real description + dom('div.g_record_detail_description', kd.text("Description testing")), dom('div.g_record_detail_value', kd.toggleClass('scissors', isCopyActive), kd.toggleClass('record-add', row._isAddRow), diff --git a/app/client/ui2018/cssVars.ts b/app/client/ui2018/cssVars.ts index 4014739a..6b84698c 100644 --- a/app/client/ui2018/cssVars.ts +++ b/app/client/ui2018/cssVars.ts @@ -605,6 +605,11 @@ export const theme = { menuToggleBg: new CustomProp('theme-menu-toggle-bg', undefined, 'white'), menuToggleBorder: new CustomProp('theme-menu-toggle-border', undefined, colors.slate), + /* Info Toggles */ + infoToggleFg: new CustomProp('theme-info-toggle-fg', undefined, "#8F8F8F"), + infoToggleHoverFg: new CustomProp('theme-info-toggle-hover-fg', undefined, "#707070"), + infoToggleActiveFg: new CustomProp('theme-info-toggle-active-fg', undefined, "#5C5C5C"), + /* Button Groups */ buttonGroupFg: new CustomProp('theme-button-group-fg', undefined, colors.dark), buttonGroupLightFg: new CustomProp('theme-button-group-light-fg', undefined, colors.slate), From 009ebefd96704e514cfcf1ae09ab99e969d70f52 Mon Sep 17 00:00:00 2001 From: Camille Date: Thu, 19 Jan 2023 12:37:31 +0100 Subject: [PATCH 03/18] feat(ColumnDesc): create column description in database and link it to visual behaviors --- app/client/components/DetailView.js | 3 +-- app/client/models/entities/ViewFieldRec.ts | 2 ++ app/client/ui/FieldConfig.ts | 25 +++++++++++++++++++++- app/client/ui/RightPanel.ts | 2 +- app/common/schema.ts | 4 +++- app/server/lib/initialDocSql.ts | 16 +++++++------- sandbox/grist/migrations.py | 8 +++++++ sandbox/grist/schema.py | 3 ++- 8 files changed, 49 insertions(+), 14 deletions(-) diff --git a/app/client/components/DetailView.js b/app/client/components/DetailView.js index 9cd34461..ba0eaa58 100644 --- a/app/client/components/DetailView.js +++ b/app/client/components/DetailView.js @@ -259,8 +259,7 @@ DetailView.prototype.buildFieldDom = function(field, row) { dom.autoDispose(isCellActive), kd.cssClass(function() { return 'detail_theme_field_' + self.viewSection.themeDef(); }), dom('div.g_record_detail_label', kd.text(field.displayLabel)), - // TODO : show the real description - dom('div.g_record_detail_description', kd.text("Description testing")), + dom('div.g_record_detail_description', kd.text(field.description)), dom('div.g_record_detail_value', kd.toggleClass('scissors', isCopyActive), kd.toggleClass('record-add', row._isAddRow), diff --git a/app/client/models/entities/ViewFieldRec.ts b/app/client/models/entities/ViewFieldRec.ts index 68578fd4..30b5b0c0 100644 --- a/app/client/models/entities/ViewFieldRec.ts +++ b/app/client/models/entities/ViewFieldRec.ts @@ -20,6 +20,7 @@ export interface ViewFieldRec extends IRowModel<"_grist_Views_section_field">, R origCol: ko.Computed; colId: ko.Computed; label: ko.Computed; + description: ko.Computed; // displayLabel displays label by default but switches to the more helpful colId whenever a // formula field in the view is being edited. @@ -108,6 +109,7 @@ export function createViewFieldRec(this: ViewFieldRec, docModel: DocModel): void this.origCol = ko.pureComputed(() => this.column().origCol()); this.colId = ko.pureComputed(() => this.column().colId()); this.label = ko.pureComputed(() => this.column().label()); + this.description = ko.pureComputed(() => this.column().description()); // displayLabel displays label by default but switches to the more helpful colId whenever a // formula field in the view is being edited. diff --git a/app/client/ui/FieldConfig.ts b/app/client/ui/FieldConfig.ts index bba473a0..12726392 100644 --- a/app/client/ui/FieldConfig.ts +++ b/app/client/ui/FieldConfig.ts @@ -95,11 +95,34 @@ export function buildDescriptionConfig( cursor: ko.Computed, ) { const editedDescription = Observable.create(owner, ''); + const saveDescription = async (val: string) => { + await origColumn.description.saveOnly(val); + editedDescription.set(''); + } + + // We will listen to cursor position and force a blur event on + // the text input, which will trigger save before the column observable + // will change its value. + // Otherwise, blur will be invoked after column change and save handler will + // update a different column. + let editor: HTMLTextAreaElement | undefined; + owner.autoDispose( + cursor.subscribe(() => { + editor?.blur(); + }) + ); return [ cssLabel(t("DESCRIPTION")), cssRow( - cssTextArea(editedDescription, {}) + editor = cssTextArea(fromKo(origColumn.description), + { onInput: false }, + { placeholder: t("If necesary, describe the column") }, + dom.on('input', (e, elem) => { + editedDescription.set(elem.value); + saveDescription(elem.value) + }), + ) ), ]; diff --git a/app/client/ui/RightPanel.ts b/app/client/ui/RightPanel.ts index 04778f1a..50416ee3 100644 --- a/app/client/ui/RightPanel.ts +++ b/app/client/ui/RightPanel.ts @@ -237,7 +237,7 @@ export class RightPanel extends Disposable { dom.create(buildNameConfig, origColumn, cursor, isMultiSelect), ), cssSection( - dom.create(buildDescriptionConfig, origColumn, cursor, isMultiSelect), + dom.create(buildDescriptionConfig, origColumn, cursor), ), cssSeparator(), cssSection( diff --git a/app/common/schema.ts b/app/common/schema.ts index 7e0912a1..ea2cc587 100644 --- a/app/common/schema.ts +++ b/app/common/schema.ts @@ -4,7 +4,7 @@ import { GristObjCode } from "app/plugin/GristData"; // tslint:disable:object-literal-key-quotes -export const SCHEMA_VERSION = 35; +export const SCHEMA_VERSION = 36; export const schema = { @@ -34,6 +34,7 @@ export const schema = { isFormula : "Bool", formula : "Text", label : "Text", + description: "Text", untieColIdFromLabel : "Bool", summarySourceCol : "Ref:_grist_Tables_column", displayCol : "Ref:_grist_Tables_column", @@ -239,6 +240,7 @@ export interface SchemaTypes { isFormula: boolean; formula: string; label: string; + description: string; untieColIdFromLabel: boolean; summarySourceCol: number; displayCol: number; diff --git a/app/server/lib/initialDocSql.ts b/app/server/lib/initialDocSql.ts index b986c9b1..eab77b4c 100644 --- a/app/server/lib/initialDocSql.ts +++ b/app/server/lib/initialDocSql.ts @@ -6,9 +6,9 @@ export const GRIST_DOC_SQL = ` PRAGMA foreign_keys=OFF; BEGIN TRANSACTION; CREATE TABLE IF NOT EXISTS "_grist_DocInfo" (id INTEGER PRIMARY KEY, "docId" TEXT DEFAULT '', "peers" TEXT DEFAULT '', "basketId" TEXT DEFAULT '', "schemaVersion" INTEGER DEFAULT 0, "timezone" TEXT DEFAULT '', "documentSettings" TEXT DEFAULT ''); -INSERT INTO _grist_DocInfo VALUES(1,'','','',35,'',''); +INSERT INTO _grist_DocInfo VALUES(1,'','','',36,'',''); CREATE TABLE IF NOT EXISTS "_grist_Tables" (id INTEGER PRIMARY KEY, "tableId" TEXT DEFAULT '', "primaryViewId" INTEGER DEFAULT 0, "summarySourceTable" INTEGER DEFAULT 0, "onDemand" BOOLEAN DEFAULT 0, "rawViewSectionRef" INTEGER DEFAULT 0); -CREATE TABLE IF NOT EXISTS "_grist_Tables_column" (id INTEGER PRIMARY KEY, "parentId" INTEGER DEFAULT 0, "parentPos" NUMERIC DEFAULT 1e999, "colId" TEXT DEFAULT '', "type" TEXT DEFAULT '', "widgetOptions" TEXT DEFAULT '', "isFormula" BOOLEAN DEFAULT 0, "formula" TEXT DEFAULT '', "label" TEXT DEFAULT '', "untieColIdFromLabel" BOOLEAN DEFAULT 0, "summarySourceCol" INTEGER DEFAULT 0, "displayCol" INTEGER DEFAULT 0, "visibleCol" INTEGER DEFAULT 0, "rules" TEXT DEFAULT NULL, "recalcWhen" INTEGER DEFAULT 0, "recalcDeps" TEXT DEFAULT NULL); +CREATE TABLE IF NOT EXISTS "_grist_Tables_column" (id INTEGER PRIMARY KEY, "parentId" INTEGER DEFAULT 0, "parentPos" NUMERIC DEFAULT 1e999, "colId" TEXT DEFAULT '', "type" TEXT DEFAULT '', "widgetOptions" TEXT DEFAULT '', "isFormula" BOOLEAN DEFAULT 0, "formula" TEXT DEFAULT '', "label" TEXT DEFAULT '', "description" TEXT DEFAULT '', "untieColIdFromLabel" BOOLEAN DEFAULT 0, "summarySourceCol" INTEGER DEFAULT 0, "displayCol" INTEGER DEFAULT 0, "visibleCol" INTEGER DEFAULT 0, "rules" TEXT DEFAULT NULL, "recalcWhen" INTEGER DEFAULT 0, "recalcDeps" TEXT DEFAULT NULL); CREATE TABLE IF NOT EXISTS "_grist_Imports" (id INTEGER PRIMARY KEY, "tableRef" INTEGER DEFAULT 0, "origFileName" TEXT DEFAULT '', "parseFormula" TEXT DEFAULT '', "delimiter" TEXT DEFAULT '', "doublequote" BOOLEAN DEFAULT 0, "escapechar" TEXT DEFAULT '', "quotechar" TEXT DEFAULT '', "skipinitialspace" BOOLEAN DEFAULT 0, "encoding" TEXT DEFAULT '', "hasHeaders" BOOLEAN DEFAULT 0); CREATE TABLE IF NOT EXISTS "_grist_External_database" (id INTEGER PRIMARY KEY, "host" TEXT DEFAULT '', "port" INTEGER DEFAULT 0, "username" TEXT DEFAULT '', "dialect" TEXT DEFAULT '', "database" TEXT DEFAULT '', "storage" TEXT DEFAULT ''); CREATE TABLE IF NOT EXISTS "_grist_External_table" (id INTEGER PRIMARY KEY, "tableRef" INTEGER DEFAULT 0, "databaseRef" INTEGER DEFAULT 0, "tableName" TEXT DEFAULT ''); @@ -43,14 +43,14 @@ export const GRIST_DOC_WITH_TABLE1_SQL = ` PRAGMA foreign_keys=OFF; BEGIN TRANSACTION; CREATE TABLE IF NOT EXISTS "_grist_DocInfo" (id INTEGER PRIMARY KEY, "docId" TEXT DEFAULT '', "peers" TEXT DEFAULT '', "basketId" TEXT DEFAULT '', "schemaVersion" INTEGER DEFAULT 0, "timezone" TEXT DEFAULT '', "documentSettings" TEXT DEFAULT ''); -INSERT INTO _grist_DocInfo VALUES(1,'','','',35,'',''); +INSERT INTO _grist_DocInfo VALUES(1,'','','',36,'',''); CREATE TABLE IF NOT EXISTS "_grist_Tables" (id INTEGER PRIMARY KEY, "tableId" TEXT DEFAULT '', "primaryViewId" INTEGER DEFAULT 0, "summarySourceTable" INTEGER DEFAULT 0, "onDemand" BOOLEAN DEFAULT 0, "rawViewSectionRef" INTEGER DEFAULT 0); INSERT INTO _grist_Tables VALUES(1,'Table1',1,0,0,2); -CREATE TABLE IF NOT EXISTS "_grist_Tables_column" (id INTEGER PRIMARY KEY, "parentId" INTEGER DEFAULT 0, "parentPos" NUMERIC DEFAULT 1e999, "colId" TEXT DEFAULT '', "type" TEXT DEFAULT '', "widgetOptions" TEXT DEFAULT '', "isFormula" BOOLEAN DEFAULT 0, "formula" TEXT DEFAULT '', "label" TEXT DEFAULT '', "untieColIdFromLabel" BOOLEAN DEFAULT 0, "summarySourceCol" INTEGER DEFAULT 0, "displayCol" INTEGER DEFAULT 0, "visibleCol" INTEGER DEFAULT 0, "rules" TEXT DEFAULT NULL, "recalcWhen" INTEGER DEFAULT 0, "recalcDeps" TEXT DEFAULT NULL); -INSERT INTO _grist_Tables_column VALUES(1,1,1,'manualSort','ManualSortPos','',0,'','manualSort',0,0,0,0,NULL,0,NULL); -INSERT INTO _grist_Tables_column VALUES(2,1,2,'A','Any','',1,'','A',0,0,0,0,NULL,0,NULL); -INSERT INTO _grist_Tables_column VALUES(3,1,3,'B','Any','',1,'','B',0,0,0,0,NULL,0,NULL); -INSERT INTO _grist_Tables_column VALUES(4,1,4,'C','Any','',1,'','C',0,0,0,0,NULL,0,NULL); +CREATE TABLE IF NOT EXISTS "_grist_Tables_column" (id INTEGER PRIMARY KEY, "parentId" INTEGER DEFAULT 0, "parentPos" NUMERIC DEFAULT 1e999, "colId" TEXT DEFAULT '', "type" TEXT DEFAULT '', "widgetOptions" TEXT DEFAULT '', "isFormula" BOOLEAN DEFAULT 0, "formula" TEXT DEFAULT '', "label" TEXT DEFAULT '', "description" TEXT DEFAULT '', "untieColIdFromLabel" BOOLEAN DEFAULT 0, "summarySourceCol" INTEGER DEFAULT 0, "displayCol" INTEGER DEFAULT 0, "visibleCol" INTEGER DEFAULT 0, "rules" TEXT DEFAULT NULL, "recalcWhen" INTEGER DEFAULT 0, "recalcDeps" TEXT DEFAULT NULL); +INSERT INTO _grist_Tables_column VALUES(1,1,1,'manualSort','ManualSortPos','',0,'','manualSort','',0,0,0,0,NULL,0,NULL); +INSERT INTO _grist_Tables_column VALUES(2,1,2,'A','Any','',1,'','A','',0,0,0,0,NULL,0,NULL); +INSERT INTO _grist_Tables_column VALUES(3,1,3,'B','Any','',1,'','B','',0,0,0,0,NULL,0,NULL); +INSERT INTO _grist_Tables_column VALUES(4,1,4,'C','Any','',1,'','C','',0,0,0,0,NULL,0,NULL); CREATE TABLE IF NOT EXISTS "_grist_Imports" (id INTEGER PRIMARY KEY, "tableRef" INTEGER DEFAULT 0, "origFileName" TEXT DEFAULT '', "parseFormula" TEXT DEFAULT '', "delimiter" TEXT DEFAULT '', "doublequote" BOOLEAN DEFAULT 0, "escapechar" TEXT DEFAULT '', "quotechar" TEXT DEFAULT '', "skipinitialspace" BOOLEAN DEFAULT 0, "encoding" TEXT DEFAULT '', "hasHeaders" BOOLEAN DEFAULT 0); CREATE TABLE IF NOT EXISTS "_grist_External_database" (id INTEGER PRIMARY KEY, "host" TEXT DEFAULT '', "port" INTEGER DEFAULT 0, "username" TEXT DEFAULT '', "dialect" TEXT DEFAULT '', "database" TEXT DEFAULT '', "storage" TEXT DEFAULT ''); CREATE TABLE IF NOT EXISTS "_grist_External_table" (id INTEGER PRIMARY KEY, "tableRef" INTEGER DEFAULT 0, "databaseRef" INTEGER DEFAULT 0, "tableName" TEXT DEFAULT ''); diff --git a/sandbox/grist/migrations.py b/sandbox/grist/migrations.py index f04ad057..b58031b8 100644 --- a/sandbox/grist/migrations.py +++ b/sandbox/grist/migrations.py @@ -1188,3 +1188,11 @@ def migration35(tdset): )) return tdset.apply_doc_actions(doc_actions) + + +@migration(schema_version=36) +def migration36(tdset): + """ + Add description to column + """ + return tdset.apply_doc_actions([add_column('_grist_Tables_column', 'description', 'Text')]) diff --git a/sandbox/grist/schema.py b/sandbox/grist/schema.py index 9a7d5014..8679b8fb 100644 --- a/sandbox/grist/schema.py +++ b/sandbox/grist/schema.py @@ -15,7 +15,7 @@ import six import actions -SCHEMA_VERSION = 35 +SCHEMA_VERSION = 36 def make_column(col_id, col_type, formula='', isFormula=False): return { @@ -70,6 +70,7 @@ def schema_create_actions(): make_column("isFormula", "Bool"), make_column("formula", "Text"), make_column("label", "Text"), + make_column("description", "Text"), # Normally a change to label changes colId as well, unless untieColIdFromLabel is True. # (We intentionally pick a variable whose default value is false.) From 969280629bfdc7c40e478bafab282396d6164a6a Mon Sep 17 00:00:00 2001 From: Camille Date: Thu, 19 Jan 2023 12:51:02 +0100 Subject: [PATCH 04/18] fix: linter correction --- app/client/ui/FieldConfig.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/app/client/ui/FieldConfig.ts b/app/client/ui/FieldConfig.ts index 12726392..a35ee7cb 100644 --- a/app/client/ui/FieldConfig.ts +++ b/app/client/ui/FieldConfig.ts @@ -95,10 +95,6 @@ export function buildDescriptionConfig( cursor: ko.Computed, ) { const editedDescription = Observable.create(owner, ''); - const saveDescription = async (val: string) => { - await origColumn.description.saveOnly(val); - editedDescription.set(''); - } // We will listen to cursor position and force a blur event on // the text input, which will trigger save before the column observable @@ -118,9 +114,10 @@ export function buildDescriptionConfig( editor = cssTextArea(fromKo(origColumn.description), { onInput: false }, { placeholder: t("If necesary, describe the column") }, - dom.on('input', (e, elem) => { + dom.on('input', async (e, elem) => { editedDescription.set(elem.value); - saveDescription(elem.value) + await origColumn.description.saveOnly(elem.value); + editedDescription.set(''); }), ) ), From f0dc24306af13873f3765d9aa4b3399025839297 Mon Sep 17 00:00:00 2001 From: Camille Date: Thu, 19 Jan 2023 18:37:24 +0100 Subject: [PATCH 05/18] feat(ColumnDesc): Info Tooltip with column desc in DetailView --- app/client/components/DetailView.css | 22 +---- app/client/components/DetailView.js | 15 ++- app/client/components/DocumentUsage.ts | 4 +- app/client/ui/FieldConfig.ts | 6 +- app/client/ui/PageWidgetPicker.ts | 4 +- app/client/ui/ShareMenu.ts | 4 +- app/client/ui/UserManager.ts | 4 +- app/client/ui/tooltips.ts | 126 +++++++++++++++++-------- app/client/ui2018/cssVars.ts | 8 +- app/client/widgets/ConditionalStyle.ts | 4 +- 10 files changed, 120 insertions(+), 77 deletions(-) diff --git a/app/client/components/DetailView.css b/app/client/components/DetailView.css index 84e7e171..cf7d026e 100644 --- a/app/client/components/DetailView.css +++ b/app/client/components/DetailView.css @@ -37,13 +37,6 @@ font-weight: bold; } -.g_record_detail_description { - min-height: 1rem; - color: #666; - font-size: 0.9rem; - font-weight: normal; - margin-bottom: 2px; -} .g_record_detail_value { position: relative; min-height: 16px; @@ -237,7 +230,7 @@ padding: 1px 1px 1px 5px; } -.detail_theme_field_form > .g_record_detail_label { +.detail_theme_field_form>.g_record_detail_label { font-size: var(--grist-small-font-size); color: var(--grist-theme-card-form-label, var(--grist-color-slate)); font-weight: bold; @@ -248,16 +241,11 @@ margin-right: -8px; } -.detail_theme_field_form>.g_record_detail_description { - font-size: var(--grist-small-font-size); - color: var(--grist-theme-card-form-label, var(--grist-color-slate)); - font-weight: normal; - min-height: 0px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - margin-right: -8px; +.detail_theme_field_form .info_toggle_icon { + width: 13px; + height: 13px; } + /* TODO want to style better the values themselves (e.g. more padding, rounded corners, move label * inside value box for compact view for better cursor looks, etc), but first the cell editor * needs to learn to match the value box's style. Right now, the cell editor style is hard-coded. diff --git a/app/client/components/DetailView.js b/app/client/components/DetailView.js index ba0eaa58..b4d4d753 100644 --- a/app/client/components/DetailView.js +++ b/app/client/components/DetailView.js @@ -15,6 +15,7 @@ const RecordLayout = require('./RecordLayout'); const commands = require('./commands'); const {RowContextMenu} = require('../ui/RowContextMenu'); const {parsePasteForView} = require("./BaseView2"); +const {withInfoTooltip} = require('../ui/tooltips'); /** * DetailView component implements a list of record layouts. @@ -227,8 +228,11 @@ DetailView.prototype.buildFieldDom = function(field, row) { if (field.isNewField) { return dom('div.g_record_detail_el.flexitem', kd.cssClass(function() { return 'detail_theme_field_' + self.viewSection.themeDef(); }), - dom('div.g_record_detail_label', field.label), - dom('div.g_record_detail_description', field.description), + field.description.peek() ? + withInfoTooltip( + dom('div.g_record_detail_label', kd.text(field.displayLabel)), + dom('div.g_record_detail_description', kd.text(field.description)), + ) : dom('div.g_record_detail_label', kd.text(field.displayLabel)), dom('div.g_record_detail_value', field.value) ); } @@ -258,8 +262,11 @@ DetailView.prototype.buildFieldDom = function(field, row) { dom.autoDispose(isCellSelected), dom.autoDispose(isCellActive), kd.cssClass(function() { return 'detail_theme_field_' + self.viewSection.themeDef(); }), - dom('div.g_record_detail_label', kd.text(field.displayLabel)), - dom('div.g_record_detail_description', kd.text(field.description)), + field.description.peek() ? + withInfoTooltip( + dom('div.g_record_detail_label', kd.text(field.displayLabel)), + dom('div.g_record_detail_description', kd.text(field.description)), + ) : dom('div.g_record_detail_label', kd.text(field.displayLabel)), dom('div.g_record_detail_value', kd.toggleClass('scissors', isCopyActive), kd.toggleClass('record-add', row._isAddRow), diff --git a/app/client/components/DocumentUsage.ts b/app/client/components/DocumentUsage.ts index 243c6f64..4dc82616 100644 --- a/app/client/components/DocumentUsage.ts +++ b/app/client/components/DocumentUsage.ts @@ -2,7 +2,7 @@ import {DocPageModel} from 'app/client/models/DocPageModel'; import {urlState} from 'app/client/models/gristUrlState'; import {docListHeader} from 'app/client/ui/DocMenuCss'; import {GristTooltips, TooltipContentFunc} from 'app/client/ui/GristTooltips'; -import {withInfoTooltip} from 'app/client/ui/tooltips'; +import { withQuestionMarkTooltip } from 'app/client/ui/tooltips'; import {mediaXSmall, theme} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {loadingDots, loadingSpinner} from 'app/client/ui2018/loaders'; @@ -284,7 +284,7 @@ function buildUsageMetric(options: MetricOptions, ...domArgs: DomElementArg[]) { return cssUsageMetric( cssMetricName( tooltipContentFunc - ? withInfoTooltip( + ? withQuestionMarkTooltip( cssOverflowableText(name, testId('name')), tooltipContentFunc() ) diff --git a/app/client/ui/FieldConfig.ts b/app/client/ui/FieldConfig.ts index a35ee7cb..ac66ffd2 100644 --- a/app/client/ui/FieldConfig.ts +++ b/app/client/ui/FieldConfig.ts @@ -5,7 +5,7 @@ import {BEHAVIOR, ColumnRec} from 'app/client/models/entities/ColumnRec'; import {buildHighlightedCode, cssCodeBlock} from 'app/client/ui/CodeHighlight'; import {GristTooltips} from 'app/client/ui/GristTooltips'; import {cssBlockedCursor, cssLabel, cssRow} from 'app/client/ui/RightPanelStyles'; -import {withInfoTooltip} from 'app/client/ui/tooltips'; +import { withQuestionMarkTooltip } from 'app/client/ui/tooltips'; import {buildFormulaTriggers} from 'app/client/ui/TriggerFormulas'; import {textButton} from 'app/client/ui2018/buttons'; import {testId, theme} from 'app/client/ui2018/cssVars'; @@ -371,7 +371,7 @@ export function buildFormulaConfig( dom.prop("disabled", disableOtherActions), testId("field-set-formula") )), - cssRow(withInfoTooltip( + cssRow(withQuestionMarkTooltip( textButton( t("Set trigger formula"), dom.on("click", setTrigger), @@ -424,7 +424,7 @@ export function buildFormulaConfig( // Else offer a way to convert to trigger formula. dom.maybe((use) => !(use(maybeTrigger) || use(origColumn.hasTriggerFormula)), () => [ cssEmptySeparator(), - cssRow(withInfoTooltip( + cssRow(withQuestionMarkTooltip( textButton( t("Set trigger formula"), dom.on("click", convertDataColumnToTriggerColumn), diff --git a/app/client/ui/PageWidgetPicker.ts b/app/client/ui/PageWidgetPicker.ts index 8f4b278f..6d351fa6 100644 --- a/app/client/ui/PageWidgetPicker.ts +++ b/app/client/ui/PageWidgetPicker.ts @@ -5,7 +5,7 @@ import { reportError } from 'app/client/models/AppModel'; import { ColumnRec, TableRec, ViewSectionRec } from 'app/client/models/DocModel'; import { GristTooltips } from 'app/client/ui/GristTooltips'; import { linkId, NoLink } from 'app/client/ui/selectBy'; -import { withInfoTooltip } from 'app/client/ui/tooltips'; +import { withQuestionMarkTooltip } from 'app/client/ui/tooltips'; import { getWidgetTypes, IWidgetType } from 'app/client/ui/widgetTypes'; import { bigPrimaryButton } from "app/client/ui2018/buttons"; import { theme, vars } from "app/client/ui2018/cssVars"; @@ -358,7 +358,7 @@ export class PageWidgetSelect extends Disposable { cssFooterContent( // If _selectByOptions exists and has more than then "NoLinkOption", show the selector. dom.maybe((use) => this._selectByOptions && use(this._selectByOptions).length > 1, () => - withInfoTooltip( + withQuestionMarkTooltip( cssSelectBy( cssSmallLabel('SELECT BY'), dom.update(cssSelect(this._value.link, this._selectByOptions!), diff --git a/app/client/ui/ShareMenu.ts b/app/client/ui/ShareMenu.ts index 1c65bd9f..572e45e7 100644 --- a/app/client/ui/ShareMenu.ts +++ b/app/client/ui/ShareMenu.ts @@ -5,7 +5,7 @@ import {docUrl, urlState} from 'app/client/models/gristUrlState'; import {GristTooltips} from 'app/client/ui/GristTooltips'; import {makeCopy, replaceTrunkWithFork} from 'app/client/ui/MakeCopyMenu'; import {sendToDrive} from 'app/client/ui/sendToDrive'; -import {hoverTooltip, withInfoTooltip} from 'app/client/ui/tooltips'; +import { hoverTooltip, withQuestionMarkTooltip } from 'app/client/ui/tooltips'; import {cssHoverCircle, cssTopBarBtn} from 'app/client/ui/TopBarCss'; import {primaryButton} from 'app/client/ui2018/buttons'; import {mediaXSmall, testId, theme} from 'app/client/ui2018/cssVars'; @@ -207,7 +207,7 @@ function menuWorkOnCopy(pageModel: DocPageModel) { return [ menuItem(makeUnsavedCopy, t("Work on a Copy"), testId('work-on-copy')), menuText( - withInfoTooltip( + withQuestionMarkTooltip( t("Edit without affecting the original"), GristTooltips.workOnACopy(), {tooltipMenuOptions: {attach: null}} diff --git a/app/client/ui/UserManager.ts b/app/client/ui/UserManager.ts index 82c75ca2..98c5d862 100644 --- a/app/client/ui/UserManager.ts +++ b/app/client/ui/UserManager.ts @@ -28,7 +28,7 @@ import {UserManagerModel, UserManagerModelImpl} from 'app/client/models/UserMana import {getResourceParent, ResourceType} from 'app/client/models/UserManagerModel'; import {GristTooltips} from 'app/client/ui/GristTooltips'; import {shadowScroll} from 'app/client/ui/shadowScroll'; -import {hoverTooltip, ITooltipControl, showTransientTooltip, withInfoTooltip} from 'app/client/ui/tooltips'; +import { hoverTooltip, ITooltipControl, showTransientTooltip, withQuestionMarkTooltip } from 'app/client/ui/tooltips'; import {createUserImage} from 'app/client/ui/UserImage'; import {cssMemberBtn, cssMemberImage, cssMemberListItem, cssMemberPrimary, cssMemberSecondary, cssMemberText, cssMemberType, cssMemberTypeProblem, @@ -169,7 +169,7 @@ function buildUserManagerModal( testId('um-cancel') ), (model.resourceType === 'document' && model.gristDoc && !model.isPersonal - ? withInfoTooltip( + ? withQuestionMarkTooltip( cssLink({href: urlState().makeUrl({docPage: 'acl'})}, dom.text(use => use(model.isAnythingChanged) ? 'Save & ' : ''), 'Open Access Rules', diff --git a/app/client/ui/tooltips.ts b/app/client/ui/tooltips.ts index 68d26655..73866d42 100644 --- a/app/client/ui/tooltips.ts +++ b/app/client/ui/tooltips.ts @@ -9,7 +9,7 @@ import {prepareForTransition} from 'app/client/ui/transitions'; import {testId, theme, vars} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {menuCssClass} from 'app/client/ui2018/menus'; -import {dom, DomContents, DomElementArg, DomElementMethod, styled} from 'grainjs'; +import { dom, DomContents, DomElementArg, DomElementMethod, IDomArgs, styled } from 'grainjs'; import Popper from 'popper.js'; import {cssMenu, defaultMenuOptions, IMenuOptions, setPopupToCreateDom} from 'popweasel'; @@ -240,43 +240,59 @@ export function tooltipCloseButton(ctl: ITooltipControl): HTMLElement { } /** - * Renders an info icon that shows a tooltip with the specified `content` on click. + * Renders an icon that shows a tooltip with the specified `content` on click. */ -function infoTooltip(content: DomContents, menuOptions?: IMenuOptions, ...domArgs: DomElementArg[]) { - return cssInfoTooltipButton('?', - (elem) => { - setPopupToCreateDom( - elem, - (ctl) => { - return cssInfoTooltipPopup( - cssInfoTooltipPopupCloseButton( - icon('CrossSmall'), - dom.on('click', () => ctl.close()), - testId('info-tooltip-close'), - ), - cssInfoTooltipPopupBody( - content, - testId('info-tooltip-popup-body'), - ), - dom.cls(menuCssClass), - dom.cls(cssMenu.className), - dom.onKeyDown({ - Enter: () => ctl.close(), - Escape: () => ctl.close(), - }), - (popup) => { setTimeout(() => popup.focus(), 0); }, - testId('info-tooltip-popup'), - ); - }, - {...defaultMenuOptions, ...{placement: 'bottom-end'}, ...menuOptions}, - ); - }, - testId('info-tooltip'), - ...domArgs, - ); +function iconTooltip( + cssStyledFunc: (...args: IDomArgs) => HTMLElement, + tooltipButtonContent: HTMLElement, + content: DomContents, + menuOptions?: IMenuOptions, + ...domArgs: DomElementArg[] +) { + return cssStyledFunc(tooltipButtonContent, (elem) => { + setPopupToCreateDom( + elem, + (ctl) => { + return cssInfoTooltipPopup( + cssInfoTooltipPopupCloseButton( + icon("CrossSmall"), + dom.on("click", () => ctl.close()), + testId("info-tooltip-close") + ), + cssInfoTooltipPopupBody(content, testId("info-tooltip-popup-body")), + dom.cls(menuCssClass), + dom.cls(cssMenu.className), + dom.onKeyDown({ + Enter: () => ctl.close(), + Escape: () => ctl.close(), + }), + (popup) => { + setTimeout(() => popup.focus(), 0); + }, + testId("info-tooltip-popup") + ); + }, + { ...defaultMenuOptions, ...{ placement: "bottom-end" }, ...menuOptions } + ); + }); } -export interface WithInfoTooltipOptions { +function questionMarkTooltip(content: DomContents, menuOptions?: IMenuOptions, ...domArgs: DomElementArg[]) { + return iconTooltip( + cssQuestionMarkTooltipButton, + cssQuestionMark('?', testId('info-tooltip'), ...domArgs), + content, + menuOptions); +} + +function infoTooltip(content: DomContents, menuOptions?: IMenuOptions, ...domArgs: DomElementArg[]) { + return iconTooltip( + cssInfoTooltipButton, + icon('Info', dom.cls('info_toggle_icon'), testId('info-tooltip'), ...domArgs), + content, menuOptions); +} + +export interface WithIconTooltipOptions { domArgs?: DomElementArg[]; tooltipButtonDomArgs?: DomElementArg[]; tooltipMenuOptions?: IMenuOptions; @@ -296,17 +312,30 @@ export interface WithInfoTooltipOptions { * * Usage: * - * withInfoTooltip( + * withQuestionMarkTooltip( * dom('div', 'Hello World!'), * dom('p', 'This is some text to show inside the tooltip.'), * ) */ +export function withQuestionMarkTooltip( + domContents: DomContents, + tooltipContent: DomContents, + options: WithIconTooltipOptions = {}, +) { + const { domArgs, tooltipButtonDomArgs, tooltipMenuOptions } = options; + return cssDomWithTooltip( + domContents, + questionMarkTooltip(tooltipContent, tooltipMenuOptions, tooltipButtonDomArgs), + ...(domArgs ?? []) + ); +} + export function withInfoTooltip( domContents: DomContents, tooltipContent: DomContents, - options: WithInfoTooltipOptions = {}, + options: WithIconTooltipOptions = {}, ) { - const {domArgs, tooltipButtonDomArgs, tooltipMenuOptions} = options; + const { domArgs, tooltipButtonDomArgs, tooltipMenuOptions } = options; return cssDomWithTooltip( domContents, infoTooltip(tooltipContent, tooltipMenuOptions, tooltipButtonDomArgs), @@ -347,7 +376,7 @@ const cssTooltipCloseButton = styled('div', ` } `); -const cssInfoTooltipButton = styled('div', ` +const cssQuestionMark = styled('div', ` flex-shrink: 0; display: flex; align-items: center; @@ -366,6 +395,25 @@ const cssInfoTooltipButton = styled('div', ` } `); +const cssQuestionMarkTooltipButton = styled('div', ` + cursor: pointer; +`); + +const cssInfoTooltipButton = styled('div', ` + cursor: pointer; + --icon-color: ${theme.infoButtonFg}; + border-radius: 50%; + &:hover { + --icon-color: ${theme.infoButtonHoverFg}; + } + &:active { + --icon-color: ${theme.infoButtonActiveFg}; + } + & > .info_toggle_icon { + display: block; /* don't create a line */ + } +`); + const cssInfoTooltipPopup = styled('div', ` display: flex; flex-direction: column; diff --git a/app/client/ui2018/cssVars.ts b/app/client/ui2018/cssVars.ts index 6b84698c..e995f6c1 100644 --- a/app/client/ui2018/cssVars.ts +++ b/app/client/ui2018/cssVars.ts @@ -605,10 +605,10 @@ export const theme = { menuToggleBg: new CustomProp('theme-menu-toggle-bg', undefined, 'white'), menuToggleBorder: new CustomProp('theme-menu-toggle-border', undefined, colors.slate), - /* Info Toggles */ - infoToggleFg: new CustomProp('theme-info-toggle-fg', undefined, "#8F8F8F"), - infoToggleHoverFg: new CustomProp('theme-info-toggle-hover-fg', undefined, "#707070"), - infoToggleActiveFg: new CustomProp('theme-info-toggle-active-fg', undefined, "#5C5C5C"), + /* Info Button */ + infoButtonFg: new CustomProp('theme-info-button-fg', undefined, "#8F8F8F"), + infoButtonHoverFg: new CustomProp('theme-info-button-hover-fg', undefined, "#707070"), + infoButtonActiveFg: new CustomProp('theme-info-button-active-fg', undefined, "#5C5C5C"), /* Button Groups */ buttonGroupFg: new CustomProp('theme-button-group-fg', undefined, colors.dark), diff --git a/app/client/widgets/ConditionalStyle.ts b/app/client/widgets/ConditionalStyle.ts index 7ae0f87c..945b07d2 100644 --- a/app/client/widgets/ConditionalStyle.ts +++ b/app/client/widgets/ConditionalStyle.ts @@ -6,7 +6,7 @@ import {RuleOwner} from 'app/client/models/RuleOwner'; import {Style} from 'app/client/models/Styles'; import {cssFieldFormula} from 'app/client/ui/FieldConfig'; import {GristTooltips} from 'app/client/ui/GristTooltips'; -import {withInfoTooltip} from 'app/client/ui/tooltips'; +import { withQuestionMarkTooltip } from 'app/client/ui/tooltips'; import {textButton} from 'app/client/ui2018/buttons'; import {ColorOption, colorSelect} from 'app/client/ui2018/ColorSelect'; import {theme, vars} from 'app/client/ui2018/cssVars'; @@ -71,7 +71,7 @@ export class ConditionalStyle extends Disposable { return [ cssRow( { style: 'margin-top: 16px' }, - withInfoTooltip( + withQuestionMarkTooltip( textButton( t('Add conditional style'), testId('add-conditional-style'), From 51f6cf3070acf61b4b713c29e393ec2996ff245e Mon Sep 17 00:00:00 2001 From: Camille Date: Tue, 24 Jan 2023 14:30:14 +0100 Subject: [PATCH 06/18] feat(columnDesc): code styling --- app/common/schema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/common/schema.ts b/app/common/schema.ts index ea2cc587..c805d200 100644 --- a/app/common/schema.ts +++ b/app/common/schema.ts @@ -34,7 +34,7 @@ export const schema = { isFormula : "Bool", formula : "Text", label : "Text", - description: "Text", + description : "Text", untieColIdFromLabel : "Bool", summarySourceCol : "Ref:_grist_Tables_column", displayCol : "Ref:_grist_Tables_column", From 5ca0b935766d220b447227e0bd7fa4d043301dd2 Mon Sep 17 00:00:00 2001 From: Camille Date: Tue, 24 Jan 2023 15:27:51 +0100 Subject: [PATCH 07/18] test(columnDesc): add description to column field in DocApi server test --- test/server/lib/DocApi.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/server/lib/DocApi.ts b/test/server/lib/DocApi.ts index 0d4bcd26..796701c1 100644 --- a/test/server/lib/DocApi.ts +++ b/test/server/lib/DocApi.ts @@ -496,6 +496,7 @@ function testDocApi() { isFormula: false, formula: '', label: 'A', + description: '', untieColIdFromLabel: false, summarySourceCol: 0, displayCol: 0, @@ -516,6 +517,7 @@ function testDocApi() { isFormula: false, formula: '', label: 'B', + description: '', untieColIdFromLabel: false, summarySourceCol: 0, displayCol: 0, @@ -536,6 +538,7 @@ function testDocApi() { isFormula: false, formula: '', label: 'C', + description: '', untieColIdFromLabel: false, summarySourceCol: 0, displayCol: 0, @@ -556,6 +559,7 @@ function testDocApi() { isFormula: true, formula: '', label: 'D', + description: '', untieColIdFromLabel: false, summarySourceCol: 0, displayCol: 0, @@ -576,6 +580,7 @@ function testDocApi() { isFormula: true, formula: '$A.upper()', label: 'E', + description: '', untieColIdFromLabel: false, summarySourceCol: 0, displayCol: 0, From 5afb2e2feee861d1957aece71853a7aba8189563 Mon Sep 17 00:00:00 2001 From: Camille Date: Thu, 26 Jan 2023 15:54:02 +0100 Subject: [PATCH 08/18] fix(columnDesc): improve styling --- app/client/ui/FieldConfig.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/app/client/ui/FieldConfig.ts b/app/client/ui/FieldConfig.ts index ac66ffd2..03f21b44 100644 --- a/app/client/ui/FieldConfig.ts +++ b/app/client/ui/FieldConfig.ts @@ -8,7 +8,7 @@ import {cssBlockedCursor, cssLabel, cssRow} from 'app/client/ui/RightPanelStyles import { withQuestionMarkTooltip } from 'app/client/ui/tooltips'; import {buildFormulaTriggers} from 'app/client/ui/TriggerFormulas'; import {textButton} from 'app/client/ui2018/buttons'; -import {testId, theme} from 'app/client/ui2018/cssVars'; +import { testId, theme, vars } from 'app/client/ui2018/cssVars'; import {textInput} from 'app/client/ui2018/editableLabel'; import {cssIconButton, icon} from 'app/client/ui2018/icons'; import {IconName} from 'app/client/ui2018/IconList'; @@ -113,8 +113,8 @@ export function buildDescriptionConfig( cssRow( editor = cssTextArea(fromKo(origColumn.description), { onInput: false }, - { placeholder: t("If necesary, describe the column") }, - dom.on('input', async (e, elem) => { + { rows: '3' }, + dom.on('blur', async (e, elem) => { editedDescription.set(elem.value); await origColumn.description.saveOnly(elem.value); editedDescription.set(''); @@ -544,6 +544,15 @@ const cssTextArea = styled(textarea, ` &[readonly] { background-color: ${theme.inputDisabledBg}; - color: ${theme.inputDisabledFg}; + padding: 3px 7px; + border-radius: 3px; + resize: vertical; + border: 1px solid ${theme.inputBorder}; + color: ${theme.inputFg}; + background-color: ${theme.inputBg}; + flex: 1 1 0; + font-size: ${vars.mediumFontSize}; + font-family: ${vars.fontFamily}; + outline: none; } `); From 64a0a2be58a64a003907e95bdd748ed0fb36fd35 Mon Sep 17 00:00:00 2001 From: Camille Date: Thu, 26 Jan 2023 15:57:48 +0100 Subject: [PATCH 09/18] translation(columnDesc): generate translation --- static/locales/en.client.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/static/locales/en.client.json b/static/locales/en.client.json index ce14ca56..da88b5b5 100644 --- a/static/locales/en.client.json +++ b/static/locales/en.client.json @@ -325,7 +325,8 @@ "Mixed Behavior": "Mixed Behavior", "Set formula": "Set formula", "Set trigger formula": "Set trigger formula", - "TRIGGER FORMULA": "TRIGGER FORMULA" + "TRIGGER FORMULA": "TRIGGER FORMULA", + "DESCRIPTION": "DESCRIPTION" }, "FieldMenus": { "Revert to common settings": "Revert to common settings", From bd9c9aa8f93494856a91245078dfd94f1b2bf56d Mon Sep 17 00:00:00 2001 From: Camille Date: Wed, 1 Feb 2023 13:55:41 +0100 Subject: [PATCH 10/18] codeStyle(column-desc): create separate iconTooltip for column description --- app/client/components/DetailView.css | 1 + app/client/components/DetailView.js | 21 ++- app/client/components/DocumentUsage.ts | 4 +- app/client/ui/FieldConfig.ts | 6 +- app/client/ui/PageWidgetPicker.ts | 4 +- app/client/ui/ShareMenu.ts | 4 +- app/client/ui/UserManager.ts | 4 +- app/client/ui/tooltips.ts | 179 +++++++++++++------------ app/client/widgets/ConditionalStyle.ts | 4 +- 9 files changed, 115 insertions(+), 112 deletions(-) diff --git a/app/client/components/DetailView.css b/app/client/components/DetailView.css index cf7d026e..4e30d6d9 100644 --- a/app/client/components/DetailView.css +++ b/app/client/components/DetailView.css @@ -244,6 +244,7 @@ .detail_theme_field_form .info_toggle_icon { width: 13px; height: 13px; + margin-bottom: 3px; } /* TODO want to style better the values themselves (e.g. more padding, rounded corners, move label diff --git a/app/client/components/DetailView.js b/app/client/components/DetailView.js index b4d4d753..4b48426a 100644 --- a/app/client/components/DetailView.js +++ b/app/client/components/DetailView.js @@ -15,7 +15,7 @@ const RecordLayout = require('./RecordLayout'); const commands = require('./commands'); const {RowContextMenu} = require('../ui/RowContextMenu'); const {parsePasteForView} = require("./BaseView2"); -const {withInfoTooltip} = require('../ui/tooltips'); +const {columnInfoTooltip} = require("../ui/tooltips"); /** * DetailView component implements a list of record layouts. @@ -228,12 +228,10 @@ DetailView.prototype.buildFieldDom = function(field, row) { if (field.isNewField) { return dom('div.g_record_detail_el.flexitem', kd.cssClass(function() { return 'detail_theme_field_' + self.viewSection.themeDef(); }), - field.description.peek() ? - withInfoTooltip( - dom('div.g_record_detail_label', kd.text(field.displayLabel)), - dom('div.g_record_detail_description', kd.text(field.description)), - ) : dom('div.g_record_detail_label', kd.text(field.displayLabel)), - dom('div.g_record_detail_value', field.value) + dom('div.g_record_detail_label', + kd.text(field.displayLabel), + kd.scope(field.description, desc => desc ? columnInfoTooltip(kd.text(field.description)) : null) + ), ); } @@ -262,11 +260,10 @@ DetailView.prototype.buildFieldDom = function(field, row) { dom.autoDispose(isCellSelected), dom.autoDispose(isCellActive), kd.cssClass(function() { return 'detail_theme_field_' + self.viewSection.themeDef(); }), - field.description.peek() ? - withInfoTooltip( - dom('div.g_record_detail_label', kd.text(field.displayLabel)), - dom('div.g_record_detail_description', kd.text(field.description)), - ) : dom('div.g_record_detail_label', kd.text(field.displayLabel)), + dom('div.g_record_detail_label', + kd.text(field.displayLabel), + kd.scope(field.description, desc => desc ? columnInfoTooltip(kd.text(field.description)) : null) + ), dom('div.g_record_detail_value', kd.toggleClass('scissors', isCopyActive), kd.toggleClass('record-add', row._isAddRow), diff --git a/app/client/components/DocumentUsage.ts b/app/client/components/DocumentUsage.ts index 4dc82616..243c6f64 100644 --- a/app/client/components/DocumentUsage.ts +++ b/app/client/components/DocumentUsage.ts @@ -2,7 +2,7 @@ import {DocPageModel} from 'app/client/models/DocPageModel'; import {urlState} from 'app/client/models/gristUrlState'; import {docListHeader} from 'app/client/ui/DocMenuCss'; import {GristTooltips, TooltipContentFunc} from 'app/client/ui/GristTooltips'; -import { withQuestionMarkTooltip } from 'app/client/ui/tooltips'; +import {withInfoTooltip} from 'app/client/ui/tooltips'; import {mediaXSmall, theme} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {loadingDots, loadingSpinner} from 'app/client/ui2018/loaders'; @@ -284,7 +284,7 @@ function buildUsageMetric(options: MetricOptions, ...domArgs: DomElementArg[]) { return cssUsageMetric( cssMetricName( tooltipContentFunc - ? withQuestionMarkTooltip( + ? withInfoTooltip( cssOverflowableText(name, testId('name')), tooltipContentFunc() ) diff --git a/app/client/ui/FieldConfig.ts b/app/client/ui/FieldConfig.ts index 03f21b44..d2cf2755 100644 --- a/app/client/ui/FieldConfig.ts +++ b/app/client/ui/FieldConfig.ts @@ -5,7 +5,7 @@ import {BEHAVIOR, ColumnRec} from 'app/client/models/entities/ColumnRec'; import {buildHighlightedCode, cssCodeBlock} from 'app/client/ui/CodeHighlight'; import {GristTooltips} from 'app/client/ui/GristTooltips'; import {cssBlockedCursor, cssLabel, cssRow} from 'app/client/ui/RightPanelStyles'; -import { withQuestionMarkTooltip } from 'app/client/ui/tooltips'; +import { withInfoTooltip } from 'app/client/ui/tooltips'; import {buildFormulaTriggers} from 'app/client/ui/TriggerFormulas'; import {textButton} from 'app/client/ui2018/buttons'; import { testId, theme, vars } from 'app/client/ui2018/cssVars'; @@ -371,7 +371,7 @@ export function buildFormulaConfig( dom.prop("disabled", disableOtherActions), testId("field-set-formula") )), - cssRow(withQuestionMarkTooltip( + cssRow(withInfoTooltip( textButton( t("Set trigger formula"), dom.on("click", setTrigger), @@ -424,7 +424,7 @@ export function buildFormulaConfig( // Else offer a way to convert to trigger formula. dom.maybe((use) => !(use(maybeTrigger) || use(origColumn.hasTriggerFormula)), () => [ cssEmptySeparator(), - cssRow(withQuestionMarkTooltip( + cssRow(withInfoTooltip( textButton( t("Set trigger formula"), dom.on("click", convertDataColumnToTriggerColumn), diff --git a/app/client/ui/PageWidgetPicker.ts b/app/client/ui/PageWidgetPicker.ts index 6d351fa6..8f4b278f 100644 --- a/app/client/ui/PageWidgetPicker.ts +++ b/app/client/ui/PageWidgetPicker.ts @@ -5,7 +5,7 @@ import { reportError } from 'app/client/models/AppModel'; import { ColumnRec, TableRec, ViewSectionRec } from 'app/client/models/DocModel'; import { GristTooltips } from 'app/client/ui/GristTooltips'; import { linkId, NoLink } from 'app/client/ui/selectBy'; -import { withQuestionMarkTooltip } from 'app/client/ui/tooltips'; +import { withInfoTooltip } from 'app/client/ui/tooltips'; import { getWidgetTypes, IWidgetType } from 'app/client/ui/widgetTypes'; import { bigPrimaryButton } from "app/client/ui2018/buttons"; import { theme, vars } from "app/client/ui2018/cssVars"; @@ -358,7 +358,7 @@ export class PageWidgetSelect extends Disposable { cssFooterContent( // If _selectByOptions exists and has more than then "NoLinkOption", show the selector. dom.maybe((use) => this._selectByOptions && use(this._selectByOptions).length > 1, () => - withQuestionMarkTooltip( + withInfoTooltip( cssSelectBy( cssSmallLabel('SELECT BY'), dom.update(cssSelect(this._value.link, this._selectByOptions!), diff --git a/app/client/ui/ShareMenu.ts b/app/client/ui/ShareMenu.ts index 572e45e7..1c65bd9f 100644 --- a/app/client/ui/ShareMenu.ts +++ b/app/client/ui/ShareMenu.ts @@ -5,7 +5,7 @@ import {docUrl, urlState} from 'app/client/models/gristUrlState'; import {GristTooltips} from 'app/client/ui/GristTooltips'; import {makeCopy, replaceTrunkWithFork} from 'app/client/ui/MakeCopyMenu'; import {sendToDrive} from 'app/client/ui/sendToDrive'; -import { hoverTooltip, withQuestionMarkTooltip } from 'app/client/ui/tooltips'; +import {hoverTooltip, withInfoTooltip} from 'app/client/ui/tooltips'; import {cssHoverCircle, cssTopBarBtn} from 'app/client/ui/TopBarCss'; import {primaryButton} from 'app/client/ui2018/buttons'; import {mediaXSmall, testId, theme} from 'app/client/ui2018/cssVars'; @@ -207,7 +207,7 @@ function menuWorkOnCopy(pageModel: DocPageModel) { return [ menuItem(makeUnsavedCopy, t("Work on a Copy"), testId('work-on-copy')), menuText( - withQuestionMarkTooltip( + withInfoTooltip( t("Edit without affecting the original"), GristTooltips.workOnACopy(), {tooltipMenuOptions: {attach: null}} diff --git a/app/client/ui/UserManager.ts b/app/client/ui/UserManager.ts index e8bffb4a..5b12fb1f 100644 --- a/app/client/ui/UserManager.ts +++ b/app/client/ui/UserManager.ts @@ -28,7 +28,7 @@ import {UserManagerModel, UserManagerModelImpl} from 'app/client/models/UserMana import {getResourceParent, ResourceType} from 'app/client/models/UserManagerModel'; import {GristTooltips} from 'app/client/ui/GristTooltips'; import {shadowScroll} from 'app/client/ui/shadowScroll'; -import { hoverTooltip, ITooltipControl, showTransientTooltip, withQuestionMarkTooltip } from 'app/client/ui/tooltips'; +import {hoverTooltip, ITooltipControl, showTransientTooltip, withInfoTooltip} from 'app/client/ui/tooltips'; import {createUserImage} from 'app/client/ui/UserImage'; import {cssMemberBtn, cssMemberImage, cssMemberListItem, cssMemberPrimary, cssMemberSecondary, cssMemberText, cssMemberType, cssMemberTypeProblem, @@ -169,7 +169,7 @@ function buildUserManagerModal( testId('um-cancel') ), (model.resourceType === 'document' && model.gristDoc && !model.isPersonal - ? withQuestionMarkTooltip( + ? withInfoTooltip( cssLink({href: urlState().makeUrl({docPage: 'acl'})}, dom.text(use => use(model.isAnythingChanged) ? 'Save & ' : ''), 'Open Access Rules', diff --git a/app/client/ui/tooltips.ts b/app/client/ui/tooltips.ts index 73866d42..12ca62c8 100644 --- a/app/client/ui/tooltips.ts +++ b/app/client/ui/tooltips.ts @@ -9,7 +9,7 @@ import {prepareForTransition} from 'app/client/ui/transitions'; import {testId, theme, vars} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {menuCssClass} from 'app/client/ui2018/menus'; -import { dom, DomContents, DomElementArg, DomElementMethod, IDomArgs, styled } from 'grainjs'; +import {dom, DomContents, DomElementArg, DomElementMethod, styled} from 'grainjs'; import Popper from 'popper.js'; import {cssMenu, defaultMenuOptions, IMenuOptions, setPopupToCreateDom} from 'popweasel'; @@ -240,59 +240,43 @@ export function tooltipCloseButton(ctl: ITooltipControl): HTMLElement { } /** - * Renders an icon that shows a tooltip with the specified `content` on click. + * Renders an info icon that shows a tooltip with the specified `content` on click. */ -function iconTooltip( - cssStyledFunc: (...args: IDomArgs) => HTMLElement, - tooltipButtonContent: HTMLElement, - content: DomContents, - menuOptions?: IMenuOptions, - ...domArgs: DomElementArg[] -) { - return cssStyledFunc(tooltipButtonContent, (elem) => { - setPopupToCreateDom( - elem, - (ctl) => { - return cssInfoTooltipPopup( - cssInfoTooltipPopupCloseButton( - icon("CrossSmall"), - dom.on("click", () => ctl.close()), - testId("info-tooltip-close") - ), - cssInfoTooltipPopupBody(content, testId("info-tooltip-popup-body")), - dom.cls(menuCssClass), - dom.cls(cssMenu.className), - dom.onKeyDown({ - Enter: () => ctl.close(), - Escape: () => ctl.close(), - }), - (popup) => { - setTimeout(() => popup.focus(), 0); - }, - testId("info-tooltip-popup") - ); - }, - { ...defaultMenuOptions, ...{ placement: "bottom-end" }, ...menuOptions } - ); - }); +export function infoTooltip(content: DomContents, menuOptions?: IMenuOptions, ...domArgs: DomElementArg[]) { + return cssInfoTooltipButton('?', + (elem) => { + setPopupToCreateDom( + elem, + (ctl) => { + return cssInfoTooltipPopup( + cssInfoTooltipPopupCloseButton( + icon('CrossSmall'), + dom.on('click', () => ctl.close()), + testId('info-tooltip-close'), + ), + cssInfoTooltipPopupBody( + content, + testId('info-tooltip-popup-body'), + ), + dom.cls(menuCssClass), + dom.cls(cssMenu.className), + dom.onKeyDown({ + Enter: () => ctl.close(), + Escape: () => ctl.close(), + }), + (popup) => { setTimeout(() => popup.focus(), 0); }, + testId('info-tooltip-popup'), + ); + }, + {...defaultMenuOptions, ...{placement: 'bottom-end'}, ...menuOptions}, + ); + }, + testId('info-tooltip'), + ...domArgs, + ); } -function questionMarkTooltip(content: DomContents, menuOptions?: IMenuOptions, ...domArgs: DomElementArg[]) { - return iconTooltip( - cssQuestionMarkTooltipButton, - cssQuestionMark('?', testId('info-tooltip'), ...domArgs), - content, - menuOptions); -} - -function infoTooltip(content: DomContents, menuOptions?: IMenuOptions, ...domArgs: DomElementArg[]) { - return iconTooltip( - cssInfoTooltipButton, - icon('Info', dom.cls('info_toggle_icon'), testId('info-tooltip'), ...domArgs), - content, menuOptions); -} - -export interface WithIconTooltipOptions { +export interface WithInfoTooltipOptions { domArgs?: DomElementArg[]; tooltipButtonDomArgs?: DomElementArg[]; tooltipMenuOptions?: IMenuOptions; @@ -312,30 +296,17 @@ export interface WithIconTooltipOptions { * * Usage: * - * withQuestionMarkTooltip( + * withInfoTooltip( * dom('div', 'Hello World!'), * dom('p', 'This is some text to show inside the tooltip.'), * ) */ -export function withQuestionMarkTooltip( - domContents: DomContents, - tooltipContent: DomContents, - options: WithIconTooltipOptions = {}, -) { - const { domArgs, tooltipButtonDomArgs, tooltipMenuOptions } = options; - return cssDomWithTooltip( - domContents, - questionMarkTooltip(tooltipContent, tooltipMenuOptions, tooltipButtonDomArgs), - ...(domArgs ?? []) - ); -} - export function withInfoTooltip( domContents: DomContents, tooltipContent: DomContents, - options: WithIconTooltipOptions = {}, + options: WithInfoTooltipOptions = {}, ) { - const { domArgs, tooltipButtonDomArgs, tooltipMenuOptions } = options; + const {domArgs, tooltipButtonDomArgs, tooltipMenuOptions} = options; return cssDomWithTooltip( domContents, infoTooltip(tooltipContent, tooltipMenuOptions, tooltipButtonDomArgs), @@ -343,6 +314,59 @@ export function withInfoTooltip( ); } +/** + * Renders an column info icon that shows a tooltip with the specified `content` on click. + */ + export function columnInfoTooltip(content: DomContents, menuOptions?: IMenuOptions, ...domArgs: DomElementArg[]) { + return cssColumnInfoTooltipButton( + icon('Info', dom.cls("info_toggle_icon")), + (elem) => { + setPopupToCreateDom( + elem, + (ctl) => { + return cssInfoTooltipPopup( + cssInfoTooltipPopupCloseButton( + icon('CrossSmall'), + dom.on('click', () => ctl.close()), + testId('column-info-tooltip-close'), + ), + cssInfoTooltipPopupBody( + content, + testId('column-info-tooltip-popup-body'), + ), + dom.cls(menuCssClass), + dom.cls(cssMenu.className), + dom.onKeyDown({ + Enter: () => ctl.close(), + Escape: () => ctl.close(), + }), + (popup) => { setTimeout(() => popup.focus(), 0); }, + testId('column-info-tooltip-popup'), + ); + }, + { ...defaultMenuOptions, ...{ placement: 'bottom-end' }, ...menuOptions }, + ); + }, + testId('column-info-tooltip'), + ...domArgs, + ); +} + +const cssColumnInfoTooltipButton = styled('div', ` + cursor: pointer; + --icon-color: ${theme.infoButtonFg}; + border-radius: 50%; + display: inline-block; + margin-left: 5px; + &:hover { + --icon-color: ${theme.infoButtonHoverFg}; + } + &:active { + --icon-color: ${theme.infoButtonActiveFg}; + } +`); + + const cssTooltip = styled('div', ` position: absolute; z-index: 5000; /* should be higher than a modal */ @@ -376,7 +400,7 @@ const cssTooltipCloseButton = styled('div', ` } `); -const cssQuestionMark = styled('div', ` +const cssInfoTooltipButton = styled('div', ` flex-shrink: 0; display: flex; align-items: center; @@ -395,25 +419,6 @@ const cssQuestionMark = styled('div', ` } `); -const cssQuestionMarkTooltipButton = styled('div', ` - cursor: pointer; -`); - -const cssInfoTooltipButton = styled('div', ` - cursor: pointer; - --icon-color: ${theme.infoButtonFg}; - border-radius: 50%; - &:hover { - --icon-color: ${theme.infoButtonHoverFg}; - } - &:active { - --icon-color: ${theme.infoButtonActiveFg}; - } - & > .info_toggle_icon { - display: block; /* don't create a line */ - } -`); - const cssInfoTooltipPopup = styled('div', ` display: flex; flex-direction: column; diff --git a/app/client/widgets/ConditionalStyle.ts b/app/client/widgets/ConditionalStyle.ts index 945b07d2..7ae0f87c 100644 --- a/app/client/widgets/ConditionalStyle.ts +++ b/app/client/widgets/ConditionalStyle.ts @@ -6,7 +6,7 @@ import {RuleOwner} from 'app/client/models/RuleOwner'; import {Style} from 'app/client/models/Styles'; import {cssFieldFormula} from 'app/client/ui/FieldConfig'; import {GristTooltips} from 'app/client/ui/GristTooltips'; -import { withQuestionMarkTooltip } from 'app/client/ui/tooltips'; +import {withInfoTooltip} from 'app/client/ui/tooltips'; import {textButton} from 'app/client/ui2018/buttons'; import {ColorOption, colorSelect} from 'app/client/ui2018/ColorSelect'; import {theme, vars} from 'app/client/ui2018/cssVars'; @@ -71,7 +71,7 @@ export class ConditionalStyle extends Disposable { return [ cssRow( { style: 'margin-top: 16px' }, - withQuestionMarkTooltip( + withInfoTooltip( textButton( t('Add conditional style'), testId('add-conditional-style'), From e46b93d57f7c4de22f2ff4f2c2a129a8b0206eed Mon Sep 17 00:00:00 2001 From: Camille Date: Mon, 6 Feb 2023 18:19:16 +0100 Subject: [PATCH 11/18] test(columnDesc): create column description simple test --- app/client/ui/FieldConfig.ts | 1 + test/nbrowser/DescriptionColumn.ts | 43 ++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 test/nbrowser/DescriptionColumn.ts diff --git a/app/client/ui/FieldConfig.ts b/app/client/ui/FieldConfig.ts index d2cf2755..21639a7b 100644 --- a/app/client/ui/FieldConfig.ts +++ b/app/client/ui/FieldConfig.ts @@ -119,6 +119,7 @@ export function buildDescriptionConfig( await origColumn.description.saveOnly(elem.value); editedDescription.set(''); }), + testId('column-description'), ) ), diff --git a/test/nbrowser/DescriptionColumn.ts b/test/nbrowser/DescriptionColumn.ts new file mode 100644 index 00000000..2cc9bc42 --- /dev/null +++ b/test/nbrowser/DescriptionColumn.ts @@ -0,0 +1,43 @@ +import { assert, driver } from 'mocha-webdriver'; +import * as gu from 'test/nbrowser/gristUtils'; +import { setupTestSuite } from 'test/nbrowser/testUtils'; + +function getDescriptionInput() { + return driver.find('.test-right-panel .test-column-description') +} + +describe('DescriptionColumn', function() { + this.timeout(20000); + const cleanup = setupTestSuite(); + + it('should support basic edition', async () => { + + const mainSession = await gu.session().teamSite.login(); + const api = mainSession.createHomeApi(); + const docId = await mainSession.tempNewDoc(cleanup, 'FormulaCounts', { load: true }); + + // Make a column and add a description + await api.applyUserActions(docId, [ + [ 'ModifyColumn', 'Table1', 'C', { + type: 'Text', + description: 'This is the column description \nI am in two lines' + } ], + ]); + await driver.find('.test-right-opener').click(); + await gu.getCell({ rowNum: 1, col: 'C' }).click(); + await driver.find('.test-right-tab-field').click(); + + assert.equal(await getDescriptionInput().value(), 'This is the column description \nI am in two lines'); + + await getDescriptionInput().click() + + // Remove the description + await api.applyUserActions(docId, [ + [ 'ModifyColumn', 'Table1', 'C', { + description: '' + } ], + ]); + + assert.equal(await getDescriptionInput().value(), ''); + }) +}) From 4a239ad93b6be85f508dfa0c3b98e542fed689f4 Mon Sep 17 00:00:00 2001 From: Camille Date: Mon, 6 Feb 2023 18:24:35 +0100 Subject: [PATCH 12/18] test(columnDesc): add semi column --- test/nbrowser/DescriptionColumn.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test/nbrowser/DescriptionColumn.ts b/test/nbrowser/DescriptionColumn.ts index 2cc9bc42..2bc3c803 100644 --- a/test/nbrowser/DescriptionColumn.ts +++ b/test/nbrowser/DescriptionColumn.ts @@ -3,7 +3,7 @@ import * as gu from 'test/nbrowser/gristUtils'; import { setupTestSuite } from 'test/nbrowser/testUtils'; function getDescriptionInput() { - return driver.find('.test-right-panel .test-column-description') + return driver.find('.test-right-panel .test-column-description'); } describe('DescriptionColumn', function() { @@ -29,8 +29,6 @@ describe('DescriptionColumn', function() { assert.equal(await getDescriptionInput().value(), 'This is the column description \nI am in two lines'); - await getDescriptionInput().click() - // Remove the description await api.applyUserActions(docId, [ [ 'ModifyColumn', 'Table1', 'C', { @@ -39,5 +37,5 @@ describe('DescriptionColumn', function() { ]); assert.equal(await getDescriptionInput().value(), ''); - }) -}) + }); +}); From 7abecd47835ac4d493620310dcb5a572c477fba0 Mon Sep 17 00:00:00 2001 From: Camille Date: Fri, 10 Feb 2023 12:41:36 +0100 Subject: [PATCH 13/18] test(ColmunDesc): add Card column description tooltip test --- test/nbrowser/DescriptionColumn.ts | 66 ++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/test/nbrowser/DescriptionColumn.ts b/test/nbrowser/DescriptionColumn.ts index 2bc3c803..e7ae3f0b 100644 --- a/test/nbrowser/DescriptionColumn.ts +++ b/test/nbrowser/DescriptionColumn.ts @@ -1,7 +1,16 @@ +import { UserAPIImpl } from 'app/common/UserAPI'; import { assert, driver } from 'mocha-webdriver'; import * as gu from 'test/nbrowser/gristUtils'; import { setupTestSuite } from 'test/nbrowser/testUtils'; +async function addColumnDescription(api: UserAPIImpl, docId: string, columnName: string) { + await api.applyUserActions(docId, [ + [ 'ModifyColumn', 'Table1', columnName, { + description: 'This is the column description' + } ], + ]); +} + function getDescriptionInput() { return driver.find('.test-right-panel .test-column-description'); } @@ -11,31 +20,62 @@ describe('DescriptionColumn', function() { const cleanup = setupTestSuite(); it('should support basic edition', async () => { - const mainSession = await gu.session().teamSite.login(); const api = mainSession.createHomeApi(); - const docId = await mainSession.tempNewDoc(cleanup, 'FormulaCounts', { load: true }); + const doc = await mainSession.tempDoc(cleanup, "CardView.grist", { load: true }); + const docId = doc.id; - // Make a column and add a description - await api.applyUserActions(docId, [ - [ 'ModifyColumn', 'Table1', 'C', { - type: 'Text', - description: 'This is the column description \nI am in two lines' - } ], - ]); + addColumnDescription(api, docId, 'B'); + + // Column description editable in right panel await driver.find('.test-right-opener').click(); - await gu.getCell({ rowNum: 1, col: 'C' }).click(); - await driver.find('.test-right-tab-field').click(); - assert.equal(await getDescriptionInput().value(), 'This is the column description \nI am in two lines'); + await gu.getCell({ rowNum: 1, col: 'B' }).click(); + await driver.find('.test-right-tab-field').click(); + assert.equal(await getDescriptionInput().value(), 'This is the column description'); + + await gu.getCell({ rowNum: 1, col: 'A' }).click(); + assert.equal(await getDescriptionInput().value(), ''); // Remove the description await api.applyUserActions(docId, [ - [ 'ModifyColumn', 'Table1', 'C', { + [ 'ModifyColumn', 'Table1', 'B', { description: '' } ], ]); + await gu.getCell({ rowNum: 1, col: 'B' }).click(); assert.equal(await getDescriptionInput().value(), ''); }); + + it('should show info tooltip only if there is a description', async () => { + const mainSession = await gu.session().teamSite.login(); + const api = mainSession.createHomeApi(); + const doc = await mainSession.tempDoc(cleanup, "CardView.grist", { load: true }); + const docId = doc.id; + + addColumnDescription(api, docId, 'B'); + + await gu.changeWidget('Card'); + + const detailUndescribedColumnFirstRow = await gu.getDetailCell('A', 1); + assert.isFalse(await detailUndescribedColumnFirstRow.findClosest(".g_record_detail_el").find(".test-column-info-tooltip").isPresent()); + + const detailDescribedColumnFirstRow = await gu.getDetailCell('B', 1); + const toggle = await detailDescribedColumnFirstRow.findClosest(".g_record_detail_el").find(".test-column-info-tooltip"); + // The toggle to show the description is present if there is a description + assert.isTrue(await toggle.isPresent()); + + // Open the tooltip + await toggle.click(); + assert.isTrue(await driver.findWait('.test-column-info-tooltip-popup', 1000).isDisplayed()); + + // Check the content of the tooltip + const descriptionTooltip = await driver.find('.test-column-info-tooltip-popup .test-column-info-tooltip-popup-body'); + assert.equal(await descriptionTooltip.getText(), 'This is the column description'); + + // Close the tooltip + await toggle.click(); + assert.lengthOf(await driver.findAll('.test-column-info-tooltip-popup'), 0); + }) }); From 4e8e087abf99449943888b8487e0d5925d28e9e7 Mon Sep 17 00:00:00 2001 From: Camille Date: Mon, 13 Feb 2023 09:24:12 +0100 Subject: [PATCH 14/18] fix(columnDesc): await async function --- test/nbrowser/DescriptionColumn.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/nbrowser/DescriptionColumn.ts b/test/nbrowser/DescriptionColumn.ts index e7ae3f0b..27d9cb36 100644 --- a/test/nbrowser/DescriptionColumn.ts +++ b/test/nbrowser/DescriptionColumn.ts @@ -25,7 +25,7 @@ describe('DescriptionColumn', function() { const doc = await mainSession.tempDoc(cleanup, "CardView.grist", { load: true }); const docId = doc.id; - addColumnDescription(api, docId, 'B'); + await addColumnDescription(api, docId, 'B'); // Column description editable in right panel await driver.find('.test-right-opener').click(); @@ -54,7 +54,7 @@ describe('DescriptionColumn', function() { const doc = await mainSession.tempDoc(cleanup, "CardView.grist", { load: true }); const docId = doc.id; - addColumnDescription(api, docId, 'B'); + await addColumnDescription(api, docId, 'B'); await gu.changeWidget('Card'); From 1641c31c067f0fde62381d23b180eb138b5c7394 Mon Sep 17 00:00:00 2001 From: Camille Date: Mon, 13 Feb 2023 09:34:50 +0100 Subject: [PATCH 15/18] style(columnDesc): add semi column and cut too long lines --- test/nbrowser/DescriptionColumn.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/test/nbrowser/DescriptionColumn.ts b/test/nbrowser/DescriptionColumn.ts index 27d9cb36..29f6ca95 100644 --- a/test/nbrowser/DescriptionColumn.ts +++ b/test/nbrowser/DescriptionColumn.ts @@ -59,10 +59,17 @@ describe('DescriptionColumn', function() { await gu.changeWidget('Card'); const detailUndescribedColumnFirstRow = await gu.getDetailCell('A', 1); - assert.isFalse(await detailUndescribedColumnFirstRow.findClosest(".g_record_detail_el").find(".test-column-info-tooltip").isPresent()); + assert.isFalse( + await detailUndescribedColumnFirstRow + .findClosest(".g_record_detail_el") + .find(".test-column-info-tooltip") + .isPresent() + ); const detailDescribedColumnFirstRow = await gu.getDetailCell('B', 1); - const toggle = await detailDescribedColumnFirstRow.findClosest(".g_record_detail_el").find(".test-column-info-tooltip"); + const toggle = await detailDescribedColumnFirstRow + .findClosest(".g_record_detail_el") + .find(".test-column-info-tooltip"); // The toggle to show the description is present if there is a description assert.isTrue(await toggle.isPresent()); @@ -71,11 +78,12 @@ describe('DescriptionColumn', function() { assert.isTrue(await driver.findWait('.test-column-info-tooltip-popup', 1000).isDisplayed()); // Check the content of the tooltip - const descriptionTooltip = await driver.find('.test-column-info-tooltip-popup .test-column-info-tooltip-popup-body'); + const descriptionTooltip = await driver + .find('.test-column-info-tooltip-popup .test-column-info-tooltip-popup-body'); assert.equal(await descriptionTooltip.getText(), 'This is the column description'); // Close the tooltip await toggle.click(); assert.lengthOf(await driver.findAll('.test-column-info-tooltip-popup'), 0); - }) + }); }); From 0fa908d41a776cc028d666e948df9402805aa3b2 Mon Sep 17 00:00:00 2001 From: Camille Date: Wed, 15 Feb 2023 16:41:58 +0100 Subject: [PATCH 16/18] fix(ColumnDesc): tooltip show multiline description + remove useless variable + remove outline style --- app/client/ui/FieldConfig.ts | 19 +++++-------------- app/client/ui/tooltips.ts | 1 + test/nbrowser/DescriptionColumn.ts | 6 +++--- 3 files changed, 9 insertions(+), 17 deletions(-) diff --git a/app/client/ui/FieldConfig.ts b/app/client/ui/FieldConfig.ts index 21639a7b..38a317d6 100644 --- a/app/client/ui/FieldConfig.ts +++ b/app/client/ui/FieldConfig.ts @@ -8,7 +8,7 @@ import {cssBlockedCursor, cssLabel, cssRow} from 'app/client/ui/RightPanelStyles import { withInfoTooltip } from 'app/client/ui/tooltips'; import {buildFormulaTriggers} from 'app/client/ui/TriggerFormulas'; import {textButton} from 'app/client/ui2018/buttons'; -import { testId, theme, vars } from 'app/client/ui2018/cssVars'; +import { testId, theme } from 'app/client/ui2018/cssVars'; import {textInput} from 'app/client/ui2018/editableLabel'; import {cssIconButton, icon} from 'app/client/ui2018/icons'; import {IconName} from 'app/client/ui2018/IconList'; @@ -94,7 +94,6 @@ export function buildDescriptionConfig( origColumn: ColumnRec, cursor: ko.Computed, ) { - const editedDescription = Observable.create(owner, ''); // We will listen to cursor position and force a blur event on // the text input, which will trigger save before the column observable @@ -115,9 +114,7 @@ export function buildDescriptionConfig( { onInput: false }, { rows: '3' }, dom.on('blur', async (e, elem) => { - editedDescription.set(elem.value); await origColumn.description.saveOnly(elem.value); - editedDescription.set(''); }), testId('column-description'), ) @@ -538,6 +535,9 @@ const cssTextArea = styled(textarea, ` background-color: ${theme.mainPanelBg}; border: 1px solid ${theme.inputBorder}; width: 100%; + outline: none; + border-radius: 3px; + padding: 3px 7px; &::placeholder { color: ${theme.inputPlaceholderFg}; @@ -545,15 +545,6 @@ const cssTextArea = styled(textarea, ` &[readonly] { background-color: ${theme.inputDisabledBg}; - padding: 3px 7px; - border-radius: 3px; - resize: vertical; - border: 1px solid ${theme.inputBorder}; - color: ${theme.inputFg}; - background-color: ${theme.inputBg}; - flex: 1 1 0; - font-size: ${vars.mediumFontSize}; - font-family: ${vars.fontFamily}; - outline: none; + color: ${theme.inputDisabledFg}; } `); diff --git a/app/client/ui/tooltips.ts b/app/client/ui/tooltips.ts index 12ca62c8..553b7ea7 100644 --- a/app/client/ui/tooltips.ts +++ b/app/client/ui/tooltips.ts @@ -332,6 +332,7 @@ export function withInfoTooltip( ), cssInfoTooltipPopupBody( content, + { style: 'white-space: pre-wrap;' }, testId('column-info-tooltip-popup-body'), ), dom.cls(menuCssClass), diff --git a/test/nbrowser/DescriptionColumn.ts b/test/nbrowser/DescriptionColumn.ts index 29f6ca95..6922ba2e 100644 --- a/test/nbrowser/DescriptionColumn.ts +++ b/test/nbrowser/DescriptionColumn.ts @@ -6,7 +6,7 @@ import { setupTestSuite } from 'test/nbrowser/testUtils'; async function addColumnDescription(api: UserAPIImpl, docId: string, columnName: string) { await api.applyUserActions(docId, [ [ 'ModifyColumn', 'Table1', columnName, { - description: 'This is the column description' + description: 'This is the column description\nIt is in two lines' } ], ]); } @@ -32,7 +32,7 @@ describe('DescriptionColumn', function() { await gu.getCell({ rowNum: 1, col: 'B' }).click(); await driver.find('.test-right-tab-field').click(); - assert.equal(await getDescriptionInput().value(), 'This is the column description'); + assert.equal(await getDescriptionInput().value(), 'This is the column description\nIt is in two lines'); await gu.getCell({ rowNum: 1, col: 'A' }).click(); assert.equal(await getDescriptionInput().value(), ''); @@ -80,7 +80,7 @@ describe('DescriptionColumn', function() { // Check the content of the tooltip const descriptionTooltip = await driver .find('.test-column-info-tooltip-popup .test-column-info-tooltip-popup-body'); - assert.equal(await descriptionTooltip.getText(), 'This is the column description'); + assert.equal(await descriptionTooltip.getText(), 'This is the column description\nIt is in two lines'); // Close the tooltip await toggle.click(); From 16d7f16840197681f5ffb9db4307be4c3d116515 Mon Sep 17 00:00:00 2001 From: Camille Date: Tue, 21 Feb 2023 11:33:11 +0100 Subject: [PATCH 17/18] style(ColmunDesc): icon size and visible when there is a long colmun name --- app/client/components/DetailView.css | 25 +++++++++++++++++-------- app/client/components/DetailView.js | 8 ++++---- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/app/client/components/DetailView.css b/app/client/components/DetailView.css index 4e30d6d9..5ca3fc0a 100644 --- a/app/client/components/DetailView.css +++ b/app/client/components/DetailView.css @@ -30,6 +30,17 @@ padding: .5rem; } +.g_record_detail_label_container { + display: flex; + justify-content: flex-start; + gap: 3px; +} + +.g_record_detail_label_container .info_toggle_icon { + width: 13px; + height: 13px; + margin-bottom: 3px; +} .g_record_detail_label { min-height: 1rem; color: #666; @@ -150,7 +161,7 @@ -webkit-flex-direction: column-reverse; } -.detail_theme_field_under > .g_record_detail_label { +.detail_theme_field_under .g_record_detail_label { border-top: 1px solid #333; } @@ -208,7 +219,7 @@ line-height: 1.2; } -.detail_theme_field_compact > .g_record_detail_label { +.detail_theme_field_compact .g_record_detail_label { font-weight: normal; font-size: var(--grist-small-font-size); color: var(--grist-theme-card-compact-label, var(--grist-color-slate)); @@ -230,7 +241,7 @@ padding: 1px 1px 1px 5px; } -.detail_theme_field_form>.g_record_detail_label { +.detail_theme_field_form .g_record_detail_label { font-size: var(--grist-small-font-size); color: var(--grist-theme-card-form-label, var(--grist-color-slate)); font-weight: bold; @@ -241,10 +252,8 @@ margin-right: -8px; } -.detail_theme_field_form .info_toggle_icon { - width: 13px; - height: 13px; - margin-bottom: 3px; +.detail_theme_field_form .g_record_detail_label_container { + gap: 8px; } /* TODO want to style better the values themselves (e.g. more padding, rounded corners, move label @@ -294,7 +303,7 @@ border-radius: 2px; } -.detail_theme_field_blocks > .g_record_detail_label { +.detail_theme_field_blocks .g_record_detail_label { font-size: var(--grist-small-font-size); color: var(--grist-theme-card-blocks-label, var(--grist-color-slate)); font-weight: normal; diff --git a/app/client/components/DetailView.js b/app/client/components/DetailView.js index 4b48426a..75c11f53 100644 --- a/app/client/components/DetailView.js +++ b/app/client/components/DetailView.js @@ -228,8 +228,8 @@ DetailView.prototype.buildFieldDom = function(field, row) { if (field.isNewField) { return dom('div.g_record_detail_el.flexitem', kd.cssClass(function() { return 'detail_theme_field_' + self.viewSection.themeDef(); }), - dom('div.g_record_detail_label', - kd.text(field.displayLabel), + dom('div.g_record_detail_label_container', + dom('div.g_record_detail_label', kd.text(field.displayLabel)), kd.scope(field.description, desc => desc ? columnInfoTooltip(kd.text(field.description)) : null) ), ); @@ -260,8 +260,8 @@ DetailView.prototype.buildFieldDom = function(field, row) { dom.autoDispose(isCellSelected), dom.autoDispose(isCellActive), kd.cssClass(function() { return 'detail_theme_field_' + self.viewSection.themeDef(); }), - dom('div.g_record_detail_label', - kd.text(field.displayLabel), + dom('div.g_record_detail_label_container', + dom('div.g_record_detail_label', kd.text(field.displayLabel)), kd.scope(field.description, desc => desc ? columnInfoTooltip(kd.text(field.description)) : null) ), dom('div.g_record_detail_value', From 5e244a9a24531da7485e35effd8b53cc34288203 Mon Sep 17 00:00:00 2001 From: CamilleLegeron Date: Thu, 23 Feb 2023 10:18:38 +0100 Subject: [PATCH 18/18] style(colmunDesc): tooltips icon line heigh to 0 Co-authored-by: jarek --- app/client/ui/tooltips.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/client/ui/tooltips.ts b/app/client/ui/tooltips.ts index 553b7ea7..af2bb767 100644 --- a/app/client/ui/tooltips.ts +++ b/app/client/ui/tooltips.ts @@ -359,6 +359,8 @@ const cssColumnInfoTooltipButton = styled('div', ` border-radius: 50%; display: inline-block; margin-left: 5px; + line-height: 0px; + &:hover { --icon-color: ${theme.infoButtonHoverFg}; }