From c78a59632f911bfe3207f79ca2da4a3c82c884d9 Mon Sep 17 00:00:00 2001 From: mbarto Date: Mon, 8 May 2017 10:36:07 +0200 Subject: [PATCH 01/11] Fixes #1788: configurability of SettingModal showElevationChart (#1789) --- web/client/components/TOC/fragments/SettingsModal.jsx | 2 ++ .../components/TOC/fragments/settings/Elevation.jsx | 10 ++++++---- .../fragments/settings/__tests__/Elevation-test.jsx | 1 - web/client/utils/LayersUtils.js | 1 - 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/web/client/components/TOC/fragments/SettingsModal.jsx b/web/client/components/TOC/fragments/SettingsModal.jsx index 14ca2b96ab..94cf794dc5 100644 --- a/web/client/components/TOC/fragments/SettingsModal.jsx +++ b/web/client/components/TOC/fragments/SettingsModal.jsx @@ -41,6 +41,7 @@ const SettingsModal = React.createClass({ closeText: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.element]), options: React.PropTypes.object, chartStyle: React.PropTypes.object, + showElevationChart: React.PropTypes.bool, buttonSize: React.PropTypes.string, closeGlyph: React.PropTypes.string, panelStyle: React.PropTypes.object, @@ -135,6 +136,7 @@ const SettingsModal = React.createClass({ return ( {} + onChange: () => {}, + showElevationChart: true }; }, shouldComponentUpdate(nextProps) { return this.props.element.id !== nextProps.element.id; }, renderElevationsChart(elevations) { - if (this.props.elevations.showChart) { + if (this.props.showElevationChart) { return ( { this.props.onChange("params", Object.assign({}, { [this.props.elevations.name]: value[0] diff --git a/web/client/components/TOC/fragments/settings/__tests__/Elevation-test.jsx b/web/client/components/TOC/fragments/settings/__tests__/Elevation-test.jsx index ce2ab3b914..debd309413 100644 --- a/web/client/components/TOC/fragments/settings/__tests__/Elevation-test.jsx +++ b/web/client/components/TOC/fragments/settings/__tests__/Elevation-test.jsx @@ -40,7 +40,6 @@ describe('test Layer Properties Elevation component', () => { name: "ELEVATION", units: "Meters", positive: false, - showChart: true, values: ["1.5", "5.0", "10.0", "15.0", "20.0", "25.0", "30.0"] } }; diff --git a/web/client/utils/LayersUtils.js b/web/client/utils/LayersUtils.js index 065f439641..9c08f6ddfa 100644 --- a/web/client/utils/LayersUtils.js +++ b/web/client/utils/LayersUtils.js @@ -47,7 +47,6 @@ const getElevationDimension = (dimensions = []) => { return dimensions.reduce((previous, dim) => { return (dim.name.toLowerCase() === 'elevation' || dim.name.toLowerCase() === 'depth') ? assign({ - showChart: true, positive: dim.name.toLowerCase() === 'elevation' }, dim, { name: dim.name.toLowerCase() === 'elevation' ? dim.name : 'DIM_' + dim.name From 1cdb8f008e7c6dbb0dac09874a63727fea0bf2a3 Mon Sep 17 00:00:00 2001 From: Lorenzo Natali Date: Mon, 8 May 2017 14:44:23 +0200 Subject: [PATCH 02/11] Fix #1776. Move measure into burger menu (#1790) The dialog for measure is now in the burger menu. This fixes #1776. The old plugin will be kept in MeasurePanel + MeasureResult plugin and deprecated. All the duplicated functionalities are now merged in a unique component. --- docma-config.json | 3 + .../mapcontrols/locate/LocateBtn.jsx | 1 + .../mapcontrols/measure/MeasureComponent.jsx | 50 ++++--- .../mapcontrols/measure/MeasureDialog.jsx | 45 ++++++ .../mapcontrols/measure/MeasureResults.jsx | 136 ------------------ .../__tests__/MeasureComponent-test.jsx | 13 +- .../measure/__tests__/MeasureDialog-test.jsx | 47 ++++++ web/client/localConfig.json | 7 +- web/client/plugins/Measure.jsx | 79 +++++----- web/client/plugins/MeasurePanel.jsx | 66 +++++++++ web/client/plugins/MeasureResults.jsx | 34 ++++- web/client/plugins/measure/index.js | 15 ++ web/client/product/plugins.js | 1 - 13 files changed, 286 insertions(+), 211 deletions(-) create mode 100644 web/client/components/mapcontrols/measure/MeasureDialog.jsx delete mode 100644 web/client/components/mapcontrols/measure/MeasureResults.jsx create mode 100644 web/client/components/mapcontrols/measure/__tests__/MeasureDialog-test.jsx create mode 100644 web/client/plugins/MeasurePanel.jsx create mode 100644 web/client/plugins/measure/index.js diff --git a/docma-config.json b/docma-config.json index b8498bf5b0..97552b51e2 100644 --- a/docma-config.json +++ b/docma-config.json @@ -145,6 +145,9 @@ "web/client/plugins/GlobeViewSwitcher.jsx", "web/client/plugins/GoFull.jsx", "web/client/plugins/Map.jsx", + "web/client/plugins/Measure.jsx", + "web/client/plugins/MeasurePanel.jsx", + "web/client/plugins/MeasureResults.jsx", "web/client/plugins/FullScreen.jsx", "web/client/plugins/Identify.jsx", "web/client/plugins/Login.jsx", diff --git a/web/client/components/mapcontrols/locate/LocateBtn.jsx b/web/client/components/mapcontrols/locate/LocateBtn.jsx index 66e077b261..c7020cb22e 100644 --- a/web/client/components/mapcontrols/locate/LocateBtn.jsx +++ b/web/client/components/mapcontrols/locate/LocateBtn.jsx @@ -17,6 +17,7 @@ let geoLocationAllowed = false; const LocateBtn = React.createClass({ propTypes: { id: React.PropTypes.string, + hide: React.PropTypes.bool, btnConfig: React.PropTypes.object, text: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.element]), help: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.element]), diff --git a/web/client/components/mapcontrols/measure/MeasureComponent.jsx b/web/client/components/mapcontrols/measure/MeasureComponent.jsx index 1a9057ef47..52e3f7b29b 100644 --- a/web/client/components/mapcontrols/measure/MeasureComponent.jsx +++ b/web/client/components/mapcontrols/measure/MeasureComponent.jsx @@ -9,11 +9,8 @@ const React = require('react'); const {Panel, ButtonGroup, Tooltip, Glyphicon, Button} = require('react-bootstrap'); const ToggleButton = require('../../buttons/ToggleButton'); -var NumberFormat = require('../../I18N/Number'); - -const lineRuleIcon = require('./img/line-ruler.png'); -const areaRuleIcon = require('./img/area-ruler.png'); -const bearingRuleIcon = require('./img/bearing-ruler.png'); +const NumberFormat = require('../../I18N/Number'); +const Message = require('../../I18N/Message'); const measureUtils = require('../../../utils/MeasureUtils'); const localeUtils = require('../../../utils/LocaleUtils'); @@ -49,12 +46,15 @@ const MeasureComponent = React.createClass({ lineMeasureEnabled: React.PropTypes.bool, areaMeasureEnabled: React.PropTypes.bool, bearingMeasureEnabled: React.PropTypes.bool, + showButtons: React.PropTypes.bool, showResults: React.PropTypes.bool, lineGlyph: React.PropTypes.string, areaGlyph: React.PropTypes.string, bearingGlyph: React.PropTypes.string, useButtonGroup: React.PropTypes.bool, withReset: React.PropTypes.bool, + showButtonsLabels: React.PropTypes.bool, + inlineGlyph: React.PropTypes.bool, formatLength: React.PropTypes.func, formatArea: React.PropTypes.func, formatBearing: React.PropTypes.func @@ -64,7 +64,6 @@ const MeasureComponent = React.createClass({ }, getDefaultProps() { return { - icon: , columnProperties: { xs: 4, sm: 4, @@ -75,12 +74,17 @@ const MeasureComponent = React.createClass({ length: {unit: 'm', label: 'm'}, area: {unit: 'sqm', label: 'm²'} }, + showButtons: true, showResults: true, useButtonGroup: false, withReset: false, lineGlyph: "1-measure-lenght", areaGlyph: "1-measure-area", bearingGlyph: "1-bearing", + showButtonsLabels: true, + lengthLabel: , + areaLabel: , + bearingLabel: , formatLength: (uom, value) => measureUtils.getFormattedLength(uom, value), formatArea: (uom, value) => measureUtils.getFormattedArea(uom, value), formatBearing: (value) => measureUtils.getFormattedBearingValue(round(value || 0, 6)) @@ -135,33 +139,41 @@ const MeasureComponent = React.createClass({ } return null; }, + renderLabel(msgId) { + if (this.props.showButtonsLabels) { + return {localeUtils.getMessageById(this.context.messages, msgId)}; + } + }, + renderText(glyph, labelId) { + if (glyph) { + return ( + + {this.renderLabel(labelId)} + ); + } + return this.renderLabel(labelId); + }, renderButtons() { let {lineToolTip, areaToolTip, bearingToolTip} = this.getToolTips(); return (
- {this.props.lineGlyph ? : } - {localeUtils.getMessageById(this.context.messages, "measureComponent.MeasureLength")} - } + text={this.renderText(this.props.inlineGlyph && this.props.lineGlyph, "measureComponent.MeasureLength")} + glyphicon={!this.props.inlineGlyph && this.props.lineGlyph} style={{"width": "100%", textAlign: "left"}} pressed={this.props.lineMeasureEnabled} onClick={this.onLineClick} tooltip={lineToolTip} /> - {this.props.areaGlyph ? : } - {localeUtils.getMessageById(this.context.messages, "measureComponent.MeasureArea")} - } + text={this.renderText(this.props.inlineGlyph && this.props.areaGlyph, "measureComponent.MeasureArea")} + glyphicon={!this.props.inlineGlyph && this.props.areaGlyph} style={{"width": "100%", textAlign: "left"}} pressed={this.props.areaMeasureEnabled} onClick={this.onAreaClick} tooltip={areaToolTip} /> - {this.props.bearingGlyph ? : } - {localeUtils.getMessageById(this.context.messages, "measureComponent.MeasureBearing")} - } + text={this.renderText(this.props.inlineGlyph && this.props.bearingGlyph, "measureComponent.MeasureBearing")} + glyphicon={!this.props.inlineGlyph && this.props.bearingGlyph} style={{"width": "100%", textAlign: "left"}} pressed={this.props.bearingMeasureEnabled} onClick={this.onBearingClick} @@ -185,7 +197,7 @@ const MeasureComponent = React.createClass({ render() { return ( - {this.props.useButtonGroup ? this.renderButtonGroup() : this.renderButtons() } + {this.props.showButtons && (this.props.useButtonGroup ? this.renderButtonGroup() : this.renderButtons()) } {this.renderPanel()} ); diff --git a/web/client/components/mapcontrols/measure/MeasureDialog.jsx b/web/client/components/mapcontrols/measure/MeasureDialog.jsx new file mode 100644 index 0000000000..97f2ab0c7a --- /dev/null +++ b/web/client/components/mapcontrols/measure/MeasureDialog.jsx @@ -0,0 +1,45 @@ +/* + * Copyright 2017, 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 {Glyphicon} = require('react-bootstrap'); +const MeasureComponent = require('./MeasureComponent'); +const Message = require('../../I18N/Message'); +const Dialog = require('../../misc/Dialog'); + + +const MeasureDialog = React.createClass({ + propTypes: { + show: React.PropTypes.bool, + closeGlyph: React.PropTypes.string, + onClose: React.PropTypes.func + }, + getDefaultProps() { + return { + show: false, + closeGlyph: "1-close" + }; + }, + onClose() { + this.props.onClose(false); + }, + render() { + return this.props.show ? ( +
+   + +
+
+ +
+
) : null; + } +}); +module.exports = MeasureDialog; diff --git a/web/client/components/mapcontrols/measure/MeasureResults.jsx b/web/client/components/mapcontrols/measure/MeasureResults.jsx deleted file mode 100644 index 6d933e53ac..0000000000 --- a/web/client/components/mapcontrols/measure/MeasureResults.jsx +++ /dev/null @@ -1,136 +0,0 @@ -/** - * Copyright 2016, 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 {Panel, Glyphicon} = require('react-bootstrap'); -const Draggable = require('react-draggable'); -const Dialog = require('../../misc/Dialog'); - -const Message = require('../../../plugins/locale/Message'); -const NumberFormat = require('../../I18N/Number'); -const measureUtils = require('../../../utils/MeasureUtils'); - -const {isEqual} = require('lodash'); - -const MeasureResults = React.createClass({ - propTypes: { - id: React.PropTypes.string, - name: React.PropTypes.string, - columnProperties: React.PropTypes.object, - propertiesChangeHandler: React.PropTypes.func, - lengthLabel: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.element]), - areaLabel: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.element]), - bearingLabel: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.element]), - uom: React.PropTypes.shape({ - length: React.PropTypes.shape({ - unit: React.PropTypes.string.isRequired, - label: React.PropTypes.string.isRequired - }), - area: React.PropTypes.shape({ - unit: React.PropTypes.string.isRequired, - label: React.PropTypes.string.isRequired - }) - }), - toggleMeasure: React.PropTypes.func, - measurement: React.PropTypes.object, - lineMeasureEnabled: React.PropTypes.bool, - areaMeasureEnabled: React.PropTypes.bool, - bearingMeasureEnabled: React.PropTypes.bool, - separatePanel: React.PropTypes.bool, - title: React.PropTypes.object, - panelStyle: React.PropTypes.object, - panelClassName: React.PropTypes.string, - style: React.PropTypes.object, - withPanelAsContainer: React.PropTypes.bool, - closeGlyph: React.PropTypes.string - }, - getDefaultProps() { - return { - columnProperties: { - xs: 4, - sm: 4, - md: 4 - }, - id: "measure-result-panel", - uom: { - length: {unit: 'm', label: 'm'}, - area: {unit: 'sqm', label: 'm²'} - }, - separatePanel: true, - title: , - panelStyle: { - minWidth: "300px", - left: "302px", - zIndex: 100, - position: "absolute", - overflow: "auto" - }, - panelClassName: "drawer-menu-panel", - withPanelAsContainer: false, - closeGlyph: "1-close" - }; - }, - shouldComponentUpdate(nextProps) { - return !isEqual(nextProps, this.props); - }, - onModalHiding() { - const newMeasureState = { - lineMeasureEnabled: false, - areaMeasureEnabled: false, - bearingMeasureEnabled: false, - geomType: null, - // reset old measurements - len: 0, - area: 0, - bearing: 0 - }; - this.props.toggleMeasure(newMeasureState); - }, - renderHeader() { - return ( - - - - - ); - }, - renderBody() { - let decimalFormat = {style: "decimal", minimumIntegerDigits: 1, maximumFractionDigits: 2, minimumFractionDigits: 2}; - return ( -
-

{this.props.lengthLabel}: {this.props.uom.length.label}

-

{this.props.areaLabel}: {this.props.uom.area.label}

-

{this.props.bearingLabel}: {measureUtils.getFormattedBearingValue(this.props.measurement.bearing)}

-
- ); - }, - render() { - if (this.props.lineMeasureEnabled || this.props.areaMeasureEnabled || this.props.bearingMeasureEnabled ) { - // compatibility with old theme - if (this.props.withPanelAsContainer) { - return ( - - - {this.renderBody()} - - - ); - } - // else use the new theme - return ( - - {this.renderHeader()} - {this.renderBody()} - - ); - } - return null; - } -}); - -module.exports = MeasureResults; diff --git a/web/client/components/mapcontrols/measure/__tests__/MeasureComponent-test.jsx b/web/client/components/mapcontrols/measure/__tests__/MeasureComponent-test.jsx index d5cc548bb9..7992f0ff0f 100644 --- a/web/client/components/mapcontrols/measure/__tests__/MeasureComponent-test.jsx +++ b/web/client/components/mapcontrols/measure/__tests__/MeasureComponent-test.jsx @@ -34,7 +34,7 @@ describe("test the MeasureComponent", () => { it('test creation of button UIs ', () => { let measurement = {}; - const mc = ReactDOM.render(, document.getElementById("container")); + const mc = ReactDOM.render(, document.getElementById("container")); expect(mc).toExist(); const domNode = ReactDOM.findDOMNode(mc); expect(domNode).toExist(); @@ -43,6 +43,17 @@ describe("test the MeasureComponent", () => { expect(domButtons.length).toBe(3); }); + it('test creation of button UIs useButtonGroup option ', () => { + let measurement = {}; + const mc = ReactDOM.render(, document.getElementById("container")); + expect(mc).toExist(); + const domNode = ReactDOM.findDOMNode(mc); + expect(domNode).toExist(); + const domButtons = domNode.getElementsByClassName('btn-block'); + expect(domButtons).toExist(); + expect(domButtons.length).toBe(1); + }); + it('test creation of measurement result panel UI ', () => { let measurement = {}; const mc = ReactDOM.render(, document.getElementById("container")); diff --git a/web/client/components/mapcontrols/measure/__tests__/MeasureDialog-test.jsx b/web/client/components/mapcontrols/measure/__tests__/MeasureDialog-test.jsx new file mode 100644 index 0000000000..5e2c69c94b --- /dev/null +++ b/web/client/components/mapcontrols/measure/__tests__/MeasureDialog-test.jsx @@ -0,0 +1,47 @@ +/** + * Copyright 2015, 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. + */ +var expect = require('expect'); + +var React = require('react'); +var ReactDOM = require('react-dom'); +const ReactTestUtils = require('react-addons-test-utils'); +var MeasureDialog = require('../MeasureDialog'); + + +describe("test the MeasureComponent", () => { + beforeEach((done) => { + document.body.innerHTML = '
'; + setTimeout(done); + }); + + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById("container")); + document.body.innerHTML = ''; + setTimeout(done); + }); + + it('test component creation', () => { + let measurement = {}; + const mc = ReactDOM.render(, document.getElementById("container")); + expect(mc).toExist(); + }); + it('test close', () => { + let measurement = {}; + const handlers = { + onClose() {} + }; + let spy = expect.spyOn(handlers, "onClose"); + const mc = ReactDOM.render(, document.getElementById("container")); + expect(mc).toExist(); + const dom = ReactDOM.findDOMNode(mc); + const closeBtn = dom.getElementsByClassName('close')[0]; + expect(closeBtn).toExist(); + ReactTestUtils.Simulate.click(closeBtn); + expect(spy.calls.length).toBe(1); + }); +}); diff --git a/web/client/localConfig.json b/web/client/localConfig.json index ad40e9e39c..7a6ca49270 100644 --- a/web/client/localConfig.json +++ b/web/client/localConfig.json @@ -137,11 +137,8 @@ "activateQueryTool": true } }, "Tutorial", "BackgroundSwitcher", { - "name": "Measure", - "cfg": { - "showResults": false - } - }, "MeasureResults", "Print", "ShapeFile", { + "name": "Measure" + }, "Print", "ShapeFile", { "name": "Settings", "cfg": { "wrap": true diff --git a/web/client/plugins/Measure.jsx b/web/client/plugins/Measure.jsx index b2dc0f7d83..e68e8d90aa 100644 --- a/web/client/plugins/Measure.jsx +++ b/web/client/plugins/Measure.jsx @@ -1,4 +1,4 @@ -/** +/* * Copyright 2016, GeoSolutions Sas. * All rights reserved. * @@ -11,65 +11,54 @@ const {Glyphicon} = require('react-bootstrap'); const Message = require('./locale/Message'); -const lineRuleIcon = require('./toolbar/assets/img/line-ruler.png'); - const assign = require('object-assign'); - +const {createSelector} = require('reselect'); const {changeMeasurement} = require('../actions/measurement'); +const {toggleControl} = require('../actions/controls'); +const {MeasureDialog} = require('./measure'); -const Measure = require('../components/mapcontrols/measure/MeasureComponent'); - -const MeasureComponent = React.createClass({ - render() { - const labels = { - lengthButtonText: , - areaButtonText: , - resetButtonText: , - lengthLabel: , - areaLabel: , - bearingLabel: - }; - return ; - } -}); - -const MeasurePlugin = connect((state) => { +const selector = (state) => { return { measurement: state.measurement || {}, lineMeasureEnabled: state.measurement && state.measurement.lineMeasureEnabled || false, areaMeasureEnabled: state.measurement && state.measurement.areaMeasureEnabled || false, bearingMeasureEnabled: state.measurement && state.measurement.bearingMeasureEnabled || false }; -}, { - toggleMeasure: changeMeasurement -}, null, {pure: false})(MeasureComponent); +}; +const toggleMeasureTool = toggleControl.bind(null, 'measure', null); +/** + * Measure plugin. Allows to show the tool to measure dinstances, areas and bearing. + * @class + * @name Measure + * @memberof plugins + * @prop {boolean} showResults shows the measure in the panel itself. + */ +const Measure = connect( + createSelector([ + selector, + (state) => state && state.controls && state.controls.measure && state.controls.measure.enabled + ], + (measure, show) => ({ + show, + ...measure + } + )), + { + toggleMeasure: changeMeasurement, + onClose: toggleMeasureTool + }, null, {pure: false})(MeasureDialog); module.exports = { - MeasurePlugin: assign(MeasurePlugin, { - Toolbar: { + MeasurePlugin: assign(Measure, { + BurgerMenu: { name: 'measurement', position: 9, - panel: true, - exclusive: true, - wrap: true, + panel: false, help: , tooltip: "measureComponent.tooltip", - icon: , - title: "measureComponent.title", - priority: 1 - }, - DrawerMenu: { - name: 'measurement', - position: 3, - glyph: "1-stilo", - icon: , - title: 'measureComponent.title', - showPanel: false, - buttonConfig: { - buttonClassName: "square-button no-border", - tooltip: "toc.measure" - }, - priority: 2 + text: , + icon: , + action: toggleMeasureTool } }), reducers: {measurement: require('../reducers/measurement')} diff --git a/web/client/plugins/MeasurePanel.jsx b/web/client/plugins/MeasurePanel.jsx new file mode 100644 index 0000000000..a004b9effd --- /dev/null +++ b/web/client/plugins/MeasurePanel.jsx @@ -0,0 +1,66 @@ +/* + * Copyright 2016, 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 {connect} = require('react-redux'); +const {Glyphicon} = require('react-bootstrap'); + +const Message = require('./locale/Message'); + +const assign = require('object-assign'); +const {changeMeasurement} = require('../actions/measurement'); +const {MeasureComponent} = require('./measure'); + +const selector = (state) => { + return { + measurement: state.measurement || {}, + lineMeasureEnabled: state.measurement && state.measurement.lineMeasureEnabled || false, + areaMeasureEnabled: state.measurement && state.measurement.areaMeasureEnabled || false, + bearingMeasureEnabled: state.measurement && state.measurement.bearingMeasureEnabled || false + }; +}; +/** + * MeasurePanel plugin. Shows the measure tool in the TOC. This is an old version of measure tool that will be removed soon. + * @class + * @name MeasurePanel + * @memberof plugins + * @deprecated since version 2017.03.01 + * @prop {boolean} showResults shows the measure in the panel itself. It can be disabled if you are using a plugin like MeasureResults to show the results + */ +const MeasurePanelPlugin = connect(selector, { + toggleMeasure: changeMeasurement +}, null, {pure: false})(MeasureComponent); + +module.exports = { + MeasurePanelPlugin: assign(MeasurePanelPlugin, { + Toolbar: { + name: 'measurement', + position: 9, + panel: true, + exclusive: true, + wrap: true, + help: , + tooltip: "measureComponent.tooltip", + icon: , + title: "measureComponent.title", + priority: 1 + }, + DrawerMenu: { + name: 'measurement', + position: 3, + glyph: "1-ruler", + title: 'measureComponent.title', + showPanel: false, + buttonConfig: { + buttonClassName: "square-button no-border", + tooltip: "toc.measure" + }, + priority: 2 + } + }), + reducers: {measurement: require('../reducers/measurement')} +}; diff --git a/web/client/plugins/MeasureResults.jsx b/web/client/plugins/MeasureResults.jsx index 3de34bed9d..b189bd7943 100644 --- a/web/client/plugins/MeasureResults.jsx +++ b/web/client/plugins/MeasureResults.jsx @@ -1,4 +1,4 @@ -/** +/* * Copyright 2016, GeoSolutions Sas. * All rights reserved. * @@ -12,19 +12,45 @@ const Message = require('./locale/Message'); const {changeMeasurement} = require('../actions/measurement'); -const MeasureRes = require('../components/mapcontrols/measure/MeasureResults'); +const {MeasureDialog} = require('./measure'); const MeasureComponent = React.createClass({ + propTypes: { + lineMeasureEnabled: React.PropTypes.bool, + areaMeasureEnabled: React.PropTypes.bool, + bearingMeasureEnabled: React.PropTypes.bool, + toggleMeasure: React.PropTypes.func + }, + onModalHiding() { + const newMeasureState = { + lineMeasureEnabled: false, + areaMeasureEnabled: false, + bearingMeasureEnabled: false, + geomType: null, + // reset old measurements + len: 0, + area: 0, + bearing: 0 + }; + this.props.toggleMeasure(newMeasureState); + }, render() { const labels = { lengthLabel: , areaLabel: , bearingLabel: }; - return ; + return ; } }); - +/** + * MeasureResults plugin. Shows the measure results. This is an old version of measure tool that will be removed soon. + * It should be used with the MeasurePanel plugin + * @class + * @name MeasureResults + * @memberof plugins + * @deprecated since version 2017.03.01 + */ const MeasureResultsPlugin = connect((state) => { return { measurement: state.measurement || {}, diff --git a/web/client/plugins/measure/index.js b/web/client/plugins/measure/index.js new file mode 100644 index 0000000000..48cb850bc8 --- /dev/null +++ b/web/client/plugins/measure/index.js @@ -0,0 +1,15 @@ +/* + * Copyright 2017, 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 MeasureComponent = require('../../components/mapcontrols/measure/MeasureComponent'); +const MeasureDialog = require('../../components/mapcontrols/measure/MeasureDialog'); + +module.exports = { + MeasureComponent, + MeasureDialog +}; diff --git a/web/client/product/plugins.js b/web/client/product/plugins.js index b7e1dd1372..926749e6a0 100644 --- a/web/client/product/plugins.js +++ b/web/client/product/plugins.js @@ -14,7 +14,6 @@ module.exports = { TOCPlugin: require('../plugins/TOC'), BackgroundSwitcherPlugin: require('../plugins/BackgroundSwitcher'), MeasurePlugin: require('../plugins/Measure'), - MeasureResultsPlugin: require('../plugins/MeasureResults'), MapPlugin: require('../plugins/Map'), ToolbarPlugin: require('../plugins/Toolbar'), DrawerMenuPlugin: require('../plugins/DrawerMenu'), From ce68cd8ed59a3c2712192e8c5152f4d0f0541ec3 Mon Sep 17 00:00:00 2001 From: Lorenzo Natali Date: Mon, 8 May 2017 18:13:11 +0200 Subject: [PATCH 03/11] Fixed include for windows path --- web/client/plugins/Measure.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/client/plugins/Measure.jsx b/web/client/plugins/Measure.jsx index e68e8d90aa..5696f5984b 100644 --- a/web/client/plugins/Measure.jsx +++ b/web/client/plugins/Measure.jsx @@ -15,7 +15,7 @@ const assign = require('object-assign'); const {createSelector} = require('reselect'); const {changeMeasurement} = require('../actions/measurement'); const {toggleControl} = require('../actions/controls'); -const {MeasureDialog} = require('./measure'); +const {MeasureDialog} = require('./measure/index'); const selector = (state) => { return { From cf91f48119d9fa0f3f30c39a8c4d6378769d7b2d Mon Sep 17 00:00:00 2001 From: Matteo V Date: Tue, 9 May 2017 11:08:36 +0200 Subject: [PATCH 04/11] Fixed extra features for openlayers (#1793) * Fixed extra features for openlayers * added test * fixed test --- .../components/map/openlayers/Feature.jsx | 21 ++- .../map/openlayers/__tests__/Feature-test.jsx | 106 ++++++++++++ .../map/openlayers/plugins/VectorLayer.js | 152 +++++++++--------- web/client/plugins/Map.jsx | 6 +- 4 files changed, 197 insertions(+), 88 deletions(-) create mode 100644 web/client/components/map/openlayers/__tests__/Feature-test.jsx diff --git a/web/client/components/map/openlayers/Feature.jsx b/web/client/components/map/openlayers/Feature.jsx index b3bd7c5abe..a690ef94a0 100644 --- a/web/client/components/map/openlayers/Feature.jsx +++ b/web/client/components/map/openlayers/Feature.jsx @@ -1,5 +1,5 @@ -/** - * Copyright 2015, GeoSolutions Sas. +/* + * Copyright 2017, GeoSolutions Sas. * All rights reserved. * * This source code is licensed under the BSD-style license found in the @@ -14,6 +14,7 @@ let Feature = React.createClass({ propTypes: { type: React.PropTypes.string, properties: React.PropTypes.object, + crs: React.PropTypes.string, container: React.PropTypes.object, // TODO it must be a ol.layer.vector (maybe pass the source is more correct here?) geometry: React.PropTypes.object, // TODO check for geojson format for geometry msId: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.number]), @@ -29,8 +30,12 @@ let Feature = React.createClass({ const geometry = this.props.geometry && this.props.geometry.coordinates; if (this.props.container && geometry) { - this._feature = format.readFeatures({type: this.props.type, properties: this.props.properties, geometry: this.props.geometry, id: this.props.msId}); - this._feature.forEach((f) => f.getGeometry().transform(this.props.featuresCrs, 'EPSG:3857')); + this._feature = format.readFeatures({ + type: this.props.type, + properties: this.props.properties, + geometry: this.props.geometry, + id: this.props.msId}); + this._feature.forEach((f) => f.getGeometry().transform(this.props.featuresCrs, this.props.crs || 'EPSG:3857')); this.props.container.getSource().addFeatures(this._feature); } }, @@ -42,7 +47,7 @@ let Feature = React.createClass({ if (newProps.container && geometry) { this._feature = format.readFeatures({type: newProps.type, properties: newProps.properties, geometry: newProps.geometry, id: this.props.msId}); - this._feature.forEach((f) => f.getGeometry().transform(newProps.featuresCrs, 'EPSG:3857')); + this._feature.forEach((f) => f.getGeometry().transform(newProps.featuresCrs, this.props.crs || 'EPSG:3857')); newProps.container.getSource().addFeatures(this._feature); } } @@ -61,11 +66,11 @@ let Feature = React.createClass({ if (Array.isArray(this._feature)) { const layersSource = this.props.container.getSource(); this._feature.map((feature) => { - let fetureId = feature.getId(); - if (fetureId === undefined) { + let featureId = feature.getId(); + if (featureId === undefined) { layersSource.removeFeature(feature); }else { - layersSource.removeFeature(layersSource.getFeatureById(fetureId)); + layersSource.removeFeature(layersSource.getFeatureById(featureId)); } }); } else { diff --git a/web/client/components/map/openlayers/__tests__/Feature-test.jsx b/web/client/components/map/openlayers/__tests__/Feature-test.jsx new file mode 100644 index 0000000000..323c36f82f --- /dev/null +++ b/web/client/components/map/openlayers/__tests__/Feature-test.jsx @@ -0,0 +1,106 @@ +/* + * Copyright 2017, 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 ol = require('openlayers'); +const Feature = require('../Feature.jsx'); +const expect = require('expect'); +require('../../../../utils/openlayers/Layers'); +require('../plugins/VectorLayer'); + +describe('Test Feature', () => { + document.body.innerHTML = '
'; + let map; + + beforeEach((done) => { + document.body.innerHTML = '
'; + map = new ol.Map({ + layers: [ + ], + controls: ol.control.defaults({ + attributionOptions: /** @type {olx.control.AttributionOptions} */ ({ + collapsible: false + }) + }), + target: 'map', + view: new ol.View({ + center: [0, 0], + zoom: 5 + }) + }); + setTimeout(done); + }); + + afterEach((done) => { + map.setTarget(null); + document.body.innerHTML = ''; + setTimeout(done); + }); + + it('adding a feature to a vector layer', () => { + var options = { + crs: 'EPSG:4326', + features: { + type: 'FeatureCollection', + crs: { + 'type': 'name', + 'properties': { + 'name': 'EPSG:4326' + } + }, + features: [ + { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [[ + [13, 43], + [15, 43], + [15, 44], + [13, 44] + ]] + }, + properties: { + 'name': "some name" + } + } + ] + } + }; + const source = new ol.source.Vector({ + features: [] + }); + const msId = "some value"; + let container = new ol.layer.Vector({ + msId, + source: source, + visible: true, + zIndex: 1 + }); + const geometry = options.features.features[0].geometry; + const type = options.features.features[0].type; + const properties = options.features.features[0].properties; + + // create layers + let layer = ReactDOM.render( + , document.getElementById("container")); + + expect(layer).toExist(); + // count layers + expect(container.getSource().getFeatures().length === 1 ); + }); +}); diff --git a/web/client/components/map/openlayers/plugins/VectorLayer.js b/web/client/components/map/openlayers/plugins/VectorLayer.js index dd7d1221b6..803021f372 100644 --- a/web/client/components/map/openlayers/plugins/VectorLayer.js +++ b/web/client/components/map/openlayers/plugins/VectorLayer.js @@ -10,6 +10,7 @@ var Layers = require('../../../../utils/openlayers/Layers'); var markerIcon = require('../img/marker-icon.png'); var markerShadow = require('../img/marker-shadow.png'); var ol = require('openlayers'); +const {isEqual} = require('lodash'); const assign = require('object-assign'); @@ -104,106 +105,101 @@ var styleFunction = function(feature) { return defaultStyles[feature.getGeometry().getType()]; }; -Layers.registerType('vector', { - create: (options) => { - let features; - let featuresCrs = options.featuresCrs || 'EPSG:4326'; - let layerCrs = options.crs || 'EPSG:3857'; - if (options.features) { - let featureCollection = options.features; - if (Array.isArray(options.features)) { - featureCollection = { "type": "FeatureCollection", features: featureCollection}; - } - features = (new ol.format.GeoJSON()).readFeatures(featureCollection); - if (featuresCrs !== layerCrs) { - features.forEach((f) => f.getGeometry().transform(featuresCrs, layerCrs)); - } - } +function getStyle(options) { + let style = options.nativeStyle; + if (!style && options.style) { + style = { + stroke: new ol.style.Stroke( options.style.stroke ? options.style.stroke : { + color: 'blue', + width: 1 + }), + fill: new ol.style.Fill(options.style.fill ? options.style.fill : { + color: 'blue' + }) + }; - const source = new ol.source.Vector({ - features: features - }); - - let style = options.nativeStyle; - if (!style && options.style) { + if (options.style.type === "Point") { style = { - stroke: new ol.style.Stroke( options.style.stroke ? options.style.stroke : { - color: 'blue', - width: 1 - }), - fill: new ol.style.Fill(options.style.fill ? options.style.fill : { - color: 'blue' - }) + image: new ol.style.Circle(assign({}, style, {radius: options.style.radius || 5})) }; + } - if (options.style.type === "Point") { - style = { - image: new ol.style.Circle(assign({}, style, {radius: options.style.radius || 5})) - }; - } - - if (options.style.iconUrl ) { - let markerStyle = [new ol.style.Style({ + if (options.style.iconUrl ) { + let markerStyle = [new ol.style.Style({ + image: new ol.style.Icon(({ + anchor: options.iconAnchor || [0.5, 1], + anchorXUnits: ( options.iconAnchor || options.iconAnchor === 0) ? 'pixels' : 'fraction', + anchorYUnits: ( options.iconAnchor || options.iconAnchor === 0) ? 'pixels' : 'fraction', + src: options.style.iconUrl + })) + })]; + if (options.style.shadowUrl) { + markerStyle = [new ol.style.Style({ image: new ol.style.Icon(({ - anchor: options.iconAnchor || [0.5, 1], - anchorXUnits: ( options.iconAnchor || options.iconAnchor === 0) ? 'pixels' : 'fraction', - anchorYUnits: ( options.iconAnchor || options.iconAnchor === 0) ? 'pixels' : 'fraction', - src: options.style.iconUrl - })) - })]; - if (options.style.shadowUrl) { - markerStyle = [new ol.style.Style({ - image: new ol.style.Icon(({ - anchor: [12, 41], - anchorXUnits: 'pixels', - anchorYUnits: 'pixels', - src: options.style.shadowUrl || markerShadow - })) - }), markerStyle [0]]; + anchor: [12, 41], + anchorXUnits: 'pixels', + anchorYUnits: 'pixels', + src: options.style.shadowUrl || markerShadow + })) + }), markerStyle [0]]; + } + style = (feature) => { + const type = feature.getGeometry().getType(); + switch (type) { + case "Point": + case "MultiPoint": + return markerStyle; + default: + return styleFunction(feature); } - style = (feature) => { - const type = feature.getGeometry().getType(); - switch (type) { - case "Point": - case "MultiPoint": - return markerStyle; - default: - return styleFunction(feature); - } - }; - } else { - style = new ol.style.Style(style); + }; + } else { + style = new ol.style.Style(style); + } + } + return (options.styleName && !options.overrideOLStyle) ? (feature) => { + if (options.styleName === "marker") { + const type = feature.getGeometry().getType(); + switch (type) { + case "Point": + case "MultiPoint": + return defaultStyles.marker; + default: + break; } } + return defaultStyles[options.styleName]; + } : style || styleFunction; +} +Layers.registerType('vector', { + create: (options) => { + let features = []; + + const source = new ol.source.Vector({ + features: features + }); + + const style = getStyle(options); return new ol.layer.Vector({ msId: options.id, source: source, visible: options.visibility !== false, zIndex: options.zIndex, - style: (options.styleName && !options.overrideOLStyle) ? (feature) => { - if (options.styleName === "marker") { - const type = feature.getGeometry().getType(); - switch (type) { - case "Point": - case "MultiPoint": - return defaultStyles.marker; - default: - break; - } - } - return defaultStyles[options.styleName]; - } : style || styleFunction + style }); }, update: (layer, newOptions, oldOptions) => { - const oldCrs = oldOptions.crs || 'EPSG:3857'; - const newCrs = newOptions.crs || 'EPSG:3857'; + const oldCrs = oldOptions.crs || oldOptions.srs || 'EPSG:3857'; + const newCrs = newOptions.crs || newOptions.srs || 'EPSG:3857'; if (newCrs !== oldCrs) { layer.getSource().forEachFeature((f) => { f.getGeometry().transform(oldCrs, newCrs); }); } + if (!isEqual(oldOptions.style, newOptions.style)) { + layer.setStyle(getStyle(newOptions)); + } }, render: () => { return null; diff --git a/web/client/plugins/Map.jsx b/web/client/plugins/Map.jsx index 6ad1fa7bce..aae84e09fa 100644 --- a/web/client/plugins/Map.jsx +++ b/web/client/plugins/Map.jsx @@ -172,6 +172,7 @@ const MapPlugin = React.createClass({ {this.props.features.map( (feature) => { return (); })} @@ -195,18 +196,19 @@ const MapPlugin = React.createClass({ return this.props.layers.map((layer, index) => { return ( - {this.renderLayerContent(layer)} + {this.renderLayerContent(layer, projection)} ); }).concat(this.props.features && this.props.features.length && this.getHighlightLayer(projection, this.props.layers.length) || []); }, - renderLayerContent(layer) { + renderLayerContent(layer, projection) { if (layer.features && layer.type === "vector") { return layer.features.map( (feature) => { return ( Date: Tue, 9 May 2017 12:07:30 +0200 Subject: [PATCH 05/11] Fix #1795. Use proxyUtils to guess CORS policies (#1796) --- .../cesium/__tests__/Layer-test-chrome.jsx | 54 ++++++++++++++++++- .../components/map/cesium/plugins/WMSLayer.js | 25 +++------ .../map/cesium/plugins/WMTSLayer.js | 13 +---- web/client/utils/ProxyUtils.js | 12 +++-- web/client/utils/__tests__/ProxyUtils-test.js | 7 ++- 5 files changed, 74 insertions(+), 37 deletions(-) diff --git a/web/client/components/map/cesium/__tests__/Layer-test-chrome.jsx b/web/client/components/map/cesium/__tests__/Layer-test-chrome.jsx index 84787e85eb..d9437985e7 100644 --- a/web/client/components/map/cesium/__tests__/Layer-test-chrome.jsx +++ b/web/client/components/map/cesium/__tests__/Layer-test-chrome.jsx @@ -139,7 +139,7 @@ describe('Cesium layer', () => { expect(layer).toExist(); }); - it('creates a wms layer for CesiumLayer map', () => { + it('creates a wms layer for Cesium map', () => { var options = { "type": "wms", "visibility": true, @@ -157,9 +157,30 @@ describe('Cesium layer', () => { expect(map.imageryLayers.length).toBe(1); expect(map.imageryLayers._layers[0]._imageryProvider._url).toBe('{s}'); expect(map.imageryLayers._layers[0]._imageryProvider._tileProvider._subdomains.length).toBe(1); + expect(map.imageryLayers._layers[0]._imageryProvider.proxy.proxy).toExist(); }); + it('check wms layer proxy skip for relative urls', () => { + var options = { + "type": "wms", + "visibility": true, + "name": "nurc:Arc_Sample", + "group": "Meteo", + "format": "image/png", + "url": "/geoserver/wms" + }; + // create layers + var layer = ReactDOM.render( + , document.getElementById("container")); - it('creates a wmts layer for openlayers map', () => { + expect(layer).toExist(); + expect(map.imageryLayers.length).toBe(1); + expect(map.imageryLayers._layers[0]._imageryProvider._url).toBe('{s}'); + expect(map.imageryLayers._layers[0]._imageryProvider._tileProvider._subdomains.length).toBe(1); + expect(map.imageryLayers._layers[0]._imageryProvider.proxy.proxy).toNotExist(); + }); + + it('creates a wmts layer for Cesium map', () => { var options = { "type": "wmts", "visibility": true, @@ -187,6 +208,35 @@ describe('Cesium layer', () => { // count layers expect(map.imageryLayers.length).toBe(1); expect(map.imageryLayers._layers[0]._imageryProvider._url).toExist(); + expect(map.imageryLayers._layers[0]._imageryProvider.proxy.proxy).toExist(); + }); + it('check a wmts layer skips proxy config', () => { + var options = { + "type": "wmts", + "visibility": true, + "name": "nurc:Arc_Sample", + "group": "Meteo", + "format": "image/png", + "tileMatrixSet": "EPSG:900913", + "matrixIds": { + "EPSG:4326": [{ + ranges: { + cols: {max: 0, min: 0}, + rows: {max: 0, min: 0} + } + }] + }, + "url": "/geoserver/gwc/service/wmts" + }; + // create layers + var layer = ReactDOM.render( + , document.getElementById("container")); + expect(layer).toExist(); + // count layers + expect(map.imageryLayers.length).toBe(1); + expect(map.imageryLayers._layers[0]._imageryProvider._url).toExist(); + expect(map.imageryLayers._layers[0]._imageryProvider.proxy.proxy).toNotExist(); }); it('creates a wms layer with single tile for CesiumLayer map', () => { diff --git a/web/client/components/map/cesium/plugins/WMSLayer.js b/web/client/components/map/cesium/plugins/WMSLayer.js index 0348e5d80b..5af9dfcec9 100644 --- a/web/client/components/map/cesium/plugins/WMSLayer.js +++ b/web/client/components/map/cesium/plugins/WMSLayer.js @@ -1,4 +1,4 @@ -/** +/* * Copyright 2015, GeoSolutions Sas. * All rights reserved. * @@ -6,11 +6,12 @@ * LICENSE file in the root directory of this source tree. */ -var Layers = require('../../../../utils/cesium/Layers'); -var ConfigUtils = require('../../../../utils/ConfigUtils'); -var Cesium = require('../../../../libs/cesium'); -var assign = require('object-assign'); -var {isObject, isArray} = require('lodash'); +const Layers = require('../../../../utils/cesium/Layers'); +const ConfigUtils = require('../../../../utils/ConfigUtils'); +const ProxyUtils = require('../../../../utils/ProxyUtils'); +const Cesium = require('../../../../libs/cesium'); +const assign = require('object-assign'); +const {isArray} = require('lodash'); function getWMSURLs( urls ) { return urls.map((url) => url.split("\?")[0]); @@ -76,17 +77,7 @@ function wmsToCesiumOptions(options) { let proxyUrl = ConfigUtils.getProxyUrl({}); let proxy; if (proxyUrl) { - let useCORS = []; - if (isObject(proxyUrl)) { - useCORS = proxyUrl.useCORS || []; - proxyUrl = proxyUrl.url; - } - let url = options.url; - if (isArray(url)) { - url = url[0]; - } - const isCORS = useCORS.reduce((found, current) => found || url.indexOf(current) === 0, false); - proxy = !isCORS && proxyUrl; + proxy = ProxyUtils.needProxy(options.url) && proxyUrl; } // NOTE: can we use opacity to manage visibility? return assign({ diff --git a/web/client/components/map/cesium/plugins/WMTSLayer.js b/web/client/components/map/cesium/plugins/WMTSLayer.js index 049f37d623..b017e1ff1b 100644 --- a/web/client/components/map/cesium/plugins/WMTSLayer.js +++ b/web/client/components/map/cesium/plugins/WMTSLayer.js @@ -9,6 +9,7 @@ var Layers = require('../../../../utils/cesium/Layers'); var ConfigUtils = require('../../../../utils/ConfigUtils'); const CoordinatesUtils = require('../../../../utils/CoordinatesUtils'); +const ProxyUtils = require('../../../../utils/ProxyUtils'); const WMTSUtils = require('../../../../utils/WMTSUtils'); var Cesium = require('../../../../libs/cesium'); var assign = require('object-assign'); @@ -95,17 +96,7 @@ function wmtsToCesiumOptions(options) { let proxyUrl = ConfigUtils.getProxyUrl({}); let proxy; if (proxyUrl) { - let useCORS = []; - if (isObject(proxyUrl)) { - useCORS = proxyUrl.useCORS || []; - proxyUrl = proxyUrl.url; - } - let url = options.url; - if (isArray(url)) { - url = url[0]; - } - const isCORS = useCORS.reduce((found, current) => found || url.indexOf(current) === 0, false); - proxy = !isCORS && proxyUrl; + proxy = ProxyUtils.needProxy(options.url) && proxyUrl; } // NOTE: can we use opacity to manage visibility? const isValid = isValidTile(options.matrixIds && options.matrixIds[tileMatrixSet]); diff --git a/web/client/utils/ProxyUtils.js b/web/client/utils/ProxyUtils.js index c50d98405c..1ea9606df1 100644 --- a/web/client/utils/ProxyUtils.js +++ b/web/client/utils/ProxyUtils.js @@ -7,13 +7,15 @@ */ const ConfigUtils = require('./ConfigUtils'); -const {isObject} = require('lodash'); - +const {isArray, isObject} = require('lodash'); var ProxyUtils = { needProxy: function(uri, config = {}) { - var needed = false; - var sameOrigin = !(uri.indexOf("http") === 0); - var urlParts = !sameOrigin && uri.match(/([^:]*:)\/\/([^:]*:?[^@]*@)?([^:\/\?]*):?([^\/\?]*)/); + if ( isArray(uri) ) { + return uri.reduce((result, current) => ProxyUtils.needProxy(current) && result, true); + } + let needed = false; + let sameOrigin = !(uri.indexOf("http") === 0); + let urlParts = !sameOrigin && uri.match(/([^:]*:)\/\/([^:]*:?[^@]*@)?([^:\/\?]*):?([^\/\?]*)/); if (urlParts) { let location = window.location; sameOrigin = diff --git a/web/client/utils/__tests__/ProxyUtils-test.js b/web/client/utils/__tests__/ProxyUtils-test.js index 53f8cb2416..6996fa1d4c 100644 --- a/web/client/utils/__tests__/ProxyUtils-test.js +++ b/web/client/utils/__tests__/ProxyUtils-test.js @@ -10,8 +10,11 @@ var ProxyUtils = require('../ProxyUtils'); describe('ProxyUtils test', () => { it('Need Proxy', () => { - let res = ProxyUtils.needProxy("http:someurl.com"); - expect(res).toBe(true); + expect(ProxyUtils.needProxy("http:someurl.com")).toBe(true); + expect(ProxyUtils.needProxy(["http:someurl.com", "http:someotherurl.com"])).toBe(true); + expect(ProxyUtils.needProxy("/geoserver")).toBe(false); + expect(ProxyUtils.needProxy(["/geoserver", "/geoserver1"])).toBe(false); + expect(ProxyUtils.needProxy([location.href])).toBe(false); }); it('GetProxyUrl', () => { let res = ProxyUtils.getProxyUrl({proxyUrl: "http:someurl.com"}); From e34ad2a702f3af74e544267b07e70f993ba35126 Mon Sep 17 00:00:00 2001 From: Lorenzo Natali Date: Tue, 9 May 2017 17:02:09 +0200 Subject: [PATCH 06/11] Add documentation for selectors and plugins (#1798) This is a porting of the documentation developed wrote durin the work on 3d. * Search * Zoom In * ZoomOut * ScaleBox * Locate * CoordinateUtils. --- docma-config.json | 7 ++++++- web/client/epics/index.jsdoc | 5 +++++ web/client/plugins/DrawerMenu.jsx | 2 +- web/client/plugins/Locate.jsx | 15 ++++++++++++++- web/client/plugins/ScaleBox.jsx | 14 +++++++++++++- web/client/plugins/ZoomIn.jsx | 10 ++++++++++ web/client/plugins/ZoomOut.jsx | 15 +++++++++++---- web/client/selectors/index.jsdoc | 11 +++++++++++ web/client/selectors/map.js | 17 +++++++++++++++-- web/client/utils/CoordinatesUtils.js | 10 ++++++---- web/client/utils/PluginsUtils.js | 1 - web/client/utils/ShareUtils.js | 3 +-- 12 files changed, 93 insertions(+), 17 deletions(-) create mode 100644 web/client/selectors/index.jsdoc diff --git a/docma-config.json b/docma-config.json index 97552b51e2..3044ab3b48 100644 --- a/docma-config.json +++ b/docma-config.json @@ -134,6 +134,7 @@ "web/client/epics/search.js", "web/client/utils/index.jsdoc", + "web/client/utils/CoordinatesUtils.js", "web/client/utils/PluginsUtils.js", "web/client/utils/ShareUtils.js" ], @@ -150,9 +151,13 @@ "web/client/plugins/MeasureResults.jsx", "web/client/plugins/FullScreen.jsx", "web/client/plugins/Identify.jsx", + "web/client/plugins/Locate.jsx", "web/client/plugins/Login.jsx", + "web/client/plugins/ScaleBox.jsx", "web/client/plugins/ScrollTop.jsx", - "web/client/plugins/Search.jsx" + "web/client/plugins/Search.jsx", + "web/client/plugins/ZoomIn.jsx", + "web/client/plugins/ZoomOut.jsx" ] }, "./docs/**/*md", diff --git a/web/client/epics/index.jsdoc b/web/client/epics/index.jsdoc index 3bce386328..da77fcc1c0 100644 --- a/web/client/epics/index.jsdoc +++ b/web/client/epics/index.jsdoc @@ -3,3 +3,8 @@ * @see {@link https://redux-observable.js.org/docs/basics/Epics.html|the redux-observable documentation} for details * @name epics */ + /** + * @external Observable + * @description A RxJS Observable + * @see {@link http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html|Observable} + */ diff --git a/web/client/plugins/DrawerMenu.jsx b/web/client/plugins/DrawerMenu.jsx index d39b7ab1df..bdefbb3e95 100644 --- a/web/client/plugins/DrawerMenu.jsx +++ b/web/client/plugins/DrawerMenu.jsx @@ -1,4 +1,4 @@ -/** +/* * Copyright 2016, GeoSolutions Sas. * All rights reserved. * diff --git a/web/client/plugins/Locate.jsx b/web/client/plugins/Locate.jsx index fda906b30f..f3a704b32b 100644 --- a/web/client/plugins/Locate.jsx +++ b/web/client/plugins/Locate.jsx @@ -1,4 +1,4 @@ -/** +/* * Copyright 2016, GeoSolutions Sas. * All rights reserved. * @@ -16,6 +16,19 @@ const {Glyphicon} = require('react-bootstrap'); const assign = require('object-assign'); +/** + * Locate Plugin. Provides button to locate the user's position on the map. + * By deafault it will follow the user until he moves the map. He can click again to + * restore the following mode. + * @class Locate + * @memberof plugins + * @static + * + * @prop {string} cfg.style CSS to apply to the button + * @prop {string} cfg.text The button text, if any + * @prop {string} cfg.className the class name for the button + * + */ const LocatePlugin = connect((state) => ({ locate: state.locate && state.locate.state || 'DISABLED' }), { diff --git a/web/client/plugins/ScaleBox.jsx b/web/client/plugins/ScaleBox.jsx index c98b76091c..1cb4fc9c31 100644 --- a/web/client/plugins/ScaleBox.jsx +++ b/web/client/plugins/ScaleBox.jsx @@ -1,4 +1,4 @@ -/** +/* * Copyright 2016, GeoSolutions Sas. * All rights reserved. * @@ -30,6 +30,18 @@ const selector = createSelector([mapSelector], (map) => ({ require('./scalebox/scalebox.css'); +/** + * ScaleBox Plugin. Provides a selector for the scale of the map. + * @class ScaleBox + * @memberof plugins + * @static + * + * @prop {object} cfg.style CSS to apply to the scalebox + * @prop {Boolean} cfg.readOnly the selector is readonly + * @prop {string} cfg.label label for the selector + * @prop {Boolean} cfg.useRawInput set true if you want to use an normal html input object + * + */ const ScaleBoxPlugin = React.createClass({ render() { return ( (state.map && state.map.present) || (state.map) || (state.config && state.config.map) || null; const projectionSelector = createSelector([mapSelector], (map) => map && map.projection); - +/** + * Get the scales of the current map + * @function + * @memberof selectors.map + * @param {object} state the state + * @return {number[]} the scales of the map + */ const scalesSelector = createSelector( [projectionSelector], (projection) => { diff --git a/web/client/utils/CoordinatesUtils.js b/web/client/utils/CoordinatesUtils.js index bb79e7529c..3ee29dc62b 100644 --- a/web/client/utils/CoordinatesUtils.js +++ b/web/client/utils/CoordinatesUtils.js @@ -1,10 +1,11 @@ -/** +/* * Copyright 2015, 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 Proj4js = require('proj4'); const proj4 = Proj4js; const assign = require('object-assign'); @@ -48,7 +49,10 @@ function determineCrs(crs) { } return crs; } - +/** + * Utilities for Coordinates conversion. + * @memberof utils + */ const CoordinatesUtils = { getUnits: function(projection) { const proj = new Proj4js.Proj(projection); @@ -126,11 +130,9 @@ const CoordinatesUtils = { }, /** * Reprojects a bounding box. - * * @param bbox {array} [minx, miny, maxx, maxy] * @param source {string} SRS of the given bbox * @param dest {string} SRS of the returned bbox - * * @return {array} [minx, miny, maxx, maxy] */ reprojectBbox: function(bbox, source, dest, normalize = true) { diff --git a/web/client/utils/PluginsUtils.js b/web/client/utils/PluginsUtils.js index 97697fe74b..fe5745f311 100644 --- a/web/client/utils/PluginsUtils.js +++ b/web/client/utils/PluginsUtils.js @@ -155,7 +155,6 @@ const defaultEpicWrapper = epic => (...args) => }); /** * Utilities to manage plugins - * @class * @memberof utils */ const PluginsUtils = { diff --git a/web/client/utils/ShareUtils.js b/web/client/utils/ShareUtils.js index 3ee231445c..e00a248f35 100644 --- a/web/client/utils/ShareUtils.js +++ b/web/client/utils/ShareUtils.js @@ -8,9 +8,8 @@ const Url = require('url'); /** - * Utility functions for Share tools + * Utility functions for Share tools. * @memberof utils - * @type {Object} */ var ShareUtils = { /** From eb9b6e9f20f06b15e8e616cbbc8d20162b9e1c50 Mon Sep 17 00:00:00 2001 From: Lorenzo Natali Date: Tue, 9 May 2017 17:02:35 +0200 Subject: [PATCH 07/11] Print support for relative URLs (#1797) Actually print plugin fails when you have a WMS like "/geoserver". This fixes this behavior using the origin of the browser. --- docma-config.json | 1 + web/client/utils/PrintUtils.js | 76 ++++++++++++++++++- web/client/utils/__tests__/PrintUtils-test.js | 72 +++++++++++++++++- 3 files changed, 145 insertions(+), 4 deletions(-) diff --git a/docma-config.json b/docma-config.json index 3044ab3b48..4bc183313d 100644 --- a/docma-config.json +++ b/docma-config.json @@ -136,6 +136,7 @@ "web/client/utils/index.jsdoc", "web/client/utils/CoordinatesUtils.js", "web/client/utils/PluginsUtils.js", + "web/client/utils/PrintUtils.js", "web/client/utils/ShareUtils.js" ], "jsapi": "web/client/jsapi/MapStore2.js", diff --git a/web/client/utils/PrintUtils.js b/web/client/utils/PrintUtils.js index 4ae3ac64a7..64e213841c 100644 --- a/web/client/utils/PrintUtils.js +++ b/web/client/utils/PrintUtils.js @@ -1,4 +1,4 @@ -/** +/* * Copyright 2016, GeoSolutions Sas. * All rights reserved. * @@ -21,15 +21,55 @@ const assign = require('object-assign'); const getGeomType = function(layer) { return (layer.features && layer.features[0]) ? layer.features[0].geometry.type : undefined; }; - +/** + * Utilities for Print + * @memberof utils + */ const PrintUtils = { + /** + * Given a static resource, returns the resource's absolute + * URL. Supports file paths with or without origin/protocol. + * @param {string} uri the uri to transform + * @param {string} [origin=window.location.origin] the origin to use + */ + toAbsoluteURL: (uri, origin) => { + // Handle absolute URLs (with protocol-relative prefix) + // Example: //domain.com/file.png + if (uri.search(/^\/\//) !== -1) { + return window.location.protocol + uri; + } + + // Handle absolute URLs (with explicit origin) + // Example: http://domain.com/file.png + if (uri.search(/:\/\//) !== -1) { + return uri; + } + + // Handle absolute URLs (without explicit origin) + // Example: /file.png + if (uri.search(/^\//) !== -1) { + return (origin || window.location.origin) + uri; + } + }, + /** + * Tranform the original URL configuration of the layer into a URL + * usable for the print service. + * @param {string|array} input Original URL + * @return {string} the URL modified as GeoServer requires + */ normalizeUrl: (input) => { let result = isArray(input) ? input[0] : input; if (result.indexOf('?') !== -1) { result = result.substring(0, result.indexOf('?')); } - return result; + return PrintUtils.toAbsoluteURL(result); }, + /** + * Find the layout name for the given options. + * The convention is: `PAGE_FORMAT + ("_2_pages_legend"|"_2_pages_legend"|"") + ("_landscape"|"")`` + * @param {object} spec the spec with the options + * @return {string} the layout name. + */ getLayoutName: (spec) => { let layoutName = [spec.sheet]; if (spec.includeLegend) { @@ -44,15 +84,33 @@ const PrintUtils = { } return layoutName.join('_'); }, + /** + * Gets the print scales allowed from the capabilities of the print service. + * @param {capabilities} capabilities the capabilities of the print service + * @return {array} the scales array + */ getPrintScales: (capabilities) => { return capabilities.scales.slice(0).reverse().map((scale) => parseFloat(scale.value)) || []; }, + /** + * Guest the nearest zoom level in the allowed scales + * @param {number} zoom the zoom level + * @param {array} scales the allowed scales + * @param {array} [mapScales=defaultScales] the map scales + * @return {number} the index that best approximates the current map scale + */ getNearestZoom: (zoom, scales, mapScales = defaultScales) => { const mapScale = mapScales[zoom]; return scales.reduce((previous, current, index) => { return current < mapScale ? previous : index; }, 0); }, + /** + * Get the mapSize for print preview, parsing the layout and limiting the width. + * @param {object} layout the layout object + * @param {number} maxWidth the max width for the mapSize + * @return {object} width and height of a map limited by the maxWidth and with the same ratio of the layout + */ getMapSize: (layout, maxWidth) => { if (layout) { const width = layout.rotation ? layout.map.height : layout.map.width; @@ -67,6 +125,11 @@ const PrintUtils = { height: 100 }; }, + /** + * Creates the mapfish print specification from the current configuration + * @param {object} spec the current configuration + * @return {object} the mapfish print configuration to send to the server + */ getMapfishPrintSpecification: (spec) => { const projectedCenter = CoordinatesUtils.reproject(spec.center, 'EPSG:4326', spec.projection); return { @@ -92,6 +155,13 @@ const PrintUtils = { "legends": PrintUtils.getMapfishLayersSpecification(spec.layers, spec, 'legend') }; }, + /** + * Generate the layers (or legend) specification for print. + * @param {array} layers the layers configurations + * @param {spec} spec the print configurations + * @param {string} purpose allowed values: `map|legend`. Tells which spec to generate. + * @return {array} the configuration array for layers (or legend) to send to the print service. + */ getMapfishLayersSpecification: (layers, spec, purpose) => { return layers.filter((layer) => PrintUtils.specCreators[layer.type] && PrintUtils.specCreators[layer.type][purpose]) .map((layer) => PrintUtils.specCreators[layer.type][purpose](layer, spec)); diff --git a/web/client/utils/__tests__/PrintUtils-test.js b/web/client/utils/__tests__/PrintUtils-test.js index 6b070e44db..42f7acdec4 100644 --- a/web/client/utils/__tests__/PrintUtils-test.js +++ b/web/client/utils/__tests__/PrintUtils-test.js @@ -1,4 +1,4 @@ -/** +/* * Copyright 2016, GeoSolutions Sas. * All rights reserved. * @@ -83,6 +83,50 @@ const mapFishVectorLayer = { ] } }; + +const testSpec = { + "antiAliasing": true, + "iconSize": 24, + "legendDpi": 96, + "fontFamily": "Verdana", + "fontSize": 8, + "bold": false, + "italic": false, + "resolution": "96", + "name": "", + "description": "", + "sheet": "A2", + "includeLegend": true, + "twoPages": true, + "center": { + "x": 8.930511, + "y": 44.417107, + "crs": "EPSG:4326" + }, + "zoom": 11, + "scaleZoom": 3, + "scale": 50000, + "layers": [ + { + "group": "background", + "source": "osm", + "name": "mapnik", + "title": "Open Street Map", + "type": "osm", + "visibility": true, + "singleTile": false, + "dimensions": [], + "id": "mapnik__0", + "loading": false, + "loadingError": false + } + ], + "projection": "EPSG:900913", + "size": { + "height": 462, + "width": 368 + } +}; describe('PrintUtils', () => { it('custom params are applied to wms layers', () => { @@ -99,6 +143,11 @@ describe('PrintUtils', () => { expect(specs.length).toBe(1); expect(specs[0].geoJson.features[0].geometry.coordinates[0], mapFishVectorLayer).toBe(mapFishVectorLayer.geoJson.features[0].geometry.coordinates[0]); }); + it('vector layer generation for legend', () => { + const specs = PrintUtils.getMapfishLayersSpecification([layer], {projection: "EPSG:3857"}, 'legend'); + expect(specs).toExist(); + expect(specs.length).toBe(1); + }); it('vector layer default point style', () => { const style = PrintUtils.getOlDefaultStyle({features: [{geometry: {type: "Point"}}]}); expect(style).toExist(); @@ -120,4 +169,25 @@ describe('PrintUtils', () => { expect(style).toExist(); expect(style.strokeWidth).toBe(3); }); + it('toAbsoluteUrl', () => { + const url = PrintUtils.toAbsoluteURL("/geoserver", "http://localhost:8080"); + expect(url).toExist(); + expect(url).toBe("http://localhost:8080/geoserver"); + expect(PrintUtils.toAbsoluteURL("//someurl/geoserver").indexOf("http")).toBe(0); + }); + it('getMapSize', () => { + expect(PrintUtils.getMapSize()).toExist(); // check defaults + expect(PrintUtils.getMapSize({map: {width: 200, height: 200}}, 150).height).toBe(150); + expect(PrintUtils.getMapSize({rotation: true, map: {width: 200, height: 100}}, 200).height).toBe(400); + }); + it('getNearestZoom', () => { + const scales = [1000, 1000, 1000000, 10000000]; + expect(PrintUtils.getNearestZoom(0, scales)).toBe(0); + }); + it('getMapfishPrintSpecification', () => { + const printSpec = PrintUtils.getMapfishPrintSpecification(testSpec); + expect(printSpec).toExist(); + expect(printSpec.dpi).toBe(96); + expect(printSpec.layers.length).toBe(1); + }); }); From ea0e9b103e78a7d177050bb0bf6fcf0a1368c57f Mon Sep 17 00:00:00 2001 From: Lorenzo Natali Date: Wed, 10 May 2017 10:51:26 +0200 Subject: [PATCH 08/11] Update inch.json --- inch.json | 1 + 1 file changed, 1 insertion(+) diff --git a/inch.json b/inch.json index 0d9f14e7b5..fd55653cb9 100644 --- a/inch.json +++ b/inch.json @@ -3,6 +3,7 @@ "included": [ "web/client/plugins/*.jsx", "web/client/actions/*js", + "web/client/selectors/*js", "web/client/reducers/*js", "web/client/utils/*.js", "web/client/epics/*js", From a222a30a802a89efb91ea13a0a7ab1e7d1558af6 Mon Sep 17 00:00:00 2001 From: mbarto Date: Wed, 10 May 2017 15:03:24 +0200 Subject: [PATCH 09/11] Fixed windows path issue (#1802) --- web/client/plugins/MeasureResults.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/client/plugins/MeasureResults.jsx b/web/client/plugins/MeasureResults.jsx index b189bd7943..1d7c86be00 100644 --- a/web/client/plugins/MeasureResults.jsx +++ b/web/client/plugins/MeasureResults.jsx @@ -12,7 +12,7 @@ const Message = require('./locale/Message'); const {changeMeasurement} = require('../actions/measurement'); -const {MeasureDialog} = require('./measure'); +const {MeasureDialog} = require('./measure/index'); const MeasureComponent = React.createClass({ propTypes: { From 1774aa0c1b16dda188b3d4fdd122f0cb69ad5733 Mon Sep 17 00:00:00 2001 From: mbarto Date: Thu, 11 May 2017 09:41:49 +0200 Subject: [PATCH 10/11] Fixes #1748: increase tooltips z-index (#1806) --- web/client/themes/default/ms2-theme.less | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web/client/themes/default/ms2-theme.less b/web/client/themes/default/ms2-theme.less index 5f107ed2ac..31c1ed8bac 100644 --- a/web/client/themes/default/ms2-theme.less +++ b/web/client/themes/default/ms2-theme.less @@ -1020,3 +1020,7 @@ select.form-control option { .mapstore2-embedded { overflow: hidden; } + +.tooltip { + z-index: 10000; +} From c84fd7b22c9330f69fc415b39202eecb53506eac Mon Sep 17 00:00:00 2001 From: Lorenzo Natali Date: Thu, 11 May 2017 09:46:08 +0200 Subject: [PATCH 11/11] Plugins disable functionalities (#1807) * add expression to disable plugins * add unit tests --- .../components/plugins/PluginsContainer.jsx | 2 +- web/client/containers/Embedded.jsx | 2 +- web/client/containers/MapViewer.jsx | 2 +- web/client/containers/Page.jsx | 2 +- web/client/plugins/Locate.jsx | 1 + web/client/plugins/Measure.jsx | 1 + web/client/plugins/Print.jsx | 1 + web/client/plugins/ScaleBox.jsx | 32 ++++++++++--------- web/client/plugins/ShapeFile.jsx | 1 + web/client/plugins/ZoomIn.jsx | 2 ++ web/client/plugins/ZoomOut.jsx | 1 + web/client/utils/PluginsUtils.js | 13 ++++++-- .../utils/__tests__/PluginUtils-test.js | 19 +++++++++++ 13 files changed, 58 insertions(+), 21 deletions(-) diff --git a/web/client/components/plugins/PluginsContainer.jsx b/web/client/components/plugins/PluginsContainer.jsx index 12be177440..8bf9fcd2c2 100644 --- a/web/client/components/plugins/PluginsContainer.jsx +++ b/web/client/components/plugins/PluginsContainer.jsx @@ -76,7 +76,7 @@ const PluginsContainer = React.createClass({ }, renderPlugins(plugins) { return plugins - .filter((Plugin) => !PluginsUtils.handleExpression(this.props.pluginsState, this.props.plugins && this.props.plugins.requires, Plugin.hide)) + .filter((Plugin) => !PluginsUtils.handleExpression(this.getState, this.props.plugins && this.props.plugins.requires, Plugin.hide)) .map(this.getPluginDescriptor) .filter((Plugin) => Plugin && !Plugin.impl.loadPlugin) .filter(this.filterPlugins) diff --git a/web/client/containers/Embedded.jsx b/web/client/containers/Embedded.jsx index e79d8b05b8..3d31ff61b4 100644 --- a/web/client/containers/Embedded.jsx +++ b/web/client/containers/Embedded.jsx @@ -17,7 +17,7 @@ const ConfigUtils = require('../utils/ConfigUtils'); const PluginsContainer = connect((state) => ({ mode: (urlQuery.mode || (state.browser && state.browser.mobile ? 'mobile' : 'desktop')), pluginsState: state && state.controls || {}, - monitoredState: PluginsUtils.filterState(state, ConfigUtils.getConfigProp('monitorState') || []) + monitoredState: PluginsUtils.getMonitoredState(state, ConfigUtils.getConfigProp('monitorState')) }))(require('../components/plugins/PluginsContainer')); const Embedded = React.createClass({ diff --git a/web/client/containers/MapViewer.jsx b/web/client/containers/MapViewer.jsx index a4fa40e636..c7f44b8de4 100644 --- a/web/client/containers/MapViewer.jsx +++ b/web/client/containers/MapViewer.jsx @@ -19,7 +19,7 @@ const PluginsContainer = connect((state) => ({ pluginsConfig: state.plugins || ConfigUtils.getConfigProp('plugins') || null, mode: (urlQuery.mode || state.mode || (state.browser && state.browser.mobile ? 'mobile' : 'desktop')), pluginsState: state && state.controls || {}, - monitoredState: PluginsUtils.filterState(state, ConfigUtils.getConfigProp('monitorState') || []) + monitoredState: PluginsUtils.getMonitoredState(state, ConfigUtils.getConfigProp('monitorState')) }))(require('../components/plugins/PluginsContainer')); const MapViewer = React.createClass({ diff --git a/web/client/containers/Page.jsx b/web/client/containers/Page.jsx index 39f265940a..be07dd2283 100644 --- a/web/client/containers/Page.jsx +++ b/web/client/containers/Page.jsx @@ -16,7 +16,7 @@ const ConfigUtils = require('../utils/ConfigUtils'); const PluginsContainer = connect((state) => ({ mode: urlQuery.mode || ((urlQuery.mobile || (state.browser && state.browser.mobile)) ? 'mobile' : 'desktop'), - monitoredState: PluginsUtils.filterState(state, ConfigUtils.getConfigProp('monitorState') || []) + monitoredState: PluginsUtils.getMonitoredState(state, ConfigUtils.getConfigProp('monitorState')) }))(require('../components/plugins/PluginsContainer')); const Page = React.createClass({ diff --git a/web/client/plugins/Locate.jsx b/web/client/plugins/Locate.jsx index f3a704b32b..5f8177e5e2 100644 --- a/web/client/plugins/Locate.jsx +++ b/web/client/plugins/Locate.jsx @@ -39,6 +39,7 @@ require('./locate/locate.css'); module.exports = { LocatePlugin: assign(LocatePlugin, { + disablePluginIf: "{state('mapType') === 'cesium'}", Toolbar: { name: 'locate', position: 2, diff --git a/web/client/plugins/Measure.jsx b/web/client/plugins/Measure.jsx index 5696f5984b..258fc5ffc5 100644 --- a/web/client/plugins/Measure.jsx +++ b/web/client/plugins/Measure.jsx @@ -50,6 +50,7 @@ const Measure = connect( module.exports = { MeasurePlugin: assign(Measure, { + disablePluginIf: "{state('mapType') === 'cesium'}", BurgerMenu: { name: 'measurement', position: 9, diff --git a/web/client/plugins/Print.jsx b/web/client/plugins/Print.jsx index ba6944b8bc..cb7ecf1992 100644 --- a/web/client/plugins/Print.jsx +++ b/web/client/plugins/Print.jsx @@ -356,6 +356,7 @@ const PrintPlugin = connect(selector, { module.exports = { PrintPlugin: assign(PrintPlugin, { + disablePluginIf: "{state('mapType') === 'cesium'}", Toolbar: { name: 'print', position: 7, diff --git a/web/client/plugins/ScaleBox.jsx b/web/client/plugins/ScaleBox.jsx index 1cb4fc9c31..4e996a13ce 100644 --- a/web/client/plugins/ScaleBox.jsx +++ b/web/client/plugins/ScaleBox.jsx @@ -18,7 +18,7 @@ const Message = require('./locale/Message'); const ScaleBox = require("../components/mapcontrols/scale/ScaleBox"); const mapUtils = require('../utils/MapUtils'); - +const assign = require('object-assign'); const selector = createSelector([mapSelector], (map) => ({ currentZoomLvl: map && map.zoom, @@ -30,6 +30,16 @@ const selector = createSelector([mapSelector], (map) => ({ require('./scalebox/scalebox.css'); +const ScaleBoxTool = React.createClass({ + render() { + return (}> + + ); + } +}); + /** * ScaleBox Plugin. Provides a selector for the scale of the map. * @class ScaleBox @@ -42,20 +52,12 @@ require('./scalebox/scalebox.css'); * @prop {Boolean} cfg.useRawInput set true if you want to use an normal html input object * */ -const ScaleBoxPlugin = React.createClass({ - render() { - return (}> - - ); - } -}); - - +const ScaleBoxPlugin = connect(selector, { + onChange: changeZoomLevel +})(ScaleBoxTool); module.exports = { - ScaleBoxPlugin: connect(selector, { - onChange: changeZoomLevel - })(ScaleBoxPlugin), + ScaleBoxPlugin: assign(ScaleBoxPlugin, { + disablePluginIf: "{state('mapType') === 'cesium'}" + }), reducers: {} }; diff --git a/web/client/plugins/ShapeFile.jsx b/web/client/plugins/ShapeFile.jsx index 89f3c55913..0b1b738e10 100644 --- a/web/client/plugins/ShapeFile.jsx +++ b/web/client/plugins/ShapeFile.jsx @@ -50,6 +50,7 @@ module.exports = { resolve(ShapeFilePlugin); }); }, enabler: (state) => state.shapefile && state.shapefile.enabled || state.toolbar && state.toolbar.active === 'shapefile'}, { + disablePluginIf: "{state('mapType') === 'cesium'}", Toolbar: { name: 'shapefile', position: 9, diff --git a/web/client/plugins/ZoomIn.jsx b/web/client/plugins/ZoomIn.jsx index 378bbfc215..e9eeeebd38 100644 --- a/web/client/plugins/ZoomIn.jsx +++ b/web/client/plugins/ZoomIn.jsx @@ -10,6 +10,7 @@ const React = require('react'); const {connect} = require('react-redux'); const {createSelector} = require('reselect'); const {mapSelector} = require('../selectors/map'); + // TODO: make step and glyphicon configurable const selector = createSelector([mapSelector], (map) => ({currentZoom: map && map.zoom, id: "zoomin-btn", step: 1, glyphicon: "plus"})); @@ -38,6 +39,7 @@ const assign = require('object-assign'); module.exports = { ZoomInPlugin: assign(ZoomInButton, { + disablePluginIf: "{state('mapType') === 'cesium'}", Toolbar: { name: "ZoomIn", position: 3, diff --git a/web/client/plugins/ZoomOut.jsx b/web/client/plugins/ZoomOut.jsx index 16f0eb1e0a..73436d5380 100644 --- a/web/client/plugins/ZoomOut.jsx +++ b/web/client/plugins/ZoomOut.jsx @@ -36,6 +36,7 @@ const assign = require('object-assign'); module.exports = { ZoomOutPlugin: assign(ZoomOutButton, { + disablePluginIf: "{state('mapType') === 'cesium'}", Toolbar: { name: "ZoomOut", position: 4, diff --git a/web/client/utils/PluginsUtils.js b/web/client/utils/PluginsUtils.js index fe5745f311..44a5c8c938 100644 --- a/web/client/utils/PluginsUtils.js +++ b/web/client/utils/PluginsUtils.js @@ -11,7 +11,7 @@ const {omit, isObject, head, isArray, isString} = require('lodash'); const {combineReducers} = require('redux'); const {connect} = require('react-redux'); const url = require('url'); - +const defaultMonitoredState = [{name: "mapType", path: 'maptype.mapType'}, {name: "user", path: 'security.user'}]; const {combineEpics} = require('redux-observable'); const {memoize, get} = require('lodash'); @@ -66,6 +66,13 @@ const handleExpression = (state, context, expression) => { return expression; }; +const filterDisabledPlugins = (item, state = {}, plugins = {}) => { + const disablePluginIf = item && item.plugin && item.plugin.disablePluginIf || item.cfg.disablePluginIf; + if (disablePluginIf && !(item && item.cfg && item.cfg.skipAutoDisable)) { + return !handleExpression(state, plugins.requires, disablePluginIf); + } + return true; +}; const showIn = (state, requires, cfg, name, id, isDefault) => { return ((id && cfg.showIn && handleExpression(state, requires, cfg.showIn).indexOf(id) !== -1) || (cfg.showIn && handleExpression(state, requires, cfg.showIn).indexOf(name) !== -1) || @@ -130,7 +137,7 @@ const getPluginItems = (state, plugins, pluginsConfig, name, id, isDefault, load plugin: pluginImpl, items: getPluginItems(state, plugins, pluginsConfig, pluginName, null, true, loadedPlugins) }); - }); + }).filter( (item) => filterDisabledPlugins(item, state, plugins) ); }; const getReducers = (plugins) => Object.keys(plugins).map((name) => plugins[name].reducers) @@ -182,6 +189,8 @@ const PluginsUtils = { }, getReducers, filterState, + filterDisabledPlugins, + getMonitoredState: (state, monitorState = []) => filterState(state, defaultMonitoredState.concat(monitorState)), getPlugins: (plugins) => Object.keys(plugins).map((name) => plugins[name]) .reduce((previous, current) => assign({}, previous, omit(current, 'reducers')), {}), /** diff --git a/web/client/utils/__tests__/PluginUtils-test.js b/web/client/utils/__tests__/PluginUtils-test.js index 78a9908996..fdc9d1455a 100644 --- a/web/client/utils/__tests__/PluginUtils-test.js +++ b/web/client/utils/__tests__/PluginUtils-test.js @@ -140,6 +140,25 @@ describe('PluginsUtils', () => { const domElement = ReactDOM.findDOMNode(app); expect(domElement.innerText).toBe("plugintest"); }); + it('handleExpression', () => { + expect(PluginsUtils.handleExpression({state1: "test1"}, {context1: "test2"}, "{state.state1 + ' ' + context.context1}")).toBe("test1 test2"); + }); + it('filterState', () => { + expect(PluginsUtils.filterState({state1: "test1"}, [{name: "A", path: "state1"}]).A).toBe("test1"); + }); + it('filterDisabledPlugins', () => { + expect(PluginsUtils.filterDisabledPlugins( + {plugin: { + disablePluginIf: "{true}" + }}, + {}, + {} + )).toBe(false); + }); + it('getMonitoredState', () => { + expect(PluginsUtils.getMonitoredState({maptype: {mapType: "leaflet"}}).mapType).toBe("leaflet"); + }); + it('handleExpression', () => { expect(PluginsUtils.handleExpression({state1: "test1"}, {context1: "test2"}, "{state.state1 + ' ' + context.context1}")).toBe("test1 test2"); });