diff --git a/web/client/components/widgets/builder/wizard/map/Toolbar.jsx b/web/client/components/widgets/builder/wizard/map/Toolbar.jsx index b0495728bb..d62705470e 100644 --- a/web/client/components/widgets/builder/wizard/map/Toolbar.jsx +++ b/web/client/components/widgets/builder/wizard/map/Toolbar.jsx @@ -17,7 +17,7 @@ const getSaveTooltipId = (step, { id } = {}) => { return "widgets.builder.wizard.addToTheMap"; }; -module.exports = ({ step = 0, buttons, tocButtons = [], editorData = {}, setPage = () => { }, onFinish = () => { }, toggleLayerSelector = () => { } } = {}) => ( { }, onFinish = () => { }, toggleLayerSelector = () => { } } = {}) => ( setPage(Math.min(step + 1, 2)), visible: step === 0, glyph: "arrow-right", diff --git a/web/client/components/widgets/enhancers/__tests__/dependenciesToMapProp-test.jsx b/web/client/components/widgets/enhancers/__tests__/dependenciesToMapProp-test.jsx new file mode 100644 index 0000000000..74a083c2b7 --- /dev/null +++ b/web/client/components/widgets/enhancers/__tests__/dependenciesToMapProp-test.jsx @@ -0,0 +1,40 @@ +/* + * Copyright 2018, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ +const React = require('react'); +const ReactDOM = require('react-dom'); +const {createSink} = require('recompose'); +const expect = require('expect'); +const dependenciesToMapProp = require('../dependenciesToMapProp'); + +describe('dependenciesToMapProp enhancer', () => { + beforeEach((done) => { + document.body.innerHTML = '
'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById("container")); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('dependenciesToMapProp rendering with defaults', (done) => { + const Sink = dependenciesToMapProp('center')(createSink( props => { + expect(props.map.center.x).toBe(1); + expect(props.map.center.y).toBe(1); + done(); + })); + ReactDOM.render(, document.getElementById("container")); + }); + it('dependenciesToMapProp rendering with mapSync', (done) => { + const Sink = dependenciesToMapProp('center')(createSink(props => { + expect(props.map.center.x).toBe(2); + expect(props.map.center.y).toBe(2); + done(); + })); + ReactDOM.render(, document.getElementById("container")); + }); +}); diff --git a/web/client/components/widgets/enhancers/dependenciesToMapProp.js b/web/client/components/widgets/enhancers/dependenciesToMapProp.js new file mode 100644 index 0000000000..ce4636a6b7 --- /dev/null +++ b/web/client/components/widgets/enhancers/dependenciesToMapProp.js @@ -0,0 +1,24 @@ +/* + * Copyright 2018, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ +const {set} = require('../../../utils/ImmutableUtils'); +const {shallowEqual, branch, withPropsOnChange} = require('recompose'); +/** + * Syncs map center + */ +module.exports = (prop) => branch( + (({mapSync} = {}) => mapSync), + withPropsOnChange( + ({ mapSync, dependencies = {} } = {}, { mapSync: newMapSync, dependencies: newDependencies }) => + newDependencies && shallowEqual(dependencies[prop], newDependencies[prop]) + || mapSync === newMapSync, + ({ map, mapSync, dependencies = {} }) => ({ + mapStateSource: "__dependency_system__", + map: dependencies[prop] && mapSync ? set(prop, dependencies[prop], map) : map + }) + ) +); diff --git a/web/client/components/widgets/widget/DefaultWidget.jsx b/web/client/components/widgets/widget/DefaultWidget.jsx index eee562fd12..d0d6dae16e 100644 --- a/web/client/components/widgets/widget/DefaultWidget.jsx +++ b/web/client/components/widgets/widget/DefaultWidget.jsx @@ -13,6 +13,7 @@ const wpsChart = require('../enhancers/wpsChart'); const {compose} = require('recompose'); const dependenciesToFilter = require('../enhancers/dependenciesToFilter'); const dependenciesToWidget = require('../enhancers/dependenciesToWidget'); +const dependenciesToMapProp = require('../enhancers/dependenciesToMapProp'); const ChartWidget = compose( dependenciesToWidget, dependenciesToFilter, @@ -20,7 +21,12 @@ const ChartWidget = compose( enhanceChartWidget )(require('./ChartWidget')); const TextWidget = deleteWidget(require('./TextWidget')); -const MapWidget = deleteWidget(require('./MapWidget')); +const MapWidget = compose( + dependenciesToWidget, + dependenciesToMapProp('center'), + dependenciesToMapProp('zoom'), + deleteWidget +)(require('./MapWidget')); const TableWidget = compose( dependenciesToWidget, dependenciesToFilter, diff --git a/web/client/components/widgets/widget/MapWidget.jsx b/web/client/components/widgets/widget/MapWidget.jsx index 0c64a86c2b..9d60cc01cb 100644 --- a/web/client/components/widgets/widget/MapWidget.jsx +++ b/web/client/components/widgets/widget/MapWidget.jsx @@ -8,7 +8,7 @@ const React = require('react'); const WidgetContainer = require('./WidgetContainer'); const InfoPopover = require('./InfoPopover'); - +const { omit } = require('lodash'); const Message = require('../../I18N/Message'); const {withHandlers} = require('recompose'); const MapView = withHandlers({ @@ -33,6 +33,7 @@ module.exports = ({ toggleDeleteConfirm = () => { }, id, title, loading, description, map, + mapStateSource, confirmDelete = false, onDelete = () => {} } = {}) => @@ -45,5 +46,5 @@ module.exports = ({ } > - + ); diff --git a/web/client/epics/__tests__/widgets-test.js b/web/client/epics/__tests__/widgets-test.js index 00cdb9d81d..219b6e10c7 100644 --- a/web/client/epics/__tests__/widgets-test.js +++ b/web/client/epics/__tests__/widgets-test.js @@ -9,10 +9,13 @@ var expect = require('expect'); const { testEpic, addTimeoutEpic, TEST_TIMEOUT } = require('./epicTestUtils'); const { - clearWidgetsOnLocationChange + clearWidgetsOnLocationChange, + alignDependenciesToWidgets } = require('../widgets'); const { - CLEAR_WIDGETS + CLEAR_WIDGETS, + insertWidget, + LOAD_DEPENDENCIES } = require('../../actions/widgets'); const { savingMap, @@ -126,4 +129,22 @@ describe('widgets Epics', () => { }; }); }); + it('alignDependenciesToWidgets triggered on insertWidget', (done) => { + const checkActions = actions => { + expect(actions.length).toBe(1); + const action = actions[0]; + expect(action.type).toBe(LOAD_DEPENDENCIES); + expect(action.dependencies).toExist(); + expect(action.dependencies.center).toBe("map.center"); + expect(action.dependencies.viewport).toBe("map.bbox"); + expect(action.dependencies.zoom).toBe("map.zoom"); + done(); + }; + testEpic(alignDependenciesToWidgets, + 1, + [insertWidget({id: 'test'})], + checkActions, + {}); + }); + }); diff --git a/web/client/epics/widgets.js b/web/client/epics/widgets.js index 0e53eb2f2d..de0f6e6e74 100644 --- a/web/client/epics/widgets.js +++ b/web/client/epics/widgets.js @@ -49,7 +49,8 @@ module.exports = { .map((maps=[]) => loadDependencies(maps.reduce( (deps, m) => ({ ...deps, [m === "map" ? "viewport" : `${m}.viewport`]: `${m}.bbox`, // {viewport: "map.bbox"} or {"widgets[ID_W].viewport": "widgets[ID_W].bbox"} - [m === "map" ? "center" : `${m}.center`]: `${m}.center` // {center: "map.center"} or {"widgets[ID_W].center": "widgets[ID_W].center"} + [m === "map" ? "center" : `${m}.center`]: `${m}.center`, // {center: "map.center"} or {"widgets[ID_W].center": "widgets[ID_W].center"} + [m === "map" ? "zoom" : `${m}.zoom`]: `${m}.zoom` }), {})) ), clearWidgetsOnLocationChange: (action$, {getState = () => {}} = {}) => diff --git a/web/client/plugins/widgetbuilder/ChartBuilder.jsx b/web/client/plugins/widgetbuilder/ChartBuilder.jsx index 1242a88cd0..ebbc5a6365 100644 --- a/web/client/plugins/widgetbuilder/ChartBuilder.jsx +++ b/web/client/plugins/widgetbuilder/ChartBuilder.jsx @@ -66,7 +66,7 @@ const chooseLayerEnhancer = compose( ) ); -module.exports = chooseLayerEnhancer(({ enabled, onClose = () => { }, availableDependencies = {}, dependencies, ...props} = {}) => +module.exports = chooseLayerEnhancer(({ enabled, onClose = () => { }, availableDependencies = [], dependencies, ...props} = {}) => (} diff --git a/web/client/plugins/widgetbuilder/CounterBuilder.jsx b/web/client/plugins/widgetbuilder/CounterBuilder.jsx index 84a0ae3d7e..6c3f8e58d1 100644 --- a/web/client/plugins/widgetbuilder/CounterBuilder.jsx +++ b/web/client/plugins/widgetbuilder/CounterBuilder.jsx @@ -67,7 +67,7 @@ const chooseLayerEnhancer = compose( ) ); -module.exports = chooseLayerEnhancer(({ enabled, onClose = () => { }, availableDependencies={}, dependencies, ...props } = {}) => +module.exports = chooseLayerEnhancer(({ enabled, onClose = () => { }, availableDependencies=[], dependencies, ...props } = {}) => (} diff --git a/web/client/plugins/widgetbuilder/MapBuilder.jsx b/web/client/plugins/widgetbuilder/MapBuilder.jsx index 28d31e8e01..8ffdcbd52e 100644 --- a/web/client/plugins/widgetbuilder/MapBuilder.jsx +++ b/web/client/plugins/widgetbuilder/MapBuilder.jsx @@ -8,7 +8,7 @@ const React = require('react'); const {connect} = require('react-redux'); const {onEditorChange} = require('../../actions/widgets'); -const { wizardSelector, wizardStateToProps, availableDependenciesSelector} = require('./commons'); +const { wizardSelector, wizardStateToProps} = require('./commons'); const layerSelector = require('./enhancers/layerSelector'); const manageLayers = require('./enhancers/manageLayers'); const mapToolbar = require('./enhancers/mapToolbar'); @@ -63,17 +63,16 @@ const mapBuilder = compose( map: editorData.map })), handleNodeSelection, - handleNodeEditing, - connect(availableDependenciesSelector) + handleNodeEditing ); module.exports = mapBuilder(({ - enabled, onClose = () => {}, - toggleLayerSelector = () => {}, - editNode, setEditNode, closeNodeEditor, selectedGroups=[], selectedLayers=[], selectedNodes, onNodeSelect = () => {} } = {}, - availableDependencies = [] - ) => + enabled, onClose = () => {}, + toggleLayerSelector = () => {}, + editNode, setEditNode, closeNodeEditor, selectedGroups=[], selectedLayers=[], selectedNodes, onNodeSelect = () => {}, + availableDependencies =[] + } = {}) => ( diff --git a/web/client/plugins/widgetbuilder/TableBuilder.jsx b/web/client/plugins/widgetbuilder/TableBuilder.jsx index c93da143e5..0543586f4d 100644 --- a/web/client/plugins/widgetbuilder/TableBuilder.jsx +++ b/web/client/plugins/widgetbuilder/TableBuilder.jsx @@ -78,7 +78,7 @@ const chooseLayerEnhancer = compose( ) ); -module.exports = chooseLayerEnhancer(({ enabled, onClose = () => { }, editorData = {}, availableDependencies = {}, dependencies, ...props } = {}) => +module.exports = chooseLayerEnhancer(({ enabled, onClose = () => { }, editorData = {}, availableDependencies = [], dependencies, ...props } = {}) => ( ({ + availableDependencies: availableDependencies.filter(d => !(editorData.id && d.indexOf(editorData.id) >= 0)) + })), + withProps(({ editorData = {} }) => ({ + canConnect: true, + connected: editorData.mapSync + })), + withHandlers({ + toggleConnection: ({ onChange = () => { }, editorData = {} }) => (widget, id) => { + onChange('mapSync', !editorData.mapSync); + const center = + !editorData.mapSync + ? id === 'map' + ? 'center' + : `${id}.center` + : undefined; + const zoom = + !editorData.mapSync + ? id === 'map' + ? 'zoom' + : `${id}.zoom` + : undefined; + const { dependenciesMap = {} } = editorData; + onChange('dependenciesMap', !editorData.mapSync && center && zoom !== undefined + ? { ...dependenciesMap, center, zoom } : + omit(dependenciesMap, ['center', 'zoom'])); + + + } + }), + withHandlers({ + toggleConnection: ({ toggleConnection = () => { }, toggleDependencySelector = () => { }, widget, editorData = {} }) => + (available = []) => available.length === 1 || editorData.mapSync + ? toggleConnection(widget, available[0]) + : toggleDependencySelector() + }) +); diff --git a/web/client/plugins/widgetbuilder/enhancers/connection/withConnectButton.js b/web/client/plugins/widgetbuilder/enhancers/connection/withConnectButton.js new file mode 100644 index 0000000000..df946e00d7 --- /dev/null +++ b/web/client/plugins/widgetbuilder/enhancers/connection/withConnectButton.js @@ -0,0 +1,36 @@ +/* + * Copyright 2018, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ +const { withProps, compose } = require('recompose'); +/** + * Returns an enhancer that add `stepButtons` for viewport connection to a wizard toolbar + * @param {function} showCondition parses props to allow visualization of the buttons (if other connect condition are satisfied) + */ +module.exports = (showCondition = () => true) => compose( + withProps(({ + stepButtons = [], + toggleConnection = () => { }, + availableDependencies = [], + canConnect, + connected, + ...props + }) => ({ + stepButtons: [{ + onClick: () => toggleConnection(availableDependencies), + disabled: availableDependencies.length > 1, // TODO: remove when support multi map + visible: showCondition(props) && canConnect && availableDependencies.length > 0, + bsStyle: connected ? "success" : "primary", + glyph: connected ? "plug" : "unplug", + tooltipId: connected + ? "widgets.builder.wizard.clearConnection" + : availableDependencies.length === 1 + ? "widgets.builder.wizard.connectToTheMap" + : "connection to multiple maps not supported yet" // TODO: "widgets.builder.wizard.connectToAMap" + }, ...stepButtons + ] + })) +); diff --git a/web/client/plugins/widgetbuilder/enhancers/mapToolbar.js b/web/client/plugins/widgetbuilder/enhancers/mapToolbar.js index f61555d8af..0dec88677f 100644 --- a/web/client/plugins/widgetbuilder/enhancers/mapToolbar.js +++ b/web/client/plugins/widgetbuilder/enhancers/mapToolbar.js @@ -7,14 +7,16 @@ */ const { compose, branch, withProps, withHandlers} = require('recompose'); const {connect} = require('react-redux'); -const { insertWidget, setPage} = require('../../../actions/widgets'); +const { insertWidget, setPage, onEditorChange} = require('../../../actions/widgets'); const manageLayers = require('./manageLayers'); const handleNodeEditing = require('./handleNodeEditing'); const { wizardSelector, wizardStateToProps } = require('../commons'); - +const withConnectButton = require('./connection/withConnectButton'); +const mapPositionConnect = require('./connection/mapPositionConnect'); module.exports = compose( connect(wizardSelector, { setPage, + onChange: onEditorChange, insertWidget }, wizardStateToProps @@ -49,6 +51,8 @@ module.exports = compose( tooltipId: "toc.toolTrashLayerTooltip" }] })) - ) + ), + mapPositionConnect, + withConnectButton(({step}) => step === 0) ); diff --git a/web/client/reducers/__tests__/widgets-test.js b/web/client/reducers/__tests__/widgets-test.js index 739c70073f..c1497a3981 100644 --- a/web/client/reducers/__tests__/widgets-test.js +++ b/web/client/reducers/__tests__/widgets-test.js @@ -29,6 +29,10 @@ describe('Test the widgets reducer', () => { it('initial state', () => { const state = widgets(undefined, {type: "START"}); expect(state.containers).toExist(); + expect(state.dependencies.key).toBeFalsy(); + expect(state.dependencies.viewport).toBe("map.bbox"); + expect(state.dependencies.center).toBe("map.center"); + expect(state.dependencies.zoom).toBe("map.zoom"); }); it('editNewWidget', () => { const state = widgets(undefined, editNewWidget({a: "A"}, {step: 0})); @@ -121,5 +125,7 @@ describe('Test the widgets reducer', () => { expect(state).toExist(); expect(state.dependencies.key).toBeFalsy(); expect(state.dependencies.viewport).toBe("map.bbox"); + expect(state.dependencies.center).toBe("map.center"); + expect(state.dependencies.zoom).toBe("map.zoom"); }); }); diff --git a/web/client/reducers/widgets.js b/web/client/reducers/widgets.js index 9cf9283f5f..076b28fd1e 100644 --- a/web/client/reducers/widgets.js +++ b/web/client/reducers/widgets.js @@ -19,7 +19,8 @@ const {arrayUpsert, arrayDelete} = require('../utils/ImmutableUtils'); const emptyState = { dependencies: { viewport: "map.bbox", - center: "map.center" + center: "map.center", + zoom: "map.zoom" }, containers: { floating: {