UI for removing points

This commit is contained in:
Fabio Manganiello 2025-03-10 02:26:33 +01:00
parent 2dd3a54a8a
commit b5b1e03e35
Signed by: blacklight
GPG key ID: D90FBA7F76362774
3 changed files with 111 additions and 7 deletions
frontend/src

View file

@ -29,6 +29,7 @@
<PointInfo :point="selectedPoint" <PointInfo :point="selectedPoint"
ref="popup" ref="popup"
@remove="onRemove"
@close="selectedPoint = null" /> @close="selectedPoint = null" />
<div class="controls"> <div class="controls">
@ -57,6 +58,19 @@
@show-metrics="setShowMetrics" /> @show-metrics="setShowMetrics" />
</div> </div>
</div> </div>
<ConfirmDialog
:visible="true"
:disabled="loading"
v-if="pointToRemove"
@close="pointToRemove = null"
@confirm="removePoint">
<template #title>
Remove point
</template>
Are you sure you want to remove this point?
</ConfirmDialog>
</main> </main>
</template> </template>
@ -72,7 +86,9 @@ import { useGeographic } from 'ol/proj';
import type { Optional } from '../models/Types'; import type { Optional } from '../models/Types';
import Api from '../mixins/Api.vue'; import Api from '../mixins/Api.vue';
import ConfirmDialog from '../elements/ConfirmDialog.vue';
import Dates from '../mixins/Dates.vue'; import Dates from '../mixins/Dates.vue';
import Feature from 'ol/Feature';
import FilterButton from './filter/ToggleButton.vue'; import FilterButton from './filter/ToggleButton.vue';
import FilterForm from './filter/Form.vue'; import FilterForm from './filter/Form.vue';
import GPSPoint from '../models/GPSPoint'; import GPSPoint from '../models/GPSPoint';
@ -99,6 +115,7 @@ export default {
], ],
components: { components: {
ConfirmDialog,
FilterButton, FilterButton,
FilterForm, FilterForm,
PointInfo, PointInfo,
@ -110,10 +127,13 @@ export default {
loading: false, loading: false,
map: null as Optional<Map>, map: null as Optional<Map>,
mapView: null as Optional<View>, mapView: null as Optional<View>,
pointToRemove: null as Optional<GPSPoint>,
pointsLayer: null as Optional<VectorLayer>, pointsLayer: null as Optional<VectorLayer>,
popup: null as Optional<Overlay>, popup: null as Optional<Overlay>,
queryInitialized: false, queryInitialized: false,
refreshPoints: 0,
routesLayer: null as Optional<VectorLayer>, routesLayer: null as Optional<VectorLayer>,
selectedFeature: null as Optional<Point>,
selectedPoint: null as Optional<GPSPoint>, selectedPoint: null as Optional<GPSPoint>,
showControls: false, showControls: false,
showMetrics: new TimelineMetricsConfiguration(), showMetrics: new TimelineMetricsConfiguration(),
@ -122,10 +142,14 @@ export default {
computed: { computed: {
groupedGPSPoints(): GPSPoint[] { groupedGPSPoints(): GPSPoint[] {
// Reference refreshPoints to force reactivity
this.refreshPoints;
return this.groupPoints(this.gpsPoints) return this.groupPoints(this.gpsPoints)
}, },
mappedPoints(): Record<string, Point> { mappedPoints(): Record<string, Point> {
// Reference refreshPoints to force reactivity
this.refreshPoints;
return this.toMappedPoints(this.groupedGPSPoints) return this.toMappedPoints(this.groupedGPSPoints)
.reduce((acc: Record<string, Point>, point: Point) => { .reduce((acc: Record<string, Point>, point: Point) => {
// @ts-expect-error // @ts-expect-error
@ -148,6 +172,50 @@ export default {
} }
}, },
async removePoint() {
if (!this.pointToRemove) {
return
}
this.loading = true
try {
this.deletePoints([this.pointToRemove])
if (this.selectedFeature) {
const routeFeatures = this.routesLayer?.getSource().getFeatures().filter((f: Feature) => {
const [start, end] = (f.getGeometry() as any).getCoordinates()
return (
(
this.pointToRemove?.longitude === start[0] &&
this.pointToRemove?.latitude === start[1]
) || (
this.pointToRemove?.longitude === end[0] &&
this.pointToRemove?.latitude === end[1]
)
)
})
if (routeFeatures.length) {
this.routesLayer?.getSource().removeFeatures(routeFeatures)
}
this.pointsLayer?.getSource().removeFeature(this.selectedFeature)
}
this.gpsPoints = this.gpsPoints.filter((p: GPSPoint) => p.id !== this.pointToRemove?.id)
this.refreshPoints++
} finally {
this.loading = false
this.pointToRemove = null
this.selectedPoint = null
this.selectedFeature = null
}
},
onRemove(point: GPSPoint) {
this.pointToRemove = point
this.$emit('remove', this.point)
},
fetchNextPage() { fetchNextPage() {
const nextPageQuery = this.nextPageQuery() const nextPageQuery = this.nextPageQuery()
if (!nextPageQuery) { if (!nextPageQuery) {
@ -190,12 +258,22 @@ export default {
return map return map
}, },
refreshMap() {
// @ts-ignore
this.refreshMapView(this.mapView, this.gpsPoints)
// @ts-ignore
this.refreshPointsLayer(this.pointsLayer, Object.values(this.mappedPoints))
// @ts-ignore
this.refreshRoutesLayer(this.routesLayer, Object.values(this.mappedPoints))
},
bindClick(map: Map) { bindClick(map: Map) {
map.on('click', (event) => { map.on('click', (event) => {
this.showControls = false this.showControls = false
const feature = map.forEachFeatureAtPixel(event.pixel, (feature) => feature) const feature = map.forEachFeatureAtPixel(event.pixel, (feature) => feature)
if (feature) { if (feature) {
this.selectedFeature = feature
const point = this.gpsPoints.find((gps: GPSPoint) => { const point = this.gpsPoints.find((gps: GPSPoint) => {
const [longitude, latitude] = (feature.getGeometry() as any).getCoordinates() const [longitude, latitude] = (feature.getGeometry() as any).getCoordinates()
return gps.longitude === longitude && gps.latitude === latitude return gps.longitude === longitude && gps.latitude === latitude
@ -210,6 +288,7 @@ export default {
} }
} else { } else {
this.selectedPoint = null this.selectedPoint = null
this.selectedFeature = null
} }
}) })
}, },
@ -318,12 +397,7 @@ export default {
} }
if (this.mapView) { if (this.mapView) {
// @ts-ignore this.refreshMap()
this.refreshMapView(this.mapView, this.gpsPoints)
// @ts-ignore
this.refreshPointsLayer(this.pointsLayer, Object.values(this.mappedPoints))
// @ts-ignore
this.refreshRoutesLayer(this.routesLayer, Object.values(this.mappedPoints))
} }
}, },
deep: true, deep: true,

View file

@ -30,6 +30,12 @@
<span class="continent">{{ country.continent }}</span> <span class="continent">{{ country.continent }}</span>
</p> </p>
<p class="timestamp" v-if="timeString">{{ timeString }}</p> <p class="timestamp" v-if="timeString">{{ timeString }}</p>
<div class="remove">
<button title="Remove" @click="$emit('remove', point)">
<font-awesome-icon icon="fas fa-trash-alt" />&nbsp; Remove
</button>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -45,7 +51,7 @@ import Dates from '../mixins/Dates.vue';
import GPSPoint from '../models/GPSPoint'; import GPSPoint from '../models/GPSPoint';
export default { export default {
emit: ['close'], emit: ['close', 'remove'],
mixins: [Dates], mixins: [Dates],
props: { props: {
point: { point: {
@ -157,5 +163,22 @@ export default {
font-weight: bold; font-weight: bold;
font-size: 0.9em; font-size: 0.9em;
} }
.remove {
font-size: 0.85em;
margin-top: 0.5em;
button {
width: 100%;
background: none;
border: none;
color: var(--vt-c-red-fg-light);
margin-left: -0.5em;
&:hover {
color: var(--vt-c-red-fg-dark);
}
}
}
} }
</style> </style>

View file

@ -20,6 +20,13 @@ export default {
) )
.sort((a: GPSPoint, b: GPSPoint) => a.timestamp.getTime() - b.timestamp.getTime()) .sort((a: GPSPoint, b: GPSPoint) => a.timestamp.getTime() - b.timestamp.getTime())
}, },
async deletePoints(points: GPSPoint[]) {
await this.request('/gpsdata', {
method: 'DELETE',
body: points.map((point: GPSPoint) => point.id),
})
},
}, },
} }
</script> </script>