From fac0390311158c25b4d510d4330497a074769ceb Mon Sep 17 00:00:00 2001 From: Lorenzo Natali Date: Thu, 4 Apr 2019 15:30:22 +0200 Subject: [PATCH] Align OL measure number format of tooltip to measure panel's one (#3673) --- .../enhancers/__tests__/addI18NProps-test.js | 48 +++++++++++++++++ .../components/I18N/enhancers/addI18NProps.js | 54 +++++++++++++++++++ .../map/openlayers/MeasurementSupport.jsx | 7 ++- web/client/plugins/map/openlayers/index.js | 6 ++- 4 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 web/client/components/I18N/enhancers/__tests__/addI18NProps-test.js create mode 100644 web/client/components/I18N/enhancers/addI18NProps.js diff --git a/web/client/components/I18N/enhancers/__tests__/addI18NProps-test.js b/web/client/components/I18N/enhancers/__tests__/addI18NProps-test.js new file mode 100644 index 0000000000..cf27c85bc9 --- /dev/null +++ b/web/client/components/I18N/enhancers/__tests__/addI18NProps-test.js @@ -0,0 +1,48 @@ +/* + * Copyright 2019, 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 addI18NProps = require('../addI18NProps'); +var Localized = require('../../Localized'); + + +describe('addI18NProps enhancer', () => { + beforeEach((done) => { + document.body.innerHTML = '
'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById("container")); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('addI18NProps format with no context', () => { + const Sink = addI18NProps(['formatNumber'])(createSink(props => { + expect(props).toExist(); + expect(props.formatNumber).toExist(); + // this is the default implementation. + expect(props.formatNumber(1.1)).toBe(1.1); + expect(props.formatNumber(1000)).toBe(1000); + })); + ReactDOM.render(, document.getElementById("container")); + }); + it('addI18NProps format numbers', () => { + const Sink = addI18NProps(['formatNumber'])(createSink( props => { + expect(props).toExist(); + expect(props.formatNumber).toExist(); + expect(typeof props.formatNumber(1)).toBe('string'); + expect(props.formatNumber(1.1)).toBe("1.1"); + expect(props.formatNumber(1000)).toBe("1,000"); + })); + ReactDOM.render( + + , document.getElementById("container")); + }); +}); diff --git a/web/client/components/I18N/enhancers/addI18NProps.js b/web/client/components/I18N/enhancers/addI18NProps.js new file mode 100644 index 0000000000..ff33652efb --- /dev/null +++ b/web/client/components/I18N/enhancers/addI18NProps.js @@ -0,0 +1,54 @@ +/* + * Copyright 2019, 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 {injectIntl} = require('react-intl'); +const PropTypes = require('prop-types'); +const {omit} = require('lodash'); +const { compose, branch, getContext, withProps, withPropsOnChange, mapProps } = require('recompose'); + +const omitProps = keys => mapProps(props => omit(props, keys)); + +// TODO: provide better defaults +const defaults = { + locale: navigator && navigator.language, + formats: { }, + messages: { }, + defaultLocale: 'en', + defaultFormats: {}, + formatDate: v => v, + formatTime: v => v, + formatRelative: v => v, + formatNumber: v => v, + formatPlural: v => v, + formatMessage: v => v, + formatHTMLMessage: v => v, + now: () => new Date() +}; + +/** + * Add i18n functionalities and properties as props. Useful to get format functions when the react-intl components can not be used (i.e. with wrapped libs) + * @name addI18NFormat + * @param {string[]} props add the props to format as props. Should be keys of of this interface https://github.com/yahoo/react-intl/wiki/API#intlshape + * @example + * addI18NProps(['formatNumber'])(MyCmp); // MyCmp will receive `formatNumber` from current locale Intl object as a property + */ +module.exports = (propsToAdd = []) => compose( + // check intl and inject (or add default dummy object) + getContext({intl: PropTypes.object}), + branch( + ({intl}) => !!intl, + injectIntl, + withProps({intl: defaults}) + ), + // add propsToAdd properties from intl object + withPropsOnChange(['intl'], ({ intl = {} }) => propsToAdd.reduce((acc = {}, k) => ({ + ...acc, + [k]: intl[k] + }), {})), + // clean up intl property + omitProps(['intl']) +); diff --git a/web/client/components/map/openlayers/MeasurementSupport.jsx b/web/client/components/map/openlayers/MeasurementSupport.jsx index 41cffd1d0e..90386da0a8 100644 --- a/web/client/components/map/openlayers/MeasurementSupport.jsx +++ b/web/client/components/map/openlayers/MeasurementSupport.jsx @@ -11,6 +11,7 @@ const PropTypes = require('prop-types'); const {round, isEqual, dropRight, pick} = require('lodash'); const assign = require('object-assign'); const ol = require('openlayers'); + const wgs84Sphere = new ol.Sphere(6378137); const {reprojectGeoJson, reproject, calculateAzimuth, calculateDistance, transformLineToArcs} = require('../../../utils/CoordinatesUtils'); const {convertUom, getFormattedBearingValue} = require('../../../utils/MeasureUtils'); @@ -30,6 +31,7 @@ class MeasurementSupport extends React.Component { measurement: PropTypes.object, enabled: PropTypes.bool, uom: PropTypes.object, + formatNumber: PropTypes.func, changeMeasurementState: PropTypes.func, updateMeasures: PropTypes.func, resetGeometry: PropTypes.func, @@ -46,6 +48,7 @@ class MeasurementSupport extends React.Component { resetGeometry: () => {}, updateMeasures: () => {}, changeGeometry: () => {}, + formatNumber: n => n, startEndPoint: { startPointOptions: { radius: 3, @@ -451,7 +454,7 @@ class MeasurementSupport extends React.Component { const length = calculateDistance(reprojectedCoords, props.measurement.lengthFormula); const {label, unit} = props.uom && props.uom.length; const output = round(convertUom(length, "m", unit), 2); - return output + " " + (label); + return this.props.formatNumber(output) + " " + (label); }; /** @@ -464,7 +467,7 @@ class MeasurementSupport extends React.Component { const {label, unit} = props.uom && props.uom.area; const output = round(convertUom(area, "sqm", unit), 2); - return output + " " + label; + return this.props.formatNumber(output) + " " + label; }; removeHelpTooltip = () => { diff --git a/web/client/plugins/map/openlayers/index.js b/web/client/plugins/map/openlayers/index.js index ef46b01af8..5e025686ff 100644 --- a/web/client/plugins/map/openlayers/index.js +++ b/web/client/plugins/map/openlayers/index.js @@ -5,13 +5,17 @@ * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ +const addI18NProps = require('../../../components/I18N/enhancers/addI18NProps'); + +// number format localization for measurements +const addFormatNumber = addI18NProps(['formatNumber']); module.exports = { LMap: require('../../../components/map/openlayers/Map'), Layer: require('../../../components/map/openlayers/Layer'), Feature: require('../../../components/map/openlayers/Feature'), Locate: require('../../../components/map/openlayers/Locate'), - MeasurementSupport: require('../../../components/map/openlayers/MeasurementSupport'), + MeasurementSupport: addFormatNumber(require('../../../components/map/openlayers/MeasurementSupport')), Overview: require('../../../components/map/openlayers/Overview'), ScaleBar: require('../../../components/map/openlayers/ScaleBar'), DrawSupport: require('../../../components/map/openlayers/DrawSupport'),