feature widget description (#483)

Add description to widget title popup and right panel
This commit is contained in:
CamilleLegeron
2023-05-12 15:08:28 +02:00
committed by GitHub
parent a019c406ab
commit c16204f8ad
15 changed files with 359 additions and 174 deletions

View File

@@ -16,7 +16,7 @@ const RecordLayout = require('./RecordLayout');
const commands = require('./commands');
const {RowContextMenu} = require('../ui/RowContextMenu');
const {parsePasteForView} = require("./BaseView2");
const {columnInfoTooltip} = require("../ui/tooltips");
const {descriptionInfoTooltip} = require("../ui/tooltips");
/**
@@ -247,7 +247,7 @@ DetailView.prototype.buildFieldDom = function(field, row) {
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.label)),
kd.scope(field.description, desc => desc ? columnInfoTooltip(kd.text(field.description)) : null)
kd.scope(field.description, desc => desc ? descriptionInfoTooltip(kd.text(field.description), "colmun") : null)
),
dom('div.g_record_detail_value'),
);
@@ -280,7 +280,7 @@ DetailView.prototype.buildFieldDom = function(field, row) {
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)),
kd.scope(field.description, desc => desc ? columnInfoTooltip(kd.text(field.description)) : null)
kd.scope(field.description, desc => desc ? descriptionInfoTooltip(kd.text(field.description), "column") : null)
),
dom('div.g_record_detail_value',
kd.toggleClass('scissors', isCopyActive),

View File

@@ -44,7 +44,7 @@ const {testId, isNarrowScreen} = require('app/client/ui2018/cssVars');
const {contextMenu} = require('app/client/ui/contextMenu');
const {mouseDragMatchElem} = require('app/client/ui/mouseDrag');
const {menuToggle} = require('app/client/ui/MenuToggle');
const {columnInfoTooltip, showTooltip} = require('app/client/ui/tooltips');
const {descriptionInfoTooltip, showTooltip} = require('app/client/ui/tooltips');
const {parsePasteForView} = require("./BaseView2");
const {NEW_FILTER_JSON} = require('app/client/models/ColumnFilter');
const {CombinedStyle} = require("app/client/models/Styles");
@@ -1087,7 +1087,7 @@ GridView.prototype.buildDom = function() {
if (btn) { btn.click(); }
}),
dom('div.g-column-label',
kd.scope(field.description, desc => desc ? columnInfoTooltip(kd.text(field.description)) : null),
kd.scope(field.description, desc => desc ? descriptionInfoTooltip(kd.text(field.description), "column") : null),
dom.on('mousedown', ev => isEditingLabel() ? ev.stopPropagation() : true),
// We are using editableLabel here, but we don't use it for editing.
kf.editableLabel(self.isPreview ? field.label : field.displayLabel, ko.observable(false)),

View File

@@ -53,6 +53,8 @@ export interface ViewSectionRec extends IRowModel<"_grist_Views_section">, RuleO
// Default widget title (the one that is used in titleDef).
defaultWidgetTitle: ko.PureComputed<string>;
description: modelUtil.KoSaveableObservable<string>;
// true if this record is its table's rawViewSection, i.e. a 'raw data view'
// in which case the UI prevents various things like hiding columns or changing the widget type.
isRaw: ko.Computed<boolean>;
@@ -363,6 +365,9 @@ export function createViewSectionRec(this: ViewSectionRec, docModel: DocModel):
// Widget title.
this.titleDef = modelUtil.fieldWithDefault(this.title, this.defaultWidgetTitle);
// Widget description
this.description = modelUtil.fieldWithDefault(this.description, this.description());
// true if this record is its table's rawViewSection, i.e. a 'raw data view'
// in which case the UI prevents various things like hiding columns or changing the widget type.
this.isRaw = this.autoDispose(ko.pureComputed(() => this.table().rawViewSectionRef() === this.getRowId()));

View File

@@ -6,17 +6,16 @@ import {makeT} from 'app/client/lib/localization';
import {setTestState} from 'app/client/lib/testState';
import {ViewFieldRec} from 'app/client/models/DocModel';
import {autoGrow} from 'app/client/ui/forms';
import {textarea} from 'app/client/ui/inputs';
import {showTransientTooltip} from 'app/client/ui/tooltips';
import {basicButton, primaryButton, textButton} from 'app/client/ui2018/buttons';
import {theme, vars} from 'app/client/ui2018/cssVars';
import {cssTextInput} from 'app/client/ui2018/editableLabel';
import {icon} from 'app/client/ui2018/icons';
import {menuCssClass} from 'app/client/ui2018/menus';
import {Computed, dom, IInputOptions, input, makeTestId, Observable, styled} from 'grainjs';
import {Computed, dom, makeTestId, Observable, styled} from 'grainjs';
import * as ko from 'knockout';
import {IOpenController, PopupControl, setPopupToCreateDom} from 'popweasel';
import { cssInput, cssLabel, cssRenamePopup, cssTextArea } from 'app/client/ui/RenamePopupStyles';
const testId = makeTestId('test-column-title-');
@@ -281,16 +280,6 @@ const cssAddDescription = styled('div', `
}
`);
const cssRenamePopup = styled('div', `
display: flex;
flex-direction: column;
min-width: 280px;
padding: 16px;
background-color: ${theme.popupBg};
border-radius: 2px;
outline: none;
`);
const cssColLabelBlock = styled('div', `
display: flex;
flex-direction: column;
@@ -298,17 +287,6 @@ const cssColLabelBlock = styled('div', `
min-width: 80px;
`);
const cssLabel = styled('label', `
color: ${theme.text};
font-size: ${vars.xsmallFontSize};
font-weight: ${vars.bigControlTextWeight};
text-transform: uppercase;
margin: 0 0 8px 0;
&:not(:first-child) {
margin-top: 16px;
}
`);
const cssColId = styled('div', `
font-size: ${vars.xsmallFontSize};
font-weight: ${vars.bigControlTextWeight};
@@ -321,29 +299,6 @@ const cssColId = styled('div', `
align-self: start;
`);
const cssTextArea = styled(textarea, `
color: ${theme.inputFg};
background-color: ${theme.mainPanelBg};
border: 1px solid ${theme.inputBorder};
width: 100%;
padding: 3px 7px;
outline: none;
max-width: 100%;
min-width: calc(280px - 16px*2);
max-height: 500px;
min-height: calc(3em * 1.5);
resize: none;
border-radius: 3px;
&::placeholder {
color: ${theme.inputPlaceholderFg};
}
&[readonly] {
background-color: ${theme.inputDisabledBg};
color: ${theme.inputDisabledFg};
}
`);
const cssButtons = styled('div', `
display: flex;
margin-top: 16px;
@@ -352,29 +307,3 @@ const cssButtons = styled('div', `
min-width: calc(50 / 13 * 1em); /* Min 50px for 13px font size, to make Save and Close buttons equal width */
}
`);
const cssInputWithIcon = styled('div', `
position: relative;
display: flex;
flex-direction: column;
`);
const cssInput = styled((
obs: Observable<string>,
opts: IInputOptions,
...args) => input(obs, opts, cssTextInput.cls(''), ...args), `
text-overflow: ellipsis;
color: ${theme.inputFg};
background-color: transparent;
&:disabled {
color: ${theme.inputDisabledFg};
background-color: ${theme.inputDisabledBg};
pointer-events: none;
}
&::placeholder {
color: ${theme.inputPlaceholderFg};
}
.${cssInputWithIcon.className} > &:disabled {
padding-right: 28px;
}
`);

View File

@@ -1,18 +1,21 @@
import {CursorPos} from 'app/client/components/Cursor';
import {makeT} from 'app/client/lib/localization';
import {ColumnRec} from 'app/client/models/DocModel';
import { KoSaveableObservable } from 'app/client/models/modelUtil';
import {autoGrow} from 'app/client/ui/forms';
import {textarea} from 'app/client/ui/inputs';
import {cssLabel, cssRow} from 'app/client/ui/RightPanelStyles';
import {testId, theme} from 'app/client/ui2018/cssVars';
import {dom, fromKo, MultiHolder, styled} from 'grainjs';
const t = makeT('FieldConfig');
const t = makeT('DescriptionConfig');
export function buildDescriptionConfig(
owner: MultiHolder,
origColumn: ColumnRec,
cursor: ko.Computed<CursorPos>,
description: KoSaveableObservable<string>,
options: {
cursor: ko.Computed<CursorPos>,
testPrefix: string,
},
) {
// We will listen to cursor position and force a blur event on
@@ -22,7 +25,7 @@ export function buildDescriptionConfig(
// update a different column.
let editor: HTMLTextAreaElement | undefined;
owner.autoDispose(
cursor.subscribe(() => {
options.cursor.subscribe(() => {
editor?.blur();
})
);
@@ -30,14 +33,14 @@ export function buildDescriptionConfig(
return [
cssLabel(t("DESCRIPTION")),
cssRow(
editor = cssTextArea(fromKo(origColumn.description),
editor = cssTextArea(fromKo(description),
{ onInput: false },
{ rows: '3' },
dom.on('blur', async (e, elem) => {
await origColumn.description.setAndSave(elem.value.trim());
await description.saveOnly(elem.value);
}),
testId('column-description'),
autoGrow(fromKo(origColumn.description))
testId(`${options.testPrefix}-description`),
autoGrow(fromKo(description))
)
),
];

View File

@@ -0,0 +1,75 @@
import { theme, vars } from 'app/client/ui2018/cssVars';
import {textarea} from 'app/client/ui/inputs';
import {cssTextInput} from 'app/client/ui2018/editableLabel';
import {IInputOptions, input, Observable, styled} from 'grainjs';
export const cssRenamePopup = styled('div', `
display: flex;
flex-direction: column;
min-width: 280px;
padding: 16px;
background-color: ${theme.popupBg};
border-radius: 2px;
outline: none;
`);
export const cssLabel = styled('label', `
color: ${theme.text};
font-size: ${vars.xsmallFontSize};
font-weight: ${vars.bigControlTextWeight};
text-transform: uppercase;
margin: 0 0 8px 0;
&:not(:first-child) {
margin-top: 16px;
}
`);
const cssInputWithIcon = styled('div', `
position: relative;
display: flex;
flex-direction: column;
`);
export const cssInput = styled((
obs: Observable<string>,
opts: IInputOptions,
...args) => input(obs, opts, cssTextInput.cls(''), ...args), `
text-overflow: ellipsis;
color: ${theme.inputFg};
background-color: transparent;
&:disabled {
color: ${theme.inputDisabledFg};
background-color: ${theme.inputDisabledBg};
pointer-events: none;
}
&::placeholder {
color: ${theme.inputPlaceholderFg};
}
.${cssInputWithIcon.className} > &:disabled {
padding-right: 28px;
}
`);
export const cssTextArea = styled(textarea, `
color: ${theme.inputFg};
background-color: ${theme.mainPanelBg};
border: 1px solid ${theme.inputBorder};
width: 100%;
padding: 3px 6px;
outline: none;
max-width: 100%;
min-width: calc(280px - 16px*2);
max-height: 500px;
min-height: calc(3em * 1.5);
resize: none;
border-radius: 3px;
&::placeholder {
color: ${theme.inputPlaceholderFg};
}
&[readonly] {
background-color: ${theme.inputDisabledBg};
color: ${theme.inputDisabledFg};
}
`);

View File

@@ -251,7 +251,7 @@ export class RightPanel extends Disposable {
dom.create(buildNameConfig, origColumn, cursor, isMultiSelect),
),
cssSection(
dom.create(buildDescriptionConfig, origColumn, cursor),
dom.create(buildDescriptionConfig, origColumn.description, { cursor, "testPrefix": "column" }),
),
cssSeparator(),
cssSection(
@@ -361,6 +361,13 @@ export class RightPanel extends Disposable {
const hasColumnMapping = use(activeSection.columnsToMap);
return Boolean(isCustom && hasColumnMapping);
});
// build cursor position observable
const cursor = owner.autoDispose(ko.computed(() => {
const vsi = this._gristDoc.viewModel.activeSection?.().viewInstance();
return vsi?.cursor.currentPosition() ?? {};
}));
return dom.maybe(viewConfigTab, (vct) => [
this._disableIfReadonly(),
cssLabel(dom.text(use => use(activeSection.isRaw) ? t("DATA TABLE NAME") : t("WIDGET TITLE")),
@@ -377,6 +384,10 @@ export class RightPanel extends Disposable {
testId('right-widget-title')
)),
cssSection(
dom.create(buildDescriptionConfig, activeSection.description, { cursor, "testPrefix": "right-widget" }),
),
dom.maybe(
(use) => !use(activeSection.isRaw),
() => cssRow(

View File

@@ -1,13 +1,16 @@
import * as commands from 'app/client/components/commands';
import {makeT} from 'app/client/lib/localization';
import {FocusLayer} from 'app/client/lib/FocusLayer';
import { FocusLayer } from 'app/client/lib/FocusLayer';
import {ViewSectionRec} from 'app/client/models/entities/ViewSectionRec';
import {basicButton, cssButton, primaryButton} from 'app/client/ui2018/buttons';
import {theme, vars} from 'app/client/ui2018/cssVars';
import {cssTextInput} from 'app/client/ui2018/editableLabel';
import { theme } from 'app/client/ui2018/cssVars';
import {menuCssClass} from 'app/client/ui2018/menus';
import {ModalControl} from 'app/client/ui2018/modals';
import {Computed, dom, DomElementArg, IInputOptions, input, makeTestId, Observable, styled} from 'grainjs';
import { Computed, dom, DomElementArg, makeTestId, Observable, styled } from 'grainjs';
import {IOpenController, setPopupToCreateDom} from 'popweasel';
import { descriptionInfoTooltip } from './tooltips';
import { autoGrow } from './forms';
import { cssInput, cssLabel, cssRenamePopup, cssTextArea } from 'app/client/ui/RenamePopupStyles';
const testId = makeTestId('test-widget-title-');
const t = makeT('WidgetTitle');
@@ -19,17 +22,20 @@ interface WidgetTitleOptions {
export function buildWidgetTitle(vs: ViewSectionRec, options: WidgetTitleOptions, ...args: DomElementArg[]) {
const title = Computed.create(null, use => use(vs.titleDef));
return buildRenameWidget(vs, title, options, dom.autoDispose(title), ...args);
const description = Computed.create(null, use => use(vs.description));
return buildRenameWidget(vs, title, description, options, dom.autoDispose(title), ...args);
}
export function buildTableName(vs: ViewSectionRec, ...args: DomElementArg[]) {
const title = Computed.create(null, use => use(use(vs.table).tableNameDef));
return buildRenameWidget(vs, title, { widgetNameHidden: true }, dom.autoDispose(title), ...args);
const description = Computed.create(null, use => use(vs.description));
return buildRenameWidget(vs, title, description, { widgetNameHidden: true }, dom.autoDispose(title), ...args);
}
export function buildRenameWidget(
vs: ViewSectionRec,
title: Observable<string>,
description: Observable<string>,
options: WidgetTitleOptions,
...args: DomElementArg[]) {
return cssTitleContainer(
@@ -48,6 +54,9 @@ export function buildRenameWidget(
},
dom.on('click', (ev) => { ev.stopPropagation(); ev.preventDefault(); }),
),
dom.maybe(description, () => [
descriptionInfoTooltip(description.get(), "widget")
]),
...args
);
}
@@ -69,11 +78,19 @@ function buildWidgetRenamePopup(ctrl: IOpenController, vs: ViewSectionRec, optio
// - when widget title is set, shows just a text to override it.
const inputWidgetPlaceholder = !vs.title.peek() ? t("Override widget title") : vs.defaultWidgetTitle.peek();
// User input for widget description
const inputWidgetDesc = Observable.create(ctrl, vs.description.peek() ?? '');
const disableSave = Computed.create(ctrl, (use) => {
const newTableName = use(inputTableName)?.trim() ?? '';
const newWidgetTitle = use(inputWidgetTitle)?.trim() ?? '';
const newWidgetDesc = use(inputWidgetDesc)?.trim() ?? '';
// Can't save when table name is empty or there wasn't any change.
return !newTableName || (newTableName === tableName && newWidgetTitle === use(vs.title));
return !newTableName || (
newTableName === tableName
&& newWidgetTitle === use(vs.title)
&& newWidgetDesc === use(vs.description)
);
});
const modalCtl = ModalControl.create(ctrl, () => ctrl.close());
@@ -99,10 +116,20 @@ function buildWidgetRenamePopup(ctrl: IOpenController, vs: ViewSectionRec, optio
await vs.title.saveOnly(newTitle);
}
};
const doSave = modalCtl.doWork(() => Promise.all([
const saveWidgetDesc = async () => {
const newWidgetDesc = inputWidgetDesc.get().trim() ?? '';
// If value was changed.
if (newWidgetDesc !== vs.description.peek()) {
await vs.description.saveOnly(newWidgetDesc);
}
};
const save = () => Promise.all([
saveTableName(),
saveWidgetTitle()
]), {close: true});
saveWidgetTitle(),
saveWidgetDesc()
]);
function initialFocus() {
const isRawView = !widgetInput;
@@ -122,18 +149,72 @@ function buildWidgetRenamePopup(ctrl: IOpenController, vs: ViewSectionRec, optio
}
}
// Build actual dom that looks like:
// DATA TABLE NAME
// [input]
// WIDGET TITLE
// [input]
// [Save] [Cancel]
// When the popup is closing we will save everything, unless the user has pressed the cancel button.
let cancelled = false;
// Function to close the popup with saving.
const close = () => ctrl.close();
// Function to close the popup without saving.
const cancel = () => { cancelled = true; close(); };
// Function that is called when popup is closed.
const onClose = () => {
if (!cancelled) {
save().catch(reportError);
}
};
// User interface for the popup.
const myCommands = {
// Escape key: just close the popup.
cancel,
// Enter key: save and close the popup, unless the description input is focused.
// There is also a variant for Ctrl+Enter which will always save.
accept: () => {
// Enters are ignored in the description input (unless ctrl is pressed)
if (document.activeElement === descInput) { return true; }
close();
},
// ArrowUp
cursorUp: () => {
// moves focus to the widget title input if it is already at the top of widget description
if (document.activeElement === descInput && descInput?.selectionStart === 0) {
widgetInput?.focus();
widgetInput?.select();
} else if (document.activeElement === widgetInput) {
tableInput?.focus();
tableInput?.select();
} else {
return true;
}
},
// ArrowDown
cursorDown: () => {
if (document.activeElement === tableInput) {
widgetInput?.focus();
widgetInput?.select();
} else if (document.activeElement === widgetInput) {
descInput?.focus();
descInput?.select();
} else {
return true;
}
}
};
// Create this group and attach it to the popup and all inputs.
const commandGroup = commands.createGroup({ ...myCommands }, ctrl, true);
let tableInput: HTMLInputElement|undefined;
let widgetInput: HTMLInputElement|undefined;
let descInput: HTMLTextAreaElement | undefined;
return cssRenamePopup(
// Create a FocusLayer to keep focus in this popup while it's active, and prevent keyboard
// shortcuts from being seen by the view underneath.
elem => { FocusLayer.create(ctrl, {defaultFocusElem: elem, pauseMousetrap: true}); },
elem => { FocusLayer.create(ctrl, { defaultFocusElem: elem, pauseMousetrap: false }); },
dom.onDispose(onClose),
dom.autoDispose(commandGroup),
testId('popup'),
dom.cls(menuCssClass),
dom.maybe(!options.tableNameHidden, () => [
@@ -144,30 +225,41 @@ function buildWidgetRenamePopup(ctrl: IOpenController, vs: ViewSectionRec, optio
inputTableName,
updateOnKey,
{disabled: isSummary, placeholder: t("Provide a table name")},
testId('table-name-input')
testId('table-name-input'),
commandGroup.attach(),
),
]),
dom.maybe(!options.widgetNameHidden, () => [
cssLabel(t("WIDGET TITLE")),
widgetInput = cssInput(inputWidgetTitle, updateOnKey, {placeholder: inputWidgetPlaceholder},
testId('section-name-input')
testId('section-name-input'),
commandGroup.attach(),
),
]),
cssLabel(t("WIDGET DESCRIPTION")),
descInput = cssTextArea(inputWidgetDesc, updateOnKey,
testId('section-description-input'),
commandGroup.attach(),
autoGrow(inputWidgetDesc),
),
cssButtons(
primaryButton(t("Save"),
dom.on('click', doSave),
dom.on('click', close),
dom.boolAttr('disabled', use => use(disableSave) || use(modalCtl.workInProgress)),
testId('save'),
),
basicButton(t("Cancel"),
testId('cancel'),
dom.on('click', () => modalCtl.close())
dom.on('click', cancel)
),
),
dom.onKeyDown({
Escape: () => modalCtl.close(),
// On enter save or cancel - depending on the change.
Enter: () => disableSave.get() ? modalCtl.close() : doSave(),
Enter$: e => {
if (e.ctrlKey || e.metaKey) {
close();
return false;
}
}
}),
elem => { setTimeout(initialFocus, 0); },
);
@@ -180,6 +272,10 @@ const cssTitleContainer = styled('div', `
flex: 1 1 0px;
min-width: 0px;
display: flex;
.info_toggle_icon {
width: 13px;
height: 13px;
}
`);
const cssTitle = styled('div', `
@@ -199,26 +295,6 @@ const cssTitle = styled('div', `
}
`);
const cssRenamePopup = styled('div', `
display: flex;
flex-direction: column;
min-width: 280px;
padding: 16px;
background-color: ${theme.popupBg};
border-radius: 2px;
outline: none;
`);
const cssLabel = styled('label', `
color: ${theme.text};
font-size: ${vars.xsmallFontSize};
font-weight: ${vars.bigControlTextWeight};
margin: 0 0 8px 0;
&:not(:first-child) {
margin-top: 16px;
}
`);
const cssButtons = styled('div', `
display: flex;
margin-top: 16px;
@@ -226,29 +302,3 @@ const cssButtons = styled('div', `
margin-left: 8px;
}
`);
const cssInputWithIcon = styled('div', `
position: relative;
display: flex;
flex-direction: column;
`);
const cssInput = styled((
obs: Observable<string>,
opts: IInputOptions,
...args) => input(obs, opts, cssTextInput.cls(''), ...args), `
text-overflow: ellipsis;
color: ${theme.inputFg};
background-color: transparent;
&:disabled {
color: ${theme.inputDisabledFg};
background-color: ${theme.inputDisabledBg};
pointer-events: none;
}
&::placeholder {
color: ${theme.inputPlaceholderFg};
}
.${cssInputWithIcon.className} > &:disabled {
padding-right: 28px;
}
`);

View File

@@ -347,15 +347,18 @@ export function withInfoTooltip(
}
/**
* Renders an column info icon that shows a tooltip with the specified `content` on click.
* Renders an description info icon that shows a tooltip with the specified `content` on click.
*/
export function columnInfoTooltip(content: DomContents, menuOptions?: IMenuOptions, ...domArgs: DomElementArg[]) {
return cssColumnInfoTooltipButton(
export function descriptionInfoTooltip(
content: DomContents,
testPrefix: string,
...domArgs: DomElementArg[]) {
return cssDescriptionInfoTooltipButton(
icon('Info', dom.cls("info_toggle_icon")),
testId('column-info-tooltip'),
testId(`${testPrefix}-info-tooltip`),
dom.on('mousedown', (e) => e.stopPropagation()),
dom.on('click', (e) => e.stopPropagation()),
hoverTooltip(() => cssColumnInfoTooltip(content, testId('column-info-tooltip-popup')), {
hoverTooltip(() => cssDescriptionInfoTooltip(content, testId(`${testPrefix}-info-tooltip-popup`)), {
closeDelay: 200,
key: 'columnDescription',
openOnClick: true,
@@ -365,7 +368,8 @@ export function withInfoTooltip(
);
}
const cssColumnInfoTooltip = styled('div', `
const cssDescriptionInfoTooltip = styled('div', `
white-space: pre-wrap;
text-align: left;
text-overflow: ellipsis;
@@ -373,7 +377,7 @@ const cssColumnInfoTooltip = styled('div', `
max-width: min(500px, calc(100vw - 80px)); /* can't use 100%, 500px and 80px are picked by hand */
`);
const cssColumnInfoTooltipButton = styled('div', `
const cssDescriptionInfoTooltipButton = styled('div', `
cursor: pointer;
--icon-color: ${theme.infoButtonFg};
border-radius: 50%;

View File

@@ -4,7 +4,7 @@ import { GristObjCode } from "app/plugin/GristData";
// tslint:disable:object-literal-key-quotes
export const SCHEMA_VERSION = 37;
export const SCHEMA_VERSION = 38;
export const schema = {
@@ -104,6 +104,7 @@ export const schema = {
parentId : "Ref:_grist_Views",
parentKey : "Text",
title : "Text",
description : "Text",
defaultWidth : "Int",
borderWidth : "Int",
theme : "Text",
@@ -311,6 +312,7 @@ export interface SchemaTypes {
parentId: number;
parentKey: string;
title: string;
description: string;
defaultWidth: number;
borderWidth: number;
theme: string;

View File

@@ -6,7 +6,7 @@ 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,'','','',37,'','');
INSERT INTO _grist_DocInfo VALUES(1,'','','',38,'','');
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 '', "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);
@@ -17,7 +17,7 @@ CREATE TABLE IF NOT EXISTS "_grist_TabItems" (id INTEGER PRIMARY KEY, "tableRef"
CREATE TABLE IF NOT EXISTS "_grist_TabBar" (id INTEGER PRIMARY KEY, "viewRef" INTEGER DEFAULT 0, "tabPos" NUMERIC DEFAULT 1e999);
CREATE TABLE IF NOT EXISTS "_grist_Pages" (id INTEGER PRIMARY KEY, "viewRef" INTEGER DEFAULT 0, "indentation" INTEGER DEFAULT 0, "pagePos" NUMERIC DEFAULT 1e999);
CREATE TABLE IF NOT EXISTS "_grist_Views" (id INTEGER PRIMARY KEY, "name" TEXT DEFAULT '', "type" TEXT DEFAULT '', "layoutSpec" TEXT DEFAULT '');
CREATE TABLE IF NOT EXISTS "_grist_Views_section" (id INTEGER PRIMARY KEY, "tableRef" INTEGER DEFAULT 0, "parentId" INTEGER DEFAULT 0, "parentKey" TEXT DEFAULT '', "title" TEXT DEFAULT '', "defaultWidth" INTEGER DEFAULT 0, "borderWidth" INTEGER DEFAULT 0, "theme" TEXT DEFAULT '', "options" TEXT DEFAULT '', "chartType" TEXT DEFAULT '', "layoutSpec" TEXT DEFAULT '', "filterSpec" TEXT DEFAULT '', "sortColRefs" TEXT DEFAULT '', "linkSrcSectionRef" INTEGER DEFAULT 0, "linkSrcColRef" INTEGER DEFAULT 0, "linkTargetColRef" INTEGER DEFAULT 0, "embedId" TEXT DEFAULT '', "rules" TEXT DEFAULT NULL);
CREATE TABLE IF NOT EXISTS "_grist_Views_section" (id INTEGER PRIMARY KEY, "tableRef" INTEGER DEFAULT 0, "parentId" INTEGER DEFAULT 0, "parentKey" TEXT DEFAULT '', "title" TEXT DEFAULT '', "description" TEXT DEFAULT '', "defaultWidth" INTEGER DEFAULT 0, "borderWidth" INTEGER DEFAULT 0, "theme" TEXT DEFAULT '', "options" TEXT DEFAULT '', "chartType" TEXT DEFAULT '', "layoutSpec" TEXT DEFAULT '', "filterSpec" TEXT DEFAULT '', "sortColRefs" TEXT DEFAULT '', "linkSrcSectionRef" INTEGER DEFAULT 0, "linkSrcColRef" INTEGER DEFAULT 0, "linkTargetColRef" INTEGER DEFAULT 0, "embedId" TEXT DEFAULT '', "rules" TEXT DEFAULT NULL);
CREATE TABLE IF NOT EXISTS "_grist_Views_section_field" (id INTEGER PRIMARY KEY, "parentId" INTEGER DEFAULT 0, "parentPos" NUMERIC DEFAULT 1e999, "colRef" INTEGER DEFAULT 0, "width" INTEGER DEFAULT 0, "widgetOptions" TEXT DEFAULT '', "displayCol" INTEGER DEFAULT 0, "visibleCol" INTEGER DEFAULT 0, "filter" TEXT DEFAULT '', "rules" TEXT DEFAULT NULL);
CREATE TABLE IF NOT EXISTS "_grist_Validations" (id INTEGER PRIMARY KEY, "formula" TEXT DEFAULT '', "name" TEXT DEFAULT '', "tableRef" INTEGER DEFAULT 0);
CREATE TABLE IF NOT EXISTS "_grist_REPL_Hist" (id INTEGER PRIMARY KEY, "code" TEXT DEFAULT '', "outputText" TEXT DEFAULT '', "errorText" TEXT DEFAULT '');
@@ -43,7 +43,7 @@ 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,'','','',37,'','');
INSERT INTO _grist_DocInfo VALUES(1,'','','',38,'','');
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 '', "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);
@@ -62,9 +62,9 @@ CREATE TABLE IF NOT EXISTS "_grist_Pages" (id INTEGER PRIMARY KEY, "viewRef" INT
INSERT INTO _grist_Pages VALUES(1,1,0,1);
CREATE TABLE IF NOT EXISTS "_grist_Views" (id INTEGER PRIMARY KEY, "name" TEXT DEFAULT '', "type" TEXT DEFAULT '', "layoutSpec" TEXT DEFAULT '');
INSERT INTO _grist_Views VALUES(1,'Table1','raw_data','');
CREATE TABLE IF NOT EXISTS "_grist_Views_section" (id INTEGER PRIMARY KEY, "tableRef" INTEGER DEFAULT 0, "parentId" INTEGER DEFAULT 0, "parentKey" TEXT DEFAULT '', "title" TEXT DEFAULT '', "defaultWidth" INTEGER DEFAULT 0, "borderWidth" INTEGER DEFAULT 0, "theme" TEXT DEFAULT '', "options" TEXT DEFAULT '', "chartType" TEXT DEFAULT '', "layoutSpec" TEXT DEFAULT '', "filterSpec" TEXT DEFAULT '', "sortColRefs" TEXT DEFAULT '', "linkSrcSectionRef" INTEGER DEFAULT 0, "linkSrcColRef" INTEGER DEFAULT 0, "linkTargetColRef" INTEGER DEFAULT 0, "embedId" TEXT DEFAULT '', "rules" TEXT DEFAULT NULL);
INSERT INTO _grist_Views_section VALUES(1,1,1,'record','',100,1,'','','','','','[]',0,0,0,'',NULL);
INSERT INTO _grist_Views_section VALUES(2,1,0,'record','',100,1,'','','','','','',0,0,0,'',NULL);
CREATE TABLE IF NOT EXISTS "_grist_Views_section" (id INTEGER PRIMARY KEY, "tableRef" INTEGER DEFAULT 0, "parentId" INTEGER DEFAULT 0, "parentKey" TEXT DEFAULT '', "title" TEXT DEFAULT '', "description" TEXT DEFAULT '', "defaultWidth" INTEGER DEFAULT 0, "borderWidth" INTEGER DEFAULT 0, "theme" TEXT DEFAULT '', "options" TEXT DEFAULT '', "chartType" TEXT DEFAULT '', "layoutSpec" TEXT DEFAULT '', "filterSpec" TEXT DEFAULT '', "sortColRefs" TEXT DEFAULT '', "linkSrcSectionRef" INTEGER DEFAULT 0, "linkSrcColRef" INTEGER DEFAULT 0, "linkTargetColRef" INTEGER DEFAULT 0, "embedId" TEXT DEFAULT '', "rules" TEXT DEFAULT NULL);
INSERT INTO _grist_Views_section VALUES(1,1,1,'record','','',100,1,'','','','','','[]',0,0,0,'',NULL);
INSERT INTO _grist_Views_section VALUES(2,1,0,'record','','',100,1,'','','','','','',0,0,0,'',NULL);
CREATE TABLE IF NOT EXISTS "_grist_Views_section_field" (id INTEGER PRIMARY KEY, "parentId" INTEGER DEFAULT 0, "parentPos" NUMERIC DEFAULT 1e999, "colRef" INTEGER DEFAULT 0, "width" INTEGER DEFAULT 0, "widgetOptions" TEXT DEFAULT '', "displayCol" INTEGER DEFAULT 0, "visibleCol" INTEGER DEFAULT 0, "filter" TEXT DEFAULT '', "rules" TEXT DEFAULT NULL);
INSERT INTO _grist_Views_section_field VALUES(1,1,1,2,0,'',0,0,'',NULL);
INSERT INTO _grist_Views_section_field VALUES(2,1,2,3,0,'',0,0,'',NULL);