+ {this.props.isShownOperators ? this.renderOperatorField() : null}
{this.renderTooltip(this.renderInput())}
);
@@ -63,7 +133,7 @@ class AttributeFilter extends React.PureComponent {
handleChange = (e) => {
const value = e.target.value;
this.setState({value});
- this.props.onChange({value, attribute: this.props.column && this.props.column.key});
+ 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..706baaf8dc 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 CustomDateTimePickerWithRange from '../../../misc/datetimepicker/CustomDateTimePickerWithRange';
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"
+})(CustomDateTimePickerWithRange);
+
class DateFilter extends AttributeFilter {
static propTypes = {
@@ -45,6 +52,7 @@ class DateFilter extends AttributeFilter {
if (this.props.column.filterable === false) {
return this.handleChange(date, stringDate)}
+ />
+ );
+ }
return ();
}
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 });
}
}
diff --git a/web/client/components/data/featuregrid/filterRenderers/DateTimeFilter.jsx b/web/client/components/data/featuregrid/filterRenderers/DateTimeFilter.jsx
index 8dd637fa25..311b6ecc61 100644
--- a/web/client/components/data/featuregrid/filterRenderers/DateTimeFilter.jsx
+++ b/web/client/components/data/featuregrid/filterRenderers/DateTimeFilter.jsx
@@ -15,7 +15,7 @@ export default compose(
value: null
}),
withHandlers({
- onChange: props => ({ value, attribute, stringValue } = {}) => {
+ onChange: props => ({ value, attribute, stringValue, inputOperator } = {}) => {
const match = /\s*(!==|!=|<>|<=|>=|===|==|=|<|>)?(.*)/.exec(stringValue);
const operator = match[1];
let enhancedOperator = match[1] || '=';
@@ -28,7 +28,7 @@ export default compose(
props.onValueChange(value);
props.onChange({
value: { startDate: value, operator },
- operator: enhancedOperator,
+ operator: inputOperator || enhancedOperator,
type: props.type,
attribute
});
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..b4caf1085f 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", // need to read operator from redux beased on operator selected option
type: 'string',
attribute
});
diff --git a/web/client/components/data/featuregrid/filterRenderers/index.js b/web/client/components/data/featuregrid/filterRenderers/index.js
index c41567041f..e69bc7be26 100644
--- a/web/client/components/data/featuregrid/filterRenderers/index.js
+++ b/web/client/components/data/featuregrid/filterRenderers/index.js
@@ -15,13 +15,13 @@ 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(() =>({type: props.type, isShownOperators: props.isShownOperators || false}))(DefaultFilter),
+ "string": (props) => withProps(() =>({type: 'string', isShownOperators: props.isShownOperators || false}))(StringFilter),
+ "number": (props) => withProps(() =>({type: 'number', isShownOperators: props.isShownOperators || false}))(NumberFilter),
+ "int": (props) => withProps(() =>({type: 'integer', isShownOperators: props.isShownOperators || false}))(NumberFilter),
+ "date": (props) => withProps(() =>({type: "date", isShownOperators: props.isShownOperators || false}))(DateTimeFilter),
+ "time": (props) => withProps(() =>({type: "time", isShownOperators: props.isShownOperators || false}))(DateTimeFilter),
+ "date-time": (props) => withProps(() =>({type: "date-time", isShownOperators: props.isShownOperators || false}))(DateTimeFilter),
"geometry": () => GeometryFilter
};
@@ -46,11 +46,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, isShownOperators}) => {
if (name) {
return getFilterRendererByName(name);
}
- return types[type] ? types[type](type) : types.defaultFilter(type);
+ return types[type] ? types[type]({type, isShownOperators}) : types.defaultFilter({type, isShownOperators});
};
diff --git a/web/client/components/misc/datetimepicker/CustomDateTimePickerWithRange.js b/web/client/components/misc/datetimepicker/CustomDateTimePickerWithRange.js
new file mode 100644
index 0000000000..de6c98379f
--- /dev/null
+++ b/web/client/components/misc/datetimepicker/CustomDateTimePickerWithRange.js
@@ -0,0 +1,591 @@
+/*
+ * Copyright 2019, GeoSolutions Sas.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+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 } from 'react-bootstrap';
+import { isDate, isNil } from 'lodash';
+import OverlayTrigger from '../OverlayTrigger';
+import Hours from './Hours';
+
+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 DateTimePicker
+ * 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 DateTimePicker 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'
+ }
+
+ state = {
+ openRangeContainer: false,
+ openRangeInputs: 'start', // start, end
+ openDateTCalendar: false, // start, end
+ openDateTTime: false, // start, end
+ focused: false,
+ inputValue: '',
+ operator: '',
+ date: null
+ }
+
+ componentDidMount() {
+ const { value, operator } = this.props;
+ this.setDateFromValueProp(value, operator);
+ }
+
+ componentDidUpdate(prevProps) {
+ if (prevProps.value !== this.props.value || prevProps.operator !== this.props.operator) {
+ 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, disabled, isFullWidth) => {
+ if (toolTip) {
+ return ({toolTip}}>
+
+ );
+ }
+ return ();
+ }
+ renderHoursRange = () =>{
+ const { inputValue, operator, focused, openRangeInputs } = this.state;
+ const { toolTip, placeholder, tabIndex } = 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
+ return acc;
+ }
+ acc[key] = this.props[key];
+ return acc;
+
+ }, {});
+ // const calendarVisible = open === 'date';
+ // const timeVisible = open === 'time';
+ return (
+
+
+
+ Start
+
+ Please Enter ...
+
+
+
+ End
+
+ Please Enter ...
+
+
+
+
+
+ {this.renderInput(inputValue, operator, toolTip, placeholder, tabIndex, false, true)}
+
+
+
+
+
+
+
+
+ {this.renderInput(inputValue, operator, toolTip, placeholder, tabIndex, false, true)}
+
+
+
+
+
+
+
+
+
+ );
+ }
+ renderHours = () =>{
+ const { inputValue, operator, focused, openRangeContainer } = this.state;
+ const { toolTip, placeholder, tabIndex, 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
+ // return acc;
+ // }
+ // acc[key] = this.props[key];
+ // return acc;
+
+ // }, {});
+ // const calendarVisible = open === 'date';
+ // const timeVisible = open === 'time';
+ return (
+
+ {this.renderInput(inputValue, operator, toolTip, placeholder, tabIndex, false, true, true)}
+
+
+
+ { openRangeContainer && <>
+
+ {this.renderHoursRange()}
+
+ > }
+
+
+ );
+ }
+ renderCalendarRange = () =>{
+ const { openRangeInputs } = this.state;
+ // const { 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
+ return acc;
+ }
+ acc[key] = this.props[key];
+ return acc;
+
+ }, {});
+ // const calendarVisible = type === 'date';
+ // const timeVisible = open === 'time';
+ return (
+
+
+
+ Start
+
+ Please Enter ...
+
+
+
+ End
+
+ Please Enter ...
+
+
+
+
+
+ );
+ }
+ renderCalendar = () =>{
+ const { inputValue, operator, focused, openRangeContainer, popupPosition } = this.state;
+ const { toolTip, placeholder, tabIndex } = 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
+ // return acc;
+ // }
+ // acc[key] = this.props[key];
+ // return acc;
+
+ // }, {});
+ // const calendarVisible = type === 'date';
+ // const timeVisible = open === 'time';
+ return (
+
+ {this.renderInput(inputValue, operator, toolTip, placeholder, tabIndex, true, false, true)}
+
+
+
+ { openRangeContainer && <>
+
+ { this.renderCalendarRange() }
+
+ >}
+
+ );
+ }
+ renderDateTimeRange = () =>{
+ const { inputValue, operator, openRangeInputs, openDateTTime } = this.state;
+ const { toolTip, placeholder, tabIndex } = this.props;
+
+ // const { 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
+ return acc;
+ }
+ acc[key] = this.props[key];
+ return acc;
+
+ }, {});
+ // const calendarVisible = type === 'date';
+ // const timeVisible = open === 'time';
+ return (
+
+
+
+ Start
+
+ Please Enter ...
+
+
+
+ End
+
+ Please Enter ...
+
+
+
+
+
+
+
+
+ {this.renderInput(inputValue, operator, toolTip, placeholder, tabIndex, false, true, false, true)}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {this.renderInput(inputValue, operator, toolTip, placeholder, tabIndex, false, true, false, true)}
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+ renderCalendarTimeDate = () =>{
+ const { openRangeContainer, inputValue, operator, focused } = this.state;
+ const { toolTip, placeholder, tabIndex, 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
+ // return acc;
+ // }
+ // acc[key] = this.props[key];
+ // return acc;
+
+ // }, {});
+ // const calendarVisible = open === 'date';
+ // const timeVisible = open === 'time';
+ return (
+
+ {this.renderInput(inputValue, operator, toolTip, placeholder, tabIndex, true, true, true)}
+
+
+
+ { openRangeContainer && <>
+
+ {this.renderDateTimeRange()}
+
+ > }
+
+ );
+ }
+
+ render() {
+ // const { open } = this.state;
+ const { type } = 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
+ // return acc;
+ // }
+ // acc[key] = this.props[key];
+ // return acc;
+
+ // }, {});
+ // const calendarVisible = open === 'date';
+ // const timeVisible = open === 'time';
+ 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 = () => {
+ if (this.ignoreBlur) {
+ return;
+ }
+ this.setState({ open: '', focused: false });
+ }
+ rangeContainerMouseLeaveHandler = () => {
+ this.setState({ openRangeContainer: false });
+ }
+ handleMouseDown = () => {
+ this.ignoreBlur = true;
+ }
+
+ toggleStart = () => {
+ if (this.state.openRangeInputs !== 'start') {
+ this.setState({ openRangeInputs: 'start', openDateTTime: false, openDateTCalendar: false });
+ }
+ }
+ toggleEnd = () => {
+ if (this.state.openRangeInputs !== 'end') {
+ this.setState({ openRangeInputs: 'end', openDateTTime: false, openDateTCalendar: false });
+ }
+ }
+ toggleTimeInDateTime = () => {
+ this.setState(prev => ({ openDateTTime: !prev.openDateTTime }));
+ }
+ toggleHandler = () => {
+ this.setState(prevState => ({ openRangeContainer: !prevState.openRangeContainer, openDateTTime: 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}`);
+ }
+ }
+
+ setDateFromValueProp = (value, operator) => {
+ if (isDate(value)) {
+ const inputValue = this.format(value);
+ 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({ open: '' });
+ }
+
+ openHandler = () => {
+ const { calendar, time } = this.props;
+ return !calendar && time ? this.setState({ open: 'time' }) : calendar && !time ? this.setState({ open: 'date' }) : '';
+ }
+
+ handleKeyDown = e => {
+ const { open } = this.state;
+ const timeVisible = open === 'time';
+ const calVisible = open === 'date';
+
+ if (e.defaultPrevented) return;
+
+ if (e.key === 'Escape') {
+ // escape key should close the calendar or time popup
+ this.close();
+ return;
+ }
+ if (e.altKey && e.key === 'ArrowDown') {
+ // user press control/option key for mac together with arrow down
+ // this should open the popup
+ e.preventDefault();
+ this.open();
+ return;
+ }
+
+ if (e.altKey && e.key === 'ArrowUp') {
+ // user press control/option key for mac together with arrow up
+ // this should close the popup
+ e.preventDefault();
+ this.close();
+ return;
+ }
+
+ if (timeVisible) {
+ this.timeRef.handleKeyDown(e);
+ }
+
+ if (calVisible) {
+ this.calRef.refs.inner.handleKeyDown(e);
+ }
+
+ if (!timeVisible && !calVisible && e.key === 'Enter') {
+ // enter key is pressed while hours and calendar are not visible
+ // 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}`);
+ }
+ }
+
+
+ handleValueChange = (event) => {
+ const { value } = event.target;
+ const match = /\s*(!==|!=|<>|<=|>=|===|==|=|<|>)?(.*)/.exec(value);
+ this.setState({ inputValue: match[2], operator: match[1] || '' });
+ this.inputFlush = true;
+ }
+
+ handleCalendarChange = value => {
+ const date = setTime(value, this.state.date || new Date());
+ const inputValue = this.format(date);
+ this.setState({ date, inputValue, open: '' });
+ this.props.onChange(date, `${this.state.operator}${inputValue}`);
+ }
+
+ handleTimeSelect = time => {
+ const selectedDate = this.state.date || new Date();
+ const date = setTime(selectedDate, time.date);
+ const inputValue = this.format(date);
+ this.setState({ date, inputValue, open: '' });
+ this.props.onChange(date, `${this.state.operator}${inputValue}`);
+ }
+
+ attachTimeRef = ref => (this.timeRef = ref)
+
+ attachCalRef = ref => (this.calRef = ref)
+
+}
+export default DateTimePicker;
+
diff --git a/web/client/plugins/featuregrid/FeatureEditor.jsx b/web/client/plugins/featuregrid/FeatureEditor.jsx
index 935efab545..65e9ba5fd5 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.isShownOperators);
+ }, [props.describe, props.fields]);
return (
{ props.onSizeChange(size, dockProps); }}>
@@ -209,6 +211,7 @@ const FeatureDock = (props = {
footer={getFooter(props)}>
{getDialogs(props.tools)}
({}),
(dispatch) => ({
onMount: bindActionCreators(setUp, dispatch),
diff --git a/web/client/plugins/featuregrid/panels/index.jsx b/web/client/plugins/featuregrid/panels/index.jsx
index 3d5dc6f2f5..8009a5083b 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 = [], isShownOperators) => {
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, isShownOperators}))
};
}, {});
}
diff --git a/web/client/reducers/featuregrid.js b/web/client/reducers/featuregrid.js
index ebfa22e0e7..b1aecd4a05 100644
--- a/web/client/reducers/featuregrid.js
+++ b/web/client/reducers/featuregrid.js
@@ -39,6 +39,7 @@ import {
OPEN_FEATURE_GRID,
CLOSE_FEATURE_GRID,
UPDATE_FILTER,
+ UPDATE_OPERATOR_QUICK_FILTER,
INIT_PLUGIN,
SIZE_CHANGE,
STORE_ADVANCED_SEARCH_FILTER,
@@ -367,6 +368,22 @@ function featuregrid(state = emptyResultsState, action) {
}
return state;
}
+ case UPDATE_OPERATOR_QUICK_FILTER : {
+ const {attribute, operator} = (action || {});
+ if (attribute) {
+ const filter = state.filters[attribute];
+ return assign({}, state, {
+ filters: {
+ [attribute]: {
+ ...filter,
+ operator: operator,
+ value: undefined
+ }
+ }
+ });
+ }
+ return state;
+ }
case UPDATE_FILTER : {
const {attribute} = (action.update || {});
if (attribute && action.append) {
diff --git a/web/client/themes/default/less/featuregrid.less b/web/client/themes/default/less/featuregrid.less
index 2d7c22da24..8d0e2f32e1 100644
--- a/web/client/themes/default/less/featuregrid.less
+++ b/web/client/themes/default/less/featuregrid.less
@@ -59,6 +59,36 @@
}
}
+// ****************
+/*styles related to feature grid columns filters*/
+// ****************
+.feature-grid-drag-handle-show{
+ div.form-group{
+ display:flex;
+ div.rw-dropdownlist.rw-widget{
+ min-width:fit-content;
+ }
+ div.rw-popup.rw-widget, div.rw-widget{
+ width:100%;
+ }
+ div.rw-popup.rw-widget{
+ height: 150px;
+ }
+ input.form-control.input-sm, div.rw-popup.rw-widget ul.rw-list{
+ height:100%;
+ }
+ div.rw-widget div.rw-input{
+ padding-right: 0.5rem;
+ }
+ .range-time-input div.rw-calendar-popup.rw-popup-container{
+ left: -2.5rem;
+ overflow-x:hidden;
+ overflow-y:auto;
+ }
+ }
+
+}
+
// **************
// 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 ab8eafb870..41b7a61774 100644
--- a/web/client/themes/default/less/react-data-grid.less
+++ b/web/client/themes/default/less/react-data-grid.less
@@ -297,6 +297,10 @@
.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{
+ width: auto;
+ height:100%;
+ }
}
}
}
diff --git a/web/client/utils/FeatureGridUtils.js b/web/client/utils/FeatureGridUtils.js
index 19e41a2643..a0fbe91435 100644
--- a/web/client/utils/FeatureGridUtils.js
+++ b/web/client/utils/FeatureGridUtils.js
@@ -129,11 +129,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 = () => {}, isShownOperators = 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),
@@ -147,8 +147,10 @@ export const featureTypeToGridColumns = (
filterable,
editor: getEditor(desc, field),
formatter: getFormatter(desc, field),
- filterRenderer: getFilterRenderer(desc, field)
+ filterRenderer: getFilterRenderer(desc, field, isShownOperators)
};
+ if (isShownOperators) columnProp.width = 300;
+ return columnProp;
});
/**
* Create a column from the configruation. Maps the events to call a function with the whole property
@@ -266,7 +268,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(),