mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) Record Cards
Summary: Adds a new Record Card view section to each non-summary table, which can be from opened from various parts of the Grist UI to view and edit records in a popup card view. Work is still ongoing, so the feature is locked away behind a flag; follow-up work is planned to finish up the implementation and add end-to-end tests. Test Plan: Python and server tests. Browser tests will be included in a follow-up. Reviewers: jarek, paulfitz Reviewed By: jarek Subscribers: paulfitz Differential Revision: https://phab.getgrist.com/D4114
This commit is contained in:
49
app/client/ui/CardContextMenu.ts
Normal file
49
app/client/ui/CardContextMenu.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { allCommands } from 'app/client/components/commands';
|
||||
import { makeT } from 'app/client/lib/localization';
|
||||
import { menuDivider, menuItemCmd } from 'app/client/ui2018/menus';
|
||||
import { dom } from 'grainjs';
|
||||
|
||||
const t = makeT('CardContextMenu');
|
||||
|
||||
export interface ICardContextMenu {
|
||||
disableInsert: boolean;
|
||||
disableDelete: boolean;
|
||||
isViewSorted: boolean;
|
||||
numRows: number;
|
||||
}
|
||||
|
||||
export function CardContextMenu({
|
||||
disableInsert,
|
||||
disableDelete,
|
||||
isViewSorted,
|
||||
numRows
|
||||
}: ICardContextMenu) {
|
||||
const result: Element[] = [];
|
||||
if (isViewSorted) {
|
||||
result.push(
|
||||
menuItemCmd(allCommands.insertRecordAfter, t("Insert card"),
|
||||
dom.cls('disabled', disableInsert)),
|
||||
);
|
||||
} else {
|
||||
result.push(
|
||||
menuItemCmd(allCommands.insertRecordBefore, t("Insert card above"),
|
||||
dom.cls('disabled', disableInsert)),
|
||||
menuItemCmd(allCommands.insertRecordAfter, t("Insert card below"),
|
||||
dom.cls('disabled', disableInsert)),
|
||||
);
|
||||
}
|
||||
result.push(
|
||||
menuItemCmd(allCommands.duplicateRows, t("Duplicate card"),
|
||||
dom.cls('disabled', disableInsert || numRows === 0)),
|
||||
);
|
||||
result.push(
|
||||
menuDivider(),
|
||||
menuItemCmd(allCommands.deleteRecords, t("Delete card"),
|
||||
dom.cls('disabled', disableDelete)),
|
||||
);
|
||||
result.push(
|
||||
menuDivider(),
|
||||
menuItemCmd(allCommands.copyLink, t("Copy anchor link"))
|
||||
);
|
||||
return result;
|
||||
}
|
||||
@@ -2,32 +2,36 @@ import { allCommands } from 'app/client/components/commands';
|
||||
import { makeT } from 'app/client/lib/localization';
|
||||
import { menuDivider, menuItemCmd } from 'app/client/ui2018/menus';
|
||||
import { IMultiColumnContextMenu } from 'app/client/ui/GridViewMenus';
|
||||
import { IRowContextMenu } from 'app/client/ui/RowContextMenu';
|
||||
import { COMMENTS } from 'app/client/models/features';
|
||||
import { dom } from 'grainjs';
|
||||
|
||||
const t = makeT('CellContextMenu');
|
||||
|
||||
export function CellContextMenu(rowOptions: IRowContextMenu, colOptions: IMultiColumnContextMenu) {
|
||||
export interface ICellContextMenu {
|
||||
disableInsert: boolean;
|
||||
disableDelete: boolean;
|
||||
isViewSorted: boolean;
|
||||
numRows: number;
|
||||
}
|
||||
|
||||
const { disableInsert, disableDelete, isViewSorted } = rowOptions;
|
||||
const { disableModify, isReadonly } = colOptions;
|
||||
export function CellContextMenu(cellOptions: ICellContextMenu, colOptions: IMultiColumnContextMenu) {
|
||||
|
||||
const { disableInsert, disableDelete, isViewSorted, numRows } = cellOptions;
|
||||
const { numColumns, disableModify, isReadonly, isFiltered } = colOptions;
|
||||
|
||||
// disableModify is true if the column is a summary column or is being transformed.
|
||||
// isReadonly is true for readonly mode.
|
||||
const disableForReadonlyColumn = dom.cls('disabled', Boolean(disableModify) || isReadonly);
|
||||
const disableForReadonlyView = dom.cls('disabled', isReadonly);
|
||||
|
||||
const numCols: number = colOptions.numColumns;
|
||||
const nameClearColumns = colOptions.isFiltered ?
|
||||
t("Reset {{count}} entire columns", {count: numCols}) :
|
||||
t("Reset {{count}} columns", {count: numCols});
|
||||
const nameDeleteColumns = t("Delete {{count}} columns", {count: numCols});
|
||||
const nameClearColumns = isFiltered ?
|
||||
t("Reset {{count}} entire columns", {count: numColumns}) :
|
||||
t("Reset {{count}} columns", {count: numColumns});
|
||||
const nameDeleteColumns = t("Delete {{count}} columns", {count: numColumns});
|
||||
|
||||
const numRows: number = rowOptions.numRows;
|
||||
const nameDeleteRows = t("Delete {{count}} rows", {count: numRows});
|
||||
|
||||
const nameClearCells = (numRows > 1 || numCols > 1) ? t("Clear values") : t("Clear cell");
|
||||
const nameClearCells = (numRows > 1 || numColumns > 1) ? t("Clear values") : t("Clear cell");
|
||||
|
||||
const result: Array<Element|null> = [];
|
||||
|
||||
@@ -42,13 +46,13 @@ export function CellContextMenu(rowOptions: IRowContextMenu, colOptions: IMultiC
|
||||
menuItemCmd(allCommands.clearColumns, nameClearColumns, disableForReadonlyColumn),
|
||||
|
||||
...(
|
||||
(numCols > 1 || numRows > 1) ? [] : [
|
||||
(numColumns > 1 || numRows > 1) ? [] : [
|
||||
menuDivider(),
|
||||
menuItemCmd(allCommands.copyLink, t("Copy anchor link")),
|
||||
menuDivider(),
|
||||
menuItemCmd(allCommands.filterByThisCellValue, t("Filter by this value")),
|
||||
menuItemCmd(allCommands.openDiscussion, t('Comment'), dom.cls('disabled', (
|
||||
isReadonly || numRows === 0 || numCols === 0
|
||||
isReadonly || numRows === 0 || numColumns === 0
|
||||
)), dom.hide(use => !use(COMMENTS()))) //TODO: i18next
|
||||
]
|
||||
),
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {allCommands} from 'app/client/components/commands';
|
||||
import {makeT} from 'app/client/lib/localization';
|
||||
import {IRowContextMenu} from 'app/client/ui/RowContextMenu';
|
||||
import {menuDivider, menuItemCmd} from 'app/client/ui2018/menus';
|
||||
import {dom} from 'grainjs';
|
||||
|
||||
@@ -11,7 +10,7 @@ export interface IFieldContextMenu {
|
||||
isReadonly: boolean;
|
||||
}
|
||||
|
||||
export function FieldContextMenu(_rowOptions: IRowContextMenu, fieldOptions: IFieldContextMenu) {
|
||||
export function FieldContextMenu(fieldOptions: IFieldContextMenu) {
|
||||
const {disableModify, isReadonly} = fieldOptions;
|
||||
const disableForReadonlyColumn = dom.cls('disabled', disableModify || isReadonly);
|
||||
return [
|
||||
|
||||
@@ -86,7 +86,7 @@ function removeView(activeDoc: GristDoc, viewId: number, pageName: string) {
|
||||
const docData = activeDoc.docData;
|
||||
// Create a set with tables on other pages (but not on this one).
|
||||
const tablesOnOtherViews = new Set(activeDoc.docModel.viewSections.rowModels
|
||||
.filter(vs => !vs.isRaw.peek() && vs.parentId.peek() !== viewId)
|
||||
.filter(vs => !vs.isRaw.peek() && !vs.isRecordCard.peek() && vs.parentId.peek() !== viewId)
|
||||
.map(vs => vs.tableRef.peek()));
|
||||
|
||||
// Check if this page is a last page for some tables.
|
||||
|
||||
@@ -356,7 +356,10 @@ export class RightPanel extends Disposable {
|
||||
dom.maybe(this._validSection, (activeSection) => (
|
||||
buildConfigContainer(
|
||||
subTab === 'widget' ? dom.create(this._buildPageWidgetConfig.bind(this), activeSection) :
|
||||
subTab === 'sortAndFilter' ? dom.create(this._buildPageSortFilterConfig.bind(this)) :
|
||||
subTab === 'sortAndFilter' ? [
|
||||
dom.create(this._buildPageSortFilterConfig.bind(this)),
|
||||
cssConfigContainer.cls('-disabled', activeSection.isRecordCard),
|
||||
] :
|
||||
subTab === 'data' ? dom.create(this._buildPageDataConfig.bind(this), activeSection) :
|
||||
null
|
||||
)
|
||||
@@ -397,33 +400,35 @@ export class RightPanel extends Disposable {
|
||||
|
||||
return dom.maybe(viewConfigTab, (vct) => [
|
||||
this._disableIfReadonly(),
|
||||
cssLabel(dom.text(use => use(activeSection.isRaw) ? t("DATA TABLE NAME") : t("WIDGET TITLE")),
|
||||
dom.style('margin-bottom', '14px'),
|
||||
),
|
||||
cssRow(cssTextInput(
|
||||
Computed.create(owner, (use) => use(activeSection.titleDef)),
|
||||
val => activeSection.titleDef.saveOnly(val),
|
||||
dom.boolAttr('disabled', use => {
|
||||
const isRawTable = use(activeSection.isRaw);
|
||||
const isSummaryTable = use(use(activeSection.table).summarySourceTable) !== 0;
|
||||
return isRawTable && isSummaryTable;
|
||||
}),
|
||||
testId('right-widget-title')
|
||||
)),
|
||||
dom.maybe(use => !use(activeSection.isRecordCard), () => [
|
||||
cssLabel(dom.text(use => use(activeSection.isRaw) ? t("DATA TABLE NAME") : t("WIDGET TITLE")),
|
||||
dom.style('margin-bottom', '14px'),
|
||||
),
|
||||
cssRow(cssTextInput(
|
||||
Computed.create(owner, (use) => use(activeSection.titleDef)),
|
||||
val => activeSection.titleDef.saveOnly(val),
|
||||
dom.boolAttr('disabled', use => {
|
||||
const isRawTable = use(activeSection.isRaw);
|
||||
const isSummaryTable = use(use(activeSection.table).summarySourceTable) !== 0;
|
||||
return isRawTable && isSummaryTable;
|
||||
}),
|
||||
testId('right-widget-title')
|
||||
)),
|
||||
|
||||
cssSection(
|
||||
dom.create(buildDescriptionConfig, activeSection.description, { cursor, "testPrefix": "right-widget" }),
|
||||
),
|
||||
cssSection(
|
||||
dom.create(buildDescriptionConfig, activeSection.description, { cursor, "testPrefix": "right-widget" }),
|
||||
),
|
||||
]),
|
||||
|
||||
dom.maybe(
|
||||
(use) => !use(activeSection.isRaw),
|
||||
(use) => !use(activeSection.isRaw) && !use(activeSection.isRecordCard),
|
||||
() => cssRow(
|
||||
primaryButton(t("Change Widget"), this._createPageWidgetPicker()),
|
||||
cssRow.cls('-top-space')
|
||||
),
|
||||
),
|
||||
|
||||
cssSeparator(),
|
||||
cssSeparator(dom.hide(activeSection.isRecordCard)),
|
||||
|
||||
dom.maybe((use) => ['detail', 'single'].includes(use(this._pageWidgetType)!), () => [
|
||||
cssLabel(t("Theme")),
|
||||
@@ -744,7 +749,7 @@ export class RightPanel extends Disposable {
|
||||
dom.hide((use) => !use(use(table).summarySourceTable)),
|
||||
),
|
||||
|
||||
dom.maybe((use) => !use(activeSection.isRaw), () =>
|
||||
dom.maybe((use) => !use(activeSection.isRaw) && !use(activeSection.isRecordCard), () =>
|
||||
cssButtonRow(primaryButton(t("Edit Data Selection"), this._createPageWidgetPicker(),
|
||||
testId('pwc-editDataSelection')),
|
||||
dom.maybe(
|
||||
@@ -764,9 +769,9 @@ export class RightPanel extends Disposable {
|
||||
dom.maybe(viewConfigTab, (vct) => cssRow(
|
||||
dom('div', vct._buildAdvancedSettingsDom()),
|
||||
)),
|
||||
cssSeparator(),
|
||||
|
||||
dom.maybe((use) => !use(activeSection.isRaw), () => [
|
||||
dom.maybe((use) => !use(activeSection.isRaw) && !use(activeSection.isRecordCard), () => [
|
||||
cssSeparator(),
|
||||
cssLabel(t("SELECT BY")),
|
||||
cssRow(
|
||||
dom.update(
|
||||
@@ -1033,6 +1038,10 @@ const cssConfigContainer = styled('div.test-config-container', `
|
||||
& .fieldbuilder_settings {
|
||||
margin: 16px 0 0 0;
|
||||
}
|
||||
&-disabled {
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
}
|
||||
`);
|
||||
|
||||
const cssDataLabel = styled('div', `
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { allCommands } from 'app/client/components/commands';
|
||||
import { makeT } from 'app/client/lib/localization';
|
||||
import { menuDivider, menuItemCmd } from 'app/client/ui2018/menus';
|
||||
import { RECORD_CARDS } from 'app/client/models/features';
|
||||
import { menuDivider, menuIcon, menuItemCmd, menuItemCmdLabel } from 'app/client/ui2018/menus';
|
||||
import { dom } from 'grainjs';
|
||||
|
||||
const t = makeT('RowContextMenu');
|
||||
@@ -8,12 +9,29 @@ const t = makeT('RowContextMenu');
|
||||
export interface IRowContextMenu {
|
||||
disableInsert: boolean;
|
||||
disableDelete: boolean;
|
||||
disableShowRecordCard: boolean;
|
||||
isViewSorted: boolean;
|
||||
numRows: number;
|
||||
}
|
||||
|
||||
export function RowContextMenu({ disableInsert, disableDelete, isViewSorted, numRows }: IRowContextMenu) {
|
||||
export function RowContextMenu({
|
||||
disableInsert,
|
||||
disableDelete,
|
||||
disableShowRecordCard,
|
||||
isViewSorted,
|
||||
numRows
|
||||
}: IRowContextMenu) {
|
||||
const result: Element[] = [];
|
||||
if (RECORD_CARDS() && numRows === 1) {
|
||||
result.push(
|
||||
menuItemCmd(
|
||||
allCommands.viewAsCard,
|
||||
() => menuItemCmdLabel(menuIcon('TypeCard'), t("View as card")),
|
||||
dom.cls('disabled', disableShowRecordCard),
|
||||
),
|
||||
menuDivider(),
|
||||
);
|
||||
}
|
||||
if (isViewSorted) {
|
||||
// When the view is sorted, any newly added records get shifts instantly at the top or
|
||||
// bottom. It could be very confusing for users who might expect the record to stay above or
|
||||
|
||||
@@ -16,7 +16,7 @@ import {buildUrlId, isFeatureEnabled, parseUrlId} from 'app/common/gristUrls';
|
||||
import * as roles from 'app/common/roles';
|
||||
import {Document} from 'app/common/UserAPI';
|
||||
import {dom, DomContents, styled} from 'grainjs';
|
||||
import {MenuCreateFunc} from 'popweasel';
|
||||
import {cssMenuItem, MenuCreateFunc} from 'popweasel';
|
||||
import {makeT} from 'app/client/lib/localization';
|
||||
|
||||
const t = makeT('ShareMenu');
|
||||
@@ -378,9 +378,12 @@ const cssMenuIconLink = styled('a', `
|
||||
padding: 8px 24px;
|
||||
--icon-color: ${theme.controlFg};
|
||||
|
||||
&:hover {
|
||||
background-color: ${theme.hover};
|
||||
--icon-color: ${theme.controlHoverFg};
|
||||
.${cssMenuItem.className}-sel > & {
|
||||
--icon-color: ${theme.menuItemIconSelectedFg};
|
||||
}
|
||||
|
||||
.${cssMenuItem.className}.disabled & {
|
||||
--icon-color: ${theme.menuItemDisabledFg};
|
||||
}
|
||||
`);
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ export function makeViewLayoutMenu(viewSection: ViewSectionRec, isReadonly: bool
|
||||
|
||||
const showRawData = (use: UseCB) => {
|
||||
return !use(viewSection.isRaw)// Don't show raw data if we're already in raw data.
|
||||
&& !use(viewSection.isRecordCard)
|
||||
&& !isSinglePage // Don't show raw data in single page mode.
|
||||
;
|
||||
};
|
||||
@@ -88,20 +89,22 @@ export function makeViewLayoutMenu(viewSection: ViewSectionRec, isReadonly: bool
|
||||
dom.maybe(!isSinglePage, () => [
|
||||
menuDivider(),
|
||||
menuItemCmd(allCommands.viewTabOpen, t("Widget options"), testId('widget-options')),
|
||||
menuItemCmd(allCommands.sortFilterTabOpen, t("Advanced Sort & Filter")),
|
||||
menuItemCmd(allCommands.dataSelectionTabOpen, t("Data selection")),
|
||||
menuItemCmd(allCommands.sortFilterTabOpen, t("Advanced Sort & Filter"), dom.hide(viewSection.isRecordCard)),
|
||||
menuItemCmd(allCommands.dataSelectionTabOpen, t("Data selection"), dom.hide(viewSection.isRecordCard)),
|
||||
]),
|
||||
|
||||
menuDivider(),
|
||||
menuDivider(dom.hide(viewSection.isRecordCard)),
|
||||
dom.maybe((use) => use(viewSection.parentKey) === 'custom' && use(viewSection.hasCustomOptions), () =>
|
||||
menuItemCmd(allCommands.openWidgetConfiguration, t("Open configuration"),
|
||||
testId('section-open-configuration')),
|
||||
),
|
||||
menuItemCmd(allCommands.collapseSection, t("Collapse widget"),
|
||||
dom.cls('disabled', dontCollapseSection()),
|
||||
dom.hide(viewSection.isRecordCard),
|
||||
testId('section-collapse')),
|
||||
menuItemCmd(allCommands.deleteSection, t("Delete widget"),
|
||||
dom.cls('disabled', dontRemoveSection()),
|
||||
dom.hide(viewSection.isRecordCard),
|
||||
testId('section-delete')),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ export function viewSectionMenu(
|
||||
&& use(gristDoc.maximizedSectionId) !== use(viewSection.id) // not in when we are maximized
|
||||
&& use(gristDoc.externalSectionId) !== use(viewSection.id) // not in when we are external
|
||||
&& !use(viewSection.isRaw) // not in raw mode
|
||||
&& !use(viewSection.isRecordCard)
|
||||
&& !use(singleVisible) // not in single section
|
||||
;
|
||||
});
|
||||
@@ -145,6 +146,7 @@ export function viewSectionMenu(
|
||||
ctl.close();
|
||||
}),
|
||||
]}),
|
||||
dom.hide(viewSection.isRecordCard),
|
||||
),
|
||||
cssMenu(
|
||||
testId('viewLayout'),
|
||||
|
||||
@@ -7,7 +7,7 @@ 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, makeTestId, Observable, styled } from 'grainjs';
|
||||
import {IOpenController, setPopupToCreateDom} from 'popweasel';
|
||||
import {IOpenController, IPopupOptions, PopupControl, setPopupToCreateDom} from 'popweasel';
|
||||
import { descriptionInfoTooltip } from './tooltips';
|
||||
import { autoGrow } from './forms';
|
||||
import { cssInput, cssLabel, cssRenamePopup, cssTextArea } from 'app/client/ui/RenamePopupStyles';
|
||||
@@ -18,41 +18,105 @@ const t = makeT('WidgetTitle');
|
||||
interface WidgetTitleOptions {
|
||||
tableNameHidden?: boolean,
|
||||
widgetNameHidden?: boolean,
|
||||
disabled?: boolean,
|
||||
}
|
||||
|
||||
export function buildWidgetTitle(vs: ViewSectionRec, options: WidgetTitleOptions, ...args: DomElementArg[]) {
|
||||
const title = Computed.create(null, use => use(vs.titleDef));
|
||||
const description = Computed.create(null, use => use(vs.description));
|
||||
return buildRenameWidget(vs, title, description, options, dom.autoDispose(title), ...args);
|
||||
return buildRenamableTitle(vs, title, description, options, dom.autoDispose(title), ...args);
|
||||
}
|
||||
|
||||
export function buildTableName(vs: ViewSectionRec, ...args: DomElementArg[]) {
|
||||
interface TableNameOptions {
|
||||
isEditing: Observable<boolean>,
|
||||
disabled?: boolean,
|
||||
}
|
||||
|
||||
export function buildTableName(vs: ViewSectionRec, options: TableNameOptions, ...args: DomElementArg[]) {
|
||||
const title = Computed.create(null, use => use(use(vs.table).tableNameDef));
|
||||
const description = Computed.create(null, use => use(vs.description));
|
||||
return buildRenameWidget(vs, title, description, { widgetNameHidden: true }, dom.autoDispose(title), ...args);
|
||||
return buildRenamableTitle(
|
||||
vs,
|
||||
title,
|
||||
description,
|
||||
{
|
||||
openOnClick: false,
|
||||
widgetNameHidden: true,
|
||||
...options,
|
||||
},
|
||||
dom.autoDispose(title),
|
||||
...args
|
||||
);
|
||||
}
|
||||
|
||||
export function buildRenameWidget(
|
||||
interface RenamableTitleOptions {
|
||||
tableNameHidden?: boolean,
|
||||
widgetNameHidden?: boolean,
|
||||
/** Defaults to true. */
|
||||
openOnClick?: boolean,
|
||||
isEditing?: Observable<boolean>,
|
||||
disabled?: boolean,
|
||||
}
|
||||
|
||||
function buildRenamableTitle(
|
||||
vs: ViewSectionRec,
|
||||
title: Observable<string>,
|
||||
description: Observable<string>,
|
||||
options: WidgetTitleOptions,
|
||||
...args: DomElementArg[]) {
|
||||
options: RenamableTitleOptions,
|
||||
...args: DomElementArg[]
|
||||
) {
|
||||
const {openOnClick = true, disabled = false, isEditing, ...renameTitleOptions} = options;
|
||||
let popupControl: PopupControl | undefined;
|
||||
return cssTitleContainer(
|
||||
cssTitle(
|
||||
testId('text'),
|
||||
dom.text(title),
|
||||
dom.on('click', () => {
|
||||
// The popup doesn't close if `openOnClick` is false and the title is
|
||||
// clicked. Make sure that it does.
|
||||
if (!openOnClick) { popupControl?.close(); }
|
||||
}),
|
||||
// In case titleDef is all blank space, make it visible on hover.
|
||||
cssTitle.cls("-empty", use => !use(title)?.trim()),
|
||||
cssTitle.cls("-open-on-click", openOnClick),
|
||||
cssTitle.cls("-disabled", disabled),
|
||||
elem => {
|
||||
setPopupToCreateDom(elem, ctl => buildWidgetRenamePopup(ctl, vs, options), {
|
||||
if (disabled) { return; }
|
||||
|
||||
// The widget title popup can be configured to open in up to two ways:
|
||||
// 1. When the title is clicked - done by setting `openOnClick` to `true`.
|
||||
// 2. When `isEditing` is set to true - done by setting `isEditing` to `true`.
|
||||
//
|
||||
// Typically, the former should be set. The latter is useful for triggering the
|
||||
// popup from a different part of the UI, like a menu item.
|
||||
const trigger: IPopupOptions['trigger'] = [];
|
||||
if (openOnClick) { trigger.push('click'); }
|
||||
if (isEditing) {
|
||||
trigger.push((_: Element, ctl: PopupControl) => {
|
||||
popupControl = ctl;
|
||||
ctl.autoDispose(isEditing.addListener((editing) => {
|
||||
if (editing) {
|
||||
ctl.open();
|
||||
} else if (!ctl.isDisposed()) {
|
||||
ctl.close();
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
setPopupToCreateDom(elem, ctl => {
|
||||
if (isEditing) {
|
||||
ctl.onDispose(() => isEditing.set(false));
|
||||
}
|
||||
|
||||
return buildRenameTitlePopup(ctl, vs, renameTitleOptions);
|
||||
}, {
|
||||
placement: 'bottom-start',
|
||||
trigger: ['click'],
|
||||
trigger,
|
||||
attach: 'body',
|
||||
boundaries: 'viewport',
|
||||
});
|
||||
},
|
||||
dom.on('click', (ev) => { ev.stopPropagation(); ev.preventDefault(); }),
|
||||
openOnClick ? dom.on('click', (ev) => { ev.stopPropagation(); ev.preventDefault(); }) : null,
|
||||
),
|
||||
dom.maybe(description, () => [
|
||||
descriptionInfoTooltip(description.get(), "widget")
|
||||
@@ -61,7 +125,7 @@ export function buildRenameWidget(
|
||||
);
|
||||
}
|
||||
|
||||
function buildWidgetRenamePopup(ctrl: IOpenController, vs: ViewSectionRec, options: WidgetTitleOptions) {
|
||||
function buildRenameTitlePopup(ctrl: IOpenController, vs: ViewSectionRec, options: RenamableTitleOptions) {
|
||||
const tableRec = vs.table.peek();
|
||||
// If the table is a summary table.
|
||||
const isSummary = Boolean(tableRec.summarySourceTable.peek());
|
||||
@@ -279,14 +343,16 @@ const cssTitleContainer = styled('div', `
|
||||
`);
|
||||
|
||||
const cssTitle = styled('div', `
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
border-radius: 3px;
|
||||
margin: -4px;
|
||||
padding: 4px;
|
||||
text-overflow: ellipsis;
|
||||
align-self: start;
|
||||
&:hover {
|
||||
&-open-on-click:not(&-disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
&-open-on-click:not(&-disabled):hover {
|
||||
background-color: ${theme.hover};
|
||||
}
|
||||
&-empty {
|
||||
|
||||
Reference in New Issue
Block a user