diff --git a/src/controls/editor.js b/src/controls/editor.js index a38775741..feda1e806 100644 --- a/src/controls/editor.js +++ b/src/controls/editor.js @@ -14,6 +14,9 @@ const Editor = function Editor(options = {}) { let viewer; let isVisible = isActive; let toolbarVisible = false; + /** Keeps track of the last selected item in featureinfo. We need to use our own variable for this + * in order to determine if editor got activated directly from featureinfo or some other tool has been active between. */ + let lastSelectedItem; /** The handler were all state is kept */ let editHandler; @@ -26,6 +29,16 @@ const Editor = function Editor(options = {}) { // There are some serious event dependencies between viewer, editor, edithandler, editortoolbar, editorlayers, dropdown and editorbutton, // which makes it almost impossible to do things in correct order. isVisible = detail.active; + // Actually, if we're going visible + if (isVisible) { + if (lastSelectedItem && lastSelectedItem.getLayer().get('editable') && !lastSelectedItem.getLayer().get('isTable')) { + // Set a preselected feature. No use to set layer in handler as toolbar keeps state that will override a change of layer in handler anyway + // when editor toolbar becomes visible. + editHandler.preselectFeature(lastSelectedItem.getFeature()); + // Have to set layer in toolbar instead of handler. + editorToolbar.changeActiveLayer(lastSelectedItem.getLayer().get('name')); + } + } viewer.dispatch('toggleClickInteraction', detail); }; @@ -76,10 +89,39 @@ const Editor = function Editor(options = {}) { }); editHandler = EditHandler(handlerOptions, viewer); + // Set up eventhandler for when featureinfo selects (or highlights) a feature + const featureInfo = viewer.getFeatureinfo(); + featureInfo.on('changeselection', detail => { + if (isVisible) { + // This can only happen if featureInfo.ShowFeatureInfo, featureInfo.showInfow or featureInfo.render is called + // from api, as featureInfo component can not be active when editor is so the user can not click in the map to select anything. + if (detail && detail.getLayer().get('editable') && !detail.getLayer().get('isTable')) { + // Set a preselected feature. + editHandler.preselectFeature(detail.getFeature()); + // Actually change active layer. + editHandler.setActiveLayer(detail.getLayer().get('name')); + // Have to update state in toolbar as well. + editorToolbar.changeActiveLayer(detail.getLayer().get('name')); + // Clear featureinfo to close the popup which otherwise would just be annoying + featureInfo.clear(); + } + } else { + lastSelectedItem = detail; + } + }); + + // Set up eventhandler for when featureinfo clears selection + // Event is sent from featureinfo when popup etc is closed or cleared, but not when tool changes + featureInfo.on('clearselection', () => { + lastSelectedItem = null; + }); + viewer.on('toggleClickInteraction', (detail) => { if (detail.name === 'editor' && detail.active) { editorButton.dispatch('change', { state: 'active' }); } else { + // Someone else got active. Ditch the last selected item as we don't go directly from featureinfo to edit + lastSelectedItem = null; editorButton.dispatch('change', { state: 'initial' }); } }); @@ -131,7 +173,7 @@ const Editor = function Editor(options = {}) { editFeatureAttributes, deleteFeature, changeActiveLayer: (layerName) => { - // Only need to actually cahne layer if editor is active. Otherwise state is just set in toolbar and will + // Only need to actually change layer if editor is active. Otherwise state is just set in toolbar and will // activate set layer when toggled visible if (isVisible) { editHandler.setActiveLayer(layerName); diff --git a/src/controls/editor/edithandler.js b/src/controls/editor/edithandler.js index 6053bc58c..b9bfc6f63 100644 --- a/src/controls/editor/edithandler.js +++ b/src/controls/editor/edithandler.js @@ -56,6 +56,7 @@ let allowEditGeometry; let breadcrumbs = []; let autoCreatedFeature = false; let infowindowCmp = false; +let preselectedFeature; function isActive() { // FIXME: this only happens at startup as they are set to null on closing. If checking for null/falsley/not truely it could work as isVisible with @@ -571,6 +572,12 @@ function setInteractions(drawType) { } }); } + + if (preselectedFeature) { + select.getFeatures().push(preselectedFeature); + } + // Clear it so we won't get stuck on this feature. This makes it unnecessary to clear it anywhere else. + preselectedFeature = null; if (allowEditGeometry) { modify = new Modify({ features: select.getFeatures() @@ -592,6 +599,7 @@ function setInteractions(drawType) { // If snap should be active then add snap internactions for all snap layers hasSnap = editLayer.get('snap'); if (hasSnap) { + // FIXME: selection will almost certainly be empty as featureInfo is cleared const selectionSource = featureInfo.getSelectionLayer().getSource(); const snapSources = editLayer.get('snapLayers') ? getSnapSources(editLayer.get('snapLayers')) : [editLayer.get('source')]; snapSources.push(selectionSource); @@ -599,20 +607,25 @@ function setInteractions(drawType) { } } +/** Closes all modals and resets breadcrumbs */ function closeAllModals() { - // Close all modals first to get rid of tags in DOM + // Close all modals before resetting breadcrumbs to get rid of tags in DOM if (modal) modal.closeModal(); modal = null; breadcrumbs.forEach(br => { if (br.modal) br.modal.closeModal(); }); + if (breadcrumbs.length > 0) { + currentLayer = breadcrumbs[0].layerName; + title = breadcrumbs[0].title; + attributes = breadcrumbs[0].attributes; + } breadcrumbs = []; } function setEditLayer(layerName) { - // It is not possible to actually change layer while having breadcrubs as all modals must be closed, which will - // pop off all breadcrumbs. - // But just in case something changes, reset the breadcrumbs when a new layer is edited. + // Close all modals first and restore state. This can only happen if calling using api, as + // the modal prevents user from clicking in the map conrol closeAllModals(); currentLayer = layerName; setAllowedOperations(); @@ -1388,10 +1401,8 @@ function editAttributesDialogApi(featureId, layerName = null) { const layer = viewer.getLayer(layerName); const feature = layer.getSource().getFeatureById(featureId); // Hijack the current layer for a while. If there's a modal visible it is closed (without saving) as editAttributes can not handle - // multiple dialogs for the same layer so to be safe we always close. Technically the user can not - // call this function when a modal is visible, as they can't click anywhere. + // multiple dialogs for the same layer so to be safe we always close. // Restoring currentLayer is performed in onModalClosed(), as we can't await the modal. - // Close all modals and eat all breadcrumbs closeAllModals(); // If editing in another layer, add a breadcrumb to restore layer when modal is closed. if (layerName && layerName !== currentLayer) { @@ -1463,6 +1474,15 @@ function onDeleteChild(e) { deleteFeature(e.detail.feature, e.detail.layer).then(() => refreshRelatedTablesForm(e.detail.parentFeature)); } +/** + * Sets a feature that will be active for editing when editor is activated. When the edit session starts, the feature's layer must + * be active and that state is kept in the editor toolbar and sent through an event, so you better update toolbar as well. + * @param {any} feature + */ +function preselectFeature(feature) { + preselectedFeature = feature; +} + /** * Creates the handler. It is used as sort of a singelton, but in theory there could be many handlers. * It communicates with the editor toolbar and forms using DOM events, which makes it messy to have more than one instance as they would use the same events. @@ -1508,6 +1528,7 @@ export default function editHandler(options, v) { createFeature: createFeatureApi, editAttributesDialog: editAttributesDialogApi, deleteFeature: deleteFeatureApi, - setActiveLayer: setActiveLayerApi + setActiveLayer: setActiveLayerApi, + preselectFeature }; } diff --git a/src/featureinfo.js b/src/featureinfo.js index 6c58e51c4..65b84aab7 100644 --- a/src/featureinfo.js +++ b/src/featureinfo.js @@ -17,7 +17,6 @@ import getAttributes, { getContent, featureinfotemplates } from './getattributes import relatedtables from './utils/relatedtables'; const styleTypes = StyleTypes(); -let selectionLayer; const Featureinfo = function Featureinfo(options = {}) { const { @@ -32,6 +31,7 @@ const Featureinfo = function Featureinfo(options = {}) { autoplay = false } = options; + let selectionLayer; let identifyTarget; let overlay; let items; @@ -47,6 +47,47 @@ const Featureinfo = function Featureinfo(options = {}) { const savedFeature = savedPin || savedSelection || undefined; const uiOutput = 'infowindow' in options ? options.infowindow : 'overlay'; + /** Dispatches a clearselectionevent. Should be emitted when window is closed or cleared but not when featureinfo is closed as a result of tool change. */ + const dispatchClearSelection = function dispatchClearSelection() { + component.dispatch('clearselection', null); + }; + + /** Eventhandler for Selectionmanager's clear event. */ + function onSelectionManagerClear() { + // Not much do to, just dispatch event as our own. + dispatchClearSelection(); + } + + /** + * Clears selection in all possible infowindows (overlay, sidebar and infoWindow) and closes the windows + * @param {any} supressEvent Set to true when closing as a result of tool change to supress sending clearselection event + */ + const clear = function clear(supressEvent) { + selectionLayer.clear(); + // Sidebar is static and always present. + sidebar.setVisibility(false); + // check needed for when sidebar or overlay are selected. + if (overlay) { + viewer.removeOverlays(overlay); + } + if (selectionManager) { + // clearSelection will fire an cleared event, but we don't want our handler to emit a clear event as we are the one closing, + // so we stop listening for a while. + selectionManager.un('cleared', onSelectionManagerClear); + // This actually closes infowindow as infowindow closes automatically when selection is empty. + selectionManager.clearSelection(); + selectionManager.on('cleared', onSelectionManagerClear); + } + if (!supressEvent) { + dispatchClearSelection(); + } + }; + + /** Callback called from overlay and sidebar when they are closed by their close buttons */ + function onInfoClosed() { + clear(false); + } + function setUIoutput(v) { switch (uiOutput) { case 'infowindow': @@ -54,7 +95,7 @@ const Featureinfo = function Featureinfo(options = {}) { break; case 'sidebar': - sidebar.init(v); + sidebar.init(v, { closeCb: onInfoClosed }); identifyTarget = 'sidebar'; break; @@ -64,16 +105,6 @@ const Featureinfo = function Featureinfo(options = {}) { } } - const clear = function clear() { - selectionLayer.clear(); - // check needed for when sidebar or overlay are selected. - if (selectionManager) selectionManager.clearSelection(); - sidebar.setVisibility(false); - if (overlay) { - viewer.removeOverlays(overlay); - } - }; - // FIXME: overly complex. Don't think layer can be a string anymore const getTitle = function getTitle(item) { let featureinfoTitle; @@ -379,14 +410,14 @@ const Featureinfo = function Featureinfo(options = {}) { const map = viewer.getMap(); items = identifyItems; - clear(); + clear(false); // FIXME: variable is overwritten in next row let content = items.map((i) => i.content).join(''); content = '