Skip to content

Commit

Permalink
wip: Provide a selection manager component (#1098)
Browse files Browse the repository at this point in the history
  • Loading branch information
claustres committed Feb 18, 2025
1 parent 84ab15f commit 95fe4b7
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 17 deletions.
11 changes: 9 additions & 2 deletions core/client/composables/selection.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import _ from 'lodash'
import { onBeforeUnmount } from 'vue'
import { useStore } from './store.js'

export function useSelection (name, options = {}) {
Expand All @@ -8,7 +9,7 @@ export function useSelection (name, options = {}) {
// selection store
const { store, set, get, has } = useStore(`selections.${name}`)

// functions
// Functions
// Single selection will rely on the lastly selected item only
// Multiple selection mode will rely on all items
function clearSelection () {
Expand Down Expand Up @@ -78,7 +79,13 @@ export function useSelection (name, options = {}) {
set('enabled', true)
}

// expose
// Cleanup on destroy
onBeforeUnmount(() => {
setSelectionEnabled()
clearSelection()
})

// Expose
return {
selection: store,
clearSelection,
Expand Down
12 changes: 7 additions & 5 deletions map/client/components/selection/KSelectedLayerFeatures.vue
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ function isFeaturePropertiesNode (node) {
}
function getIcon (node) {
if (isLayerNode(node)) return (editedFeatures.value.length > 0 ? 'las la-edit' : '')
if (isFeatureNode(node)) return (editedFeatures.value.contains(node) ? 'las la-edit' : '')
if (isFeatureNode(node)) return (editedFeatures.value.contains(getFeatureId(node, props.item.layer)) ? 'las la-edit' : '')
if (isFeaturePropertiesNode(node)) return 'las la-address-card'
return ''
}
Expand All @@ -200,8 +200,9 @@ function zoomToSelectedFeature (feature) {
function editSelectedFeatures () {
// Zoom to then edit
zoomToSelectedFeatures()
editedFeatures.value = props.item.features.map(feature => getFeatureId(feature, props.item.layer))
CurrentActivity.value.startEditLayer(props.item.layer, {
features: props.item.features.map(feature => getFeatureId(feature, props.item.layer)),
features: editedFeatures.value,
editMode: 'edit-geometry',
allowedEditModes: [
'edit-properties',
Expand All @@ -210,15 +211,16 @@ function editSelectedFeatures () {
'rotate'
],
callback: (event) => {
editedFeatures.value = (event.status === 'edit-start' ? props.item.features : [])
if (event.status === 'accept') editedFeatures.value = []
}
})
}
function editSelectedFeature (feature) {
// Zoom to then edit
zoomToSelectedFeature(feature)
editedFeatures.value = [getFeatureId(feature, props.item.layer)]
CurrentActivity.value.startEditLayer(props.item.layer, {
features: [getFeatureId(feature, props.item.layer)],
features: editedFeatures.value,
editMode: 'edit-geometry',
allowedEditModes: [
'edit-properties',
Expand All @@ -227,7 +229,7 @@ function editSelectedFeature (feature) {
'rotate'
],
callback: (event) => {
editedFeatures.value = (event.status === 'edit-start' ? [feature] : [])
if (event.status === 'accept') editedFeatures.value = []
}
})
}
Expand Down
72 changes: 62 additions & 10 deletions map/client/composables/selection.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import circle from '@turf/circle'
import bboxPolygon from '@turf/bbox-polygon'
import intersects from '@turf/boolean-intersects'
import { featureEach } from '@turf/meta'
import { unref } from 'vue'
import { unref, onBeforeMount, onBeforeUnmount } from 'vue'
import * as composables from '../../../core/client/composables/index.js'
import { getFeatureId } from '../utils.js'
import * as utils from '../utils.js'
import { convertPolygonStyleToLeafletPath } from '../leaflet/utils/index.js'

export function useSelection (name, options = {}) {
// Data
let layerServiceEventListeners = {}
// Retrieve core selection
const selection = composables.useSelection(name, options)
// Selection store, as we store options inside check if already initialized
Expand All @@ -29,8 +31,8 @@ export function useSelection (name, options = {}) {
if (layer1 !== layer2) return false
// Then compare features
if (item1.feature && item2.feature) {
const id1 = getFeatureId(item1.feature, item1.layer)
const id2 = getFeatureId(item2.feature, item2.layer)
const id1 = utils.getFeatureId(item1.feature, item1.layer)
const id2 = utils.getFeatureId(item2.feature, item2.layer)
return id1 === id2
} else {
// If only one has a feature then it cannot be the same
Expand Down Expand Up @@ -70,13 +72,17 @@ export function useSelection (name, options = {}) {
activity.$engineEvents.off('click', onClicked)
if (options.boxSelection) activity.$engineEvents.off('boxselectionend', onBoxSelection)
if (options.clusterSelection) activity.$engineEvents.off('spiderfied', onClusterSelection)
activity.$engineEvents.off('layer-added', listenToFeaturesServiceEventsForLayer)
activity.$engineEvents.off('layer-removed', unlistenToFeaturesServiceEventsForLayer)
activity.$engineEvents.off('layer-hidden', onSelectedLayerHidden)
}
activity = newActivity
if (newActivity && newActivity.$engineEvents) {
newActivity.$engineEvents.on('click', onClicked)
if (options.boxSelection) newActivity.$engineEvents.on('boxselectionend', onBoxSelection)
if (options.clusterSelection) newActivity.$engineEvents.on('spiderfied', onClusterSelection)
newActivity.$engineEvents.on('layer-added', listenToFeaturesServiceEventsForLayer)
newActivity.$engineEvents.on('layer-removed', unlistenToFeaturesServiceEventsForLayer)
newActivity.$engineEvents.on('layer-hidden', onSelectedLayerHidden)
}
}
Expand Down Expand Up @@ -143,17 +149,20 @@ export function useSelection (name, options = {}) {
function getSelectedLocation () {
return selection.getSelectedItem().location
}
function isFeatureSelected (feature, layer) {
function findSelectedFeature (feature, layer) {
const items = selection.getSelectedItems()
for (let i = 0; i < items.length; i++) {
const item = items[i]
if (item.feature && item.layer && (item.layer.name === layer.name)) {
const selectedId = getFeatureId(item.feature, item.layer)
const featureId = getFeatureId(feature, layer)
if (featureId === selectedId) return true
const selectedId = utils.getFeatureId(item.feature, item.layer)
const featureId = utils.getFeatureId(feature, layer)
if (featureId === selectedId) return item
}
}
return false
return null
}
function isFeatureSelected (feature, layer) {
return findSelectedFeature(feature, layer) !== null
}
function getWidgetForSelection () {
let widget
Expand Down Expand Up @@ -341,8 +350,51 @@ export function useSelection (name, options = {}) {
const hiddenFeatures = selection.getSelectedItems().filter(item => layer.name === _.get(item, 'layer.name'))
hiddenFeatures.forEach((item) => selection.unselectItem(item))
}
function listenToFeaturesServiceEventsForLayer (layer) {
const listeners = utils.listenToFeaturesServiceEventsForLayer(layer, {
all: onFeatureUpdated, removed: onFeatureRemoved
}, layerServiceEventListeners[layer._id])
if (listeners) layerServiceEventListeners[layer._id] = listeners
}
function unlistenToFeaturesServiceEventsForLayer (layer) {
utils.unlistenToFeaturesServiceEventsForLayer(layer, layerServiceEventListeners[layer._id])
delete layerServiceEventListeners[layer._id]
}
function listenToFeaturesServiceEventsForLayers () {
layerServiceEventListeners = {}
_.forEach(activity.getLayers(), listenToFeaturesServiceEventsForLayer)
}
function unlistenToFeaturesServiceEventsForLayers () {
_.forOwn(layerServiceEventListeners, unlistenToFeaturesServiceEventsForLayer)
layerServiceEventListeners = {}
}
function onFeatureUpdated (feature, layer) {
// Find related layer, either directly given in feature if coming from user-defined features service
// otherwise bound to the listener for features services attached to a built-in layer
if (!layer && feature.layer) layer = activity.getLayerById(feature.layer)
if (!layer) return
const item = findSelectedFeature(feature, layer)
if (item) Object.assign(item.feature, feature)
}
function onFeatureRemoved (feature, layer) {
// Find related layer, either directly given in feature if coming from user-defined features service
// otherwise bound to the listener for features services attached to a built-in layer
if (!layer && feature.layer) layer = activity.getLayerById(feature.layer)
if (!layer) return
const item = findSelectedFeature(feature, layer)
if (item) selection.unselectItem(item)
}

// expose
// Here we need to listen to service events for all realtime layers
onBeforeMount(() => {
listenToFeaturesServiceEventsForLayers()
})
// Cleanup on destroy
onBeforeUnmount(() => {
unlistenToFeaturesServiceEventsForLayers()
})

// Expose
return {
...selection,
getSelectionOptions: () => get('options'),
Expand Down

0 comments on commit 95fe4b7

Please sign in to comment.