react-mapfilter
Version:
These components are designed for viewing data in Mapeo. They share a common interface:
452 lines (365 loc) • 17.6 kB
JavaScript
;
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