diff --git a/package.json b/package.json
index 604540c51b..113a881300 100644
--- a/package.json
+++ b/package.json
@@ -84,6 +84,7 @@
"ag-grid": "3.3.3",
"ag-grid-react": "3.3.1",
"axios": "0.11.1",
+ "b64-to-blob": "1.2.19",
"babel-polyfill": "6.8.0",
"babel-standalone": "6.7.7",
"bootstrap": "3.3.5",
@@ -174,7 +175,7 @@
"reselect": "2.5.1",
"rxjs": "5.1.1",
"screenfull": "3.1.0",
- "shpjs": "3.3.2",
+ "shpjs": "3.4.2",
"turf-bbox": "3.0.10",
"turf-buffer": "3.0.10",
"turf-intersect": "3.0.10",
diff --git a/web/client/components/TOC/Toolbar.jsx b/web/client/components/TOC/Toolbar.jsx
index 66f2c8e5cb..890456d4dc 100644
--- a/web/client/components/TOC/Toolbar.jsx
+++ b/web/client/components/TOC/Toolbar.jsx
@@ -20,6 +20,7 @@ class Toolbar extends React.Component {
static propTypes = {
groups: PropTypes.array,
selectedLayers: PropTypes.array,
+ generalInfoFormat: PropTypes.string,
selectedGroups: PropTypes.array,
onToolsActions: PropTypes.object,
text: PropTypes.object,
@@ -128,6 +129,7 @@ class Toolbar extends React.Component {
{...this.props.options.settingsOptions}
settings={this.props.settings}
element={this.props.selectedLayers[0]}
+ generalInfoFormat={this.props.generalInfoFormat}
retrieveLayerData={this.props.onToolsActions.onRetrieveLayerData}
updateSettings={this.props.onToolsActions.onUpdateSettings}
hideSettings={this.props.onToolsActions.onHideSettings}
diff --git a/web/client/components/TOC/fragments/SettingsModal.jsx b/web/client/components/TOC/fragments/SettingsModal.jsx
index 5746c1ede8..547197b2b0 100644
--- a/web/client/components/TOC/fragments/SettingsModal.jsx
+++ b/web/client/components/TOC/fragments/SettingsModal.jsx
@@ -35,6 +35,7 @@ class SettingsModal extends React.Component {
updateNode: PropTypes.func,
removeNode: PropTypes.func,
retrieveLayerData: PropTypes.func,
+ generalInfoFormat: PropTypes.string,
titleText: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
opacityText: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
elevationText: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
@@ -176,6 +177,7 @@ class SettingsModal extends React.Component {
return (}
element={this.props.element}
+ generalInfoFormat={this.props.generalInfoFormat}
onInfoFormatChange={(key, value) => this.updateParams({[key]: value}, this.props.realtimeUpdate)} />);
}
}
diff --git a/web/client/components/TOC/fragments/settings/FeatureInfoFormat.jsx b/web/client/components/TOC/fragments/settings/FeatureInfoFormat.jsx
index f44e29e9a7..6e7b19dd08 100644
--- a/web/client/components/TOC/fragments/settings/FeatureInfoFormat.jsx
+++ b/web/client/components/TOC/fragments/settings/FeatureInfoFormat.jsx
@@ -1,4 +1,3 @@
-const PropTypes = require('prop-types');
/**
* Copyright 2017, GeoSolutions Sas.
* All rights reserved.
@@ -8,26 +7,45 @@ const PropTypes = require('prop-types');
*/
const React = require('react');
+const PropTypes = require('prop-types');
const {DropdownList} = require('react-widgets');
const MapInfoUtils = require('../../../../utils/MapInfoUtils');
+
+/**
+ * FeatureInfoFormat shows the infoformat selected for that layer or the default one taken
+ * from the general settings.
+ * @class
+ * @memberof components.toc
+ * @prop {object} [element] the layer options
+ * @prop {object} [label] the label shown for the combobox
+ * @prop {object} [defaultInfoFormat] the object used to show options labels
+ * @prop {string} [generalInfoFormat] the infoFormat value set in the general settings
+ * @prop {function} [onInfoFormatChange] it updates the infoFormat and/or viewer for the given layer
+ */
module.exports = class extends React.Component {
static propTypes = {
element: PropTypes.object,
label: PropTypes.object,
defaultInfoFormat: PropTypes.object,
+ generalInfoFormat: PropTypes.string,
onInfoFormatChange: PropTypes.func
};
static defaultProps = {
defaultInfoFormat: MapInfoUtils.getAvailableInfoFormat(),
+ generalInfoFormat: "text/plain",
onInfoFormatChange: () => {}
};
- render() {
- const data = Object.keys(this.props.defaultInfoFormat).map((infoFormat) => {
+ getInfoFormat = (infoFormats) => {
+ return Object.keys(infoFormats).map((infoFormat) => {
return infoFormat;
});
+ }
+ render() {
+ // the selected value if missing on that layer should be set to the general info format value and not the first one.
+ const data = this.getInfoFormat(this.props.defaultInfoFormat);
const checkDisabled = !!(this.props.element.featureInfo && this.props.element.featureInfo.viewer);
return (
@@ -42,7 +60,7 @@ module.exports = class extends React.Component {
(
{
diff --git a/web/client/components/TOC/fragments/settings/__tests__/FeatureInfoFormat-test.jsx b/web/client/components/TOC/fragments/settings/__tests__/FeatureInfoFormat-test.jsx
index 701f316447..11f7fbf390 100644
--- a/web/client/components/TOC/fragments/settings/__tests__/FeatureInfoFormat-test.jsx
+++ b/web/client/components/TOC/fragments/settings/__tests__/FeatureInfoFormat-test.jsx
@@ -73,4 +73,22 @@ describe('test Layer Properties FeatureInfoFormat module component', () => {
li[0].click();
expect(spy.calls.length).toBe(1);
});
+ it('tests FeatureInfoFormat component for wms using generalInfoFormat', () => {
+ const l = {
+ name: 'layer00',
+ title: 'Layer',
+ visibility: true,
+ storeIndex: 9,
+ type: 'wms',
+ url: 'fakeurl'
+ };
+ const generalInfoFormat = "text/html";
+ const label = "label";
+ const comp = ReactDOM.render( {}}/>, document.getElementById("container"));
+ expect(comp).toExist();
+ const div = ReactTestUtils.scryRenderedDOMComponentsWithTag( comp, "div" );
+ expect(div[2]).toExist();
+ expect(div[2].textContent).toBe("HTML");
+
+ });
});
diff --git a/web/client/components/mapcontrols/annotations/Annotations.jsx b/web/client/components/mapcontrols/annotations/Annotations.jsx
index 03a9b9e5ee..1595efdb62 100644
--- a/web/client/components/mapcontrols/annotations/Annotations.jsx
+++ b/web/client/components/mapcontrols/annotations/Annotations.jsx
@@ -52,6 +52,8 @@ const defaultConfig = require('./AnnotationsConfig');
* @prop {function} onCleanHighlight triggered when the mouse is out of any annotation card
* @prop {function} onDetail triggered when the user clicks on an annotation card
* @prop {function} onFilter triggered when the user enters some text in the filtering widget
+ * @prop {function} classNameSelector optional selector to assign custom a CSS class to annotations, based on
+ * the annotation's attributes.
*/
class Annotations extends React.Component {
static propTypes = {
@@ -72,7 +74,8 @@ class Annotations extends React.Component {
current: PropTypes.string,
config: PropTypes.object,
filter: PropTypes.string,
- onFilter: PropTypes.func
+ onFilter: PropTypes.func,
+ classNameSelector: PropTypes.func
};
static contextTypes = {
@@ -81,7 +84,8 @@ class Annotations extends React.Component {
static defaultProps = {
mode: 'list',
- config: defaultConfig
+ config: defaultConfig,
+ classNameSelector: () => ''
};
getConfig = () => {
@@ -112,7 +116,7 @@ class Annotations extends React.Component {
};
renderCard = (annotation) => {
- return ( this.props.onHighlight(annotation.properties.id)} onMouseOut={this.props.onCleanHighlight} onClick={() => this.props.onDetail(annotation.properties.id)}>
+ return (
this.props.onHighlight(annotation.properties.id)} onMouseOut={this.props.onCleanHighlight} onClick={() => this.props.onDetail(annotation.properties.id)}>
{this.renderThumbnail(annotation.style)}
{this.getConfig().fields.map(f => this.renderField(f, annotation))}
);
diff --git a/web/client/components/mapcontrols/annotations/__tests__/Annotations-test.jsx b/web/client/components/mapcontrols/annotations/__tests__/Annotations-test.jsx
index d295614bad..dc894e5885 100644
--- a/web/client/components/mapcontrols/annotations/__tests__/Annotations-test.jsx
+++ b/web/client/components/mapcontrols/annotations/__tests__/Annotations-test.jsx
@@ -186,4 +186,44 @@ describe("test the Annotations Panel", () => {
expect(TestUtils.scryRenderedDOMComponentsWithClass(annotations, "mapstore-annotations-panel-card").length).toBe(0);
expect(TestUtils.scryRenderedDOMComponentsWithClass(annotations, "myeditor").length).toBe(1);
});
+
+ it('test rendering custom class', () => {
+ const annotationsList = [{
+ properties: {
+ title: 'a',
+ description: 'b'
+ },
+ style: {
+ iconShape: 'square',
+ iconColor: 'blue'
+ }
+ }, {
+ properties: {
+ external: true,
+ title: 'c',
+ description: 'd'
+ },
+ style: {
+ iconShape: 'square',
+ iconColor: 'blue'
+ }
+ }];
+
+ const classNameSelector = (annotation) => {
+ if (annotation && annotation.properties && annotation.properties.external) {
+ return 'external';
+ }
+ return '';
+ };
+
+ const annotations = ReactDOM.render(
, document.getElementById("container"));
+
+ expect(annotations).toExist();
+
+ const cards = TestUtils.scryRenderedDOMComponentsWithClass(annotations, "mapstore-annotations-panel-card");
+ expect(cards.length).toBe(2);
+
+ const cardsExternal = TestUtils.scryRenderedDOMComponentsWithClass(annotations, "mapstore-annotations-panel-card external");
+ expect(cardsExternal.length).toBe(1);
+ });
});
diff --git a/web/client/components/shapefile/SelectShape.jsx b/web/client/components/shapefile/SelectShape.jsx
index 635a244cac..5939003ea2 100644
--- a/web/client/components/shapefile/SelectShape.jsx
+++ b/web/client/components/shapefile/SelectShape.jsx
@@ -13,6 +13,10 @@ const Spinner = require('react-spinkit');
const LocaleUtils = require('../../utils/LocaleUtils');
+const JSZip = require('jszip');
+const FileUtils = require('../../utils/FileUtils');
+const {Promise} = require('es6-promise');
+
class SelectShape extends React.Component {
static propTypes = {
text: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
@@ -43,17 +47,32 @@ class SelectShape extends React.Component {
);
}
+ tryUnzip = (file) => {
+ return FileUtils.readZip(file).then((buffer) => {
+ return new JSZip(buffer);
+ });
+ };
+
+ isZip = (file) => {
+ return new Promise((resolve, reject) => {
+ if (file.type === 'application/zip' || file.type === 'application/x-zip-compressed') {
+ resolve();
+ } else {
+ this.tryUnzip(file).then(resolve).catch(reject);
+ }
+ });
+ };
+
checkfile = (files) => {
- const allZip = files.filter((file) => { return file.type !== 'application/zip' && file.type !== 'application/x-zip-compressed'; }).length === 0;
- if (allZip) {
+ Promise.all(files.map(file => this.isZip(file))).then(() => {
if (this.props.error) {
this.props.onShapeError(null);
}
this.props.onShapeChoosen(files);
- } else {
+ }).catch(() => {
const error = LocaleUtils.getMessageById(this.context.messages, this.props.errorMessage);
this.props.onShapeError(error);
- }
+ });
};
}
diff --git a/web/client/components/shapefile/__tests__/SelectShape-test.jsx b/web/client/components/shapefile/__tests__/SelectShape-test.jsx
index f3ed01520a..3b68fa88a5 100644
--- a/web/client/components/shapefile/__tests__/SelectShape-test.jsx
+++ b/web/client/components/shapefile/__tests__/SelectShape-test.jsx
@@ -2,6 +2,7 @@ const expect = require('expect');
const React = require('react');
const ReactDOM = require('react-dom');
const SelectShape = require('../SelectShape');
+const b64toBlob = require('b64-to-blob');
const TestUtils = require('react-dom/test-utils');
@@ -43,10 +44,9 @@ describe("Test the select shapefile component", () => {
expect(dom.innerHTML.indexOf('TEST') !== -1).toBe(true);
});
- it('upload file', () => {
- let dropped = false;
+ it('upload file', (done) => {
const handler = () => {
- dropped = true;
+ done();
};
const cmp = ReactDOM.render(
, document.getElementById("container"));
expect(cmp).toExist();
@@ -59,13 +59,24 @@ describe("Test the select shapefile component", () => {
type: 'application/zip'
}];
TestUtils.Simulate.drop(content, { dataTransfer: { files } });
- expect(dropped).toBe(true);
});
- it('upload wrong file', () => {
- let error = false;
+ it('upload wrong mime, right file', (done) => {
const handler = () => {
- error = true;
+ done();
+ };
+ const cmp = ReactDOM.render(, document.getElementById("container"));
+ expect(cmp).toExist();
+ const dom = ReactDOM.findDOMNode(cmp);
+ expect(dom.getElementsByTagName('input').length).toBe(1);
+ const content = TestUtils.findRenderedDOMComponentWithClass(cmp, 'dropzone-content');
+ const files = [b64toBlob('UEsDBAoAAAAAACGPaktDvrfoAQAAAAEAAAAKAAAAc2FtcGxlLnR4dGFQSwECPwAKAAAAAAAhj2pLQ7636AEAAAABAAAACgAkAAAAAAAAACAAAAAAAAAAc2FtcGxlLnR4dAoAIAAAAAAAAQAYAGILh+1EWtMBy3f86URa0wHLd/zpRFrTAVBLBQYAAAAAAQABAFwAAAApAAAAAAA=', 'application/pdf')];
+ TestUtils.Simulate.drop(content, { dataTransfer: { files } });
+ });
+
+ it('upload wrong file', (done) => {
+ const handler = () => {
+ done();
};
const cmp = ReactDOM.render(, document.getElementById("container"));
expect(cmp).toExist();
@@ -78,6 +89,5 @@ describe("Test the select shapefile component", () => {
type: 'application/pdf'
}];
TestUtils.Simulate.drop(content, { dataTransfer: { files } });
- expect(error).toBe(true);
});
});
diff --git a/web/client/plugins/TOC.jsx b/web/client/plugins/TOC.jsx
index 741cf72202..d7d9ed9696 100644
--- a/web/client/plugins/TOC.jsx
+++ b/web/client/plugins/TOC.jsx
@@ -12,13 +12,15 @@ const {createSelector} = require('reselect');
const {Glyphicon} = require('react-bootstrap');
const {changeLayerProperties, changeGroupProperties, toggleNode, contextNode,
- sortNode, showSettings, hideSettings, updateSettings, updateNode, removeNode, browseData, selectNode, filterLayers, refreshLayerVersion} = require('../actions/layers');
+ sortNode, showSettings, hideSettings, updateSettings, updateNode, removeNode,
+ browseData, selectNode, filterLayers, refreshLayerVersion} = require('../actions/layers');
const {getLayerCapabilities} = require('../actions/layerCapabilities');
const {zoomToExtent} = require('../actions/map');
const {groupsSelector, layersSelector, selectedNodesSelector, layerFilterSelector, layerSettingSelector} = require('../selectors/layers');
const {mapSelector, mapNameSelector} = require('../selectors/map');
const {currentLocaleSelector} = require("../selectors/locale");
const {widgetBuilderAvailable} = require('../selectors/controls');
+const {generalInfoFormatSelector} = require("../selectors/mapInfo");
const LayersUtils = require('../utils/LayersUtils');
const mapUtils = require('../utils/MapUtils');
@@ -71,8 +73,9 @@ const tocSelector = createSelector(
layersSelector,
mapNameSelector,
activeSelector,
- widgetBuilderAvailable
- ], (enabled, groups, settings, map, currentLocale, selectedNodes, filterText, layers, mapName, catalogActive, activateWidgetTool) => ({
+ widgetBuilderAvailable,
+ generalInfoFormatSelector
+ ], (enabled, groups, settings, map, currentLocale, selectedNodes, filterText, layers, mapName, catalogActive, activateWidgetTool, generalInfoFormat) => ({
enabled,
groups,
settings,
@@ -84,6 +87,7 @@ const tocSelector = createSelector(
currentLocale,
selectedNodes,
filterText,
+ generalInfoFormat,
selectedLayers: layers.filter((l) => head(selectedNodes.filter(s => s === l.id))),
noFilterResults: layers.filter((l) => filterLayersByTitle(l, filterText, currentLocale)).length === 0,
selectedGroups: selectedNodes.map(n => LayersUtils.getNode(groups, n)).filter(n => n && n.nodes),
@@ -163,6 +167,7 @@ class LayerTree extends React.Component {
currentLocale: PropTypes.string,
onFilter: PropTypes.func,
filterText: PropTypes.string,
+ generalInfoFormat: PropTypes.string,
selectedLayers: PropTypes.array,
selectedGroups: PropTypes.array,
mapName: PropTypes.string,
@@ -295,6 +300,7 @@ class LayerTree extends React.Component {
groups={this.props.groups}
selectedLayers={this.props.selectedLayers}
selectedGroups={this.props.selectedGroups}
+ generalInfoFormat={this.props.generalInfoFormat}
settings={this.props.settings}
activateTool={{
activateToolsContainer: this.props.activateToolsContainer,
diff --git a/web/client/selectors/__tests__/mapinfo-test.js b/web/client/selectors/__tests__/mapinfo-test.js
new file mode 100644
index 0000000000..fa67ace5a3
--- /dev/null
+++ b/web/client/selectors/__tests__/mapinfo-test.js
@@ -0,0 +1,30 @@
+/*
+* 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 expect = require('expect');
+const {
+ generalInfoFormatSelector
+} = require('../mapinfo');
+
+describe('Test mapinfo', () => {
+ it('test generalInfoFormatSelector default value', () => {
+ const mapinfo = generalInfoFormatSelector({});
+ expect(mapinfo).toBe("text/plain");
+ });
+ it('test generalInfoFormatSelector infoFormat: undefined', () => {
+ const mapinfo = generalInfoFormatSelector({mapInfo: {infoFormat: undefined}});
+ expect(mapinfo).toBe("text/plain");
+ });
+ it('test generalInfoFormatSelector ', () => {
+ const mapinfo = generalInfoFormatSelector({mapInfo: {infoFormat: "text/html"}});
+
+ expect(mapinfo).toExist();
+ expect(mapinfo).toBe("text/html");
+ });
+});
diff --git a/web/client/selectors/mapinfo.js b/web/client/selectors/mapinfo.js
new file mode 100644
index 0000000000..3a1916a421
--- /dev/null
+++ b/web/client/selectors/mapinfo.js
@@ -0,0 +1,27 @@
+/*
+* 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 {get} = require('lodash');
+/**
+ * selects mapinfo state
+ * @name mapinfo
+ * @memberof selectors
+ * @static
+ */
+
+/**
+ * selects generalInfoFormat from state
+ * @memberof selectors.mapinfo
+ * @param {object} state the state
+ * @return {string} the maptype in the state
+ */
+const generalInfoFormatSelector = (state) => get(state, "mapInfo.infoFormat", "text/plain");
+
+module.exports = {
+ generalInfoFormatSelector
+};
diff --git a/web/client/utils/FileUtils.js b/web/client/utils/FileUtils.js
index 8489b7ee01..b6a6a60601 100644
--- a/web/client/utils/FileUtils.js
+++ b/web/client/utils/FileUtils.js
@@ -9,6 +9,7 @@
const FileSaver = require('file-saver');
const toBlob = require('canvas-to-blob');
const shp = require('shpjs');
+const {Promise} = require('es6-promise');
const FileUtils = {
download: function(blob, name, mimetype) {
diff --git a/web/client/utils/MapInfoUtils.js b/web/client/utils/MapInfoUtils.js
index df671d4c56..bd37b7e4b9 100644
--- a/web/client/utils/MapInfoUtils.js
+++ b/web/client/utils/MapInfoUtils.js
@@ -31,6 +31,12 @@ const MapInfoUtils = {
return prev;
}, {});
},
+ /**
+ * @return the label of an inputformat given the value
+ */
+ getLabelFromValue(value) {
+ return MapInfoUtils.getAvailableInfoFormatLabels().filter(info => INFO_FORMATS[info] === value)[0] || "TEXT";
+ },
/**
* @return like getAvailableInfoFormat but return an array filled with the keys
*/
diff --git a/web/client/utils/__tests__/MapInfoUtils-test.js b/web/client/utils/__tests__/MapInfoUtils-test.js
index a154590a00..d2483a0432 100644
--- a/web/client/utils/__tests__/MapInfoUtils-test.js
+++ b/web/client/utils/__tests__/MapInfoUtils-test.js
@@ -16,7 +16,8 @@ var {
buildIdentifyRequest,
getValidator,
getViewer,
- setViewer
+ setViewer,
+ getLabelFromValue
} = require('../MapInfoUtils');
const CoordinatesUtils = require('../CoordinatesUtils');
@@ -329,4 +330,16 @@ describe('MapInfoUtils', () => {
let validResponses = validator.getValidResponses(response);
expect(validResponses.length).toBe(1);
});
+ it('get the label given the text/plain value', () => {
+ let label = getLabelFromValue("text/plain");
+ expect(label).toBe("TEXT");
+ });
+ it('get the label given the text/html value', () => {
+ let label = getLabelFromValue("text/html");
+ expect(label).toBe("HTML");
+ });
+ it('get the default label given the wrong value', () => {
+ let label = getLabelFromValue("text_or_something_else");
+ expect(label).toBe("TEXT");
+ });
});