@@ -30,9 +31,9 @@ module.exports = ({onNavChange = () => {}, onExit = () => {}, disableSave = true
);
};
diff --git a/web/client/components/manager/rulesmanager/ruleseditor/__tests__/AttributesEditor-test.jsx b/web/client/components/manager/rulesmanager/ruleseditor/__tests__/AttributesEditor-test.jsx
new file mode 100644
index 0000000000..df0ad5e035
--- /dev/null
+++ b/web/client/components/manager/rulesmanager/ruleseditor/__tests__/AttributesEditor-test.jsx
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2018, GeoSolutions Sas.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+const React = require('react');
+const ReactDOM = require('react-dom');
+const expect = require('expect');
+const AttributesEditor = require('../AttributesEditor.jsx');
+const constraints = {
+ attributes: {
+ attribute: {access: "READONLY", name: "cat"}
+ }
+};
+const attributes = [
+ {
+ "name": "the_geom",
+ "type": "gml:Point",
+ "localType": "Point"},
+ {
+ "name": "cat",
+ "maxOccurs": 1,
+ "type": "xsd:int",
+ "localType": "int"}];
+
+describe('Attributes Editor component', () => {
+ beforeEach((done) => {
+ document.body.innerHTML = '
';
+ setTimeout(done);
+ });
+ afterEach((done) => {
+ ReactDOM.unmountComponentAtNode(document.getElementById("container"));
+ document.body.innerHTML = '';
+ setTimeout(done);
+ });
+ it('render nothing if not active', () => {
+ ReactDOM.render(
, document.getElementById("container"));
+ const container = document.getElementById('container');
+ const el = container.querySelector('.ms-rule-editor');
+ expect(el).toExist();
+ expect(el.style.display).toBe("none");
+ });
+ it('render attributes', () => {
+ ReactDOM.render(
, document.getElementById("container"));
+ const container = document.getElementById('container');
+ const rows = container.querySelectorAll('.row');
+ expect(rows).toExist();
+ expect(rows.length).toBe(3);
+ });
+
+});
diff --git a/web/client/components/manager/rulesmanager/ruleseditor/__tests__/Header-test.jsx b/web/client/components/manager/rulesmanager/ruleseditor/__tests__/Header-test.jsx
index d0e3969a49..028ec7dbd6 100644
--- a/web/client/components/manager/rulesmanager/ruleseditor/__tests__/Header-test.jsx
+++ b/web/client/components/manager/rulesmanager/ruleseditor/__tests__/Header-test.jsx
@@ -37,7 +37,7 @@ describe('Rules Editor Header component', () => {
});
it('render navigation items with details active', () => {
- ReactDOM.render(
, document.getElementById("container"));
+ ReactDOM.render(
, document.getElementById("container"));
const container = document.getElementById('container');
const el = container.querySelector('.ms-panel-header-container');
expect(el).toExist();
diff --git a/web/client/components/manager/rulesmanager/ruleseditor/__tests__/StylesEditor-test.jsx b/web/client/components/manager/rulesmanager/ruleseditor/__tests__/StylesEditor-test.jsx
new file mode 100644
index 0000000000..af7d4bb70a
--- /dev/null
+++ b/web/client/components/manager/rulesmanager/ruleseditor/__tests__/StylesEditor-test.jsx
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2018, GeoSolutions Sas.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+const React = require('react');
+const ReactDOM = require('react-dom');
+const expect = require('expect');
+const StylesEditor = require('../StylesEditor.jsx');
+const constraints = {
+ allowedStyles: {style: ["poly_landmarks"]},
+ defaultStyle: "poly_landmarks"
+};
+const ReactTestUtils = require('react-dom/test-utils');
+const styles = [{name: "poly_landmarks", title: "poly_landmarks"}];
+describe('Styles Editor component', () => {
+ beforeEach((done) => {
+ document.body.innerHTML = '
';
+ setTimeout(done);
+ });
+ afterEach((done) => {
+ ReactDOM.unmountComponentAtNode(document.getElementById("container"));
+ document.body.innerHTML = '';
+ setTimeout(done);
+ });
+ it('render nothing if not active', () => {
+ ReactDOM.render(
, document.getElementById("container"));
+ const container = document.getElementById('container');
+ const el = container.querySelector('.ms-rule-editor');
+ expect(el).toExist();
+ expect(el.style.display).toBe("none");
+ });
+ it('render defaults when active', () => {
+ ReactDOM.render(
, document.getElementById("container"));
+ const container = document.getElementById('container');
+ const rows = container.querySelectorAll('.ms-add-style');
+ expect(rows).toExist();
+ expect(rows.length).toBe(2);
+ const btns = container.querySelectorAll('button');
+ expect(btns).toExist();
+ expect(btns.length).toBe(2);
+ ReactTestUtils.Simulate.click(btns[0]);
+ ReactTestUtils.Simulate.click(btns[1]);
+
+ });
+ it('render defaults style and styles list', () => {
+ ReactDOM.render(
, document.getElementById("container"));
+ const container = document.getElementById('container');
+ const cards = container.querySelectorAll('.mapstore-side-card');
+ expect(cards).toExist();
+ expect(cards.length).toBe(2);
+ });
+ it('render default style modal', () => {
+ ReactDOM.render(
, document.getElementById("container"));
+ const modal = document.querySelector('.ms-style-modal');
+ expect(modal).toExist();
+ const sideCard = modal.querySelector(".mapstore-side-card");
+ expect(sideCard).toExist();
+ ReactTestUtils.Simulate.click(sideCard);
+ });
+ it('render availables styles modal', () => {
+ ReactDOM.render(
, document.getElementById("container"));
+ const modal = document.querySelector('.ms-style-modal');
+ expect(modal).toExist();
+ const sideCard = modal.querySelector(".mapstore-side-card");
+ expect(sideCard).toExist();
+ ReactTestUtils.Simulate.click(sideCard);
+ const btns = modal.querySelectorAll('button');
+ expect(btns).toExist();
+ expect(btns.length).toBe(2);
+ ReactTestUtils.Simulate.click(btns[0]);
+ ReactTestUtils.Simulate.click(btns[1]);
+ });
+
+});
diff --git a/web/client/components/manager/rulesmanager/ruleseditor/enhancers/filters.js b/web/client/components/manager/rulesmanager/ruleseditor/enhancers/filters.js
index aa70765243..b2404147fe 100644
--- a/web/client/components/manager/rulesmanager/ruleseditor/enhancers/filters.js
+++ b/web/client/components/manager/rulesmanager/ruleseditor/enhancers/filters.js
@@ -5,17 +5,62 @@
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
-const {compose, withStateHandlers} = require("recompose");
-
-
+const {compose, withStateHandlers, withPropsOnChange} = require("recompose");
+const {connect} = require("react-redux");
+const {changeDrawingStatus} = require("../../../../../actions/draw");
+const {geometryStateSel} = require("../../../../../selectors/rulesmanager");
+const {error} = require("../../../../../actions/notifications");
module.exports = compose(
- withStateHandlers(() => ({
- panels: {}
- }),
+ connect(state => ({geometryState: geometryStateSel(state)}), {onChangeDrawingStatus: changeDrawingStatus, onError: error}),
+ withStateHandlers(({constraints = {}}) => {
+ const {restrictedAreaWkt: wkt} = constraints;
+ return {
+ mapActive: wkt && wkt.length > 0,
+ spatialField: {}
+ };
+ },
{
- onSwitch: ({panels}) => (panel, expanded) => ({
- panels: {...panels, [panel]: expanded}
- })
-
+ toggleMap: (state, {onChangeDrawingStatus}) => (isActive) => {
+ if (!isActive) {
+ onChangeDrawingStatus( "clean",
+ "",
+ "rulesmanager",
+ [],
+ {});
+ return {
+ mapActive: isActive,
+ spatialField: {}
+ };
+ }
+ return {
+ mapActive: isActive
+ };
+ },
+ onSelectSpatialMethod: ({spatialField}) => (method, name) => (
+ {spatialField: {...spatialField, [name]: method}}
+ ),
+ onMapReady: (state, {geometryState = {}, spatialField = {}, onChangeDrawingStatus}) => () => {
+ if (geometryState.geometry && geometryState.geometry.coordinates) {
+ onChangeDrawingStatus( "create",
+ "MultiPolygons",
+ "rulesmanager",
+ [geometryState.geometry],
+ {});
+ return {spatialField: {...spatialField, method: "Polygon"}};
+ }
+ return {};
+ }
+ }),
+ withPropsOnChange("toggleMap", ({toggleMap, onSelectSpatialMethod, onError, onChangeDrawingStatus}) => ({
+ actions: {
+ onChangeDrawingStatus,
+ onExpandSpatialFilterPanel: toggleMap,
+ onSelectSpatialMethod,
+ onError
+ }
+ })),
+ withPropsOnChange(["spatialField", "geometryState", "constraints"], ({spatialField = {}, geometryState = {}, constraints = {}}) => {
+ const {restrictedAreaWkt: wkt} = constraints;
+ return {spatialField: {...geometryState, ...spatialField, wkt}};
})
);
diff --git a/web/client/components/manager/rulesmanager/ruleseditor/enhancers/switch.js b/web/client/components/manager/rulesmanager/ruleseditor/enhancers/switch.js
index aa565357ea..b0cdfaa14b 100644
--- a/web/client/components/manager/rulesmanager/ruleseditor/enhancers/switch.js
+++ b/web/client/components/manager/rulesmanager/ruleseditor/enhancers/switch.js
@@ -9,13 +9,18 @@ const {compose, withStateHandlers} = require("recompose");
module.exports = compose(
- withStateHandlers(() => ({
- expanded: false
- }),
+ withStateHandlers(({initExpanded}) => {
+ return {
+ expanded: !!initExpanded
+ }; },
{
- onSwitch: () => (expanded) => ({
- expanded
- })
-
+ onSwitch: (state, {reset}) => (expanded) => {
+ if (!expanded) {
+ reset();
+ }
+ return {
+ expanded
+ };
+ }
})
);
diff --git a/web/client/components/map/enhancers/withDraw.js b/web/client/components/map/enhancers/withDraw.js
new file mode 100644
index 0000000000..0e9279e17a
--- /dev/null
+++ b/web/client/components/map/enhancers/withDraw.js
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2018, GeoSolutions Sas.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+const { withPropsOnChange } = require("recompose");
+
+const {connect} = require("react-redux");
+const {changeDrawingStatus, endDrawing, setCurrentStyle, geometryChanged, drawStopped} = require('../../../actions/draw');
+
+const defaultDrawConnect = connect((state) =>
+state.draw || {}, {
+ onChangeDrawingStatus: changeDrawingStatus,
+ onEndDrawing: endDrawing,
+ onGeometryChanged: geometryChanged,
+ onDrawStopped: drawStopped,
+ setCurrentStyle: setCurrentStyle
+});
+/**
+ * Add the draw tool to base-map. The draw support is already present in plugins but It needs
+ * to be connected and added to tools to work.
+ * It's possible to pass a connect function to override default connection to state and action
+ * @param {function} connectFunction connect function to override default connection of the draw tool.
+ */
+module.exports = (connectFunction = defaultDrawConnect) => withPropsOnChange(
+ ['plugins'],
+ ({plugins}= {}) => {
+ const {DrawSupport, tools = {}, ...rest} = plugins;
+ if (!DrawSupport) {
+ return {};
+ }
+ const Draw = connectFunction(DrawSupport);
+ return {plugins: {...rest, tools: {...tools, draw: Draw}}};
+ }
+);
diff --git a/web/client/components/misc/combobox/PagedCombobox.jsx b/web/client/components/misc/combobox/PagedCombobox.jsx
index 72bd9f8c3a..7e6eceb356 100644
--- a/web/client/components/misc/combobox/PagedCombobox.jsx
+++ b/web/client/components/misc/combobox/PagedCombobox.jsx
@@ -50,7 +50,9 @@ class PagedCombobox extends React.Component {
tooltip: PropTypes.object,
valueField: PropTypes.string,
placeholder: PropTypes.string,
- stopPropagation: PropTypes.bool
+ stopPropagation: PropTypes.bool,
+ clearable: PropTypes.bool,
+ onReset: PropTypes.func
};
static contextTypes = {
@@ -77,6 +79,7 @@ class PagedCombobox extends React.Component {
onToggle: () => {},
onChange: () => {},
onSelect: () => {},
+ onReset: () => {},
textField: "label",
tooltip: {
customizedTooltip: undefined,
@@ -86,7 +89,8 @@ class PagedCombobox extends React.Component {
overlayTriggerKey: "",
placement: "top"
},
- valueField: "value"
+ valueField: "value",
+ clearable: false
};
renderWithTooltip = (field) => {
@@ -159,17 +163,25 @@ class PagedCombobox extends React.Component {
textField={this.props.textField}
valueField={this.props.valueField}
value={this.props.selectedValue}
- />);
+ />
+ );
return this.props.tooltip && this.props.tooltip.enabled ? this.renderWithTooltip(field) : field;
}
render() {
- let label = this.props.label ? (
) : (
); // TODO change "the else case" value with null ?
+ const {selectedValue: v, disabled, onReset, label: l, clearable} = this.props;
+ let label = l ? (
) : (
); // TODO change "the else case" value with null ?
return (
{label}
- {this.renderField()}
+ {clearable ? (
+
+ {this.renderField()}
+ x
+
) : this.renderField()
+ }
);
}
}
+
module.exports = PagedCombobox;
diff --git a/web/client/epics/rulesmanager.js b/web/client/epics/rulesmanager.js
index 708ed7e881..60a2d48aa2 100644
--- a/web/client/epics/rulesmanager.js
+++ b/web/client/epics/rulesmanager.js
@@ -1,14 +1,16 @@
const Rx = require("rxjs");
-const {SAVE_RULE, setLoading, RULE_SAVED, DELETE_RULES} = require("../actions/rulesmanager");
-const {error} = require("../actions/notifications");
-const {updateRule, createRule, deleteRule} = require("../observables/rulesmanager");
+const {SAVE_RULE, setLoading, RULE_SAVED, DELETE_RULES, CACHE_CLEAN} = require("../actions/rulesmanager");
+const {error, success} = require("../actions/notifications");
+const {drawSupportReset} = require("../actions/draw");
+const {updateRule, createRule, deleteRule, cleanCache } = require("../observables/rulesmanager");
// To do add Error management
const {get} = require("lodash");
const saveRule = stream$ => stream$
.mapTo({type: RULE_SAVED})
+ .concat(Rx.Observable.of(drawSupportReset()))
.catch(({data}) => {
- const isDuplicate = data.indexOf("Duplicate Rule") === 0;
+ const isDuplicate = data.indexOf("Duplicat") === 0;
return Rx.Observable.of(error({title: "rulesmanager.errorTitle", message: isDuplicate ? "rulesmanager.errorDuplicateRule" : "rulesmanager.errorUpdatingRule"}));
})
.startWith(setLoading(true))
@@ -21,6 +23,15 @@ module.exports = {
onDelete: (action$, {getState}) => action$.ofType(DELETE_RULES)
.switchMap(({ids = get(getState(), "rulesmanager.selectedRules", []).map(row => row.id)}) => {
return Rx.Observable.combineLatest(ids.map(id => deleteRule(id))).let(saveRule);
- })
+ }),
+ onCacheClean: action$ => action$.ofType(CACHE_CLEAN)
+ .exhaustMap( () =>
+ cleanCache()
+ .mapTo(success({title: "rulesmanager.errorTitle", message: "rulesmanager.cacheCleaned"}))
+ .startWith(setLoading(true))
+ .catch(() => {
+ return Rx.Observable.of(error({title: "rulesmanager.errorTitle", message: "rulesmanager.errorCleaningCache"}));
+ })
+ .concat(Rx.Observable.of(setLoading(false))))
};
diff --git a/web/client/observables/rulesmanager.js b/web/client/observables/rulesmanager.js
index 92296929cc..b1a28b10e7 100644
--- a/web/client/observables/rulesmanager.js
+++ b/web/client/observables/rulesmanager.js
@@ -1,16 +1,16 @@
const Rx = require('rxjs');
-
const {parseString} = require('xml2js');
const {stripPrefix} = require('xml2js/lib/processors');
const CatalogAPI = require('../api/CSW');
const GeoFence = require('../api/geoserver/GeoFence');
const ConfigUtils = require('../utils/ConfigUtils');
const {trim} = require("lodash");
-
+const WMS = require('../api/WMS');
const {getLayerCapabilities, describeLayer} = require("./wms");
const {describeFeatureType} = require("./wfs");
+const {RULE_SAVED} = require("../actions/rulesmanager");
const xmlToJson$ = response => Rx.Observable.bindNodeCallback( (data, callback) => parseString(data, {
tagNameProcessors: [stripPrefix],
@@ -66,7 +66,7 @@ const fullUpdate = (update$) => update$.filter(({rule: r, origRule: oR}) =>getUp
const {priority: p, id: omit, ...oldRule} = origRule;
oldRule.position = {value: p, position: "fixedPriority"};
// We have to restore original rule and to throw the exception!!
- return Rx.Observable.defer(() => GeoFence.addRule(oldRule)).do(() => { throw (e); });
+ return Rx.Observable.defer(() => GeoFence.addRule(oldRule)).concat(Rx.Observable.of({type: RULE_SAVED}).do(() => { throw (e); }));
});
})
.switchMap(({data: id}) => {
@@ -82,8 +82,8 @@ const grantUpdate = (update$) => update$.filter(({rule: r, origRule: oR}) => get
.catch((e) => {
const {priority: p, id: omit, ...oldRule} = origRule;
oldRule.position = {value: p, position: "fixedPriority"};
- // We have to restore original rule and to throw the exception!!
- return Rx.Observable.defer(() => GeoFence.addRule(oldRule)).do(() => { throw (e); });
+ // We have to restore original rule and to throw the exception and reload the rules!!
+ return Rx.Observable.defer(() => GeoFence.addRule(oldRule)).concat(Rx.Observable.of({type: RULE_SAVED}).do(() => { throw (e); }));
});
})
);
@@ -129,15 +129,17 @@ module.exports = {
deleteRule,
getStylesAndAttributes: (layer, workspace) => {
const {url} = ConfigUtils.getDefaults().geoFenceGeoServerInstance || {};
- const l = {url: `${fixUrl(url)}ows`, name: `${workspace}:${layer}`};
+ const name = `${workspace}:${layer}`;
+ const l = {url: `${fixUrl(url)}ows`, name};
return Rx.Observable.combineLatest(getLayerCapabilities(l)
- .map(({style}) => ({style})),
+ .map((cp) => ({style: cp.style, ly: {bbox: WMS.getBBox(cp), name, url: `${fixUrl(url)}wms`, type: "wms", visibility: true, format: "image/png", title: cp.title}})),
describeLayer(l).map(({data}) => data.layerDescriptions[0])
.switchMap(({owsType}) => {
return owsType === "WCS" ? Rx.Observable.of({properties: [], type: "RASTER"}) : describeFeatureType({layer: l})
.map(({data}) => ({properties: data.featureTypes[0] && data.featureTypes[0].properties || [], type: "VECTOR"}));
- }), ({style}, {properties, type}) => ({styles: style || [], properties, type}));
+ }), ({style, ly}, {properties, type}) => ({styles: style || [], properties, type, layer: ly}));
- }
+ },
+ cleanCache: () => Rx.Observable.defer(() => GeoFence.cleanCache())
};
diff --git a/web/client/plugins/manager/EditorEnhancer.js b/web/client/plugins/manager/EditorEnhancer.js
index 4ad52b7376..4bc1183d8b 100644
--- a/web/client/plugins/manager/EditorEnhancer.js
+++ b/web/client/plugins/manager/EditorEnhancer.js
@@ -1,7 +1,10 @@
-const {compose, withStateHandlers, defaultProps} = require('recompose');
+const {compose, withStateHandlers, defaultProps, withPropsOnChange} = require('recompose');
const propsStreamFactory = require('../../components/misc/enhancers/propsStreamFactory');
const Rx = require("rxjs");
+const {isEmpty} = require("lodash");
+const {connect} = require("react-redux");
+const {changeDrawingStatus} = require("../..//actions/draw");
const sameLayer = ({activeRule: f1}, {activeRule: f2}) => f1.layer === f2.layer;
const emitStop = stream$ => stream$.filter(() => false).startWith({});
const {getStylesAndAttributes} = require("../../observables/rulesmanager");
@@ -21,7 +24,9 @@ const dataStreamFactory = prop$ => {
}).do(() => setLoading(false));
}).let(emitStop);
};
-module.exports = compose(
+module.exports = compose(connect(() => ({}), {
+ onChangeDrawingStatus: changeDrawingStatus
+ }),
defaultProps({dataStreamFactory}),
withStateHandlers(({activeRule: initRule}) => ({
activeRule: initRule,
@@ -49,6 +54,7 @@ module.exports = compose(
activeRule: {...activeRule, [key]: value}
};
},
+ setRule: () => (activeRule) => ({activeRule}),
setConstraintsOption: ({activeRule, type}) => ({key, value}) => {
const constraints = {...(activeRule.constraints || {}), type, [key]: value};
return {activeRule: {...activeRule, constraints}};
@@ -56,13 +62,25 @@ module.exports = compose(
onNavChange: () => activeEditor => ({
activeEditor
}),
- cleanConstraints: ({activeRule}) => () => {
+ cleanConstraints: ({activeRule}, {onChangeDrawingStatus}) => (keepLayer) => {
const {constraints, ...rule} = activeRule;
- return {activeRule: rule};
+ onChangeDrawingStatus( "clean",
+ "",
+ "rulesmanager",
+ [],
+ {});
+ return keepLayer && {activeRule: rule} || {activeRule: rule, styles: undefined, properties: undefined, type: undefined, layer: undefined};
},
- optionsLoaded: () => ({styles, properties, type}) => {
- return {styles, properties, type};
+ optionsLoaded: () => ({styles, properties, type, layer}) => {
+ return {styles, properties, type, layer};
+ }
+ }),// Merge geometry state from draw into activeRule
+ withPropsOnChange(["geometryState"], ({activeRule = {}, geometryState, setRule}) => {
+ if (!isEmpty(geometryState)) {
+ const {constraints = {}} = activeRule;
+ setRule({...activeRule, constraints: {...constraints, restrictedAreaWkt: geometryState.wkt}});
}
+ return {};
}),
propsStreamFactory
);
diff --git a/web/client/plugins/manager/RulesEditor.jsx b/web/client/plugins/manager/RulesEditor.jsx
index 90b787a053..b7d8b263ef 100644
--- a/web/client/plugins/manager/RulesEditor.jsx
+++ b/web/client/plugins/manager/RulesEditor.jsx
@@ -13,9 +13,9 @@ const {createSelector} = require("reselect");
const {compose} = require('recompose');
const enhancer = require("./EditorEnhancer");
const {cleanEditing, saveRule, setLoading} = require("../../actions/rulesmanager");
-const {activeRuleSelector} = require("../../selectors/rulesmanager");
+const {activeRuleSelector, geometryStateSel} = require("../../selectors/rulesmanager");
-const {isSaveDisabled, areDetailsActive, isRulePristine, isRuleValid, askConfirm} = require("../../utils/RulesEditor");
+const {isSaveDisabled, isRulePristine, isRuleValid, askConfirm} = require("../../utils/RulesEditor");
const Message = require('../../components/I18N/Message');
const BorderLayout = require("../../components/layout/BorderLayout");
const Header = require("../../components/manager/rulesmanager/ruleseditor/Header");
@@ -41,7 +41,8 @@ class RuleEditor extends React.Component {
type: PropTypes.string,
properties: PropTypes.array,
loading: PropTypes.bool,
- cleanConstraints: PropTypes.func
+ cleanConstraints: PropTypes.func,
+ layer: PropTypes.object
}
static defaultProps = {
activeEditor: "1",
@@ -53,7 +54,7 @@ class RuleEditor extends React.Component {
type: ""
}
render() {
- const {loading, activeRule, activeEditor, onNavChange, initRule, styles = [], setConstraintsOption, type, properties} = this.props;
+ const {loading, activeRule, layer, activeEditor, onNavChange, initRule, styles = [], setConstraintsOption, type, properties} = this.props;
const {modalProps} = this.state || {};
return (
}
>
-
+
);
@@ -125,7 +126,7 @@ class RuleEditor extends React.Component {
onClick: () => {
this.cancel();
this.props.setOption({key, value});
- this.props.cleanConstraints();
+ this.props.cleanConstraints(key === 'grant');
}
}
], closeAction: this.cancel, msg: "rulesmanager.constraintsmsg"}}));
@@ -138,7 +139,7 @@ class RuleEditor extends React.Component {
}
module.exports = compose(
- connect(createSelector(activeRuleSelector, activeRule => ({activeRule})), {
+ connect(createSelector([activeRuleSelector, geometryStateSel], (activeRule, geometryState) => ({activeRule, geometryState})), {
onExit: cleanEditing,
onSave: saveRule,
setLoading
diff --git a/web/client/plugins/manager/RulesToolbar.jsx b/web/client/plugins/manager/RulesToolbar.jsx
index 721cae86e2..2b145303c2 100644
--- a/web/client/plugins/manager/RulesToolbar.jsx
+++ b/web/client/plugins/manager/RulesToolbar.jsx
@@ -10,7 +10,7 @@ import { withPropsOnChange } from "recompose";
const React = require("react");
const {compose, withProps, withStateHandlers} = require("recompose");
const {connect} = require("react-redux");
-const { onEditRule, delRules} = require('../../actions/rulesmanager');
+const { onEditRule, delRules, onCacheClean} = require('../../actions/rulesmanager');
const {rulesEditorToolbarSelector} = require('../../selectors/rulesmanager');
const Toolbar = require('../../components/misc/toolbar/Toolbar');
const Modal = require("./ModalDialog");
@@ -31,7 +31,8 @@ const EditorToolbar = compose(
rulesEditorToolbarSelector,
{
deleteRules: delRules,
- editOrCreate: onEditRule
+ editOrCreate: onEditRule,
+ cleanCache: onCacheClean
}
),
withStateHandlers(() => ({
@@ -84,7 +85,7 @@ const EditorToolbar = compose(
}
}]
})),
- withPropsOnChange(["modal"], ({modal, cancelModal, deleteRules}) => {
+ withPropsOnChange(["modal"], ({modal, cancelModal, deleteRules, cleanCache}) => {
switch (modal) {
case "delete":
return {
@@ -119,7 +120,7 @@ const EditorToolbar = compose(
{
text:
,
bsStyle: 'primary',
- onClick: () => { cancelModal(); }
+ onClick: () => { cancelModal(); cleanCache(); }
}
],
closeAction: cancelModal,
diff --git a/web/client/reducers/rulesmanager.js b/web/client/reducers/rulesmanager.js
index ddff84cd2e..b686b47b2e 100644
--- a/web/client/reducers/rulesmanager.js
+++ b/web/client/reducers/rulesmanager.js
@@ -7,10 +7,16 @@
*/
const assign = require('object-assign');
+const wk = require('wellknown');
+const {isEmpty} = require("lodash");
const { RULES_SELECTED, RULES_LOADED, UPDATE_ACTIVE_RULE,
ACTION_ERROR, OPTIONS_LOADED, UPDATE_FILTERS_VALUES,
LOADING, EDIT_RULE, SET_FILTER, CLEAN_EDITING, RULE_SAVED} = require('../actions/rulesmanager');
+const {
+ CHANGE_DRAWING_STATUS
+} = require('../actions/draw');
+
const _ = require('lodash');
const defaultState = {
services: {
@@ -45,6 +51,17 @@ const getPosition = ({targetPosition = {}}, priority) => {
return 0;
}
};
+// GEOFENCE ACCEPTS ONLY MULTYPOLYGON
+const fixGeometry = (g, method = "") => {
+ if (method === "" || isEmpty(g) || !g.coordinates || g.coordinates.length === 0) {
+ return g;
+ }
+ const c = g.coordinates[0];
+ if (method === "Polygon") {
+ return {...g, type: "MultiPolygon", coordinates: [[[...c, c[0]]]]};
+ }
+ return {...g, type: "MultiPolygon", coordinates: [[c]]};
+};
function rulesmanager(state = defaultState, action) {
switch (action.type) {
@@ -133,14 +150,35 @@ function rulesmanager(state = defaultState, action) {
if (createNew) {
return assign({}, state, {activeRule: {grant: state.grantDefault, position: {value: getPosition(state, targetPriority), position: "offsetFromTop"}}});
}
- return assign({}, state, {activeRule: {...(state.selectedRules[0] || {}), position: {value: state.targetPosition.offsetFromTop, position: "offsetFromTop"}}});
+ const activeRule = state.selectedRules[0] || {};
+ const geometryState = activeRule.constraints && activeRule.constraints.restrictedAreaWkt && {
+ wkt: activeRule.constraints.restrictedAreaWkt,
+ geometry: wk.parse(activeRule.constraints.restrictedAreaWkt)} || {};
+ return assign({}, state, {activeRule,
+ position: {value: state.targetPosition.offsetFromTop, position: "offsetFromTop"},
+ geometryState});
}
case RULE_SAVED: {
- return assign({}, state, {triggerLoad: (state.triggerLoad || 0) + 1, activeRule: undefined, selectedRules: [], targetPosition: undefined });
+ return assign({}, state, {triggerLoad: (state.triggerLoad || 0) + 1, geometryState: undefined, activeRule: undefined, selectedRules: [], targetPosition: undefined });
}
case CLEAN_EDITING: {
- return assign({}, state, {activeRule: undefined});
+ return assign({}, state, {activeRule: undefined, geometryState: undefined});
}
+ case CHANGE_DRAWING_STATUS: {
+ let newState;
+ if (action.owner === "rulesmanager" && (action.status === "stop" || action.status === "start" || action.status === "clean")) {
+ const geometry = fixGeometry(((action.features || [])[0] || {}), action.method);
+ newState = assign({}, state, {geometryState: assign({}, {
+ geometry,
+ wkt: !isEmpty(geometry) && wk.stringify(geometry) || undefined
+ })});
+ } else {
+ newState = state;
+ }
+
+ return newState;
+ }
+
default:
return state;
}
diff --git a/web/client/selectors/rulesmanager.js b/web/client/selectors/rulesmanager.js
index a0dbcb1454..524fc279e8 100644
--- a/web/client/selectors/rulesmanager.js
+++ b/web/client/selectors/rulesmanager.js
@@ -65,6 +65,7 @@ const isRulesManagerConfigured = state => state.localConfig && state.localConfig
const isEditorActive = state => state.rulesmanager && !!state.rulesmanager.activeRule;
const triggerLoadSel = state => state.rulesmanager && state.rulesmanager.triggerLoad;
const isLoading = state => state.rulesmanager && state.rulesmanager.loading;
+const geometryStateSel = state => state.rulesmanager && state.rulesmanager.geometryState;
module.exports = {
rulesSelector,
optionsSelector,
@@ -77,5 +78,6 @@ module.exports = {
servicesConfigSel,
triggerLoadSel,
isLoading,
- isRulesManagerConfigured
+ isRulesManagerConfigured,
+ geometryStateSel
};
diff --git a/web/client/themes/default/less/select.less b/web/client/themes/default/less/select.less
index e49d2c985f..2061d80adb 100644
--- a/web/client/themes/default/less/select.less
+++ b/web/client/themes/default/less/select.less
@@ -32,7 +32,33 @@
.rw-select {
border-left: 1px solid @ms2-color-shade-lighter !important;
}
-
+.rw-combo-clearable {
+ background-color: @ms2-color-background !important;
+}
+.rw-combo-clearable input.rw-input{
+ -webkit-box-shadow: inset 0 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px rgba(0, 0, 0, 0.075);
+}
+.rw-combo-clearable button{
+ border: none !important;
+ -webkit-box-shadow: inset 0 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px rgba(0, 0, 0, 0.075);
+}
+.rw-combo-clear .hidden{
+ display: none;
+}
+.rw-combo-clear {
+ position: relative;
+ float: right;
+ bottom: 2em;
+ right: 2.3em;
+ color: @ms2-color-primary !important;
+ cursor: pointer;
+}
+.rw-combo-clearable.disabled .rw-combo-clear{
+ cursor: not-allowed;
+ color: fadeout(@ms2-color-primary, 30) !important;
+}
.Select-placeholder {
background-color: @ms2-color-background;
border: 1px solid @ms2-color-shade-lighter;
diff --git a/web/client/translations/data.de-DE b/web/client/translations/data.de-DE
index 73435aedff..5a4c711efc 100644
--- a/web/client/translations/data.de-DE
+++ b/web/client/translations/data.de-DE
@@ -708,7 +708,8 @@
"box": "BoundingBox",
"buffer": "Buffer",
"circle": "Kreis",
- "poly": "Polygon"
+ "poly": "Polygon",
+ "cql": "CQL"
},
"operations": {
"intersects": "Schneidet",
@@ -1362,6 +1363,8 @@
"layerlabel": "Ebene"
},
"rulesmanager": {
+ "apply": "Anwenden",
+ "remove": "Entfernen Sie die Geometrie",
"resetconstraints": "Setzt Einschränkungen zurück",
"constraintsmsg": "Wenn Sie die Berechtigung, den Arbeitsbereich oder die Ebene ändern, werden die Details gelöscht. Bist du sicher, dass du das machen willst?",
"defstyle": "Standardstil",
@@ -1423,7 +1426,10 @@
"close": "Schließen",
"previous": "vorheriger",
"next": "nächster",
+ "cacheCleaned": "Der Cache wurde erfolgreich bereinigt",
"errorTitle": "Geofence",
+ "errorCQL": "Geometrie nicht gültig!",
+ "errorCleaningCache": "Fehler beim Reinigen des Geofence-Cache.",
"errorLoadingRoles": "Fehler beim Laden der Rollen.",
"errorLoadingUsers": "Fehler beim Laden der Benutzer.",
"errorLoadingWorkspaces": "Fehler beim Laden der Workspaces.",
diff --git a/web/client/translations/data.en-US b/web/client/translations/data.en-US
index 58a6abe91c..040ddf7b85 100644
--- a/web/client/translations/data.en-US
+++ b/web/client/translations/data.en-US
@@ -709,7 +709,8 @@
"box": "Rectangle",
"buffer": "Buffer",
"circle": "Circle",
- "poly": "Polygon"
+ "poly": "Polygon",
+ "cql": "CQL"
},
"operations": {
"intersects": "Intersects",
@@ -1363,6 +1364,8 @@
"layerlabel": "Layer"
},
"rulesmanager": {
+ "apply": "Apply",
+ "remove": "Remove geometry",
"resetconstraints": "Resets Constraints",
"constraintsmsg": "Changing grant, workspace or layer, the details will be deleted. Are you sure you want to do that?",
"defstyle": "Default Style",
@@ -1425,7 +1428,10 @@
"close": "Close",
"previous": "previous",
"next": "next",
+ "cacheCleaned": "Cache successfully cleaned",
"errorTitle": "Geofence",
+ "errorCQL": "Geometry not valid!",
+ "errorCleaningCache": "Error cleaning geofence cache.",
"errorLoadingRoles": "Error loading roles.",
"errorLoadingUsers": "Error loading users.",
"errorLoadingWorkspaces": "Error loading workspaces.",
diff --git a/web/client/translations/data.es-ES b/web/client/translations/data.es-ES
index 3406ffe542..c9814be4e0 100644
--- a/web/client/translations/data.es-ES
+++ b/web/client/translations/data.es-ES
@@ -708,7 +708,8 @@
"box": "Rectángulo",
"buffer": "Buffer",
"circle": "Círculo",
- "poly": "Polígono"
+ "poly": "Polígono",
+ "cql": "CQL"
},
"operations": {
"intersects": "intersecciona",
@@ -1362,6 +1363,8 @@
"layerlabel": "Capa"
},
"rulesmanager": {
+ "apply": "Aplicar",
+ "remove": "Eliminar geometría",
"resetconstraints": "Restablece las restricciones",
"constraintsmsg": "Cambiando concesión, área de trabajo o capa, los detalles serán eliminados. ¿Estás seguro de que quieres hacer eso?",
"defstyle": "Estilo por Defecto",
@@ -1423,7 +1426,10 @@
"close": "Cerrar",
"previous": "previo",
"next": "próximo",
+ "cacheCleaned": "Caché limpiada con éxito",
"errorTitle": "Geofence",
+ "errorCQL": "Geometría no válida!",
+ "errorCleaningCache": "Error al limpiar la memoria caché geofence.",
"errorLoadingRoles": "Error al cargar los roles.",
"errorLoadingUsers": "Error al cargar los usuarios.",
"errorLoadingWorkspaces": "Error al cargar los espacios de trabajo.",
diff --git a/web/client/translations/data.fr-FR b/web/client/translations/data.fr-FR
index 24eec6ccf8..f68b40ab9d 100644
--- a/web/client/translations/data.fr-FR
+++ b/web/client/translations/data.fr-FR
@@ -709,7 +709,8 @@
"box": "Rectangle",
"buffer": "Zone tampon",
"circle": "Cercle",
- "poly": "Polygone"
+ "poly": "Polygone",
+ "cql": "CQL"
},
"operations": {
"intersects": "intersecte",
@@ -1362,6 +1363,8 @@
"layerlabel": "Couche"
},
"rulesmanager": {
+ "apply": "Appliquer",
+ "remove": "Supprimer la géométrie",
"resetconstraints": "Réinitialise les contraintes",
"constraintsmsg": "En changeant la subvention, l'espace de travail ou la couche, les détails seront supprimés. Êtes-vous sûr de vouloir faire ça?",
"defstyle": "Style par Défaut",
@@ -1423,7 +1426,10 @@
"close": "Fermer",
"previous": "précédent",
"next": "prochain",
+ "cacheCleaned": "Cache nettoyé avec succès",
"errorTitle": "Geofence",
+ "errorCQL": "La géométrie n'est pas valide!",
+ "errorCleaningCache": "Erreur lors du nettoyage du cache de geofence.",
"errorLoadingRoles": "Erreur lors du chargement des rôles.",
"errorLoadingUsers": "Erreur lors du chargement des utilisateurs.",
"errorLoadingWorkspaces": "Erreur lors du chargement des workspaces.",
diff --git a/web/client/translations/data.it-IT b/web/client/translations/data.it-IT
index 11eff97ac3..ab02ff6d5d 100644
--- a/web/client/translations/data.it-IT
+++ b/web/client/translations/data.it-IT
@@ -708,7 +708,8 @@
"box": "Rettangolo",
"buffer": "Buffer",
"circle": "Cerchio",
- "poly": "Poligono"
+ "poly": "Poligono",
+ "cql": "CQL"
},
"operations": {
"intersects": "Intersezione",
@@ -1362,6 +1363,8 @@
"layerlabel": "Layer"
},
"rulesmanager": {
+ "apply": "Applica",
+ "remove": "Elimina la geometia",
"resetconstraints": "Resetta Vincoli",
"constraintsmsg": "Cambinado grant, workspace o layer, i dettagli saranno eliminati. Sei sicuro di volerlo fare?",
"defstyle": "Stile Predefinito",
@@ -1423,7 +1426,10 @@
"close": "Chiudi",
"previous": "precedente",
"next": "prossima",
+ "cacheCleaned": "Cache eliminata",
"errorTitle": "Geofence",
+ "errorCQL": "Geometria non valida!",
+ "errorCleaningCache": "Errore durante la pulizia della cache.",
"errorLoadingRoles": "Errore durante il caricamento dei ruoli",
"errorLoadingUsers": "Errore durante il caricamento degli utenti.",
"errorLoadingWorkspaces": "Errore durante il caricamento dei workspaces.",
diff --git a/web/client/utils/RulesEditor.js b/web/client/utils/RulesEditor.js
index 2d2eba382b..738ac68c9b 100644
--- a/web/client/utils/RulesEditor.js
+++ b/web/client/utils/RulesEditor.js
@@ -11,8 +11,8 @@ const RulesEditorUtils = {
isSaveDisabled: (currentRule, initRule) => {
return RulesEditorUtils.isRulePristine(currentRule, initRule) && initRule.hasOwnProperty("id");
},
- areDetailsActive: ({layer, grant} = {}) => {
- return !!layer && grant === "ALLOW";
+ areDetailsActive: ({layer} = {}) => {
+ return !!layer;
},
isRulePristine: (currentRule, initRule) => {
return isEqual(currentRule, initRule);
@@ -24,7 +24,7 @@ const RulesEditorUtils = {
return true;
},
askConfirm: ({constraints = {}} = {}, key, value) => {
- return !isEmpty(constraints) && (key === "workspace" || key === "layer" || (key === "grant" && value === "DENY"));
+ return !isEmpty(constraints) && (key === "workspace" || key === "layer" || (key === "grant" && value !== "ALLOW"));
},
checkIp
};