diff --git a/web/client/actions/__tests__/widgets-test.js b/web/client/actions/__tests__/widgets-test.js index fb3a95e86e..61d4544665 100644 --- a/web/client/actions/__tests__/widgets-test.js +++ b/web/client/actions/__tests__/widgets-test.js @@ -49,11 +49,22 @@ import { toggleTray, toggleMaximize, replaceWidgets, - REPLACE + REPLACE, + init, INIT } from '../widgets'; describe('Test correctness of the widgets actions', () => { + it('init', () => { + const defaults = {ratio_md: { + w: 4, + h: 4 + }}; + const retval = init(defaults); + expect(retval).toExist(); + expect(retval.type).toBe(INIT); + expect(retval.cfg).toEqual(defaults); + }); it('exportCSV', () => { const data = [{a: "a"}]; const retval = exportCSV({data, title: "TITLE"}); diff --git a/web/client/actions/widgets.js b/web/client/actions/widgets.js index d214810a6b..1b6ce9b595 100644 --- a/web/client/actions/widgets.js +++ b/web/client/actions/widgets.js @@ -13,6 +13,7 @@ export const EDIT = "WIDGETS:EDIT"; export const EDIT_NEW = "WIDGETS:EDIT_NEW"; export const EDITOR_CHANGE = "WIDGETS:EDITOR_CHANGE"; export const EDITOR_SETTING_CHANGE = "WIDGETS:EDITOR_SETTING_CHANGE"; +export const INIT = "WIDGETS:INIT"; export const UPDATE = "WIDGETS:UPDATE"; export const UPDATE_PROPERTY = "WIDGETS:UPDATE_PROPERTY"; export const UPDATE_LAYER = "WIDGETS:UPDATE_LAYER"; @@ -134,7 +135,6 @@ export const deleteWidget = (widget, target = DEFAULT_TARGET) => ({ target, widget }); - /** * Removes all the widget from the containers * @return {object} action of type CLEAR_WIDGETS @@ -175,6 +175,15 @@ export const editNewWidget = (widget, settings) => ({ settings }); +/** + * init widgets plugin + * @param {object} cfg the config to push inside the plugin + */ +export const init = (cfg) => ({ + type: INIT, + cfg +}); + /** * Changes an entry in the widget editor * @param {string} key the key of the value to set. even a path is allowed diff --git a/web/client/plugins/Widgets.jsx b/web/client/plugins/Widgets.jsx index 27dc3e3549..2b4eb3f87c 100644 --- a/web/client/plugins/Widgets.jsx +++ b/web/client/plugins/Widgets.jsx @@ -99,6 +99,8 @@ compose( }), withProps(({ width, + rowHeight, + cols, height, maximized, leftOffset, @@ -112,7 +114,7 @@ compose( ? (height - backgroundSelectorOffset - 120) / 2 : height - backgroundSelectorOffset - 120; const nRows = isSingleWidgetLayout ? 1 : 4; - const rowHeight = !isSingleWidgetLayout + const rowHeightRecalculated = !isSingleWidgetLayout ? Math.floor(divHeight / nRows - 20) : divHeight > singleWidgetLayoutMaxHeight ? singleWidgetLayoutMaxHeight @@ -140,12 +142,12 @@ compose( const widthOptions = width ? {width: viewWidth - 1} : {}; const baseHeight = isSingleWidgetLayout ? rowHeight - : Math.floor((height - 100) / (rowHeight + 10)) * (rowHeight + 10); + : Math.floor((height - 100) / (rowHeightRecalculated + 10)) * (rowHeightRecalculated + 10); return ({ - rowHeight, + rowHeight: isSingleWidgetLayout ? rowHeightRecalculated : rowHeight, className: "on-map", breakpoints: isSingleWidgetLayout ? { xxs: 0 } : { md: 0 }, - cols: { md: 6, xxs: 1 }, + cols: cols || { md: 6, xxs: 1 }, ...widthOptions, useDefaultWidthProvider: false, style: { @@ -278,6 +280,9 @@ class Widgets extends React.Component { static defaultProps = { enabled: true }; + componentDidMount() { + this.props.onMount(this.props.pluginCfg?.defaults); + } render() { return this.props.enabled ? : null; } @@ -296,6 +301,32 @@ class Widgets extends React.Component { * @prop {boolean|string|array} [toolsOptions.showPin] show lock tool. By default is visible only to the admin * @prop {boolean|string|array} [toolsOptions.showHide] show the "hide tool" for the widget (the tool allows to hide the widget to users that have `seeHidden=false` ). By default is false, in the most common case it should be the same of `seeHidden`. * @prop {boolean|string|array} [toolsOptions.seeHidden] hides the widgets under particular conditions + * @prop {number} cfg.rowHeight Rows have a static height + * @prop {object} cfg.cols Number of columns in this layout. default is { md: 6, xxs: 1 } + * @prop {object} cfg.defaults options that are used to initialize the plugin when mounted + * @prop {object} cfg.defaults.ratio_md ratio to be used for md layout when recalculating width and height of widgets based on a custom cols prop. If you change cols.md to 48 make sure to have a proper ratio, otherwise widgets will remain small + * @prop {number} cfg.defaults.ratio_md.w ratio for the width + * @prop {number} cfg.defaults.ratio_md.h ratio for the height + * @prop {number} cfg.defaults.ratio_md.x ratio for the x position in the grid + * @prop {number} cfg.defaults.ratio_md.y ratio for the y position in the grid + * @example + * ``` + * { + * "name": "Widgets", + * "cfg": { + * "cols": { "md": 48, "xxs": 1 }, + * "rowHeight": 10, + * "defaults": { + * "ratio_md": { + * "h": 8, + * "w": 8, + * "x": 8, + * "y": 8 + * } + * } + * } + * } + * ``` * @prop {object} [dateFormats] object containing custom formats for date/time attribute types ( in [ISO_8601](https://en.wikipedia.org/wiki/ISO_8601) format). Once specified, custom formats will be applied for specific attribute types in Table widget. Following keys are supported: `date-time`, `date`, `time`. Example: * ``` * "dateFormats": { diff --git a/web/client/plugins/widgets/autoDisableWidgets.js b/web/client/plugins/widgets/autoDisableWidgets.js index c5e709c2ca..8893ccbcf3 100644 --- a/web/client/plugins/widgets/autoDisableWidgets.js +++ b/web/client/plugins/widgets/autoDisableWidgets.js @@ -10,6 +10,7 @@ import { createSelector } from 'reselect'; import { connect } from 'react-redux'; import { rightPanelOpenSelector, bottomPanelOpenSelector } from '../../selectors/maplayout'; +import { init } from '../../actions/widgets'; /** * enhances the component disabling it (setting `enabled` property to `false`) when rightPanel or when bottomPanel are open @@ -21,6 +22,6 @@ const autoDisableWidgets = connect( (rightPanel, bottomPanel) => ({ enabled: !rightPanel && !bottomPanel }) - ) + ), {onMount: init} ); export default autoDisableWidgets; diff --git a/web/client/reducers/__tests__/widgets-test.js b/web/client/reducers/__tests__/widgets-test.js index 57b92bcb4e..e51ee7c564 100644 --- a/web/client/reducers/__tests__/widgets-test.js +++ b/web/client/reducers/__tests__/widgets-test.js @@ -14,6 +14,7 @@ import { updateWidgetLayer, updateWidgetProperty, deleteWidget, + init, changeLayout, clearWidgets, addDependency, @@ -29,6 +30,7 @@ import { import { configureMap } from '../../actions/config'; import { dashboardLoaded } from '../../actions/dashboard'; + import widgets from '../widgets'; import { getFloatingWidgets, getVisibleFloatingWidgets, getCollapsedIds } from '../../selectors/widgets'; import expect from 'expect'; @@ -190,9 +192,40 @@ describe('Test the widgets reducer', () => { expect(uWidgets[0].dependenciesMap).toBeFalsy(); expect(uWidgets[0].id).toBe("2"); }); + it('init', () => { + const defaults = {ratio_md: { + w: 4, + h: 4 + }}; + const state = widgets(undefined, init(defaults)); + expect(state.defaults).toEqual(defaults); + }); it('configureMap', () => { - const state = widgets(undefined, configureMap({widgetsConfig: {widgets: [{id: "1"}]}})); + const state = widgets(undefined, configureMap({ + widgetsConfig: {widgets: [{id: "1"}]}})); + expect(state.containers[DEFAULT_TARGET].widgets.length).toBe(1); + }); + it('configureMap with changed ratio_md', () => { + const state = widgets({ + defaults: { + ratio_md: { + w: 4, + h: 4 + } + } + }, configureMap({widgetsConfig: { + widgets: [{id: "1"}], + layouts: { + md: [{ + h: 1, + w: 3, + x: 0, + y: 4 + }] + } + }})); expect(state.containers[DEFAULT_TARGET].widgets.length).toBe(1); + expect(state.containers[DEFAULT_TARGET].layouts.md).toEqual( [ { h: 4, w: 12, x: 0, y: 16 } ]); }); it('configureMap with no widgetsConfig', () => { const state = widgets(undefined, configureMap({})); diff --git a/web/client/reducers/widgets.js b/web/client/reducers/widgets.js index 16b5bf2506..a79f217cb7 100644 --- a/web/client/reducers/widgets.js +++ b/web/client/reducers/widgets.js @@ -15,6 +15,7 @@ import { DELETE, EDITOR_CHANGE, EDITOR_SETTING_CHANGE, + INIT, CHANGE_LAYOUT, CLEAR_WIDGETS, DEFAULT_TARGET, @@ -82,6 +83,9 @@ const emptyState = { */ function widgetsReducer(state = emptyState, action) { switch (action.type) { + case INIT: { + return set(`defaults`, action.cfg, state); + } case EDITOR_SETTING_CHANGE: { return set(`builder.settings.${action.key}`, action.value, state); } @@ -184,6 +188,24 @@ function widgetsReducer(state = emptyState, action) { if (!isEmpty(widgetsConfig)) { widgetsConfig = convertToCompatibleWidgets(widgetsConfig); } + if (!isEmpty(state?.defaults?.ratio_md)) { + widgetsConfig = { + ...widgetsConfig, + layouts: { + ...widgetsConfig.layouts, + md: (widgetsConfig.layouts?.md || []).map(layout => { + return { + ...layout, + w: layout.w * state?.defaults?.ratio_md.w, + h: layout.h * state?.defaults?.ratio_md.h, + x: layout.x * (state?.defaults?.ratio_md.x || state?.defaults?.ratio_md.w), + y: layout.y * (state?.defaults?.ratio_md.y || state?.defaults?.ratio_md.h) + }; + }) + + } + }; + } return set(`containers[${DEFAULT_TARGET}]`, { ...widgetsConfig }, state);