mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) custom widget appear as build-in widget
Summary: Added boilerplate code needed to create new wigets in "Add new" menu, that are wrapped around existing custom widgets. More details can be found here: https://grist.quip.com/larhAGRKyl6Z/Custom-widgets-in-Add-Widget-menu Test Plan: nbowser tests added to verify if item in menu exits, if widget is rendered, and right side menu has widget selection and read access selection hided. Reviewers: georgegevoian Reviewed By: georgegevoian Differential Revision: https://phab.getgrist.com/D3994
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import {allCommands} from 'app/client/components/commands';
|
||||
import {GristDoc} from 'app/client/components/GristDoc';
|
||||
import * as kf from 'app/client/lib/koForm';
|
||||
import {makeT} from 'app/client/lib/localization';
|
||||
import {ColumnToMapImpl} from 'app/client/models/ColumnToMap';
|
||||
import {ColumnRec, ViewSectionRec} from 'app/client/models/DocModel';
|
||||
import {reportError} from 'app/client/models/errors';
|
||||
@@ -16,10 +17,19 @@ import {cssLink} from 'app/client/ui2018/links';
|
||||
import {IOptionFull, menu, menuItem, menuText, select} from 'app/client/ui2018/menus';
|
||||
import {AccessLevel, ICustomWidget, isSatisfied} from 'app/common/CustomWidget';
|
||||
import {GristLoadConfig} from 'app/common/gristUrls';
|
||||
import {nativeCompare, unwrap} from 'app/common/gutil';
|
||||
import {bundleChanges, Computed, Disposable, dom, fromKo, makeTestId,
|
||||
MultiHolder, Observable, styled, UseCBOwner} from 'grainjs';
|
||||
import {makeT} from 'app/client/lib/localization';
|
||||
import {unwrap} from 'app/common/gutil';
|
||||
import {
|
||||
bundleChanges,
|
||||
Computed,
|
||||
Disposable,
|
||||
dom,
|
||||
fromKo,
|
||||
makeTestId,
|
||||
MultiHolder,
|
||||
Observable,
|
||||
styled,
|
||||
UseCBOwner
|
||||
} from 'grainjs';
|
||||
|
||||
const t = makeT('CustomSectionConfig');
|
||||
|
||||
@@ -165,8 +175,7 @@ class ColumnListPicker extends Disposable {
|
||||
const columns = use(this._section.columns).filter(this._typeFilter(use));
|
||||
const columnMap = new Map(columns.map(c => [c.id.peek(), c]));
|
||||
// Remove any columns that are no longer there.
|
||||
const selectedFields = selectedRefs.map(s => columnMap.get(s)!).filter(c => Boolean(c));
|
||||
return selectedFields;
|
||||
return selectedRefs.map(s => columnMap.get(s)!).filter(c => Boolean(c));
|
||||
}
|
||||
private _renderItem(use: UseCBOwner, field: ColumnRec): any {
|
||||
return cssFieldEntry(
|
||||
@@ -221,27 +230,82 @@ class ColumnListPicker extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
class CustomSectionConfigurationConfig extends Disposable{
|
||||
// Does widget has custom configuration.
|
||||
private readonly _hasConfiguration: Computed<boolean>;
|
||||
constructor(private _section: ViewSectionRec) {
|
||||
super();
|
||||
this._hasConfiguration = Computed.create(this, use => use(_section.hasCustomOptions));
|
||||
}
|
||||
public buildDom() {
|
||||
// Show prompt, when desired access level is different from actual one.
|
||||
return dom(
|
||||
'div',
|
||||
dom.maybe(this._hasConfiguration, () =>
|
||||
cssSection(
|
||||
textButton(
|
||||
t("Open configuration"),
|
||||
dom.on('click', () => this._openConfiguration()),
|
||||
testId('open-configuration')
|
||||
)
|
||||
)
|
||||
),
|
||||
dom.maybeOwned(use => use(this._section.columnsToMap), (owner, columns) => {
|
||||
const createObs = (column: ColumnToMapImpl) => {
|
||||
const obs = Computed.create(owner, use => {
|
||||
const savedDefinition = use(this._section.customDef.columnsMapping) || {};
|
||||
return savedDefinition[column.name];
|
||||
});
|
||||
obs.onWrite(async (value) => {
|
||||
const savedDefinition = this._section.customDef.columnsMapping.peek() || {};
|
||||
savedDefinition[column.name] = value;
|
||||
await this._section.customDef.columnsMapping.setAndSave(savedDefinition);
|
||||
});
|
||||
return obs;
|
||||
};
|
||||
// Create observables for all columns to pick.
|
||||
const mappings = columns.map(c => new ColumnToMapImpl(c)).map((column) => ({
|
||||
value: createObs(column),
|
||||
column
|
||||
}));
|
||||
return [
|
||||
...mappings.map(m => m.column.allowMultiple
|
||||
? dom.create(ColumnListPicker, m.value, m.column, this._section)
|
||||
: dom.create(ColumnPicker, m.value, m.column, this._section))
|
||||
];
|
||||
})
|
||||
);
|
||||
}
|
||||
private _openConfiguration(): void {
|
||||
allCommands.openWidgetConfiguration.run();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export class CustomSectionConfig extends Disposable {
|
||||
|
||||
protected _customSectionConfigurationConfig: CustomSectionConfigurationConfig;
|
||||
// Holds all available widget definitions.
|
||||
private _widgets: Observable<ICustomWidget[]>;
|
||||
// Holds selected option (either custom string or a widgetId).
|
||||
private _selectedId: Computed<string | null>;
|
||||
private readonly _selectedId: Computed<string | null>;
|
||||
// Holds custom widget URL.
|
||||
private _url: Computed<string>;
|
||||
private readonly _url: Computed<string>;
|
||||
// Enable or disable widget repository.
|
||||
private _canSelect = true;
|
||||
private readonly _canSelect: boolean = true;
|
||||
// When widget is changed, it sets its desired access level. We will prompt
|
||||
// user to approve or reject it.
|
||||
private _desiredAccess: Observable<AccessLevel|null>;
|
||||
private readonly _desiredAccess: Observable<AccessLevel|null>;
|
||||
// Current access level (stored inside a section).
|
||||
private _currentAccess: Computed<AccessLevel>;
|
||||
// Does widget has custom configuration.
|
||||
private _hasConfiguration: Computed<boolean>;
|
||||
private readonly _currentAccess: Computed<AccessLevel>;
|
||||
|
||||
constructor(private _section: ViewSectionRec, private _gristDoc: GristDoc) {
|
||||
|
||||
|
||||
|
||||
constructor(protected _section: ViewSectionRec, private _gristDoc: GristDoc) {
|
||||
super();
|
||||
|
||||
const api = _gristDoc.app.topAppModel.api;
|
||||
this._customSectionConfigurationConfig = new CustomSectionConfigurationConfig(_section);
|
||||
|
||||
// Test if we can offer widget list.
|
||||
const gristConfig: GristLoadConfig = (window as any).gristConfig || {};
|
||||
@@ -249,29 +313,8 @@ export class CustomSectionConfig extends Disposable {
|
||||
|
||||
// Array of available widgets - will be updated asynchronously.
|
||||
this._widgets = Observable.create(this, []);
|
||||
|
||||
if (this._canSelect) {
|
||||
// From the start we will provide single widget definition
|
||||
// that was chosen previously.
|
||||
if (_section.customDef.widgetDef.peek()) {
|
||||
this._widgets.set([_section.customDef.widgetDef.peek()!]);
|
||||
}
|
||||
// Request for rest of the widgets.
|
||||
api
|
||||
.getWidgets()
|
||||
.then(widgets => {
|
||||
if (this.isDisposed()) {
|
||||
return;
|
||||
}
|
||||
const existing = _section.customDef.widgetDef.peek();
|
||||
// Make sure we have current widget in place.
|
||||
if (existing && !widgets.some(w => w.widgetId === existing.widgetId)) {
|
||||
widgets.push(existing);
|
||||
}
|
||||
this._widgets.set(widgets.sort((a, b) => nativeCompare(a.name.toLowerCase(), b.name.toLowerCase())));
|
||||
})
|
||||
.catch(reportError);
|
||||
}
|
||||
this._getWidgets().catch(reportError);
|
||||
// Request for rest of the widgets.
|
||||
|
||||
// Selected value from the dropdown (contains widgetId or "custom" string for Custom URL)
|
||||
this._selectedId = Computed.create(this, use => {
|
||||
@@ -350,8 +393,6 @@ export class CustomSectionConfig extends Disposable {
|
||||
|
||||
// Clear intermediate state when section changes.
|
||||
this.autoDispose(_section.id.subscribe(() => this._reject()));
|
||||
|
||||
this._hasConfiguration = Computed.create(this, use => use(_section.hasCustomOptions));
|
||||
}
|
||||
|
||||
public buildDom() {
|
||||
@@ -395,16 +436,17 @@ export class CustomSectionConfig extends Disposable {
|
||||
return dom(
|
||||
'div',
|
||||
dom.autoDispose(holder),
|
||||
this.shouldRenderWidgetSelector() &&
|
||||
this._canSelect
|
||||
? cssRow(
|
||||
select(this._selectedId, options, {
|
||||
defaultLabel: t("Select Custom Widget"),
|
||||
menuCssClass: cssMenu.className,
|
||||
}),
|
||||
testId('select')
|
||||
)
|
||||
select(this._selectedId, options, {
|
||||
defaultLabel: t("Select Custom Widget"),
|
||||
menuCssClass: cssMenu.className,
|
||||
}),
|
||||
testId('select')
|
||||
)
|
||||
: null,
|
||||
dom.maybe(isCustom, () => [
|
||||
dom.maybe(isCustom && this.shouldRenderWidgetSelector(), () => [
|
||||
cssRow(
|
||||
cssTextInput(
|
||||
this._url,
|
||||
@@ -452,15 +494,6 @@ export class CustomSectionConfig extends Disposable {
|
||||
cssRow(select(this._currentAccess, levels), testId('access')),
|
||||
]
|
||||
),
|
||||
dom.maybe(this._hasConfiguration, () =>
|
||||
cssSection(
|
||||
textButton(
|
||||
t("Open configuration"),
|
||||
dom.on('click', () => this._openConfiguration()),
|
||||
testId('open-configuration')
|
||||
)
|
||||
)
|
||||
),
|
||||
cssSection(
|
||||
cssLink(
|
||||
dom.attr('href', 'https://support.getgrist.com/widget-custom'),
|
||||
@@ -468,38 +501,38 @@ export class CustomSectionConfig extends Disposable {
|
||||
t("Learn more about custom widgets")
|
||||
)
|
||||
),
|
||||
dom.maybeOwned(use => use(this._section.columnsToMap), (owner, columns) => {
|
||||
const createObs = (column: ColumnToMapImpl) => {
|
||||
const obs = Computed.create(owner, use => {
|
||||
const savedDefinition = use(this._section.customDef.columnsMapping) || {};
|
||||
return savedDefinition[column.name];
|
||||
});
|
||||
obs.onWrite(async (value) => {
|
||||
const savedDefinition = this._section.customDef.columnsMapping.peek() || {};
|
||||
savedDefinition[column.name] = value;
|
||||
await this._section.customDef.columnsMapping.setAndSave(savedDefinition);
|
||||
});
|
||||
return obs;
|
||||
};
|
||||
// Create observables for all columns to pick.
|
||||
const mappings = columns.map(c => new ColumnToMapImpl(c)).map((column) => ({
|
||||
value: createObs(column),
|
||||
column
|
||||
}));
|
||||
return [
|
||||
cssSeparator(),
|
||||
...mappings.map(m => m.column.allowMultiple
|
||||
? dom.create(ColumnListPicker, m.value, m.column, this._section)
|
||||
: dom.create(ColumnPicker, m.value, m.column, this._section))
|
||||
];
|
||||
})
|
||||
cssSeparator(),
|
||||
this._customSectionConfigurationConfig.buildDom(),
|
||||
cssSection(
|
||||
cssLink(
|
||||
dom.attr('href', 'https://support.getgrist.com/widget-custom'),
|
||||
dom.attr('target', '_blank'),
|
||||
t("Learn more about custom widgets")
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private _openConfiguration(): void {
|
||||
allCommands.openWidgetConfiguration.run();
|
||||
protected shouldRenderWidgetSelector(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async _getWidgets() {
|
||||
const api = this._gristDoc.app.topAppModel.api;
|
||||
const wigets = await api.getWidgets();
|
||||
// Request for rest of the widgets.
|
||||
if (this._canSelect) {
|
||||
// From the start we will provide single widget definition
|
||||
// that was chosen previously.
|
||||
if (this._section.customDef.widgetDef.peek()) {
|
||||
wigets.push(this._section.customDef.widgetDef.peek()!);
|
||||
}
|
||||
}
|
||||
this._widgets.set(wigets);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private _accept() {
|
||||
if (this._desiredAccess.get()) {
|
||||
this._currentAccess.set(this._desiredAccess.get()!);
|
||||
@@ -512,7 +545,6 @@ export class CustomSectionConfig extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const cssWarningWrapper = styled('div', `
|
||||
padding-left: 8px;
|
||||
padding-top: 6px;
|
||||
|
||||
@@ -1,23 +1,37 @@
|
||||
import { BehavioralPromptsManager } from 'app/client/components/BehavioralPromptsManager';
|
||||
import { GristDoc } from 'app/client/components/GristDoc';
|
||||
import { makeT } from 'app/client/lib/localization';
|
||||
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 { getWidgetTypes, IWidgetType } from 'app/client/ui/widgetTypes';
|
||||
import { bigPrimaryButton } from "app/client/ui2018/buttons";
|
||||
import { overflowTooltip } from "app/client/ui/tooltips";
|
||||
import { theme, vars } from "app/client/ui2018/cssVars";
|
||||
import { icon } from "app/client/ui2018/icons";
|
||||
import { spinnerModal } from 'app/client/ui2018/modals';
|
||||
import { isLongerThan, nativeCompare } from "app/common/gutil";
|
||||
import { computed, Computed, Disposable, dom, domComputed, DomElementArg, fromKo, IOption, select} from "grainjs";
|
||||
import { makeTestId, Observable, onKeyDown, styled} from "grainjs";
|
||||
import without = require('lodash/without');
|
||||
import {BehavioralPromptsManager} from 'app/client/components/BehavioralPromptsManager';
|
||||
import {GristDoc} from 'app/client/components/GristDoc';
|
||||
import {makeT} from 'app/client/lib/localization';
|
||||
import {reportError} from 'app/client/models/AppModel';
|
||||
import {ColumnRec, TableRec, ViewSectionRec} from 'app/client/models/DocModel';
|
||||
import {PERMITTED_CUSTOM_WIDGETS} from "app/client/models/features";
|
||||
import {GristTooltips} from 'app/client/ui/GristTooltips';
|
||||
import {linkId, NoLink} from 'app/client/ui/selectBy';
|
||||
import {overflowTooltip, withInfoTooltip} from 'app/client/ui/tooltips';
|
||||
import {getWidgetTypes} from "app/client/ui/widgetTypesMap";
|
||||
import {bigPrimaryButton} from "app/client/ui2018/buttons";
|
||||
import {theme, vars} from "app/client/ui2018/cssVars";
|
||||
import {icon} from "app/client/ui2018/icons";
|
||||
import {spinnerModal} from 'app/client/ui2018/modals';
|
||||
import {isLongerThan, nativeCompare} from "app/common/gutil";
|
||||
import {IAttachedCustomWidget, IWidgetType} from 'app/common/widgetTypes';
|
||||
import {
|
||||
computed,
|
||||
Computed,
|
||||
Disposable,
|
||||
dom,
|
||||
domComputed,
|
||||
DomElementArg,
|
||||
fromKo,
|
||||
IOption,
|
||||
makeTestId,
|
||||
Observable,
|
||||
onKeyDown,
|
||||
select,
|
||||
styled
|
||||
} from "grainjs";
|
||||
import Popper from 'popper.js';
|
||||
import { IOpenController, popupOpen, setPopupToCreateDom } from 'popweasel';
|
||||
import {IOpenController, popupOpen, setPopupToCreateDom} from 'popweasel';
|
||||
import without = require('lodash/without');
|
||||
|
||||
const t = makeT('PageWidgetPicker');
|
||||
|
||||
@@ -79,7 +93,7 @@ const testId = makeTestId('test-wselect-');
|
||||
// compatible types given the tableId and whether user is creating a new page or not.
|
||||
function getCompatibleTypes(tableId: TableId, isNewPage: boolean|undefined): IWidgetType[] {
|
||||
if (tableId !== 'New Table') {
|
||||
return ['record', 'single', 'detail', 'chart', 'custom'];
|
||||
return ['record', 'single', 'detail', 'chart', 'custom', 'custom.calendar'];
|
||||
} else if (isNewPage) {
|
||||
// New view + new table means we'll be switching to the primary view.
|
||||
return ['record'];
|
||||
@@ -240,9 +254,15 @@ export interface ISelectOptions {
|
||||
selectBy?: (val: IPageWidget) => Array<IOption<string>>;
|
||||
}
|
||||
|
||||
const registeredCustomWidgets: IAttachedCustomWidget[] = ['custom.calendar'];
|
||||
|
||||
const permittedCustomWidgets: IAttachedCustomWidget[] = PERMITTED_CUSTOM_WIDGETS().get().map((widget) =>
|
||||
widget as IAttachedCustomWidget)??[];
|
||||
// the list of widget types in the order they should be listed by the widget.
|
||||
const finalListOfCustomWidgetToShow = permittedCustomWidgets.filter(a=>
|
||||
registeredCustomWidgets.includes(a));
|
||||
const sectionTypes: IWidgetType[] = [
|
||||
'record', 'single', 'detail', 'chart', 'custom'
|
||||
'record', 'single', 'detail', 'chart', ...finalListOfCustomWidgetToShow, 'custom'
|
||||
];
|
||||
|
||||
|
||||
|
||||
23
app/client/ui/PredefinedCustomSectionConfig.ts
Normal file
23
app/client/ui/PredefinedCustomSectionConfig.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import {GristDoc} from "../components/GristDoc";
|
||||
import {ViewSectionRec} from "../models/entities/ViewSectionRec";
|
||||
import {CustomSectionConfig} from "./CustomSectionConfig";
|
||||
|
||||
export class PredefinedCustomSectionConfig extends CustomSectionConfig {
|
||||
|
||||
|
||||
constructor(section: ViewSectionRec, gristDoc: GristDoc) {
|
||||
super(section, gristDoc);
|
||||
}
|
||||
|
||||
public buildDom() {
|
||||
return this._customSectionConfigurationConfig.buildDom();
|
||||
}
|
||||
|
||||
protected shouldRenderWidgetSelector(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected async _getWidgets(): Promise<void> {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
@@ -24,15 +24,16 @@ import {makeT} from 'app/client/lib/localization';
|
||||
import {createSessionObs} from 'app/client/lib/sessionObs';
|
||||
import {reportError} from 'app/client/models/AppModel';
|
||||
import {ViewSectionRec} from 'app/client/models/DocModel';
|
||||
import {GridOptions} from 'app/client/ui/GridOptions';
|
||||
import {attachPageWidgetPicker, IPageWidget, toPageWidget} from 'app/client/ui/PageWidgetPicker';
|
||||
import {linkId, selectBy} from 'app/client/ui/selectBy';
|
||||
import {CustomSectionConfig} from 'app/client/ui/CustomSectionConfig';
|
||||
import {buildDescriptionConfig} from 'app/client/ui/DescriptionConfig';
|
||||
import {BuildEditorOptions} from 'app/client/ui/FieldConfig';
|
||||
import {GridOptions} from 'app/client/ui/GridOptions';
|
||||
import {attachPageWidgetPicker, IPageWidget, toPageWidget} from 'app/client/ui/PageWidgetPicker';
|
||||
import {PredefinedCustomSectionConfig} from "app/client/ui/PredefinedCustomSectionConfig";
|
||||
import {cssLabel} from 'app/client/ui/RightPanelStyles';
|
||||
import {linkId, selectBy} from 'app/client/ui/selectBy';
|
||||
import {VisibleFieldsConfig} from 'app/client/ui/VisibleFieldsConfig';
|
||||
import {IWidgetType, widgetTypes} from 'app/client/ui/widgetTypes';
|
||||
import {widgetTypesMap} from "app/client/ui/widgetTypesMap";
|
||||
import {basicButton, primaryButton} from 'app/client/ui2018/buttons';
|
||||
import {testId, theme, vars} from 'app/client/ui2018/cssVars';
|
||||
import {textInput} from 'app/client/ui2018/editableLabel';
|
||||
@@ -41,9 +42,22 @@ import {icon} from 'app/client/ui2018/icons';
|
||||
import {select} from 'app/client/ui2018/menus';
|
||||
import {FieldBuilder} from 'app/client/widgets/FieldBuilder';
|
||||
import {StringUnion} from 'app/common/StringUnion';
|
||||
import {bundleChanges, Computed, Disposable, dom, domComputed, DomContents,
|
||||
DomElementArg, DomElementMethod, IDomComponent} from 'grainjs';
|
||||
import {MultiHolder, Observable, styled, subscribe} from 'grainjs';
|
||||
import {IWidgetType} from 'app/common/widgetTypes';
|
||||
import {
|
||||
bundleChanges,
|
||||
Computed,
|
||||
Disposable,
|
||||
dom,
|
||||
domComputed,
|
||||
DomContents,
|
||||
DomElementArg,
|
||||
DomElementMethod,
|
||||
IDomComponent,
|
||||
MultiHolder,
|
||||
Observable,
|
||||
styled,
|
||||
subscribe
|
||||
} from 'grainjs';
|
||||
import * as ko from 'knockout';
|
||||
|
||||
const t = makeT('RightPanel');
|
||||
@@ -170,7 +184,7 @@ export class RightPanel extends Disposable {
|
||||
|
||||
private _buildStandardHeader() {
|
||||
return dom.maybe(this._pageWidgetType, (type) => {
|
||||
const widgetInfo = widgetTypes.get(type) || {label: 'Table', icon: 'TypeTable'};
|
||||
const widgetInfo = widgetTypesMap.get(type) || {label: 'Table', icon: 'TypeTable'};
|
||||
const fieldInfo = getFieldType(type);
|
||||
return [
|
||||
cssTopBarItem(cssTopBarIcon(widgetInfo.icon), widgetInfo.label,
|
||||
@@ -359,7 +373,8 @@ export class RightPanel extends Disposable {
|
||||
// refactored, but if not, should be made public.
|
||||
const viewConfigTab = this._createViewConfigTab(owner);
|
||||
const hasCustomMapping = Computed.create(owner, use => {
|
||||
const isCustom = use(this._pageWidgetType) === 'custom';
|
||||
const widgetType = use(this._pageWidgetType);
|
||||
const isCustom = widgetType === 'custom' || widgetType?.startsWith('custom.');
|
||||
const hasColumnMapping = use(activeSection.columnsToMap);
|
||||
return Boolean(isCustom && hasColumnMapping);
|
||||
});
|
||||
@@ -441,10 +456,15 @@ export class RightPanel extends Disposable {
|
||||
() => dom('div', parts[2].buildDom())),
|
||||
// In the default url mode, allow picking a url and granting/forbidding
|
||||
// access to data.
|
||||
dom.maybe(use => use(activeSection.customDef.mode) === 'url',
|
||||
dom.maybe(use => use(activeSection.customDef.mode) === 'url' && use(this._pageWidgetType) === 'custom',
|
||||
() => dom.create(CustomSectionConfig, activeSection, this._gristDoc)),
|
||||
];
|
||||
}),
|
||||
dom.maybe((use) => use(this._pageWidgetType)?.startsWith('custom.'), () => {
|
||||
return [
|
||||
dom.create(PredefinedCustomSectionConfig, activeSection, this._gristDoc),
|
||||
];
|
||||
}),
|
||||
|
||||
dom.maybe(
|
||||
(use) => !(
|
||||
|
||||
@@ -5,7 +5,7 @@ import { makeT } from 'app/client/lib/localization';
|
||||
import * as tableUtil from 'app/client/lib/tableUtil';
|
||||
import { ColumnRec, ViewFieldRec, ViewSectionRec } from "app/client/models/DocModel";
|
||||
import { getFieldType } from "app/client/ui/RightPanel";
|
||||
import { IWidgetType } from "app/client/ui/widgetTypes";
|
||||
import { IWidgetType } from "../../common/widgetTypes";
|
||||
import { basicButton, cssButton, primaryButton } from 'app/client/ui2018/buttons';
|
||||
import * as checkbox from "app/client/ui2018/checkbox";
|
||||
import { theme, vars } from "app/client/ui2018/cssVars";
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* Exposes utilities for getting the types information associated to each of the widget types.
|
||||
*/
|
||||
|
||||
import { IconName } from "app/client/ui2018/IconList";
|
||||
|
||||
// all widget types
|
||||
export type IWidgetType = 'record' | 'detail' | 'single' | 'chart' | 'custom';
|
||||
|
||||
// Widget type info.
|
||||
export interface IWidgetTypeInfo {
|
||||
label: string;
|
||||
icon: IconName;
|
||||
}
|
||||
|
||||
// the list of widget types with their labels and icons
|
||||
export const widgetTypes = new Map<IWidgetType, IWidgetTypeInfo> ([
|
||||
['record', {label: 'Table', icon: 'TypeTable'}],
|
||||
['single', {label: 'Card', icon: 'TypeCard'}],
|
||||
['detail', {label: 'Card List', icon: 'TypeCardList'}],
|
||||
['chart', {label: 'Chart', icon: 'TypeChart'}],
|
||||
['custom', {label: 'Custom', icon: 'TypeCustom'}]
|
||||
]);
|
||||
|
||||
// Returns the widget type info for sectionType, or the one for 'record' if sectionType is null.
|
||||
export function getWidgetTypes(sectionType: IWidgetType|null): IWidgetTypeInfo {
|
||||
return widgetTypes.get(sectionType || 'record') || widgetTypes.get('record')!;
|
||||
}
|
||||
23
app/client/ui/widgetTypesMap.ts
Normal file
23
app/client/ui/widgetTypesMap.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
// the list of widget types with their labels and icons
|
||||
import {IWidgetType} from "app/common/widgetTypes";
|
||||
import {IconName} from "app/client/ui2018/IconList";
|
||||
|
||||
export const widgetTypesMap = new Map<IWidgetType, IWidgetTypeInfo>([
|
||||
['record', {label: 'Table', icon: 'TypeTable'}],
|
||||
['single', {label: 'Card', icon: 'TypeCard'}],
|
||||
['detail', {label: 'Card List', icon: 'TypeCardList'}],
|
||||
['chart', {label: 'Chart', icon: 'TypeChart'}],
|
||||
['custom', {label: 'Custom', icon: 'TypeCustom'}],
|
||||
['custom.calendar', {label: 'Calendar', icon: 'FieldDate'}]
|
||||
]);
|
||||
|
||||
// Widget type info.
|
||||
export interface IWidgetTypeInfo {
|
||||
label: string;
|
||||
icon: IconName;
|
||||
}
|
||||
|
||||
// Returns the widget type info for sectionType, or the one for 'record' if sectionType is null.
|
||||
export function getWidgetTypes(sectionType: IWidgetType | null): IWidgetTypeInfo {
|
||||
return widgetTypesMap.get(sectionType || 'record') || widgetTypesMap.get('record')!;
|
||||
}
|
||||
Reference in New Issue
Block a user