add geolocalization
This commit is contained in:
@@ -30,11 +30,30 @@ v-row
|
|||||||
:label="$t('common.address')"
|
:label="$t('common.address')"
|
||||||
@change="changeAddress"
|
@change="changeAddress"
|
||||||
:value="value.address")
|
:value="value.address")
|
||||||
|
v-combobox.mr-4(ref='detailsView' v-if='settings.allow_geolocalization'
|
||||||
|
:prepend-icon='mdiMapSearch'
|
||||||
|
:disabled='disableDetails'
|
||||||
|
@input.native='searchCoordinates'
|
||||||
|
:label="$t('common.coordinates')"
|
||||||
|
:value='value.detailsView'
|
||||||
|
persistent-hint hide-no-data clearable no-filter
|
||||||
|
:loading='loading'
|
||||||
|
@change='selectDetails'
|
||||||
|
@focus='searchCoordinates'
|
||||||
|
:items="detailsList"
|
||||||
|
:hint="$t('event.coordinates_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.display_name')
|
||||||
|
v-list-item-subtitle(v-text='`${item.lat}`+`,`+`${item.lon}`')
|
||||||
|
v-text-field(ref='details' v-show='false' v-if='settings.allow_geolocalization')
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { mdiMap, mdiMapMarker, mdiPlus } from '@mdi/js'
|
import { mdiMap, mdiMapMarker, mdiPlus, mdiMapSearch } from '@mdi/js'
|
||||||
import debounce from 'lodash/debounce'
|
import debounce from 'lodash/debounce'
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'WhereInput',
|
name: 'WhereInput',
|
||||||
@@ -43,14 +62,20 @@ export default {
|
|||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
mdiMap, mdiMapMarker, mdiPlus,
|
mdiMap, mdiMapMarker, mdiPlus, mdiMapSearch,
|
||||||
place: { },
|
place: { },
|
||||||
placeName: '',
|
placeName: '',
|
||||||
places: [],
|
places: [],
|
||||||
disableAddress: true
|
disableAddress: true,
|
||||||
|
details: { },
|
||||||
|
detailsView: '',
|
||||||
|
detailsList: [],
|
||||||
|
disableDetails: true,
|
||||||
|
loading: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
...mapState(['settings']),
|
||||||
filteredPlaces () {
|
filteredPlaces () {
|
||||||
if (!this.placeName) { return this.places }
|
if (!this.placeName) { return this.places }
|
||||||
const placeName = this.placeName.trim().toLowerCase()
|
const placeName = this.placeName.trim().toLowerCase()
|
||||||
@@ -85,6 +110,9 @@ export default {
|
|||||||
if (typeof p === 'object' && !p.create) {
|
if (typeof p === 'object' && !p.create) {
|
||||||
this.place.name = p.name.trim()
|
this.place.name = p.name.trim()
|
||||||
this.place.address = p.address
|
this.place.address = p.address
|
||||||
|
if (this.settings.allow_geolocalization) {
|
||||||
|
this.place.details = p.details
|
||||||
|
}
|
||||||
this.place.id = p.id
|
this.place.id = p.id
|
||||||
this.disableAddress = true
|
this.disableAddress = true
|
||||||
} else { // this is a new place
|
} else { // this is a new place
|
||||||
@@ -100,6 +128,9 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
delete this.place.id
|
delete this.place.id
|
||||||
this.place.address = ''
|
this.place.address = ''
|
||||||
|
if (this.settings.allow_geolocalization) {
|
||||||
|
this.place.details = p.details
|
||||||
|
}
|
||||||
this.disableAddress = false
|
this.disableAddress = false
|
||||||
this.$refs.place.blur()
|
this.$refs.place.blur()
|
||||||
this.$refs.address.focus()
|
this.$refs.address.focus()
|
||||||
@@ -110,7 +141,30 @@ export default {
|
|||||||
changeAddress (v) {
|
changeAddress (v) {
|
||||||
this.place.address = v
|
this.place.address = v
|
||||||
this.$emit('input', { ...this.place })
|
this.$emit('input', { ...this.place })
|
||||||
}
|
this.disableDetails = false
|
||||||
|
},
|
||||||
|
selectDetails (v) {
|
||||||
|
if (!v) { return }
|
||||||
|
if (typeof v === 'object') {
|
||||||
|
this.place.detailsView = v.lat+', '+v.lon
|
||||||
|
let c = {}
|
||||||
|
c.lat = v.lat
|
||||||
|
c.lon = v.lon
|
||||||
|
if (typeof c === 'object') {
|
||||||
|
this.place.details = JSON.stringify(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.$emit('input', { ...this.place })
|
||||||
|
},
|
||||||
|
searchCoordinates: debounce(async function(ev) {
|
||||||
|
this.loading = true
|
||||||
|
const searchCoordinates = ev.target.value.trim().toLowerCase()
|
||||||
|
// this.detailsList = await this.$axios.$get(`placeNominatim?search=${searchCoordinates}`)
|
||||||
|
this.detailsList = await this.$axios.$get(`https://nominatim.openstreetmap.org/search?limit=3&format=json&namedetails=1&q=${searchCoordinates}` )
|
||||||
|
if (this.detailsList) {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
}, 300),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -25,6 +25,13 @@ v-container
|
|||||||
v-model='place.address'
|
v-model='place.address'
|
||||||
:placeholder='$t("common.address")')
|
:placeholder='$t("common.address")')
|
||||||
|
|
||||||
|
v-textarea(v-if="settings.allow_geolocalization"
|
||||||
|
row-height="15"
|
||||||
|
:disabled="true"
|
||||||
|
:label="$t('common.details')"
|
||||||
|
v-model='place.details'
|
||||||
|
:placeholder='$t("common.details")')
|
||||||
|
|
||||||
v-card-actions
|
v-card-actions
|
||||||
v-spacer
|
v-spacer
|
||||||
v-btn(@click='dialog=false' color='warning') {{$t('common.cancel')}}
|
v-btn(@click='dialog=false' color='warning') {{$t('common.cancel')}}
|
||||||
@@ -47,6 +54,7 @@ v-container
|
|||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { mdiPencil, mdiChevronLeft, mdiChevronRight, mdiMagnify, mdiEye } from '@mdi/js'
|
import { mdiPencil, mdiChevronLeft, mdiChevronRight, mdiMagnify, mdiEye } from '@mdi/js'
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data () {
|
data () {
|
||||||
@@ -68,10 +76,16 @@ export default {
|
|||||||
async fetch () {
|
async fetch () {
|
||||||
this.places = await this.$axios.$get('/place/all')
|
this.places = await this.$axios.$get('/place/all')
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(['settings']),
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
editPlace (item) {
|
editPlace (item) {
|
||||||
this.place.name = item.name
|
this.place.name = item.name
|
||||||
this.place.address = item.address
|
this.place.address = item.address
|
||||||
|
if (this.settings.allow_geolocalization) {
|
||||||
|
this.place.details = JSON.parse(item.details)
|
||||||
|
}
|
||||||
this.place.id = item.id
|
this.place.id = item.id
|
||||||
this.dialog = true
|
this.dialog = true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ v-container
|
|||||||
inset
|
inset
|
||||||
:label="$t('admin.recurrent_event_visible')")
|
:label="$t('admin.recurrent_event_visible')")
|
||||||
|
|
||||||
|
v-switch.mt-1(v-model='allow_geolocalization'
|
||||||
|
inset
|
||||||
|
:label="$t('admin.allow_geolocalization')")
|
||||||
|
|
||||||
v-dialog(v-model='showSMTP' destroy-on-close max-width='700px' :fullscreen='$vuetify.breakpoint.xsOnly')
|
v-dialog(v-model='showSMTP' destroy-on-close max-width='700px' :fullscreen='$vuetify.breakpoint.xsOnly')
|
||||||
SMTP(@close='showSMTP = false')
|
SMTP(@close='showSMTP = false')
|
||||||
|
|
||||||
@@ -107,6 +111,10 @@ export default {
|
|||||||
get () { return this.settings.recurrent_event_visible },
|
get () { return this.settings.recurrent_event_visible },
|
||||||
set (value) { this.setSetting({ key: 'recurrent_event_visible', value }) }
|
set (value) { this.setSetting({ key: 'recurrent_event_visible', value }) }
|
||||||
},
|
},
|
||||||
|
allow_geolocalization: {
|
||||||
|
get () { return this.settings.allow_geolocalization },
|
||||||
|
set (value) { this.setSetting({ key: 'allow_geolocalization', value }) }
|
||||||
|
},
|
||||||
filteredTimezones () {
|
filteredTimezones () {
|
||||||
const current_timezone = moment.tz.guess()
|
const current_timezone = moment.tz.guess()
|
||||||
tzNames.unshift(current_timezone)
|
tzNames.unshift(current_timezone)
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
"send": "Send",
|
"send": "Send",
|
||||||
"where": "Where",
|
"where": "Where",
|
||||||
"address": "Address",
|
"address": "Address",
|
||||||
|
"coordinates": "Coordinates",
|
||||||
|
"details": "Details",
|
||||||
"when": "When",
|
"when": "When",
|
||||||
"what": "What",
|
"what": "What",
|
||||||
"media": "Media",
|
"media": "Media",
|
||||||
@@ -129,6 +131,7 @@
|
|||||||
"added_anon": "Event added, but has yet to be confirmed.",
|
"added_anon": "Event added, but has yet to be confirmed.",
|
||||||
"updated": "Event updated",
|
"updated": "Event updated",
|
||||||
"where_description": "Where's the event? If not present you can create it.",
|
"where_description": "Where's the event? If not present you can create it.",
|
||||||
|
"coordinates_description": "You can search the place by name, or paste the coordinates separated by comma: lat,lon",
|
||||||
"confirmed": "Event confirmed",
|
"confirmed": "Event confirmed",
|
||||||
"not_found": "Could not find event",
|
"not_found": "Could not find event",
|
||||||
"remove_confirmation": "Are you sure you want to remove this event?",
|
"remove_confirmation": "Are you sure you want to remove this event?",
|
||||||
@@ -182,6 +185,7 @@
|
|||||||
"allow_registration_description": "Allow open registrations?",
|
"allow_registration_description": "Allow open registrations?",
|
||||||
"allow_anon_event": "Allow anonymous events (has to be confirmed)?",
|
"allow_anon_event": "Allow anonymous events (has to be confirmed)?",
|
||||||
"allow_recurrent_event": "Allow recurring events",
|
"allow_recurrent_event": "Allow recurring events",
|
||||||
|
"allow_geolocalization": "Allow geolocalization of events",
|
||||||
"recurrent_event_visible": "Show recurring events by default",
|
"recurrent_event_visible": "Show recurring events by default",
|
||||||
"federation": "Federation / ActivityPub",
|
"federation": "Federation / ActivityPub",
|
||||||
"enable_federation": "Turn on federation",
|
"enable_federation": "Turn on federation",
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ export default {
|
|||||||
valid: false,
|
valid: false,
|
||||||
openImportDialog: false,
|
openImportDialog: false,
|
||||||
event: {
|
event: {
|
||||||
place: { name: '', address: '' },
|
place: { name: '', address: '', details: {} },
|
||||||
title: '',
|
title: '',
|
||||||
description: '',
|
description: '',
|
||||||
tags: [],
|
tags: [],
|
||||||
@@ -214,6 +214,7 @@ export default {
|
|||||||
}
|
}
|
||||||
formData.append('place_name', this.event.place.name)
|
formData.append('place_name', this.event.place.name)
|
||||||
formData.append('place_address', this.event.place.address)
|
formData.append('place_address', this.event.place.address)
|
||||||
|
formData.append('place_details', this.event.place.details)
|
||||||
formData.append('description', this.event.description)
|
formData.append('description', this.event.description)
|
||||||
formData.append('multidate', !!this.date.multidate)
|
formData.append('multidate', !!this.date.multidate)
|
||||||
formData.append('start_datetime', dayjs(this.date.from).unix())
|
formData.append('start_datetime', dayjs(this.date.from).unix())
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ const eventController = {
|
|||||||
attributes: ['tag'],
|
attributes: ['tag'],
|
||||||
through: { attributes: [] }
|
through: { attributes: [] }
|
||||||
},
|
},
|
||||||
{ model: Place, required: true, attributes: ['id', 'name', 'address'] }
|
{ model: Place, required: true, attributes: ['id', 'name', 'address', 'details'] }
|
||||||
],
|
],
|
||||||
replacements,
|
replacements,
|
||||||
limit: 30,
|
limit: 30,
|
||||||
@@ -192,7 +192,7 @@ const eventController = {
|
|||||||
},
|
},
|
||||||
include: [
|
include: [
|
||||||
{ model: Tag, required: false, attributes: ['tag'], through: { attributes: [] } },
|
{ model: Tag, required: false, attributes: ['tag'], through: { attributes: [] } },
|
||||||
{ model: Place, attributes: ['name', 'address', 'id'] },
|
{ model: Place, attributes: ['name', 'address', 'details', 'id'] },
|
||||||
{
|
{
|
||||||
model: Resource,
|
model: Resource,
|
||||||
where: !is_admin && { hidden: false },
|
where: !is_admin && { hidden: false },
|
||||||
@@ -405,7 +405,8 @@ const eventController = {
|
|||||||
}
|
}
|
||||||
place = await Place.create({
|
place = await Place.create({
|
||||||
name: body.place_name,
|
name: body.place_name,
|
||||||
address: body.place_address
|
address: body.place_address,
|
||||||
|
details: body.place_details
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -559,7 +560,8 @@ const eventController = {
|
|||||||
}
|
}
|
||||||
place = await Place.create({
|
place = await Place.create({
|
||||||
name: body.place_name,
|
name: body.place_name,
|
||||||
address: body.place_address
|
address: body.place_address,
|
||||||
|
details: body.place_details
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -698,7 +700,7 @@ const eventController = {
|
|||||||
attributes: ['tag'],
|
attributes: ['tag'],
|
||||||
through: { attributes: [] }
|
through: { attributes: [] }
|
||||||
},
|
},
|
||||||
{ model: Place, required: true, attributes: ['id', 'name', 'address'] }
|
{ model: Place, required: true, attributes: ['id', 'name', 'address', 'details'] }
|
||||||
],
|
],
|
||||||
...pagination,
|
...pagination,
|
||||||
replacements
|
replacements
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const exportController = require('./export')
|
|||||||
|
|
||||||
const log = require('../../log')
|
const log = require('../../log')
|
||||||
const { Op, where, col, fn, cast } = require('sequelize')
|
const { Op, where, col, fn, cast } = require('sequelize')
|
||||||
|
const NOMINATIM_URL = 'https://nominatim.openstreetmap.org/search?limit=3&format=geocodejson&accept-language=it&q='
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
||||||
@@ -60,7 +61,7 @@ module.exports = {
|
|||||||
{ address: where(fn('LOWER', col('address')), 'LIKE', '%' + search + '%')},
|
{ address: where(fn('LOWER', col('address')), 'LIKE', '%' + search + '%')},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
attributes: ['name', 'address', 'id'],
|
attributes: ['name', 'address', 'details', 'id'],
|
||||||
include: [{ model: Event, where: { is_visible: true }, required: true, attributes: [] }],
|
include: [{ model: Event, where: { is_visible: true }, required: true, attributes: [] }],
|
||||||
group: ['place.id'],
|
group: ['place.id'],
|
||||||
raw: true,
|
raw: true,
|
||||||
@@ -70,6 +71,16 @@ module.exports = {
|
|||||||
|
|
||||||
// TOFIX: don't know why limit does not work
|
// TOFIX: don't know why limit does not work
|
||||||
return res.json(places.slice(0, 10))
|
return res.json(places.slice(0, 10))
|
||||||
}
|
},
|
||||||
|
|
||||||
|
// async _nominatim (req, res) {
|
||||||
|
// const details = req.params.place_details
|
||||||
|
// const ret = await axios.get(`${NOMINATIM_URL}${details}`, { headers: { 'User-Agent': 'gancio 0.20' } })
|
||||||
|
// debug(`${NOMINATIM_URL}${details}`)
|
||||||
|
// debug(ret.status)
|
||||||
|
// debug(ret.statusText)
|
||||||
|
// debug(ret.data)
|
||||||
|
// return ret
|
||||||
|
// },
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -29,6 +29,7 @@ const defaultSettings = {
|
|||||||
allow_anon_event: true,
|
allow_anon_event: true,
|
||||||
allow_recurrent_event: false,
|
allow_recurrent_event: false,
|
||||||
recurrent_event_visible: false,
|
recurrent_event_visible: false,
|
||||||
|
allow_geolocalization: false,
|
||||||
enable_federation: true,
|
enable_federation: true,
|
||||||
enable_resources: false,
|
enable_resources: false,
|
||||||
hide_boosts: true,
|
hide_boosts: true,
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ if (config.status !== 'READY') {
|
|||||||
* @param {string} description - event's description (html accepted and sanitized)
|
* @param {string} description - event's description (html accepted and sanitized)
|
||||||
* @param {string} place_name - the name of the place
|
* @param {string} place_name - the name of the place
|
||||||
* @param {string} [place_address] - the address of the place
|
* @param {string} [place_address] - the address of the place
|
||||||
|
* @param {array} [place_details] - the details of the place
|
||||||
* @param {integer} start_datetime - start timestamp
|
* @param {integer} start_datetime - start timestamp
|
||||||
* @param {integer} multidate - is a multidate event?
|
* @param {integer} multidate - is a multidate event?
|
||||||
* @param {array} tags - List of tags
|
* @param {array} tags - List of tags
|
||||||
@@ -160,6 +161,7 @@ if (config.status !== 'READY') {
|
|||||||
api.get('/place/all', isAdmin, placeController.getAll)
|
api.get('/place/all', isAdmin, placeController.getAll)
|
||||||
api.get('/place/:placeName', cors, placeController.getEvents)
|
api.get('/place/:placeName', cors, placeController.getEvents)
|
||||||
api.get('/place', cors, placeController.search)
|
api.get('/place', cors, placeController.search)
|
||||||
|
// api.get('/placeNominatim/:place_details', cors, placeController._nominatim)
|
||||||
api.put('/place', isAdmin, placeController.updatePlace)
|
api.put('/place', isAdmin, placeController.updatePlace)
|
||||||
|
|
||||||
api.get('/tag', cors, tagController.search)
|
api.get('/tag', cors, tagController.search)
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ Place.init({
|
|||||||
index: true,
|
index: true,
|
||||||
allowNull: false
|
allowNull: false
|
||||||
},
|
},
|
||||||
address: DataTypes.STRING
|
address: DataTypes.STRING,
|
||||||
|
details: DataTypes.JSON
|
||||||
}, { sequelize, modelName: 'place' })
|
}, { sequelize, modelName: 'place' })
|
||||||
|
|
||||||
module.exports = Place
|
module.exports = Place
|
||||||
|
|||||||
25
server/migrations/20220706090946-place-details.js
Normal file
25
server/migrations/20220706090946-place-details.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
async up (queryInterface, Sequelize) {
|
||||||
|
/**
|
||||||
|
* Add altering commands here.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* await queryInterface.createTable('users', { id: Sequelize.INTEGER });
|
||||||
|
*/
|
||||||
|
return queryInterface.addColumn('places', 'details', { type: Sequelize.JSON })
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
async down (queryInterface, Sequelize) {
|
||||||
|
/**
|
||||||
|
* Add reverting commands here.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* await queryInterface.dropTable('users');
|
||||||
|
*/
|
||||||
|
return queryInterface.removeColumn('places', 'details')
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -8,6 +8,7 @@ export const state = () => ({
|
|||||||
allow_anon_event: true,
|
allow_anon_event: true,
|
||||||
allow_recurrent_event: true,
|
allow_recurrent_event: true,
|
||||||
recurrent_event_visible: false,
|
recurrent_event_visible: false,
|
||||||
|
allow_geolocalization: false,
|
||||||
enable_federation: false,
|
enable_federation: false,
|
||||||
enable_resources: false,
|
enable_resources: false,
|
||||||
hide_boosts: true,
|
hide_boosts: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user