UNPKG

react-mapfilter

Version:

These components are designed for viewing data in Mapeo. They share a common interface:

452 lines (365 loc) 17.6 kB
"use strict"; var _interopRequireWildcard = require("@babel/runtime-corejs3/helpers/interopRequireWildcard"); var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault"); var _Object$defineProperty = require("@babel/runtime-corejs3/core-js-stable/object/define-property"); require("core-js/modules/es.function.name"); require("core-js/modules/es.object.to-string"); require("core-js/modules/es.regexp.to-string"); _Object$defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _getIterator2 = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/get-iterator")); var _getIteratorMethod2 = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/get-iterator-method")); var _symbol = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/symbol")); var _from = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/array/from")); var _typeof2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/typeof")); var _find = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/find")); var _keys = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/keys")); var _map = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/map")); var _values = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/values")); var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/toConsumableArray")); var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes")); var _every = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/every")); var _isArray = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/array/is-array")); var _slice = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/slice")); var _keys2 = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/keys")); var _forEach = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/for-each")); var _sort = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/sort")); var _stringify = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/json/stringify")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/defineProperty")); var _assign = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/assign")); var _filter = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/filter")); var _map2 = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/map")); var _react = _interopRequireWildcard(require("react")); var _styles = require("@material-ui/core/styles"); var _List = _interopRequireDefault(require("@material-ui/core/List")); var _reactIntl = require("react-intl"); var _isEqual = _interopRequireDefault(require("lodash/isEqual")); var _omit = _interopRequireDefault(require("lodash/omit")); var _dateFns = _interopRequireDefault(require("@date-io/date-fns")); var _pickers = require("@material-ui/pickers"); var _index = require("../lib/data_analysis/index"); var _DiscreteFilter = _interopRequireDefault(require("./DiscreteFilter")); var _DateFilter = _interopRequireDefault(require("./DateFilter")); var _stats = _interopRequireDefault(require("../stats")); var _FormattedFieldname = _interopRequireDefault(require("../internal/FormattedFieldname")); function _createForOfIteratorHelper(o, allowArrayLike) { var it; if (typeof _symbol.default === "undefined" || (0, _getIteratorMethod2.default)(o) == null) { if ((0, _isArray.default)(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = (0, _getIterator2.default)(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; } function _unsupportedIterableToArray(o, minLen) { var _context9; if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = (0, _slice.default)(_context9 = Object.prototype.toString.call(o)).call(_context9, 8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return (0, _from.default)(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } var m = (0, _reactIntl.defineMessages)({ // Button text to change which fields are shown and filterable in the filter pane editFilters: { "id": "eO0bIg==", "defaultMessage": 'Edit Filters…' }, // Label for filter by date observation was created created: { "id": "pxFasw==", "defaultMessage": 'Date of observation' }, // Label for filter by date observation was modified (e.g. edited by a user) modified: { "id": "eykPgw==", "defaultMessage": 'Modified' }, // Label for filter by category (e.g. the preset) preset: { "id": "KdbzkA==", "defaultMessage": 'Category' } }); var memoizedStats = (0, _index.createMemoizedStats)(); // Stats just for the created and modified fields. The other stat instance we // use is for memoized stats of observation tags which does not include these // top-level props var getTimestampStats = function getTimestampStats(observations /*: Observation[]*/ ) /*: Statistics*/ { return memoizedStats((0, _map2.default)(observations).call(observations, function (o) { return { $created: o.created_at, $modified: o.timestamp }; })); }; var FilterPanel = function FilterPanel(_ref) { var _context2, _context3; var filter = (0, _filter.default)(_ref), onChangeFilter = _ref.onChangeFilter, _ref$observations = _ref.observations, observations = _ref$observations === void 0 ? [] : _ref$observations, _ref$fields = _ref.fields, fields = _ref$fields === void 0 ? [] : _ref$fields, _ref$presets = _ref.presets, presets = _ref$presets === void 0 ? [] : _ref$presets; var cx = useStyles(); var _useIntl = (0, _reactIntl.useIntl)(), t = _useIntl.formatMessage; var filterByField /*: { [fieldId: string]: Filter | null }*/ = {}; var filterError; try { filterByField = parseFilter(filter); } catch (e) { console.log(filter); console.warn(e); filterError = false; } var timestampStats = (0, _react.useMemo)(function () { return getTimestampStats(observations); }, [observations]); var stats = (0, _react.useMemo)(function () { return (0, _stats.default)(observations); }, [observations]); var handleChangeFilter = function handleChangeFilter(fieldId) { return function (filter) { var newFilterByField = filter == null ? (0, _omit.default)(filterByField, fieldId) : (0, _assign.default)({}, filterByField, (0, _defineProperty2.default)({}, fieldId, filter)); var newFilter = compileFilter(newFilterByField); onChangeFilter(newFilter); }; }; // Filters are shown for: date the observation was created, date modified // (edited), the category (preset), and any fields defined in the preset which // are select_one, date or date_time. Currently we don't show presets for // free-form text fields (which could have too many values, or too many ways // of spelling things to makes sense). var filterFields = (0, _react.useMemo)(function () { var _context; var $createdId = (0, _stringify.default)(['$created']); var $presetId = (0, _stringify.default)(['$preset']); var filterFields /*: { [fieldId: string]: Field }*/ = (0, _defineProperty2.default)({}, $createdId, { id: $createdId, key: ['$created'], label: t(m.created), type: 'datetime', min_value: timestampStats[$createdId] && timestampStats[$createdId].datetime.min, max_value: timestampStats[$createdId] && timestampStats[$createdId].datetime.max }); var presetOptions = (0, _map2.default)(_context = (0, _sort.default)(presets // .filter( // preset => // stats['categoryId'] && // stats['categoryId'].string.values.has(preset.id) // ) ).call(presets, presetCompare)).call(_context, function (preset) { return { value: preset.id, label: preset.name }; }); filterFields[$presetId] = { id: $presetId, key: ['$preset'], label: t(m.preset), type: 'select_one', options: presetOptions }; // Enable filtering by any select_one, date or date_time field that is // defined in the preset, but add in options (for select_one) and min/max // (for dates) from the actual data, since the data could include values // outside the range defined in the preset (0, _forEach.default)(fields).call(fields, function (field) { var fieldId = (0, _stringify.default)(field.key); var fieldStats = stats[fieldId]; if (field.type === 'select_one') { // $FlowFixMe filterFields[fieldId] = (0, _assign.default)({}, field, { options: combineOptionsWithStats(field.options, fieldStats) }); } else if (field.type === 'date' || field.type === 'datetime') { // $FlowFixMe filterFields[fieldId] = (0, _assign.default)({}, field, { min_value: fieldStats && fieldStats[field.type].min, max_value: fieldStats && fieldStats[field.type].max }); } }); return filterFields; }, [t, timestampStats, presets, fields, stats]); (0, _react.useEffect)(function () { if (!filterError) return; onChangeFilter(null); }, [filterError, onChangeFilter]); if (filterError) return null; return /*#__PURE__*/_react.default.createElement(_pickers.MuiPickersUtilsProvider, { utils: _dateFns.default }, /*#__PURE__*/_react.default.createElement(_List.default, { className: cx.list }, (0, _filter.default)(_context2 = (0, _map2.default)(_context3 = (0, _keys2.default)(filterFields)).call(_context3, function (id) { var field = filterFields[id]; if (!field) return; var fieldId = (0, _stringify.default)(field.key); switch (field.type) { case 'select_one': return /*#__PURE__*/_react.default.createElement(_DiscreteFilter.default, { key: field.id, fieldKey: field.key, label: /*#__PURE__*/_react.default.createElement(_FormattedFieldname.default, { field: field }), filter: filterByField[fieldId], options: field.options, onChangeFilter: handleChangeFilter(fieldId) }); case 'date': case 'datetime': return /*#__PURE__*/_react.default.createElement(_DateFilter.default, { key: field.id, fieldKey: field.key, label: /*#__PURE__*/_react.default.createElement(_FormattedFieldname.default, { field: field }), filter: filterByField[fieldId], min: field.min_value || '2001-01-01', max: field.max_value || new Date().toISOString(), onChangeFilter: handleChangeFilter(fieldId) }); } })).call(_context2, Boolean))); }; var _default = FilterPanel; exports.default = _default; var comparisonOps = ['<=', '>=']; var membershipOps = ['in', '!in']; // Parse a filter and return filter expressions by field id function parseFilter(filter /*: Filter | null*/ ) /*: { [fieldId: string]: Filter | null }*/ { var _context4; var filterByField = {}; if (filter == null) return filterByField; if (!isValidFilter(filter)) throw new Error('Unsupported filter expression'); (0, _forEach.default)(_context4 = (0, _slice.default)(filter).call(filter, 1)).call(_context4, function (subFilter) { if (!(0, _isArray.default)(subFilter)) return; if (subFilter[0] === 'all') { var _fieldId = (0, _stringify.default)(subFilter[1][1]); filterByField[_fieldId] = subFilter; } else { var _fieldId2 = (0, _stringify.default)(subFilter[1]); filterByField[_fieldId2] = subFilter; } }); return filterByField; } function compileFilter(filterByField /*: { [fieldId: string]: Filter }*/ ) /*: Filter | null*/ { var _context5; var filter = ['all']; (0, _forEach.default)(_context5 = (0, _keys2.default)(filterByField)).call(_context5, function (fieldId) { return filter.push(filterByField[fieldId]); }); if (filter.length === 1) return null; // $FlowFixMe return filter; } // Currently we only support a very specific filter structure function isValidFilter(filter) /*: boolean*/ { var _context6; if (!(0, _isArray.default)(filter)) return false; if (filter[0] !== 'all') return false; return (0, _every.default)(_context6 = (0, _slice.default)(filter).call(filter, 1)).call(_context6, function (subFilter) { if (!(0, _isArray.default)(subFilter)) return false; if (subFilter[0] === 'all') { var _context7; if (subFilter.length < 2) return false; var key; return (0, _every.default)(_context7 = (0, _slice.default)(subFilter).call(subFilter, 1)).call(_context7, function (subFilter) { if (!(0, _isArray.default)(subFilter)) return false; if (!(0, _includes.default)(comparisonOps).call(comparisonOps, subFilter[0])) return false; if (subFilter.length !== 3) return false; if (key && !(0, _isEqual.default)(key, subFilter[1])) return false; key = subFilter[1]; return true; }); } if (!(0, _includes.default)(membershipOps).call(membershipOps, subFilter[0])) return false; if (subFilter.length < 3) return false; return true; }); } // Takes a list of options for a select_one field defined in a preset, and adds // in any values that exist in the data that are not already listed in the field // definition. Mapeo Desktop (currently) allows you to add new option to a // select_one field which aren't defined in the preset, and this allows you to // filter by these new values function combineOptionsWithStats(fieldOptions /*: SelectOptions*/ , fieldStats /*: FieldStatistic*/ ) /*: SelectOptions*/ { var _context8; if (!fieldStats) return fieldOptions; var optionsWithStats = (0, _toConsumableArray2.default)(fieldOptions); (0, _forEach.default)(_context8 = (0, _keys2.default)(fieldStats)).call(_context8, function (valueType) { if (!fieldStats[valueType] || !(0, _values.default)(fieldStats[valueType])) return; var values = (0, _values.default)(fieldStats[valueType]); if (!(values instanceof _map.default)) return; var _iterator = _createForOfIteratorHelper((0, _keys.default)(values).call(values)), _step; try { var _loop = function _loop() { var value = _step.value; if ((0, _find.default)(optionsWithStats).call(optionsWithStats, function (o) { return o === value || (0, _typeof2.default)(o) === 'object' && o !== null && o.value === value; })) return "continue"; optionsWithStats.push(value); }; for (_iterator.s(); !(_step = _iterator.n()).done;) { var _ret = _loop(); if (_ret === "continue") continue; } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } }); return optionsWithStats; } var useStyles = (0, _styles.makeStyles)(function (theme) { return { list: { paddingTop: 0, paddingBottom: 0, overflowY: 'scroll' }, settingsItem: { paddingTop: 8, paddingBottom: 8 }, listIcon: { minWidth: 40 } }; }); // Sort presets by sort property and then by name, then filter only point presets function presetCompare(a, b) { if (typeof (0, _sort.default)(a) !== 'undefined' && typeof (0, _sort.default)(b) !== 'undefined') { // If sort value is the same, then sort by name if ((0, _sort.default)(a) === (0, _sort.default)(b)) return compareStrings(a.name, b.name); // Lower sort numbers come before higher numbers else return (0, _sort.default)(a) - (0, _sort.default)(b); } else if (typeof (0, _sort.default)(a) !== 'undefined') { // If a has a sort field but b doesn't, a comes first return -1; } else if (typeof (0, _sort.default)(b) !== 'undefined') { // if b has a sort field but a doesn't, b comes first return 1; } else { // if neither have sort defined, compare by name return compareStrings(a.name, b.name); } } function compareStrings() { var a = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; var b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; return a.toLowerCase().localeCompare(b.toLowerCase()); } //# sourceMappingURL=FilterPanel.js.map