3
0

Update geometry page block UX/UI and fix title field not displaying

This commit is contained in:
Jože Fortun 2024-06-03 16:47:22 +02:00
parent 94ffd1748b
commit 7a4c1028c8
10 changed files with 287 additions and 224 deletions

View File

@ -151,9 +151,25 @@
:title="field.label || field.name"
size="lg"
body-class="p-0"
footer-class="flex align-items-center"
>
<template #modal-footer>
{{ $t('clickToPlaceMarker') }}
<b-button
variant="light"
class="ml-auto"
@click="closeMap()"
>
{{ $t('general:label.cancel') }}
</b-button>
<b-button
variant="primary"
@click="saveMapValue()"
>
{{ $t('general:label.save') }}
</b-button>
</template>
<c-map
@ -167,6 +183,7 @@
style="height: 75vh; width: 100%; cursor: pointer;"
@on-map-click="placeMarker"
@on-marker-click="removeMarker"
@location-found="placeMarker($event, localValueIndex, true)"
@on-geosearch-error="onGeoSearchError"
/>
</b-modal>
@ -197,16 +214,21 @@ export default {
map: {
show: false,
value: undefined,
},
}
},
computed: {
markers () {
let markers = [{ value: this.localValue.coordinates, opacity: 1.0 }]
if (!this.map.value) {
return []
}
let markers = [{ value: this.map.value.coordinates, opacity: 1.0 }]
if (this.field.isMulti) {
markers = this.localValue.map(({ coordinates }, i) => ({
markers = this.map.value.map(({ coordinates }, i) => ({
value: coordinates && coordinates.length ? coordinates : undefined,
opacity: this.localValueIndex === undefined || i === this.localValueIndex ? 1.0 : 0.6,
}))
@ -223,10 +245,9 @@ export default {
this.value = this.field.isMulti ? value.filter(v => (v || {}).coordinates).map(v => JSON.stringify(v)) : JSON.stringify(value)
},
},
'field.isMulti': {
immediate: true,
handler (value) {
handler () {
if (this.field.isMulti) {
this.localValue = this.value.map(v => {
return JSON.parse(v || '{"coordinates":[]}')
@ -253,6 +274,8 @@ export default {
methods: {
openMap (index) {
this.map.value = this.localValue
this.localValueIndex = index
const firstCoordinates = (index >= 0 ? this.localValue[index] : this.localValue) || {}
firstCoordinates.coordinates = firstCoordinates.coordinates ? [...firstCoordinates.coordinates] : []
@ -265,7 +288,11 @@ export default {
this.map.show = true
},
placeMarker (e, index = this.localValueIndex) {
closeMap () {
this.map.show = false
},
placeMarker (e, index = this.localValueIndex, map = true) {
const { lat = 0, lng = 0 } = e.latlng || {}
const coords = {
coordinates: [
@ -276,23 +303,28 @@ export default {
if (this.field.isMulti) {
if (index >= 0) {
this.localValue.splice(index, 1, coords)
map ? this.map.value.splice(index, 1, coords) : this.localValue.splice(index, 1, coords)
} else {
this.localValue.push(coords)
map ? this.map.value.push(coords) : this.localValue.push(coords)
}
} else {
this.localValue = coords
map ? this.map.value = coords : this.localValue = coords
}
},
removeMarker ({ index }) {
if (this.field.isMulti) {
this.localValue.splice(index, 1)
this.map.value.splice(index, 1)
} else {
this.localValue = { coordinates: [] }
this.map.value = { coordinates: [] }
}
},
saveMapValue () {
this.localValue = this.map.value
this.closeMap()
},
useCurrentLocation (index) {
try {
if (!navigator.geolocation) {
@ -302,7 +334,7 @@ export default {
navigator.geolocation.getCurrentPosition(
({ coords }) => {
const latlng = { lat: coords.latitude, lng: coords.longitude }
this.placeMarker({ latlng }, index)
this.placeMarker({ latlng }, index, false)
},
error => {
switch (error.code) {

View File

@ -76,9 +76,9 @@ export default {
if (this.field.isMulti) {
return this.value.map(v => {
return { value: JSON.parse(v || '{"coordinates":[]}').coordinates || [] }
}).filter(c => c)
}).filter(c => c && c.value && c.value.length)
} else {
return [{ value: JSON.parse(this.value || '{"coordinates":[]}').coordinates || [] }].filter(c => c)
return [{ value: JSON.parse(this.value || '{"coordinates":[]}').coordinates || [] }].filter(c => c && c.value && c.value.length)
}
},
},

View File

@ -31,7 +31,13 @@
class="w-100 h-100"
@on-marker-click="onMarkerCLick"
@on-geosearch-error="onGeoSearchError"
/>
>
<template #marker-tooltip="{ marker }">
<h6 class="mb-0">
{{ marker.title }}
</h6>
</template>
</c-map>
</div>
</wrap>
</template>
@ -84,19 +90,18 @@ export default {
const values = []
this.geometries.forEach((geo) => {
geo.forEach((value) => {
if (value.displayMarker) {
value.markers.map(subValue => {
if (subValue) {
values.push({
value: subValue || {},
color: value.color,
recordID: value.recordID,
moduleID: value.moduleID,
})
}
})
}
geo.filter(({ displayMarker }) => displayMarker).forEach(value => {
value.markers.forEach(subValue => {
if (subValue) {
values.push({
title: value.title,
value: subValue || {},
color: value.color,
recordID: value.recordID,
moduleID: value.moduleID,
})
}
})
})
})
@ -202,34 +207,34 @@ export default {
return compose.PageBlockGeometry.RecordFeed(this.$ComposeAPI, module, this.namespace, f, { cancelToken: this.cancelTokenSource.token })
.then(records => {
const mapModuleField = module.fields.find(f => f.name === f.geometryField)
const mapModuleField = module.fields.find(field => field.name === f.geometryField)
if (mapModuleField) {
this.geometries[idx] = records.map(record => {
let geometry = record.values[f.geometryField]
let markers = []
if (!mapModuleField) return
if (mapModuleField.isMulti) {
geometry = geometry.map(value => this.parseGeometryField(value))
markers = geometry
} else {
geometry = this.parseGeometryField(geometry)
markers = [geometry]
}
this.geometries[idx] = records.map(record => {
let geometry = record.values[f.geometryField]
let markers = []
if (geometry.length && geometry.length === 2) {
return ({
title: record.values[f.titleField],
geometry: f.displayPolygon ? geometry : [],
markers,
color: f.options.color,
displayMarker: f.displayMarker,
recordID: record.recordID,
moduleID: record.moduleID,
})
}
}).filter(g => g)
}
if (mapModuleField.isMulti) {
geometry = geometry.map(value => this.parseGeometryField(value))
markers = geometry
} else {
geometry = this.parseGeometryField(geometry)
markers = [geometry]
}
if (geometry.length && geometry.length === 2) {
return ({
title: record.values[f.titleField],
geometry: f.displayPolygon ? geometry : [],
markers,
color: f.options.color,
displayMarker: f.displayMarker,
recordID: record.recordID,
moduleID: record.moduleID,
})
}
}).filter(g => g)
})
})
})).finally(() => {

View File

@ -84,16 +84,12 @@
lg="4"
>
<b-form-group
:label="$t('geometry.bounds.lockBounds')"
:label="$t('geometry.onMarkerClick')"
label-class="text-primary"
class="rounded-left"
>
<b-form-checkbox
v-model="options.lockBounds"
name="lock-bounds"
switch
size="lg"
@change="updateBounds"
<b-form-select
v-model="options.displayOption"
:options="displayOptions"
/>
</b-form-group>
</b-col>
@ -103,12 +99,14 @@
lg="4"
>
<b-form-group
:label="$t('geometry.onMarkerClick')"
:label="$t('geometry.bounds.lockBounds')"
label-class="text-primary"
class="rounded-left"
>
<b-form-select
v-model="options.displayOption"
:options="displayOptions"
<c-input-checkbox
v-model="options.lockBounds"
switch
:labels="checkboxLabel"
/>
</b-form-group>
</b-col>
@ -138,6 +136,11 @@ export default {
localValue: { coordinates: [] },
center: [],
bounds: null,
checkboxLabel: {
on: this.$t('general:label.yes'),
off: this.$t('general:label.no'),
},
}
},

View File

@ -1,130 +1,142 @@
<template>
<div>
<b-row>
<template v-if="feed.options">
<b-form-group
:label-cols="3"
:label="$t('geometry.recordFeed.moduleLabel')"
horizontal
breakpoint="md"
label-class="text-primary"
>
<b-input-group>
<c-input-select
v-model="feed.options.moduleID"
:options="modules"
:reduce="o => o.moduleID"
:placeholder="$t('calendar.recordFeed.modulePlaceholder')"
default-value="0"
label="name"
/>
</b-input-group>
</b-form-group>
<b-col cols="12">
<b-form-group
:label="$t('geometry.recordFeed.moduleLabel')"
label-class="text-primary"
>
<b-input-group>
<c-input-select
v-model="feed.options.moduleID"
:options="modules"
:reduce="o => o.moduleID"
:placeholder="$t('calendar.recordFeed.modulePlaceholder')"
default-value="0"
label="name"
@input="onModuleChange"
/>
</b-input-group>
</b-form-group>
</b-col>
<template v-if="module">
<b-form-group
:label-cols="3"
:label="$t('geometry.recordFeed.geometryFieldLabel')"
horizontal
breakpoint="md"
label-class="text-primary"
<b-col
cols="12"
lg="6"
>
<b-form-select
v-model="feed.geometryField"
:options="geometryFields | optionizeFields"
<b-form-group
:label="$t('geometry.recordFeed.geometryFieldLabel')"
label-class="text-primary"
>
<template slot="first">
<option
disabled
value=""
>
{{ $t('geometry.recordFeed.geometryFieldPlaceholder') }}
</option>
</template>
</b-form-select>
</b-form-group>
<c-input-select
v-model="feed.geometryField"
:options="geometryFields"
:placeholder="$t('geometry.recordFeed.geometryFieldPlaceholder')"
:reduce="o => o.name"
/>
</b-form-group>
</b-col>
<b-form-group
:label-cols="3"
:label="$t('geometry.recordFeed.titleLabel')"
horizontal
breakpoint="md"
label-class="text-primary"
<b-col
cols="12"
lg="6"
>
<c-input-select
v-model="feed.titleField"
:options="titleFields | optionizeFields"
:reduce="o => o.value"
label="text"
:placeholder="$t('geometry.recordFeed.titlePlaceholder')"
/>
</b-form-group>
<b-form-group
:label="$t('geometry.recordFeed.titleLabel')"
label-class="text-primary"
>
<c-input-select
v-model="feed.titleField"
:options="titleFields"
:reduce="o => o.name"
:placeholder="$t('geometry.recordFeed.titlePlaceholder')"
/>
</b-form-group>
</b-col>
<b-form-group
:label-cols="3"
:label="$t('calendar.recordFeed.prefilterLabel')"
horizontal
breakpoint="md"
label-class="text-primary"
<b-col
cols="12"
>
<b-form-textarea
v-model="feed.options.prefilter"
:value="true"
:placeholder="$t('calendar.recordFeed.prefilterPlaceholder')"
/>
</b-form-group>
<b-form-group
:label="$t('calendar.recordFeed.prefilterLabel')"
label-class="text-primary"
>
<b-form-textarea
v-model="feed.options.prefilter"
:value="true"
:placeholder="$t('calendar.recordFeed.prefilterPlaceholder')"
/>
<b-form-group
:label-cols="3"
:label="$t('geometry.recordFeed.colorLabel')"
horizontal
breakpoint="md"
label-class="text-primary"
>
<c-input-color-picker
v-model="feed.options.color"
:translations="{
modalTitle: $t('geometry.recordFeed.colorPicker'),
light: $t('general:themes.labels.light'),
dark: $t('general:themes.labels.dark'),
cancelBtnLabel: $t('general:label.cancel'),
saveBtnLabel: $t('general:label.saveAndClose')
}"
:theme-settings="themeSettings"
/>
</b-form-group>
<i18next
path="interpolationFootnote"
tag="small"
class="text-muted"
>
<code>${record.values.fieldName}</code>
<code>${recordID}</code>
<code>${ownerID}</code>
<span><code>${userID}</code>, <code>${user.name}</code></span>
</i18next>
</b-form-group>
</b-col>
<b-form-group
:label-cols="3"
:label="$t('geometry.recordFeed.displayMarker')"
horizontal
breakpoint="md"
label-class="text-primary"
<b-col
cols="12"
lg="4"
>
<b-form-checkbox
v-model="feed.displayMarker"
name="display-marker"
switch
size="lg"
/>
</b-form-group>
<b-form-group
:label="$t('geometry.recordFeed.displayMarker')"
label-class="text-primary"
>
<c-input-checkbox
v-model="feed.displayMarker"
switch
:labels="checkboxLabel"
/>
</b-form-group>
</b-col>
<b-form-group
:label-cols="3"
:label="$t('geometry.recordFeed.displayPolygon')"
horizontal
breakpoint="md"
label-class="text-primary"
<b-col
cols="12"
lg="4"
>
<b-form-checkbox
v-model="feed.displayPolygon"
name="display-marker"
switch
size="lg"
/>
</b-form-group>
<b-form-group
:label="$t('geometry.recordFeed.displayPolygon')"
label-class="text-primary"
>
<c-input-checkbox
v-model="feed.displayPolygon"
switch
:labels="checkboxLabel"
/>
</b-form-group>
</b-col>
<b-col
cols="12"
lg="4"
>
<b-form-group
:label="$t('geometry.recordFeed.colorLabel')"
label-class="text-primary"
>
<c-input-color-picker
v-model="feed.options.color"
:translations="{
modalTitle: $t('geometry.recordFeed.colorPicker'),
light: $t('general:themes.labels.light'),
dark: $t('general:themes.labels.dark'),
cancelBtnLabel: $t('general:label.cancel'),
saveBtnLabel: $t('general:label.saveAndClose')
}"
:theme-settings="themeSettings"
/>
</b-form-group>
</b-col>
</template>
</template>
</div>
</b-row>
</template>
<script>
@ -143,6 +155,15 @@ export default {
extends: base,
data () {
return {
checkboxLabel: {
on: this.$t('general:label.yes'),
off: this.$t('general:label.no'),
},
}
},
computed: {
/**
* Finds the module, this feed configurator should use
@ -175,7 +196,7 @@ export default {
if (!this.module) {
return []
}
return [...this.module.fields]
return this.module.fields
.filter(f => [
'DateTime',
'Select',
@ -184,8 +205,7 @@ export default {
'String',
'Record',
'User',
].includes(f.kind))
.sort((a, b) => a.label.localeCompare(b.label))
].includes(f.kind)).toSorted((a, b) => a.label.localeCompare(b.label))
},
/**
@ -198,20 +218,25 @@ export default {
return []
}
const moduleFields = this.module.fields.slice().sort((a, b) => a.label.localeCompare(b.label))
return [
...moduleFields,
...this.module.fields,
...this.module.systemFields().map(sf => {
sf.label = this.$t(`field:system.${sf.name}`)
return sf
}),
].filter(f => f.kind === 'Geometry')
].filter(f => f.kind === 'Geometry').toSorted((a, b) => a.label.localeCompare(b.label))
},
themeSettings () {
return this.$Settings.get('ui.studio.themes', [])
},
},
methods: {
onModuleChange () {
this.feed.geometryField = ''
this.feed.titleField = ''
},
},
}
</script>

View File

@ -4,22 +4,6 @@
* Define common props, methods, ... in here.
*/
export default {
filters: {
/**
* Prepares a set of select options
* @param {Array} ff Raw options
* @returns {Array}
*/
optionizeFields (ff) {
return ff.map(f => {
return {
text: f.label || f.name,
value: f.name,
}
})
},
},
props: {
feed: {
type: Object,

View File

@ -1,41 +1,46 @@
<template>
<div>
<div
<b-row>
<b-col
v-for="(feed, i) in options.feeds"
:key="i"
cols="12"
class="p-0"
>
<div
v-if="feed.resource"
class="d-flex justify-content-end mb-3"
<b-card
class="list-background mx-3 mb-3"
>
<c-input-confirm
show-icon
size="md"
@confirmed="onRemoveFeed(i)"
/>
</div>
<b-form-group horizontal>
<h5
v-if="feed.resource"
class="d-flex align-items-center mb-3"
>
{{ $t('geometry.source.label') }} {{ i + 1 }}
<c-input-confirm
show-icon
class="ml-auto mt-1"
@confirmed="onRemoveFeed(i)"
/>
</h5>
<component
:is="configurator(feed)"
v-if="feed.resource && configurator(feed)"
:feed="feed"
:modules="modules"
/>
</b-form-group>
</b-card>
</b-col>
<hr>
</div>
<b-button
variant="primary"
class="test-feed-add"
@click.prevent="handleAddButton"
>
{{ $t('geometry.addSource') }}
</b-button>
</div>
<b-col cols="12">
<b-button
variant="primary"
class="test-feed-add"
@click.prevent="handleAddButton"
>
{{ $t('geometry.addSource') }}
</b-button>
</b-col>
</b-row>
</template>
<script>
import { mapGetters } from 'vuex'
import base from '../../base'
@ -107,3 +112,9 @@ export default {
},
}
</script>
<style lang="scss" scoped>
.list-background {
background-color: var(--body-bg);
}
</style>

View File

@ -22,8 +22,8 @@ export default class Feed {
public resource = 'compose:record'
public titleField = ''
public geometryField = ''
public displayMarker = false
public displayPolygon = true
public displayMarker = true
public displayPolygon = false
public options: FeedOptions = { ...defOptions }
constructor (i?: FeedInput) {

View File

@ -233,6 +233,7 @@ export default {
onLocationFound ({ latitude, longitude }) {
const zoom = this.$refs.map.mapObject._zoom >= 13 ? this.$refs.map.mapObject._zoom : 13
this.$refs.map.mapObject.flyTo([latitude, longitude], zoom)
this.$emit('location-found', { latlng: { lat: latitude, lng: longitude }})
},
disableMap () {

View File

@ -761,6 +761,8 @@ geometry:
label: Geometry
viewLabel: Geometry
feedLabel: Configure Sources
source:
label: Source
addSource: Add source
lock: Lock
mapHelpText: Move and scroll the map to the position that will be used as a default. You can also lock the position or zoom to prevent users from moving too far.