mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
Merge pull request #406 from incubateur-territoires/column-description
feat: Add a description to a grist table column
This commit is contained in:
commit
cee0cdcd67
@ -30,6 +30,17 @@
|
|||||||
padding: .5rem;
|
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 {
|
.g_record_detail_label {
|
||||||
min-height: 1rem;
|
min-height: 1rem;
|
||||||
color: #666;
|
color: #666;
|
||||||
@ -150,7 +161,7 @@
|
|||||||
-webkit-flex-direction: column-reverse;
|
-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;
|
border-top: 1px solid #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,7 +219,7 @@
|
|||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail_theme_field_compact > .g_record_detail_label {
|
.detail_theme_field_compact .g_record_detail_label {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-size: var(--grist-small-font-size);
|
font-size: var(--grist-small-font-size);
|
||||||
color: var(--grist-theme-card-compact-label, var(--grist-color-slate));
|
color: var(--grist-theme-card-compact-label, var(--grist-color-slate));
|
||||||
@ -230,7 +241,7 @@
|
|||||||
padding: 1px 1px 1px 5px;
|
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);
|
font-size: var(--grist-small-font-size);
|
||||||
color: var(--grist-theme-card-form-label, var(--grist-color-slate));
|
color: var(--grist-theme-card-form-label, var(--grist-color-slate));
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@ -241,6 +252,10 @@
|
|||||||
margin-right: -8px;
|
margin-right: -8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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
|
/* 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
|
* 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.
|
* needs to learn to match the value box's style. Right now, the cell editor style is hard-coded.
|
||||||
@ -288,7 +303,7 @@
|
|||||||
border-radius: 2px;
|
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);
|
font-size: var(--grist-small-font-size);
|
||||||
color: var(--grist-theme-card-blocks-label, var(--grist-color-slate));
|
color: var(--grist-theme-card-blocks-label, var(--grist-color-slate));
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
@ -15,6 +15,7 @@ const RecordLayout = require('./RecordLayout');
|
|||||||
const commands = require('./commands');
|
const commands = require('./commands');
|
||||||
const {RowContextMenu} = require('../ui/RowContextMenu');
|
const {RowContextMenu} = require('../ui/RowContextMenu');
|
||||||
const {parsePasteForView} = require("./BaseView2");
|
const {parsePasteForView} = require("./BaseView2");
|
||||||
|
const {columnInfoTooltip} = require("../ui/tooltips");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DetailView component implements a list of record layouts.
|
* DetailView component implements a list of record layouts.
|
||||||
@ -227,8 +228,10 @@ DetailView.prototype.buildFieldDom = function(field, row) {
|
|||||||
if (field.isNewField) {
|
if (field.isNewField) {
|
||||||
return dom('div.g_record_detail_el.flexitem',
|
return dom('div.g_record_detail_el.flexitem',
|
||||||
kd.cssClass(function() { return 'detail_theme_field_' + self.viewSection.themeDef(); }),
|
kd.cssClass(function() { return 'detail_theme_field_' + self.viewSection.themeDef(); }),
|
||||||
dom('div.g_record_detail_label', field.label),
|
dom('div.g_record_detail_label_container',
|
||||||
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)
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,7 +260,10 @@ DetailView.prototype.buildFieldDom = function(field, row) {
|
|||||||
dom.autoDispose(isCellSelected),
|
dom.autoDispose(isCellSelected),
|
||||||
dom.autoDispose(isCellActive),
|
dom.autoDispose(isCellActive),
|
||||||
kd.cssClass(function() { return 'detail_theme_field_' + self.viewSection.themeDef(); }),
|
kd.cssClass(function() { return 'detail_theme_field_' + self.viewSection.themeDef(); }),
|
||||||
|
dom('div.g_record_detail_label_container',
|
||||||
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',
|
dom('div.g_record_detail_value',
|
||||||
kd.toggleClass('scissors', isCopyActive),
|
kd.toggleClass('scissors', isCopyActive),
|
||||||
kd.toggleClass('record-add', row._isAddRow),
|
kd.toggleClass('record-add', row._isAddRow),
|
||||||
|
@ -20,6 +20,7 @@ export interface ViewFieldRec extends IRowModel<"_grist_Views_section_field">, R
|
|||||||
origCol: ko.Computed<ColumnRec>;
|
origCol: ko.Computed<ColumnRec>;
|
||||||
colId: ko.Computed<string>;
|
colId: ko.Computed<string>;
|
||||||
label: ko.Computed<string>;
|
label: ko.Computed<string>;
|
||||||
|
description: ko.Computed<string>;
|
||||||
|
|
||||||
// displayLabel displays label by default but switches to the more helpful colId whenever a
|
// displayLabel displays label by default but switches to the more helpful colId whenever a
|
||||||
// formula field in the view is being edited.
|
// 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.origCol = ko.pureComputed(() => this.column().origCol());
|
||||||
this.colId = ko.pureComputed(() => this.column().colId());
|
this.colId = ko.pureComputed(() => this.column().colId());
|
||||||
this.label = ko.pureComputed(() => this.column().label());
|
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
|
// displayLabel displays label by default but switches to the more helpful colId whenever a
|
||||||
// formula field in the view is being edited.
|
// formula field in the view is being edited.
|
||||||
|
@ -5,10 +5,10 @@ import {BEHAVIOR, ColumnRec} from 'app/client/models/entities/ColumnRec';
|
|||||||
import {buildHighlightedCode, cssCodeBlock} from 'app/client/ui/CodeHighlight';
|
import {buildHighlightedCode, cssCodeBlock} from 'app/client/ui/CodeHighlight';
|
||||||
import {GristTooltips} from 'app/client/ui/GristTooltips';
|
import {GristTooltips} from 'app/client/ui/GristTooltips';
|
||||||
import {cssBlockedCursor, cssLabel, cssRow} from 'app/client/ui/RightPanelStyles';
|
import {cssBlockedCursor, cssLabel, cssRow} from 'app/client/ui/RightPanelStyles';
|
||||||
import {withInfoTooltip} from 'app/client/ui/tooltips';
|
import { withInfoTooltip } from 'app/client/ui/tooltips';
|
||||||
import {buildFormulaTriggers} from 'app/client/ui/TriggerFormulas';
|
import {buildFormulaTriggers} from 'app/client/ui/TriggerFormulas';
|
||||||
import {textButton} from 'app/client/ui2018/buttons';
|
import {textButton} from 'app/client/ui2018/buttons';
|
||||||
import {testId, theme} from 'app/client/ui2018/cssVars';
|
import { testId, theme } from 'app/client/ui2018/cssVars';
|
||||||
import {textInput} from 'app/client/ui2018/editableLabel';
|
import {textInput} from 'app/client/ui2018/editableLabel';
|
||||||
import {cssIconButton, icon} from 'app/client/ui2018/icons';
|
import {cssIconButton, icon} from 'app/client/ui2018/icons';
|
||||||
import {IconName} from 'app/client/ui2018/IconList';
|
import {IconName} from 'app/client/ui2018/IconList';
|
||||||
@ -18,6 +18,7 @@ import {sanitizeIdent} from 'app/common/gutil';
|
|||||||
import {bundleChanges, Computed, dom, DomContents, DomElementArg, fromKo, MultiHolder,
|
import {bundleChanges, Computed, dom, DomContents, DomElementArg, fromKo, MultiHolder,
|
||||||
Observable, styled} from 'grainjs';
|
Observable, styled} from 'grainjs';
|
||||||
import * as ko from 'knockout';
|
import * as ko from 'knockout';
|
||||||
|
import { textarea } from './inputs';
|
||||||
|
|
||||||
const t = makeT('FieldConfig');
|
const t = makeT('FieldConfig');
|
||||||
|
|
||||||
@ -88,6 +89,40 @@ export function buildNameConfig(
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildDescriptionConfig(
|
||||||
|
owner: MultiHolder,
|
||||||
|
origColumn: ColumnRec,
|
||||||
|
cursor: ko.Computed<CursorPos>,
|
||||||
|
) {
|
||||||
|
|
||||||
|
// 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(
|
||||||
|
editor = cssTextArea(fromKo(origColumn.description),
|
||||||
|
{ onInput: false },
|
||||||
|
{ rows: '3' },
|
||||||
|
dom.on('blur', async (e, elem) => {
|
||||||
|
await origColumn.description.saveOnly(elem.value);
|
||||||
|
}),
|
||||||
|
testId('column-description'),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
type SaveHandler = (column: ColumnRec, formula: string) => Promise<void>;
|
type SaveHandler = (column: ColumnRec, formula: string) => Promise<void>;
|
||||||
type BuildEditor = (
|
type BuildEditor = (
|
||||||
cellElem: Element,
|
cellElem: Element,
|
||||||
@ -494,3 +529,22 @@ const cssInput = styled(textInput, `
|
|||||||
color: ${theme.inputDisabledFg};
|
color: ${theme.inputDisabledFg};
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
const cssTextArea = styled(textarea, `
|
||||||
|
color: ${theme.inputFg};
|
||||||
|
background-color: ${theme.mainPanelBg};
|
||||||
|
border: 1px solid ${theme.inputBorder};
|
||||||
|
width: 100%;
|
||||||
|
outline: none;
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 3px 7px;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: ${theme.inputPlaceholderFg};
|
||||||
|
}
|
||||||
|
|
||||||
|
&[readonly] {
|
||||||
|
background-color: ${theme.inputDisabledBg};
|
||||||
|
color: ${theme.inputDisabledFg};
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
@ -43,6 +43,7 @@ import {bundleChanges, Computed, Disposable, dom, domComputed, DomContents,
|
|||||||
DomElementArg, DomElementMethod, IDomComponent} from 'grainjs';
|
DomElementArg, DomElementMethod, IDomComponent} from 'grainjs';
|
||||||
import {MultiHolder, Observable, styled, subscribe} from 'grainjs';
|
import {MultiHolder, Observable, styled, subscribe} from 'grainjs';
|
||||||
import * as ko from 'knockout';
|
import * as ko from 'knockout';
|
||||||
|
import { buildDescriptionConfig } from './FieldConfig';
|
||||||
|
|
||||||
const t = makeT('RightPanel');
|
const t = makeT('RightPanel');
|
||||||
|
|
||||||
@ -235,6 +236,9 @@ export class RightPanel extends Disposable {
|
|||||||
cssSection(
|
cssSection(
|
||||||
dom.create(buildNameConfig, origColumn, cursor, isMultiSelect),
|
dom.create(buildNameConfig, origColumn, cursor, isMultiSelect),
|
||||||
),
|
),
|
||||||
|
cssSection(
|
||||||
|
dom.create(buildDescriptionConfig, origColumn, cursor),
|
||||||
|
),
|
||||||
cssSeparator(),
|
cssSeparator(),
|
||||||
cssSection(
|
cssSection(
|
||||||
dom.create(buildFormulaConfig,
|
dom.create(buildFormulaConfig,
|
||||||
|
@ -242,7 +242,7 @@ export function tooltipCloseButton(ctl: ITooltipControl): HTMLElement {
|
|||||||
/**
|
/**
|
||||||
* Renders an info 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 infoTooltip(content: DomContents, menuOptions?: IMenuOptions, ...domArgs: DomElementArg[]) {
|
export function infoTooltip(content: DomContents, menuOptions?: IMenuOptions, ...domArgs: DomElementArg[]) {
|
||||||
return cssInfoTooltipButton('?',
|
return cssInfoTooltipButton('?',
|
||||||
(elem) => {
|
(elem) => {
|
||||||
setPopupToCreateDom(
|
setPopupToCreateDom(
|
||||||
@ -314,6 +314,62 @@ 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,
|
||||||
|
{ style: 'white-space: pre-wrap;' },
|
||||||
|
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;
|
||||||
|
line-height: 0px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
--icon-color: ${theme.infoButtonHoverFg};
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
--icon-color: ${theme.infoButtonActiveFg};
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
|
||||||
const cssTooltip = styled('div', `
|
const cssTooltip = styled('div', `
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 5000; /* should be higher than a modal */
|
z-index: 5000; /* should be higher than a modal */
|
||||||
|
@ -605,6 +605,11 @@ export const theme = {
|
|||||||
menuToggleBg: new CustomProp('theme-menu-toggle-bg', undefined, 'white'),
|
menuToggleBg: new CustomProp('theme-menu-toggle-bg', undefined, 'white'),
|
||||||
menuToggleBorder: new CustomProp('theme-menu-toggle-border', undefined, colors.slate),
|
menuToggleBorder: new CustomProp('theme-menu-toggle-border', undefined, colors.slate),
|
||||||
|
|
||||||
|
/* 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 */
|
/* Button Groups */
|
||||||
buttonGroupFg: new CustomProp('theme-button-group-fg', undefined, colors.dark),
|
buttonGroupFg: new CustomProp('theme-button-group-fg', undefined, colors.dark),
|
||||||
buttonGroupLightFg: new CustomProp('theme-button-group-light-fg', undefined, colors.slate),
|
buttonGroupLightFg: new CustomProp('theme-button-group-light-fg', undefined, colors.slate),
|
||||||
|
@ -4,7 +4,7 @@ import { GristObjCode } from "app/plugin/GristData";
|
|||||||
|
|
||||||
// tslint:disable:object-literal-key-quotes
|
// tslint:disable:object-literal-key-quotes
|
||||||
|
|
||||||
export const SCHEMA_VERSION = 35;
|
export const SCHEMA_VERSION = 36;
|
||||||
|
|
||||||
export const schema = {
|
export const schema = {
|
||||||
|
|
||||||
@ -34,6 +34,7 @@ export const schema = {
|
|||||||
isFormula : "Bool",
|
isFormula : "Bool",
|
||||||
formula : "Text",
|
formula : "Text",
|
||||||
label : "Text",
|
label : "Text",
|
||||||
|
description : "Text",
|
||||||
untieColIdFromLabel : "Bool",
|
untieColIdFromLabel : "Bool",
|
||||||
summarySourceCol : "Ref:_grist_Tables_column",
|
summarySourceCol : "Ref:_grist_Tables_column",
|
||||||
displayCol : "Ref:_grist_Tables_column",
|
displayCol : "Ref:_grist_Tables_column",
|
||||||
@ -239,6 +240,7 @@ export interface SchemaTypes {
|
|||||||
isFormula: boolean;
|
isFormula: boolean;
|
||||||
formula: string;
|
formula: string;
|
||||||
label: string;
|
label: string;
|
||||||
|
description: string;
|
||||||
untieColIdFromLabel: boolean;
|
untieColIdFromLabel: boolean;
|
||||||
summarySourceCol: number;
|
summarySourceCol: number;
|
||||||
displayCol: number;
|
displayCol: number;
|
||||||
|
@ -6,9 +6,9 @@ export const GRIST_DOC_SQL = `
|
|||||||
PRAGMA foreign_keys=OFF;
|
PRAGMA foreign_keys=OFF;
|
||||||
BEGIN TRANSACTION;
|
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 '');
|
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" (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_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_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 '');
|
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;
|
PRAGMA foreign_keys=OFF;
|
||||||
BEGIN TRANSACTION;
|
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 '');
|
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" (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);
|
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);
|
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(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(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(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);
|
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_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_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 '');
|
CREATE TABLE IF NOT EXISTS "_grist_External_table" (id INTEGER PRIMARY KEY, "tableRef" INTEGER DEFAULT 0, "databaseRef" INTEGER DEFAULT 0, "tableName" TEXT DEFAULT '');
|
||||||
|
@ -1188,3 +1188,11 @@ def migration35(tdset):
|
|||||||
))
|
))
|
||||||
|
|
||||||
return tdset.apply_doc_actions(doc_actions)
|
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')])
|
||||||
|
@ -15,7 +15,7 @@ import six
|
|||||||
|
|
||||||
import actions
|
import actions
|
||||||
|
|
||||||
SCHEMA_VERSION = 35
|
SCHEMA_VERSION = 36
|
||||||
|
|
||||||
def make_column(col_id, col_type, formula='', isFormula=False):
|
def make_column(col_id, col_type, formula='', isFormula=False):
|
||||||
return {
|
return {
|
||||||
@ -70,6 +70,7 @@ def schema_create_actions():
|
|||||||
make_column("isFormula", "Bool"),
|
make_column("isFormula", "Bool"),
|
||||||
make_column("formula", "Text"),
|
make_column("formula", "Text"),
|
||||||
make_column("label", "Text"),
|
make_column("label", "Text"),
|
||||||
|
make_column("description", "Text"),
|
||||||
|
|
||||||
# Normally a change to label changes colId as well, unless untieColIdFromLabel is True.
|
# Normally a change to label changes colId as well, unless untieColIdFromLabel is True.
|
||||||
# (We intentionally pick a variable whose default value is false.)
|
# (We intentionally pick a variable whose default value is false.)
|
||||||
|
@ -326,7 +326,8 @@
|
|||||||
"Mixed Behavior": "Mixed Behavior",
|
"Mixed Behavior": "Mixed Behavior",
|
||||||
"Set formula": "Set formula",
|
"Set formula": "Set formula",
|
||||||
"Set trigger formula": "Set trigger formula",
|
"Set trigger formula": "Set trigger formula",
|
||||||
"TRIGGER FORMULA": "TRIGGER FORMULA"
|
"TRIGGER FORMULA": "TRIGGER FORMULA",
|
||||||
|
"DESCRIPTION": "DESCRIPTION"
|
||||||
},
|
},
|
||||||
"FieldMenus": {
|
"FieldMenus": {
|
||||||
"Revert to common settings": "Revert to common settings",
|
"Revert to common settings": "Revert to common settings",
|
||||||
|
89
test/nbrowser/DescriptionColumn.ts
Normal file
89
test/nbrowser/DescriptionColumn.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
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\nIt is in two lines'
|
||||||
|
} ],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 doc = await mainSession.tempDoc(cleanup, "CardView.grist", { load: true });
|
||||||
|
const docId = doc.id;
|
||||||
|
|
||||||
|
await addColumnDescription(api, docId, 'B');
|
||||||
|
|
||||||
|
// Column description editable in right panel
|
||||||
|
await driver.find('.test-right-opener').click();
|
||||||
|
|
||||||
|
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\nIt is in two lines');
|
||||||
|
|
||||||
|
await gu.getCell({ rowNum: 1, col: 'A' }).click();
|
||||||
|
assert.equal(await getDescriptionInput().value(), '');
|
||||||
|
|
||||||
|
// Remove the description
|
||||||
|
await api.applyUserActions(docId, [
|
||||||
|
[ '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;
|
||||||
|
|
||||||
|
await 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\nIt is in two lines');
|
||||||
|
|
||||||
|
// Close the tooltip
|
||||||
|
await toggle.click();
|
||||||
|
assert.lengthOf(await driver.findAll('.test-column-info-tooltip-popup'), 0);
|
||||||
|
});
|
||||||
|
});
|
@ -496,6 +496,7 @@ function testDocApi() {
|
|||||||
isFormula: false,
|
isFormula: false,
|
||||||
formula: '',
|
formula: '',
|
||||||
label: 'A',
|
label: 'A',
|
||||||
|
description: '',
|
||||||
untieColIdFromLabel: false,
|
untieColIdFromLabel: false,
|
||||||
summarySourceCol: 0,
|
summarySourceCol: 0,
|
||||||
displayCol: 0,
|
displayCol: 0,
|
||||||
@ -516,6 +517,7 @@ function testDocApi() {
|
|||||||
isFormula: false,
|
isFormula: false,
|
||||||
formula: '',
|
formula: '',
|
||||||
label: 'B',
|
label: 'B',
|
||||||
|
description: '',
|
||||||
untieColIdFromLabel: false,
|
untieColIdFromLabel: false,
|
||||||
summarySourceCol: 0,
|
summarySourceCol: 0,
|
||||||
displayCol: 0,
|
displayCol: 0,
|
||||||
@ -536,6 +538,7 @@ function testDocApi() {
|
|||||||
isFormula: false,
|
isFormula: false,
|
||||||
formula: '',
|
formula: '',
|
||||||
label: 'C',
|
label: 'C',
|
||||||
|
description: '',
|
||||||
untieColIdFromLabel: false,
|
untieColIdFromLabel: false,
|
||||||
summarySourceCol: 0,
|
summarySourceCol: 0,
|
||||||
displayCol: 0,
|
displayCol: 0,
|
||||||
@ -556,6 +559,7 @@ function testDocApi() {
|
|||||||
isFormula: true,
|
isFormula: true,
|
||||||
formula: '',
|
formula: '',
|
||||||
label: 'D',
|
label: 'D',
|
||||||
|
description: '',
|
||||||
untieColIdFromLabel: false,
|
untieColIdFromLabel: false,
|
||||||
summarySourceCol: 0,
|
summarySourceCol: 0,
|
||||||
displayCol: 0,
|
displayCol: 0,
|
||||||
@ -576,6 +580,7 @@ function testDocApi() {
|
|||||||
isFormula: true,
|
isFormula: true,
|
||||||
formula: '$A.upper()',
|
formula: '$A.upper()',
|
||||||
label: 'E',
|
label: 'E',
|
||||||
|
description: '',
|
||||||
untieColIdFromLabel: false,
|
untieColIdFromLabel: false,
|
||||||
summarySourceCol: 0,
|
summarySourceCol: 0,
|
||||||
displayCol: 0,
|
displayCol: 0,
|
||||||
|
Loading…
Reference in New Issue
Block a user