diff --git a/web/client/components/data/featuregrid/enhancers/editor.js b/web/client/components/data/featuregrid/enhancers/editor.js
index ba284da190..55987eb551 100644
--- a/web/client/components/data/featuregrid/enhancers/editor.js
+++ b/web/client/components/data/featuregrid/enhancers/editor.js
@@ -73,7 +73,8 @@ const featuresToGrid = compose(
focusOnEdit: false,
editors,
dataStreamFactory,
- virtualScroll: true
+ virtualScroll: true,
+ isWithinAttrTbl: false
}),
withPropsOnChange("showDragHandle", ({showDragHandle = true} = {}) => ({
className: showDragHandle ? 'feature-grid-drag-handle-show' : 'feature-grid-drag-handle-hide'
@@ -170,7 +171,8 @@ const featuresToGrid = compose(
return props.editors(desc.localType, generalProps);
},
getFilterRenderer: getFilterRendererFunc,
- getFormatter: (desc) => getFormatter(desc, (props.fields ?? []).find(f => f.name === desc.name), {dateFormats: props.dateFormats})
+ getFormatter: (desc) => getFormatter(desc, (props.fields ?? []).find(f => f.name === desc.name), {dateFormats: props.dateFormats}),
+ isWithinAttrTbl: props.isWithinAttrTbl
}))
});
return result;
diff --git a/web/client/components/data/featuregrid/filterRenderers/AttributeFilter.jsx b/web/client/components/data/featuregrid/filterRenderers/AttributeFilter.jsx
index ed073adfac..640191647d 100644
--- a/web/client/components/data/featuregrid/filterRenderers/AttributeFilter.jsx
+++ b/web/client/components/data/featuregrid/filterRenderers/AttributeFilter.jsx
@@ -12,6 +12,7 @@ import PropTypes from 'prop-types';
import { getMessageById } from '../../../../utils/LocaleUtils';
import { Tooltip } from 'react-bootstrap';
import OverlayTrigger from '../../../misc/OverlayTrigger';
+import ComboField from '../../query/ComboField';
class AttributeFilter extends React.PureComponent {
static propTypes = {
@@ -21,7 +22,10 @@ class AttributeFilter extends React.PureComponent {
value: PropTypes.any,
column: PropTypes.object,
placeholderMsgId: PropTypes.string,
- tooltipMsgId: PropTypes.string
+ tooltipMsgId: PropTypes.string,
+ operator: PropTypes.string,
+ type: PropTypes.string,
+ isWithinAttrTbl: PropTypes.bool
};
static contextTypes = {
@@ -33,7 +37,46 @@ class AttributeFilter extends React.PureComponent {
valid: true,
onChange: () => {},
column: {},
- placeholderMsgId: "featuregrid.filter.placeholders.default"
+ placeholderMsgId: "featuregrid.filter.placeholders.default",
+ operator: "=",
+ isWithinAttrTbl: false
+ };
+ constructor(props) {
+ super(props);
+ this.state = {
+ listOperators: ["="],
+ stringOperators: ["=", "<>", "like", "ilike", "isNull"],
+ arrayOperators: ["contains"],
+ booleanOperators: ["="],
+ defaultOperators: ["=", ">", "<", ">=", "<=", "<>", "isNull"],
+ timeDateOperators: ["=", ">", "<", ">=", "<=", "<>", "><", "isNull"],
+ operator: this.props.isWithinAttrTbl ? "=" : "",
+ isInputValid: true
+ };
+ }
+ getOperator = (type) => {
+ switch (type) {
+ case "list": {
+ return this.state.listOperators;
+ }
+ case "string": {
+ return this.state.stringOperators;
+ }
+ case "boolean": {
+ return this.state.booleanOperators;
+ }
+ case "array": {
+ return this.state.arrayOperators;
+ }
+ case "date":
+ case "time":
+ case "date-time":
+ {
+ return this.state.timeDateOperators;
+ }
+ default:
+ return this.state.defaultOperators;
+ }
};
renderInput = () => {
if (this.props.column.filterable === false) {
@@ -41,7 +84,19 @@ class AttributeFilter extends React.PureComponent {
}
const placeholder = getMessageById(this.context.messages, this.props.placeholderMsgId) || "Search";
let inputKey = 'header-filter-' + this.props.column.key;
- return ();
+ let isValueExist = this.state?.value ?? this.props.value;
+ if (['date', 'time', 'date-time'].includes(this.props.type) && this.props.isWithinAttrTbl) isValueExist = this.state?.value ?? this.props.value?.startDate ?? this.props.value;
+ let isNullOperator = this.state.operator === 'isNull';
+ return (
+
+
);
}
renderTooltip = (cmp) => {
if (this.props.tooltipMsgId && getMessageById(this.context.messages, this.props.tooltipMsgId)) {
@@ -51,19 +106,67 @@ class AttributeFilter extends React.PureComponent {
}
return cmp;
}
-
+ renderOperatorField = () => {
+ return (
+ {
+ // if select the same operator -> don't do anything
+ if (selectedOperator === this.state.operator) return;
+ let isValueExist; // entered value
+ if (['date', 'time', 'date-time'].includes(this.props.type)) {
+ isValueExist = this.state?.value ?? this.props.value?.startDate ?? this.props.value;
+ } else {
+ isValueExist = this.state?.value ?? this.props.value;
+ }
+ let isNullOperatorSelected = selectedOperator === 'isNull';
+ let isOperatorChangedFromRange = this.state.operator === '><';
+ // set the selected operator + value and reset the value in case of isNull
+ this.setState({ operator: selectedOperator, value: (isNullOperatorSelected || isOperatorChangedFromRange) ? undefined : isValueExist });
+ // get flag of being (operator was isNull then changes to other operator)
+ let isOperatorChangedFromIsNull = this.state.operator === 'isNull' && selectedOperator !== 'isNull';
+ // apply filter if value exists 'OR' operator = isNull 'OR' (prev operator was isNull and changes --> reset filter)
+ if (isNullOperatorSelected || isOperatorChangedFromIsNull || isOperatorChangedFromRange) {
+ // reset data --> operator = isNull 'OR' (prev operator was isNull and changes)
+ this.props.onChange({value: null, attribute: this.props.column && this.props.column.key, inputOperator: selectedOperator});
+ } else if (isValueExist) {
+ // apply filter --> if value exists
+ this.props.onChange({value: isValueExist, attribute: this.props.column && this.props.column.key, inputOperator: selectedOperator});
+ }
+ }}
+ fieldValue={this.state.operator}
+ onUpdateField={() => {}}/>
+ );
+ };
render() {
let inputKey = 'header-filter--' + this.props.column.key;
return (
-
- {this.renderTooltip(this.renderInput())}
+
+ {this.props.isWithinAttrTbl ? <>
+ {this.renderOperatorField()}
+ {['time', 'date', 'date-time'].includes(this.props.type) ? this.renderInput() : this.renderTooltip(this.renderInput())}
+ > : this.renderTooltip(this.renderInput())}
);
}
handleChange = (e) => {
const value = e.target.value;
- this.setState({value});
- this.props.onChange({value, attribute: this.props.column && this.props.column.key});
+ // todo: validate input based on type
+ let isValid = true;
+ if (this.props.isWithinAttrTbl) {
+ const match = /\s*(!==|!=|<>|<=|>=|===|==|=|<|>)?(.*)/.exec(value);
+ if (match[1]) isValid = false;
+ if (match[2]) {
+ if (['integer', 'number'].includes(this.props.type) && isNaN(match[2])) isValid = false;
+ }
+ }
+ this.setState({value, isInputValid: isValid});
+ if (isValid) {
+ this.props.onChange({value, attribute: this.props.column && this.props.column.key, inputOperator: this.state.operator});
+ }
}
}
diff --git a/web/client/components/data/featuregrid/filterRenderers/BaseDateTimeFilter.js b/web/client/components/data/featuregrid/filterRenderers/BaseDateTimeFilter.js
index eccde3b3e9..cb3ac224fd 100644
--- a/web/client/components/data/featuregrid/filterRenderers/BaseDateTimeFilter.js
+++ b/web/client/components/data/featuregrid/filterRenderers/BaseDateTimeFilter.js
@@ -11,6 +11,7 @@ import PropTypes from 'prop-types';
import {intlShape} from 'react-intl';
import {getContext} from 'recompose';
import DateTimePicker from '../../../misc/datetimepicker';
+import RangedDateTimePicker from '../../../misc/datetimepicker/RangedDateTimePicker';
import {getMessageById} from '../../../../utils/LocaleUtils';
import { getDateTimeFormat } from '../../../../utils/TimeUtils';
import AttributeFilter from './AttributeFilter';
@@ -22,6 +23,12 @@ const UTCDateTimePicker = utcDateWrapper({
setDateProp: "onChange"
})(DateTimePicker);
+const UTCDateTimePickerWithRange = utcDateWrapper({
+ dateProp: "value",
+ dateTypeProp: "type",
+ setDateProp: "onChange"
+})(RangedDateTimePicker );
+
class DateFilter extends AttributeFilter {
static propTypes = {
@@ -45,6 +52,7 @@ class DateFilter extends AttributeFilter {
if (this.props.column.filterable === false) {
return
;
}
+ const operator = this.props.value && this.props.value.operator || this.state.operator;
const format = getDateTimeFormat(this.context.locale, this.props.type);
const placeholder = getMessageById(this.context.messages, this.props.placeholderMsgId) || "Insert date";
const toolTip = this.props.intl && this.props.intl.formatMessage({id: `${this.props.tooltipMsgId}`}, {format}) || `Insert date in ${format} format`;
@@ -58,8 +66,27 @@ class DateFilter extends AttributeFilter {
val = this.props.value && this.props.value.startDate || this.props.value;
}
const dateValue = this.props.value ? val : null;
- const operator = this.props.value && this.props.value.operator;
+ if (operator === '><') {
+ return (
+
this.handleChangeRangeFilter(date, stringDate, order)}
+ />
+ );
+ }
return ( this.handleChange(date, stringDate)}
/>);
}
handleChange = (value, stringValue) => {
- this.props.onChange({ value, stringValue, attribute: this.props.column && this.props.column.name });
+ this.props.onChange({ value, stringValue, attribute: this.props.column && this.props.column.name, inputOperator: this.state.operator || this.props.operator });
+ }
+ handleChangeRangeFilter = (value, stringValue, order = 'start') => {
+ let reqVal = {};
+ if (order === 'end') {
+ reqVal = {
+ startDate: this.props.value?.startDate,
+ endDate: value
+ };
+ } else {
+ reqVal = {
+ startDate: value,
+ endDate: this.props.value?.endDate
+ };
+ }
+ this.props.onChange({ value: reqVal, stringValue, attribute: this.props.column && this.props.column.name, inputOperator: this.state.operator || this.props.operator });
}
}
diff --git a/web/client/components/data/featuregrid/filterRenderers/DateTimeFilter.jsx b/web/client/components/data/featuregrid/filterRenderers/DateTimeFilter.jsx
index 8dd637fa25..591245f0f7 100644
--- a/web/client/components/data/featuregrid/filterRenderers/DateTimeFilter.jsx
+++ b/web/client/components/data/featuregrid/filterRenderers/DateTimeFilter.jsx
@@ -15,23 +15,41 @@ export default compose(
value: null
}),
withHandlers({
- onChange: props => ({ value, attribute, stringValue } = {}) => {
- const match = /\s*(!==|!=|<>|<=|>=|===|==|=|<|>)?(.*)/.exec(stringValue);
- const operator = match[1];
- let enhancedOperator = match[1] || '=';
- // replace with standard operators
- if (operator === "!==" | operator === "!=") {
- enhancedOperator = "<>";
- } else if (operator === "===" | operator === "==") {
- enhancedOperator = "=";
+ onChange: props => ({ value, attribute, stringValue, inputOperator } = {}) => {
+ if (typeof value === 'string') {
+ const match = /\s*(!==|!=|<>|<=|>=|===|==|=|<|>)?(.*)/.exec(stringValue);
+ const operator = match[1];
+ let enhancedOperator = match[1] || '=';
+ // replace with standard operators
+ if (operator === "!==" | operator === "!=") {
+ enhancedOperator = "<>";
+ } else if (operator === "===" | operator === "==") {
+ enhancedOperator = "=";
+ }
+ props.onValueChange(value);
+ props.onChange({
+ value: { startDate: value, operator: inputOperator || operator },
+ operator: inputOperator || enhancedOperator,
+ type: props.type,
+ attribute
+ });
+ } else if (value && typeof value === 'object') {
+ props.onValueChange(value);
+ props.onChange({
+ value: { startDate: value?.startDate, endDate: value?.endDate, operator: inputOperator },
+ operator: inputOperator,
+ type: props.type,
+ attribute
+ });
+ } else if (!value) {
+ props.onValueChange(value);
+ props.onChange({
+ value: { startDate: value, operator: inputOperator },
+ operator: inputOperator,
+ type: props.type,
+ attribute
+ });
}
- props.onValueChange(value);
- props.onChange({
- value: { startDate: value, operator },
- operator: enhancedOperator,
- type: props.type,
- attribute
- });
}
}),
defaultProps({
diff --git a/web/client/components/data/featuregrid/filterRenderers/DefaultFilter.jsx b/web/client/components/data/featuregrid/filterRenderers/DefaultFilter.jsx
index e168fa3df0..73c957738d 100644
--- a/web/client/components/data/featuregrid/filterRenderers/DefaultFilter.jsx
+++ b/web/client/components/data/featuregrid/filterRenderers/DefaultFilter.jsx
@@ -15,11 +15,11 @@ export default compose(
onValueChange: () => {}
}),
withHandlers({
- onChange: props => ({value, attribute} = {}) => {
+ onChange: props => ({value, attribute, inputOperator} = {}) => {
props.onValueChange(value);
props.onChange({
value: value,
- operator: "=",
+ operator: inputOperator || "=",
type: props.type,
attribute
});
diff --git a/web/client/components/data/featuregrid/filterRenderers/NumberFilter.jsx b/web/client/components/data/featuregrid/filterRenderers/NumberFilter.jsx
index 32c51dcc85..0dd2e5a652 100644
--- a/web/client/components/data/featuregrid/filterRenderers/NumberFilter.jsx
+++ b/web/client/components/data/featuregrid/filterRenderers/NumberFilter.jsx
@@ -19,7 +19,7 @@ export default compose(
}),
withState("valid", "setValid", true),
withHandlers({
- onChange: props => ({value, attribute} = {}) => {
+ onChange: props => ({value, attribute, inputOperator} = {}) => {
props.onValueChange(value);
if (!COMMA_REGEX.exec(value)) {
let {operator, newVal} = getOperatorAndValue(value, "number");
@@ -31,7 +31,7 @@ export default compose(
props.onChange({
value: isNaN(newVal) ? undefined : newVal,
rawValue: value,
- operator,
+ operator: inputOperator || operator,
type: 'number',
attribute
});
@@ -48,7 +48,7 @@ export default compose(
isValid && props.onChange({
value,
rawValue: value,
- operator: "=",
+ operator: inputOperator || "=",
type: 'number',
attribute
});
diff --git a/web/client/components/data/featuregrid/filterRenderers/StringFilter.jsx b/web/client/components/data/featuregrid/filterRenderers/StringFilter.jsx
index 82f3ca29ce..adb13f44e3 100644
--- a/web/client/components/data/featuregrid/filterRenderers/StringFilter.jsx
+++ b/web/client/components/data/featuregrid/filterRenderers/StringFilter.jsx
@@ -8,12 +8,12 @@ export default compose(
placeholderMsgId: "featuregrid.filter.placeholders.string"
}),
withHandlers({
- onChange: props => ({value, attribute} = {}) => {
+ onChange: props => ({value, attribute, inputOperator} = {}) => {
props.onValueChange(value);
props.onChange({
rawValue: value,
value: trim(value) ? trim(value) : undefined,
- operator: "ilike",
+ operator: inputOperator || "ilike",
type: 'string',
attribute
});
diff --git a/web/client/components/data/featuregrid/filterRenderers/__tests__/AttributeFilter-test.jsx b/web/client/components/data/featuregrid/filterRenderers/__tests__/AttributeFilter-test.jsx
index 0ceb08193d..d309eef0dd 100644
--- a/web/client/components/data/featuregrid/filterRenderers/__tests__/AttributeFilter-test.jsx
+++ b/web/client/components/data/featuregrid/filterRenderers/__tests__/AttributeFilter-test.jsx
@@ -68,4 +68,22 @@ describe('Test for AttributeFilter component', () => {
ReactTestUtils.Simulate.change(input);
expect(spyonChange).toHaveBeenCalled();
});
+ it('test rendering with operator DD', () => {
+ const cmp = ReactDOM.render(, document.getElementById("container"));
+ const el = document.getElementsByClassName("form-control input-sm")[0];
+ expect(el).toExist();
+ const input = ReactTestUtils.findRenderedDOMComponentWithTag(cmp, "input");
+ expect(input.value).toBe("TEST");
+ const operatorDropdownListEl = ReactTestUtils.findRenderedDOMComponentWithClass(cmp, 'rw-dropdownlist');
+ expect(operatorDropdownListEl).toExist();
+ });
+ it('test rendering without operator DD', () => {
+ const cmp = ReactDOM.render(, document.getElementById("container"));
+ const el = document.getElementsByClassName("form-control input-sm")[0];
+ expect(el).toExist();
+ const input = ReactTestUtils.findRenderedDOMComponentWithTag(cmp, "input");
+ expect(input.value).toBe("TEST");
+ const operatorDropdownListEl = document.getElementsByClassName('rw-dropdownlist');
+ expect(operatorDropdownListEl.length).toEqual(0);
+ });
});
diff --git a/web/client/components/data/featuregrid/filterRenderers/__tests__/BaseDateTimeFilter-test.jsx b/web/client/components/data/featuregrid/filterRenderers/__tests__/BaseDateTimeFilter-test.jsx
index 3381a61fff..b1f0e5c1de 100644
--- a/web/client/components/data/featuregrid/filterRenderers/__tests__/BaseDateTimeFilter-test.jsx
+++ b/web/client/components/data/featuregrid/filterRenderers/__tests__/BaseDateTimeFilter-test.jsx
@@ -53,4 +53,24 @@ describe('Test for BaseDateTimeFilter component', () => {
expect(el).toExist();
});
+ it('render with range operator ><', () => {
+ // for type date
+ ReactDOM.render(<'}} isWithinAttrTbl={"true"} />, document.getElementById("container"));
+ let el = document.getElementsByTagName("input")[0];
+ expect(el).toExist();
+ let dateTimePickerWithRangeElement = document.getElementsByClassName('rw-datetimepicker range-time-input rw-widget')[0];
+ expect(dateTimePickerWithRangeElement).toExist();
+ // for time date
+ ReactDOM.render(<'}} isWithinAttrTbl={"true"} />, document.getElementById("container"));
+ el = document.getElementsByTagName("input")[0];
+ expect(el).toExist();
+ dateTimePickerWithRangeElement = document.getElementsByClassName('rw-datetimepicker range-time-input rw-widget')[0];
+ expect(dateTimePickerWithRangeElement).toExist();
+ // for type date-time
+ ReactDOM.render(<'}} isWithinAttrTbl={"true"} />, document.getElementById("container"));
+ el = document.getElementsByTagName("input")[0];
+ expect(el).toExist();
+ dateTimePickerWithRangeElement = document.getElementsByClassName('rw-datetimepicker range-time-input rw-widget')[0];
+ expect(dateTimePickerWithRangeElement).toExist();
+ });
});
diff --git a/web/client/components/data/featuregrid/filterRenderers/__tests__/NumberFilter-test.jsx b/web/client/components/data/featuregrid/filterRenderers/__tests__/NumberFilter-test.jsx
index 04af11804b..722c68b837 100644
--- a/web/client/components/data/featuregrid/filterRenderers/__tests__/NumberFilter-test.jsx
+++ b/web/client/components/data/featuregrid/filterRenderers/__tests__/NumberFilter-test.jsx
@@ -33,15 +33,17 @@ const EXPRESSION_TESTS = [
[" ", "=", undefined],
["ZZZ", "=", undefined]
];
-const testExpression = (spyonChange, spyonValueChange, rawValue, expectedOperator, expectedValue) => {
+const testExpression = (spyonChange, spyonValueChange, rawValue, expectedOperator, expectedValue, index) => {
const input = document.getElementsByTagName("input")[0];
input.value = rawValue;
ReactTestUtils.Simulate.change(input);
- const args = spyonChange.calls[spyonChange.calls.length - 1].arguments[0];
- const valueArgs = spyonValueChange.calls[spyonValueChange.calls.length - 1].arguments[0];
- expect(args.value).toBe(expectedValue);
- expect(args.operator).toBe(expectedOperator);
- expect(valueArgs).toBe(rawValue);
+ const args = spyonChange.calls[index]?.arguments[0];
+ const valueArgs = spyonValueChange.calls[index]?.arguments[0];
+ if (valueArgs) { // in case of invalid number expression it will be undefined
+ expect(args.value).toBe(expectedValue);
+ expect(args.operator).toBe(expectedOperator);
+ expect(valueArgs).toBe(rawValue);
+ }
};
describe('Test for NumberFilter component', () => {
@@ -75,7 +77,7 @@ describe('Test for NumberFilter component', () => {
ReactDOM.render(, document.getElementById("container"));
const input = document.getElementsByTagName("input")[0];
- input.value = "> 2";
+ input.value = "2";
ReactTestUtils.Simulate.change(input);
expect(spyonChange).toHaveBeenCalled();
});
@@ -115,6 +117,6 @@ describe('Test for NumberFilter component', () => {
const spyonChange = expect.spyOn(actions, 'onChange');
const spyonValueChange = expect.spyOn(actions, 'onValueChange');
ReactDOM.render(, document.getElementById("container"));
- EXPRESSION_TESTS.map( params => testExpression(spyonChange, spyonValueChange, ...params));
+ EXPRESSION_TESTS.map( (params, index) => testExpression(spyonChange, spyonValueChange, ...params, index));
});
});
diff --git a/web/client/components/data/featuregrid/filterRenderers/__tests__/index-test.jsx b/web/client/components/data/featuregrid/filterRenderers/__tests__/index-test.jsx
index a3c233457b..69e15c224a 100644
--- a/web/client/components/data/featuregrid/filterRenderers/__tests__/index-test.jsx
+++ b/web/client/components/data/featuregrid/filterRenderers/__tests__/index-test.jsx
@@ -66,4 +66,56 @@ describe('Test for filterRenderer function', () => {
unregisterFilterRenderer("test");
}
});
+ it('render filter components for attribute table', () => {
+ // default filter
+ let Cmp = getFilterRenderer({type: "unknown", isWithinAttrTbl: true});
+ expect(Cmp).toExist();
+ ReactDOM.render(, document.getElementById("container"));
+ let operatorDropdownEl = document.getElementsByClassName('rw-dropdownlist')[0];
+ let input = document.getElementsByClassName("form-control input-sm")[0];
+ expect(operatorDropdownEl).toExist();
+ expect(input).toExist();
+ // string filter
+ Cmp = getFilterRenderer({type: "string", isWithinAttrTbl: true});
+ expect(Cmp).toExist();
+ ReactDOM.render(, document.getElementById("container"));
+ operatorDropdownEl = document.getElementsByClassName('rw-dropdownlist')[0];
+ input = document.getElementsByClassName("form-control input-sm")[0];
+ expect(operatorDropdownEl).toExist();
+ expect(input).toExist();
+ // number filter
+ Cmp = getFilterRenderer({type: "int", isWithinAttrTbl: true});
+ expect(Cmp).toExist();
+ ReactDOM.render(, document.getElementById("container"));
+ operatorDropdownEl = document.getElementsByClassName('rw-dropdownlist')[0];
+ input = document.getElementsByClassName("form-control input-sm")[0];
+ expect(operatorDropdownEl).toExist();
+ expect(input).toExist();
+ // number filter
+ Cmp = getFilterRenderer({type: "number", isWithinAttrTbl: true});
+ expect(Cmp).toExist();
+ ReactDOM.render(, document.getElementById("container"));
+ operatorDropdownEl = document.getElementsByClassName('rw-dropdownlist')[0];
+ input = document.getElementsByClassName("form-control input-sm")[0];
+ expect(operatorDropdownEl).toExist();
+ expect(input).toExist();
+ // time filter
+ Cmp = getFilterRenderer({type: "time", isWithinAttrTbl: true});
+ expect(Cmp).toExist();
+ ReactDOM.render(, document.getElementById("container"));
+ operatorDropdownEl = document.getElementsByClassName('rw-dropdownlist')[0];
+ expect(operatorDropdownEl).toExist();
+ // date filter
+ Cmp = getFilterRenderer({type: "date", isWithinAttrTbl: true});
+ expect(Cmp).toExist();
+ ReactDOM.render(, document.getElementById("container"));
+ operatorDropdownEl = document.getElementsByClassName('rw-dropdownlist')[0];
+ expect(operatorDropdownEl).toExist();
+ // date-time filter
+ Cmp = getFilterRenderer({type: "date-time", isWithinAttrTbl: true});
+ expect(Cmp).toExist();
+ ReactDOM.render(, document.getElementById("container"));
+ operatorDropdownEl = document.getElementsByClassName('rw-dropdownlist')[0];
+ expect(operatorDropdownEl).toExist();
+ });
});
diff --git a/web/client/components/data/featuregrid/filterRenderers/index.js b/web/client/components/data/featuregrid/filterRenderers/index.js
index c41567041f..3f6cc8ea42 100644
--- a/web/client/components/data/featuregrid/filterRenderers/index.js
+++ b/web/client/components/data/featuregrid/filterRenderers/index.js
@@ -15,13 +15,41 @@ import NumberFilter from './NumberFilter';
import StringFilter from './StringFilter';
const types = {
- "defaultFilter": (type) => withProps(() =>({type: type}))(DefaultFilter),
- "string": () => StringFilter,
- "number": () => NumberFilter,
- "int": () => NumberFilter,
- "date": () => withProps(() =>({type: "date"}))(DateTimeFilter),
- "time": () => withProps(() =>({type: "time"}))(DateTimeFilter),
- "date-time": () => withProps(() =>({type: "date-time"}))(DateTimeFilter),
+ "defaultFilter": (props) => withProps(() =>{
+ let placeholderMsgId = props.isWithinAttrTbl ? "featuregrid.attributeFilter.placeholders.default" : '';
+ let tooltipMsgId = props.isWithinAttrTbl ? "featuregrid.attributeFilter.tooltips.default" : "";
+ return { type: props.type, isWithinAttrTbl: props.isWithinAttrTbl || false, placeholderMsgId, tooltipMsgId };
+ })(DefaultFilter),
+ "string": (props) => withProps(() =>{
+ let placeholderMsgId = props.isWithinAttrTbl ? "featuregrid.attributeFilter.placeholders.string" : '';
+ let tooltipMsgId = props.isWithinAttrTbl ? "featuregrid.attributeFilter.tooltips.string" : "";
+ return { type: 'string', isWithinAttrTbl: props.isWithinAttrTbl || false, placeholderMsgId, tooltipMsgId };
+ })(StringFilter),
+ "number": (props) => withProps(() =>{
+ let placeholderMsgId = props.isWithinAttrTbl ? "featuregrid.attributeFilter.placeholders.number" : '';
+ let tooltipMsgId = props.isWithinAttrTbl ? "featuregrid.attributeFilter.tooltips.number" : "";
+ return { type: 'number', isWithinAttrTbl: props.isWithinAttrTbl || false, placeholderMsgId, tooltipMsgId };
+ })(NumberFilter),
+ "int": (props) => withProps(() =>{
+ let placeholderMsgId = props.isWithinAttrTbl ? "featuregrid.attributeFilter.placeholders.number" : '';
+ let tooltipMsgId = props.isWithinAttrTbl ? "featuregrid.attributeFilter.tooltips.number" : "";
+ return { type: 'integer', isWithinAttrTbl: props.isWithinAttrTbl || false, placeholderMsgId, tooltipMsgId };
+ })(NumberFilter),
+ "date": (props) => withProps(() =>{
+ let placeholderMsgId = props.isWithinAttrTbl ? "featuregrid.attributeFilter.placeholders.date" : '';
+ let tooltipMsgId = props.isWithinAttrTbl ? "featuregrid.attributeFilter.tooltips.date" : "";
+ return { type: "date", isWithinAttrTbl: props.isWithinAttrTbl || false, placeholderMsgId, tooltipMsgId };
+ })(DateTimeFilter),
+ "time": (props) => withProps(() =>{
+ let placeholderMsgId = props.isWithinAttrTbl ? "featuregrid.attributeFilter.placeholders.date" : '';
+ let tooltipMsgId = props.isWithinAttrTbl ? "featuregrid.attributeFilter.tooltips.date" : "";
+ return { type: "time", isWithinAttrTbl: props.isWithinAttrTbl || false, placeholderMsgId, tooltipMsgId };
+ })(DateTimeFilter),
+ "date-time": (props) => withProps(() =>{
+ let placeholderMsgId = props.isWithinAttrTbl ? "featuregrid.attributeFilter.placeholders.date" : '';
+ let tooltipMsgId = props.isWithinAttrTbl ? "featuregrid.attributeFilter.tooltips.date" : "";
+ return { type: "date-time", isWithinAttrTbl: props.isWithinAttrTbl || false, placeholderMsgId, tooltipMsgId };
+ })(DateTimeFilter),
"geometry": () => GeometryFilter
};
@@ -46,11 +74,11 @@ export const getFilterRendererByName = (name) => {
* @param {string} [params.type] the type of the filter renderer. The available types are: "defaultFilter", "string", "number", "int", "date", "time", "date-time", "geometry".
* @returns {React.Component} the filter renderer
*/
-export const getFilterRenderer = ({name, type}) => {
+export const getFilterRenderer = ({name, type, isWithinAttrTbl}) => {
if (name) {
return getFilterRendererByName(name);
}
- return types[type] ? types[type](type) : types.defaultFilter(type);
+ return types[type] ? types[type]({type, isWithinAttrTbl}) : types.defaultFilter({type, isWithinAttrTbl});
};
diff --git a/web/client/components/misc/datetimepicker/DateTimePicker.js b/web/client/components/misc/datetimepicker/DateTimePicker.js
index 1a58d8b8db..22d0f82524 100644
--- a/web/client/components/misc/datetimepicker/DateTimePicker.js
+++ b/web/client/components/misc/datetimepicker/DateTimePicker.js
@@ -12,10 +12,12 @@ import PropTypes from 'prop-types';
import moment from 'moment';
import { Calendar } from 'react-widgets';
import localizer from 'react-widgets/lib/localizers/moment';
-import { Tooltip } from 'react-bootstrap';
-import { isDate, isNil } from 'lodash';
+import { Tooltip, Glyphicon } from 'react-bootstrap';
+import { isDate, isNil, omit } from 'lodash';
import OverlayTrigger from '../OverlayTrigger';
import Hours from './Hours';
+import Popover from '../../styleeditor/Popover';
+import {getMessageById} from '../../../utils/LocaleUtils';
localizer(moment);
@@ -66,16 +68,21 @@ class DateTimePicker extends Component {
culture: PropTypes.string,
toolTip: PropTypes.string,
tabIndex: PropTypes.string,
- options: PropTypes.object
+ options: PropTypes.object,
+ isWithinAttrTbl: PropTypes.bool
}
-
+ static contextTypes = {
+ messages: PropTypes.object,
+ locale: PropTypes.string
+ };
static defaultProps = {
placeholder: 'Type date...',
calendar: true,
time: true,
onChange: () => { },
value: null,
- popupPosition: 'bottom'
+ popupPosition: 'bottom',
+ isWithinAttrTbl: false
}
state = {
@@ -95,6 +102,7 @@ class DateTimePicker extends Component {
if (prevProps.value !== this.props.value || prevProps.operator !== this.props.operator) {
const { value, operator } = this.props;
this.setDateFromValueProp(value, operator);
+ if (this.props.operator === 'isNull') this.setState({ inputValue: '', date: null });
}
}
@@ -103,19 +111,61 @@ class DateTimePicker extends Component {
const { date: dateFormat, time: timeFormat, base: defaultFormat } = formats;
return format ? format : !time && calendar ? dateFormat : time && !calendar ? timeFormat : defaultFormat;
}
+ renderCustomDateTimePopup = () => {
+ const { inputValue, operator, open } = this.state;
+ const { tabIndex, type } = this.props;
+
+ const timeVisible = open === 'time';
+ const props = omit(this.props, ['placeholder', 'calendar', 'time', 'onChange', 'value']);
+ const calendarVal = this.props.value?.startDate ?? this.props.value;
+ let timePlaceholderMsgId = getMessageById(this.context.messages, "featuregrid.attributeFilter.placeholders.time");
- renderInput = (inputValue, operator, toolTip, placeholder, tabIndex, calendarVisible, timeVisible) => {
+ return (
+
+
+
+
{this.attachCalRef = elem;}}
+ onMouseDown={this.handleMouseDown}
+ onChange={this.handleCalendarChange}
+ {...props}
+ value={!isNil(calendarVal) ? new Date(calendarVal) : undefined}
+ />
+
+
+ {this.renderInput(inputValue, operator, '', timePlaceholderMsgId, tabIndex, false, true, 'form-control')}
+
+
+
+
+
+ {this.attachTimeRef = elem; }} value={inputValue} {...props} onClose={this.close} onSelect={(time) => this.handleTimeSelect(time, type)} />
+
+
+
+
+
+ );
+ };
+ renderInput = (inputValue, operator, toolTip, placeholder, tabIndex, calendarVisible, timeVisible, className) => {
+ let inputV = this.props.isWithinAttrTbl ? `${inputValue}` : `${operator}${inputValue}`;
+ let isNullOperator = this.props.operator === 'isNull';
+ if (isNullOperator) inputV = '';
+ const inputEl = ;
if (toolTip) {
return ({toolTip}}>
-
+ {inputEl}
);
}
- return ();
+ return inputEl;
}
render() {
- const { open, inputValue, operator, focused } = this.state;
- const { calendar, time, toolTip, placeholder, tabIndex, popupPosition } = this.props;
+ const { open, inputValue, operator, focused, openDateTime } = this.state;
+ const { calendar, time, toolTip, placeholder, tabIndex, type, popupPosition } = this.props;
const props = Object.keys(this.props).reduce((acc, key) => {
if (['placeholder', 'calendar', 'time', 'onChange', 'value'].includes(key)) {
// remove these props because they might have undesired effects to the subsequent components
@@ -127,42 +177,89 @@ class DateTimePicker extends Component {
}, {});
const calendarVisible = open === 'date';
const timeVisible = open === 'time';
-
+ const dateTimeVisible = openDateTime === 'dateTime';
+ const calendarVal = this.props.value?.startDate ?? this.props.value;
+ let timePlaceholderMsgId = getMessageById(this.context.messages, "featuregrid.attributeFilter.placeholders.time");
+
+ if (type === 'date-time') {
+ return ( {this.dateTimeRef = elem;}} onBlur={() => this.handleWidgetBlur(type)} onKeyDown={this.handleKeyDown} onFocus={this.handleWidgetFocus} className={`rw-datetimepicker range-time-input rw-widget ${focused ? 'rw-state-focus' : ''}`}>
+ {this.renderInput(inputValue, operator, dateTimeVisible ? '' : toolTip, placeholder, tabIndex, true, true)}
+
+
+ {this.renderCustomDateTimePopup()}
+
+ }
+ >
+
+
+
+ );
+ } else if (type === 'time') {
+ return (
+
+ {this.renderInput(inputValue, operator, timeVisible ? '' : toolTip, timePlaceholderMsgId, tabIndex, calendarVisible, timeVisible)}
+
+
+
+
+ { this.attachTimeRef = elem;}} value={inputValue} onMouseDown={this.handleMouseDown} {...props} onClose={this.close} onSelect={this.handleTimeSelect} />
+
+
+
+ }
+ >
+
+
+
+
+ );
+ }
return (
-
- {this.renderInput(inputValue, operator, toolTip, placeholder, tabIndex, calendarVisible, timeVisible)}
- {calendar || time ?
+
+ {this.renderInput(inputValue, operator, calendarVisible ? '' : toolTip, placeholder, tabIndex, calendarVisible, timeVisible)}
+ {calendar ?
- {
- calendar ?
+ }
+ >
+
: ''
- }
- {
- time ?
: ''
- }
+
+
: ''
}
-
-
);
}
@@ -176,11 +273,16 @@ class DateTimePicker extends Component {
this.ignoreBlur = false;
}
- handleWidgetBlur = () => {
+ handleWidgetBlur = (type) => {
if (this.ignoreBlur) {
return;
}
- this.setState({ open: '', focused: false });
+ if (type === 'date-time') {
+ // this.dateTimeRef.click();
+ this.setState({ openDateTime: '', focused: false });
+ } else {
+ this.setState({ open: '', focused: false });
+ }
}
handleMouseDown = () => {
@@ -190,7 +292,9 @@ class DateTimePicker extends Component {
toggleCalendar = () => {
this.setState(prevState => ({ open: prevState.open !== 'date' ? 'date' : '' }));
}
-
+ toggleDateTime = () => {
+ this.setState(prevState => ({ openDateTime: prevState.openDateTime !== 'dateTime' ? 'dateTime' : '', open: '' }));
+ }
toggleTime = () => {
this.setState(prevState => ({ open: prevState.open !== 'time' ? 'time' : '' }));
}
@@ -236,7 +340,7 @@ class DateTimePicker extends Component {
}
close = () => {
- this.setState({ open: '' });
+ this.setState({ open: '', openDateTime: '' });
}
open = () => {
@@ -273,11 +377,11 @@ class DateTimePicker extends Component {
}
if (timeVisible) {
- this.timeRef.handleKeyDown(e);
+ this.timeRef?.handleKeyDown(e);
}
if (calVisible) {
- this.calRef.refs.inner.handleKeyDown(e);
+ this.calRef?.refs?.inner?.handleKeyDown(e);
}
if (!timeVisible && !calVisible && e.key === 'Enter') {
@@ -303,13 +407,16 @@ class DateTimePicker extends Component {
}
handleCalendarChange = value => {
- const date = setTime(value, this.state.date || new Date());
+ const date = setTime(value, this.state.date || new Date(value));
const inputValue = this.format(date);
this.setState({ date, inputValue, open: '' });
this.props.onChange(date, `${this.state.operator}${inputValue}`);
}
- handleTimeSelect = time => {
+ handleTimeSelect = (time, pickerType) => {
+ if (pickerType === 'date-time') {
+ this.ignoreBlur = true;
+ }
const selectedDate = this.state.date || new Date();
const date = setTime(selectedDate, time.date);
const inputValue = this.format(date);
diff --git a/web/client/components/misc/datetimepicker/Hours.js b/web/client/components/misc/datetimepicker/Hours.js
index 6db0a025bb..0844b0d455 100644
--- a/web/client/components/misc/datetimepicker/Hours.js
+++ b/web/client/components/misc/datetimepicker/Hours.js
@@ -1,6 +1,7 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
+import { getLocalTimePart } from '../../../utils/TimeUtils';
const getDates = (step) => {
let min = moment().startOf('day');
@@ -23,15 +24,21 @@ class Hours extends Component {
static propTypes = {
onSelect: PropTypes.func,
onMouseDown: PropTypes.func,
- disabled: PropTypes.bool
+ disabled: PropTypes.bool,
+ style: PropTypes.object,
+ value: PropTypes.string,
+ type: PropTypes.string
}
static defaultProps = {
onSelect: () => { },
onMouseDown: () => {},
- disabled: false
+ disabled: false,
+ style: {},
+ value: '',
+ type: ''
}
- state = { focusedItemIndex: 0, times: [] };
+ state = { focusedItemIndex: -1, times: [] };
componentDidMount() {
this.setState({ times: getDates() });
@@ -39,10 +46,11 @@ class Hours extends Component {
render() {
const { focusedItemIndex, times } = this.state;
- const { onMouseDown, onSelect, disabled } = this.props;
+ const { onMouseDown, onSelect, disabled, style, value, type } = this.props;
+ let selectedVal = type === 'date-time' ? (value?.split(" ")[1] || "") : value; // in case of date-time --> extract hours from selected passed value ex.: 01/01/2024 10:00:00
return (
-
- {times.map((time, index) => - {} : onMouseDown} onClick={disabled ? () => {} : () => onSelect(time)} ref={instance => {this.itemsRef[index] = instance;}} role="option" tabIndex="0" aria-selected="false" className={`rw-list-option ${focusedItemIndex === index && !disabled ? 'rw-state-focus' : ''} ${disabled ? 'rw-state-disabled' : ''}`} id="rw_1_time_listbox__option__0">{time.label}
)}
+
+ {times.map((time, index) => - {} : onMouseDown} onClick={disabled ? () => {} : () => onSelect(time)} ref={instance => {this.itemsRef[index] = instance;}} role="option" tabIndex="0" aria-selected="false" className={`rw-list-option ${(selectedVal === getLocalTimePart(time.date) || focusedItemIndex === index ) && !disabled ? 'rw-state-focus is-selected' : ''} ${disabled ? 'rw-state-disabled' : ''}`} id="rw_1_time_listbox__option__0">{time.label}
)}
);
}
diff --git a/web/client/components/misc/datetimepicker/RangedDateTimePicker.js b/web/client/components/misc/datetimepicker/RangedDateTimePicker.js
new file mode 100644
index 0000000000..8979bda49c
--- /dev/null
+++ b/web/client/components/misc/datetimepicker/RangedDateTimePicker.js
@@ -0,0 +1,567 @@
+/*
+ * Copyright 2023, 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.
+ */
+
+import React, { Component } from 'react';
+
+import PropTypes from 'prop-types';
+import moment from 'moment';
+import { Calendar } from 'react-widgets';
+import localizer from 'react-widgets/lib/localizers/moment';
+import { Tooltip, Glyphicon } from 'react-bootstrap';
+import { isDate, isNil, omit } from 'lodash';
+import OverlayTrigger from '../OverlayTrigger';
+import Hours from './Hours';
+import Popover from '../../styleeditor/Popover';
+import { getMessageById } from '../../../utils/LocaleUtils';
+import Message from '../../I18N/Message';
+
+localizer(moment);
+
+// lang is supported by moment < 2.8.0 in favour of locale
+const localField = typeof moment().locale === 'function' ? 'locale' : 'lang';
+
+function getMoment(culture, value, format) {
+ return culture ? moment(value, format)[localField](culture) : moment(value, format);
+}
+
+const setTime = (date, dateWithTime) => {
+ const value = moment(date);
+ value.hours(dateWithTime.getHours())
+ .minute(dateWithTime.getMinutes())
+ .seconds(dateWithTime.getSeconds())
+ .milliseconds(dateWithTime.getMilliseconds());
+ return value.toDate();
+};
+
+const formats = {
+ base: 'lll',
+ date: 'L',
+ time: 'LT'
+};
+
+/**
+ * @name DateTimePickerWithRange
+ * The revised react-widget datetimepicker to support operator in addition to date and time.
+ * This component mimick the react-widget date time picker component behaviours and
+ * props. Please see https://jquense.github.io/react-widgets/api/DateTimePicker/.
+ * The operator supported must be placed before date in input field and it should be
+ * one of !==|!=|<>|<=|>=|===|==|=|<|> operator. Anything else should not be
+ * considered as operator by this component.
+ *
+ */
+class DateTimePickerWithRange extends Component {
+
+ static propTypes = {
+ format: PropTypes.string,
+ type: PropTypes.string,
+ placeholder: PropTypes.string,
+ onChange: PropTypes.func,
+ calendar: PropTypes.bool,
+ popupPosition: PropTypes.oneOf(['top', 'bottom']),
+ time: PropTypes.bool,
+ value: PropTypes.any,
+ operator: PropTypes.string,
+ culture: PropTypes.string,
+ toolTip: PropTypes.string,
+ tabIndex: PropTypes.string,
+ options: PropTypes.object
+ }
+
+ static defaultProps = {
+ placeholder: 'Type date...',
+ calendar: true,
+ time: true,
+ onChange: () => { },
+ value: null,
+ popupPosition: 'bottom'
+ }
+ static contextTypes = {
+ messages: PropTypes.object,
+ locale: PropTypes.string
+ };
+ state = {
+ openRangeContainer: false,
+ openRangeInputs: 'start', // start, end
+ openDateTCalendar: false,
+ openTime: false,
+ focused: false,
+ mainInputValue: '',
+ inputValue: { // what's shown on input for user
+ startDate: '',
+ endDate: ''
+ },
+ operator: '><',
+ date: { // stored values
+ startDate: null,
+ endDate: null
+ },
+ isInputNotValid: false
+ }
+
+ componentDidMount() {
+ const { value, operator } = this.props;
+ this.setDateFromValueProp(value, operator);
+ }
+
+ componentDidUpdate(prevProps) {
+ if (prevProps.value?.startDate !== this.props.value?.startDate || prevProps.value?.endDate !== this.props.value?.endDate ) {
+ const { value, operator } = this.props;
+ this.setDateFromValueProp(value, operator);
+ }
+ }
+
+ getFormat = () => {
+ const { format, time, calendar } = this.props;
+ const { date: dateFormat, time: timeFormat, base: defaultFormat } = formats;
+ return format ? format : !time && calendar ? dateFormat : time && !calendar ? timeFormat : defaultFormat;
+ }
+
+ renderInput = (inputValue, operator, toolTip, placeholder, tabIndex, calendarVisible, timeVisible, style = {}, className) => {
+ let inputV = this.props.isWithinAttrTbl ? `${inputValue}` : `${operator}${inputValue}`;
+ const inputEl = ;
+ if (toolTip) {
+ return ({toolTip}}>
+ {inputEl}
+ );
+ }
+ return inputEl;
+ }
+ renderHoursRange = () =>{
+ const { inputValue, operator, focused, openRangeInputs} = this.state;
+ const { placeholder, tabIndex } = this.props;
+ const props = omit(this.props, ['placeholder', 'calendar', 'time', 'onChange', 'value', 'toolTip', 'onMouseOver']);
+ return (
+
+
+
+
+
+ {inputValue.startDate || getMessageById(this.context.messages, "featuregrid.attributeFilter.placeholders.range") }
+
+
+
+
+
+ {inputValue.endDate || getMessageById(this.context.messages, "featuregrid.attributeFilter.placeholders.range") }
+
+
+
+
+
+ {this.renderInput(inputValue.startDate, operator, '', placeholder, tabIndex, false, true, {width: '90%'})}
+
+
+
+
+ {this.attachStartTimeRef = elem;}} value={inputValue?.startDate || ''} onMouseDown={this.handleMouseDown} {...props} onClose={this.close} onSelect={(evt) => this.handleTimeSelect(evt, 'start')} />
+
+
+
+ {this.renderInput(inputValue.endDate, operator, '', placeholder, tabIndex, false, true, {width: '90%'})}
+
+
+
+
+ {this.attachTimeEndRef = elem;}} value={inputValue?.endDate || ''} onMouseDown={this.handleMouseDown} {...props} onClose={this.close} onSelect={(evt) => this.handleTimeSelect(evt, 'end')} />
+
+
+
+
+ );
+ }
+ renderHours = () =>{
+ const { inputValue, operator, focused } = this.state;
+ const { toolTip, placeholder, tabIndex, popupPosition } = this.props;
+ let shownVal = (inputValue.endDate || inputValue.startDate) ? Object.values(inputValue).join(" : ") : '';
+ return (
+
+ {this.renderInput(shownVal, operator, toolTip, placeholder, tabIndex, false, true)}
+
+
+ { this.renderHoursRange() }
+
+ }
+ >
+
+
+
+
+ );
+ }
+ rangeTabClassName = (type) => {
+ const openRangeInputs = this.state.openRangeInputs;
+ return "range-tab" + (openRangeInputs === type || !openRangeInputs ? '' : ' selected');
+ }
+ renderCalendarRange = () =>{
+ const { openRangeInputs, inputValue } = this.state;
+ const props = omit(this.props, ['placeholder', 'calendar', 'time', 'onChange', 'value', 'toolTip', 'onMouseOver']);
+ return (
+
+
+
+
+
+ {inputValue.startDate || getMessageById(this.context.messages, "featuregrid.attributeFilter.placeholders.range")}
+
+
+
+
+
+ {inputValue.endDate || getMessageById(this.context.messages, "featuregrid.attributeFilter.placeholders.range")}
+
+
+
+
+
+ {this.attachCalStartRef = elem;}}
+ onMouseDown={this.handleMouseDown}
+ onChange={(value) => this.handleCalendarChange(value, 'start')}
+ {...props}
+ value={!isNil(this.state.date?.startDate || this.props.value?.startDate) ? new Date(this.state.date?.startDate || this.props.value?.startDate) : undefined}
+ />
+
+
+ { this.attachCalEndRef = elem;}}
+ onMouseDown={this.handleMouseDown}
+ onChange={(value) => this.handleCalendarChange(value, 'end')}
+ {...props}
+ value={!isNil(this.state.date?.endDate || this.props.value?.endDate) ? new Date(this.state.date?.endDate || this.props.value?.endDate) : undefined}
+ />
+
+
+
+ );
+ }
+ renderCalendar = () =>{
+ const { inputValue, operator, focused } = this.state;
+ const { toolTip, placeholder, tabIndex, popupPosition, type } = this.props;
+ let shownVal = (inputValue.endDate || inputValue.startDate) ? Object.values(inputValue).join(" : ") : '';
+
+ return (
+ {this.calendarRef = elem;}} onBlur={()=> this.handleWidgetBlur(type)} onFocus={this.handleWidgetFocus} className={`rw-datetimepicker range-time-input rw-widget rw-has-neither ${focused ? 'rw-state-focus' : ''}`}>
+ {this.renderInput(shownVal, operator, toolTip, placeholder, tabIndex, true, false)}
+
+
+ { this.renderCalendarRange() }
+
+ }
+ >
+
+
+
+
+ );
+ }
+ renderDateTimeRange = () =>{
+ const { inputValue, operator, openRangeInputs, openTime } = this.state;
+ const { tabIndex } = this.props;
+ const props = omit(this.props, ['placeholder', 'calendar', 'time', 'onChange', 'value', 'toolTip', 'onMouseOver']);
+ let timePlaceholderMsgId = getMessageById(this.context.messages, "featuregrid.attributeFilter.placeholders.time");
+
+ return (
+
+
+
+
+
+ {inputValue.startDate || getMessageById(this.context.messages, "featuregrid.attributeFilter.placeholders.range")}
+
+
+
+
+
+ {inputValue.endDate || getMessageById(this.context.messages, "featuregrid.attributeFilter.placeholders.range")}
+
+
+
+
+
+
{this.attachCalStartRef = elem;}}
+ onMouseDown={this.handleMouseDown}
+ onChange={(evt) => this.handleCalendarChange(evt, 'start')}
+ {...props}
+ value={!isNil(this.state.date?.startDate || this.props.value?.startDate) ? new Date(this.state.date?.startDate || this.props.value?.startDate) : undefined}
+ />
+
+
+ {this.renderInput(inputValue.startDate, operator, '', timePlaceholderMsgId, tabIndex, false, true, {}, 'form-control')}
+
+
+
+
+
+ {this.attachTimeStartRef = elem;}} onMouseDown={this.handleMouseDown} {...props} onClose={this.close} onSelect={(evt) => this.handleTimeSelect(evt, 'start')} />
+
+
+
+
+
{ this.attachCalEndRef = elem;}}
+ onMouseDown={this.handleMouseDown}
+ onChange={(evt) => this.handleCalendarChange(evt, 'end')}
+ {...props}
+ value={!isNil(this.state.date?.endDate || this.props.value?.endDate) ? new Date(this.state.date?.endDate || this.props.value?.endDate) : undefined}
+ />
+
+
+ {this.renderInput(inputValue.endDate, operator, '', timePlaceholderMsgId, tabIndex, false, true, {}, 'form-control')}
+
+
+
+
+
+ { this.attachTimeEndRef = elem;}} onMouseDown={this.handleMouseDown} {...props} onClose={this.close} onSelect={(evt) => this.handleTimeSelect(evt, 'end')} />
+
+
+
+
+
+ );
+ }
+ renderCalendarTimeDate = () =>{
+ const { inputValue, operator, focused } = this.state;
+ const { toolTip, placeholder, tabIndex, popupPosition, type } = this.props;
+ let shownVal = (inputValue.endDate || inputValue.startDate) ? Object.values(inputValue).join(" : ") : '';
+
+ return (
+ {this.dateTimeRef = elem;}} onBlur={() => this.handleWidgetBlur(type)} onFocus={this.handleWidgetFocus} className={`rw-datetimepicker range-time-input rw-widget ${focused ? 'rw-state-focus' : ''}`}>
+ {this.renderInput(shownVal, operator, toolTip, placeholder, tabIndex, true, true)}
+
+
+ {this.renderDateTimeRange()}
+
+ }
+ >
+
+
+
+
+ );
+ }
+
+ render() {
+ const { type } = this.props;
+ if (type === 'time') return this.renderHours();
+ else if (type === 'date') return this.renderCalendar();
+ return this.renderCalendarTimeDate();
+ }
+
+ inputFlush = false;
+ // Ignore blur to manual control de-rendering of cal/time popup
+ ignoreBlur = false;
+
+ handleWidgetFocus = () => {
+ this.setState({ focused: true });
+ this.ignoreBlur = false;
+ }
+
+ handleWidgetBlur = (type) => {
+ if (this.ignoreBlur) {
+ return;
+ }
+ if (type === 'date') {
+ this.calendarRef.click();
+ } else if (type === 'dat-time') {
+ this.dateTimeRef.click();
+ }
+ this.setState({ openRangeContainer: '', focused: false });
+ }
+ rangeContainerMouseLeaveHandler = () => {
+ this.setState({ openRangeContainer: false });
+ }
+ handleMouseDown = () => {
+ this.ignoreBlur = true;
+ }
+
+ toggleStart = () => {
+ if (this.state.openRangeInputs !== 'start') {
+ this.setState({ openRangeInputs: 'start', openTime: false, openDateTCalendar: false });
+ }
+ }
+ toggleEnd = () => {
+ if (this.state.openRangeInputs !== 'end') {
+ this.setState({ openRangeInputs: 'end', openTime: false, openDateTCalendar: false });
+ }
+ }
+ toggleTime = () => {
+ this.setState(prev => ({ openTime: !prev.openTime }));
+ }
+ toggleHandler = () => {
+ this.setState(prevState => ({ openRangeContainer: !prevState.openRangeContainer, openTime: false, openDateTCalendar: false }));
+ }
+
+ handleInputBlur = () => {
+ if (this.inputFlush) {
+ // date has changed
+ const parsed = this.parse(this.state.inputValue);
+ const dateStr = this.format(parsed);
+ this.setState({
+ inputValue: dateStr,
+ date: parsed
+ });
+ this.inputFlush = false;
+ this.props.onChange(parsed, `${this.state.operator}${dateStr}`);
+ }
+ }
+ handleKeyDown = e => {
+ if (e.key === 'Escape') {
+ // escape key should close the calendar or time popup
+ this.close();
+ return;
+ }
+ }
+ setDateFromValueProp = (value, operator) => {
+ if ((isDate(value?.startDate) && value?.startDate) || (isDate(value?.endtDate) && value?.endtDate)) {
+ let startValue = this.format(value?.startDate);
+ let endValue = this.format(value?.endDate);
+ let inputValue = {
+ startDate: startValue,
+ endDate: endValue
+ };
+ this.setState(prevState => ({ date: { ...value }, inputValue, operator: operator || prevState.operator }));
+ }
+ }
+
+ parse = (value) => {
+ const { culture } = this.props;
+ const format = this.getFormat();
+ if (value) {
+ const m = getMoment(culture, value, format);
+ if (m.isValid()) return m.toDate();
+ }
+ return null;
+ }
+
+ format = (value) => {
+ if (!value) return '';
+ const { culture } = this.props;
+ const format = this.getFormat();
+ const m = getMoment(culture, value);
+ if (m.isValid()) return m.format(format);
+ return '';
+ }
+
+ close = () => {
+ this.setState({ openRangeContainer: '' });
+ }
+
+ openHandler = () => {
+ const { calendar, time } = this.props;
+ return !calendar && time ? this.setState({ open: 'time' }) : calendar && !time ? this.setState({ open: 'date' }) : '';
+ }
+
+
+ handleValueChange = (event) => {
+ const { value } = event.target;
+ const match = /\s*(!==|!=|<>|<=|>=|===|==|=|<|>)?(.*)/.exec(value);
+ this.setState({ mainInputValue: match[2], operator: match[1] || '' });
+ this.inputFlush = true;
+ }
+
+ handleCalendarChange = (value, order) => {
+ const date = setTime(value, new Date(value));
+ const inputValue = this.format(date);
+ let inputValueClone = { ...this.state.inputValue};
+ if (order === 'start') {
+ inputValueClone.startDate = inputValue;
+ } else {
+ inputValueClone.endDate = inputValue;
+ }
+ let upadtedDate = {
+ startDate: order === 'start' ? date : this.state.date.startDate,
+ endDate: order === 'end' ? date : this.state.date.endDate
+ };
+ let startTimeStamp = upadtedDate.startDate ? (upadtedDate.startDate).getTime() : 0;
+ let endTimeStamp = upadtedDate.endDate ? (upadtedDate.endDate).getTime() : 0;
+ if (upadtedDate.startDate && upadtedDate.endDate && endTimeStamp < startTimeStamp) {
+ this.setState({
+ date: upadtedDate,
+ inputValue: inputValueClone,
+ isInputNotValid: true
+ });
+ return;
+ }
+ this.setState({
+ date: upadtedDate,
+ inputValue: inputValueClone,
+ isInputNotValid: false
+ });
+ if (order === 'start') {
+ this.props.onChange(date, inputValueClone.startDate, 'start');
+ } else if (order === 'end') {
+ this.props.onChange(date, inputValueClone.endDate, 'end');
+ }
+ }
+
+ handleTimeSelect = (time, order) => {
+ const selectedDate = (order === 'start' ? this.state.date.startDate : this.state.date.endDate ) || new Date();
+ const date = setTime(selectedDate, time.date);
+ const inputValue = this.format(date);
+ let inputValueClone = { ...this.state.inputValue};
+ if (order === 'start') inputValueClone.startDate = inputValue;
+ else inputValueClone.endDate = inputValue;
+ let upadtedDate = {
+ startDate: order === 'start' ? date : this.state.date.startDate,
+ endDate: order === 'end' ? date : this.state.date.endDate
+ };
+ let startTimeStamp = upadtedDate.startDate ? (upadtedDate.startDate).getTime() : 0;
+ let endTimeStamp = upadtedDate.endDate ? (upadtedDate.endDate).getTime() : 0;
+ if (upadtedDate.startDate && upadtedDate.endDate && endTimeStamp < startTimeStamp) {
+ this.setState({ inputValue: inputValueClone, date: upadtedDate, openTime: '', isInputNotValid: true});
+ } else {
+ this.setState({ inputValue: inputValueClone, openTime: '', date: upadtedDate, isInputNotValid: false});
+ if (order === 'start') this.props.onChange(date, inputValueClone.startDate, 'start');
+ else if (order === 'end') this.props.onChange(date, inputValueClone.endDate, 'end');
+ }
+ }
+
+ attachTimeStartRef = ref => (this.timeStartRef = ref)
+ attachTimeEndRef = ref => (this.timeEndRef = ref)
+
+ attachCalStartRef = ref => (this.calStartRef = ref)
+ attachCalEndRef = ref => (this.calEndRef = ref)
+
+}
+export default DateTimePickerWithRange;
+
diff --git a/web/client/components/misc/datetimepicker/Time.js b/web/client/components/misc/datetimepicker/Time.js
new file mode 100644
index 0000000000..f81290ee90
--- /dev/null
+++ b/web/client/components/misc/datetimepicker/Time.js
@@ -0,0 +1,147 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import moment from 'moment';
+import PagedCombobox from '../combobox/PagedCombobox';
+// import localizedProps from '../enhancers/localizedProps';
+
+// const Select = localizedProps(['placeholder', 'clearValueText', 'noResultsText'])(require('react-select').default);
+const getDates = (step) => {
+ let min = moment().startOf('day');
+ const max = moment().startOf('day').add(1, 'd');
+ let times = [];
+ let startDay = min.date();
+
+ while (min.date() === startDay && min.isBefore(max)) {
+ times.push({
+ value: min.toDate(),
+ label: min.format('LT')
+ });
+ min = min.add(step || 30, 'm');
+ }
+ return times;
+};
+
+class TimePicker extends Component {
+
+ static propTypes = {
+ onSelect: PropTypes.func,
+ onMouseDown: PropTypes.func,
+ disabled: PropTypes.bool
+ }
+ static defaultProps = {
+ onSelect: () => { },
+ onMouseDown: () => { },
+ disabled: false
+ }
+
+ state = { focusedItemIndex: 0, times: [], selectedMember: "", openSelectMember: false };
+
+ componentDidMount() {
+ this.setState({ times: getDates() });
+ }
+
+ render() {
+ // const { times } = this.state;
+ // const { onMouseDown, onSelect, disabled } = this.props;
+ const placeholder = this.context.intl ? this.context.intl.formatMessage({ id: 'usergroups.selectMemberPlaceholder' }) : 'Select time from list';
+
+ return (
+ //
+
+ //
+ //
+ {
+ if (typeof selected === 'string') {
+ this.selectMemberPage = 0;
+ this.setState({selectedMember: selected});
+ this.searchUsers(selected, true);
+ return;
+ }
+ if (selected.value) {
+ this.setState({selectedMember: selected.label});
+ // this.selectMemberPage = 0;
+ }
+ }}
+ placeholder={placeholder}
+ // pagination={pagination}
+ selectedValue={this.state.selectedMember}
+ onSelect={() => {}}
+ stopPropagation
+ // dropUp
+
+ />
+ //
+ // {times.map((time, index) => - {} : onMouseDown} onClick={disabled ? () => {} : () => onSelect(time)} ref={instance => {this.itemsRef[index] = instance;}} role="option" tabIndex="0" aria-selected="false" className={`rw-list-option ${focusedItemIndex === index && !disabled ? 'rw-state-focus' : ''} ${disabled ? 'rw-state-disabled' : ''}`} id="rw_1_time_listbox__option__0">{time.label}
)}
+ //
+ );
+ }
+ handleToggleSelectMember = () => {
+ this.setState(prevState => ({
+ openSelectMember: !prevState.openSelectMember
+ }));
+ }
+ handleKeyDown = event => {
+ let { key } = event;
+ let { focusedItemIndex, times } = this.state;
+ if (key === 'Enter') {
+ const focusedItem = times[focusedItemIndex];
+ this.props.onSelect(focusedItem);
+ event.preventDefault();
+ } else if (key === 'ArrowDown') {
+ event.preventDefault();
+ const index = focusedItemIndex + 1;
+ this.scrollDown(index);
+ if (index < times.length) {
+ this.setState({ focusedItemIndex: index });
+ }
+ } else if (key === 'ArrowUp') {
+ event.preventDefault();
+ const index = focusedItemIndex - 1;
+ this.scrollUp(index);
+ if (index > -1) {
+ this.setState({ focusedItemIndex: index });
+ }
+ }
+ }
+
+ scrollDown = index => {
+ const item = this.itemsRef[index];
+ if (item && item.offsetTop > this.listRef.offsetHeight) {
+ this.listRef.scrollTop = item.offsetTop - this.listRef.offsetTop;
+ }
+ }
+
+ scrollUp = index => {
+ const item = this.itemsRef[index];
+ if (item) {
+ const topScroll = this.listRef.scrollTop;
+ const itemTop = item.offsetTop;
+ if (topScroll && (itemTop < topScroll)) {
+ this.listRef.scrollTop = item.offsetTop - this.listRef.offsetTop;
+ }
+ }
+ }
+
+ attachListRef = ref => (this.listRef = ref)
+
+ itemsRef = {};
+}
+
+export default TimePicker;
diff --git a/web/client/components/misc/datetimepicker/__tests__/DateTimePicker-test.js b/web/client/components/misc/datetimepicker/__tests__/DateTimePicker-test.js
index 5100603485..9ffb5abc25 100644
--- a/web/client/components/misc/datetimepicker/__tests__/DateTimePicker-test.js
+++ b/web/client/components/misc/datetimepicker/__tests__/DateTimePicker-test.js
@@ -44,7 +44,7 @@ describe('DateTimePicker component', () => {
it('DateTimePicker with value and operator prop', function() {
const today = new Date();
- ReactDOM.render(, document.getElementById("container"));
+ ReactDOM.render(, document.getElementById("container"));
const container = document.getElementById('container');
const input = container.querySelector('input');
const match = /\s*(!==|!=|<>|<=|>=|===|==|=|<|>)?(.*)/.exec(input.value);
@@ -53,37 +53,38 @@ describe('DateTimePicker component', () => {
});
it('DateTimePicker show calendar on calendar button click', function() {
- ReactDOM.render(, document.getElementById("container"));
+ ReactDOM.render(, document.getElementById("container"));
const container = document.getElementById('container');
const button = container.querySelector('.rw-btn-calendar');
TestUtils.Simulate.click(button);
- const calendar = container.querySelector('.rw-calendar-popup');
- expect(calendar.style.display).toBe('block');
+ const calendar = document.querySelector('.shadow-soft.picker-container .rw-calendar.rw-widget');
+ expect(calendar).toExist();
+ expect(calendar.style.display).toNotEqual('none');
});
it('calendar opens to today when value is null or undefined', function() {
- ReactDOM.render(, document.getElementById("container"));
+ ReactDOM.render(, document.getElementById("container"));
const container = document.getElementById('container');
const button = container.querySelector('.rw-btn-calendar');
TestUtils.Simulate.click(button);
- const monthLabel = document.querySelector('#container > div > div.rw-calendar-popup.rw-popup-container .rw-btn-view');
+ const monthLabel = document.querySelector('.ms-popover-overlay > div > div.shadow-soft .rw-calendar.rw-widget .rw-btn-view');
expect(monthLabel.innerHTML).toBe(moment().format('MMMM YYYY'));
});
it('calendar opens at the values date', function() {
const value = "01-01-2010";
- ReactDOM.render(, document.getElementById("container"));
+ ReactDOM.render(, document.getElementById("container"));
const container = document.getElementById('container');
const button = container.querySelector('.rw-btn-calendar');
TestUtils.Simulate.click(button);
- const monthLabel = document.querySelector('#container > div > div.rw-calendar-popup.rw-popup-container .rw-btn-view');
+ const monthLabel = document.querySelector('.ms-popover-overlay > div > div.shadow-soft .rw-calendar.rw-widget .rw-btn-view');
expect(monthLabel.innerHTML).toBe(moment(value).format('MMMM YYYY'));
});
it('DateTimePicker show hours on time button click', function() {
- ReactDOM.render(, document.getElementById("container"));
+ ReactDOM.render(, document.getElementById("container"));
const container = document.getElementById('container');
const button = container.querySelector('.rw-btn-time');
TestUtils.Simulate.click(button);
- const hourPopup = container.querySelector('.rw-popup-container');
- expect(hourPopup.style.display).toBe('block');
+ const hourPopup = document.querySelector('.ms-popover-overlay > div > div.shadow-soft .dateTime-picker-hours');
+ expect(hourPopup.style.display).toNotEqual('none');
});
it('DateTimePicker can parse !== operator', function(done) {
@@ -94,7 +95,7 @@ describe('DateTimePicker component', () => {
expect(match[1]).toEqual('!==');
done();
};
- ReactDOM.render(, document.getElementById("container"));
+ ReactDOM.render(, document.getElementById("container"));
const container = document.getElementById('container');
const input = container.querySelector('input');
TestUtils.Simulate.focus(input);
@@ -152,8 +153,8 @@ describe('DateTimePicker component', () => {
const container = document.getElementById('container');
const calendar = container.querySelector('.rw-btn-calendar');
TestUtils.Simulate.click(calendar);
- const dropUp = container.querySelector('.rw-dropup');
- expect(dropUp).toExist();
+ const dropUp = document.querySelector('.shadow-soft.picker-container div').getAttribute('popupposition');
+ expect(dropUp).toEqual('top');
done();
});
@@ -169,7 +170,7 @@ describe('DateTimePicker component', () => {
const container = document.getElementById('container');
const calendar = container.querySelector('.rw-btn-calendar');
TestUtils.Simulate.click(calendar);
- const day = container.querySelector('.rw-calendar-grid tbody tr td:first-child .rw-btn');
+ const day = document.querySelector('.shadow-soft.picker-container tbody tr td:first-child .rw-btn');
TestUtils.Simulate.click(day);
});
});
diff --git a/web/client/components/misc/datetimepicker/__tests__/RangedDateTimePicker-test.js b/web/client/components/misc/datetimepicker/__tests__/RangedDateTimePicker-test.js
new file mode 100644
index 0000000000..c40dc06f17
--- /dev/null
+++ b/web/client/components/misc/datetimepicker/__tests__/RangedDateTimePicker-test.js
@@ -0,0 +1,53 @@
+
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+import expect from 'expect';
+import TestUtils from 'react-dom/test-utils';
+import DateTimePickerWithRange from '../RangedDateTimePicker';
+
+describe('DateTimePickerWithRange component', () => {
+ beforeEach((done) => {
+ document.body.innerHTML = '';
+ setTimeout(done);
+ });
+ afterEach((done) => {
+ ReactDOM.unmountComponentAtNode(document.getElementById("container"));
+ document.body.innerHTML = '';
+ setTimeout(done);
+ });
+ it('DateTimePickerWithRange with type time rendering with defaults', () => {
+ ReactDOM.render(, document.getElementById("container"));
+ const container = document.getElementById('container');
+ const el = container.querySelector('.rw-datetimepicker.range-time-input.rw-widget');
+ const clockIcon = container.querySelector('.rw-i.rw-i-clock-o');
+ expect(el).toExist();
+ expect(clockIcon).toExist();
+ });
+
+ it('DateTimePickerWithRange with type date rendering with defaults', () => {
+ ReactDOM.render(, document.getElementById("container"));
+ const container = document.getElementById('container');
+ const el = container.querySelector('.rw-datetimepicker.range-time-input.rw-widget');
+ const clockIcon = container.querySelector('.rw-i.rw-i-calendar');
+ expect(el).toExist();
+ expect(clockIcon).toExist();
+ });
+ it('DateTimePickerWithRange with type date-time rendering with defaults', () => {
+ ReactDOM.render(, document.getElementById("container"));
+ const container = document.getElementById('container');
+ const el = container.querySelector('.rw-datetimepicker.range-time-input.rw-widget');
+ const dateTimeIcon = container.querySelector('.glyphicon-date-time');
+ expect(el).toExist();
+ expect(dateTimeIcon).toExist();
+ });
+
+ it('DateTimePickerWithRange show calendar on calendar button click', function() {
+ ReactDOM.render(, document.getElementById("container"));
+ const container = document.getElementById('container');
+ const button = container.querySelector('.rw-btn-calendar');
+ TestUtils.Simulate.click(button);
+ const calendar = document.querySelector('.shadow-soft.picker-container.range');
+ expect(calendar).toBeTruthy();
+ });
+});
diff --git a/web/client/components/misc/enhancers/utcDateWrapper.js b/web/client/components/misc/enhancers/utcDateWrapper.js
index b4a14e7ece..2e6c688c1d 100644
--- a/web/client/components/misc/enhancers/utcDateWrapper.js
+++ b/web/client/components/misc/enhancers/utcDateWrapper.js
@@ -57,7 +57,7 @@ export default ({dateTypeProp = "type", dateProp = 'date', setDateProp = 'onSetD
}
}
let resultDate = dateToParse;
- if (dateToParse) {
+ if (isDate(dateToParse)) {
switch (type) {
case "time": {
timePart = getUTCTimePart(dateToParse);
@@ -87,7 +87,7 @@ export default ({dateTypeProp = "type", dateProp = 'date', setDateProp = 'onSetD
};
}),
withHandlers({
- [setDateProp]: ({[setDateProp]: changeVal, datePropFormat = "short", [dateTypeProp]: type, useUTCOffset = true} = {}) => (date, stringDate) => {
+ [setDateProp]: ({[setDateProp]: changeVal, datePropFormat = "short", [dateTypeProp]: type, useUTCOffset = true} = {}) => (date, stringDate, order) => {
if (!date) {
changeVal(null);
} else {
@@ -105,25 +105,25 @@ export default ({dateTypeProp = "type", dateProp = 'date', setDateProp = 'onSetD
switch (datePropFormat) {
case "full-iso": {
- changeVal(resultDate.toISOString(), stringDate);
+ changeVal(resultDate.toISOString(), stringDate, order);
break;
}
case "short": {
switch (type) {
case "time": {
- changeVal(`${getUTCTimePart(resultDate)}Z`, stringDate);
+ changeVal(`${getUTCTimePart(resultDate)}Z`, stringDate, order);
break;
}
case "date": {
- changeVal(`${getUTCDatePart(resultDate)}Z`, stringDate);
+ changeVal(`${getUTCDatePart(resultDate)}Z`, stringDate, order);
break;
}
case "date-time": {
- changeVal(resultDate.toISOString(), stringDate);
+ changeVal(resultDate.toISOString(), stringDate, order);
break;
}
default: {
- changeVal(resultDate, stringDate);
+ changeVal(resultDate, stringDate, order);
break;
}
}
diff --git a/web/client/components/styleeditor/Popover.jsx b/web/client/components/styleeditor/Popover.jsx
index 213b8341e8..42d5263963 100644
--- a/web/client/components/styleeditor/Popover.jsx
+++ b/web/client/components/styleeditor/Popover.jsx
@@ -34,7 +34,8 @@ export function ControlledPopover({
open,
onOpen = () => {},
onClick = () => {},
- disabled
+ disabled,
+ triggerScrollableElement // [Optional] it is a DOM element I want to update the popover position if user scrolls within it
}) {
const margin = 10;
@@ -215,8 +216,14 @@ export function ControlledPopover({
setStyles(computeStyles());
const updateStyleOnResize = () => setStyles(computeStyles());
window.addEventListener('resize', updateStyleOnResize);
+ if (triggerScrollableElement && open) {
+ triggerScrollableElement.addEventListener('scroll', updateStyleOnResize);
+ }
return () => {
window.removeEventListener('resize', updateStyleOnResize);
+ if (triggerScrollableElement) {
+ triggerScrollableElement.removeEventListener('scroll', updateStyleOnResize);
+ }
};
}, [ computeStyles ]);
diff --git a/web/client/plugins/featuregrid/FeatureEditor.jsx b/web/client/plugins/featuregrid/FeatureEditor.jsx
index 061eac7855..e693ee5a0c 100644
--- a/web/client/plugins/featuregrid/FeatureEditor.jsx
+++ b/web/client/plugins/featuregrid/FeatureEditor.jsx
@@ -10,7 +10,7 @@ import {connect} from 'react-redux';
import {createSelector, createStructuredSelector} from 'reselect';
import {bindActionCreators} from 'redux';
import { get, pick, isEqual } from 'lodash';
-import {compose, lifecycle} from 'recompose';
+import {compose, lifecycle, defaultProps } from 'recompose';
import ReactDock from 'react-dock';
import ContainerDimensions from 'react-container-dimensions';
@@ -187,7 +187,9 @@ const FeatureDock = (props = {
};
const items = props?.items ?? [];
const toolbarItems = items.filter(({target}) => target === 'toolbar');
- const filterRenderers = useMemo(() => getFilterRenderers(props.describe, props.fields), [props.describe, props.fields]);
+ const filterRenderers = useMemo(() => {
+ return getFilterRenderers(props.describe, props.fields, props.isWithinAttrTbl);
+ }, [props.describe, props.fields]);
return (
{ props.onSizeChange(size, dockProps); }}>
@@ -209,6 +211,7 @@ const FeatureDock = (props = {
footer={getFooter(props)}>
{getDialogs(props.tools)}
({
isSyncWmsActive: isSyncWmsActive(state)
}),
diff --git a/web/client/plugins/featuregrid/panels/index.jsx b/web/client/plugins/featuregrid/panels/index.jsx
index 3d5dc6f2f5..6a7ffb04f5 100644
--- a/web/client/plugins/featuregrid/panels/index.jsx
+++ b/web/client/plugins/featuregrid/panels/index.jsx
@@ -193,7 +193,7 @@ export const getEmptyRowsView = () => {
* @param {object[]} fields array of fields (with `filterRenderer` property)
* @returns {object} object with field name as key and filterRenderer as value
*/
-export const getFilterRenderers = (describe, fields = []) => {
+export const getFilterRenderers = (describe, fields = [], isWithinAttrTbl) => {
if (describe) {
return (getFeatureTypeProperties(describe) || []).reduce( (out, cur) => {
const field = fields.find(f => f.name === cur.name);
@@ -206,6 +206,7 @@ export const getFilterRenderers = (describe, fields = []) => {
(filter, mode) => {
const props = {
value: filter && (filter.rawValue || filter.value),
+ operator: filter && (filter.operator),
...(isGeometryType(cur) ? {
filterEnabled: filter?.enabled,
filterDeactivated: filter?.deactivated
@@ -217,7 +218,7 @@ export const getFilterRenderers = (describe, fields = []) => {
} : {};
return mode === "EDIT" ? {...props, ...editProps} : props;
}
- ))(getFilterRenderer({type: isGeometryType(cur) ? 'geometry' : cur.localType, name: field?.filterRenderer?.name, options: field?.filterRenderer?.options}))
+ ))(getFilterRenderer({type: isGeometryType(cur) ? 'geometry' : cur.localType, name: field?.filterRenderer?.name, options: field?.filterRenderer?.options, isWithinAttrTbl}))
};
}, {});
}
diff --git a/web/client/themes/default/icons.less b/web/client/themes/default/icons.less
index 8cc1d4e9a6..83b30106ca 100644
--- a/web/client/themes/default/icons.less
+++ b/web/client/themes/default/icons.less
@@ -155,249 +155,250 @@
.glyphicon-crs:before { content: "\f171"; }
.glyphicon-dashboard-save:before { content: "\f172"; }
.glyphicon-dashboard:before { content: "\f173"; }
-.glyphicon-download-alt:before { content: "\f174"; }
-.glyphicon-download-comment:before { content: "\f175"; }
-.glyphicon-download:before { content: "\f176"; }
-.glyphicon-dropper:before { content: "\f177"; }
-.glyphicon-duplicate:before { content: "\f178"; }
-.glyphicon-edit:before { content: "\f179"; }
-.glyphicon-envelope:before { content: "\f17a"; }
-.glyphicon-exclamation-mark:before { content: "\f17b"; }
-.glyphicon-exclamation-sign:before { content: "\f17c"; }
-.glyphicon-expand:before { content: "\f17d"; }
-.glyphicon-export:before { content: "\f17e"; }
-.glyphicon-ext-empty:before { content: "\f17f"; }
-.glyphicon-ext-html:before { content: "\f180"; }
-.glyphicon-ext-json:before { content: "\f181"; }
-.glyphicon-ext-pdf:before { content: "\f182"; }
-.glyphicon-ext-txt:before { content: "\f183"; }
-.glyphicon-ext-wmc:before { content: "\f184"; }
-.glyphicon-eye-close:before { content: "\f185"; }
-.glyphicon-eye-open:before { content: "\f186"; }
-.glyphicon-fast-backward:before { content: "\f187"; }
-.glyphicon-fast-forward:before { content: "\f188"; }
-.glyphicon-features-grid-download:before { content: "\f189"; }
-.glyphicon-features-grid-set:before { content: "\f18a"; }
-.glyphicon-features-grid:before { content: "\f18b"; }
-.glyphicon-file:before { content: "\f18c"; }
-.glyphicon-filter-layer:before { content: "\f18d"; }
-.glyphicon-filter:before { content: "\f18e"; }
-.glyphicon-fit-contain:before { content: "\f18f"; }
-.glyphicon-fit-cover:before { content: "\f190"; }
-.glyphicon-flag:before { content: "\f191"; }
-.glyphicon-flash:before { content: "\f192"; }
-.glyphicon-floppy-disk:before { content: "\f193"; }
-.glyphicon-floppy-open:before { content: "\f194"; }
-.glyphicon-floppy-remove:before { content: "\f195"; }
-.glyphicon-floppy-save:before { content: "\f196"; }
-.glyphicon-floppy-saved:before { content: "\f197"; }
-.glyphicon-folder-close:before { content: "\f198"; }
-.glyphicon-folder-open:before { content: "\f199"; }
-.glyphicon-font-add:before { content: "\f19a"; }
-.glyphicon-font:before { content: "\f19b"; }
-.glyphicon-forward:before { content: "\f19c"; }
-.glyphicon-geometry-collection:before { content: "\f19d"; }
-.glyphicon-geoserver:before { content: "\f19e"; }
-.glyphicon-geostory:before { content: "\f19f"; }
-.glyphicon-globe-settings:before { content: "\f1a0"; }
-.glyphicon-globe:before { content: "\f1a1"; }
-.glyphicon-grab-handle:before { content: "\f1a2"; }
-.glyphicon-gray-scale:before { content: "\f1a3"; }
-.glyphicon-grid-custom:before { content: "\f1a4"; }
-.glyphicon-grid-regular:before { content: "\f1a5"; }
-.glyphicon-hand-down:before { content: "\f1a6"; }
-.glyphicon-hand-left:before { content: "\f1a7"; }
-.glyphicon-hand-right:before { content: "\f1a8"; }
-.glyphicon-hand-up:before { content: "\f1a9"; }
-.glyphicon-hdd:before { content: "\f1aa"; }
-.glyphicon-heart:before { content: "\f1ab"; }
-.glyphicon-height-auto:before { content: "\f1ac"; }
-.glyphicon-height-from-terrain:before { content: "\f1ad"; }
-.glyphicon-height-view:before { content: "\f1ae"; }
-.glyphicon-hide-marker:before { content: "\f1af"; }
-.glyphicon-home:before { content: "\f1b0"; }
-.glyphicon-hourglass:before { content: "\f1b1"; }
-.glyphicon-import:before { content: "\f1b2"; }
-.glyphicon-inbox:before { content: "\f1b3"; }
-.glyphicon-info-sign:before { content: "\f1b4"; }
-.glyphicon-italic:before { content: "\f1b5"; }
-.glyphicon-layer-info:before { content: "\f1b6"; }
-.glyphicon-leaf:before { content: "\f1b7"; }
-.glyphicon-level-up:before { content: "\f1b8"; }
-.glyphicon-line-dash:before { content: "\f1b9"; }
-.glyphicon-line-minus:before { content: "\f1ba"; }
-.glyphicon-line-plus:before { content: "\f1bb"; }
-.glyphicon-line-remove:before { content: "\f1bc"; }
-.glyphicon-line-trash:before { content: "\f1bd"; }
-.glyphicon-line:before { content: "\f1be"; }
-.glyphicon-link:before { content: "\f1bf"; }
-.glyphicon-list-alt:before { content: "\f1c0"; }
-.glyphicon-list:before { content: "\f1c1"; }
-.glyphicon-lock:before { content: "\f1c2"; }
-.glyphicon-log-in:before { content: "\f1c3"; }
-.glyphicon-log-out:before { content: "\f1c4"; }
-.glyphicon-loop:before { content: "\f1c5"; }
-.glyphicon-magnet:before { content: "\f1c6"; }
-.glyphicon-map-context:before { content: "\f1c7"; }
-.glyphicon-map-edit:before { content: "\f1c8"; }
-.glyphicon-map-filter:before { content: "\f1c9"; }
-.glyphicon-map-marker:before { content: "\f1ca"; }
-.glyphicon-map-synch:before { content: "\f1cb"; }
-.glyphicon-map-view:before { content: "\f1cc"; }
-.glyphicon-maps-catalog:before { content: "\f1cd"; }
-.glyphicon-menu-hamburger:before { content: "\f1ce"; }
-.glyphicon-minus-sign:before { content: "\f1cf"; }
-.glyphicon-minus:before { content: "\f1d0"; }
-.glyphicon-model-plus:before { content: "\f1d1"; }
-.glyphicon-model:before { content: "\f1d2"; }
-.glyphicon-mouse:before { content: "\f1d3"; }
-.glyphicon-move-row-after:before { content: "\f1d4"; }
-.glyphicon-move-row-before:before { content: "\f1d5"; }
-.glyphicon-move:before { content: "\f1d6"; }
-.glyphicon-muted:before { content: "\f1d7"; }
-.glyphicon-new-window:before { content: "\f1d8"; }
-.glyphicon-next:before { content: "\f1d9"; }
-.glyphicon-off:before { content: "\f1da"; }
-.glyphicon-ok-circle:before { content: "\f1db"; }
-.glyphicon-ok-sign:before { content: "\f1dc"; }
-.glyphicon-ok:before { content: "\f1dd"; }
-.glyphicon-open:before { content: "\f1de"; }
-.glyphicon-option-horizontal:before { content: "\f1df"; }
-.glyphicon-option-vertical:before { content: "\f1e0"; }
-.glyphicon-paperclip:before { content: "\f1e1"; }
-.glyphicon-paste:before { content: "\f1e2"; }
-.glyphicon-pause:before { content: "\f1e3"; }
-.glyphicon-pencil-add:before { content: "\f1e4"; }
-.glyphicon-pencil-edit:before { content: "\f1e5"; }
-.glyphicon-pencil:before { content: "\f1e6"; }
-.glyphicon-phone:before { content: "\f1e7"; }
-.glyphicon-picture:before { content: "\f1e8"; }
-.glyphicon-pie-chart:before { content: "\f1e9"; }
-.glyphicon-plane:before { content: "\f1ea"; }
-.glyphicon-play-circle:before { content: "\f1eb"; }
-.glyphicon-play:before { content: "\f1ec"; }
-.glyphicon-playback:before { content: "\f1ed"; }
-.glyphicon-plug:before { content: "\f1ee"; }
-.glyphicon-plus-sign:before { content: "\f1ef"; }
-.glyphicon-plus-square:before { content: "\f1f0"; }
-.glyphicon-plus:before { content: "\f1f1"; }
-.glyphicon-point-coordinates:before { content: "\f1f2"; }
-.glyphicon-point-dash:before { content: "\f1f3"; }
-.glyphicon-point-minus:before { content: "\f1f4"; }
-.glyphicon-point-plus:before { content: "\f1f5"; }
-.glyphicon-point-remove:before { content: "\f1f6"; }
-.glyphicon-point-trash:before { content: "\f1f7"; }
-.glyphicon-point:before { content: "\f1f8"; }
-.glyphicon-polygon-3d:before { content: "\f1f9"; }
-.glyphicon-polygon-dash:before { content: "\f1fa"; }
-.glyphicon-polygon-minus:before { content: "\f1fb"; }
-.glyphicon-polygon-plus:before { content: "\f1fc"; }
-.glyphicon-polygon-remove:before { content: "\f1fd"; }
-.glyphicon-polygon-trash:before { content: "\f1fe"; }
-.glyphicon-polygon:before { content: "\f1ff"; }
-.glyphicon-polyline-3d:before { content: "\f200"; }
-.glyphicon-polyline-dash:before { content: "\f201"; }
-.glyphicon-polyline-minus:before { content: "\f202"; }
-.glyphicon-polyline-plus:before { content: "\f203"; }
-.glyphicon-polyline-remove:before { content: "\f204"; }
-.glyphicon-polyline-trash:before { content: "\f205"; }
-.glyphicon-polyline:before { content: "\f206"; }
-.glyphicon-preview:before { content: "\f207"; }
-.glyphicon-print:before { content: "\f208"; }
-.glyphicon-pushpin:before { content: "\f209"; }
-.glyphicon-qrcode:before { content: "\f20a"; }
-.glyphicon-question-sign:before { content: "\f20b"; }
-.glyphicon-random:before { content: "\f20c"; }
-.glyphicon-range-end:before { content: "\f20d"; }
-.glyphicon-range-start:before { content: "\f20e"; }
-.glyphicon-record:before { content: "\f20f"; }
-.glyphicon-redo:before { content: "\f210"; }
-.glyphicon-refresh:before { content: "\f211"; }
-.glyphicon-remove-circle:before { content: "\f212"; }
-.glyphicon-remove-sign:before { content: "\f213"; }
-.glyphicon-remove-square:before { content: "\f214"; }
-.glyphicon-remove:before { content: "\f215"; }
-.glyphicon-repeat:before { content: "\f216"; }
-.glyphicon-resize-full:before { content: "\f217"; }
-.glyphicon-resize-horizontal:before { content: "\f218"; }
-.glyphicon-resize-small:before { content: "\f219"; }
-.glyphicon-resize-vertical:before { content: "\f21a"; }
-.glyphicon-retweet:before { content: "\f21b"; }
-.glyphicon-rgb:before { content: "\f21c"; }
-.glyphicon-road:before { content: "\f21d"; }
-.glyphicon-row-add:before { content: "\f21e"; }
-.glyphicon-row-trash:before { content: "\f21f"; }
-.glyphicon-save:before { content: "\f220"; }
-.glyphicon-saved:before { content: "\f221"; }
-.glyphicon-scissors:before { content: "\f222"; }
-.glyphicon-screenshot:before { content: "\f223"; }
-.glyphicon-search-coords:before { content: "\f224"; }
-.glyphicon-search:before { content: "\f225"; }
-.glyphicon-send:before { content: "\f226"; }
-.glyphicon-share-alt:before { content: "\f227"; }
-.glyphicon-share:before { content: "\f228"; }
-.glyphicon-sheet:before { content: "\f229"; }
-.glyphicon-shopping-cart:before { content: "\f22a"; }
-.glyphicon-signal:before { content: "\f22b"; }
-.glyphicon-size-extra-large:before { content: "\f22c"; }
-.glyphicon-size-large:before { content: "\f22d"; }
-.glyphicon-size-medium:before { content: "\f22e"; }
-.glyphicon-size-small:before { content: "\f22f"; }
-.glyphicon-slope:before { content: "\f230"; }
-.glyphicon-sort-by-alphabet-alt:before { content: "\f231"; }
-.glyphicon-sort-by-alphabet:before { content: "\f232"; }
-.glyphicon-sort-by-attributes-alt:before { content: "\f233"; }
-.glyphicon-sort-by-attributes:before { content: "\f234"; }
-.glyphicon-sort:before { content: "\f235"; }
-.glyphicon-star-empty:before { content: "\f236"; }
-.glyphicon-star:before { content: "\f237"; }
-.glyphicon-stats:before { content: "\f238"; }
-.glyphicon-step-backward:before { content: "\f239"; }
-.glyphicon-step-forward:before { content: "\f23a"; }
-.glyphicon-stop:before { content: "\f23b"; }
-.glyphicon-story-banner-section:before { content: "\f23c"; }
-.glyphicon-story-carousel-section:before { content: "\f23d"; }
-.glyphicon-story-immersive-content:before { content: "\f23e"; }
-.glyphicon-story-immersive-section:before { content: "\f23f"; }
-.glyphicon-story-media-section:before { content: "\f240"; }
-.glyphicon-story-paragraph-section:before { content: "\f241"; }
-.glyphicon-story-title-section:before { content: "\f242"; }
-.glyphicon-story-webpage-section:before { content: "\f243"; }
-.glyphicon-tag:before { content: "\f244"; }
-.glyphicon-tags:before { content: "\f245"; }
-.glyphicon-tasks:before { content: "\f246"; }
-.glyphicon-text-background:before { content: "\f247"; }
-.glyphicon-text-colour:before { content: "\f248"; }
-.glyphicon-text-height:before { content: "\f249"; }
-.glyphicon-text-width:before { content: "\f24a"; }
-.glyphicon-th-large:before { content: "\f24b"; }
-.glyphicon-th-list:before { content: "\f24c"; }
-.glyphicon-th:before { content: "\f24d"; }
-.glyphicon-thumbs-down:before { content: "\f24e"; }
-.glyphicon-thumbs-up:before { content: "\f24f"; }
-.glyphicon-time-current:before { content: "\f250"; }
-.glyphicon-time-offset:before { content: "\f251"; }
-.glyphicon-time:before { content: "\f252"; }
-.glyphicon-tint:before { content: "\f253"; }
-.glyphicon-transfer:before { content: "\f254"; }
-.glyphicon-trash-square:before { content: "\f255"; }
-.glyphicon-trash:before { content: "\f256"; }
-.glyphicon-unchecked:before { content: "\f257"; }
-.glyphicon-undo:before { content: "\f258"; }
-.glyphicon-unplug:before { content: "\f259"; }
-.glyphicon-upload:before { content: "\f25a"; }
-.glyphicon-usd:before { content: "\f25b"; }
-.glyphicon-user:before { content: "\f25c"; }
-.glyphicon-vert-dashed:before { content: "\f25d"; }
-.glyphicon-viewport-filter:before { content: "\f25e"; }
-.glyphicon-warning-sign:before { content: "\f25f"; }
-.glyphicon-webpage:before { content: "\f260"; }
-.glyphicon-wrench:before { content: "\f261"; }
-.glyphicon-zoom-in:before { content: "\f262"; }
-.glyphicon-zoom-out:before { content: "\f263"; }
-.glyphicon-zoom-to:before { content: "\f264"; }
+.glyphicon-date-time:before { content: "\f174"; }
+.glyphicon-download-alt:before { content: "\f175"; }
+.glyphicon-download-comment:before { content: "\f176"; }
+.glyphicon-download:before { content: "\f177"; }
+.glyphicon-dropper:before { content: "\f178"; }
+.glyphicon-duplicate:before { content: "\f179"; }
+.glyphicon-edit:before { content: "\f17a"; }
+.glyphicon-envelope:before { content: "\f17b"; }
+.glyphicon-exclamation-mark:before { content: "\f17c"; }
+.glyphicon-exclamation-sign:before { content: "\f17d"; }
+.glyphicon-expand:before { content: "\f17e"; }
+.glyphicon-export:before { content: "\f17f"; }
+.glyphicon-ext-empty:before { content: "\f180"; }
+.glyphicon-ext-html:before { content: "\f181"; }
+.glyphicon-ext-json:before { content: "\f182"; }
+.glyphicon-ext-pdf:before { content: "\f183"; }
+.glyphicon-ext-txt:before { content: "\f184"; }
+.glyphicon-ext-wmc:before { content: "\f185"; }
+.glyphicon-eye-close:before { content: "\f186"; }
+.glyphicon-eye-open:before { content: "\f187"; }
+.glyphicon-fast-backward:before { content: "\f188"; }
+.glyphicon-fast-forward:before { content: "\f189"; }
+.glyphicon-features-grid-download:before { content: "\f18a"; }
+.glyphicon-features-grid-set:before { content: "\f18b"; }
+.glyphicon-features-grid:before { content: "\f18c"; }
+.glyphicon-file:before { content: "\f18d"; }
+.glyphicon-filter-layer:before { content: "\f18e"; }
+.glyphicon-filter:before { content: "\f18f"; }
+.glyphicon-fit-contain:before { content: "\f190"; }
+.glyphicon-fit-cover:before { content: "\f191"; }
+.glyphicon-flag:before { content: "\f192"; }
+.glyphicon-flash:before { content: "\f193"; }
+.glyphicon-floppy-disk:before { content: "\f194"; }
+.glyphicon-floppy-open:before { content: "\f195"; }
+.glyphicon-floppy-remove:before { content: "\f196"; }
+.glyphicon-floppy-save:before { content: "\f197"; }
+.glyphicon-floppy-saved:before { content: "\f198"; }
+.glyphicon-folder-close:before { content: "\f199"; }
+.glyphicon-folder-open:before { content: "\f19a"; }
+.glyphicon-font-add:before { content: "\f19b"; }
+.glyphicon-font:before { content: "\f19c"; }
+.glyphicon-forward:before { content: "\f19d"; }
+.glyphicon-geometry-collection:before { content: "\f19e"; }
+.glyphicon-geoserver:before { content: "\f19f"; }
+.glyphicon-geostory:before { content: "\f1a0"; }
+.glyphicon-globe-settings:before { content: "\f1a1"; }
+.glyphicon-globe:before { content: "\f1a2"; }
+.glyphicon-grab-handle:before { content: "\f1a3"; }
+.glyphicon-gray-scale:before { content: "\f1a4"; }
+.glyphicon-grid-custom:before { content: "\f1a5"; }
+.glyphicon-grid-regular:before { content: "\f1a6"; }
+.glyphicon-hand-down:before { content: "\f1a7"; }
+.glyphicon-hand-left:before { content: "\f1a8"; }
+.glyphicon-hand-right:before { content: "\f1a9"; }
+.glyphicon-hand-up:before { content: "\f1aa"; }
+.glyphicon-hdd:before { content: "\f1ab"; }
+.glyphicon-heart:before { content: "\f1ac"; }
+.glyphicon-height-auto:before { content: "\f1ad"; }
+.glyphicon-height-from-terrain:before { content: "\f1ae"; }
+.glyphicon-height-view:before { content: "\f1af"; }
+.glyphicon-hide-marker:before { content: "\f1b0"; }
+.glyphicon-home:before { content: "\f1b1"; }
+.glyphicon-hourglass:before { content: "\f1b2"; }
+.glyphicon-import:before { content: "\f1b3"; }
+.glyphicon-inbox:before { content: "\f1b4"; }
+.glyphicon-info-sign:before { content: "\f1b5"; }
+.glyphicon-italic:before { content: "\f1b6"; }
+.glyphicon-layer-info:before { content: "\f1b7"; }
+.glyphicon-leaf:before { content: "\f1b8"; }
+.glyphicon-level-up:before { content: "\f1b9"; }
+.glyphicon-line-dash:before { content: "\f1ba"; }
+.glyphicon-line-minus:before { content: "\f1bb"; }
+.glyphicon-line-plus:before { content: "\f1bc"; }
+.glyphicon-line-remove:before { content: "\f1bd"; }
+.glyphicon-line-trash:before { content: "\f1be"; }
+.glyphicon-line:before { content: "\f1bf"; }
+.glyphicon-link:before { content: "\f1c0"; }
+.glyphicon-list-alt:before { content: "\f1c1"; }
+.glyphicon-list:before { content: "\f1c2"; }
+.glyphicon-lock:before { content: "\f1c3"; }
+.glyphicon-log-in:before { content: "\f1c4"; }
+.glyphicon-log-out:before { content: "\f1c5"; }
+.glyphicon-loop:before { content: "\f1c6"; }
+.glyphicon-magnet:before { content: "\f1c7"; }
+.glyphicon-map-context:before { content: "\f1c8"; }
+.glyphicon-map-edit:before { content: "\f1c9"; }
+.glyphicon-map-filter:before { content: "\f1ca"; }
+.glyphicon-map-marker:before { content: "\f1cb"; }
+.glyphicon-map-synch:before { content: "\f1cc"; }
+.glyphicon-map-view:before { content: "\f1cd"; }
+.glyphicon-maps-catalog:before { content: "\f1ce"; }
+.glyphicon-menu-hamburger:before { content: "\f1cf"; }
+.glyphicon-minus-sign:before { content: "\f1d0"; }
+.glyphicon-minus:before { content: "\f1d1"; }
+.glyphicon-model-plus:before { content: "\f1d2"; }
+.glyphicon-model:before { content: "\f1d3"; }
+.glyphicon-mouse:before { content: "\f1d4"; }
+.glyphicon-move-row-after:before { content: "\f1d5"; }
+.glyphicon-move-row-before:before { content: "\f1d6"; }
+.glyphicon-move:before { content: "\f1d7"; }
+.glyphicon-muted:before { content: "\f1d8"; }
+.glyphicon-new-window:before { content: "\f1d9"; }
+.glyphicon-next:before { content: "\f1da"; }
+.glyphicon-off:before { content: "\f1db"; }
+.glyphicon-ok-circle:before { content: "\f1dc"; }
+.glyphicon-ok-sign:before { content: "\f1dd"; }
+.glyphicon-ok:before { content: "\f1de"; }
+.glyphicon-open:before { content: "\f1df"; }
+.glyphicon-option-horizontal:before { content: "\f1e0"; }
+.glyphicon-option-vertical:before { content: "\f1e1"; }
+.glyphicon-paperclip:before { content: "\f1e2"; }
+.glyphicon-paste:before { content: "\f1e3"; }
+.glyphicon-pause:before { content: "\f1e4"; }
+.glyphicon-pencil-add:before { content: "\f1e5"; }
+.glyphicon-pencil-edit:before { content: "\f1e6"; }
+.glyphicon-pencil:before { content: "\f1e7"; }
+.glyphicon-phone:before { content: "\f1e8"; }
+.glyphicon-picture:before { content: "\f1e9"; }
+.glyphicon-pie-chart:before { content: "\f1ea"; }
+.glyphicon-plane:before { content: "\f1eb"; }
+.glyphicon-play-circle:before { content: "\f1ec"; }
+.glyphicon-play:before { content: "\f1ed"; }
+.glyphicon-playback:before { content: "\f1ee"; }
+.glyphicon-plug:before { content: "\f1ef"; }
+.glyphicon-plus-sign:before { content: "\f1f0"; }
+.glyphicon-plus-square:before { content: "\f1f1"; }
+.glyphicon-plus:before { content: "\f1f2"; }
+.glyphicon-point-coordinates:before { content: "\f1f3"; }
+.glyphicon-point-dash:before { content: "\f1f4"; }
+.glyphicon-point-minus:before { content: "\f1f5"; }
+.glyphicon-point-plus:before { content: "\f1f6"; }
+.glyphicon-point-remove:before { content: "\f1f7"; }
+.glyphicon-point-trash:before { content: "\f1f8"; }
+.glyphicon-point:before { content: "\f1f9"; }
+.glyphicon-polygon-3d:before { content: "\f1fa"; }
+.glyphicon-polygon-dash:before { content: "\f1fb"; }
+.glyphicon-polygon-minus:before { content: "\f1fc"; }
+.glyphicon-polygon-plus:before { content: "\f1fd"; }
+.glyphicon-polygon-remove:before { content: "\f1fe"; }
+.glyphicon-polygon-trash:before { content: "\f1ff"; }
+.glyphicon-polygon:before { content: "\f200"; }
+.glyphicon-polyline-3d:before { content: "\f201"; }
+.glyphicon-polyline-dash:before { content: "\f202"; }
+.glyphicon-polyline-minus:before { content: "\f203"; }
+.glyphicon-polyline-plus:before { content: "\f204"; }
+.glyphicon-polyline-remove:before { content: "\f205"; }
+.glyphicon-polyline-trash:before { content: "\f206"; }
+.glyphicon-polyline:before { content: "\f207"; }
+.glyphicon-preview:before { content: "\f208"; }
+.glyphicon-print:before { content: "\f209"; }
+.glyphicon-pushpin:before { content: "\f20a"; }
+.glyphicon-qrcode:before { content: "\f20b"; }
+.glyphicon-question-sign:before { content: "\f20c"; }
+.glyphicon-random:before { content: "\f20d"; }
+.glyphicon-range-end:before { content: "\f20e"; }
+.glyphicon-range-start:before { content: "\f20f"; }
+.glyphicon-record:before { content: "\f210"; }
+.glyphicon-redo:before { content: "\f211"; }
+.glyphicon-refresh:before { content: "\f212"; }
+.glyphicon-remove-circle:before { content: "\f213"; }
+.glyphicon-remove-sign:before { content: "\f214"; }
+.glyphicon-remove-square:before { content: "\f215"; }
+.glyphicon-remove:before { content: "\f216"; }
+.glyphicon-repeat:before { content: "\f217"; }
+.glyphicon-resize-full:before { content: "\f218"; }
+.glyphicon-resize-horizontal:before { content: "\f219"; }
+.glyphicon-resize-small:before { content: "\f21a"; }
+.glyphicon-resize-vertical:before { content: "\f21b"; }
+.glyphicon-retweet:before { content: "\f21c"; }
+.glyphicon-rgb:before { content: "\f21d"; }
+.glyphicon-road:before { content: "\f21e"; }
+.glyphicon-row-add:before { content: "\f21f"; }
+.glyphicon-row-trash:before { content: "\f220"; }
+.glyphicon-save:before { content: "\f221"; }
+.glyphicon-saved:before { content: "\f222"; }
+.glyphicon-scissors:before { content: "\f223"; }
+.glyphicon-screenshot:before { content: "\f224"; }
+.glyphicon-search-coords:before { content: "\f225"; }
+.glyphicon-search:before { content: "\f226"; }
+.glyphicon-send:before { content: "\f227"; }
+.glyphicon-share-alt:before { content: "\f228"; }
+.glyphicon-share:before { content: "\f229"; }
+.glyphicon-sheet:before { content: "\f22a"; }
+.glyphicon-shopping-cart:before { content: "\f22b"; }
+.glyphicon-signal:before { content: "\f22c"; }
+.glyphicon-size-extra-large:before { content: "\f22d"; }
+.glyphicon-size-large:before { content: "\f22e"; }
+.glyphicon-size-medium:before { content: "\f22f"; }
+.glyphicon-size-small:before { content: "\f230"; }
+.glyphicon-slope:before { content: "\f231"; }
+.glyphicon-sort-by-alphabet-alt:before { content: "\f232"; }
+.glyphicon-sort-by-alphabet:before { content: "\f233"; }
+.glyphicon-sort-by-attributes-alt:before { content: "\f234"; }
+.glyphicon-sort-by-attributes:before { content: "\f235"; }
+.glyphicon-sort:before { content: "\f236"; }
+.glyphicon-star-empty:before { content: "\f237"; }
+.glyphicon-star:before { content: "\f238"; }
+.glyphicon-stats:before { content: "\f239"; }
+.glyphicon-step-backward:before { content: "\f23a"; }
+.glyphicon-step-forward:before { content: "\f23b"; }
+.glyphicon-stop:before { content: "\f23c"; }
+.glyphicon-story-banner-section:before { content: "\f23d"; }
+.glyphicon-story-carousel-section:before { content: "\f23e"; }
+.glyphicon-story-immersive-content:before { content: "\f23f"; }
+.glyphicon-story-immersive-section:before { content: "\f240"; }
+.glyphicon-story-media-section:before { content: "\f241"; }
+.glyphicon-story-paragraph-section:before { content: "\f242"; }
+.glyphicon-story-title-section:before { content: "\f243"; }
+.glyphicon-story-webpage-section:before { content: "\f244"; }
+.glyphicon-tag:before { content: "\f245"; }
+.glyphicon-tags:before { content: "\f246"; }
+.glyphicon-tasks:before { content: "\f247"; }
+.glyphicon-text-background:before { content: "\f248"; }
+.glyphicon-text-colour:before { content: "\f249"; }
+.glyphicon-text-height:before { content: "\f24a"; }
+.glyphicon-text-width:before { content: "\f24b"; }
+.glyphicon-th-large:before { content: "\f24c"; }
+.glyphicon-th-list:before { content: "\f24d"; }
+.glyphicon-th:before { content: "\f24e"; }
+.glyphicon-thumbs-down:before { content: "\f24f"; }
+.glyphicon-thumbs-up:before { content: "\f250"; }
+.glyphicon-time-current:before { content: "\f251"; }
+.glyphicon-time-offset:before { content: "\f252"; }
+.glyphicon-time:before { content: "\f253"; }
+.glyphicon-tint:before { content: "\f254"; }
+.glyphicon-transfer:before { content: "\f255"; }
+.glyphicon-trash-square:before { content: "\f256"; }
+.glyphicon-trash:before { content: "\f257"; }
+.glyphicon-unchecked:before { content: "\f258"; }
+.glyphicon-undo:before { content: "\f259"; }
+.glyphicon-unplug:before { content: "\f25a"; }
+.glyphicon-upload:before { content: "\f25b"; }
+.glyphicon-usd:before { content: "\f25c"; }
+.glyphicon-user:before { content: "\f25d"; }
+.glyphicon-vert-dashed:before { content: "\f25e"; }
+.glyphicon-viewport-filter:before { content: "\f25f"; }
+.glyphicon-warning-sign:before { content: "\f260"; }
+.glyphicon-webpage:before { content: "\f261"; }
+.glyphicon-wrench:before { content: "\f262"; }
+.glyphicon-zoom-in:before { content: "\f263"; }
+.glyphicon-zoom-out:before { content: "\f264"; }
+.glyphicon-zoom-to:before { content: "\f265"; }
-/*
+/*
classes with icon code
they can be used as mixin to get the content code
structure .glyphicon-{iconName}-content
@@ -517,244 +518,245 @@ structure .glyphicon-{iconName}-content
.glyphicon-crs-content { content: "\f171"; }
.glyphicon-dashboard-save-content { content: "\f172"; }
.glyphicon-dashboard-content { content: "\f173"; }
-.glyphicon-download-alt-content { content: "\f174"; }
-.glyphicon-download-comment-content { content: "\f175"; }
-.glyphicon-download-content { content: "\f176"; }
-.glyphicon-dropper-content { content: "\f177"; }
-.glyphicon-duplicate-content { content: "\f178"; }
-.glyphicon-edit-content { content: "\f179"; }
-.glyphicon-envelope-content { content: "\f17a"; }
-.glyphicon-exclamation-mark-content { content: "\f17b"; }
-.glyphicon-exclamation-sign-content { content: "\f17c"; }
-.glyphicon-expand-content { content: "\f17d"; }
-.glyphicon-export-content { content: "\f17e"; }
-.glyphicon-ext-empty-content { content: "\f17f"; }
-.glyphicon-ext-html-content { content: "\f180"; }
-.glyphicon-ext-json-content { content: "\f181"; }
-.glyphicon-ext-pdf-content { content: "\f182"; }
-.glyphicon-ext-txt-content { content: "\f183"; }
-.glyphicon-ext-wmc-content { content: "\f184"; }
-.glyphicon-eye-close-content { content: "\f185"; }
-.glyphicon-eye-open-content { content: "\f186"; }
-.glyphicon-fast-backward-content { content: "\f187"; }
-.glyphicon-fast-forward-content { content: "\f188"; }
-.glyphicon-features-grid-download-content { content: "\f189"; }
-.glyphicon-features-grid-set-content { content: "\f18a"; }
-.glyphicon-features-grid-content { content: "\f18b"; }
-.glyphicon-file-content { content: "\f18c"; }
-.glyphicon-filter-layer-content { content: "\f18d"; }
-.glyphicon-filter-content { content: "\f18e"; }
-.glyphicon-fit-contain-content { content: "\f18f"; }
-.glyphicon-fit-cover-content { content: "\f190"; }
-.glyphicon-flag-content { content: "\f191"; }
-.glyphicon-flash-content { content: "\f192"; }
-.glyphicon-floppy-disk-content { content: "\f193"; }
-.glyphicon-floppy-open-content { content: "\f194"; }
-.glyphicon-floppy-remove-content { content: "\f195"; }
-.glyphicon-floppy-save-content { content: "\f196"; }
-.glyphicon-floppy-saved-content { content: "\f197"; }
-.glyphicon-folder-close-content { content: "\f198"; }
-.glyphicon-folder-open-content { content: "\f199"; }
-.glyphicon-font-add-content { content: "\f19a"; }
-.glyphicon-font-content { content: "\f19b"; }
-.glyphicon-forward-content { content: "\f19c"; }
-.glyphicon-geometry-collection-content { content: "\f19d"; }
-.glyphicon-geoserver-content { content: "\f19e"; }
-.glyphicon-geostory-content { content: "\f19f"; }
-.glyphicon-globe-settings-content { content: "\f1a0"; }
-.glyphicon-globe-content { content: "\f1a1"; }
-.glyphicon-grab-handle-content { content: "\f1a2"; }
-.glyphicon-gray-scale-content { content: "\f1a3"; }
-.glyphicon-grid-custom-content { content: "\f1a4"; }
-.glyphicon-grid-regular-content { content: "\f1a5"; }
-.glyphicon-hand-down-content { content: "\f1a6"; }
-.glyphicon-hand-left-content { content: "\f1a7"; }
-.glyphicon-hand-right-content { content: "\f1a8"; }
-.glyphicon-hand-up-content { content: "\f1a9"; }
-.glyphicon-hdd-content { content: "\f1aa"; }
-.glyphicon-heart-content { content: "\f1ab"; }
-.glyphicon-height-auto-content { content: "\f1ac"; }
-.glyphicon-height-from-terrain-content { content: "\f1ad"; }
-.glyphicon-height-view-content { content: "\f1ae"; }
-.glyphicon-hide-marker-content { content: "\f1af"; }
-.glyphicon-home-content { content: "\f1b0"; }
-.glyphicon-hourglass-content { content: "\f1b1"; }
-.glyphicon-import-content { content: "\f1b2"; }
-.glyphicon-inbox-content { content: "\f1b3"; }
-.glyphicon-info-sign-content { content: "\f1b4"; }
-.glyphicon-italic-content { content: "\f1b5"; }
-.glyphicon-layer-info-content { content: "\f1b6"; }
-.glyphicon-leaf-content { content: "\f1b7"; }
-.glyphicon-level-up-content { content: "\f1b8"; }
-.glyphicon-line-dash-content { content: "\f1b9"; }
-.glyphicon-line-minus-content { content: "\f1ba"; }
-.glyphicon-line-plus-content { content: "\f1bb"; }
-.glyphicon-line-remove-content { content: "\f1bc"; }
-.glyphicon-line-trash-content { content: "\f1bd"; }
-.glyphicon-line-content { content: "\f1be"; }
-.glyphicon-link-content { content: "\f1bf"; }
-.glyphicon-list-alt-content { content: "\f1c0"; }
-.glyphicon-list-content { content: "\f1c1"; }
-.glyphicon-lock-content { content: "\f1c2"; }
-.glyphicon-log-in-content { content: "\f1c3"; }
-.glyphicon-log-out-content { content: "\f1c4"; }
-.glyphicon-loop-content { content: "\f1c5"; }
-.glyphicon-magnet-content { content: "\f1c6"; }
-.glyphicon-map-context-content { content: "\f1c7"; }
-.glyphicon-map-edit-content { content: "\f1c8"; }
-.glyphicon-map-filter-content { content: "\f1c9"; }
-.glyphicon-map-marker-content { content: "\f1ca"; }
-.glyphicon-map-synch-content { content: "\f1cb"; }
-.glyphicon-map-view-content { content: "\f1cc"; }
-.glyphicon-maps-catalog-content { content: "\f1cd"; }
-.glyphicon-menu-hamburger-content { content: "\f1ce"; }
-.glyphicon-minus-sign-content { content: "\f1cf"; }
-.glyphicon-minus-content { content: "\f1d0"; }
-.glyphicon-model-plus-content { content: "\f1d1"; }
-.glyphicon-model-content { content: "\f1d2"; }
-.glyphicon-mouse-content { content: "\f1d3"; }
-.glyphicon-move-row-after-content { content: "\f1d4"; }
-.glyphicon-move-row-before-content { content: "\f1d5"; }
-.glyphicon-move-content { content: "\f1d6"; }
-.glyphicon-muted-content { content: "\f1d7"; }
-.glyphicon-new-window-content { content: "\f1d8"; }
-.glyphicon-next-content { content: "\f1d9"; }
-.glyphicon-off-content { content: "\f1da"; }
-.glyphicon-ok-circle-content { content: "\f1db"; }
-.glyphicon-ok-sign-content { content: "\f1dc"; }
-.glyphicon-ok-content { content: "\f1dd"; }
-.glyphicon-open-content { content: "\f1de"; }
-.glyphicon-option-horizontal-content { content: "\f1df"; }
-.glyphicon-option-vertical-content { content: "\f1e0"; }
-.glyphicon-paperclip-content { content: "\f1e1"; }
-.glyphicon-paste-content { content: "\f1e2"; }
-.glyphicon-pause-content { content: "\f1e3"; }
-.glyphicon-pencil-add-content { content: "\f1e4"; }
-.glyphicon-pencil-edit-content { content: "\f1e5"; }
-.glyphicon-pencil-content { content: "\f1e6"; }
-.glyphicon-phone-content { content: "\f1e7"; }
-.glyphicon-picture-content { content: "\f1e8"; }
-.glyphicon-pie-chart-content { content: "\f1e9"; }
-.glyphicon-plane-content { content: "\f1ea"; }
-.glyphicon-play-circle-content { content: "\f1eb"; }
-.glyphicon-play-content { content: "\f1ec"; }
-.glyphicon-playback-content { content: "\f1ed"; }
-.glyphicon-plug-content { content: "\f1ee"; }
-.glyphicon-plus-sign-content { content: "\f1ef"; }
-.glyphicon-plus-square-content { content: "\f1f0"; }
-.glyphicon-plus-content { content: "\f1f1"; }
-.glyphicon-point-coordinates-content { content: "\f1f2"; }
-.glyphicon-point-dash-content { content: "\f1f3"; }
-.glyphicon-point-minus-content { content: "\f1f4"; }
-.glyphicon-point-plus-content { content: "\f1f5"; }
-.glyphicon-point-remove-content { content: "\f1f6"; }
-.glyphicon-point-trash-content { content: "\f1f7"; }
-.glyphicon-point-content { content: "\f1f8"; }
-.glyphicon-polygon-3d-content { content: "\f1f9"; }
-.glyphicon-polygon-dash-content { content: "\f1fa"; }
-.glyphicon-polygon-minus-content { content: "\f1fb"; }
-.glyphicon-polygon-plus-content { content: "\f1fc"; }
-.glyphicon-polygon-remove-content { content: "\f1fd"; }
-.glyphicon-polygon-trash-content { content: "\f1fe"; }
-.glyphicon-polygon-content { content: "\f1ff"; }
-.glyphicon-polyline-3d-content { content: "\f200"; }
-.glyphicon-polyline-dash-content { content: "\f201"; }
-.glyphicon-polyline-minus-content { content: "\f202"; }
-.glyphicon-polyline-plus-content { content: "\f203"; }
-.glyphicon-polyline-remove-content { content: "\f204"; }
-.glyphicon-polyline-trash-content { content: "\f205"; }
-.glyphicon-polyline-content { content: "\f206"; }
-.glyphicon-preview-content { content: "\f207"; }
-.glyphicon-print-content { content: "\f208"; }
-.glyphicon-pushpin-content { content: "\f209"; }
-.glyphicon-qrcode-content { content: "\f20a"; }
-.glyphicon-question-sign-content { content: "\f20b"; }
-.glyphicon-random-content { content: "\f20c"; }
-.glyphicon-range-end-content { content: "\f20d"; }
-.glyphicon-range-start-content { content: "\f20e"; }
-.glyphicon-record-content { content: "\f20f"; }
-.glyphicon-redo-content { content: "\f210"; }
-.glyphicon-refresh-content { content: "\f211"; }
-.glyphicon-remove-circle-content { content: "\f212"; }
-.glyphicon-remove-sign-content { content: "\f213"; }
-.glyphicon-remove-square-content { content: "\f214"; }
-.glyphicon-remove-content { content: "\f215"; }
-.glyphicon-repeat-content { content: "\f216"; }
-.glyphicon-resize-full-content { content: "\f217"; }
-.glyphicon-resize-horizontal-content { content: "\f218"; }
-.glyphicon-resize-small-content { content: "\f219"; }
-.glyphicon-resize-vertical-content { content: "\f21a"; }
-.glyphicon-retweet-content { content: "\f21b"; }
-.glyphicon-rgb-content { content: "\f21c"; }
-.glyphicon-road-content { content: "\f21d"; }
-.glyphicon-row-add-content { content: "\f21e"; }
-.glyphicon-row-trash-content { content: "\f21f"; }
-.glyphicon-save-content { content: "\f220"; }
-.glyphicon-saved-content { content: "\f221"; }
-.glyphicon-scissors-content { content: "\f222"; }
-.glyphicon-screenshot-content { content: "\f223"; }
-.glyphicon-search-coords-content { content: "\f224"; }
-.glyphicon-search-content { content: "\f225"; }
-.glyphicon-send-content { content: "\f226"; }
-.glyphicon-share-alt-content { content: "\f227"; }
-.glyphicon-share-content { content: "\f228"; }
-.glyphicon-sheet-content { content: "\f229"; }
-.glyphicon-shopping-cart-content { content: "\f22a"; }
-.glyphicon-signal-content { content: "\f22b"; }
-.glyphicon-size-extra-large-content { content: "\f22c"; }
-.glyphicon-size-large-content { content: "\f22d"; }
-.glyphicon-size-medium-content { content: "\f22e"; }
-.glyphicon-size-small-content { content: "\f22f"; }
-.glyphicon-slope-content { content: "\f230"; }
-.glyphicon-sort-by-alphabet-alt-content { content: "\f231"; }
-.glyphicon-sort-by-alphabet-content { content: "\f232"; }
-.glyphicon-sort-by-attributes-alt-content { content: "\f233"; }
-.glyphicon-sort-by-attributes-content { content: "\f234"; }
-.glyphicon-sort-content { content: "\f235"; }
-.glyphicon-star-empty-content { content: "\f236"; }
-.glyphicon-star-content { content: "\f237"; }
-.glyphicon-stats-content { content: "\f238"; }
-.glyphicon-step-backward-content { content: "\f239"; }
-.glyphicon-step-forward-content { content: "\f23a"; }
-.glyphicon-stop-content { content: "\f23b"; }
-.glyphicon-story-banner-section-content { content: "\f23c"; }
-.glyphicon-story-carousel-section-content { content: "\f23d"; }
-.glyphicon-story-immersive-content-content { content: "\f23e"; }
-.glyphicon-story-immersive-section-content { content: "\f23f"; }
-.glyphicon-story-media-section-content { content: "\f240"; }
-.glyphicon-story-paragraph-section-content { content: "\f241"; }
-.glyphicon-story-title-section-content { content: "\f242"; }
-.glyphicon-story-webpage-section-content { content: "\f243"; }
-.glyphicon-tag-content { content: "\f244"; }
-.glyphicon-tags-content { content: "\f245"; }
-.glyphicon-tasks-content { content: "\f246"; }
-.glyphicon-text-background-content { content: "\f247"; }
-.glyphicon-text-colour-content { content: "\f248"; }
-.glyphicon-text-height-content { content: "\f249"; }
-.glyphicon-text-width-content { content: "\f24a"; }
-.glyphicon-th-large-content { content: "\f24b"; }
-.glyphicon-th-list-content { content: "\f24c"; }
-.glyphicon-th-content { content: "\f24d"; }
-.glyphicon-thumbs-down-content { content: "\f24e"; }
-.glyphicon-thumbs-up-content { content: "\f24f"; }
-.glyphicon-time-current-content { content: "\f250"; }
-.glyphicon-time-offset-content { content: "\f251"; }
-.glyphicon-time-content { content: "\f252"; }
-.glyphicon-tint-content { content: "\f253"; }
-.glyphicon-transfer-content { content: "\f254"; }
-.glyphicon-trash-square-content { content: "\f255"; }
-.glyphicon-trash-content { content: "\f256"; }
-.glyphicon-unchecked-content { content: "\f257"; }
-.glyphicon-undo-content { content: "\f258"; }
-.glyphicon-unplug-content { content: "\f259"; }
-.glyphicon-upload-content { content: "\f25a"; }
-.glyphicon-usd-content { content: "\f25b"; }
-.glyphicon-user-content { content: "\f25c"; }
-.glyphicon-vert-dashed-content { content: "\f25d"; }
-.glyphicon-viewport-filter-content { content: "\f25e"; }
-.glyphicon-warning-sign-content { content: "\f25f"; }
-.glyphicon-webpage-content { content: "\f260"; }
-.glyphicon-wrench-content { content: "\f261"; }
-.glyphicon-zoom-in-content { content: "\f262"; }
-.glyphicon-zoom-out-content { content: "\f263"; }
-.glyphicon-zoom-to-content { content: "\f264"; }
+.glyphicon-date-time-content { content: "\f174"; }
+.glyphicon-download-alt-content { content: "\f175"; }
+.glyphicon-download-comment-content { content: "\f176"; }
+.glyphicon-download-content { content: "\f177"; }
+.glyphicon-dropper-content { content: "\f178"; }
+.glyphicon-duplicate-content { content: "\f179"; }
+.glyphicon-edit-content { content: "\f17a"; }
+.glyphicon-envelope-content { content: "\f17b"; }
+.glyphicon-exclamation-mark-content { content: "\f17c"; }
+.glyphicon-exclamation-sign-content { content: "\f17d"; }
+.glyphicon-expand-content { content: "\f17e"; }
+.glyphicon-export-content { content: "\f17f"; }
+.glyphicon-ext-empty-content { content: "\f180"; }
+.glyphicon-ext-html-content { content: "\f181"; }
+.glyphicon-ext-json-content { content: "\f182"; }
+.glyphicon-ext-pdf-content { content: "\f183"; }
+.glyphicon-ext-txt-content { content: "\f184"; }
+.glyphicon-ext-wmc-content { content: "\f185"; }
+.glyphicon-eye-close-content { content: "\f186"; }
+.glyphicon-eye-open-content { content: "\f187"; }
+.glyphicon-fast-backward-content { content: "\f188"; }
+.glyphicon-fast-forward-content { content: "\f189"; }
+.glyphicon-features-grid-download-content { content: "\f18a"; }
+.glyphicon-features-grid-set-content { content: "\f18b"; }
+.glyphicon-features-grid-content { content: "\f18c"; }
+.glyphicon-file-content { content: "\f18d"; }
+.glyphicon-filter-layer-content { content: "\f18e"; }
+.glyphicon-filter-content { content: "\f18f"; }
+.glyphicon-fit-contain-content { content: "\f190"; }
+.glyphicon-fit-cover-content { content: "\f191"; }
+.glyphicon-flag-content { content: "\f192"; }
+.glyphicon-flash-content { content: "\f193"; }
+.glyphicon-floppy-disk-content { content: "\f194"; }
+.glyphicon-floppy-open-content { content: "\f195"; }
+.glyphicon-floppy-remove-content { content: "\f196"; }
+.glyphicon-floppy-save-content { content: "\f197"; }
+.glyphicon-floppy-saved-content { content: "\f198"; }
+.glyphicon-folder-close-content { content: "\f199"; }
+.glyphicon-folder-open-content { content: "\f19a"; }
+.glyphicon-font-add-content { content: "\f19b"; }
+.glyphicon-font-content { content: "\f19c"; }
+.glyphicon-forward-content { content: "\f19d"; }
+.glyphicon-geometry-collection-content { content: "\f19e"; }
+.glyphicon-geoserver-content { content: "\f19f"; }
+.glyphicon-geostory-content { content: "\f1a0"; }
+.glyphicon-globe-settings-content { content: "\f1a1"; }
+.glyphicon-globe-content { content: "\f1a2"; }
+.glyphicon-grab-handle-content { content: "\f1a3"; }
+.glyphicon-gray-scale-content { content: "\f1a4"; }
+.glyphicon-grid-custom-content { content: "\f1a5"; }
+.glyphicon-grid-regular-content { content: "\f1a6"; }
+.glyphicon-hand-down-content { content: "\f1a7"; }
+.glyphicon-hand-left-content { content: "\f1a8"; }
+.glyphicon-hand-right-content { content: "\f1a9"; }
+.glyphicon-hand-up-content { content: "\f1aa"; }
+.glyphicon-hdd-content { content: "\f1ab"; }
+.glyphicon-heart-content { content: "\f1ac"; }
+.glyphicon-height-auto-content { content: "\f1ad"; }
+.glyphicon-height-from-terrain-content { content: "\f1ae"; }
+.glyphicon-height-view-content { content: "\f1af"; }
+.glyphicon-hide-marker-content { content: "\f1b0"; }
+.glyphicon-home-content { content: "\f1b1"; }
+.glyphicon-hourglass-content { content: "\f1b2"; }
+.glyphicon-import-content { content: "\f1b3"; }
+.glyphicon-inbox-content { content: "\f1b4"; }
+.glyphicon-info-sign-content { content: "\f1b5"; }
+.glyphicon-italic-content { content: "\f1b6"; }
+.glyphicon-layer-info-content { content: "\f1b7"; }
+.glyphicon-leaf-content { content: "\f1b8"; }
+.glyphicon-level-up-content { content: "\f1b9"; }
+.glyphicon-line-dash-content { content: "\f1ba"; }
+.glyphicon-line-minus-content { content: "\f1bb"; }
+.glyphicon-line-plus-content { content: "\f1bc"; }
+.glyphicon-line-remove-content { content: "\f1bd"; }
+.glyphicon-line-trash-content { content: "\f1be"; }
+.glyphicon-line-content { content: "\f1bf"; }
+.glyphicon-link-content { content: "\f1c0"; }
+.glyphicon-list-alt-content { content: "\f1c1"; }
+.glyphicon-list-content { content: "\f1c2"; }
+.glyphicon-lock-content { content: "\f1c3"; }
+.glyphicon-log-in-content { content: "\f1c4"; }
+.glyphicon-log-out-content { content: "\f1c5"; }
+.glyphicon-loop-content { content: "\f1c6"; }
+.glyphicon-magnet-content { content: "\f1c7"; }
+.glyphicon-map-context-content { content: "\f1c8"; }
+.glyphicon-map-edit-content { content: "\f1c9"; }
+.glyphicon-map-filter-content { content: "\f1ca"; }
+.glyphicon-map-marker-content { content: "\f1cb"; }
+.glyphicon-map-synch-content { content: "\f1cc"; }
+.glyphicon-map-view-content { content: "\f1cd"; }
+.glyphicon-maps-catalog-content { content: "\f1ce"; }
+.glyphicon-menu-hamburger-content { content: "\f1cf"; }
+.glyphicon-minus-sign-content { content: "\f1d0"; }
+.glyphicon-minus-content { content: "\f1d1"; }
+.glyphicon-model-plus-content { content: "\f1d2"; }
+.glyphicon-model-content { content: "\f1d3"; }
+.glyphicon-mouse-content { content: "\f1d4"; }
+.glyphicon-move-row-after-content { content: "\f1d5"; }
+.glyphicon-move-row-before-content { content: "\f1d6"; }
+.glyphicon-move-content { content: "\f1d7"; }
+.glyphicon-muted-content { content: "\f1d8"; }
+.glyphicon-new-window-content { content: "\f1d9"; }
+.glyphicon-next-content { content: "\f1da"; }
+.glyphicon-off-content { content: "\f1db"; }
+.glyphicon-ok-circle-content { content: "\f1dc"; }
+.glyphicon-ok-sign-content { content: "\f1dd"; }
+.glyphicon-ok-content { content: "\f1de"; }
+.glyphicon-open-content { content: "\f1df"; }
+.glyphicon-option-horizontal-content { content: "\f1e0"; }
+.glyphicon-option-vertical-content { content: "\f1e1"; }
+.glyphicon-paperclip-content { content: "\f1e2"; }
+.glyphicon-paste-content { content: "\f1e3"; }
+.glyphicon-pause-content { content: "\f1e4"; }
+.glyphicon-pencil-add-content { content: "\f1e5"; }
+.glyphicon-pencil-edit-content { content: "\f1e6"; }
+.glyphicon-pencil-content { content: "\f1e7"; }
+.glyphicon-phone-content { content: "\f1e8"; }
+.glyphicon-picture-content { content: "\f1e9"; }
+.glyphicon-pie-chart-content { content: "\f1ea"; }
+.glyphicon-plane-content { content: "\f1eb"; }
+.glyphicon-play-circle-content { content: "\f1ec"; }
+.glyphicon-play-content { content: "\f1ed"; }
+.glyphicon-playback-content { content: "\f1ee"; }
+.glyphicon-plug-content { content: "\f1ef"; }
+.glyphicon-plus-sign-content { content: "\f1f0"; }
+.glyphicon-plus-square-content { content: "\f1f1"; }
+.glyphicon-plus-content { content: "\f1f2"; }
+.glyphicon-point-coordinates-content { content: "\f1f3"; }
+.glyphicon-point-dash-content { content: "\f1f4"; }
+.glyphicon-point-minus-content { content: "\f1f5"; }
+.glyphicon-point-plus-content { content: "\f1f6"; }
+.glyphicon-point-remove-content { content: "\f1f7"; }
+.glyphicon-point-trash-content { content: "\f1f8"; }
+.glyphicon-point-content { content: "\f1f9"; }
+.glyphicon-polygon-3d-content { content: "\f1fa"; }
+.glyphicon-polygon-dash-content { content: "\f1fb"; }
+.glyphicon-polygon-minus-content { content: "\f1fc"; }
+.glyphicon-polygon-plus-content { content: "\f1fd"; }
+.glyphicon-polygon-remove-content { content: "\f1fe"; }
+.glyphicon-polygon-trash-content { content: "\f1ff"; }
+.glyphicon-polygon-content { content: "\f200"; }
+.glyphicon-polyline-3d-content { content: "\f201"; }
+.glyphicon-polyline-dash-content { content: "\f202"; }
+.glyphicon-polyline-minus-content { content: "\f203"; }
+.glyphicon-polyline-plus-content { content: "\f204"; }
+.glyphicon-polyline-remove-content { content: "\f205"; }
+.glyphicon-polyline-trash-content { content: "\f206"; }
+.glyphicon-polyline-content { content: "\f207"; }
+.glyphicon-preview-content { content: "\f208"; }
+.glyphicon-print-content { content: "\f209"; }
+.glyphicon-pushpin-content { content: "\f20a"; }
+.glyphicon-qrcode-content { content: "\f20b"; }
+.glyphicon-question-sign-content { content: "\f20c"; }
+.glyphicon-random-content { content: "\f20d"; }
+.glyphicon-range-end-content { content: "\f20e"; }
+.glyphicon-range-start-content { content: "\f20f"; }
+.glyphicon-record-content { content: "\f210"; }
+.glyphicon-redo-content { content: "\f211"; }
+.glyphicon-refresh-content { content: "\f212"; }
+.glyphicon-remove-circle-content { content: "\f213"; }
+.glyphicon-remove-sign-content { content: "\f214"; }
+.glyphicon-remove-square-content { content: "\f215"; }
+.glyphicon-remove-content { content: "\f216"; }
+.glyphicon-repeat-content { content: "\f217"; }
+.glyphicon-resize-full-content { content: "\f218"; }
+.glyphicon-resize-horizontal-content { content: "\f219"; }
+.glyphicon-resize-small-content { content: "\f21a"; }
+.glyphicon-resize-vertical-content { content: "\f21b"; }
+.glyphicon-retweet-content { content: "\f21c"; }
+.glyphicon-rgb-content { content: "\f21d"; }
+.glyphicon-road-content { content: "\f21e"; }
+.glyphicon-row-add-content { content: "\f21f"; }
+.glyphicon-row-trash-content { content: "\f220"; }
+.glyphicon-save-content { content: "\f221"; }
+.glyphicon-saved-content { content: "\f222"; }
+.glyphicon-scissors-content { content: "\f223"; }
+.glyphicon-screenshot-content { content: "\f224"; }
+.glyphicon-search-coords-content { content: "\f225"; }
+.glyphicon-search-content { content: "\f226"; }
+.glyphicon-send-content { content: "\f227"; }
+.glyphicon-share-alt-content { content: "\f228"; }
+.glyphicon-share-content { content: "\f229"; }
+.glyphicon-sheet-content { content: "\f22a"; }
+.glyphicon-shopping-cart-content { content: "\f22b"; }
+.glyphicon-signal-content { content: "\f22c"; }
+.glyphicon-size-extra-large-content { content: "\f22d"; }
+.glyphicon-size-large-content { content: "\f22e"; }
+.glyphicon-size-medium-content { content: "\f22f"; }
+.glyphicon-size-small-content { content: "\f230"; }
+.glyphicon-slope-content { content: "\f231"; }
+.glyphicon-sort-by-alphabet-alt-content { content: "\f232"; }
+.glyphicon-sort-by-alphabet-content { content: "\f233"; }
+.glyphicon-sort-by-attributes-alt-content { content: "\f234"; }
+.glyphicon-sort-by-attributes-content { content: "\f235"; }
+.glyphicon-sort-content { content: "\f236"; }
+.glyphicon-star-empty-content { content: "\f237"; }
+.glyphicon-star-content { content: "\f238"; }
+.glyphicon-stats-content { content: "\f239"; }
+.glyphicon-step-backward-content { content: "\f23a"; }
+.glyphicon-step-forward-content { content: "\f23b"; }
+.glyphicon-stop-content { content: "\f23c"; }
+.glyphicon-story-banner-section-content { content: "\f23d"; }
+.glyphicon-story-carousel-section-content { content: "\f23e"; }
+.glyphicon-story-immersive-content-content { content: "\f23f"; }
+.glyphicon-story-immersive-section-content { content: "\f240"; }
+.glyphicon-story-media-section-content { content: "\f241"; }
+.glyphicon-story-paragraph-section-content { content: "\f242"; }
+.glyphicon-story-title-section-content { content: "\f243"; }
+.glyphicon-story-webpage-section-content { content: "\f244"; }
+.glyphicon-tag-content { content: "\f245"; }
+.glyphicon-tags-content { content: "\f246"; }
+.glyphicon-tasks-content { content: "\f247"; }
+.glyphicon-text-background-content { content: "\f248"; }
+.glyphicon-text-colour-content { content: "\f249"; }
+.glyphicon-text-height-content { content: "\f24a"; }
+.glyphicon-text-width-content { content: "\f24b"; }
+.glyphicon-th-large-content { content: "\f24c"; }
+.glyphicon-th-list-content { content: "\f24d"; }
+.glyphicon-th-content { content: "\f24e"; }
+.glyphicon-thumbs-down-content { content: "\f24f"; }
+.glyphicon-thumbs-up-content { content: "\f250"; }
+.glyphicon-time-current-content { content: "\f251"; }
+.glyphicon-time-offset-content { content: "\f252"; }
+.glyphicon-time-content { content: "\f253"; }
+.glyphicon-tint-content { content: "\f254"; }
+.glyphicon-transfer-content { content: "\f255"; }
+.glyphicon-trash-square-content { content: "\f256"; }
+.glyphicon-trash-content { content: "\f257"; }
+.glyphicon-unchecked-content { content: "\f258"; }
+.glyphicon-undo-content { content: "\f259"; }
+.glyphicon-unplug-content { content: "\f25a"; }
+.glyphicon-upload-content { content: "\f25b"; }
+.glyphicon-usd-content { content: "\f25c"; }
+.glyphicon-user-content { content: "\f25d"; }
+.glyphicon-vert-dashed-content { content: "\f25e"; }
+.glyphicon-viewport-filter-content { content: "\f25f"; }
+.glyphicon-warning-sign-content { content: "\f260"; }
+.glyphicon-webpage-content { content: "\f261"; }
+.glyphicon-wrench-content { content: "\f262"; }
+.glyphicon-zoom-in-content { content: "\f263"; }
+.glyphicon-zoom-out-content { content: "\f264"; }
+.glyphicon-zoom-to-content { content: "\f265"; }
diff --git a/web/client/themes/default/icons/icons.eot b/web/client/themes/default/icons/icons.eot
index c205cbde04..012603a43a 100644
Binary files a/web/client/themes/default/icons/icons.eot and b/web/client/themes/default/icons/icons.eot differ
diff --git a/web/client/themes/default/icons/icons.svg b/web/client/themes/default/icons/icons.svg
index 8170e2afbb..d800cf59ed 100644
--- a/web/client/themes/default/icons/icons.svg
+++ b/web/client/themes/default/icons/icons.svg
@@ -697,1448 +697,1454 @@
-
+
+
div.rw-popup.rw-widget{
+ left:0;
+ right:0;
+ }
+ }
+ }
+ div.rw-datetimepicker.rw-widget:not(.rw-datetimepicker.range-time-input.rw-widget){
+ div.rw-popup-container.rw-calendar-popup:not(.rw-hours-popup){
+ right: -12px;
+ left:auto;
+ }
+ }
+ span.glyphicon-date-time{
+ color: var(--ms-primary, #078aa3);
+ }
+ }
+
+
+}
+/* minimize calendar row height in feature-grid that renders within popover */
+.ms-popover-overlay{
+ div.picker-container .rw-calendar.rw-widget tbody span.rw-btn{
+ line-height: 2em;
+ }
+ .picker-container {
+ .range-tab{
+ &:hover{
+ cursor:pointer;
+ }
+ width: 50%;
+ cursor: pointer;
+ font-size: 8px;
+ height: 40px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ padding: 0.5rem;
+ }
+ position: relative;
+ width: 300px;
+ height: 290px;
+ &.range {
+ height: 328px;
+ }
+ &.date-time, &.time {
+ height: 100%;
+ }
+ &.date-time {
+ .date-time-container {
+ .rw-widget {
+ height: 290px;
+ }
+ .date-time-hour-input {
+ display: flex;
+ align-items: center;
+ padding: 2px;
+ .rw-input {
+ height: 30px;
+ }
+ .time-icon {
+ width: 30px;
+ height: 30px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border: 1px solid;
+ border-left-width: 1px;
+ }
+ }
+ }
+ }
+ overflow: auto;
+ .rw-widget {
+ border: none !important;
+ }
+ }
+}
// **************
// FeatureEditorFallback Layout
// **************
diff --git a/web/client/themes/default/less/react-data-grid.less b/web/client/themes/default/less/react-data-grid.less
index ab0a446559..9c0ee4f9dd 100644
--- a/web/client/themes/default/less/react-data-grid.less
+++ b/web/client/themes/default/less/react-data-grid.less
@@ -297,6 +297,9 @@
.rw-datetimepicker.rw-widget input {
.input-size(@input-height-small; @padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @input-border-radius-small);
}
+ .rw-datetimepicker.rw-widget input:disabled{
+ height:100%;
+ }
}
}
}
diff --git a/web/client/themes/default/less/select.less b/web/client/themes/default/less/select.less
index a860970160..74b0f8d06c 100644
--- a/web/client/themes/default/less/select.less
+++ b/web/client/themes/default/less/select.less
@@ -26,7 +26,10 @@
.color-var(@theme-vars[primary-contrast], true);
.background-color-var(@theme-vars[primary], true);
}
-
+ .rw-list-option.is-selected{
+ .background-color-var(@theme-vars[primary], true);
+ .color-var(@theme-vars[primary-contrast], true);
+ }
.rw-state-focus {
.color-var(@theme-vars[primary], true);
.border-color-var(@theme-vars[focus-color], true);
diff --git a/web/client/themes/default/svg/date-time.svg b/web/client/themes/default/svg/date-time.svg
new file mode 100644
index 0000000000..ce291040a0
--- /dev/null
+++ b/web/client/themes/default/svg/date-time.svg
@@ -0,0 +1,60 @@
+
+
diff --git a/web/client/translations/data.de-DE.json b/web/client/translations/data.de-DE.json
index d30fcef15f..6ec1a336b5 100644
--- a/web/client/translations/data.de-DE.json
+++ b/web/client/translations/data.de-DE.json
@@ -1832,6 +1832,7 @@
"placeholders": {
"default": "Beispiele...",
"string": "Geben Sie Text ein, um Ergebnisse zu filtern ...",
+ "time": "Geben Sie Zeit ein, um die Ergebnisse zu filtern ...",
"date": "Geben Sie das zu filternde Datum ein...",
"number": "Geben Sie eine Zahl oder einen Ausdruck ein..."
},
@@ -1839,6 +1840,7 @@
"editMode": "Die Schnellsuche ist im Bearbeitungsmodus nicht verfügbar",
"default": "Beispiele...",
"string": "Geben Sie Text ein, um Ergebnisse zu filtern ...",
+ "time": "Geben Sie Zeit ein, um die Ergebnisse zu filtern ...",
"number": "Geben Sie eine Zahl oder einen Ausdruck ein. Examples: 10, > 2, < 10",
"date": "Geben Sie ein Datum oder einen Ausdruck ein. Verwenden Sie {format} für das Datum.",
"geometry": {
@@ -1848,6 +1850,33 @@
}
}
},
+ "attributeFilter": {
+ "placeholders": {
+ "default": "Beispiele...",
+ "string": "Geben Sie Text ein, um Ergebnisse zu filtern ...",
+ "time": "Geben Sie Zeit ein, um die Ergebnisse zu filtern ...",
+ "date": "Geben Sie das zu filternde Datum ein...",
+ "number": "Geben Sie eine Zahl ...",
+ "range": "Bitte eingeben ..."
+ },
+ "tooltips": {
+ "editMode": "Die Schnellsuche ist im Bearbeitungsmodus nicht verfügbar",
+ "default": "Beispiele...",
+ "string": "Geben Sie Text ein, um Ergebnisse zu filtern ...",
+ "time": "Geben Sie Zeit ein, um die Ergebnisse zu filtern ...",
+ "number": "Geben Sie eine Zahl ...",
+ "date": "Geben Sie ein Datum. Verwenden Sie {format} für das Datum.",
+ "geometry": {
+ "disabled": "Klicken Sie auf die Karte, um zu filtern / Alt + Maus ziehen, um einen Box-Filter zu zeichnen. Halten Sie auch die Strg-Taste gedrückt, um dem aktuellen Filter weitere Funktionen hinzuzufügen",
+ "enabled": "Klicken Sie auf die Karte, um ein Feature auszuwählen / Alt + Maus ziehen, um einen Boxfilter zu zeichnen. Halten Sie die Strg-Taste gedrückt und klicken Sie, um mehrere Features auszuwählen",
+ "applied": "Auswahl bereinigen"
+ }
+ },
+ "rangeTab": {
+ "start": "Start",
+ "end": "Ende"
+ }
+ },
"toolbar": {
"synchPopoverTitle": "Karte mit Filter synchronisieren ",
"synchPopoverText": "Verwenden Sie dieses Werkzeug, um die Karte mit dem ausgewählten Filter zu synchronisieren",
diff --git a/web/client/translations/data.en-US.json b/web/client/translations/data.en-US.json
index 82262591d2..ed6cdb5a73 100644
--- a/web/client/translations/data.en-US.json
+++ b/web/client/translations/data.en-US.json
@@ -1794,12 +1794,14 @@
"default": "Search...",
"string": "Type text to filter...",
"date": "Type date to filter...",
+ "time": "Type time to filter...",
"number": "Type number or expression..."
},
"tooltips": {
"editMode": "Quick search is not available in edit mode",
"default": "Search...",
"string": "Type text to filter...",
+ "time": "Type time to filter...",
"number": "Type a number or an expression. Examples: 10, > 2, < 10",
"date": "Type a date or an expression. Use {format} for the date.",
"geometry": {
@@ -1809,6 +1811,33 @@
}
}
},
+ "attributeFilter": {
+ "placeholders": {
+ "default": "Search...",
+ "string": "Type text to filter...",
+ "time": "Type time to filter...",
+ "date": "Type date to filter...",
+ "number": "Type number to filter...",
+ "range": "Please Enter ..."
+ },
+ "tooltips": {
+ "editMode": "Quick search is not available in edit mode",
+ "default": "Search...",
+ "string": "Type text to filter...",
+ "time": "Type time to filter...",
+ "number": "Type a number ...",
+ "date": "Type a date. Use {format} for the date.",
+ "geometry": {
+ "disabled": "Filter clicking on map / Alt + mouse drag to draw a box filter. Hold also ctrl to add more features to the current filter",
+ "enabled": "Click on map to select a feature / Alt + mouse drag to draw a box filter. Hold ctrl and click to select multiple features",
+ "applied": "Clean the selection"
+ }
+ },
+ "rangeTab": {
+ "start": "Start",
+ "end": "End"
+ }
+ },
"toolbar": {
"synchPopoverTitle": "Sync map with filter ",
"synchPopoverText": "Use this tool to synchronize the map with the selected filter",
diff --git a/web/client/translations/data.es-ES.json b/web/client/translations/data.es-ES.json
index f90e69a3be..aa1115406c 100644
--- a/web/client/translations/data.es-ES.json
+++ b/web/client/translations/data.es-ES.json
@@ -1794,6 +1794,7 @@
"placeholders": {
"default": "Buscar...",
"string": "Escribe texto para filtrar...",
+ "time": "Escriba el tiempo para filtrar ...",
"date": "Escriba la fecha para filtrar...",
"number": "Escriba el número o la expresión..."
},
@@ -1801,6 +1802,7 @@
"editMode": "La búsqueda rápida no está disponible en modo de edición",
"default": "Buscar...",
"string": "Escribe texto para filtrar...",
+ "time": "Escriba el tiempo para filtrar ...",
"number": "Escriba el número o la expresión. Ejemplos: 10, > 2, < 10",
"date": "Escriba una fecha o una expresión. Usa {format} para la fecha.",
"geometry": {
@@ -1810,6 +1812,33 @@
}
}
},
+ "attributeFilter": {
+ "placeholders": {
+ "default": "Buscar...",
+ "string": "Escribe texto para filtrar...",
+ "time": "Escriba el tiempo para filtrar ...",
+ "date": "Escriba la fecha para filtrar...",
+ "number": "Escriba el número...",
+ "range": "Por favor escribe ..."
+ },
+ "tooltips": {
+ "editMode": "La búsqueda rápida no está disponible en modo de edición",
+ "default": "Buscar...",
+ "string": "Escribe texto para filtrar...",
+ "time": "Escriba el tiempo para filtrar ...",
+ "number": "Escriba el número",
+ "date": "Escriba una fecha. Usa {format} para la fecha.",
+ "geometry": {
+ "disabled": "Hacer clic en el filtro en el mapa / Alt + arrastrar el mouse para dibujar un filtro de cuadro. Mantenga presionada también la tecla Ctrl para agregar más funciones al filtro actual",
+ "enabled": "Haga clic en el mapa para seleccionar una característica / Alt + arrastre del mouse para dibujar un filtro de cuadro. Mantenga presionada la tecla Ctrl y haga clic para seleccionar varias características",
+ "applied": "Limpiar la selección"
+ }
+ },
+ "rangeTab": {
+ "start": "Comenzar",
+ "end": "Fin"
+ }
+ },
"toolbar": {
"synchPopoverTitle": "Sincronizar mapa con filtro ",
"synchPopoverText": "Utilice esta herramienta para sincronizar el mapa con el filtro seleccionado",
diff --git a/web/client/translations/data.fr-FR.json b/web/client/translations/data.fr-FR.json
index 7538df75a9..1e492955d8 100644
--- a/web/client/translations/data.fr-FR.json
+++ b/web/client/translations/data.fr-FR.json
@@ -1794,6 +1794,7 @@
"placeholders": {
"default": "Recherche...",
"string": "Tapez le texte à filtrer...",
+ "time": "Tapez le temps à filtrer...",
"date": "Tapez la date à filtrer...",
"number": "Tapez un nombre ou une expression..."
},
@@ -1801,6 +1802,7 @@
"editMode": "La recherche rapide n'est pas disponible en mode édition",
"default": "Recherche...",
"string": "Tapez le texte à filtrer...",
+ "time": "Tapez le temps à filtrer...",
"number": "Tapez un nombre ou une expression... Exemples : 10, > 2, < 10",
"date": "Tapez une date ou une expression. Utilisez {format} pour la date.",
"geometry": {
@@ -1810,6 +1812,33 @@
}
}
},
+ "attributeFilter": {
+ "placeholders": {
+ "default": "Recherche...",
+ "string": "Tapez le texte à filtrer...",
+ "time": "Tapez le temps à filtrer...",
+ "date": "Tapez la date à filtrer...",
+ "number": "Tapez un nombre...",
+ "range": "Entrez s'il vous plait ..."
+ },
+ "tooltips": {
+ "editMode": "La recherche rapide n'est pas disponible en mode édition",
+ "default": "Recherche...",
+ "string": "Tapez le texte à filtrer...",
+ "time": "Tapez le temps à filtrer...",
+ "number": "Tapez un nombre...",
+ "date": "Tapez une date. Utilisez {format} pour la date.",
+ "geometry": {
+ "disabled": "Filtrer en cliquant sur la carte / Alt + glisser la souris pour dessiner un filtre de boîte. Maintenez également la touche Ctrl pour ajouter plus de fonctionnalités au filtre actuel",
+ "enabled": "Cliquez sur la carte pour sélectionner une caractéristique / Alt + glissement de la souris pour dessiner un filtre de boîte. Maintenez la touche Ctrl enfoncée et cliquez pour sélectionner plusieurs entités",
+ "applied": "Nettoyer la sélection"
+ }
+ },
+ "rangeTab": {
+ "start": "Commencer",
+ "end": "Fin"
+ }
+ },
"toolbar": {
"synchPopoverTitle": "Synchroniser la carte avec un filtre ",
"synchPopoverText": "Utilisez cet outil pour synchroniser la carte avec le filtre sélectionné",
diff --git a/web/client/translations/data.is-IS.json b/web/client/translations/data.is-IS.json
index 67a233c29b..1cde433caf 100644
--- a/web/client/translations/data.is-IS.json
+++ b/web/client/translations/data.is-IS.json
@@ -1737,6 +1737,26 @@
}
}
},
+ "attributeFilter": {
+ "placeholders": {
+ "default": "Search...",
+ "string": "Type text to filter...",
+ "date": "Type date to filter...",
+ "number": "Type number to filter..."
+ },
+ "tooltips": {
+ "editMode": "Quick search is not available in edit mode",
+ "default": "Search...",
+ "string": "Type text to filter...",
+ "number": "Type a number ...",
+ "date": "Type a date. Use {format} for the date.",
+ "geometry": {
+ "disabled": "Filter clicking on map / Alt + mouse drag to draw a box filter. Hold also ctrl to add more features to the current filter",
+ "enabled": "Click on map to select a feature / Alt + mouse drag to draw a box filter. Hold ctrl and click to select multiple features",
+ "applied": "Clean the selection"
+ }
+ }
+ },
"toolbar": {
"synchPopoverTitle": "Sync map with filter ",
"synchPopoverText": "Use this tool to synchronize the map with the selected filter",
diff --git a/web/client/translations/data.it-IT.json b/web/client/translations/data.it-IT.json
index 9ab1ada5aa..920a2f91c9 100644
--- a/web/client/translations/data.it-IT.json
+++ b/web/client/translations/data.it-IT.json
@@ -1793,6 +1793,7 @@
"placeholders": {
"default": "Cerca...",
"string": "Digita testo da cercare...",
+ "time": "Digita l'ora da cercare...",
"date": "Digita data da cercare...",
"number": "Digita numero o espressione..."
},
@@ -1800,6 +1801,7 @@
"editMode": "La ricerca veloce non è disponibile in modalità editing",
"default": "Cerca...",
"string": "Digita il testo da cercare...",
+ "time": "Digita l'ora da cercare...",
"number": "Digita un numero o una espressione. Esempi: 10, > 2, < 10",
"date":"Digita una data o un'espressione. Utilizza {format} per la data",
"geometry": {
@@ -1809,6 +1811,33 @@
}
}
},
+ "attributeFilter": {
+ "placeholders": {
+ "default": "Cerca...",
+ "string": "Digita testo da cercare...",
+ "time": "Digita l'ora da cercare...",
+ "date": "Digita data da cercare...",
+ "number": "Digita numero...",
+ "range": "Prego entra ..."
+ },
+ "tooltips": {
+ "editMode": "La ricerca veloce non è disponibile in modalità editing",
+ "default": "Cerca...",
+ "string": "Digita il testo da cercare...",
+ "time": "Digita l'ora da cercare...",
+ "number": "Digita un numero...",
+ "date":"Digita una data. Utilizza {format} per la data",
+ "geometry": {
+ "disabled": "Filtra facendo click sulla mappa / Alt + trascinamento per disegnare un filtro rettangolare. Tieni premuto anche ctrl per aggiungere features alla selezione corrente",
+ "enabled": "Clicca per selezionare le features / Alt + trascinamento per disegnare un filtro rettangolare. Tieni premuto anche ctrl per aggiungere features alla selezione corrente",
+ "applied": "Pulisci la selezione"
+ }
+ },
+ "rangeTab": {
+ "start": "Inizio",
+ "end": "Fine"
+ }
+ },
"toolbar": {
"synchPopoverTitle": "Sincronizza la mappa con il filtro ",
"synchPopoverText": "Usa questa icona per sincronizzare la mappa con il filtro selezionato",
diff --git a/web/client/translations/data.nl-NL.json b/web/client/translations/data.nl-NL.json
index b3c2bac16d..2fd7904b09 100644
--- a/web/client/translations/data.nl-NL.json
+++ b/web/client/translations/data.nl-NL.json
@@ -1702,6 +1702,26 @@
}
}
},
+ "attributeFilter": {
+ "placeholders": {
+ "default": "Zoeken...",
+ "string": "Typ tekst om te filteren...",
+ "date": "Typ de datum om te filteren...",
+ "number": "Typ een getal..."
+ },
+ "tooltips": {
+ "editMode": "Snel zoeken is niet beschikbaar in de editeermodus",
+ "default": "Zoeken...",
+ "string": "Typ tekst om te filteren...Standaard wordt in de hele tekstwaarde gezochtGebruik * om een willekeurig aantal karakters aan te gevenGebruik . om een enkel karakter aan te geven Plaats ! voor een speciaal teken (* of .) om het te behandelen als een normaal karakter",
+ "number": "Typ een getal ...",
+ "date": "Typ een datum . Gebruik {format} voor de datum.",
+ "geometry": {
+ "disabled": "Filter door te klikken op de kaart / Alt + sleep muis om filtervenster te tekenen. Hou ctrl ingedrukt om meerdere afzonderlijke objecten te selecteren",
+ "enabled": "Klik op de kaart om de resultaten te filteren / Alt + sleep muis om filtervenster te tekenen. Hou ctrl ingedrukt om meerdere afzonderlijke objecten te selecteren",
+ "applied": "Verwijder de filter met een muisklik"
+ }
+ }
+ },
"toolbar": {
"synchPopoverTitle": "Synchroniseer kaart met filter ",
"synchPopoverText": "Gebruik deze tool om de kaart te synchroniseren met de geselecteerde filter",
diff --git a/web/client/utils/FeatureGridUtils.js b/web/client/utils/FeatureGridUtils.js
index 16f9e3851f..2443710282 100644
--- a/web/client/utils/FeatureGridUtils.js
+++ b/web/client/utils/FeatureGridUtils.js
@@ -130,11 +130,11 @@ export const featureTypeToGridColumns = (
columnSettings = {},
fields = [],
{editable = false, sortable = true, resizable = true, filterable = true, defaultSize = 200, options = []} = {},
- {getEditor = () => {}, getFilterRenderer = () => {}, getFormatter = () => {}, getHeaderRenderer = () => {}} = {}) =>
+ {getEditor = () => {}, getFilterRenderer = () => {}, getFormatter = () => {}, getHeaderRenderer = () => {}, isWithinAttrTbl = false} = {}) =>
getAttributeFields(describe).filter(e => !(columnSettings[e.name] && columnSettings[e.name].hide)).map((desc) => {
const option = options.find(o => o.name === desc.name);
const field = fields.find(f => f.name === desc.name);
- return {
+ let columnProp = {
sortable,
key: desc.name,
width: columnSettings[desc.name] && columnSettings[desc.name].width || (defaultSize ? defaultSize : undefined),
@@ -150,6 +150,8 @@ export const featureTypeToGridColumns = (
formatter: getFormatter(desc, field),
filterRenderer: getFilterRenderer(desc, field)
};
+ if (isWithinAttrTbl) columnProp.width = 300;
+ return columnProp;
});
/**
* Create a column from the configruation. Maps the events to call a function with the whole property
@@ -267,7 +269,7 @@ export const gridUpdateToQueryUpdate = ({attribute, operator, value, type, filte
index: 0
}]),
filters: (oldFilterObj?.filters?.filter((filter) => attribute !== filter?.attribute) ?? []).concat(filters),
- filterFields: type === 'geometry' ? oldFilterObj.filterFields : !isNil(value)
+ filterFields: type === 'geometry' ? oldFilterObj.filterFields : !isNil(value) || operator === 'isNull'
? upsertFilterField((oldFilterObj.filterFields || []), {attribute: attribute}, {
attribute,
rowId: Date.now(),
diff --git a/web/client/utils/TimeUtils.js b/web/client/utils/TimeUtils.js
index 710962597a..e172e4c180 100644
--- a/web/client/utils/TimeUtils.js
+++ b/web/client/utils/TimeUtils.js
@@ -373,3 +373,21 @@ export const getStartEndDomainValues = (value) => {
let [startTime, endTime] = values?.filter(v => !!v) || [];
return [startTime, endTime];
};
+
+/**
+ * @param {Date|string} date to parse
+ * @return {string} date part of the TimeStamp for local time not UTC
+ **/
+export const getLocalTimePart = (date) => {
+ let dateToParse = date;
+ if (!isDate(date) & isString(date)) {
+ dateToParse = new Date(date);
+ }
+ let hours = dateToParse.getHours();
+ hours = hours < 10 ? "0" + hours : hours;
+ let minutes = dateToParse.getMinutes();
+ minutes = minutes < 10 ? "0" + minutes : minutes;
+ let seconds = dateToParse.getSeconds();
+ seconds = seconds < 10 ? "0" + seconds : seconds;
+ return `${hours}:${minutes}:${seconds}`;
+};
diff --git a/web/client/utils/__tests__/FeatureGridUtils-test.js b/web/client/utils/__tests__/FeatureGridUtils-test.js
index 677017bf1e..53f140dcd3 100644
--- a/web/client/utils/__tests__/FeatureGridUtils-test.js
+++ b/web/client/utils/__tests__/FeatureGridUtils-test.js
@@ -132,6 +132,39 @@ describe('FeatureGridUtils', () => {
expect(queryUpdateFilter.filterFields[2].value).toBe(10);
expect(queryUpdateFilter.filterFields[2].operator).toBe("<");
+ });
+ it('gridUpdateToQueryUpdate with isNull operator', () => {
+ const gridUpdate1 = {
+ type: "number",
+ attribute: "ATTRIBUTE",
+ operator: "isNull",
+ value: "",
+ rawValue: ""
+ };
+ const queryUpdateFilter = gridUpdateToQueryUpdate(gridUpdate1, {});
+ expect(queryUpdateFilter.filterFields.length).toBe(1);
+ expect(queryUpdateFilter.groupFields.length).toBe(1);
+ expect(queryUpdateFilter.groupFields[0].logic).toBe("AND");
+ expect(queryUpdateFilter.filterFields[0].value).toBe('');
+ expect(queryUpdateFilter.filterFields[0].operator).toBe("isNull");
+
+ });
+ it('gridUpdateToQueryUpdate with range operator', () => {
+ const gridUpdate1 = {
+ type: "date",
+ attribute: "ATTR_2_DATE",
+ operator: "><",
+ value: "2023-01-01 >< 2023-01-10",
+ rawValue: "2023-01-01 >< 2023-01-10"
+ };
+
+ const queryUpdateFilter = gridUpdateToQueryUpdate(gridUpdate1, {});
+ expect(queryUpdateFilter.filterFields.length).toBe(1);
+ expect(queryUpdateFilter.groupFields.length).toBe(1);
+ expect(queryUpdateFilter.groupFields[0].logic).toBe("AND");
+ expect(queryUpdateFilter.filterFields[0].value).toBe('2023-01-01 >< 2023-01-10');
+ expect(queryUpdateFilter.filterFields[0].operator).toBe("><");
+
});
it('gridUpdateToQueryUpdate with multiple numbers and multiple strings', () => {
const gridUpdate1 = {
@@ -333,6 +366,31 @@ describe('FeatureGridUtils', () => {
expect(fgColumns.editor).toBeTruthy();
});
});
+ it('test featureTypeToGridColumns with headerRenderer, filterRenderer, formatter and editor for attribute table', () => {
+ const DUMMY = () => {};
+ const describe = {featureTypes: [{properties: [{name: 'Test1', type: "xsd:number"}, {name: 'Test2', type: "xsd:number"}]}]};
+ const columnSettings = {name: 'Test1', hide: false};
+ const options = [{name: 'Test1', title: 'Some title', description: 'Some description'}];
+ const featureGridColumns = featureTypeToGridColumns(describe, columnSettings, [], {options}, {getHeaderRenderer: () => DUMMY, getFilterRenderer: () => DUMMY, getFormatter: () => DUMMY, getEditor: () => DUMMY, isWithinAttrTbl: true});
+ expect(featureGridColumns.length).toBe(2);
+ featureGridColumns.forEach((fgColumns, index) => {
+ if (index === 0) {
+ expect(fgColumns.description).toBe('Some description');
+ expect(fgColumns.title).toBe('Some title');
+ expect(fgColumns.showTitleTooltip).toBeTruthy();
+ }
+ expect(['Test1', 'Test2'].includes(fgColumns.name)).toBeTruthy();
+ expect(fgColumns.resizable).toBeTruthy();
+ expect(fgColumns.filterable).toBeTruthy();
+ expect(fgColumns.editable).toBeFalsy();
+ expect(fgColumns.sortable).toBeTruthy();
+ expect(fgColumns.width).toBe(300); // for attribute table
+ expect(fgColumns.headerRenderer).toBeTruthy();
+ expect(fgColumns.filterRenderer).toBeTruthy();
+ expect(fgColumns.formatter).toBeTruthy();
+ expect(fgColumns.editor).toBeTruthy();
+ });
+ });
it('featureTypeToGridColumns with fields', () => {
const describe = {featureTypes: [{properties: [{name: 'Test1', type: "xsd:number"}, {name: 'Test2', type: "xsd:number"}]}]};
const columnSettings = {name: 'Test1', hide: false};
diff --git a/web/client/utils/__tests__/TimeUtils-test.js b/web/client/utils/__tests__/TimeUtils-test.js
index 226ae379cc..157e7a84da 100644
--- a/web/client/utils/__tests__/TimeUtils-test.js
+++ b/web/client/utils/__tests__/TimeUtils-test.js
@@ -18,7 +18,8 @@ import {
getDatesInRange,
getLowestAndHighestDates,
getStartEndDomainValues,
- roundRangeResolution
+ roundRangeResolution,
+ getLocalTimePart
} from '../TimeUtils';
import { describeDomains } from '../../api/MultiDim';
@@ -166,4 +167,8 @@ describe('TimeUtils', () => {
resolution: 'PT10M'
});
});
+ it('test getLocalTimePart', () => {
+ expect(getLocalTimePart(new Date("2018-01-09T01:00:00"))).toBe("01:00:00");
+ expect(getLocalTimePart(new Date("2018-01-09T12:00:00"))).toBe("12:00:00");
+ });
});