Merge remote-tracking branch 'sedum/feat/allowgeoloc' into dev_geo

This commit is contained in:
lesion
2022-11-15 17:37:13 +01:00
37 changed files with 663 additions and 107 deletions

View File

@@ -12,8 +12,8 @@ v-dialog(v-model='show'
v-card-text(v-show='!!message') {{ message }}
v-card-actions
v-spacer
v-btn(text color='error' @click='cancel') {{$t('common.cancel')}}
v-btn(text color='primary' @click='agree') {{$t('common.ok')}}
v-btn(outlined color='error' @click='cancel') {{$t('common.cancel')}}
v-btn(outlined color='primary' @click='agree') {{$t('common.ok')}}
</template>
<script>

View File

@@ -8,6 +8,8 @@ v-col(cols=12)
p {{ $t(`event.${type}_description`) }}
p {{value}}
v-btn-toggle.v-col-6.flex-column.flex-sm-row(v-if='type === "recurrent"' color='primary' :value='value.recurrent.frequency' @change='fq => change("frequency", fq)')
v-btn(v-for='f in frequencies' :key='f.value' :value='f.value') {{ f.text }}
@@ -232,6 +234,11 @@ export default {
this.$emit('input', { ...this.value, fromHour: null, dueHour: null })
}
} else if (what === 'dueHour') {
if (value) {
this.value.due = this.value.due ? this.value.due : this.value.from
} else {
this.value.due = null
}
this.$emit('input', { ...this.value, dueHour: value })
// if (value) {

View File

@@ -11,10 +11,9 @@ v-card.h-event.event.d-flex(itemscope itemtype="https://schema.org/Event")
nuxt-link.place.d-block.p-location.pl-0(text :to='`/place/${encodeURIComponent(event.place.name)}`' itemprop="location" itemscope itemtype="https://schema.org/Place") <v-icon v-text='mdiMapMarker'></v-icon> <span itemprop='name'>{{ event.place.name }}</span>
.d-none(itemprop='address') {{ event.place.address }}
v-card-actions.pt-0.actions.justify-space-between
.tags
v-chip.ml-1.mt-1(v-for='tag in event.tags.slice(0, 6)' small :to='`/tag/${encodeURIComponent(tag)}`'
:key='tag' outlined color='primary') {{ tag }}
v-card-actions
v-chip.ml-1.mt-1(v-for='tag in event.tags.slice(0, 6)' small label :to='`/tag/${encodeURIComponent(tag)}`'
:key='tag' outlined color='primary') {{ tag }}
</template>
<script>

View File

@@ -15,8 +15,8 @@ v-card
v-card-actions(v-if='isDialog')
v-spacer
v-btn(v-if='isDialog' color='warning' @click="$emit('close')") {{$t("common.cancel")}}
v-btn(:disabled='(!couldGo || !proceed)' :href='link' target='_blank'
v-btn(v-if='isDialog' outlined color='warning' @click="$emit('close')") {{$t("common.cancel")}}
v-btn(:disabled='(!couldGo || !proceed)' outlined :href='link' target='_blank'
:loading='loading' color="primary") {{$t("common.follow")}}
</template>
<script>

116
components/Map.vue Normal file
View File

@@ -0,0 +1,116 @@
<template lang="pug">
client-only(placeholder='Loading...' )
v-card
v-card-text
v-container
LMap(ref="map"
id="leaflet-map"
:zoom="zoom"
:options="{attributionControl: false}"
:center="center")
LControlAttribution(position='bottomright' prefix="")
LTileLayer(
:url="url"
:attribution="attribution")
LMarker(
:lat-lng="marker.coordinates")
v-row.my-4.d-flex.flex-column.align-center.text-center
.text-h6
v-icon(v-text='mdiMapMarker' )
nuxt-link.ml-2.text-decoration-none(v-text="event.place.name" :to='`/place/${event.place.name}`')
.mx-2(v-text="`${event.place.address}`")
v-card-actions
v-row
//- p.my-4(v-text="$t('common.getting_there')")
v-btn.ml-2(icon large :href="routeBy('foot')")
v-icon(v-text='mdiWalk' color='white')
v-btn.ml-2(icon large :href="routeBy('bike')")
v-icon(v-text='mdiBike' color='white')
v-btn.ml-2(icon large :href="routeBy('car')")
v-icon(v-text='mdiCar' color='white')
v-spacer
v-btn(@click='$emit("close")' outlined) Close
</template>
<script>
import "leaflet/dist/leaflet.css"
import { LMap, LTileLayer, LMarker, LPopup, LControlAttribution } from 'vue2-leaflet'
import { mapActions, mapState } from 'vuex'
import { Icon } from 'leaflet'
import { mdiWalk, mdiBike, mdiCar, mdiMapMarker } from '@mdi/js'
export default {
components: {
LMap,
LTileLayer,
LMarker,
LPopup,
LControlAttribution
},
data ({ $store }) {
return {
mdiWalk, mdiBike, mdiCar, mdiMapMarker,
url: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
attribution: '<a target="_blank" href="http://osm.org/copyright">OpenStreetMap</a> contributors',
zoom: 14,
center: [this.event.place.latitude, this.event.place.longitude],
marker: {
address: this.event.place.address,
coordinates: {lat: this.event.place.latitude, lon: this.event.place.longitude}
},
routingProvider: 'openstreetmap',
}
},
props: {
event: { type: Object, default: () => ({}) }
},
mounted() {
delete Icon.Default.prototype._getIconUrl;
Icon.Default.mergeOptions({
iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
iconUrl: require('leaflet/dist/images/marker-icon.png'),
shadowUrl: require('leaflet/dist/images/marker-shadow.png'),
});
setTimeout(() => {
this.$refs.map.mapObject.invalidateSize();
}, 200);
},
computed: {
...mapState(['settings']),
},
methods: {
...mapActions(['setSetting']),
// mountLocateControl() {
// this.$refs.map.mapObject.locate({
// locateOptions: {
// maxZoom: 10
// }
// });
// this.$refs.map.mapObject.MyLocate();
// },
routeBy () {
const lat = this.event.place.latitude
const lon = this.event.place.longitude
const routingType = {
foot: "engine=fossgis_osrm_foot",
bike: "engine=fossgis_osrm_bike",
transit: null,
car: "engine=fossgis_osrm_car"
}
return `https://www.openstreetmap.org/directions?from=&to=${lat},${lon}&${routingType}#map=14/${lat}/${lon}`
},
}
}
</script>
<style>
#leaflet-map {
height: 55vh;
width: 100%;
border-radius: .3rem;
border: 1px solid #fff;
z-index: 1;
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<v-app-bar prominent fade-img-on-scroll app hide-on-scroll
<v-app-bar absolute prominent app hide-on-scroll
src="/headerimage.png">
<template v-slot:img="{ props }">
<v-img

View File

@@ -6,6 +6,7 @@ v-snackbar(
:top="top"
:left="left"
:right="right"
transition='scroll-x-reverse-transition'
:timeout="timeout")
v-icon.mr-3(color="white" v-text='icon')
span {{ message }}
@@ -25,7 +26,7 @@ export default {
bottom: true,
top: false,
left: false,
right: false,
right: true,
active: false,
timeout: 5000,
message: ''

View File

@@ -1,5 +1,5 @@
<template lang="pug">
v-row
v-row.mb-4
v-col(cols=12 md=6)
v-combobox(ref='place'
:rules="[$validators.required('common.where')]"
@@ -10,9 +10,8 @@ v-row
hide-no-data
@input.native='search'
persistent-hint
:value='value'
:value='value.name'
:items="places"
item-text='name'
@focus='search'
@change='selectPlace')
template(v-slot:item="{ item, attrs, on }")
@@ -23,19 +22,49 @@ v-row
v-list-item-title(v-text='item.name')
v-list-item-subtitle(v-text='item.address')
//- v-text-field(
//- ref='address'
//- :prepend-icon='mdiMap'
//- :disabled='disableAddress'
//- :rules="[ v => disableAddress ? true : $validators.required('common.address')(v)]"
//- :label="$t('common.address')"
//- @change="changeAddress"
//- :value="value.address")
v-col(cols=12 md=6)
v-text-field(ref='address'
:prepend-icon='mdiMap'
v-combobox.mr-4(ref='address'
:prepend-icon='mdiMapSearch'
:disabled='disableAddress'
:rules="[ v => disableAddress ? true : $validators.required('common.address')(v)]"
:label="$t('common.address')"
@change="changeAddress"
:value="value.address")
@input.native='searchCoordinates'
:label="$t('event.coordinates_search')"
:value='value.address'
persistent-hint hide-no-data clearable no-filter
:loading='loading'
@change='selectDetails'
@focus='searchCoordinates'
:items="detailsList"
:hint="$t('event.coordinates_search_description')")
template(v-slot:item="{ item, attrs, on }")
v-list-item(v-bind='attrs' v-on='on')
v-list-item-content(two-line v-if='item')
v-list-item-title(v-text='item.name')
v-list-item-subtitle(v-text='`${item.address}`')
//- v-col(cols=12 md=3 v-if='settings.allow_geolocation')
//- v-text-field(ref='latitude' :value='value.latitude'
//- :prepend-icon='mdiLatitude'
//- :disabled='disableDetails'
//- :label="$t('common.latitude')" )
//- v-col(cols=12 md=3 v-if='settings.allow_geolocation')
//- v-text-field(ref='longitude' :value='value.longitude'
//- :prepend-icon='mdiLongitude'
//- :disabled='disableDetails'
//- :label="$t('common.longitude')")
</template>
<script>
import { mdiMap, mdiMapMarker, mdiPlus } from '@mdi/js'
import { mdiMap, mdiMapMarker, mdiPlus, mdiMapSearch, mdiLatitude, mdiLongitude } from '@mdi/js'
import debounce from 'lodash/debounce'
import { mapState } from 'vuex'
import get from 'lodash/get'
export default {
name: 'WhereInput',
@@ -44,14 +73,20 @@ export default {
},
data () {
return {
mdiMap, mdiMapMarker, mdiPlus,
mdiMap, mdiMapMarker, mdiPlus, mdiMapSearch, mdiLatitude, mdiLongitude,
place: { },
placeName: '',
places: [],
disableAddress: true
disableAddress: true,
details: { },
detailsView: '',
detailsList: [],
disableDetails: true,
loading: false
}
},
computed: {
...mapState(['settings']),
filteredPlaces () {
if (!this.placeName) { return this.places }
const placeName = this.placeName.trim().toLowerCase()
@@ -86,6 +121,11 @@ export default {
if (typeof p === 'object' && !p.create) {
this.place.name = p.name
this.place.address = p.address
if (this.settings.allow_geolocation) {
this.place.details = p.details
this.place.latitude = p.latitude
this.place.longitude = p.longitude
}
this.place.id = p.id
this.disableAddress = true
} else { // this is a new place
@@ -101,6 +141,11 @@ export default {
} else {
delete this.place.id
this.place.address = ''
if (this.settings.allow_geolocation) {
this.place.details = p.details
this.place.latitude = p.latitude
this.place.longitude = p.longitude
}
this.disableAddress = false
this.$refs.place.blur()
this.$refs.address.focus()
@@ -111,7 +156,73 @@ export default {
changeAddress (v) {
this.place.address = v
this.$emit('input', { ...this.place })
}
this.disableDetails = false
},
selectDetails (v) {
if (!v) { return }
if (typeof v === 'object') {
this.place.latitude = v.lat
this.place.longitude = v.lon
this.place.address = v.address
// }
} else {
this.place.address = v
this.place.latitude = this.place.longitude = null
}
this.$emit('input', { ...this.place })
},
searchCoordinates: debounce(async function(ev) {
const pre_searchCoordinates = ev.target.value.trim().toLowerCase()
// allow pasting coordinates lat/lon and lat,lon
const searchCoordinates = pre_searchCoordinates.replace('/', ',')
// const regex_coords_comma = "-?[1-9][0-9]*(\\.[0-9]+)?,\\s*-?[1-9][0-9]*(\\.[0-9]+)?";
// const regex_coords_slash = "-?[1-9][0-9]*(\\.[0-9]+)?/\\s*-?[1-9][0-9]*(\\.[0-9]+)?";
// const setCoords = (v) => {
// const lat = v[0].trim()
// const lon = v[1].trim()
// // check coordinates are valid
// if ((lat < 90 && lat > -90)
// && (lon < 180 && lon > -180)) {
// this.place.latitude = lat
// this.place.longitude = lon
// } else {
// this.$root.$message("Non existent coordinates", { color: 'error' })
// return
// }
// }
// if (pre_searchCoordinates.match(regex_coords_comma)) {
// let v = pre_searchCoordinates.split(",")
// setCoords(v)
// return
// }
// if (pre_searchCoordinates.match(regex_coords_slash)) {
// let v = pre_searchCoordinates.split("/")
// setCoords(v)
// return
// }
if (searchCoordinates.length) {
this.loading = true
const ret = await this.$axios.$get(`placeNominatim/${searchCoordinates}`)
if (ret && ret.length) {
this.detailsList = ret.map(v => {
const name = get(v.namedetails, 'alt_name', get(v.namedetails, 'name'))
const address = v.display_name ? v.display_name.replace(name, '').replace(/^, ?/, '') : ''
return {
lat: v.lat,
lon: v.lon,
name,
address
}
})
} else {
this.detailsList = []
}
this.loading = false
}
}, 300)
}
}
</script>

View File

@@ -25,6 +25,19 @@ v-container
v-model='place.address'
:placeholder='$t("common.address")')
v-text-field(v-if="settings.allow_geolocation"
:rules="[$validators.required('common.latitude')]"
:label="$t('common.latitude')"
v-model='place.latitude'
:placeholder='$t("common.latitude")')
v-text-field(v-if="settings.allow_geolocation"
:rules="[$validators.required('common.longitude')]"
:label="$t('common.longitude')"
v-model='place.longitude'
:placeholder='$t("common.longitude")')
v-card-actions
v-spacer
v-btn(@click='dialog = false' outlined color='warning') {{ $t('common.cancel') }}
@@ -47,6 +60,7 @@ v-container
</template>
<script>
import { mdiPencil, mdiChevronLeft, mdiChevronRight, mdiMagnify, mdiEye } from '@mdi/js'
import { mapState } from 'vuex'
export default {
data() {
@@ -68,10 +82,17 @@ export default {
async fetch() {
this.places = await this.$axios.$get('/place/all')
},
computed: {
...mapState(['settings']),
},
methods: {
editPlace(item) {
this.place.name = item.name
this.place.address = item.address
if (this.settings.allow_geolocation) {
this.place.latitude = item.latitude
this.place.longitude = item.longitude
}
this.place.id = item.id
this.dialog = true
},

View File

@@ -48,6 +48,10 @@ v-container
inset
:label="$t('admin.recurrent_event_visible')")
v-switch.mt-1(v-model='allow_geolocation'
inset
:label="$t('admin.allow_geolocation')")
v-dialog(v-model='showSMTP' destroy-on-close max-width='700px' :fullscreen='$vuetify.breakpoint.xsOnly')
SMTP(@close='showSMTP = false')
@@ -107,6 +111,10 @@ export default {
get () { return this.settings.recurrent_event_visible },
set (value) { this.setSetting({ key: 'recurrent_event_visible', value }) }
},
allow_geolocation: {
get () { return this.settings.allow_geolocation },
set (value) { this.setSetting({ key: 'allow_geolocation', value }) }
},
filteredTimezones () {
const current_timezone = moment.tz.guess()
tzNames.unshift(current_timezone)

View File

@@ -11,8 +11,8 @@ v-card
gancio-event(:id='event.id' :baseurl='settings.baseurl')
v-card-actions
v-spacer
v-btn(text color='warning' @click="$emit('close')") {{$t("common.cancel")}}
v-btn(text @click='clipboard(code)' color="primary") {{$t("common.copy")}}
v-btn(outlined color='warning' @click="$emit('close')") {{$t("common.close")}}
v-btn(outlined @click='clipboard(code)' color="primary") {{$t("common.copy")}}
</template>
<script>
import { mapState } from 'vuex'

View File

@@ -1,24 +1,43 @@
<template lang='pug'>
div
v-btn(text color='primary' v-if='event.is_visible' @click='toggle(false)') {{$t(`common.${event.parentId?'skip':'hide'}`)}}
v-btn(text color='success' v-else @click='toggle(false)') <v-icon color='yellow' v-text='mdiAlert'></v-icon> {{$t('common.confirm')}}
v-btn(text color='primary' @click='$router.push(`/add/${event.id}`)') {{$t('common.edit')}}
v-btn(text color='primary' v-if='!event.parentId' @click='remove(false)') {{$t('common.remove')}}
v-list(dense nav)
v-list-group(:append-icon='mdiChevronUp' :value='true')
template(v-slot:activator)
v-list-item.text-overline Admin actions
template(v-if='event.parentId')
v-divider
span.mr-1 <v-icon v-text='mdiRepeat'></v-icon> {{$t('event.edit_recurrent')}}
v-btn(text color='primary' v-if='event.parent.is_visible' @click='toggle(true)') {{$t('common.pause')}}
v-btn(text color='primary' v-else @click='toggle(true)') {{$t('common.start')}}
v-btn(text color='primary' @click='$router.push(`/add/${event.parentId}`)') {{$t('common.edit')}}
v-btn(text color='primary' @click='remove(true)') {{$t('common.remove')}}
//- Hide / confirm event
v-list-item(@click='toggle(false)')
v-list-item-content
v-list-item-title(v-text="$t(`common.${event.is_visible?'hide':'confirm'}`)")
//- Edit event
v-list-item(:to='`/add/${event.id}`')
v-list-item-content
v-list-item-title(v-text="$t('common.edit')")
//- Remove
v-list-item(@click='remove(false)')
v-list-item-content
v-list-item-title(v-text="$t('common.remove')")
//- v-btn(text color='primary' v-if='event.is_visible' @click='toggle(false)') {{$t(`common.${event.parentId?'skip':'hide'}`)}}
//- v-btn(text color='success' v-else @click='toggle(false)') <v-icon color='yellow' v-text='mdiAlert'></v-icon> {{$t('common.confirm')}}
//- v-btn(text color='primary' @click='$router.push(`/add/${event.id}`)') {{$t('common.edit')}}
//- v-btn(text color='primary' v-if='!event.parentId' @click='remove(false)') {{$t('common.remove')}}
//- template(v-if='event.parentId')
//- v-divider
//- span.mr-1 <v-icon v-text='mdiRepeat'></v-icon> {{$t('event.edit_recurrent')}}
//- v-btn(text color='primary' v-if='event.parent.is_visible' @click='toggle(true)') {{$t('common.pause')}}
//- v-btn(text color='primary' v-else @click='toggle(true)') {{$t('common.start')}}
//- v-btn(text color='primary' @click='$router.push(`/add/${event.parentId}`)') {{$t('common.edit')}}
//- v-btn(text color='primary' @click='remove(true)') {{$t('common.remove')}}
</template>
<script>
import { mdiAlert, mdiRepeat } from '@mdi/js'
import { mdiChevronUp, mdiRepeat } from '@mdi/js'
export default {
name: 'EventAdmin',
data () {
return { mdiAlert, mdiRepeat }
return { mdiChevronUp, mdiRepeat }
},
props: {
event: {