From dc6b993b39d75b81941b450174020fea5f0974e3 Mon Sep 17 00:00:00 2001 From: Matteo V Date: Tue, 7 Nov 2023 15:30:29 +0100 Subject: [PATCH] Fix #9622 add possibility to configure a fine grid for widgets in the viewer (#9664) --- web/client/actions/__tests__/widgets-test.js | 13 +++++- web/client/actions/widgets.js | 11 ++++- web/client/plugins/Widgets.jsx | 41 +++++++++++++++---- .../plugins/widgets/autoDisableWidgets.js | 2 +- web/client/reducers/__tests__/widgets-test.js | 26 +++++++++++- web/client/reducers/widgets.js | 13 +++++- 6 files changed, 94 insertions(+), 12 deletions(-) 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 2a2baac4bd..ff84cb23e2 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"; @@ -135,7 +136,6 @@ export const deleteWidget = (widget, target = DEFAULT_TARGET) => ({ target, widget }); - /** * Removes all the widget from the containers * @return {object} action of type CLEAR_WIDGETS @@ -176,6 +176,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..da389a4ca9 100644 --- a/web/client/plugins/Widgets.jsx +++ b/web/client/plugins/Widgets.jsx @@ -12,7 +12,6 @@ import {connect} from 'react-redux'; import {createSelector} from 'reselect'; import {compose, defaultProps, withHandlers, withProps, withPropsOnChange, withState} from 'recompose'; - import {createPlugin} from '../utils/PluginsUtils'; import {mapIdSelector} from '../selectors/map'; @@ -33,7 +32,8 @@ import { toggleCollapse, toggleCollapseAll, toggleMaximize, - updateWidgetProperty + updateWidgetProperty, + init } from '../actions/widgets'; import editOptions from './widgets/editOptions'; import autoDisableWidgets from './widgets/autoDisableWidgets'; @@ -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,26 @@ 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.initialSize new widget's default sizes in grid units. It contains 2 integers, `w` and `h`, representing the initial size of the new widget. This is useful when customizing `rowHeight` and/or `cols`, to generate a widget with a proportionated size. + * @example + * ``` + * { + * "name": "Widgets", + * "cfg": { + * "cols": { "md": 48, "xxs": 1 }, + * "rowHeight": 10, + * "defaults": { + * "initialSize": { + * "h": 8, + * "w": 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": { @@ -306,7 +331,9 @@ class Widgets extends React.Component { * ``` * */ -const WidgetsPlugin = autoDisableWidgets(Widgets); + +const WidgetsPlugin = connect(null, {onMount: init} +)(autoDisableWidgets(Widgets)); export default createPlugin("WidgetsPlugin", { component: WidgetsPlugin, diff --git a/web/client/plugins/widgets/autoDisableWidgets.js b/web/client/plugins/widgets/autoDisableWidgets.js index c5e709c2ca..ef72c04820 100644 --- a/web/client/plugins/widgets/autoDisableWidgets.js +++ b/web/client/plugins/widgets/autoDisableWidgets.js @@ -21,6 +21,6 @@ const autoDisableWidgets = connect( (rightPanel, bottomPanel) => ({ enabled: !rightPanel && !bottomPanel }) - ) + ), {} ); export default autoDisableWidgets; diff --git a/web/client/reducers/__tests__/widgets-test.js b/web/client/reducers/__tests__/widgets-test.js index 57b92bcb4e..2864c9cdc3 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'; @@ -91,6 +93,18 @@ describe('Test the widgets reducer', () => { const state = widgets(undefined, insertWidget({id: "1"})); expect(state.containers[DEFAULT_TARGET].widgets.length).toBe(1); }); + it('insertWidget with default initialSize', () => { + const state = widgets({ + defaults: { + initialSize: { + w: 4, + h: 4 + }} + }, insertWidget({id: "1"})); + expect(state.containers[DEFAULT_TARGET].widgets.length).toBe(1); + expect(state.containers[DEFAULT_TARGET].widgets[0].dataGrid.w).toBe(4); + expect(state.containers[DEFAULT_TARGET].widgets[0].dataGrid.h).toBe(4); + }); it('updateWidgetLayers', () => { const targetLayer = { name: "layer2", @@ -190,10 +204,20 @@ describe('Test the widgets reducer', () => { expect(uWidgets[0].dependenciesMap).toBeFalsy(); expect(uWidgets[0].id).toBe("2"); }); + it('init', () => { + const defaults = {initialSize: { + 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 no widgetsConfig', () => { const state = widgets(undefined, configureMap({})); expect(state.containers[DEFAULT_TARGET].widgets).toBeFalsy(); diff --git a/web/client/reducers/widgets.js b/web/client/reducers/widgets.js index 16b5bf2506..074f02484b 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); } @@ -104,10 +108,17 @@ function widgetsReducer(state = emptyState, action) { if (widget.widgetType === 'chart') { widget = omit(widget, ["layer", "url"]); } + const w = state?.defaults?.initialSize?.w ?? 1; + const h = state?.defaults?.initialSize?.h ?? 1; return arrayUpsert(`containers[${action.target}].widgets`, { id: action.id, ...widget, - dataGrid: action.id && {y: 0, x: 0, w: 1, h: 1} + dataGrid: action.id && { + w, + h, + x: 0, + y: 0 + } }, { id: action.widget.id || action.id }, state);