UNPKG

qwc2-lts

Version:
503 lines (501 loc) 24.4 kB
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); } function _possibleConstructorReturn(t, e) { if (e && ("object" == _typeof(e) || "function" == typeof e)) return e; if (void 0 !== e) throw new TypeError("Derived constructors may only return object or undefined"); return _assertThisInitialized(t); } function _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); return e; } function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); } function _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); } function _inherits(t, e) { if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, "prototype", { writable: !1 }), e && _setPrototypeOf(t, e); } function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } /** * Copyright 2017-2024 Sourcepole AG * 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 from 'react'; import { connect } from 'react-redux'; import isEmpty from 'lodash.isempty'; import isEqual from 'lodash.isequal'; import PropTypes from 'prop-types'; import { v1 as uuidv1 } from 'uuid'; import { setEditContext, clearEditContext } from '../actions/editing'; import { LayerRole, addLayerFeatures, removeLayer, refreshLayer, changeLayerProperty } from '../actions/layers'; import { setSnappingConfig } from '../actions/map'; import { setCurrentTask, setCurrentTaskBlocked } from '../actions/task'; import AttributeForm from '../components/AttributeForm'; import Icon from '../components/Icon'; import PickFeature from '../components/PickFeature'; import SideBar from '../components/SideBar'; import ButtonBar from '../components/widgets/ButtonBar'; import ConfigUtils from '../utils/ConfigUtils'; import EditingInterface from '../utils/EditingInterface'; import { getFeatureTemplate } from '../utils/EditingUtils'; import LayerUtils from '../utils/LayerUtils'; import LocaleUtils from '../utils/LocaleUtils'; import MapUtils from '../utils/MapUtils'; import './style/Editing.css'; /** * Allows editing geometries and attributes of datasets. * * The attribute form is generated from the QGIS attribute form configuration. * * This plugin queries the dataset via the editing service specified by * `editServiceUrl` in `config.json` (by default the `qwc-data-service`). */ var Editing = /*#__PURE__*/function (_React$Component) { function Editing() { var _this; _classCallCheck(this, Editing); for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } _this = _callSuper(this, Editing, [].concat(args)); _defineProperty(_this, "state", { selectedLayer: null, selectedLayerVisibility: null, pickedFeatures: null, busy: false, minimized: false, drawPick: false }); _defineProperty(_this, "onShow", function () { if (_this.props.taskData) { _this.changeSelectedLayer(_this.props.taskData.layer, "Pick", _this.props.taskData.feature); } else { _this.changeSelectedLayer(_this.state.selectedLayer, "Pick"); } _this.props.setSnappingConfig(_this.props.snapping, _this.props.snappingActive); }); _defineProperty(_this, "onHide", function () { _this.props.clearEditContext('Editing'); _this.setLayerVisibility(_this.state.selectedLayer, _this.state.selectedLayerVisibility); _this.setState({ minimized: false, drawPick: false }); }); _defineProperty(_this, "renderBody", function () { if (!_this.props.theme || isEmpty(_this.props.theme.editConfig)) { return /*#__PURE__*/React.createElement("div", { role: "body", style: { padding: "1em" } }, LocaleUtils.tr("editing.noeditablelayers")); } var editConfig = _this.props.theme.editConfig; var curConfig = editConfig[_this.state.selectedLayer]; if (!curConfig) { return /*#__PURE__*/React.createElement("div", { role: "body", style: { padding: "1em" } }, LocaleUtils.tr("editing.noeditablelayers")); } var editPermissions = curConfig.permissions || {}; var actionButtons = []; actionButtons.push({ key: 'Pick', icon: 'pick', label: LocaleUtils.tr("editing.pick"), data: { action: 'Pick', geomReadOnly: false, feature: null } }); if (editPermissions.creatable !== false) { // Draw button will appear by default if no permissions are defined in theme editConfig or when creatable permission is set actionButtons.push({ key: 'Draw', icon: 'editdraw', label: LocaleUtils.tr("editing.draw"), data: { action: 'Draw', geomReadOnly: false } }); } if (ConfigUtils.havePlugin("AttributeTable")) { actionButtons.push({ key: 'AttribTable', icon: 'editing', label: LocaleUtils.tr("editing.attrtable"), data: { action: 'AttrTable' } }); } var featureSelection = null; if (_this.state.pickedFeatures) { var featureText = LocaleUtils.tr("editing.feature"); featureSelection = /*#__PURE__*/React.createElement("div", { className: "editing-feature-selection" }, /*#__PURE__*/React.createElement("select", { className: "combo editing-feature-select", disabled: _this.props.editContext.changed === true || _this.props.editContext.id !== _this.props.currentEditContext, onChange: function onChange(ev) { return _this.setEditFeature(ev.target.value); }, value: (_this.props.editContext.feature || {}).id || "" }, _this.state.pickedFeatures.map(function (feature) { return /*#__PURE__*/React.createElement("option", { key: feature.id, value: feature.id }, curConfig.displayField ? feature.properties[curConfig.displayField] : featureText + " " + feature.id); }))); } var pickBar = null; if (_this.props.allowCloneGeometry && (_this.props.editContext.action === "Draw" || _this.state.drawPick) && !(_this.props.editContext.feature || {}).geometry) { var pickButtons = [{ key: 'DrawPick', icon: 'pick', label: LocaleUtils.tr("editing.pickdrawfeature") }]; pickBar = /*#__PURE__*/React.createElement(ButtonBar, { active: _this.state.drawPick ? "DrawPick" : null, buttons: pickButtons, onClick: _this.toggleDrawPick }); } var attributeForm = null; if (_this.props.editContext.feature && (_this.props.editContext.action === "Pick" || _this.props.editContext.feature.geometry)) { attributeForm = /*#__PURE__*/React.createElement(AttributeForm, { editConfig: curConfig, editContext: _this.props.editContext, iface: _this.props.iface }); } var themeSublayers = _this.props.layers.reduce(function (accum, layer) { return layer.role === LayerRole.THEME ? accum.concat(LayerUtils.getSublayerNames(layer)) : accum; }, []); return /*#__PURE__*/React.createElement("div", { className: "editing-body" }, /*#__PURE__*/React.createElement("div", { className: "editing-layer-selection" }, /*#__PURE__*/React.createElement("select", { className: "combo editing-layer-select", disabled: _this.props.editContext.changed === true || _this.props.editContext.id !== _this.props.currentEditContext, onChange: function onChange(ev) { return _this.changeSelectedLayer(ev.target.value); }, value: _this.state.selectedLayer || "" }, Object.keys(editConfig).filter(function (layerId) { return themeSublayers.includes(layerId); }).map(function (layerId) { var layerName = editConfig[layerId].layerName; var match = LayerUtils.searchLayer(_this.props.layers, 'name', layerName, [LayerRole.THEME]); return /*#__PURE__*/React.createElement("option", { key: layerId, value: layerId }, match ? match.sublayer.title : layerName); }))), /*#__PURE__*/React.createElement(ButtonBar, { active: _this.state.drawPick ? "Draw" : _this.props.editContext.action, buttons: actionButtons, disabled: _this.props.editContext.changed || _this.props.editContext.id !== _this.props.currentEditContext, onClick: _this.actionClicked }), featureSelection, pickBar, attributeForm); }); _defineProperty(_this, "actionClicked", function (action, data) { _this.setState({ drawPick: false, pickedFeatures: null }); if (action === "AttribTable") { _this.props.setCurrentTask("AttributeTable", null, null, { layer: _this.state.selectedLayer }); } else if (action === "Draw") { var editConfig = _this.props.theme.editConfig; var curConfig = editConfig[_this.state.selectedLayer]; var featureSkel = { type: "Feature", properties: {} }; var mapPrefix = (curConfig.editDataset.match(/^[^.]+\./) || [""])[0]; getFeatureTemplate(curConfig, featureSkel, _this.props.iface, mapPrefix, _this.props.map.projection, function (feature) { _this.props.setEditContext('Editing', _objectSpread(_objectSpread({}, data), {}, { feature: feature, geomReadOnly: false })); }); } else { _this.props.setEditContext('Editing', _objectSpread({}, data)); } }); _defineProperty(_this, "pickFilter", function (feature) { var _this$props$theme$edi; var geomType = (_this$props$theme$edi = _this.props.theme.editConfig[_this.state.selectedLayer]) === null || _this$props$theme$edi === void 0 ? void 0 : _this$props$theme$edi.geomType; return feature.geometry && (feature.geometry.type === geomType || "Multi" + feature.geometry.type === geomType || feature.geometry.type.replace(/^Multi/, "") === geomType && feature.geometry.coordinates.length === 1); }); _defineProperty(_this, "geomPicked", function (layer, feature) { var _this$props$theme$edi2; var geomType = (_this$props$theme$edi2 = _this.props.theme.editConfig[_this.state.selectedLayer]) === null || _this$props$theme$edi2 === void 0 ? void 0 : _this$props$theme$edi2.geomType; var geometry = feature.geometry; if (geometry.type !== geomType) { if ("Multi" + feature.geometry.type === geomType) { // Convert picked geometry to multi-type geometry = { type: "Multi" + geometry.type, coordinates: [geometry.coordinates] }; } else if (geometry.type.replace(/^Multi/, "") === geomType && geometry.coordinates.length === 1) { // Convert picked geometry to single type geometry = { type: geometry.type.replace(/^Multi/, ""), coordinates: geometry.coordinates[0] }; } else { // Should not happen, mismatching geometries should already have been filtered from the list of choices return; } } var editFeature = { type: "Feature", geometry: geometry, id: uuidv1() }; _this.props.setEditContext('Editing', { action: "Draw", feature: editFeature, changed: true }); _this.setState({ drawPick: false }); }); _defineProperty(_this, "setLayerVisibility", function (selectedLayer, visibility) { if (selectedLayer !== null) { var path = []; var sublayer = null; var layer = _this.props.layers.find(function (l) { return l.role === LayerRole.THEME && (sublayer = LayerUtils.searchSubLayer(l, 'name', selectedLayer, path)); }); if (layer && sublayer) { var oldvisibility = sublayer.visibility; if (oldvisibility !== visibility && visibility !== null) { var recurseDirection = !oldvisibility ? "both" : "children"; _this.props.changeLayerProperty(layer.uuid, "visibility", visibility, path, recurseDirection); } return oldvisibility; } } return null; }); _defineProperty(_this, "changeSelectedLayer", function (selectedLayer) { var action = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; var feature = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; var curConfig = _this.props.theme && _this.props.theme.editConfig && selectedLayer ? _this.props.theme.editConfig[selectedLayer] : null; var editPermissions = curConfig ? curConfig.permissions || {} : {}; _this.props.setEditContext('Editing', { action: action || (_this.state.drawPick ? "Draw" : _this.props.editContext.action), feature: feature, geomType: curConfig ? curConfig.geomType : null, geomReadOnly: editPermissions.updatable === false }); var prevLayerVisibility = null; if (_this.state.selectedLayer !== null) { _this.setLayerVisibility(_this.state.selectedLayer, _this.state.selectedLayerVisibility); prevLayerVisibility = _this.setLayerVisibility(selectedLayer, true); } _this.setState({ selectedLayer: selectedLayer, selectedLayerVisibility: prevLayerVisibility, drawPick: false }); }); _defineProperty(_this, "setEditFeature", function (featureId) { var feature = _this.state.pickedFeatures.find(function (f) { return f.id.toString() === featureId; }); var editConfig = _this.props.theme.editConfig[_this.state.selectedLayer]; var editPermissions = editConfig.permissions || {}; _this.props.setEditContext('Editing', { feature: feature, changed: false, geomReadOnly: editPermissions.updatable === false }); }); _defineProperty(_this, "toggleDrawPick", function () { _this.setState(function (state) { var pickActive = !state.drawPick; _this.props.setEditContext('Editing', { action: pickActive ? null : "Draw" }); return { drawPick: pickActive }; }); }); return _this; } _inherits(Editing, _React$Component); return _createClass(Editing, [{ key: "componentDidUpdate", value: function componentDidUpdate(prevProps, prevState) { var _this2 = this; var themeSublayers = this.props.layers.reduce(function (accum, layer) { return layer.role === LayerRole.THEME ? accum.concat(LayerUtils.getSublayerNames(layer)) : accum; }, []); // Update selected layer on layers change if (this.props.enabled && (this.props.layers !== prevProps.layers || !prevProps.enabled)) { var layerIds = Object.keys(this.props.theme && this.props.theme.editConfig || {}).filter(function (layerId) { return themeSublayers.includes(layerId); }); if (!isEmpty(layerIds)) { if (!layerIds.includes(this.state.selectedLayer)) { this.changeSelectedLayer(layerIds[0], "Pick"); } } else if (this.state.selectedLayer) { this.changeSelectedLayer(null); } } // If click point changed and in pick mode with a selected layer, trigger a pick var isCurrentContext = this.props.editContext.id === this.props.currentEditContext; if (this.props.enabled && isCurrentContext && this.props.editContext.action === 'Pick' && this.state.selectedLayer && !this.props.editContext.changed) { var newPoint = this.props.map.click || {}; var oldPoint = prevProps.map.click || {}; if (newPoint.coordinate && !isEqual(newPoint.coordinate, oldPoint.coordinate)) { var _this$props$filter$fi; var scale = Math.round(MapUtils.computeForZoom(this.props.map.scales, this.props.map.zoom)); var editConfig = this.props.theme.editConfig[this.state.selectedLayer]; var editPermissions = editConfig.permissions || {}; var editDataset = editConfig.editDataset; this.props.iface.getFeature(editDataset, newPoint.coordinate, this.props.map.projection, scale, 96, function (featureCollection) { var features = featureCollection ? featureCollection.features : null; _this2.setState({ pickedFeatures: features }); var feature = features ? features[0] : null; _this2.props.setEditContext('Editing', { feature: feature, changed: false, geomReadOnly: editPermissions.updatable === false }); }, (_this$props$filter$fi = this.props.filter.filterParams) === null || _this$props$filter$fi === void 0 ? void 0 : _this$props$filter$fi[this.state.selectedLayer], this.props.filter.filterGeom); } } if (prevProps.editContext.changed !== this.props.editContext.changed) { this.props.setCurrentTaskBlocked(this.props.editContext.changed === true, LocaleUtils.tr("editing.unsavedchanged")); } if (!this.props.editContext.feature && prevState.pickedFeatures) { this.setState({ pickedFeatures: null }); } } }, { key: "render", value: function render() { var _this3 = this; var minMaxTooltip = this.state.minimized ? LocaleUtils.tr("editing.maximize") : LocaleUtils.tr("editing.minimize"); var extraTitlebarContent = /*#__PURE__*/React.createElement(Icon, { className: "editing-minimize-maximize", icon: this.state.minimized ? 'chevron-down' : 'chevron-up', onClick: function onClick() { return _this3.setState(function (state) { return { minimized: !state.minimized }; }); }, title: minMaxTooltip }); var attribFormVisible = !!(this.props.editContext.feature && (this.props.editContext.action === "Pick" || this.props.editContext.feature.geometry)); return [/*#__PURE__*/React.createElement(SideBar, { extraTitlebarContent: extraTitlebarContent, heightResizeable: !this.state.minimized && attribFormVisible, icon: "editing", id: "Editing", key: "EditingSidebar", onHide: this.onHide, onShow: this.onShow, side: this.props.side, title: LocaleUtils.tr("appmenu.items.Editing"), width: this.props.width }, function () { return { body: _this3.state.minimized ? null : _this3.renderBody() }; }), this.state.drawPick ? /*#__PURE__*/React.createElement(PickFeature, { featureFilter: this.pickFilter, featurePicked: this.geomPicked, key: "FeaturePicker" }) : null]; } }]); }(React.Component); _defineProperty(Editing, "propTypes", { addLayerFeatures: PropTypes.func, /** Whether to enable the "Clone existing geometry" functionality. */ allowCloneGeometry: PropTypes.bool, changeLayerProperty: PropTypes.func, clearEditContext: PropTypes.func, currentEditContext: PropTypes.string, editContext: PropTypes.object, enabled: PropTypes.bool, filter: PropTypes.object, iface: PropTypes.object, layers: PropTypes.array, map: PropTypes.object, refreshLayer: PropTypes.func, removeLayer: PropTypes.func, setCurrentTask: PropTypes.func, setCurrentTaskBlocked: PropTypes.func, setEditContext: PropTypes.func, setSnappingConfig: PropTypes.func, /** The side of the application on which to display the sidebar. */ side: PropTypes.string, /** Whether snapping is available when editing. */ snapping: PropTypes.bool, /** Whether snapping is enabled by default when editing. * Either `false`, `edge`, `vertex` or `true` (i.e. both vertex and edge). */ snappingActive: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]), taskData: PropTypes.object, theme: PropTypes.object, /** The default width of the editing sidebar, as a CSS width string. */ width: PropTypes.string }); _defineProperty(Editing, "defaultProps", { width: "30em", side: 'right', snapping: true, snappingActive: true, allowCloneGeometry: true }); export default (function () { var iface = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : EditingInterface; return connect(function (state) { return { enabled: state.task.id === 'Editing', theme: state.theme.current, layers: state.layers.flat, filter: state.layers.filter, map: state.map, iface: iface, editContext: state.editing.contexts.Editing || {}, currentEditContext: state.editing.currentContext, taskData: state.task.id === "Editing" ? state.task.data : null }; }, { addLayerFeatures: addLayerFeatures, removeLayer: removeLayer, clearEditContext: clearEditContext, setEditContext: setEditContext, setSnappingConfig: setSnappingConfig, setCurrentTask: setCurrentTask, setCurrentTaskBlocked: setCurrentTaskBlocked, refreshLayer: refreshLayer, changeLayerProperty: changeLayerProperty })(Editing); });